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,351 @@
1
+ define(["./mmHistory"], function() {
2
+
3
+ function Router() {
4
+ var table = {}
5
+ "get,post,delete,put".replace(avalon.rword, function(name) {
6
+ table[name] = []
7
+ })
8
+ this.routingTable = table
9
+ }
10
+
11
+ function parseQuery(url) {
12
+ var array = url.split("?"), query = {}, path = array[0], querystring = array[1]
13
+ if (querystring) {
14
+ var seg = querystring.split("&"),
15
+ len = seg.length, i = 0, s;
16
+ for (; i < len; i++) {
17
+ if (!seg[i]) {
18
+ continue
19
+ }
20
+ s = seg[i].split("=")
21
+ query[decodeURIComponent(s[0])] = decodeURIComponent(s[1])
22
+ }
23
+ }
24
+ return {
25
+ path: path,
26
+ query: query
27
+ }
28
+ }
29
+
30
+
31
+ function queryToString(obj) {
32
+ if(typeof obj == 'string') return obj
33
+ var str = []
34
+ for(var i in obj) {
35
+ if(i == "query") continue
36
+ str.push(i + '=' + encodeURIComponent(obj[i]))
37
+ }
38
+ return str.length ? '?' + str.join("&") : ''
39
+ }
40
+
41
+ var placeholder = /([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g
42
+ Router.prototype = {
43
+ error: function(callback) {
44
+ this.errorback = callback
45
+ },
46
+ _pathToRegExp: function(pattern, opts) {
47
+ var keys = opts.keys = [],
48
+ // segments = opts.segments = [],
49
+ compiled = '^', last = 0, m, name, regexp, segment;
50
+
51
+ while ((m = placeholder.exec(pattern))) {
52
+ name = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
53
+ regexp = m[4] || (m[1] == '*' ? '.*' : 'string')
54
+ segment = pattern.substring(last, m.index);
55
+ var type = this.$types[regexp]
56
+ var key = {
57
+ name: name
58
+ }
59
+ if (type) {
60
+ regexp = type.pattern
61
+ key.decode = type.decode
62
+ }
63
+ keys.push(key)
64
+ compiled += quoteRegExp(segment, regexp, false)
65
+ // segments.push(segment)
66
+ last = placeholder.lastIndex
67
+ }
68
+ segment = pattern.substring(last);
69
+ compiled += quoteRegExp(segment) + (opts.strict ? opts.last : "\/?") + '$';
70
+ var sensitive = typeof opts.caseInsensitive === "boolean" ? opts.caseInsensitive : true
71
+ // segments.push(segment);
72
+ opts.regexp = new RegExp(compiled, sensitive ? 'i' : undefined);
73
+ return opts
74
+
75
+ },
76
+ //添加一个路由规则
77
+ add: function(method, path, callback, opts) {
78
+ var array = this.routingTable[method.toLowerCase()]
79
+ if (path.charAt(0) !== "/") {
80
+ throw "path必须以/开头"
81
+ }
82
+ opts = opts || {}
83
+ opts.callback = callback
84
+ if (path.length > 2 && path.charAt(path.length - 1) === "/") {
85
+ path = path.slice(0, -1)
86
+ opts.last = "/"
87
+ }
88
+ avalon.Array.ensure(array, this._pathToRegExp(path, opts))
89
+ },
90
+ //判定当前URL与已有状态对象的路由规则是否符合
91
+ route: function(method, path, query) {
92
+ path = path.trim()
93
+ var states = this.routingTable[method]
94
+ for (var i = 0, el; el = states[i++]; ) {
95
+ var args = path.match(el.regexp)
96
+ if (args) {
97
+ el.query = query || {}
98
+ el.path = path
99
+ el.params = {}
100
+ var keys = el.keys
101
+ args.shift()
102
+ if (keys.length) {
103
+ this._parseArgs(args, el)
104
+ }
105
+ return el.callback.apply(el, args)
106
+ }
107
+ }
108
+ if (this.errorback) {
109
+ this.errorback()
110
+ }
111
+ },
112
+ _parseArgs: function(match, stateObj) {
113
+ var keys = stateObj.keys
114
+ for (var j = 0, jn = keys.length; j < jn; j++) {
115
+ var key = keys[j]
116
+ var value = match[j] || ""
117
+ if (typeof key.decode === "function") {//在这里尝试转换参数的类型
118
+ var val = key.decode(value)
119
+ } else {
120
+ try {
121
+ val = JSON.parse(value)
122
+ } catch (e) {
123
+ val = value
124
+ }
125
+ }
126
+ match[j] = stateObj.params[key.name] = val
127
+ }
128
+ },
129
+ getLastPath: function() {
130
+ return getCookie("msLastPath")
131
+ },
132
+ setLastPath: function(path) {
133
+ setCookie("msLastPath", path)
134
+ },
135
+ /*
136
+ * @interface avalon.router.redirect
137
+ * @param hash 访问的url hash
138
+ */
139
+ redirect: function(hash) {
140
+ this.navigate(hash, {replace: true})
141
+ },
142
+ /*
143
+ * @interface avalon.router.navigate
144
+ * @param hash 访问的url hash
145
+ * @param options 扩展配置
146
+ * @param options.replace true替换history,否则生成一条新的历史记录
147
+ * @param options.silent true表示只同步url,不触发url变化监听绑定
148
+ */
149
+ navigate: function(hash, options) {
150
+ var parsed = parseQuery((hash.charAt(0) !== "/" ? "/" : "") + hash),
151
+ options = options || {}
152
+ if(hash.charAt(0) === "/")
153
+ hash = hash.slice(1)// 修正出现多扛的情况 fix http://localhost:8383/index.html#!//
154
+ // 在state之内有写history的逻辑
155
+ if(!avalon.state || options.silent) avalon.history && avalon.history.updateLocation(hash, avalon.mix({}, options, {silent: true}))
156
+ // 只是写历史而已
157
+ if(!options.silent) {
158
+ this.route("get", parsed.path, parsed.query, options)
159
+ }
160
+ },
161
+ /*
162
+ * @interface avalon.router.when 配置重定向规则
163
+ * @param path 被重定向的表达式,可以是字符串或者数组
164
+ * @param redirect 重定向的表示式或者url
165
+ */
166
+ when: function(path, redirect) {
167
+ var me = this,
168
+ path = path instanceof Array ? path : [path]
169
+ avalon.each(path, function(index, p) {
170
+ me.add("get", p, function() {
171
+ var info = me.urlFormate(redirect, this.params, this.query)
172
+ me.navigate(info.path + info.query, {replace: true})
173
+ })
174
+ })
175
+ return this
176
+ },
177
+ /*
178
+ * @interface avalon.router.get 添加一个router规则
179
+ * @param path url表达式
180
+ * @param callback 对应这个url的回调
181
+ */
182
+ get: function(path, callback) {},
183
+ urlFormate: function(url, params, query) {
184
+ var query = query ? queryToString(query) : "",
185
+ hash = url.replace(placeholder, function(mat) {
186
+ var key = mat.replace(/[\{\}]/g, '').split(":")
187
+ key = key[0] ? key[0] : key[1]
188
+ return params[key] !== undefined ? params[key] : ''
189
+ }).replace(/^\//g, '')
190
+ return {
191
+ path: hash,
192
+ query: query
193
+ }
194
+ },
195
+ /* *
196
+ `'/hello/'` - 匹配'/hello/'或'/hello'
197
+ `'/user/:id'` - 匹配 '/user/bob' 或 '/user/1234!!!' 或 '/user/' 但不匹配 '/user' 与 '/user/bob/details'
198
+ `'/user/{id}'` - 同上
199
+ `'/user/{id:[^/]*}'` - 同上
200
+ `'/user/{id:[0-9a-fA-F]{1,8}}'` - 要求ID匹配/[0-9a-fA-F]{1,8}/这个子正则
201
+ `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
202
+ path into the parameter 'path'.
203
+ `'/files/*path'` - ditto.
204
+ */
205
+ // avalon.router.get("/ddd/:dddID/",callback)
206
+ // avalon.router.get("/ddd/{dddID}/",callback)
207
+ // avalon.router.get("/ddd/{dddID:[0-9]{4}}/",callback)
208
+ // avalon.router.get("/ddd/{dddID:int}/",callback)
209
+ // 我们甚至可以在这里添加新的类型,avalon.router.$type.d4 = { pattern: '[0-9]{4}', decode: Number}
210
+ // avalon.router.get("/ddd/{dddID:d4}/",callback)
211
+ $types: {
212
+ date: {
213
+ pattern: "[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])",
214
+ decode: function(val) {
215
+ return new Date(val.replace(/\-/g, "/"))
216
+ }
217
+ },
218
+ string: {
219
+ pattern: "[^\\/]*"
220
+ },
221
+ bool: {
222
+ decode: function(val) {
223
+ return parseInt(val, 10) === 0 ? false : true;
224
+ },
225
+ pattern: "0|1"
226
+ },
227
+ 'int': {
228
+ decode: function(val) {
229
+ return parseInt(val, 10);
230
+ },
231
+ pattern: "\\d+"
232
+ }
233
+ }
234
+ }
235
+
236
+ "get,put,delete,post".replace(avalon.rword, function(method) {
237
+ return Router.prototype[method] = function(a, b, c) {
238
+ this.add(method, a, b, c)
239
+ }
240
+ })
241
+ function quoteRegExp(string, pattern, isOptional) {
242
+ var result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
243
+ if (!pattern)
244
+ return result;
245
+ var flag = isOptional ? '?' : '';
246
+ return result + flag + '(' + pattern + ')' + flag;
247
+ }
248
+ function supportLocalStorage() {
249
+ try {
250
+ localStorage.setItem("avalon", 1)
251
+ localStorage.removeItem("avalon")
252
+ return true
253
+ } catch (e) {
254
+ return false
255
+ }
256
+ }
257
+
258
+ if (supportLocalStorage()) {
259
+ Router.prototype.getLastPath = function() {
260
+ return localStorage.getItem("msLastPath")
261
+ }
262
+ var cookieID
263
+ Router.prototype.setLastPath = function (path) {
264
+ if (cookieID) {
265
+ clearTimeout(cookieID)
266
+ cookieID = null
267
+ }
268
+ localStorage.setItem("msLastPath", path)
269
+ cookieID = setTimeout(function () {
270
+ localStorage.removItem("msLastPath")
271
+ }, 1000 * 60 * 60 * 24)
272
+ }
273
+ }
274
+
275
+
276
+
277
+ function escapeCookie(value) {
278
+ return String(value).replace(/[,;"\\=\s%]/g, function(character) {
279
+ return encodeURIComponent(character)
280
+ });
281
+ }
282
+ function setCookie(key, value) {
283
+ var date = new Date()//将date设置为1天以后的时间
284
+ date.setTime(date.getTime() + 1000 * 60 * 60 * 24)
285
+ document.cookie = escapeCookie(key) + '=' + escapeCookie(value) + ";expires=" + date.toGMTString()
286
+ }
287
+ function getCookie(name) {
288
+ var m = String(document.cookie).match(new RegExp('(?:^| )' + name + '(?:(?:=([^;]*))|;|$)')) || ["", ""]
289
+ return decodeURIComponent(m[1])
290
+ }
291
+
292
+ avalon.router = new Router
293
+
294
+ return avalon
295
+ })
296
+ /*
297
+ <!DOCTYPE html>
298
+ <html>
299
+ <head>
300
+ <meta charset="utf-8">
301
+ <title>路由系统</title>
302
+ <script src="avalon.js"></script>
303
+ <script>
304
+ require(["mmRouter"], function() {
305
+ var model = avalon.define('xxx', function(vm) {
306
+ vm.currPath = ""
307
+ })
308
+ avalon.router.get("/aaa", function(a) {
309
+ model.currPath = this.path
310
+ })
311
+ avalon.router.get("/bbb", function(a) {
312
+ model.currPath = this.path
313
+ })
314
+ avalon.router.get("/ccc", function(a) {
315
+ model.currPath = this.path
316
+ })
317
+ avalon.router.get("/ddd/:ddd", function(a) {//:ddd为参数
318
+ avalon.log(a)
319
+ model.currPath = this.path
320
+ })
321
+ avalon.router.get("/eee", function(a) {
322
+ model.currPath = this.path
323
+ })
324
+ avalon.history.start({
325
+ html5Mode: true,
326
+ basepath: "/avalon"
327
+ })
328
+ avalon.scan()
329
+ })
330
+ </script>
331
+ </head>
332
+ <body >
333
+ <div ms-controller="xxx">
334
+ <ul>
335
+ <li><a href="#!/aaa">aaa</a></li>
336
+ <li><a href="#!/bbb">bbb</a></li>
337
+ <li><a href="#!/ccc">ccc</a></li>
338
+ <li><a href="#!/ddd/222">ddd</a></li>
339
+ <li><a href="#!/eee">eee</a></li>
340
+ </ul>
341
+ <div style="color:red">{{currPath}}</div>
342
+ <div style="height: 600px;width:1px;">
343
+
344
+ </div>
345
+ <p id="eee">会定位到这里</p>
346
+ </div>
347
+
348
+ </body>
349
+ </html>
350
+
351
+ */
@@ -0,0 +1,1027 @@
1
+ define(["./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
+ for (var i = 0, el; el = states[i++]; ) {//el为一个个状态对象,状态对象的callback总是返回一个Promise
7
+ var args = path.match(el.regexp)
8
+ if (args && el.abstract !== true) {//不能是抽象状态
9
+ var newParams = {params: {}}
10
+ avalon.mix(newParams.params, el.params)
11
+ newParams.keys = el.keys
12
+ newParams.params.query = query || {}
13
+ args.shift()
14
+ if (el.keys.length) {
15
+ this._parseArgs(args, newParams)
16
+ }
17
+ if (el.stateName) {
18
+ mmState.transitionTo(mmState.currentState, el, newParams.params, options)
19
+ } else {
20
+ el.callback.apply(el, args)
21
+ }
22
+ return
23
+ }
24
+ }
25
+ if (this.errorback) {
26
+ this.errorback()
27
+ }
28
+ }
29
+ var _root, undefine, _controllers = {}, _states = {}
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.confirmed true不触发onBeforeUnload,$onBeforeUnload,onBeforeExit
39
+ */
40
+ avalon.router.go = function(toName, params, options) {
41
+ var from = mmState.currentState,
42
+ to = StateModel.is(toName) ? toName : getStateByName(toName),
43
+ params = params || {}
44
+ var params = avalon.mix(true, {}, to.params, params)
45
+ if (to) {
46
+ mmState.transitionTo(from, to, params, options)
47
+ }
48
+ }
49
+ // 事件管理器
50
+ var Event = window.$eventManager = avalon.define("$eventManager", function(vm) {
51
+ vm.$flag = 0;
52
+ vm.uiqKey = function() {
53
+ vm.$flag++
54
+ return "flag" + vm.$flag++
55
+ }
56
+ })
57
+ function removeOld() {
58
+ var nodes = mmState.oldNodes
59
+ while(nodes.length) {
60
+ var i = nodes.length - 1,
61
+ node = nodes[i]
62
+ node.parentNode && node.parentNode.removeChild(node)
63
+ nodes.splice(i, 1)
64
+ node = null
65
+ }
66
+ }
67
+ Event.$watch("onAbort", removeOld)
68
+ var mmState = window.mmState = {
69
+ prevState: NaN,
70
+ currentState: NaN, // 当前状态,可能还未切换到该状态
71
+ activeState: NaN, // 当前实际处于的状态
72
+ oldNodes: [],
73
+ query: {}, // 从属于currentState
74
+ popOne: function(chain, params, callback, notConfirmed) {
75
+ if(mmState._toParams !== params) return callback(false, {type: "abort"})
76
+ var cur = chain.pop(), me = this
77
+ if(!cur) return callback()
78
+ // 阻止退出
79
+ if(notConfirmed && cur.onBeforeExit() === false) return callback(false)
80
+ me.activeState = cur.parentState || _root
81
+ cur.done = function(success) {
82
+ cur._pending = false
83
+ cur.done = null
84
+ cur._local = null
85
+ if(success !== false) {
86
+ if(me.activeState) return me.popOne(chain, params, callback, notConfirmed)
87
+ }
88
+ return callback(success)
89
+ }
90
+ var success = cur.onExit()
91
+ if(!cur._pending && cur.done) cur.done(success)
92
+ },
93
+ pushOne: function(chain, params, callback, _local, toLocals) {
94
+ if(mmState._toParams !== params) return callback(false, {type: "abort"})
95
+ var cur = chain.shift(), me = this
96
+ // 退出
97
+ if(!cur) {
98
+ chain = null
99
+ return callback()
100
+ }
101
+ cur.syncParams(params)
102
+ // 阻止进入该状态
103
+ if(cur.onBeforeEnter() === false) {
104
+ // 恢复params
105
+ cur.syncParams(cur.oldParams)
106
+ return callback(false)
107
+ }
108
+ var _local = inherit(_local)
109
+ me.activeState = cur // 更新当前实际处于的状态
110
+ cur.done = function(success) {
111
+ // 防止async处触发已经销毁的done
112
+ if(!cur.done) return
113
+ cur._pending = false
114
+ cur.done = null
115
+ cur.visited = true
116
+ // 退出
117
+ if(success === false) {
118
+ // 这里斟酌一下 - 去掉
119
+ // cur.callback.apply(cur, [params, _local])
120
+ return callback(success)
121
+ }
122
+ var resolved = cur.callback.apply(cur, [params, _local])
123
+ resolved.$then(function(res) {
124
+ // sync params to oldParams
125
+ avalon.mix(true, cur.oldParams, cur.params)
126
+ // 继续状态链
127
+ me.pushOne(chain, params, callback, _local)
128
+ })
129
+ }
130
+ // 一般在这个回调里准备数据
131
+ var args = []
132
+ avalon.each(cur.keys, function(index, item) {
133
+ var key = item.name
134
+ args.push(cur.params[key])
135
+ })
136
+ cur._onEnter.apply(cur, args)
137
+ if(!cur._pending && cur.done) cur.done()
138
+ },
139
+ transitionTo: function(fromState, toState, toParams, options) {
140
+ var toParams = toParams || toState.params, fromAbort
141
+ // state machine on transition
142
+ if(this.activeState && (this.activeState != this.currentState)) {
143
+ avalon.log("navigating to [" +
144
+ this.currentState.stateName +
145
+ "] will be stopped, redirect to [" +
146
+ toState.stateName + "] now")
147
+ this.activeState.done && this.activeState.done(!"stopped")
148
+ fromState = this.activeState // 更新实际的fromState
149
+ fromAbort = true
150
+ }
151
+ mmState.oldNodes = []
152
+ var info = avalon.router.urlFormate(toState.url, toParams, toParams.query),
153
+ me = this,
154
+ options = options || {},
155
+ // 是否强制reload,参照angular,这个时候会触发整个页面重刷
156
+ reload = options.reload,
157
+ over,
158
+ fromChain = fromState && fromState.chain || [],
159
+ toChain = toState.chain,
160
+ i = 0,
161
+ changeType, // 是params变化?query变化?这个东西可以用来配置是否屏蔽视图刷新逻辑
162
+ state = toChain[i],
163
+ _local = _root.sourceLocal,
164
+ toLocals = []
165
+ if(!reload) {
166
+ // 找到共有父状态chain,params未变化
167
+ while(state && state === fromChain[i] && !state.paramsChanged(toParams)) {
168
+ _local = toLocals[i] = state._local
169
+ i++
170
+ state = toChain[i]
171
+ }
172
+ }
173
+ var exitChain = fromChain.slice(i),// 需要退出的chain
174
+ enterChain = toChain.slice(i),// 需要进入的chain
175
+ commonLocal = _local
176
+ // 建立toLocals,用来计算哪些view会被替换
177
+ while(state = toChain[i]) {
178
+ _local = toLocals[i] = inherit(_local, state.sourceLocal)
179
+ i++
180
+ }
181
+ mmState._local = _local
182
+ done = function(success, e) {
183
+ if(over) return
184
+ over = true
185
+ me.currentState = me.activeState
186
+ enterChain = exitChain = commonLocal = _local = toParams= null
187
+ mmState.oldNodes = []
188
+ if(success !== false) {
189
+ mmState.lastLocal = mmState.currentState._local
190
+ _root.fire("updateview", me.currentState, changeType)
191
+ avalon.log("transitionTo " + toState.stateName + " success")
192
+ callStateFunc("onLoad", me, fromState, toState)
193
+ } else {
194
+ return callStateFunc("onError", me, {
195
+ type: "transition",
196
+ message: "transitionTo " + toState.stateName + " faild",
197
+ error: e,
198
+ fromState:fromState,
199
+ toState:toState,
200
+ params: toParams
201
+ }, me.currentState)
202
+ }
203
+ }
204
+ toState.path = ("/" + info.path).replace(/^[\/]{2,}/g, "/")
205
+ if(!reload && fromState === toState) {
206
+ changeType = toState.paramsChanged(toParams)
207
+ if(!changeType) {
208
+ // redirect的目的状态 == this.activeState && abort
209
+ if(toState == this.activeState && fromAbort) return done()
210
+ // 重复点击直接return
211
+ return
212
+ }
213
+ }
214
+
215
+ mmState.query = avalon.mix({}, toParams.query)
216
+
217
+ // onBeforeUnload check
218
+ if(options && !options.confirmed && (callStateFunc("onBeforeUnload", this, fromState, toState) === false || broadCastBeforeUnload(exitChain, enterChain, fromState, toState) === false)) {
219
+ return callStateFunc("onAbort", this, fromState, toState)
220
+ }
221
+ if(over === true) {
222
+ return
223
+ }
224
+ avalon.log("begin transitionTo " + toState.stateName + " from " + (fromState && fromState.stateName || "unknown"))
225
+ callStateFunc("onUnload", this, fromState, toState)
226
+ this.currentState = toState
227
+ this.prevState = fromState
228
+ mmState._toParams = toParams
229
+ if(info && avalon.history) avalon.history.updateLocation(info.path + info.query, avalon.mix({}, options, {silent: true}), !fromState && location.hash)
230
+ callStateFunc("onBegin", this, fromState, toState)
231
+ this.popOne(exitChain, toParams, function(success) {
232
+ // 中断
233
+ if(success === false) return done(success)
234
+ me.pushOne(enterChain, toParams, done, commonLocal, toLocals)
235
+ }, !(options && options.confirmed))
236
+ }
237
+ }
238
+ //将template,templateUrl,templateProvider等属性从opts对象拷贝到新生成的view对象上的
239
+ function copyTemplateProperty(newObj, oldObj, name) {
240
+ if(name in oldObj) {
241
+ newObj[name] = oldObj[name]
242
+ delete oldObj[name]
243
+ }
244
+ }
245
+ function getCacheContainer() {
246
+ return document.getElementsByTagName("avalon")[0]
247
+ }
248
+ var templateCache = {},
249
+ cacheContainer = getCacheContainer()
250
+ function loadCache(name) {
251
+ var fragment = document.createDocumentFragment(),
252
+ divPlaceHolder = document.getElementById(name),
253
+ f,
254
+ eles = divPlaceHolder.eles,
255
+ i = 0
256
+ if(divPlaceHolder) {
257
+ while(f = eles[i]) {
258
+ fragment.appendChild(f)
259
+ i++
260
+ }
261
+ }
262
+ return fragment
263
+ }
264
+ function setCache(name, element) {
265
+ var fragment = document.createDocumentFragment(),
266
+ divPlaceHolder = document.getElementById(name),
267
+ f
268
+ if(!divPlaceHolder) {
269
+ divPlaceHolder = document.createElement("div")
270
+ divPlaceHolder.id = name
271
+ cacheContainer.appendChild(divPlaceHolder)
272
+ }
273
+ // 引用
274
+ if(divPlaceHolder.eles) {
275
+ avalon.each(divPlaceHolder.eles, function(index, ele) {
276
+ fragment.appendChild(ele)
277
+ })
278
+ } else {
279
+ divPlaceHolder.eles = []
280
+ while(f = element.firstChild) {
281
+ fragment.appendChild(f)
282
+ divPlaceHolder.eles.push(f)
283
+ }
284
+ templateCache[name] = true
285
+ }
286
+ divPlaceHolder.appendChild(fragment)
287
+ }
288
+ function broadCastBeforeUnload(exitChain, enterChain, fromState, toState) {
289
+ var lastLocal = mmState.lastLocal
290
+ if(!lastLocal || !enterChain[0] && !exitChain[0]) return
291
+ var newLocal = mmState._local,
292
+ cacheQueue = []
293
+ for(var i in lastLocal) {
294
+ var local = lastLocal[i]
295
+ // 所有被更新的view
296
+ if(!(i in newLocal) || newLocal[i] != local) {
297
+ if(local.$ctrl && ("$onBeforeUnload" in local.$ctrl)) {
298
+ if(local.$ctrl.$onBeforeUnload(fromState, toState) === false) return false
299
+ }
300
+ if(local.element && (exitChain[0] != enterChain[0])) cacheQueue.push(local)
301
+ }
302
+ }
303
+ avalon.each(cacheQueue, function(index, local) {
304
+ var ele = local.element,
305
+ name = avalon(ele).data("currentCache")
306
+ if(name) {
307
+ setCache(name, ele)
308
+ }
309
+ })
310
+ cacheQueue = null
311
+ }
312
+ // 靠谱的解决方法
313
+ avalon.bindingHandlers.view = function(data) {
314
+ var vmodels = data.vmodels || arguments[1]
315
+ var currentState = mmState.currentState,
316
+ element = data.element,
317
+ $element = avalon(element),
318
+ viewname = data.value || data.expr || "",
319
+ comment = document.createComment("ms-view:" + viewname),
320
+ par = element.parentNode,
321
+ defaultHTML = element.innerHTML,
322
+ statename = $element.data("statename") || "",
323
+ parentState = getStateByName(statename) || _root,
324
+ currentLocal = {},
325
+ oldElement = element,
326
+ tpl = element.outerHTML
327
+ element.removeAttribute("ms-view") // remove right now
328
+ par.insertBefore(comment, element)
329
+ function update(firsttime, currentState, changeType) {
330
+ // node removed, remove callback
331
+ if(!document.contains(comment)) {
332
+ data = vmodels = element = par = comment = $element = oldElement = update = null
333
+ return !"delete from watch"
334
+ }
335
+ var definedParentStateName = $element.data("statename") || "",
336
+ parentState = getStateByName(definedParentStateName) || _root,
337
+ _local
338
+ if (viewname.indexOf("@") < 0) viewname += "@" + parentState.stateName
339
+ _local = mmState.currentState._local && mmState.currentState._local[viewname]
340
+ if(firsttime && !_local || currentLocal === _local) return
341
+ currentLocal = _local
342
+ _currentState = _local && _local.state
343
+ // 缓存,如果加载dom上,则是全局配置,针对template还可以开一个单独配置
344
+ var cacheTpl = $element.data("viewCache"),
345
+ lastCache = $element.data("currentCache")
346
+ if(_local) {
347
+ cacheTpl = (_local.viewCache === false ? false : _local.viewCache || cacheTpl) && (viewname + "@" + _currentState.stateName)
348
+ } else if(cacheTpl) {
349
+ cacheTpl = viewname + "@__default__"
350
+ }
351
+ // stateB->stateB,配置了参数变化不更新dom
352
+ if(_local && _currentState === currentState && _local.ignoreChange && _local.ignoreChange(changeType, viewname)) return
353
+ // 需要load和使用的cache是一份
354
+ if(cacheTpl && cacheTpl === lastCache) return
355
+ compileNode(tpl, element, $element, _currentState)
356
+ var html = _local ? _local.template : defaultHTML,
357
+ fragment
358
+ if(cacheTpl) {
359
+ if(_local) {
360
+ _local.element = element
361
+ } else {
362
+ mmState.currentState._local[viewname] = {
363
+ state: mmState.currentState,
364
+ template: defaultHTML,
365
+ element: element
366
+ }
367
+ }
368
+ }
369
+ avalon.clearHTML(element)
370
+ // oldElement = element
371
+ element.removeAttribute("ms-view")
372
+ element.setAttribute("ui-view", data.value || data.expr || "")
373
+ // 本次更新的dom需要用缓存
374
+ if(cacheTpl) {
375
+ // 已缓存
376
+ if(templateCache[cacheTpl]) {
377
+ fragment = loadCache(cacheTpl)
378
+ // 未缓存
379
+ } else {
380
+ fragment = avalon.parseHTML(html)
381
+ }
382
+ element.appendChild(fragment)
383
+ // 更新现在使用的cache名字
384
+ $element.data("currentCache", cacheTpl)
385
+ if(templateCache[cacheTpl]) return
386
+ } else {
387
+ element.innerHTML = html
388
+ $element.data("currentCache", false)
389
+ }
390
+ // default
391
+ if(!_local && cacheTpl) $element.data("currentCache", cacheTpl)
392
+ avalon.each(getViewNodes(element), function(i, node) {
393
+ avalon(node).data("statename", _currentState && _currentState.stateName || "")
394
+ })
395
+ // merge上下文vmodels + controller指定的vmodels
396
+ avalon.scan(element, (_local && _local.vmodels || []).concat(vmodels || []))
397
+ // 触发视图绑定的controller的事件
398
+ if(_local && _local.$ctrl) {
399
+ _local.$ctrl.$onRendered && _local.$ctrl.$onRendered.apply(element, [_local])
400
+ }
401
+ }
402
+ update("firsttime")
403
+ _root.watch("updateview", function(state, changeType) {
404
+ return update.call(this, undefine, state, changeType)
405
+ })
406
+ }
407
+ if(avalon.directives) {
408
+ avalon.directive("view", {
409
+ init: avalon.bindingHandlers.view
410
+ })
411
+ }
412
+ function compileNode(tpl, element, $element, _currentState) {
413
+ if($element.hasClass("oni-mmRouter-slide")) {
414
+ // 拷贝一个镜像
415
+ var copy = element.cloneNode(true)
416
+ copy.setAttribute("ms-skip", "true")
417
+ avalon(copy).removeClass("oni-mmRouter-enter").addClass("oni-mmRouter-leave")
418
+ avalon(element).addClass("oni-mmRouter-enter")
419
+ element.parentNode.insertBefore(copy, element)
420
+ mmState.oldNodes.push(copy)
421
+ callStateFunc("onViewEnter", _currentState, element, copy)
422
+ }
423
+ return element
424
+ }
425
+
426
+ function inherit(parent, extra) {
427
+ return avalon.mix(new (avalon.mix(function() {}, { prototype: parent }))(), extra);
428
+ }
429
+
430
+ /*
431
+ * @interface avalon.state 对avalon.router.get 进行重新封装,生成一个状态对象
432
+ * @param stateName 指定当前状态名
433
+ * @param opts 配置
434
+ * @param opts.url 当前状态对应的路径规则,与祖先状态们组成一个完整的匹配规则
435
+ * @param {Function} opts.ignoreChange 当mmState.currentState == this时,更新视图的时候调用该函数,return true mmRouter则不会去重写视图和scan,请确保该视图内用到的数据没有放到avalon vmodel $skipArray内
436
+ * @param opts.controller 如果不写views属性,则默认view为"",为默认的view指定一个控制器,该配置会直接作为avalon.controller的参数生成一个$ctrl对象
437
+ * @param opts.controllerUrl 指定默认view控制器的路径,适用于模块化开发,该情形下默认通过avalon.controller.loader去加载一个符合amd规范,并返回一个avalon.controller定义的对象,传入opts.params作参数
438
+ * @param opts.controllerProvider 指定默认view控制器的提供者,它可以是一个Promise,也可以为一个函数,传入opts.params作参数
439
+ @param opts.viewCache 是否缓存这个模板生成的dom,设置会覆盖dom元素上的data-view-cache,也可以分别配置到views上每个单独的view上
440
+ * @param opts.views: 如果不写views属性,则默认view【ms-view=""】为"",也可以通过指定一个viewname属性来配置【ms-view="viewname"】,对多个[ms-view]容器进行处理,每个对象应拥有template, templateUrl, templateProvider,可以给每个对象搭配一个controller||controllerUrl||controllerProvider属性
441
+ * views的结构为
442
+ *<pre>
443
+ * {
444
+ * "": {template: "xxx"}
445
+ * "aaa": {template: "xxx"}
446
+ * "bbb@": {template: "xxx"}
447
+ * }
448
+ *</pre>
449
+ * views的每个键名(keyname)的结构为viewname@statename,
450
+ * 如果名字不存在@,则viewname直接为keyname,statename为opts.stateName
451
+ * 如果名字存在@, viewname为match[0], statename为match[1]
452
+ * @param opts.views.{viewname}.template 指定当前模板,也可以为一个函数,传入opts.params作参数,* @param opts.views.viewname.cacheController 是否缓存view的控制器,默认true
453
+ * @param opts.views.{viewname}.templateUrl 指定当前模板的路径,也可以为一个函数,传入opts.params作参数
454
+ * @param opts.views.{viewname}.templateProvider 指定当前模板的提供者,它可以是一个Promise,也可以为一个函数,传入opts.params作参数
455
+ * @param opts.views.{viewname}.ignoreChange 用法同state.ignoreChange,只是针对的粒度更细一些,针对到具体的view
456
+ * @param {Function} opts.onBeforeEnter 切入某个state之前触发,this指向对应的state,如果return false则会中断并退出整个状态机
457
+ * @param {Function} opts.onEnter 进入状态触发,可以返回false,或任意不为true的错误信息或一个promise对象,用法跟视图的$onEnter一致
458
+ * @param {Function} onEnter.params 视图所属的state的参数
459
+ * @param {Function} onEnter.resolve $onEnter return false的时候,进入同步等待,直到手动调用resolve
460
+ * @param {Function} onEnter.reject 数据加载失败,调用
461
+ * @param {Function} opts.onBeforeExit state退出前触发,this指向对应的state,如果return false则会中断并退出整个状态机
462
+ * @param {Function} opts.onExit 退出后触发,this指向对应的state
463
+ * @param opts.ignoreChange.changeType 值为"param",表示params变化,值为"query",表示query变化
464
+ * @param opts.ignoreChange.viewname 关联的ms-view name
465
+ * @param opts.abstract 表示它不参与匹配,this指向对应的state
466
+ * @param {private} opts.parentState 父状态对象(框架内部生成)
467
+ */
468
+ avalon.state = function(stateName, opts) {
469
+ var state = StateModel(stateName, opts)
470
+ avalon.router.get(state.url, function(params, _local) {
471
+ var me = this, promises = [], _resovle, _reject, _data = [], _callbacks = []
472
+ state.resolved = getPromise(function(rs, rj) {
473
+ _resovle = rs
474
+ _reject = rj
475
+ })
476
+ avalon.each(state.views, function(name, view) {
477
+ var params = me.params,
478
+ reason = {
479
+ type: "view",
480
+ name: name,
481
+ params: params,
482
+ state: state,
483
+ view: view
484
+ },
485
+ viewLocal = _local[name] = {
486
+ name: name,
487
+ state: state,
488
+ params: state.filterParams(params),
489
+ ignoreChange: "ignoreChange" in view ? view.ignoreChange : me.ignoreChange,
490
+ viewCache: "viewCache" in view ? view.viewCache : me.viewCache
491
+ },
492
+ promise = fromPromise(view, params, reason)
493
+ promises.push(promise)
494
+ // template不能cache
495
+ promise.then(function(s) {
496
+ viewLocal.template = s
497
+ }, avalon.noop) // 捕获模板报错
498
+ var prom,
499
+ callback = function($ctrl) {
500
+ viewLocal.vmodels = $ctrl.$vmodels
501
+ view.$controller = viewLocal.$ctrl = $ctrl
502
+ resolveData()
503
+ },
504
+ resolveData = function() {
505
+ var $onEnter = view.$controller && view.$controller.$onEnter
506
+ if($onEnter) {
507
+ var innerProm = getPromise(function(rs, rj) {
508
+ var reason = {
509
+ type: "data",
510
+ state: state,
511
+ params: params
512
+ },
513
+ res = $onEnter(params, rs, function(message) {
514
+ reason.message = message
515
+ rj(reason)
516
+ })
517
+ // if promise
518
+ if(res && res.then) {
519
+ _data.push(res)
520
+ res.then(function() {
521
+ rs(res)
522
+ })
523
+ // error msg
524
+ } else if(res && res !== true) {
525
+ reason.message = res
526
+ rj(reason)
527
+ } else if(res === undefine) {
528
+ rs()
529
+ }
530
+ // res === false will pause here
531
+ })
532
+ innerProm = innerProm.then(function(cb) {
533
+ avalon.isFunction(cb) && _callbacks.push(cb)
534
+ })
535
+ _data.push(innerProm)
536
+ }
537
+ }
538
+ // controller似乎可以缓存着
539
+ if(view.$controller && view.cacheController !== false) {
540
+ return callback(view.$controller)
541
+ }
542
+ // 加载controller模块
543
+ if(view.controller) {
544
+ prom = promise.then(function() {
545
+ callback(avalon.controller(view.controller))
546
+ })
547
+ } else if(view.controllerUrl) {
548
+ prom = getPromise(function(rs, rj) {
549
+ var url = avalon.isFunction(view.controllerUrl) ? view.controllerUrl(params) : view.controllerUrl
550
+ url = url instanceof Array ? url : [url]
551
+ avalon.controller.loader(url, function($ctrl) {
552
+ promise.then(function() {
553
+ callback($ctrl)
554
+ rs()
555
+ })
556
+ })
557
+ })
558
+ } else if(view.controllerProvider) {
559
+ var res = avalon.isFunction(view.controllerProvider) ? view.controllerProvider(params) : view.controllerProvider
560
+ prom = getPromise(function(rs, rj) {
561
+ // if promise
562
+ if(res && res.then) {
563
+ _data.push(res)
564
+ res.then(function(r) {
565
+ promise.then(function() {
566
+ callback(r)
567
+ rs()
568
+ })
569
+ }, function(e) {
570
+ reason.message = e
571
+ rj(reason)
572
+ })
573
+ // error msg
574
+ } else {
575
+ promise.then(function() {
576
+ callback(res)
577
+ rs()
578
+ })
579
+ }
580
+ })
581
+ }
582
+ // is promise
583
+ if(prom && prom.then) {
584
+ promises.push(prom)
585
+ }
586
+ })
587
+ // 模板和controller就绪
588
+ getPromise(promises).$then(function(values) {
589
+ state._local = _local
590
+ // 数据就绪
591
+ getPromise(_data).$then(function() {
592
+ avalon.each(_callbacks, function(i, func) {
593
+ func()
594
+ })
595
+ promises = _data = _callbacks = null
596
+ _resovle()
597
+ })
598
+ })
599
+ return state.resolved
600
+
601
+ }, state)
602
+
603
+ return this
604
+ }
605
+
606
+ function isError(e) {
607
+ return e instanceof Error
608
+ }
609
+
610
+ // 将所有的promise error适配到这里来
611
+ function promiseError(e) {
612
+ if(isError(e)) {
613
+ throw e
614
+ } else {
615
+ callStateFunc("onError", mmState, e, e && e.state)
616
+ }
617
+ }
618
+
619
+ function getPromise(excutor) {
620
+ var prom = avalon.isFunction(excutor) ? new Promise(excutor) : Promise.all(excutor)
621
+ return prom
622
+ }
623
+ Promise.prototype.$then = function(onFulfilled, onRejected) {
624
+ var prom = this.then(onFulfilled, onRejected)
625
+ prom["catch"](promiseError)
626
+ return prom
627
+ }
628
+ avalon.state.onViewEntered = function(newNode, oldNode) {
629
+ if(newNode != oldNode) oldNode.parentNode.removeChild(oldNode)
630
+ }
631
+ /*
632
+ * @interface avalon.state.config 全局配置
633
+ * @param {Object} config 配置对象
634
+ * @param {Function} config.onBeforeUnload 开始切前的回调,this指向router对象,第一个参数是fromState,第二个参数是toState,return false可以用来阻止切换进行
635
+ * @param {Function} config.onAbort onBeforeUnload return false之后,触发的回调,this指向mmState对象,参数同onBeforeUnload
636
+ * @param {Function} config.onUnload url切换时候触发,this指向mmState对象,参数同onBeforeUnload
637
+ * @param {Function} config.onBegin 开始切换的回调,this指向mmState对象,参数同onBeforeUnload,如果配置了onBegin,则忽略begin
638
+ * @param {Function} config.onLoad 切换完成并成功,this指向mmState对象,参数同onBeforeUnload
639
+ * @param {Function} config.onViewEnter 视图插入动画函数,有一个默认效果
640
+ * @param {Node} config.onViewEnter.arguments[0] 新视图节点
641
+ * @param {Node} config.onViewEnter.arguments[1] 旧的节点
642
+ * @param {Function} config.onError 出错的回调,this指向对应的state,第一个参数是一个object,object.type表示出错的类型,比如view表示加载出错,object.name则对应出错的view name,object.xhr则是当使用默认模板加载器的时候的httpRequest对象,第二个参数是对应的state
643
+ */
644
+ avalon.state.config = function(config) {
645
+ avalon.mix(avalon.state, config || {})
646
+ return avalon
647
+ }
648
+ function callStateFunc(name, state) {
649
+ Event.$fire.apply(Event, arguments)
650
+ return avalon.state[name] ? avalon.state[name].apply(state || mmState.currentState, [].slice.call(arguments, 2)) : 0
651
+ }
652
+ // 状态原型,所有的状态都要继承这个原型
653
+ function StateModel(stateName, options) {
654
+ if(this instanceof StateModel) {
655
+ this.stateName = stateName
656
+ this.formate(options)
657
+ } else {
658
+ var state = _states[stateName] = new StateModel(stateName, options || {})
659
+ return state
660
+ }
661
+ }
662
+ StateModel.is = function(state) {
663
+ return state instanceof StateModel
664
+ }
665
+ StateModel.prototype = {
666
+ formate: function(options) {
667
+ avalon.mix(true, this, options)
668
+ var stateName = this.stateName,
669
+ me = this,
670
+ chain = stateName.split("."),
671
+ len = chain.length - 1,
672
+ sourceLocal = me.sourceLocal = {}
673
+ this.chain = []
674
+ avalon.each(chain, function(key, name) {
675
+ if(key == len) {
676
+ me.chain.push(me)
677
+ } else {
678
+ var n = chain.slice(0, key + 1).join("."),
679
+ state = getStateByName(n)
680
+ if(!state) throw new Error("必须先定义" + n)
681
+ me.chain.push(state)
682
+ }
683
+ })
684
+ if (this.url === void 0) {
685
+ this.abstract = true
686
+ }
687
+ var parent = this.chain[len - 1] || _root
688
+ if (parent) {
689
+ this.url = parent.url + (this.url || "")
690
+ this.parentState = parent
691
+ }
692
+ if (!this.views && stateName != "") {
693
+ var view = {}
694
+ "template,templateUrl,templateProvider,controller,controllerUrl,controllerProvider,viewCache".replace(/\w+/g, function(prop) {
695
+ copyTemplateProperty(view, me, prop)
696
+ })
697
+ var viewname = "viewname" in this ? this.viewname : ""
698
+ this.views = {}
699
+ this.views[viewname] = view
700
+ }
701
+ var views = {},
702
+ viewsIsArray = this.views instanceof Array // 如果是一个数组
703
+
704
+ avalon.each(this.views, function(maybeName, view) {
705
+ var name = viewsIsArray ? view.name || "" : maybeName // 默认缺省
706
+ if (name.indexOf("@") < 0) {
707
+ name += "@" + (parent ? parent.stateName || "" : "")
708
+ }
709
+ views[name] = view
710
+ sourceLocal[name] = {}
711
+ })
712
+ this.views = views
713
+ this._self = options
714
+ this._pending = false
715
+ this.visited = false
716
+ this.params = inherit(parent && parent.params || {})
717
+ this.oldParams = {}
718
+ this.keys = []
719
+
720
+ this.events = {}
721
+ },
722
+ watch: function(eventName, func) {
723
+ var events = this.events[eventName] || []
724
+ this.events[eventName] = events
725
+ events.push(func)
726
+ return func
727
+ },
728
+ fire: function(eventName, state) {
729
+ var events = this.events[eventName] || [], i = 0
730
+ while(events[i]) {
731
+ var res = events[i].apply(this, [].slice.call(arguments, 1))
732
+ if(res === false) {
733
+ events.splice(i, 1)
734
+ } else {
735
+ i++
736
+ }
737
+ }
738
+ },
739
+ unwatch: function(eventName, func) {
740
+ var events = this.events[eventName]
741
+ if(!events) return
742
+ var i = 0
743
+ while(events[i]) {
744
+ if(events[i] == func) return events.splice(i, 1)
745
+ i++
746
+ }
747
+ },
748
+ paramsChanged: function(toParams) {
749
+ var changed = false, keys = this.keys, me= this, params = this.params
750
+ avalon.each(keys, function(index, item) {
751
+ var key = item.name
752
+ if(params[key] != toParams[key]) changed = "param"
753
+ })
754
+ // query
755
+ if(!changed && mmState.currentState === this) {
756
+ changed = !objectCompare(toParams.query, mmState.query) && "query"
757
+ }
758
+ return changed
759
+ },
760
+ filterParams: function(toParams) {
761
+ var params = avalon.mix(true, {}, this.params), keys = this.keys
762
+ avalon.each(keys, function(index, item) {
763
+ params[item.name] = toParams[item.name]
764
+ })
765
+ return params
766
+ },
767
+ syncParams: function(toParams) {
768
+ var me = this
769
+ avalon.each(this.keys, function(index, item) {
770
+ var key = item.name
771
+ if(key in toParams) me.params[key] = toParams[key]
772
+ })
773
+ },
774
+ _onEnter: function() {
775
+ this.query = this.getQuery()
776
+ var me = this,
777
+ arg = Array.prototype.slice.call(arguments),
778
+ done = me._async(),
779
+ prom = getPromise(function(rs, rj) {
780
+ var reason = {
781
+ type: "data",
782
+ state: me,
783
+ params: me.params
784
+ },
785
+ _reject = function(message) {
786
+ reason.message = message
787
+ done.apply(me, [false])
788
+ rj(reason)
789
+ },
790
+ _resovle = function() {
791
+ done.apply(me)
792
+ rs()
793
+ },
794
+ res = me.onEnter.apply(me, arg.concat([_resovle, _reject]))
795
+ // if promise
796
+ if(res && res.then) {
797
+ res.then(_resovle)["catch"](promiseError)
798
+ // error msg
799
+ } else if(res && res !== true) {
800
+ _reject(res)
801
+ } else if(res === undefine) {
802
+ _resovle()
803
+ }
804
+ // res === false will pause here
805
+ })
806
+ },
807
+ /*
808
+ * @interface state.getQuery 获取state的query,等价于state.query
809
+ *<pre>
810
+ * onEnter: function() {
811
+ * var query = this.getQuery()
812
+ * or
813
+ * this.query
814
+ * }
815
+ *</pre>
816
+ */
817
+ getQuery: function() {return mmState.query},
818
+ /*
819
+ * @interface state.getParams 获取state的params,等价于state.params
820
+ *<pre>
821
+ * onEnter: function() {
822
+ * var params = this.getParams()
823
+ * or
824
+ * this.params
825
+ * }
826
+ *</pre>
827
+ */
828
+ getParams: function() {return this.params},
829
+ _async: function() {
830
+ // 没有done回调的时候,防止死球
831
+ if(this.done) this._pending = true
832
+ return this.done || avalon.noop
833
+ },
834
+ onBeforeEnter: avalon.noop, // 切入某个state之前触发
835
+ onEnter: avalon.noop, // 切入触发
836
+ onBeforeExit: avalon.noop, // state退出前触发
837
+ onExit: avalon.noop // 退出后触发
838
+ }
839
+
840
+ _root = StateModel("", {
841
+ url: "",
842
+ views: null,
843
+ "abstract": true
844
+ })
845
+
846
+ /*
847
+ * @interface avalon.controller 给avalon.state视图对象配置控制器
848
+ * @param name 控制器名字
849
+ * @param {Function} factory 控制器函数,传递一个内部生成的控制器对象作为参数
850
+ * @param {Object} factory.arguments[0] $ctrl 控制器的第一个参数:实际生成的控制器对象
851
+ * @param {Object} $ctrl.$vmodels 给视图指定一个scan的vmodels数组,实际scan的时候$vmodels.concat(dom树上下文继承的vmodels)
852
+ * @param {Function} $ctrl.$onBeforeUnload 该视图被卸载前触发,return false可以阻止视图卸载,并阻止跳转
853
+ * @param {Function} $ctrl.$onEnter 给该视图加载数据,可以返回false,或任意不为true的错误信息或一个promise对象,传递3个参数
854
+ * @param {Object} $ctrl.$onEnter.arguments[0] params第一个参数:视图所属的state的参数
855
+ * @param {Function} $ctrl.$onEnter.arguments[1] resolve $onEnter 第二个参数:return false的时候,进入同步等待,直到手动调用resolve
856
+ * @param {Function} $ctrl.$onEnter.arguments[2] reject 第三个参数:数据加载失败,调用
857
+ * @param {Function} $ctrl.$onRendered 视图元素scan完成之后,调用
858
+ */
859
+ avalon.controller = function() {
860
+ var first = arguments[0],
861
+ second = arguments[1]
862
+ if(first && (first instanceof _controller)) return first
863
+ var $ctrl = _controller()
864
+ if(avalon.isFunction(first)) {
865
+ first($ctrl)
866
+ } else if(avalon.isFunction(second)) {
867
+ $ctrl.name = first
868
+ second($ctrl)
869
+ } else if(typeof first == "string" || typeof first == "object") {
870
+ first = first instanceof Array ? first : Array.prototype.slice.call(arguments)
871
+ avalon.each(first, function(index, item) {
872
+ if(typeof item == "string") {
873
+ first[index] = avalon.vmodels[item]
874
+ }
875
+ item = first[index]
876
+ if("$onRendered" in item) $ctrl.$onRendered = item["$onRendered"]
877
+ if("$onEnter" in item) $ctrl.$onEnter = item["$onEnter"]
878
+ })
879
+ $ctrl.$vmodels = first
880
+ } else {
881
+ throw new Error("参数错误" + arguments)
882
+ }
883
+ return $ctrl
884
+ }
885
+ /*
886
+ * @interface avalon.controller.loader avalon.controller异步引入模块的加载器,默认是通过avalon.require加载
887
+ */
888
+ avalon.controller.loader = function(url, callback) {
889
+ // 没有错误回调...
890
+ avalon.require(url, function($ctrl) {
891
+ callback && callback($ctrl)
892
+ })
893
+ }
894
+
895
+ function _controller() {
896
+ if(!(this instanceof _controller)) return new _controller
897
+ this.$vmodels = []
898
+ }
899
+ _controller.prototype = {
900
+ }
901
+
902
+ function objectCompare(objA, objB) {
903
+ for(var i in objA) {
904
+ if(!(i in objB) || objA[i] !== objB[i]) return false
905
+ }
906
+ for(var i in objB) {
907
+ if(!(i in objA) || objA[i] !== objB[i]) return false
908
+ }
909
+ return true
910
+ }
911
+
912
+ //【avalon.state】的辅助函数,确保返回的是函数
913
+ function getFn(object, name) {
914
+ return typeof object[name] === "function" ? object[name] : avalon.noop
915
+ }
916
+
917
+ function getStateByName(stateName) {
918
+ return _states[stateName]
919
+ }
920
+ function getViewNodes(node, query) {
921
+ var nodes, query = query || "ms-view"
922
+ if(node.querySelectorAll) {
923
+ nodes = node.querySelectorAll("[" + query + "]")
924
+ } else {
925
+ nodes = Array.prototype.filter.call(node.getElementsByTagName("*"), function(node) {
926
+ return typeof node.getAttribute(query) === "string"
927
+ })
928
+ }
929
+ return nodes
930
+ }
931
+
932
+ // 【avalon.state】的辅助函数,opts.template的处理函数
933
+ function fromString(template, params, reason) {
934
+ var promise = getPromise(function(resolve, reject) {
935
+ var str = typeof template === "function" ? template(params) : template
936
+ if (typeof str == "string") {
937
+ resolve(str)
938
+ } else {
939
+ reason.message = "template必须对应一个字符串或一个返回字符串的函数"
940
+ reject(reason)
941
+ }
942
+ })
943
+ return promise
944
+ }
945
+ // 【fromUrl】的辅助函数,得到一个XMLHttpRequest对象
946
+ var getXHR = function() {
947
+ return new (window.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP")
948
+ }/*
949
+ * @interface avalon.state.templateLoader 通过url异步加载模板的函数,默认是通过内置的httpRequest去加载,但是在node-webkit环境是不work的,因此开放了这个配置,用以自定义url模板加载器,会在一个promise实例里调用这个方法去加载模板
950
+ * @param url 模板地址
951
+ * @param resolve 加载成功,如果需要缓存模板,请调用<br>
952
+ resolve(avalon.templateCache[url] = templateString)<br>
953
+ 否则,请调用<br>
954
+ resolve(templateString)<br>
955
+ * @param reject 加载失败,请调用reject(reason)
956
+ * @param reason 挂在失败原因的对象
957
+ */
958
+ avalon.state.templateLoader = function(url, resolve, reject, reason) {
959
+ var xhr = getXHR()
960
+ xhr.onreadystatechange = function() {
961
+ if (xhr.readyState === 4) {
962
+ var status = xhr.status;
963
+ if (status > 399 && status < 600) {
964
+ reason.message = "templateUrl对应资源不存在或没有开启 CORS"
965
+ reason.status = status
966
+ reason.xhr = xhr
967
+ reject(reason)
968
+ } else {
969
+ resolve(avalon.templateCache[url] = xhr.responseText)
970
+ }
971
+ }
972
+ }
973
+ xhr.open("GET", url, true)
974
+ if ("withCredentials" in xhr) {
975
+ xhr.withCredentials = true
976
+ }
977
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
978
+ xhr.send()
979
+ }
980
+ // 【avalon.state】的辅助函数,opts.templateUrl的处理函数
981
+ function fromUrl(url, params, reason) {
982
+ var promise = getPromise(function(resolve, reject) {
983
+ if (typeof url === "function") {
984
+ url = url(params)
985
+ }
986
+ if (typeof url !== "string") {
987
+ reason.message = "templateUrl必须对应一个URL"
988
+ return reject(reason)
989
+ }
990
+ if (avalon.templateCache[url]) {
991
+ return resolve(avalon.templateCache[url])
992
+ }
993
+ avalon.state.templateLoader(url, resolve, reject, reason)
994
+ })
995
+ return promise
996
+ }
997
+ // 【avalon.state】的辅助函数,opts.templateProvider的处理函数
998
+ function fromProvider(fn, params, reason) {
999
+ var promise = getPromise(function(resolve, reject) {
1000
+ if (typeof fn === "function") {
1001
+ var ret = fn(params)
1002
+ if (ret && ret.then || typeof ret == "string") {
1003
+ resolve(ret)
1004
+ } else {
1005
+ reason.message = "templateProvider为函数时应该返回一个Promise或thenable对象或字符串"
1006
+ reject(reason)
1007
+ }
1008
+ } else if (fn && fn.then) {
1009
+ resolve(fn)
1010
+ } else {
1011
+ reason.message = "templateProvider不为函数时应该对应一个Promise或thenable对象"
1012
+ reject(reason)
1013
+ }
1014
+ })
1015
+ return promise
1016
+ }
1017
+ // 【avalon.state】的辅助函数,将template或templateUrl或templateProvider转换为可用的Promise对象
1018
+ function fromPromise(config, params, reason) {
1019
+ return config.template ? fromString(config.template, params, reason) :
1020
+ config.templateUrl ? fromUrl(config.templateUrl, params, reason) :
1021
+ config.templateProvider ? fromProvider(config.templateProvider, params, reason) :
1022
+ getPromise(function(resolve, reject) {
1023
+ reason.message = "必须存在template, templateUrl, templateProvider中的一个"
1024
+ reject(reason)
1025
+ })
1026
+ }
1027
+ })