rbbt-rest 1.7.19 → 1.7.20

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