使用ExtJs创建新的UI控件(转)
組合或擴(kuò)展
當(dāng)創(chuàng)建一個(gè)新類,往往要作出這么的一個(gè)選擇:要么擁有某個(gè)工具類的實(shí)例來扮演首要的角色,要么擴(kuò)展那個(gè)類。
使用ExtJs過程中,推薦從最靠近的基類開始擴(kuò)展,實(shí)現(xiàn)所需的功能即可。這是因?yàn)镋xt提供的自動(dòng)生存周期引入了自動(dòng)渲染的機(jī)制、自動(dòng)大小調(diào)整和承擔(dān)接受來自布局管理器的UI組件布局調(diào)控,還有在容器(Container)中自動(dòng)銷毀的功能。
組織一個(gè)新類,它就是ExtJs的類,實(shí)現(xiàn)起來是很方便的,這就會(huì)導(dǎo)致了Container→Component層次的形成,相比較,新類擁有一個(gè)ExtJs類的話,必須從外圍對(duì)其渲染和組織。
The Template method Pattern模板方法模式
Ext Js的Component的繼承層次采用Template Method pattern(模板方法模式)來委派給子類,交由子類來負(fù)責(zé)其特定的行為。
其意義在于,繼承鏈中的每個(gè)子類得以在,組件生存周期的某個(gè)階段內(nèi),“提交(contribute)”額外的邏輯功能。這樣每一個(gè)類擁有屬于其自身的行為;其他類加入自己的行為時(shí)也不會(huì)相互造成影響。
其中一個(gè)例子是render(渲染)函數(shù)。render函數(shù)不能夠被覆蓋,應(yīng)該是一層一層地在各子類的實(shí)現(xiàn)中加入onRender方法,那么render函數(shù)就會(huì)在執(zhí)行的時(shí)候把各onRender方法訪問調(diào)用。每一個(gè)onRender方法必須調(diào)用其父類的onRender方法繼而再“埋頭處理(contribute)”自己(子類)的邏輯部分。
下面的圖例演示了模板方法onRender的機(jī)能過程。
render是由容器的布局管理器(Container’s layout manager)負(fù)責(zé)調(diào)用。該方法的實(shí)現(xiàn)不能被“沖掉(overridden)”,它是由Ext基類提供的。this.onRender表示當(dāng)前子類所寫的實(shí)現(xiàn)(如有提供的話)。它會(huì)訪問父類的版本、父類的版本又會(huì)調(diào)用父、父類的版本……最終,每個(gè)類完成了其功能,render函數(shù)的返回值對(duì)生存周期進(jìn)行控制。
在ExtJS組件(Component)生存周期中,提供了若干有意義的模板方法以實(shí)現(xiàn)類特定的邏輯功能。
強(qiáng)調(diào): 當(dāng)編寫子類時(shí),模板方法應(yīng)在實(shí)例化的過程中調(diào)用,屬于生存周期內(nèi)的一部分的動(dòng)作,而不應(yīng)是事件的一部分。事件有可能會(huì)由handler掛起,或中止。
以下是Component子類都可享有的模板方法:
- onRender
- afterRender
- onShow
- onHide
- onDisable
- onEnable
- onDestroy
Ext組件類的各層次中的均有其自身的模板方法,我們可以打開來看看,這些都是根據(jù)自身不同的需求而作出的設(shè)計(jì)。
提示: 當(dāng)調(diào)用父類的模板方法時(shí),最簡(jiǎn)潔的方法就是使用Function.apply,保證所有的參數(shù)都可以接受得到,傳送給那個(gè)模板方法:
Ext.ux.Subclass.superclass.onRender.apply(this, arguments);要擴(kuò)展哪個(gè)類
選擇適合的類來擴(kuò)展不但要考慮基類提供哪些功能,而且對(duì)性能方面也要著重考慮。無論有多少個(gè)UI控件被渲染或調(diào)控,Ext.Panel常常就是被衍生(extend)的對(duì)象。
Panel類擁有許多的能力:
- Border(軀干)
- Header(頭部)
- Header工具條
- Footer(底部)
- Footer按鈕
- Top toolbar(頂部工具條)
- Bottom toolbar(底部工具條)
- 承托和管理子組件
如果這些派不上用場(chǎng),那使用Panel便是資源浪費(fèi)。
Component(組件類)
如果要求的UI控件不需要其他的細(xì)節(jié)的控件,也就是,僅僅是封裝某部分的HTML元素的話,那么可取的擴(kuò)展對(duì)象就是Ext.BoxComponent或Ext.Component。如果再縮窄一步,我不需要聽?wèi){父容器提供的大小調(diào)控功能,那么使用Ext.Component就可以了。
強(qiáng)調(diào): Component類并不會(huì)內(nèi)省而得知哪一種元素作為holder。因此為了創(chuàng)建所需的元素(Element),應(yīng)設(shè)定autoEl的配置項(xiàng)。
例如,要把一張圖片封裝為Component,我們于是乎這樣定義:
Ext.ux.Image = Ext.extend(Ext.Component, {autoEl: {tag: 'img',src: Ext.BLANK_IMAGE_URL,cls: 'tng-managed-image'},// Add our custom processing to the onRender phase. // We add a ‘load’ listener to our element.onRender: function() {Ext.ux.Image.superclass.onRender.apply(this, arguments);this.el.on('load', this.onLoad, this);},onLoad: function() {this.fireEvent('load', this);},setSrc: function(src) {this.el.dom.src = src;} });這是一個(gè)可封裝圖片的Ext Component類,可參與非箱子方寸模型(non box-sizing)的布局。
BoxComponent
如果要求的UI控件不需要其他的細(xì)節(jié)的控件,也就是,僅僅是封裝某部分的HTML元素的話,還要聽?wèi){布局管理器提供的大小尺寸、布局的調(diào)控,那么這個(gè)的擴(kuò)展對(duì)象就是Ext.BoxComponent。
例如,假設(shè)一個(gè)Logger類打算是簡(jiǎn)單地顯示log信息,就必須嵌入某種布局的風(fēng)格,例如插入到一個(gè)layout:’fit’窗體,可以這樣定義:
Ext.ux.Logger = Ext.extend(Ext.BoxComponent, {tpl: new Ext.Template("<li class='x-log-entry x-log-{0:lowercase}-entry'>","<div class='x-log-level'>","{0:capitalize}","</div>","<span class='x-log-time'>","{2:date('H:i:s.u')}","</span>","<span class='x-log-message'>","{1}","</span>","</li>"),autoEl: {tag: 'ul',cls: 'x-logger'},onRender: function() {Ext.ux.Logger.superclass.onRender.apply(this, arguments);this.contextMenu = new Ext.menu.Menu({items: [new Ext.menu.CheckItem({id: 'debug',text: 'Debug',checkHandler: Ext.ux.Logger.prototype.onMenuCheck,scope: this}), new Ext.menu.CheckItem({id: 'info',text: 'Info',checkHandler: Ext.ux.Logger.prototype.onMenuCheck,scope: this}), new Ext.menu.CheckItem({id: 'warning',text: 'Warning',checkHandler: Ext.ux.Logger.prototype.onMenuCheck,scope: this}), new Ext.menu.CheckItem({id: 'error',text: 'Error',checkHandler: Ext.ux.Logger.prototype.onMenuCheck,scope: this})]});this.el.on('contextmenu', this.onContextMenu, this, {stopEvent: true});},onContextMenu: function(e) {this.contextMenu.logger = this;this.contextMenu.showAt(e.getXY());},onMenuCheck: function(checkItem, state) {var logger = checkItem.parentMenu.logger;var cls = 'x-log-show-' + checkItem.id;if (state) {logger.el.addClass(cls);} else {logger.el.removeClass(cls);}},debug: function(msg) {this.tpl.insertFirst(this.el, ['debug', msg, new Date()]);this.el.scrollTo("top", 0, true);},info: function(msg) {this.tpl.insertFirst(this.el, ['info', msg, new Date()]);this.el.scrollTo("top", 0, true);},warning: function(msg) {this.tpl.insertFirst(this.el, ['warning', msg, new Date()]);this.el.scrollTo("top", 0, true);},error: function(msg) {this.tpl.insertFirst(this.el, ['error', msg, new Date()]);this.el.scrollTo("top", 0, true);} });接著是CSS:
.x-logger {overflow: auto; } .x-log-entry .x-log-level {float: left;width: 4em;text-align: center;margin-right: 3px; } .x-log-entry .x-log-time {margin-right: 3px; } .x-log-entry .x-log-message {margin-right: 3px; } .x-log-debug-entry, .x-log-info-entry, .x-log-warning-entry, .x-log-error-entry {display: none; }.x-log-show-debug .x-log-debug-entry { display: block } .x-log-show-info .x-log-info-entry { display: block } .x-log-show-warning .x-log-warning-entry { display: block } .x-log-show-error .x-log-error-entry { display: block }.x-log-debug-entry .x-log-level { background-color: #46c } .x-log-info-entry .x-log-level { background-color: green } .x-log-warning-entry .x-log-level { background-color: yellow } .x-log-error-entry .x-log-level { background-color: red }我們吧log的信息的HTML列表均放置在一個(gè)布局中。我們?cè)趏nRender的階段加入處理,使得右鍵菜單可以根據(jù)CSS樣式類的名稱操控logged條目的可見性。位于該層次的對(duì)象還提供了特別的模板方法:
- onResize
- onPosition
Container(容器類)
如果要求的UI控件將用于承載(Contain)其他UI元素在其身上,但并不需要前文提及到的Ext.Panel那么多的功能,為避免臃腫,應(yīng)采用Ext.Container容器類來繼承。同樣地,autoEl指定元素的配置項(xiàng)亦必不可少,將用于容器在某個(gè)元素之上進(jìn)行渲染。同樣,在視覺控制方面,滾動(dòng)條是否顯示方面(即overflow屬性),用戶都可以使用Style配置項(xiàng),或容器元素的class屬性的兩種方式進(jìn)行CSS樣式制定。
注意: 對(duì)于Container層次,不要忘記哪種布局類是被用于渲染和調(diào)控子組件的。
示例中的類封裝了條件命令的查詢,允許用戶對(duì)Store基于測(cè)試字段的數(shù)據(jù)篩選。除了功能上的封裝外,還把查詢?nèi)蝿?wù)作統(tǒng)一布局,封裝在一個(gè)可控類中,可方便從容器身上自動(dòng)添加或移除查詢的條目,靈活性更高:
Ext.ux.FilterCondition = Ext.extend(Ext.Container, {layout: 'table',layoutConfig: {columns: 7},autoEl: {cls: 'x-filter-condition'},Field: Ext.data.Record.create(['name', 'type']),initComponent: function() {this.fields = this.store.reader.recordType.prototype.fields;this.fieldStore = new Ext.data.Store();// Create a Store containing the field names and types // in the passed Store.this.fields.each(function(f) {this.fieldStore.add(new this.Field(f))}, this);// Create a Combo which allows selection of a fieldthis.fieldCombo = new Ext.form.ComboBox({triggerAction: 'all',store: this.fieldStore,valueField: 'name',displayField: 'name',editable: false,forceSelection: true,mode: 'local',listeners: {select: this.onFieldSelect,scope: this}});// Create a Combo which allows selection of a testthis.testCombo = new Ext.form.ComboBox({triggerAction: 'all',store: ['<', '<=', '=', '!=', '>=', '>']});// Inputs for each type of field. Hidden and shown as necessarythis.booleanInput = new Ext.form.Checkbox({hideParent: true,hidden: true});this.intInput = new Ext.form.NumberField({allowDecimals: false,hideParent: true,hidden: true});this.floatInput = new Ext.form.NumberField({hideParent: true,hidden: true});this.textInput = new Ext.form.TextField({hideParent: true,hidden: true});this.dateInput = new Ext.form.DateField({hideParent: true,hidden: true});this.items = [ this.fieldCombo, this.testCombo, this.booleanInput, this.intInput, this.floatInput, this.textInput, this.dateInput];Ext.ux.FilterCondition.superclass.initComponent.apply(this, arguments);},onFieldSelect: function(combo, rec, index) {this.booleanInput.hide();this.intInput.hide();this.floatInput.hide();this.textInput.hide();this.dateInput.hide();var t = rec.get('type');if (t == 'boolean') {this.booleanInput.show();this.valueInput = this.booleanInput;} else if (t == 'int') {this.intInput.show();this.valueInput = this.intInput;} else if (t == 'float') {this.floatInput.show();this.valueInput = this.floatInput;} else if (t == 'date') {this.dateInput.show();this.valueInput = this.dateInput;} else {this.textInput.show();this.valueInput = this.textInput;}},getValue: function() {return {field: this.fieldCombo.getValue(),test: this.testCombo.getValue(),value: this.valueInput.getValue()};} });此類管理了其包含的輸入字段,可以精確的布局-大小調(diào)整,外補(bǔ)丁等等——都是通過CSS樣式分配到元素身上這樣來起作用的。
位于該層次的對(duì)象還提供了特別的模板方法:
- onBeforeAdd
Panel
如果所需的UI控件要求頭部、底部、或工具條之類的元素,那么Ext.Panel就是一個(gè)很不錯(cuò)的類給予繼承了。
注意: Panel是容器的一種,不要忘記哪種布局類是被用于渲染和調(diào)控子組件的。
通常Ext.Panel所實(shí)現(xiàn)的類會(huì)有很高的程序結(jié)合性,一般用于與其他UI控件協(xié)調(diào)使用(通常Containers,或表單字段),并對(duì)其有特定配置的布局風(fēng)格。另外,要對(duì)在其內(nèi)的組件提供操作的命令,可以從tbar(頂部工具欄),bbar(底部工具欄)的兩方面設(shè)置加以控制。
Field
如果所需的UI控件要求為用戶交互,可以把程序的數(shù)據(jù)顯示給用戶,或修改進(jìn)而發(fā)生給服務(wù)器的功能,那么要被擴(kuò)展的類應(yīng)該是Ext.form.TextField,或Ext.Form.NumberField。另外,如果要求輪換按鈕(Trigger button),以備鍵盤按鍵的輪換,那就是Ext.form.TriggerField。
位于該層次的對(duì)象還提供了特別的模板方法:
- onFocus:input輸入框得到焦點(diǎn)后即會(huì)觸發(fā)該方法的執(zhí)行。
- onBlur:input輸入框失去焦點(diǎn)后即會(huì)觸發(fā)該方法的執(zhí)行。
什么時(shí)候不需要子類
有些時(shí)候,濫用子類無異于“殺雞用牛刀”。在一些特定應(yīng)用場(chǎng)合,某個(gè)現(xiàn)有的類它的方法被添加、被重寫,是由這個(gè)類的構(gòu)造器實(shí)例化過程中依靠參數(shù)傳入的。
轉(zhuǎn)載于:https://www.cnblogs.com/ibravias/archive/2011/08/11/2134594.html
總結(jié)
以上是生活随笔為你收集整理的使用ExtJs创建新的UI控件(转)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 有关 Session 操作的几个误区
- 下一篇: 好代码、坏代码之一