extjs 插件开发

Ext JS高级插件开发教程 Advanced Plugin Development with Ext JS

当创建一个跨浏览器的富客户端应用时,我们大多数人首先要考虑的是选择一个组件数量较多的框架。然而,当框架并没有满足我们需要的特定组件或功能的时候,问题就会演变成为,它能否让你灵活地加强和扩展其产品,而且变得非常重要。所幸的是,Ext JS不仅拥有丰富的UI功能,并且在我们活跃的社区中就有很多人创建了令人印象深刻的、适合大多数应用的扩展。但有时,你需要为一些不够用的组件增加额外的功能的情况下,Ext JS优雅的设计就让我们从现有的组件中来增加新的功能,根据自己的需要自定义组件。我们将通过多种方式来处理这一项高级的任务。

Ext插件的定义(What is an Ext Plugin?)

何谓Ext插件?任何组件Component都有一个叫做plugins 的配置项,可供开发者来进行插件的定义。这里的plugings的Config就是指向所谓的“Ext插件”对象,它可以写成一个类(Class)或单例(Singleton)的形式封装。任何一个插件都必须提供一个init的方法,这个方法就是组件的初始化方法。什么时候来执行这个方法?就是在组件生存周期的开始阶段,组件被渲染的时候。执行该init()方法的时候,当前组件对象就会被送入其中,作为Component类型的参数。

怎么在组件与插件之间实施交互控制呢?就是可以从初始化插件其本身的过程,和怎么与其所服务的组件(也就是“客户组件Client Component”)结合在一起的这两个层面来展开讨论。

一般的情况,可以选择在客户组件方法之前或之后“连带”某些新的方法(augmenting certain methods,简述为“方法 + 方法”)形成新的方法。这句话好像有点费解,似乎不通,怎么说呢,下面就会讲讲。要满足上述的任务,就需要扩展Function的原型增加新的方法。Ext已对JavaScript中的全体函数 类型加入了createInterceptor 或createSequence 的功能。下文也会逐个讲解这些函数新方法的作用。

添加功能的方法步骤(Approaches to Adding Functionality)

当Ext组件未能满足程序开发的要求,此时就要增强组件的功能;或者要修改一下现有的组件才可以。依据我们所知,有好几种方法或策略来实现。我们就此来分析它们的利与弊,相比较一下。

例如,处于Form 布局的容器中,表单Fields接纳filedLabel的配置项。不妨想一想,我们的需求是手动渲染Fields为两个lable,就是扩展新的输入框。应该怎么做?不必过于担心,有不同的途径来在Ext中灵活地增加这项新的功能。

以此为例子,我们来讲讲三种可能的方式:

可选方案一:组件的配置项的参数 Component configuration

用输入控件Fields对象的监听器来广播一组事件,不就可以达到这个需求的设计了吗?是的,开发者可使用TextField类的发布接口制定上述功能。具体一点说,就是设置listeners 的配置项。或者实例化组件之后,用on 方法设置事件处理器。

一体化的方案中,配置项往往是最简单的方案。但随着不同情景的变换便有不同的配置项参数,复制&粘贴代码很容易会乱。明显地,在这个例子中我们就不能用这个方法,因为不可能每个Field都携带长长的代码。

另外,如果事件发生了中止suspended 的指令,或者被当前的事件处理器取消掉的话,后面所加入监听器将会无效。这是该方式的不足之处。

可选方案二:创建一个子类 Create a subclass

写一个新的子类,开发者可从中重写模板方法(稍后再述)参与组件的构建和生存周期的流程控制。既可在父类同名的父类方法调用之前或之后执行所添加的功能。

一个例子就是self labeling TextField 类。

不足之处在于,为了加入新的功能,该方法会支离破解现有的继承层次,“隔离”现有类、子类的关系,别的类不能访问新功能。

本例子中,我们创建了增强版的TextField渲染自己的label,但诸如TriggerField、TextArea和NumberField这些子类就不能使用新的功能了。

另外一个不足是,如果有两个子类需要同一个功能,类与类之间不能共享,故该方法就无能为力了。

可选方案三:创建一个插件 Create a plugin

如果费点思量好好写一个插件,那么这是代码复用率最高的一个方案,亦是灵活性最高的。一个类的插件同样可以让任意一层子类使用。而且组件支持多个插件同时调用,当然组件之间就不能够相冲突。

实现一个插件 Implementing a Plugin

在我们真正了解插件之前,我们看看怎么不通过 事件来让Ext组件现有功能与其插件之间,以产生盘根错节的必要联系。之所以不使用事件是由于无法保证事件处理器在前面的时刻或打后的时刻调用我们插件的监听器。另外,由于可能返回false或对事件的手工控制,监听器也可能因执行suspended 骤然停止。

