avalon-mmRouter-rails 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +17 -0
- data/Rakefile +2 -0
- data/avalon-mmRouter-rails.gemspec +23 -0
- data/lib/avalon/mmRouter/rails.rb +13 -0
- data/lib/avalon/mmRouter/rails/version.rb +7 -0
- data/vendor/assets/javascripts/avalon/mmHistory.js +310 -0
- data/vendor/assets/javascripts/avalon/mmPromise.js +221 -0
- data/vendor/assets/javascripts/avalon/mmRouter.js +351 -0
- data/vendor/assets/javascripts/avalon/mmState.js +1027 -0
- data/vendor/assets/javascripts/avalon/old-mmState.js +771 -0
- metadata +86 -0
| @@ -0,0 +1,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 | 
            +
            })
         |