avalon-mmRouter-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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
+ })