opalla 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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));