avalon-mmRouter-rails 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +17 -0
- data/Rakefile +2 -0
- data/avalon-mmRouter-rails.gemspec +23 -0
- data/lib/avalon/mmRouter/rails.rb +13 -0
- data/lib/avalon/mmRouter/rails/version.rb +7 -0
- data/vendor/assets/javascripts/avalon/mmHistory.js +310 -0
- data/vendor/assets/javascripts/avalon/mmPromise.js +221 -0
- data/vendor/assets/javascripts/avalon/mmRouter.js +351 -0
- data/vendor/assets/javascripts/avalon/mmState.js +1027 -0
- data/vendor/assets/javascripts/avalon/old-mmState.js +771 -0
- metadata +86 -0
@@ -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
|
+
})
|