Prototype.js 解释

  1. /*  Prototype javascript framework, version 1.4.0
  2.  *  (c) 2005 Sam Stephenson <sam@conio.net>
  3.  *
  4.  *  Prototype is freely distributable under the terms of an MIT-style license.
  5.  *  For details, see the Prototype web site: http://prototype.conio.net/
  6.  *
  7. /*--------------------------------------------------------------------------*/
  8. /* 
  9. prototype-1.4.0注释版本 by http://www.x2blog.cn/supNate/
  10. prototype框架最早是出于方便Ruby开发人员进行javascript开发所构建的,从这个版本上更加体现的淋
  11. 漓尽致。
  12. 比起1.3.1版本,1.4.0中的编程思想和技巧更加令人拍案叫绝,对于开拓编程思路很有帮助。
  13. 该版本主要加入了迭代器思想,也是Ruby中的一个核心概念,从而使用此框架进行javascript开发几乎可
  14. 以避免for循环的使用。
  15. /*-----------------------------------------------------------------------------------------
  16. -----------*/
  17. /*
  18. 定义prototype对象,告知版本信息,有利于程序的自动检测
  19. ScriptFragment是正则表达式,用于捕获字符串中的<script>标记及其中的内容
  20. emptyFunction:空函数
  21. K:返回参数自身的函数,后面会有应用
  22. 在这里使用了直接定义对象的语法:
  23. var obj={
  24. property1:value1,
  25. property2:value2,
  26. ....
  27. }
  28. 后面会经常用到
  29. */
  30. var Prototype = {
  31.   Version: '1.4.0',
  32.   ScriptFragment: '(?:<script.*?>)((/n|/r|.)*?)(?:<//script>)',
  33.   emptyFunction: function() {},
  34.   K: function(x) {return x}
  35. }
  36. /*
  37. 定义创建类的模式,使用此模式创建的类能够实现构造函数
  38. 其中initialize是一个抽象方法,apply使得能对其保持参数。
  39. 如果直接调用this.initialize(arguments),则整个参数数组作为了一个参数。
  40. */
  41. var Class = {
  42.   create: function() {
  43.     return function() {
  44.       this.initialize.apply(this, arguments);
  45.     }
  46.   }
  47. }
  48. //表示命名空间或者抽象类的东西,使代码逻辑更加清楚
  49. var Abstract = new Object();
  50. /*
  51. 将source的所有属性复制到destination
  52. 例如:
  53. var a={};
  54. var b={p:1};
  55. Object.extent(a,b);
  56. alert(a.p);
  57. 可以看到a具有了属性p且值等于1。
  58. 如果属性相同则覆盖。
  59. */
  60. Object.extend = function(destination, source) {
  61.   for (property in source) {
  62.     destination[property] = source[property];
  63.   }
  64.   return destination;
  65. }
  66. /*
  67. 相比prototype-1.3.1这里少了下面的函数:
  68. Object.prototype.extend = function(object) {
  69.   return Object.extend.apply(this, [this, object]);
  70. }
  71. 所以原先基于1.3.1框架的js脚本升级到1.4.0时会产生兼容性问题。只要在1.4.0里加上上述函数即可。
  72. 去掉的原因大概因为为每个object都增加extend方法显的很浪费,毕竟95%的对象是不会用到的。
  73. 而且增加了extend方法也为反射枚举带来一定的麻烦,这从后面Hash对象的用法可以看到。
  74. */
  75. /*
  76. 将对象转换为字符串,这里能够更详细一些,只要对象自定义了inspect函数。而不是原来对象的
  77. toString总是[object]。
  78. 例如后面对数组定义了inspect函数,使得
  79. var arr=[1,2,3];
  80. -》arr.inspect()=="[1,2,3]";
  81. */
  82. Object.inspect = function(object) {
  83.   try {
  84.     if (object == undefined) return 'undefined';
  85.     if (object == nullreturn 'null';
  86.     return object.inspect ? object.inspect() : object.toString();
  87.   } catch (e) {
  88.     if (e instanceof RangeError) return '...';
  89.     throw e;
  90.   }
  91. }
  92. /*
  93. 一个很重要的方法,能够将函数绑定到某个对象运行
  94. 和1.3.1版本相比,原来不能在绑定的时候就添加参数,而现在可以。
  95. 例如:
  96. var obj1={p:"obj1"};
  97. var obj2={
  98. p:"obj2",
  99. method:function(arg){
  100. alert(arg+this.p);
  101. }
  102. }
  103. obj2.method("this is ");//显示“this is obj2”;
  104. obj2.method.bind(obj1,"now this is ");//显示“now this is obj1”;
  105. 最后一句在1.3.1中必须写为:
  106. obj2.method.bind(obj1)("now this is ");//显示“now this is obj1”;
  107. */
  108. Function.prototype.bind = function() {
  109.   var __method = this, args = $A(arguments), object = args.shift();
  110.   return function() {
  111.     return __method.apply(object, args.concat($A(arguments)));
  112.   }
  113. }
  114. /*
  115. 将函数作为对象的事件监听器,这样可以产生独立而且通用的事件处理程序,例如要对单击事件进行处理
  116. function clickHandler(element){
  117. //处理element的单击事件
  118. }
  119. 假设有节点node1,则:
  120. node1.οnclick=function(){
  121. clickHandler.bindAsEventListener(this)(event||window.event);
  122. }
  123. */
  124. Function.prototype.bindAsEventListener = function(object) {
  125.   var __method = this;
  126.   return function(event) {
  127.     return __method.call(object, event || window.event);
  128.   }
  129. }
  130. /*
  131. 所有的数字类型都是Number类的实例,下面就是给Number类定义一些方法
  132. */
  133. Object.extend(Number.prototype, {
  134. /*
  135. 将数字转换为颜色的形式
  136. */
  137.   toColorPart: function() {
  138.     var digits = this.toString(16);
  139.     if (this < 16) return '0' + digits;
  140.     return digits;
  141.   },
  142. //加1
  143.   succ: function() {
  144.     return this + 1;
  145.   },
  146. /*
  147. 执行指定次数的循环,例如获取10个随机数
  148. var ran=[]
  149. var c=10;
  150. c.times(function(){
  151. ran.push(Math.random());
  152. });
  153. $R是ObjectRange对象的快捷创建形式,后面会有介绍。
  154. */
  155.   times: function(iterator) {
  156.     $R(0, thistrue).each(iterator);
  157.     return this;
  158.   }
  159. });
  160. /*
  161. Try对象,仅有一个方法these
  162. */
  163. var Try = {
  164. /*
  165. 根据参数指定的函数进行调用,返回第一个调用成功的值
  166. 在后面跨浏览器建立XMLHttpRequest对象时就用到了。
  167. 如果所有都不成功则返回undefined
  168. */
  169.   these: function() {
  170.     var returnvalue;
  171.     for (var i = 0; i < arguments.length; i++) {
  172.       var lambda = arguments[i];
  173.       try {
  174.         returnvalue = lambda();
  175.         break;
  176.       } catch (e) {}
  177.     }
  178.     return returnvalue;
  179.   }
  180. }
  181. /*--------------------------------------------------------------------------*/
  182. /*
  183. 定时器类,比起window.setInterval函数,该类能够使得回调函数不会被并发调用,见onTimerEvent的注
  184. 释。
  185. */
  186. var PeriodicalExecuter = Class.create();
  187. PeriodicalExecuter.prototype = {
  188. /*
  189. 构造函数,指定回调函数和执行频率,单位为秒
  190. */
  191.   initialize: function(callback, frequency) {
  192.     this.callback = callback;
  193.     this.frequency = frequency;
  194.     this.currentlyExecuting = false;
  195.     this.registerCallback();
  196.   },
  197. /*
  198. 开始执行定时器,一般不要显示调用,在构造函数中被调用
  199. 注意这里写为:
  200. this.onTimerEvent.bind(this)
  201. 如果写为:
  202. this.onTimerEvent
  203. 则onTimerEvent中的函数的this指针将指向window对象,即setInterval的默认对象。
  204. */
  205.   registerCallback: function() {
  206.     setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  207.   },
  208. /*
  209. 相当于回调函数的一个代理。
  210. 在传统的setInterval函数中,时间一到,便强制执行回调函数,而这里加入了currentlyExecuting属性
  211. 判断,
  212. 则如果callback函数的执行时间超过了一个时间片,则阻止其被重复执行。
  213. */
  214.   onTimerEvent: function() {
  215.     if (!this.currentlyExecuting) {
  216.       try {
  217.         this.currentlyExecuting = true;
  218.         this.callback();
  219.       } finally {
  220.         this.currentlyExecuting = false;
  221.       }
  222.     }
  223.   }
  224. }
  225. /*--------------------------------------------------------------------------*/
  226. /*
  227. 很方便的一个快速链接函数,能够获得参数所指定的页面节点,如果有多个参数则返回数组。
  228. 参数的形式既可以是节点的id值,也可以是节点的引用,即$($("someId"))和$("someId")是等价的;
  229. */
  230. function $() {
  231.   var elements = new Array();
  232.   for (var i = 0; i < arguments.length; i++) {
  233.     var element = arguments[i];
  234.     if (typeof element == 'string')
  235.       element = document.getElementById(element);
  236.     if (arguments.length == 1)
  237.       return element;
  238.     elements.push(element);
  239.   }
  240.   return elements;
  241. }
  242. /*
  243. 为字符串对象添加方法,和前面为Number添加方法的原理相同
  244. */
  245. Object.extend(String.prototype, {
  246. /*
  247. 将Html转换为纯文本,例如:
  248. var s="<font color='red'>hello</font>";
  249. s.stripTags()将得到“hello”。
  250. */
  251.   stripTags: function() {
  252.     return this.replace(/<//?[^>]+>/gi, '');
  253.   },
  254. /*
  255. 删除文本中的脚本代码(<script xxx>...</script>)
  256. */
  257.   stripScripts: function() {
  258.     return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  259.   },
  260. //提取字符串中的脚本,返回所有脚本内容组成的数组
  261.   extractScripts: function() {
  262.     var matchAll = new RegExp(Prototype.ScriptFragment, 'img');//先找到所有包括<script>的代
  263. 码标记
  264.     var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); //再对每个脚本删除<script>标
  265.     return (this.match(matchAll) || []).map(function(scriptTag) {
  266.       return (scriptTag.match(matchOne) || [''''])[1];
  267.     });
  268.   },
  269. //先提取字符串中的脚本块,再执行这些脚本
  270.   evalScripts: function() {
  271.     return this.extractScripts().map(eval);
  272.   },
  273. /*
  274. 利用浏览器本身的机制对Html字符串进行编码,例如将<转换为<
  275. */
  276.   escapeHTML: function() {
  277.     var div = document.createElement('div');
  278.     var text = document.createTextNode(this);
  279.     div.appendChild(text);
  280.     return div.innerHTML;
  281.   },
  282. /*
  283. 对Html进行解码
  284. */
  285.   unescapeHTML: function() {
  286.     var div = document.createElement('div');
  287.     div.innerHTML = this.stripTags();
  288.     return div.childNodes[0] ? div.childNodes[0].nodevalue : '';
  289.   },
  290. //获取查询字符串数组,例如通过document.location.toQueryParams()就可以得到由键和值组成的哈希
  291. 表(用对象表示)。
  292.   toQueryParams: function() {
  293.     var pairs = this.match(/^/??(.*)$/)[1].split('&');
  294.     return pairs.inject({}, function(params, pairString) {
  295.       var pair = pairString.split('=');
  296.       params[pair[0]] = pair[1];
  297.       return params;
  298.     });
  299.   },
  300. //将字符串转换为字符数组
  301.   toArray: function() {
  302.     return this.split('');
  303.   },
  304. /*
  305. 将用"-"连接的字符串骆驼化。例如:
  306. var s="background-color";
  307. alert(s.camelize());
  308. 将得到“backgroundColor”。
  309. */
  310.   camelize: function() {
  311.     var oStringList = this.split('-');
  312.     if (oStringList.length == 1) return oStringList[0];
  313.     var camelizedString = this.indexOf('-') == 0
  314.       ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
  315.       : oStringList[0];
  316.     for (var i = 1, len = oStringList.length; i < len; i++) {
  317.       var s = oStringList[i];
  318.       camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
  319.     }
  320.     return camelizedString;
  321.   },
  322. /*
  323. inspect是观察的意思。这里大概就是将字符串转换为可观察的形式。这里将转义字符写成转义前的字符
  324. 串形式,
  325. 例如:
  326. var s="abc/ndef";
  327. alert(s);
  328. 将得到两行字符串,上一行是abc,下一行是def
  329. alert(s.inspect());
  330. 将得到abc/ndef
  331. 即给字符串赋值时的形式,这和数组的inspect作用类似。
  332. */
  333.   inspect: function() {
  334.     return "'" + this.replace('//', '').replace("'", '///'') + "'";
  335.   }
  336. });
  337. //做一个名称链接
  338. String.prototype.parseQuery = String.prototype.toQueryParams;
  339. //定义了两个异常对象,主要用于迭代控制
  340. var $break    = new Object();
  341. var $continue = new Object();
  342. /*
  343. 这是一个非常Ruby的机制,事实上可以将Enumerable看作一个枚举接口,
  344. 而_each是必须实现的方法,只要实现了此方法的类,都可以调用接口类中的其他成员。
  345. 例如后面Array就实现了此接口,也是最典型的应用
  346. */
  347. var Enumerable = {
  348. /*
  349. 对可枚举对象的每个成员调用iterator(迭代器)方法,
  350. 如果迭代器方法抛出$continue异常,则继续执行,如果抛出$break异常,则不再继续迭代
  351. 其中调用了_each这个抽象方法,
  352. _each是由具体的继承于Enumerable的类实现的
  353. index计数器的作用是用于告诉迭代器当前执行到第几个元素,是迭代器可选实现的。
  354. */
  355.   each: function(iterator) {
  356.     var index = 0;
  357.     try {
  358.       this._each(function(value) {
  359.         try {
  360.           iterator(value, index++);
  361.         } catch (e) {
  362.           if (e != $continuethrow e;
  363.         }
  364.       });
  365.     } catch (e) {
  366.       if (e != $breakthrow e;
  367.     }
  368.   },
  369. /*
  370. 判断枚举对象中的所有元素是否都能使得迭代器返回true。如果没有指定迭代器,则判断所有元素是否都
  371. 对应于布尔类型的true
  372. 如果所有都满足,则返回true;否则返回false;
  373. 注意这里就使用了$break异常,用于实现“逻辑与”操作的短路效果
  374. 另外值得注意的一个技巧是使用了!!将一个变量强制转换为布尔类型,可以参考:
  375. http://www.x2blog.cn/supNate/?tid=4669
  376. */
  377. all: function(iterator) {
  378. var result = true;
  379. this.each(function(value, index) {
  380. result = result && !!(iterator || Prototype.K)(value, index);
  381. if (!result) throw $break;
  382. });
  383. return result;
  384. },
  385. /*
  386. 判断枚举对象中的所有元素是否有满足指定迭代器的值(返回true),如果有则返回true,否则返回
  387. false
  388. 其原理和all方法类似
  389. 如果数组为空,仍然返回true,这一点有点匪夷所思。
  390. */
  391.   any: function(iterator) {
  392.     var result = true;
  393.     this.each(function(value, index) {
  394.       if (result = !!(iterator || Prototype.K)(value, index))
  395.         throw $break;
  396.     });
  397.     return result;
  398.   },
  399. /*
  400. 返回所有枚举元素通过迭代器执行的结果,作为数组返回
  401. */
  402.   collect: function(iterator) {
  403.     var results = [];
  404.     this.each(function(value, index) {
  405.       results.push(iterator(value, index));
  406.     });
  407.     return results;
  408.   },
  409. /*
  410. 返回第一个能够使得迭代器返回true的枚举元素的值,如果没有true,则返回"undefined",即result未被
  411. 赋值
  412. 这有可能是作者考虑的一个小失误,毕竟返回"undefined"并不是一个好的风格(仅是猜测)
  413. */
  414.   detect: function (iterator) {
  415.     var result;
  416.     this.each(function(value, index) {
  417.       if (iterator(value, index)) {
  418.         result = value;
  419.         throw $break;
  420.       }
  421.     });
  422.     return result;
  423.   },
  424. /*
  425. 返回所有能够使得迭代器返回true的枚举元素,作为数组返回。
  426. */
  427.   findAll: function(iterator) {
  428.     var results = [];
  429.     this.each(function(value, index) {
  430.       if (iterator(value, index))
  431.         results.push(value);
  432.     });
  433.     return results;
  434.   },
  435. /*
  436. grep是unix类操作系统下的一个经典命令,而这里则是javascript的一个类似实现
  437. pattern是正则模式,对所有符合此模式的枚举元素进行迭代器运算,并将运算结果保存到数组中并返回
  438. 需要注意,这里的iterator参数是可选的,此时仅仅对枚举元素进行模式匹配,返回所有的匹配结果
  439. */
  440.   grep: function(pattern, iterator) {
  441.     var results = [];
  442.     this.each(function(value, index) {
  443.       var stringvalue = value.toString();
  444.       if (stringvalue.match(pattern))
  445.         results.push((iterator || Prototype.K)(value, index));
  446.     })
  447.     return results;
  448.   },
  449. /*
  450. 判断枚举对象中是否包含指定值的枚举元素,这里仍然使用了each方法,而不是循环,可见prototype致
  451. 力于提供一种ruby化的编程方式,
  452. 如果用循环实现,则是类似于以下的代码:
  453. for(var i=0;i<this.length;i++){
  454. if(this[i]==object)return true;
  455. }
  456. 而该函数中,定义了迭代器:
  457. function(value) {
  458.       if (value == object) {
  459.         found = true;
  460.         throw $break;
  461.       }
  462.     }
  463. 这个迭代器作为each方法的参数。
  464. */
  465.   include: function(object) {
  466.     var found = false;
  467.     this.each(function(value) {
  468.       if (value == object) {
  469.         found = true;
  470.         throw $break;
  471.       }
  472.     });
  473.     return found;
  474.   },
  475. /*
  476. 字面意思是“注入”,其作用相当于将memo作为联系各个迭代器的全局变量,每次迭代都对其进行操作,
  477. 返回操作的最后结果。例如对于数组:
  478. var arr=[1,2,3];
  479. 现在想将其字符串化为:123
  480. 如果不调用join方法,传统做法是:
  481. var s="";
  482. for(var i=0;i<arr.length;i++){
  483. s+=arr[i];
  484. }
  485. 现在通过调用inject函数,则:
  486. var s=arr.inject("",function(memo,value){return memo+value});
  487. 两者运行的结果是完全相同的。
  488. */
  489.   inject: function(memo, iterator) {
  490.     this.each(function(value, index) {
  491.       memo = iterator(memo, value, index);
  492.     });
  493.     return memo;
  494.   },
  495. /*
  496. 在所有枚举元素上调用method方法,并可以给这个方法传递参数
  497. 返回所有method的执行结果,作为数组返回
  498. */
  499.   invoke: function(method) {
  500.     var args = $A(arguments).slice(1);
  501.     return this.collect(function(value) {
  502.       return value[method].apply(value, args);
  503.     });
  504.   },
  505. /*
  506. 返回最大的迭代器返回值
  507. */
  508.   max: function(iterator) {
  509.     var result;
  510.     this.each(function(value, index) {
  511.       value = (iterator || Prototype.K)(value, index);
  512.       if (value >= (result || value))
  513.         result = value;
  514.     });
  515.     return result;
  516.   },
  517. /*
  518. 返回最小的迭代器返回值
  519. */
  520.   min: function(iterator) {
  521.     var result;
  522.     this.each(function(value, index) {
  523.       value = (iterator || Prototype.K)(value, index);
  524.       if (value <= (result || value))
  525.         result = value;
  526.     });
  527.     return result;
  528.   },
  529. /*
  530. 按照迭代器的返回结果,将枚举元素分为两个数组trues和falses,其中trues包括迭代器返回true的枚举
  531. 元素,falses则相反。
  532. */
  533.   partition: function(iterator) {
  534.     var trues = [], falses = [];
  535.     this.each(function(value, index) {
  536.       ((iterator || Prototype.K)(value, index) ?
  537.         trues : falses).push(value);
  538.     });
  539.     return [trues, falses];
  540.   },
  541. /*
  542. 返回所有枚举元素的property属性
  543. */
  544.   pluck: function(property) {
  545.     var results = [];
  546.     this.each(function(value, index) {
  547.       results.push(value[property]);
  548.     });
  549.     return results;
  550.   },
  551. /*
  552. 返回所有迭代器执行结果为false的枚举元素
  553. */
  554.   reject: function(iterator) {
  555.     var results = [];
  556.     this.each(function(value, index) {
  557.       if (!iterator(value, index))
  558.         results.push(value);
  559.     });
  560.     return results;
  561.   },
  562. /*
  563. 结构复杂的一个函数,作用是根据迭代器iterator的结果对枚举元素进行排序。使iterator执行结果小的
  564. 元素排在前面。
  565. 主要包括三个函数的调用:
  566. 1。collect方法,返回的每个数组元素包括:值和迭代器运行该值的结果,用
  567. {value:value,criteria:iterator(value,index)}得到
  568. 2。对collect返回的数组执行sort方法,这时数组对象内置的对象,参数是一个委托函数,用于指定排序
  569. 规则。其标准是对迭代器返回的值排序,小的在前面
  570. 3。对sort的结果执行pluck方法,即返回value属性的值,于是最后还是返回的枚举对象中的原有值,只
  571. 是根据迭代器iterator的结果对这些元素进行排序
  572. */
  573.   sortBy: function(iterator) {
  574.     return this.collect(function(value, index) {
  575.       return {value: value, criteria: iterator(value, index)};
  576.     }).sort(function(left, right) {
  577.       var a = left.criteria, b = right.criteria;
  578.       return a < b ? -1 : a > b ? 1 : 0;
  579.     }).pluck('value');
  580.   },
  581. /*
  582. 将枚举对象转换为数组,使用了collect方法和Prototype.K函数,减少了重复代码
  583. */
  584.   toArray: function() {
  585.     return this.collect(Prototype.K);
  586.   },
  587. /*
  588. 压缩函数,实现复杂,作用尚不能体会-_-。
  589. 接收的参数需要是可枚举对象,可以有多个参数。最后一个参数是迭代器,可选。
  590. 作用是将自身和参数组成的二维阵列进行行列对换,并切除多余的数据,或补充缺少的数据(用
  591. undefined)。切换后的行数由调用者中元素的个数决定,而列数是数组参数的个数加1。
  592. 每个数组参数的第一个元素顺序组成第一行,第二个元素顺序组成第二行,依次类推。直到调用者中的元
  593. 素用完为止。
  594. 迭代器的作用就是对转换后的每一行进行一次运算。
  595. 例如:
  596. var arr1=[1,2,3];
  597. var arr2=[4,5,6];
  598. var arr3=[7,8,9];
  599. var arr=arr1.zip(arr2,arr3);
  600. //使用迭代器输出结果,inspect用于输出数组语法表示的数组字符串,后面有介绍
  601. arr.each(function(s){
  602. document.write(s.inspect());
  603. document.write("<br/>");
  604. }
  605. );
  606. 得到的结果为:
  607. [1, 4, 7]
  608. [2, 5, 8]
  609. [3, 6, 9]
  610. 如果让arr1=[1,2],其他不变,则执行结果为:
  611. [1, 4, 7]
  612. [2, 5, 8]
  613. */
  614.   zip: function() {
  615.     var iterator = Prototype.K, args = $A(arguments);
  616.     if (typeof args.last() == 'function')
  617.       iterator = args.pop();
  618. //将自身枚举对象作为一个元素,与参数(也是可枚举的)组成一个数组,并将枚举对象转换为数组(通
  619. 过$A迭代器)
  620.     var collections = [this].concat(args).map($A);
  621.     return this.map(function(value, index) {
  622.       iterator(value = collections.pluck(index));
  623.       return value;
  624.     });
  625.   },
  626. /*
  627. 这实际上这是一个待实现的抽象方法,在Array对象中有对其进行的重定义
  628. 所以将this转换为数组(toArray()),再调用inspect。
  629. 对于非数组形式的枚举对象,则会加上'#<Enumerable:....>'这样的形式
  630. */
  631.   inspect: function() {
  632.     return '#<Enumerable:' + this.toArray().inspect() + '>';
  633.   }
  634. }
  635. //对Enumerable基类的一些方法做了快速链接
  636. Object.extend(Enumerable, {
  637.   map:     Enumerable.collect,
  638.   find:    Enumerable.detect,
  639.   select:  Enumerable.findAll,
  640.   member:  Enumerable.include,
  641.   entries: Enumerable.toArray
  642. });
  643. /*
  644. 将一个对象转换为数组。
  645. 对于字符串则直接变为字符数组,例如$A("abc")将得到,["a","b","c"]
  646. 否则集合对象变为数组,这类对象包括函数的参数集合arguments,<select>的options集合,
  647. <form>的elements集合等等,一个节点的所有子结点childNodes等等。
  648. */
  649. var $A = Array.from = function(iterable) {
  650. if (!iterable) return [];
  651. if (iterable.toArray) {
  652. return iterable.toArray();
  653. else {
  654. var results = [];
  655. for (var i = 0; i < iterable.length; i++)
  656. results.push(iterable[i]);
  657. return results;
  658. }
  659. }
  660. /*
  661. 让数组继承于Enumarable对象(基类)
  662. */
  663. Object.extend(Array.prototype, Enumerable);
  664. /*
  665. 做一个链接,prototype中一般私有的成员或抽象成员都用下划线开头,这里的_reverse大概就是起一个
  666. 说明性的作用,将其作为抽象方法使用。
  667. */
  668. Array.prototype._reverse = Array.prototype.reverse;
  669. /*
  670. 为数组对象添加一些快捷方法
  671. */
  672. Object.extend(Array.prototype, {
  673. /*
  674. 迭代器方法,源于Ruby中的迭代器用法
  675. _each方法的作用就是将数组的每个元素作为iterator函数的参数,并执行iterator方法。例如对于数组
  676. :var arr=[1,2,3,4,5,6];
  677. 如果要显示其中的每个元素,通常的做法是
  678. for(var i=0;i<arr.length;i++){
  679. alert(arr[i]);
  680. }
  681. 而使用此方法则:
  682. arr._each(function(s){alert(s)});
  683. 因此,在Ruby的代码中很少出现循环,这个函数使得javascript同样也能够实现。
  684. */
  685.   _each: function(iterator) {
  686.     for (var i = 0; i < this.length; i++)
  687.       iterator(this[i]);
  688.   },
  689. //清空数组
  690.   clear: function() {
  691.     this.length = 0;
  692.     return this;
  693.   },
  694. //获取第一个元素的值
  695.   first: function() {
  696.     return this[0];
  697.   },
  698. //获取最后一个元素的值
  699.   last: function() {
  700.     return this[this.length - 1];
  701.   },
  702. /*
  703. 用于删除一个数组中的未定义值和null值
  704. 这里的select是从Emurable中继承的方法,而select又是findAll函数的别名
  705. */
  706.   compact: function() {
  707.     return this.select(function(value) {
  708.       return value != undefined || value != null;
  709.     });
  710.   },
  711. /*
  712. 将一个枚举对象中的所有数组元素全部展开,最后返回一个数组,是一个递归的过程
  713. */
  714.   flatten: function() {
  715.     return this.inject([], function(array, value) {
  716.       return array.concat(value.constructor == Array ?
  717.         value.flatten() : [value]);
  718.     });
  719.   },
  720. /*
  721. 从数组中删除参数指定的元素,返回删除后的结果
  722. */
  723.   without: function() {
  724.     var values = $A(arguments);
  725.     return this.select(function(value) {
  726.       return !values.include(value);
  727.     });
  728.   },
  729. /*
  730. 返回一个元素在数组中的索引
  731. */
  732.   indexOf: function(object) {
  733.     for (var i = 0; i < this.length; i++)
  734.       if (this[i] == object) return i;
  735.     return -1;
  736.   },
  737. /*
  738. 将数组元素顺序逆转,inline用于确保是数组
  739. */
  740.   reverse: function(inline) {
  741.     return (inline !== false ? this : this.toArray())._reverse();
  742.   },
  743. /*
  744. 取出数组的第一个元素并返回
  745. */
  746.   shift: function() {
  747.     var result = this[0];
  748.     for (var i = 0; i < this.length - 1; i++)
  749.       this[i] = this[i + 1];
  750.     this.length--;
  751.     return result;
  752.   },
  753. /*
  754. 返回数组的字符串表示
  755. */
  756.   inspect: function() {
  757.     return '[' + this.map(Object.inspect).join(', ') + ']';
  758.   }
  759. });
  760. /*
  761. 定义哈希对象的通用操作
  762. */
  763. var Hash = {
  764. /*
  765. 实现可枚举接口。
  766. 对hash对象中的每个元素进行迭代操作,迭代器被认为接收一个数组参数,数组的第一个元素是key,第
  767. 二个元素是value
  768. 同时,此数组对象还增加了两个属性key和value。分表表示键和值。
  769. */
  770.   _each: function(iterator) {
  771.     for (key in this) {
  772.       var value = this[key];
  773.       if (typeof value == 'function'continue;//不处理方法
  774.       var pair = [key, value];
  775.       pair.key = key;
  776.       pair.value = value;
  777.       iterator(pair);
  778.     }
  779.   },
  780. /*
  781. 返回所有的键组成的数组
  782. */
  783.   keys: function() {
  784.     return this.pluck('key');
  785.   },
  786. /*
  787. 返回所有的值组成的数组
  788. */
  789.   values: function() {
  790.     return this.pluck('value');
  791.   },
  792. /*
  793. 将两个hash对象合并,如果键相同,则用参数中相应键对应的值覆盖调用者的。
  794. */
  795.   merge: function(hash) {
  796.     return $H(hash).inject($H(this), function(mergedHash, pair) {
  797.       mergedHash[pair.key] = pair.value;
  798.       return mergedHash;
  799.     });
  800.   },
  801. /*
  802. 将hash对象转换为查询字符串表示的形式
  803. */
  804.   toQueryString: function() {
  805.     return this.map(function(pair) {
  806.       return pair.map(encodeURIComponent).join('=');
  807.     }).join('&');
  808.   },
  809. /*
  810. 获取hash对象的字符串表示
  811. */
  812.   inspect: function() {
  813.     return '#<Hash:{' + this.map(function(pair) {
  814.       return pair.map(Object.inspect).join(': ');
  815.     }).join(', ') + '}>';
  816.   }
  817. }
  818. /*
  819. 将一个对象转换为哈希对象,对象的属性名(方法名)作为key,值作为value
  820. 同时hash对象也是一个可枚举对象
  821. */
  822. function $H(object) {
  823. /*
  824. object || {}使得参数为空时也能够创建一个hash对象
  825. */
  826. var hash = Object.extend({}, object || {});
  827. Object.extend(hash, Enumerable);
  828. Object.extend(hash, Hash);
  829. return hash;
  830. }
  831. /*
  832. 又一个实现Enumerable接口的对象。
  833. 有了这个类,基本上就可以完全避免使用for循环了,一个例子,计算1到100的和:
  834. 传统写法:
  835. var s=0;
  836. for(var i=0;i<=100;i++){
  837. s+=i;
  838. }
  839. document.write(s);
  840. 使用ObjectRange:
  841. var s=$R(0,100,false).inject(0,function(s,i){
  842. return s+i;
  843. });
  844. document.write(s);
  845. */
  846. ObjectRange = Class.create();
  847. Object.extend(ObjectRange.prototype, Enumerable);
  848. Object.extend(ObjectRange.prototype, {
  849. /*
  850. 构造函数,start表示开始的位置,end表示结束的位置,exclusive表示是否排除最后一个索引位置
  851. exclusive=true时对应于:
  852. for(var i=start;i<end;i++){
  853. //语句
  854. }
  855. exclusive=false时对应于:
  856. for(var i=start;i<=end;i++){
  857. //语句
  858. }
  859. */
  860.   initialize: function(start, end, exclusive) {
  861.     this.start = start;
  862.     this.end = end;
  863.     this.exclusive = exclusive;
  864.   },
  865. /*
  866. 实现枚举接口的_each方法
  867. 相当于:
  868. for(var i=start;i<end;i++){
  869. iterator(i);
  870. }
  871. */
  872.   _each: function(iterator) {
  873.     var value = this.start;
  874.     do {
  875.       iterator(value);
  876.       value = value.succ();
  877.     } while (this.include(value));
  878.   },
  879. /*
  880. 判断是否包含指定的索引
  881. */
  882.   include: function(value) {
  883.     if (value < this.start)
  884.       return false;
  885.     if (this.exclusive)
  886.       return value < this.end;
  887.     return value <= this.end;
  888.   }
  889. });
  890. /*
  891. 做一个快速链接,用于生成ObjectRange对象
  892. */
  893. var $R = function(start, end, exclusive) {
  894.   return new ObjectRange(start, end, exclusive);
  895. }
  896. /*
  897. 封装XMLHttpRequest的相关操作
  898. */
  899. var Ajax = {
  900. //浏览器兼容的获取XMLHttpRequest对象的函数
  901. getTransport: function() {
  902. return Try.these(
  903. function() {return new ActiveXObject('Msxml2.XMLHTTP')},
  904. function() {return new ActiveXObject('Microsoft.XMLHTTP')},
  905. function() {return new XMLHttpRequest()}
  906. ) || false;
  907. },
  908. //当前激活的请求数目
  909. activeRequestCount: 0
  910. }
  911. /*
  912. Ajax的返回值
  913. */
  914. Ajax.Responders = {
  915.   responders: [],
  916. //实现枚举接口的_each方法
  917.   _each: function(iterator) {
  918.     this.responders._each(iterator);
  919.   },
  920.   register: function(responderToAdd) {
  921.     if (!this.include(responderToAdd))
  922.       this.responders.push(responderToAdd);
  923.   },
  924.   unregister: function(responderToRemove) {
  925.     this.responders = this.responders.without(responderToRemove);
  926.   },
  927.   dispatch: function(callback, request, transport, json) {
  928.     this.each(function(responder) {
  929.       if (responder[callback] && typeof responder[callback] == 'function') {
  930.         try {
  931.           responder[callback].apply(responder, [request, transport, json]);
  932.         } catch (e) {}
  933.       }
  934.     });
  935.   }
  936. };
  937. /*
  938. 让Ajax.Responders可枚举迭代
  939. */
  940. Object.extend(Ajax.Responders, Enumerable);
  941. Ajax.Responders.register({
  942. onCreate: function() {
  943. Ajax.activeRequestCount++;
  944. },
  945. onComplete: function() {
  946. Ajax.activeRequestCount--;
  947. }
  948. });
  949. //定义Ajax的基类
  950. Ajax.Base = function() {};
  951. Ajax.Base.prototype = {
  952. /*
  953. 设置XMLHttp调用的参数,提供了默认值:method:'post',异步,无参数
  954. */
  955.   setOptions: function(options) {
  956.     this.options = {
  957.       method:       'post',
  958.       asynchronous: true,
  959.       parameters:   ''
  960.     }
  961.     Object.extend(this.options, options || {});
  962.   },
  963. /*
  964. 判断请求是否成功
  965. */
  966.   responseIsSuccess: function() {
  967.     return this.transport.status == undefined
  968.         || this.transport.status == 0
  969.         || (this.transport.status >= 200 && this.transport.status < 300);
  970.   },
  971. /*
  972. 判断请求是否失败
  973. */
  974.   responseIsFailure: function() {
  975.     return !this.responseIsSuccess();
  976.   }
  977. }
  978. //定义一个Ajax请求类
  979. Ajax.Request = Class.create();
  980. Ajax.Request.Events =
  981.   ['Uninitialized''Loading''Loaded''Interactive''Complete'];
  982. Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
  983.   initialize: function(url, options) {
  984.     this.transport = Ajax.getTransport();
  985.     this.setOptions(options);
  986.     this.request(url);
  987.   },
  988.   request: function(url) {
  989.     var parameters = this.options.parameters || '';
  990.     if (parameters.length > 0) parameters += '&_=';
  991.     try {
  992.       this.url = url;
  993.       if (this.options.method == 'get' && parameters.length > 0)
  994.         this.url += (this.url.match(//?/) ? '&' : '?') + parameters;
  995.       Ajax.Responders.dispatch('onCreate'thisthis.transport);
  996.       this.transport.open(this.options.method, this.url,
  997.         this.options.asynchronous);
  998.       if (this.options.asynchronous) {
  999.         this.transport.onreadystatechange = this.onStateChange.bind(this);
  1000.         setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
  1001.       }
  1002.       this.setRequestHeaders();
  1003.       var body = this.options.postBody ? this.options.postBody : parameters;
  1004.       this.transport.send(this.options.method == 'post' ? body : null);
  1005.     } catch (e) {
  1006.       this.dispatchException(e);
  1007.     }
  1008.   },
  1009.   setRequestHeaders: function() {
  1010.     var requestHeaders =
  1011.       ['X-Requested-With''XMLHttpRequest',
  1012.        'X-Prototype-Version', Prototype.Version];
  1013.     if (this.options.method == 'post') {
  1014.       requestHeaders.push('Content-type',
  1015.         'application/x-www-form-urlencoded');
  1016.       /* Force "Connection: close" for Mozilla browsers to work around
  1017.        * a bug where XMLHttpReqeuest sends an incorrect Content-length
  1018.        * header. See Mozilla Bugzilla #246651.
  1019.        */
  1020.       if (this.transport.overrideMimeType)
  1021.         requestHeaders.push('Connection''close');
  1022.     }
  1023.     if (this.options.requestHeaders)
  1024.       requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
  1025.     for (var i = 0; i < requestHeaders.length; i += 2)
  1026.       this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
  1027.   },
  1028.   onStateChange: function() {
  1029.     var readyState = this.transport.readyState;
  1030.     if (readyState != 1)
  1031.       this.respondToReadyState(this.transport.readyState);
  1032.   },
  1033.   header: function(name) {
  1034.     try {
  1035.       return this.transport.getResponseHeader(name);
  1036.     } catch (e) {}
  1037.   },
  1038.   evalJSON: function() {
  1039.     try {
  1040.       return eval(this.header('X-JSON'));
  1041.     } catch (e) {}
  1042.   },
  1043.   evalResponse: function() {
  1044.     try {
  1045.       return eval(this.transport.responseText);
  1046.     } catch (e) {
  1047.       this.dispatchException(e);
  1048.     }
  1049.   },
  1050.   respondToReadyState: function(readyState) {
  1051.     var event = Ajax.Request.Events[readyState];
  1052.     var transport = this.transport, json = this.evalJSON();
  1053.     if (event == 'Complete') {
  1054.       try {
  1055.         (this.options['on' + this.transport.status]
  1056.          || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
  1057.          || Prototype.emptyFunction)(transport, json);
  1058.       } catch (e) {
  1059.         this.dispatchException(e);
  1060.       }
  1061.       if ((this.header('Content-type') || '').match(/^text//javascript/i))
  1062.         this.evalResponse();
  1063.     }
  1064.     try {
  1065.       (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
  1066.       Ajax.Responders.dispatch('on' + event, this, transport, json);
  1067.     } catch (e) {
  1068.       this.dispatchException(e);
  1069.     }
  1070.     /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
  1071.     if (event == 'Complete')
  1072.       this.transport.onreadystatechange = Prototype.emptyFunction;
  1073.   },
  1074.   dispatchException: function(exception) {
  1075.     (this.options.onException || Prototype.emptyFunction)(this, exception);
  1076.     Ajax.Responders.dispatch('onException'this, exception);
  1077.   }
  1078. });
  1079. Ajax.Updater = Class.create();
  1080. Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
  1081.   initialize: function(container, url, options) {
  1082.     this.containers = {
  1083.       success: container.success ? $(container.success) : $(container),
  1084.       failure: container.failure ? $(container.failure) :
  1085.         (container.success ? null : $(container))
  1086.     }
  1087.     this.transport = Ajax.getTransport();
  1088.     this.setOptions(options);
  1089.     var onComplete = this.options.onComplete || Prototype.emptyFunction;
  1090.     this.options.onComplete = (function(transport, object) {
  1091.       this.updateContent();
  1092.       onComplete(transport, object);
  1093.     }).bind(this);
  1094.     this.request(url);
  1095.   },
  1096.   updateContent: function() {
  1097.     var receiver = this.responseIsSuccess() ?
  1098.       this.containers.success : this.containers.failure;
  1099.     var response = this.transport.responseText;
  1100.     if (!this.options.evalScripts)
  1101.       response = response.stripScripts();
  1102.     if (receiver) {
  1103.       if (this.options.insertion) {
  1104.         new this.options.insertion(receiver, response);
  1105.       } else {
  1106.         Element.update(receiver, response);
  1107.       }
  1108.     }
  1109.     if (this.responseIsSuccess()) {
  1110.       if (this.onComplete)
  1111.         setTimeout(this.onComplete.bind(this), 10);
  1112.     }
  1113.   }
  1114. });
  1115. Ajax.PeriodicalUpdater = Class.create();
  1116. Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
  1117.   initialize: function(container, url, options) {
  1118.     this.setOptions(options);
  1119.     this.onComplete = this.options.onComplete;
  1120.     this.frequency = (this.options.frequency || 2);
  1121.     this.decay = (this.options.decay || 1);
  1122.     this.updater = {};
  1123.     this.container = container;
  1124.     this.url = url;
  1125.     this.start();
  1126.   },
  1127.   start: function() {
  1128.     this.options.onComplete = this.updateComplete.bind(this);
  1129.     this.onTimerEvent();
  1130.   },
  1131.   stop: function() {
  1132.     this.updater.onComplete = undefined;
  1133.     clearTimeout(this.timer);
  1134.     (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  1135.   },
  1136.   updateComplete: function(request) {
  1137.     if (this.options.decay) {
  1138.       this.decay = (request.responseText == this.lastText ?
  1139.         this.decay * this.options.decay : 1);
  1140.       this.lastText = request.responseText;
  1141.     }
  1142.     this.timer = setTimeout(this.onTimerEvent.bind(this),
  1143.       this.decay * this.frequency * 1000);
  1144.   },
  1145.   onTimerEvent: function() {
  1146.     this.updater = new Ajax.Updater(this.container, this.url, this.options);
  1147.   }
  1148. });
  1149. document.getElementsByClassName = function(className, parentElement) {
  1150.   var children = ($(parentElement) || document.body).getElementsByTagName('*');
  1151.   return $A(children).inject([], function(elements, child) {
  1152.     if (child.className.match(new RegExp("(^|//s)" + className + "(//s|$)")))
  1153.       elements.push(child);
  1154.     return elements;
  1155.   });
  1156. }
  1157. /*--------------------------------------------------------------------------*/
  1158. //定义一些Html节点通用的操作
  1159. if (!window.Element) {
  1160.   var Element = new Object();
  1161. }
  1162. Object.extend(Element, {
  1163. /*
  1164. 判断节点是否可见
  1165. */
  1166.   visible: function(element) {
  1167.     return $(element).style.display != 'none';
  1168.   },
  1169. /*
  1170. 切换节点的可见状态
  1171. */
  1172.   toggle: function() {
  1173.     for (var i = 0; i < arguments.length; i++) {
  1174.       var element = $(arguments[i]);
  1175.       Element[Element.visible(element) ? 'hide' : 'show'](element);
  1176.     }
  1177.   },
  1178. /*
  1179. 隐藏参数所指定的节点
  1180. */
  1181.   hide: function() {
  1182.     for (var i = 0; i < arguments.length; i++) {
  1183.       var element = $(arguments[i]);
  1184.       element.style.display = 'none';
  1185.     }
  1186.   },
  1187. /*
  1188. 显示参数所指定的节点
  1189. */
  1190.   show: function() {
  1191.     for (var i = 0; i < arguments.length; i++) {
  1192.       var element = $(arguments[i]);
  1193.       element.style.display = '';
  1194.     }
  1195.   },
  1196. /*
  1197. 删除一个节点
  1198. */
  1199.   remove: function(element) {
  1200.     element = $(element);
  1201.     element.parentNode.removeChild(element);
  1202.   },
  1203. /*
  1204. 用指定html填充element表示的节点
  1205. setTimeout是极具技巧的用法,让人惊叹。
  1206. update函数之所以会取代:element.innerHTML=html的用法,主要因为它实现了浏览器的兼容性:
  1207. (1)对于IE,如果给innerHTML赋值的字符串中含有脚本标记,脚本是被忽略的,不起作用;而firefox
  1208. 则会执行脚本;
  1209. (2)setTimeout使得可以在函数内可以通过eval定义全局函数,这是由于setTimeout的默认空间就是全
  1210. 局空间决定的(它是window对象的方法,而所有全局变量和全局函数实际上都是window对象的属性和方法
  1211. )。
  1212. */
  1213.   update: function(element, html) {
  1214.     $(element).innerHTML = html.stripScripts();
  1215.     setTimeout(function() {html.evalScripts()}, 10);
  1216.   },
  1217. //获取节点的高度
  1218.   getHeight: function(element) {
  1219.     element = $(element);
  1220.     return element.offsetHeight;
  1221.   },
  1222. //获取一个元素的class,返回一个数组,包括了所有的class名称,ClassNames后面实现
  1223.   classNames: function(element) {
  1224.     return new Element.ClassNames(element);
  1225.   },
  1226. /*判断一个元素是否具有指定的class值*/
  1227.   hasClassName: function(element, className) {
  1228.     if (!(element = $(element))) return;
  1229.     return Element.classNames(element).include(className);
  1230.   },
  1231. //为一个节点添加class名称
  1232.   addClassName: function(element, className) {
  1233.     if (!(element = $(element))) return;
  1234.     return Element.classNames(element).add(className);
  1235.   },
  1236. //从一个节点移除一个class名称
  1237.   removeClassName: function(element, className) {
  1238.     if (!(element = $(element))) return;
  1239.     return Element.classNames(element).remove(className);
  1240.   },
  1241.   // removes whitespace-only text node children
  1242.   //删除空白文本节点,使用此方法能够使得childNodes属性对所有浏览器兼容,否则ie不认为空白文本
  1243. 节点是子节点。而firefox则会认为这些节点是子节点。
  1244.   cleanWhitespace: function(element) {
  1245.     element = $(element);
  1246.     for (var i = 0; i < element.childNodes.length; i++) {
  1247.       var node = element.childNodes[i];
  1248.       if (node.nodeType == 3 && !//S/.test(node.nodevalue))
  1249.         Element.remove(node);
  1250.     }
  1251.   },
  1252. //判断一个节点是否为空,如果全是空白内容,也认为空
  1253.   empty: function(element) {
  1254.     return $(element).innerHTML.match(/^/s*$/);
  1255.   },
  1256. /*
  1257. 将滚动条滚动到指定节点的位置
  1258. */
  1259.   scrollTo: function(element) {
  1260.     element = $(element);
  1261.     var x = element.x ? element.x : element.offsetLeft,
  1262.         y = element.y ? element.y : element.offsetTop;
  1263.     window.scrollTo(x, y);
  1264.   },
  1265. /*
  1266. 得到指定节点的指定样式的绝对值。
  1267. 即可以获得继承得到的样式。
  1268. */
  1269.   getStyle: function(element, style) {
  1270.     element = $(element);
  1271.     var value = element.style[style.camelize()];
  1272.     if (!value) {
  1273.       if (document.defaultView && document.defaultView.getComputedStyle) {
  1274.         var css = document.defaultView.getComputedStyle(element, null);
  1275.         value = css ? css.getPropertyvalue(style) : null;
  1276.       } else if (element.currentStyle) {
  1277.         value = element.currentStyle[style.camelize()];
  1278.       }
  1279.     }
  1280.     if (window.opera && ['left''top''right''bottom'].include(style))
  1281.       if (Element.getStyle(element, 'position') == 'static') value = 'auto';
  1282.     return value == 'auto' ? null : value;
  1283.   },
  1284. /*
  1285. 设置指定节点的样式,这里可以由style参数同时指定多个属性
  1286. 例如:
  1287. Element.setStyle($("someElement"),{color:'#ff0000',background-color:'#000000'});
  1288. 就将指定节点的样式设置为红字黑底
  1289. */
  1290.   setStyle: function(element, style) {
  1291.     element = $(element);
  1292.     for (name in style)
  1293.       element.style[name.camelize()] = style[name];
  1294.   },
  1295. /*
  1296. 返回节点的宽度和高度,以{width:xx,height:xx}形式返回。
  1297. 该方法使得无论节点可见与否,都能够获取其显示时的大小。
  1298. */
  1299.   getDimensions: function(element) {
  1300.     element = $(element);
  1301.     if (Element.getStyle(element, 'display') != 'none')
  1302.       return {width: element.offsetWidth, height: element.offsetHeight};
  1303.     // All *Width and *Height properties give 0 on elements with display none,
  1304.     // so enable the element temporarily
  1305.     var els = element.style;
  1306.     var originalVisibility = els.visibility;
  1307.     var originalPosition = els.position;
  1308.     els.visibility = 'hidden';
  1309.     els.position = 'absolute';
  1310.     els.display = '';
  1311.     var originalWidth = element.clientWidth;
  1312.     var originalHeight = element.clientHeight;
  1313.     els.display = 'none';
  1314.     els.position = originalPosition;
  1315.     els.visibility = originalVisibility;
  1316.     return {width: originalWidth, height: originalHeight};
  1317.   },
  1318. /*
  1319. 使的元素相对定位
  1320. */
  1321.   makePositioned: function(element) {
  1322.     element = $(element);
  1323.     var pos = Element.getStyle(element, 'position');
  1324.     if (pos == 'static' || !pos) {
  1325.       element._madePositioned = true;
  1326.       element.style.position = 'relative';
  1327.       // Opera returns the offset relative to the positioning context, when an
  1328.       // element is position relative but top and left have not been defined
  1329.       if (window.opera) {
  1330.         element.style.top = 0;
  1331.         element.style.left = 0;
  1332.       }
  1333.     }
  1334.   },
  1335. /*
  1336. 取消节点的相对定位。
  1337. */
  1338.   undoPositioned: function(element) {
  1339.     element = $(element);
  1340.     if (element._madePositioned) {
  1341.       element._madePositioned = undefined;
  1342.       element.style.position =
  1343.         element.style.top =
  1344.         element.style.left =
  1345.         element.style.bottom =
  1346.         element.style.right = '';
  1347.     }
  1348.   },
  1349. /*
  1350. */
  1351.   makeClipping: function(element) {
  1352.     element = $(element);
  1353.     if (element._overflow) return;
  1354.     element._overflow = element.style.overflow;
  1355.     if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
  1356.       element.style.overflow = 'hidden';
  1357.   },
  1358.   undoClipping: function(element) {
  1359.     element = $(element);
  1360.     if (element._overflow) return;
  1361.     element.style.overflow = element._overflow;
  1362.     element._overflow = undefined;
  1363.   }
  1364. });
  1365. var Toggle = new Object();
  1366. Toggle.display = Element.toggle;
  1367. /*--------------------------------------------------------------------------*/
  1368. Abstract.Insertion = function(adjacency) {
  1369.   this.adjacency = adjacency;
  1370. }
  1371. Abstract.Insertion.prototype = {
  1372.   initialize: function(element, content) {
  1373.     this.element = $(element);
  1374.     this.content = content.stripScripts();
  1375.     if (this.adjacency && this.element.insertAdjacentHTML) {
  1376.       try {
  1377.         this.element.insertAdjacentHTML(this.adjacency, this.content);
  1378.       } catch (e) {
  1379.         if (this.element.tagName.toLowerCase() == 'tbody') {
  1380.           this.insertContent(this.contentFromAnonymousTable());
  1381.         } else {
  1382.           throw e;
  1383.         }
  1384.       }
  1385.     } else {
  1386.       this.range = this.element.ownerDocument.createRange();
  1387.       if (this.initializeRange) this.initializeRange();
  1388.       this.insertContent([this.range.createContextualFragment(this.content)]);
  1389.     }
  1390.     setTimeout(function() {content.evalScripts()}, 10);
  1391.   },
  1392.   contentFromAnonymousTable: function() {
  1393.     var div = document.createElement('div');
  1394.     div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
  1395.     return $A(div.childNodes[0].childNodes[0].childNodes);
  1396.   }
  1397. }
  1398. var Insertion = new Object();
  1399. Insertion.Before = Class.create();
  1400. Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
  1401.   initializeRange: function() {
  1402.     this.range.setStartBefore(this.element);
  1403.   },
  1404.   insertContent: function(fragments) {
  1405.     fragments.each((function(fragment) {
  1406.       this.element.parentNode.insertBefore(fragment, this.element);
  1407.     }).bind(this));
  1408.   }
  1409. });
  1410. Insertion.Top = Class.create();
  1411. Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
  1412.   initializeRange: function() {
  1413.     this.range.selectNodeContents(this.element);
  1414.     this.range.collapse(true);
  1415.   },
  1416.   insertContent: function(fragments) {
  1417.     fragments.reverse(false).each((function(fragment) {
  1418.       this.element.insertBefore(fragment, this.element.firstChild);
  1419.     }).bind(this));
  1420.   }
  1421. });
  1422. Insertion.Bottom = Class.create();
  1423. Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
  1424.   initializeRange: function() {
  1425.     this.range.selectNodeContents(this.element);
  1426.     this.range.collapse(this.element);
  1427.   },
  1428.   insertContent: function(fragments) {
  1429.     fragments.each((function(fragment) {
  1430.       this.element.appendChild(fragment);
  1431.     }).bind(this));
  1432.   }
  1433. });
  1434. Insertion.After = Class.create();
  1435. Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
  1436.   initializeRange: function() {
  1437.     this.range.setStartAfter(this.element);
  1438.   },
  1439.   insertContent: function(fragments) {
  1440.     fragments.each((function(fragment) {
  1441.       this.element.parentNode.insertBefore(fragment,
  1442.         this.element.nextSibling);
  1443.     }).bind(this));
  1444.   }
  1445. });
  1446. /*--------------------------------------------------------------------------*/
  1447. Element.ClassNames = Class.create();
  1448. Element.ClassNames.prototype = {
  1449.   initialize: function(element) {
  1450.     this.element = $(element);
  1451.   },
  1452.   _each: function(iterator) {
  1453.     this.element.className.split(//s+/).select(function(name) {
  1454.       return name.length > 0;
  1455.     })._each(iterator);
  1456.   },
  1457.   set: function(className) {
  1458.     this.element.className = className;
  1459.   },
  1460.   add: function(classNameToAdd) {
  1461.     if (this.include(classNameToAdd)) return;
  1462.     this.set(this.toArray().concat(classNameToAdd).join(' '));
  1463.   },
  1464.   remove: function(classNameToRemove) {
  1465.     if (!this.include(classNameToRemove)) return;
  1466.     this.set(this.select(function(className) {
  1467.       return className != classNameToRemove;
  1468.     }).join(' '));
  1469.   },
  1470.   toString: function() {
  1471.     return this.toArray().join(' ');
  1472.   }
  1473. }
  1474. Object.extend(Element.ClassNames.prototype, Enumerable);
  1475. var Field = {
  1476.   clear: function() {
  1477.     for (var i = 0; i < arguments.length; i++)
  1478.       $(arguments[i]).value = '';
  1479.   },
  1480.   focus: function(element) {
  1481.     $(element).focus();
  1482.   },
  1483.   present: function() {
  1484.     for (var i = 0; i < arguments.length; i++)
  1485.       if ($(arguments[i]).value == ''return false;
  1486.     return true;
  1487.   },
  1488.   select: function(element) {
  1489.     $(element).select();
  1490.   },
  1491.   activate: function(element) {
  1492.     element = $(element);
  1493.     element.focus();
  1494.     if (element.select)
  1495.       element.select();
  1496.   }
  1497. }
  1498. /*--------------------------------------------------------------------------*/
  1499. var Form = {
  1500.   serialize: function(form) {
  1501.     var elements = Form.getElements($(form));
  1502.     var queryComponents = new Array();
  1503.     for (var i = 0; i < elements.length; i++) {
  1504.       var queryComponent = Form.Element.serialize(elements[i]);
  1505.       if (queryComponent)
  1506.         queryComponents.push(queryComponent);
  1507.     }
  1508.     return queryComponents.join('&');
  1509.   },
  1510.   getElements: function(form) {
  1511.     form = $(form);
  1512.     var elements = new Array();
  1513.     for (tagName in Form.Element.Serializers) {
  1514.       var tagElements = form.getElementsByTagName(tagName);
  1515.       for (var j = 0; j < tagElements.length; j++)
  1516.         elements.push(tagElements[j]);
  1517.     }
  1518.     return elements;
  1519.   },
  1520.   getInputs: function(form, typeName, name) {
  1521.     form = $(form);
  1522.     var inputs = form.getElementsByTagName('input');
  1523.     if (!typeName && !name)
  1524.       return inputs;
  1525.     var matchingInputs = new Array();
  1526.     for (var i = 0; i < inputs.length; i++) {
  1527.       var input = inputs[i];
  1528.       if ((typeName && input.type != typeName) ||
  1529.           (name && input.name != name))
  1530.         continue;
  1531.       matchingInputs.push(input);
  1532.     }
  1533.     return matchingInputs;
  1534.   },
  1535.   disable: function(form) {
  1536.     var elements = Form.getElements(form);
  1537.     for (var i = 0; i < elements.length; i++) {
  1538.       var element = elements[i];
  1539.       element.blur();
  1540.       element.disabled = 'true';
  1541.     }
  1542.   },
  1543.   enable: function(form) {
  1544.     var elements = Form.getElements(form);
  1545.     for (var i = 0; i < elements.length; i++) {
  1546.       var element = elements[i];
  1547.       element.disabled = '';
  1548.     }
  1549.   },
  1550.   findFirstElement: function(form) {
  1551.     return Form.getElements(form).find(function(element) {
  1552.       return element.type != 'hidden' && !element.disabled &
  1553.         ['input''select''textarea'].include(element.tagName.toLowerCase());
  1554.     });
  1555.   },
  1556.   focusFirstElement: function(form) {
  1557.     Field.activate(Form.findFirstElement(form));
  1558.   },
  1559.   reset: function(form) {
  1560.     $(form).reset();
  1561.   }
  1562. }
  1563. Form.Element = {
  1564.   serialize: function(element) {
  1565.     element = $(element);
  1566.     var method = element.tagName.toLowerCase();
  1567.     var parameter = Form.Element.Serializers[method](element);
  1568.     if (parameter) {
  1569.       var key = encodeURIComponent(parameter[0]);
  1570.       if (key.length == 0) return;
  1571.       if (parameter[1].constructor != Array)
  1572.         parameter[1] = [parameter[1]];
  1573.       return parameter[1].map(function(value) {
  1574.         return key + '=' + encodeURIComponent(value);
  1575.       }).join('&');
  1576.     }
  1577.   },
  1578.   getvalue: function(element) {
  1579.     element = $(element);
  1580.     var method = element.tagName.toLowerCase();
  1581.     var parameter = Form.Element.Serializers[method](element);
  1582.     if (parameter)
  1583.       return parameter[1];
  1584.   }
  1585. }
  1586. Form.Element.Serializers = {
  1587. //对input标记序列化
  1588.   input: function(element) {
  1589.     switch (element.type.toLowerCase()) {
  1590.       case 'submit':
  1591.       case 'hidden':
  1592.       case 'password':
  1593.       case 'text':
  1594.         return Form.Element.Serializers.textarea(element);
  1595.       case 'checkbox':
  1596.       case 'radio':
  1597.         return Form.Element.Serializers.inputSelector(element);
  1598.     }
  1599.     return false;
  1600.   },
  1601. //对单选框和复选框序列化
  1602.   inputSelector: function(element) {
  1603.     if (element.checked)
  1604.       return [element.name, element.value];
  1605.   },
  1606. //对文本框序列化
  1607.   textarea: function(element) {
  1608.     return [element.name, element.value];
  1609.   },
  1610. //对下拉列表框序列化
  1611.   select: function(element) {
  1612.     return Form.Element.Serializers[element.type == 'select-one' ?
  1613.       'selectOne' : 'selectMany'](element);
  1614.   },
  1615. //对单选下拉列表框序列化
  1616.   selectOne: function(element) {
  1617.     var value = '', opt, index = element.selectedIndex;
  1618.     if (index >= 0) {
  1619.       opt = element.options[index];
  1620.       value = opt.value;
  1621.       if (!value && !('value' in opt))
  1622.         value = opt.text;
  1623.     }
  1624.     return [element.name, value];
  1625.   },
  1626. //对多选下拉列表框序列化
  1627.   selectMany: function(element) {
  1628.     var value = new Array();
  1629.     for (var i = 0; i < element.length; i++) {
  1630.       var opt = element.options[i];
  1631.       if (opt.selected) {
  1632.         var optvalue = opt.value;
  1633.         if (!optvalue && !('value' in opt))
  1634.           optvalue = opt.text;
  1635.         value.push(optvalue);
  1636.       }
  1637.     }
  1638.     return [element.name, value];
  1639.   }
  1640. }
  1641. /*--------------------------------------------------------------------------*/
  1642. var $F = Form.Element.getvalue;
  1643. /*--------------------------------------------------------------------------*/
  1644. Abstract.TimedObserver = function() {}
  1645. Abstract.TimedObserver.prototype = {
  1646.   initialize: function(element, frequency, callback) {
  1647.     this.frequency = frequency;
  1648.     this.element   = $(element);
  1649.     this.callback  = callback;
  1650.     this.lastvalue = this.getvalue();
  1651.     this.registerCallback();
  1652.   },
  1653.   registerCallback: function() {
  1654.     setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  1655.   },
  1656.   onTimerEvent: function() {
  1657.     var value = this.getvalue();
  1658.     if (this.lastvalue != value) {
  1659.       this.callback(this.element, value);
  1660.       this.lastvalue = value;
  1661.     }
  1662.   }
  1663. }
  1664. Form.Element.Observer = Class.create();
  1665. Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  1666.   getvalue: function() {
  1667.     return Form.Element.getvalue(this.element);
  1668.   }
  1669. });
  1670. Form.Observer = Class.create();
  1671. Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  1672.   getvalue: function() {
  1673.     return Form.serialize(this.element);
  1674.   }
  1675. });
  1676. /*--------------------------------------------------------------------------*/
  1677. Abstract.EventObserver = function() {}
  1678. Abstract.EventObserver.prototype = {
  1679.   initialize: function(element, callback) {
  1680.     this.element  = $(element);
  1681.     this.callback = callback;
  1682.     this.lastvalue = this.getvalue();
  1683.     if (this.element.tagName.toLowerCase() == 'form')
  1684.       this.registerFormCallbacks();
  1685.     else
  1686.       this.registerCallback(this.element);
  1687.   },
  1688.   onElementEvent: function() {
  1689.     var value = this.getvalue();
  1690.     if (this.lastvalue != value) {
  1691.       this.callback(this.element, value);
  1692.       this.lastvalue = value;
  1693.     }
  1694.   },
  1695.   registerFormCallbacks: function() {
  1696.     var elements = Form.getElements(this.element);
  1697.     for (var i = 0; i < elements.length; i++)
  1698.       this.registerCallback(elements[i]);
  1699.   },
  1700.   registerCallback: function(element) {
  1701.     if (element.type) {
  1702.       switch (element.type.toLowerCase()) {
  1703.         case 'checkbox':
  1704.         case 'radio':
  1705.           Event.observe(element, 'click'this.onElementEvent.bind(this));
  1706.           break;
  1707.         case 'password':
  1708.         case 'text':
  1709.         case 'textarea':
  1710.         case 'select-one':
  1711.         case 'select-multiple':
  1712.           Event.observe(element, 'change'this.onElementEvent.bind(this));
  1713.           break;
  1714.       }
  1715.     }
  1716.   }
  1717. }
  1718. Form.Element.EventObserver = Class.create();
  1719. Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  1720.   getvalue: function() {
  1721.     return Form.Element.getvalue(this.element);
  1722.   }
  1723. });
  1724. Form.EventObserver = Class.create();
  1725. Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  1726.   getvalue: function() {
  1727.     return Form.serialize(this.element);
  1728.   }
  1729. });
  1730. if (!window.Event) {
  1731.   var Event = new Object();
  1732. }
  1733. Object.extend(Event, {
  1734.   KEY_BACKSPACE: 8,
  1735.   KEY_TAB:       9,
  1736.   KEY_RETURN:   13,
  1737.   KEY_ESC:      27,
  1738.   KEY_LEFT:     37,
  1739.   KEY_UP:       38,
  1740.   KEY_RIGHT:    39,
  1741.   KEY_DOWN:     40,
  1742.   KEY_DELETE:   46,
  1743.   element: function(event) {
  1744.     return event.target || event.srcElement;
  1745.   },
  1746.   isLeftClick: function(event) {
  1747.     return (((event.which) && (event.which == 1)) ||
  1748.             ((event.button) && (event.button == 1)));
  1749.   },
  1750.   pointerX: function(event) {
  1751.     return event.pageX || (event.clientX +
  1752.       (document.documentElement.scrollLeft || document.body.scrollLeft));
  1753.   },
  1754.   pointerY: function(event) {
  1755.     return event.pageY || (event.clientY +
  1756.       (document.documentElement.scrollTop || document.body.scrollTop));
  1757.   },
  1758.   stop: function(event) {
  1759.     if (event.preventDefault) {
  1760.       event.preventDefault();
  1761.       event.stopPropagation();
  1762.     } else {
  1763.       event.returnvalue = false;
  1764.       event.cancelBubble = true;
  1765.     }
  1766.   },
  1767.   // find the first node with the given tagName, starting from the
  1768.   // node the event was triggered on; traverses the DOM upwards
  1769.   findElement: function(event, tagName) {
  1770.     var element = Event.element(event);
  1771.     while (element.parentNode && (!element.tagName ||
  1772.         (element.tagName.toUpperCase() != tagName.toUpperCase())))
  1773.       element = element.parentNode;
  1774.     return element;
  1775.   },
  1776.   observers: false,
  1777.   _observeAndCache: function(element, name, observer, useCapture) {
  1778.     if (!this.observers) this.observers = [];
  1779.     if (element.addEventListener) {
  1780.       this.observers.push([element, name, observer, useCapture]);
  1781.       element.addEventListener(name, observer, useCapture);
  1782.     } else if (element.attachEvent) {
  1783.       this.observers.push([element, name, observer, useCapture]);
  1784.       element.attachEvent('on' + name, observer);
  1785.     }
  1786.   },
  1787.   unloadCache: function() {
  1788.     if (!Event.observers) return;
  1789.     for (var i = 0; i < Event.observers.length; i++) {
  1790.       Event.stopObserving.apply(this, Event.observers[i]);
  1791.       Event.observers[i][0] = null;
  1792.     }
  1793.     Event.observers = false;
  1794.   },
  1795.   observe: function(element, name, observer, useCapture) {
  1796.     var element = $(element);
  1797.     useCapture = useCapture || false;
  1798.     if (name == 'keypress' &
  1799.         (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
  1800.         || element.attachEvent))
  1801.       name = 'keydown';
  1802.     this._observeAndCache(element, name, observer, useCapture);
  1803.   },
  1804.   stopObserving: function(element, name, observer, useCapture) {
  1805.     var element = $(element);
  1806.     useCapture = useCapture || false;
  1807.     if (name == 'keypress' &
  1808.         (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
  1809.         || element.detachEvent))
  1810.       name = 'keydown';
  1811.     if (element.removeEventListener) {
  1812.       element.removeEventListener(name, observer, useCapture);
  1813.     } else if (element.detachEvent) {
  1814.       element.detachEvent('on' + name, observer);
  1815.     }
  1816.   }
  1817. });
  1818. /* prevent memory leaks in IE */
  1819. Event.observe(window, 'unload', Event.unloadCache, false);
  1820. var Position = {
  1821.   // set to true if needed, warning: firefox performance problems
  1822.   // NOT neeeded for page scrolling, only if draggable contained in
  1823.   // scrollable elements
  1824.   includeScrollOffsets: false,
  1825.   // must be called before calling withinIncludingScrolloffset, every time the
  1826.   // page is scrolled
  1827.   prepare: function() {
  1828.     this.deltaX =  window.pageXOffset
  1829.                 || document.documentElement.scrollLeft
  1830.                 || document.body.scrollLeft
  1831.                 || 0;
  1832.     this.deltaY =  window.pageYOffset
  1833.                 || document.documentElement.scrollTop
  1834.                 || document.body.scrollTop
  1835.                 || 0;
  1836.   },
  1837.   realOffset: function(element) {
  1838.     var valueT = 0, valueL = 0;
  1839.     do {
  1840.       valueT += element.scrollTop  || 0;
  1841.       valueL += element.scrollLeft || 0;
  1842.       element = element.parentNode;
  1843.     } while (element);
  1844.     return [valueL, valueT];
  1845.   },
  1846.   cumulativeOffset: function(element) {
  1847.     var valueT = 0, valueL = 0;
  1848.     do {
  1849.       valueT += element.offsetTop  || 0;
  1850.       valueL += element.offsetLeft || 0;
  1851.       element = element.offsetParent;
  1852.     } while (element);
  1853.     return [valueL, valueT];
  1854.   },
  1855.   positionedOffset: function(element) {
  1856.     var valueT = 0, valueL = 0;
  1857.     do {
  1858.       valueT += element.offsetTop  || 0;
  1859.       valueL += element.offsetLeft || 0;
  1860.       element = element.offsetParent;
  1861.       if (element) {
  1862.         p = Element.getStyle(element, 'position');
  1863.         if (p == 'relative' || p == 'absolute'break;
  1864.       }
  1865.     } while (element);
  1866.     return [valueL, valueT];
  1867.   },
  1868.   offsetParent: function(element) {
  1869.     if (element.offsetParent) return element.offsetParent;
  1870.     if (element == document.body) return element;
  1871.     while ((element = element.parentNode) && element != document.body)
  1872.       if (Element.getStyle(element, 'position') != 'static')
  1873.         return element;
  1874.     return document.body;
  1875.   },
  1876.   // caches x/y coordinate pair to use with overlap
  1877.   within: function(element, x, y) {
  1878.     if (this.includeScrollOffsets)
  1879.       return this.withinIncludingScrolloffsets(element, x, y);
  1880.     this.xcomp = x;
  1881.     this.ycomp = y;
  1882.     this.offset = this.cumulativeOffset(element);
  1883.     return (y >= this.offset[1] &
  1884.             y <  this.offset[1] + element.offsetHeight &
  1885.             x >= this.offset[0] &
  1886.             x <  this.offset[0] + element.offsetWidth);
  1887.   },
  1888.   withinIncludingScrolloffsets: function(element, x, y) {
  1889.     var offsetcache = this.realOffset(element);
  1890.     this.xcomp = x + offsetcache[0] - this.deltaX;
  1891.     this.ycomp = y + offsetcache[1] - this.deltaY;
  1892.     this.offset = this.cumulativeOffset(element);
  1893.     return (this.ycomp >= this.offset[1] &
  1894.             this.ycomp <  this.offset[1] + element.offsetHeight &
  1895.             this.xcomp >= this.offset[0] &
  1896.             this.xcomp <  this.offset[0] + element.offsetWidth);
  1897.   },
  1898.   // within must be called directly before
  1899.   overlap: function(mode, element) {
  1900.     if (!mode) return 0;
  1901.     if (mode == 'vertical')
  1902.       return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
  1903.         element.offsetHeight;
  1904.     if (mode == 'horizontal')
  1905.       return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
  1906.         element.offsetWidth;
  1907.   },
  1908.   clone: function(source, target) {
  1909.     source = $(source);
  1910.     target = $(target);
  1911.     target.style.position = 'absolute';
  1912.     var offsets = this.cumulativeOffset(source);
  1913.     target.style.top    = offsets[1] + 'px';
  1914.     target.style.left   = offsets[0] + 'px';
  1915.     target.style.width  = source.offsetWidth + 'px';
  1916.     target.style.height = source.offsetHeight + 'px';
  1917.   },
  1918.   page: function(forElement) {
  1919.     var valueT = 0, valueL = 0;
  1920.     var element = forElement;
  1921.     do {
  1922.       valueT += element.offsetTop  || 0;
  1923.       valueL += element.offsetLeft || 0;
  1924.       // Safari fix
  1925.       if (element.offsetParent==document.body)
  1926.         if (Element.getStyle(element,'position')=='absolute'break;
  1927.     } while (element = element.offsetParent);
  1928.     element = forElement;
  1929.     do {
  1930.       valueT -= element.scrollTop  || 0;
  1931.       valueL -= element.scrollLeft || 0;
  1932.     } while (element = element.parentNode);
  1933.     return [valueL, valueT];
  1934.   },
  1935.   clone: function(source, target) {
  1936.     var options = Object.extend({
  1937.       setLeft:    true,
  1938.       setTop:     true,
  1939.       setWidth:   true,
  1940.       setHeight:  true,
  1941.       offsetTop:  0,
  1942.       offsetLeft: 0
  1943.     }, arguments[2] || {})
  1944.     // find page position of source
  1945.     source = $(source);
  1946.     var p = Position.page(source);
  1947.     // find coordinate system to use
  1948.     target = $(target);
  1949.     var delta = [0, 0];
  1950.     var parent = null;
  1951.     // delta [0,0] will do fine with position: fixed elements,
  1952.     // position:absolute needs offsetParent deltas
  1953.     if (Element.getStyle(target,'position') == 'absolute') {
  1954.       parent = Position.offsetParent(target);
  1955.       delta = Position.page(parent);
  1956.     }
  1957.     // correct by body offsets (fixes Safari)
  1958.     if (parent == document.body) {
  1959.       delta[0] -= document.body.offsetLeft;
  1960.       delta[1] -= document.body.offsetTop;
  1961.     }
  1962.     // set position
  1963.     if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 
  1964. 'px';
  1965.     if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
  1966.     if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
  1967.     if(options.setHeight) target.style.height = source.offsetHeight + 'px';
  1968.   },
  1969.   absolutize: function(element) {
  1970.     element = $(element);
  1971.     if (element.style.position == 'absolute'return;
  1972.     Position.prepare();
  1973.     var offsets = Position.positionedOffset(element);
  1974.     var top     = offsets[1];
  1975.     var left    = offsets[0];
  1976.     var width   = element.clientWidth;
  1977.     var height  = element.clientHeight;
  1978.     element._originalLeft   = left - parseFloat(element.style.left  || 0);
  1979.     element._originalTop    = top  - parseFloat(element.style.top || 0);
  1980.     element._originalWidth  = element.style.width;
  1981.     element._originalHeight = element.style.height;
  1982.     element.style.position = 'absolute';
  1983.     element.style.top    = top + 'px';;
  1984.     element.style.left   = left + 'px';;
  1985.     element.style.width  = width + 'px';;
  1986.     element.style.height = height + 'px';;
  1987.   },
  1988.   relativize: function(element) {
  1989.     element = $(element);
  1990.     if (element.style.position == 'relative'return;
  1991.     Position.prepare();
  1992.     element.style.position = 'relative';
  1993.     var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
  1994.     var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
  1995.     element.style.top    = top + 'px';
  1996.     element.style.left   = left + 'px';
  1997.     element.style.height = element._originalHeight;
  1998.     element.style.width  = element._originalWidth;
  1999.   }
  2000. }
  2001. // Safari returns margins on body which is incorrect if the child is absolutely
  2002. // positioned.  For performance reasons, redefine Position.cumulativeOffset for
  2003. // KHTML/WebKit only.
  2004. if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  2005.   Position.cumulativeOffset = function(element) {
  2006.     var valueT = 0, valueL = 0;
  2007.     do {
  2008.       valueT += element.offsetTop  || 0;
  2009.       valueL += element.offsetLeft || 0;
  2010.       if (element.offsetParent == document.body)
  2011.         if (Element.getStyle(element, 'position') == 'absolute'break;
  2012.       element = element.offsetParent;
  2013.     } while (element);
  2014.     return [valueL, valueT];
  2015.   }
  2016. }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值