knockout-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +12 -0
  3. data/Gemfile.lock +134 -0
  4. data/HISTORY.md +12 -0
  5. data/README.md +106 -0
  6. data/Rakefile +37 -0
  7. data/knockout-rails.gemspec +27 -0
  8. data/lib/assets/javascripts/knockout/bindings.js.coffee +1 -0
  9. data/lib/assets/javascripts/knockout/model.js.coffee +112 -0
  10. data/lib/assets/javascripts/knockout/observables.js.coffee +1 -0
  11. data/lib/knockout-rails.rb +5 -0
  12. data/lib/knockout-rails/engine.rb +6 -0
  13. data/lib/knockout-rails/version.rb +3 -0
  14. data/lib/tasks/knockout-rails_tasks.rake +4 -0
  15. data/spec/dummy/Rakefile +7 -0
  16. data/spec/dummy/app/assets/javascripts/application.js +9 -0
  17. data/spec/dummy/app/assets/stylesheets/application.css +7 -0
  18. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  19. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  20. data/spec/dummy/app/mailers/.gitkeep +0 -0
  21. data/spec/dummy/app/models/.gitkeep +0 -0
  22. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  23. data/spec/dummy/config.ru +4 -0
  24. data/spec/dummy/config/application.rb +53 -0
  25. data/spec/dummy/config/boot.rb +10 -0
  26. data/spec/dummy/config/database.yml +25 -0
  27. data/spec/dummy/config/environment.rb +5 -0
  28. data/spec/dummy/config/environments/development.rb +30 -0
  29. data/spec/dummy/config/environments/production.rb +60 -0
  30. data/spec/dummy/config/environments/test.rb +39 -0
  31. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  32. data/spec/dummy/config/initializers/inflections.rb +10 -0
  33. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  34. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  35. data/spec/dummy/config/initializers/session_store.rb +8 -0
  36. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  37. data/spec/dummy/config/locales/en.yml +5 -0
  38. data/spec/dummy/config/routes.rb +58 -0
  39. data/spec/dummy/lib/assets/.gitkeep +0 -0
  40. data/spec/dummy/public/404.html +26 -0
  41. data/spec/dummy/public/422.html +26 -0
  42. data/spec/dummy/public/500.html +26 -0
  43. data/spec/dummy/public/favicon.ico +0 -0
  44. data/spec/dummy/script/rails +6 -0
  45. data/spec/javascripts/knockout/bindings_spec.js.coffee +2 -0
  46. data/spec/javascripts/knockout/model_spec.js.coffee +85 -0
  47. data/spec/javascripts/knockout/observables_spec.js.coffee +3 -0
  48. data/spec/javascripts/spec.css +3 -0
  49. data/spec/javascripts/spec.js.coffee +3 -0
  50. data/spec/javascripts/support/mock-ajax.js +207 -0
  51. data/spec/knockout_spec.rb +14 -0
  52. data/spec/spec_helper.rb +10 -0
  53. data/spec/support/global.rb +7 -0
  54. data/spec/support/matchers.rb +36 -0
  55. data/vendor/assets/javascripts/knockout.js +7 -0
  56. data/vendor/assets/javascripts/knockout/knockout.js +3153 -0
  57. data/vendor/assets/javascripts/knockout/knockout.mapping.js +676 -0
  58. data/vendor/assets/javascripts/knockout/sugar-1.1.1.js +5828 -0
  59. 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
+ })();