抽几天时间看了一下 Prototype.js 的源代码,有些地方还没有彻底理解,但总算是扒完了。
本来就不会用Prototype框架,所以对某些方法理解也是半知半解,带<?>的我都不敢确定。
注释结合了一些实例,比较容易理解,有些地方上下结合的看起来有些费力,这里多亏webStorm 编辑器的ctrl+shift+i 的调出相关方法的功能
非常好用,看完也算对Prototype有一些初步理解,对里面的枚举对象印象深刻,ajax部分有些绕还需多读几遍,
总体还是迷迷糊糊,请大家有空挑挑错!
/* Prototype JavaScript framework, version 1.4.0 * (c) 2005 Sam Stephenson <sam@conio.net> * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://prototype.conio.net/ * /*--------------------------------------------------------------------------*/ /** Time 2012.05.07 09:13:39 Time by VVG **/ /**结合 http://www.prototypejs.org/api/hash#method-merge 中的实例***/ /** ..<?> 标记为暂不确定 **/ var Prototype = { Version:'1.4.0', //版本号 ScriptFragment:'(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', //过滤<script></script>标签,?:不捕获组 ,*?懒惰模式 emptyFunction:function () { }, //空函数 K:function (x) { return x } //返回参数本身.. } // Class类的使用方法: /* * var Animal = Class.create(); Animal.prototype = { initialize: function(name, sound) { this.name = name; this.sound = sound; }, speak: function() { alert(name + " says: " + sound + "!"); } }; var snake = new Animal("Ringneck", "hissssssssss"); snake.speak(); // -> alerts "Ringneck says: hissssssssss!" var Dog = Class.create(); Dog.prototype = Object.extend(new Animal(), { initialize: function(name) { this.name = name; this.sound = "woof"; } }); var fido = new Dog("Fido"); fido.speak(); // -> alerts "Fido says: woof!" */ var Class = { // create方法,执行对象的初始化initialize方法,存在其原型中,请结合以上的实例理解 // 如:var Person = Class.create(); // Person.prototype.inintialize = function(){ this.name = name } // var person1 = new Person(name); create:function () { return function () { this.initialize.apply(this, arguments); //在THIS作用域执行初始化方法,并传递参数 } } } var Abstract = new Object(); //定义抽象对象,后面会用到 //Object 扩展方法,复制源对象source的属性或者方法到目标对象destination,有相同的会覆盖原来的 Object.extend = function (destination, source) { for (property in source) { destination[property] = source[property]; } return destination; } //检查对象的类型,注意传递的参数object与前面的Object不一样 //Prototype 框架 后面的相关对像一般都设置insepect方法,作用显示详细样式 //inspect 返回可识别的形式,比toString详细 Object.inspect = function (object) { try { if (object == undefined) return 'undefined'; if (object == null) return 'null'; //如果传入的参数对象含有inspect方法,就使用inspect方法,没有就使用本身的toString方法 return object.inspect ? object.inspect() : object.toString(); } catch (e) { //抛出错误 if (e instanceof RangeError) return '...'; throw e; } } //$A 函数把具有length属性的伪数组的转化为真正的数组形式,比如arguments nodelist //bind 的意思是绑定某个方法到指定的作用域 Function.prototype.bind = function () { //每个函数都是 Function 的实例,所以这里的this是调用bind的函数 var __method = this, args = $A(arguments), object = args.shift();//shift 移除数组的第一项并返回第一项 return function () { return __method.apply(object, args.concat($A(arguments))); } } //绑定一个事件处理函数到一个对象,并传递一个事件参数 Function.prototype.bindAsEventListener = function (object) { var __method = this; return function (event) { return __method.call(object, event || window.event); } } //扩展Number对象的原型 //相当于Number.prototype.toColorPart = function(){}; Object.extend(Number.prototype, { //把数字转换为16进制格式 toColorPart:function () { var digits = this.toString(16); if (this < 16) return '0' + digits; return digits; }, //返回数字加1,计数功能 succ:function () { return this + 1; }, // 执行指定次数 (10).times(fun); 执行fun函数10次 // 用到$R(0,10,true) 和 for(var i=0;i<=10;i++) 差不多,最后一个参数为false的话相当于for(var i=0;i<10;i++) // each方法,为迭代传递每个值到iterator函数里面执行 times:function (iterator) { $R(0, this, true).each(iterator); return this; } }); var Try = { these:function () { var returnValue; //循环参数,这里的参数为函数形式 for (var i = 0; i < arguments.length; i++) { var lambda = arguments[i]; try { //执行函数,成功即跳出循环 returnValue = lambda(); break; } catch (e) { } } return returnValue; } } /*--------------------------------------------------------------------------*/ //PeriodicalExecuter 字面意思:周期执行 var PeriodicalExecuter = Class.create(); PeriodicalExecuter.prototype = { //初始化 initialize:function (callback, frequency) { this.callback = callback; this.frequency = frequency; this.currentlyExecuting = false; //调用方法 this.registerCallback(); }, registerCallback:function () { //在this作用域周期执行onTimerEvent事件 setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, onTimerEvent:function () { if (!this.currentlyExecuting) { try { //currentlyExecuting 控制避免并发调用 this.currentlyExecuting = true; this.callback(); } finally { this.currentlyExecuting = false; } } } } /*--------------------------------------------------------------------------*/ //模拟选择器,可传入多个ID,或者NODE对象,返回数组 function $() { var elements = new Array(); for (var i = 0; i < arguments.length; i++) { var element = arguments[i]; if (typeof element == 'string') element = document.getElementById(element); if (arguments.length == 1) return element; elements.push(element); } return elements; } //扩展String的对象原型 Object.extend(String.prototype, { //替换HTML标签如<div>...</div>,返回纯内容 stripTags:function () { return this.replace(/<\/?[^>]+>/gi, ''); }, //替换<script>,Prototype.ScriptFragment 在最开头定义 ‘img’ 忽略大小写,忽略换行,全局搜索 stripScripts:function () { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); }, extractScripts:function () { //匹配全部的<script>对 var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); //匹配第一个<script>对 var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); //返回每个<script>对的内容.. return (this.match(matchAll) || []).map(function (scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); }, //执行提取的脚本 evalScripts:function () { return this.extractScripts().map(eval); }, //HTML编码 escapeHTML:function () { var div = document.createElement('div'); var text = document.createTextNode(this); div.appendChild(text); return div.innerHTML; }, //HTML解码 unescapeHTML:function () { var div = document.createElement('div'); div.innerHTML = this.stripTags(); return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; }, //转换成URL参数转换为对象形式{name:value,name:value},inject后面会提到 toQueryParams:function () { // /^\??(.*)$/ 含义:\? 匹配问号,后面的? 表示有或者没有 ,以任意字符结尾,捕获到组1 var pairs = this.match(/^\??(.*)$/)[1].split('&'); return pairs.inject({}, function (params, pairString) { var pair = pairString.split('='); params[pair[0]] = pair[1]; return params; }); }, //字符串转换为数组 toArray:function () { return this.split(''); }, //font-size 转换为 驼峰式命名 fontSize camelize:function () { var oStringList = this.split('-'); if (oStringList.length == 1) return oStringList[0]; var camelizedString = this.indexOf('-') == 0 ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) : oStringList[0]; for (var i = 1, len = oStringList.length; i < len; i++) { var s = oStringList[i]; camelizedString += s.charAt(0).toUpperCase() + s.substring(1); } return camelizedString; }, //字符串的观察模式,\n 这种直接输出不转义 inspect:function () { return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'"; } }); String.prototype.parseQuery = String.prototype.toQueryParams; // 定义两个具有表达含义的新建对象,用来控制是否跳出迭代 var $break = new Object(); var $continue = new Object(); /** Time 2012.05.08 10:00:00 Time by VVG**/ //Enumerable 字面意思:枚举 var Enumerable = { //each 方法需依赖 this的私有_each()方法,this可以是数组,HASH对象,等等,后面会讲到; //iterator 参数为处理迭代值的函数 //比如 数组Array的私有方法_each() 方法如下: /*** _each:function (iterator) { *for (var i = 0; i < this.length; i++) *iterator(this[i]); *}, ***/ //联系起来比较容易理解 each:function (iterator) { //index记录下标 var index = 0; try { this._each(function (value) { try { iterator(value, index++); } catch (e) { //如果抛出的错误为$continue,则跳出本次迭代,执行下一次 if (e != $continue) throw e; } }); } catch (e) { //如果抛出的错误为$break,则跳整个迭代 if (e != $break) throw e; } }, //iterator迭代后的所有值为true时,返回true,与&&操作符相当,全部真则为真,一个假则为假 all:function (iterator) { var result = true; this.each(function (value, index) { //&&,表示result为true的时候才执行后面的,!!强制转换迭代的返回值为布尔值 //只要后面迭代值有一个是false,及result就为false,而下面的&&就会阻止后面的迭代 result = result && !!(iterator || Prototype.K)(value, index); //如果有一个为假则抛出$break if (!result) throw $break; }); return result; }, //与all 相反,只要有一个迭代出true则返回true ,与||操作符相当,一个真即为真,全部假才为假 any:function (iterator) { var result = true; this.each(function (value, index) { //如果一个为真,则跳出迭代,返回真 if (result = !!(iterator || Prototype.K)(value, index)) throw $break; }); return result; }, //返回经过迭代后的枚举值的数组形式 collect:function (iterator) { var results = []; this.each(function (value, index) { results.push(iterator(value, index)); }); return results; }, //返回迭代值第一个为真的枚举value值 detect:function (iterator) { var result; this.each(function (value, index) { if (iterator(value, index)) { result = value; throw $break; } }); return result; }, //返回迭代值为真的枚举value值的数组 findAll:function (iterator) { var results = []; this.each(function (value, index) { if (iterator(value, index)) results.push(value); }); return results; }, //返回符合正则表达式验证为true的枚举value项的迭代值数组 grep:function (pattern, iterator) { var results = []; this.each(function (value, index) { var stringValue = value.toString(); if (stringValue.match(pattern)) results.push((iterator || Prototype.K)(value, index)); }) return results; }, //如果枚举value组包含object项,就返回true,不包含返回false include:function (object) { var found = false; this.each(function (value) { if (value == object) { found = true; throw $break; } }); return found; }, // 注入一个memo参数,作为iterator函数的第一个参数 // 返回计算过后的memo值 ... inject:function (memo, iterator) { this.each(function (value, index) { memo = iterator(memo, value, index); }); return memo; }, // invoke 字面意思为调用 // 调用每个枚举值的method方法,返回值的数组 // ['hello', 'world', 'cool!'].invoke('toUpperCase') // ['HELLO', 'WORLD', 'COOL!'] // ['hello', 'world', 'cool!'].invoke('substring', 0, 3) // ['hel', 'wor', 'coo'] invoke:function (method) { //去除第一个参数method的其它参数 var args = $A(arguments).slice(1); return this.collect(function (value) { return value[method].apply(value, args); }); }, //返回枚举值中经过迭代以后最大的值 max:function (iterator) { var result; this.each(function (value, index) { value = (iterator || Prototype.K)(value, index); if (value >= (result || value)) //取最大 result = value; }); return result; }, //返回枚举值中经过迭代以后最小的值 min:function (iterator) { var result; this.each(function (value, index) { value = (iterator || Prototype.K)(value, index); //取最小 if (value <= (result || value)) result = value; }); return result; }, //返回经过迭代函数迭代返回值的trues falses枚举值数组 partition:function (iterator) { var trues = [], falses = []; this.each(function (value, index) { //注意三元运算符使用方法 ((iterator || Prototype.K)(value, index) ? trues : falses).push(value); }); return [trues, falses]; }, //返回每个枚举值的property属性值数组 //比如: // ['hello', 'world', 'this', 'is', 'nice'].pluck('length'); // -> [5, 5, 4, 2, 4] pluck:function (property) { var results = []; this.each(function (value, index) { results.push(value[property]); }); return results; }, //返回迭代值执行为假的枚举值数组 reject:function (iterator) { var results = []; this.each(function (value, index) { if (!iterator(value, index)) results.push(value); }); return results; }, // 按照指定的方法排序 // ['hello', 'world', 'this', 'is', 'nice'].sortBy(function(s) { return s.length; }) 按照字符串的length属性排序 // -> 'is', 'this', 'nice', 'hello', 'world'] // 先collect生成一个对象数组,对象中包含两个属性:枚举值value和criteria迭代值 // 再对生成的数组采用数组的原生方法sort,相对于迭代值criteria排序,返回排序数组 // 再使用pluck方法得到枚举值的一个数组 sortBy:function (iterator) { return this.collect( function (value, index) { return {value:value, criteria:iterator(value, index)}; }).sort( function (left, right) { var a = left.criteria, b = right.criteria; //相当于 (a < b ? -1 : (a > b ? 1 : 0)) return a < b ? -1 : a > b ? 1 : 0; }).pluck('value'); }, // 转为数组 // $R(1, 5).toArray() // -> [1, 2, 3, 4, 5] toArray:function () { return this.collect(Prototype.K); }, // 例子 ... // var firstNames = ['Justin', 'Mislav', 'Tobie', 'Christophe']; // var lastNames = ['Palmer', 'Marohnić', 'Langel', 'Porteneuve']; // firstNames.zip(lastNames) // -> [['Justin', 'Palmer'], ['Mislav', 'Marohnić'], ['Tobie', 'Langel'], ['Christophe', 'Porteneuve']] zip:function () { var iterator = Prototype.K, args = $A(arguments); if (typeof args.last() == 'function') iterator = args.pop(); var collections = [this].concat(args).map($A); return this.map(function (value, index) { iterator(value = collections.pluck(index)); return value; }); }, //可识别toString() inspect:function () { return '#<Enumerable:' + this.toArray().inspect() + '>'; } } //为Enumerable的方法 扩展几个快速链接 Object.extend(Enumerable, { map:Enumerable.collect, find:Enumerable.detect, select:Enumerable.findAll, member:Enumerable.include, entries:Enumerable.toArray }); // $A 把伪数组转换为数组的方法 var $A = Array.from = function (iterable) { //如果 没有参数,则返回空数组 if (!iterable) return []; // 如果参数有toArray方法,就执行toArray()方法 if (iterable.toArray) { return iterable.toArray(); } else { // 否则就循环,push到数组 var results = []; for (var i = 0; i < iterable.length; i++) results.push(iterable[i]); return results; } } // 把枚举对象的所有方法添加到Array的原型对象 // 因此每个新数组都拥有枚举对象的方法 Object.extend(Array.prototype, Enumerable); // Array 增加一个_reverse 反转方法,标记私有方法... Array.prototype._reverse = Array.prototype.reverse; // 扩展Array原型 Object.extend(Array.prototype, { // _each 方法,枚举数组的所有元素传递给iterator函数做参数,并执行 _each:function (iterator) { for (var i = 0; i < this.length; i++) iterator(this[i]); }, // 清除数组的所有元素,即设置数组的长度为0 clear:function () { this.length = 0; return this; }, // 获取第一个元素 first:function () { return this[0]; }, // 获取最后一个元素 last:function () { return this[this.length - 1]; }, // 返回value值不为undefined 或者 null 的数组 // 这里的select是从Enumerable中继承的方法,而select又是findAll函数的别名 // findAll 返回所有迭代器值为真的 枚举 compact:function () { return this.select(function (value) { return value != undefined || value != null; }); }, // 把复杂的数组压缩为扁平的,如下: // ['frank', ['bob', 'lisa'], ['jill', ['tom', 'sally']]].flatten() // -> ['frank', 'bob', 'lisa', 'jill', 'tom', 'sally'] // 传递的[]空数组即为array参数,如果value是数组,则调用flatten递归展开 flatten:function () { return this.inject([], function (array, value) { return array.concat(value.constructor == Array ? value.flatten() : [value]); }); }, // 排除参数中含有的元素,返回排除后的数组 without:function () { var values = $A(arguments); return this.select(function (value) { // select 只取返回值为true 的value // values 中不含有 value 则返回 true return !values.include(value); }); }, // 同string的indexOf方法,查找object在数组出现的位置 // 用each来实现应该像下面这个样子吧 /* * indexOf:function(object){ * this.each(function(value,index){ * if(value == object) return index; * } * return -1; * )} * */ indexOf:function (object) { for (var i = 0; i < this.length; i++) if (this[i] == object) return i; return -1; }, // 数组反转参数为false 则不影响原数组,不带参数则原数组反转实例如下: /*var nums = [3, 5, 6, 1, 20]; nums.reverse(false) // -> [20, 1, 6, 5, 3] nums // -> [3, 5, 6, 1, 20] nums.reverse() // -> [20, 1, 6, 5, 3] nums // -> [20, 1, 6, 5, 3]*/ reverse:function (inline) { return (inline !== false ? this : this.toArray())._reverse(); }, // 移除数组的第一个元素,返回移除的元素 // 影响原数组,原数组长度减一 shift:function () { var result = this[0]; for (var i = 0; i < this.length - 1; i++) this[i] = this[i + 1]; this.length--; return result; }, // 数组的toString详细版 inspect:function () { return '[' + this.map(Object.inspect).join(', ') + ']'; } }); // Hash对象是指一个数组对象,每个数组包含两个元素,第一个值为key,第二个值为value,每个数组又包含两个属性 // key 和 value,分别对应各自的值 // 定义Hash对象的方法 var Hash = { // Hash对象的枚举方法 // 枚举对象的每个属性和对应的值到一个数组,并给此数组(也是一个对象)增加一个属性和属性值的属性。。。。绕口啊 // 传递这个数组对象给迭代函数,并执行迭代函数 _each:function (iterator) { for (key in this) { var value = this[key]; if (typeof value == 'function') continue; var pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } }, // 返回Hash对象的键Key 数组 // pluck 作用又忘了?,调用枚举值的属性,代码如下: /* pluck:function (property) { var results = []; this.each(function (value, index) { results.push(value[property]); }); return results; }*/ keys:function () { return this.pluck('key'); }, // 返回Hash对象的值value 数组 values:function () { return this.pluck('value'); }, /** Time 2012.05.09 Time by VVG**/ // merge example // var h = $H({ name: 'Prototype', version: 1.5 }); // h.merge({ version: 1.6, author: 'Sam' }).inspect(); // -> #<Hash:{'name': 'Prototype', 'version': 1.6, 'author': 'Sam'}> // h.inspect(); // -> #<Hash:{'name': 'Prototype', 'version': 1.5}> // 合并两个Hash对象,返回一个新的Hash对象,不影响原对象 // this ---> mergedHash merge:function (hash) { return $H(hash).inject($H(this), function (mergedHash, pair) { mergedHash[pair.key] = pair.value; return mergedHash; }); }, // 转换为参数形式,如:value=124&key=number toQueryString:function () { // 传递Hash的每项到hash的map函数处理,枚举项为数组 return this.map( function (pair) { //传递数组中的每项值到数组的map方法利用encodeURIComponent转码后返回数组,然后在用=号链接数组(即名值对) return pair.map(encodeURIComponent).join('='); }).join('&'); //最后用&符号链接 }, // Hash对象的toString() 详细显示 inspect:function () { return '#<Hash:{' + this.map( function (pair) { return pair.map(Object.inspect).join(': '); }).join(', ') + '}>'; } } // $H 把普通对象转化为Hash对象,复制枚举的方法,和Hash对象的方法...vvg function $H(object) { var hash = Object.extend({}, object || {}); Object.extend(hash, Enumerable); Object.extend(hash, Hash); return hash; } // 创建一个ObjectRange类; ObjectRange = Class.create(); // 复制枚举的方法 Object.extend(ObjectRange.prototype, Enumerable); Object.extend(ObjectRange.prototype, { // 初始化类,传递相应的值参数 initialize:function (start, end, exclusive) { this.start = start; this.end = end; this.exclusive = exclusive; }, // 迭代 _each:function (iterator) { var value = this.start; do { iterator(value); value = value.succ(); } while (this.include(value)); }, // 判断条件 include:function (value) { if (value < this.start) return false; if (this.exclusive) //当exclusive 为true时,不判断等于this.end return value < this.end; return value <= this.end; } }); // 简洁名称 var $R = function (start, end, exclusive) { return new ObjectRange(start, end, exclusive); } // Ajax 对象,命名空间 var Ajax = { // getTransport方法,返回XHR对象实例 getTransport:function () { return Try.these( function () { return new ActiveXObject('Msxml2.XMLHTTP') }, function () { return new ActiveXObject('Microsoft.XMLHTTP') }, function () { return new XMLHttpRequest() } ) || false; }, // activeRequestCount ...<?> 激活次数? activeRequestCount:0 } // Ajax下的私有对象 Ajax.Responders = { responders:[], // 定义数组 // Ajax.Responders对象的迭代方法,就是迭代内部的responders数组 _each:function (iterator) { this.responders._each(iterator); }, // 注册一个对象,推入responders数组...<?> register:function (responderToAdd) { if (!this.include(responderToAdd)) this.responders.push(responderToAdd); }, // 排除一个对像...<?> unregister:function (responderToRemove) { this.responders = this.responders.without(responderToRemove); }, // 给数组中的对象的回调方法传递参数并执行...<?> dispatch:function (callback, request, transport, json) { this.each(function (responder) { if (responder[callback] && typeof responder[callback] == 'function') { try { responder[callback].apply(responder, [request, transport, json]); } catch (e) { } } }); } }; // 扩展Ajax.Responders对象,复制枚举方法 Object.extend(Ajax.Responders, Enumerable); // 注册一个对象,即往this.responders 数组推入一个对象,这个对象含有两个方法onCreate,onComplete ...<?> // 激活次数加+1 , 减-1 Ajax.Responders.register({ onCreate:function () { Ajax.activeRequestCount++; }, onComplete:function () { Ajax.activeRequestCount--; } }); // Ajax 基础库相关 Ajax.Base = function () { }; Ajax.Base.prototype = { // 配置相关默认属性,如果参数有配置,则用参数的属性覆盖默认属性 setOptions:function (options) { this.options = { method:'post', asynchronous:true, parameters:'' } Object.extend(this.options, options || {}); }, // 判断请求状态是否成功 // this.transport = Ajax.getTransport(); responseIsSuccess:function () { return this.transport.status == undefined || this.transport.status == 0 || (this.transport.status >= 200 && this.transport.status < 300); }, // 判断请求是否失败 responseIsFailure:function () { return !this.responseIsSuccess(); } } // 创建Rquest类 Ajax.Request = Class.create(); // 建立Events数组,识别对象发送各阶段的状态... Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; // Request请求的原型 就是 Ajax.Base 的实例,然后添加了初始化方法等 Ajax.Request.prototype = Object.extend(new Ajax.Base(), { // 创建一个new Ajax.Request(url,options)实例,就相当于运行以下的initialize函数 initialize:function (url, options) { this.transport = Ajax.getTransport(); //获取XHR对象 this.setOptions(options); //Ajax.Base里面的setOptions方法,配置一些参数 this.request(url); //给request方法传递url ,request方法在下面↓ }, // 只接受参数URL request:function (url) { // 如果options对象配置了parameters就使用配置的,要么就为空 var parameters = this.options.parameters || ''; // &_= ...<?> if (parameters.length > 0) parameters += '&_='; try { this.url = url; // 如果为get 方法 和 参数大于0 if (this.options.method == 'get' && parameters.length > 0) this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; //Ajax.activeRequestCount++ .......<?> Ajax.Responders.dispatch('onCreate', this, this.transport); this.transport.open(this.options.method, this.url, this.options.asynchronous); if (this.options.asynchronous) { this.transport.onreadystatechange = this.onStateChange.bind(this); //Loading setTimeout((function () { this.respondToReadyState(1) }).bind(this), 10); } this.setRequestHeaders(); // 判断传递方法为Post时,传递的URL var body = this.options.postBody ? this.options.postBody : parameters; this.transport.send(this.options.method == 'post' ? body : null); } catch (e) { this.dispatchException(e); } }, // 自定义头部信息 setRequestHeaders:function () { var requestHeaders = ['X-Requested-With', 'XMLHttpRequest', 'X-Prototype-Version', Prototype.Version]; if (this.options.method == 'post') { // 设置header信息,模拟Form表单 requestHeaders.push('Content-type', 'application/x-www-form-urlencoded'); /* Force "Connection: close" for Mozilla browsers to work around * a bug where XMLHttpReqeuest sends an incorrect Content-length * header. See Mozilla Bugzilla #246651. */ if (this.transport.overrideMimeType) requestHeaders.push('Connection', 'close'); } if (this.options.requestHeaders) requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); // 这个用法不错.. for (var i = 0; i < requestHeaders.length; i += 2) this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i + 1]); }, // 状态改变 onStateChange:function () { var readyState = this.transport.readyState; if (readyState != 1) this.respondToReadyState(this.transport.readyState); }, // 读取头部信息 header:function (name) { try { return this.transport.getResponseHeader(name); } catch (e) { } }, // 执行JSON evalJSON:function () { try { return eval(this.header('X-JSON')); } catch (e) { } }, evalResponse:function () { try { return eval(this.transport.responseText); } catch (e) { this.dispatchException(e); } }, // 检查状态,执行方法 respondToReadyState:function (readyState) { var event = Ajax.Request.Events[readyState]; var transport = this.transport, json = this.evalJSON(); if (event == 'Complete') { try { (this.options['on' + this.transport.status] || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] || Prototype.emptyFunction)(transport, json); } catch (e) { this.dispatchException(e); } if ((this.header('Content-type') || '').match(/^text\/javascript/i)) this.evalResponse(); } try { (this.options['on' + event] || Prototype.emptyFunction)(transport, json); Ajax.Responders.dispatch('on' + event, this, transport, json); } catch (e) { this.dispatchException(e); } /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ if (event == 'Complete') this.transport.onreadystatechange = Prototype.emptyFunction; }, dispatchException:function (exception) { (this.options.onException || Prototype.emptyFunction)(this, exception); Ajax.Responders.dispatch('onException', this, exception); } }); // 直接更新的类 Ajax.Updater = Class.create(); Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { initialize:function (container, url, options) { this.containers = { success:container.success ? $(container.success) : $(container), failure:container.failure ? $(container.failure) : (container.success ? null : $(container)) } this.transport = Ajax.getTransport(); this.setOptions(options); var onComplete = this.options.onComplete || Prototype.emptyFunction; this.options.onComplete = (function (transport, object) { this.updateContent(); onComplete(transport, object); }).bind(this); this.request(url); }, //更新内容 updateContent:function () { var receiver = this.responseIsSuccess() ? this.containers.success : this.containers.failure; var response = this.transport.responseText; if (!this.options.evalScripts) response = response.stripScripts(); if (receiver) { if (this.options.insertion) { new this.options.insertion(receiver, response); } else { Element.update(receiver, response); } } if (this.responseIsSuccess()) { if (this.onComplete) setTimeout(this.onComplete.bind(this), 10); } } }); Ajax.PeriodicalUpdater = Class.create(); Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { initialize:function (container, url, options) { this.setOptions(options); this.onComplete = this.options.onComplete; this.frequency = (this.options.frequency || 2); this.decay = (this.options.decay || 1); this.updater = {}; this.container = container; this.url = url; this.start(); }, start:function () { this.options.onComplete = this.updateComplete.bind(this); this.onTimerEvent(); }, stop:function () { this.updater.onComplete = undefined; clearTimeout(this.timer); (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, updateComplete:function (request) { if (this.options.decay) { this.decay = (request.responseText == this.lastText ? this.decay * this.options.decay : 1); this.lastText = request.responseText; } this.timer = setTimeout(this.onTimerEvent.bind(this), this.decay * this.frequency * 1000); }, onTimerEvent:function () { this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); // 为document对象设置getElementsByClassName方法 document.getElementsByClassName = function (className, parentElement) { var children = ($(parentElement) || document.body).getElementsByTagName('*'); return $A(children).inject([], function (elements, child) { if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) elements.push(child); return elements; }); } /*--------------------------------------------------------------------------*/ if (!window.Element) { var Element = new Object(); } //扩展Element对象 Object.extend(Element, { // 是否可视,返true 或者 false visible:function (element) { return $(element).style.display != 'none'; }, // 显示和隐藏之间交替 toggle:function () { for (var i = 0; i < arguments.length; i++) { var element = $(arguments[i]); Element[Element.visible(element) ? 'hide' : 'show'](element); } }, // 隐藏对象,可接收多个id或者element hide:function () { for (var i = 0; i < arguments.length; i++) { var element = $(arguments[i]); element.style.display = 'none'; } }, // 显示对象 show:function () { for (var i = 0; i < arguments.length; i++) { var element = $(arguments[i]); element.style.display = ''; } }, // 移除对象 remove:function (element) { element = $(element); element.parentNode.removeChild(element); }, // 更新内容 update:function (element, html) { // 替换html里面的脚本↓ $(element).innerHTML = html.stripScripts(); // 执行脚本 setTimeout(function () { html.evalScripts() }, 10); }, // 获取对象的高度 getHeight:function (element) { element = $(element); return element.offsetHeight; }, // 返回一个ClassNames对象实例,拥有Element.ClassNames里的所有方法 /* * div id="mutsu" class="apple fruit food"></div> * $('mutsu').classNames().inspect() * // -> "#<Enumerable:['apple', 'fruit', 'food']>" * * */ // .............究竟是返回对象还是返回className数组.................<?> classNames:function (element) { return new Element.ClassNames(element); }, // 元素是否含有class hasClassName:function (element, className) { // 页面不含此元素时返回 if (!(element = $(element))) return; return Element.classNames(element).include(className); }, // 增加一个class addClassName:function (element, className) { if (!(element = $(element))) return; return Element.classNames(element).add(className); }, // 移除class removeClassName:function (element, className) { if (!(element = $(element))) return; return Element.classNames(element).remove(className); }, // 移除element下的空白节点 // removes whitespace-only text node children cleanWhitespace:function (element) { element = $(element); for (var i = 0; i < element.childNodes.length; i++) { var node = element.childNodes[i]; if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) Element.remove(node); } }, //节点是否为空 empty:function (element) { return $(element).innerHTML.match(/^\s*$/); }, // 滚动到element的位置 scrollTo:function (element) { element = $(element); var x = element.x ? element.x : element.offsetLeft, y = element.y ? element.y : element.offsetTop; window.scrollTo(x, y); }, // 获取元素某个的样式 getStyle:function (element, style) { element = $(element); //value 获取直接样式值 var value = element.style[style.camelize()]; if (!value) { // 如果没有直接样式就读取计算样式,IE中为currentStyle,firefox中为defaultView if (document.defaultView && document.defaultView.getComputedStyle) { // 获得元素所有的计算样式 var css = document.defaultView.getComputedStyle(element, null); // 获取具体的样式值style value = css ? css.getPropertyValue(style) : null; } else if (element.currentStyle) { // IE value = element.currentStyle[style.camelize()]; } } // opera if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) if (Element.getStyle(element, 'position') == 'static') value = 'auto'; return value == 'auto' ? null : value; }, // 设置样式 setStyle:function (element, style) { element = $(element); for (name in style) element.style[name.camelize()] = style[name]; }, // 获取元素高宽大小,无论是否可见 getDimensions:function (element) { element = $(element); // 如果元素显示,则好办 if (Element.getStyle(element, 'display') != 'none') return {width:element.offsetWidth, height:element.offsetHeight}; // All *Width and *Height properties give 0 on elements with display none, // so enable the element temporarily // 临时显示后获取高度 var els = element.style; // 记录原始属性 var originalVisibility = els.visibility; var originalPosition = els.position; els.visibility = 'hidden'; els.position = 'absolute'; els.display = ''; var originalWidth = element.clientWidth; var originalHeight = element.clientHeight; // 还原原始属性 els.display = 'none'; els.position = originalPosition; els.visibility = originalVisibility; return {width:originalWidth, height:originalHeight}; }, // 设置相对定位 makePositioned:function (element) { element = $(element); var pos = Element.getStyle(element, 'position'); if (pos == 'static' || !pos) { element._madePositioned = true; element.style.position = 'relative'; // Opera returns the offset relative to the positioning context, when an // element is position relative but top and left have not been defined if (window.opera) { element.style.top = 0; element.style.left = 0; } } }, // 取消设置的相对定位 undoPositioned:function (element) { element = $(element); if (element._madePositioned) { element._madePositioned = undefined; element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = ''; } }, // 设置超过元素边界隐藏 makeClipping:function (element) { element = $(element); if (element._overflow) return; element._overflow = element.style.overflow; if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') element.style.overflow = 'hidden'; }, // 撤销设置过的overflow undoClipping:function (element) { element = $(element); if (element._overflow) return; element.style.overflow = element._overflow; element._overflow = undefined; } }); // 交替切换对象 var Toggle = new Object(); // 交替显示隐藏 Toggle.display = Element.toggle; /*--------------------------------------------------------------------------*/ // Abstract 词义:抽象的 adjacency n. 毗邻;四周;邻接物 Insertion n. 插入;嵌入;插入物 /* * adjacency 的四个参数 * beforeBegin: 插入到标签开始前 * afterBegin:插入到标签开始标记之后 * beforeEnd:插入到标签结束标记前 * afterEnd:插入到标签结束标记后 * */ // DOM 插入等操作类 Abstract.Insertion = function (adjacency) { this.adjacency = adjacency; } Abstract.Insertion.prototype = { initialize:function (element, content) { this.element = $(element); this.content = content.stripScripts(); // 如果支持insertAdjacentHTML 方法 if (this.adjacency && this.element.insertAdjacentHTML) { try { // 插入内容 this.element.insertAdjacentHTML(this.adjacency, this.content); } catch (e) { if (this.element.tagName.toLowerCase() == 'tbody') { this.insertContent(this.contentFromAnonymousTable()); } else { throw e; } } } else { this.range = this.element.ownerDocument.createRange(); if (this.initializeRange) this.initializeRange(); this.insertContent([this.range.createContextualFragment(this.content)]); } setTimeout(function () { // 执行里面的JS content.evalScripts() }, 10); }, // 表格相关 contentFromAnonymousTable:function () { var div = document.createElement('div'); div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>'; return $A(div.childNodes[0].childNodes[0].childNodes); } } var Insertion = new Object(); // 新建类,插入到标签开始前 Insertion.Before = Class.create(); Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { initializeRange:function () { this.range.setStartBefore(this.element); }, insertContent:function (fragments) { fragments.each((function (fragment) { this.element.parentNode.insertBefore(fragment, this.element); }).bind(this)); } }); //afterBegin:插入到标签开始标记之后 Insertion.Top = Class.create(); Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { initializeRange:function () { this.range.selectNodeContents(this.element); this.range.collapse(true); }, insertContent:function (fragments) { fragments.reverse(false).each((function (fragment) { this.element.insertBefore(fragment, this.element.firstChild); }).bind(this)); } }); // beforeEnd:插入到标签结束标记前 Insertion.Bottom = Class.create(); Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { initializeRange:function () { this.range.selectNodeContents(this.element); this.range.collapse(this.element); }, insertContent:function (fragments) { fragments.each((function (fragment) { this.element.appendChild(fragment); }).bind(this)); } }); // afterEnd:插入到标签结束标记后 Insertion.After = Class.create(); Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { initializeRange:function () { this.range.setStartAfter(this.element); }, insertContent:function (fragments) { fragments.each((function (fragment) { this.element.parentNode.insertBefore(fragment, this.element.nextSibling); }).bind(this)); } }); /*--------------------------------------------------------------------------*/ // 创建一个ClassNames 类,以及这个类拥有的方法 Element.ClassNames = Class.create(); Element.ClassNames.prototype = { // 初始化,this.element initialize:function (element) { this.element = $(element); }, // 支持each方法 _each:function (iterator) { this.element.className.split(/\s+/).select( function (name) { return name.length > 0; })._each(iterator); }, // 设置class,会替换原来的 set:function (className) { this.element.className = className; }, // 在原有的基础上增加一个CLASS add:function (classNameToAdd) { if (this.include(classNameToAdd)) return; this.set(this.toArray().concat(classNameToAdd).join(' ')); }, // 移除 remove:function (classNameToRemove) { if (!this.include(classNameToRemove)) return; this.set(this.select( function (className) { return className != classNameToRemove; }).join(' ')); }, // 转化为字符串 toString:function () { return this.toArray().join(' '); } } Object.extend(Element.ClassNames.prototype, Enumerable); // 域 var Field = { clear:function () { for (var i = 0; i < arguments.length; i++) $(arguments[i]).value = ''; }, focus:function (element) { $(element).focus(); }, // 有为空的就返回false present:function () { for (var i = 0; i < arguments.length; i++) if ($(arguments[i]).value == '') return false; return true; }, select:function (element) { $(element).select(); }, activate:function (element) { element = $(element); element.focus(); if (element.select) element.select(); } } /*--------------------------------------------------------------------------*/ var Form = { // 整个表单的值序列化 /* * $('person-example').serialize() * -> 'username=sulien&age=22&hobbies=coding&hobbies=hiking' */ serialize:function (form) { // 获取表单下的elements var elements = Form.getElements($(form)); var queryComponents = new Array(); for (var i = 0; i < elements.length; i++) { // var queryComponent = Form.Element.serialize(elements[i]); if (queryComponent) queryComponents.push(queryComponent); } return queryComponents.join('&'); }, getElements:function (form) { form = $(form); var elements = new Array(); for (tagName in Form.Element.Serializers) { var tagElements = form.getElementsByTagName(tagName); for (var j = 0; j < tagElements.length; j++) elements.push(tagElements[j]); } return elements; }, /* var form = $('myform') form.getInputs() // -> all INPUT elements form.getInputs('text') // -> only text inputs var buttons = form.getInputs('radio', 'education') // -> only radio buttons of name "education" * */ // 获取input表单 getInputs:function (form, typeName, name) { form = $(form); var inputs = form.getElementsByTagName('input'); if (!typeName && !name) return inputs; var matchingInputs = new Array(); for (var i = 0; i < inputs.length; i++) { var input = inputs[i]; if ((typeName && input.type != typeName) || (name && input.name != name)) continue; matchingInputs.push(input); } return matchingInputs; }, // 整个form表单禁用 disable:function (form) { var elements = Form.getElements(form); for (var i = 0; i < elements.length; i++) { var element = elements[i]; element.blur(); element.disabled = 'true'; } }, // 整个form表单启用 enable:function (form) { var elements = Form.getElements(form); for (var i = 0; i < elements.length; i++) { var element = elements[i]; element.disabled = ''; } }, // 找到第一个不是隐藏的表单域 findFirstElement:function (form) { return Form.getElements(form).find(function (element) { return element.type != 'hidden' && !element.disabled && ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); }); }, // 第一个表单获取焦点 focusFirstElement:function (form) { Field.activate(Form.findFirstElement(form)); }, // 整个表单重设 reset:function (form) { $(form).reset(); } } Form.Element = { serialize:function (element) { element = $(element); var method = element.tagName.toLowerCase(); var parameter = Form.Element.Serializers[method](element); if (parameter) { var key = encodeURIComponent(parameter[0]); if (key.length == 0) return; if (parameter[1].constructor != Array) parameter[1] = [parameter[1]]; return parameter[1].map( function (value) { return key + '=' + encodeURIComponent(value); }).join('&'); } }, // 获取值 getValue:function (element) { element = $(element); var method = element.tagName.toLowerCase(); var parameter = Form.Element.Serializers[method](element); if (parameter) return parameter[1]; } } // 区分不同的element,返回不同的值,数组形式[name,value] Form.Element.Serializers = { input:function (element) { switch (element.type.toLowerCase()) { case 'submit': case 'hidden': case 'password': case 'text': return Form.Element.Serializers.textarea(element); case 'checkbox': case 'radio': return Form.Element.Serializers.inputSelector(element); } return false; }, inputSelector:function (element) { if (element.checked) return [element.name, element.value]; }, textarea:function (element) { return [element.name, element.value]; }, select:function (element) { return Form.Element.Serializers[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); }, selectOne:function (element) { var value = '', opt, index = element.selectedIndex; if (index >= 0) { opt = element.options[index]; value = opt.value; if (!value && !('value' in opt)) value = opt.text; } return [element.name, value]; }, selectMany:function (element) { var value = new Array(); for (var i = 0; i < element.length; i++) { var opt = element.options[i]; if (opt.selected) { var optValue = opt.value; if (!optValue && !('value' in opt)) optValue = opt.text; value.push(optValue); } } return [element.name, value]; } } /*--------------------------------------------------------------------------*/ var $F = Form.Element.getValue; /*--------------------------------------------------------------------------*/ // 时间相关方法 Abstract.TimedObserver = function () { } Abstract.TimedObserver.prototype = { initialize:function (element, frequency, callback) { this.frequency = frequency; this.element = $(element); this.callback = callback; this.lastValue = this.getValue(); this.registerCallback(); }, registerCallback:function () { setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, onTimerEvent:function () { var value = this.getValue(); if (this.lastValue != value) { this.callback(this.element, value); this.lastValue = value; } } } Form.Element.Observer = Class.create(); Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue:function () { return Form.Element.getValue(this.element); } }); Form.Observer = Class.create(); Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue:function () { return Form.serialize(this.element); } }); /*--------------------------------------------------------------------------*/ Abstract.EventObserver = function () { } Abstract.EventObserver.prototype = { initialize:function (element, callback) { this.element = $(element); this.callback = callback; this.lastValue = this.getValue(); if (this.element.tagName.toLowerCase() == 'form') this.registerFormCallbacks(); else this.registerCallback(this.element); }, onElementEvent:function () { var value = this.getValue(); if (this.lastValue != value) { this.callback(this.element, value); this.lastValue = value; } }, registerFormCallbacks:function () { var elements = Form.getElements(this.element); for (var i = 0; i < elements.length; i++) this.registerCallback(elements[i]); }, registerCallback:function (element) { if (element.type) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': Event.observe(element, 'click', this.onElementEvent.bind(this)); break; case 'password': case 'text': case 'textarea': case 'select-one': case 'select-multiple': Event.observe(element, 'change', this.onElementEvent.bind(this)); break; } } } } Form.Element.EventObserver = Class.create(); Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { getValue:function () { return Form.Element.getValue(this.element); } }); // 事件类 Form.EventObserver = Class.create(); Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { getValue:function () { return Form.serialize(this.element); } }); if (!window.Event) { var Event = new Object(); } // 定义常量 Object.extend(Event, { KEY_BACKSPACE:8, KEY_TAB:9, KEY_RETURN:13, KEY_ESC:27, KEY_LEFT:37, KEY_UP:38, KEY_RIGHT:39, KEY_DOWN:40, KEY_DELETE:46, // 事件目标 element:function (event) { return event.target || event.srcElement; }, isLeftClick:function (event) { return (((event.which) && (event.which == 1)) || ((event.button) && (event.button == 1))); }, pointerX:function (event) { return event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)); }, pointerY:function (event) { return event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop)); }, stop:function (event) { if (event.preventDefault) { event.preventDefault(); event.stopPropagation(); } else { event.returnValue = false; event.cancelBubble = true; } }, // find the first node with the given tagName, starting from the // node the event was triggered on; traverses the DOM upwards findElement:function (event, tagName) { var element = Event.element(event); while (element.parentNode && (!element.tagName || (element.tagName.toUpperCase() != tagName.toUpperCase()))) element = element.parentNode; return element; }, observers:false, _observeAndCache:function (element, name, observer, useCapture) { if (!this.observers) this.observers = []; if (element.addEventListener) { this.observers.push([element, name, observer, useCapture]); element.addEventListener(name, observer, useCapture); } else if (element.attachEvent) { this.observers.push([element, name, observer, useCapture]); element.attachEvent('on' + name, observer); } }, unloadCache:function () { if (!Event.observers) return; for (var i = 0; i < Event.observers.length; i++) { Event.stopObserving.apply(this, Event.observers[i]); Event.observers[i][0] = null; } Event.observers = false; }, observe:function (element, name, observer, useCapture) { var element = $(element); useCapture = useCapture || false; if (name == 'keypress' && (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.attachEvent)) name = 'keydown'; this._observeAndCache(element, name, observer, useCapture); }, stopObserving:function (element, name, observer, useCapture) { var element = $(element); useCapture = useCapture || false; if (name == 'keypress' && (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.detachEvent)) name = 'keydown'; if (element.removeEventListener) { element.removeEventListener(name, observer, useCapture); } else if (element.detachEvent) { element.detachEvent('on' + name, observer); } } }); /* prevent memory leaks in IE */ Event.observe(window, 'unload', Event.unloadCache, false); var Position = { // set to true if needed, warning: firefox performance problems // NOT neeeded for page scrolling, only if draggable contained in // scrollable elements includeScrollOffsets:false, // must be called before calling withinIncludingScrolloffset, every time the // page is scrolled prepare:function () { this.deltaX = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; this.deltaY = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; }, realOffset:function (element) { var valueT = 0, valueL = 0; do { valueT += element.scrollTop || 0; valueL += element.scrollLeft || 0; element = element.parentNode; } while (element); return [valueL, valueT]; }, cumulativeOffset:function (element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; } while (element); return [valueL, valueT]; }, positionedOffset:function (element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { p = Element.getStyle(element, 'position'); if (p == 'relative' || p == 'absolute') break; } } while (element); return [valueL, valueT]; }, // 父元素 offsetParent:function (element) { if (element.offsetParent) return element.offsetParent; if (element == document.body) return element; while ((element = element.parentNode) && element != document.body) if (Element.getStyle(element, 'position') != 'static') return element; return document.body; }, // caches x/y coordinate pair to use with overlap within:function (element, x, y) { if (this.includeScrollOffsets) return this.withinIncludingScrolloffsets(element, x, y); this.xcomp = x; this.ycomp = y; this.offset = this.cumulativeOffset(element); return (y >= this.offset[1] && y < this.offset[1] + element.offsetHeight && x >= this.offset[0] && x < this.offset[0] + element.offsetWidth); }, withinIncludingScrolloffsets:function (element, x, y) { var offsetcache = this.realOffset(element); this.xcomp = x + offsetcache[0] - this.deltaX; this.ycomp = y + offsetcache[1] - this.deltaY; this.offset = this.cumulativeOffset(element); return (this.ycomp >= this.offset[1] && this.ycomp < this.offset[1] + element.offsetHeight && this.xcomp >= this.offset[0] && this.xcomp < this.offset[0] + element.offsetWidth); }, // within must be called directly before overlap:function (mode, element) { if (!mode) return 0; if (mode == 'vertical') return ((this.offset[1] + element.offsetHeight) - this.ycomp) / element.offsetHeight; if (mode == 'horizontal') return ((this.offset[0] + element.offsetWidth) - this.xcomp) / element.offsetWidth; }, clone:function (source, target) { source = $(source); target = $(target); target.style.position = 'absolute'; var offsets = this.cumulativeOffset(source); target.style.top = offsets[1] + 'px'; target.style.left = offsets[0] + 'px'; target.style.width = source.offsetWidth + 'px'; target.style.height = source.offsetHeight + 'px'; }, page:function (forElement) { var valueT = 0, valueL = 0; var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; // Safari fix if (element.offsetParent == document.body) if (Element.getStyle(element, 'position') == 'absolute') break; } while (element = element.offsetParent); element = forElement; do { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } while (element = element.parentNode); return [valueL, valueT]; }, clone:function (source, target) { var options = Object.extend({ setLeft:true, setTop:true, setWidth:true, setHeight:true, offsetTop:0, offsetLeft:0 }, arguments[2] || {}) // find page position of source source = $(source); var p = Position.page(source); // find coordinate system to use target = $(target); var delta = [0, 0]; var parent = null; // delta [0,0] will do fine with position: fixed elements, // position:absolute needs offsetParent deltas if (Element.getStyle(target, 'position') == 'absolute') { parent = Position.offsetParent(target); delta = Position.page(parent); } // correct by body offsets (fixes Safari) if (parent == document.body) { delta[0] -= document.body.offsetLeft; delta[1] -= document.body.offsetTop; } // set position if (options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; if (options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; if (options.setWidth) target.style.width = source.offsetWidth + 'px'; if (options.setHeight) target.style.height = source.offsetHeight + 'px'; }, absolutize:function (element) { element = $(element); if (element.style.position == 'absolute') return; Position.prepare(); var offsets = Position.positionedOffset(element); var top = offsets[1]; var left = offsets[0]; var width = element.clientWidth; var height = element.clientHeight; element._originalLeft = left - parseFloat(element.style.left || 0); element._originalTop = top - parseFloat(element.style.top || 0); element._originalWidth = element.style.width; element._originalHeight = element.style.height; element.style.position = 'absolute'; element.style.top = top + 'px'; ; element.style.left = left + 'px'; ; element.style.width = width + 'px'; ; element.style.height = height + 'px'; ; }, relativize:function (element) { element = $(element); if (element.style.position == 'relative') return; Position.prepare(); element.style.position = 'relative'; var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.height = element._originalHeight; element.style.width = element._originalWidth; } } // Safari returns margins on body which is incorrect if the child is absolutely // positioned. For performance reasons, redefine Position.cumulativeOffset for // KHTML/WebKit only. if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { Position.cumulativeOffset = function (element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent == document.body) if (Element.getStyle(element, 'position') == 'absolute') break; element = element.offsetParent; } while (element); return [valueL, valueT]; } }