您的位置:首页 > 其它

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

2013-02-01 10:19 405 查看
// 后退按钮和书签功能
/*
通过一些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;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