aboutsummaryrefslogtreecommitdiff
path: root/src/libs/stateman.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/stateman.js')
-rw-r--r--src/libs/stateman.js1140
1 files changed, 1140 insertions, 0 deletions
diff --git a/src/libs/stateman.js b/src/libs/stateman.js
new file mode 100644
index 0000000..b8c155e
--- /dev/null
+++ b/src/libs/stateman.js
@@ -0,0 +1,1140 @@
+/**
+@author leeluolee
+@version 0.2.0
+@homepage https://github.com/leeluolee/stateman
+*/
+
+
+
+(function webpackUniversalModuleDefinition(root, factory) {
+ if(typeof exports === 'object' && typeof module === 'object')
+ module.exports = factory();
+ else if(typeof define === 'function' && define.amd)
+ define([], factory);
+ else if(typeof exports === 'object')
+ exports["StateMan"] = factory();
+ else
+ root["StateMan"] = factory();
+})(this, function() {
+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);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+
+ var StateMan = __webpack_require__(1);
+ StateMan.Histery = __webpack_require__(4);
+ StateMan.util = __webpack_require__(3);
+ StateMan.State = __webpack_require__(2);
+
+ module.exports = StateMan;
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var State = __webpack_require__(2),
+ Histery = __webpack_require__(4),
+ brow = __webpack_require__(5),
+ _ = __webpack_require__(3),
+ baseTitle = document.title,
+ stateFn = State.prototype.state;
+
+
+ function StateMan(options){
+
+ if(this instanceof StateMan === false){ return new StateMan(options)}
+ options = options || {};
+ // if(options.history) this.history = options.history;
+
+ this._states = {};
+ 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 ) ;
+ })
+
+ }
+
+
+ _.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);
+
+ },
+ start: function(options){
+
+ if( !this.history ) this.history = new Histery(options);
+ if( !this.history.isStart ){
+ this.history.on("change", _.bind(this._afterPathChange, this));
+ this.history.start();
+ }
+ return this;
+
+ },
+ stop: function(){
+ this.history.stop();
+ },
+ // @TODO direct go the point state
+ go: function(state, option, callback){
+ option = option || {};
+ if(typeof state === "string") state = this.state(state);
+
+ if(!state) return;
+
+ if(typeof option === "function"){
+ callback = option;
+ option = {};
+ }
+
+ if(option.encode !== false){
+ var url = state.encode(option.param)
+ option.path = url;
+ this.nav(url, {silent: true, replace: option.replace});
+ }
+
+ this._go(state, option, callback);
+
+ return this;
+ },
+ nav: function(url, options, callback){
+ if(typeof options === "function"){
+ callback = options;
+ options = {};
+ }
+ options = options || {};
+
+ options.path = url;
+
+ this.history.nav( url, _.extend({silent: true}, options));
+ 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){
+
+ this.emit("history:change", path);
+
+ var found = this.decode(path);
+
+ options = options || {};
+
+ options.path = path;
+
+ if(!found){
+ // loc.nav("$default", {silent: true})
+ return this._notfound(options);
+ }
+
+ options.param = found.param;
+
+ this._go( found, options, callback );
+ },
+ _notfound: function(options){
+
+ // var $notfound = this.state("$notfound");
+
+ // if( $notfound ) this._go($notfound, options);
+
+ return this.emit("notfound", options);
+ },
+ // goto the state with some option
+ _go: function(state, option, callback){
+
+ 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,
+ baseState = this._findBase(current, state),
+ prepath = this.path,
+ self = this;
+
+
+ if( typeof callback === "function" ) this._stashCallback.push(callback);
+ // if we done the navigating when start
+ function done(success){
+ over = true;
+ if( success !== false ) self.emit("end");
+ self.pending = null;
+ self._popStash(option);
+ }
+
+ option.previous = current;
+ option.current = state;
+
+ if(current !== state){
+ 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 ){
+
+ if( notRejected===false ){
+ // if reject in callForPermission, we will return to old
+ prepath && this.nav( prepath, {silent: true})
+
+ 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);
+ }
+
+
+ this.active = option.current;
+
+ option.phase = 'completion';
+ return done()
+
+ }, this) )
+
+ }, this) )
+
+ }else{
+ self._checkQueryAndParam(baseState, option);
+ this.pending = null;
+ done();
+ }
+
+ },
+ _popStash: function(option){
+
+ var stash = this._stashCallback, len = stash.length;
+
+ this._stashCallback = [];
+
+ if(!len) return;
+
+ for(var i = 0; i < len; i++){
+ stash[i].call(this, option)
+ }
+ },
+
+ // the transition logic Used in Both canLeave canEnter && leave enter LifeCycle
+
+ _walk: function(from, to, option, callForPermit , callback){
+
+ // nothing -> app.state
+ var parent = this._findBase(from , to);
+
+
+ option.basckward = true;
+ this._transit( from, parent, option, callForPermit , _.bind( function( notRejected ){
+
+ if( notRejected === false ) return callback( notRejected );
+
+ // only actual transiton need update base state;
+ if( !callForPermit ) this._checkQueryAndParam(parent, option)
+
+ option.basckward = false;
+ this._transit( parent, to, option, callForPermit, callback)
+
+ }, this) )
+
+ },
+
+ _transit: function(from, to, option, callForPermit, callback){
+ // touch the ending
+ if( from === to ) return callback();
+
+ var back = from.name.length > to.name.length;
+ var method = back? 'leave': 'enter';
+ var applied;
+
+ // use canEnter to detect permission
+ if( callForPermit) method = 'can' + method.replace(/^\w/, function(a){ return a.toUpperCase() });
+
+ var loop = _.bind(function( notRejected ){
+
+
+ // stop transition or touch the end
+ if( applied === to || notRejected === false ) return callback(notRejected);
+
+ if( !applied ) {
+
+ applied = back? from : this._computeNext(from, to);
+
+ }else{
+
+ applied = this._computeNext(applied, to);
+ }
+
+ if( (back && applied === to) || !applied )return callback( notRejected )
+
+ this._moveOn( applied, method, option, loop );
+
+ }, this);
+
+ loop();
+ },
+
+ _moveOn: function( applied, method, option, callback){
+
+ var isDone = false;
+ var isPending = false;
+
+ option.async = function(){
+
+ isPending = true;
+
+ return done;
+ }
+
+ function done( notRejected ){
+ if( isDone ) return;
+ isPending = false;
+ isDone = true;
+ callback( notRejected );
+ }
+
+
+
+ option.stop = function(){
+ done( false );
+ }
+
+
+ this.active = applied;
+ var retValue = applied[method]? applied[method]( option ): true;
+
+ if(method === 'enter') applied.visited = true;
+ // promise
+ // need breadk , if we call option.stop first;
+
+ if( _.isPromise(retValue) ){
+
+ return this._wrapPromise(retValue, done);
+
+ }
+
+ // if haven't call option.async yet
+ if( !isPending ) done( retValue )
+
+ },
+
+
+ _wrapPromise: function( promise, next ){
+
+ return promise.then( next, function(){next(false)}) ;
+
+ },
+
+ _computeNext: function( from, to ){
+
+ var fname = from.name;
+ var tname = to.name;
+
+ var tsplit = tname.split('.')
+ var fsplit = fname.split('.')
+
+ var tlen = tsplit.length;
+ var flen = fsplit.length;
+
+ if(fname === '') flen = 0;
+ if(tname === '') tlen = 0;
+
+ if( flen < tlen ){
+ fsplit[flen] = tsplit[flen];
+ }else{
+ fsplit.pop();
+ }
+
+ return this.state(fsplit.join('.'))
+
+ },
+
+ _findQuery: function(querystr){
+
+ 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];
+ }
+ }
+ 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;
+ }
+ },
+ // 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;
+ }
+ },
+ // check the query and Param
+ _checkQueryAndParam: function(baseState, options){
+
+ var from = baseState;
+ while( from !== this ){
+ from.update && from.update(options);
+ from = from.parent;
+ }
+
+ }
+
+ }, true)
+
+
+
+ module.exports = StateMan;
+
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _ = __webpack_require__(3);
+
+
+
+ function State(option){
+ this._states = {};
+ this._pending = false;
+ this.visited = false;
+ if(option) this.config(option);
+ }
+
+
+ //regexp cache
+ State.rCache = {};
+
+ _.extend( _.emitable( State ), {
+
+ state: function(stateName, config){
+ if(_.typeOf(stateName) === "object"){
+ for(var i in stateName){
+ this.state(i, stateName[i])
+ }
+ return this;
+ }
+ var current, next, nextName, states = this._states, i=0;
+
+ if( typeof stateName === "string" ) stateName = stateName.split(".");
+
+ var slen = stateName.length, current = this;
+ var stack = [];
+
+
+ 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(config){
+ next.config(config);
+ return this;
+ } else {
+ return current;
+ }
+ },
+
+ config: function(configure){
+
+ configure = this._getConfig(configure);
+
+ 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;
+ }
+ }
+ },
+
+ // children override
+ _getConfig: function(configure){
+ return typeof configure === "function"? {enter: configure} : configure;
+ },
+
+ //from url
+
+ configUrl: function(){
+ var url = "" , base = this, currentUrl;
+ var _watchedParam = [];
+
+ while( base ){
+
+ url = (typeof base.url === "string" ? base.url: (base.currentName || "")) + "/" + url;
+
+ // means absolute;
+ if(url.indexOf("^/") === 0) {
+ url = url.slice(1);
+ break;
+ }
+ base = base.parent;
+ }
+ this.pattern = _.cleanPath("/" + url);
+ var pathAndQuery = this.pattern.split("?");
+ this.pattern = pathAndQuery[0];
+ // some Query we need watched
+
+ _.extend(this, _.normalize(this.pattern), true);
+ },
+ encode: function(param){
+ var state = this;
+ param = param || {};
+
+ var matched = "%";
+
+ var url = state.matches.replace(/\(([\w-]+)\)/g, function(all, capture){
+ var sec = param[capture] || "";
+ matched+= capture + "%";
+ return sec;
+ }) + "?";
+
+ // 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] + "&";
+ }
+ return _.cleanPath( url.replace(/(?:\?|&)$/,"") )
+ },
+ decode: function( path ){
+ var matched = this.regexp.exec(path),
+ keys = this.keys;
+
+ if(matched){
+
+ var param = {};
+ for(var i =0,len=keys.length;i<len;i++){
+ param[keys[i]] = matched[i+1]
+ }
+ return param;
+ }else{
+ return false;
+ }
+ },
+ // by default, all lifecycle is permitted
+
+ async: function(){
+ throw new Error( 'please use option.async instead')
+ }
+
+ })
+
+
+ module.exports = State;
+
+/***/ },
+/* 3 */
+/***/ function(module, exports) {
+
+ var _ = module.exports = {};
+ var slice = [].slice, o2str = ({}).toString;
+
+
+ // merge o2's properties to Object o1.
+ _.extend = function(o1, o2, override){
+ for(var i in o2) if(override || o1[i] === undefined){
+ o1[i] = o2[i]
+ }
+ return o1;
+ }
+
+
+
+ _.slice = function(arr, index){
+ return slice.call(arr, index);
+ }
+
+ _.typeOf = function typeOf (o) {
+ return o == null ? String(o) : o2str.call(o).slice(8, -1).toLowerCase();
+ }
+
+ //strict eql
+ _.eql = function(o1, o2){
+ var t1 = _.typeOf(o1), t2 = _.typeOf(o2);
+ if( t1 !== t2) return false;
+ if(t1 === 'object'){
+ var equal = true;
+ // only check the first's propertie
+ for(var i in o1){
+ if( o1[i] !== o2[i] ) equal = false;
+ }
+ return equal;
+ }
+ return o1 === o2;
+ }
+
+
+ // small emitter
+ _.emitable = (function(){
+ function norm(ev){
+ var eventAndNamespace = (ev||'').split(':');
+ return {event: eventAndNamespace[0], namespace: eventAndNamespace[1]}
+ }
+ var API = {
+ once: function(event, fn){
+ var callback = function(){
+ fn.apply(this, arguments)
+ this.off(event, callback)
+ }
+ return this.on(event, callback)
+ },
+ on: function(event, fn) {
+ if(typeof event === 'object'){
+ for (var i in event) {
+ this.on(i, event[i]);
+ }
+ return this;
+ }
+ var ne = norm(event);
+ event=ne.event;
+ if(event && typeof fn === 'function' ){
+ var handles = this._handles || (this._handles = {}),
+ calls = handles[event] || (handles[event] = []);
+ fn._ns = ne.namespace;
+ calls.push(fn);
+ }
+ return this;
+ },
+ off: function(event, fn) {
+ var ne = norm(event); event = ne.event;
+ if(!event || !this._handles) this._handles = {};
+
+ var handles = this._handles , calls;
+
+ if (calls = handles[event]) {
+ if (!fn && !ne.namespace) {
+ handles[event] = [];
+ }else{
+ for (var i = 0, len = calls.length; i < len; i++) {
+ if ( (!fn || fn === calls[i]) && (!ne.namespace || calls[i]._ns === ne.namespace) ) {
+ calls.splice(i, 1);
+ return this;
+ }
+ }
+ }
+ }
+ return this;
+ },
+ emit: function(event){
+ var ne = norm(event); event = ne.event;
+
+ var args = _.slice(arguments, 1),
+ handles = this._handles, calls;
+
+ if (!handles || !(calls = handles[event])) return this;
+ for (var i = 0, len = calls.length; i < len; i++) {
+ var fn = calls[i];
+ if( !ne.namespace || fn._ns === ne.namespace ) fn.apply(this, args)
+ }
+ return this;
+ }
+ }
+ return function(obj){
+ obj = typeof obj == "function" ? obj.prototype : obj;
+ return _.extend(obj, API)
+ }
+ })();
+
+
+
+ _.bind = function(fn, context){
+ return function(){
+ return fn.apply(context, arguments);
+ }
+ }
+
+ var rDbSlash = /\/+/g, // double slash
+ rEndSlash = /\/$/; // end slash
+
+ _.cleanPath = function (path){
+ return ("/" + path).replace( rDbSlash,"/" ).replace( rEndSlash, "" ) || "/";
+ }
+
+ // normalize the path
+ function normalizePath(path) {
+ // means is from
+ // (?:\:([\w-]+))?(?:\(([^\/]+?)\))|(\*{2,})|(\*(?!\*)))/g
+ var preIndex = 0;
+ var keys = [];
+ var index = 0;
+ var matches = "";
+
+ path = _.cleanPath(path);
+
+ var regStr = path
+ // :id(capture)? | (capture) | ** | *
+ .replace(/\:([\w-]+)(?:\(([^\/]+?)\))?|(?:\(([^\/]+)\))|(\*{2,})|(\*(?!\*))/g,
+ function(all, key, keyformat, capture, mwild, swild, startAt) {
+ // move the uncaptured fragment in the path
+ if(startAt > preIndex) matches += path.slice(preIndex, startAt);
+ preIndex = startAt + all.length;
+ if( key ){
+ matches += "(" + key + ")";
+ keys.push(key)
+ return "("+( keyformat || "[\\w-]+")+")";
+ }
+ matches += "(" + index + ")";
+
+ keys.push( index++ );
+
+ if( capture ){
+ // sub capture detect
+ return "(" + capture + ")";
+ }
+ if(mwild) return "(.*)";
+ if(swild) return "([^\\/]*)";
+ })
+
+ if(preIndex !== path.length) matches += path.slice(preIndex)
+
+ return {
+ regexp: new RegExp("^" + regStr +"/?$"),
+ keys: keys,
+ matches: matches || path
+ }
+ }
+
+ _.log = function(msg, type){
+ typeof console !== "undefined" && console[type || "log"](msg)
+ }
+
+ _.isPromise = function( obj ){
+
+ return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
+
+ }
+
+
+
+ _.normalize = normalizePath;
+
+
+
+/***/ },
+/* 4 */
+/***/ 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__(5);
+ var _ = __webpack_require__(3);
+
+
+ // 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;
+
+ // hash prefix , used for hash or quirk mode
+ this.prefix = "#" + (options.prefix || "") ;
+ this.rPrefix = new RegExp(this.prefix + '(.*)$');
+ this.interval = options.interval || 66;
+
+ // the root regexp for remove the root for the path. used in History mode
+ this.root = options.root || "/" ;
+ this.rRoot = new RegExp("^" + this.root);
+
+ this._fixInitState();
+
+ this.autolink = options.autolink!==false;
+
+ this.curPath = undefined;
+ }
+
+ _.extend( _.emitable(Histery), {
+ // check the
+ start: function(){
+ var path = this.getPath();
+ this._checkPath = _.bind(this.checkPath, this);
+
+ if( this.isStart ) return;
+ this.isStart = true;
+
+ if(this.mode === QUIRK){
+ this._fixHashProbelm(path);
+ }
+
+ 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();
+
+ this.curPath = path;
+
+ this.emit("change", path);
+ },
+ // 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;
+ },
+ // get the path modify
+ checkPath: function(ev){
+
+ var path = this.getPath(), curPath = this.curPath;
+
+ //for oldIE hash history issue
+ if(path === curPath && this.iframe){
+ path = this.getPath(this.iframe.location);
+ }
+
+ 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]: "";
+
+ }else{
+ return _.cleanPath(( location.pathname + location.search || "" ).replace( this.rRoot, "/" ))
+ }
+ },
+
+ nav: function(to, options ){
+
+ var iframe = this.iframe;
+
+ options = options || {};
+
+ 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;
+
+ // 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 ) )
+ }
+
+ if( !options.silent ) this.emit('change', to);
+ },
+ _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){
+
+ 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(!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);
+ }
+ else location.hash = this.prefix+ path;
+ },
+ // for browser that not support onhashchange
+ _checkLoop: function(){
+ var self = this;
+ this.tid = setTimeout( function(){
+ self._checkPath();
+ self._checkLoop();
+ }, this.interval );
+ },
+ // if we use real url in hash env( browser no history popstate support)
+ // or we use hash in html5supoort mode (when paste url in other url)
+ // then , histery should repara it
+ _fixInitState: function(){
+ var pathname = _.cleanPath(this.location.pathname), hash, hashInPathName;
+
+ // dont support history popstate but config the html5 mode
+ if( this.mode !== HISTORY && this.html5){
+
+ hashInPathName = pathname.replace(this.rRoot, "")
+ if(hashInPathName) this.location.replace(this.root + this.prefix + hashInPathName);
+
+ }else if( this.mode === HISTORY /* && pathname === this.root*/){
+
+ hash = this.location.hash.replace(this.prefix, "");
+ if(hash) history.replaceState({}, document.title, _.cleanPath(this.root + hash))
+
+ }
+ },
+ // Thanks for backbone.history and https://github.com/cowboy/jquery-hashchange/blob/master/jquery.ba-hashchange.js
+ // for helping stateman fixing the oldie hash history issues when with iframe hack
+ _fixHashProbelm: function(path){
+ var iframe = document.createElement('iframe'), body = document.body;
+ iframe.src = 'javascript:;';
+ iframe.style.display = 'none';
+ iframe.tabIndex = -1;
+ iframe.title = "";
+ this.iframe = body.insertBefore(iframe, body.firstChild).contentWindow;
+ this.iframe.document.open().close();
+ this.iframe.location.hash = '#' + path;
+ }
+
+ })
+
+
+
+
+
+ module.exports = Histery;
+
+/***/ },
+/* 5 */
+/***/ function(module, exports) {
+
+
+ var win = window,
+ doc = document;
+
+ var b = module.exports = {
+ hash: "onhashchange" in win && (!doc.documentMode || doc.documentMode > 7),
+ history: win.history && "onpopstate" in win,
+ location: win.location,
+ 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 )}
+ }
+
+
+
+/***/ }
+/******/ ])
+});
+