avalon-mmRouter-rails 0.0.1

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