dynamic form building with knockoutjs

ajax, asp.net-mvc-3, form-post, json.net, knockout.js

Solution

For tracking changes you can use `dirty flag` from this article: http://www.knockmeout.net/2011/05/creating-smart-dirty-flag-in-knockoutjs.html.

Create the following view model:

function FormField(data) {
    var self = this;

    self.Name = ko.observable(data.Name);
    self.Type = ko.observable(data.Type);
    self.Options = ko.observableArray(data.Type != 'text' ? data.Options : []);
    self.Default = ko.observable(data.Default);
}

function ViewModel(data) {
    var self = this;

    self.Fields = ko.observableArray(ko.utils.arrayMap(data, function(item) {
        return new FormField(item);
    }));

    self.dirtyFlag = new ko.dirtyFlag(this);

    self.isDirty = ko.computed(function (){
        if (self.dirtyFlag.isDirty())
        {
            alert("Value changed!");
            // Do async update.
        }
    });
}

Html markup:

<div data-bind="foreach: Fields">
    <b data-bind="text: Name"></b>
    <!--ko if: Type() == "combo"-->
        <select data-bind="options: Options, value: Default"></select>                 <!--/ko-->
    <!--ko if: Type() == "radio"-->
        <div data-bind="foreach: Options">
            <input type="radio" value="cherry" data-bind="value: $data, checked: $parent.Default" />
            <span data-bind="text: $data"></span>
        </div>
    <!--/ko-->    
    <!--ko if: Type() == "text"-->
        <input type="text" data-bind="value: Default"></input>                 
    <!--/ko-->   
    <br/>    
</div>

Here is working fiddle: http://jsfiddle.net/vyshniakov/CWTTR/

EDIT:

Here are answers on your questions if I understood them right:

To post all fields to server you could use `ko.toJSON(self)` function. Your ajax call will look as follow:

    $.ajax({
        type: "POST",
        url: "controller/action",
        contentType: 'application/json',
        data: JSON.stringify(ko.toJSON(self)),
        success: function(result) {
            self.HiddenElements(result);
        }
    });

Look at updated fiddle to see how hide some fields depending on response from server: http://jsfiddle.net/vyshniakov/CWTTR/1/.

Problem

I need to build a dynamic form from database. I have following Entity to define form fields on the fly: ``` public class FormField { public int ID { get; set; } public string Name { get; set; } public string Type { get; set; } // Possible values are: 'Radio','Combo','Text'. A dropdown will be created for a Combo type of element, a radio set for Radio type of element and a text input for Text type of element. public string Options { get; set; } // Only relevant in case of Radio/Combo type public string Default { get; set; } // Default value in case of Type 'Text' and selected value in case of Type 'Radio/Combo' public string Blankout { get; set; }// An expression to define when this field should be hidden } /* A sample JSON array (from the DB) to build the form would be: [ { Name:"Gender", Type:"radio", Options:["Male","Female","Unknown"], Default:"Male", Blankout:"Never" }, { Name:"Age", Type:"text", Options:"None", Default:15, Blankout:"Never" }, { Name:"Neighbourhood", Type:"Combo", Options:["Eastern","Western","Northern","Southern","Central"], Default:"Central", Blankout:"if (Age < 40 or Voted='Obama')" }, { Name:"Voted", Type:"Combo", Options:["Obama","Romney","Harry Potter"], Default:"Harry Potter", Blankout:"if ((Gender='Female' and Age < 15) or Neighbourhood='Eastern'" } ] */ ``` I can build a dynamic form from the 'FormField' records in DB, BUT the problem is i need to track the changes in values of any form field, and when a change in value happens i need to send all the form data to server (asynchronously) in order to evaluate the 'Blankout' formula on Server. If i do this change tracking thing without KnockoutJS its not responsive and becomes very very complex. I have gone through several tutorials of KnockoutJS, but could not figure out how to organize my ViewModel for this particular problem. Any help would be appreciated. Update 1 I have tried to post this form data to controller by using following code: ``` $.ajax({ type: "POST", url: "/MyController/GetBlankoutElements", contentType: 'application/json', dataType: 'json', data: JSON.stringify(ko.toJSON(self)), success: function(result) { alert(result); //self.HiddenElements(result.split(',')); } }); ``` In my controller i have tried following code: ``` [HttpPost] public ActionResult GetBlankoutElements(List<MyFieldViewModel> Fields) { return Json(Fields); // List, Fields is null here } ``` Her is the what the MyFieldViewModel class looks like: ``` public class MyFieldViewModel { public string Title { get; set; } public string Name { get; set; } public string Type { get; set; } public string Default { get; set; } public string[] Options { get; set; } } ``` I have tried tips described at Post an Array of Objects via JSON to ASP.Net MVC3 Following is the Json data that prints out when i execute alert(ko.toJSON(self)) ``` {"Fields": [{"Title":"CCType","Name":"CCType","Type":"Radio","Default":"Enterprise","Options":["Enterprise","Express","CVP","PCCE"]},{"Title":"Industry","Name":"Industry","Type":"Combo","Default":"Banks","Options":["Banks","ServiceProvider","Outsourcer","Airlines","Utilities","Government","Retail"]},{"Title":"Customer Lab","Name":"CustomerLab","Type":"Combo","Default":"0","Options":["0","1"]},{"Title":"No of Agents","Name":"Agents","Type":"Text","Default":"if(c.CCType==\"CVP\") then 10 else 25","Options":[]},{"Title":"ExpLicType","Name":"ExpLicType","Type":"Radio","Default":"if(c.CCType==\"Express\") then \"Enhanced\" else \"None\"","Options":["None","Premium","Standard","Enhanced"]},{"Title":"Multimedia","Name":"Multimedia","Type":"Combo","Default":"WIM","Options":["None","EIM","WIM","EIM&WIM","BSMediaRouting","MCAL"]}], "HiddenElements":[] } ``` What i need is just the field name and its selected value by the user, and i am confused even if i get this json data mapped to my MyFieldViewModel class, still how would i get the selected VALUES ? Update 2 (JSON data Mapping worked) When i changed ``` data: JSON.stringify(ko.toJSON(self)) ``` with data: ko.toJSON(self) Mapping worked perfectly on my controller, as you can see in the following screenshot: Now, the problem remains, the whole point of posting form was to update server with user's input on the form i.e. values against every form field element. How do i post the current selected/typed values of form fields ? For example, in above screenshot, i can see the Default but not the current selected value.

Original source

Related problems