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?

Ruslan
  • 9,927
  • 15
  • 55
  • 89

3 Answers3

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

Community
  • 1
  • 1
Jason Goemaat
  • 28,692
  • 15
  • 86
  • 113
  • 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 Jun 25 '12 at 13:09
  • ...and if I do viewModel.Title = ko.observable(viewModel.Title) to set initial value, knockout.validation.js throws "Uncaught TypeError" – Ruslan Jun 25 '12 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. – Jason Goemaat Jun 26 '12 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.

madcapnmckay
  • 15,782
  • 6
  • 61
  • 78
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" })
Community
  • 1
  • 1
Deutro
  • 88
  • 4