mithril-pipeline 0.2.5

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,2233 @@
1
+ ;(function (global, factory) { // eslint-disable-line
2
+ "use strict"
3
+ /* eslint-disable no-undef */
4
+ var m = factory(global)
5
+ if (typeof module === "object" && module != null && module.exports) {
6
+ module.exports = m
7
+ } else if (typeof define === "function" && define.amd) {
8
+ define(function () { return m })
9
+ } else {
10
+ global.m = m
11
+ }
12
+ /* eslint-enable no-undef */
13
+ })(typeof window !== "undefined" ? window : this, function (global, undefined) { // eslint-disable-line
14
+ "use strict"
15
+
16
+ m.version = function () {
17
+ return "v0.2.5"
18
+ }
19
+
20
+ var hasOwn = {}.hasOwnProperty
21
+ var type = {}.toString
22
+
23
+ function isFunction(object) {
24
+ return typeof object === "function"
25
+ }
26
+
27
+ function isObject(object) {
28
+ return type.call(object) === "[object Object]"
29
+ }
30
+
31
+ function isString(object) {
32
+ return type.call(object) === "[object String]"
33
+ }
34
+
35
+ var isArray = Array.isArray || function (object) {
36
+ return type.call(object) === "[object Array]"
37
+ }
38
+
39
+ function noop() {}
40
+
41
+ var voidElements = {
42
+ AREA: 1,
43
+ BASE: 1,
44
+ BR: 1,
45
+ COL: 1,
46
+ COMMAND: 1,
47
+ EMBED: 1,
48
+ HR: 1,
49
+ IMG: 1,
50
+ INPUT: 1,
51
+ KEYGEN: 1,
52
+ LINK: 1,
53
+ META: 1,
54
+ PARAM: 1,
55
+ SOURCE: 1,
56
+ TRACK: 1,
57
+ WBR: 1
58
+ }
59
+
60
+ // caching commonly used variables
61
+ var $document, $location, $requestAnimationFrame, $cancelAnimationFrame
62
+
63
+ // self invoking function needed because of the way mocks work
64
+ function initialize(mock) {
65
+ $document = mock.document
66
+ $location = mock.location
67
+ $cancelAnimationFrame = mock.cancelAnimationFrame || mock.clearTimeout
68
+ $requestAnimationFrame = mock.requestAnimationFrame || mock.setTimeout
69
+ }
70
+
71
+ // testing API
72
+ m.deps = function (mock) {
73
+ initialize(global = mock || window)
74
+ return global
75
+ }
76
+
77
+ m.deps(global)
78
+
79
+ /**
80
+ * @typedef {String} Tag
81
+ * A string that looks like -> div.classname#id[param=one][param2=two]
82
+ * Which describes a DOM node
83
+ */
84
+
85
+ function parseTagAttrs(cell, tag) {
86
+ var classes = []
87
+ var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g
88
+ var match
89
+
90
+ while ((match = parser.exec(tag))) {
91
+ if (match[1] === "" && match[2]) {
92
+ cell.tag = match[2]
93
+ } else if (match[1] === "#") {
94
+ cell.attrs.id = match[2]
95
+ } else if (match[1] === ".") {
96
+ classes.push(match[2])
97
+ } else if (match[3][0] === "[") {
98
+ var pair = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/.exec(match[3])
99
+ cell.attrs[pair[1]] = pair[3] || ""
100
+ }
101
+ }
102
+
103
+ return classes
104
+ }
105
+
106
+ function getVirtualChildren(args, hasAttrs) {
107
+ var children = hasAttrs ? args.slice(1) : args
108
+
109
+ if (children.length === 1 && isArray(children[0])) {
110
+ return children[0]
111
+ } else {
112
+ return children
113
+ }
114
+ }
115
+
116
+ function assignAttrs(target, attrs, classes) {
117
+ var classAttr = "class" in attrs ? "class" : "className"
118
+
119
+ for (var attrName in attrs) {
120
+ if (hasOwn.call(attrs, attrName)) {
121
+ if (attrName === classAttr &&
122
+ attrs[attrName] != null &&
123
+ attrs[attrName] !== "") {
124
+ classes.push(attrs[attrName])
125
+ // create key in correct iteration order
126
+ target[attrName] = ""
127
+ } else {
128
+ target[attrName] = attrs[attrName]
129
+ }
130
+ }
131
+ }
132
+
133
+ if (classes.length) target[classAttr] = classes.join(" ")
134
+ }
135
+
136
+ /**
137
+ *
138
+ * @param {Tag} The DOM node tag
139
+ * @param {Object=[]} optional key-value pairs to be mapped to DOM attrs
140
+ * @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array,
141
+ * or splat (optional)
142
+ */
143
+ function m(tag, pairs) {
144
+ var args = []
145
+
146
+ for (var i = 1, length = arguments.length; i < length; i++) {
147
+ args[i - 1] = arguments[i]
148
+ }
149
+
150
+ if (isObject(tag)) return parameterize(tag, args)
151
+
152
+ if (!isString(tag)) {
153
+ throw new Error("selector in m(selector, attrs, children) should " +
154
+ "be a string")
155
+ }
156
+
157
+ var hasAttrs = pairs != null && isObject(pairs) &&
158
+ !("tag" in pairs || "view" in pairs || "subtree" in pairs)
159
+
160
+ var attrs = hasAttrs ? pairs : {}
161
+ var cell = {
162
+ tag: "div",
163
+ attrs: {},
164
+ children: getVirtualChildren(args, hasAttrs)
165
+ }
166
+
167
+ assignAttrs(cell.attrs, attrs, parseTagAttrs(cell, tag))
168
+ return cell
169
+ }
170
+
171
+ function forEach(list, f) {
172
+ for (var i = 0; i < list.length && !f(list[i], i++);) {
173
+ // function called in condition
174
+ }
175
+ }
176
+
177
+ function forKeys(list, f) {
178
+ forEach(list, function (attrs, i) {
179
+ return (attrs = attrs && attrs.attrs) &&
180
+ attrs.key != null &&
181
+ f(attrs, i)
182
+ })
183
+ }
184
+ // This function was causing deopts in Chrome.
185
+ function dataToString(data) {
186
+ // data.toString() might throw or return null if data is the return
187
+ // value of Console.log in some versions of Firefox (behavior depends on
188
+ // version)
189
+ try {
190
+ if (data != null && data.toString() != null) return data
191
+ } catch (e) {
192
+ // silently ignore errors
193
+ }
194
+ return ""
195
+ }
196
+
197
+ // This function was causing deopts in Chrome.
198
+ function injectTextNode(parentElement, first, index, data) {
199
+ try {
200
+ insertNode(parentElement, first, index)
201
+ first.nodeValue = data
202
+ } catch (e) {
203
+ // IE erroneously throws error when appending an empty text node
204
+ // after a null
205
+ }
206
+ }
207
+
208
+ function flatten(list) {
209
+ // recursively flatten array
210
+ for (var i = 0; i < list.length; i++) {
211
+ if (isArray(list[i])) {
212
+ list = list.concat.apply([], list)
213
+ // check current index again and flatten until there are no more
214
+ // nested arrays at that index
215
+ i--
216
+ }
217
+ }
218
+ return list
219
+ }
220
+
221
+ function insertNode(parentElement, node, index) {
222
+ parentElement.insertBefore(node,
223
+ parentElement.childNodes[index] || null)
224
+ }
225
+
226
+ var DELETION = 1
227
+ var INSERTION = 2
228
+ var MOVE = 3
229
+
230
+ function handleKeysDiffer(data, existing, cached, parentElement) {
231
+ forKeys(data, function (key, i) {
232
+ existing[key = key.key] = existing[key] ? {
233
+ action: MOVE,
234
+ index: i,
235
+ from: existing[key].index,
236
+ element: cached.nodes[existing[key].index] ||
237
+ $document.createElement("div")
238
+ } : {action: INSERTION, index: i}
239
+ })
240
+
241
+ var actions = []
242
+ for (var prop in existing) {
243
+ if (hasOwn.call(existing, prop)) {
244
+ actions.push(existing[prop])
245
+ }
246
+ }
247
+
248
+ var changes = actions.sort(sortChanges)
249
+ var newCached = new Array(cached.length)
250
+
251
+ newCached.nodes = cached.nodes.slice()
252
+
253
+ forEach(changes, function (change) {
254
+ var index = change.index
255
+ if (change.action === DELETION) {
256
+ clear(cached[index].nodes, cached[index])
257
+ newCached.splice(index, 1)
258
+ }
259
+ if (change.action === INSERTION) {
260
+ var dummy = $document.createElement("div")
261
+ dummy.key = data[index].attrs.key
262
+ insertNode(parentElement, dummy, index)
263
+ newCached.splice(index, 0, {
264
+ attrs: {key: data[index].attrs.key},
265
+ nodes: [dummy]
266
+ })
267
+ newCached.nodes[index] = dummy
268
+ }
269
+
270
+ if (change.action === MOVE) {
271
+ var changeElement = change.element
272
+ var maybeChanged = parentElement.childNodes[index]
273
+ if (maybeChanged !== changeElement && changeElement !== null) {
274
+ parentElement.insertBefore(changeElement,
275
+ maybeChanged || null)
276
+ }
277
+ newCached[index] = cached[change.from]
278
+ newCached.nodes[index] = changeElement
279
+ }
280
+ })
281
+
282
+ return newCached
283
+ }
284
+
285
+ function diffKeys(data, cached, existing, parentElement) {
286
+ var keysDiffer = data.length !== cached.length
287
+
288
+ if (!keysDiffer) {
289
+ forKeys(data, function (attrs, i) {
290
+ var cachedCell = cached[i]
291
+ return keysDiffer = cachedCell &&
292
+ cachedCell.attrs &&
293
+ cachedCell.attrs.key !== attrs.key
294
+ })
295
+ }
296
+
297
+ if (keysDiffer) {
298
+ return handleKeysDiffer(data, existing, cached, parentElement)
299
+ } else {
300
+ return cached
301
+ }
302
+ }
303
+
304
+ function diffArray(data, cached, nodes) {
305
+ // diff the array itself
306
+
307
+ // update the list of DOM nodes by collecting the nodes from each item
308
+ forEach(data, function (_, i) {
309
+ if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes)
310
+ })
311
+ // remove items from the end of the array if the new array is shorter
312
+ // than the old one. if errors ever happen here, the issue is most
313
+ // likely a bug in the construction of the `cached` data structure
314
+ // somewhere earlier in the program
315
+ forEach(cached.nodes, function (node, i) {
316
+ if (node.parentNode != null && nodes.indexOf(node) < 0) {
317
+ clear([node], [cached[i]])
318
+ }
319
+ })
320
+
321
+ if (data.length < cached.length) cached.length = data.length
322
+ cached.nodes = nodes
323
+ }
324
+
325
+ function buildArrayKeys(data) {
326
+ var guid = 0
327
+ forKeys(data, function () {
328
+ forEach(data, function (attrs) {
329
+ if ((attrs = attrs && attrs.attrs) && attrs.key == null) {
330
+ attrs.key = "__mithril__" + guid++
331
+ }
332
+ })
333
+ return 1
334
+ })
335
+ }
336
+
337
+ function isDifferentEnough(data, cached, dataAttrKeys) {
338
+ if (data.tag !== cached.tag) return true
339
+
340
+ if (dataAttrKeys.sort().join() !==
341
+ Object.keys(cached.attrs).sort().join()) {
342
+ return true
343
+ }
344
+
345
+ if (data.attrs.id !== cached.attrs.id) {
346
+ return true
347
+ }
348
+
349
+ if (data.attrs.key !== cached.attrs.key) {
350
+ return true
351
+ }
352
+
353
+ if (m.redraw.strategy() === "all") {
354
+ return !cached.configContext || cached.configContext.retain !== true
355
+ }
356
+
357
+ if (m.redraw.strategy() === "diff") {
358
+ return cached.configContext && cached.configContext.retain === false
359
+ }
360
+
361
+ return false
362
+ }
363
+
364
+ function maybeRecreateObject(data, cached, dataAttrKeys) {
365
+ // if an element is different enough from the one in cache, recreate it
366
+ if (isDifferentEnough(data, cached, dataAttrKeys)) {
367
+ if (cached.nodes.length) clear(cached.nodes)
368
+
369
+ if (cached.configContext &&
370
+ isFunction(cached.configContext.onunload)) {
371
+ cached.configContext.onunload()
372
+ }
373
+
374
+ if (cached.controllers) {
375
+ forEach(cached.controllers, function (controller) {
376
+ if (controller.onunload) {
377
+ controller.onunload({preventDefault: noop})
378
+ }
379
+ })
380
+ }
381
+ }
382
+ }
383
+
384
+ function getObjectNamespace(data, namespace) {
385
+ if (data.attrs.xmlns) return data.attrs.xmlns
386
+ if (data.tag === "svg") return "http://www.w3.org/2000/svg"
387
+ if (data.tag === "math") return "http://www.w3.org/1998/Math/MathML"
388
+ return namespace
389
+ }
390
+
391
+ var pendingRequests = 0
392
+ m.startComputation = function () { pendingRequests++ }
393
+ m.endComputation = function () {
394
+ if (pendingRequests > 1) {
395
+ pendingRequests--
396
+ } else {
397
+ pendingRequests = 0
398
+ m.redraw()
399
+ }
400
+ }
401
+
402
+ function unloadCachedControllers(cached, views, controllers) {
403
+ if (controllers.length) {
404
+ cached.views = views
405
+ cached.controllers = controllers
406
+ forEach(controllers, function (controller) {
407
+ if (controller.onunload && controller.onunload.$old) {
408
+ controller.onunload = controller.onunload.$old
409
+ }
410
+
411
+ if (pendingRequests && controller.onunload) {
412
+ var onunload = controller.onunload
413
+ controller.onunload = noop
414
+ controller.onunload.$old = onunload
415
+ }
416
+ })
417
+ }
418
+ }
419
+
420
+ function scheduleConfigsToBeCalled(configs, data, node, isNew, cached) {
421
+ // schedule configs to be called. They are called after `build` finishes
422
+ // running
423
+ if (isFunction(data.attrs.config)) {
424
+ var context = cached.configContext = cached.configContext || {}
425
+
426
+ // bind
427
+ configs.push(function () {
428
+ return data.attrs.config.call(data, node, !isNew, context,
429
+ cached)
430
+ })
431
+ }
432
+ }
433
+
434
+ function buildUpdatedNode(
435
+ cached,
436
+ data,
437
+ editable,
438
+ hasKeys,
439
+ namespace,
440
+ views,
441
+ configs,
442
+ controllers
443
+ ) {
444
+ var node = cached.nodes[0]
445
+
446
+ if (hasKeys) {
447
+ setAttributes(node, data.tag, data.attrs, cached.attrs, namespace)
448
+ }
449
+
450
+ cached.children = build(
451
+ node,
452
+ data.tag,
453
+ undefined,
454
+ undefined,
455
+ data.children,
456
+ cached.children,
457
+ false,
458
+ 0,
459
+ data.attrs.contenteditable ? node : editable,
460
+ namespace,
461
+ configs
462
+ )
463
+
464
+ cached.nodes.intact = true
465
+
466
+ if (controllers.length) {
467
+ cached.views = views
468
+ cached.controllers = controllers
469
+ }
470
+
471
+ return node
472
+ }
473
+
474
+ function handleNonexistentNodes(data, parentElement, index) {
475
+ var nodes
476
+ if (data.$trusted) {
477
+ nodes = injectHTML(parentElement, index, data)
478
+ } else {
479
+ nodes = [$document.createTextNode(data)]
480
+ if (!(parentElement.nodeName in voidElements)) {
481
+ insertNode(parentElement, nodes[0], index)
482
+ }
483
+ }
484
+
485
+ var cached
486
+
487
+ if (typeof data === "string" ||
488
+ typeof data === "number" ||
489
+ typeof data === "boolean") {
490
+ cached = new data.constructor(data)
491
+ } else {
492
+ cached = data
493
+ }
494
+
495
+ cached.nodes = nodes
496
+ return cached
497
+ }
498
+
499
+ function reattachNodes(
500
+ data,
501
+ cached,
502
+ parentElement,
503
+ editable,
504
+ index,
505
+ parentTag
506
+ ) {
507
+ var nodes = cached.nodes
508
+ if (!editable || editable !== $document.activeElement) {
509
+ if (data.$trusted) {
510
+ clear(nodes, cached)
511
+ nodes = injectHTML(parentElement, index, data)
512
+ } else if (parentTag === "textarea") {
513
+ // <textarea> uses `value` instead of `nodeValue`.
514
+ parentElement.value = data
515
+ } else if (editable) {
516
+ // contenteditable nodes use `innerHTML` instead of `nodeValue`.
517
+ editable.innerHTML = data
518
+ } else {
519
+ // was a trusted string
520
+ if (nodes[0].nodeType === 1 || nodes.length > 1 ||
521
+ (nodes[0].nodeValue.trim &&
522
+ !nodes[0].nodeValue.trim())) {
523
+ clear(cached.nodes, cached)
524
+ nodes = [$document.createTextNode(data)]
525
+ }
526
+
527
+ injectTextNode(parentElement, nodes[0], index, data)
528
+ }
529
+ }
530
+ cached = new data.constructor(data)
531
+ cached.nodes = nodes
532
+ return cached
533
+ }
534
+
535
+ function handleTextNode(
536
+ cached,
537
+ data,
538
+ index,
539
+ parentElement,
540
+ shouldReattach,
541
+ editable,
542
+ parentTag
543
+ ) {
544
+ if (!cached.nodes.length) {
545
+ return handleNonexistentNodes(data, parentElement, index)
546
+ } else if (cached.valueOf() !== data.valueOf() || shouldReattach) {
547
+ return reattachNodes(data, cached, parentElement, editable, index,
548
+ parentTag)
549
+ } else {
550
+ return (cached.nodes.intact = true, cached)
551
+ }
552
+ }
553
+
554
+ function getSubArrayCount(item) {
555
+ if (item.$trusted) {
556
+ // fix offset of next element if item was a trusted string w/ more
557
+ // than one html element
558
+ // the first clause in the regexp matches elements
559
+ // the second clause (after the pipe) matches text nodes
560
+ var match = item.match(/<[^\/]|\>\s*[^<]/g)
561
+ if (match != null) return match.length
562
+ } else if (isArray(item)) {
563
+ return item.length
564
+ }
565
+ return 1
566
+ }
567
+
568
+ function buildArray(
569
+ data,
570
+ cached,
571
+ parentElement,
572
+ index,
573
+ parentTag,
574
+ shouldReattach,
575
+ editable,
576
+ namespace,
577
+ configs
578
+ ) {
579
+ data = flatten(data)
580
+ var nodes = []
581
+ var intact = cached.length === data.length
582
+ var subArrayCount = 0
583
+
584
+ // keys algorithm: sort elements without recreating them if keys are
585
+ // present
586
+ //
587
+ // 1) create a map of all existing keys, and mark all for deletion
588
+ // 2) add new keys to map and mark them for addition
589
+ // 3) if key exists in new list, change action from deletion to a move
590
+ // 4) for each key, handle its corresponding action as marked in
591
+ // previous steps
592
+
593
+ var existing = {}
594
+ var shouldMaintainIdentities = false
595
+
596
+ forKeys(cached, function (attrs, i) {
597
+ shouldMaintainIdentities = true
598
+ existing[cached[i].attrs.key] = {action: DELETION, index: i}
599
+ })
600
+
601
+ buildArrayKeys(data)
602
+ if (shouldMaintainIdentities) {
603
+ cached = diffKeys(data, cached, existing, parentElement)
604
+ }
605
+ // end key algorithm
606
+
607
+ var cacheCount = 0
608
+ // faster explicitly written
609
+ for (var i = 0, len = data.length; i < len; i++) {
610
+ // diff each item in the array
611
+ var item = build(
612
+ parentElement,
613
+ parentTag,
614
+ cached,
615
+ index,
616
+ data[i],
617
+ cached[cacheCount],
618
+ shouldReattach,
619
+ index + subArrayCount || subArrayCount,
620
+ editable,
621
+ namespace,
622
+ configs)
623
+
624
+ if (item !== undefined) {
625
+ intact = intact && item.nodes.intact
626
+ subArrayCount += getSubArrayCount(item)
627
+ cached[cacheCount++] = item
628
+ }
629
+ }
630
+
631
+ if (!intact) diffArray(data, cached, nodes)
632
+ return cached
633
+ }
634
+
635
+ function makeCache(data, cached, index, parentIndex, parentCache) {
636
+ if (cached != null) {
637
+ if (type.call(cached) === type.call(data)) return cached
638
+
639
+ if (parentCache && parentCache.nodes) {
640
+ var offset = index - parentIndex
641
+ var end = offset + (isArray(data) ? data : cached.nodes).length
642
+ clear(
643
+ parentCache.nodes.slice(offset, end),
644
+ parentCache.slice(offset, end))
645
+ } else if (cached.nodes) {
646
+ clear(cached.nodes, cached)
647
+ }
648
+ }
649
+
650
+ cached = new data.constructor()
651
+ // if constructor creates a virtual dom element, use a blank object as
652
+ // the base cached node instead of copying the virtual el (#277)
653
+ if (cached.tag) cached = {}
654
+ cached.nodes = []
655
+ return cached
656
+ }
657
+
658
+ function constructNode(data, namespace) {
659
+ if (data.attrs.is) {
660
+ if (namespace == null) {
661
+ return $document.createElement(data.tag, data.attrs.is)
662
+ } else {
663
+ return $document.createElementNS(namespace, data.tag,
664
+ data.attrs.is)
665
+ }
666
+ } else if (namespace == null) {
667
+ return $document.createElement(data.tag)
668
+ } else {
669
+ return $document.createElementNS(namespace, data.tag)
670
+ }
671
+ }
672
+
673
+ function constructAttrs(data, node, namespace, hasKeys) {
674
+ if (hasKeys) {
675
+ return setAttributes(node, data.tag, data.attrs, {}, namespace)
676
+ } else {
677
+ return data.attrs
678
+ }
679
+ }
680
+
681
+ function constructChildren(
682
+ data,
683
+ node,
684
+ cached,
685
+ editable,
686
+ namespace,
687
+ configs
688
+ ) {
689
+ if (data.children != null && data.children.length > 0) {
690
+ return build(
691
+ node,
692
+ data.tag,
693
+ undefined,
694
+ undefined,
695
+ data.children,
696
+ cached.children,
697
+ true,
698
+ 0,
699
+ data.attrs.contenteditable ? node : editable,
700
+ namespace,
701
+ configs)
702
+ } else {
703
+ return data.children
704
+ }
705
+ }
706
+
707
+ function reconstructCached(
708
+ data,
709
+ attrs,
710
+ children,
711
+ node,
712
+ namespace,
713
+ views,
714
+ controllers
715
+ ) {
716
+ var cached = {
717
+ tag: data.tag,
718
+ attrs: attrs,
719
+ children: children,
720
+ nodes: [node]
721
+ }
722
+
723
+ unloadCachedControllers(cached, views, controllers)
724
+
725
+ if (cached.children && !cached.children.nodes) {
726
+ cached.children.nodes = []
727
+ }
728
+
729
+ // edge case: setting value on <select> doesn't work before children
730
+ // exist, so set it again after children have been created
731
+ if (data.tag === "select" && "value" in data.attrs) {
732
+ setAttributes(node, data.tag, {value: data.attrs.value}, {},
733
+ namespace)
734
+ }
735
+
736
+ return cached
737
+ }
738
+
739
+ function getController(views, view, cachedControllers, controller) {
740
+ var controllerIndex
741
+
742
+ if (m.redraw.strategy() === "diff" && views) {
743
+ controllerIndex = views.indexOf(view)
744
+ } else {
745
+ controllerIndex = -1
746
+ }
747
+
748
+ if (controllerIndex > -1) {
749
+ return cachedControllers[controllerIndex]
750
+ } else if (isFunction(controller)) {
751
+ return new controller()
752
+ } else {
753
+ return {}
754
+ }
755
+ }
756
+
757
+ var unloaders = []
758
+
759
+ function updateLists(views, controllers, view, controller) {
760
+ if (controller.onunload != null &&
761
+ unloaders.map(function (u) { return u.handler })
762
+ .indexOf(controller.onunload) < 0) {
763
+ unloaders.push({
764
+ controller: controller,
765
+ handler: controller.onunload
766
+ })
767
+ }
768
+
769
+ views.push(view)
770
+ controllers.push(controller)
771
+ }
772
+
773
+ var forcing = false
774
+ function checkView(
775
+ data,
776
+ view,
777
+ cached,
778
+ cachedControllers,
779
+ controllers,
780
+ views
781
+ ) {
782
+ var controller = getController(
783
+ cached.views,
784
+ view,
785
+ cachedControllers,
786
+ data.controller)
787
+
788
+ var key = data && data.attrs && data.attrs.key
789
+
790
+ if (pendingRequests === 0 ||
791
+ forcing ||
792
+ cachedControllers &&
793
+ cachedControllers.indexOf(controller) > -1) {
794
+ data = data.view(controller)
795
+ } else {
796
+ data = {tag: "placeholder"}
797
+ }
798
+
799
+ if (data.subtree === "retain") return data
800
+ data.attrs = data.attrs || {}
801
+ data.attrs.key = key
802
+ updateLists(views, controllers, view, controller)
803
+ return data
804
+ }
805
+
806
+ function markViews(data, cached, views, controllers) {
807
+ var cachedControllers = cached && cached.controllers
808
+
809
+ while (data.view != null) {
810
+ data = checkView(
811
+ data,
812
+ data.view.$original || data.view,
813
+ cached,
814
+ cachedControllers,
815
+ controllers,
816
+ views)
817
+ }
818
+
819
+ return data
820
+ }
821
+
822
+ function buildObject( // eslint-disable-line max-statements
823
+ data,
824
+ cached,
825
+ editable,
826
+ parentElement,
827
+ index,
828
+ shouldReattach,
829
+ namespace,
830
+ configs
831
+ ) {
832
+ var views = []
833
+ var controllers = []
834
+
835
+ data = markViews(data, cached, views, controllers)
836
+
837
+ if (data.subtree === "retain") return cached
838
+
839
+ if (!data.tag && controllers.length) {
840
+ throw new Error("Component template must return a virtual " +
841
+ "element, not an array, string, etc.")
842
+ }
843
+
844
+ data.attrs = data.attrs || {}
845
+ cached.attrs = cached.attrs || {}
846
+
847
+ var dataAttrKeys = Object.keys(data.attrs)
848
+ var hasKeys = dataAttrKeys.length > ("key" in data.attrs ? 1 : 0)
849
+
850
+ maybeRecreateObject(data, cached, dataAttrKeys)
851
+
852
+ if (!isString(data.tag)) return
853
+
854
+ var isNew = cached.nodes.length === 0
855
+
856
+ namespace = getObjectNamespace(data, namespace)
857
+
858
+ var node
859
+ if (isNew) {
860
+ node = constructNode(data, namespace)
861
+ // set attributes first, then create children
862
+ var attrs = constructAttrs(data, node, namespace, hasKeys)
863
+
864
+ // add the node to its parent before attaching children to it
865
+ insertNode(parentElement, node, index)
866
+
867
+ var children = constructChildren(data, node, cached, editable,
868
+ namespace, configs)
869
+
870
+ cached = reconstructCached(
871
+ data,
872
+ attrs,
873
+ children,
874
+ node,
875
+ namespace,
876
+ views,
877
+ controllers)
878
+ } else {
879
+ node = buildUpdatedNode(
880
+ cached,
881
+ data,
882
+ editable,
883
+ hasKeys,
884
+ namespace,
885
+ views,
886
+ configs,
887
+ controllers)
888
+ }
889
+
890
+ if (!isNew && shouldReattach === true && node != null) {
891
+ insertNode(parentElement, node, index)
892
+ }
893
+
894
+ // The configs are called after `build` finishes running
895
+ scheduleConfigsToBeCalled(configs, data, node, isNew, cached)
896
+
897
+ return cached
898
+ }
899
+
900
+ function build(
901
+ parentElement,
902
+ parentTag,
903
+ parentCache,
904
+ parentIndex,
905
+ data,
906
+ cached,
907
+ shouldReattach,
908
+ index,
909
+ editable,
910
+ namespace,
911
+ configs
912
+ ) {
913
+ /*
914
+ * `build` is a recursive function that manages creation/diffing/removal
915
+ * of DOM elements based on comparison between `data` and `cached` the
916
+ * diff algorithm can be summarized as this:
917
+ *
918
+ * 1 - compare `data` and `cached`
919
+ * 2 - if they are different, copy `data` to `cached` and update the DOM
920
+ * based on what the difference is
921
+ * 3 - recursively apply this algorithm for every array and for the
922
+ * children of every virtual element
923
+ *
924
+ * The `cached` data structure is essentially the same as the previous
925
+ * redraw's `data` data structure, with a few additions:
926
+ * - `cached` always has a property called `nodes`, which is a list of
927
+ * DOM elements that correspond to the data represented by the
928
+ * respective virtual element
929
+ * - in order to support attaching `nodes` as a property of `cached`,
930
+ * `cached` is *always* a non-primitive object, i.e. if the data was
931
+ * a string, then cached is a String instance. If data was `null` or
932
+ * `undefined`, cached is `new String("")`
933
+ * - `cached also has a `configContext` property, which is the state
934
+ * storage object exposed by config(element, isInitialized, context)
935
+ * - when `cached` is an Object, it represents a virtual element; when
936
+ * it's an Array, it represents a list of elements; when it's a
937
+ * String, Number or Boolean, it represents a text node
938
+ *
939
+ * `parentElement` is a DOM element used for W3C DOM API calls
940
+ * `parentTag` is only used for handling a corner case for textarea
941
+ * values
942
+ * `parentCache` is used to remove nodes in some multi-node cases
943
+ * `parentIndex` and `index` are used to figure out the offset of nodes.
944
+ * They're artifacts from before arrays started being flattened and are
945
+ * likely refactorable
946
+ * `data` and `cached` are, respectively, the new and old nodes being
947
+ * diffed
948
+ * `shouldReattach` is a flag indicating whether a parent node was
949
+ * recreated (if so, and if this node is reused, then this node must
950
+ * reattach itself to the new parent)
951
+ * `editable` is a flag that indicates whether an ancestor is
952
+ * contenteditable
953
+ * `namespace` indicates the closest HTML namespace as it cascades down
954
+ * from an ancestor
955
+ * `configs` is a list of config functions to run after the topmost
956
+ * `build` call finishes running
957
+ *
958
+ * there's logic that relies on the assumption that null and undefined
959
+ * data are equivalent to empty strings
960
+ * - this prevents lifecycle surprises from procedural helpers that mix
961
+ * implicit and explicit return statements (e.g.
962
+ * function foo() {if (cond) return m("div")}
963
+ * - it simplifies diffing code
964
+ */
965
+ data = dataToString(data)
966
+ if (data.subtree === "retain") return cached
967
+ cached = makeCache(data, cached, index, parentIndex, parentCache)
968
+
969
+ if (isArray(data)) {
970
+ return buildArray(
971
+ data,
972
+ cached,
973
+ parentElement,
974
+ index,
975
+ parentTag,
976
+ shouldReattach,
977
+ editable,
978
+ namespace,
979
+ configs)
980
+ } else if (data != null && isObject(data)) {
981
+ return buildObject(
982
+ data,
983
+ cached,
984
+ editable,
985
+ parentElement,
986
+ index,
987
+ shouldReattach,
988
+ namespace,
989
+ configs)
990
+ } else if (!isFunction(data)) {
991
+ return handleTextNode(
992
+ cached,
993
+ data,
994
+ index,
995
+ parentElement,
996
+ shouldReattach,
997
+ editable,
998
+ parentTag)
999
+ } else {
1000
+ return cached
1001
+ }
1002
+ }
1003
+
1004
+ function sortChanges(a, b) {
1005
+ return a.action - b.action || a.index - b.index
1006
+ }
1007
+
1008
+ function copyStyleAttrs(node, dataAttr, cachedAttr) {
1009
+ for (var rule in dataAttr) {
1010
+ if (hasOwn.call(dataAttr, rule)) {
1011
+ if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) {
1012
+ node.style[rule] = dataAttr[rule]
1013
+ }
1014
+ }
1015
+ }
1016
+
1017
+ for (rule in cachedAttr) {
1018
+ if (hasOwn.call(cachedAttr, rule)) {
1019
+ if (!hasOwn.call(dataAttr, rule)) node.style[rule] = ""
1020
+ }
1021
+ }
1022
+ }
1023
+
1024
+ var shouldUseSetAttribute = {
1025
+ list: 1,
1026
+ style: 1,
1027
+ form: 1,
1028
+ type: 1,
1029
+ width: 1,
1030
+ height: 1
1031
+ }
1032
+
1033
+ function setSingleAttr(
1034
+ node,
1035
+ attrName,
1036
+ dataAttr,
1037
+ cachedAttr,
1038
+ tag,
1039
+ namespace
1040
+ ) {
1041
+ if (attrName === "config" || attrName === "key") {
1042
+ // `config` isn't a real attribute, so ignore it
1043
+ return true
1044
+ } else if (isFunction(dataAttr) && attrName.slice(0, 2) === "on") {
1045
+ // hook event handlers to the auto-redrawing system
1046
+ node[attrName] = autoredraw(dataAttr, node)
1047
+ } else if (attrName === "style" && dataAttr != null &&
1048
+ isObject(dataAttr)) {
1049
+ // handle `style: {...}`
1050
+ copyStyleAttrs(node, dataAttr, cachedAttr)
1051
+ } else if (namespace != null) {
1052
+ // handle SVG
1053
+ if (attrName === "href") {
1054
+ node.setAttributeNS("http://www.w3.org/1999/xlink",
1055
+ "href", dataAttr)
1056
+ } else {
1057
+ node.setAttribute(
1058
+ attrName === "className" ? "class" : attrName,
1059
+ dataAttr)
1060
+ }
1061
+ } else if (attrName in node && !shouldUseSetAttribute[attrName]) {
1062
+ // handle cases that are properties (but ignore cases where we
1063
+ // should use setAttribute instead)
1064
+ //
1065
+ // - list and form are typically used as strings, but are DOM
1066
+ // element references in js
1067
+ //
1068
+ // - when using CSS selectors (e.g. `m("[style='']")`), style is
1069
+ // used as a string, but it's an object in js
1070
+ //
1071
+ // #348 don't set the value if not needed - otherwise, cursor
1072
+ // placement breaks in Chrome
1073
+ try {
1074
+ if (tag !== "input" || node[attrName] !== dataAttr) {
1075
+ node[attrName] = dataAttr
1076
+ }
1077
+ } catch (e) {
1078
+ node.setAttribute(attrName, dataAttr)
1079
+ }
1080
+ }
1081
+ else node.setAttribute(attrName, dataAttr)
1082
+ }
1083
+
1084
+ function trySetAttr(
1085
+ node,
1086
+ attrName,
1087
+ dataAttr,
1088
+ cachedAttr,
1089
+ cachedAttrs,
1090
+ tag,
1091
+ namespace
1092
+ ) {
1093
+ if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr) || ($document.activeElement === node)) {
1094
+ cachedAttrs[attrName] = dataAttr
1095
+ try {
1096
+ return setSingleAttr(
1097
+ node,
1098
+ attrName,
1099
+ dataAttr,
1100
+ cachedAttr,
1101
+ tag,
1102
+ namespace)
1103
+ } catch (e) {
1104
+ // swallow IE's invalid argument errors to mimic HTML's
1105
+ // fallback-to-doing-nothing-on-invalid-attributes behavior
1106
+ if (e.message.indexOf("Invalid argument") < 0) throw e
1107
+ }
1108
+ } else if (attrName === "value" && tag === "input" &&
1109
+ node.value !== dataAttr) {
1110
+ // #348 dataAttr may not be a string, so use loose comparison
1111
+ node.value = dataAttr
1112
+ }
1113
+ }
1114
+
1115
+ function setAttributes(node, tag, dataAttrs, cachedAttrs, namespace) {
1116
+ for (var attrName in dataAttrs) {
1117
+ if (hasOwn.call(dataAttrs, attrName)) {
1118
+ if (trySetAttr(
1119
+ node,
1120
+ attrName,
1121
+ dataAttrs[attrName],
1122
+ cachedAttrs[attrName],
1123
+ cachedAttrs,
1124
+ tag,
1125
+ namespace)) {
1126
+ continue
1127
+ }
1128
+ }
1129
+ }
1130
+ return cachedAttrs
1131
+ }
1132
+
1133
+ function clear(nodes, cached) {
1134
+ for (var i = nodes.length - 1; i > -1; i--) {
1135
+ if (nodes[i] && nodes[i].parentNode) {
1136
+ try {
1137
+ nodes[i].parentNode.removeChild(nodes[i])
1138
+ } catch (e) {
1139
+ /* eslint-disable max-len */
1140
+ // ignore if this fails due to order of events (see
1141
+ // http://stackoverflow.com/questions/21926083/failed-to-execute-removechild-on-node)
1142
+ /* eslint-enable max-len */
1143
+ }
1144
+ cached = [].concat(cached)
1145
+ if (cached[i]) unload(cached[i])
1146
+ }
1147
+ }
1148
+ // release memory if nodes is an array. This check should fail if nodes
1149
+ // is a NodeList (see loop above)
1150
+ if (nodes.length) {
1151
+ nodes.length = 0
1152
+ }
1153
+ }
1154
+
1155
+ function unload(cached) {
1156
+ if (cached.configContext && isFunction(cached.configContext.onunload)) {
1157
+ cached.configContext.onunload()
1158
+ cached.configContext.onunload = null
1159
+ }
1160
+ if (cached.controllers) {
1161
+ forEach(cached.controllers, function (controller) {
1162
+ if (isFunction(controller.onunload)) {
1163
+ controller.onunload({preventDefault: noop})
1164
+ }
1165
+ })
1166
+ }
1167
+ if (cached.children) {
1168
+ if (isArray(cached.children)) forEach(cached.children, unload)
1169
+ else if (cached.children.tag) unload(cached.children)
1170
+ }
1171
+ }
1172
+
1173
+ function appendTextFragment(parentElement, data) {
1174
+ try {
1175
+ parentElement.appendChild(
1176
+ $document.createRange().createContextualFragment(data))
1177
+ } catch (e) {
1178
+ parentElement.insertAdjacentHTML("beforeend", data)
1179
+ replaceScriptNodes(parentElement)
1180
+ }
1181
+ }
1182
+
1183
+ // Replace script tags inside given DOM element with executable ones.
1184
+ // Will also check children recursively and replace any found script
1185
+ // tags in same manner.
1186
+ function replaceScriptNodes(node) {
1187
+ if (node.tagName === "SCRIPT") {
1188
+ node.parentNode.replaceChild(buildExecutableNode(node), node)
1189
+ } else {
1190
+ var children = node.childNodes
1191
+ if (children && children.length) {
1192
+ for (var i = 0; i < children.length; i++) {
1193
+ replaceScriptNodes(children[i])
1194
+ }
1195
+ }
1196
+ }
1197
+
1198
+ return node
1199
+ }
1200
+
1201
+ // Replace script element with one whose contents are executable.
1202
+ function buildExecutableNode(node){
1203
+ var scriptEl = document.createElement("script")
1204
+ var attrs = node.attributes
1205
+
1206
+ for (var i = 0; i < attrs.length; i++) {
1207
+ scriptEl.setAttribute(attrs[i].name, attrs[i].value)
1208
+ }
1209
+
1210
+ scriptEl.text = node.innerHTML
1211
+ return scriptEl
1212
+ }
1213
+
1214
+ function injectHTML(parentElement, index, data) {
1215
+ var nextSibling = parentElement.childNodes[index]
1216
+ if (nextSibling) {
1217
+ var isElement = nextSibling.nodeType !== 1
1218
+ var placeholder = $document.createElement("span")
1219
+ if (isElement) {
1220
+ parentElement.insertBefore(placeholder, nextSibling || null)
1221
+ placeholder.insertAdjacentHTML("beforebegin", data)
1222
+ parentElement.removeChild(placeholder)
1223
+ } else {
1224
+ nextSibling.insertAdjacentHTML("beforebegin", data)
1225
+ }
1226
+ } else {
1227
+ appendTextFragment(parentElement, data)
1228
+ }
1229
+
1230
+ var nodes = []
1231
+
1232
+ while (parentElement.childNodes[index] !== nextSibling) {
1233
+ nodes.push(parentElement.childNodes[index])
1234
+ index++
1235
+ }
1236
+
1237
+ return nodes
1238
+ }
1239
+
1240
+ function autoredraw(callback, object) {
1241
+ return function (e) {
1242
+ e = e || event
1243
+ m.redraw.strategy("diff")
1244
+ m.startComputation()
1245
+ try {
1246
+ return callback.call(object, e)
1247
+ } finally {
1248
+ endFirstComputation()
1249
+ }
1250
+ }
1251
+ }
1252
+
1253
+ var html
1254
+ var documentNode = {
1255
+ appendChild: function (node) {
1256
+ if (html === undefined) html = $document.createElement("html")
1257
+ if ($document.documentElement &&
1258
+ $document.documentElement !== node) {
1259
+ $document.replaceChild(node, $document.documentElement)
1260
+ } else {
1261
+ $document.appendChild(node)
1262
+ }
1263
+
1264
+ this.childNodes = $document.childNodes
1265
+ },
1266
+
1267
+ insertBefore: function (node) {
1268
+ this.appendChild(node)
1269
+ },
1270
+
1271
+ childNodes: []
1272
+ }
1273
+
1274
+ var nodeCache = []
1275
+ var cellCache = {}
1276
+
1277
+ m.render = function (root, cell, forceRecreation) {
1278
+ if (!root) {
1279
+ throw new Error("Ensure the DOM element being passed to " +
1280
+ "m.route/m.mount/m.render is not undefined.")
1281
+ }
1282
+ var configs = []
1283
+ var id = getCellCacheKey(root)
1284
+ var isDocumentRoot = root === $document
1285
+ var node
1286
+
1287
+ if (isDocumentRoot || root === $document.documentElement) {
1288
+ node = documentNode
1289
+ } else {
1290
+ node = root
1291
+ }
1292
+
1293
+ if (isDocumentRoot && cell.tag !== "html") {
1294
+ cell = {tag: "html", attrs: {}, children: cell}
1295
+ }
1296
+
1297
+ if (cellCache[id] === undefined) clear(node.childNodes)
1298
+ if (forceRecreation === true) reset(root)
1299
+
1300
+ cellCache[id] = build(
1301
+ node,
1302
+ null,
1303
+ undefined,
1304
+ undefined,
1305
+ cell,
1306
+ cellCache[id],
1307
+ false,
1308
+ 0,
1309
+ null,
1310
+ undefined,
1311
+ configs)
1312
+
1313
+ forEach(configs, function (config) { config() })
1314
+ }
1315
+
1316
+ function getCellCacheKey(element) {
1317
+ var index = nodeCache.indexOf(element)
1318
+ return index < 0 ? nodeCache.push(element) - 1 : index
1319
+ }
1320
+
1321
+ m.trust = function (value) {
1322
+ value = new String(value) // eslint-disable-line no-new-wrappers
1323
+ value.$trusted = true
1324
+ return value
1325
+ }
1326
+
1327
+ function gettersetter(store) {
1328
+ function prop() {
1329
+ if (arguments.length) store = arguments[0]
1330
+ return store
1331
+ }
1332
+
1333
+ prop.toJSON = function () {
1334
+ return store
1335
+ }
1336
+
1337
+ return prop
1338
+ }
1339
+
1340
+ m.prop = function (store) {
1341
+ if ((store != null && (isObject(store) || isFunction(store)) || ((typeof Promise !== "undefined") && (store instanceof Promise))) &&
1342
+ isFunction(store.then)) {
1343
+ return propify(store)
1344
+ }
1345
+
1346
+ return gettersetter(store)
1347
+ }
1348
+
1349
+ var roots = []
1350
+ var components = []
1351
+ var controllers = []
1352
+ var lastRedrawId = null
1353
+ var lastRedrawCallTime = 0
1354
+ var computePreRedrawHook = null
1355
+ var computePostRedrawHook = null
1356
+ var topComponent
1357
+ var FRAME_BUDGET = 16 // 60 frames per second = 1 call per 16 ms
1358
+
1359
+ function parameterize(component, args) {
1360
+ function controller() {
1361
+ /* eslint-disable no-invalid-this */
1362
+ return (component.controller || noop).apply(this, args) || this
1363
+ /* eslint-enable no-invalid-this */
1364
+ }
1365
+
1366
+ if (component.controller) {
1367
+ controller.prototype = component.controller.prototype
1368
+ }
1369
+
1370
+ function view(ctrl) {
1371
+ var currentArgs = [ctrl].concat(args)
1372
+ for (var i = 1; i < arguments.length; i++) {
1373
+ currentArgs.push(arguments[i])
1374
+ }
1375
+
1376
+ return component.view.apply(component, currentArgs)
1377
+ }
1378
+
1379
+ view.$original = component.view
1380
+ var output = {controller: controller, view: view}
1381
+ if (args[0] && args[0].key != null) output.attrs = {key: args[0].key}
1382
+ return output
1383
+ }
1384
+
1385
+ m.component = function (component) {
1386
+ var args = new Array(arguments.length - 1)
1387
+
1388
+ for (var i = 1; i < arguments.length; i++) {
1389
+ args[i - 1] = arguments[i]
1390
+ }
1391
+
1392
+ return parameterize(component, args)
1393
+ }
1394
+
1395
+ function checkPrevented(component, root, index, isPrevented) {
1396
+ if (!isPrevented) {
1397
+ m.redraw.strategy("all")
1398
+ m.startComputation()
1399
+ roots[index] = root
1400
+ var currentComponent
1401
+
1402
+ if (component) {
1403
+ currentComponent = topComponent = component
1404
+ } else {
1405
+ currentComponent = topComponent = component = {controller: noop}
1406
+ }
1407
+
1408
+ var controller = new (component.controller || noop)()
1409
+
1410
+ // controllers may call m.mount recursively (via m.route redirects,
1411
+ // for example)
1412
+ // this conditional ensures only the last recursive m.mount call is
1413
+ // applied
1414
+ if (currentComponent === topComponent) {
1415
+ controllers[index] = controller
1416
+ components[index] = component
1417
+ }
1418
+ endFirstComputation()
1419
+ if (component === null) {
1420
+ removeRootElement(root, index)
1421
+ }
1422
+ return controllers[index]
1423
+ } else if (component == null) {
1424
+ removeRootElement(root, index)
1425
+ }
1426
+ }
1427
+
1428
+ m.mount = m.module = function (root, component) {
1429
+ if (!root) {
1430
+ throw new Error("Please ensure the DOM element exists before " +
1431
+ "rendering a template into it.")
1432
+ }
1433
+
1434
+ var index = roots.indexOf(root)
1435
+ if (index < 0) index = roots.length
1436
+
1437
+ var isPrevented = false
1438
+ var event = {
1439
+ preventDefault: function () {
1440
+ isPrevented = true
1441
+ computePreRedrawHook = computePostRedrawHook = null
1442
+ }
1443
+ }
1444
+
1445
+ forEach(unloaders, function (unloader) {
1446
+ unloader.handler.call(unloader.controller, event)
1447
+ unloader.controller.onunload = null
1448
+ })
1449
+
1450
+ if (isPrevented) {
1451
+ forEach(unloaders, function (unloader) {
1452
+ unloader.controller.onunload = unloader.handler
1453
+ })
1454
+ } else {
1455
+ unloaders = []
1456
+ }
1457
+
1458
+ if (controllers[index] && isFunction(controllers[index].onunload)) {
1459
+ controllers[index].onunload(event)
1460
+ }
1461
+
1462
+ return checkPrevented(component, root, index, isPrevented)
1463
+ }
1464
+
1465
+ function removeRootElement(root, index) {
1466
+ roots.splice(index, 1)
1467
+ controllers.splice(index, 1)
1468
+ components.splice(index, 1)
1469
+ reset(root)
1470
+ nodeCache.splice(getCellCacheKey(root), 1)
1471
+ }
1472
+
1473
+ var redrawing = false
1474
+ m.redraw = function (force) {
1475
+ if (redrawing) return
1476
+ redrawing = true
1477
+ if (force) forcing = true
1478
+
1479
+ try {
1480
+ // lastRedrawId is a positive number if a second redraw is requested
1481
+ // before the next animation frame
1482
+ // lastRedrawId is null if it's the first redraw and not an event
1483
+ // handler
1484
+ if (lastRedrawId && !force) {
1485
+ // when setTimeout: only reschedule redraw if time between now
1486
+ // and previous redraw is bigger than a frame, otherwise keep
1487
+ // currently scheduled timeout
1488
+ // when rAF: always reschedule redraw
1489
+ if ($requestAnimationFrame === global.requestAnimationFrame ||
1490
+ new Date() - lastRedrawCallTime > FRAME_BUDGET) {
1491
+ if (lastRedrawId > 0) $cancelAnimationFrame(lastRedrawId)
1492
+ lastRedrawId = $requestAnimationFrame(redraw, FRAME_BUDGET)
1493
+ }
1494
+ } else {
1495
+ redraw()
1496
+ lastRedrawId = $requestAnimationFrame(function () {
1497
+ lastRedrawId = null
1498
+ }, FRAME_BUDGET)
1499
+ }
1500
+ } finally {
1501
+ redrawing = forcing = false
1502
+ }
1503
+ }
1504
+
1505
+ m.redraw.strategy = m.prop()
1506
+ function redraw() {
1507
+ if (computePreRedrawHook) {
1508
+ computePreRedrawHook()
1509
+ computePreRedrawHook = null
1510
+ }
1511
+ forEach(roots, function (root, i) {
1512
+ var component = components[i]
1513
+ if (controllers[i]) {
1514
+ var args = [controllers[i]]
1515
+ m.render(root,
1516
+ component.view ? component.view(controllers[i], args) : "")
1517
+ }
1518
+ })
1519
+ // after rendering within a routed context, we need to scroll back to
1520
+ // the top, and fetch the document title for history.pushState
1521
+ if (computePostRedrawHook) {
1522
+ computePostRedrawHook()
1523
+ computePostRedrawHook = null
1524
+ }
1525
+ lastRedrawId = null
1526
+ lastRedrawCallTime = new Date()
1527
+ m.redraw.strategy("diff")
1528
+ }
1529
+
1530
+ function endFirstComputation() {
1531
+ if (m.redraw.strategy() === "none") {
1532
+ pendingRequests--
1533
+ m.redraw.strategy("diff")
1534
+ } else {
1535
+ m.endComputation()
1536
+ }
1537
+ }
1538
+
1539
+ m.withAttr = function (prop, withAttrCallback, callbackThis) {
1540
+ return function (e) {
1541
+ e = e || window.event
1542
+ /* eslint-disable no-invalid-this */
1543
+ var currentTarget = e.currentTarget || this
1544
+ var _this = callbackThis || this
1545
+ /* eslint-enable no-invalid-this */
1546
+ var target = prop in currentTarget ?
1547
+ currentTarget[prop] :
1548
+ currentTarget.getAttribute(prop)
1549
+ withAttrCallback.call(_this, target)
1550
+ }
1551
+ }
1552
+
1553
+ // routing
1554
+ var modes = {pathname: "", hash: "#", search: "?"}
1555
+ var redirect = noop
1556
+ var isDefaultRoute = false
1557
+ var routeParams, currentRoute
1558
+
1559
+ m.route = function (root, arg1, arg2, vdom) { // eslint-disable-line
1560
+ // m.route()
1561
+ if (arguments.length === 0) return currentRoute
1562
+ // m.route(el, defaultRoute, routes)
1563
+ if (arguments.length === 3 && isString(arg1)) {
1564
+ redirect = function (source) {
1565
+ var path = currentRoute = normalizeRoute(source)
1566
+ if (!routeByValue(root, arg2, path)) {
1567
+ if (isDefaultRoute) {
1568
+ throw new Error("Ensure the default route matches " +
1569
+ "one of the routes defined in m.route")
1570
+ }
1571
+
1572
+ isDefaultRoute = true
1573
+ m.route(arg1, true)
1574
+ isDefaultRoute = false
1575
+ }
1576
+ }
1577
+
1578
+ var listener = m.route.mode === "hash" ?
1579
+ "onhashchange" :
1580
+ "onpopstate"
1581
+
1582
+ global[listener] = function () {
1583
+ var path = $location[m.route.mode]
1584
+ if (m.route.mode === "pathname") path += $location.search
1585
+ if (currentRoute !== normalizeRoute(path)) redirect(path)
1586
+ }
1587
+
1588
+ computePreRedrawHook = setScroll
1589
+ global[listener]()
1590
+
1591
+ return
1592
+ }
1593
+
1594
+ // config: m.route
1595
+ if (root.addEventListener || root.attachEvent) {
1596
+ var base = m.route.mode !== "pathname" ? $location.pathname : ""
1597
+ root.href = base + modes[m.route.mode] + vdom.attrs.href
1598
+ if (root.addEventListener) {
1599
+ root.removeEventListener("click", routeUnobtrusive)
1600
+ root.addEventListener("click", routeUnobtrusive)
1601
+ } else {
1602
+ root.detachEvent("onclick", routeUnobtrusive)
1603
+ root.attachEvent("onclick", routeUnobtrusive)
1604
+ }
1605
+
1606
+ return
1607
+ }
1608
+ // m.route(route, params, shouldReplaceHistoryEntry)
1609
+ if (isString(root)) {
1610
+ var oldRoute = currentRoute
1611
+ currentRoute = root
1612
+
1613
+ var args = arg1 || {}
1614
+ var queryIndex = currentRoute.indexOf("?")
1615
+ var params
1616
+
1617
+ if (queryIndex > -1) {
1618
+ params = parseQueryString(currentRoute.slice(queryIndex + 1))
1619
+ } else {
1620
+ params = {}
1621
+ }
1622
+
1623
+ for (var i in args) {
1624
+ if (hasOwn.call(args, i)) {
1625
+ params[i] = args[i]
1626
+ }
1627
+ }
1628
+
1629
+ var querystring = buildQueryString(params)
1630
+ var currentPath
1631
+
1632
+ if (queryIndex > -1) {
1633
+ currentPath = currentRoute.slice(0, queryIndex)
1634
+ } else {
1635
+ currentPath = currentRoute
1636
+ }
1637
+
1638
+ if (querystring) {
1639
+ currentRoute = currentPath +
1640
+ (currentPath.indexOf("?") === -1 ? "?" : "&") +
1641
+ querystring
1642
+ }
1643
+
1644
+ var replaceHistory =
1645
+ (arguments.length === 3 ? arg2 : arg1) === true ||
1646
+ oldRoute === root
1647
+
1648
+ if (global.history.pushState) {
1649
+ var method = replaceHistory ? "replaceState" : "pushState"
1650
+ computePreRedrawHook = setScroll
1651
+ computePostRedrawHook = function () {
1652
+ try {
1653
+ global.history[method](null, $document.title,
1654
+ modes[m.route.mode] + currentRoute)
1655
+ } catch (err) {
1656
+ // In the event of a pushState or replaceState failure,
1657
+ // fallback to a standard redirect. This is specifically
1658
+ // to address a Safari security error when attempting to
1659
+ // call pushState more than 100 times.
1660
+ $location[m.route.mode] = currentRoute
1661
+ }
1662
+ }
1663
+ redirect(modes[m.route.mode] + currentRoute)
1664
+ } else {
1665
+ $location[m.route.mode] = currentRoute
1666
+ redirect(modes[m.route.mode] + currentRoute)
1667
+ }
1668
+ }
1669
+ }
1670
+
1671
+ m.route.param = function (key) {
1672
+ if (!routeParams) {
1673
+ throw new Error("You must call m.route(element, defaultRoute, " +
1674
+ "routes) before calling m.route.param()")
1675
+ }
1676
+
1677
+ if (!key) {
1678
+ return routeParams
1679
+ }
1680
+
1681
+ return routeParams[key]
1682
+ }
1683
+
1684
+ m.route.mode = "search"
1685
+
1686
+ function normalizeRoute(route) {
1687
+ return route.slice(modes[m.route.mode].length)
1688
+ }
1689
+
1690
+ function routeByValue(root, router, path) {
1691
+ routeParams = {}
1692
+
1693
+ var queryStart = path.indexOf("?")
1694
+ if (queryStart !== -1) {
1695
+ routeParams = parseQueryString(
1696
+ path.substr(queryStart + 1, path.length))
1697
+ path = path.substr(0, queryStart)
1698
+ }
1699
+
1700
+ // Get all routes and check if there's
1701
+ // an exact match for the current path
1702
+ var keys = Object.keys(router)
1703
+ var index = keys.indexOf(path)
1704
+
1705
+ if (index !== -1){
1706
+ m.mount(root, router[keys [index]])
1707
+ return true
1708
+ }
1709
+
1710
+ for (var route in router) {
1711
+ if (hasOwn.call(router, route)) {
1712
+ if (route === path) {
1713
+ m.mount(root, router[route])
1714
+ return true
1715
+ }
1716
+
1717
+ var matcher = new RegExp("^" + route
1718
+ .replace(/:[^\/]+?\.{3}/g, "(.*?)")
1719
+ .replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$")
1720
+
1721
+ if (matcher.test(path)) {
1722
+ /* eslint-disable no-loop-func */
1723
+ path.replace(matcher, function () {
1724
+ var keys = route.match(/:[^\/]+/g) || []
1725
+ var values = [].slice.call(arguments, 1, -2)
1726
+ forEach(keys, function (key, i) {
1727
+ routeParams[key.replace(/:|\./g, "")] =
1728
+ decodeURIComponent(values[i])
1729
+ })
1730
+ m.mount(root, router[route])
1731
+ })
1732
+ /* eslint-enable no-loop-func */
1733
+ return true
1734
+ }
1735
+ }
1736
+ }
1737
+ }
1738
+
1739
+ function routeUnobtrusive(e) {
1740
+ e = e || event
1741
+ if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return
1742
+
1743
+ if (e.preventDefault) {
1744
+ e.preventDefault()
1745
+ } else {
1746
+ e.returnValue = false
1747
+ }
1748
+
1749
+ var currentTarget = e.currentTarget || e.srcElement
1750
+ var args
1751
+
1752
+ if (m.route.mode === "pathname" && currentTarget.search) {
1753
+ args = parseQueryString(currentTarget.search.slice(1))
1754
+ } else {
1755
+ args = {}
1756
+ }
1757
+
1758
+ while (currentTarget && !/a/i.test(currentTarget.nodeName)) {
1759
+ currentTarget = currentTarget.parentNode
1760
+ }
1761
+
1762
+ // clear pendingRequests because we want an immediate route change
1763
+ pendingRequests = 0
1764
+ m.route(currentTarget[m.route.mode]
1765
+ .slice(modes[m.route.mode].length), args)
1766
+ }
1767
+
1768
+ function setScroll() {
1769
+ if (m.route.mode !== "hash" && $location.hash) {
1770
+ $location.hash = $location.hash
1771
+ } else {
1772
+ global.scrollTo(0, 0)
1773
+ }
1774
+ }
1775
+
1776
+ function buildQueryString(object, prefix) {
1777
+ var duplicates = {}
1778
+ var str = []
1779
+
1780
+ for (var prop in object) {
1781
+ if (hasOwn.call(object, prop)) {
1782
+ var key = prefix ? prefix + "[" + prop + "]" : prop
1783
+ var value = object[prop]
1784
+
1785
+ if (value === null) {
1786
+ str.push(encodeURIComponent(key))
1787
+ } else if (isObject(value)) {
1788
+ str.push(buildQueryString(value, key))
1789
+ } else if (isArray(value)) {
1790
+ var keys = []
1791
+ duplicates[key] = duplicates[key] || {}
1792
+ /* eslint-disable no-loop-func */
1793
+ forEach(value, function (item) {
1794
+ /* eslint-enable no-loop-func */
1795
+ if (!duplicates[key][item]) {
1796
+ duplicates[key][item] = true
1797
+ keys.push(encodeURIComponent(key) + "=" +
1798
+ encodeURIComponent(item))
1799
+ }
1800
+ })
1801
+ str.push(keys.join("&"))
1802
+ } else if (value !== undefined) {
1803
+ str.push(encodeURIComponent(key) + "=" +
1804
+ encodeURIComponent(value))
1805
+ }
1806
+ }
1807
+ }
1808
+
1809
+ return str.join("&")
1810
+ }
1811
+
1812
+ function parseQueryString(str) {
1813
+ if (str === "" || str == null) return {}
1814
+ if (str.charAt(0) === "?") str = str.slice(1)
1815
+
1816
+ var pairs = str.split("&")
1817
+ var params = {}
1818
+
1819
+ forEach(pairs, function (string) {
1820
+ var pair = string.split("=")
1821
+ var key = decodeURIComponent(pair[0])
1822
+ var value = pair.length === 2 ? decodeURIComponent(pair[1]) : null
1823
+ if (params[key] != null) {
1824
+ if (!isArray(params[key])) params[key] = [params[key]]
1825
+ params[key].push(value)
1826
+ }
1827
+ else params[key] = value
1828
+ })
1829
+
1830
+ return params
1831
+ }
1832
+
1833
+ m.route.buildQueryString = buildQueryString
1834
+ m.route.parseQueryString = parseQueryString
1835
+
1836
+ function reset(root) {
1837
+ var cacheKey = getCellCacheKey(root)
1838
+ clear(root.childNodes, cellCache[cacheKey])
1839
+ cellCache[cacheKey] = undefined
1840
+ }
1841
+
1842
+ m.deferred = function () {
1843
+ var deferred = new Deferred()
1844
+ deferred.promise = propify(deferred.promise)
1845
+ return deferred
1846
+ }
1847
+
1848
+ function propify(promise, initialValue) {
1849
+ var prop = m.prop(initialValue)
1850
+ promise.then(prop)
1851
+ prop.then = function (resolve, reject) {
1852
+ return propify(promise.then(resolve, reject), initialValue)
1853
+ }
1854
+
1855
+ prop.catch = prop.then.bind(null, null)
1856
+ return prop
1857
+ }
1858
+ // Promiz.mithril.js | Zolmeister | MIT
1859
+ // a modified version of Promiz.js, which does not conform to Promises/A+
1860
+ // for two reasons:
1861
+ //
1862
+ // 1) `then` callbacks are called synchronously (because setTimeout is too
1863
+ // slow, and the setImmediate polyfill is too big
1864
+ //
1865
+ // 2) throwing subclasses of Error cause the error to be bubbled up instead
1866
+ // of triggering rejection (because the spec does not account for the
1867
+ // important use case of default browser error handling, i.e. message w/
1868
+ // line number)
1869
+
1870
+ var RESOLVING = 1
1871
+ var REJECTING = 2
1872
+ var RESOLVED = 3
1873
+ var REJECTED = 4
1874
+
1875
+ function Deferred(onSuccess, onFailure) {
1876
+ var self = this
1877
+ var state = 0
1878
+ var promiseValue = 0
1879
+ var next = []
1880
+
1881
+ self.promise = {}
1882
+
1883
+ self.resolve = function (value) {
1884
+ if (!state) {
1885
+ promiseValue = value
1886
+ state = RESOLVING
1887
+
1888
+ fire()
1889
+ }
1890
+
1891
+ return self
1892
+ }
1893
+
1894
+ self.reject = function (value) {
1895
+ if (!state) {
1896
+ promiseValue = value
1897
+ state = REJECTING
1898
+
1899
+ fire()
1900
+ }
1901
+
1902
+ return self
1903
+ }
1904
+
1905
+ self.promise.then = function (onSuccess, onFailure) {
1906
+ var deferred = new Deferred(onSuccess, onFailure)
1907
+
1908
+ if (state === RESOLVED) {
1909
+ deferred.resolve(promiseValue)
1910
+ } else if (state === REJECTED) {
1911
+ deferred.reject(promiseValue)
1912
+ } else {
1913
+ next.push(deferred)
1914
+ }
1915
+
1916
+ return deferred.promise
1917
+ }
1918
+
1919
+ function finish(type) {
1920
+ state = type || REJECTED
1921
+ next.map(function (deferred) {
1922
+ if (state === RESOLVED) {
1923
+ deferred.resolve(promiseValue)
1924
+ } else {
1925
+ deferred.reject(promiseValue)
1926
+ }
1927
+ })
1928
+ }
1929
+
1930
+ function thennable(then, success, failure, notThennable) {
1931
+ if (((promiseValue != null && isObject(promiseValue)) ||
1932
+ isFunction(promiseValue)) && isFunction(then)) {
1933
+ try {
1934
+ // count protects against abuse calls from spec checker
1935
+ var count = 0
1936
+ then.call(promiseValue, function (value) {
1937
+ if (count++) return
1938
+ promiseValue = value
1939
+ success()
1940
+ }, function (value) {
1941
+ if (count++) return
1942
+ promiseValue = value
1943
+ failure()
1944
+ })
1945
+ } catch (e) {
1946
+ m.deferred.onerror(e)
1947
+ promiseValue = e
1948
+ failure()
1949
+ }
1950
+ } else {
1951
+ notThennable()
1952
+ }
1953
+ }
1954
+
1955
+ function fire() {
1956
+ // check if it's a thenable
1957
+ var then
1958
+ try {
1959
+ then = promiseValue && promiseValue.then
1960
+ } catch (e) {
1961
+ m.deferred.onerror(e)
1962
+ promiseValue = e
1963
+ state = REJECTING
1964
+ return fire()
1965
+ }
1966
+
1967
+ if (state === REJECTING) {
1968
+ m.deferred.onerror(promiseValue)
1969
+ }
1970
+
1971
+ thennable(then, function () {
1972
+ state = RESOLVING
1973
+ fire()
1974
+ }, function () {
1975
+ state = REJECTING
1976
+ fire()
1977
+ }, function () {
1978
+ try {
1979
+ if (state === RESOLVING && isFunction(onSuccess)) {
1980
+ promiseValue = onSuccess(promiseValue)
1981
+ } else if (state === REJECTING && isFunction(onFailure)) {
1982
+ promiseValue = onFailure(promiseValue)
1983
+ state = RESOLVING
1984
+ }
1985
+ } catch (e) {
1986
+ m.deferred.onerror(e)
1987
+ promiseValue = e
1988
+ return finish()
1989
+ }
1990
+
1991
+ if (promiseValue === self) {
1992
+ promiseValue = TypeError()
1993
+ finish()
1994
+ } else {
1995
+ thennable(then, function () {
1996
+ finish(RESOLVED)
1997
+ }, finish, function () {
1998
+ finish(state === RESOLVING && RESOLVED)
1999
+ })
2000
+ }
2001
+ })
2002
+ }
2003
+ }
2004
+
2005
+ m.deferred.onerror = function (e) {
2006
+ if (type.call(e) === "[object Error]" &&
2007
+ !/ Error/.test(e.constructor.toString())) {
2008
+ pendingRequests = 0
2009
+ throw e
2010
+ }
2011
+ }
2012
+
2013
+ m.sync = function (args) {
2014
+ var deferred = m.deferred()
2015
+ var outstanding = args.length
2016
+ var results = []
2017
+ var method = "resolve"
2018
+
2019
+ function synchronizer(pos, resolved) {
2020
+ return function (value) {
2021
+ results[pos] = value
2022
+ if (!resolved) method = "reject"
2023
+ if (--outstanding === 0) {
2024
+ deferred.promise(results)
2025
+ deferred[method](results)
2026
+ }
2027
+ return value
2028
+ }
2029
+ }
2030
+
2031
+ if (args.length > 0) {
2032
+ forEach(args, function (arg, i) {
2033
+ arg.then(synchronizer(i, true), synchronizer(i, false))
2034
+ })
2035
+ } else {
2036
+ deferred.resolve([])
2037
+ }
2038
+
2039
+ return deferred.promise
2040
+ }
2041
+
2042
+ function identity(value) { return value }
2043
+
2044
+ function handleJsonp(options) {
2045
+ var callbackKey = options.callbackName || "mithril_callback_" +
2046
+ new Date().getTime() + "_" +
2047
+ (Math.round(Math.random() * 1e16)).toString(36)
2048
+
2049
+ var script = $document.createElement("script")
2050
+
2051
+ global[callbackKey] = function (resp) {
2052
+ script.parentNode.removeChild(script)
2053
+ options.onload({
2054
+ type: "load",
2055
+ target: {
2056
+ responseText: resp
2057
+ }
2058
+ })
2059
+ global[callbackKey] = undefined
2060
+ }
2061
+
2062
+ script.onerror = function () {
2063
+ script.parentNode.removeChild(script)
2064
+
2065
+ options.onerror({
2066
+ type: "error",
2067
+ target: {
2068
+ status: 500,
2069
+ responseText: JSON.stringify({
2070
+ error: "Error making jsonp request"
2071
+ })
2072
+ }
2073
+ })
2074
+ global[callbackKey] = undefined
2075
+
2076
+ return false
2077
+ }
2078
+
2079
+ script.onload = function () {
2080
+ return false
2081
+ }
2082
+
2083
+ script.src = options.url +
2084
+ (options.url.indexOf("?") > 0 ? "&" : "?") +
2085
+ (options.callbackKey ? options.callbackKey : "callback") +
2086
+ "=" + callbackKey +
2087
+ "&" + buildQueryString(options.data || {})
2088
+
2089
+ $document.body.appendChild(script)
2090
+ }
2091
+
2092
+ function createXhr(options) {
2093
+ var xhr = new global.XMLHttpRequest()
2094
+ xhr.open(options.method, options.url, true, options.user,
2095
+ options.password)
2096
+
2097
+ xhr.onreadystatechange = function () {
2098
+ if (xhr.readyState === 4) {
2099
+ if (xhr.status >= 200 && xhr.status < 300) {
2100
+ options.onload({type: "load", target: xhr})
2101
+ } else {
2102
+ options.onerror({type: "error", target: xhr})
2103
+ }
2104
+ }
2105
+ }
2106
+
2107
+ if (options.serialize === JSON.stringify &&
2108
+ options.data &&
2109
+ options.method !== "GET") {
2110
+ xhr.setRequestHeader("Content-Type",
2111
+ "application/json; charset=utf-8")
2112
+ }
2113
+
2114
+ if (options.deserialize === JSON.parse) {
2115
+ xhr.setRequestHeader("Accept", "application/json, text/*")
2116
+ }
2117
+
2118
+ if (isFunction(options.config)) {
2119
+ var maybeXhr = options.config(xhr, options)
2120
+ if (maybeXhr != null) xhr = maybeXhr
2121
+ }
2122
+
2123
+ var data = options.method === "GET" || !options.data ? "" : options.data
2124
+
2125
+ if (data && !isString(data) && data.constructor !== global.FormData) {
2126
+ throw new Error("Request data should be either be a string or " +
2127
+ "FormData. Check the `serialize` option in `m.request`")
2128
+ }
2129
+
2130
+ xhr.send(data)
2131
+ return xhr
2132
+ }
2133
+
2134
+ function ajax(options) {
2135
+ if (options.dataType && options.dataType.toLowerCase() === "jsonp") {
2136
+ return handleJsonp(options)
2137
+ } else {
2138
+ return createXhr(options)
2139
+ }
2140
+ }
2141
+
2142
+ function bindData(options, data, serialize) {
2143
+ if (options.method === "GET" && options.dataType !== "jsonp") {
2144
+ var prefix = options.url.indexOf("?") < 0 ? "?" : "&"
2145
+ var querystring = buildQueryString(data)
2146
+ options.url += (querystring ? prefix + querystring : "")
2147
+ } else {
2148
+ options.data = serialize(data)
2149
+ }
2150
+ }
2151
+
2152
+ function parameterizeUrl(url, data) {
2153
+ if (data) {
2154
+ url = url.replace(/:[a-z]\w+/gi, function (token){
2155
+ var key = token.slice(1)
2156
+ var value = data[key] || token
2157
+ delete data[key]
2158
+ return value
2159
+ })
2160
+ }
2161
+ return url
2162
+ }
2163
+
2164
+ m.request = function (options) {
2165
+ if (options.background !== true) m.startComputation()
2166
+ var deferred = new Deferred()
2167
+ var isJSONP = options.dataType &&
2168
+ options.dataType.toLowerCase() === "jsonp"
2169
+
2170
+ var serialize, deserialize, extract
2171
+
2172
+ if (isJSONP) {
2173
+ serialize = options.serialize =
2174
+ deserialize = options.deserialize = identity
2175
+
2176
+ extract = function (jsonp) { return jsonp.responseText }
2177
+ } else {
2178
+ serialize = options.serialize = options.serialize || JSON.stringify
2179
+
2180
+ deserialize = options.deserialize =
2181
+ options.deserialize || JSON.parse
2182
+ extract = options.extract || function (xhr) {
2183
+ if (xhr.responseText.length || deserialize !== JSON.parse) {
2184
+ return xhr.responseText
2185
+ } else {
2186
+ return null
2187
+ }
2188
+ }
2189
+ }
2190
+
2191
+ options.method = (options.method || "GET").toUpperCase()
2192
+ options.url = parameterizeUrl(options.url, options.data)
2193
+ bindData(options, options.data, serialize)
2194
+ options.onload = options.onerror = function (ev) {
2195
+ try {
2196
+ ev = ev || event
2197
+ var response = deserialize(extract(ev.target, options))
2198
+ if (ev.type === "load") {
2199
+ if (options.unwrapSuccess) {
2200
+ response = options.unwrapSuccess(response, ev.target)
2201
+ }
2202
+
2203
+ if (isArray(response) && options.type) {
2204
+ forEach(response, function (res, i) {
2205
+ response[i] = new options.type(res)
2206
+ })
2207
+ } else if (options.type) {
2208
+ response = new options.type(response)
2209
+ }
2210
+
2211
+ deferred.resolve(response)
2212
+ } else {
2213
+ if (options.unwrapError) {
2214
+ response = options.unwrapError(response, ev.target)
2215
+ }
2216
+
2217
+ deferred.reject(response)
2218
+ }
2219
+ } catch (e) {
2220
+ deferred.reject(e)
2221
+ m.deferred.onerror(e)
2222
+ } finally {
2223
+ if (options.background !== true) m.endComputation()
2224
+ }
2225
+ }
2226
+
2227
+ ajax(options)
2228
+ deferred.promise = propify(deferred.promise, options.initialValue)
2229
+ return deferred.promise
2230
+ }
2231
+
2232
+ return m
2233
+ }); // eslint-disable-line