ExtJS Component分析

很多JS开发人员可能都会熟悉JQuery,这是一个非常流行成熟的、免费的、开源JS框架,但是使用过ExtJS的人就少多了。近半年都是用ExtJS,然后大概看了一下JQuery,有了自己的一些新的体会。可以在稍后的博客中分享一下个人对二者的看法。在这里我们仍然侧重于讲解ExtJS,尤其是它比JQuery强大的地方——Component体系。


所有的JS框架似乎起码要包括如下几部分:跨浏览器,操作Dom元素,提供面向对象的编程机制,好用的辅助方法,如prototype,jquery,extjs。如果仅仅包含这样的功能其代码量比较小,加载速度自然快。JQuery的核心就是加载很快,但是ExtJs加载始终要慢得多。为什么?因为除了这些核心内容之外,ExtJs还提供了一套完整的Component体系,其中包括各种常用的Component,如Text, Date, ComboBox等,还有很多Container以实现布局要求,如HBox, VBox,Border,Column等layout。正是这些东西导致ExtJS的膨胀,但也是这些东西使得ExtJS异常的强大和易用。这里我们仅仅简单分析一下Ext Component的实现。以后我们可以再分析一下ExtJs的Layout,Plugin和Event等很多有意思的东西。很重要的一点是,要想对这些东西有稍微深刻的理解必须要自己翻阅代码。面试过一些人,他们对于自己天天在用的东西没有一点点的好奇心,丝毫不想掀开盖子看一下里面到底是什么东西,仅仅是用他们来解决一些应用上的问题,这一类人我通常觉得他们不是合格的程序员,至少不是我想要的程序员。下面言归正传。


简单的Component结构可以参考ExtJS的官方文档http://docs.sencha.com/ext-js/4-0/#!/guide/components,它简单介绍了如何使用Component、Component的生命周期以及何时可以加入自己的逻辑——通过事件或者template method。可以看到ExtJS更加推荐用template method的方式加入自己的逻辑,至少这样不需要通过事件的方式再绕一圈才调用事件handler,而是可以直接像调用函数一样调用template method,如AbstractComponent的onRender方法就是template method中的一种。


知道这些基本的东西之后,然后再看看真的一个简单ComboBox在Dom树中的样子如下:
<div id="combobox-1994" class="x-field x-form-item x-box-item x-field-default x-form-dirty" style="width: 60px; margin-left: 0px; margin-bottom: 0px; margin-right: 0px; margin-top: 0px; left: 0px; top: 0px; "><div class="x-form-item-body x-form-trigger-wrap-focus" id="combobox-1994-bodyEl" role="presentation" style="width: 60px; "><div class="x-hide-display x-form-data-hidden" role="presentation" id="ext-gen1941"><input type="hidden" name="ext-gen1940" value=">"></div><input id="ext-gen1940" type="text" size="20" class="x-form-field x-form-text x-form-focus" autocomplete="off" aria-invalid="false" data-errorqtip="" style="-webkit-user-select: text; width: 43px; " role="textbox" aria-describedby="combobox-1994-errorEl" aria-required="false"><div id="combobox-1994-triggerWrap" class="x-form-trigger-wrap" role="presentation" style="width: 17px; "><div class="x-trigger-index-0 x-form-trigger x-form-arrow-trigger x-form-trigger-last x-unselectable" role="button" id="ext-gen1942" style="-webkit-user-select: none; "></div><div class="x-clear" role="presentation"></div></div></div><div id="combobox-1994-errorEl" class="x-form-error-msg" style="display:none"></div><div class="x-clear" role="presentation"><!-- --></div></div>


这就让人困惑了。简单的一个下拉框怎么会有这么多乱七八糟的东西呢,为什么不是简单的一个select标签呢?因为select标签的功能太简单了,远远不如ExtJS中的ComboBox,你看官方文档就会发现它是多么的强大了,包括自动提示,分页,查找等等现在很常见但是select不容易实现的功能。要提供这么多功能,自然就要好好地封装一层了。但是有一点要记住,不管怎么封装,最终还是基本的html元素显示在页面,不外乎就是像上面的代码,其html结构复杂一些而已。


ExtJS如何通过html来实现自己的Component呢?这也是本文的重点讨论。首先看看AbstractComponent里面的onRender方法
    onRender : function(container, position) {
        var me = this,
            el = me.el,
            styles = me.initStyles(),
            renderTpl, renderData, i;
        创建el
        position = me.getInsertPosition(position);


        if (!el) {
            if (position) {
                el = Ext.DomHelper.insertBefore(position, me.getElConfig(), true);
            }
            else {
                el = Ext.DomHelper.append(container, me.getElConfig(), true);
            }
        }
        else if (me.allowDomMove !== false) {
            if (position) {
                container.dom.insertBefore(el.dom, position);
            } else {
                container.dom.appendChild(el.dom);
            }
        }


        .......


        me.el = el;


        me.initFrame();
        ///生成Component内部html元素
        renderTpl = me.initRenderTpl();
        if (renderTpl) {
            renderData = me.initRenderData();
            renderTpl.append(me.getTargetEl(), renderData);
        }
        //获取Component内部重要的子元素
        me.applyRenderSelectors();


        me.rendered = true;
    }


