ADS-ajaxj详解(2)-检测hash变化解决ajax后退按钮和书签问题

// 后退按钮和书签功能
/*
通过一些DOM脚本并利用URL的hash值可以补救后退按钮和标签的问题。
其中,hash值表示浏览器应该保持在当前页面上,但同时必须通过重新定位页面使得与hash匹配的命名的锚(或带有相同ID属性的元素),在浏览器的视口(view port)中可见。如果文档中不存在与hash匹配的项,则浏览器只会改变地址栏中的URL。这里的hash可以连同URL一起被作为标签使用,而且通过一些DOM脚本及hack技术,hash还可以在用户单击后退按钮时用于更新页面。
 */
/**
 * 1.不那么简单的修复
 * 修复后退按钮和变迁涉及监视和识别URL中hash值的变化,并通过该变化来调用Ajax请求。要做到这一点,需要创建一个检测地址中hash值变化的对象,同时以预先定义的适当的方法进行响应。这个对象需要做以下几件事:
 *      (1)是永不唐突的DOM脚本增强文档以跟踪页面的变化。
 *      (2)允许注册根据不同hash值作为反映的不同方法。
 *      (3)监事地址栏的变化并调用注册过的适当方法。
 *      (4)在处理后退按钮和标签时,适应各种不同产品化浏览器的古怪行为。
 * 每种浏览器处理地址的方式都有点不一样:
 *      (1)在IE中,后退和前进按钮会忽略hash值,而只基于URL的其余部分进行导航。为了解决这个问题,需要使用一个隐藏的<iframe>并通过向GET字符串中添加hash来模拟导航到不同的网页。
 *      (2)在Safari中,当通过带hash的URL向前或向后导航时,浏览器的history及history.length会相应改变。但是,window.location.href的值会保持为通过后退和前进按钮导航之前,浏览器中打开的最后一个地址。考虑到安全原因,我们无法访问history中的URL,因此需要相对于Safari的history的长度保持跟踪访问过的hash值,并且能够从存储列表中找回适当的hash。
 *      (3)Firefox和Opera的行为则都更符合常理,他们会在使用后退和前进按钮通过hash来导航时,同步更新window对象的location值。
 *
 * 2.针对产品功能的浏览器嗅探
 * 在处理具体浏览器的产品功能时,浏览器嗅探是一种可以接受的方案,而且也是唯一可能的方案,因为产品的差异并不针对Javascript中的能力或对象。
 *
 * 3.跟踪地质变化
 */

