avalon-mmRouter-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,771 @@
1
+ define("mmState", ["mmPromise", "mmRouter"], function() {
2
+ //重写mmRouter中的route方法
3
+ avalon.router.route = function(method, path, query, options) {
4
+ path = path.trim()
5
+ var states = this.routingTable[method]
6
+ var currentState = mmState.currentState
7
+ for (var i = 0, el; el = states[i++]; ) {//el为一个个状态对象,状态对象的callback总是返回一个Promise
8
+ var args = path.match(el.regexp)
9
+ if (args && el.abstract !== true) {//不能是抽象状态
10
+ mmState.query = query || {}
11
+ el.path = path
12
+ el.params = {}
13
+ var keys = el.keys
14
+ args.shift()
15
+ if (keys.length) {
16
+ this._parseArgs(args, el)
17
+ }
18
+ if (el.stateName) {
19
+ mmState.transitionTo(currentState, el, args, options)
20
+ } else {
21
+ el.callback.apply(el, args)
22
+ }
23
+ return
24
+ }
25
+ }
26
+ if (this.errorback) {
27
+ this.errorback()
28
+ }
29
+ }
30
+ /*
31
+ * @interface avalon.router.go 跳转到一个已定义状态上,params对参数对象
32
+ * @param toName 状态name
33
+ * @param params 附加参数
34
+ * @param params.query 在hash后面附加的类似search'的参数对
35
+ * @param options 扩展配置
36
+ * @param options.reload true强制reload,即便url、参数并未发生变化
37
+ * @param options.replace true替换history,否则生成一条新的历史记录
38
+ * @param options.replaceQuery true表示完全覆盖query,而不是merge,默认为false,为true则会用params指定的query去清空
39
+ */
40
+ avalon.router.go = function(toName, params, options) {
41
+ var from = mmState.currentState, to = getStateByName(toName), replaceQuery = options && options.replaceQuery, params = params || {}
42
+ if (to) {
43
+ // params is not defined or is {}
44
+ if(!to.params || !mmState.isParamsChanged(to.params, {})) {
45
+ to.params = avalon.mix({}, to.parentState ? to.parentState.params || {} : {})
46
+ }
47
+ // query is shared by all states
48
+ mmState.query = avalon.mix({}, replaceQuery ? {} : mmState.query || {}, params.query || {})
49
+ // merge params
50
+ var args = to.keys.map(function(el) {
51
+ return params[el.name] || to.params[el.name] || ""
52
+ })
53
+ avalon.router._parseArgs(args, to)
54
+ mmState.transitionTo(from, to, args, options)
55
+ }
56
+ }
57
+ // 事件管理器
58
+ var Event = window.$eventManager = avalon.define("$eventManager", function(vm) {
59
+ vm.$flag = 0;
60
+ vm.uiqKey = function() {
61
+ vm.$flag++
62
+ return "flag" + vm.$flag++
63
+ }
64
+ })
65
+ Event.$watch("onLoad", function(e) {
66
+ var _onloadCallback = mmState._onloadCallback
67
+ mmState._onloadCallback = {}
68
+ for(var i in _onloadCallback) {
69
+ _onloadCallback[i].apply(arguments[0], [].slice.call(arguments, 2))
70
+ }
71
+ })
72
+ function removeOld() {
73
+ var nodes = mmState.oldNodes
74
+ while(nodes.length) {
75
+ var i = nodes.length - 1,
76
+ node = nodes[i]
77
+ node.parentNode && node.parentNode.removeChild(node)
78
+ nodes.splice(i, 1)
79
+ }
80
+ }
81
+ Event.$watch("onAbort", removeOld)
82
+ var mmState = window.mmState = {
83
+ prevState: NaN,
84
+ currentState: NaN, // 当前状态,可能还未切换到该状态
85
+ activeState: NaN, // 当前实际处于的状态
86
+ oldQuery: {},
87
+ query: {},
88
+ oldNodes: [],
89
+ _onloadCallback: {},
90
+ addCallback: function(key, func) {
91
+ if(!this._onloadCallback[key]) this._onloadCallback[key] = func
92
+ },
93
+ removeCallback: function(key) {
94
+ this._onloadCallback[key] = avalon.noop
95
+ },
96
+ // params changed
97
+ isParamsChanged: function(p, op) {
98
+ var isQuery = p == void 0,
99
+ p = isQuery ? this.query : p,
100
+ op = isQuery ? this.oldQuery : op,
101
+ res = false
102
+ for(var i in p) {
103
+ if(!(i in op) || op[i] != p[i]) {
104
+ res = true
105
+ break
106
+ }
107
+ }
108
+ if(!res) {
109
+ for(var i in op) {
110
+ if(!(i in p) || op[i] != p[i]) {
111
+ res = true
112
+ break
113
+ }
114
+ }
115
+ }
116
+ if(res && isQuery) this.oldQuery = avalon.mix({}, this.query)
117
+ return res
118
+ },
119
+ // 状态离开
120
+ popState: function(end, args, callback) {
121
+ callback = callback || avalon.noop
122
+ if(!this.activeState || end === this.activeState) return callback()
123
+ this.popOne(end, args, callback)
124
+ },
125
+ popOne: function(end, args, callback) {
126
+ var cur = this.activeState, me = this
127
+ if(end === this.activeState || !cur) return callback()
128
+ // 阻止退出
129
+ if(cur.onBeforeUnload() === false) return callback(false)
130
+ // 如果没有父状态了,说明已经退出到最上层,需要退出,不再继续迭代
131
+ me.activeState = cur.parentState || NaN
132
+ cur.done = function(success) {
133
+ cur._pending = false
134
+ cur.done = null
135
+ if(success !== false) {
136
+ if(me.activeState) return me.popOne(end, args, callback)
137
+ }
138
+ return callback(success)
139
+ }
140
+ var success = cur.onAfterUnload()
141
+ if(!cur._pending && cur.done) cur.done(success)
142
+ },
143
+ // 状态进入
144
+ pushState: function(end, args, callback){
145
+ callback = callback || avalon.noop
146
+ var active = this.activeState
147
+ // 切换到目标状态
148
+ if(active == end) return callback()
149
+ var chain = [] // 状态链
150
+ var _local = this.currentState._local
151
+ while(end !== active && end){
152
+ chain.push(end)
153
+ var tmp = end
154
+ end = end.parentState
155
+ // 拷贝父状态的视图文件
156
+ if(end) tmp._local.template = avalon.mix({}, end._local.template, tmp._local.template)
157
+ }
158
+ // merge模板
159
+ for(var i = chain.length - 1; i > -1; i--) {
160
+ var cur = chain[i]
161
+ var cv = cur.views
162
+ avalon.router._parseArgs(args, cur)
163
+ if(cv) {
164
+ for(var n in cv) {
165
+ var pts = n.split("@"),
166
+ viewName = pts[0],
167
+ statename = (pts.length == 2 ? pts[1] : cur.stateName) || ""
168
+ _local.template[viewName + "@" + (statename || (viewName ? "" : cur.stateName || ""))] = cv[n]
169
+ // 没有模板名字,却指定了其他的状态名,创建一个别名
170
+ if(!viewName && statename && statename != cur.stateName) _local.template["@" + cur.stateName] = cv[n]
171
+ }
172
+ }
173
+ }
174
+ // 逐一迭代
175
+ this.pushOne(chain, args, callback)
176
+ },
177
+ pushOne: function(chain, args, callback) {
178
+ var cur = chain.pop(), me = this
179
+ // 退出
180
+ if(!cur) return callback()
181
+ // 阻止进入该状态
182
+ if(cur.onBeforeChange() === false) return callback(false)
183
+ me.activeState = cur // 更新当前实际处于的状态
184
+ cur.done = function(success) {
185
+ // 防止async处触发已经销毁的done
186
+ if(!cur.done) return
187
+ cur._pending = false
188
+ cur.done = null
189
+ cur.visited = true
190
+ // 退出
191
+ if(success === false) {
192
+ cur.callback.apply(cur, args)
193
+ return callback(success)
194
+ }
195
+ new Promise(function(resolve) {
196
+ resolve()
197
+ }).then(function() {
198
+ // view的load,切换以及scan
199
+ return cur.callback.apply(cur, args)
200
+ }).done(function() {
201
+ // 继续状态链
202
+ me.pushOne(chain, args, callback)
203
+ })
204
+ }
205
+ cur.oldParams = avalon.mix({}, cur.params)
206
+ // 一般在这个回调里准备数据
207
+ cur._onChange.apply(cur, args)
208
+ if(!cur._pending && cur.done) cur.done()
209
+ },
210
+ transitionTo: function(fromState, toState, args, options) {
211
+ // 状态机正处于切换过程中
212
+ var abort
213
+ if(this.activeState && (this.activeState != this.currentState)) {
214
+ avalon.log("navigating to [" + this.currentState.stateName + "] will be stopped, redirect to [" + toState.stateName + "] now")
215
+ this.activeState.done && this.activeState.done(!"stopped")
216
+ fromState = this.activeState // 更新实际的fromState
217
+ abort = true
218
+ }
219
+
220
+ var info = avalon.router.urlFormate(toState.url, toState.params, mmState.query),
221
+ me = this,
222
+ options = options || {},
223
+ // 是否强制reload或者query发生变化,参照angular,这个时候会触发整个页面重刷
224
+ reload = options.reload || this.isParamsChanged(),
225
+ over,
226
+ // 找共同的父节点,那么也是需要参考params和reload,reload情况,将所有栈里的state全部退出,否则退出到params没有发生变化的地方
227
+ commonParent = reload ? NaN : findCommonBranch(fromState, toState, args),
228
+ done = function(success) {
229
+ if(over) return
230
+ over = true
231
+ me.currentState = me.activeState
232
+ if(success !== false) {
233
+ avalon.log("transitionTo " + toState.stateName + " success")
234
+ callStateFunc("onLoad", me, fromState, toState)
235
+ } else if(options.fromHistory){
236
+ var cur = me.currentState
237
+ info = avalon.router.urlFormate(cur.url, cur.params, mmState.query)
238
+ } else {
239
+ info = null
240
+ }
241
+ if(info && avalon.history) avalon.history.updateLocation(info.path + info.query, avalon.mix({}, options, {silent: true}))
242
+ }
243
+ toState.path = ("/" + info.path).replace(/^[\/]{2,}/g, "/")
244
+ if(!reload && fromState == toState && !mmState.isParamsChanged(toState.oldParams, toState.params)) {
245
+ // redirect的目的状态 == this.activeState && abort
246
+ if(toState == this.activeState && abort) return done()
247
+ // 重复点击直接return
248
+ return
249
+ }
250
+ if(callStateFunc("onBeforeUnload", this, fromState, toState) === false) {
251
+ if(fromState) done(false)
252
+ return callStateFunc("onAbort", this, fromState, toState)
253
+ }
254
+ if(over === true) {
255
+ return
256
+ }
257
+ avalon.log("begin transitionTo " + toState.stateName + " from " + (fromState && fromState.stateName || "unknown"))
258
+ callStateFunc("onUnload", this, fromState, toState)
259
+ this.currentState = toState
260
+ this.prevState = fromState
261
+ callStateFunc("onBegin", this, fromState, toState)
262
+ this.popState(commonParent, args, function(success) {
263
+ // 中断
264
+ if(success === false) return done(success)
265
+ me.pushState(toState, args, done)
266
+ })
267
+ }
268
+ }
269
+ //【avalon.state】的辅助函数,用于收集可用于扫描的vmodels
270
+ function getVModels(opts) {
271
+ var array = []
272
+ function getVModel(opts, array) {
273
+ var ctrl = opts.controller
274
+ if (avalon.vmodels[ctrl]) {
275
+ avalon.Array.ensure(array, avalon.vmodels[ctrl])
276
+ }
277
+ if (opts.parentState) {
278
+ getVModel(opts.parentState, array)
279
+ }
280
+ }
281
+ getVModel(opts, array)
282
+ return array
283
+ }
284
+ //将template,templateUrl,templateProvider,onBeforeLoad,onAfterLoad这五个属性从opts对象拷贝到新生成的view对象上的
285
+ function copyTemplateProperty(newObj, oldObj, name) {
286
+ newObj[name] = oldObj[name]
287
+ delete oldObj[name]
288
+ }
289
+ function viewAnimate(oldNode, topCtrlName, statename, me) {
290
+ var $node = avalon(oldNode)
291
+ if($node.hasClass("oni-mmRouter-slide")) {
292
+ var node = oldNode.cloneNode(true)
293
+ avalon(node).addClass("oni-mmRouter-enter")
294
+ $node.addClass("oni-mmRouter-leave")
295
+ oldNode.removeAttribute("ms-view")
296
+ avalon.each(getViews(topCtrlName, statename, oldNode), function(i, n) {
297
+ n.removeAttribute("ms-view")
298
+ })
299
+ oldNode.parentNode.insertBefore(node, oldNode.nextSibling)
300
+ callStateFunc("onViewEnter", me, node, oldNode)
301
+ return node
302
+ }
303
+ return oldNode
304
+ }
305
+ /*
306
+ * @interface avalon.state 对avalon.router.get 进行重新封装,生成一个状态对象
307
+ * @param stateName: 指定当前状态名
308
+ * @param opts 配置
309
+ * @param opts.url: 当前状态对应的路径规则,与祖先状态们组成一个完整的匹配规则
310
+ * @param opts.controller: 指定当前所在的VM的名字(如果是顶级状态对象,必须指定)
311
+ * @param opts.views: 如果不写views属性,则默认view为"",对多个[ms-view]容器进行处理,每个对象应拥有template, templateUrl, templateProvider
312
+ * @param opts.views.template 指定当前模板,也可以为一个函数,传入opts.params作参数
313
+ * @param opts.views.templateUrl 指定当前模板的路径,也可以为一个函数,传入opts.params作参数
314
+ * @param opts.views.templateProvider 指定当前模板的提供者,它可以是一个Promise,也可以为一个函数,传入opts.params作参数
315
+ * views的结构为
316
+ *<pre>
317
+ * {
318
+ * "": {template: "xxx"}
319
+ * "aaa": {template: "xxx"}
320
+ * "bbb@": {template: "xxx"}
321
+ * }
322
+ *</pre>
323
+ * views的每个键名(keyname)的结构为viewname@statename,
324
+ * 如果名字不存在@,则viewname直接为keyname,statename为opts.stateName
325
+ * 如果名字存在@, viewname为match[0], statename为match[1]
326
+ * @param opts.onBeforeLoad 模板还没有插入DOM树执行的回调,this指向[ms-view]元素节点集合,参数为关联的state对象
327
+ * @param opts.onAfterLoad 模板插入DOM树执行的回调,this指向[ms-view]元素节点,参数为为关联的state对象
328
+ * @param opts.onBeforeChange 切入某个state之前触发,this指向对应的state,如果return false则会中断并退出整个状态机
329
+ * @param opts.onChange 当切换为当前状态时调用的回调,this指向状态对象,参数为匹配的参数, 我们可以在此方法 定义此模板用到的VM, 或修改VM的属性
330
+ * @param opts.onBeforeUnload state退出前触发,this指向对应的state,如果return false则会中断并退出整个状态机
331
+ * @param opts.onAfterUnload 退出后触发,this指向对应的state
332
+ * @param opts.abstract 表示它不参与匹配,this指向对应的state
333
+ * @param {private} opts.parentState 父状态对象(框架内部生成)
334
+ */
335
+ avalon.state = function(stateName, opts) {
336
+ var parent = getParent(stateName), state = StateModel(stateName, opts), defKey = "viewDefaultInnerHTMLKey"
337
+ if (state.url === void 0) {
338
+ state.abstract = true
339
+ }
340
+ if (parent) {
341
+ state.url = parent.url + (state.url || "")
342
+ state.parentState = parent
343
+ }
344
+ if (!state.views) {
345
+ var view = {}
346
+ "template,templateUrl,templateProvider,onBeforeLoad,onAfterLoad".replace(/\w+/g, function(prop) {
347
+ copyTemplateProperty(view, state, prop)
348
+ })
349
+ state.views = {
350
+ "": view
351
+ }
352
+ }
353
+ avalon.router.get(state.url, function() {
354
+ var me = this, args = arguments
355
+ var promises = [], nodeList = [], funcList = []
356
+ var vmodes = getVModels(state)
357
+ var topCtrlName = vmodes[vmodes.length - 1]
358
+ if (!topCtrlName) {
359
+ avalon.log("topController不存在")
360
+ return
361
+ }
362
+ topCtrlName = topCtrlName.$id
363
+ var prevState = mmState.prevState && (mmState.prevState.stateName +".")
364
+ var currentState = mmState.currentState
365
+ avalon.each(state.views, function(keyname, view) {
366
+ if (keyname.indexOf("@") >= 0) {
367
+ var match = keyname.split("@")
368
+ var viewname = match[0]
369
+ var statename = match[1]
370
+ } else {
371
+ var viewname = keyname || ""
372
+ var statename = stateName
373
+ }
374
+ var kn = viewname + "@" + statename
375
+ // :模板的状态是当前状态的父状态
376
+ // if(currentState.stateName.indexOf(statename) == 0) {
377
+ if(kn in currentState._local.template) {
378
+ var nodes = getViews(topCtrlName, statename)
379
+ var node = getNamedView(nodes, viewname)
380
+ var warnings = "warning: " + stateName + "状态对象的【" + keyname + "】视图对象" //+ viewname
381
+ // 重置view
382
+ avalon.each(nodes, function(i, _node) {
383
+ var $node = avalon(_node),
384
+ viewname = $node.attr("ms-view")
385
+ vn = viewname + "@" + (viewname ? "" : state.stateName || ""),
386
+ vnCurrent = viewname + "@" + (mmState.currentState.stateName || "")
387
+ // 如何盘点这个节点是否需要回复默认内容
388
+ if((vn in currentState._local.template) || (vnCurrent in currentState._local.template) || $node.hasClass("oni-mmRouter-leave") || avalon.contains(node, _node)) return
389
+ // 通过这种方式方式消除重复刷新
390
+ var _html = $node.data(defKey),
391
+ key = viewKey(_node)
392
+ if (_html) {
393
+ mmState.addCallback(key, function() {
394
+ _node = viewAnimate(_node, topCtrlName, statename, me)
395
+ avalon.innerHTML(_node, _html)
396
+ avalon.scan(node, vmodes)
397
+ })
398
+ }
399
+ })
400
+ if (node) {
401
+ var oldNode = avalon(node),
402
+ key = viewKey(node)
403
+ key && mmState.removeCallback(key)
404
+ node = viewAnimate(node, topCtrlName, statename, me)
405
+ // 需要记下当前view下的所有子view的默认innerHTML
406
+ // 以便在恢复到当前view的时候重置回来
407
+ var _html = avalon(node).data(defKey)
408
+ _html === null && avalon(node).data(defKey, node.innerHTML)
409
+ var promise = fromPromise(view, me.params)
410
+ var modelBindOnNode = node.getAttribute("avalonctrl")
411
+ modelBindOnNode = avalon.vmodels[modelBindOnNode]
412
+ var newVmodes = vmodes && (modelBindOnNode ? [modelBindOnNode] : []).concat(vmodes)
413
+ nodeList.push(node)
414
+ funcList.push(function() {
415
+ promise.then(function(s) {
416
+ avalon.innerHTML(node, s)
417
+ avalon.scan(node, newVmodes)
418
+ }, function(msg) {
419
+ avalon.log(warnings + " " + msg)
420
+ callStateFunc("onLoadError", me, keyname, state)
421
+ })
422
+ })
423
+ promises.push(promise)
424
+ } else {
425
+ avalon.log(warnings + "对应的元素节点不存在")
426
+ }
427
+ }
428
+ })
429
+ getFn(state, "onBeforeLoad").call(nodeList, me)
430
+ avalon.each(funcList, function(key, func) {
431
+ func()
432
+ })
433
+
434
+ return Promise.all(promises).then(function(values) {
435
+ getFn(state, "onAfterLoad").call(nodeList, me)
436
+ })
437
+
438
+ }, state)
439
+
440
+ return this
441
+ }
442
+ avalon.state.onViewEntered = function(newNode, oldNode) {
443
+ if(newNode != oldNode) oldNode.parentNode.removeChild(oldNode)
444
+ }
445
+ /*
446
+ * @interface avalon.state.config 全局配置
447
+ * @param {Object} config 配置对象
448
+ * @param {Function} config.beforeUnload 请使用onBeforeUnload
449
+ * @param {Function} config.onBeforeUnload 开始切前的回调,this指向router对象,第一个参数是fromState,第二个参数是toState,return false可以用来阻止切换进行
450
+ * @param {Function} config.abort 请使用onAbort
451
+ * @param {Function} config.onAbort onBeforeUnload return false之后,触发的回调,this指向mmState对象,参数同onBeforeUnload
452
+ * @param {Function} config.unload 请使用onUnload
453
+ * @param {Function} config.onUnload url切换时候触发,this指向mmState对象,参数同onBeforeUnload
454
+ * @param {Function} config.begin 请使用onBegin
455
+ * @param {Function} config.onBegin 开始切换的回调,this指向mmState对象,参数同onBeforeUnload,如果配置了onBegin,则忽略begin
456
+ * @param {Function} config.onload 请使用onLoad
457
+ * @param {Function} config.onLoad 切换完成并成功,this指向mmState对象,参数同onBeforeUnload
458
+ * @param {Function} config.onViewEnter 视图插入动画函数,有一个默认效果
459
+ * @param {Node} config.onViewEnter.arguments[0] 新视图节点
460
+ * @param {Node} config.onViewEnter.arguments[1] 旧的节点
461
+ * @param {Function} config.onloadError 请使用onLoadError
462
+ * @param {Function} config.onLoadError 加载模板资源出错的回调,this指向对应的state,第一个参数对应的模板配置keyname,第二个参数是对应的state
463
+ */
464
+ avalon.state.config = function(config) {
465
+ avalon.each(config, function(key, func) {
466
+ delete config[key]
467
+ if(key.indexOf("on") !== 0) {
468
+ config["on" + key.replace(/^[a-z]/g, function(mat) {
469
+ return mat.toUpperCase()
470
+ })] = func
471
+ } else {
472
+ config[key.replace(/^on[a-z]/g, function(mat) {
473
+ return "on" + mat.split("")[2].toUpperCase()
474
+ })] = func
475
+ }
476
+ })
477
+ avalon.mix(avalon.state, config || {})
478
+ return this
479
+ }
480
+ function callStateFunc(name, state) {
481
+ Event.$fire.apply(Event, arguments)
482
+ return avalon.state[name] ? avalon.state[name].apply(state || mmState.currentState, [].slice.call(arguments, 2)) : 0
483
+ }
484
+ // 状态原型,所有的状态都要继承这个原型
485
+ function StateModel(stateName, options) {
486
+ if(this instanceof StateModel) {
487
+ this.stateName = stateName
488
+ this._pending = false
489
+ this.visited = false
490
+ this.params = {}
491
+ this.oldParams = {}
492
+ this.keys = []
493
+ this._local = {resolved: null, params: {}, template: {}}
494
+ avalon.mix(this, options)
495
+ } else {
496
+ return new StateModel(stateName, options || {})
497
+ }
498
+ }
499
+ StateModel.prototype = {
500
+ paramsChanged: function() {
501
+ var res = mmState.isParamsChanged(this.oldParams, this.params)
502
+ if(res) this.oldParams = avalon.mix({}, this.params)
503
+ return res
504
+ },
505
+ _onChange: function() {
506
+ this.query = this.getQuery()
507
+ this.onChange.apply(this, arguments)
508
+ },
509
+ /*
510
+ * @interface state.getQuery 获取state的query,等价于state.query
511
+ *<pre>
512
+ * onChange: function() {
513
+ * var query = this.getQuery()
514
+ * or
515
+ * this.query
516
+ * }
517
+ *</pre>
518
+ */
519
+ getQuery: function() {return mmState.query},
520
+ /*
521
+ * @interface state.getParams 获取state的params,等价于state.params
522
+ *<pre>
523
+ * onChange: function() {
524
+ * var params = this.getParams()
525
+ * or
526
+ * this.params
527
+ * }
528
+ *</pre>
529
+ */
530
+ getParams: function() {return this.params},
531
+ /*
532
+ * @interface state.async 表示当前的状态是异步,中断状态chain的继续执行,返回一个done函数,通过done(false)终止状态链的执行,任意其他非false参数,将继续
533
+ *<pre>
534
+ * onChange: function() {
535
+ * var done = this.async()
536
+ * setTimeout(done, 4000)
537
+ * }
538
+ *</pre>
539
+ */
540
+ async: function() {
541
+ // 没有done回调的时候,防止死球
542
+ if(this.done) this._pending = true
543
+ return this.done || avalon.noop
544
+ },
545
+ onBeforeChange: avalon.noop, // 切入某个state之前触发
546
+ onChange: avalon.noop, // 切入触发
547
+ onBeforeLoad: avalon.noop, // 所有资源开始加载前触发
548
+ onAfterLoad: avalon.noop, // 加载后触发
549
+ onBeforeUnload: avalon.noop, // state退出前触发
550
+ onAfterUnload: avalon.noop // 退出后触发
551
+ }
552
+ //【avalon.state】的辅助函数,确保返回的是函数
553
+ function getFn(object, name) {
554
+ return typeof object[name] === "function" ? object[name] : avalon.noop
555
+ }
556
+ // 找共同的父状态,注意但a是b孩子或者父亲的时候,是需要取parent.parent
557
+ function findCommonBranch(fromState, toState, args) {
558
+ var a = fromState && fromState.stateName || "",
559
+ b = toState.stateName || "",
560
+ partsA = a.split("."),
561
+ partsB = b.split("."),
562
+ i = 0,
563
+ lenA = partsA.length
564
+ for(; i < lenA; i++) {
565
+ if(partsA[i] == partsB[i]) {
566
+ continue
567
+ }
568
+ break
569
+ }
570
+ // 存在父子关系
571
+ if(i == lenA || i == partsB.length) {
572
+ if(i < 1) return NaN
573
+ // 继续向上寻找到params未发生变化为止
574
+ if(i == partsB.length) {
575
+ var tmp = partsB
576
+ partsB = partsA
577
+ partsA = tmp
578
+ }
579
+ var state = true, lenA = partsA.length, res = true
580
+ while(res && state && lenA) {
581
+ partsA.splice(lenA - 1, 1)
582
+ lenA = partsA.length
583
+ state = getStateByName(partsA.join("."))
584
+ if(state) {
585
+ avalon.router._parseArgs(args, state)
586
+ res = mmState.isParamsChanged(state.oldParams, state.params)
587
+ }
588
+ }
589
+ return state || NaN
590
+ }
591
+ return getStateByName(partsA.slice(0, i).join("."))
592
+ }
593
+ function getStateByName(stateName) {
594
+ var states = avalon.router.routingTable.get,
595
+ state = NaN
596
+ for (var i = 0, el; el = states[i++]; ) {
597
+ if (el.stateName === stateName) {
598
+ state = el
599
+ break
600
+ }
601
+ }
602
+ return state
603
+ }
604
+ // 【avalon.state】的辅助函数,收集所有要渲染的[ms-view]元素
605
+ function getViews(ctrl, name, node) {
606
+ var v = avalon.vmodels[ctrl]
607
+ var firstExpr = v && v.$events.expr || "[ms-controller=\"" + ctrl + "\"]"
608
+ var otherExpr = [],
609
+ node = node || document
610
+ name.split(".").forEach(function() {
611
+ otherExpr.push("[ms-view]")
612
+ })
613
+ if (node.querySelectorAll) {
614
+ return node.querySelectorAll(firstExpr + " " + otherExpr.join(" "))
615
+ } else {
616
+ //DIV[avalonctrl="test"] [ms-view] 从右到左查找,先找到所有匹配[ms-view]的元素
617
+ var seeds = Array.prototype.filter.call(node.getElementsByTagName("*"), function(node) {
618
+ return typeof node.getAttribute("ms-view") === "string"
619
+ })
620
+ while (otherExpr.length > 1) {
621
+ otherExpr.pop()
622
+ seeds = matchSelectors(seeds, function(node) {
623
+ return typeof node.getAttribute("ms-view") === "string"
624
+ })
625
+ }
626
+ //判定这些候先节点是否匹配[avalonctrl="test"]或[ms-controller="test"]
627
+ seeds = matchSelectors(seeds, function(node) {
628
+ return node.getAttribute("avalonctrl") === ctrl ||
629
+ node.getAttribute("ms-controller") === ctrl
630
+ })
631
+ return seeds.map(function(el) {
632
+ return el.node
633
+ })
634
+ }
635
+ }
636
+ function viewKey(node) {
637
+ var kn = "view-id",
638
+ key = node.getAttribute(kn)
639
+ if(key) return key
640
+ key = Event.uiqKey()
641
+ node.setAttribute(kn, key)
642
+ return key
643
+ }
644
+ // 【avalon.state】的辅助函数,从已有集合中寻找匹配[ms-view="viewname"]表达式的元素节点
645
+ function getNamedView(nodes, viewname) {
646
+ for (var i = 0, el; el = nodes[i++]; ) {
647
+ if (el.getAttribute("ms-view") === viewname) {
648
+ return el
649
+ }
650
+ }
651
+ }
652
+ // 【getViews】的辅助函数,从array数组中得到匹配match回调的子集,此为一个节点集合
653
+ function matchSelectors(array, match) {
654
+ for (var i = 0, n = array.length; i < n; i++) {
655
+ matchSelector(i, array, match)
656
+ }
657
+ return array.filter(function(el) {
658
+ return el
659
+ })
660
+ }
661
+ // 【matchSelectors】的辅助函数,判定array[i]及其祖先是否匹配match回调
662
+ function matchSelector(i, array, match) {
663
+ var elem = array[i]
664
+ if (elem.node) {
665
+ var node = elem.node
666
+ var parent = elem.parent
667
+ } else {
668
+ node = elem
669
+ parent = elem.parentNode
670
+ }
671
+ while (parent && parent.nodeType !== 9) {
672
+ if (match(parent)) {
673
+ return array[i] = {
674
+ node: node,
675
+ parent: parent.parentNode
676
+ }
677
+ }
678
+ parent = parent.parentNode
679
+ }
680
+ array[i] = false
681
+ }
682
+ // 【avalon.state】的辅助函数,opts.template的处理函数
683
+ function fromString(template, params) {
684
+ var promise = new Promise(function(resolve, reject) {
685
+ var str = typeof template === "function" ? template(params) : template
686
+ if (typeof str == "string") {
687
+ resolve(str)
688
+ } else {
689
+ reject("template必须对应一个字符串或一个返回字符串的函数")
690
+ }
691
+ })
692
+ return promise
693
+ }
694
+ // 【fromUrl】的辅助函数,得到一个XMLHttpRequest对象
695
+ var getXHR = function() {
696
+ return new (window.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP")
697
+ }
698
+ // 【avalon.state】的辅助函数,opts.templateUrl的处理函数
699
+ function fromUrl(url, params) {
700
+ var promise = new Promise(function(resolve, reject) {
701
+ if (typeof url === "function") {
702
+ url = url(params)
703
+ }
704
+ if (typeof url !== "string") {
705
+ return reject("templateUrl必须对应一个URL")
706
+ }
707
+ if (avalon.templateCache[url]) {
708
+ return resolve(avalon.templateCache[url])
709
+ }
710
+ var xhr = getXHR()
711
+ xhr.onreadystatechange = function() {
712
+ if (xhr.readyState === 4) {
713
+ var status = xhr.status;
714
+ if (status > 399 && status < 600) {
715
+ reject("templateUrl对应资源不存在或没有开启 CORS")
716
+ } else {
717
+ resolve(avalon.templateCache[url] = xhr.responseText)
718
+ }
719
+ }
720
+ }
721
+ xhr.open("GET", url, true)
722
+ if ("withCredentials" in xhr) {
723
+ xhr.withCredentials = true
724
+ }
725
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
726
+ xhr.send()
727
+ })
728
+ return promise
729
+ }
730
+ // 【avalon.state】的辅助函数,opts.templateProvider的处理函数
731
+ function fromProvider(fn, params) {
732
+ var promise = new Promise(function(resolve, reject) {
733
+ if (typeof fn === "function") {
734
+ var ret = fn(params)
735
+ if (ret && ret.then) {
736
+ resolve(ret)
737
+ } else {
738
+ reject("templateProvider为函数时应该返回一个Promise或thenable对象")
739
+ }
740
+ } else if (fn && fn.then) {
741
+ resolve(fn)
742
+ } else {
743
+ reject("templateProvider不为函数时应该对应一个Promise或thenable对象")
744
+ }
745
+ })
746
+ return promise
747
+ }
748
+ // 【avalon.state】的辅助函数,将template或templateUrl或templateProvider转换为可用的Promise对象
749
+ function fromPromise(config, params) {
750
+ return config.template ? fromString(config.template, params) :
751
+ config.templateUrl ? fromUrl(config.templateUrl, params) :
752
+ config.templateProvider ? fromProvider(config.templateProvider, params) :
753
+ new Promise(function(resolve, reject) {
754
+ reject("必须存在template, templateUrl, templateProvider中的一个")
755
+ })
756
+ }
757
+ //【avalon.state】的辅助函数,得到目标状态对象对应的父状态对象
758
+ function getParent(stateName) {
759
+ var match = stateName.match(/([-\.\w]+)\./) || ["", ""]
760
+ var parentName = match[1]
761
+ if (parentName) {
762
+ var states = avalon.router.routingTable.get
763
+ for (var i = 0, state; state = states[i++]; ) {
764
+ if (state.stateName === parentName) {
765
+ return state
766
+ }
767
+ }
768
+ throw new Error("必须先定义[" + parentName + "]")
769
+ }
770
+ }
771
+ })