[
  {
    "path": ".brackets.json",
    "content": "{\n    \"sbruchmann.staticpreview.basepath\": \"G:/轮子/coffce-pjax/\"\n}"
  },
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n\n# Custom for Visual Studio\n*.cs     diff=csharp\n\n# Standard to msysgit\n*.doc\t diff=astextplain\n*.DOC\t diff=astextplain\n*.docx diff=astextplain\n*.DOCX diff=astextplain\n*.dot  diff=astextplain\n*.DOT  diff=astextplain\n*.pdf  diff=astextplain\n*.PDF\t diff=astextplain\n*.rtf\t diff=astextplain\n*.RTF\t diff=astextplain\n"
  },
  {
    "path": "README.md",
    "content": "coffce-pjax\n===\ncoffce-pjax可以将页面所有的跳转替换为AJAX请求，把网站改造成单页面应用。<br>\nnote: 由于浏览器限制，pjax需要在服务器环境下使用，即不要使用file://xxx.html运行。\n\n###有何用处：\n* 可以在页面切换间平滑过渡，增加Loading动画。\n* 可以在各个页面间传递数据，不依赖URL。\n* 可以选择性的保留状态，如音乐网站，切换页面时不会停止播放歌曲。\n* 所有的标签都可以用来跳转，不仅仅是a标签。\n* 避免了公共JS的反复执行，如无需在各个页面打开时都判断是否登录过等等。\n* 减少了请求体积，节省流量，加快页面响应速度。\n* 平滑降级到低版本浏览器上，对SEO也不会有影响。\n\n###兼容性：\n* Chrome, Firefox, Safari, Android Browser, IE8+等。\n* 在IE8和IE9上使用URL Hash，即地址栏的#号。\n* 在更低版本的浏览器和搜索引擎蜘蛛上，保持默认跳转，不受影响。\n\n如何使用\n---\n####安装：\n    npm install coffce-pjax\n\n#### 引入\n``` javascript\n// 使用全局变量\nvar pjax = window.CoffcePJAX\n```\n\n``` javascript\n// 使用commonJS或AMD\nvar pjax = require(\"coffce-pjax\");\n```\n####简单配置：\n``` javascript\npjax.init({\n    // 替换新页面内容的容器\n    container: \"body\",\n    // 是否在低版本浏览器上使用Hash\n    hash: true\n});\n```\n####完整配置:\n``` javascript\npjax.init({\n    // 选择器，支持querySelector选择器\n    selector: \"a\",\n    // 要替换内容的容器，可为选择器字符串或DOM对象\n    container: \"body\",\n    // 是否在前进后退时开启本地缓存功能\n    cache : true,\n    // 是否对低版本浏览器启用hash方案，不启用此项的低版本浏览器则会按照普通模式跳转\n    hash: false,\n    // 是否允许跳转到当前相同URL，相当于刷新\n    same: true,\n    // 调试模式，console.log调试信息\n    debug: false,\n    \n    // 各个执行阶段的过滤函数，返回false则停止pjax执行\n    filter: {\n        // 选择器过滤，如果querySelector无法满足需求，可以在此函数里二次过滤\n        selector: function(a) {},\n        // 接收到ajax请求返回的内容时触发\n        content: function(title, html) {}\n    },\n    // 各个阶段的自定义函数，将代替默认函数\n    custom: {\n        // 自定义更换页面函数，可以在此实现动画效果等\n        append: function(html, container) {}\n    },\n    // 要监听的事件，相当于pjax.on(...)，事件列表看下面\n    events: {}\n});\n```\n\n接口\n---\n```javascript\n/**\n * 初始化\n * @param {Object} options 配置，详情见上面↑\n */\npjax.init(config);\n```\n\n```javascript\n // 注销插件，一般来说你不需要使用这个方法\npjax.destroy();\n```\n\n```javascript\n/**\n * 使用pjax跳转到指定页面\n * @param {String}   url\n * @param {Object}   data     要传到新页面的参数，可以为null或undefined\n * @param {Function} callback 请求成功时的回调，可以为null或undefined\n */\npjax.turn(url, data, callback);\n```\n\n```javascript\n/**\n * 监听事件，事件类型见下面↓\n * @param {String}   type     事件类型\n * @param {Function} listener 回调\n * @param {String}   url      只监听某个url，可以是相对和绝对路径\n */\npjax.on(type, listener);\npjax.on(type, url, listener);\n```\n\n```javascript\n/**\n * 解除监听\n * @param {String} type 事件类型\n * @param {String} url  只监听某个url，可以是相对和绝对路径\n */\npjax.off(type);\npjax.off(type, url);\n```\n\n```javascript\n/**\n * 触发事件\n * @param {String} type 事件类型\n * @param {Object} args 参数\n */\npjax.trigger(type, args);\n```\n\n事件\n---\n####监听事件\n```javascript\n// 通过接口监听\npjax.on(type, url, function);\npjax.on(type, function);\n```\n```javsctipy\n// 通过配置监听\npjax.init({\n    // ....\n    events: {\n        type: function(){}\n    }\n});\n```\n\n####事件类型\n**init**<br>\n在每个页面加载完成后触发，有一个object参数：{ title, html }\n\n**end**<br>\n在每个页面离开前触发\n\n**ajaxBegin**<br>\n在请求开始时触发。有一个object参数： { url, fnb, data, xhr }, url表示新页面的url，fnb表示是否由浏览器前进后退触发，data表示传到新页面的数据，xhr是请求的XMLHttpRequest()实例\n\n**ajaxSuccess**<br>\n在请求成功后触发。参数与begin一样。\n\n**ajaxError**<br>\n在请求失败后触发。参数与begin一样。\n\n\n特性\n---\n* 优先使用标签上的data-coffce-pjax-href，其次使用href\n* 标签上若有data-coffce-pjax属性，将作为data属性传递到新页面\n\n```html\n// 将跳转到b.html，并传递字符串data\n<a href=\"a.html\" data-coffce-pjax-href=\"b.html\" data-coffce-pjax=\"data\"></a>\n```\n\n服务端配合\n---\n* 对于PJAX请求，服务端并不需要返回完整的HTML，只返回变动的Content部分即可。对于普通请求(一般由浏览器地址栏直接打开)，则需要返回完整的HTML。\n* coffce-pjax在发送请求时，会带上请求头COFFCE-PJAX：true，你可以依此来判断当前请求是PJAX请求还是普通请求。\n* 由于没有返回完整的HTML，服务端应该将document.title放在请求头COFFCE-PJAX-TITLE里。\n\n注意：\n------\n作者很懒，没有认真测试过，接口也可能随时变动，使用需自己小心。\n\nLicense\n-----\nMIT"
  },
  {
    "path": "demo/1.html",
    "content": "<p>1.html</p>"
  },
  {
    "path": "demo/2.html",
    "content": "<p>2.html</p>"
  },
  {
    "path": "demo/3.html",
    "content": "<p>3.html</p>"
  },
  {
    "path": "demo/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>COFFCE-PJAX-DEMO</title>\n    <link rel=\"stylesheet\" href=\"../styles/style.css\">\n</head>\n<body>\n    <div id=\"container\"></div>\n    <div id=\"main\">\n        <a href=\"1.html\">1.html</a>\n        <a href=\"2.html\">2.html</a>\n        <a href=\"3.html\" data-coffce-pjax=\"传递到新页面的数据\">3.html</a>\n    </div>\n    <!--Scripts-->\n    <script src=\"../scripts/coffce-pjax.js\"></script>\n    <script>\n        var container = document.getElementById(\"container\");\n        var pjax = CoffcePJAX;\n        \n        // 配置PJAX\n        pjax.init({\n            container: container,\n            hash: true,\n            filter: {\n                content: function(title, html) {\n                    // 已后退到首页，真实环境不需要这一步。\n                    if (html.indexOf('<html lang=\"en\">') > -1) {\n                        container.innerHTML = \"\";\n                        return false;\n                    }\n\n                    return true;\n                }\n            },\n            events: {\n                init: function() {\n                    console.log(\"init\");\n                },\n                end: function() {\n                    console.log(\"end\");  \n                },\n                ajaxBegin: function() {\n                    console.log(\"ajaxBegin\");\n                },\n                ajaxSuccess: function() {\n                    console.log(\"ajaxSuccess\");\n                },\n                ajaxError: function() {\n                    console.log(\"ajaxError\")\n                }\n            }\n        });\n    </script>\n</body>\n</html>"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"coffce-pjax\",\n  \"version\": \"0.0.4\",\n  \"description\": \"A simple pjax library\",\n  \"main\": \"scripts/coffce-pjax.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/Coffcer/Coffce-PJAX.git\"\n  },\n  \"keywords\": [\n    \"pjax\",\n    \"coffce\"\n  ],\n  \"author\": \"coffce\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/Coffcer/Coffce-PJAX/issues\"\n  },\n  \"homepage\": \"https://github.com/Coffcer/Coffce-PJAX\"\n}\n"
  },
  {
    "path": "scripts/coffce-pjax.js",
    "content": "/*jshint eqnull: true, expr: true, sub: true, browser: true, devel: true*/\n/*global define, module */\n\n/*!\n * Coffce-Pjax\n * 将页面所有的跳转替换为ajax请求，把网站改造成单页面应用\n * 兼容Chrome, Firefox, Safari, Android Browser, IE8+等\n * 在IE8和IE9上使用URL Hash，即地址栏的#号，你也可以选择不启用\n * 在更低版本的浏览器和搜索引擎蜘蛛上，保持默认跳转，不受影响\n */\n\n(function (window, undefined) {\n    \"use strict\";\n\n    // 配置\n    var config = {\n        // 选择器，支持querySelector选择器\n        selector: \"a\",\n        // 要替换内容的容器，可为选择器字符串或DOM对象\n        container: \"body\",\n        // 是否在前进后退时开启本地缓存功能\n        cache: true,\n        // 是否对低版本浏览器启用hash方案\n        hash: false,\n        // 是否允许跳转到当前相同URL，相当于刷新\n        same: true,\n        // 调试模式，console.log调试信息\n        debug: false,\n        // 各个执行阶段的过滤函数，返回false则停止pjax执行\n        filter: {\n            // params: element\n            // 选择器过滤，如果querySelector无法满足需求，可以在此函数里二次过滤\n            selector: null,\n            // params: title, html\n            // 接收到ajax请求返回的内容时触发\n            content: null\n        },\n        // 各个阶段的自定义函数，将替换默认实现\n        custom: {\n            // params: html, container\n            // 自定义更换页面函数，可以在此实现动画效果等\n            append: null\n        },\n        // 事件监听，合并到CoffcePJAX.on()里\n        events: null\n    };\n\n    // 使用模式 枚举\n    var SUPPORT = {\n        // 不支持\n        PASS: 0,\n        // 使用Hash\n        HASH: 1,\n        // 使用HTML History API\n        HTML5: 2\n    };\n\n    // 浏览器支持情况\n    var suppost = history.pushState ? SUPPORT.HTML5 : (\"onhashchange\" in window ? SUPPORT.HASH : SUPPORT.PASS);\n\n    var util = {\n        /**\n         * 合并两个对象，浅拷贝\n         * @param {Object} obj1\n         * @param {Object} obj2\n         */\n        extend: function (obj1, obj2) {\n            if (!obj2) return;\n\n            for (var key in obj2) {\n                if (obj2.hasOwnProperty(key)) {\n                    obj1[key] = obj2[key];\n                }\n            }\n\n            return obj1;\n        },\n        /**\n         * 输出调试信息，仅在config.debug为true时输出\n         * @param {String} text\n         */\n        log: function (text) {\n            config.debug && console.log(\"coffce-pjax: \" + text);\n        },\n        /**\n         * 获取url中的路径， 如：www.google.com/abcd 返回 /abcd\n         * @param {String} url\n         */\n        getPath: function (url) {\n            return url.replace(location.protocol + \"//\" + location.host, \"\");\n        },\n        /**\n         * 通过相对路径获取完整的url\n         * @param {String} href\n         */\n        getFullHref: function (href) {\n            // 利用a标签来获取href，除此之外，a标签还能用来获取许多url相关信息\n            var a = document.createElement(\"a\");\n            a.href = href;\n            return a.href;\n        },\n        /**\n         * 判断dom是否匹配选择器\n         * @param {Object} element\n         * @param {String} selector\n         */\n        matchSelector: function (element, selector) {\n            var match =\n                document.documentElement.webkitMatchesSelector ||\n                document.documentElement.mozMatchesSelector ||\n                document.documentElement.msMatchesSelector ||\n                // 兼容IE8及以下浏览器\n                function (selector, element) {\n                    // 这是一个好方法，可惜IE8连indexOf都不支持\n                    // return Array.prototype.indexOf.call(document.querySelectorAll(selector), this) !== -1;\n\n                    if (element.tagName === selector.toUpperCase()) return true;\n\n                    var elements = document.querySelectorAll(selector),\n                        length = elements.length;\n\n                    while (length--) {\n                        if (elements[length] === this) return true;\n                    }\n\n                    return false;\n                };\n\n            // 重写函数自身，使用闭包keep住match函数，不用每次都判断兼容\n            util.matchSelector = function (element, selector) {\n                return match.call(element, selector);\n            };\n\n            return util.matchSelector(element, selector);\n        }\n    };\n\n    var cache = {\n        key: function (url) {\n            return \"coffce-pjax[\" + url + \"]\";\n        },\n        get: function (url) {\n            var value = sessionStorage.getItem(cache.key(url));\n            return value && JSON.parse(value);\n        },\n        set: function (url, value) {\n            // storage有容量上限，超出限额会报错\n            try {\n                sessionStorage.setItem(cache.key(url), JSON.stringify(value));\n            } catch (e) {\n                util.log(\"超出本地存储容量上线，本次操作将不使用本地缓存\");\n            }\n        },\n        clear: function () {\n            var i = sessionStorage.length;\n            while (i--) {\n                var key = sessionStorage.key(i);\n                if (key.indexOf(\"coffce-pjax\") > -1) {\n                    sessionStorage.removeItem(key);\n                }\n            }\n        },\n    };\n\n    var event = {\n        // 在浏览器前进后退时执行\n        popstate: function () {\n            core.fnb = true;\n            core.turn(location.href, null, null);\n        },\n        // hash改变时执行，由于过滤了手动改变，所以也只在浏览器前进后退时执行\n        hashchange: function () {\n            if (!core.fnb) return;\n            core.turn(location.href.replace(\"#/\", \"\"), null, null);\n        },\n        click: function (e) {\n            var element = e.target || e.srcElement;\n\n            // 过滤不匹配选择器的元素\n            if (!util.matchSelector(element, config.selector)) return;\n\n            // 调用自定义过滤函数\n            if (config.filter.selector && !config.filter.selector(element)) return;\n\n            // 优先使用data-coffce-pjax-href\n            var url = element.getAttribute(\"data-coffce-pjax-href\");\n            url = url ? util.getFullHref(url) : element.href;\n\n            // 过滤空值\n            if (url === undefined || url === \"\") return;\n\n            // 阻止默认跳转，\n            // 在这上面的return，仍会执行默认跳转，下面的就不会了\n            e.preventDefault ? e.preventDefault() : (window.event.returnValue = false);\n\n            // 阻止相同链接\n            if (!config.same && url === location.href) return;\n\n            // 标签上有这个值的话，将作为data传入新页面\n            var data = element.getAttribute(\"data-coffce-pjax\");\n\n            core.fnb = false;\n            core.turn(url, data, null);\n        },\n        bindEvent: function () {\n            if (suppost === SUPPORT.HTML5) {\n                window.addEventListener(\"popstate\", event.popstate);\n                window.addEventListener(\"click\", event.click);\n            } else {\n                window.attachEvent(\"onhashchange\", event.hashchange);\n                document.documentElement.attachEvent(\"onclick\", event.click);\n            }\n        },\n        unbindEvent: function () {\n            if (suppost === SUPPORT.HTML5) {\n                window.removeEventListener(\"popstate\", event.popstate);\n                window.removeEventListener(\"click\", event.click);\n            } else {\n                window.detachEvent(\"onhashchange\", event.hashchange);\n                document.documentElement.detachEvent(\"onclick\", event.click);\n            }\n        }\n    };\n\n    var core = {\n        // Forward And Back，表示当前操作是否由前进和后退触发\n        fnb: false,\n        // 显示新页面\n        show: function (title, html) {\n            pjax.trigger(\"end\");\n\n            document.title = title;\n\n            if (config.custom.append) {\n                config.custom.append(html, config.container);\n            } else {\n                config.container.innerHTML = html;\n            }\n\n            pjax.trigger(\"init\");\n        },\n        // 跳转到指定页面\n        turn: function (url, data, callback) {\n            var eventData = {\n                url: url,\n                fnb: core.fnb,\n                data: data\n            };\n\n            //pjax.trigger(\"begin\", eventData);\n\n            // 如果是由前进后退触发，并且开启了缓存，则试着从缓存中获取数据\n            if (core.fnb && config.cache) {\n                var value = cache.get(url);\n                if (value !== null) {\n                    core.show(value.title, value.html);\n                    /*pjax.trigger(\"success\", eventData);\n                    pjax.trigger(\"end\", eventData);*/\n                    return;\n                }\n            }\n\n            // 开始发送请求\n            var xhr = new XMLHttpRequest();\n\n            xhr.open(\"GET\", url, true);\n            xhr.setRequestHeader(\"COFFCE-PJAX\", \"true\");\n            xhr.setRequestHeader(\"X-Requested-With\", \"XMLHttpRequest\");\n\n            eventData.xhr = xhr;\n            pjax.trigger(\"ajaxBegin\", eventData);\n\n            xhr.onreadystatechange = function () {\n                if (xhr.readyState === 4) {\n                    // 姑且认为200-300之间都是成功的请求，304是缓存\n                    if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {\n                        var title = xhr.getResponseHeader(\"COFFCE-PJAX-TITLE\") || document.title,\n                            html = xhr.responseText;\n\n                        // 内容过滤器\n                        if (config.filter.content && !config.filter.content(title, html)) {\n                            util.log(\"filter.content过滤不通过\");\n                        } else {\n                            callback && callback(data);\n                            pjax.trigger(\"ajaxSuccess\", eventData);\n                            \n                            // 显示新页面\n                            core.show(title, html);\n\n                            if (!core.fnb) {\n                                // 修改URL\n                                if (suppost === SUPPORT.HTML5) {\n                                    history.pushState(null, null, url);\n                                } else {\n                                    location.hash = util.getPath(url);\n                                }\n\n                                // 添加到缓存\n                                if (config.cache) {\n                                    cache.set(url, {\n                                        title: title,\n                                        html: html\n                                    });\n                                }\n                            }\n                        }\n                    } else {\n                        pjax.trigger(\"ajaxError\", null, eventData);\n                        util.log(\"请求失败，错误码：\" + xhr.status);\n                    }\n\n                    core.fnb = true;\n                }\n            };\n            xhr.send();\n        }\n    };\n\n    var pjax = {\n        ready: false,\n        events: {},\n        /**\n         * 初始化\n         * @param {Object} options 配置\n         */\n        init: function (options) {\n            if (suppost === SUPPORT.PASS) {\n                util.log(\"不支持该版本的浏览器\");\n                return;\n            }\n\n            util.extend(config, options);\n\n            // 将config.container转换为dom\n            if (typeof config.container === \"string\") {\n                var selectorName = config.container;\n\n                config.container = document.querySelector(config.container);\n                if (config.container === null) {\n                    throw new Error(\"找不到Element：\" + selectorName);\n                }\n            }\n\n            // 监听配置里的事件\n            if (config.events) {\n                for (var key in config.events) {\n                    pjax.on(key, null, config.events[key]);\n                }\n            }\n\n            // 如果一打开就已经带有hash, 则立刻发请求\n            // 由于hash不会被传到服务器，此时页面多半是首页，如打开www.google.com/#/abcd，其实是打开了www.google.com\n            if (suppost === SUPPORT.HASH && location.hash.length > 2) {\n                // 先删了当前内容，防止用户误会\n                config.container.innerHTML = \"\";\n                pjax.ready = true;\n\n                core.fnd = false;\n                core.turn(location.href.replace(\"#/\", \"\"), null, function () {\n                    pjax.trigger(\"init\");\n                });\n            }\n\n            event.bindEvent();\n\n            if (!pjax.ready) {\n                pjax.ready = true;\n                pjax.trigger(\"init\");\n            }\n        },\n        // 注销插件，一般来说你并不需要使用这个方法\n        destroy: function () {\n            pjax.events = null;\n            event.unbindEvent();\n            util.clearCache();\n        },\n        /**\n         * 使用pjax跳转到指定页面\n         * @param {String}   url\n         * @param {Object}   data     要传到新页面的参数，可以为null\n         * @param {Function} callback 请求成功时的回调\n         */\n        turn: function (url, data, callback) {\n            url = util.getFullHref(url);\n            core.fnb = false;\n            core.turn(url, data, callback);\n        },\n        /**\n         * 监听事件\n         * @param {String}   type     事件类型\n         * @param {String}   url      指定监听该事件的页面，null表示所有页面都监听\n         * @param {Function} listener 回调\n         */\n        on: function (type, url, listener) {\n            // 只有两个参数，跳过中间的url\n            if (listener === undefined) {\n                listener = url;\n                url = null;\n            } else if (url) {\n                url = util.getFullHref(url);\n            }\n\n            pjax.events[type] = pjax.events[type] || [];\n            pjax.events[type].push({\n                listener: listener,\n                url: url\n            });\n        },\n        /**\n         * 解除监听\n         * @param {String} type 事件类型\n         * @param {String} url 解绑该事件的页面，null表示所有页面都解绑\n         */\n        off: function (type, url) {\n            if (url) {\n                var list = pjax.events[type];\n                url = util.getFullHref(url);\n\n                for (var i = 0; i < list.length; i++) {\n                    if (list[i].url === url) {\n                        list.splice(i, 1);\n                        i--;\n                    }\n                }\n\n                if (list.length) return;\n            }\n\n            delete pjax.events[type];\n        },\n        /**\n         * 触发事件\n         * @param {String} type 事件类型\n         * @param {Object} args 参数\n         */\n        trigger: function (type, args) {\n            var list = pjax.events[type];\n            if (list) {\n                for (var i = 0, length = list.length; i < length; i++) {\n                    list[i].listener.call(pjax, args);\n                }\n            }\n        }\n    };\n\n    if (typeof define === \"function\" && define.amd) {\n        define([], function () {\n            return pjax;\n        });\n    } else if (typeof module === \"object\" && typeof exports === \"object\") {\n        module.exports = pjax;\n    } else {\n        window.CoffcePJAX = pjax;\n    }\n\n})(window);"
  },
  {
    "path": "styles/style.css",
    "content": "* {\n    box-sizing: border-box;\n}\nhtml {\n    height: 100%;\n    background-color: #EFEFEF;\n}\nbody {\n    margin: 0;\n    height: 100%;\n    overflow: hidden;\n}\n#container {\n    position: relative;\n    height: 50%;\n    border-bottom: solid 1px #C1C1C1;\n}\n#container p {\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    margin: auto;\n    height: 40px;\n    width: 100%;\n    color: #555;\n    text-align: center;\n    font: 30px \"microsoft yahei\";\n    transition: all .3s;\n}\n#main {\n    padding: 40px;\n    height: 50%;\n    text-align: center;\n    border-top: solid 1px #FFF;\n}\n#main a {\n    margin: 0 20px;\n    color: #1075BF;\n    text-decoration: none;\n    font-family: \"microsoft yahei\";\n}"
  }
]