Knockout JS mapping plugin without initial data / empty form
Asked Answered
T

3

18

We are using knockout and the knockout mapping plugin to facilitate databinding in our jQTouch web application. The reason we use the mapping plugin, is to be able to use knockout without the need to define/change viewmodels manually in javascript. The mapping plugin works great when you have an initial load of data from the server/client side database.

The problem we are having is that we have some screens/views which have a form in which it is possible that there isn't any initial data. Without this initial data, the mapping plugin can't 'generate' the viewmodel (ko.mapping.fromJS). This means that we still need to define our viewmodels by hand for large parts of our views.

Am I wrong in assuming that this is a scenario which the mapping plugin (should) support? I mean, this means that the mapping plugin is only usable in scenarios in which you always have an initial load of data.

Translation answered 18/7, 2011 at 15:19 Comment(0)
B
19

A couple of options for you besides just manually managing your view model. The mapping plugin supports a create callback that lets you customize how it gets created. This can be used to add default properties to an object, if they happen to be missing.

Something like this: http://jsfiddle.net/rniemeyer/WQGVC/

Another alternative is to use a binding that creates properties that are missing. It might look like:

//create an observable if it does not exist and populate it with the input's value
ko.bindingHandlers.valueWithInit = {
    init: function(element, valueAccessor, allBindingsAccessor, data) {
        var property = valueAccessor(),
            value = element.value;

        //create the observable, if it doesn't exist 
        if (!ko.isWriteableObservable(data[property])) {
            data[property] = ko.observable();
        }

        //populate the observable with the element's value (could be optional)
        data[property](value);

        ko.applyBindingsToNode(element, { value: data[property] });
    }
}

You would use it like this (need to pass the property as a string, otherwise it will error):

<input data-bind="valueWithInit: 'name'" />

Sample here: http://jsfiddle.net/rniemeyer/JPYLp/

Burdelle answered 18/7, 2011 at 18:59 Comment(5)
Thanks RP Niemeyer, I'm going to try this out in the next couple of days. Sounds promising!Translation
the cust init binding worked great for me, but i didn't like the dynamic updating of the data-bind attribute in the binding so i suggest using virual elements. Something like: <!-- ko valueWithInit: 'PropC'--><!-- /ko --> <input data-bind="visible: PropC, value: PropC"></input>Teutonism
@ChrisDaMour - this version was kind of outdated. I updated the answer with a custom binding that would apply the value binding to the element, but not overwrite any other bindings. Another option is to just use a separate init binding that you place before any other bindings.Burdelle
@RPNiemeyer, is this still the best way to go about this?Lavonnelaw
@Lavonnelaw - if you want to create observables "on-the-fly", then this is still a good option.Burdelle
C
4

I think the solution to you problem comes from thinking about the view model in the wrong way. A view model isn't only something that delivers data to the view, but also a place holder for submitting the data back.

The way i usually end up working with knockout, i never end up sending an empty view model to the view. The view model usually has all the fields i am binding on. While they might be empty strings, or initialized objects with no display values, the actual objects to still exits, with a proper representation of each object to the fields i am binding to.

You might want to look into simply sending empty objects instead of nothing to the view.

EDIT: The example is un ASP.NET MVC

So basiclaly, i on the server side, i create an view model object, which contains all the data that needs to be displayed as well as all the data that needs to be collected. For easier validation code i generally put the data to be collected into it's own subclass, but that all a matter of the needs of your code.

In anycase, the any object going to the view inherts from a vmBase class which basically provides a toJSON() method which generates the JSON serialization of the object. This gets called in my view by the view engine. As shown in the code below.

      <script type='text/javascript'>         
        var viewModel = ko.mapping.fromJS(<%= Model.ToJson() %>);                     

        $(document).ready( function () {        
            ko.applyBindings(viewModel);                             
        });                  
     </script>   

When i am ready to send the code back up, i simply remove pull a JS version of the view model.

<script type='text/javascript'>
     var dataToSendToServer = ko.toJS(viewModel);
</script>

In some sanarios, where only a part of the view model is changing (this is if you are doing AJAX updates), you can do some cool stuff like, switching templates so that different binding can be applyed. In this case we are using a #ID_of_Container as container of the original data/template and replacing the template (which can contain data-bind="" elements) a new template ID_of_Template

<script type='text/javascript'>
    ko.cleanNode($("#ID_of_Container"));
    delete (viewModel.Some_Element_To_Be_Updated);
    viewModel = ko.mapping.updateFromJS(viewModel, New_Data_For_That_Element);

    // Use ko.toJS(viewModel) because standard template plugin doesn't understand 
    // knockout observables
    $("#ID_of_Container").html($("#ID_of_Template").tmpl(ko.toJS(viewModel)))
    ko.applyBindings(viewModel, $("#ID_of_Container")[0]);

</script>
Centigrade answered 20/7, 2011 at 11:0 Comment(2)
Are you also using the KO mapping plugin? That enables you to generate the viewmodel, instead of having to define it 'by hand'. This is the part we are having problems with.Translation
Hope the edit helps. It's taken me a bit to realize that i can do this type of stuff with knockout. BUt it ends up with some pretty great results. It really gives the website a thick client fell and very smooth AJAX flow.Centigrade
C
0

One approach I am exploring now is to create an additional web service method called ReturnEmptyObject() which does nothing other than create and return a newly instantiated object (whose properties would be default values) on the server side. The (in my case C#) object gets serialized to JSON, and finally arrives on the jQuery Ajax call.... and then passed into ko.mapping.updateFromJS()... which creates the needed observables on initial page load... before I call ko.applyBindings().

When ko.applyBindings() executes, it finds the observables it needs so it won't throw an error, even though they are mostly empty.

This way the page can initially be opened without any of the fields filled in. Yet, if I add some new properties to my class on the server side, they automatically show up on the client side still.

Coralyn answered 28/2, 2013 at 23:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.