js 代码
- /**
- * Form.Element.getValue 会经常用到,所以做了一个快捷引用
- * 取得某个表单控件的值,可以简化调用为 $F("username"),真是方便啊
- */
- var $F = Form.Element.getValue;
- /**
- * Abstract.TimedObserver 也没有用 Class.create() 来创建,和Ajax.Base 意图应该一样
- * Abstract.TimedObserver 顾名思义,是套用Observer设计模式来跟踪指定表单元素,
- * 当表单元素的值发生变化的时候,就执行回调函数
- *
- * 我想 Observer 与注册onchange事件相似,不同点在于 onchange 事件是在元素失去焦点的时候才激发。
- * 同样的与 onpropertychange 事件也相似,不过它只关注表单元素的值的变化,而且提供timeout的控制。
- *
- * 除此之外,Observer 的好处大概就在与更面向对象,另外可以动态的更换回调函数,这就比注册事件要灵活一些。
- * Observer 应该可以胜任动态数据校验,或者多个关联下拉选项列表的连动等等
- *
- */
- Abstract.TimedObserver = function() {}
- /**
- * 这个设计和 PeriodicalExecuter 一样,bind 方法是实现的核心
- */
- 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 监视指定表单域的值是否变化
- */
- Form.Element.Observer = Class.create();
- Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({
- getValue: function() {
- return Form.Element.getValue(this.element);
- }
- });
- /**
- * Form.Element.Observer 监视指定表单所有控件的值是否有变化
- */
- Form.Observer = Class.create();
- Form.Observer.prototype = (new Abstract.TimedObserver()).extend({
- getValue: function() {
- return Form.serialize(this.element);
- }
- });
- /*--------------------------------------------------------------------------*/
- /**
- * EventObserver 相比上面的 TimedObserver,是更具主动性的一种监测
- * 它直接为表单控件(根据 type 的不同) 注册相应的事件处理, 只要发现某个控件值发生改变,就执行回调函数
- */
- 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);
- },
- registerCallback: function(element) {
- if (element.type) {
- switch (element.type.toLowerCase()) {
- /**
- * checkbox 和 radio 类型的控件注册 onclick 事件处理
- */
- case 'checkbox':
- case 'radio':
- element.target = this;
- element.prev_onclick = element.onclick || Prototype.emptyFunction;
- /**
- * 相信这里有改进的空间,应该使用其后的 Event对象提供的注册管理功能来统一注册
- */
- element.onclick = function() {
- this.prev_onclick();
- this.target.onElementEvent();
- }
- break;
- /**
- * 其他类型的控件注册 onchange 事件处理
- */
- case 'password':
- case 'text':
- case 'textarea':
- case 'select-one':
- case 'select-multiple':
- element.target = this;
- element.prev_onchange = element.onchange || Prototype.emptyFunction;
- element.onchange = function() {
- this.prev_onchange();
- this.target.onElementEvent();
- }
- break;
- }
- }
- }
- }
- /**
- * 监视指定表单控件
- */
- Form.Element.EventObserver = Class.create();
- Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({
- getValue: function() {
- return Form.Element.getValue(this.element);
- }
- });
- /**
- * 监视指定表单所有控件
- */
- Form.EventObserver = Class.create();
- Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({
- 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)));
- },
- /**
- * click事件时鼠标以页面为基准的x坐标值, 考虑到了滚动条导致的位移差
- */
- pointerX: function(event) {
- return event.pageX || (event.clientX +
- (document.documentElement.scrollLeft || document.body.scrollLeft));
- },
- /**
- * click事件时鼠标以页面为基准的y坐标值, 考虑到了滚动条导致的位移差
- */
- pointerY: function(event) {
- return event.pageY || (event.clientY +
- (document.documentElement.scrollTop || document.body.scrollTop));
- },
- /**
- * 停止冒泡(参见 http://www.quirksmode.org/js/events_order.html) 和阻止浏览器执行与事件相关的默认动作
- * 比如
- * <a href="http://www.google.com" οnclick="Event.stop(event);">google</a>
- * 那么点击该连接,页面并不会执行转向
- */
- stop: function(event) {
- if (event.preventDefault) {
- event.preventDefault();
- event.stopPropagation();
- } else {
- event.returnValue = false;
- }
- },
- // find the first node with the given tagName, starting from the
- // node the event was triggered on; traverses the DOM upwards
- /**
- * 找到事件元素的父级元素中,最接近事件元素且等同于指定标签名的父元素。
- * 如果到达顶级元素(HTML),那么就返回顶级元素
- */
- findElement: function(event, tagName) {
- var element = Event.element(event);
- while (element.parentNode && (!element.tagName ||
- (element.tagName.toUpperCase() != tagName.toUpperCase())))
- element = element.parentNode;
- return element;
- },
- /**
- * 其后的代码封装了事件的注册和反注册,避免ie的内存泄露的bug
- * 参见 http://javascript.weblogsinc.com/en...34000267034921/
- */
- observers: false,
- /**
- * this.observers 的数据格式是一个二维数组,二维的数组分别四个元素分别是
- * [注册事件对象,事件名,事件处理函数,事件处理模式布尔值]
- */
- _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++) {
- /**
- * 这里与 Ajax.Request 对象设置 request header 的代码异曲同工
- */
- Event.stopObserving.apply(this, Event.observers);
- Event.observers[0] = null;
- }
- Event.observers = false;
- },
- /**
- * 注册对象的事件处理,并记录到cache中
- */
- observe: function(element, name, observer, useCapture) {
- var element = $(element);
- useCapture = useCapture || false;
- if (name == 'keypress' &&
- ((navigator.appVersion.indexOf('AppleWebKit') > 0)
- || 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.indexOf('AppleWebKit') > 0)
- || 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 */
- /**
- * 页面onload 的时候取消所有事件注册,避免ie内存泄漏的bug
- */
- Event.observe(window, 'unload', Event.unloadCache, false);
- /**
- * Position 对象也是常用的工具类,提供了获取元素在页面上位置的函数,Drag&Drop的效果一定常会用到
- * 具体的应用参考 script.aculo.us 基于prototype 的实现,尤其是dragdrop.js。
- */
- 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];
- },
- // 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
- /**
- * 调用该方法时,确保首先调用了within方法
- * 如果x,y坐标位于element的空间范围中,那么返回一个小于1的标示位置的值,比如0.5标示该坐标位于element空间的中线上
- */
- 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';
- }
- }