/**! 
 * @author @copyright zhangxinxu(.com) 
 * @since 2014-09-26
 * @description mobilebone.js(v2.8.1) - bone of switch for mobile web app - https://github.com/zhangxinxu/mobilebone
 * @license MIT
**/

(function(root, factory) {
	if (document.MBLOADED) {
		root.console && console.warn("Don\'t repeat load Mobilebone!");
		return;
	}
	// Set up Mobilebone appropriately for the environment.
	if (typeof define === "function" && (define.amd || define.cmd)) {
		define("mobilebone", function(exports) {
			return factory(root, exports);
		});
	} else if ( typeof module === "object" && typeof module.exports === "object" ) {
		module.exports = factory(root, {});
	} else {
		// Finally, as a browser global.
		root.Mobilebone = factory(root, {});
	}
})((typeof global !== "undefined") ? global
: ((typeof window !== "undefined") ? window
	: ((typeof self !== "undefined") ? self : this)), function(root, Mobilebone) {
	// Avoid repeated callbacks
	var store = {};

	// Create local references to array methods we'll want to use later.
	var array = [];
	var slice = array.slice;

	// Is it a id selector
	var isSimple = /^#?\w+(?:[\-_]\w+)*$/i;

	// Is it webkit
	var isWebkit = "WebkitAppearance" in document.documentElement.style || typeof document.webkitHidden != "undefined";

	// Is it suppory history API
	var supportHistory = "pushState" in history && "replaceState" in history;

	Mobilebone.support = supportHistory;

	var hasInited = false;

	/**
	 * Current version of the library. Keep in sync with `package.json`.
	 *
	 * @type string
	**/
	Mobilebone.VERSION = "2.8.1";

	/**
	 * Whether auto init Mobilebone
	 * If this value is false, you shou excute Mobilebone.init() in the right place
	 */
	Mobilebone.autoInit = true;

	/**
	 * Whether catch attribute of href from element with tag 'a'
	 * If the value set to false, jump links in a refresh form(not slide)
	 * In most cases, you do not need to care about this parameter.
	   Except some special pages that should refresh all links, as test/index.html show.
	   However, if your only want several links refesh, you can use data-ajax="false" or data-rel="external"
	 *
	 * @type boolean
	**/
	Mobilebone.captureLink = true;

	/**
	 * Whether catch events of 'submit' from <form> element
	 * If the value set to false, <form> is a normal form except data-ajax="true"
	 * If the value set to true, <form> will submit as a ajax request,
	   and the return value will be used to create a new page and transition into meanwhile.
	   However, if data-ajax="false", <form> won't submit as a ajax.
	 *
	 * @type boolean
	**/
	Mobilebone.captureForm = true;

	/**
	 * The root of transition-callback
	 * Default value is 'root', you can consider as window-object.
	   However, there are may many callbacks, it's impossible that all functions are global function.
	   We may custom a global object to store our callbacks, such as:
	   Callback = {
		 fun1: function() {},
		 fun2: function() {},
		 fun3: function() {},
	   }
	   In this case, the value of 'obilebone.rootTransition' should set Callback;
	 *
	 * @type object
	**/
	Mobilebone.rootTransition = root;

	/**
	 * Whether merge(vs cover) global callback and local callback
	 *
	 * @type boolean
	**/
	Mobilebone.mergeCallback = true;

	/**
	 *  className of animation
	 *
	 * @type string
	**/
	Mobilebone.classAnimation = "slide";
	/**
	 *  for mark page element
	 *
	 * @type string
	**/
	Mobilebone.classPage = "page";
	/**
	 * className for mark mask element
	 *
	 * @type string
	**/
	Mobilebone.classMask = "mask";
	/**
	 * Whether url changes when history changes
	 * If this value is false, the url will be no change.
	 *
	 * @type boolean
	**/
	Mobilebone.pushStateEnabled = true;
	/**
	 * Whether excute JavaScript when ajax HTML loaded
	 * If this value is true, the script will excute.
	 *
	 * @type boolean
	**/
	Mobilebone.evalScript = false;
	
	// When running inside a FF iframe, calling replaceState causes an error. So set 'pushStateEnabled = false'
	if (navigator.userAgent.indexOf( "Firefox" ) >= 0 && window.top !== window) {
		Mobilebone.pushStateEnabled = false;
	}

	/**
	 * if browser do not support history/classList, stop here
	**/
	if (supportHistory == false) {
		return Mobilebone;
	}

	/**
	 * don't excute window.onpopstate when page load
	**/
	history.popstate = false;

	/**
	 * Function for transition
	 * In most cases, you are unnecessary to use this function , unlike Mobilebone.createPage

	 * @params  pageInto: dom-object. Element which will transform into. - Necessary
	            pageOut:  dom-object. Element which will transform out.   - Optional
			    back:     boolean.    Direction of transition.          - Optional
			    options:  object.     Cover or add parameters.           - Optional
	 * @returns undefined
	 * @example Mobilebone.transition(element);
	            Mobilebone.transition(element1, element2);
		        Mobilebone.transition(element1, element2, true);
		        Mobilebone.transition(element1, element2, { id: "only" });
		        Mobilebone.transition(element1, element2, true, { id: "only" });
	**/
	Mobilebone.transition = function(pageInto, pageOut, back, options) {
		if (arguments.length == 0 || pageInto == pageOut) return;
		if (arguments.length == 3 && isNaN(back * 1) == true) {
			options = back;
			back = options.back;
		};

		//if those parameters is missing
		pageOut = pageOut || null, back = back || false, options = options || {};

		// defaults parameters
		var defaults = {
			// the value of callback is a key name, and the host is root here.
			// eg. if the name of animationstart is 'doLoading', so the script will execute 'root.doLoading()'
			// By default, the value of root is 'window'
			root: this.rootTransition,
			// the form of transition, the value (eg. 'slide') will be a className to add or remove.
			// of course, u can set to other valeu, for example, 'fade' or 'flip'. However, u shou add corresponding CSS3 code.
			form: this.form || this.classAnimation,
			// 'animationstart/animationend/...' are callbacks params
			// Note: those all global callbacks!
			onpagefirstinto: this.onpagefirstinto,
			animationstart: this.animationstart,
			animationend: this.animationend,
			preventdefault: this.preventdefault,
			fallback: this.fallback,
			callback: this.callback
		}, params = function(element) {
			if (!element || !element.getAttribute) return {};

			var _params = {}, _dataparams = _queryToObject(element.getAttribute("data-params") || "");

			// rules as follow:
			// data-* > data-params > options > defaults
			["title", "root", "form"].forEach(function(key) {
				_params[key] = element.getAttribute("data-" + key) || _dataparams[key] || options[key] || defaults[key];
			});

			if (typeof _params.root == "string") {
				_params.root = Mobilebone.getFunction(_params.root);
			}

			["onpagefirstinto", "callback", "fallback", "animationstart", "animationend", "preventdefault"].forEach(function(key) {
				if (Mobilebone.mergeCallback == true && typeof defaults[key] == "function") {
					// merge global callback
					var local_function_key = element.getAttribute("data-" + key) || _dataparams[key];
					if (typeof _params.root[local_function_key] == "function") {
						_params[key] = function() {
							defaults[key].apply(this, arguments);
							_params.root[local_function_key].apply(this, arguments);
						}
					} else if (typeof options[key] == "function") {
						_params[key] = function() {
							defaults[key].apply(this, arguments);
							options[key].apply(this, arguments);
						}
					} else {
						_params[key] = defaults[key];
					}
				} else {
					// replace global callback
					_params[key] = element.getAttribute("data-" + key) || _dataparams[key] || options[key] || defaults[key];
				}
			});

			return _params;
		};

		// get params of each
		var paramsOut = params(pageOut), paramsIn = params(pageInto);

		if (pageOut != null && pageOut.classList) {
			// weather prevent transition
			var preventOut = paramsOut.preventdefault, isPreventOut = false;
			if (typeof preventOut == "string") {
				preventOut = paramsOut.root[preventOut];
			}
		}
		if (pageInto != null && pageInto.classList) {
			// weather prevent transition
			var preventInto = paramsIn.preventdefault, isPreventInto = false;
			if (typeof preventInto == "string") {
				preventInto = paramsIn.root[preventInto];
			}
		}
		if (typeof preventOut == "function") {
			isPreventOut = preventOut.call(paramsOut.root, pageInto, pageOut, options);
		}

		// if functions of 'preventdefault' are same for pageIn and pageout, just excute once.
		if (isPreventOut == true && preventOut === preventInto) {
			return false;
		}

		if (typeof preventInto == "function") {
			isPreventInto = preventInto.call(paramsIn.root, pageInto, pageOut, options);
		}
		// if pageinto stopped, stop all
		if (isPreventInto == true) {
			// only run here and nothing more
			return false;
		}

		// set animation callback as a method
		var funAnimationCall = function(page, data) {
			if (page.flagAniBind == true) return;
			// do callback when animation start/end
			["animationstart", "animationend"].forEach(function(animationkey, index) {
				var animition = paramsIn[animationkey], webkitkey = "webkit" + animationkey.replace(/^a|s|e/g, function(matchs) {
					return matchs.toUpperCase();
				});
				var animateEventName = isWebkit? webkitkey: animationkey;
				// if it's the out element, hide it when 'animationend'
				if (index) {
					page.addEventListener(animateEventName, function() {
						if (this.classList.contains("in") == false) {
							this.style.display = "none";
							// add on v2.5.5
							// move here on v2.5.8
							// main for remove page is just current page
							// remove on v2.7.0
							// if (this.removeSelf == true) {
							// 	this.parentElement.removeChild(this);
							// 	this.removeSelf = null;
							// }
						}
						this.classList.remove(params(this).form);
					});
				}
				// bind animation events
				if (typeof animition == "string" && paramsIn.root[animition]) {
					page.addEventListener(animateEventName, function() {
						data.root[animition].call(data.root, this, this.classList.contains("in")? "into": "out", options);
					});
				} else if (typeof animition == "function") {
					page.addEventListener(animateEventName, function() {
						animition.call(data.root, this, this.classList.contains("in")? "into": "out", options);
					});
				}
				// set a flag
				page.flagAniBind = true;
			});
		};

		if (pageOut != null && pageOut.classList) {
			// do transition if there are no 'prevent'
			if (isPreventOut != true) {
				pageOut.classList.add(paramsOut.form);
				// reflow
				pageOut.offsetWidth;
				// go, go, go
				pageOut.style.display = "block";
				pageOut.classList.add("out");
				pageOut.classList.remove("in");
				// if reverse direction
				pageOut.classList[back? "add": "remove"]("reverse");

				// add on v2.5.5
				pageOut.removeSelf = pageOut.removeSelf || null;

				// set animation callback for 'pageInto'
				// for issues #153
				funAnimationCall(pageOut, paramsOut);

				// do fallback every time
				var fallback = paramsOut.fallback;
				if (typeof fallback == "string") {
					fallback = paramsOut.root[fallback];
				}
				if (typeof fallback == "function") {
					fallback.call(paramsOut.root, pageInto, pageOut, options);
				}
			}
		}

		if (pageInto != null && pageInto.classList) {
			// for title change
			var title = paramsIn.title,
			    header = document.querySelector("h1"),
			    firstPage = document.querySelector("." + this.classPage);

			// do title change
			if (title && options.title !== false) {
				document.title = title;
				if (header) {
					header.innerHTML = title;
					header.title = title;
				}
			} else if (firstPage == pageInto && !pageOut && document.title) {
				// set data-title for first visibie page
				pageInto.setAttribute("data-title", document.title);
			}

			// delete page with same id when options.remove !== false
			var pageid = options.id || pageInto.id;
			var hashid = options.id || pageInto.id;

			// v2.7.0 change rule -> don't auto delete pages
			//                    -> delete by using Mobilebone.remove();
			// v2.7.2 add using data-reload="xxxxId" to auto remove pages
			// Reason Two:
			// 1. Most websites are so simple that it's not enough to talk about performance;
			// 2. We can't judge relation by two page's url
				// eg, below 'if' sentence only cover sence 0:
				/*
				if (options.id) {
					// eg. get 'ajax.php' from 'ajax.php?xx=1'
				 	pageid = pageid.split("?")[0];
				}
				*/

				// 0. url format: //xxx.com/list.php
				                  //xxx.com/detail.php?id=1
				                  //xxx.com/detail.php?id=2

				// but not sence 1 and 2:

				// 1. url format: //xxx.com/list/111/
				                  //xxx.com/list/222/
				                  //xxx.com/list/333/
				                  // 明明需要删除的,没删除

	            // 2. url format: //xxx.com/index.php   列表页
	                              //xxx.com/index.php?list=1   列表详情页1
	                              //xxx.com/index.php?list=2   列表详情页2
	                              // 以上列表页就嗝屁了

			// var relid = store["_" + pageid];
			// // only delete page auto when data-reload is not 'false' or null
			// // hashid may store the same page, we should delete also
			// // when data-reload not 'false' or null
			// // v2.4.4+
			// if (relid && store[relid] && options.reload == true) {
			// 	delete store[relid];
			// 	delete store["_" + pageid];
			// }

			// // below commented on v2.6.2
			// if (options.reload == true) {
			// 	// v2.5.8 for issues #147
			// 	pageInto.removeSelf = true;
			// }

			// if (store[pageid] && store[pageid] != pageInto) {


			// 	if (store[pageid] != pageOut) {
			// 		store[pageid].parentElement && store[pageid].parentElement.removeChild(store[pageid]);
			// 	} else {
			// 		// if the page element same as store
			// 		// remove when animationend
			// 		pageOut.removeSelf = true;
			// 	}
			// 	delete store[pageid];
			// }

			// do transition
			if (pageOut) {
				pageInto.classList.add(paramsIn.form);
			}
			// iOS bug
			// reflow for fixing issues #80, #86
			pageInto.offsetWidth;
			// go~ as normal
			pageInto.style.display = "block";
			pageInto.classList.remove("out");
			pageInto.classList.add("in");
			// if reverse direction
			pageInto.classList[back? "add": "remove"]("reverse");

			// do callback when come in first time
			var onpagefirstinto = paramsIn.onpagefirstinto;
			// first judge change to pageInto store
			// v2.5.5 add for fix issues #138
			if (!pageInto.firstintoBind) {
				if (typeof onpagefirstinto == "string" && paramsIn.root[onpagefirstinto]) {
					paramsIn.root[onpagefirstinto].call(paramsIn.root, pageInto, pageOut, options);
				} else if (typeof onpagefirstinto == "function") {
					onpagefirstinto.call(paramsIn.root, pageInto, pageOut, options);
				}
				// capture form submit
				slice.call(pageInto.querySelectorAll("form")).forEach(function(form) {
					Mobilebone.submit(form);
				});

				pageInto.firstintoBind = true;
			}

			// set animation callback for 'pageInto'
			funAnimationCall(pageInto, paramsIn);

			// history
			// hashid should a full url address
			// different with pageid
			// add on 2.4.2
			var urlPush = hashid, urlPushReplace = "";

			if (urlPush && /#/.test(urlPush) == false) {
				urlPush = "#" + urlPush;
			}
			urlPushReplace = urlPush.replace(/#/, "#&");

			if (supportHistory && this.pushStateEnabled && options.history !== false && urlPush
				// hash should be different
				// can fix issues #79, #87 maybe
				&& urlPushReplace != location.hash
			) {
				// don't trigger 'popstate' events
				history.popstate = false;
				// if only pageIn, use 'replaceState'
				history[pageOut ? "pushState" : "replaceState"](null, document.title, urlPush.replace(/#/, "#&"));
			}

			// store page-id, just once
			if (!store[pageid]) {
				store[pageid] = pageInto;
				// when we back/prev, we need to get true
				// comment on v2.7.0
				// if (hashid !== pageid) {
				// 	store[hashid] = pageInto;
				// 	store["_" + pageid] = hashid;
				// }
			}

			// do callback every time
			var callback = paramsIn.callback;

			if (typeof callback == "string") {
				callback = paramsIn.root[callback];
			}
			if (typeof callback == "function") {
				callback.call(paramsIn.root, pageInto, pageOut, options);
			}

			// Safari do 'popstate' after 'pushState/replaceState'
			// So, we neet setTimeout to avoid excuting 'Mobilebone.transition()' twice
			setTimeout(function() {
				// reset to popable state
				history.popstate = true;
			}, 17);

			// add on v2.7.5 improve back user experence
			// add on v2.7.6
			// only not back transiton store last ship
			if (back == false && history.popstate == false) {
				store.lastShip = [pageInto, pageOut];
			} else {
				store.lastShip = null;
			}
		}
	};

	/**
	 * Remove page DOM add on v2.7.0
	 * @param  domOrId: dom-object|string
	 *          page DOM or <a> DOM
	 *          page id or <a> href value
	 * @return {[type]}         [description]
	 */
	Mobilebone.remove = function (domOrId) {
		if (!domOrId) {
			return this;
		}

		var elePage = domOrId;
		var pageid = domOrId;
		if (typeof pageid == "string") {
			elePage = store[pageid];			
		} else if (elePage.tagName && elePage.tagName.toLowerCase() == "a") {
			pageid = this.getCleanUrl(elePage);
			elePage = store[pageid];
		}

		if (elePage && elePage.parentElement) {
			// remove store
			for (var key in store) {
				if (store[key] == elePage) {
					delete store[key];
				}
			}
			elePage.parentElement.removeChild(elePage);
		}
	};


	/**
	 * For getting whole ajax url
	 * In most cases, you are unnecessary to use this function

	 * @params  trigger: dom-object. element with tag-"a".  - Optional(at least one)
	            url:     string. ajax url.                  - Optional(at least one)
			    params:  string|object. ajax params.        - Optional
	 * @returns string
	 * @example Mobilebone.getCleanUrl(elementOfA);
	            Mobilebone.getCleanUrl(elementOfForm);
	            Mobilebone.getCleanUrl(elementOfA, '', "a=1&b=2");
		        Mobilebone.getCleanUrl(null, "xxx.html");
		        Mobilebone.getCleanUrl(null, "xxx.html?a=1&b=2");
		        Mobilebone.getCleanUrl(null, "xxx.html", "a=1&b=2");
	**/
	Mobilebone.getCleanUrl = function(trigger, url, params) {
		var href = "", formdata = "", cleanUrl = "";
		if (trigger) {
			if (trigger.nodeType == 1) {
				// form element
				if (trigger.action) {
					href = trigger.getAttribute("action");
					// add on v2.4.1
					if (trigger.method && trigger.method.toUpperCase() == "POST") {
						return href;
					} else if (window.$ && $.fn && $.fn.serialize) {
						// use jquery serialize()
						formdata = $(trigger).serialize();
					} else {
						formdata = {};
						// simple serialize from Mobilebone
						slice.call(trigger.querySelectorAll("input,select,textarea")).forEach(function(control) {
							if (control.name && !control.disabled) {
								var val = control.value.trim(), name = control.name;
								if (/^radio|checkbox/i.test(control.type)) {
									if (control.checked) {
										if (formdata[name]) {
											formdata[name].push(val);
										} else {
											formdata[name] = [val];
										}
									}
								} else {
									formdata[name] = [val];
								}
							}
						});
					}
				} else {
					// a element
					href = trigger.getAttribute("href");
					if (/^javascript/.test(href)) {
						href = '';
					}
					formdata = trigger.getAttribute("data-formdata") || trigger.getAttribute("data-params") || "";
					// v2.6.1 for #107
					// remember container when refresh
					var strContainer = "container", attrContainer = trigger.getAttribute("data-" + strContainer);
					if (formdata.indexOf(strContainer) == -1 && attrContainer) {
						var queryContainer = strContainer + "=" + attrContainer;
						formdata = formdata ? formdata + "&" + queryContainer : queryContainer;
					}
				}
			} else if (trigger.url) {
				href = trigger.url;
				formdata = trigger.data;
			}
		}

		if (!(href = href || url)) {
			return "";
		}

		// get formdata
		formdata = formdata || params || "";

		if (typeof formdata == "object") {
			var arrData = [];
			for (key in formdata) {
				if (!formdata[key].forEach) {
					formdata[key] = [formdata[key]];
				}
				formdata[key].forEach(function(keyValue) {
					arrData.push(key + "=" + encodeURIComponent(keyValue));
				});

			}
			if (arrData.length > 0) {
				formdata = arrData.join("&");
			} else {
				formdata = "";
			}
		}

		// get url of root
		cleanUrl = href.split("#")[0].replace(/&+$/, "").replace(/^\.\/+/, "");

		if (cleanUrl.slice(-1) == "?") {
			cleanUrl = cleanUrl.split("?")[0];
		}
		// url = root_url + joiner + formdata
		if (formdata != "") {
			if (/\?/.test(cleanUrl)) {
				formdata = formdata.replace(/^&|\?/, "");
				cleanUrl = cleanUrl + "&" + formdata;
			} else if (formdata != "") {
				formdata = formdata.replace("?", "");
				cleanUrl = cleanUrl + "?" + formdata;
			}
		}
		return cleanUrl;
	};

	/**
	 * Create page according to given Dom-element or HTML string. And, notice!!!!! will do transition auto.

	 * @params  domHtml:        dom-object|string. Create this to dom element as a role of into-page.               - Necessary
	            eleOrObj: dom-object|object. '.page element', or 'a element', or 'options' for get out-page   - Optional
				options:            object.            basically, options = ajax options, of course, u can custom it!   - Optional
	 * @returns undefined
	 * @example Mobilebone.createPage(pageDom);
	            Mobilebone.createPage(generalDom);
		        Mobilebone.createPage('<div class="page out">xxx</div>');
		        Mobilebone.createPage('<p>xxx</p>');
		        Mobilebone.createPage(pageDom, triggerLink);
		        Mobilebone.createPage(pageDom, { response: '<div...>' });
		        Mobilebone.createPage(pageDom, triggerLink, { response: '<div...>' });
	 *
	**/
	Mobilebone.createPage = function(domHtml, eleOrObj, options) {
		var response = null, container = null, classPage = this.classPage, isreload = null;
		// 'eleOrObj' can '.page element', or 'a element', or 'options'
		// basically, options = ajax options, of course, u can custom it!
		if (!domHtml) return;
		if (typeof options == "undefined" && typeof eleOrObj == "object") {
			options = eleOrObj;
		}
		options = options || {};

		// 'options' that 'Mobilebone.transition()' needs
		var optionsTransition = {};

		// get page-title from eleOrObj or options
		var titlePage, idContainer, classPageInside;

		if (eleOrObj) {
			if (eleOrObj.nodeType == 1) {
				// legal elements
				if (eleOrObj.href || eleOrObj.action) {
					titlePage = eleOrObj.getAttribute("data-title") || options.title;
				}
				response = options.response;
				idContainer = eleOrObj.getAttribute("data-container");
				container = document.getElementById(idContainer);
				classPageInside = eleOrObj.getAttribute("data-classpage");
				// pass element as target params, add on v2.3.0
				optionsTransition.target = eleOrObj;
				// v2.4.4 is_root → isreload
				isreload = eleOrObj.getAttribute("data-reload");
				if (eleOrObj.tagName.toLowerCase() == "form" || (isreload !== null && isreload != "false")) {
					optionsTransition.reload = true;
				}
				// v2.5.2
				// is back? for issues #128
				optionsTransition.back = eleOrObj.getAttribute("data-rel") == "back";

				// v2.6.0 history
				if (eleOrObj.getAttribute("data-history") == "false") {
					optionsTransition.history = false;
				}
			} else {
				response = eleOrObj.response || options.response;
				titlePage = eleOrObj.title || options.title;
				container = eleOrObj.container || options.container;
				classPageInside = eleOrObj.classPage || options.classPage;
				optionsTransition.target = eleOrObj.target;
				// v2.5.2
				// is back? for issues #128
				optionsTransition.back = eleOrObj.back || options.back;
			}
			if (container && classPageInside) {
				classPage = classPageInside;
			}
		}

		// get current page(will be out) according to 'page_or_child'
		var eleCurrentPage = (classPage == classPageInside? container : document).querySelector(".in." + classPage);

		// get create page (will be into) according to 'domHtml'
		var eleCreatePage = null;

		var create = document.createElement("div");
		if (typeof domHtml == "string") {
			create.innerHTML = domHtml;
		} else {
			create.appendChild(domHtml);
		}

		// excute inline JavaScript
		if (Mobilebone.evalScript == true && domHtml.firstintoBind != true) {
			slice.call(create.getElementsByTagName("script")).forEach(function(originScript) {
				var scriptContent = originScript.innerHTML.trim();
				var type = originScript.type || originScript.getAttribute("type");

				var head = document.getElementsByTagName("head")[0] || document.documentElement;
				var script = document.createElement("script");
				if (type) {
					script.type = type;
				}

				if (scriptContent) {
					script.appendChild(document.createTextNode(scriptContent));
				} else if (originScript.src && !document.querySelector('script[src="'+ originScript.src +'"]')) {
					// issues #199
                	script.src = originScript.src;
            	}

				setTimeout(function() {
					head.insertBefore(script, head.firstChild);
					head.removeChild(script);
					script = null;
				}, 17);
				originScript = null;
			});
		}

		var eleCreateTitle = create.getElementsByTagName("title")[0];

		// get the page element
		if (!(eleCreatePage = create.querySelector("." + classPage))) {
			// if there no .page, create as eleCreatePage
			create.className = classPage + " out";
			eleCreatePage = create;
		}
		// set and store title
		if (typeof titlePage == "string") {
			eleCreatePage.setAttribute("data-title", titlePage);
		} else if (eleCreateTitle && eleCreateTitle.innerText) { // the judge behind '&&' for issues #144
			eleCreatePage.setAttribute("data-title", eleCreateTitle.innerText);
		}

		// do transition
		optionsTransition.response = response || domHtml;
		optionsTransition.id = options.id || this.getCleanUrl(eleOrObj) || eleCreatePage.id || options.url || ("unique" + Date.now());

		// 'if' statement below added on v2.0.0
		if (typeof options == "object") {
			if (typeof options.history != "undefined") {
				optionsTransition.history = options.history;
			}
			if (typeof options.remove != "undefined") {
				optionsTransition.remove = options.remove;
			}
			if (typeof options.target != "undefined") {
				optionsTransition.target = options.target;
			}
			if (typeof options.title != "undefined") {
				optionsTransition.title = options.title;
			}
			// add on v2.7.4
			if (typeof options.query != "undefined") {
				optionsTransition.query = options.query;
			}
		}
		if (classPage == classPageInside) {
			optionsTransition.history = false;
			optionsTransition.classPage = classPage;
		}

		// append to a accurate position
		container = container || document.body;

		// remove page if have same id when optionsTransition.remove == true
		if (optionsTransition.remove == true && optionsTransition.id) {
			this.remove(optionsTransition.id);
			// v2.8.0 dom remove and ajax remove share same id
			if (options.id) {
				slice.call(document.querySelectorAll('a[data-reload="'+ options.id +'"]')).forEach(function (ele) {
					Mobilebone.remove(ele);
				});
			}
		}
		// 1. if new page, that insert create page as a last-child
		// 2. if replace a page, that insert before replaced page
		var pageid = optionsTransition.id.split("?")[0];
		if (pageid && store[pageid] && container.contains(store[pageid])) {
			container.insertBefore(eleCreatePage, store[pageid]);
		} else {
			container.appendChild(eleCreatePage);
		}

		// release memory
		create = null;

		// do transition
		this.transition(eleCreatePage, eleCurrentPage, optionsTransition);

		var objRelationShip = store.backShip || [];
		objRelationShip.push({
			pageIn: eleCreatePage,
			pageOut: eleCurrentPage,
			isBack: !!optionsTransition.back
		});

		store.backShip = objRelationShip;
	};

	/**
	 * For ajax callback.
	 * For example, data-success="a.b.c". We can't use 'a.b.c' as a function, because it's a string. We should do some work to get it!

	 * @params  keys:        string. - Necessary
	 * @returns function
	            undefined keys is not string
				window    keys undefined
	 * @example Mobilebone.getFunction("a.b.c");
	 *
	**/
	Mobilebone.getFunction = function(keys) {
		if (typeof keys != "string") return;
		// eg. 'globalObject.functionName'
		var fun = Mobilebone.rootTransition, arrKey = keys.split(".");
		for (var index=0; index<arrKey.length; index+=1) {
			if (!(fun = fun[arrKey[index]])) {
				break;
			}
		}
		return fun;
	};

	/**
	 * For ajax request to get HTML or JSON.

	 * @params  eleOrObj        - Necessary
	            1. dom-object:<a>|<form>.
				2. object.
	 * @returns undefined
	 * @example Mobilebone.ajax(document.querySelector("a"));
	            Mobilebone.ajax({
				  url: 'xxx.html',
				  success: function() {}
		    	});
	 *
	**/
	Mobilebone.ajax = function(eleOrObj) {
		if (!eleOrObj) {
			return;
		}

		var trigger = eleOrObj;

		// default params
		var defaults = {
			url: "",
			type: "",
			dataType: "",
			data: {},
			timeout: 10000,
			success: function() {},
			error: function() {},
			complete: function() {}
		};

		var params = {}, eleMask = null, formData = null;

		// classname of mask
		var classMask = this.classMask;

		// if 'eleOrObj' is a element, we should turn it to options-object
		var paramsFromTrigger = {}, attrMask;
		if (eleOrObj.nodeType == 1) {
			paramsFromTrigger = _queryToObject(trigger.getAttribute("data-params") || "");
			// get params
			for (key in defaults) {
				// data-* > data-params > defaults
				params[key] = trigger.getAttribute("data-" + key) || paramsFromTrigger[key] || defaults[key];
				if (typeof defaults[key] == "function" && typeof params[key] == "string") {
					// eg. globalObject.functionName
					params[key] = this.getFunction(params[key]);
					if (typeof params[key] != "function") {
						params[key] = defaults[key];
					}
				}
			}

			// address of ajax url
			var cleanUrl = this.getCleanUrl(trigger, params.url);
			params.url = cleanUrl;

			var queryFromUrl = _queryToObject(cleanUrl.split("?")[1]);

			// v2.7.4 fix params may ingore problem
			for (var key in queryFromUrl) {
				if (typeof paramsFromTrigger[key] == "undefined") {
					paramsFromTrigger[key] = queryFromUrl[key];
				}
			}
			// v2.7.4
			params.query = paramsFromTrigger;
			// store target
			params.target = trigger;
			// v2.5.2
			// is back? for issues #128
			params.back = trigger.getAttribute("data-rel") == "back";

			var tagName = trigger.tagName.toLowerCase();
			if (tagName == "form") {
				params.type = trigger.method;

				formData = new FormData(trigger);
			} else if (tagName == "a") {
				// v2.5.8 for issues #157
				var idContainer = trigger.getAttribute("data-container"),
					classPageInside = trigger.getAttribute("data-classpage"),
					container = idContainer && document.getElementById(idContainer);
				if (container && classPageInside && classPageInside != Mobilebone.classPage) {
					// inner ajax no history change
					params.history = false;
					// title do not change
					params.title = false;
				}

				// v2.8.0 move to here
				var attrReload = trigger.getAttribute("data-reload");
				if (typeof attrReload == "string" && attrReload != "false") {
					if (attrReload != "" && attrReload != "true") {
						// v2.7.2 a new method to remove pafe
						// think 'attrReload' as special ID
						// remove all page using this ID
						slice.call(document.querySelectorAll('a[data-reload="'+ attrReload +'"]')).forEach(function (ele) {
							Mobilebone.remove(ele);
						});
						Mobilebone.remove(store[attrReload]);
					} else {
						// remove page
						Mobilebone.remove(store[cleanUrl]);
					}
				}
			}

			// get mask element
			attrMask = eleOrObj.getAttribute("data-mask");
			if (attrMask == "true" || attrMask == "") {
				eleMask = eleOrObj.querySelector("." + classMask);
			}
		}
		// if 'eleOrObj' is a object
		else if (eleOrObj.url) {
			params = eleOrObj;
			// get params
			for (key2 in defaults) {
				params[key2] = eleOrObj[key2] || defaults[key2];
			}
			// get url
			params.url = this.getCleanUrl(null, params.url, params.data);
			// here params.title will become page title;
			params.title = eleOrObj.title;
			// v2.5.2
			// is back? for issues #128
			// when history.back()
			params.back = eleOrObj.back;
			// v2.6.1
			params.container = eleOrObj.container;
			// v2.7.4
			params.query = _queryToObject(eleOrObj.url.split('?')[1]);
		} else {
			return;
		}

		// do ajax
		// get mask and loading element
		var body = container || document.body;
		if (typeof attrMask != "string") {
			eleMask = [].slice.call(body.children).filter(function (element) {
				return element.classList.contains(classMask);
			})[0];
		}

		if (eleMask == null) {
			eleMask = document.createElement("div");
			eleMask.className = classMask;
			eleMask.innerHTML = '<s class="loading"></s>';
			if (typeof attrMask == "string") {
				eleOrObj.appendChild(eleMask);
			} else {
				body.appendChild(eleMask);
			}
		}
		// show loading
		eleMask.style.display = "inline";
		if (this.showLoading) {
			this.showLoading();
		}

		// ajax request
		var xhr = new XMLHttpRequest();
		xhr.open(params.type || "GET", params.url + (/\?/.test(params.url)? "&" : "?") + "r=" + Date.now());
		xhr.timeout = params.timeout;

		xhr.onload = function() {
			// so far, many browser hasn't supported responseType = 'json', so, use JSON.parse instead
			var response = null;

			if (params.dataType == "json" || params.dataType == "JSON") {
				try {
					response = JSON.parse(xhr.response);
					params.response = response;
					Mobilebone.createPage(Mobilebone.jsonHandle(response, params), eleOrObj, params);
				} catch (e) {
					params.message = "JSON parse error:" + e.message;
					params.error.call(params, xhr, xhr.status);
				}
			} else if (params.dataType == "unknown") {
				// ajax send by url
				// no history hush
				params.history = false;
				// I don't remember why add 'params.remove = false' here,
				// but it seems that this will cause issues #147
				// no element remove
				// del → v2.5.8 // params.remove = false;
				try {
					// as json
					response = JSON.parse(xhr.response);
					params.response = response;
					Mobilebone.createPage(Mobilebone.jsonHandle(response, params), eleOrObj, params);
				} catch (e) {
					// as html
					response = xhr.response;
					Mobilebone.createPage(response, eleOrObj, params);
				}
			} else {
				response = xhr.response;
				// 'response' is string
				Mobilebone.createPage(response, eleOrObj, params);
			}
			params.success.call(params, response, xhr.status);
		};

		xhr.onerror = function(e) {
			params.message = "Illegal request address or an unexpected network error!";
			params.error.call(params, xhr, xhr.status);
		};

		xhr.onloadend = function () {
			// hide loading
			eleMask.style.display = "none";
			if (this.hideLoading) {
				this.hideLoading();
			}

			params.complete.call(params, xhr, xhr.status);
		};

		xhr.ontimeout = function() {
			params.message = "The request timeout!";
			params.error.call(params, xhr, xhr.status);
			// hide loading
			eleMask.style.display = "none";
			if (this.hideLoading) {
				this.hideLoading();
			}
		};

		// set request header for server
		xhr.setRequestHeader("Type", "ajax");
		xhr.setRequestHeader("From", "mobilebone");

		xhr.send(formData);
	};

	/**
	 * capture form submit events to a ajax request.

	 * @params  form:        formElement. - Necessary
	 * @example Mobilebone.form(document.querySelector("form"));
	 *
	**/
	Mobilebone.submit = function(form) {
		if (!form || typeof form.action != "string") {
			return;
		}
		var ajax = form.getAttribute("data-ajax");
		if (ajax == "false" || (Mobilebone.captureForm == false && ajax != "true")) return;

		form.addEventListener("submit", function(event) {
			// prevent detect
			var attrPrevent = this.getAttribute("data-preventdefault");
			// get 'preventDefault' function
			var funPrevent = Mobilebone.getFunction(attrPrevent);
			if (typeof funPrevent == "function" && funPrevent(this) == true) {
				// if the return value of prevent function is true, prevent everything~
				event.preventDefault();
				return false;
			}

			Mobilebone.ajax(this);
			event.preventDefault();
		});
	};


	/**
	 * Sometime we don't know direction of transition. Such as browser history change, or data-rel="auto".
	   In this case, we ensure the direction(back or prev) by the sorts of two pages(into or out)

	 * @params  pageIn  dom-object      - Necessary
	            pageOut  dom-object      - Optional

	 * @returns boolean
	 *
	**/
	Mobilebone.isBack = function(pageIn, pageOut) {
		// back or forwards, according to the order of two pages
		if (history.tempBack == true) {
			// backwords
			history.tempBack = null;
			return true;
		}

		if (history.tempGo == true) {
			// forwards
			history.tempGo = null;
			return false;
		}
		// 2.7.5 return true -> false
		if (typeof pageIn == "undefined") {
			return false;
		}
		if (!pageOut) {
			return false;
		}

		// 2.7.5 store pageIn pageOut backward or forward
		var objRelationShip = store.backShip || [];
		var isBack = null;
		objRelationShip.forEach(function (ship) {
			if (ship.pageIn == pageIn && ship.pageOut == pageOut) {
				isBack = ship.isBack;
			} else if (ship.pageIn == pageOut && ship.pageOut == pageIn) {
				isBack = !ship.isBack;
			}
		});

		if (isBack === null) {
			isBack = !!(pageIn.compareDocumentPosition(pageOut) & Node.DOCUMENT_POSITION_FOLLOWING);
		}

		return isBack;
	};

	/**
	 * If dataType of ajax is 'json', we can't convert json-data to page-element.
	   So, we export a function names 'jsonHandle' to handle json-data.
	 * Attention, it's a global interface. If your project has many json call, you should use JSON itself to make a distinction.
	   For example, every JSON include the only json-id:
	   {
		  "id": "homePage" ,
		  "data": []
	   }
	   different with
	   {
		  "id": "listPage" ,
		  "data": []
	   }
	 *
	 * @params  json    - Necessary
	 * @returns dom-object|string
	 *
	**/
	Mobilebone.jsonHandle = function(json, params) {
		return '<p style="text-align:center;">Dear master, if you see me, show that JSON parsing function is undefined!</p>';
	},

	/**
	 * Initialization. Load page according to location.hash. And bind link-catch events.
	**/
	Mobilebone.init = function() {
		if (hasInited == true) return 'Don\'t repeat initialization!';

		var hash = location.hash.replace("#&", "#"), eleIn = null, container = null;
		// 查询和根处理
		var key = hash.split("?")[0];
		var query = hash.split("?")[1];

		var options = {
	    	query: {}
	    };
	    if (query) {
	    	options.query = _queryToObject(query);
	    }

		if (key == "" || key == "#") {
			this.transition(document.querySelector("." + this.classPage));
		} else if (isSimple.test(key) == true && (eleIn = document.querySelector(key)) && eleIn.classList.contains(this.classPage)) {
		    // add on v2.7.4 pure inner hash also support query params
		    // eg. #somePageId?id=1&type=2
		    options.id = hash.replace(/^#/, "");

			this.transition(eleIn, null, options);
		} else {
			// add on v2.6.1
			if (hash.split("container=").length == 2) {
				container = document.getElementById(hash.split("container=")[1].split("&")[0]);
			}
			// as a ajax
			this.ajax({
				url: hash.replace("#", ""),
				dataType: "unknown",
				container: container,
				error: function() {
					// add on v2.7.4
					options.id = hash.replace(/^#/, "");

					eleIn = document.querySelector("." + Mobilebone.classPage);
					Mobilebone.transition(eleIn, null, options);
				}
			});
		}

		// Initialization link-catch events.
		document.addEventListener("click", this.handleTapEvent);

		// Important:
		// In ios7+, swipe the edge of page will navigate Safari
		// that will trigger 'popstate' events and the page will transition twice
		var isSafari7 = !!navigator.userAgent.match(/safari/i) && !navigator.userAgent.match(/chrome/i) && typeof document.hidden !== "undefined" && !window.chrome;
		if ('ontouchstart' in window == true && isSafari7) {
			document.addEventListener("touchmove", function() {
				history.popstateswipe = true;
			});
			document.addEventListener("touchend", function() {
				history.popstateswipe = false;
			});
		}

		// change flag-var for avoiding repeat init
		hasInited = true;
	};

	/**
	 * If 'a' element has href, slide auto when clicked
	**/
	Mobilebone.handleTapEvent = function(event) {
		var target = null;
		// you can pass target as params directly
		if (event && event.nodeType == 1) {
			target = event;
			target.preventDefault = function() {};
		}
		// get target and href
		target = (target || event.target) && (target || event.target).closest('a');

		if (!target) {
			return;
		}

		// current href
		var href = target.href;

		// the page that current actived
		var selfPage = document.querySelector(".in." + Mobilebone.classPage);

		if (selfPage == null) {
			return;
		}

		// optional params for Mobilebone.transition
		var options = {
			target: target
		};

		// prevent detect
		var attrPrevent = target.getAttribute("data-preventdefault") || _queryToObject(target.getAttribute("data-params") || "").preventdefault;
		// get 'preventDefault' function
		var funPrevent = Mobilebone.getFunction(attrPrevent);
		if (typeof funPrevent == "function" && funPrevent(target) == true) {
			// if the return value of prevent function is true, prevent everything~
			event.preventDefault();
			return false;
		}

		// if mask element exist and displaying, prevent double trigger
		var eleMask = target.getElementsByClassName(Mobilebone.classMask)[0];
		if (eleMask && eleMask.style.display != "none") {
			event.preventDefault();
			return false;
		}

		var idContainer = target.getAttribute("data-container");
		var classPageInside = target.getAttribute("data-classpage");

		var container = idContainer && document.getElementById(idContainer);
		if (container && classPageInside && classPageInside != Mobilebone.classPage) {
			selfPage = container.querySelector(".in." + classPageInside) || container.querySelector(classPageInside);
			// if (selfPage == null) return false;
			options.history = false;
			options.title = false;
			options.classPage = classPageInside;
		}

		// history
		if (target.getAttribute("data-history") == "false") {
			options.history = false;
		}

		// if captureLink
		var capture = (Mobilebone.captureLink == true);
		// get rel
		var rel = target.getAttribute("data-rel");
		if (!rel) {
			rel = 'auto';
		}
		// if back
		var back = false;
		if (rel == "back") {
			back = true;
		}
		// if go
		var go;
		if (rel == "go") {
			go = true;
		}

		// if external link
		var external = (rel == "external");

		// if the 'href' is not legal, return
		// include:
		// 1. undefined
		// 2. javascript: (except data-rel="back")
		// 3. cros, or not capture (except data-ajax="true")
		if (!href) {
			return;
		}

		// 直接获取属性设置的值
		var attrHref = target.getAttribute("href");

		href = href.replace("#&", "#");

		if (attrHref.replace(/#/g, "") === "") {
			event.preventDefault();
			return;
		}
		if (/^javascript/.test(href)) {
			if (back == false && !go) {
				return;
			}
		} else {
			external = external || (href.replace("://", "").split("/")[0] !== location.href.replace("://", "").split("/")[0]);
			if ((external == true || capture == false) && target.getAttribute("data-ajax") != "true") {
				return;
			}
		}

		// judge that if it's a ajax request
		if (/^#/.test(attrHref) == true) {
			event.preventDefault();
			// hash slide
			var hashTargetPage = href.split("#")[1];
			var idTargetPage = hashTargetPage.split("?")[0];
			var queryTargetPage = hashTargetPage.split("?")[1];
			var paramTargetPage = target.getAttribute('data-formdata') || target.getAttribute('data-params');
			if (paramTargetPage) {
				queryTargetPage = queryTargetPage + '&' + paramTargetPage;
			}
			
			// add on v2.7.4
			options.query = _queryToObject(queryTargetPage);
			options.id = hashTargetPage;

			var eleTargetPage = idTargetPage && document.getElementById(idTargetPage);
			if (back == false && rel == "auto") {
				back = Mobilebone.isBack(eleTargetPage, selfPage);
			}

			if (eleTargetPage) {
				var lastShip = store.lastShip;

				if (lastShip && eleTargetPage == lastShip[1] && selfPage == lastShip[0] && !go) {
					// back
					history.tempBack = true;
					history.back();
				} else {
					Mobilebone.transition(eleTargetPage, selfPage, back, options);
				}
			}
		} else if (/^javascript/.test(href)) {
			// back
			if (back) {
				history.tempBack = true;
				history.back();
			} else if (go) {
				history.tempGo = true;
				history.go(1);
			}			
		} else if (target.getAttribute("data-ajax") != "false") {
			event.preventDefault();
			// get a clean ajax url as page id
			var cleanUrl = Mobilebone.getCleanUrl(target);
			// add on v2.7.4
			options.query = _queryToObject(cleanUrl.split("?")[1]);
			// if has loaded and the value of 'data-reload' is not 'true'
			var attrReload = target.getAttribute("data-reload");

			// 之前已经请求过,内存中已经有,则直接使用
			if ((attrReload == null || attrReload == "false") && store[cleanUrl]) {
				if (back == false && rel == "auto") {
					back = Mobilebone.isBack(store[cleanUrl], selfPage);
				}
				options.id = cleanUrl;

				var body = container || document.body;

				if (body.contains(store[cleanUrl]) == false) {
					body.appendChild(store[cleanUrl]);
				}
				Mobilebone.transition(store[cleanUrl], selfPage, back, options);
			} else {	
				// as ajax
				Mobilebone.ajax(target);
			}
			
		}
	};

	/**
	 * closest polyfill
	 */
	if (!Element.prototype.matches) {
		Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
	}

	if (!Element.prototype.closest) {
		Element.prototype.closest = function (s) {
			var el = this;

			do {
				if (el.matches(s)) return el;
				el = el.parentElement || el.parentNode;
			} while (el !== null && el.nodeType === 1);

			return null;
		};
	}


	/**
	 * private method: convert query string to key-value object
	**/
	var _queryToObject = function(string) {
		var obj = {};
		if (typeof string == "string") {
			string.split("&").forEach(function(part) {
				var arrPart = part.split("=");
				if (arrPart.length > 1) {
					obj[arrPart[0]] = part.replace(arrPart[0] + "=", "");
				}
			});
		}
		return obj;
	};

	/**
	 * auto init
	**/
	window.addEventListener("DOMContentLoaded", function() {
		if (hasInited == false && Mobilebone.autoInit == true) {
			Mobilebone.init();
		}
	});

	/**
	 * page change when history change
	**/
	window.addEventListener("popstate", function() {
		if (history.popstateswipe == true) {
			location.reload();
			history.popstateswipe = false;
			return;
		}
		if (history.popstate == false) {
			history.popstate = true;
			return;
		}

		var hash = location.hash.replace("#&", "#").replace(/^#/, "");
		// add on v2.7.4
		var key = hash.split('?')[0];
		var pageIn = null;
		// add on v2.7.5
		var pageOut = document.querySelector(".in." + Mobilebone.classPage);
		// add on v2.6.1
		var container = null;

		if (hash == "") {
			// if no hash, get first page as 'pageIn'
			pageIn = document.querySelector("." + Mobilebone.classPage);
			if (pageIn.id) return;
		} else {
			// add on v2.7.4
			pageIn = store[key] || store[hash];

			// add on v2.6.1
			if (hash.split("container=").length == 2) {
				container = document.getElementById(hash.split("container=")[1].split("&")[0]);
			}

			// url address and with cache
			if (pageIn && isSimple.test(key) == false) {
				// just transition
				Mobilebone.transition(pageIn, pageOut, Mobilebone.isBack(pageIn, pageOut), {
					id: hash,  // fix issue #83
					history: false,
					container: container,
					target: document.activeElement,  // add on v2.7.7
					query: _queryToObject(hash.split('?')[1])
				});
				return;
			}
		}

		if (!pageIn) {
			if (isSimple.test(hash) == false) {
				// as a url
				Mobilebone.ajax({
					url: hash,
					dataType: "unknown",
					// no cache url, usually reload ajax
					back: Mobilebone.isBack(),
					container: container
				});
				return;
			}
			pageIn = document.querySelector("#" + key) || document.querySelector("#" + hash);
		}

		if ((pageIn && pageIn == pageOut) || Mobilebone.pushStateEnabled == false) return;

		// hash ↔ id
		if (pageIn) {
			Mobilebone.transition(pageIn, pageOut, Mobilebone.isBack(pageIn, pageOut), {
				id: hash,  // fix issue #83
				history: false,
				target: document.activeElement,  // add on v2.7.7
				query: _queryToObject(hash.split('?')[1])
			});
		}
	});

	document.MBLOADED = true;

	return Mobilebone;
});