/* 一个用来基于hash触发注册的方法的URL hash侦听器 */
    var actionPager = {
        // 前一个hash
        lastHash: '',
        // 为hash模式注册的方法列表
        callbacks: [],
        // Safari历史记录列表
        safariHistory: false,
        // 对为IE准备的iframe的引用
        msieHistory: false,
        // 应该被转换的链接的类名
        ajaxifyClassName: '',
        // 应用程序的根目录。当创建hash时
        // 它将是被清理后的URL
        ajaxifyRoot: '',

        /**
         * 初始化页面(pager)功能
         * @param {String} ajaxifyClass 预定义的类名,只有符合该类名的链接才具备功能,默认为 ADSActionLink
         * @param {String} ajaxifyRoot 预定义的根路径
         * @param {String} startingHash 预定义的hash值,默认为start
         * @example actionPager.init();
         */
        init: function (ajaxifyClass, ajaxifyRoot, startingHash) {

            this.ajaxifyClassName = ajaxifyClass || 'ADSActionLink';
            this.ajaxifyRoot = ajaxifyRoot || '';

            var ua = navigator.userAgent;
            if (/Safari/i.test(ua) && /Version\/3/.test(ua)) {
                this.safariHistory = [];
            } else if (/MSIE/i.test(ua) && (parseInt(ua.split('MSIE')[1]) < 8)) {
                // 如果是MSIE 6/7,添加一个iframe以便
                // 跟踪重写(override)后退按钮
                this.msieHistory = document.createElement('iframe');
                this.msieHistory.setAttribute('id', 'msieHistory');
                this.msieHistory.setAttribute('name', 'msieHistory');
                this.msieHistory.setAttribute('src', 'fakepage');
                setStyleById(this.msieHistory, {
                    'width': '100px',
                    'height': '100px',
                    'border': '1px solid black',
                    'display': 'none',
                    'z-index': -1
                });
                document.body.appendChild(this.msieHistory);
                this.msieHistory = frames.msieHistory;
            }

            // 将链接转换为Ajax链接
            this.ajaxifyLinks();

            // 取得当前的地址
            var locat = this.getLocation();

            // 检测地址中是否包含hash(来自书签)
            // 或者是否已经提供了hash
            if (!locat.hash && !startingHash) {
                startingHash = 'start';
            }

            // 按照需要保存hash
            var ajaxHash = this.getHashFromURL(locat.hash) || startingHash;
            this.addBackButtonHash(ajaxHash);

            // 添加监视事件以观察地址栏中的变化
            var watcherCallback = makeCallback(this.watchLocationForChange, this);

            if (/MSIE/i.test(ua) && (parseInt(ua.split('MSIE')[1]) < 8)) {
                window.setInterval(watcherCallback, 200);
            } else {
                addEvent(window,'hashchange',watcherCallback);
            }

        },

        /**
         * 会自动将任何带标识的锚标签转换为页面(pager)可以识别的链接
         */
        ajaxifyLinks: function () {
            // 将链接转换为锚以便Ajax进行处理
            var links = getElementsByClassName(this.ajaxifyClassName, document, 'a');
            for (var i = 0, len = links.length; i < len; i++) {
                var curLink = links[i];
                if (hasClassName(curLink, 'ADSActionPagerModified')) {
                    continue;
                }

                // 将href属性转换为#value形式
                curLink.setAttribute('href', this.convertURLToHash(curLink.getAttribute('href')));
                addClassName(curLink, 'ADSActionPagerModified');

                // 注册单击事件以便在必要时添加历史纪录
                addEvent(curLink, 'click', function () {
                    if (this.href && this.href.indexOf('#') > -1) {
                        actionPager.addBackButtonHash(actionPager.getHashFromURL(this.href));
                    }
                });
            }
        },

        /**
         * 记录后退行为
         * @param {String} ajaxHash hash值
         * @return {Boolean}
         */
        addBackButtonHash: function (ajaxHash) {
            // 保存hash
            if (!ajaxHash) {
                return false;
            }
            if (this.safariHistory) {
                // 为Safari使用特殊数组
                if (this.safariHistory.length === 0) {
                    this.safariHistory[window.history.length] = ajaxHash;
                } else {
                    this.safariHistory[window.history.length + 1] = ajaxHash;
                }
                return true;
            } else if (this.msieHistory) {
                // 在MSIE中通过导航iframe
                this.msieHistory.document.execCommand('Stop');
                this.msieHistory.location.href = 'fakepage?hash=' +
                    ajaxHash +
                    '&title=' +
                    document.title;
                return true;
            } else {
                // 通过改变地址的值
                // 使用makeCallback包装函数
                // 以便在超时方法内部使this
                // 引用actionPager
                var timeoutCallback = makeCallback(function () {
                    if (this.getHashFromURL(window.location.href + window.location.hash) !== ajaxHash) {
                        location.replace(location.href + '#' + ajaxHash);
                    }
                }, this);
                setTimeout(timeoutCallback, 200);
                return true;
            }
            return false;
        },

        /**
         * 间隔地检测hash的变化,还可执行注册的侦听器
         */
        watchLocationForChange: function () {
            var newHash;
            // 取得新的hash值
            if (this.safariHistory) {
                // 在Safari中从历史记录数组中取得
                if (this.safariHistory[history.length]) {
                    newHash = this.safariHistory[history.length];
                }
            } else if (this.msieHistory) {
                // 在MSIE中从iframe的地址中取得
                newHash = this.msieHistory.location.href.split('&')[0].split('=')[1];
            } else if (location.hash !== '') {
                // 对其他浏览器从window.location中取得
                newHash = this.getHashFromURL(window.location.href);
            }

            // 如果新hash与最后一次的hash不相同,则更新页面
            if (newHash && this.lastHash !== newHash) {
                if (this.msieHistory && this.getHashFromURL(window.location.href) !== newHash) {
                    // 修复MSIE中的地址栏
                    // 以便能适当地加上标签(或加入收藏夹)
                    location.hash = newHash;
                }

                // 在发生异常的情况下使用try/catch
                // 结构尝试执行任何注册的侦听器
                try {
                    this.executeListeners(newHash);
                    // 在通过处理程序添加任何
                    // 新链接的情况下进行更新
                    this.ajaxifyLinks();
                } catch (ex) {
                    // 这里将捕获到回调函数中的任何异常JS
                    alert(ex);
                }

                // 将其保存为最后一个hash
                this.lastHash = newHash;
            }
        },

        /**
         * 用于根据特殊的hash来注册侦听器
         * @param {RegExp || String} regex 正则表达式
         * @param {Function} method 执行的方法
         * @param {Object || Element} context 执行环境,上下文
         */
        register: function (regex, method, context) {
            var obj = {'regex': regex};
            context = context || window;
            // 一个已经指定的环境
            obj.callback = function (matches) {
                method.apply(context, matches);
            };

            // 将侦听器添加到回调函数数则中
            this.callbacks.push(obj);
        },

        /**
         * 把链接的URL地址转换为hash值
         * @param {String} url
         * @return {*}
         */
        convertURLToHash: function (url) {
            if (!url) {
                // 没有url,因而返回一个'#'
                return '#';
            } else if (url.indexOf('#') > -1) {
                // 存在hash,因而返回它
                return url.split('#')[1];
            } else {
                // ie67会自动添加域名
                // 如果URL中包含域名(MSIE)则去掉它
                if (url.indexOf('://') > -1) {
                    var locatH = window.location.href;
                    locatH = locatH.substring(0, locatH.lastIndexOf('/'));
                    var s = '';
                    var len = Math.min(locatH.length, url.length);
                    for (var i = 0; i < len; i++) {
                        if (locatH.charAt(i) !== url.charAt(i)) {
                            break;
                        } else {
                            s += url.charAt(i);
                        }
                    }

                    var reg = new RegExp(s + '/');
                    url = url.replace(reg, '');
                    //url = url.match(/:\/\/[^\/]+(.*)/)[1];
                }
                // 按照init()约定去掉根目录
                return '#' + url.substring(this.ajaxifyRoot.length);
            }
        },

        /**
         * 从指定的URL中提取出hash值
         * @param url
         * @return {*}
         */
        getHashFromURL: function (url) {
            if (!url || url.indexOf('#') === -1) {
                return '';
            }
            return url.split('#')[1];
        },

        /**
         * 获取当前URL地址
         * @return {*}
         */
        getLocation: function () {
            // 检查hash
            if (!window.location.hash) {
                // 没有则生成一个
                var url = {
                    domain: null,
                    hash: null
                };
                if (window.location.href.indexOf('#') > -1) {
                    var parts = window.location.href.split('#')[1];
                    url.domain = parts[0];
                    url.hash = parts[1];
                } else {
                    url.domain = window.location;
                }
                return url;
            }
            return window.location;
        },

        /**
         * 执行侦听器
         * @param hash
         */
        executeListeners: function (hash) {
            var matches;
            // 执行与hash匹配的任何侦听器
            if (this.callbacks.length) {
                for (var i in this.callbacks) {
                    if ((matches = hash.match(this.callbacks[i].regex))) {
                        this.callbacks[i].callback(matches);
                    }
                }
            }

        }
    };

    window.ADS.actionPager = actionPager;

 

posted @ 2013-02-01 10:19  LukeLin  阅读(452)  评论(0编辑  收藏  举报