Prototype源码分析2
/*--------------------------------------------------------------------------*/ /** *表单工具类 */ varForm={ /** *将表单元素序列化后的值组合成QueryString的形式 */ serialize:function(form){ varelements=Form.getElements($(form)); varqueryComponents=newArray(); for(vari=0;i<elements.length;i++){ varqueryComponent=Form.Element.serialize(elements[i]); if(queryComponent) queryComponents.push(queryComponent); } returnqueryComponents.join('&'); }, /** *得到表单的所有元素对象 */ getElements:function(form){ form=$(form); varelements=newArray(); for(tagNameinForm.Element.Serializers){ vartagElements=form.getElementsByTagName(tagName); for(varj=0;j<tagElements.length;j++) elements.push(tagElements[j]); } returnelements; }, /** *将指定表单的元素置于不可用状态 */ disable:function(form){ varelements=Form.getElements(form); for(vari=0;i<elements.length;i++){ varelement=elements[i]; element.blur(); element.disable='true'; } }, /** *使表单的第一个非hidden类型而且处于可用状态的元素获得焦点 */ focusFirstElement:function(form){ form=$(form); varelements=Form.getElements(form); for(vari=0;i<elements.length;i++){ varelement=elements[i]; if(element.type!='hidden'&&!element.disabled){ Field.activate(element); break; } } }, /* *重置表单 */ reset:function(form){ $(form).reset(); } } /** *表单元素工具类 */ Form.Element={ /** *返回表单元素的值先序列化再进行URL编码后的值 */ serialize:function(element){ element=$(element); varmethod=element.tagName.toLowerCase(); varparameter=Form.Element.Serializers[method](element); if(parameter) returnencodeURIComponent(parameter[0])+'='+ encodeURIComponent(parameter[1]); }, /** *返回表单元素序列化后的值 */ getValue:function(element){ element=$(element); varmethod=element.tagName.toLowerCase(); varparameter=Form.Element.Serializers[method](element); if(parameter) returnparameter[1]; } } /** *prototype的所谓序列化其实就是将表单的名字和值组合成一个数组 */ Form.Element.Serializers={ input:function(element){ switch(element.type.toLowerCase()){ case'hidden': case'password': case'text': returnForm.Element.Serializers.textarea(element); case'checkbox': case'radio': returnForm.Element.Serializers.inputSelector(element); } returnfalse; }, inputSelector:function(element){ if(element.checked) return[element.name,element.value]; }, textarea:function(element){ return[element.name,element.value]; }, /** *看样子,也不支持多选框(select-multiple) */ select:function(element){ varindex=element.selectedIndex; varvalue=element.options[index].value||element.options[index].text; return[element.name,(index>=0)?value:'']; } } /*--------------------------------------------------------------------------*/ /** *Form.Element.getValue也许会经常用到,所以做了一个快捷引用 */ 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(){ setTimeout(this.onTimerEvent.bind(this),this.frequency*1000); }, onTimerEvent:function(){ varvalue=this.getValue(); if(this.lastValue!=value){ this.callback(this.element,value); this.lastValue=value; } this.registerCallback(); } } /** *Form.Element.Observer和Form.Observer其实是一样的 *注意Form.Observer并不是用来跟踪整个表单的,我想大概只是为了减少书写(这是Ruby的一个设计原则) */ Form.Element.Observer=Class.create(); Form.Element.Observer.prototype=(newAbstract.TimedObserver()).extend({ getValue:function(){ returnForm.Element.getValue(this.element); } }); Form.Observer=Class.create(); Form.Observer.prototype=(newAbstract.TimedObserver()).extend({ getValue:function(){ returnForm.serialize(this.element); } }); /** *根据classattribute的名字得到对象数组,支持multipleclass * */ document.getElementsByClassName=function(className){ varchildren=document.getElementsByTagName('*')||document.all; varelements=newArray(); for(vari=0;i<children.length;i++){ varchild=children[i]; varclassNames=child.className.split(''); for(varj=0;j<classNames.length;j++){ if(classNames[j]==className){ elements.push(child); break; } } } returnelements; } /*--------------------------------------------------------------------------*/ /** *Element就象一个java的工具类,主要用来隐藏/显示/销除对象,以及获取对象的简单属性。 * */ varElement={ toggle:function(){ for(vari=0;i<arguments.length;i++){ varelement=$(arguments[i]); element.style.display= (element.style.display=='none'?'':'none'); } }, hide:function(){ for(vari=0;i<arguments.length;i++){ varelement=$(arguments[i]); element.style.display='none'; } }, show:function(){ for(vari=0;i<arguments.length;i++){ varelement=$(arguments[i]); element.style.display=''; } }, remove:function(element){ element=$(element); element.parentNode.removeChild(element); }, getHeight:function(element){ element=$(element); returnelement.offsetHeight; } } /** *为Element.toggle做了一个符号连接,大概是兼容性的考虑 */ varToggle=newObject(); Toggle.display=Element.toggle; /*--------------------------------------------------------------------------*/ /** *动态插入内容的实现,MS的Jscript实现中对象有一个insertAdjacentHTML方法(http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/insertadjacenthtml.asp) *这里算是一个对象形式的封装。 */ Abstract.Insertion=function(adjacency){ this.adjacency=adjacency; } Abstract.Insertion.prototype={ initialize:function(element,content){ this.element=$(element); this.content=content; if(this.adjacency&&this.element.insertAdjacentHTML){ this.element.insertAdjacentHTML(this.adjacency,this.content); }else{ /** *gecko不支持insertAdjacentHTML方法,但可以用如下代码代替 */ this.range=this.element.ownerDocument.createRange(); /** *如果定义了initializeRange方法,则实行,这里相当与定义了一个抽象的initializeRange方法 */ if(this.initializeRange)this.initializeRange(); this.fragment=this.range.createContextualFragment(this.content); /** *insertContent也是一个抽象方法,子类必须实现 */ this.insertContent(); } } } /** *prototype加深了我的体会,就是写js如何去遵循 Don’tRepeatYourself(DRY)原则 *上文中Abstract.Insertion算是一个抽象类,定义了名为 initializeRange的一个抽象方法 *varInsertion=newObject() 建立一个命名空间 *Insertion.Before|Top|Bottom|After就象是四个java中的四个静态内部类,而它们分别继承于Abstract.Insertion,并实现了initializeRange方法。 */ varInsertion=newObject(); Insertion.Before=Class.create(); Insertion.Before.prototype=(newAbstract.Insertion('beforeBegin')).extend({ initializeRange:function(){ this.range.setStartBefore(this.element); }, /** *将内容插入到指定节点的前面,与指定节点同级 */ insertContent:function(){ this.element.parentNode.insertBefore(this.fragment,this.element); } }); Insertion.Top=Class.create(); Insertion.Top.prototype=(newAbstract.Insertion('afterBegin')).extend({ initializeRange:function(){ this.range.selectNodeContents(this.element); this.range.collapse(true); }, /** *将内容插入到指定节点的第一个子节点前,于是内容变为该节点的第一个子节点 */ insertContent:function(){ this.element.insertBefore(this.fragment,this.element.firstChild); } }); Insertion.Bottom=Class.create(); Insertion.Bottom.prototype=(newAbstract.Insertion('beforeEnd')).extend({ initializeRange:function(){ this.range.selectNodeContents(this.element); this.range.collapse(this.element); }, /** *将内容插入到指定节点的最后,于是内容变为该节点的最后一个子节点 */ insertContent:function(){ this.element.appendChild(this.fragment); } }); Insertion.After=Class.create(); Insertion.After.prototype=(newAbstract.Insertion('afterEnd')).extend({ initializeRange:function(){ this.range.setStartAfter(this.element); }, /** *将内容插入到指定节点的后面,与指定节点同级 */ insertContent:function(){ this.element.parentNode.insertBefore(this.fragment, this.element.nextSibling); } }); |