相对地,我们增加特定的方法于组件中,就可以允许我们在组件内生存周期的某个关键时刻、关键点中,让组件“受制于其插件”。

模板方法模式 The Template Method Pattern

Ext JS其组件的对象层次采用了模板方法的模式理论 来产生衍生类特定行为的委托。每个子类都有其不同的行为。

这就是说,居于继承链中的每一个类,都可以与组件生存周期中某一特定阶段相适应,使欲添加的逻辑快“参与(contribute)”其中。每一阶段中,类实现了自身的行为功能,接下来继承链中还有其他类就继续“参与属于哪个阶段的逻辑块”,各司其职。总得来说,模板方法跟事件(Event)有点相似,只是模板方法属于组件生存周期中规定好某个特点执行的内置的过程。模版方法和事件两者相比较的话,前者会稍为宽松一点。

render 函数算是一个例子,它不须要覆盖和复制复杂的代码,每个子类可以实现的模板方法或钩子(hooks),它都会提供给你。组件渲染过程被执行的两个钩子方法是onRender和afterRender。每个子例的实例也可以加入任何模板方法。当调用父类钩子时,此时也承接(up)往上的整个继承层次。我们既可以巧妙地保持了继承链,又可以让我们自身的行为进一步参与进来。

图示中表示,模板方法onRender 是如何在组件之中完整地走完一遍继承链的。当前子类的实例执行render 方法的时候就是执行子类this.onRender 方法的时候,首先从子类的onRender开始(若有实现的话)。例如,写了一个叫MyPanel的Panel 扩展子类正在渲染,此时此刻,MyPanel.onRender就会首先被调用。这个方法所执行的过程其实是与Panel类的过程有所不同的,而其父类的版本又与Panel的不同,逐级的父类都不尽相同。每一个类所参与的特定代码,都是一道加工的过程。每次都应该有return返回的结果,而最终,走完this.onRender后,还是返回到render函数中。

Ext JS组件运行的生产周期中有若干的模板方法供大家方便地,来设定显示面向特定类的、有实际用途的固定点。

全体组件子类的模板方法有:

  • onRender

    允许渲染期间加入新的行为。当父类的onRender调用完毕,组件元素即被创建。可在此阶段加入其他额外的DOM处理手段,以符合特定的需求控制。

  • afterRender

    允许渲染完毕之后加入新的行为。本阶段中组件的元素依据配置项的参数情况,加入元素的样式、配置要加入的CSS样式名称、配置可及性和是否激活的模式。

  • onAdded

    允许在组件加入到某一容器之后加入新的行为。当父类的onAdded调用完毕,Component 就有一个ownerCt的属性,执行其父容器的引用。

  • onRemoved

    允许在组件从某一容器移出后加入新的行为。当父类的onRemoved调用完毕,就不存在Component的ownerCt 引用。

  • onShow

    允许在显示操作后加入新的行为。当父类的onShow调用完毕,组件就会显示。

  • onHide

    允许在隐藏操作后加入新的行为。当父类的onHide调用完毕,组件就会隐藏。

  • onDisable

    允许在禁用操作后加入新的行为。当父类的onDisable调用完毕,组件就会被禁用。

  • onEnable

    允许在激活操作后加入新的行为。当父类的onEnable调用完毕,组件就会被激活。

  • onDestroy

    允许在销毁操作后加入新的行为。当父类的onDestroy调用完毕,组件就会被销毁。

除此之外,Ext底下还有其他的组件子类。它们都根据不同需求特质,加入了新的模板方法。关于模版方法中加入新过程的这方面内容,请参考Ext官方站点“学习专区 ”中的《使用Ext Js创建新的UI控件》 一文。

构入模板方法 Hooking into Template Methods

如果需要在组件模板方法执行的时间点上,必须对其“之前”或“之后”插入额外的功能代码,就必须使用函数interceptors 和函数sequences 。理解它们有什么妙诀?我们来看看。

中断器 Interceptors

中断器(Interceptors )的意思是某个函数在已知函数执行之前 就必须运行了。举个例子,应用中断器于 Observable 的fireEvent 方法之前插入一些代码,比如log的代码,就可以在Firebug低下观察事件的执行的了(忠告, 得很!)。

[javascript] view plain copy print ?
  1. // A class's methods are stored in its <b>prototype</b>, so grab that.
  2. var o = Ext.util.Observable.prototype;
  3. // This is the function we will be executing <b>before</b> the real fireEventMethod.
  4. // It will be passed all the same argments, and has the same scope (this reference).
  5. // Log the firer, the event name, and the arguments to the console.
  6. function fireEventInterceptor(evt) {
  7. var a = arguments;
  8. var msg = "fired the following event {0} with args";
  9. console.log(this);
  10. console.log(String.format(msg, evt));
  11. console.log(Array.prototype.slice.call(a, 1, a.length));
  12. }
  13. // Set the class's fireEvent to be our interceptor.
  14. o.fireEvent = o.fireEvent.createInterceptor(fireEventInterceptor);
