Question

I have a schema and a data object. With the schema I need to create a form and with the data object I need to populate it. What's the best way to go about this?

Example:

var schema = {
    object: {
        someData: {
           type: "String",
           required: "true"
        },
        name: {
          type: "String",
          required: "false"
       }
   }
}

var data = {
   object: {
       someData: "foo",
       name: "John"
   }
}

From this I would need to generate something like this

<form>
<fieldset><legend>object</legend>
<input type="text" name="someData" value="foo">
<input type="text" name="name" value="John">
</fieldset>
</form>

I've thought of a few ways to accomplish this, but I think I'm overcomplicating it. There must be a simpler way, right?

There are two catches: The Schema is not fixed, it can change. And the other one is that some data types are objects themselves.

EDIT: I oversimplified this question. This is the simplest possible case, but in my particular case I have more complex data objects and schemas and they change, and also I need to recreate the data object from the form once the user edits something. I edited the data to take this into consideration.

Was it helpful?

Solution

Create a function that accepts the schema and the data, then create the form in that function. Here is a basic example. In production you could use a switch statement in the function for different data types.

Fiddle: http://jsfiddle.net/CR58L/8/

var FormBuilder = function(schema, data) {
    this.schema = schema
    this.data = data

    this.form = document.createElement("form")
    this.inputs = []

    this.form.addEventListener("submit", this)
    this.createInputs()
}

FormBuilder.prototype = {
    "createElement": function(field, data, fieldset) {
        var fieldset = fieldset || false,
            element, value

        switch ( data.type ) {
            case "String":
                element = document.createElement("input")
                element.setAttribute("name", field)

                value = (fieldset ? this.data[fieldset][field] : this.data[field]) || ""
                element.setAttribute("value", value)

                if ( data.required === "true" ) element.setAttribute("required", "required")
                if ( fieldset ) element.setAttribute("data-fieldset", fieldset)
                break;
        }

        this.inputs.push(element)
        return element
    },    
    "createInputs": function() {
        var element, fieldset

        for ( var field in this.schema ) {
            if ( this.schema[field]["type"] ) {
                element = this.createElement(field, this.schema[field])
                this.form.appendChild(element)
            } else {
                fieldset = document.createElement("fieldset")
                legend = document.createElement("legend")
                legend.innerHTML = field
                fieldset.appendChild(legend)

                for ( var input in this.schema[field] ) {
                    element = this.createElement(input, this.schema[field][input], field)
                    fieldset.appendChild(element)
                }

                this.form.appendChild(fieldset)
            }
        }

        element = document.createElement("button")
        element.setAttribute("type", "submit")
        element.innerHTML = "Submit"

        this.form.appendChild(element)
    },
    "handleEvent": function(e) {
        e.preventDefault()
        var element, _dataRef, fieldset

        for ( var input in this.inputs ) {
            element = this.inputs[input]
            fieldset = element.getAttribute("data-fieldset")

            _dataRef = fieldset ? this.data[fieldset] : this.data
            _dataRef[element.getAttribute("name")] = element.value
        }

        console.log(data)
    }
}

var fb = new FormBuilder(schema, data)
document.getElementById("container").appendChild(fb.form)

OTHER TIPS

The simplest thing you could do in my opinion is loop over the keys of the data object and then immediately get each type from the schema. You could also add an object with methods that create inputs for each type to avoid a huge switch statement.

var controlFactory = {
  'String': function (name, value) {
     return '<input type="text" name="'+name+'" value="'+value+'">';
   }
}

var info, value;
for (var name in data) {
  value = data[name];
  info = schema[name];

  if (info && controlFactory[info.type]) {
     // create form field from factory
  }
}

If you are using JQuery, you can try something like this:

for(var key in schema){
    if(schema.hasOwnProperty(key)){
        var $field = $('<input>').attr({
            type : 'text',
            name : key,
            value : data[key]
        });
        $body.append($field);
    }
}

Here's the JSFiddle: http://jsfiddle.net/L8QQm/1/

Using ECMA5 methods you could do something like this.

HTML

<form id="myForm"></form>

Javascript

var schema = {
        object: {
            someData: {
                type: "String",
                required: "true"
            },
            name: {
                type: "String",
                required: "false"
            }
        }
    },
    data = {
        object: {
            someData: "foo",
            name: "John"
        }
    },
    myForm = document.getElementById('myForm');

Object.keys(schema).forEach(function (legendText) {
    var fieldsetGroup = this[legendText],
        fieldset = document.createElement('fieldset'),
        legend = document.createElement('legend');

    legend.appendChild(document.createTextNode(legendText));
    fieldset.appendChild(legend);
    Object.keys(fieldsetGroup).forEach(function (elementName) {
        var attributes = this[elementName],
            input = document.createElement('input');

        input.name = elementName;
        input.value = data[legendText][elementName];
        Object.keys(attributes).forEach(function (attributeName) {
            var value = this[attributeName];

            if (attributeName === 'required') {
                value = (value === 'true');
            } else if (attributeName === 'type') {
                if (value === 'String') {
                    value = 'text';
                } else {
                    // whatever else
                    value = 'text';
                }
            }

            input[attributeName] = value;
            fieldset.appendChild(input);
        }, attributes);
    }, fieldsetGroup);

    myForm.appendChild(fieldset);
}, schema);

On jsFiddle

Piece of cake!

// Define the variables, like you want
var schema = {
  someData: {
    type: "String",
    required: "true"
  },
  name: {
    type: "String",
    required: "false"
  }
};

var data = {
  someData: "foo",
  name: "John"
};

//n for name
for (var n in data) {
  //good practice to check
  if (data.hasOwnProperty(n) && schema.hasOwnProperty(n)) {
    var t = schema[n].type; //t for type
    var v = data[n];  //v for value
    console.log('<input type="' + t + '" name="' + n + '" value="' + v + '">');
  }
}

Hope it helps :)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top