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.
- how use html helpers knockout.js
why document ready needed make work(see first edit more information)
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 };
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.
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 bindingvalue
property ofinput
controlcourseid
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:
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
Post a Comment