From 8fb2bfb1b3b857e5a95c3ebdef4406a4b7e737d7 Mon Sep 17 00:00:00 2001 From: chenhao Date: Thu, 16 Apr 2015 16:22:24 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=94=AF=E6=8C=81URL=E4=BC=A0=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mobilebone.js | 664 +++++++++++++++++++++++----------------------- 1 file changed, 334 insertions(+), 330 deletions(-) diff --git a/src/mobilebone.js b/src/mobilebone.js index 50e0dbc..8be4786 100644 --- a/src/mobilebone.js +++ b/src/mobilebone.js @@ -3,7 +3,7 @@ * by zhangxinxu(.com) 2014-09-26 * https://github.com/zhangxinxu/mobilebone * bone of switch for mobile web app -**/ + **/ (function(root, factory) { if (document.MBLOADED) { return; } @@ -12,7 +12,7 @@ define('mobilebone', function(exports) { return factory(root, exports); }); - // Finally, as a browser global. + // Finally, as a browser global. } else { root.Mobilebone = factory(root, {}); } @@ -20,141 +20,141 @@ if (document.MBLOADED) { return 'Don\'t repeat load 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 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.5.0'; - + /** * 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" + * 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
element * If the value set to false, is a normal form except data-ajax="true" - * If the value set to true, 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", won't submit as a ajax. + * If the value set to true, 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", 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 = { + * 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; + 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; - - + + if (// When running inside a FF iframe, calling replaceState causes an error. So set 'pushStateEnabled = false' (window.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 + 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(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) { @@ -163,7 +163,7 @@ }; //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. @@ -183,24 +183,24 @@ 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) { + ["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") { + if (typeof _params.root[local_function_key] == "function") { _params[key] = function() { defaults[key].apply(this, arguments); _params.root[local_function_key].apply(this, arguments); @@ -218,13 +218,13 @@ _params[key] = element.getAttribute("data-" + key) || _dataparams[key] || options[key] || defaults[key]; } }); - + return _params; }; - + // get params of each var params_out = params(pageOut), params_in = params(pageInto); - + if (pageOut != null && pageOut.classList) { // weather prevent transition var preventOut = params_out.preventdefault, isPreventOut = false; @@ -234,20 +234,20 @@ // weather prevent transition var preventInto = params_in.preventdefault, isPreventInto = false; if (typeof preventInto == "string") preventInto = params_in.root[preventInto]; - + } if (typeof preventOut == "function") isPreventOut = preventOut.call(params_out.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(params_in.root, pageInto, pageOut, options); // if pageinto stopped, stop all if (isPreventInto == true) { // only run here and nothing more - return false; + return false; } - + if (pageOut != null && pageOut.classList) { // do transition if there are no 'prevent' if (isPreventOut != true) { @@ -260,20 +260,20 @@ pageOut.classList.remove("in"); // if reverse direction pageOut.classList[back? "add": "remove"]("reverse"); - + // do fallback every time var fallback = params_out.fallback; if (typeof fallback == "string") fallback = params_out.root[fallback]; if (typeof fallback == "function") fallback.call(params_out.root, pageInto, pageOut, options); } } - - if (pageInto != null && pageInto.classList) { + + if (pageInto != null && pageInto.classList) { // for title change - var title = params_in.title, - header = document.querySelector("h1"), - first_page = document.querySelector("." + this.classPage); - + var title = params_in.title, + header = document.querySelector("h1"), + first_page = document.querySelector("." + this.classPage); + // do title change if (title) { document.title = title; @@ -285,10 +285,10 @@ // 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, hashid = options.id || pageInto.id; - + if (options.id) { pageid = pageid.split("?")[0]; } @@ -304,7 +304,7 @@ delete store["_" + pageid]; } } - + // do transition if (pageOut) pageInto.classList.add(params_in.form); // iOS bug @@ -328,15 +328,15 @@ // capture form submit slice.call(pageInto.querySelectorAll("form")).forEach(function(form) { Mobilebone.submit(form); - }); + }); } - + // do callback when animation start/end var isWebkit = 'WebkitAppearance' in document.documentElement.style || typeof document.webkitHidden != "undefined"; ["animationstart", "animationend"].forEach(function(animationkey, index) { var animition = params_in[animationkey], webkitkey = "webkit" + animationkey.replace(/^a|s|e/g, function(matchs) { - return matchs.toUpperCase(); - }); + return matchs.toUpperCase(); + }); if (!store[pageid]) { var animateEventName = isWebkit? webkitkey: animationkey; // if it's the out element, hide it when 'animationend' @@ -353,12 +353,12 @@ }); } else if (typeof animition == "function") { pageInto.addEventListener(animateEventName, function() { - animition(this, this.classList.contains("in")? "into": "out", options); + animition(this, this.classList.contains("in")? "into": "out", options); }); - } - } + } + } }); - + // history // hashid should a full url address // different with pageid @@ -368,18 +368,22 @@ url_push = "#" + url_push; } url_push_replaced = url_push.replace(/^#/, "#&"); - - if (supportHistory && this.pushStateEnabled && options.history !== false && url_push - // hash should be different - // can fix issues #79, #87 maybe + + if (supportHistory && this.pushStateEnabled && options.history !== false && url_push + // hash should be different + // can fix issues #79, #87 maybe && url_push_replaced != location.hash ) { // don't trigger 'popstate' events history.popstate = false; // if only pageIn, use 'replaceState' - history[pageOut? "pushState": "replaceState"](null, document.title, url_push.replace(/^#/, "#&")); + var hashParameter=''; + if(/#&.*\?(.*)/.test(location.hash)||options.target&&/#&.*\?(.*)/.test(options.target.href)){ + hashParameter='?'+RegExp.$1; + } + history[pageOut? "pushState": "replaceState"](null, document.title, url_push.replace(/^#/, "#&")+hashParameter); } - + // store page-id, just once if (!store[pageid]) { store[pageid] = pageInto; @@ -389,52 +393,52 @@ store["_" + pageid] = hashid; } } - + // do callback every time var callback = params_in.callback; - + if (typeof callback == "string") callback = params_in.root[callback]; if (typeof callback == "function") callback.call(params_in.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; + history.popstate = true; }, 17); } }; - - + + /** * 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 + 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(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 = "", clean_url = ""; 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) { + 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 { + } else { formdata = {}; // simple serialize from Mobilebone slice.call(trigger.querySelectorAll("input,select,textarea")).forEach(function(control) { @@ -453,20 +457,20 @@ } } }); - } - } else { + } + } else { // a element href = trigger.getAttribute("href"); formdata = trigger.getAttribute("data-formdata") || trigger.getAttribute("data-data"); - } - } else if (trigger.url) { - href = trigger.url; - formdata = trigger.data; - } + } + } else if (trigger.url) { + href = trigger.url; + formdata = trigger.data; + } } if (!(href = href || url)) return ''; - + // get formdata formdata = formdata || params || ""; @@ -474,12 +478,12 @@ var arr_data = []; for (key in formdata) { if (!formdata[key].forEach) { - formdata[key] = [formdata[key]]; + formdata[key] = [formdata[key]]; } formdata[key].forEach(function(keyValue) { - arr_data.push(key + "=" + encodeURIComponent(keyValue)); + arr_data.push(key + "=" + encodeURIComponent(keyValue)); }); - + } if (arr_data.length > 0) { formdata = arr_data.join("&"); @@ -487,15 +491,15 @@ formdata = ""; } } - + // get url of root clean_url = href.split("#")[0].replace(/&+$/, ""); if (clean_url.slice(-1) == "?") { - clean_url = clean_url.split("?")[0]; + clean_url = clean_url.split("?")[0]; } // url = root_url + joiner + formdata - if (formdata != "") { + if (formdata != "") { if (/\?/.test(clean_url)) { formdata = formdata.replace(/^&|\?/, ""); clean_url = clean_url + "&" + formdata; @@ -506,33 +510,33 @@ } return clean_url; }; - + /** * 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 + 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('
xxx
'); - Mobilebone.createPage('

xxx

'); - Mobilebone.createPage(pageDom, triggerLink); - Mobilebone.createPage(pageDom, { response: '' }); - Mobilebone.createPage(pageDom, triggerLink, { response: '' }); + Mobilebone.createPage(generalDom); + Mobilebone.createPage('
xxx
'); + Mobilebone.createPage('

xxx

'); + Mobilebone.createPage(pageDom, triggerLink); + Mobilebone.createPage(pageDom, { response: '' }); + Mobilebone.createPage(pageDom, triggerLink, { response: '' }); * - **/ + **/ 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; options = options || {}; - + // 'options' that 'Mobilebone.transition()' needs var optionsTransition = {}; - + // get page-title from eleOrObj or options var page_title, id_container, classPageInside; if (eleOrObj) { @@ -553,31 +557,31 @@ optionsTransition.reload = true; } } else { - response = eleOrObj.response || options.response; + response = eleOrObj.response || options.response; page_title = eleOrObj.title || options.title; container = eleOrObj.container || options.container; classPageInside = eleOrObj.classPage || options.classPage; optionsTransition.target = eleOrObj.target; } - if (container && classPageInside) classPage = classPageInside; + if (container && classPageInside) classPage = classPageInside; } - + // get current page(will be out) according to 'page_or_child' var current_page = (classPage == classPageInside? container : document).querySelector(".in." + classPage); // get create page (will be into) according to 'domHtml' var create_page = null; - + var create = document.createElement("div"); if (typeof domHtml == "string") { create.innerHTML = domHtml; - + if (Mobilebone.evalScript == true) { slice.call(create.getElementsByTagName("script")).forEach(function(originScript) { var scriptContent = originScript.innerHTML.trim(), type = originScript.getAttribute("type"); if (scriptContent.trim() == "" || originScript.src) return; var head = document.getElementsByTagName("head")[0] || document.documentElement, - script = document.createElement("script"); + script = document.createElement("script"); if (type) script.type = type; script.appendChild(document.createTextNode(scriptContent)); setTimeout(function() { @@ -585,7 +589,7 @@ head.removeChild(script); }, 16); originScript = null; - }); + }); } } else { create.appendChild(domHtml); @@ -605,16 +609,16 @@ } // insert create page as a last-child (container || document.body).appendChild(create_page); - + // release memory create = null; - + // do transition optionsTransition.response = response || domHtml; optionsTransition.id = this.getCleanUrl(eleOrObj) || create_page.id || ("unique" + Date.now()); - + // 'if' statement below added on v2.0.0 - if (typeof options == "object") { + if (typeof options == "object") { if (typeof options.history != "undefined") { optionsTransition.history = options.history; } @@ -632,18 +636,18 @@ // do transition this.transition(create_page, current_page, optionsTransition); }; - + /** - * For ajax callback. + * 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 + 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' @@ -655,24 +659,24 @@ } return fun; }; - + /** - * For ajax request to get HTML or JSON. - - * @params trigger_or_options - Necessary - 1. dom-object:|. - 2. object. + * For ajax request to get HTML or JSON. + + * @params trigger_or_options - Necessary + 1. dom-object:|. + 2. object. * @returns undefined * @example Mobilebone.ajax(document.querySelector("a")); - Mobilebone.ajax({ + Mobilebone.ajax({ url: 'xxx.html', success: function() {} }); * - **/ + **/ Mobilebone.ajax = function(trigger_or_options) { if (!trigger_or_options) return; - + // default params var defaults = { url: "", @@ -685,11 +689,11 @@ password: "", success: function() {}, error: function() {}, - complete: function() {} + complete: function() {} }; - + var params = {}, ele_mask = null, formData = null; - + // if 'trigger_or_options' is a element, we should turn it to options-object var params_from_trigger = {}, attr_mask; if (trigger_or_options.nodeType == 1) { @@ -706,18 +710,18 @@ } } } - + // ajax的url地址 - params.url = this.getCleanUrl(trigger_or_options, params.url); + params.url = this.getCleanUrl(trigger_or_options, params.url); params.target = trigger_or_options; - + var tagName = trigger_or_options.tagName.toLowerCase(); if (tagName == "form") { params.type = trigger_or_options.method; - + formData = new FormData(trigger_or_options); - } - + } + // get mask element attr_mask = trigger_or_options.getAttribute("data-mask"); if (attr_mask == "true" || attr_mask == "") { @@ -735,9 +739,9 @@ // here params.title will become page title; params.title = trigger_or_options.title; } else { - return; + return; } - + // do ajax // get mask and loading element if (typeof attr_mask != "string") { @@ -755,16 +759,16 @@ } // show loading ele_mask.style.visibility = "visible"; - + // ajax request - var xhr = new XMLHttpRequest(); + var xhr = new XMLHttpRequest(); xhr.open(params.type || "GET", params.url + (/\?/.test(params.url)? "&" : "?") + "r=" + Date.now(), params.async, params.username, params.password); 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 (xhr.status == 200) { if (params.dataType == "json" || params.dataType == "JSON") { try { @@ -801,46 +805,46 @@ params.message = "The status code exception!"; params.error.call(params, xhr, xhr.status); } - + params.complete.call(params, xhr, xhr.status); - + // hide loading ele_mask.style.visibility = "hidden"; } - + xhr.onerror = function(e) { params.message = "Illegal request address or an unexpected network error!"; params.error.call(params, xhr, xhr.status); // hide loading ele_mask.style.visibility = "hidden"; } - + xhr.ontimeout = function() { params.message = "The request timeout!"; params.error.call(params, xhr, xhr.status); // hide loading ele_mask.style.visibility = "hidden"; }; - + // 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; + if (!form || typeof form.action != "string") return; var ajax = form.getAttribute("data-ajax"); if (ajax == "false" || (this.captureForm == false && ajax != "true")) return; - + form.addEventListener("submit", function(event) { // prevent detect var attrPrevent = this.getAttribute("data-preventdefault"); @@ -851,23 +855,23 @@ 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 page_in dom-object - Necessary - page_out dom-object - Optional - + In this case, we ensure the direction(back or prev) by the sorts of two pages(into or out) + + * @params page_in dom-object - Necessary + page_out dom-object - Optional + * @returns boolean * - **/ + **/ Mobilebone.isBack = function(page_in, page_out) { // back or forword, according to the order of two pages if (history.tempBack == true) { @@ -877,116 +881,116 @@ } return page_in.compareDocumentPosition(page_out) == 4; }; - + /** - * 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. + * 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": [] - } + For example, every JSON include the only json-id: + { + "id": "homePage" , + "data": [] + } + different with + { + "id": "listPage" , + "data": [] + } * - * @params json - Necessary + * @params json - Necessary * @returns dom-object|string * - **/ + **/ Mobilebone.jsonHandle = function(json) { return '

Dear master, if you see me, show that JSON parsing function is undefined!

'; }, - + /** * 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("#&", "#"), ele_in = null; - - if (hash == "" || hash == "#") { - this.transition(document.querySelector("." + this.classPage)); - } else if (isSimple.test(hash) == true && (ele_in = document.querySelector(hash)) && ele_in.classList.contains(this.classPage)) { // 'ele_in' must be a page element - this.transition(ele_in); - } else { - // as a ajax - this.ajax({ - url: hash.replace("#", ""), - dataType: "unknown", - error: function() { - ele_in = document.querySelector("." + Mobilebone.classPage); - Mobilebone.transition(ele_in); - } - }); - } - - // Initialization link-catch events. - var eventName = "click", $ = root.$ || root.jQuery || root.Zepto; - if ($ && $.fn && $.fn.tap && ('ontouchstart' in window == true)) eventName = "tap"; - - if (this.captureLink == true) { - document.addEventListener(eventName, this.handleTapEvent); - if (eventName == "tap") { - // zepto tap event.preventDefault can't prevent default click-events - document.addEventListener("click", function(event) { - var target = event.target; - if (!target) return; - if (target.tagName.toLowerCase() != "a" && !(target = target.getParentElementByTag("a"))) { - return; + **/ + Mobilebone.init = function() { + if (hasInited == true) return 'Don\'t repeat initialization!'; + + var hash = location.hash.replace("#&", "#").replace(/\?.*/,""), ele_in = null; + + if (hash == "" || hash == "#") { + this.transition(document.querySelector("." + this.classPage)); + } else if (isSimple.test(hash) == true && (ele_in = document.querySelector(hash)) && ele_in.classList.contains(this.classPage)) { // 'ele_in' must be a page element + this.transition(ele_in); + } else { + // as a ajax + this.ajax({ + url: hash.replace("#", ""), + dataType: "unknown", + error: function() { + ele_in = document.querySelector("." + Mobilebone.classPage); + Mobilebone.transition(ele_in); } - var ajax = target.getAttribute("data-ajax"), href = target.href; - // if not ajax request - if (target.getAttribute("data-rel") == "external" - || ajax == "false" - || (href.replace("://", "").split("/")[0] !== location.href.replace("://", "").split("/")[0] && ajax != "true") - || (Mobilebone.captureLink == false && ajax != "true") - ) return; - event.preventDefault(); - }); + }); } - } - // 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; - }; - + + // Initialization link-catch events. + var eventName = "click", $ = root.$ || root.jQuery || root.Zepto; + if ($ && $.fn && $.fn.tap && ('ontouchstart' in window == true)) eventName = "tap"; + + if (this.captureLink == true) { + document.addEventListener(eventName, this.handleTapEvent); + if (eventName == "tap") { + // zepto tap event.preventDefault can't prevent default click-events + document.addEventListener("click", function(event) { + var target = event.target; + if (!target) return; + if (target.tagName.toLowerCase() != "a" && !(target = target.getParentElementByTag("a"))) { + return; + } + var ajax = target.getAttribute("data-ajax"), href = target.href; + // if not ajax request + if (target.getAttribute("data-rel") == "external" + || ajax == "false" + || (href.replace("://", "").split("/")[0] !== location.href.replace("://", "").split("/")[0] && ajax != "true") + || (Mobilebone.captureLink == false && ajax != "true") + ) return; + event.preventDefault(); + }); + } + } + // 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 tapping~ - **/ - Mobilebone.handleTapEvent = function(event) { + **/ + Mobilebone.handleTapEvent = function(event) { /** - // iscroll(set tap: true) may cause twice tap problem - // which is none of Mobilebone's business - // However, you can let code below go to avoid twice tap in Mobilebone - // but the tap event bind out of Mobilebone also has bug - // so my advice is that: - // 1. use Date.now to judge as Mobilebone did; - // or - // 2. keep this code in the form of comment and fixed bug outside - if (store.timerTap && Date.now() - store.timerTap < 100) { + // iscroll(set tap: true) may cause twice tap problem + // which is none of Mobilebone's business + // However, you can let code below go to avoid twice tap in Mobilebone + // but the tap event bind out of Mobilebone also has bug + // so my advice is that: + // 1. use Date.now to judge as Mobilebone did; + // or + // 2. keep this code in the form of comment and fixed bug outside + if (store.timerTap && Date.now() - store.timerTap < 100) { event.preventDefault(); return false; } - store.timerTap = Date.now(); - */ - + store.timerTap = Date.now(); + */ + // get target and href var target = event.target || event.touches[0], href = target.href; if ((!href || /a/i.test(target.tagName) == false) && (target = target.getParentElementByTag("a"))) { @@ -995,16 +999,16 @@ // the page that current touched or actived var self_page = document.querySelector(".in." + Mobilebone.classPage); - + if (self_page == null || !target) return; // optional params for Mobilebone.transition var options = { - target: target + target: target }; - + // prevent detect - var attrPrevent = target.getAttribute("data-preventdefault") + var attrPrevent = target.getAttribute("data-preventdefault") || _queryToObject(target.getAttribute("data-params") || "").preventdefault; // get 'preventDefault' function var funPrevent = Mobilebone.getFunction(attrPrevent); @@ -1013,14 +1017,14 @@ event.preventDefault(); return false; } - + // if mask element exist and displaying, prevent double trigger var ele_mask = target.getElementsByClassName(Mobilebone.classMask)[0]; if (ele_mask && ele_mask.style.visibility != "hidden") { event.preventDefault(); return false; } - + var idContainer = target.getAttribute("data-container"), classPageInside = target.getAttribute("data-classpage"), container = idContainer && document.getElementById(idContainer); @@ -1030,7 +1034,7 @@ options.history = false; options.classPage = classPageInside; } - + // if captureLink var capture = (Mobilebone.captureLink == true); // get rel @@ -1040,31 +1044,31 @@ if (rel == "back") { back = 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; - - href = href.replace("#&", "#"); - + + href = href.replace("#&", "#").replace(/\?.*/,""); + if (target.getAttribute("href").replace(/#/g, "") === "") { event.preventDefault(); return; } if (/^javascript/.test(href)) { - if (back == false) return; + if (back == false) 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(target.getAttribute("href")) == true) { // hash slide @@ -1072,7 +1076,7 @@ if (back == false && rel == "auto") { back = Mobilebone.isBack(eleTargetPage, self_page); } - + if (eleTargetPage) { Mobilebone.transition(eleTargetPage, self_page, back, options); } @@ -1081,13 +1085,13 @@ // back history.tempBack = true; history.back(); - } else if (target.getAttribute("data-ajax") != "false") { + } else if (target.getAttribute("data-ajax") != "false") { // get a clean ajax url as page id var clean_url = Mobilebone.getCleanUrl(target); - + // if has loaded and the value of 'data-reload' is not 'true' var attr_reload = target.getAttribute("data-reload"), id = target.getAttribute("href"); - + if ((attr_reload == null || attr_reload == "false") && store[clean_url]) { if (back == false && rel == "auto") { back = Mobilebone.isBack(store[clean_url], self_page); @@ -1103,11 +1107,11 @@ event.preventDefault(); } }; - - + + /** * prototype extend method: get parent element by tagName - **/ + **/ Element.prototype.getParentElementByTag = function(tag) { if (!tag) return null; var element = null, parent = this; @@ -1126,10 +1130,10 @@ popup(); return element; }; - + /** * private method: convert query string to key-value object - **/ + **/ var _queryToObject = function(string) { var obj = {}; if (typeof string == "string") { @@ -1142,19 +1146,19 @@ } return obj; }; - + /** * auto init - **/ + **/ window.addEventListener("DOMContentLoaded", function() { if (hasInited == false) { Mobilebone.init(); } }); - + /** * page change when history change - **/ + **/ window.addEventListener("popstate", function() { if (history.popstateswipe == true) { location.reload(); @@ -1165,31 +1169,31 @@ history.popstate = true; return; } - - var hash = location.hash.replace("#&", "").replace(/^#/, ""), page_in = null; - + + var hash = location.hash.replace("#&", "").replace(/^#/, "").replace(/\?.*/,""), page_in = null; + if (hash == "") { // if no hash, get first page as 'page_in' page_in = document.querySelector("." + Mobilebone.classPage); - if (page_in.id) return; + if (page_in.id) return; } else { page_in = store[hash]; } - + if (!page_in) { if(isSimple.test(hash) == false) { // as a url Mobilebone.ajax({ url: hash, dataType: "unknown" - }); + }); return; } page_in = document.querySelector("#" + hash) } - + var page_out = document.querySelector(".in." + Mobilebone.classPage); - + if ((page_in && page_in == page_out) || Mobilebone.pushStateEnabled == false) return; // hash ↔ id @@ -1201,8 +1205,8 @@ }); } }); - + document.MBLOADED = true; - + return Mobilebone; }); \ No newline at end of file From e9d2097840860aa42fa7ce779a6548a141d70ec4 Mon Sep 17 00:00:00 2001 From: chenhao Date: Mon, 18 May 2015 16:44:19 +0800 Subject: [PATCH 2/4] =?UTF-8?q?a=E6=A0=87=E7=AD=BE=E5=A6=82=E6=9E=9C?= =?UTF-8?q?=E6=9C=89data-title=E5=8F=AF=E4=BF=AE=E6=94=B9=E6=96=B0?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E7=9A=84title?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mobilebone.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mobilebone.js b/src/mobilebone.js index 8be4786..15b6b3e 100644 --- a/src/mobilebone.js +++ b/src/mobilebone.js @@ -1004,7 +1004,8 @@ // optional params for Mobilebone.transition var options = { - target: target + target: target, + title : target.getAttribute("data-title") }; // prevent detect From 79069cc0c7cc0870f876b20930240a0891907c93 Mon Sep 17 00:00:00 2001 From: chenhao Date: Mon, 18 May 2015 21:18:08 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E5=88=86=E6=94=AF=E8=B7=9F=E8=BF=9B?= =?UTF-8?q?=E4=B8=BB=E5=88=86=E6=94=AF=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/dictionaries/chenhao.xml | 3 + .idea/fileColors.xml | 6 ++ src/mobilebone.js | 118 ++++++++++++++++----------------- 3 files changed, 68 insertions(+), 59 deletions(-) create mode 100644 .idea/dictionaries/chenhao.xml create mode 100644 .idea/fileColors.xml diff --git a/.idea/dictionaries/chenhao.xml b/.idea/dictionaries/chenhao.xml new file mode 100644 index 0000000..5f6ff59 --- /dev/null +++ b/.idea/dictionaries/chenhao.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/fileColors.xml b/.idea/fileColors.xml new file mode 100644 index 0000000..646829e --- /dev/null +++ b/.idea/fileColors.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/mobilebone.js b/src/mobilebone.js index df5104f..fd6e350 100644 --- a/src/mobilebone.js +++ b/src/mobilebone.js @@ -957,66 +957,66 @@ Mobilebone.init = function() { if (hasInited == true) return 'Don\'t repeat initialization!'; - var hash = location.hash.replace("#&", "#"), ele_in = null; - - if (hash == "" || hash == "#") { - this.transition(document.querySelector("." + this.classPage)); - } else if (isSimple.test(hash) == true && (ele_in = document.querySelector(hash)) && ele_in.classList.contains(this.classPage)) { // 'ele_in' must be a page element - this.transition(ele_in); - } else { - // as a ajax - this.ajax({ - url: hash.replace("#", ""), - dataType: "unknown", - error: function() { - ele_in = document.querySelector("." + Mobilebone.classPage); - Mobilebone.transition(ele_in); - } - }); - } - - // Initialization link-catch events. - var $ = root.$ || root.jQuery || root.Zepto; - if ($ && $.fn && $.fn.tap && ('ontouchstart' in window == true)) { - // for some unknown 'tap' plugin - $(document).tap(this.handleTapEvent); - - // zepto tap event.preventDefault can't prevent default click-events - document.addEventListener("click", function(event) { - var target = event.target; - if (!target) return; - if (target.tagName.toLowerCase() != "a" && !(target = target.getParentElementByTag("a"))) { - return; - } - var ajax = target.getAttribute("data-ajax"), href = target.href; - // if not ajax request - if (target.getAttribute("data-rel") == "external" - || ajax == "false" - || (href.replace("://", "").split("/")[0] !== location.href.replace("://", "").split("/")[0] && ajax != "true") - || (Mobilebone.captureLink == false && ajax != "true") - ) return; - event.preventDefault(); - }); - } else { - document.addEventListener("click", this.handleTapEvent); - } + var hash = location.hash.replace("#&", "#").replace(/\?.*/,""), ele_in = null; - // 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 (hash == "" || hash == "#") { + this.transition(document.querySelector("." + this.classPage)); + } else if (isSimple.test(hash) == true && (ele_in = document.querySelector(hash)) && ele_in.classList.contains(this.classPage)) { // 'ele_in' must be a page element + this.transition(ele_in); + } else { + // as a ajax + this.ajax({ + url: hash.replace("#", ""), + dataType: "unknown", + error: function() { + ele_in = document.querySelector("." + Mobilebone.classPage); + Mobilebone.transition(ele_in); + } + }); + } + + // Initialization link-catch events. + var $ = root.$ || root.jQuery || root.Zepto; + if ($ && $.fn && $.fn.tap && ('ontouchstart' in window == true)) { + // for some unknown 'tap' plugin + $(document).tap(this.handleTapEvent); + + // zepto tap event.preventDefault can't prevent default click-events + document.addEventListener("click", function(event) { + var target = event.target; + if (!target) return; + if (target.tagName.toLowerCase() != "a" && !(target = target.getParentElementByTag("a"))) { + return; + } + var ajax = target.getAttribute("data-ajax"), href = target.href; + // if not ajax request + if (target.getAttribute("data-rel") == "external" + || ajax == "false" + || (href.replace("://", "").split("/")[0] !== location.href.replace("://", "").split("/")[0] && ajax != "true") + || (Mobilebone.captureLink == false && ajax != "true") + ) return; + event.preventDefault(); + }); + } else { + 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 tapping~ From a7174925471746d17e3571befb06974719804ff0 Mon Sep 17 00:00:00 2001 From: chenhao Date: Mon, 18 May 2015 21:26:02 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=88=86=E6=94=AF?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 149 ++---------------------------------------------------- 1 file changed, 3 insertions(+), 146 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 37ba11e..f9fc804 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,147 +1,4 @@ -mobilebone.js -============= -单页切换骨架。适用于移动web APP, Hybrid混合APP, Phonegap开发, 无兼容要求单页PC应用等。官方网站:
http://www.mobilebone.org - -实例、测试和文档 ------------------- -
    -
  • 实例1-仿QQ界面:http://iancj.github.io/qq/
  • -
  • 实例2-暴走漫画制作器:http://baozoumanhua.com/mobile_makers/
  • -
- - - - - -为何需要? -------------- -类原生APP的过场体验,适用于这些场景:
-1. Phonegap等类似跨移动开发平台,其静态页面都是index.html, 单页面,因此,需要跟原生一样的过场体验。
-2. Hybrid app开发,原生APP内嵌web APP, 为了两者体验一致,不至于交互太唐突,也需要无刷新过场效果。
-3. 就算是纯粹的移动web APP, 使用无刷新模式也不失为一种不错的选型策略。
-4. 对兼容性没有要求的单页PC应用,如类PowerPoint web文档,单页翻屏web站点等。 - - -如何使用? ---------------- -引入相关的CSS以及JS, 如下: -
<link rel="stylesheet" href="mobilebone.css">
-
<script src="mobilebone.js"></script>
- -HTML结构需要有一定的要求: -
body
-  page
-  page
-  page
- -每个page是个满屏元素, 相当于一个独立的页面。 - -Mobilebone会自动捕获页面上的a元素,如果其href值存在猫腻,就会触发切换行为。例如: -
<a href="#targetPage">目标页面</a>
- -当click/tap此元素时候,页面会自动无刷新切换到idtargetPage的页面。 - -也可以使用ajax请求。例如: -
<a href="detail.php?id=112">请求详情页</a>
- -所有ajax请求默认是缓存的,如果你想根据url地址不缓存,可以设置data-reload或者data-reload="true";如果你想根据url根地址不缓存,需要设置data-reload="root"。 - -你可以控制切换的方向,任意扩展动画类型,可以被seajs, requiejs模块化加载(require('mobilebone')),可以和Backbone组合使用等。 - -当然,上面介绍的,只是强大功能的冰山一角,更多信息请[参考这里](http://www.zhangxinxu.com/wordpress/?p=4381). - -插件 ----------------- -
    -
  1. mobilebone.ppt.js: 可以让web页面表现如幻灯片演示,尺寸自适应。 demo点击这里
  2. -
- -优势? ------------------- -mobilebone.js只做了一件事情,切换。所以,JS文件很小,gzip后4~5K, 而且很灵活,几乎没有任何UI的限制,适用于各个项目各个场景。同时,巧妙提供各类缓存管理、事件管理的接口,就像是个完整健全的骨架体系,就等你来加血添肉了! - -许可 -------------------- -MIT许可 - -捐赠 ------------------- -左支付宝,右微信
-支付宝     微信 - -mobilebone.js -============= -Single Page Switching bone for mobile web APP, Hybrid APP, Phonegap, ... - -
git clone git://github.com/zhangxinxu/mobilebone.git
- -use npm: -
npm install mobilebone
- -Examples and Tests and Documents ------------------- -
    -
  1. Example1: http://iancj.github.io/qq/
  2. -
  3. Example2: http://baozoumanhua.com/mobile_makers/
  4. -
- - - - - -Why need this? ------------------ -Interaction experience is good enough to compare with the native APP.
-1. Phonegap that to native APP is a single index.html, We need the same switch effects as native.
-2. Hybrid app, as you know, mixed web-app and native-app. So, it' better that they have some switching experience.
-3. Even no refresh interaction is not something bad for mobile APP. - -How to use? ----------------- -Just include mobilebone.css and mobilebone.js, as follow: -
<link rel="stylesheet" href="mobilebone.css">
-
<script src="mobilebone.js"></script>
- -And, you should use specific HTML structure. -
body
-  page
-  page
-  page
- -Then Mobilebone will catch your attribute of href of 'a' element, and do switch. For example: -
<a href="#targetPage">target page</a>
- -The interface will switch to page whitch's value of id is targetPage when you tap this link. - -Of course, you can control the direction of switching, or use a ajax get, or as a modular loaded by seajs, requirejs using require('mobilebone')... - -For more detail, you can [visit here](http://www.zhangxinxu.com/wordpress/?p=4381). - -Plugins ----------------- -
    -
  1. mobilebone.ppt.js: make web page to powerpoint presentation. demo here.
  2. -
- -Advantage? --------------- -what mobilebone.js do just one thing - switching. So, it's small, flexible, and no any UI restriction. In a word, it's fit for variety of designs and scenes. - -License -------------------- -The MIT License - - - - +该项目是[mobilebone](https://github.com/zhangxinxu/mobilebone)的分支,因为项目需要进行了以下修改: + 1.支持URL传值,例如:`产品列表` + 2.a标签如果有`data-title`可修改新页面的title \ No newline at end of file