From db7245657963461c951dea505c4afa131b2a1b44 Mon Sep 17 00:00:00 2001 From: leeluolee <87399126@163.com> Date: Wed, 29 Jun 2016 16:47:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:canEnter=E5=92=8CcanLeave=E7=9A=84option?= =?UTF-8?q?=E7=BC=BA=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/restate.js | 11 +- restate-full.js | 1221 +++++++++++++++++++++++++------------------- restate.js | 11 +- 3 files changed, 712 insertions(+), 531 deletions(-) diff --git a/example/restate.js b/example/restate.js index 7f28d58..8deecca 100644 --- a/example/restate.js +++ b/example/restate.js @@ -80,19 +80,20 @@ component: null, // @TODO: - canUpdate: function(){ + canUpdate: function(option){ var canUpdate = this.component && this.component.canUpdate; - if( canUpdate ) return this.component.canUpdate(); + if( canUpdate ) return this.component.canUpdate(option); }, - canLeave: function(){ + canLeave: function(option){ + var canLeave = this.component && this.component.canLeave; - if( canLeave ) return this.component.canLeave(); + if( canLeave ) return this.component.canLeave(option); }, @@ -160,7 +161,7 @@ } var canEnter = this.component && this.component.canEnter; - if( canEnter ) return this.component.canEnter(); + if( canEnter ) return this.component.canEnter(option); }, enter: function( option ){ diff --git a/restate-full.js b/restate-full.js index b0fca52..adae3fa 100644 --- a/restate-full.js +++ b/restate-full.js @@ -7,7 +7,7 @@ if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) - define(factory); + define([], factory); else if(typeof exports === 'object') exports["restate"] = factory(); else @@ -16,41 +16,41 @@ return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; -/******/ + /******/ // The require function /******/ function __webpack_require__(moduleId) { -/******/ + /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; -/******/ + /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; -/******/ + /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ + /******/ // Flag the module as loaded /******/ module.loaded = true; -/******/ + /******/ // Return the exports of the module /******/ return module.exports; /******/ } -/******/ -/******/ + + /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; -/******/ + /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; -/******/ + /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; -/******/ + /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) @@ -141,19 +141,20 @@ return /******/ (function(modules) { // webpackBootstrap component: null, // @TODO: - canUpdate: function(){ + canUpdate: function(option){ var canUpdate = this.component && this.component.canUpdate; - if( canUpdate ) return this.component.canUpdate(); + if( canUpdate ) return this.component.canUpdate(option); }, - canLeave: function(){ + canLeave: function(option){ + var canLeave = this.component && this.component.canLeave; - if( canLeave ) return this.component.canLeave(); + if( canLeave ) return this.component.canLeave(option); }, @@ -221,7 +222,7 @@ return /******/ (function(modules) { // webpackBootstrap } var canEnter = this.component && this.component.canEnter; - if( canEnter ) return this.component.canEnter(); + if( canEnter ) return this.component.canEnter(option); }, enter: function( option ){ @@ -297,71 +298,59 @@ return /******/ (function(modules) { // webpackBootstrap /* 1 */ /***/ function(module, exports, __webpack_require__) { - - var StateMan = __webpack_require__(2); - StateMan.Histery = __webpack_require__(3); - StateMan.util = __webpack_require__(4); - StateMan.State = __webpack_require__(5); + var stateman; + + if( typeof window === 'object' ){ + stateman = __webpack_require__(2); + stateman.History = __webpack_require__(5); + stateman.util = __webpack_require__(4); + stateman.isServer = false; + }else{ + stateman = __webpack_require__(8); + stateman.isServer = true; + } - module.exports = StateMan; + + stateman.State = __webpack_require__(3); + + module.exports = stateman; /***/ }, /* 2 */ /***/ function(module, exports, __webpack_require__) { - var State = __webpack_require__(5), - Histery = __webpack_require__(3), - brow = __webpack_require__(6), + + var State = __webpack_require__(3), + History = __webpack_require__(5), + Base = __webpack_require__(7), _ = __webpack_require__(4), baseTitle = document.title, stateFn = State.prototype.state; - function StateMan(options){ - if(this instanceof StateMan === false){ return new StateMan(options)} + if(this instanceof StateMan === false){ return new StateMan(options); } options = options || {}; - // if(options.history) this.history = options.history; - - this._states = {}; + Base.call(this, options); + if(options.history) this.history = options.history; this._stashCallback = []; - this.strict = options.strict; this.current = this.active = this; - this.title = options.title; - this.on("end", function(){ - var cur = this.current,title; - while( cur ){ - title = cur.title; - if(title) break; - cur = cur.parent; - } - document.title = typeof title === "function"? cur.title(): String( title || baseTitle ) ; - }) - + // auto update document.title, when navigation has been down + this.on("end", function( options ){ + var cur = this.current; + document.title = cur.getTitle( options ) || baseTitle ; + }); } + var o =_.inherit( StateMan, Base.prototype ); - _.extend( _.emitable( StateMan ), { - // keep blank - name: '', - - state: function(stateName, config){ - - var active = this.active; - if(typeof stateName === "string" && active){ - stateName = stateName.replace("~", active.name) - if(active.parent) stateName = stateName.replace("^", active.parent.name || ""); - } - // ^ represent current.parent - // ~ represent current - // only - return stateFn.apply(this, arguments); + _.extend(o , { - }, - start: function(options){ + start: function(options, callback){ - if( !this.history ) this.history = new Histery(options); + this._startCallback = callback; + if( !this.history ) this.history = new History(options); if( !this.history.isStart ){ this.history.on("change", _.bind(this._afterPathChange, this)); this.history.start(); @@ -375,9 +364,13 @@ return /******/ (function(modules) { // webpackBootstrap // @TODO direct go the point state go: function(state, option, callback){ option = option || {}; - if(typeof state === "string") state = this.state(state); + var statename; + if(typeof state === "string") { + statename = state; + state = this.state(state); + } - if(!state) return; + if(!state) return this._notfound({state:statename}); if(typeof option === "function"){ callback = option; @@ -385,7 +378,7 @@ return /******/ (function(modules) { // webpackBootstrap } if(option.encode !== false){ - var url = state.encode(option.param) + var url = state.encode(option.param); option.path = url; this.nav(url, {silent: true, replace: option.replace}); } @@ -404,33 +397,11 @@ return /******/ (function(modules) { // webpackBootstrap options.path = url; this.history.nav( url, _.extend({silent: true}, options)); - if(!options.silent) this._afterPathChange( _.cleanPath(url) , options , callback) + if(!options.silent) this._afterPathChange( _.cleanPath(url) , options , callback); return this; }, - decode: function(path){ - - var pathAndQuery = path.split("?"); - var query = this._findQuery(pathAndQuery[1]); - path = pathAndQuery[0]; - var state = this._findState(this, path); - if(state) _.extend(state.param, query); - return state; - }, - encode: function(stateName, param){ - var state = this.state(stateName); - return state? state.encode(param) : ''; - }, - // notify specify state - // check the active statename whether to match the passed condition (stateName and param) - is: function(stateName, param, isStrict){ - if(!stateName) return false; - var stateName = (stateName.name || stateName); - var current = this.current, currentName = current.name; - var matchPath = isStrict? currentName === stateName : (currentName + ".").indexOf(stateName + ".")===0; - return matchPath && (!param || _.eql(param, this.param)); - }, // after pathchange changed // @TODO: afterPathChange need based on decode _afterPathChange: function(path, options ,callback){ @@ -444,19 +415,20 @@ return /******/ (function(modules) { // webpackBootstrap options.path = path; if(!found){ - // loc.nav("$default", {silent: true}) return this._notfound(options); } options.param = found.param; - this._go( found, options, callback ); + if( options.firstTime && !callback){ + callback = this._startCallback; + delete this._startCallback; + } + + this._go( found.state, options, callback ); }, _notfound: function(options){ - // var $notfound = this.state("$notfound"); - - // if( $notfound ) this._go($notfound, options); return this.emit("notfound", options); }, @@ -465,25 +437,11 @@ return /******/ (function(modules) { // webpackBootstrap var over; - // if(typeof state === "string") state = this.state(state); - - // if(!state) return _.log("destination is not defined") + if(state.hasNext && this.strict) return this._notfound({name: state.name}); - // not touch the end in previous transtion - - // if( this.pending ){ - // var pendingCurrent = this.pending.current; - // this.pending.stop(); - // _.log("naving to [" + pendingCurrent.name + "] will be stoped, trying to ["+state.name+"] now"); - // } - // if(this.active !== this.current){ - // // we need return - // _.log("naving to [" + this.current.name + "] will be stoped, trying to ["+state.name+"] now"); - // this.current = this.active; - // // back to before - // } + option.param = option.param || {}; var current = this.current, @@ -496,7 +454,7 @@ return /******/ (function(modules) { // webpackBootstrap // if we done the navigating when start function done(success){ over = true; - if( success !== false ) self.emit("end"); + if( success !== false ) self.emit("end", option); self.pending = null; self._popStash(option); } @@ -508,61 +466,53 @@ return /******/ (function(modules) { // webpackBootstrap option.stop = function(){ done(false); self.nav( prepath? prepath: "/", {silent:true}); - } + }; self.emit("begin", option); } // if we stop it in 'begin' listener if(over === true) return; - if(current !== state){ - // option as transition object. + option.phase = 'permission'; + this._walk(current, state, option, true , _.bind( function( notRejected ){ - option.phase = 'permission'; - this._walk(current, state, option, true , _.bind( function( notRejected ){ + if( notRejected===false ){ + // if reject in callForPermission, we will return to old + prepath && this.nav( prepath, {silent: true}); - if( notRejected===false ){ - // if reject in callForPermission, we will return to old - prepath && this.nav( prepath, {silent: true}) + done(false, 2); - done(false, 2) + return this.emit('abort', option); + + } + // stop previous pending. + if(this.pending) this.pending.stop(); + this.pending = option; + this.path = option.path; + this.current = option.current; + this.param = option.param; + this.previous = option.previous; + option.phase = 'navigation'; + this._walk(current, state, option, false, _.bind(function( notRejected ){ + + if( notRejected === false ){ + this.current = this.active; + done(false); return this.emit('abort', option); + } - } - - // stop previous pending. - if(this.pending) this.pending.stop() - this.pending = option; - this.path = option.path; - this.current = option.current; - this.param = option.param; - this.previous = option.previous; - option.phase = 'navigation'; - this._walk(current, state, option, false, _.bind(function( notRejected ){ - - if( notRejected === false ){ - this.current = this.active; - done(false) - return this.emit('abort', option); - } + this.active = option.current; - this.active = option.current; + option.phase = 'completion'; + return done(); - option.phase = 'completion'; - return done() + }, this) ); - }, this) ) + }, this) ); - }, this) ) - }else{ - self._checkQueryAndParam(baseState, option); - this.pending = null; - done(); - } - }, _popStash: function(option){ @@ -573,30 +523,35 @@ return /******/ (function(modules) { // webpackBootstrap if(!len) return; for(var i = 0; i < len; i++){ - stash[i].call(this, option) + stash[i].call(this, option); } }, // the transition logic Used in Both canLeave canEnter && leave enter LifeCycle _walk: function(from, to, option, callForPermit , callback){ + // if(from === to) return callback(); // nothing -> app.state var parent = this._findBase(from , to); + var self = this; - option.basckward = true; - this._transit( from, parent, option, callForPermit , _.bind( function( notRejected ){ + option.backward = true; + this._transit( from, parent, option, callForPermit , function( notRejected ){ if( notRejected === false ) return callback( notRejected ); // only actual transiton need update base state; - if( !callForPermit ) this._checkQueryAndParam(parent, option) + option.backward = false; + self._walkUpdate(self, parent, option, callForPermit, function(notRejected){ + if(notRejected === false) return callback(notRejected); + + self._transit( parent, to, option, callForPermit, callback); - option.basckward = false; - this._transit( parent, to, option, callForPermit, callback) + }); - }, this) ) + }); }, @@ -609,7 +564,7 @@ return /******/ (function(modules) { // webpackBootstrap var applied; // use canEnter to detect permission - if( callForPermit) method = 'can' + method.replace(/^\w/, function(a){ return a.toUpperCase() }); + if( callForPermit) method = 'can' + method.replace(/^\w/, function(a){ return a.toUpperCase(); }); var loop = _.bind(function( notRejected ){ @@ -626,7 +581,7 @@ return /******/ (function(modules) { // webpackBootstrap applied = this._computeNext(applied, to); } - if( (back && applied === to) || !applied )return callback( notRejected ) + if( (back && applied === to) || !applied )return callback( notRejected ); this._moveOn( applied, method, option, loop ); @@ -645,7 +600,7 @@ return /******/ (function(modules) { // webpackBootstrap isPending = true; return done; - } + }; function done( notRejected ){ if( isDone ) return; @@ -654,11 +609,9 @@ return /******/ (function(modules) { // webpackBootstrap callback( notRejected ); } - - option.stop = function(){ done( false ); - } + }; this.active = applied; @@ -675,14 +628,18 @@ return /******/ (function(modules) { // webpackBootstrap } // if haven't call option.async yet - if( !isPending ) done( retValue ) + if( !isPending ) done( retValue ); }, _wrapPromise: function( promise, next ){ - return promise.then( next, function(){next(false)}) ; + return promise.then( next, function(err){ + //TODO: 万一promise中throw了Error如何处理? + if(err instanceof Error) throw err; + next(false); + }) ; }, @@ -691,8 +648,8 @@ return /******/ (function(modules) { // webpackBootstrap var fname = from.name; var tname = to.name; - var tsplit = tname.split('.') - var fsplit = fname.split('.') + var tsplit = tname.split('.'); + var fsplit = fname.split('.'); var tlen = tsplit.length; var flen = fsplit.length; @@ -706,7 +663,7 @@ return /******/ (function(modules) { // webpackBootstrap fsplit.pop(); } - return this.state(fsplit.join('.')) + return this.state(fsplit.join('.')); }, @@ -715,7 +672,6 @@ return /******/ (function(modules) { // webpackBootstrap var queries = querystr && querystr.split("&"), query= {}; if(queries){ var len = queries.length; - var query = {}; for(var i =0; i< len; i++){ var tmp = queries[i].split("="); query[tmp[0]] = tmp[1]; @@ -724,25 +680,9 @@ return /******/ (function(modules) { // webpackBootstrap return query; }, - _findState: function(state, path){ - var states = state._states, found, param; - // leaf-state has the high priority upon branch-state - if(state.hasNext){ - for(var i in states) if(states.hasOwnProperty(i)){ - found = this._findState( states[i], path ); - if( found ) return found; - } - } - // in strict mode only leaf can be touched - // if all children is don. will try it self - param = state.regexp && state.decode(path); - if(param){ - state.param = param; - return state; - }else{ - return false; - } + _sortState: function( a, b ){ + return ( b.priority || 0 ) - ( a.priority || 0 ); }, // find the same branch; _findBase: function(now, before){ @@ -759,298 +699,277 @@ return /******/ (function(modules) { // webpackBootstrap } }, // check the query and Param - _checkQueryAndParam: function(baseState, options){ + _walkUpdate: function(baseState, to, options, callForPermit, done){ + var method = callForPermit? 'canUpdate': 'update'; var from = baseState; - while( from !== this ){ - from.update && from.update(options); - from = from.parent; - } + var self = this; - } + var pathes = [], node = to; + while(node !== this){ + pathes.push( node ); + node = node.parent; + } - }, true) + var loop = function( notRejected ){ + if( notRejected === false ) return done( false ); + if( !pathes.length ) return done(); + from = pathes.pop(); + self._moveOn( from, method, options, loop ) + } + self._moveOn( from, method, options, loop ) + } + }, true); module.exports = StateMan; - /***/ }, /* 3 */ /***/ function(module, exports, __webpack_require__) { - - // MIT - // Thx Backbone.js 1.1.2 and https://github.com/cowboy/jquery-hashchange/blob/master/jquery.ba-hashchange.js - // for iframe patches in old ie. - - var browser = __webpack_require__(6); var _ = __webpack_require__(4); + function State(option){ + this._states = {}; + this._pending = false; + this.visited = false; + if(option) this.config(option); + } - // the mode const - var QUIRK = 3, - HASH = 1, - HISTORY = 2; - - - - // extract History for test - // resolve the conficlt with the Native History - function Histery(options){ - options = options || {}; - - // Trick from backbone.history for anchor-faked testcase - this.location = options.location || browser.location; - // mode config, you can pass absolute mode (just for test); - this.html5 = options.html5; - this.mode = options.html5 && browser.history ? HISTORY: HASH; - if( !browser.hash ) this.mode = QUIRK; - if(options.mode) this.mode = options.mode; + //regexp cache + State.rCache = {}; - // hash prefix , used for hash or quirk mode - this.prefix = "#" + (options.prefix || "") ; - this.rPrefix = new RegExp(this.prefix + '(.*)$'); - this.interval = options.interval || 66; + _.extend( _.emitable( State ), { - // the root regexp for remove the root for the path. used in History mode - this.root = options.root || "/" ; - this.rRoot = new RegExp("^" + this.root); + getTitle: function(options){ + var cur = this ,title; + while( cur ){ + title = cur.title; + if(title) return typeof title === 'function'? cur.title(options): cur.title + cur = cur.parent; + } + return title; + }, - this._fixInitState(); - this.autolink = options.autolink!==false; + state: function(stateName, config){ + if(_.typeOf(stateName) === "object"){ + var keys = _.values(stateName, true); + keys.sort(function(ka, kb){ + return _.countDot(ka) - _.countDot(kb); + }); + + for(var i = 0, len = keys.length; i< len ;i++){ + var key = keys[i]; + this.state(key, stateName[key]) + } + return this; + } + var current = this, next, nextName, states = this._states, i=0; - this.curPath = undefined; - } + if( typeof stateName === "string" ) stateName = stateName.split("."); - _.extend( _.emitable(Histery), { - // check the - start: function(){ - var path = this.getPath(); - this._checkPath = _.bind(this.checkPath, this); + var slen = stateName.length; + var stack = []; - if( this.isStart ) return; - this.isStart = true; + do{ + nextName = stateName[i]; + next = states[nextName]; + stack.push(nextName); + if(!next){ + if(!config) return; + next = states[nextName] = new State(); + _.extend(next, { + parent: current, + manager: current.manager || current, + name: stack.join("."), + currentName: nextName + }); + current.hasNext = true; + next.configUrl(); + } + current = next; + states = next._states; + }while((++i) < slen ) - if(this.mode === QUIRK){ - this._fixHashProbelm(path); + if(config){ + next.config(config); + return this; + } else { + return current; } + }, - switch ( this.mode ){ - case HASH: - browser.on(window, "hashchange", this._checkPath); - break; - case HISTORY: - browser.on(window, "popstate", this._checkPath); - break; - case QUIRK: - this._checkLoop(); - } - // event delegate - this.autolink && this._autolink(); + config: function(configure){ - this.curPath = path; + configure = this._getConfig(configure); - this.emit("change", path); + for(var i in configure){ + var prop = configure[i]; + switch(i){ + case "url": + if(typeof prop === "string"){ + this.url = prop; + this.configUrl(); + } + break; + case "events": + this.on(prop); + break; + default: + this[i] = prop; + } + } }, - // the history teardown - stop: function(){ - browser.off(window, 'hashchange', this._checkPath) - browser.off(window, 'popstate', this._checkPath) - clearTimeout(this.tid); - this.isStart = false; - this._checkPath = null; + // children override + _getConfig: function(configure){ + return typeof configure === "function"? {enter: configure} : configure; }, - // get the path modify - checkPath: function(ev){ - var path = this.getPath(), curPath = this.curPath; + //from url + configUrl: function(){ + var url = "" , base = this; - //for oldIE hash history issue - if(path === curPath && this.iframe){ - path = this.getPath(this.iframe.location); - } + while( base ){ - if( path !== curPath ) { - this.iframe && this.nav(path, {silent: true}); - this.curPath = path; - this.emit('change', path); - } - }, - // get the current path - getPath: function(location){ - var location = location || this.location, tmp; - if( this.mode !== HISTORY ){ - tmp = location.href.match(this.rPrefix); - return tmp && tmp[1]? tmp[1]: ""; + url = (typeof base.url === "string" ? base.url: (base.currentName || "")) + "/" + url; - }else{ - return _.cleanPath(( location.pathname + location.search || "" ).replace( this.rRoot, "/" )) + // means absolute; + if(url.indexOf("^/") === 0) { + url = url.slice(1); + break; + } + base = base.parent; } - }, - - nav: function(to, options ){ + this.pattern = _.cleanPath("/" + url); + var pathAndQuery = this.pattern.split("?"); + this.pattern = pathAndQuery[0]; + // some Query we need watched - var iframe = this.iframe; + _.extend(this, _.normalize(this.pattern), true); + }, + encode: function(param){ + var state = this; + param = param || {}; - options = options || {}; + var matched = "%"; - to = _.cleanPath(to); - - if(this.curPath == to) return; - - // pushState wont trigger the checkPath - // but hashchange will - // so we need set curPath before to forbit the CheckPath - this.curPath = to; + var url = state.matches.replace(/\(([\w-]+)\)/g, function(all, capture){ + var sec = param[capture] || ""; + matched+= capture + "%"; + return sec; + }) + "?"; - // 3 or 1 is matched - if( this.mode !== HISTORY ){ - this._setHash(this.location, to, options.replace) - if( iframe && this.getPath(iframe.location) !== to ){ - if(!options.replace) iframe.document.open().close(); - this._setHash(this.iframe.location, to, options.replace) - } - }else{ - history[options.replace? 'replaceState': 'pushState']( {}, options.title || "" , _.cleanPath( this.root + to ) ) + // remained is the query, we need concat them after url as query + for(var i in param) { + if( matched.indexOf("%"+i+"%") === -1) url += i + "=" + param[i] + "&"; } - - if( !options.silent ) this.emit('change', to); + return _.cleanPath( url.replace(/(?:\?|&)$/,"") ); }, - _autolink: function(){ - if(this.mode!==HISTORY) return; - // only in html5 mode, the autolink is works - // if(this.mode !== 2) return; - var prefix = this.prefix, self = this; - browser.on( document.body, "click", function(ev){ + decode: function( path ){ + var matched = this.regexp.exec(path), + keys = this.keys; - var target = ev.target || ev.srcElement; - if( target.tagName.toLowerCase() !== "a" ) return; - var tmp = (browser.getHref(target)||"").match(self.rPrefix); - var hash = tmp && tmp[1]? tmp[1]: ""; + if(matched){ - if(!hash) return; - - ev.preventDefault && ev.preventDefault(); - self.nav( hash ) - return (ev.returnValue = false); - } ) - }, - _setHash: function(location, path, replace){ - var href = location.href.replace(/(javascript:|#).*$/, ''); - if (replace){ - location.replace(href + this.prefix+ path); + var param = {}; + for(var i =0,len=keys.length;i 7), history: win.history && "onpopstate" in win, location: win.location, + isSameDomain: function(url){ + var matched = url.match(/^.*?:\/\/([^/]*)/); + if(matched){ + return matched[0] == this.location.origin; + } + return true; + }, getHref: function(node){ return "href" in node ? node.getAttribute("href", 2) : node.getAttribute("href"); }, on: "addEventListener" in win ? // IE10 attachEvent is not working when binding the onpopstate, so we need check addEventLister first function(node,type,cb){return node.addEventListener( type, cb )} : function(node,type,cb){return node.attachEvent( "on" + type, cb )}, - + off: "removeEventListener" in win ? function(node,type,cb){return node.removeEventListener( type, cb )} : function(node,type,cb){return node.detachEvent( "on" + type, cb )} } +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + + var State = __webpack_require__(3), + _ = __webpack_require__(4), + stateFn = State.prototype.state; + + function BaseMan( options ){ + + options = options || {}; + + this._states = {}; + + this.strict = options.strict; + this.title = options.title; + + if(options.routes) this.state(options.routes); + + } + + _.extend( _.emitable( BaseMan ), { + // keep blank + name: '', + + root: true, + + + state: function(stateName){ + + var active = this.active; + var args = _.slice(arguments, 1); + + if(typeof stateName === "string" && active){ + stateName = stateName.replace("~", active.name); + if(active.parent) stateName = stateName.replace("^", active.parent.name || ""); + } + // ^ represent current.parent + // ~ represent current + // only + args.unshift(stateName); + return stateFn.apply(this, args); + + }, + + decode: function(path, needLocation){ + + var pathAndQuery = path.split("?"); + var query = this._findQuery(pathAndQuery[1]); + path = pathAndQuery[0]; + var found = this._findState(this, path); + if(found) _.extend(found.param, query); + return found; + + }, + encode: function(stateName, param, needLink){ + var state = this.state(stateName); + var history = this.history; + if(!state) return; + var url = state.encode(param); + + return needLink? (history.mode!==2? history.prefix + url : url ): url; + }, + // notify specify state + // check the active statename whether to match the passed condition (stateName and param) + is: function(stateName, param, isStrict){ + if(!stateName) return false; + stateName = (stateName.name || stateName); + var current = this.current, currentName = current.name; + var matchPath = isStrict? currentName === stateName : (currentName + ".").indexOf(stateName + ".")===0; + return matchPath && (!param || _.eql(param, this.param)); + }, + + + _wrapPromise: function( promise, next ){ + + return promise.then( next, function(){ next(false); }) ; + + }, + + _findQuery: function(querystr){ + + var queries = querystr && querystr.split("&"), query= {}; + if(queries){ + var len = queries.length; + for(var i =0; i< len; i++){ + var tmp = queries[i].split("="); + query[tmp[0]] = tmp[1]; + } + } + return query; + + }, + _findState: function(state, path){ + var states = state._states, found, param; + + // leaf-state has the high priority upon branch-state + if(state.hasNext){ + + var stateList = _.values( states ).sort( this._sortState ); + var len = stateList.length; + + for(var i = 0; i < len; i++){ + + found = this._findState( stateList[i], path ); + if( found ) return found; + } + + } + // in strict mode only leaf can be touched + // if all children is don. will try it self + param = state.regexp && state.decode(path); + if(param){ + return { + state: state, + param: param + } + }else{ + return false; + } + }, + _sortState: function( a, b ){ + return ( b.priority || 0 ) - ( a.priority || 0 ); + }, + // find the same branch; + _findBase: function(now, before){ + + if(!now || !before || now == this || before == this) return this; + var np = now, bp = before, tmp; + while(np && bp){ + tmp = bp; + while(tmp){ + if(np === tmp) return tmp; + tmp = tmp.parent; + } + np = np.parent; + } + }, + + }, true); + + module.exports = BaseMan; + + + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + + var _ = __webpack_require__(4); + var Base = __webpack_require__(7); + + function ServerManager( options ){ + if(this instanceof ServerManager === false){ return new ServerManager(options); } + Base.apply( this, arguments ); + } + + var o =_.inherit( ServerManager, Base.prototype ); + + _.extend(o , { + exec: function ( path ){ + var found = this.decode(path); + if( !found ) return; + var param = found.param; + + //@FIXIT: We NEED decodeURIComponent in server side!! + + for(var i in param){ + if(typeof param[i] === 'string') param[i] = decodeURIComponent(param[i]); + } + var states = []; + var state = found.state; + this.current = state; + + while(state && !state.root){ + states.unshift( state ); + state = state.parent; + } + + return { + states: states, + param: param + } + } + }) + + + module.exports = ServerManager /***/ } /******/ ]) }); +; \ No newline at end of file diff --git a/restate.js b/restate.js index 7f28d58..8deecca 100644 --- a/restate.js +++ b/restate.js @@ -80,19 +80,20 @@ component: null, // @TODO: - canUpdate: function(){ + canUpdate: function(option){ var canUpdate = this.component && this.component.canUpdate; - if( canUpdate ) return this.component.canUpdate(); + if( canUpdate ) return this.component.canUpdate(option); }, - canLeave: function(){ + canLeave: function(option){ + var canLeave = this.component && this.component.canLeave; - if( canLeave ) return this.component.canLeave(); + if( canLeave ) return this.component.canLeave(option); }, @@ -160,7 +161,7 @@ } var canEnter = this.component && this.component.canEnter; - if( canEnter ) return this.component.canEnter(); + if( canEnter ) return this.component.canEnter(option); }, enter: function( option ){