// A class's methods are stored in its <b>prototype</b>, so grab that. var o = Ext.util.Observable.prototype; // This is the function we will be executing <b>before</b> the real fireEventMethod. // It will be passed all the same argments, and has the same scope (this reference). // Log the firer, the event name, and the arguments to the console. function fireEventInterceptor(evt) { var a = arguments; var msg = "fired the following event {0} with args"; console.log(this); console.log(String.format(msg, evt)); console.log(Array.prototype.slice.call(a, 1, a.length)); } // Set the class's fireEvent to be our interceptor. o.fireEvent = o.fireEvent.createInterceptor(fireEventInterceptor);

就这么简单。我们写的方法总是在Ext原来的fireEvent方法之前调用。

顺序 Sequences

顺序(sequence )的意思是某个函数在已知函数执行过后 才运行。

我们插件的实现就使用了sequences来在Field渲染后加入插件的控制过程。

A FieldLabeler Plugin

这个例子,插件是以单例的形式出现,就不能够实例化了,所以用法如下:

[javascript] view plain copy print ?
  1. plugins : [ Ext.ux.FieldLabeler ]
plugins : [ Ext.ux.FieldLabeler ]

FieldLabeler将其自身的方法连带到Filed的模板方法上。Field执行起来就好像在执行自己的成员方法一样。其this引用便是Field本身,所以可以执行生存周期内任何阶段所需代码。这就是采用单例的原因。——所有的上下文信息皆在客户端组件中。

该插件所连带的方法有这三个方法:

  • onRender
    新加入的代码要负责渲染其他的DOM元素。
  • onResize
    新加入的代码保证了其他的DOM元素适当地进行大小调节 。
  • onDestroy
    新加入的代码保证了其他的DOM元素立刻被清理。

使用插件的话,我们就解决了下流插件不能接收新功能的问题,这是比写为子类好的地方。该插件具备了 TriggerField、TextArea和NumberField的整合能力。


马上去看看该插件的例子,在这儿 。源码已彻底文档化和可读了,在这儿 。

小结

本文对怎么创建可复用的插件进行了多个角度的探索,实现了自定义的功能性增强,而且这是避免拆碎、肢解继承的层次的前提下进行的。 在Ext现有的基础上,使用插件来增加Ext原有功能的不足,实在是一种非常恰当的方法,尤其在混合或者匹配新功能的时候。 而其中至为关键的要点是,在组件运行的生存周期中,必须确保组件一定会执行那些,连带在组件的方法上额外增加的功能代码。我们推荐你看看Ext论坛上插件例子,有数千款之多。希望借此点燃阁下投入研发Ext插件的兴趣,造福Ext社群。

//

在项目开发中,难免会用到Extjs写插件来实现需求,有一些功能写插件就可以搞定的,无需重新封装一个类。

下面用一个以前写的插件来讲解。

插件功能说明:

1,实现功能:鼠标放在文本框上面时提示信息,鼠标离开后提示信息消失。

2,要求:提示信息可以自定义。

JavaScript Code 复制内容到剪贴板
  1. FieldTips = Ext.extend(Object, {
  2. init : function(f){
  3. this.field = f;
  4. if(f.msg){
  5. this.tips = new Ext.ToolTip({
  6. html : '<div style="margins:0px 0px 0px 2px">'+ f.msg +'</div>'
  7. });
  8. }
  9. f.on('afterrender',this.initTips, this);
  10. },
  11. initTips : function(){
  12. this.field.el.on('mouseover', this.showTips, this);
  13. this.field.el.on('mouseout', this.hideTips, this);
  14. },
  15. showTips : function(e, t, o){
  16. if(this.tips)this.tips.showAt(e.getXY());
  17. },
  18. hideTips : function(e, t, o){
  19. if(this.tips)this.tips.hide();
  20. }
  21. });
JavaScript Code 复制内容到剪贴板
  1. var fp = new Ext.FormPanel({
  2. labelWidth: 75,
  3. labelAlign:'right',
  4. frame:true,
  5. title: '测试',
  6. bodyStyle:'padding:5px 5px 0',
  7. width: 350,
  8. defaults: {width: 230},
  9. defaultType: 'textfield',
  10. items: [{
  11. fieldLabel: '标题',
  12. name: 'title',
  13. plugins : new FieldTips(),
  14. msg : "来匆匆去冲冲!"
  15. },{
  16. fieldLabel: '内容',
  17. xtype : "textarea",
  18. name: 'content',
  19. plugins : new FieldTips(),
  20. msg : "请您写下宝贵的建议,吾万分感谢!"
  21. }]
  22. }).render(document.body);

运行效果图:

 

你可能感兴趣的:(extjs 插件开发)