knockout-rails 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +134 -0
- data/HISTORY.md +12 -0
- data/README.md +106 -0
- data/Rakefile +37 -0
- data/knockout-rails.gemspec +27 -0
- data/lib/assets/javascripts/knockout/bindings.js.coffee +1 -0
- data/lib/assets/javascripts/knockout/model.js.coffee +112 -0
- data/lib/assets/javascripts/knockout/observables.js.coffee +1 -0
- data/lib/knockout-rails.rb +5 -0
- data/lib/knockout-rails/engine.rb +6 -0
- data/lib/knockout-rails/version.rb +3 -0
- data/lib/tasks/knockout-rails_tasks.rake +4 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +9 -0
- data/spec/dummy/app/assets/stylesheets/application.css +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +53 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +30 -0
- data/spec/dummy/config/environments/production.rb +60 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/javascripts/knockout/bindings_spec.js.coffee +2 -0
- data/spec/javascripts/knockout/model_spec.js.coffee +85 -0
- data/spec/javascripts/knockout/observables_spec.js.coffee +3 -0
- data/spec/javascripts/spec.css +3 -0
- data/spec/javascripts/spec.js.coffee +3 -0
- data/spec/javascripts/support/mock-ajax.js +207 -0
- data/spec/knockout_spec.rb +14 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/global.rb +7 -0
- data/spec/support/matchers.rb +36 -0
- data/vendor/assets/javascripts/knockout.js +7 -0
- data/vendor/assets/javascripts/knockout/knockout.js +3153 -0
- data/vendor/assets/javascripts/knockout/knockout.mapping.js +676 -0
- data/vendor/assets/javascripts/knockout/sugar-1.1.1.js +5828 -0
- metadata +206 -0
@@ -0,0 +1,676 @@
|
|
1
|
+
// Knockout Mapping plugin v2.0.2
|
2
|
+
// (c) 2011 Steven Sanderson, Roy Jacobs - http://knockoutjs.com/
|
3
|
+
// License: Ms-Pl (http://www.opensource.org/licenses/ms-pl.html)
|
4
|
+
|
5
|
+
// Google Closure Compiler helpers (used only to make the minified file smaller)
|
6
|
+
ko.exportSymbol = function (publicPath, object) {
|
7
|
+
var tokens = publicPath.split(".");
|
8
|
+
var target = window;
|
9
|
+
for (var i = 0; i < tokens.length - 1; i++)
|
10
|
+
target = target[tokens[i]];
|
11
|
+
target[tokens[tokens.length - 1]] = object;
|
12
|
+
};
|
13
|
+
ko.exportProperty = function (owner, publicName, object) {
|
14
|
+
owner[publicName] = object;
|
15
|
+
};
|
16
|
+
|
17
|
+
(function () {
|
18
|
+
ko.mapping = {};
|
19
|
+
|
20
|
+
var mappingProperty = "__ko_mapping__";
|
21
|
+
var realKoDependentObservable = ko.dependentObservable;
|
22
|
+
var mappingNesting = 0;
|
23
|
+
var dependentObservables;
|
24
|
+
var visitedObjects;
|
25
|
+
|
26
|
+
var _defaultOptions = {
|
27
|
+
include: ["_destroy"],
|
28
|
+
ignore: [],
|
29
|
+
copy: []
|
30
|
+
};
|
31
|
+
var defaultOptions = _defaultOptions;
|
32
|
+
|
33
|
+
function extendObject(destination, source) {
|
34
|
+
for (var key in source) {
|
35
|
+
if (source.hasOwnProperty(key) && source[key]) {
|
36
|
+
if (key && destination[key] && !(destination[key] instanceof Array)) {
|
37
|
+
extendObject(destination[key], source[key]);
|
38
|
+
} else {
|
39
|
+
destination[key] = source[key];
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
function merge(obj1, obj2) {
|
46
|
+
var merged = {};
|
47
|
+
extendObject(merged, obj1);
|
48
|
+
extendObject(merged, obj2);
|
49
|
+
|
50
|
+
return merged;
|
51
|
+
}
|
52
|
+
|
53
|
+
ko.mapping.isMapped = function (viewModel) {
|
54
|
+
var unwrapped = ko.utils.unwrapObservable(viewModel);
|
55
|
+
return unwrapped && unwrapped[mappingProperty];
|
56
|
+
}
|
57
|
+
|
58
|
+
ko.mapping.fromJS = function (jsObject /*, inputOptions, target*/ ) {
|
59
|
+
if (arguments.length == 0) throw new Error("When calling ko.fromJS, pass the object you want to convert.");
|
60
|
+
|
61
|
+
// When mapping is completed, even with an exception, reset the nesting level
|
62
|
+
window.setTimeout(function () {
|
63
|
+
mappingNesting = 0;
|
64
|
+
}, 0);
|
65
|
+
|
66
|
+
if (!mappingNesting++) {
|
67
|
+
dependentObservables = [];
|
68
|
+
visitedObjects = new objectLookup();
|
69
|
+
}
|
70
|
+
|
71
|
+
var options;
|
72
|
+
var target;
|
73
|
+
|
74
|
+
if (arguments.length == 2) {
|
75
|
+
if (arguments[1][mappingProperty]) {
|
76
|
+
target = arguments[1];
|
77
|
+
} else {
|
78
|
+
options = arguments[1];
|
79
|
+
}
|
80
|
+
}
|
81
|
+
if (arguments.length == 3) {
|
82
|
+
options = arguments[1];
|
83
|
+
target = arguments[2];
|
84
|
+
}
|
85
|
+
|
86
|
+
if (target) {
|
87
|
+
options = merge(options, target[mappingProperty]);
|
88
|
+
}
|
89
|
+
options = fillOptions(options);
|
90
|
+
|
91
|
+
var result = updateViewModel(target, jsObject, options);
|
92
|
+
if (target) {
|
93
|
+
result = target;
|
94
|
+
}
|
95
|
+
|
96
|
+
// Evaluate any dependent observables that were proxied.
|
97
|
+
// Do this in a timeout to defer execution. Basically, any user code that explicitly looks up the DO will perform the first evaluation. Otherwise,
|
98
|
+
// it will be done by this code.
|
99
|
+
if (!--mappingNesting) {
|
100
|
+
window.setTimeout(function () {
|
101
|
+
ko.utils.arrayForEach(dependentObservables, function (DO) {
|
102
|
+
if (DO) DO();
|
103
|
+
});
|
104
|
+
}, 0);
|
105
|
+
}
|
106
|
+
|
107
|
+
// Save any new mapping options in the view model, so that updateFromJS can use them later.
|
108
|
+
result[mappingProperty] = merge(result[mappingProperty], options);
|
109
|
+
|
110
|
+
return result;
|
111
|
+
};
|
112
|
+
|
113
|
+
ko.mapping.fromJSON = function (jsonString /*, options, target*/ ) {
|
114
|
+
var parsed = ko.utils.parseJson(jsonString);
|
115
|
+
arguments[0] = parsed;
|
116
|
+
return ko.mapping.fromJS.apply(this, arguments);
|
117
|
+
};
|
118
|
+
|
119
|
+
ko.mapping.updateFromJS = function (viewModel) {
|
120
|
+
throw new Error("ko.mapping.updateFromJS, use ko.mapping.fromJS instead. Please note that the order of parameters is different!");
|
121
|
+
};
|
122
|
+
|
123
|
+
ko.mapping.updateFromJSON = function (viewModel) {
|
124
|
+
throw new Error("ko.mapping.updateFromJSON, use ko.mapping.fromJSON instead. Please note that the order of parameters is different!");
|
125
|
+
};
|
126
|
+
|
127
|
+
ko.mapping.toJS = function (rootObject, options) {
|
128
|
+
if (!defaultOptions) ko.mapping.resetDefaultOptions();
|
129
|
+
|
130
|
+
if (arguments.length == 0) throw new Error("When calling ko.mapping.toJS, pass the object you want to convert.");
|
131
|
+
if (!(defaultOptions.ignore instanceof Array)) throw new Error("ko.mapping.defaultOptions().ignore should be an array.");
|
132
|
+
if (!(defaultOptions.include instanceof Array)) throw new Error("ko.mapping.defaultOptions().include should be an array.");
|
133
|
+
if (!(defaultOptions.copy instanceof Array)) throw new Error("ko.mapping.defaultOptions().copy should be an array.");
|
134
|
+
|
135
|
+
// Merge in the options used in fromJS
|
136
|
+
options = fillOptions(options, rootObject[mappingProperty]);
|
137
|
+
|
138
|
+
// We just unwrap everything at every level in the object graph
|
139
|
+
return ko.mapping.visitModel(rootObject, function (x) {
|
140
|
+
return ko.utils.unwrapObservable(x)
|
141
|
+
}, options);
|
142
|
+
};
|
143
|
+
|
144
|
+
ko.mapping.toJSON = function (rootObject, options) {
|
145
|
+
var plainJavaScriptObject = ko.mapping.toJS(rootObject, options);
|
146
|
+
return ko.utils.stringifyJson(plainJavaScriptObject);
|
147
|
+
};
|
148
|
+
|
149
|
+
ko.mapping.defaultOptions = function () {
|
150
|
+
if (arguments.length > 0) {
|
151
|
+
defaultOptions = arguments[0];
|
152
|
+
} else {
|
153
|
+
return defaultOptions;
|
154
|
+
}
|
155
|
+
};
|
156
|
+
|
157
|
+
ko.mapping.resetDefaultOptions = function () {
|
158
|
+
defaultOptions = {
|
159
|
+
include: _defaultOptions.include.slice(0),
|
160
|
+
ignore: _defaultOptions.ignore.slice(0),
|
161
|
+
copy: _defaultOptions.copy.slice(0)
|
162
|
+
};
|
163
|
+
};
|
164
|
+
|
165
|
+
function getType(x) {
|
166
|
+
if ((x) && (typeof (x) === "object") && (x.constructor == (new Date).constructor)) return "date";
|
167
|
+
return typeof x;
|
168
|
+
}
|
169
|
+
|
170
|
+
function fillOptions(options, otherOptions) {
|
171
|
+
options = options || {};
|
172
|
+
|
173
|
+
// Is there only a root-level mapping present?
|
174
|
+
if ((options.create instanceof Function) || (options.update instanceof Function) || (options.key instanceof Function) || (options.arrayChanged instanceof Function)) {
|
175
|
+
options = {
|
176
|
+
"": options
|
177
|
+
};
|
178
|
+
}
|
179
|
+
|
180
|
+
if (otherOptions) {
|
181
|
+
options.ignore = mergeArrays(otherOptions.ignore, options.ignore);
|
182
|
+
options.include = mergeArrays(otherOptions.include, options.include);
|
183
|
+
options.copy = mergeArrays(otherOptions.copy, options.copy);
|
184
|
+
}
|
185
|
+
options.ignore = mergeArrays(options.ignore, defaultOptions.ignore);
|
186
|
+
options.include = mergeArrays(options.include, defaultOptions.include);
|
187
|
+
options.copy = mergeArrays(options.copy, defaultOptions.copy);
|
188
|
+
|
189
|
+
options.mappedProperties = options.mappedProperties || {};
|
190
|
+
return options;
|
191
|
+
}
|
192
|
+
|
193
|
+
function mergeArrays(a, b) {
|
194
|
+
if (!(a instanceof Array)) {
|
195
|
+
if (getType(a) === "undefined") a = [];
|
196
|
+
else a = [a];
|
197
|
+
}
|
198
|
+
if (!(b instanceof Array)) {
|
199
|
+
if (getType(b) === "undefined") b = [];
|
200
|
+
else b = [b];
|
201
|
+
}
|
202
|
+
return a.concat(b);
|
203
|
+
}
|
204
|
+
|
205
|
+
// When using a 'create' callback, we proxy the dependent observable so that it doesn't immediately evaluate on creation.
|
206
|
+
// The reason is that the dependent observables in the user-specified callback may contain references to properties that have not been mapped yet.
|
207
|
+
function withProxyDependentObservable(dependentObservables, callback) {
|
208
|
+
var localDO = ko.dependentObservable;
|
209
|
+
ko.dependentObservable = function (read, owner, options) {
|
210
|
+
options = options || {};
|
211
|
+
|
212
|
+
var realDeferEvaluation = options.deferEvaluation;
|
213
|
+
|
214
|
+
if (read && typeof read == "object") { // mirrors condition in knockout implementation of DO's
|
215
|
+
options = read;
|
216
|
+
}
|
217
|
+
|
218
|
+
var isRemoved = false;
|
219
|
+
|
220
|
+
// We wrap the original dependent observable so that we can remove it from the 'dependentObservables' list we need to evaluate after mapping has
|
221
|
+
// completed if the user already evaluated the DO themselves in the meantime.
|
222
|
+
var wrap = function (DO) {
|
223
|
+
var wrapped = realKoDependentObservable({
|
224
|
+
read: function () {
|
225
|
+
if (!isRemoved) {
|
226
|
+
ko.utils.arrayRemoveItem(dependentObservables, DO);
|
227
|
+
isRemoved = true;
|
228
|
+
}
|
229
|
+
return DO.apply(DO, arguments);
|
230
|
+
},
|
231
|
+
write: function (val) {
|
232
|
+
return DO(val);
|
233
|
+
},
|
234
|
+
deferEvaluation: true
|
235
|
+
});
|
236
|
+
wrapped.__ko_proto__ = realKoDependentObservable;
|
237
|
+
return wrapped;
|
238
|
+
};
|
239
|
+
|
240
|
+
options.deferEvaluation = true; // will either set for just options, or both read/options.
|
241
|
+
var realDependentObservable = new realKoDependentObservable(read, owner, options);
|
242
|
+
realDependentObservable.__ko_proto__ = realKoDependentObservable;
|
243
|
+
|
244
|
+
if (!realDeferEvaluation) {
|
245
|
+
dependentObservables.push(realDependentObservable);
|
246
|
+
realDependentObservable = wrap(realDependentObservable);
|
247
|
+
}
|
248
|
+
|
249
|
+
return realDependentObservable;
|
250
|
+
}
|
251
|
+
var result = callback();
|
252
|
+
ko.dependentObservable = localDO;
|
253
|
+
return result;
|
254
|
+
}
|
255
|
+
|
256
|
+
function updateViewModel(mappedRootObject, rootObject, options, parentName, parent, parentPropertyName) {
|
257
|
+
var isArray = ko.utils.unwrapObservable(rootObject) instanceof Array;
|
258
|
+
|
259
|
+
parentPropertyName = parentPropertyName || "";
|
260
|
+
|
261
|
+
// If this object was already mapped previously, take the options from there and merge them with our existing ones.
|
262
|
+
if (ko.mapping.isMapped(mappedRootObject)) {
|
263
|
+
var previousMapping = ko.utils.unwrapObservable(mappedRootObject)[mappingProperty];
|
264
|
+
options = merge(previousMapping, options);
|
265
|
+
}
|
266
|
+
|
267
|
+
var callbackParams = {
|
268
|
+
data: rootObject,
|
269
|
+
parent: parent
|
270
|
+
};
|
271
|
+
|
272
|
+
var hasCreateCallback = function () {
|
273
|
+
return options[parentName] && options[parentName].create instanceof Function;
|
274
|
+
};
|
275
|
+
|
276
|
+
var createCallback = function (data) {
|
277
|
+
return withProxyDependentObservable(dependentObservables, function () {
|
278
|
+
return options[parentName].create({
|
279
|
+
data: data || callbackParams.data,
|
280
|
+
parent: callbackParams.parent
|
281
|
+
});
|
282
|
+
});
|
283
|
+
};
|
284
|
+
|
285
|
+
var hasUpdateCallback = function () {
|
286
|
+
return options[parentName] && options[parentName].update instanceof Function;
|
287
|
+
};
|
288
|
+
|
289
|
+
var updateCallback = function (obj, data) {
|
290
|
+
var params = {
|
291
|
+
data: data || callbackParams.data,
|
292
|
+
parent: callbackParams.parent,
|
293
|
+
target: ko.utils.unwrapObservable(obj)
|
294
|
+
};
|
295
|
+
|
296
|
+
if (ko.isWriteableObservable(obj)) {
|
297
|
+
params.observable = obj;
|
298
|
+
}
|
299
|
+
|
300
|
+
return options[parentName].update(params);
|
301
|
+
}
|
302
|
+
|
303
|
+
var alreadyMapped = visitedObjects.get(rootObject);
|
304
|
+
if (alreadyMapped) {
|
305
|
+
return alreadyMapped;
|
306
|
+
}
|
307
|
+
|
308
|
+
parentName = parentName || "";
|
309
|
+
|
310
|
+
if (!isArray) {
|
311
|
+
// For atomic types, do a direct update on the observable
|
312
|
+
if (!canHaveProperties(rootObject)) {
|
313
|
+
switch (getType(rootObject)) {
|
314
|
+
case "function":
|
315
|
+
if (hasUpdateCallback()) {
|
316
|
+
if (ko.isWriteableObservable(rootObject)) {
|
317
|
+
rootObject(updateCallback(rootObject));
|
318
|
+
mappedRootObject = rootObject;
|
319
|
+
} else {
|
320
|
+
mappedRootObject = updateCallback(rootObject);
|
321
|
+
}
|
322
|
+
} else {
|
323
|
+
mappedRootObject = rootObject;
|
324
|
+
}
|
325
|
+
break;
|
326
|
+
default:
|
327
|
+
if (ko.isWriteableObservable(mappedRootObject)) {
|
328
|
+
if (hasUpdateCallback()) {
|
329
|
+
mappedRootObject(updateCallback(mappedRootObject));
|
330
|
+
} else {
|
331
|
+
mappedRootObject(ko.utils.unwrapObservable(rootObject));
|
332
|
+
}
|
333
|
+
} else {
|
334
|
+
if (hasCreateCallback()) {
|
335
|
+
mappedRootObject = createCallback();
|
336
|
+
} else {
|
337
|
+
mappedRootObject = ko.observable(ko.utils.unwrapObservable(rootObject));
|
338
|
+
}
|
339
|
+
|
340
|
+
if (hasUpdateCallback()) {
|
341
|
+
mappedRootObject(updateCallback(mappedRootObject));
|
342
|
+
}
|
343
|
+
}
|
344
|
+
break;
|
345
|
+
}
|
346
|
+
|
347
|
+
} else {
|
348
|
+
mappedRootObject = ko.utils.unwrapObservable(mappedRootObject);
|
349
|
+
if (!mappedRootObject) {
|
350
|
+
if (hasCreateCallback()) {
|
351
|
+
var result = createCallback();
|
352
|
+
|
353
|
+
if (hasUpdateCallback()) {
|
354
|
+
result = updateCallback(result);
|
355
|
+
}
|
356
|
+
|
357
|
+
return result;
|
358
|
+
} else {
|
359
|
+
if (hasUpdateCallback()) {
|
360
|
+
return updateCallback(result);
|
361
|
+
}
|
362
|
+
|
363
|
+
mappedRootObject = {};
|
364
|
+
}
|
365
|
+
}
|
366
|
+
|
367
|
+
if (hasUpdateCallback()) {
|
368
|
+
mappedRootObject = updateCallback(mappedRootObject);
|
369
|
+
}
|
370
|
+
|
371
|
+
visitedObjects.save(rootObject, mappedRootObject);
|
372
|
+
|
373
|
+
// For non-atomic types, visit all properties and update recursively
|
374
|
+
visitPropertiesOrArrayEntries(rootObject, function (indexer) {
|
375
|
+
var fullPropertyName = parentPropertyName.length ? parentPropertyName + "." + indexer : indexer;
|
376
|
+
|
377
|
+
if (ko.utils.arrayIndexOf(options.ignore, fullPropertyName) != -1) {
|
378
|
+
return;
|
379
|
+
}
|
380
|
+
|
381
|
+
if (ko.utils.arrayIndexOf(options.copy, fullPropertyName) != -1) {
|
382
|
+
mappedRootObject[indexer] = rootObject[indexer];
|
383
|
+
return;
|
384
|
+
}
|
385
|
+
|
386
|
+
// In case we are adding an already mapped property, fill it with the previously mapped property value to prevent recursion.
|
387
|
+
// If this is a property that was generated by fromJS, we should use the options specified there
|
388
|
+
var prevMappedProperty = visitedObjects.get(rootObject[indexer]);
|
389
|
+
var value = prevMappedProperty || updateViewModel(mappedRootObject[indexer], rootObject[indexer], options, indexer, mappedRootObject, fullPropertyName);
|
390
|
+
|
391
|
+
if (ko.isWriteableObservable(mappedRootObject[indexer])) {
|
392
|
+
mappedRootObject[indexer](ko.utils.unwrapObservable(value));
|
393
|
+
} else {
|
394
|
+
mappedRootObject[indexer] = value;
|
395
|
+
}
|
396
|
+
|
397
|
+
options.mappedProperties[fullPropertyName] = true;
|
398
|
+
});
|
399
|
+
}
|
400
|
+
} else {
|
401
|
+
var changes = [];
|
402
|
+
|
403
|
+
var hasKeyCallback = false;
|
404
|
+
var keyCallback = function (x) {
|
405
|
+
return x;
|
406
|
+
}
|
407
|
+
if (options[parentName] && options[parentName].key) {
|
408
|
+
keyCallback = options[parentName].key;
|
409
|
+
hasKeyCallback = true;
|
410
|
+
}
|
411
|
+
|
412
|
+
if (!ko.isObservable(mappedRootObject)) {
|
413
|
+
// When creating the new observable array, also add a bunch of utility functions that take the 'key' of the array items into account.
|
414
|
+
mappedRootObject = ko.observableArray([]);
|
415
|
+
|
416
|
+
mappedRootObject.mappedRemove = function (valueOrPredicate) {
|
417
|
+
var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) {
|
418
|
+
return value === keyCallback(valueOrPredicate);
|
419
|
+
};
|
420
|
+
return mappedRootObject.remove(function (item) {
|
421
|
+
return predicate(keyCallback(item));
|
422
|
+
});
|
423
|
+
}
|
424
|
+
|
425
|
+
mappedRootObject.mappedRemoveAll = function (arrayOfValues) {
|
426
|
+
var arrayOfKeys = filterArrayByKey(arrayOfValues, keyCallback);
|
427
|
+
return mappedRootObject.remove(function (item) {
|
428
|
+
return ko.utils.arrayIndexOf(arrayOfKeys, keyCallback(item)) != -1;
|
429
|
+
});
|
430
|
+
}
|
431
|
+
|
432
|
+
mappedRootObject.mappedDestroy = function (valueOrPredicate) {
|
433
|
+
var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) {
|
434
|
+
return value === keyCallback(valueOrPredicate);
|
435
|
+
};
|
436
|
+
return mappedRootObject.destroy(function (item) {
|
437
|
+
return predicate(keyCallback(item));
|
438
|
+
});
|
439
|
+
}
|
440
|
+
|
441
|
+
mappedRootObject.mappedDestroyAll = function (arrayOfValues) {
|
442
|
+
var arrayOfKeys = filterArrayByKey(arrayOfValues, keyCallback);
|
443
|
+
return mappedRootObject.destroy(function (item) {
|
444
|
+
return ko.utils.arrayIndexOf(arrayOfKeys, keyCallback(item)) != -1;
|
445
|
+
});
|
446
|
+
}
|
447
|
+
|
448
|
+
mappedRootObject.mappedIndexOf = function (item) {
|
449
|
+
var keys = filterArrayByKey(mappedRootObject(), keyCallback);
|
450
|
+
var key = keyCallback(item);
|
451
|
+
return ko.utils.arrayIndexOf(keys, key);
|
452
|
+
}
|
453
|
+
|
454
|
+
mappedRootObject.mappedCreate = function (value) {
|
455
|
+
if (mappedRootObject.mappedIndexOf(value) !== -1) {
|
456
|
+
throw new Error("There already is an object with the key that you specified.");
|
457
|
+
}
|
458
|
+
|
459
|
+
var item = hasCreateCallback() ? createCallback(value) : value;
|
460
|
+
if (hasUpdateCallback()) {
|
461
|
+
var newValue = updateCallback(item, value);
|
462
|
+
if (ko.isWriteableObservable(item)) {
|
463
|
+
item(newValue);
|
464
|
+
} else {
|
465
|
+
item = newValue;
|
466
|
+
}
|
467
|
+
}
|
468
|
+
mappedRootObject.push(item);
|
469
|
+
return item;
|
470
|
+
}
|
471
|
+
}
|
472
|
+
|
473
|
+
var currentArrayKeys = filterArrayByKey(ko.utils.unwrapObservable(mappedRootObject), keyCallback).sort();
|
474
|
+
var newArrayKeys = filterArrayByKey(rootObject, keyCallback);
|
475
|
+
if (hasKeyCallback) newArrayKeys.sort();
|
476
|
+
var editScript = ko.utils.compareArrays(currentArrayKeys, newArrayKeys);
|
477
|
+
|
478
|
+
var ignoreIndexOf = {};
|
479
|
+
|
480
|
+
var newContents = [];
|
481
|
+
for (var i = 0, j = editScript.length; i < j; i++) {
|
482
|
+
var key = editScript[i];
|
483
|
+
var mappedItem;
|
484
|
+
var fullPropertyName = parentPropertyName + "[" + i + "]";
|
485
|
+
switch (key.status) {
|
486
|
+
case "added":
|
487
|
+
var item = getItemByKey(ko.utils.unwrapObservable(rootObject), key.value, keyCallback);
|
488
|
+
mappedItem = ko.utils.unwrapObservable(updateViewModel(undefined, item, options, parentName, mappedRootObject, fullPropertyName));
|
489
|
+
|
490
|
+
var index = ignorableIndexOf(ko.utils.unwrapObservable(rootObject), item, ignoreIndexOf);
|
491
|
+
newContents[index] = mappedItem;
|
492
|
+
ignoreIndexOf[index] = true;
|
493
|
+
break;
|
494
|
+
case "retained":
|
495
|
+
var item = getItemByKey(ko.utils.unwrapObservable(rootObject), key.value, keyCallback);
|
496
|
+
mappedItem = getItemByKey(mappedRootObject, key.value, keyCallback);
|
497
|
+
updateViewModel(mappedItem, item, options, parentName, mappedRootObject, fullPropertyName);
|
498
|
+
|
499
|
+
var index = ignorableIndexOf(ko.utils.unwrapObservable(rootObject), item, ignoreIndexOf);
|
500
|
+
newContents[index] = mappedItem;
|
501
|
+
ignoreIndexOf[index] = true;
|
502
|
+
break;
|
503
|
+
case "deleted":
|
504
|
+
mappedItem = getItemByKey(mappedRootObject, key.value, keyCallback);
|
505
|
+
break;
|
506
|
+
}
|
507
|
+
|
508
|
+
changes.push({
|
509
|
+
event: key.status,
|
510
|
+
item: mappedItem
|
511
|
+
});
|
512
|
+
}
|
513
|
+
|
514
|
+
mappedRootObject(newContents);
|
515
|
+
|
516
|
+
if (options[parentName] && options[parentName].arrayChanged) {
|
517
|
+
ko.utils.arrayForEach(changes, function (change) {
|
518
|
+
options[parentName].arrayChanged(change.event, change.item);
|
519
|
+
});
|
520
|
+
}
|
521
|
+
}
|
522
|
+
|
523
|
+
return mappedRootObject;
|
524
|
+
}
|
525
|
+
|
526
|
+
function ignorableIndexOf(array, item, ignoreIndices) {
|
527
|
+
for (var i = 0, j = array.length; i < j; i++) {
|
528
|
+
if (ignoreIndices[i] === true) continue;
|
529
|
+
if (array[i] === item) return i;
|
530
|
+
}
|
531
|
+
return null;
|
532
|
+
}
|
533
|
+
|
534
|
+
function mapKey(item, callback) {
|
535
|
+
var mappedItem;
|
536
|
+
if (callback) mappedItem = callback(item);
|
537
|
+
if (getType(mappedItem) === "undefined") mappedItem = item;
|
538
|
+
|
539
|
+
return ko.utils.unwrapObservable(mappedItem);
|
540
|
+
}
|
541
|
+
|
542
|
+
function getItemByKey(array, key, callback) {
|
543
|
+
var filtered = ko.utils.arrayFilter(ko.utils.unwrapObservable(array), function (item) {
|
544
|
+
return mapKey(item, callback) === key;
|
545
|
+
});
|
546
|
+
|
547
|
+
if (filtered.length == 0) throw new Error("When calling ko.update*, the key '" + key + "' was not found!");
|
548
|
+
if ((filtered.length > 1) && (canHaveProperties(filtered[0]))) throw new Error("When calling ko.update*, the key '" + key + "' was not unique!");
|
549
|
+
|
550
|
+
return filtered[0];
|
551
|
+
}
|
552
|
+
|
553
|
+
function filterArrayByKey(array, callback) {
|
554
|
+
return ko.utils.arrayMap(ko.utils.unwrapObservable(array), function (item) {
|
555
|
+
if (callback) {
|
556
|
+
return mapKey(item, callback);
|
557
|
+
} else {
|
558
|
+
return item;
|
559
|
+
}
|
560
|
+
});
|
561
|
+
}
|
562
|
+
|
563
|
+
function visitPropertiesOrArrayEntries(rootObject, visitorCallback) {
|
564
|
+
if (rootObject instanceof Array) {
|
565
|
+
for (var i = 0; i < rootObject.length; i++)
|
566
|
+
visitorCallback(i);
|
567
|
+
} else {
|
568
|
+
for (var propertyName in rootObject)
|
569
|
+
visitorCallback(propertyName);
|
570
|
+
}
|
571
|
+
};
|
572
|
+
|
573
|
+
function canHaveProperties(object) {
|
574
|
+
var type = getType(object);
|
575
|
+
return (type === "object") && (object !== null) && (type !== "undefined");
|
576
|
+
}
|
577
|
+
|
578
|
+
// Based on the parentName, this creates a fully classified name of a property
|
579
|
+
|
580
|
+
function getPropertyName(parentName, parent, indexer) {
|
581
|
+
var propertyName = parentName || "";
|
582
|
+
if (parent instanceof Array) {
|
583
|
+
if (parentName) {
|
584
|
+
propertyName += "[" + indexer + "]";
|
585
|
+
}
|
586
|
+
} else {
|
587
|
+
if (parentName) {
|
588
|
+
propertyName += ".";
|
589
|
+
}
|
590
|
+
propertyName += indexer;
|
591
|
+
}
|
592
|
+
return propertyName;
|
593
|
+
}
|
594
|
+
|
595
|
+
ko.mapping.visitModel = function (rootObject, callback, options) {
|
596
|
+
options = options || {};
|
597
|
+
options.visitedObjects = options.visitedObjects || new objectLookup();
|
598
|
+
|
599
|
+
if (!options.parentName) {
|
600
|
+
options = fillOptions(options);
|
601
|
+
}
|
602
|
+
|
603
|
+
var mappedRootObject;
|
604
|
+
var unwrappedRootObject = ko.utils.unwrapObservable(rootObject);
|
605
|
+
if (!canHaveProperties(unwrappedRootObject)) {
|
606
|
+
return callback(rootObject, options.parentName);
|
607
|
+
} else {
|
608
|
+
// Only do a callback, but ignore the results
|
609
|
+
callback(rootObject, options.parentName);
|
610
|
+
mappedRootObject = unwrappedRootObject instanceof Array ? [] : {};
|
611
|
+
}
|
612
|
+
|
613
|
+
options.visitedObjects.save(rootObject, mappedRootObject);
|
614
|
+
|
615
|
+
var parentName = options.parentName;
|
616
|
+
visitPropertiesOrArrayEntries(unwrappedRootObject, function (indexer) {
|
617
|
+
if (options.ignore && ko.utils.arrayIndexOf(options.ignore, indexer) != -1) return;
|
618
|
+
|
619
|
+
var propertyValue = unwrappedRootObject[indexer];
|
620
|
+
options.parentName = getPropertyName(parentName, unwrappedRootObject, indexer);
|
621
|
+
|
622
|
+
// If we don't want to explicitly copy the unmapped property...
|
623
|
+
if (ko.utils.arrayIndexOf(options.copy, indexer) === -1) {
|
624
|
+
// ...find out if it's a property we want to explicitly include
|
625
|
+
if (ko.utils.arrayIndexOf(options.include, indexer) === -1) {
|
626
|
+
// The mapped properties object contains all the properties that were part of the original object.
|
627
|
+
// If a property does not exist, and it is not because it is part of an array (e.g. "myProp[3]"), then it should not be unmapped.
|
628
|
+
if (unwrappedRootObject[mappingProperty] && unwrappedRootObject[mappingProperty].mappedProperties && !unwrappedRootObject[mappingProperty].mappedProperties[indexer] && !(unwrappedRootObject instanceof Array)) {
|
629
|
+
return;
|
630
|
+
}
|
631
|
+
}
|
632
|
+
}
|
633
|
+
|
634
|
+
var outputProperty;
|
635
|
+
switch (getType(ko.utils.unwrapObservable(propertyValue))) {
|
636
|
+
case "object":
|
637
|
+
case "undefined":
|
638
|
+
var previouslyMappedValue = options.visitedObjects.get(propertyValue);
|
639
|
+
mappedRootObject[indexer] = (getType(previouslyMappedValue) !== "undefined") ? previouslyMappedValue : ko.mapping.visitModel(propertyValue, callback, options);
|
640
|
+
break;
|
641
|
+
default:
|
642
|
+
mappedRootObject[indexer] = callback(propertyValue, options.parentName);
|
643
|
+
}
|
644
|
+
});
|
645
|
+
|
646
|
+
return mappedRootObject;
|
647
|
+
}
|
648
|
+
|
649
|
+
function objectLookup() {
|
650
|
+
var keys = [];
|
651
|
+
var values = [];
|
652
|
+
this.save = function (key, value) {
|
653
|
+
var existingIndex = ko.utils.arrayIndexOf(keys, key);
|
654
|
+
if (existingIndex >= 0) values[existingIndex] = value;
|
655
|
+
else {
|
656
|
+
keys.push(key);
|
657
|
+
values.push(value);
|
658
|
+
}
|
659
|
+
};
|
660
|
+
this.get = function (key) {
|
661
|
+
var existingIndex = ko.utils.arrayIndexOf(keys, key);
|
662
|
+
return (existingIndex >= 0) ? values[existingIndex] : undefined;
|
663
|
+
};
|
664
|
+
};
|
665
|
+
|
666
|
+
ko.exportSymbol('ko.mapping', ko.mapping);
|
667
|
+
ko.exportSymbol('ko.mapping.fromJS', ko.mapping.fromJS);
|
668
|
+
ko.exportSymbol('ko.mapping.fromJSON', ko.mapping.fromJSON);
|
669
|
+
ko.exportSymbol('ko.mapping.isMapped', ko.mapping.isMapped);
|
670
|
+
ko.exportSymbol('ko.mapping.defaultOptions', ko.mapping.defaultOptions);
|
671
|
+
ko.exportSymbol('ko.mapping.toJS', ko.mapping.toJS);
|
672
|
+
ko.exportSymbol('ko.mapping.toJSON', ko.mapping.toJSON);
|
673
|
+
ko.exportSymbol('ko.mapping.updateFromJS', ko.mapping.updateFromJS);
|
674
|
+
ko.exportSymbol('ko.mapping.updateFromJSON', ko.mapping.updateFromJSON);
|
675
|
+
ko.exportSymbol('ko.mapping.visitModel', ko.mapping.visitModel);
|
676
|
+
})();
|