How to use knockout.js with ASP.NET MVC ViewModels? -


bounty

it's been awhile , still have couple outstanding questions. hope adding bounty maybe these questions answered.

  1. how use html helpers knockout.js
  2. why document ready needed make work(see first edit more information)

  3. how do if using knockout mapping view models? not have function due mapping.

    function appviewmodel() {      // ... leave firstname, lastname, , fullname unchanged here ...      this.capitalizelastname = function() {      var currentval = this.lastname();        // read current value      this.lastname(currentval.touppercase()); // write modified value  }; 
  4. i want use plugins instance want able rollback observables if user cancels request want able go last value. research seems achieved people making plugins editables

    how use if using mapping? don’t want go method have in view manual mapping map each mvc viewmode field ko model field want little inline javascript possible , seems double work , that’s why mapping.

  5. i concerned make work easy (by using mapping) lose lot of ko power on other hand concerned manual mapping lot of work , make views contain information , might become in future harder maintain(say if remove property in mvc model have move in ko viewmodel)


original post

i using asp.net mvc 3 , looking knockout looks pretty cool having hard time figuring out how works asp.net mvc view models.

for me right this

 public class coursevm     {         public int courseid { get; set; }         [required(errormessage = "course name required")]         [stringlength(40, errormessage = "course name cannot long.")]         public string coursename{ get; set; }           public list<studentvm> studentviewmodels { get; set; }  } 

i have vm has basic properties coursename , have simple validation on top of it. vm model might contain other view models in if needed.

i pass vm view use html helpers me display user.

@html.textboxfor(x => x.coursename) 

i might have foreach loops or data out of collection of student view models.

then when submit form use jquery , serialize array , send controller action method bind viewmodel.

with knockout.js different got viewmodels , examples seen don't use html helpers.

how use these 2 features of mvc knockout.js?

i found video , briefly(last few minutes of video @ 18:48) goes way use viewmodels having inline script has knockout.js viewmodel gets assigned values in viewmodel.

is way it? how in example having collection of viewmodels in it? have have foreach loop or extract values out , assign knockout?

as html helpers video says nothing them.

these 2 areas confuses heck out of me not many people seem talk , leaves me confused of how initial values , getting view when ever example hard-coded value example.


edit

i trying darin dimitrov has suggested , seems work(i had make changes code though). not sure why had use document ready somehow not ready without it.

@model mvcapplication1.models.test  @{     layout = null; }  <!doctype html>  <html> <head>     <title>index</title>     <script src="../../scripts/jquery-1.5.1.js" type="text/javascript"></script>     <script src="../../scripts/knockout-2.1.0.js" type="text/javascript"></script>     <script src="../../scripts/knockout.mapping-latest.js" type="text/javascript"></script>    <script type="text/javascript">     $(function()    {       var model = @html.raw(json.encode(model));   // activates knockout.js ko.applybindings(model);    });  </script>  </head> <body>     <div>         <p>first name: <strong data-bind="text: firstname"></strong></p>         <p>last name: <strong data-bind="text: lastname"></strong></p>         @model.firstname , @model.lastname     </div> </body> </html> 

i had wrap around jquery document ready make work.

i warning. not sure about.

warning 1   conditional compilation turned off   -> @html.raw 

so have starting point guess @ least update when done more playing around , how works.

i trying go through interactive tutorials use viewmodel instead.

not sure how tackle these parts yet

function appviewmodel() {     this.firstname = ko.observable("bert");     this.lastname = ko.observable("bertington"); } 

or

function appviewmodel() {     // ... leave firstname, lastname, , fullname unchanged here ...      this.capitalizelastname = function() {         var currentval = this.lastname();        // read current value         this.lastname(currentval.touppercase()); // write modified value     }; 


edit 2

i been able figure out first problem. no clue second problem. yet though. got ideas?

 @model mvcapplication1.models.test      @{         layout = null;     }      <!doctype html>      <html>     <head>         <title>index</title>         <script src="../../scripts/jquery-1.5.1.js" type="text/javascript"></script>         <script src="../../scripts/knockout-2.1.0.js" type="text/javascript"></script>         <script src="../../scripts/knockout.mapping-latest.js" type="text/javascript"></script>        <script type="text/javascript">         $(function()        {         var model = @html.raw(json.encode(model));         var viewmodel = ko.mapping.fromjs(model);         ko.applybindings(viewmodel);         });      </script>      </head>     <body>         <div>             @*grab values view model directly*@             <p>first name: <strong data-bind="text: firstname"></strong></p>             <p>last name: <strong data-bind="text: lastname"></strong></p>              @*grab values second view model made*@             <p>someothervalue <strong data-bind="text: test2.someothervalue"></strong></p>             <p>another <strong data-bind="text: test2.another"></strong></p>              @*allow changes values should sync above values.*@             <p>first name: <input data-bind="value: firstname" /></p>             <p>last name: <input data-bind="value: lastname" /></p>             <p>someothervalue <input data-bind="value: test2.someothervalue" /></p>             <p>another <input data-bind="value: test2.another" /></p>             @* seeing if can p tags , see if update.*@             <p data-bind="foreach: test3">                 <strong data-bind="text: test3value"></strong>              </p>       @*took 3rd view model in collection , output values textbox*@            <table>         <thead><tr>             <th>test3</th>         </tr></thead>           <tbody data-bind="foreach: test3">             <tr>                 <td>                         <strong data-bind="text: test3value"></strong>  <input type="text" data-bind="value: test3value"/>                 </td>             </tr>             </tbody>     </table> 

controller

  public actionresult index()     {               test2 test2 = new test2         {             = "test",             someothervalue = "test2"         };          test vm = new test         {             firstname = "bob",             lastname = "n/a",              test2 = test2,          };         (int = 0; < 10; i++)         {             test3 test3 = new test3             {                 test3value = i.tostring()             };               vm.test3.add(test3);         }          return view(vm);     } 

i think have summarized questions, if missed please let me know (if summarize questions in 1 place nice =))

note. compatibility ko.editable plug-in added

download full code

how use html helpers knockout.js

this easy:

@html.textboxfor(model => model.courseid, new { data_bind = "value: courseid" }) 

where:

  • value: courseid indicates binding value property of input control courseid property model , script model

the result is:

<input data-bind="value: courseid" data-val="true" data-val-number="the field courseid must number." data-val-required="the courseid field required." id="courseid" name="courseid" type="text" value="12" /> 

why document ready needed make work(see first edit more information)

i not understand yet why need use ready event serialize model, seems required (not worry though)

how do if using knockout mapping view models? not have function due mapping.

if understand correctly need append new method ko model, that's easy merging models

for more info, in section -mapping different sources-

function viewmodel() {     this.addstudent = function () {         alert("de");     }; };  $(function () {     var jsonmodel = '@html.raw(jsonconvert.serializeobject(this.model))';     var mvcmodel = ko.mapping.fromjson(jsonmodel);      var myviewmodel = new viewmodel();     var g = ko.mapping.fromjs(myviewmodel, mvcmodel);      ko.applybindings(g); }); 

about warning receiveing

warning 1 conditional compilation turned off -> @html.raw

you need use quotes

compatibility ko.editable plug-in

i thought going more complex, turns out integration easy, in order make model editable add following line: (remember in case using mixed model, server , adding extension in client , editable works... it's great):

    ko.editable(g);     ko.applybindings(g); 

from here need play bindings using extensions added plug-in, example, have button start editing fields , in button start edit process:

    this.editmode = function () {         this.isineditmode(!this.isineditmode());         this.beginedit();     }; 

then have commit , cancel buttons following code:

    this.executecommit = function () {         this.commit();         this.isineditmode(false);     };     this.executerollback = function () {         if (this.haschanges()) {             if (confirm("are sure want discard changes?")) {                 this.rollback();                 this.isineditmode(false);             }         }         else {             this.rollback();             this.isineditmode(false);         }     }; 

and finally, have 1 field indicate whether fields in edit mode or not, bind enable property.

this.isineditmode = ko.observable(false); 

about array question

i might have foreach loops or data out of collection of student view models.

then when submit form use jquery , serialize array , send controller action method bind viewmodel.

you can same ko, in following example, create following output:

enter image description here

basically here, have 2 lists, created using helpers , binded ko, have dblclick event binded when fired, remove selected item current list , add other list, when post controller, content of each list sent json data , re-attached server model

nuggets:

external scripts.

controller code

    [httpget]     public actionresult index()     {         var m = new coursevm { courseid = 12, coursename = ".net" };          m.studentviewmodels.add(new studentvm { id = 545, name = "name server", lastname = "last name server" });          return view(m);     }      [httppost]     public actionresult index(coursevm model)     {         if (!string.isnullorwhitespace(model.studentsserialized))         {             model.studentviewmodels = jsonconvert.deserializeobject<list<studentvm>>(model.studentsserialized);             model.studentsserialized = string.empty;         }          if (!string.isnullorwhitespace(model.selectedstudentsserialized))         {             model.selectedstudents = jsonconvert.deserializeobject<list<studentvm>>(model.selectedstudentsserialized);             model.selectedstudentsserialized = string.empty;         }          return view(model);     } 

model

public class coursevm {     public coursevm()     {         this.studentviewmodels = new list<studentvm>();         this.selectedstudents = new list<studentvm>();     }      public int courseid { get; set; }      [required(errormessage = "course name required")]     [stringlength(100, errormessage = "course name cannot long.")]     public string coursename { get; set; }      public list<studentvm> studentviewmodels { get; set; }     public list<studentvm> selectedstudents { get; set; }      public string studentsserialized { get; set; }     public string selectedstudentsserialized { get; set; } }  public class studentvm {     public int id { get; set; }     public string name { get; set; }     public string lastname { get; set; } } 

cshtml page

@using (html.beginform()) {     @html.validationsummary(true)     <fieldset>         <legend>coursevm</legend>          <div>             <div class="editor-label">                 @html.labelfor(model => model.courseid)             </div>             <div class="editor-field">                 @html.textboxfor(model => model.courseid, new { data_bind = "enable: isineditmode, value: courseid" })                 @html.validationmessagefor(model => model.courseid)             </div>              <div class="editor-label">                 @html.labelfor(model => model.coursename)             </div>             <div class="editor-field">                 @html.textboxfor(model => model.coursename, new { data_bind = "enable: isineditmode, value: coursename" })                 @html.validationmessagefor(model => model.coursename)             </div>             <div class="editor-label">                 @html.labelfor(model => model.studentviewmodels);             </div>             <div class="editor-field">                  @html.listboxfor(                     model => model.studentviewmodels,                     new selectlist(this.model.studentviewmodels, "id", "name"),                     new                     {                         style = "width: 37%;",                         data_bind = "enable: isineditmode, options: studentviewmodels, optionstext: 'name', value: leftstudentselected, event: { dblclick: movefromlefttoright }"                     }                 )                 @html.listboxfor(                     model => model.selectedstudents,                     new selectlist(this.model.selectedstudents, "id", "name"),                     new                     {                         style = "width: 37%;",                         data_bind = "enable: isineditmode, options: selectedstudents, optionstext: 'name', value: rightstudentselected, event: { dblclick: movefromrighttoleft }"                     }                 )             </div>              @html.hiddenfor(model => model.courseid, new { data_bind="value: courseid" })             @html.hiddenfor(model => model.coursename, new { data_bind="value: coursename" })             @html.hiddenfor(model => model.studentsserialized, new { data_bind = "value: studentsserialized" })             @html.hiddenfor(model => model.selectedstudentsserialized, new { data_bind = "value: selectedstudentsserialized" })         </div>          <p>             <input type="submit" value="save" data-bind="enable: !isineditmode()" />              <button data-bind="enable: !isineditmode(), click: editmode">edit mode</button><br />             <div>                 <button data-bind="enable: isineditmode, click: addstudent">add student</button>                 <button data-bind="enable: haschanges, click: executecommit">commit</button>                 <button data-bind="enable: isineditmode, click: executerollback">cancel</button>             </div>         </p>     </fieldset> } 

scripts

<script src="@url.content("~/scripts/jquery-1.7.2.min.js")" type="text/javascript"></script> <script src="@url.content("~/scripts/knockout-2.1.0.js")" type="text/javascript"></script> <script src="@url.content("~/scripts/knockout.mapping-latest.js")" type="text/javascript"></script> <script src="@url.content("~/scripts/ko.editables.js")" type="text/javascript"></script>  <script type="text/javascript">     var g = null;     function viewmodel() {         this.addstudent = function () {             this.studentviewmodels.push(new student(25, "my name" + new date(), "my last name"));             this.serializelists();         };         this.serializelists = function () {             this.studentsserialized(ko.tojson(this.studentviewmodels));             this.selectedstudentsserialized(ko.tojson(this.selectedstudents));         };         this.leftstudentselected = ko.observable();         this.rightstudentselected = ko.observable();         this.movefromlefttoright = function () {             this.selectedstudents.push(this.leftstudentselected());             this.studentviewmodels.remove(this.leftstudentselected());             this.serializelists();         };         this.movefromrighttoleft = function () {             this.studentviewmodels.push(this.rightstudentselected());             this.selectedstudents.remove(this.rightstudentselected());             this.serializelists();         };         this.isineditmode = ko.observable(false);         this.executecommit = function () {             this.commit();             this.isineditmode(false);         };         this.executerollback = function () {             if (this.haschanges()) {                 if (confirm("are sure want discard changes?")) {                     this.rollback();                     this.isineditmode(false);                 }             }             else {                 this.rollback();                 this.isineditmode(false);             }         };         this.editmode = function () {             this.isineditmode(!this.isineditmode());             this.beginedit();         };     }      function student(id, name, lastname) {         this.id = id;         this.name = name;         this.lastname = lastname;     }      $(function () {         var jsonmodel = '@html.raw(jsonconvert.serializeobject(this.model))';         var mvcmodel = ko.mapping.fromjson(jsonmodel);          var myviewmodel = new viewmodel();         g = ko.mapping.fromjs(myviewmodel, mvcmodel);          g.studentsserialized(ko.tojson(g.studentviewmodels));         g.selectedstudentsserialized(ko.tojson(g.selectedstudents));          ko.editable(g);         ko.applybindings(g);     }); </script> 

note: added these lines:

        @html.hiddenfor(model => model.courseid, new { data_bind="value: courseid" })         @html.hiddenfor(model => model.coursename, new { data_bind="value: coursename" }) 

because when submit form fields disabled, values not transmitted server, that's why added couple of hidden fields trick


Comments

Popular posts from this blog

java - Run a .jar on Heroku -

java - Jtable duplicate Rows -

validation - How to pass paramaters like unix into windows batch file -