60 | (also on Nuget) 61 |
├── Tests ├── updateFromModel-Mapping-qunit-tests.js ├── array-qunit-tests.js ├── TestForMappingCompatability.htm ├── null-Basic-qunit-tests.js ├── undefined-Basic-qunit-tests.js ├── issues-qunit-tests.js ├── fromModelToModel-Basic-qunit-tests.js ├── updateFromModel-Mapping-noncontiguous-qunit-tests.js ├── Tests.min.htm ├── Tests.htm ├── fromModelToModel-Mapping-qunit-tests.js ├── fromModel-Basic-qunit-tests.js ├── toModel-Basic-qunit-tests.js ├── simpleTypes-qunit-tests.js ├── null-Mapping-qunit-tests.js ├── undefined-Mapping-qunit-tests.js ├── nestedObject-qunit-tests.js ├── updateFromModel-Basic-qunit-tests.js └── fromModel-Mapping-qunit-tests.js ├── web ├── javascripts │ ├── main.js │ └── prism.js ├── images │ ├── bg_hr.png │ ├── blacktocat.png │ ├── icon_download.png │ └── sprite_download.png └── stylesheets │ ├── prism.css │ ├── pygment_trac.css │ └── stylesheet.css ├── nuget ├── 2.0.0 │ ├── knockout.viewmodel.2.0.0.nupkg │ ├── knockout.viewmodel.2.0.0.min.js │ └── knockout.viewmodel.2.0.0.js ├── 2.0.1 │ ├── knockout.viewmodel.2.0.1.nupkg │ ├── knockout.viewmodel.2.0.1.min.js │ └── knockout.viewmodel.2.0.1.js └── 2.0.2 │ ├── knockout.viewmodel.2.0.2.nupkg │ └── knockout.viewmodel.2.0.2.min.js ├── README.md ├── .gitignore ├── knockout.viewmodel.min.js ├── params.json └── index.html /Tests/updateFromModel-Mapping-qunit-tests.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/javascripts/main.js: -------------------------------------------------------------------------------- 1 | console.log('This would be the main JS file.'); 2 | -------------------------------------------------------------------------------- /web/images/bg_hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderenaissance/knockout.viewmodel/HEAD/web/images/bg_hr.png -------------------------------------------------------------------------------- /web/images/blacktocat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderenaissance/knockout.viewmodel/HEAD/web/images/blacktocat.png -------------------------------------------------------------------------------- /web/images/icon_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderenaissance/knockout.viewmodel/HEAD/web/images/icon_download.png -------------------------------------------------------------------------------- /web/images/sprite_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderenaissance/knockout.viewmodel/HEAD/web/images/sprite_download.png -------------------------------------------------------------------------------- /nuget/2.0.0/knockout.viewmodel.2.0.0.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderenaissance/knockout.viewmodel/HEAD/nuget/2.0.0/knockout.viewmodel.2.0.0.nupkg -------------------------------------------------------------------------------- /nuget/2.0.1/knockout.viewmodel.2.0.1.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderenaissance/knockout.viewmodel/HEAD/nuget/2.0.1/knockout.viewmodel.2.0.1.nupkg -------------------------------------------------------------------------------- /nuget/2.0.2/knockout.viewmodel.2.0.2.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderenaissance/knockout.viewmodel/HEAD/nuget/2.0.2/knockout.viewmodel.2.0.2.nupkg -------------------------------------------------------------------------------- /Tests/array-qunit-tests.js: -------------------------------------------------------------------------------- 1 | 2 | var model; 3 | module("toModel Basic", { 4 | setup: function () { 5 | //ko.viewmodel.options.logging = true; 6 | model = { 7 | array: [] 8 | }; 9 | }, 10 | teardown: function () { 11 | //ko.viewmodel.options.logging = false; 12 | model = undefined; 13 | } 14 | }); 15 | 16 | 17 | test("array push pop", function () { 18 | var viewmodel, result; 19 | 20 | viewmodel = ko.viewmodel.fromModel(model); 21 | viewmodel.array.push({ test: true }); 22 | result = viewmodel.array.pop(); 23 | 24 | assert(result.test(), true); 25 | 26 | }); 27 | 28 | test("array push pop", function () { 29 | var viewmodel, result; 30 | 31 | viewmodel = ko.viewmodel.fromModel(model); 32 | viewmodel.array.push({ test: true }, true); 33 | result = viewmodel.array.pop(); 34 | 35 | assert(result.test, true); 36 | 37 | }); 38 | 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The knockout viewmodel plugin, Copyright (c) 2012 Dave Herren 2 | 3 | The MIT License (MIT) 4 | ================== 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the "Software"), 8 | to deal in the Software without restriction, including without limitation 9 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | and/or sell copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Tests/TestForMappingCompatability.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | ko.viewmodel tests 6 | 7 | 8 | 9 | 10 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /web/stylesheets/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js default theme for JavaScript, CSS and HTML 3 | * Based on dabblet (http://dabblet.com) 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: black; 10 | text-shadow: 0 1px white; 11 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 12 | direction: ltr; 13 | text-align: left; 14 | white-space: pre; 15 | word-spacing: normal; 16 | 17 | -moz-tab-size: 4; 18 | -o-tab-size: 4; 19 | tab-size: 4; 20 | 21 | -webkit-hyphens: none; 22 | -moz-hyphens: none; 23 | -ms-hyphens: none; 24 | hyphens: none; 25 | } 26 | 27 | /* Code blocks */ 28 | pre[class*="language-"] { 29 | padding: 1em; 30 | margin: .5em 0; 31 | overflow: auto; 32 | } 33 | 34 | :not(pre) > code[class*="language-"], 35 | pre[class*="language-"] { 36 | background: #f5f2f0; 37 | } 38 | 39 | /* Inline code */ 40 | :not(pre) > code[class*="language-"] { 41 | padding: .1em; 42 | border-radius: .3em; 43 | } 44 | 45 | .token.comment, 46 | .token.prolog, 47 | .token.doctype, 48 | .token.cdata { 49 | color: slategray; 50 | } 51 | 52 | .token.punctuation { 53 | color: #999; 54 | } 55 | 56 | .namespace { 57 | opacity: .7; 58 | } 59 | 60 | .token.property, 61 | .token.tag, 62 | .token.boolean, 63 | .token.number { 64 | color: #905; 65 | } 66 | 67 | .token.selector, 68 | .token.attr-name, 69 | .token.string { 70 | color: #690; 71 | } 72 | 73 | .token.operator, 74 | .token.entity, 75 | .token.url, 76 | .language-css .token.string, 77 | .style .token.string { 78 | color: #a67f59; 79 | background: hsla(0,0%,100%,.5); 80 | } 81 | 82 | .token.atrule, 83 | .token.attr-value, 84 | .token.keyword { 85 | color: #07a; 86 | } 87 | 88 | 89 | .token.regex, 90 | .token.important { 91 | color: #e90; 92 | } 93 | 94 | .token.important { 95 | font-weight: bold; 96 | } 97 | 98 | .token.entity { 99 | cursor: help; 100 | } 101 | -------------------------------------------------------------------------------- /Tests/null-Basic-qunit-tests.js: -------------------------------------------------------------------------------- 1 | ///The knockout viewmodel plugin runs several times faster than the knockout mapping plugin. It also allows you to fine tune your viewmodel creation for even more speed. 42 | Now you can create complex observable viewmodels easily and with more structure and control than ever before! 43 |
44 |48 | There is a growing suite of unit-tests that ensures that ko.viewmodel works reliably. Bug fixes always start with one or more new unit test so reliability goes up over time. 49 |
50 |This code creates an viewModel with an observable array of users whose properties are observable.
54 |
55 | model = {//your model is normally provided via an ajax call
56 | users:[
57 | {firstName:"John", lastName:"Doe"},
58 | {firstName:"James", lastName:"Smith"}
59 | ]
60 | };
61 |
62 | viewmodel = ko.viewmodel.fromModel(model);
63 |
64 | If you need to update your viewmodel with more recent model data this can be done by calling updateFromModel.
69 | 70 |
71 | ko.viewmodel.updateFromModel(viewmodel, updatedModel);
72 |
73 |
74 | This method optionally takes a third parameter, makeNoncontiguousObjectUpdates, which can be used to eliminate long running script errors in older browsers. When set to true each object will have an update function created for it which will be called via setTimeout. Given this makeNoncontiguousObjectUpdates should only be set to true if you are getting long running script errors in older browsers. An onComplete method allows you to pass in a callback to be fired when noncontiguous object updates are completed.
75 |
76 | ko.viewmodel.updateFromModel(viewmodel, updatedModel, true).onComplete(function(){
77 | //respond to completion of update
78 | });
79 |
80 | Note: the onComplete method is only available when makeNoncontiguousObjectUpdates is set to true.
81 |At somepoint you'll need to get an updated model to send back up to the server. This can be done by calling toModel.
86 | 87 |
88 | model = ko.viewmodel.toModel(viewmodel);
89 |
90 | Now lets say that you want to extend each object in the users array with an isDeleted flag. You would do this by specifying an extend option. 94 |
95 |
96 | options:{
97 | extend:{
98 | "{root}.users[i]": function(user){
99 | user.isDeleted = ko.observable(false);
100 | }
101 | }
102 | };
103 |
104 | viewmodel = ko.viewmodel.fromModel(model,options));
105 |
106 | If we also wanted to add a delete method to an array we would use the following options:
107 |
108 | options:{
109 | extend:{
110 | "{root}.users[i]": function(user){
111 | user.isDeleted= ko.observable(false);
112 | },
113 | "{root}.users": function(users){
114 | users.Delete = function(user){
115 | user.isDeleted(true);
116 | }
117 | }
118 | }
119 | };
120 |
121 |
122 | Processing options are specified for an item by it's path. Every full path starts with {root}. Items in an array are referred to as [i]. So a path of "{root}.users[i].firstName" would be used to specify custom processing for the firstName property of every object in the users array. That is its full path name, but it's not necessary to refer to every item by it's -full path name. There are three ways of referencing a path:
128 | 129 |139 | There six types of processing options: custom, append, exclude, extend, arrayChildId, and shared. 140 |
141 | 142 |151 | The only processing types that can be used together on a path are extend and arrayChildId. If multiple processing options are specified 152 | for the same path only one will win. Which one will win is subject to internal processing logic that has been organized for performance reasons 153 | and should not be relied upon as it is subject to change in future versions. 154 |
155 |
159 | options:{
160 | extend:{
161 | "{root}.users[i]": function(user){
162 | user.isDeleted = ko.observable(false);
163 | return user;
164 | }
165 | }
166 | };
167 |
168 | 169 | The value of the path is passed to the extend function after processing has been completed on the path and its children. Objects can be modified without being returned. 170 | Note: Whatever is returned from the extend function will be persisted and replace the default processing. 171 |
172 | There is also this alternate syntax if you need to do unmapping: 173 |
174 | options:{
175 | custom:{
176 | "{root}.users[i]": {
177 | map:function(mapped){
178 | mapped.isDeleted= ko.observable(false);
179 | return mapped;
180 | },
181 | unmap:function(unmapped){
182 | //Because isDeleted was an observable it was unwrapped and added to the model.
183 | delete unmapped.isDeleted;
184 | return unmapped;
185 | }
186 | }
187 | }
188 | };
189 |
190 | Functions added with extend will be removed when your model is created, however observables will not be and can be removed using unmap if they will cause
191 | problems with servers side serialization. It should be noted that in many cases serialization will drop unexpected properties instead of throwing an
192 | error, so this is not always necessary and would depend on your platform.
193 |
200 | options:{
201 | custom:{
202 | "{root}.users[i]": function(user){
203 | user.isDeleted= ko.observable(false);
204 | return user;
205 | }
206 | }
207 | };
208 |
209 |
210 | There is also this alternate syntax if you need to do unmapping
211 |
212 | options:{
213 | custom:{
214 | "{root}.users[i]": {
215 | map:function(user){
216 | var mapped = ko.viewmodel.fromModel(user);
217 | mapped().isDeleted= ko.observable(false);
218 | return mapped;
219 | },
220 | unmap:function(user){
221 | var unmapped = ko.viewmodel.toModel(user);
222 | delete unmapped.isDeleted;
223 | return unmapped;
224 | }
225 | }
226 | }
227 | };
228 |
229 | In this case the users are passed through as is with the addition of an observable isDeleted flag.
230 |
241 | options:{
242 | append:["{root}.users[i]"]
243 | };
244 |
245 | In this case none of the users have been altered and are appended unchanged.
246 |
251 | options:{
252 | exclude:["{root}.users[i].firstName"]
253 | };
254 |
255 | The firstName property is not included.
256 |
257 | When calling fromModel:
258 |
273 | options:{
274 | arrayChildId:{
275 | "{root}.users":"id"
276 | }
277 | }
278 |
279 | But this will make more sense in context. What if you had an array of items for purchase and the selected items were added to your total.
280 | The code might look similar to this:
281 |
282 |
283 | var model = {
284 | items:[
285 | {
286 | id:256889,
287 | name:"item Name",
288 | description:"Description of Item",
289 | availble:3,
290 | price:4.25
291 | }
292 | ]
293 | };
294 |
295 | var viewmodel = ko.viewmodel(model, {
296 | arrayChildId:{//child item id
297 | "{root}.items":"id"
298 | },
299 | extend:{
300 | "{root}.items":function(items){//Toggle function added to array for better performance
301 | items.ToggleSelect = function (item){
302 | item.selected(!item.selected);
303 | }
304 | return items;
305 | },
306 | "{root}.items[i]":function (item){
307 | item.selected = ko.observable(false);
308 | }
309 | }
310 | }
311 |
312 | So now imagine the app is regularly pinging the server to for an updated model so that the number of items available is accurate.
313 | Without the arrayChildId option every time an update came back the array would be populated again and all of the items would be
314 | extended with selected set to false. With the arrayChildId specified selected state of all objects will be maintained.
315 | 376 | Global options affect all calls to ko.viewmodel and are access and set via ko.viewmodel.options. Currently there are two global options: 377 |
378 | 379 |