opalla 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,60 @@
1
+ module Opalla
2
+ class Controller < Component
3
+ attr_reader :el
4
+
5
+ def initialize(action=nil, params=nil, template: nil)
6
+ @bindings = {}
7
+ register_exposed_objects
8
+ super(template: "#{controller_name}/#{action}")
9
+ @el = Document.body
10
+ self.send(action)
11
+ end
12
+
13
+ def el
14
+ @el
15
+ end
16
+
17
+ def render
18
+ # target = Document.body.clone.html(template.render(self))
19
+ # el.morph(Element[target])
20
+ el.html(template.render(self))
21
+ bind_events
22
+ end
23
+
24
+ def el_selector
25
+ self.class.instance_variable_get :@el_selector
26
+ end
27
+
28
+ protected
29
+
30
+ def register_exposed_objects
31
+ Marshal.load($$.opalla_data)[:vars].each do |key, val|
32
+ define_singleton_method(key) { val }
33
+ end
34
+ end
35
+
36
+ def self.el(selector)
37
+ @el_selector = selector
38
+ end
39
+
40
+ def bind(binding)
41
+ @bindings.merge!(binding)
42
+ binding.each do |key, attrs|
43
+ model = send(key)
44
+ id = model.model_id
45
+ model.bind(*attrs, -> { render })
46
+ merge_events_hash({
47
+ %Q(input [data-model-id="#{id}"] [data-bind]) => -> target do
48
+ model.find(target)
49
+ attr = target.data('bind')
50
+ model.public_send(:"#{attr}=", target.value)
51
+ end
52
+ })
53
+ end
54
+ end
55
+
56
+ def controller_name
57
+ self.class.to_s.underscore.gsub('_controller', '')
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,1371 @@
1
+ (function() {
2
+ "use strict";
3
+
4
+ var diffcount;
5
+
6
+ var Diff = function(options) {
7
+ var diff = this;
8
+ if (options) {
9
+ Object.keys(options).forEach(function(option) {
10
+ diff[option] = options[option];
11
+ });
12
+ }
13
+
14
+ };
15
+
16
+ Diff.prototype = {
17
+ toString: function() {
18
+ return JSON.stringify(this);
19
+ },
20
+ setValue: function(aKey, aValue) {
21
+ this[aKey] = aValue;
22
+ return this;
23
+ }
24
+ };
25
+
26
+ var SubsetMapping = function SubsetMapping(a, b) {
27
+ this.oldValue = a;
28
+ this.newValue = b;
29
+ };
30
+
31
+ SubsetMapping.prototype = {
32
+ contains: function contains(subset) {
33
+ if (subset.length < this.length) {
34
+ return subset.newValue >= this.newValue && subset.newValue < this.newValue + this.length;
35
+ }
36
+ return false;
37
+ },
38
+ toString: function toString() {
39
+ return this.length + " element subset, first mapping: old " + this.oldValue + " → new " + this.newValue;
40
+ }
41
+ };
42
+
43
+ var elementDescriptors = function(el) {
44
+ var output = [];
45
+ if (el.nodeName !== '#text' && el.nodeName !== '#comment') {
46
+ output.push(el.nodeName);
47
+ if (el.attributes) {
48
+ if (el.attributes['class']) {
49
+ output.push(el.nodeName + '.' + el.attributes['class'].replace(/ /g, '.'));
50
+ }
51
+ if (el.attributes.id) {
52
+ output.push(el.nodeName + '#' + el.attributes.id);
53
+ }
54
+ }
55
+
56
+ }
57
+ return output;
58
+ };
59
+
60
+ var findUniqueDescriptors = function(li) {
61
+ var uniqueDescriptors = {},
62
+ duplicateDescriptors = {};
63
+
64
+ li.forEach(function(node) {
65
+ elementDescriptors(node).forEach(function(descriptor) {
66
+ var inUnique = descriptor in uniqueDescriptors,
67
+ inDupes = descriptor in duplicateDescriptors;
68
+ if (!inUnique && !inDupes) {
69
+ uniqueDescriptors[descriptor] = true;
70
+ } else if (inUnique) {
71
+ delete uniqueDescriptors[descriptor];
72
+ duplicateDescriptors[descriptor] = true;
73
+ }
74
+ });
75
+
76
+ });
77
+
78
+ return uniqueDescriptors;
79
+ };
80
+
81
+ var uniqueInBoth = function(l1, l2) {
82
+ var l1Unique = findUniqueDescriptors(l1),
83
+ l2Unique = findUniqueDescriptors(l2),
84
+ inBoth = {};
85
+
86
+ Object.keys(l1Unique).forEach(function(key) {
87
+ if (l2Unique[key]) {
88
+ inBoth[key] = true;
89
+ }
90
+ });
91
+
92
+ return inBoth;
93
+ };
94
+
95
+ var removeDone = function(tree) {
96
+ delete tree.outerDone;
97
+ delete tree.innerDone;
98
+ delete tree.valueDone;
99
+ if (tree.childNodes) {
100
+ return tree.childNodes.every(removeDone);
101
+ } else {
102
+ return true;
103
+ }
104
+ };
105
+
106
+ var isEqual = function(e1, e2) {
107
+
108
+ var e1Attributes, e2Attributes;
109
+
110
+ if (!['nodeName', 'value', 'checked', 'selected', 'data'].every(function(element) {
111
+ if (e1[element] !== e2[element]) {
112
+ return false;
113
+ }
114
+ return true;
115
+ })) {
116
+ return false;
117
+ }
118
+
119
+ if (Boolean(e1.attributes) !== Boolean(e2.attributes)) {
120
+ return false;
121
+ }
122
+
123
+ if (Boolean(e1.childNodes) !== Boolean(e2.childNodes)) {
124
+ return false;
125
+ }
126
+
127
+ if (e1.attributes) {
128
+ e1Attributes = Object.keys(e1.attributes);
129
+ e2Attributes = Object.keys(e2.attributes);
130
+
131
+ if (e1Attributes.length !== e2Attributes.length) {
132
+ return false;
133
+ }
134
+ if (!e1Attributes.every(function(attribute) {
135
+ if (e1.attributes[attribute] !== e2.attributes[attribute]) {
136
+ return false;
137
+ }
138
+ })) {
139
+ return false;
140
+ }
141
+ }
142
+
143
+ if (e1.childNodes) {
144
+ if (e1.childNodes.length !== e2.childNodes.length) {
145
+ return false;
146
+ }
147
+ if (!e1.childNodes.every(function(childNode, index) {
148
+ return isEqual(childNode, e2.childNodes[index]);
149
+ })) {
150
+
151
+ return false;
152
+ }
153
+
154
+ }
155
+
156
+ return true;
157
+
158
+ };
159
+
160
+
161
+ var roughlyEqual = function(e1, e2, uniqueDescriptors, sameSiblings, preventRecursion) {
162
+ var childUniqueDescriptors, nodeList1, nodeList2;
163
+
164
+ if (!e1 || !e2) {
165
+ return false;
166
+ }
167
+
168
+ if (e1.nodeName !== e2.nodeName) {
169
+ return false;
170
+ }
171
+
172
+ if (e1.nodeName === '#text') {
173
+ // Note that we initially don't care what the text content of a node is,
174
+ // the mere fact that it's the same tag and "has text" means it's roughly
175
+ // equal, and then we can find out the true text difference later.
176
+ return preventRecursion ? true : e1.data === e2.data;
177
+ }
178
+
179
+
180
+ if (e1.nodeName in uniqueDescriptors) {
181
+ return true;
182
+ }
183
+
184
+ if (e1.attributes && e2.attributes) {
185
+
186
+ if (e1.attributes.id && e1.attributes.id === e2.attributes.id) {
187
+ var idDescriptor = e1.nodeName + '#' + e1.attributes.id;
188
+ if (idDescriptor in uniqueDescriptors) {
189
+ return true;
190
+ }
191
+ }
192
+ if (e1.attributes['class'] && e1.attributes['class'] === e2.attributes['class']) {
193
+ var classDescriptor = e1.nodeName + '.' + e1.attributes['class'].replace(/ /g, '.');
194
+ if (classDescriptor in uniqueDescriptors) {
195
+ return true;
196
+ }
197
+ }
198
+ }
199
+
200
+ if (sameSiblings) {
201
+ return true;
202
+ }
203
+
204
+ nodeList1 = e1.childNodes ? e1.childNodes.slice().reverse() : [];
205
+ nodeList2 = e2.childNodes ? e2.childNodes.slice().reverse() : [];
206
+
207
+ if (nodeList1.length !== nodeList2.length) {
208
+ return false;
209
+ }
210
+
211
+ if (preventRecursion) {
212
+ return nodeList1.every(function(element, index) {
213
+ return element.nodeName === nodeList2[index].nodeName;
214
+ });
215
+ } else {
216
+ // note: we only allow one level of recursion at any depth. If 'preventRecursion'
217
+ // was not set, we must explicitly force it to true for child iterations.
218
+ childUniqueDescriptors = uniqueInBoth(nodeList1, nodeList2);
219
+ return nodeList1.every(function(element, index) {
220
+ return roughlyEqual(element, nodeList2[index], childUniqueDescriptors, true, true);
221
+ });
222
+ }
223
+ };
224
+
225
+
226
+ var cloneObj = function(obj) {
227
+ // TODO: Do we really need to clone here? Is it not enough to just return the original object?
228
+ return JSON.parse(JSON.stringify(obj));
229
+ //return obj;
230
+ };
231
+
232
+ /**
233
+ * based on https://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Longest_common_substring#JavaScript
234
+ */
235
+ var findCommonSubsets = function(c1, c2, marked1, marked2) {
236
+ var lcsSize = 0,
237
+ index = [],
238
+ matches = Array.apply(null, new Array(c1.length + 1)).map(function() {
239
+ return [];
240
+ }), // set up the matching table
241
+ uniqueDescriptors = uniqueInBoth(c1, c2),
242
+ // If all of the elements are the same tag, id and class, then we can
243
+ // consider them roughly the same even if they have a different number of
244
+ // children. This will reduce removing and re-adding similar elements.
245
+ subsetsSame = c1.length === c2.length,
246
+ origin, ret;
247
+
248
+ if (subsetsSame) {
249
+
250
+ c1.some(function(element, i) {
251
+ var c1Desc = elementDescriptors(element),
252
+ c2Desc = elementDescriptors(c2[i]);
253
+ if (c1Desc.length !== c2Desc.length) {
254
+ subsetsSame = false;
255
+ return true;
256
+ }
257
+ c1Desc.some(function(description, i) {
258
+ if (description !== c2Desc[i]) {
259
+ subsetsSame = false;
260
+ return true;
261
+ }
262
+ });
263
+ if (!subsetsSame) {
264
+ return true;
265
+ }
266
+
267
+ });
268
+ }
269
+
270
+ // fill the matches with distance values
271
+ c1.forEach(function(c1Element, c1Index) {
272
+ c2.forEach(function(c2Element, c2Index) {
273
+ if (!marked1[c1Index] && !marked2[c2Index] && roughlyEqual(c1Element, c2Element, uniqueDescriptors, subsetsSame)) {
274
+ matches[c1Index + 1][c2Index + 1] = (matches[c1Index][c2Index] ? matches[c1Index][c2Index] + 1 : 1);
275
+ if (matches[c1Index + 1][c2Index + 1] >= lcsSize) {
276
+ lcsSize = matches[c1Index + 1][c2Index + 1];
277
+ index = [c1Index + 1, c2Index + 1];
278
+ }
279
+ } else {
280
+ matches[c1Index + 1][c2Index + 1] = 0;
281
+ }
282
+ });
283
+ });
284
+ if (lcsSize === 0) {
285
+ return false;
286
+ }
287
+ origin = [index[0] - lcsSize, index[1] - lcsSize];
288
+ ret = new SubsetMapping(origin[0], origin[1]);
289
+ ret.length = lcsSize;
290
+
291
+ return ret;
292
+ };
293
+
294
+ /**
295
+ * This should really be a predefined function in Array...
296
+ */
297
+ var makeArray = function(n, v) {
298
+ return Array.apply(null, new Array(n)).map(function() {
299
+ return v;
300
+ });
301
+ };
302
+
303
+ /**
304
+ * Generate arrays that indicate which node belongs to which subset,
305
+ * or whether it's actually an orphan node, existing in only one
306
+ * of the two trees, rather than somewhere in both.
307
+ *
308
+ * So if t1 = <img><canvas><br>, t2 = <canvas><br><img>.
309
+ * The longest subset is "<canvas><br>" (length 2), so it will group 0.
310
+ * The second longest is "<img>" (length 1), so it will be group 1.
311
+ * gaps1 will therefore be [1,0,0] and gaps2 [0,0,1].
312
+ *
313
+ * If an element is not part of any group, it will stay being 'true', which
314
+ * is the initial value. For example:
315
+ * t1 = <img><p></p><br><canvas>, t2 = <b></b><br><canvas><img>
316
+ *
317
+ * The "<p></p>" and "<b></b>" do only show up in one of the two and will
318
+ * therefore be marked by "true". The remaining parts are parts of the
319
+ * groups 0 and 1:
320
+ * gaps1 = [1, true, 0, 0], gaps2 = [true, 0, 0, 1]
321
+ *
322
+ */
323
+ var getGapInformation = function(t1, t2, stable) {
324
+
325
+ var gaps1 = t1.childNodes ? makeArray(t1.childNodes.length, true) : [],
326
+ gaps2 = t2.childNodes ? makeArray(t2.childNodes.length, true) : [],
327
+ group = 0;
328
+
329
+ // give elements from the same subset the same group number
330
+ stable.forEach(function(subset) {
331
+ var i, endOld = subset.oldValue + subset.length,
332
+ endNew = subset.newValue + subset.length;
333
+ for (i = subset.oldValue; i < endOld; i += 1) {
334
+ gaps1[i] = group;
335
+ }
336
+ for (i = subset.newValue; i < endNew; i += 1) {
337
+ gaps2[i] = group;
338
+ }
339
+ group += 1;
340
+ });
341
+
342
+ return {
343
+ gaps1: gaps1,
344
+ gaps2: gaps2
345
+ };
346
+ };
347
+
348
+ /**
349
+ * Find all matching subsets, based on immediate child differences only.
350
+ */
351
+ var markSubTrees = function(oldTree, newTree) {
352
+ // note: the child lists are views, and so update as we update old/newTree
353
+ var oldChildren = oldTree.childNodes ? oldTree.childNodes : [],
354
+ newChildren = newTree.childNodes ? newTree.childNodes : [],
355
+ marked1 = makeArray(oldChildren.length, false),
356
+ marked2 = makeArray(newChildren.length, false),
357
+ subsets = [],
358
+ subset = true,
359
+ returnIndex = function() {
360
+ return arguments[1];
361
+ },
362
+ markBoth = function(i) {
363
+ marked1[subset.oldValue + i] = true;
364
+ marked2[subset.newValue + i] = true;
365
+ };
366
+
367
+ while (subset) {
368
+ subset = findCommonSubsets(oldChildren, newChildren, marked1, marked2);
369
+ if (subset) {
370
+ subsets.push(subset);
371
+
372
+ Array.apply(null, new Array(subset.length)).map(returnIndex).forEach(markBoth);
373
+
374
+ }
375
+ }
376
+ return subsets;
377
+ };
378
+
379
+
380
+ function swap(obj, p1, p2) {
381
+ (function(_) {
382
+ obj[p1] = obj[p2];
383
+ obj[p2] = _;
384
+ }(obj[p1]));
385
+ }
386
+
387
+
388
+ var DiffTracker = function() {
389
+ this.list = [];
390
+ };
391
+
392
+ DiffTracker.prototype = {
393
+ list: false,
394
+ add: function(diffs) {
395
+ var list = this.list;
396
+ diffs.forEach(function(diff) {
397
+ list.push(diff);
398
+ });
399
+ },
400
+ forEach: function(fn) {
401
+ this.list.forEach(fn);
402
+ }
403
+ };
404
+
405
+ var diffDOM = function(options) {
406
+
407
+ var defaults = {
408
+ debug: false,
409
+ diffcap: 10, // Limit for how many diffs are accepting when debugging. Inactive when debug is false.
410
+ maxDepth: false, // False or a numeral. If set to a numeral, limits the level of depth that the the diff mechanism looks for differences. If false, goes through the entire tree.
411
+ valueDiffing: true, // Whether to take into consideration the values of forms that differ from auto assigned values (when a user fills out a form).
412
+ // syntax: textDiff: function (node, currentValue, expectedValue, newValue)
413
+ textDiff: function() {
414
+ arguments[0].data = arguments[3];
415
+ return;
416
+ },
417
+ // empty functions were benchmarked as running faster than both
418
+ // `f && f()` and `if (f) { f(); }`
419
+ preVirtualDiffApply: function() {},
420
+ postVirtualDiffApply: function() {},
421
+ preDiffApply: function() {},
422
+ postDiffApply: function() {},
423
+ filterOuterDiff: null
424
+ },
425
+ i;
426
+
427
+ if (typeof options === "undefined") {
428
+ options = {};
429
+ }
430
+
431
+ for (i in defaults) {
432
+ if (typeof options[i] === "undefined") {
433
+ this[i] = defaults[i];
434
+ } else {
435
+ this[i] = options[i];
436
+ }
437
+ }
438
+
439
+ this._const = {
440
+ addAttribute: 0,
441
+ modifyAttribute: 1,
442
+ removeAttribute: 2,
443
+ modifyTextElement: 3,
444
+ relocateGroup: 4,
445
+ removeElement: 5,
446
+ addElement: 6,
447
+ removeTextElement: 7,
448
+ addTextElement: 8,
449
+ replaceElement: 9,
450
+ modifyValue: 10,
451
+ modifyChecked: 11,
452
+ modifySelected: 12,
453
+ modifyComment: 13,
454
+ action: 'a',
455
+ route: 'r',
456
+ oldValue: 'o',
457
+ newValue: 'n',
458
+ element: 'e',
459
+ 'group': 'g',
460
+ from: 'f',
461
+ to: 't',
462
+ name: 'na',
463
+ value: 'v',
464
+ 'data': 'd',
465
+ 'attributes': 'at',
466
+ 'nodeName': 'nn',
467
+ 'childNodes': 'c',
468
+ 'checked': 'ch',
469
+ 'selected': 's'
470
+ };
471
+ };
472
+
473
+ diffDOM.Diff = Diff;
474
+
475
+ diffDOM.prototype = {
476
+
477
+ // ===== Create a diff =====
478
+
479
+ diff: function(t1Node, t2Node) {
480
+
481
+ var t1 = this.nodeToObj(t1Node),
482
+ t2 = this.nodeToObj(t2Node);
483
+
484
+ diffcount = 0;
485
+
486
+ if (this.debug) {
487
+ this.t1Orig = this.nodeToObj(t1Node);
488
+ this.t2Orig = this.nodeToObj(t2Node);
489
+ }
490
+
491
+ this.tracker = new DiffTracker();
492
+ return this.findDiffs(t1, t2);
493
+ },
494
+ findDiffs: function(t1, t2) {
495
+ var diffs;
496
+ do {
497
+ if (this.debug) {
498
+ diffcount += 1;
499
+ if (diffcount > this.diffcap) {
500
+ window.diffError = [this.t1Orig, this.t2Orig];
501
+ throw new Error("surpassed diffcap:" + JSON.stringify(this.t1Orig) + " -> " + JSON.stringify(this.t2Orig));
502
+ }
503
+ }
504
+ diffs = this.findNextDiff(t1, t2, []);
505
+ if (diffs.length === 0) {
506
+ // Last check if the elements really are the same now.
507
+ // If not, remove all info about being done and start over.
508
+ // Somtimes a node can be marked as done, but the creation of subsequent diffs means that it has to be changed anyway.
509
+ if (!isEqual(t1, t2)) {
510
+ removeDone(t1);
511
+ diffs = this.findNextDiff(t1, t2, []);
512
+ }
513
+ }
514
+
515
+ if (diffs.length > 0) {
516
+ this.tracker.add(diffs);
517
+ this.applyVirtual(t1, diffs);
518
+ }
519
+ } while (diffs.length > 0);
520
+ return this.tracker.list;
521
+ },
522
+ findNextDiff: function(t1, t2, route) {
523
+ var diffs, fdiffs;
524
+
525
+ if (this.maxDepth && route.length > this.maxDepth) {
526
+ return [];
527
+ }
528
+ // outer differences?
529
+ if (!t1.outerDone) {
530
+ diffs = this.findOuterDiff(t1, t2, route);
531
+ if (this.filterOuterDiff) {
532
+ fdiffs = this.filterOuterDiff(t1, t2, diffs);
533
+ if (fdiffs) diffs = fdiffs;
534
+ }
535
+ if (diffs.length > 0) {
536
+ t1.outerDone = true;
537
+ return diffs;
538
+ } else {
539
+ t1.outerDone = true;
540
+ }
541
+ }
542
+ // inner differences?
543
+ if (!t1.innerDone) {
544
+ diffs = this.findInnerDiff(t1, t2, route);
545
+ if (diffs.length > 0) {
546
+ return diffs;
547
+ } else {
548
+ t1.innerDone = true;
549
+ }
550
+ }
551
+
552
+ if (this.valueDiffing && !t1.valueDone) {
553
+ // value differences?
554
+ diffs = this.findValueDiff(t1, t2, route);
555
+
556
+ if (diffs.length > 0) {
557
+ t1.valueDone = true;
558
+ return diffs;
559
+ } else {
560
+ t1.valueDone = true;
561
+ }
562
+ }
563
+
564
+ // no differences
565
+ return [];
566
+ },
567
+ findOuterDiff: function(t1, t2, route) {
568
+ var t = this;
569
+ var diffs = [],
570
+ attr1, attr2;
571
+
572
+ if (t1.nodeName !== t2.nodeName) {
573
+ return [new Diff()
574
+ .setValue(t._const.action, t._const.replaceElement)
575
+ .setValue(t._const.oldValue, cloneObj(t1))
576
+ .setValue(t._const.newValue, cloneObj(t2))
577
+ .setValue(t._const.route, route)
578
+ ];
579
+ }
580
+
581
+ if (t1.data !== t2.data) {
582
+ // Comment or text node.
583
+ if (t1.nodeName === '#text') {
584
+ return [new Diff()
585
+ .setValue(t._const.action, t._const.modifyTextElement)
586
+ .setValue(t._const.route, route)
587
+ .setValue(t._const.oldValue, t1.data)
588
+ .setValue(t._const.newValue, t2.data)
589
+ ];
590
+ } else {
591
+ return [new Diff()
592
+ .setValue(t._const.action, t._const.modifyComment)
593
+ .setValue(t._const.route, route)
594
+ .setValue(t._const.oldValue, t1.data)
595
+ .setValue(t._const.newValue, t2.data)
596
+ ];
597
+ }
598
+
599
+ }
600
+
601
+
602
+ attr1 = t1.attributes ? Object.keys(t1.attributes).sort() : [];
603
+ attr2 = t2.attributes ? Object.keys(t2.attributes).sort() : [];
604
+
605
+ attr1.forEach(function(attr) {
606
+ var pos = attr2.indexOf(attr);
607
+ if (pos === -1) {
608
+ diffs.push(new Diff()
609
+ .setValue(t._const.action, t._const.removeAttribute)
610
+ .setValue(t._const.route, route)
611
+ .setValue(t._const.name, attr)
612
+ .setValue(t._const.value, t1.attributes[attr])
613
+ );
614
+ } else {
615
+ attr2.splice(pos, 1);
616
+ if (t1.attributes[attr] !== t2.attributes[attr]) {
617
+ diffs.push(new Diff()
618
+ .setValue(t._const.action, t._const.modifyAttribute)
619
+ .setValue(t._const.route, route)
620
+ .setValue(t._const.name, attr)
621
+ .setValue(t._const.oldValue, t1.attributes[attr])
622
+ .setValue(t._const.newValue, t2.attributes[attr])
623
+ );
624
+ }
625
+ }
626
+
627
+ });
628
+
629
+
630
+ attr2.forEach(function(attr) {
631
+ diffs.push(new Diff()
632
+ .setValue(t._const.action, t._const.addAttribute)
633
+ .setValue(t._const.route, route)
634
+ .setValue(t._const.name, attr)
635
+ .setValue(t._const.value, t2.attributes[attr])
636
+ );
637
+
638
+ });
639
+
640
+ return diffs;
641
+ },
642
+ nodeToObj: function(aNode) {
643
+ var objNode = {},
644
+ dobj = this;
645
+ objNode.nodeName = aNode.nodeName;
646
+ if (objNode.nodeName === '#text' || objNode.nodeName === '#comment') {
647
+ objNode.data = aNode.data;
648
+ } else {
649
+ if (aNode.attributes && aNode.attributes.length > 0) {
650
+ objNode.attributes = {};
651
+ Array.prototype.slice.call(aNode.attributes).forEach(
652
+ function(attribute) {
653
+ objNode.attributes[attribute.name] = attribute.value;
654
+ }
655
+ );
656
+ }
657
+ if (aNode.childNodes && aNode.childNodes.length > 0) {
658
+ objNode.childNodes = [];
659
+ Array.prototype.slice.call(aNode.childNodes).forEach(
660
+ function(childNode) {
661
+ objNode.childNodes.push(dobj.nodeToObj(childNode));
662
+ }
663
+ );
664
+ }
665
+ if (this.valueDiffing) {
666
+ if (aNode.value !== undefined) {
667
+ objNode.value = aNode.value;
668
+ }
669
+ if (aNode.checked !== undefined) {
670
+ objNode.checked = aNode.checked;
671
+ }
672
+ if (aNode.selected !== undefined) {
673
+ objNode.selected = aNode.selected;
674
+ }
675
+ }
676
+ }
677
+
678
+ return objNode;
679
+ },
680
+ objToNode: function(objNode, insideSvg) {
681
+ var node, dobj = this;
682
+ if (objNode.nodeName === '#text') {
683
+ node = document.createTextNode(objNode.data);
684
+
685
+ } else if (objNode.nodeName === '#comment') {
686
+ node = document.createComment(objNode.data);
687
+ } else {
688
+ if (objNode.nodeName === 'svg' || insideSvg) {
689
+ node = document.createElementNS('http://www.w3.org/2000/svg', objNode.nodeName);
690
+ insideSvg = true;
691
+ } else {
692
+ node = document.createElement(objNode.nodeName);
693
+ }
694
+ if (objNode.attributes) {
695
+ Object.keys(objNode.attributes).forEach(function(attribute) {
696
+ node.setAttribute(attribute, objNode.attributes[attribute]);
697
+ });
698
+ }
699
+ if (objNode.childNodes) {
700
+ objNode.childNodes.forEach(function(childNode) {
701
+ node.appendChild(dobj.objToNode(childNode, insideSvg));
702
+ });
703
+ }
704
+ if (this.valueDiffing) {
705
+ if (objNode.value) {
706
+ node.value = objNode.value;
707
+ }
708
+ if (objNode.checked) {
709
+ node.checked = objNode.checked;
710
+ }
711
+ if (objNode.selected) {
712
+ node.selected = objNode.selected;
713
+ }
714
+ }
715
+ }
716
+ return node;
717
+ },
718
+ findInnerDiff: function(t1, t2, route) {
719
+ var t = this;
720
+ var subtrees = (t1.childNodes && t2.childNodes) ? markSubTrees(t1, t2) : [],
721
+ t1ChildNodes = t1.childNodes ? t1.childNodes : [],
722
+ t2ChildNodes = t2.childNodes ? t2.childNodes : [],
723
+ childNodesLengthDifference, diffs = [],
724
+ index = 0,
725
+ last, e1, e2, i;
726
+
727
+ if (subtrees.length > 0) {
728
+ /* One or more groups have been identified among the childnodes of t1
729
+ * and t2.
730
+ */
731
+ diffs = this.attemptGroupRelocation(t1, t2, subtrees, route);
732
+ if (diffs.length > 0) {
733
+ return diffs;
734
+ }
735
+ }
736
+
737
+ /* 0 or 1 groups of similar child nodes have been found
738
+ * for t1 and t2. 1 If there is 1, it could be a sign that the
739
+ * contents are the same. When the number of groups is below 2,
740
+ * t1 and t2 are made to have the same length and each of the
741
+ * pairs of child nodes are diffed.
742
+ */
743
+
744
+
745
+ last = Math.max(t1ChildNodes.length, t2ChildNodes.length);
746
+ if (t1ChildNodes.length !== t2ChildNodes.length) {
747
+ childNodesLengthDifference = true;
748
+ }
749
+
750
+ for (i = 0; i < last; i += 1) {
751
+ e1 = t1ChildNodes[i];
752
+ e2 = t2ChildNodes[i];
753
+
754
+ if (childNodesLengthDifference) {
755
+ /* t1 and t2 have different amounts of childNodes. Add
756
+ * and remove as necessary to obtain the same length */
757
+ if (e1 && !e2) {
758
+ if (e1.nodeName === '#text') {
759
+ diffs.push(new Diff()
760
+ .setValue(t._const.action, t._const.removeTextElement)
761
+ .setValue(t._const.route, route.concat(index))
762
+ .setValue(t._const.value, e1.data)
763
+ );
764
+ index -= 1;
765
+ } else {
766
+ diffs.push(new Diff()
767
+ .setValue(t._const.action, t._const.removeElement)
768
+ .setValue(t._const.route, route.concat(index))
769
+ .setValue(t._const.element, cloneObj(e1))
770
+ );
771
+ index -= 1;
772
+ }
773
+
774
+ } else if (e2 && !e1) {
775
+ if (e2.nodeName === '#text') {
776
+ diffs.push(new Diff()
777
+ .setValue(t._const.action, t._const.addTextElement)
778
+ .setValue(t._const.route, route.concat(index))
779
+ .setValue(t._const.value, e2.data)
780
+ );
781
+ } else {
782
+ diffs.push(new Diff()
783
+ .setValue(t._const.action, t._const.addElement)
784
+ .setValue(t._const.route, route.concat(index))
785
+ .setValue(t._const.element, cloneObj(e2))
786
+ );
787
+ }
788
+ }
789
+ }
790
+ /* We are now guaranteed that childNodes e1 and e2 exist,
791
+ * and that they can be diffed.
792
+ */
793
+ /* Diffs in child nodes should not affect the parent node,
794
+ * so we let these diffs be submitted together with other
795
+ * diffs.
796
+ */
797
+
798
+ if (e1 && e2) {
799
+ diffs = diffs.concat(this.findNextDiff(e1, e2, route.concat(index)));
800
+ }
801
+
802
+ index += 1;
803
+
804
+ }
805
+ t1.innerDone = true;
806
+ return diffs;
807
+
808
+ },
809
+
810
+ attemptGroupRelocation: function(t1, t2, subtrees, route) {
811
+ /* Either t1.childNodes and t2.childNodes have the same length, or
812
+ * there are at least two groups of similar elements can be found.
813
+ * attempts are made at equalizing t1 with t2. First all initial
814
+ * elements with no group affiliation (gaps=true) are removed (if
815
+ * only in t1) or added (if only in t2). Then the creation of a group
816
+ * relocation diff is attempted.
817
+ */
818
+ var t = this;
819
+ var gapInformation = getGapInformation(t1, t2, subtrees),
820
+ gaps1 = gapInformation.gaps1,
821
+ gaps2 = gapInformation.gaps2,
822
+ shortest = Math.min(gaps1.length, gaps2.length),
823
+ destinationDifferent, toGroup,
824
+ group, node, similarNode, testI, diffs = [],
825
+ index1, index2, j;
826
+
827
+
828
+ for (index2 = 0, index1 = 0; index2 < shortest; index1 += 1, index2 += 1) {
829
+ if (gaps1[index2] === true) {
830
+ node = t1.childNodes[index1];
831
+ if (node.nodeName === '#text') {
832
+ if (t2.childNodes[index2].nodeName === '#text' && node.data !== t2.childNodes[index2].data) {
833
+ testI = index1;
834
+ while (t1.childNodes.length > testI + 1 && t1.childNodes[testI + 1].nodeName === '#text') {
835
+ testI += 1;
836
+ if (t2.childNodes[index2].data === t1.childNodes[testI].data) {
837
+ similarNode = true;
838
+ break;
839
+ }
840
+ }
841
+ if (!similarNode) {
842
+ diffs.push(new Diff()
843
+ .setValue(t._const.action, t._const.modifyTextElement)
844
+ .setValue(t._const.route, route.concat(index2))
845
+ .setValue(t._const.oldValue, node.data)
846
+ .setValue(t._const.newValue, t2.childNodes[index2].data)
847
+ );
848
+ return diffs;
849
+ }
850
+ }
851
+ diffs.push(new Diff()
852
+ .setValue(t._const.action, t._const.removeTextElement)
853
+ .setValue(t._const.route, route.concat(index2))
854
+ .setValue(t._const.value, node.data)
855
+ );
856
+ gaps1.splice(index2, 1);
857
+ shortest = Math.min(gaps1.length, gaps2.length);
858
+ index2 -= 1;
859
+ } else {
860
+ diffs.push(new Diff()
861
+ .setValue(t._const.action, t._const.removeElement)
862
+ .setValue(t._const.route, route.concat(index2))
863
+ .setValue(t._const.element, cloneObj(node))
864
+ );
865
+ gaps1.splice(index2, 1);
866
+ shortest = Math.min(gaps1.length, gaps2.length);
867
+ index2 -= 1;
868
+ }
869
+
870
+ } else if (gaps2[index2] === true) {
871
+ node = t2.childNodes[index2];
872
+ if (node.nodeName === '#text') {
873
+ diffs.push(new Diff()
874
+ .setValue(t._const.action, t._const.addTextElement)
875
+ .setValue(t._const.route, route.concat(index2))
876
+ .setValue(t._const.value, node.data)
877
+ );
878
+ gaps1.splice(index2, 0, true);
879
+ shortest = Math.min(gaps1.length, gaps2.length);
880
+ index1 -= 1;
881
+ } else {
882
+ diffs.push(new Diff()
883
+ .setValue(t._const.action, t._const.addElement)
884
+ .setValue(t._const.route, route.concat(index2))
885
+ .setValue(t._const.element, cloneObj(node))
886
+ );
887
+ gaps1.splice(index2, 0, true);
888
+ shortest = Math.min(gaps1.length, gaps2.length);
889
+ index1 -= 1;
890
+ }
891
+
892
+ } else if (gaps1[index2] !== gaps2[index2]) {
893
+ if (diffs.length > 0) {
894
+ return diffs;
895
+ }
896
+ // group relocation
897
+ group = subtrees[gaps1[index2]];
898
+ toGroup = Math.min(group.newValue, (t1.childNodes.length - group.length));
899
+ if (toGroup !== group.oldValue) {
900
+ // Check whether destination nodes are different than originating ones.
901
+ destinationDifferent = false;
902
+ for (j = 0; j < group.length; j += 1) {
903
+ if (!roughlyEqual(t1.childNodes[toGroup + j], t1.childNodes[group.oldValue + j], [], false, true)) {
904
+ destinationDifferent = true;
905
+ }
906
+ }
907
+ if (destinationDifferent) {
908
+ return [new Diff()
909
+ .setValue(t._const.action, t._const.relocateGroup)
910
+ .setValue('groupLength', group.length)
911
+ .setValue(t._const.from, group.oldValue)
912
+ .setValue(t._const.to, toGroup)
913
+ .setValue(t._const.route, route)
914
+ ];
915
+ }
916
+ }
917
+ }
918
+ }
919
+ return diffs;
920
+ },
921
+
922
+ findValueDiff: function(t1, t2, route) {
923
+ // Differences of value. Only useful if the value/selection/checked value
924
+ // differs from what is represented in the DOM. For example in the case
925
+ // of filled out forms, etc.
926
+ var diffs = [];
927
+ var t = this;
928
+
929
+ if (t1.selected !== t2.selected) {
930
+ diffs.push(new Diff()
931
+ .setValue(t._const.action, t._const.modifySelected)
932
+ .setValue(t._const.oldValue, t1.selected)
933
+ .setValue(t._const.newValue, t2.selected)
934
+ .setValue(t._const.route, route)
935
+ );
936
+ }
937
+
938
+ if ((t1.value || t2.value) && t1.value !== t2.value && t1.nodeName !== 'OPTION') {
939
+ diffs.push(new Diff()
940
+ .setValue(t._const.action, t._const.modifyValue)
941
+ .setValue(t._const.oldValue, t1.value)
942
+ .setValue(t._const.newValue, t2.value)
943
+ .setValue(t._const.route, route)
944
+ );
945
+ }
946
+ if (t1.checked !== t2.checked) {
947
+ diffs.push(new Diff()
948
+ .setValue(t._const.action, t._const.modifyChecked)
949
+ .setValue(t._const.oldValue, t1.checked)
950
+ .setValue(t._const.newValue, t2.checked)
951
+ .setValue(t._const.route, route)
952
+ );
953
+ }
954
+
955
+ return diffs;
956
+ },
957
+
958
+ // ===== Apply a virtual diff =====
959
+
960
+ applyVirtual: function(tree, diffs) {
961
+ var dobj = this;
962
+ if (diffs.length === 0) {
963
+ return true;
964
+ }
965
+ diffs.forEach(function(diff) {
966
+ dobj.applyVirtualDiff(tree, diff);
967
+ });
968
+ return true;
969
+ },
970
+ getFromVirtualRoute: function(tree, route) {
971
+ var node = tree,
972
+ parentNode, nodeIndex;
973
+
974
+ route = route.slice();
975
+ while (route.length > 0) {
976
+ if (!node.childNodes) {
977
+ return false;
978
+ }
979
+ nodeIndex = route.splice(0, 1)[0];
980
+ parentNode = node;
981
+ node = node.childNodes[nodeIndex];
982
+ }
983
+ return {
984
+ node: node,
985
+ parentNode: parentNode,
986
+ nodeIndex: nodeIndex
987
+ };
988
+ },
989
+ applyVirtualDiff: function(tree, diff) {
990
+ var routeInfo = this.getFromVirtualRoute(tree, diff[this._const.route]),
991
+ node = routeInfo.node,
992
+ parentNode = routeInfo.parentNode,
993
+ nodeIndex = routeInfo.nodeIndex,
994
+ newNode, route, c;
995
+
996
+ var t = this;
997
+ // pre-diff hook
998
+ var info = {
999
+ diff: diff,
1000
+ node: node
1001
+ };
1002
+
1003
+ if (this.preVirtualDiffApply(info)) {
1004
+ return true;
1005
+ }
1006
+
1007
+ switch (diff[this._const.action]) {
1008
+ case this._const.addAttribute:
1009
+ if (!node.attributes) {
1010
+ node.attributes = {};
1011
+ }
1012
+
1013
+ node.attributes[diff[this._const.name]] = diff[this._const.value];
1014
+
1015
+ if (diff[this._const.name] === 'checked') {
1016
+ node.checked = true;
1017
+ } else if (diff[this._const.name] === 'selected') {
1018
+ node.selected = true;
1019
+ } else if (node.nodeName === 'INPUT' && diff[this._const.name] === 'value') {
1020
+ node.value = diff[this._const.value];
1021
+ }
1022
+
1023
+ break;
1024
+ case this._const.modifyAttribute:
1025
+ node.attributes[diff[this._const.name]] = diff[this._const.newValue];
1026
+ if (node.nodeName === 'INPUT' && diff[this._const.name] === 'value') {
1027
+ node.value = diff[this._const.value];
1028
+ }
1029
+ break;
1030
+ case this._const.removeAttribute:
1031
+
1032
+ delete node.attributes[diff[this._const.name]];
1033
+
1034
+ if (Object.keys(node.attributes).length === 0) {
1035
+ delete node.attributes;
1036
+ }
1037
+
1038
+ if (diff[this._const.name] === 'checked') {
1039
+ node.checked = false;
1040
+ } else if (diff[this._const.name] === 'selected') {
1041
+ delete node.selected;
1042
+ } else if (node.nodeName === 'INPUT' && diff[this._const.name] === 'value') {
1043
+ delete node.value;
1044
+ }
1045
+
1046
+ break;
1047
+ case this._const.modifyTextElement:
1048
+ node.data = diff[this._const.newValue];
1049
+
1050
+ if (parentNode.nodeName === 'TEXTAREA') {
1051
+ parentNode.value = diff[this._const.newValue];
1052
+ }
1053
+ break;
1054
+ case this._const.modifyValue:
1055
+ node.value = diff[this._const.newValue];
1056
+ break;
1057
+ case this._const.modifyComment:
1058
+ node.data = diff[this._const.newValue];
1059
+ break;
1060
+ case this._const.modifyChecked:
1061
+ node.checked = diff[this._const.newValue];
1062
+ break;
1063
+ case this._const.modifySelected:
1064
+ node.selected = diff[this._const.newValue];
1065
+ break;
1066
+ case this._const.replaceElement:
1067
+ newNode = cloneObj(diff[this._const.newValue]);
1068
+ newNode.outerDone = true;
1069
+ newNode.innerDone = true;
1070
+ newNode.valueDone = true;
1071
+ parentNode.childNodes[nodeIndex] = newNode;
1072
+ break;
1073
+ case this._const.relocateGroup:
1074
+ node.childNodes.splice(diff[this._const.from], diff.groupLength).reverse()
1075
+ .forEach(function(movedNode) {
1076
+ node.childNodes.splice(diff[t._const.to], 0, movedNode);
1077
+ });
1078
+ break;
1079
+ case this._const.removeElement:
1080
+ parentNode.childNodes.splice(nodeIndex, 1);
1081
+ break;
1082
+ case this._const.addElement:
1083
+ route = diff[this._const.route].slice();
1084
+ c = route.splice(route.length - 1, 1)[0];
1085
+ node = this.getFromVirtualRoute(tree, route).node;
1086
+ newNode = cloneObj(diff[this._const.element]);
1087
+ newNode.outerDone = true;
1088
+ newNode.innerDone = true;
1089
+ newNode.valueDone = true;
1090
+
1091
+ if (!node.childNodes) {
1092
+ node.childNodes = [];
1093
+ }
1094
+
1095
+ if (c >= node.childNodes.length) {
1096
+ node.childNodes.push(newNode);
1097
+ } else {
1098
+ node.childNodes.splice(c, 0, newNode);
1099
+ }
1100
+ break;
1101
+ case this._const.removeTextElement:
1102
+ parentNode.childNodes.splice(nodeIndex, 1);
1103
+ if (parentNode.nodeName === 'TEXTAREA') {
1104
+ delete parentNode.value;
1105
+ }
1106
+ break;
1107
+ case this._const.addTextElement:
1108
+ route = diff[this._const.route].slice();
1109
+ c = route.splice(route.length - 1, 1)[0];
1110
+ newNode = {};
1111
+ newNode.nodeName = '#text';
1112
+ newNode.data = diff[this._const.value];
1113
+ node = this.getFromVirtualRoute(tree, route).node;
1114
+ if (!node.childNodes) {
1115
+ node.childNodes = [];
1116
+ }
1117
+
1118
+ if (c >= node.childNodes.length) {
1119
+ node.childNodes.push(newNode);
1120
+ } else {
1121
+ node.childNodes.splice(c, 0, newNode);
1122
+ }
1123
+ if (node.nodeName === 'TEXTAREA') {
1124
+ node.value = diff[this._const.newValue];
1125
+ }
1126
+ break;
1127
+ default:
1128
+ console.log('unknown action');
1129
+ }
1130
+
1131
+ // capture newNode for the callback
1132
+ info.newNode = newNode;
1133
+ this.postVirtualDiffApply(info);
1134
+
1135
+ return;
1136
+ },
1137
+
1138
+
1139
+
1140
+
1141
+ // ===== Apply a diff =====
1142
+
1143
+ apply: function(tree, diffs) {
1144
+ var dobj = this;
1145
+
1146
+ if (diffs.length === 0) {
1147
+ return true;
1148
+ }
1149
+ diffs.forEach(function(diff) {
1150
+ if (!dobj.applyDiff(tree, diff)) {
1151
+ return false;
1152
+ }
1153
+ });
1154
+ return true;
1155
+ },
1156
+ getFromRoute: function(tree, route) {
1157
+ route = route.slice();
1158
+ var c, node = tree;
1159
+ while (route.length > 0) {
1160
+ if (!node.childNodes) {
1161
+ return false;
1162
+ }
1163
+ c = route.splice(0, 1)[0];
1164
+ node = node.childNodes[c];
1165
+ }
1166
+ return node;
1167
+ },
1168
+ applyDiff: function(tree, diff) {
1169
+ var node = this.getFromRoute(tree, diff[this._const.route]),
1170
+ newNode, reference, route, c;
1171
+
1172
+ var t = this;
1173
+ // pre-diff hook
1174
+ var info = {
1175
+ diff: diff,
1176
+ node: node
1177
+ };
1178
+
1179
+ if (this.preDiffApply(info)) {
1180
+ return true;
1181
+ }
1182
+
1183
+ switch (diff[this._const.action]) {
1184
+ case this._const.addAttribute:
1185
+ if (!node || !node.setAttribute) {
1186
+ return false;
1187
+ }
1188
+ node.setAttribute(diff[this._const.name], diff[this._const.value]);
1189
+ break;
1190
+ case this._const.modifyAttribute:
1191
+ if (!node || !node.setAttribute) {
1192
+ return false;
1193
+ }
1194
+ node.setAttribute(diff[this._const.name], diff[this._const.newValue]);
1195
+ break;
1196
+ case this._const.removeAttribute:
1197
+ if (!node || !node.removeAttribute) {
1198
+ return false;
1199
+ }
1200
+ node.removeAttribute(diff[this._const.name]);
1201
+ break;
1202
+ case this._const.modifyTextElement:
1203
+ if (!node || node.nodeType !== 3) {
1204
+ return false;
1205
+ }
1206
+ this.textDiff(node, node.data, diff[this._const.oldValue], diff[this._const.newValue]);
1207
+ break;
1208
+ case this._const.modifyValue:
1209
+ if (!node || typeof node.value === 'undefined') {
1210
+ return false;
1211
+ }
1212
+ node.value = diff[this._const.newValue];
1213
+ break;
1214
+ case this._const.modifyComment:
1215
+ if (!node || typeof node.data === 'undefined') {
1216
+ return false;
1217
+ }
1218
+ this.textDiff(node, node.data, diff[this._const.oldValue], diff[this._const.newValue]);
1219
+ break;
1220
+ case this._const.modifyChecked:
1221
+ if (!node || typeof node.checked === 'undefined') {
1222
+ return false;
1223
+ }
1224
+ node.checked = diff[this._const.newValue];
1225
+ break;
1226
+ case this._const.modifySelected:
1227
+ if (!node || typeof node.selected === 'undefined') {
1228
+ return false;
1229
+ }
1230
+ node.selected = diff[this._const.newValue];
1231
+ break;
1232
+ case this._const.replaceElement:
1233
+ node.parentNode.replaceChild(this.objToNode(diff[this._const.newValue], node.namespaceURI === 'http://www.w3.org/2000/svg'), node);
1234
+ break;
1235
+ case this._const.relocateGroup:
1236
+ Array.apply(null, new Array(diff.groupLength)).map(function() {
1237
+ return node.removeChild(node.childNodes[diff[t._const.from]]);
1238
+ }).forEach(function(childNode, index) {
1239
+ if (index === 0) {
1240
+ reference = node.childNodes[diff[t._const.to]];
1241
+ }
1242
+ node.insertBefore(childNode, reference);
1243
+ });
1244
+ break;
1245
+ case this._const.removeElement:
1246
+ node.parentNode.removeChild(node);
1247
+ break;
1248
+ case this._const.addElement:
1249
+ route = diff[this._const.route].slice();
1250
+ c = route.splice(route.length - 1, 1)[0];
1251
+ node = this.getFromRoute(tree, route);
1252
+ node.insertBefore(this.objToNode(diff[this._const.element], node.namespaceURI === 'http://www.w3.org/2000/svg'), node.childNodes[c]);
1253
+ break;
1254
+ case this._const.removeTextElement:
1255
+ if (!node || node.nodeType !== 3) {
1256
+ return false;
1257
+ }
1258
+ node.parentNode.removeChild(node);
1259
+ break;
1260
+ case this._const.addTextElement:
1261
+ route = diff[this._const.route].slice();
1262
+ c = route.splice(route.length - 1, 1)[0];
1263
+ newNode = document.createTextNode(diff[this._const.value]);
1264
+ node = this.getFromRoute(tree, route);
1265
+ if (!node || !node.childNodes) {
1266
+ return false;
1267
+ }
1268
+ node.insertBefore(newNode, node.childNodes[c]);
1269
+ break;
1270
+ default:
1271
+ console.log('unknown action');
1272
+ }
1273
+
1274
+ // if a new node was created, we might be interested in it
1275
+ // post diff hook
1276
+ info.newNode = newNode;
1277
+ this.postDiffApply(info);
1278
+
1279
+ return true;
1280
+ },
1281
+
1282
+ // ===== Undo a diff =====
1283
+
1284
+ undo: function(tree, diffs) {
1285
+ diffs = diffs.slice();
1286
+ var dobj = this;
1287
+ if (!diffs.length) {
1288
+ diffs = [diffs];
1289
+ }
1290
+ diffs.reverse();
1291
+ diffs.forEach(function(diff) {
1292
+ dobj.undoDiff(tree, diff);
1293
+ });
1294
+ },
1295
+ undoDiff: function(tree, diff) {
1296
+
1297
+ switch (diff[this._const.action]) {
1298
+ case this._const.addAttribute:
1299
+ diff[this._const.action] = this._const.removeAttribute;
1300
+ this.applyDiff(tree, diff);
1301
+ break;
1302
+ case this._const.modifyAttribute:
1303
+ swap(diff, this._const.oldValue, this._const.newValue);
1304
+ this.applyDiff(tree, diff);
1305
+ break;
1306
+ case this._const.removeAttribute:
1307
+ diff[this._const.action] = this._const.addAttribute;
1308
+ this.applyDiff(tree, diff);
1309
+ break;
1310
+ case this._const.modifyTextElement:
1311
+ swap(diff, this._const.oldValue, this._const.newValue);
1312
+ this.applyDiff(tree, diff);
1313
+ break;
1314
+ case this._const.modifyValue:
1315
+ swap(diff, this._const.oldValue, this._const.newValue);
1316
+ this.applyDiff(tree, diff);
1317
+ break;
1318
+ case this._const.modifyComment:
1319
+ swap(diff, this._const.oldValue, this._const.newValue);
1320
+ this.applyDiff(tree, diff);
1321
+ break;
1322
+ case this._const.modifyChecked:
1323
+ swap(diff, this._const.oldValue, this._const.newValue);
1324
+ this.applyDiff(tree, diff);
1325
+ break;
1326
+ case this._const.modifySelected:
1327
+ swap(diff, this._const.oldValue, this._const.newValue);
1328
+ this.applyDiff(tree, diff);
1329
+ break;
1330
+ case this._const.replaceElement:
1331
+ swap(diff, this._const.oldValue, this._const.newValue);
1332
+ this.applyDiff(tree, diff);
1333
+ break;
1334
+ case this._const.relocateGroup:
1335
+ swap(diff, this._const.from, this._const.to);
1336
+ this.applyDiff(tree, diff);
1337
+ break;
1338
+ case this._const.removeElement:
1339
+ diff[this._const.action] = this._const.addElement;
1340
+ this.applyDiff(tree, diff);
1341
+ break;
1342
+ case this._const.addElement:
1343
+ diff[this._const.action] = this._const.removeElement;
1344
+ this.applyDiff(tree, diff);
1345
+ break;
1346
+ case this._const.removeTextElement:
1347
+ diff[this._const.action] = this._const.addTextElement;
1348
+ this.applyDiff(tree, diff);
1349
+ break;
1350
+ case this._const.addTextElement:
1351
+ diff[this._const.action] = this._const.removeTextElement;
1352
+ this.applyDiff(tree, diff);
1353
+ break;
1354
+ default:
1355
+ console.log('unknown action');
1356
+ }
1357
+
1358
+ }
1359
+ };
1360
+
1361
+ if (typeof exports !== 'undefined') {
1362
+ if (typeof module !== 'undefined' && module.exports) {
1363
+ exports = module.exports = diffDOM;
1364
+ }
1365
+ exports.diffDOM = diffDOM;
1366
+ } else {
1367
+ // `window` in the browser, or `exports` on the server
1368
+ this.diffDOM = diffDOM;
1369
+ }
1370
+
1371
+ }.call(this));