1

So if your Controller Action returns a Model with pre-populated values, how do you make KnockoutJS aware of them?

E.g.:

@Html.TextBoxFor(m => m.Title, new { data_bind="value: title"} )

however, on $(document).ready() where I bind knockout.js ViewModel, this value isn't yet populated:

$(document).ready({
  var viewModel = {
    title: ko.observable($("#Title").val()) // too early for this?!
  }

  ko.applyBindings(viewModel);​
});

How do you make KnockoutJS work with MVC's model binding? One workaround I found was to set the JavaScript variable in my Razor View, like so:

<script>
  var strTitle = '@Model.Title';
</script>

and than use it in the Knockout model binding. That works, but I hate it. What if your form has like hundreds of fields? You don't want as many JavaScript variables in your page. Am I missing the obvious here?

3 Answers 3

3

This seems similar to this question. Normally you would set your view model by converting @Model to JSON in a script:

<script type="text/javascript">
var model = @(new HtmlString(new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(Model)));
</script>

You could also create your own binding handler that will initially load the view model based on control values. This new myvalue handler basically calls the existing value handler, except it updates the view model from the initial control value on init.

ko.bindingHandlers['myvalue'] = {
    'init': function (element, valueAccessor, allBindingsAccessor) {
        // call existing value init code
        ko.bindingHandlers['value'].init(element, valueAccessor, allBindingsAccessor);

        // valueUpdateHandler() code
        var modelValue = valueAccessor();
        var elementValue = ko.selectExtensions.readValue(element);
        modelValue(elementValue); // simplified next line, writeValueToProperty isn't exported
        //ko.jsonExpressionRewriting.writeValueToProperty(modelValue, allBindingsAccessor, 'value', elementValue, /* checkIfDifferent: */ true);
    },
    'update': function (element, valueAccessor) {
        // call existing value update code
        ko.bindingHandlers['value'].update(element, valueAccessor);
    }
};

Then when you call ko.applyBindings, your observable will be set based on the control's value initially:

<input type="text" data-bind="myvalue: Title" value="This Title will be used" />
<input type="text" data-bind="value: Title" value="This will not be used" />
<!-- MVC -->
@Html.TextBoxFor(m => m.Title, new { data_bind="myvalue: Title"} )

SAMPLE FIDDLE

3
  • thanks, unfortunately this doesn't seem to work for me :( the value disappears after ko model binding because of this line: "viewModel.Title = ko.observable()"
    – Ruslan
    Commented Jun 25, 2012 at 13:09
  • ...and if I do viewModel.Title = ko.observable(viewModel.Title) to set initial value, knockout.validation.js throws "Uncaught TypeError"
    – Ruslan
    Commented Jun 25, 2012 at 13:11
  • @Tsar - I updated the answer to get it to work and gave an example fiddle, the ko.jsonExpressionRewriting.writeValueToProperty function is not exported and only worked with the debug version of knockout, I changed it to a simpler call that should work as long as Title is an observable. Commented Jun 26, 2012 at 20:19
2

What about simply serializing your entire page model to json using JSON.NET or similar. Then your page will be populated via normal razor view bindings for non-js users. Then your page scripts can be something like:

<script>
    ko.applyBindings(@Html.ToJSON(Model));
</script>

Or if you have a typed viewModel

<script>
    var viewModel = new MyViewModel(@Html.ToJSON(Model));
    ko.applyBindings(viewModel);
</script>

It makes sense to structure your client side and actual view models the same so no manipulation of the json shape is required.

EDIT

Example of the toJSON helper.

public static MvcHtmlString ToJson(this HtmlHelper html, object obj)
{
  JavaScriptSerializer serializer = new JavaScriptSerializer();
  return MvcHtmlString.Create(serializer.Serialize(obj));
}

Hope this helps.

3
  • where did toJSON() come from? Do I need to write this method in my MVC Model?
    – Ruslan
    Commented Jun 22, 2012 at 9:03
  • Yeh sorry should really have explained that. Wrote this on a phone :) Edited to give example. Commented Jun 22, 2012 at 15:31
  • @JasonGoemaat - No I don't good spot. Should learn to never answer questions until I can run the code. Cheers. Commented Jun 26, 2012 at 21:42
2

Because I don't have a 50 point reputation to add a comment to the Jason Goemaat answer, I decided to add my comment here as an answer.

All the credits go to Jason Goemaat.

I wasn't able to make the code work for me. So I had to make a small change.

        ko.bindingHandlers['myvalue'] = {
    'init': function (element, valueAccessor, allBindingsAccessor) {
        //get initial state of the element            
        var modelValue = valueAccessor();
        var elementValue = ko.selectExtensions.readValue(element);

        // call existing value init code
        ko.bindingHandlers['value'].init(element, valueAccessor, allBindingsAccessor);

        //Save initial state
        modelValue(elementValue);
    },
    'update': function (element, valueAccessor) {
        // call existing value update code
        ko.bindingHandlers['value'].update(element, valueAccessor);
    }
};

if I had this line at the top,

// call existing value init code
ko.bindingHandlers['value'].init(element, valueAccessor, allBindingsAccessor);

It was deleting the original state of the element. So whatever value I had in the input field, it was being deleted.

Inside the model I have this:

//publishing comment
self.publishingComment = ko.observable();

And my MVC looks like this

@Html.TextBoxFor(model => model.Comment, new { data_bind = "myvalue: publishingComment" })

Not the answer you're looking for? Browse other questions tagged or ask your own question.