投放本站广告请联系:
extjscn#126.com
HTML 和 ExtJS 组件(二)
当你要创建一个网页时, 经常会发现,你知道怎样用原生的html来创建,却不知道如何使用浩如烟海的ext组件来实现。本文将介绍一些使用ext组件创建html的一些技巧。
接着前面两节, 我们来开始第三节
事件 Events
让我们给组件添加一个click事件。下面这个例子是添加事件的基本方式,很容易理解:
var clickCmp = new Ext.Component({ ... count: 0, html: 'Click Me', listeners: { // 给组件的主元素el添加一个事件监听 el: { click: function() { clickCmp.count++; clickCmp.update('Click count: ' + clickCmp.count); } } } });
例子中关键的一点是:listener被注册到了Ext组件对象的el上而不是组件对象本身。组件没有绑定click监听事件所以上面的例子还不能正常工作。例子中有一个关键问题是,这个click事件只是映射到了Ext组件对象上,但在这个闭包证并没有真正被使用。
注册监听事件的规范方式是重写initEvents来注册事件。下面这个例子中我们自定义了一个Ext组件,这个组件支持用handler属性注册click事件(类似button的handler--译者注):
Ext.define('ClickComponent', { extend: 'Ext.Component', initEvents: function() { this.callParent(); // Listen for click events on the component's el this.el.on('click', this.onClick, this); }, onClick: function() { // Fire a click event on the component this.fireEvent('click', this); // Call the handler function if it exists Ext.callback(this.handler, this); } });
我们可以像下面的方式来使用事件:
new ClickComponent({ ... count: 0, html: 'Click Me', handler: function() { this.count++; this.update('Click count: ' + this.count); } });
我们在自定义组件的onClick方法中还触发了click事件,这是Ext组件的click事件反作用到了el上, 我们在例子中没有用到;理解这两种事件的区别很重要。
事件委派 Event Delegation
使用事件委派和一些css, 我们就很容易为一个列表添加一些交互动作。下面这个例子有一些Ext组件的高级应用,我们要一点点消化:
new Ext.Component({ ... autoEl: 'ul', data: ['London', 'Paris', 'Moscow', 'New York', 'Tokyo'], listeners: { // Add the listener to the component's main el el: { // Use a CSS class to filter the propagated clicks delegate: '.list-row', click: function(ev, li) { // Toggle a CSS class on the li when it is clicked Ext.fly(li).toggleCls('list-row-selected'); } } }, tpl: [ '<tpl for=".">', '<li class="list-row">{.}</li>', '</tpl>' ] });
Html代码
.list-row { border: 1px solid #f90; border-radius: 5px; cursor: pointer; list-style: none outside; padding: 5px; } .list-row-selected { background-color: #f90; }
显示效果如下:
生成的html如下:
<ul id="..." ...> <li class="list-row">London</li> <li class="list-row">Paris</li> <li class="list-row">Moscow</li> <li class="list-row">New York</li> <li class="list-row">Tokyo</li> </ul>
当点击
- )添加了一个click事件,但是我们不想监听
- 上,为了实现这个目的,在Ext中我们使用delegate 配置项来实现。理论上我们可以用标签名字“li”作为delegate的选择器,但是css class名称是一种更好的实践。例子中的
- 都有一个名为“list-row” 的css class,我们可以使用它。
- 的事件,于是我们使用事件冒泡的特性将click事件作用到
配置delegate: '.list-row' 有两方面作用:
如果click事件不是发生在
传递给监听函数的参数将会是
和前面提到的例子一样,自定义组件的delegate事件可以通过重写initEvents函数来实现。
数据视图 DataView
如果我们想更深入地演化前面的list的例子,我们可以使用DataView来实现。Ext.view.View类常被称作DataView,它可以自动绑定到store。有关DataView的更详细介绍不在本文讨论的范围, 下面的例子演示了如何通过DataView实现列表:
new Ext.view.View({ ... autoEl: 'ul', itemSelector: '.list-row', overItemCls: 'list-row-over', selectedItemCls: 'list-row-selected', simpleSelect: true, // An ExtJS store or store config store: { data: [{city: 'London'}, ...], fields: ['city'] }, tpl: [ '<tpl for=".">', '<li class="list-row">{city}</li>', '</tpl>' ] });
这里有一个非常重要的设置项即itemSelector, 它的功能非常类似于我们前面提到的delegate; 它将DOM元素与store的数据记录对应起来,这样可以通过点击列表元素来选择数据。
renderTpl and renderData
renderTpl & renderData两个配置项功能类似 tpl & data,唯一不同的是renderTpl & renderData只在渲染组件时执行一次。renderTpl 和 tpl两个模板都能创建组件的html元素,通常用renderTpl创建的html元素被认为是组件内部标记(用来定义组件的框架或结构--译者注)而不是组件内容标记(组件显示的动态内容----译者注)。比如前面我们了解了panel组件中tpl的使用,tpl渲染的panel的动态内容部分;panel组件的类定义其实还有个 renderTpl, renderTpl定义了panel组件的框架,包括header、toolbars、body等等。tpl的定义既可以在Ext组件类中定义,也可以在组件实例中定义,但 renderTpl只能在组件类中定义。
baseCls属性
baseCls是Ext组件样式的基础,它既是添加在Ext组件外部el上的样式名称(class)又是Ext组件主要html元素上样式名称的前导符。创建组件主要html元素和他们的css class的工作通常由 renderTpl来完成。
有些Ext组件定义了他们特有的baseCls样式, 比如button组件 使用x-btn, panel组件使用x-panel, window组件使用x-window等。这些样式在Ext组件库中被广泛使用,它们很难在应用程序代码中被恰当使用。如果需要自定义组件的样式,你可以设置 baseCls来防止样式冲突。比如如果你想完全清除button组件的默认样式,你可以设置button的 baseCls。
设置baseCls一定要谨慎再谨慎。Ext组件的正常运行有可能依赖某些样式属性的特定值,如果这些值被改变有可能会带来麻烦,特别是你想升级Ext版本的时候。如果你只期望改变组件的一小部分样式,应该考虑别的方式。
childEls
Ext组件对象存储了一些自身子html元素引用,这就意味着你不用遍历html节点就可以直接使用这些html对象。组件渲染完成后el属性存储了组件对象的外部html节点的引用, childEls用来配置需要存储引用的子节点。比如panel组件对象就有两个这样的属性:el和body; button对象有btnIconEl和btnInnerEl分别用来修改button的图标和显示文本。配置了引用的组件对象不能随意的修改,因为被存储了引用的html元素不能被删除。因此在childEls配置引用的html元素通常由renderTpl创建而不是tpl创建(ext5中在tpl中创建会报错--译者注)。
如果组件的html元素需要存储引用,在renderTpl中定义html元素的时候通常会给这些html元素指定id, id的值通常是 组件id + html元素名称, 这样做是方便组件渲染完成后能够快速遍历来创建对象属性。
下面的组件对象将会创建一个名为body的对象属性。
Ext.define('TestChildEls', { extend: 'Ext.container.Container', childEls: [ 'body' ], renderTpl: [ '<div id="{id}-body" data-ref="body" ...>{bodyCls}</tpl>', ..., '</div>' ] });
Putting It All Together
下面这个例子有点复杂。我们将继续上面提到的面板的例子,继续完善,将看到如下效果:
我们将Ext组件实现类定义分两部分:首先定义一个简单直观的基类,包含header和body两个区域和适当的样式定义,现在这个组件中还没有具体的人物简介信息。
// Note: This is correct for ExtJS 4.1 and 4.2. This page // uses 4.0 so requires a slightly modified version. Ext.define('TitledComponent', { extend: 'Ext.Component', baseCls: 'titled-component', childEls: ['body', 'headerEl'], renderTpl: [ '<h4 id="{id}-headerEl" class="{baseCls}-header">{header:htmlEncode}</h4>', '<div id="{id}-body" class="{baseCls}-body">{% this.renderContent(out, values) %}</div>' ], getTargetEl: function() { return this.body; }, // Override the default implementation to add in the header text initRenderData: function() { var data = this.callParent(); // Add the header property to the renderData data.header = this.header; return data; }, setHeader: function(header) { this.header = header; // The headerEl will only exist after rendering if (this.headerEl) { this.headerEl.update(Ext.util.Format.htmlEncode(header)); } } });
然后再定义一个子类,在子类中定制一个人物简介的模板:
Ext.define('BiographyComponent', { extend: 'TitledComponent', xtype: 'biography', header: 'Biography', tpl: '{name} is {age:plural("year")} old and lives in {location}', // Override update to automatically set the date in the header update: function(data) { this.callParent(arguments); this.setHeader('Biography updated at ' + ...); } });
下面我们创建组件实例时,只需要传递数据:
var summary = new BiographyComponent({ data: { age: 26, location: 'Italy', name: 'Mario' } });
添加一个按钮来更新人物简介信息:
new Ext.button.Button({ ... handler: function() { // Update the body content via the tpl summary.update({ age: ..., location: ..., name: ... }); } });
生成的html非常简单:
<div id="biography-1035" class="titled-component ..." ...> <h4 id="biography-1035-headerEl" class="titled-component-header">Biography</h4> <div id="biography-1035-body" class="titled-component-body">Mario is ...</div> </div>
虽然样式不太令人满意,但组件的确是显示出来了。现在我们将样式绑定到在TitledComponent中定义的baseCls上:
CSS样式
.titled-component { background-color: #fec; border: 2px solid #5a7; border-radius: 5px; box-shadow: 2px 2px 2px #bbb; display: inline-block; } .titled-component-body { margin: 10px; } .titled-component-header { background-color: #5a7; color: #fff; font-weight: 700; margin: 0; padding: 5px 10px; text-align: center; }
现在我们完成了自定义组件的大部分工作,现在我们快速回顾一下:
- xtype:'biography' xtype会作为组件实例的id(“biography-1035”)的前导符,这个组件的Id同时也会作为组件外部元素el的id,我们在renderTpl中也用了两次。
- baseCls: 'titled-component' 这个样式被加在了组件外部的el上,注意一下这个样式是如何作为组件内部html元素的样式的前导符使用的。
- childEls: ['body', 'headerEl'] chidlEls配置的两个引用在组件渲染后被创建, 对应的dom元素通过id查找。body元素在 getTargetEl方法中使用,headerEl元素在方法setHeader中被使用。
- renderTpl的数据由initRenderData方法提供。initRenderData的默认实现(在Ext.util.Renderable中--译者注)已经包含了id和baseCls,我们只需要将 header属性追加上去。
- 在renderTpl中有个奇怪的标记“{% this.renderContent(out, values) %}”,这个标记内容应该显示在targetEl中用来注入组件内容。在 ExtJS 4.0中targetEl可能会在模板(template)中留下空白,组件内容部分会被插入到已经渲染到dom的targetEl后面。这样会有严重的性能问题,从ExtJS 4.1开始组件内容部分完全交给模板(template)渲染。如果你继承自容器(container)那么就将这部分内容替换为“{% this.renderContainer(out, values) %}”。
- getTargetEl 返回body元素的引用, 这个引用由 childEls 配置生成。只有有了这个方法,组件的update方法才可以更新到正确的dom元素。注意这个update方法不能与setHeader方法中调用的update方法混淆,后者是headerEl元素的方法而不是我们定义的组件的方法。
- setHeader方法更新header的内容,这种重写方式即可以在组件渲染前调用也可以在组件渲染后调用。
也许你不需要写这种底层的Ext组件,了解Ext组件的机制将有助于你更好的阅读ext源码。TitledComponent类的定义使用了Ext组件定义的典型风格,而BiographyComponent可以看做是应用程序类的典型。
作者:Skirtle's Den
原文:http://skirtlesden.com/articles/html-and-extjs-components
译者:lic0112
译文:http://lic0112.iteye.com/blog/2154771
- 要发表评论,请先登录