这段代码有三个地方值得注意。
(1)创建el容器元素。这个元素实际上是component的根元素,通常默认是用div(这个可以在函数getElConfig中找到)。也就是说通常ExtJs里面的component都是在div里面。
(2)根据renderTpl创建其它必要元素以生成Component,默认是获取renderTpl属性值。然后根据renderTpl这个XTemplate以及传入的renderData生成html元素,这些元素通常都塞到el里面(me.getTargetEl)。例如如下代码
     Ext.create('Ext.Component', {
         renderTo: Ext.getBody(),
         renderTpl: [
             '<h1 class="title">{title}</h1>',
             '<p>{desc}</p>'
         ],
         renderData: {
             title: "Error",
             desc: "Something went wrong"
         },
         renderSelectors: {
             titleEl: 'h1.title',
             descEl: 'p'
         },
         listeners: {
             afterrender: function(cmp){
                 // After rendering the component will have a titleEl and descEl properties
                 cmp.titleEl.setStyle({color: "red"});
             }
         }
     });


这段代码最后生成的htm元素就如下。要想知道XTemplate和数据之间的绑定关系,得看看ExtJS的template是如何应用的。
<div>
<h1 class="title">Error</h1>
<p>Something went wrong</p>
</div>


(3) 在生成所有元素之后,会调用me.applyRenderSelectors以获取组成Component的一些重要html元素,如上面例子中renderSelectors里面的titleEl和descEl。其中titleEl实际上是一个selector path,即class 那么为title的h1。而这个元素也会作为comp的一个属性保存下面,接下来在afterrender事件中,会设置它的color为red。为什么要获取这样的元素呢?因为很多时候我们需要对这些重要的元素进行操作,大部分这些操作就是对component的操作,如ComboBox的trigger,也就是那个带下拉箭头的小按钮,我们需要监听它的click事件,以知道用户想要查看下拉选项。
除了可以用renderSelectors这种方法获取重要的元素外,还有一种更加简单的方法,如下面一段从ExtJS的AbstractComponent中拷贝出来的代码
    /**
     * @cfg {Object[]} childEls
     * An array describing the child elements of the Component. Each member of the array
     * is an object with these properties:
     *
     * - `name` - The property name on the Component for the child element.
     * - `itemId` - The id to combine with the Component's id that is the id of the child element.
     * - `id` - The id of the child element.
     *
     * If the array member is a string, it is equivalent to `{ name: m, itemId: m }`.
     *
     * For example, a Component which renders a title and body text:
     *
     *     Ext.create('Ext.Component', {
     *         renderTo: Ext.getBody(),
     *         renderTpl: [
     *             '<h1 id="{id}-title">{title}</h1>',
     *             '<p>{msg}</p>',
     *         ],
     *         renderData: {
     *             title: "Error",
     *             msg: "Something went wrong"
     *         },
     *         childEls: ["title"],
     *         listeners: {
     *             afterrender: function(cmp){
     *                 // After rendering the component will have a title property
     *                 cmp.title.setStyle({color: "red"});
     *             }
     *         }
     *     });
     *
     * A more flexible, but somewhat slower, approach is {@link #renderSelectors}.
     */


对于ExtJs里面的Field会显得稍微复杂一点,但是原理是一样的。所有Field是继承自Base,Base的内部元素是有fieldSubTpl来控制的,数据由getSubTplData函数提供的。具体的实现可以从Baes.initRenderTpl和initRenderData看出来:
initRenderTpl: function() {
        var me = this;
        if (!me.hasOwnProperty('renderTpl')) {
            me.renderTpl = me.getTpl('labelableRenderTpl');
        }
        return me.callParent();
    }


  initRenderData: function() {
        return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
    }


而labelableRenderTpl实际上是定义在Base所mixin的Labelable里面
labelableRenderTpl: [
        '<tpl if="!hideLabel && !(!fieldLabel && hideEmptyLabel)">',
            '<label id="{id}-labelEl"<tpl if="inputId"> for="{inputId}"</tpl> class="{labelCls}"',
                '<tpl if="labelStyle"> style="{labelStyle}"</tpl>>',
                '<tpl if="fieldLabel">{fieldLabel}{labelSeparator}</tpl>',
            '</label>',
        '</tpl>',
        '<div class="{baseBodyCls} {fieldBodyCls}" id="{id}-bodyEl" role="presentation">{subTplMarkup}</div>',
        '<div id="{id}-errorEl" class="{errorMsgCls}" style="display:none"></div>',
        '<div class="{clearCls}" role="presentation"><!-- --></div>',
        {
            compiled: true,
            disableFormats: true
        }
    ]


从initRenderData知道要获取Labelable.getLabelableRenderData(),而这个方法恰好调用Base.getSubTplMarkup来填充上面template中的subTplMarkup
getSubTplMarkup: function() {
        return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
    }


这样所有的东西也就串起来了。之所以这么做,是因为增加label的任务全交给Labelable去做了,具体Field才去负责真正控件内部的事情。这种设计还是合理的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值