0

I am trying to create a form builder in AngularJS. The whole form structure is defined in a JSON object like this:

form = {
  "title": "Some form title",          // Title of the field
  "fieldsets": [                       // Each form is composed of several fieldsets 
    {
      "title": "First fieldset",       // Title of the fieldset
      "fields": [                      // Each fieldset is composed of several fields
        {
          "title": "First field",      // Title of the field, displayed in a label
          "type": "text"               // Type of the field which determines which partial to load
        },
        {
          "title": "Second field",
          "type": "date"
        },
      ]
    },
    {
      "title": "Second fieldset",
      "fields": [
        {
          "title": "First field",
          "type": "text"
        },
        {
          "title": "Second field",
          "type": "date"
        },
      ]
    }     
   ]
}

I get a JSON object like the one above and then render it in a page like this (jade template):

h5 {{ form.title }}
div(ng-repeat="fs in form.fieldsets")
  fieldset
    legend {{ fs.title }}
    div(ng-repeat="field in fs.fields")
      myfield(field="field")

myfield is a custom directive which parses each field and based on the type, renders a different partial. Here is the code:

var appFilters = angular.module('appComponents', []);
appFilters.directive('myfield', ['$compile', '$http', '$templateCache', function($compile, $http, $templateCache){
  var getTemplate = function(contentType) {
    return $http.get('partials/' + contentType + '_field', {cache: $templateCache});
    // I get the partials here, for example for a field of type text, I load text_field.jade
  };

  var linker = function(scope, element, attrs){

    scope.edit_field = function(field) {
      field.type = "template";
    };

    var loader = getTemplate(scope.field.type);

    var promise = loader.success(function(html) {
      element.html(html);
    }).then(function(response) {
      element.replaceWith($compile(element.html())(scope));
    });
  };

  return {
    restrict: 'E',
    scope: {
      field:'='
    },
    link: linker
  };
}]);

I have several partials. They all inherit from one base template, named field.jade. Here is the base template and another one that inherits from it:

field.jade: (base template for other partials):

div
  block _title

div
  div
    block _label
      label {{ field.title }}:
  div
    block _field
  div
    block _controls
      a.control(ng-click="edit_field(field)") Edit

text_field.jade: (partial for the fields of type text)

extend field

block _field
  input(type="text")

template_field.jade: (partial for the fields, when they are in edit mode)

extend field

block _label
  input(type="text", ng-model="field.title")
  // The user can edit the field label here

block _field
  select
  // Here the user can select from several different field types

Now when the user clicks the Edit button, field.type is changed to template and I want AngularJS to load the template_field.jade view instead of the main view (like text_field.jade).

Does anyone have any idea how to tell AngularJS to reload the templated_field.jade partial instead?

P.S: I wanted to create a fiddle for this, but since it was too complicated and I had to import several different files for it to run, I gave up creating the fiddle.

2
  • Think you need to add more code here. Maybe a fiddle? Commented Nov 4, 2013 at 15:47
  • @Baszz I added more detail here. Commented Nov 4, 2013 at 20:54

1 Answer 1

1

I found a way around it. It works fine, but I am not sure if it's the best way to do it. In my edit_field function, I need to call the linker function manually.

scope.edit_field = function(field) {
  field.type = "template";
  linker(scope, element, attrs);
};

And also, I had to replace the call to replaceWith() with html(), because, I don't know why, but replaceWith() only works the first time it is called.
So, this line:

element.replaceWith($compile(element.html())(scope));

should be replaced with this:

element.html($compile(element.html())(scope));

And the final code is:

var appFilters = angular.module('appComponents', []);
appFilters.directive('myfield', ['$compile', '$http', '$templateCache', function($compile, $http, $templateCache){
  var getTemplate = function(contentType) {
    return $http.get('partials/' + contentType + '_field', {cache: $templateCache});
    // I get the partials here, for example for a field of type text, I load text_field.jade
  };

  var linker = function(scope, element, attrs){

    scope.edit_field = function(field) {
      field.type = "template";
      linker(scope, element, attrs);
    };

    var loader = getTemplate(scope.field.type);

    var promise = loader.success(function(html) {
      element.html(html);
    }).then(function(response) {
      element.html($compile(element.html())(scope));
    });
  };

  return {
    restrict: 'E',
    scope: {
      field:'='
    },
    link: linker
  };
}]);

Please correct me if I am making any mistakes here.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.