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.
- 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
|
+
})
|