<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; ExtJS</title>
	<atom:link href="https://blog.gmem.cc/tag/extjs/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Fri, 10 Apr 2026 07:50:36 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Sencha Cmd学习笔记</title>
		<link>https://blog.gmem.cc/sencha-cmd-study-note</link>
		<comments>https://blog.gmem.cc/sencha-cmd-study-note#comments</comments>
		<pubDate>Tue, 29 Nov 2016 06:53:25 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Cordova]]></category>
		<category><![CDATA[ExtJS]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=13780</guid>
		<description><![CDATA[<p>简介 Sencha Cmd是一套跨平台的命令行工具，用于支持整个ExtJS开发的生命周期。它包含以下功能： 代码生成工具：可以生成整个应用程序，并且使用MVC模式扩展应用程序 JS编译器：此编译器理解Sencha的ExtJS/Touch框架，可以减少应用程序加载时间 Web服务器：一个轻量级的，开发用Web服务器 包管理系统：分布式的包管理系统，可以方便的集成其它用户开发的、发布到Sencha Package Repository的包（例如ExtJS主题）到当前应用程序 工作区管理：辅助跨越多个应用的框架、包、代码共享 构建脚本：自动生成应用的构建脚本，并且暴露before、after扩展点 支持与Cordova/Phone集成 图形捕获：为支持遗留浏览器，可以将CSS3特性转换为图片 优化工具：强大的代码选择工具，用于微调、确定哪些被包含在最终的构建中；确定跨越多个页面的公共代码并共享 灵活的配置系统 日志系统：详尽的日志，让你可以了解命令的内部工作步骤 命令 命令格式 [crayon-69d9b17a0017d597554873/] 常用选项 选项 说明 <a class="read-more" href="https://blog.gmem.cc/sencha-cmd-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/sencha-cmd-study-note">Sencha Cmd学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Sencha Cmd是一套跨平台的命令行工具，用于支持整个ExtJS开发的生命周期。它包含以下功能：</p>
<ol>
<li>代码生成工具：可以生成整个应用程序，并且使用MVC模式扩展应用程序</li>
<li>JS编译器：此编译器理解Sencha的ExtJS/Touch框架，可以减少应用程序加载时间</li>
<li>Web服务器：一个轻量级的，开发用Web服务器</li>
<li>包管理系统：分布式的包管理系统，可以方便的集成其它用户开发的、发布到Sencha Package Repository的包（例如ExtJS主题）到当前应用程序</li>
<li>工作区管理：辅助跨越多个应用的框架、包、代码共享</li>
<li>构建脚本：自动生成应用的构建脚本，并且暴露before、after扩展点</li>
<li>支持与Cordova/Phone集成</li>
<li>图形捕获：为支持遗留浏览器，可以将CSS3特性转换为图片</li>
<li>优化工具：强大的代码选择工具，用于微调、确定哪些被包含在最终的构建中；确定跨越多个页面的公共代码并共享</li>
<li>灵活的配置系统</li>
<li>日志系统：详尽的日志，让你可以了解命令的内部工作步骤</li>
</ol>
<div class="blog_h1"><span class="graybg">命令</span></div>
<div class="blog_h2"><span class="graybg">命令格式</span></div>
<pre class="crayon-plain-tag"># 一般命令调用格式
sencha [category] [command] [options...] [arguments...]
# 帮助
sencha help [module] [action]</pre>
<div class="blog_h2"><span class="graybg">常用选项</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>--debug<br />--info<br />--quiet</td>
<td>设置调试日志的级别<br />--quiet  仅仅显示警告、错误</td>
</tr>
<tr>
<td>--strict</td>
<td>把警告作为错误看待</td>
</tr>
<tr>
<td>--plain</td>
<td>禁止调试日志中的高亮</td>
</tr>
<tr>
<td>--beta</td>
<td>启用Beta版本的包仓库</td>
</tr>
<tr>
<td>--cwd</td>
<td>设置命令执行的“当前目录”</td>
</tr>
<tr>
<td>--nologo</td>
<td>禁止输出版本信息</td>
</tr>
<tr>
<td>--sdk-path</td>
<td>对于非app类子命令，指定ExtJS SDK的目录</td>
</tr>
<tr>
<td>--time</td>
<td>显示命令的执行耗时</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">常用子命令</span></div>
<div class="blog_h3"><span class="graybg">app</span></div>
<p>这类子命令执行多种应用程序的构建处理：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>upgrade</td>
<td>自动生成的应用程序中，有两类文件与Cmd有关：构建脚本（<span style="color: #444444;">scaffold</span>）、ExtJS/Touch SDK。偶尔的情况下，你需要更新这些内容：<br />
<pre class="crayon-plain-tag"># 仅仅更新scaffold：包括.sencha子目录中的内容，以及app.js等文件
sencha app upgrade
# path-to-new-framework为可选，用于在更新scaffold的同时更新你使用的ExtJS的版本
sencha app upgrade [ path-to-new-framework ]</pre>
</td>
</tr>
<tr>
<td>init</td>
<td>初始化当前目录为一个Sencha Cmd应用程序，可以从空白目录开始：<br />
<pre class="crayon-plain-tag">sencha app init --ext=/path/to/extjs/ AppName --modern</pre>
</td>
</tr>
<tr>
<td>install</td>
<td>确保不完整的Sencha Cmd应用程序变得可以运行（补充缺失的文件）：<br />
<pre class="crayon-plain-tag">sencha app install --frameworks=/path/to/extjs</pre>
</td>
</tr>
<tr>
<td>build</td>
<td>构建应用程序：<br />
<pre class="crayon-plain-tag"># 基于开发环境构建
sencha app build development</pre>
</td>
</tr>
<tr>
<td>refresh</td>
<td>执行部分构建，仅仅构建脚本相关的部分</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">generate</span></div>
<p>生成模型、控制器或者其它组件，甚至整个应用程序：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>app</td>
<td>生成一个起步应用程序：<br />
<pre class="crayon-plain-tag">sencha generate app -ext -classic AppName ./AppPath
sencha -sdk /home/alex/JavaScript/extjs/6.2.0 generate app -modern AppName ./AppPath</pre>
</td>
</tr>
<tr>
<td>controller</td>
<td>为当前应用程序生成一个控制器：<br />
<pre class="crayon-plain-tag">sencha generate controller Central</pre>
</td>
</tr>
<tr>
<td>form</td>
<td>为当前应用生成一个表单，仅Touch</td>
</tr>
<tr>
<td>model</td>
<td>为当前应用生成一个模型：<br />
<pre class="crayon-plain-tag"># 在应用根目录执行
# 生成一个名为User的模型，包含3个字段
sencha generate model User id:int,name,email</pre>
</td>
</tr>
<tr>
<td>package</td>
<td>生成一个起步的本地包，存放到packages/local下：<br />
<pre class="crayon-plain-tag">sencha generate package pkgname</pre>
</td>
</tr>
<tr>
<td>profile</td>
<td>为当前应用程序生成一个配置，仅Touch</td>
</tr>
<tr>
<td>theme</td>
<td>为slice操作生成一个主题页面，仅ExtJS</td>
</tr>
<tr>
<td>view</td>
<td>为当前应用程序生成一个视图相关组件<br />
<pre class="crayon-plain-tag"># 在view/foo子目录下生成Thing视图，以及视图模型ThingModel、视图控制器ThingController
sencha generate view foo.Thing</pre>
</td>
</tr>
<tr>
<td>workspace</td>
<td>生成一个新的工作区</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">repository</span></div>
<p>这一类子命令用于管理包的远程仓库：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>list</td>
<td>列出可用的远程仓库</td>
</tr>
<tr>
<td>add</td>
<td>添加一个远程仓库：<br />
<pre class="crayon-plain-tag">sencha repository add sencha http://cdn.sencha.com/cmd/packages/</pre>
</td>
</tr>
<tr>
<td>remove</td>
<td>删除一个远程仓库</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">其它子命令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>upgrade</td>
<td>升级Sencha Cmd本身。示例：<br />
<pre class="crayon-plain-tag"># 检查新版本
sencha upgrade --check
# 执行升级
sencha upgrade
# 无人值守的升级
sencha upgrade --unattended
# 包含beta版本
sencha upgrade --check --beta </pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">与ExtJS6联用</span></div>
<div class="blog_h2"><span class="graybg">生成应用</span></div>
<p>可以使用下面的命令生成一个Universal应用的脚手架：</p>
<pre class="crayon-plain-tag">sencha -sdk /home/alex/JavaScript/extjs/6.2.0 generate app MyApp MyApp</pre><br />
<pre class="crayon-plain-tag">.
├── app                   # 当前应用有关文件的目录
│   ├── Application.js    # 当前应用的实现类，扩展Ext.app.Application
│   ├── model             # 视图模型、模型存放目录
│   ├── store             # 存储存放目录
│   └── view              # 视图控制器、控制器存放目录
├── app.js                # 应用入口点定义
├── app.json              # 应用描述符文件
├── bootstrap.js          # 自举文件，其中包含微加载器
├── build                 # 构建结果存放目录
│   ├── development       # 构建环境：development的构建结果
│   └── temp
├── build.xml
├── classic               # 针对classic toolkit的源代码
│   ├── sass
│   └── src               # 组件，主要是UI组件
├── ext                   # 复制来的ExtJS SDK完整目录
├── index.html            # 入口点网页
├── modern                # 针对modern  toolkit的源代码
│   ├── sass
│   └── src               # 组件，主要是UI组件
├── overrides             # 对组件的重写存放在此
├── packages              # 依赖的包存放在此
├── resources             # 资源文件存放在此
├── .sencha
│   ├── app
│   │   ├── defaults.properties   # 构建属性的默认值和说明
│   │   ├── development.defaults.properties # 针对构建环境development的默认构建属性
│   │   ├── development.properties # 针对构建环境development的构建属性
│   │   ├── ext.properties  # 针对ExtJS的构建属性
│   │   ├── sencha.cfg      # Sencha Cmd配置文件
└── workspace.json        # 工作区描述符文件</pre>
<div class="blog_h2"><span class="graybg">构建应用</span></div>
<p>执行下面的命令构建当前应用：</p>
<pre class="crayon-plain-tag">sencha app build</pre>
<p>该命令会构建你的HTML页面、JS代码和主题到build目录中。</p>
<div class="blog_h2"><span class="graybg">开发者模式</span></div>
<p>Sencha Cmd根据你的app.json，生成自举脚本（bootstrap）。自举脚本向动态类加载器（Ext.Loader以及微加载器）传递必要的信息，后者负责加载程序代码。自举脚本不会被你的JS代码的变动影响，因此你需要执行编辑 - 保存 - 重新载入 这样的工作流程。</p>
<p>要保证自举脚本与最新的源码文件匹配，可以基于以下两种途径之一：</p>
<ol>
<li>使用<pre class="crayon-plain-tag">sencha app watch</pre> ，该命令启动一个Web服务器，并且监控源码，在源码改变后立即执行一次开发环境构建（development build）。该方式支持Fashion的Live Update功能</li>
<li>手工构建并更新：<br />
<pre class="crayon-plain-tag"># 除了更新JS部分，还继续样式的编译，并生成CSS
sencha app build development
# 仅仅更新bootstrap与JS相关的部分
sencha app refresh</pre>
</li>
</ol>
<div class="blog_h1"><span class="graybg">Fashion</span></div>
<p>Cmd 6引入了此组件，用于支持快速开发ExtJS 6主题。联合使用app watch可以启用所谓实时更新（<span style="color: #444444;">Live Update</span>）的特性。</p>
<p>实时更新利用Fashion来编译Scss，并且将其生成的最新CSS注入到浏览器页面。Fashion基于JavaScript实现，不需要Ruby的支持。</p>
<p>Fashion扩展了Sass语言，添加了新的特性，以便<span style="color: #444444;">Sencha Inspector之类的工具可以可视化的查看、编辑主题、应用中定义的变量。</span></p>
<div class="blog_h2"><span class="graybg">启用实时更新</span></div>
<p>配置app.json以启用实时更新：</p>
<pre class="crayon-plain-tag">"development": {
    "tags": [
        "fashion"
    ]
},</pre>
<p>也可以不指定上述配置，直接修改URL来启用实时更新：<pre class="crayon-plain-tag">?platformTags=fashion:1</pre>  </p>
<p>启用实时更新时，加载到浏览器的是Sass而非生成的CSS，当Sass发生变化时，Fashion会立即编译之并更新页面上的CSS。</p>
<div class="blog_h2"><span class="graybg">动态变量</span></div>
<p>动态变量是Fashion对Sass的语言扩展。在Fashion中动态变量非常重要，动态变量类似于普通变量，但是其值被<pre class="crayon-plain-tag">dynamic()</pre> 标记包围。动态变量之间的交互方式与普通变量不同：</p>
<pre class="crayon-plain-tag">$bar: dynamic(blue);
$foo: dynamic($bar);  // 变量$foo依赖于$bar
$bar: dynamic(red);

@debug $foo;  // $foo的值为red而不是blue</pre>
<p>可以注意到，动态变量的值并非在逐行处理过程中确定的。对动态变量的处理分为赋值、估算两个阶段。</p>
<div class="blog_h3"><span class="graybg">赋值阶段</span></div>
<p>赋值阶段的处理类似于普通变量，按照级联的顺序赋值。对动态变量的赋值只能在文件范围（位于任何控制块的外面）进行：</p>
<pre class="crayon-plain-tag">$bar: dynamic(blue);

@if something {
    $bar: dynamic(red); // 非法
}

$bar: dynamic(if(something, red, blue)); // 合法</pre>
<p>这一限制是动态变量估算、提升行为（<span style="color: #444444;">hoisting behaviors</span>）所需。</p>
<p>在声明之后，再次对动态变量赋值，必须要附带<span style="color: #3b2c48;">dynamic()：</span></p>
<pre class="crayon-plain-tag">$bar: dynamic(blue);
$bar: red;  // bar被赋值为red
$bar: green !default;  // 重新赋值为green
@debug $bar;   // green</pre>
<div class="blog_h3"><span class="graybg">估算阶段</span></div>
<p>在该阶段，根据依赖顺序（而不是声明顺序）确定动态变量的实际值，例如：</p>
<pre class="crayon-plain-tag">$bar: dynamic(mix($colorA, $colorB, 20%));
$bar: dynamic(lighten($colorC, 20%));</pre>
<p>由于对$bar进行了再次赋值，$bar依赖于$colorC，因此仅当$colorC的值被估算出来后，$bar的值即可确定，至于$colorA、$colorB何时估算，与$bar无关。</p>
<div class="blog_h3"><span class="graybg">Hoisting</span></div>
<p>为了实现估算阶段的逻辑，Fashion收集所有动态变量，<span style="background-color: #c0c0c0;">在执行任何Sass代码之前统一的估算它们</span>。这意味着类似于JavaScript变量，Fashion动态变量也是被提升到作用域最前面的。</p>
<div class="blog_h3"><span class="graybg">Elevation</span></div>
<p>当一个变量用于给动态变量赋值，其本身也被升级为动态变量：</p>
<pre class="crayon-plain-tag">$foo: blue;   // 尽管$foo作为普通变量声明，但是由于动态变量$bar依赖于它，因此$foo被升级为动态变量
$bar: dynamic($foo);</pre>
<div class="blog_h2"><span class="graybg">扩展Fashion</span></div>
<p>你可以通过编写JS代码来扩展Fashion，要在Sass中包含这些扩展代码，可以：</p>
<pre class="crayon-plain-tag">require("my-module");
require("../path/file.js");</pre>
<p>在内部，Fashion使用ES6的System.import加载标准的JS模块。</p>
<p>一个扩展的代码可以如下：</p>
<pre class="crayon-plain-tag">// ES6以前的版本
exports.init = function(runtime) {
    runtime.register({
        magic: function (first, second) {
        }
    });
};
// ES6
module foo {
    export function init (runtime) {
        runtime.register({
            magic: function (first, second) {
            }
        });
    }
} </pre>
<div class="blog_h1"><span class="graybg">编译器友好代码指南</span></div>
<p>编译器是Sencha Cmd的主要组件。此编译器并不类似于：</p>
<ol>
<li>YUI压缩器</li>
<li>Google Closure编译器</li>
<li>UglifyJS</li>
</ol>
<p>上述工具解决JS开发者面临的不同问题，但是它们不能理解Sencha框架，例如Ext.define用于定义类。</p>
<div class="blog_h2"><span class="graybg">框架知晓</span></div>
<p>编译器用于提供框架相关的优化、诊断，一旦代码通过了此编译器，其它一般性的工具可以继续处理它。这类优化可以极大的改善ExtJS的加载时间，特别是针对遗留浏览器。</p>
<p>为了编译器能够最优化的操作， 你必须遵循一系列的编码约定，这样编译器才能理解你的代码，进而执行优化操作。</p>
<div class="blog_h2"><span class="graybg">代码组织</span></div>
<p>Cmd生成的动态加载器，以及之前的JSBuilder，都假设代码按照一定的规则组织。你应该保证：</p>
<ol>
<li>每个JavaScript源文件应该在全局作用域包含一个Ext.define 语句</li>
<li>源文件的文件Base名必须和ExtJS类的Simple name匹配</li>
<li>ExtJS类的命名空间应当映射为源码子目录</li>
<li>为了让编译器能够自由的在类级别选择代码，应当确保一个类对应一个文件</li>
</ol>
<p>第2、3两条类似于Java的包与文件系统的映射规则。</p>
<div class="blog_h2"><span class="graybg">类声明</span></div>
<p>编译器能够识别Ext.define的以下关键字（keywords）并进行优化处理：requires、uses、extend、mixins、statics、alias、singleton、override、alternateClassName、xtype</p>
<p>除了Ext.define最常用的调用方法——第二个参数传递简单对象以外，编译器也能够识别以下几种调用变体：</p>
<pre class="crayon-plain-tag">// 指定第二个参数为函数
Ext.define('Foo.bar.Thing', function (Thing) {
    return {
        // 这里声明关键字
        extend: '...',
    };
});
// 指定第二个参数为函数调用
Ext.define('Foo.bar.Thing', function () {

    return {
        // 这里声明关键字
        extend: '...'
    };
}());</pre>
<div class="blog_h2"><span class="graybg">重写（Overrides）</span></div>
<p>从ExtJS 4.1/Touch 2.0开始，Ext.define能够管理重写。在历史版本中，重写用于打补丁以解决BUG或者增强功能。在引入动态加载器后，情况变得复杂，因为<pre class="crayon-plain-tag">Ext.override</pre> 的执行需要时间。在大量使用重写的应用程序中，并非所有重写都被所有页面或者build使用。</p>
<p>现在，编译器能够理解重写，以及它们对依赖、加载顺序的影响。在未来的版本中，编译器在消除dead code（被override替换）时会更加激进。遵循本节的规则，你可以在未来版本获得益处。</p>
<p>标准的重写代码范例如下：</p>
<pre class="crayon-plain-tag">// 尽管这里选定的名字空间是任意的，但是最好遵循下文说明的规则
Ext.define('MyApp.patches.grid.Panel', {
    override: 'Ext.grid.Panel',
    ...
});</pre>
<p>通过重写来<span style="background-color: #c0c0c0;">打补丁</span>是最常见的应用。你应当把所有<span style="background-color: #c0c0c0;">补丁的命名空间与它针对的目标类的命名空间对应</span>，例如MyApp.patches对应Ext。</p>
<p>除了打补丁，重写还可以用做<span style="background-color: #c0c0c0;">不完整类</span>（Partial Classes）。当你使用Sencha Architect之类的代码生成器时，一个类通常分类两个部分：机器生成部分、人工编写部分，这种划分有助于机器部分的重新生成。不完整类的用法示例：</p>
<pre class="crayon-plain-tag">// 自动生成的类
Ext.define( 'Foo.bar.Thing', {
    // NOTE: This class is generated - DO NOT EDIT...
    requires: [
        'Foo.bar.custom.Thing'
    ],
    method: function () {
    },
} );
// 重写，作为部分类
Ext.define( 'Foo.bar.custom.Thing', {
    override: 'Foo.bar.Thing',
    method: function () {
        this.callParent(); // 调用自动生成的方法
    },
} );</pre>
<p>重写的最后一种用法是<span style="background-color: #c0c0c0;">作为切面（Aspect）</span> 。OOP会导致的一个问题就是过度肥胖的基类，这是因为它包含过多的公共逻辑。使用override，可以分离出其中的逻辑，而仅仅在需要使用的时候，才require之：</p>
<pre class="crayon-plain-tag">// 作为切面的重写
Ext.define('Foo.feature.Component', {
    override: 'Ext.Component'
});
// 作为切面的重写，与Foo.feature.Component提供一个产品族
Ext.define('Foo.feature.grid.Panel', {
    override: 'Ext.grid.Panel',

    requires: [
        'Foo.feature.Component' // 由于重写不产生继承关系，这里必须显式require
    ]
});


// 需要使用上述切面的客户代码：

requires: [
    'Foo.feature.grid.Panel'
]
// 或者
requires: [
    'Foo.feature.*'
]</pre>
<p>注意：在重写时，你可以调用<pre class="crayon-plain-tag">callSuper()</pre> 来绕过对被重写方法的调用，而调用其父版本。</p>
<div class="blog_h1"><span class="graybg">资源文件管理</span></div>
<p>除了JavaScript、CSS、HTML以外，Web应用程序往往包含很多代码无关的资源文件（也叫资产，Asset）——图片、视频、数据文件。Cmd将这些资产分类并提供多种管理它们的选项。</p>
<div class="blog_h2"><span class="graybg">资源的来源</span></div>
<p>资源文件一般都放在名为resources的目录下，这些目录可能来自：</p>
<ol>
<li>对于单Toolkit应用，位于应用根目录，例如MyApp/resources</li>
<li>对于Universal应用，位于应用根目录，以及各Toolkit下，例如MyApp/resources、MyApp/classic/resources</li>
<li>主题附带的资源文件，位于主题的根目录，例如theme-neptune/resources</li>
<li>软件包附带的资源文件，位于软件包的根目录</li>
</ol>
<p>你可以应用程序描述符app.json中的resources数组，用来定制应用程序资源的来源：</p>
<pre class="crayon-plain-tag">"resources" [{
    "path": "resources",   // 改变此配置以便在其它目录中存放资源
}]</pre>
<p>对于单Toolkit应用，仅该数组的第一个元素作为实际的资源路径，其它元素、第一个元素的output设置都被忽略。 </p>
<p>对于Universal应用，该数组可以指定多个元素，并且使用output来指定使用哪个资源池：</p>
<pre class="crayon-plain-tag">"resources": [{
    "path": "resources",
    "output": "shared"   # 使用shared资源池
}, {
    "path": "${toolkit.name}/resources"   # 使用默认资源池
}],</pre>
<p>这样，可以被多个Toolkit共享的资源，就可以放在（默认）应用根目录的resources目录下，而Toolkit独有的资源则放在与Toolkit同名的目录的resources子目录下。</p>
<p>对于包，支持像Universal应用那样声明多个资源目录、使用哪个资源池。</p>
<div class="blog_h2"><span class="graybg">资源的输出</span></div>
<p>app.json的顶级output元素， 声明在构建后的应用程序文件被输出到何处：</p>
<pre class="crayon-plain-tag">"output": {
    "base": "${workspace.build.dir}/${build.environment}/${app.name}",
    // 例如 ./workspace/build/production/MyApp
},</pre>
<p>对于Universal应用，可以定义资源池（<span style="color: #444444;">resource pools</span>）：</p>
<pre class="crayon-plain-tag">"output": {
    "base": "${workspace.build.dir}/${build.environment}/${app.name}",
    // 资源池定义
    "resources": {
        // 默认资源池，对应buildId下的resources目录
        "path": "${build.id}/resources",
        // 名为shared的共享资源池，对应应用程序的根目录下的resources子目录
        "shared": "resources"
    }

},</pre>
<p>应用、主题、包中的资源文件，都输出到某个资源池中：</p>
<ol>
<li>单Toolkit应用、主题中的资源文件，输出到默认资源池</li>
<li>Universal应用，默认的，应用根目录下resources输出到共享资源池；toolkit.name/resources输出到默认资源池</li>
<li>包的资源输出到资源池的package.name子目录下。这是应用程序构建为包提供的沙盒</li>
</ol>
<div class="blog_h2"><span class="graybg">资源的覆盖</span></div>
<p>多个不同来源的资源文件，可能存在路径名相同的情况，此时会按照一定的规则覆盖：</p>
<ol>
<li>由于主题支持继承，因此子主题中的同名资源，自动覆盖父主题中的资源</li>
<li>在应用的resources目录中（如果是Universal应用，则必须是toolkit.name/resources目录中）的文件，可以覆盖同路径的来自主题、包的资源文件</li>
</ol>
<div class="blog_h2"><span class="graybg">package.json配置</span></div>
<p>与应用类似，包也具有自己的描述符package.json：</p>
<pre class="crayon-plain-tag">// 构建输出
"output": "${package.dir}/build",
// 资源路径
"resources": [{
    "path": "resources"
}]</pre>
<p>为了让非Cmd应用使用包，可以单独构建之：<pre class="crayon-plain-tag">sencha package build</pre>  。注意包的构建不区分development/production，其build目录中的东西可以被script、link等元素引用。</p>
<div class="blog_h2"><span class="graybg">包资源与沙盒</span></div>
<p>为了防止不同包中同名资源被意外的覆盖，应用程序构建（App build）为包中的资源提供沙盒，即，为每个包的资源提供resources的子目录，子目录以包名为名称。</p>
<p>由于应用程序资源、包资源路径处理方式的不同，因此在CSS中引用时，需要注意。Cmd提供了API来获取资源的正确路径：</p>
<pre class="crayon-plain-tag">.arrow-button {
    // 不使用资源池，或者使用默认资源池时：
    background-image: url(get-resource-path('images/arrow.png'));
    // 使用资源池时：
    background: url(get-resource-path('images/foo.png', $pool: 'shared'));
}</pre>
<p>在JavaScript中，也有类似的API：</p>
<pre class="crayon-plain-tag">image.setSrc(
    // 第二个参数为资源池的名称
    Ext.getResourcePath('images/arrow.png', null, 'arrow-button') 
); </pre>
<div class="blog_h1"><span class="graybg">包管理</span></div>
<p>Sencha Cmd应用的packages目录包含local、remote两个子目录，分别存放本地、远程包。</p>
<p>你可以在app.json中使用requires声明对包的依赖。sencha app build、sencha app refresh命令会自动把包集成到应用程序中。</p>
<div class="blog_h2"><span class="graybg">本地包</span></div>
<p>执行下面的命令可以生成一个本地包：</p>
<pre class="crayon-plain-tag">sencha generate package common</pre>
<p>此包的内容放置在packages/local/common目录中。此包的packages.json中会自动添加<pre class="crayon-plain-tag">local: true</pre>  配置，该配置可以放置Cmd下载远程包并覆盖此本地包。</p>
<div class="blog_h2"><span class="graybg">远程包</span></div>
<p>远程包的管理是基于包仓库（package repositories）进行的，Cmd会自动生成一个本地的包仓库，用于缓存、发布包。</p>
<p>执行下面的命令，可以查看可用的远程仓库列表：<pre class="crayon-plain-tag">sencha repository list</pre> ，你可以使用<pre class="crayon-plain-tag">sencha repository add</pre> 、<pre class="crayon-plain-tag">sencha repository remove</pre> 命令来添加、删除远程仓库。</p>
<div class="blog_h2"><span class="graybg">包命名规范</span></div>
<p>sencha-*、ext-*、touch-*、cmd-*这些前缀的包名被内部使用。</p>
<div class="blog_h2"><span class="graybg">版本管理</span></div>
<p>可以指定当前包的版本号，以及它和什么样的历史版本兼容：</p>
<pre class="crayon-plain-tag">{
    // 当前版本
    "version": "n.n.n",
    // 兼容的最低版本
    "compatVersion": "2.4.2",
}</pre>
<p>在应用描述符的requires里，可以附加版本要求：<pre class="crayon-plain-tag">"requires": [ "ext-easy-button@1.0" ]</pre>  。版本要求语法：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">示例</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-1.2</td>
<td>最高1.2</td>
</tr>
<tr>
<td>1.0-<br />1.0+</td>
<td>最低1.0</td>
</tr>
<tr>
<td>1.0-1.2</td>
<td>版本在1.0到1.2之间</td>
</tr>
<tr>
<td>1.0-1.2?</td>
<td>版本在1.0到1.2之间，或者1.0-1.2的兼容版本</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">工作区管理</span></div>
<p>Cmd引入工作区的概念，以便管理多个需要共享框架、代码、样式、资源的应用程序。</p>
<div class="blog_h2"><span class="graybg">什么是工作区</span></div>
<p>构建一个大型应用程序的步骤与构建一个单页应用程序的起始步骤一样。一旦应用程序扩展到多个页面，以下常见问题将会出现：</p>
<ol>
<li>如何共享的使用同一框架</li>
<li>如果跨页面共享代码</li>
<li>如果共享第三方包</li>
</ol>
<p>为了解决这些问题，Cmd引入了工作区的概念。一个工作区是包含了多个<span style="background-color: #c0c0c0;">页面（在Sencha的术语里叫应用，Application，因为应用一般都是单页面的，一个应用只进行一次Ext.application调用）</span>、框架（不同版本）、包以及其它共享代码/文件的目录。工作区的根目录通常会纳入版本控制。</p>
<p>如果在工作区内组织各页面的目录并不重要，但是通常把它们的目录作为工作区根目录的直接子目录。</p>
<div class="blog_h2"><span class="graybg">创建工作区</span></div>
<p>执行下面的命令可以创建一个工作区：</p>
<pre class="crayon-plain-tag">sencha generate workspace /path/to/workspace

# 生成的目录结构如下：
.
├── packages
├── .sencha               # Sencha Cmd相关文件
│   ├── .cvsignore
│   ├── .gitignore
│   └── workspace         # 工作区相关文件
│   ├── plugin.xml
│   └── sencha.cfg        # Sencha Cmd配置
└── workspace.json        # 工作区描述符</pre>
<div class="blog_h1"><span class="graybg">微加载器</span></div>
<p>所谓微加载器（<span style="color: #444444;">Microloader</span>）是Sencha的数据驱动的JavaScript/CSS动态加载器。微加载器由Cmd提供，并作为生成的应用程序的一部分。</p>
<p>ExtJS 6的微加载器实现与ExtJS 5或者Touch不同，它增强了一些功能，这些功能可以在app.json中配置。执行<pre class="crayon-plain-tag">sencha app upgrade</pre> 时微加载器会被一同升级。</p>
<div class="blog_h2"><span class="graybg">加载Manifest</span></div>
<p>Cmd会把app.json转换为manifest供微加载器在运行时使用。你也可以在运行时调用<pre class="crayon-plain-tag">Ext.manifest</pre> 获得此manifest的引用。ExtJS6本身利用此manifest完成一些工作，例如兼容性层（Compatibility Layer）。</p>
<p>有三种方式来指定微加载器使用什么Manifest：</p>
<ol>
<li>嵌入式Manifest，配置：<br />
<pre class="crayon-plain-tag">"output": {
    "manifest": {
        "embed": true
    }
}</pre></p>
<p> 可以在构建期间把Manifest嵌入到bootstrap.js文件中</p>
</li>
<li>命名Manifest，你可以指定从哪个文件中加载Manifest，默认app.json：<br />
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    var Ext = Ext || {};
    Ext.manifest = 'foo';  // 从./foo.json加载
&lt;/script&gt;
&lt;script id="microloader" data-app="f72c0f68" type="text/javascript" src="bootstrap.js"&gt;&lt;/script&gt;</pre>
</li>
<li>动态指定Manifest，你可以动态的设置Ext.manifest为任意字符串为应用描述符文件的basename</li>
</ol>
<div class="blog_h2"><span class="graybg">Manifest的生成</span></div>
<p>微加载器负责解析app.json并生成manifest，大概步骤如下：</p>
<ol>
<li>读取app.json，其中内容被后续步骤解析</li>
<li>获取app.json中与当前构建环境（production/testing/development）匹配的设置，作为基础manifest</li>
<li>获取app.json中与当前构建配置匹配（builds.***）的设置，覆盖上面的manifest</li>
<li>读取app.json中与当前Toolkit匹配（classic/modern）的设置，覆盖上面的manifest</li>
<li>读取app.json中与当前packager匹配的设置，覆盖上面的manifest</li>
<li>开始处理依赖包，如果package.json中声明了js、css，则它们按照包的依赖顺序，插入上面manifest['js']或者manifest['css']数组的最前面。package.json中其它内容被置入manifest的packages属性中</li>
</ol>
<div class="blog_h2"><span class="graybg">引用微加载器</span></div>
<p>Cmd生成的脚手架代码中已经引用的微加载器：</p>
<pre class="crayon-plain-tag">&lt;script id="microloader" data-app="12345" type="text/javascript" src="bootstrap.js"&gt;&lt;/script&gt;</pre>
<p>其中data-app为应用的UUID，该ID会在LocalStorage中作为Key使用，避免多个页面的数据混乱在一起。</p>
<div class="blog_h1"><span class="graybg">应用描述符</span></div>
<p>由Cmd自动生成的app.json叫做应用描述符，该描述符会被微加载器使用，并且在运行时暴露为 Ext.manifest。你可以定制其中的很多配置项，以改变应用的行为。下表列出最常用的配置项：</p>
<table class=" fixed-word-wrap full-width">
<tbody>
<tr>
<td><strong>indexHtmlPath</strong> 应用的HTML文档文件路径，此路径相对于app.json文件。默认值是index.html，可以根据需要修改。修改后应该同时指定output配置</td>
</tr>
<tr>
<td><strong>framework</strong> 使用什么框架，可选值ext、touch，或者工作区自定义的框架名</td>
</tr>
<tr>
<td><strong>theme</strong> 对于ExtJS应用，此配置用于指定主题名。例如<pre class="crayon-plain-tag">"theme": "ext-theme-crisp"</pre> </td>
</tr>
<tr>
<td><strong>classpath  </strong>指定源码目录。例如：<br />
<pre class="crayon-plain-tag">"classpath": [
    "app",
    "${toolkit.name}/src"
] </pre>
</td>
</tr>
<tr>
<td>
<p><strong>js</strong></p>
<p>一个数组，指定需要被微加载器自动加载的JavaScript文件，默认脚手架生成如下内容：</p>
<pre class="crayon-plain-tag">"js": [{
    "path": "app.js",
    "bundle": true
}]</pre>
<p>上述配置实际上指定了应用程序的入口点。bundle=true意味着此配置条目被构建出的concatenated classes代替。 你可以继续增加配置：</p>
<pre class="crayon-plain-tag">"js": [{
    "path": "library.js",
    "includeInBundle": true // 包含在concatenated classes中，如果false，则此文件在构建结果中存在，由微加载器独立加载
},{
    "path": "app.js",
    "bundle": true  // 只能有一个元素定义此属性，设置为true则此文件作为concatenated classes容器
}]</pre>
<p>所有JavaScript文件必须按照其<span style="background-color: #c0c0c0;">执行顺序</span>在此声明</p>
<p>对于应用程序的依赖，应该在requires配置中声明，而不是此配置 </p>
</td>
</tr>
<tr>
<td>
<p><strong>css</strong></p>
<p>一个数组，指定微加载器需要按顺序加载的CSS文件。默认内容：</p>
<pre class="crayon-plain-tag">"css": [
    {
        "path": "${build.out.css.path}",
        "bundle": true,
        "exclude": [
            "fashion"
        ]
    }
],</pre>
<p>此默认值仅仅是一个导入sass目录构建结果的存根 </p>
</td>
</tr>
<tr>
<td><strong>requires</strong> 此数组引用应用所依赖的包的名称，当Cmd处理此数组时，会自动下载、抽取缺失的软件包到工作区中。包可以在自己的package.json中声明依赖，这样的传导依赖也会被下载、抽取。你可以同时指定包的版本</td>
</tr>
<tr>
<td>
<p><strong>output</strong> </p>
<p>控制构建的如何输出、在哪里输出。此配置可以控制构建输出的多个方面。例如，修改indexHtmlPath为../page.jsp后，你需要继续设置output：</p>
<pre class="crayon-plain-tag">"output": {
    "page": {
        "path": "../page.jsp",
        "enable": false // 仅仅说明页面URL是哪个，但是不赋值源文件到输出
    }
}</pre>
<p>你可以指定appCache属性，来控制哪些资产可以被浏览器缓存供离线使用：</p>
<pre class="crayon-plain-tag">"output": {
    "appCache": {
        // 启用缓存
        "enable": true
    },
    "appCache": {
        // 哪些使用缓存
        "cache": [
            "index.html"
        ],
        // 哪些必须联网获取
        "network": [
            "*"
        ],
        // 哪些在联网获取失败时使用缓存
        "fallback": []
    }
},</pre>
</td>
</tr>
<tr>
<td>
<p><strong>LocalStorage缓存</strong>
<p>除了浏览器自带的应用程序缓存（通过appCache配置），微加载器还支持通过LocalStorage缓存每一个资产文件。在尝试进行任何远程获取之前，微加载器会首先使用UUID查询LocalStorage，如果文件没有变化则直接使用缓存，这可以让应用程序的加载变得非常块。微加载器还支持增量补丁——即仅仅资产、CSS、JS变化的比特通过网络下载，然后合并到LocalStorage中。</p>
<p>要启用LocalStorage缓存，你需要逐个设置资产的update属性，取值full表示全量更新，delta表示增量更新：</p>
<pre class="crayon-plain-tag">"js": [
    {
        "path": "app.js",
        "bundle": true,
        "update": "delta"
    }
],

"css": [
    {
        "path": "app.css",
        "update": "full"
    }
]</pre>
<p>除了在资产上设置update，要让缓存生效，还必须声明全局的cache配置：</p>
<pre class="crayon-plain-tag">"cache": {
    "enable": true
}</pre>
<p>在开发阶段，常常把全局缓存设置为false，仅仅在产品构建时才设置为true </p>
<p>你还可以设置：<pre class="crayon-plain-tag">"cache": { "deltas": true }</pre>  这样所有update=delta资产的变化会出现在构建结果的detas目录中，如果设置deltas为字符串，则此字符串对应的目录代替deltas目录的功能</p>
<p>一旦微加载器检测到Application Cache、LocalStorage缓存的内容存在更新，会立即发布全局事件，你可以监听此事件并作出反应：</p>
<pre class="crayon-plain-tag">Ext.application( {
    name: 'MyApp',
    mainView: 'MyMainView',
    // 监听缓存更新事件
    onAppUpdate: function () {
        Ext.Msg.confirm( '应用程序已更新',
            function ( choice ) {
                if ( choice === 'yes' ) {
                    window.location.reload();
                }
            }
        );
    }
} );</pre>
</td>
</tr>
<tr>
<td>
<p><strong>builds</strong>
<p>此配置用于声明一个或者多个构建配置（Build Profiles）</p>
<p>如果应用程序存在多个变体，你就可以添加一个新的构建配置。例如：</p>
<pre class="crayon-plain-tag">"builds": {
    "classic": {
        "theme": "ext-theme-classic"
    },
    "gray": {
        "theme": "ext-theme-gray"
    },
    "access": {
        "theme": "ext-theme-access"
    },
    "
}</pre>
<p>builds中的每一个属性称为一个构建配置，此属性的值会在执行使用此配置构建时，覆盖app.json中的配置项以生成最终有有效的manifest </p>
</td>
</tr>
<tr>
<td>
<p><strong>production  /  testing   /  development</strong></p>
<p>对应不同的构建环境（Build Environment）的特定配置信息</p>
</td>
</tr>
<tr>
<td>
<p><strong>classic  /  modern</strong></p>
<p>对应不同的Toolkit的特定配置信息</p>
</td>
</tr>
<tr>
<td>
<p><strong>locales</strong></p>
<p>指定支持的区域配置：<pre class="crayon-plain-tag">"locales": [ "en", "he" ]</pre> 。在同时使用builds时，最终的构建配置的名称不再是classic，而是classic-en、classic-he等</p>
</td>
</tr>
<tr>
<td>
<p><b>tags  </b></p>
<p>你可以指定多个标记：</p>
<pre class="crayon-plain-tag">"tags": ["ios", "phone", "fashion"]
// 或者
"tags": {
    "ios": true,
    "phone": true,
    "desktop": false,
    "fashion": true
}</pre>
<p>这些标记被检测到后，将覆盖自动检测到的值</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">集成Cordova/PhoneGap</span></div>
<p>PhoneGap基于Cordova构建，并提供一些辅助工具，例如远程构建服务。</p>
<p>基于Sencha Cmd构建时，仅能构建DEBUG版本的Cordova/PhoneGap应用，要构建能够在应用商店下载的APP你需要使用Android Studio/XCode。</p>
<div class="blog_h2"><span class="graybg">创建支持Cordova/PhoneGap的应用</span></div>
<p>首先，按照一般性步骤创建Cmd应用：</p>
<pre class="crayon-plain-tag">mkdir MyApp &amp;&amp; cd MyApp
sencha app init --ext=/home/alex/JavaScript/extjs/6.2.0 MyApp --universal</pre>
<p>当开发ExtJS 6 Universal应用时，则上述生成的应用结构的app.json中已经包含了builds块，这种情况下sencha cordova/phonegap子命令无法修改此块，因此你需要手工的修改：</p>
<pre class="crayon-plain-tag">"builds": {
    "classic": {},
    "modern": {
        "toolkit": "modern",
        "theme": "theme-triton",
        // 如果使用Cordova，可以添加：
        "cordova": {
            "config": {
                "platforms": "ios",
                "id": "cc.gmem.myappid"
            }
        },
        // 如果使用PhoneGap，可以添加：
        "packager": "phonegap",
        "phonegap": {
            "config": {
                "platform": "ios",
                "id": "cc.gmem.myappid"
            }
        }
    }
},</pre>
<p>然后进入工程目录，初始化Cordova/PhoneGap支持：</p>
<pre class="crayon-plain-tag">sencha phonegap init cc.gmem.myappid  MyApp
# 或者
sencha cordova init cc.gmem.myappid MyApp</pre>
<p>当开发ExtJS Modern应用程序时，上述命令会自动生成如下builds块：</p>
<pre class="crayon-plain-tag">// native仅仅是一个构建配置名（build-name），你可以将其修改为任意值
"native": {
    "packager": "cordova",
    "cordova": {
        "config": {
            "platforms": "ios android",  // 需要支持的移动平台
            "id": "cc.gmem.myappid2",    // 用于唯一识别应用程序的标识符
            "name": "MyApp2"             // 应用程序名称
        }
    }
}</pre>
<p>上述命令执行完毕后，在工程目录会出现cordova或者phonegap子目录。 这些子目录的内容与普通Cordova/PhoneGap工程一致。</p>
<p>下面的Cmd子命令可以用于构建出Native软件包： </p>
<pre class="crayon-plain-tag"># 首先构建Sencha应用程序，然后构建Native应用
sencha app build {build-name}
# 首先构建，然后尝试在连接上来的设备上运行程序
sencha app run {build-name}
# 首先构建，然后尝试在模拟器上运行程序
sencha app emulate {build-name}
# 构建Sencha应用程序，然后拷贝到Cordova/PhoneGap指定目录，准备后续的Native构建
sencha app prepare {build-name}</pre>
<div class="blog_h2"><span class="graybg">开发PhoneGap远程应用</span></div>
<p>使用PhoneGap远程构建服务，可以避免很多麻烦的问题，例如下载SDK、工具，购买Mac电脑。你仅仅需要把自己的Web应用上传到PhoneGap服务器，Adobe就可以自动的把Native应用生成给你。在开始之前，你需要到PhoneGap网站申请一个免费账号。如果要构建iOS应用，你需要在PhoneGap网站上填写一系列的credentials信息。</p>
<p>要启用远程构建，需要配置app.json：</p>
<pre class="crayon-plain-tag">"builds": {
    "native": {
        "packager": "phonegap",
        "phonegap" : {
            "config": {
                "platform": "ios",
                "remote": true,  // 远程构建
                "id": "com.mydomain.MyApp"
            }
        }
    }
}</pre>
<p>为了向Cmd提供你的PhoneGap网站账号信息，你需要在应用根目录创建一个local.properties文件：</p>
<pre class="crayon-plain-tag">phonegap.username=name@me.com
phonegap.password=passwd</pre>
<p>远程构建完毕后，你可以到PhoneGap门户去下载安装包，或者将其发布到应用商店。 </p>
<div class="blog_h1"><span class="graybg">新特性列表</span></div>
<div class="blog_h2"><span class="graybg">6</span></div>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td>
<p><strong>新的安装器</strong></p>
<p>此版本的安装程序自带了运行Cmd所需的JRE，并且移除了对Ruby的依赖（由于引入了Fasion）。新的安装器不再需要root权限</p>
<p>如果你使用老的框架版本，例如ExtJS 4/5、Touch2。你还是需要Ruby以及Compass包以便编译Sass代码</p>
</td>
</tr>
<tr>
<td>
<p><strong>Fashion</strong></p>
<p>Fashion是Sencha新开发的，用于其扩展的Sass语言的编译器。Cmd调用Fashion为ExtJS应用程序、主题构建Sass。当使用sencha app watch时，你可以获得实时更新（Live Update）—— 应用程序的CSS自动与最新代码同步，而不需要刷新浏览器</p>
<p>由于引入了Fashion，Cmd不再依赖于Ruby来生成主题</p>
</td>
</tr>
<tr>
<td>
<p><strong>工作区改进</strong></p>
<p>为了支持基于包的代码共享，Cmd6将不同的包分离到子目录中（原先一律放在packages目录）：</p>
<ol>
<li>packages/local：自己在本地生成的包</li>
<li>packages/remote：下载/抽取（extrace）的包。workspace.json中的packages.extract可以修改此默认位置</li>
</ol>
<p>remote目录可以安全的从CVS中忽略</p>
<p>从Cmd6开始，工作区的根目录存在一个workspace.json文件，此文件应该在CVS中存储</p>
</td>
</tr>
<tr>
<td>
<p><strong>Microloader</strong></p>
<p>微加载器是首先由Touch框架引入的，并且在Cmd 5中开始支持ExtJS 5应用程序</p>
<p>Cmd 6对微加载器进行了改进，添加了基于localStorage的缓存支持，并且可以在app.json中禁用缓存</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">6.1</span></div>
<table class=" fixed-word-wrap full-width">
<tbody>
<tr>
<td>
<p><strong>框架与工作区管理</strong></p>
<p>当项目规模增大后，在一个工作区中同时开发多个应用程序，并且使用多个ExtJS版本的可能性增加。在6.1之前，一个工作区仅仅可以使用单个版本的ExtJS，则要求所有应用程序必须升级使用的ExtJS版本。</p>
<p>现在，你可以在workspace.json中声明多个ExtJS版本：</p>
<pre class="crayon-plain-tag">"frameworks": {
    "ext62": "ext",
    "ext602": "ext-6.0.2"
}</pre>
<p>frameworks的每一个子项，其值对应工作区中的一个子目录，其键可以供应用程序的app.json引用：</p>
<pre class="crayon-plain-tag">"framework": "ext62"</pre>
<p>你可以随时的添加新的框架版本到工作区：</p>
<pre class="crayon-plain-tag"># 自动生成frameworks子项，其键根据目录中ExtJS的版本号推导出，例如6.2.0 版本的键为ext62，6.0.2版本的键为ext602
sencha framework add /path/to/ext
# 手工指定键值  path为拷贝到工作区的哪个子目录，key为键，必须以ext或者touch开头
sencha framework add -key extFiveO -source /path/to/ext-5.0.0 -path ext50</pre>
<p>添加完新框架后， 框架的代码会自动拷贝到工作区中。你可以使用新添加的框架，来创建一个应用程序：</p>
<pre class="crayon-plain-tag">sencha generate app -extFiveO AppName path/to/app</pre>
<p>执行下面的命令可以删除一个框架版本：</p>
<pre class="crayon-plain-tag"># --force 即使有应用程序在使用，也强制删除框架
sencha framework remove --force  extFiveO</pre>
<p>可以升级工作区内的应用程序，让其使用特定的框架版本：</p>
<pre class="crayon-plain-tag"># 从/path/to/ext目录升级以ext作为key的框架
sencha framework upgrade ext /path/to/ext
# 升级单个应用程序
sencha app upgrade ../ext62
# 升级整个工作区中所有应用程序、包中和Cmd相关的文件
sencha workspace upgrade </pre>
<p>在任何时候，你都可以列出当前工作区可用的框架版本：</p>
<pre class="crayon-plain-tag">sencha framework list</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">6.2</span></div>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td>
<p><strong>app init子命令</strong>
<p>允许初始化当前目录为一个Sencha Cmd应用程序。你可以从空白目录开始，执行下面的命令生成完整的HelloWorld程序结构：</p>
<pre class="crayon-plain-tag"># 除了--modern，还可以 --classic 或者 --universal
sencha app init --ext=/path/to/extjs/ AppName --modern</pre>
<p>执行构建后，应用程序可以在任意Web服务器上部署：<pre class="crayon-plain-tag">sencha app build</pre>  </p>
</td>
</tr>
<tr>
<td>
<p><strong>app install子命令</strong></p>
<p>确保不完整的Sencha Cmd应用程序变得可以运行（补充缺失的文件），示例：</p>
<pre class="crayon-plain-tag">sencha app install --frameworks=/path/to/extjs
# frameworks是 6.1引入的特性，允许你指向一个具有多个版本ExtJS的目录
# 当前使用的ExtJS版本定义在workspace.json中，Cmd会自动选择最匹配的版本</pre>
</td>
</tr>
<tr>
<td>
<p><strong>workspace init子命令</strong>
<p>确保当前目录是Sencha Cmd工作区或者工作区的一部分</p>
</td>
</tr>
<tr>
<td>
<p><strong>workspace install子命令</strong></p>
<p>确保不完整的Sencha Cmd工作区变得可以运行（补充缺失的文件），示例：</p>
<pre class="crayon-plain-tag">sencha workspace install --frameworks=/path/to/extjs </pre>
</td>
</tr>
<tr>
<td>
<p><strong>workspace cleanup子命令</strong>
<p>从workspace.json中移除已经不存在于工作区的应用程序条目</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">6.5</span></div>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td>
<p><strong>ES6支持</strong></p>
<p>Cmd 6.5的主要变化就是支持ES6，即使是ExtJS 4.1.1+、Touch 2.1.x+也可以使用ES6来编写代码了</p>
<p>Cmd能够把ES6代码编译（Transpile）为老版本的JavaScript，这样老的浏览器可以被很好的支持。由于Transpile处理本身的特性，它必须在产品构建时执行，因此，你必须使用支持ES6的现代浏览器才能使用开发者模式（Developer Mode）</p>
<p>产品构建支持的浏览器包括IE8+和其它主流浏览器</p>
<p>如果你对那些老浏览器没兴趣，可以禁止Transpile：</p>
<p><pre class="crayon-plain-tag">"output": {
    "js": {
        "version": "ES6"
    }
}</pre>
</td>
</tr>
</tbody>
</table>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/sencha-cmd-study-note">Sencha Cmd学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/sencha-cmd-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ExtJS 4中的选取器控件</title>
		<link>https://blog.gmem.cc/extjs-4-picker-fields</link>
		<comments>https://blog.gmem.cc/extjs-4-picker-fields#comments</comments>
		<pubDate>Fri, 17 Apr 2015 01:38:32 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ExtJS]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=5324</guid>
		<description><![CDATA[<p>选取器控件简介 选取器控件通常具有这样的UI风格： 包含一个触发按钮 点击触发按钮，在字段输入框下方弹出一个“picker” 操作picker，会改变输入框的值 选取器控件的父类是Ext.form.field.Picker，它继承于Trigger，后者继承于Text 相关ExtJS类分析 Ext.form.field.Text 这是一个基础的输入框字段，可以用来替换[crayon-69d9b17a02098655963182-i/] 表单字段，它是很多高级表单字段的基类，例如TextArea、Combo。 Text内置了若干验证机制，例如：allowBlank 、minLength、maxLength、regex ，并且，可以使用vtype、validator提供定制的验证功能 默认情况下，用户在Text输入文字后，就会立即启动验证，可以通过validateOnChange、checkChangeEvents、 checkChangeBuffer对此行为进行控制。 Text提供了以下成员： 配置项/属性/方法  说明 {disableKeyFilter} Bolean=false，设置为true可以禁用击键过滤 {emptyCls} String，当字段内容为空时，可以显示一个 emptyText，该配置定义文本的样式 <a class="read-more" href="https://blog.gmem.cc/extjs-4-picker-fields">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/extjs-4-picker-fields">ExtJS 4中的选取器控件</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">选取器控件简介</span></div>
<p>选取器控件通常具有这样的UI风格：</p>
<ol>
<li>包含一个触发按钮</li>
<li>点击触发按钮，在字段输入框下方弹出一个“picker”</li>
<li>操作picker，会改变输入框的值</li>
</ol>
<p>选取器控件的父类是Ext.form.field.Picker，它继承于Trigger，后者继承于Text</p>
<div class="blog_h2"><span class="graybg">相关ExtJS类分析</span></div>
<div class="blog_h3"><span class="graybg">Ext.form.field.Text</span></div>
<p>这是一个基础的输入框字段，可以用来替换<pre class="crayon-plain-tag">&lt;input type="text" /&gt;</pre> 表单字段，它是很多高级表单字段的基类，例如TextArea、Combo。</p>
<p>Text内置了若干验证机制，例如：allowBlank 、minLength、maxLength、regex ，并且，可以使用vtype、validator提供定制的验证功能</p>
<p>默认情况下，用户在Text输入文字后，就会立即启动验证，可以通过validateOnChange、checkChangeEvents、 checkChangeBuffer对此行为进行控制。</p>
<p>Text提供了以下成员：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">配置项/属性/方法 </td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>{disableKeyFilter}</td>
<td>Bolean=false，设置为true可以禁用击键过滤</td>
</tr>
<tr>
<td>{emptyCls}</td>
<td>String，当字段内容为空时，可以显示一个 emptyText，该配置定义文本的样式</td>
</tr>
<tr>
<td>{emptyText} </td>
<td>
<p>String，当字段内容为空时，显示一个文本，注意该值默认会提交，设置form.Basic.submit的submitEmptyText选项可以修改此行为</p>
<p>对于支持HTML5的浏览器，将使用HTML5的placeholder属性来显示emptyText，对于老版本的浏览器，则直接显示在value中，这意味着字段原始值被认为是emptyText，特别的，对于密码字段，将会显示一系列的点号</p>
</td>
</tr>
<tr>
<td>{enableKeyEvents}</td>
<td>Boolean=false，设置为true，则代理HTML input元素的键盘事件</td>
</tr>
<tr>
<td>{enforceMaxLength }</td>
<td>Boolean=false，设置为true，则自动为底层input添加maxLength属性</td>
</tr>
<tr>
<td>{grow}</td>
<td>Boolean=false，设置为true，则该字段的长度随着其内容多少自动增长或收缩</td>
</tr>
<tr>
<td>{growAppend}</td>
<td>growAppend='W'，如果grow=true，该配置用于添加在value后，以计算字段的目标长度</td>
</tr>
<tr>
<td>{growMax}</td>
<td>Number=800，自动伸缩的最大值</td>
</tr>
<tr>
<td>{growMin}</td>
<td>Number=30，自动伸缩的最小值</td>
</tr>
<tr>
<td>{maskRe}</td>
<td>RegExp，用于过滤无效击键的正则式，已经输入的部分不会过滤</td>
</tr>
<tr>
<td>{requiredCls}</td>
<td>String='x-form-required-field'，必须填写的字段的附加样式</td>
</tr>
<tr>
<td>{selectOnFocus}</td>
<td>Boolean=false，如果设置为true，该字段获取焦点时，选择已有的字段文本</td>
</tr>
<tr>
<td>{size} </td>
<td>Number=20，input元素的初始size属性，只有在width没有配置、容器布局不会设置该字段宽度时有效</td>
</tr>
<tr>
<td>{stripCharsRe} </td>
<td>RegExp，用于从输入中清除不需要内容的正则式，如果配置了该参数，那么匹配的字符序列将从输入框的值中删除</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>验证相关配置项  </strong></td>
</tr>
<tr>
<td>{allowBlank}</td>
<td>Boolean=true，如果设置为false，则要求value的长度大于0</td>
</tr>
<tr>
<td>{blankText}</td>
<td>String，allowBlank验证失败时的错误信息文本</td>
</tr>
<tr>
<td>{maxLength}</td>
<td>Number=Number.MAX_VALUE，最大输入长度</td>
</tr>
<tr>
<td>{maxLengthText}</td>
<td>String=<span style="color: #484848;"> </span><code style="color: #484848;">"The maximum length for this field is {0}"</code>，maxLength验证失败时的错误信息文本</td>
</tr>
<tr>
<td>{minLength}</td>
<td>Number=0，最小输入长度</td>
</tr>
<tr>
<td>{minLengthText}</td>
<td>minLength验证失败时的错误信息文本</td>
</tr>
<tr>
<td>{regex}</td>
<td>RegExp，用于执行验证的正则式对象，错误信息文本使用regexText 或者invalidText</td>
</tr>
<tr>
<td>{regexText}</td>
<td>String，regex验证失败时的错误信息文本</td>
</tr>
<tr>
<td>{validator}</td>
<td>Function(Object)，自定义的，在字段验证（getErrors）时调用的函数，该函数把当前字段的值作为入参，该函数会比默认验证行为先调用</td>
</tr>
<tr>
<td>{vtype}</td>
<td>String，在Ext.form.field.VTypes中定义的验证类型</td>
</tr>
<tr>
<td>{vtypeText}</td>
<td>String，vtype验证失败时的错误信息文本</td>
</tr>
<tr>
<td>autoSize()</td>
<td>如果grow=true有效，增长组件宽度以匹配value</td>
</tr>
<tr>
<td>getErrors()</td>
<td>
<p>String[]( Object value )。根据字段的验证规则执行字段验证，value默认为经过处理的原始值。如果出现任何错误，返回包含错误信息的数组。验证规则按以下顺序依次处理：</p>
<ol>
<li>字段特殊验证器，即validator配置，validator应该返回：
<ol>
<li>Boolean true，表示这一步验证成功，需要进一步验证</li>
<li>String，表示验证失败，内容为错误信息，终止验证</li>
</ol>
</li>
<li>基本验证，依次执行，每一个验证失败都导致验证终止，否则进一步验证
<ol>
<li>allowBlank</li>
<li>minLength</li>
<li>maxLength</li>
</ol>
</li>
<li>VTypes验证，如果验证失败，则终止验证，否则进一步验证</li>
<li>regex验证，使用配置的regex验证字段值</li>
</ol>
</td>
</tr>
<tr>
<td>getRawValue( )</td>
<td>String，获取原始值。覆盖Base的版本。返回input.value，如果input.value==emptyText则忽略</td>
</tr>
<tr>
<td>processRawValue()</td>
<td>String( String value ) 。覆盖Base的版本Object(Object value )。在准备对原始值进行转换、验证之前，进行必要的预处理。如果配置了stripCharsRe，将对input.value进行处理</td>
</tr>
<tr>
<td>reset( )</td>
<td>覆盖Field的版本。除了重置为初始值外，如果初始值为空，则处理emptyText、emptyCls </td>
</tr>
<tr>
<td>selectText()</td>
<td>void ( [Number start], [Number end] )。选中文本范围</td>
</tr>
<tr>
<td>⚡autosize</td>
<td>void ( Ext.form.field.Text this, Number width, Object eOpts ) autoSize被调用后触发</td>
</tr>
<tr>
<td>⚡keydown</td>
<td>void ( Ext.form.field.Text this, Ext.EventObject e, Object eOpts ) ，如果enableKeyEvents=true，则在每次按下按键时触发</td>
</tr>
<tr>
<td>⚡keypress</td>
<td>void ( Ext.form.field.Text this, Ext.EventObject e, Object eOpts )，如果enableKeyEvents=true，则在每次按键时触发</td>
</tr>
<tr>
<td>⚡keyup</td>
<td>void ( Ext.form.field.Text this, Ext.EventObject e, Object eOpts )，如果enableKeyEvents=true，则在每次放开按键时触发</td>
</tr>
</tbody>
</table>
<p> Ext.form.field.Text的源代码分析如下：</p>
<pre class="crayon-plain-tag">Ext.define('Ext.form.field.Text', {
    extend:'Ext.form.field.Base',
    alias: 'widget.textfield',
    size: 20,
    growMin : 30,
    growMax : 800,
    growAppend: 'W',
    allowBlank : true,
    minLength : 0,
    maxLength : Number.MAX_VALUE,
    minLengthText : 'The minimum length for this field is {0}',
    maxLengthText : 'The maximum length for this field is {0}',
    blankText : 'This field is required',
    regexText : '',
    emptyCls : Ext.baseCSSPrefix + 'form-empty-field',
    requiredCls : Ext.baseCSSPrefix + 'form-required-field',
    componentLayout: 'textfield', //组件布局方式
    valueContainsPlaceholder : false,
    initComponent: function () {
        var me = this;
        me.callParent();
        me.addEvents(
            'autosize',
            'keydown',
            'keyup',
            'keypress'
        );
        me.addStateEvents('change'); //值改变作为状态事件
        me.setGrowSizePolicy();
    },
    setGrowSizePolicy: function(){
        if (this.grow) {
            this.shrinkWrap |= 1;
        }    
    },
    //初始化事件处理
    initEvents : function(){
        var me = this,
            el = me.inputEl;
        me.callParent();
        if(me.selectOnFocus || me.emptyText){
            //聚焦选取
            me.mon(el, 'mousedown', me.onMouseDown, me);
        }
        if(me.maskRe || (me.vtype &amp;&amp; me.disableKeyFilter !== true &amp;&amp; (me.maskRe = Ext.form.field.VTypes[me.vtype+'Mask']))){
            //击键过滤
            me.mon(el, 'keypress', me.filterKeys, me);
        }
        if (me.enableKeyEvents) {
            //启用了按键事件
            me.mon(el, {
                scope: me,
                keyup: me.onKeyUp,
                keydown: me.onKeyDown,
                keypress: me.onKeyPress
            });
        }
    },
    //判断两值相等：按字符串比较
    isEqual: function(value1, value2) {
        return this.isEqualAsString(value1, value2);
    },
    //值改变时的模板方法
    onChange: function() {
        this.callParent();
        this.autoSize();
    },
    //字段fieldSubTpl的填充数据
    getSubTplData: function() {
        var me = this,
            value = me.getRawValue(),
            isEmpty = me.emptyText &amp;&amp; value.length &lt; 1,
            maxLength = me.maxLength,
            placeholder;
        if (me.enforceMaxLength) {
            if (maxLength === Number.MAX_VALUE) {
                maxLength = undefined;
            }
        } else {
            maxLength = undefined;
        }
        if (isEmpty) {
            if (Ext.supports.Placeholder) {
                placeholder = me.emptyText;
            } else {
                value = me.emptyText;
                me.valueContainsPlaceholder = true;
            }
        }
        //把必要的数据覆盖到父类版本
        return Ext.apply(me.callParent(), {
            maxLength   : maxLength,
            readOnly    : me.readOnly,
            placeholder : placeholder,
            value       : value,
            fieldCls    : me.fieldCls + ((isEmpty &amp;&amp; (placeholder || value)) ? ' ' + me.emptyCls : '') + (me.allowBlank ? '' :  ' ' + me.requiredCls)
        });
    },
    //渲染后模板
    afterRender: function(){
        this.autoSize();
        this.callParent();
    },
    //鼠标按下时模板
    onMouseDown: function(e){
        var me = this;
        if(!me.hasFocus){
            //如果按下鼠标时焦点已经离开当前元素，则注册单次执行的事件句柄，阻止默认行为
            me.mon(me.inputEl, 'mouseup', Ext.emptyFn, me, { single: true, preventDefault: true });
        }
    },
    //对原始值进行预处理
    processRawValue: function(value) {
        var me = this,
            stripRe = me.stripCharsRe,
            newValue;

        if (stripRe) {
            newValue = value.replace(stripRe, ''); //把匹配正则式的部分剔除
            if (newValue !== value) {
                me.setRawValue(newValue); //设置原始值
                value = newValue;
            }
        }
        return value;
    },
    //禁用时模板
    onDisable: function(){
        this.callParent();
        if (Ext.isIE) {
            //IE特殊处理，禁止选中
            this.inputEl.dom.unselectable = 'on';
        }
    },
    //启用时模板
    onEnable: function(){
        this.callParent();
        if (Ext.isIE) {
            this.inputEl.dom.unselectable = '';
        }
    },
    //按下键时的模板
    onKeyDown: function(e) {
        this.fireEvent('keydown', this, e);//发布事件
    },
    //放开按键时的模板
    onKeyUp: function(e) {
        this.fireEvent('keyup', this, e);//发布事件
    },
    //击键时模板
    onKeyPress: function(e) {
        this.fireEvent('keypress', this, e);//发布事件
    },
    //重置
    reset : function(){
        this.callParent();
        this.applyEmptyText();//显示空白文本
    },
    //使用HTML5 placeholder或者input.value显示空白文本
    applyEmptyText : function(){
        var me = this,
            emptyText = me.emptyText,
            isEmpty;

        if (me.rendered &amp;&amp; emptyText) {
            isEmpty = me.getRawValue().length &lt; 1 &amp;&amp; !me.hasFocus;

            if (Ext.supports.Placeholder) {
                me.inputEl.dom.placeholder = emptyText;
            } else if (isEmpty) {
                me.setRawValue(emptyText);
                me.valueContainsPlaceholder = true;
            }
            if (isEmpty) {
                me.inputEl.addCls(me.emptyCls);
            }
            me.autoSize();
        }
    },
    //设置组件布局后
    afterFirstLayout: function() {
        this.callParent();
        if (Ext.isIE &amp;&amp; this.disabled) {
            var el = this.inputEl;
            if (el) {
                el.dom.unselectable = 'on';
            }
        }
    },
    //聚焦前，清除空白文本
    preFocus : function(){
        var me = this,
            inputEl = me.inputEl,
            emptyText = me.emptyText,
            isEmpty;
        me.callParent(arguments);
        if ((emptyText &amp;&amp; !Ext.supports.Placeholder) &amp;&amp; (inputEl.dom.value === me.emptyText &amp;&amp; me.valueContainsPlaceholder)) {
            me.setRawValue('');
            isEmpty = true;
            inputEl.removeCls(me.emptyCls);
            me.valueContainsPlaceholder = false;
        } else if (Ext.supports.Placeholder) {
            me.inputEl.removeCls(me.emptyCls);
        }
        if (me.selectOnFocus || isEmpty) {
            inputEl.dom.select();
        }
    },
    //处理聚焦
    onFocus: function() {
        var me = this;
        me.callParent(arguments);
        if (me.emptyText) {
            me.autoSize();
        }
    },
    //聚焦后，显示空白文本
    postBlur : function(){
        this.callParent(arguments);
        this.applyEmptyText();
    },
    //获取原始值
    getRawValue: function() {
        var me = this,
            v = me.callParent();
        if (v === me.emptyText &amp;&amp; me.valueContainsPlaceholder) {
            v = ''; //空白文本处理
        }
        return v;
    },
    //设置值
    setValue: function(value) {
        var me = this,
            inputEl = me.inputEl;
        if (inputEl &amp;&amp; me.emptyText &amp;&amp; !Ext.isEmpty(value)) {
            //移除空白文本的样式
            inputEl.removeCls(me.emptyCls);
            me.valueContainsPlaceholder = false;
        }
        me.callParent(arguments);
        me.applyEmptyText();
        return me;
    },
    //执行验证
    getErrors: function(value) {
        var me = this,
            errors = me.callParent(arguments),
            validator = me.validator,
            emptyText = me.emptyText,
            allowBlank = me.allowBlank,
            vtype = me.vtype,
            vtypes = Ext.form.field.VTypes,
            regex = me.regex,
            format = Ext.String.format,
            msg;
        value = value || me.processRawValue(me.getRawValue());
        if (Ext.isFunction(validator)) {
            msg = validator.call(me, value);
            if (msg !== true) {
                errors.push(msg);
            }
        }
        if (value.length &lt; 1 || (value === me.emptyText &amp;&amp; me.valueContainsPlaceholder)) {
            if (!allowBlank) {
                errors.push(me.blankText);
            }
            return errors;
        }
        if (value.length &lt; me.minLength) {
            errors.push(format(me.minLengthText, me.minLength));
        }
        if (value.length &gt; me.maxLength) {
            errors.push(format(me.maxLengthText, me.maxLength));
        }
        if (vtype) {
            if(!vtypes[vtype](value, me)){
                errors.push(me.vtypeText || vtypes[vtype +'Text']);
            }
        }
        if (regex &amp;&amp; !regex.test(value)) {
            errors.push(me.regexText || me.invalidText);
        }
        return errors;
    },
    selectText : function(start, end){
        var me = this,
            v = me.getRawValue(),
            doFocus = true,
            el = me.inputEl.dom,
            undef,
            range;

        if (v.length &gt; 0) {
            start = start === undef ? 0 : start;
            end = end === undef ? v.length : end;
            if (el.setSelectionRange) {
                el.setSelectionRange(start, end);
            }
            else if(el.createTextRange) {
                range = el.createTextRange(); //文本选择范围
                range.moveStart('character', start);
                range.moveEnd('character', end - v.length);
                range.select();
            }
            doFocus = Ext.isGecko || Ext.isOpera;
        }
        if (doFocus) {
            me.focus();
        }
    },
    autoSize: function() {
        var me = this;
        if (me.grow &amp;&amp; me.rendered) {
            me.autoSizing = true;
            me.updateLayout();
        }
    },
    //组件布局完成后
    afterComponentLayout: function() {
        var me = this,
            width;

        me.callParent(arguments);
        if (me.autoSizing) {
            width = me.inputEl.getWidth();
            if (width !== me.lastInputWidth) {
                me.fireEvent('autosize', me, width);//触发autosize事件
                me.lastInputWidth = width;
                delete me.autoSizing;
            }
        }
    }
});</pre>
<div class="blog_h3"><span class="graybg">Ext.form.field.Trigger</span></div>
<p>该类简单的为Text添加一个可以触发某种行为的触发按钮（Trigger Button），默认效果如下拉框。触发按钮没有默认行为，子类需要通过实现onTriggerClick()方法来添加行为。</p>
<p>文件上传（File）、选取器（Picker）、数字字段（Number）是该类的直接或者间接子类型。</p>
<p>下面是一个简单的例子：</p>
<pre class="crayon-plain-tag">Ext.define( 'Ext.ux.AlertTrigger', {
    extend : 'Ext.form.field.Trigger',
    alias : 'widget.alerttrigger',
    onTriggerClick : function()
    {
        Ext.Msg.alert( 'Message', 'Current value is ' + this.getValue() );
    }
} );</pre>
<p> Trigger提供了以下成员：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">配置项/属性/方法 </td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>{editable}</td>
<td>Boolean=true，如果设置为false，文本框的内容不得修改，必须通过触发按钮引发的行为设置</td>
</tr>
<tr>
<td>{hideTrigger}</td>
<td>Boolean=false，隐藏触发按钮，只显示文本框</td>
</tr>
<tr>
<td>{readOnly}</td>
<td>Boolean=false，隐藏触发按钮，并且禁止编辑</td>
</tr>
<tr>
<td>{repeatTriggerClick}</td>
<td>Boolean=false，如果设置为true，为触发按钮元素添加ClickRepeater</td>
</tr>
<tr>
<td>{selectOnFocus }</td>
<td>Boolean=false，只有editable=true才有效</td>
</tr>
<tr>
<td>{triggerBaseCls}</td>
<td>String='x-form-trigger'，总是应用到触发按钮的样式</td>
</tr>
<tr>
<td>{triggerCls }</td>
<td>String，用于装饰触发按钮的额外样式。一般修改此配置以定制触发按钮</td>
</tr>
<tr>
<td>{triggerNoEditCls }</td>
<td>String='x-trigger-noedit'，字段只读或者不可编辑时，文本框的样式</td>
</tr>
<tr>
<td>{triggerWrapCls }</td>
<td>String= "x-form-trigger-wrap"，包裹触发按钮的TABLE元素的样式</td>
</tr>
<tr>
<td>inputCell</td>
<td>Ext.Element，包裹input的td元素，渲染后设置</td>
</tr>
<tr>
<td>triggerEl</td>
<td>Ext.CompositeElement，所有触发按钮的复合元素</td>
</tr>
<tr>
<td>triggerWrap</td>
<td>Ext.Element，包裹整个输入框、触发按钮的TABLE元素</td>
</tr>
<tr>
<td>getTriggerWidth( )</td>
<td>Number()，返回触发按钮DIV的总宽度</td>
</tr>
<tr>
<td>onTriggerClick()</td>
<td>
<p>void ( Ext.EventObject e )。</p>
<p>触发按钮点击时的处理函数，默认不做任何事情，子类必须实现自己的逻辑</p>
</td>
</tr>
</tbody>
</table>
<p>Ext.form.field.Trigger的源代码分析如下：</p>
<pre class="crayon-plain-tag">Ext.define('Ext.form.field.Trigger', {
    extend:'Ext.form.field.Text',
    alias: ['widget.triggerfield', 'widget.trigger'],
    //作为组件属性的子元素
    childEls: [
        { name: 'triggerCell', select: '.' + Ext.baseCSSPrefix + 'trigger-cell' },
        { name: 'triggerEl', select: '.' + Ext.baseCSSPrefix + 'form-trigger' },
        'triggerWrap',
        'inputCell'
    ],
    triggerBaseCls: Ext.baseCSSPrefix + 'form-trigger',
    triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap',
    triggerNoEditCls: Ext.baseCSSPrefix + 'trigger-noedit',
    hideTrigger: false,
    editable: true,
    readOnly: false,
    repeatTriggerClick: false,
    autoSize: Ext.emptyFn,
    monitorTab: true,
    mimicing: false,
    triggerIndexRe: /trigger-index-(\d+)/,
    componentLayout: 'triggerfield',
    initComponent: function() {
        this.wrapFocusCls = this.triggerWrapCls + '-focus';
        this.callParent(arguments);
    },
    //覆盖父类版本：获取HTML标记，用于插入到DOM中
    getSubTplMarkup: function() {
        var me = this,
            field = me.callParent(arguments);//Base类生成的input元素部分
        return '&lt;table id="' + me.id + '-triggerWrap" class="' + Ext.baseCSSPrefix + 'form-trigger-wrap" cellpadding="0" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr&gt;' +
            '&lt;td id="' + me.id + '-inputCell" class="' + Ext.baseCSSPrefix + 'form-trigger-input-cell"&gt;' + field + '&lt;/td&gt;' +
            me.getTriggerMarkup() +  //触发按钮
            '&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;';
    },
    //生成触发按钮的包装元素的HTML标记
    getTriggerMarkup: function() {
        var me = this,
            i = 0,
            hideTrigger = (me.readOnly || me.hideTrigger),
            triggerCls,
            triggerBaseCls = me.triggerBaseCls,
            triggerConfigs = [];
        if (!me.trigger1Cls) {
            me.trigger1Cls = me.triggerCls;
        }
        //可以指定trigger1Cls、trigger2Cls……等等，可以创建多个触发按钮
        for (i = 0; (triggerCls = me['trigger' + (i + 1) + 'Cls']) || i &lt; 1; i++) {
            triggerConfigs.push({
                tag: 'td',
                valign: 'top',//顶部对齐
                cls: Ext.baseCSSPrefix + 'trigger-cell',
                style: 'width:' + me.triggerWidth + (hideTrigger ? 'px;display:none' : 'px'),
                cn: {
                    cls: [Ext.baseCSSPrefix + 'trigger-index-' + i, triggerBaseCls, triggerCls].join(' '),
                    role: 'button'
                }
            });
        }
        triggerConfigs[i - 1].cn.cls += ' ' + triggerBaseCls + '-last';
        return Ext.DomHelper.markup(triggerConfigs);
    },
    //fieldSubTpl填充上下文
    getSubTplData: function(){
        var me = this,
            data = me.callParent(),
            readOnly = me.readOnly === true,
            editable = me.editable !== false;
        //添加额外两个数据项
        return Ext.apply(data, {
            editableCls: (readOnly || !editable) ? ' ' + me.triggerNoEditCls : '',
            readOnly: !editable || readOnly
        });  
    },
    //带标签的填充上下文
    getLabelableRenderData: function() {
        var me = this,
            triggerWrapCls = me.triggerWrapCls,
            result = me.callParent(arguments);

        return Ext.applyIf(result, {
            triggerWrapCls: triggerWrapCls,
            triggerMarkup: me.getTriggerMarkup()
        });
    },
    //检查禁用状态
    disableCheck: function() {
        return !this.disabled;    
    },
    //渲染前模板
    beforeRender: function() {
        var me = this,
            triggerBaseCls = me.triggerBaseCls,
            tempEl;
  
        if (!me.triggerWidth) {
            //从临时元素自动计算触发按钮的宽度
            tempEl = Ext.resetElement.createChild({ //resetElement专门用于存放临时元素
                style: 'position: absolute;', 
                cls: Ext.baseCSSPrefix + 'form-trigger'
            });
            Ext.form.field.Trigger.prototype.triggerWidth = tempEl.getWidth();
            tempEl.remove();
        }
        me.callParent();
        if (triggerBaseCls != Ext.baseCSSPrefix + 'form-trigger') {
            me.addChildEls({ name: 'triggerEl', select: '.' + triggerBaseCls });
        }
        me.lastTriggerStateFlags = me.getTriggerStateFlags();
    },
    //渲染时模板
    onRender: function() {
        var me = this;
        me.callParent(arguments);
        me.doc = Ext.getDoc();
        me.initTrigger();//初始化触发按钮
        me.triggerEl.unselectable();//禁止触发按钮上的文本选择
    },
    //触发按钮的初始化
    initTrigger: function() {
        var me = this,
            triggerWrap = me.triggerWrap,
            triggerEl = me.triggerEl,//注意triggerEl是符合元素，包含若干按钮
            disableCheck = me.disableCheck,
            els, eLen, el, e, idx;
        //添加事件处理句柄
        if (me.repeatTriggerClick) {
            //ClickRepeater可以应用到任何元素，在元素点击时触发click事件
            //它可以设置触发的最小时间间隔
            me.triggerRepeater = new Ext.util.ClickRepeater(triggerWrap, {
                preventDefault: true,
                handler: me.onTriggerWrapClick, //点击事件处理
                listeners: {
                    mouseup: me.onTriggerWrapMouseup, //鼠标放开事件处理
                    scope: me
                },
                scope: me
            });
        } else {
            me.mon(triggerWrap, {
                click: me.onTriggerWrapClick,
                mouseup: me.onTriggerWrapMouseup,
                scope: me
            });
        }
        //设置触发按钮的样式
        triggerEl.setVisibilityMode(Ext.Element.DISPLAY);
        triggerEl.addClsOnOver(me.triggerBaseCls + '-over', disableCheck, me);
        els  = triggerEl.elements; //所有触发按钮
        eLen = els.length;
        for (e = 0; e &lt; eLen; e++) {
            el = els[e];
            idx = e+1;
            //为所有触发按钮添加样式：trigger1Cls-over
            el.addClsOnOver(me['trigger' + (idx) + 'Cls'] + '-over', disableCheck, me);
            el.addClsOnClick(me['trigger' + (idx) + 'Cls'] + '-click', disableCheck, me);
        }
        triggerEl.addClsOnClick(me.triggerBaseCls + '-click', disableCheck, me);

    },
    //获取触发按钮总宽度
    getTriggerWidth: function() {
        var me = this,
            totalTriggerWidth = 0;

        if (me.triggerWrap &amp;&amp; !me.hideTrigger &amp;&amp; !me.readOnly) {
            totalTriggerWidth = me.triggerEl.getCount() * me.triggerWidth;
        }
        return totalTriggerWidth;
    },
    //设置触发按钮隐藏与否，更新布局
    setHideTrigger: function(hideTrigger) {
        if (hideTrigger != this.hideTrigger) {
            this.hideTrigger = hideTrigger;
            this.updateLayout();
        }
    },
    //设置可编辑性
    setEditable: function(editable) {
        if (editable != this.editable) {
            this.editable = editable;
            this.updateLayout();//更新布局
        }
    },
    //设置只读性
    setReadOnly: function(readOnly) {
        if (readOnly != this.readOnly) {
            this.readOnly = readOnly;
            this.updateLayout();//更新布局
        }
    },
    onDestroy: function() {
        var me = this;
        //销毁成员变量
        Ext.destroyMembers(me, 'triggerRepeater', 'triggerWrap', 'triggerEl');
        delete me.doc;
        me.callParent();
    },
    onFocus: function() {
        var me = this;
        me.callParent(arguments);
        if (!me.mimicing) {
            me.bodyEl.addCls(me.wrapFocusCls);
            me.mimicing = true;
            me.mon(me.doc, 'mousedown', me.mimicBlur, me, {
                delay: 10
            });
            if (me.monitorTab) {
                me.on('specialkey', me.checkTab, me);
            }
        }
    },
    checkTab: function(me, e) {
        if (!this.ignoreMonitorTab &amp;&amp; e.getKey() == e.TAB) {
            this.triggerBlur();
        }
    },
    //用于检查DOM结构是否与组件状态失去同步
    getTriggerStateFlags: function () {
        var me = this,
            state = 0;

        if (me.readOnly) {
            state += 1;
        }
        if (me.editable) {
            state += 2;
        }
        if (me.hideTrigger) {
            state += 4;
        }
        return state;
    },
    onBlur: Ext.emptyFn,
    mimicBlur: function(e) {
        if (!this.isDestroyed &amp;&amp; !this.bodyEl.contains(e.target) &amp;&amp; this.validateBlur(e)) {
            this.triggerBlur(e);
        }
    },
    triggerBlur: function(e) {
        var me = this;
        me.mimicing = false;
        me.mun(me.doc, 'mousedown', me.mimicBlur, me);
        if (me.monitorTab &amp;&amp; me.inputEl) {
            me.un('specialkey', me.checkTab, me);
        }
        Ext.form.field.Trigger.superclass.onBlur.call(me, e);
        if (me.bodyEl) {
            me.bodyEl.removeCls(me.wrapFocusCls);
        }
    },
    validateBlur: function(e) {
        return true;
    },
    //点击了触发按钮包装元素后的处理：
    onTriggerWrapClick: function() {
        var me = this,
            targetEl, match,
            triggerClickMethod,
            event;

        event = arguments[me.triggerRepeater ? 1 : 0];
        if (event &amp;&amp; !me.readOnly &amp;&amp; !me.disabled) {
                targetEl = event.getTarget('.' + me.triggerBaseCls, null);
                match = targetEl &amp;&amp; targetEl.className.match(me.triggerIndexRe);

            if (match) {
                //调用相应的onTrigger1Click……以支持多个触发按钮
                //默认调用onTriggerClick
                triggerClickMethod = me['onTrigger' + (parseInt(match[1], 10) + 1) + 'Click'] || me.onTriggerClick;
                if (triggerClickMethod) {
                    triggerClickMethod.call(me, event);
                }
            }
        }
    },
    onTriggerWrapMouseup: Ext.emptyFn,
    onTriggerClick: Ext.emptyFn
});</pre>
<div class="blog_h3"><span class="graybg">Ext.form.field.Picker</span></div>
<p>该类是一般“选取器”，例如：下拉列表（ComboBox）、日期控件（Date）、下拉树（TreePicker）等的基类。它提供了以下公共逻辑：</p>
<ol>
<li>点击触发按钮、执行键盘导航后，切换Picker下拉窗的可见性</li>
<li>依据配置matchFieldWidth、pickerAlign/pickerOffset来缩放、对齐Picker下拉窗</li>
</ol>
<p>子类必须实现createPicker()，以提供picker下拉窗的内容。</p>
<p>Picker类提供了以下成员：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">配置项/属性/方法 </td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>{matchFieldWidth }</td>
<td>Boolean=true，是否让Picker下拉窗的宽度精确的与字段宽度一致</td>
</tr>
<tr>
<td>{openCls}</td>
<td>String="x-pickerfield-open"，Picker下拉窗打开时，添加到bodyEl的样式</td>
</tr>
<tr>
<td>{pickerAlign}</td>
<td>String="tl-bl?"，Picker下拉窗的默认对齐方式</td>
</tr>
<tr>
<td>{pickerOffset}</td>
<td>Number[]，与 pickerAlign一起使用，设置Picker下拉窗的位置偏移量</td>
</tr>
<tr>
<td>isExpanded</td>
<td>Boolean，指示当前Picker下拉窗是否打开</td>
</tr>
<tr>
<td>alignPicker()</td>
<td>protected void()，把Picker下拉窗对齐到INPUT元素</td>
</tr>
<tr>
<td>collapse( )</td>
<td>收起Picker下拉窗</td>
</tr>
<tr>
<td>expand( )</td>
<td>打开Picker下拉窗</td>
</tr>
<tr>
<td>getPicker( )</td>
<td>Ext.Component()，得到当前Picker的下拉窗组件，如果必要，调用createPicker()创建之</td>
</tr>
<tr>
<td>createPicker( )</td>
<td>创建并返回一个组件，作为Picker下拉窗。子类必须实现该方法。当前Picker对象也必须作为该组件的<span style="background-color: #c0c0c0;">构造参数pickerField</span></td>
</tr>
<tr>
<td>onTriggerClick( )</td>
<td>覆盖版本。默认行为是使下拉框组件在打开/收起之间切换</td>
</tr>
<tr>
<td>⚡collapse</td>
<td>void ( Ext.form.field.Picker field, Object eOpts ) 下拉窗收起时触发</td>
</tr>
<tr>
<td>⚡expand</td>
<td>void ( Ext.form.field.Picker field, Object eOpts ) 下拉窗展开时触发</td>
</tr>
<tr>
<td>⚡select</td>
<td>void ( Ext.form.field.Picker field, Object value, Object eOpts )，通过下拉窗口选取了字段值时触发</td>
</tr>
</tbody>
</table>
<p>Ext.form.field.Picker的代码分析如下：</p>
<pre class="crayon-plain-tag">Ext.define('Ext.form.field.Picker', {
    extend: 'Ext.form.field.Trigger',
    alias: 'widget.pickerfield',
    matchFieldWidth: true,
    pickerAlign: 'tl-bl?', //对齐下拉窗的左上角到inputEl的左下角，?表示尝试按指定方式对齐，但是会收到viewport的约束
    openCls: Ext.baseCSSPrefix + 'pickerfield-open',
    editable: true,
    initComponent: function() {
        this.callParent();
        //添加额外的事件
        this.addEvents(
            'expand',
            'collapse',
            'select'
        );
    },
    //初始化必要的事件处理器，该方法会由Renderable在渲染后自动调用，那时组件的DOM结构已经生成
    initEvents: function() {
        var me = this;
        me.callParent();
        //处理打开/收起下拉窗的键盘导航事件
        me.keyNav = new Ext.util.KeyNav(me.inputEl, {
            down: me.onDownArrow, //向下箭头按钮
            esc: {
                handler: me.onEsc, //ESC按键
                scope: me,
                defaultEventAction: false
            },
            scope: me,
            forceKeyDown: true
        });
        //如果配置为不可编辑，点击输入框亦可打开下拉窗
        if (!me.editable) {
            me.mon(me.inputEl, 'click', me.onTriggerClick, me);
        }
        if (Ext.isGecko) {
            me.inputEl.dom.setAttribute('autocomplete', 'off');
        }
    },
    //处理ESC按键
    onEsc: function(e) {
        var me = this;
        if (me.isExpanded) {
            //如果是展开的，则收起，停止默认行为和事件传播
            me.collapse();
            e.stopEvent();
        } else {
            //如果当前字段具有一个Window组件，必须让当前字段失去焦点
            //因为Window会在收到ESC时隐藏，此时如果当前字段仍然获得焦点，那么向下箭头按键将导致下拉窗意外显示
            if (me.up('window')) {
                me.blur();
            }
            //如果没有启动焦点管理器，停止默认行为和事件传播
            else if ((!Ext.FocusManager || !Ext.FocusManager.enabled)) {
                e.stopEvent();
            }
        }
    },
    //向下按键
    onDownArrow: function(e) {
        if (!this.isExpanded) {
            //触发按钮点击逻辑
            this.onTriggerClick();
        }
    },
    //打开Picker下拉窗
    expand: function() {
        var me = this,
            bodyEl, picker, collapseIf;
        if (me.rendered &amp;&amp; !me.isExpanded &amp;&amp; !me.isDestroyed) {
            //如果尚未渲染，不执行任何动作
            bodyEl = me.bodyEl;
            picker = me.getPicker(); //获取或者创建下拉窗组件
            collapseIf = me.collapseIf;
            picker.show(); //显示下拉窗
            me.isExpanded = true; //设置展开状态
            me.alignPicker(); //对齐下拉窗
            bodyEl.addCls(me.openCls);
            //侦听点击和鼠标滚轮事件
            me.mon(Ext.getDoc(), {
                mousewheel: collapseIf,
                mousedown: collapseIf,
                scope: me
            });
            //当浏览器窗口大小改变后，重新对齐下拉窗
            Ext.EventManager.onWindowResize(me.alignPicker, me);
            me.fireEvent('expand', me); //发布展开事件
            me.onExpand(); //调用模板方法
        }
    },
    //打开下拉窗时的模板方法
    onExpand: Ext.emptyFn,
    //对齐下拉窗
    alignPicker: function() {
        var me = this,
            picker = me.getPicker();
        //如果是收起状态，不需要做任何事情
        if (me.isExpanded) {
            if (me.matchFieldWidth) {
                //匹配当前字段的宽度
                picker.setWidth(me.bodyEl.getWidth());
            }
            if (picker.isFloating()) {
                //如果下拉窗是浮动的（一般都是）
                me.doAlign();
            }
        }
    },
    //对齐下拉窗
    doAlign: function(){
        var me = this,
            picker = me.picker,
            aboveSfx = '-above',
            isAbove;
        //浮动组件，以inputEl为基准进行对齐
        me.picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset);
        //对齐后，是否位于输入框的上面？
        isAbove = picker.el.getY() &lt; me.inputEl.getY();
        //设置相应的样式
        me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
        picker[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
    },
    //收起下拉窗
    collapse: function() {
        if (this.isExpanded &amp;&amp; !this.isDestroyed) {
            var me = this,
                openCls = me.openCls,
                picker = me.picker,
                doc = Ext.getDoc(),
                collapseIf = me.collapseIf,
                aboveSfx = '-above';
            picker.hide(); //隐藏下拉窗组件
            me.isExpanded = false; //设置状态
            //移除打开样式
            me.bodyEl.removeCls([openCls, openCls + aboveSfx]);
            picker.el.removeCls(picker.baseCls + aboveSfx);
            //移除只有在打开时才有意义的监听器
            doc.un('mousewheel', collapseIf, me);
            doc.un('mousedown', collapseIf, me);
            Ext.EventManager.removeResizeListener(me.alignPicker, me);
            me.fireEvent('collapse', me); //发布事件
            me.onCollapse(); //调用模板
        }
    },
    //收起下拉窗时的模板方法
    onCollapse: Ext.emptyFn,
    collapseIf: function(e) {
        var me = this;
        //仅匹配以下条件时，收起
        if (!me.isDestroyed  //当前组件没有被销毁
            &amp;&amp; !e.within(me.bodyEl, false, true)  //且滚轮、鼠标事件不是发生在字段体内、或者子段体的子元素内
            &amp;&amp; !e.within(me.picker.el, false, true) //且滚轮、鼠标事件不是件发生在下拉窗体内、或者下拉窗体的子元素内
            &amp;&amp; !me.isEventWithinPickerLoadMask(e)//且滚轮、鼠标事件不是发生在下拉窗的LoadMask中
        ){
            me.collapse();
        }
    },
    //获取或者创建下拉窗组件
    getPicker: function() {
        var me = this;
        return me.picker || (me.picker = me.createPicker());
    },
    //子类必须实现
    createPicker: Ext.emptyFn,
    //默认行为：使下拉框组件在打开/收起之间切换
    onTriggerClick: function() {
        var me = this;
        if (!me.readOnly &amp;&amp; !me.disabled) {
            if (me.isExpanded) {
                me.collapse();
            } else {
                me.expand();
            }
            me.inputEl.focus();
        }
    },
    //模仿失焦
    mimicBlur: function(e) {
        var me = this,
            picker = me.picker;
        //忽略下拉窗内部发生的mousedown事件
        if (!picker || !e.within(picker.el, false, true) &amp;&amp; !me.isEventWithinPickerLoadMask(e)) {
            me.callParent(arguments);
        }
    },
    //销毁模板
    onDestroy : function(){
        var me = this,
            picker = me.picker;
        //销毁浏览器窗口大小改变监听
        Ext.EventManager.removeResizeListener(me.alignPicker, me);
        //销毁键盘导航监听
        Ext.destroy(me.keyNav);
        if (picker) {
            delete picker.pickerField; //防止循环引用
            picker.destroy();//销毁下拉窗
        }
        me.callParent();
    },
    //判断是否下拉窗当前具有一个加载遮罩，并且事件发生在遮罩或者其子元素里面
    isEventWithinPickerLoadMask: function(e) {
        var loadMask = this.picker.loadMask;

        return loadMask ? e.within(loadMask.maskEl, false, true) || e.within(loadMask.el, false, true) : false;
    }

});</pre>
<div class="blog_h2"><span class="graybg">实战：取色器控件</span></div>
<div class="blog_h3"><span class="graybg">取色器控件代码</span></div>
<p>取色面板组件：</p>
<pre class="crayon-plain-tag">/**
 * 基于HSV（色相/饱和度/亮度）色彩空间的调色板
 */
Ext.define( 'Ext.ux.panel.HSVColorPalette', {
    alias : 'widget.hsvcolorpalette',
    extend : 'Ext.panel.Panel',
    requires : [
        'Ext.Img'
    ],
    statics : {
        hsvImgSize : [
            181, 101
        ],
        sldDivCount : 60,
        hsImgData : "data:Img/png;base64,iVBORw0KGgoAAAANSUhEUgAAALUAAABlCAIAAACEDzXRAAAKQ0lEQVR42u2d23IjKwxFBeRh5v8/9uQlzXlI2gGELoCEm6pxubp6PI69WoV3C20uIQPAH4A/AH/p41/pDcTxP4BPGDtq3vYJGPpv8craeRdo/fyTI8ZhHnodOGgcV/3rnx8QI0SABO2xfAbiCfVR8Wj+CD8jenbRJNyGOxLoAaH1iLu4EUEHAv1+MsQ4zLE4AiIOcpibv4u9OET6+cP4ASnJ1JH4/G60A9km+GbBxLlpKAkADoR2JnaBrvWD/x1GCZn9EYLEHqVfI6Efx0D7E9tDF/rBSJ54l8FqTUse0J+EvxPrdK0fD4aOLXQi7zyGxMaRvvWjjH2U8o9I44OKnf+wyCpHoR/Phk6d/COq8481YrNI3/qR2B+kMl1Fl6J8o/JrU/GEA6HdiB2ha/0YBVx7rFwNHAj9JuIl6EI/qKyJug4Yvg7xA0QEpB8nQe8itoQu9EOT8M3mTsqUSUzykH6cBO1PbA9d91+ShDzV9xrtcgW21IT6L0yfcS90FKCdw+wS6Q9ISWjVfLTVVT2mdiPGGRduFKWm7dBStWlLmI0j/QExdgp7gT5qCu2BLKiLqtc94hI7PB8auQP4KuyIvSJ960fUqXWcd2F48Khr1bHSj2dDp/afmg7tMrFxpG/9mHMFuqrH3spFw0s0Bgr9OAzamdgFutaPpMuqo6WVEXWJdSL14xhof2J76MKfS2yXWGOVB1WXXGOVdxFSx587CXoLsTE0689RH7VW4RMLeKI9wPpzz4XeS2wDzdbXR52AWStjwiSAA6HdiB2h1f6cqT0whzzuzz0LejuxATTy90Xt40v7CzdGRvKS4O/zQ0AiXaRW5x/dv4vS4A/a308LYR5pFquRRvlHIrKWoCj5woCVId4eR/KPNDjURg2tdEC7rSQN5B9DYR70X5YivVxft7AyrOvrD4V2JnaBJurr+qpemLEywlRhT6qvPx16C7ExdK0fSjcj0maAzsqgBt7rfIxGP46B9iT2gi70I842bNj0U4wd/WAG/L5VP3p0W4iNoYv6Oh6eoJmSMW5laIzF8iT1TtgeV9RZoZ7QvdEUzsQu0CP+HOMKDFoZvDFg6s89CNqf2B5a4e8Hts84W0pgeohBtspBXYp8H3Tk/H23MBtHuvbn5mp7dlU9TUkP+XNJMabzHdCIbnuYDSItzZ+bdgUWrAwNPhwInRxw1/wXuYGj+4vo7flboRpjEQ6EjgojYK9/K04NFfNTauEBypXQWQLAfqQoHl9CfvpQ6C3ExtC9/q2YW2u6X1JWzfsYFPKX3L99LrQ/sT10XR+jKh+UN2AxlDOyXfLQaxxf/fkvT4d2JnaBZv258M6h4IFu2Em7/sezoLcQG0Mjfy6xrqLnVBLGWHw1i69WP5LCCn0TNEL3DLNXpCV/n5oo5jAVLY5Em6i8Pwm6uSNuCrNxpNX+nDira20qqyZriv384xhoZ2IX6Lr/wg8eiwpXYGQqPEgjsBrX+er3X46B9ie2h6bX/4i6IfcLS2mIY++7TfoCuLj6hwgN89CwBJ3Gx8rOVsn0kQ48tDS/4X1L8SzMb3BbkGdtwsD05AbP9YOU8xui+uljZei/P/3qx2HQbsSO0Ar/lveHLZYCVK4GeN1P5N+eAb2R2Aya9ueUY+8dlhLtdhWvgfEfz4AeGf9hR2wcaeTPpacvRdzLTx8J/Qq1Ij995PrJ3/mp0p8LO5YyF0s215g/9yxoZ2IXaLo+FiwtgTlvIAznp4+G3khsBk3Pf5mYvqOwMqZnNnw3i9cRDoT2JPaCZuvreskDuXkHxfQMvfbBgdBbiI2hC/3QLCg6lESZZkrfJ7nSj3gWNEMcRkYJsf5L0BXENBl1WV8/aquMLM9/eSJ0UtxZmFGGg4MigZ75ovHnvsOcUX09zRbzjCrU/K/Rev0gT2i2vu4cZrNI1+sH2e49Yb3lRC6e9fSS9ETohjj7hdkx0rQ/57/rxIrhBQdCv494Hlrtz1lo30Qjz1z+cRh02ndTNIMm1j8NxtmePs+76i+/esJHrH/6dGh/YntoRf9l2RtQWgJXzY4bdtbqx0OhnYldoHv7I79jf4+rOGGaRWr14yToLcTG0Ky/v32rjAsdX9S5vgI4ENqT2Asa7Z/9vq0yStiGNNVHkOe/RGmqwBQ0jEDHFpoJ85BLN+jPaRoHGWl2/2yx3msxVRHjZ0Td4GfV/Bf9bCM1dBiBfrHeJ1HR6dKEedDf5yPdBLilp/efC3Ribb1VxlUHGQe8iTb0598+Hdqf2B6a3b9SOerezsoIBWaJ3IBn1fp0j4PeQmwMzc5/idLK3UZWRkmKX2zA4Vc/DoPeSGwGrdvfY24RpKmVjrLuCQdCuxE7Qkv64TZ3Z44dVPrxUOh3EK9C1/lHGpz7ZzpVsYTF7K8Hyj+Ogd5FbAk9uL9H9LIyYg821sjo13gYtD+xPbRU/9Ds/b1sZTTIr5OStMGHA6GdiV2gUf2UmQcfJdUbXOoe6HoN1A0b2uQD4EBosXJqQTwKnXvXAEX+oZlfGaS9ewetDGCLeV1exH4e9Mr+2TDpv2iggYFW+LdBsTbngv/yUroGH0jleEX7MOgtxMbQ7PzKoMuajKyM8pYIiB194HnQzsQu0Gp/LvpulVHylsgK/TgG2p/YHrqXf6T3bJUBhfxFWUDPg95CbAytm/+ycasM9WOxWu0PnTniLWE2iLRi/4Z3bJXRvY76s0+Arl93I3aMNOHPBce2PdSkL7Kpnwe9l9gGutCPoFgkUtytXAGbex0vKCznSOs00o+ToLcQG0N/QErCrG6jrTIynVUDwo896vCzssPrNnAYtD+xPfR9fwmKeu/CVhm5OAn1STPkvhxBW1KXjeP60Y/DoJ2JXaAL/RCPy+PXc80L9d7OV294dVPbC/fb4EDoLcTG0IV+TCDrrIxMHIFg512BWj9OgvYk9oK+9SMoZG55q/JMiGS40/sLjacu28T3MVfd0Aha+u3QJXr+0Q//MBtHutaPQOzh3H0RBoZS5OJ/MjqJ9x2vQb7qgSuvX0StH0+FLptzrvTDh9gl0oV+iAbi2lAsqNmbK3yBAyLNdbRzW8Y6Btqf2B76A2IURE2zZY16KGfZlwqofJSL4feABka+xCNX+ekx0LuILaFr/Zit7s4ZA7y5Qg2sDr/t4zDodxCvQt/6occctDKGriMPzH85D9qN2BG61g/QwcLSVDSGl78C6LePY6A3EptBf0BKcjWWH/GqyJqoxKnMufEVBLJxAJwJ7U9sDH3fX0CxnIB4orMyuilTyRh6g+0bfDgQ2p/YHrrQD+jV6qb/SdRrMit8TRWwyEabxgFwJrQnsQt0rR+LR3VqFNiGHRBv73ge9EZiM+haPyicuRcVmIxzAMTMl9wfi3sGtBuxF3ShH92C29yJTteU+ohbRrZk3Qe9hdgY+tYPnOqanA+mTN33ZGKE06HQbsQu0LV+MF8+94rU5dK8B8/KyJaI+6D9ie2hC/3Q12QnSnYLn5p9P/6d0D7EltAh5wz/Hv8exON/LUjHOuz5CksAAAAASUVORK5CYII=",
        hvImgData : "data:Img/png;base64,iVBORw0KGgoAAAANSUhEUgAAALUAAABlCAIAAACEDzXRAAAK90lEQVR42u2d644bNwyFj7wt2gB9/1dt/khqkMSORhJvEukdtTUGxuxk4/2G0NASDymmCuDP78cX+v2L9AvE+9/AV9jeNb/27RigvzRX9s6nQPvnXzni0cym6+CgR7vqr3/9LWP2qtf3Mnvvjjx5n17uju6D6/DHda/2/7VHuZ5P6fPlnMFtP2P8+A6hMyZLDMIOU0uPJpdsXGY2KcPfn5i8GR+VtXMdBkeVqLOMXAijTK1duYF8DHQAMRAG/VvBbKQz+EV6GhtO3mcU+iEcn8PKeY7DoL2JgTDo4fulDvhT9iK5v+ZRLDP8OvPWI/jorVlX7Q1doqB3iHNPDIRZ+uo/QCMXAn8wb3uFQi4Eb6HZ1VMQV+gcC71GPNoTYZb+7j+q7ii6I/86Mm1kzU2wo/086DBiIAya8B/8IFff0DLd9us86J1xgzDo2fqWmr5QpJUc25ldJFI3gRXLnwftRwyEQQ/rF82sSZzkXR9FzYRvY7Z3HrQ3MRAGPcQ/9KuuyoaarkuBLPGurhYXoMs6dHGADjAzEGbp2fqWYhftPMRuijRlYsClyNgydF2Hrm7QrmYGwiz9/H6ps3c+tkfZvAnljVE95gOYqB4Rbj8P2oV4lCxm13ygaf8BBTW/Ks/cPxYFOMz+4xjoHeLxBnQL2hXoZn5adQqBUht4PoprksDU5UnzjzOgA4iBMOir/2DmTkU3t87zRzHrptTFWX+5KbQ3MRAGPcTHqkUqnw7v3Etdmf11jVRetaGEY6BdiYEwaCL/A4oYnqgQZFkMEEN9HkHIW0N7EANh0N/9R106FGF+LxlgQ3+5EXQYMRAGTfsPrFIbpS5vQeM86G1ipT63Ak3PP5i1F+P11Pp+lRITsChlgJYyqmqJq5zqFTZCvTdp4s2cBX0/O1qaWL9oYnuFCDIp8oOqJAzApr+I3+bMKLFAV52aoc48XTOzcf6xZWmL/vLe+LqT/nI76ADizfg6q78URhioS7E9NlStD+nVRf3lDGhXYmV+8gr0039U6b2YdQyqVED8MBGHVTNuDR1DTNU3OEDb/YfF9+ld3if5jyxD53v5jykdwqCJ+YcYuxlP8uSE+pdKFxxNFwRq/QW6Ec0mVBRFcZQmz0anv3gQA2HQbP0Lrw34SV2MJGDXXw6A9ibW63NmaKL+hU+JVMb2sjakp8mNtMQ/DoDeIc6Cvp8dodn8dWtUL/dxvNk1Q2DPNX56X+id4Omgz62FUPn89RqgDehK0d6uv+xBly3oGgAtxcd2Lb2h3yq0RbFI8e367R509YdWjgZewkWYpdn8D7A1eaL7+7D5u8KqGWop4wxoV2IgDJrNH6u6Je5I/SEsFTWL2431y92hvYnF+pd1aHX+aWFX5XVG/aGNfIzCgFP+6R2hA4iBMOjt/PVKj+2s2pXiM/LXPx/alVi5/8dO/rq+MKMqhKOPy6OYN+LrTvUvd4F2IW5YO//B74u1Am2vn2Pk587r5XlwvdCfFFY/dzvoHWKL/9iFJuYfVRG4EefWHyp9Tizp2q6/vRd0ADEQBj2sX0RtQEwhe0yWAmLymCgJGOv3bw3tTQyEQc/i66ZgHjVxenChhDpj38v5VebN7kHzQSbYoPVmrgIxEGZpNn9dv0jfk7rutBVP3gqux0Bv199u7h/kW4zRjO1s+R9h+stdoMOIgTBoy/6Fyt0AH8+jCKqi5i9s7194a2gnYlG/XYfe2P+07Lpqpersuv/pXaB3iPNzWOjyP7agXfdPfoE/h3cmQgl32j/5E6Ad909+/Pp+id4/Wb8xuBi1eRikLrHUyGn/9btABxAr9bkVaHr+oZcHqm2qVz2Vl7OhnYiBMGii/mWtZOfF+3wXu06s7Y2lM+8doWOIgTBoov4FPu5v399B9UCeB+1KDIRBX+cfyqodzaxJ+iqvG9tzSlXkS6xIv/xHgWoX1CVopZmLbjpNzD8yLfFrwmXVuL4txlXXD2unflZtXXgFrG/FbN8klJJE9vcoRn3uaWYocu35LEMyxVAdH9veiievRvL8wupv3D8okriLfxS5/mUdWh1fVwZ70+XIMY0nPEcDD509oX2be7TQCLO0or/HhuD1Sc09ToVeHj1xcqKiv4eGNJFf5e/y1udB+xEr9bkV6KX+HuX7F2CL/5j5vrKSshLW3+NG0N7EQBi0vb/H44o/ju2kehSd1IzzoAOIgTBodn8Y0NQPljdfHsU1YcAvP/mO0K7Emvjpan+PbE28f1E/ZneQLjehjPS+pb/HLaBjiIEwaNp/6Nk75GZsW9uq23eW0pudwrVAF4vUVQ3cJjPn4Z2tf9myNL2/lOj7XrwdeMMuxqmVLq+q5h9g7fyCnlpbB63f/1Saf9QNM48DRbG/5aKlLf09OiOPBu+src6zKYH9Pe4I7U0cCE3s76BJqU4Ddcee5F3TlGKRh/5yL2hXYs3+dIvQRH2DCDte7Ngh75pWdeUZ21ut3BfaiTgQeqm/xxR2drjn22/sL/X50GHEQBi0VB+1hq8Y1ZElUudB7xEHQkv1lWB5R/zX6/pVno2Ff9jSX86A9iMGwqAV9dkjdcdbruDNHSz393DVXzagSwj0JjF64kBLW/Z36KhfJxjAnyemlvBh/T02oLM/tAvxgB5laXZ/GCp2kwbk9kf0o1rsqlgkl2ffn//u0CbiNLsBaKHrDrSivwdFPUW+4pcNYQBb/T1uCr1MDI4YupXLCrRaf3k5u+4OQA5pUVWsio059/anuy+0K3EgtKS/8HMnDPhXG2i6TuiFo1X95V7QAcSa+OkitLG/R4vcUkuPonU1AM/+HreD9iYOhLb39+jcH2jwagjmBff3QMNdhI8q0m4Jb+nvYSEGwqDt/T1UCe+TWdNCeM81FElxJw7as1WGG/GGpd/c32O8let1934THv09Pg3akXh45KOgaf9hGtWP+XVxE0Dvp/E8aA/iQGh6/pFmay80qnOh/fT1UayKTSKLT3+PY6BdiYEw6EF/SfTEGsMdTOen9eeWA99OqlRD7tTfIw02j4Eu0vZu6vWLycx1tn5piIEwS1/1l9ScdKbusu7b9NkWvKV+kA0EqHz7pf4eI3TbbfiN0Or4h9XMGISiKzEQZunZ90u6IoM2eGvtNOBXVUNosWpHFz89DNqVGAiDbvp7JOIdBD4vDDSP4gKvWn85DDqGGAiDpv3HNBSMK/UU/wdy+vkoMt0lNFFfo/84BnqH+OU2Hr8+RMlq768+n3+gwU9XZAzUjyau+mgeigfZtLmwF2HL/zgMep+4HSip9x/O0LP6l0TPsF7sGGDT1drNo1gV0fS9/LHDoL2JgTBoIv+0XQDWIRKTmgx8DLmRr1GdBI+m6VdjyT89DNqPGAiDVuSv81F/Kre6/jT1XhB9WYI5DHqPGAiDfta/mG4laQ8To11/OQw6jNhqvP36l8oi8zeBiamhw8Ru/VwwtOlO/Iin0JiPD2dLE/nJacCvhJE7dvRFGnwImU/TVUxOT4W2EieBOAp6yB+brr2mI7nO8u2vd0ABWk/U+ssZ0N7EQBj0Nf80schT8eA6TeqoQYT4d36k40xnQMcQR0HT+yd3sV8+rk2Ef/ngrf7dMqU7CdqJ2AV3Dp3+6rTuNPtx+SIRPN68OAj0J0HHEEdBz+pf0uAHNS5yVqpYCfblE8UU5AxoV2JH1h4k/UElJ3mdD/Nzl/OjoWOIQ6DT7zz+3pWqWPGvXfkXQCfPK1HQ6aGZRyWn39FN3Zx2uz8P2o/4vZb+//Wfff0DYN14K3LjH3UAAAAASUVORK5CYII=",
        crossImgData : "data:Img/gif;base64,R0lGODlhDwAPAKEBAAAAAP///////////yH5BAEKAAIALAAAAAAPAA8AAAIklB8Qx53b4otSUWcvyiz4/4AeQJbmKY4p1HHapBlwPL/uVRsFADs=",
        spotImgData : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsSAAALEgHS3X78AAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAJJJREFUeNp8zjEOAVEYReF7816m1qBRiKhYAY0dEHuwLLuwAg0b0BFRsAHVzPXPXJ2Q4GtPczhfLFEURY/k1MAAAAicbe8kXTPs3kNa2cabEckR7HWWqgk+2htilquyGuK3fpYq/JNVlieA4+/Zlyxpb3tMEq8XArZBcpu67c5d0jEiUtTRijoiIg5N02xSSrfnALz7SaW7uwjMAAAAAElFTkSuQmCC"
    },
    /**
     * @cfg {String} mode
     * 调色模式，支持hsv、hvs
     */
    mode : 'hsv',
    /**
     * 所有颜色的数组，每个元素为三元数组，分别为色相（H）、饱和度（S）、明度（V）
     * 有效值范围分别为0-6, 0-1, 0-1
     */
    colors : [
        [
            0, 0, 0
        ]
    ],
    currentColorIndex : 0,
    initialPositioned : false,
    multiColorsMode : true,
    multiColorRows : 2,
    multiColorCols : 6,
    defaultColor : 'FFFFFF',
    colorCellCls : Ext.baseCSSPrefix + 'hsv-color-palette-cell',
    colorCellEmptyCls : Ext.baseCSSPrefix + 'hsv-color-palette-cell-empty',
    colorCellSelectedCls : Ext.baseCSSPrefix + 'hsv-color-palette-cell-selected',
    getCurrent : function()
    {
        return this.colors[this.currentColorIndex];
    },
    initComponent : function()
    {
        var me = this;
        me.colors = [
            [
                0, 0, 0
            ]
        ];
        me.addEvents( {
            /**
             * @event
             * 当前颜色改变时触发该事件
             */
            curcolorchange : true,
            /**
             * @event
             * 当组件第一次被渲染到预期的位置后，触发该事件
             */
            initialpositioned : true
        } );
        var sldDivCount = me.statics().sldDivCount;
        var multiColorCells = [];
        var multiColorCellsCount = me.multiColorRows * me.multiColorCols;
        for ( var i = 0; i &lt; multiColorCellsCount; i++ )
        {
            multiColorCells.push( {
                xtype : 'box',
                cls : [
                    me.colorCellCls, me.colorCellEmptyCls
                ]
            } );
        }
        Ext.apply( me, {
            width : 194,
            minHeight : 160,
            cls : Ext.baseCSSPrefix + 'hsv-color-palette',
            bodyCls : Ext.baseCSSPrefix + 'hsv-color-palette-body',
            frame : false,
            border : true,
            layout : {
                type : 'vbox',
                align : 'center'
            },
            items : [
                {
                    //上半部分部调色板
                    //包装容器
                    xtype : 'container',
                    itemId : 'palette',
                    layout : {
                        type : 'hbox'
                    },
                    items : [
                        {
                            xtype : 'container',
                            layout : 'fit',
                            cls : Ext.baseCSSPrefix + 'hsv-color-palette-pad-wrapper',
                            items : [
                                {
                                    //取色点
                                    itemId : 'spot',
                                    xtype : 'image',
                                    src : me.statics().crossImgData,
                                    //相对于容器浮动
                                    floating : true,
                                    //浮动组件的阴影效果
                                    shadow : false,
                                    //只有autoShow为true的浮动组件才会自动显示
                                    autoShow : false
                                }, {
                                    //调色板
                                    itemId : 'pad',
                                    xtype : 'image',
                                    width : me.statics().hsvImgSize[0],
                                    height : me.statics().hsvImgSize[1],
                                    src : me.mode == 'hsv' ? me.statics().hsImgData : me.statics().hvImgData
                                }
                            ]
                        }
                    ]
                }, {
                    //调色滑动器
                    xtype : 'container',
                    itemId : 'sld',
                    layout : {
                        type : 'hbox',
                        align : 'stretch'
                    },
                    cls : Ext.baseCSSPrefix + 'hsv-color-palette-sld-wrapper',
                    width : 181,
                    margin : '14 0 0 0',
                    height : 20,
                    defaults : {
                        flex : 1,
                        xtype : 'box'
                    },
                    //覆盖父类方法
                    xhooks : {
                        initComponent : function()
                        {
                            var sld = this;
                            sld.items = [];
                            for ( var i = 0; i &lt; sldDivCount; i++ )
                            {
                                sld.items.push( {} );
                            }
                            sld.items.push( {
                                xtype : 'box',
                                itemId : 'knob',
                                cls : Ext.baseCSSPrefix + 'hsv-color-palette-sld-knob',
                                floating : true,
                                shadow : false
                            } );
                            sld.callParent( arguments );
                        }
                    }
                }, {
                    //下半部分值区域
                    xtype : 'container',
                    itemId : 'cc',
                    width : 181,
                    margin : '15 0 0 0',
                    padding : '3 0 3 0',
                    hidden : !me.multiColorsMode,
                    cls : Ext.baseCSSPrefix + 'hsv-color-palette-colors-container',
                    layout : {
                        type : 'table',
                        columns : me.multiColorCols,
                        tableAttrs : {
                            style : {
                                width : '100%'
                            }
                        },
                        tdAttrs : {
                            style : {
                                padding : 3
                            }
                        }
                    },
                    items : multiColorCells
                }
            ]
        } );
        me.callParent( arguments );
    },
    /**
     * 得到调色板组件
     * @return {}
     */
    getPad : function()
    {
        var me = this;
        return me.queryById( 'pad' );
    },
    /**
     * 得到调色板中的取色点组件
     * @return {}
     */
    getPadSpot : function()
    {
        var me = this;
        return me.queryById( 'spot' );
    },
    /**
     * 得到颜色滑动条组件
     * @return {}
     */
    getSld : function()
    {
        var me = this;
        return me.queryById( 'sld' );
    },
    /**
    * 得到颜色滑动条中的滑块组件
    * @return {}
    */
    getSldKnob : function()
    {
        var me = this;
        return me.queryById( 'knob' );
    },
    /**
     * 得到颜色单元格的容器
     * @return {}
     */
    getColorContainer : function()
    {
        return this.queryById( 'cc' );
    },
    /**
     * 得到颜色单元格列表
     * @return {} Ext.util.AbstractMixedCollection
     */
    getColorCells : function()
    {
        return this.getColorContainer().items;
    },
    //@private
    getRelevantColorCellIndex : function( e )
    {
        var me = this;
        var ccs = me.getColorCells();
        var len = ccs.getCount();
        for ( var i = 0; i &lt; len; i++ )
        {
            var cell = ccs.getAt( i );
            if ( e.within( cell.getEl(), false, true ) )
            {
                return i;
            }
        }
        return -1;
    },
    /**
     * @private
     * 将RGB色彩转换为HSV色彩
     * @param {} r 0-1 红色
     * @param {} g 0-1 绿色
     * @param {} b 0-1 蓝色
     * @return {} [h,s,v]
     */
    rgb2hsv : function( r, g, b )
    {
        if ( r.length == 3 )
        {
            g = r[1];
            b = r[2];
            r = r[0];
        }
        var n = Math.min( Math.min( r, g ), b );
        var v = Math.max( Math.max( r, g ), b );
        var m = v - n;
        if ( m === 0 )
        {
            return [
                null, 0, v
            ];
        }
        var h = r === n ? 3 + ( b - g ) / m : ( g === n ? 5 + ( r - b ) / m : 1 + ( g - r ) / m );
        return [
            h === 6 ? 0 : h, m / v, v
        ];
    },
    /**
     * @private
     * 将HSV色彩转换为RGB色彩
     * @param {} h 0-6 色相
     * @param {} s 0-1 饱和度
     * @param {} v 0-1 亮度
     * @return {} [r, g, b]
     */
    hsv2rgb : function( h, s, v )
    {
        if ( h === null )
        {
            return [
                v, v, v
            ];
        }
        var i = Math.floor( h );
        var f = i % 2 ? h - i : 1 - ( h - i );
        var m = v * ( 1 - s );
        var n = v * ( 1 - s * f );
        switch ( i )
        {
            case 6 :
            case 0 :
                return [
                    v, n, m
                ];
            case 1 :
                return [
                    n, v, m
                ];
            case 2 :
                return [
                    m, v, n
                ];
            case 3 :
                return [
                    m, n, v
                ];
            case 4 :
                return [
                    n, m, v
                ];
            case 5 :
                return [
                    v, m, n
                ];
        }
    },
    /**
     * 将RGB颜色转换为十六进制颜色字符串
     * @param {} rgb [r, g, b]形式，元素的大小在0-1范围内
     * @return {}
     */
    rgb2hex : function( rgb )
    {
        var hex = ( 0x100 | Math.round( 255 * rgb[0] ) ).toString( 16 ).substr( 1 );
        hex += ( 0x100 | Math.round( 255 * rgb[1] ) ).toString( 16 ).substr( 1 );
        hex += ( 0x100 | Math.round( 255 * rgb[2] ) ).toString( 16 ).substr( 1 );
        return hex;
    },
    /**
    * 将十六进制颜色字符串转换为RGB颜色
    * @param {} hex 形如FF0000的6位十六进制数字组成的颜色字符串
    * @return {} [r, g, b]形式，元素的大小在0-1范围内
    */
    hex2rgb : function( hex )
    {
        var r = parseInt( hex.substr( 0, 2 ), 16 );
        var g = parseInt( hex.substr( 2, 2 ), 16 );
        var b = parseInt( hex.substr( 4, 2 ), 16 );
        return [
            r / 255, g / 255, b / 255
        ];
    },
    hsv2hex : function( hsv )
    {
        var me = this;
        var h = hsv[0];
        var s = hsv[1];
        var v = hsv[2];
        return me.rgb2hex( me.hsv2rgb( h, s, v ) ).toUpperCase();
    },
    hex2hsv : function( hex )
    {
        var me = this;
        return me.rgb2hsv( me.hex2rgb( hex ) );
    },
    /**
     * @private
     * 获取鼠标位置相对于调色板的位置百分比
     * @param {} e 事件对象
     * @return {} [xOffset,yOffset] 形式，元素值范围在0-1之间
     */
    getPadRelativePos : function( e )
    {
        var me = this;
        var xy = e.getXY();
        var p = me.getPad();
        var xo = ( xy[0] - p.getPosition()[0] ) / p.getWidth();
        if ( xo &gt; 1 ) xo = 1;
        if ( xo &lt; 0 ) xo = 0;
        var yo = 1 - ( xy[1] - p.getPosition()[1] ) / p.getHeight();
        if ( yo &gt; 1 ) yo = 1;
        if ( yo &lt; 0 ) yo = 0;
        return [
            xo, yo
        ];
    },
    /**
     * @private
     * 获取鼠标位置相对于滑块X轴方向的百分比
     * @param {} e 事件对象
     * @return {} xOffset，值范围在0-1之间
     */
    getSldRelativeX : function( e )
    {
        var me = this;
        var xy = e.getXY();
        var s = me.getSld();
        var xo = ( xy[0] - s.getPosition()[0] ) / s.getWidth();
        if ( xo &gt; 1 ) xo = 1;
        if ( xo &lt; 0 ) xo = 0;
        return xo;
    },
    /**
     * @private
     * 设置取色点的位置
     * @param {} xy 页面坐标
     */
    updatePadSpotByPos : function( xy )
    {
        var me = this;
        var spot = me.getPadSpot();
        spot.show();
        spot.setPagePosition( xy[0] - 8, xy[1] - 8 );
    },
    /**
     * @private
     * 更新取色点的位置
     */
    updatePadSpot : function()
    {
        var me = this;
        var pad = me.getPad();
        var xy = pad.getPosition();
        var w = pad.getWidth();
        var h = pad.getHeight();
        xy[0] += w * ( me.getCurrent()[0] / 6 );
        xy[1] += h * ( 1 - me.getCurrent()[1] );
        me.updatePadSpotByPos( xy );
    },
    /**
    * @private
    * 设置取色滑块的位置
    * @param {} xy 页面坐标
    */
    updateSldKnobByPos : function( x )
    {
        var me = this;
        var sld = me.getSld();
        var knob = me.getSldKnob();
        knob.show();
        knob.setPagePosition( x - 5, sld.getPosition()[1] - 9 );
    },
    /**
     * @private
     * 更新滑动条滑块
     */
    updateSldKnob : function()
    {
        var me = this;
        var sld = me.getSld();
        var xy = sld.getPosition();
        var w = sld.getWidth();
        xy[0] += w * me.getCurrent()[2];
        me.updateSldKnobByPos( xy[0] );
    },
    /**
     * @private
     * 更新滑动条背景颜色
     */
    updateSldBg : function()
    {
        var me = this;
        var sld = me.getSld();
        var sldDivCount = me.statics().sldDivCount;
        for ( var i = 0; i &lt; sldDivCount; i++ )
        {
            var div = sld.getComponent( i );
            var h = me.getCurrent()[0];
            var s = me.getCurrent()[1];
            var v = i / sldDivCount;
            var color = '#' + me.rgb2hex( me.hsv2rgb( h, s, v ) );
            div.getEl().setStyle( {
                backgroundColor : color
            } );
        }
    },
    /**
     * @private
     * 更新调色板组件的样式
     */
    updatePad : function()
    {
        var me = this;
        me.updatePadSpot();
    },
    /**
     * @private
     * 更新滑动条组件的样式
     */
    updateSld : function()
    {
        var me = this;
        me.updateSldBg();
        me.updateSldKnob();
    },
    /**
     * @private
     * 更新颜色容器区的单元格
     */
    updateCells : function()
    {
        var me = this;
        var colors = me.colors;
        var ccs = me.getColorCells();
        var count = ccs.getCount();
        for ( var i = 0; i &lt; count; i++ )
        {
            var cc = ccs.getAt( i );
            if ( i &gt; colors.length - 1 )
            {
                cc.addCls( me.colorCellEmptyCls );
                cc.removeCls( me.colorCellSelectedCls );
            }
            else
            {
                cc.getEl().setStyle( {
                    backgroundColor : '#' + me.hsv2hex( colors[i] )
                } );
                if ( i == me.currentColorIndex )
                {
                    cc.removeCls( me.colorCellEmptyCls );
                    cc.addCls( me.colorCellSelectedCls );
                }
                else
                {
                    cc.removeCls( me.colorCellEmptyCls );
                    cc.removeCls( me.colorCellSelectedCls );
                }
            }
        }
    },
    /**
     * @private
     * 更新当前组件的视觉样式
     */
    updateView : function()
    {
        var me = this;
        me.updateSld();
        me.updatePad();
        me.updateCells();
    },
    /**
     * @private
     * 根据取色板的位置信息来更新当前颜色
     * @param e 当前事件对象
     */
    onPadSpotPositionChange : function( e )
    {
        var me = this;
        var xy = e.getXY();
        me.updatePadSpotByPos( xy );
        //更新++++
        var rxy = me.getPadRelativePos( e );
        me.getCurrent()[0] = rxy[0] * 6;
        me.getCurrent()[1] = rxy[1];
        me.fireEvent( 'curcolorchange' );
        me.updateView();
    },
    /**
     * @private
     * 根据取色滑块的位置信息来更新当前颜色
     * @param e 当前事件对象
     */
    onSldKnobPositionChange : function( e )
    {
        var me = this;
        var xy = e.getXY();
        me.getCurrent()[2] = me.getSldRelativeX( e );
        me.fireEvent( 'curcolorchange' );
        me.updateView();
    },
    //在此模板内完成内部事件句柄的初始化，该模板方法被调用时，组件已经完成渲染
    initEvents : function()
    {
        var me = this;
        var pad = me.getPad();
        //该面板被移动后，取色点、取色滑块的位置需要被更新
        me.on( 'move', function()
        {
            me.updateView();
            if ( !me.initialPositioned )
            {
                me.initialPositioned = true;
                me.fireEvent( 'initialpositioned' )
            }
        } );
        me.getEl().on( 'mousedown', function( e )
        {
            e.preventDefault();
        } );
        pad.getEl().on( 'mousedown', function( e )
        {
            e.preventDefault();
        } );
        pad.getEl().on( 'click', function( e )
        {
            me.onPadSpotPositionChange( e );
        } );
        var sld = me.getSld();
        sld.getEl().on( 'mousedown', function( e )
        {
            e.preventDefault();
        } );
        sld.getEl().on( 'click', function( e )
        {
            me.onSldKnobPositionChange( e );
        } );
        var cc = me.getColorContainer();
        cc.getEl().on( 'dblclick', function( e )
        {
            var idx = me.getRelevantColorCellIndex( e );
            if ( idx &gt; 0 )//第一个颜色禁止删除
            {
                if ( idx &gt;= me.colors.length )
                {
                    me.addColor( me.defaultColor );
                }
                else
                {
                    me.removeColor( idx );
                }
            }
        } );
        cc.getEl().on( 'click', function( e )
        {
            var idx = me.getRelevantColorCellIndex( e );
            me.setCurrentColorIndex( idx );
        } );
    },
    onShow : function()
    {
        var me = this;
        me.getPadSpot().show();
        me.getSldKnob().show();
        me.callParent( arguments );
    },
    onHide : function()
    {
        var me = this;
        me.getPadSpot().hide();
        me.getSldKnob().hide();
        me.callParent( arguments );
    },
    //@private
    getCurrent : function()
    {
        return this.colors[this.currentColorIndex];
    },
    /**
     * 获取当前取色器颜色值代码
     * @return {} 形如FF0000的6位十六进制数字组成的字符串
     */
    getCurrentColor : function()
    {
        var me = this;
        return me.hsv2hex( me.getCurrent() );
    },
    /**
     * 设置当前取色器颜色值代码
     * @param {} hex 形如FF0000的6位十六进制数字组成的字符串
     */
    setCurrentColor : function( hex )
    {
        if ( !hex ) return;
        var me = this;
        me.colors[me.currentColorIndex] = me.hex2hsv( hex );
        me.updateView();
    },
    /**
     * 设置当前取色器的索引
     * @param {} idx
     */
    setCurrentColorIndex : function( idx )
    {
        var me = this;
        if ( idx &gt;= me.colors.length ) return;
        me.currentColorIndex = idx;
        me.updateView();
    },
    getColors : function()
    {
        var me = this;
        var hexColors = [];
        for ( var i = 0; i &lt; me.colors.length; i++ )
        {
            var c = me.colors[i];
            var h = c[0];
            var s = c[1];
            var v = c[2];
            hexColors[i] = me.rgb2hex( me.hsv2rgb( h, s, v ) ).toUpperCase();
        }
        return hexColors;
    },
    /**
     * 设置当前取色器颜色值列表
     * @param {} hexArray ['FF0000']形式的数组
     * @param {} currentIndex 当前颜色的索引
     */
    setColors : function( hexArray, currentIndex )
    {
        var me = this;
        if ( !currentIndex ) currentIndex = 0;
        for ( var i = 0; i &lt; hexArray.length; i++ )
        {
            me.colors[i] = me.rgb2hsv( me.hex2rgb( hexArray[i] ) );
        }
        me.updateView();
    },
    /**
     * 添加一个颜色到末尾并设置其为当前颜色
     * @param {} hex 'FF0000'形式的颜色代码
     */
    addColor : function( hex )
    {
        var me = this;
        me.colors.push( me.hex2hsv( hex ) );
        me.fireEvent( 'curcolorchange' );
        me.setCurrentColorIndex( me.colors.length - 1 );
    },
    removeColor : function( idx, noUpdateView )
    {
        var me = this;
        me.colors.splice( idx, 1 );
        me.fireEvent( 'curcolorchange' );
        me.setCurrentColorIndex( 0 );
    }
} );</pre>
<p>选取器代码：</p>
<pre class="crayon-plain-tag">/**
 * 基于HSV（色相/饱和度/亮度）色彩空间的取色器组件
 */
Ext.define( 'Ext.ux.form.field.HSVColorPicker', {
    alias : 'widget.hsvcolorpicker',
    extend : 'Ext.form.field.Picker',
    requires : [
        'Ext.ux.panel.HSVColorPalette'
    ],
    inputType : 'hidden',
    triggerCls : Ext.baseCSSPrefix + 'form-hsv-color-trigger',
    childEls : [
        'colorDisplay'
    ],//元素作为组件成员变量的声明
    colorDisplayCmp : null,
    /**
     * @config 是否显示色块的边框
     * @type Boolean
     */
    colorDisplayCellBorder : true,
    multiColorsMode : false,
    initComponent : function()
    {
        var me = this;
        Ext.apply( me, {
            matchFieldWidth : false
        } );
        me.callParent( arguments );
    },
    createPicker : function()
    {
        var me = this;
        var picker = Ext.create( 'Ext.ux.panel.HSVColorPalette', {
            //必须设置为浮动组件
            floating : true,
            multiColorsMode : me.multiColorsMode,
            listeners : {
                initialpositioned : function()
                {
                    //在选取器第一次定位完毕后，设置其当前颜色值，以渲染取色点、取色滑块以及滑动条颜色
                    me.setPickerValue();
                },
                curcolorchange : function()
                {
                    //回填颜色值
                    me.setValue( this.getColors() );
                }
            }
        } );
        return picker;
    },
    //多值支持，使用数组作为内部表示
    valueToRaw : function( value )
    {
        return value.join();
    },
    rawToValue : function( rawValue )
    {
        rawValue || ( rawValue = '' );
        return rawValue.split( ',' );
    },
    /**
     * 设置该控件的值
     * @param {} value 要设置的值
     * @return {}
     */
    setValue : function( value )
    {
        value || ( value = [] );
        if ( !Ext.isArray( value ) ) value = [
            value
        ];
        var me = this;
        var ret = me.callParent( arguments );
        if ( me.colorDisplayCmp ) me.updateColorDisplay();
        return ret;
    },
    /**
     * 更新颜色显示区
     */
    updateColorDisplay : function()
    {
        var me = this;
        me.colorDisplayCmp.removeAll( true );
        var v = me.getValue();
        for ( var i = 0; i &lt; v.length; i++ )
        {
            var cell = {
                style : {
                    backgroundColor : '#' + v[i]
                }
            };
            if ( !me.colorDisplayCellBorder )
            {
                Ext.apply( cell, {
                    border : 0
                } );
            }
            me.colorDisplayCmp.add( cell );
        }
    },
    /**
     * 设置调色板组件的值
     */
    setPickerValue : function()
    {
        var me = this;
        var value = me.getValue();
        if ( value ) me.getPicker().setColors( value );
    },
    isEventWithinPicker : function( e )
    {
        var me = this;
        var box = me.picker.getBox();
        var x = e.getXY()[0];
        var y = e.getXY()[1];
        return x &gt;= box.x &amp;&amp; x &lt;= ( box.x + box.width ) &amp;&amp; y &gt;= box.y &amp;&amp; y &lt;= ( box.y + box.height );
    },
    //覆盖此方法，防止点击到调色板的取色点、取色滑块时导致选取器被隐藏
    collapseIf : function( e )
    {
        var me = this;
        if ( !me.isDestroyed &amp;&amp; !e.within( me.bodyEl, false, true ) &amp;&amp; !me.isEventWithinPicker( e ) &amp;&amp; !me.isEventWithinPickerLoadMask( e ) )
        {
            me.collapse();
        }
    },
    //覆盖此方法，添加colorDisplay的点击监听
    initEvents : function()
    {
        var me = this;
        me.callParent();
        if ( !me.editable )
        {
            me.mon( me.colorDisplay, 'click', function( e )
            {
                me.onTriggerClick();
                if ( !me.readOnly &amp;&amp; !me.disabled )
                {
                    var cdp = me.colorDisplayCmp;
                    var len = cdp.items.getCount();
                    for ( var i = 0; i &lt; len; i++ )
                    {
                        var cell = cdp.items.getAt( i );
                        if ( e.within( cell.getEl(), false, true ) )
                        {
                            me.getPicker().setCurrentColorIndex( i );
                            break;
                        }
                    }
                }
            } );
        }
    },
    //覆盖此方法，改变默认的基于Ext.form.field.Text的表单字段外观
    getSubTplMarkup : function()
    {
        var me = this;
        //隐藏字段
        var field = Ext.form.field.Text.prototype.getSubTplMarkup.apply( this, arguments );
        //添加一个颜色显示的区域
        var display = '&lt;div id="' + me.id + '-colorDisplay" class="' + Ext.baseCSSPrefix + 'form-hsv-color-picker-display"&gt;&lt;/div&gt;';
        return [
            '&lt;table id="' + me.id + '-triggerWrap" class="' + Ext.baseCSSPrefix + 'form-trigger-wrap" cellpadding="0" cellspacing="0"&gt;',
            '  &lt;tbody&gt;',
            '    &lt;tr&gt;',
            '        &lt;td id="' + me.id + '-inputCell" class="' + Ext.baseCSSPrefix + 'form-trigger-input-cell"&gt;' + field + display + '&lt;/td&gt;',
            '        ' + me.getTriggerMarkup(),
            '    &lt;/tr&gt;',
            '  &lt;/tbody&gt;',
            '&lt;/table&gt;'
        ].join( '' );
    },
    afterRender : function()
    {
        var me = this;
        me.callParent( arguments );
        me.colorDisplayCmp = Ext.create( 'Ext.container.Container', {
            cls : Ext.baseCSSPrefix + 'form-hsv-color-picker-display-container',
            layout : {
                type : 'hbox',
                align : 'stretch',
                pack : 'start'
            },
            defaults : {
                xtype : 'box',
                flex : 1,
                cls : Ext.baseCSSPrefix + 'form-hsv-color-picker-display-cell'
            },
            listeners : {
                afterrender : {
                    scope : me,
                    fn : me.updateColorDisplay,
                    single : true
                }
            }
        } );
        me.colorDisplayCmp.render( me.colorDisplay );
    },
    //覆盖此方法，在组件布局执行完毕后更新色块容器的布局
    afterComponentLayout : function()
    {
        var me = this;
        me.callParent( arguments );
        me.colorDisplayCmp.updateLayout();
    },
    //覆盖此方法，销毁内部组件
    onDestroy : function()
    {
        var me = this;
        me.callParent( arguments );
        if(me.colorDisplayCmp)
        me.colorDisplayCmp.destroy();
    },
    //覆盖此方法，改变对齐参考元素
    doAlign : function()
    {
        var me = this, picker = me.picker, aboveSfx = '-above', isAbove;
        me.picker.alignTo( me.colorDisplay, me.pickerAlign, me.pickerOffset );
        isAbove = picker.el.getY() &lt; me.inputEl.getY();
        me.bodyEl[isAbove ? 'addCls' : 'removeCls']( me.openCls + aboveSfx );
        picker[isAbove ? 'addCls' : 'removeCls']( picker.baseCls + aboveSfx );
    }
} );</pre>
<p>CSS样式文件：</p>
<pre class="crayon-plain-tag">/* HSVColorPalette */
.x-hsv-color-palette {
    border: 1px solid #8AB4B8;
}

.x-hsv-color-palette-body {
    padding-top: 4px;
    background-image: none;
    background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #D8E6E7), color-stop(100%, #D8E6E7));
    background-image: -webkit-linear-gradient(top, #D8E6E7, #E3EDEE);
    background-image: -moz-linear-gradient(top, #D8E6E7, #E3EDEE);
    background-image: -o-linear-gradient(top, #D8E6E7, #E3EDEE);
    background-image: -ms-linear-gradient(top, #D8E6E7, #E3EDEE);
    background-image: linear-gradient(top, #D8E6E7, #E3EDEE);
}

.x-hsv-color-palette-pad-wrapper {
    border-top: 1px solid #888888;
    border-left: 1px solid #999999;
    border-right: 1px solid #EEEEEE;
    border-bottom: 1px solid #DDDDDD;
    background-color: #C5DADC;
}

.x-hsv-color-palette-sld-wrapper {
    border-top: 1px solid #888888;
    border-left: 1px solid #999999;
    border-right: 1px solid #DDDDDD;
    border-bottom: 1px solid #DDDDDD;
    background-color: #C5DADC;
}

.x-hsv-color-palette-sld-knob {
    width: 11px;
    height: 38px;
    background-image: url('../../resources/themes/icons/blue-green/hsv-color-panel-knob.png');
}
.x-hsv-color-palette-cell {
    border: 1px solid #D8E6E7;
    cursor: pointer;
    height: 14px;
}

.x-hsv-color-palette-cell-empty {
    background-image: url('../../resources/themes/images/blue-green/form/hsv-color-picker-display-cell-empty.png');
}

.x-hsv-color-palette-cell-selected {
    border: 2px dotted #D8E6E7;
}

.x-hsv-color-palette-colors-container {
    background-color: #D8E6E7;
}

/* HSVColorPicker */
.x-form-hsv-color-trigger {
    background-image: url('../../resources/themes/images/blue-green/form/hsv-color-picker-trigger.gif');
}
.x-form-hsv-color-picker-display {
    padding: 3px 0px;
    border: 1px solid;
    background-color: white;
    border-color: #AECBCE;
    height: 22px;
    cursor: pointer;
}
.x-form-hsv-color-picker-display-container {
    width: 100%;
}

.x-form-hsv-color-picker-display-cell {
    border: 1px dotted #C5DADC;
    height: 14px;
    margin: 0 1 0 1;
}</pre>
<div class="blog_h3"><span class="graybg">测试代码</span></div>
<pre class="crayon-plain-tag">Ext.Loader.setConfig( {
    enabled : true,
    paths : {
        'Ext.ux' : 'http://192.168.0.89:5050/sshe-static/extjs-4.1.1/ux'
    }
} );
Ext.require( [
    'Ext.ux.form.field.HSVColorPicker'
] );
Ext.onReady( function()
{
    var window = Ext.create( 'Ext.window.Window', {
        title : '下拉取色器演示',
        layout : 'fit',
        width : 360,
        height : 240,
        border : false,
        closable : false,
        items : {
            xtype : 'form',
            bodyPadding : 10,
            frame : true,
            border : false,
            defaults : {
                anchor : '100%'
            },
            items : [

                {
                    xtype : 'fieldset',
                    title : '普通取色器',
                    items : [
                        {
                            xtype : 'hsvcolorpicker',
                            fieldLabel : '颜色',
                            value : [
                                'CC324C', '33CC00', '0033CC', 'AE6FCC', '000000'
                            ],
                            colorDisplayCellBorder : true,
                            multiColorsMode : true,
                            editable : false
                        }, {
                            xtype : 'hsvcolorpicker',
                            fieldLabel : '单值颜色',
                            value : [
                                'CC324C'
                            ],
                            colorDisplayCellBorder : true,
                            multiColorsMode : false,
                            editable : false
                        }
                    ]
                }, {
                    xtype : 'fieldset',
                    title : '表格中的取色器',
                    items : {
                        xtype : 'grid',
                        store : Ext.create( 'Ext.data.ArrayStore', {
                            fields : [
                                {
                                    name : 'Item'
                                }, {
                                    name : 'Color'
                                }
                            ],
                            data : [
                                [
                                    'Item1', 'CC0000'
                                ]
                            ]
                        } ),
                        columns : [
                            {
                                text : '项目',
                                sortable : false,
                                dataIndex : 'Item'
                            }, {
                                text : '颜色',
                                sortable : true,
                                dataIndex : 'Color',
                                flex : 1,
                                editor : {
                                    xtype : 'hsvcolorpicker',
                                    colorDisplayCellBorder : false,
                                    multiColorsMode : true,
                                    allowBlank : false
                                }
                            }
                        ],
                        plugins : [
                            Ext.create( 'Ext.grid.plugin.CellEditing', {
                                clicksToEdit : 1
                            } )
                        ]
                    }
                }
            ]
        }
    } );
    window.show();
} )</pre>
<p>运行效果如下（<a href="https://cdn.gmem.cc/code/js/examples/HSVColorPicker.html" target="_blank">在线演示地址</a>）：</p>
<p><img class="aligncenter size-full wp-image-5649" src="https://blog.gmem.cc/wp-content/uploads/2015/04/HSVColorPicker.png" alt="HSVColorPicker" width="503" height="464" /></p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/extjs-4-picker-fields">ExtJS 4中的选取器控件</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/extjs-4-picker-fields/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ExtJS 4组件的生命周期</title>
		<link>https://blog.gmem.cc/life-cycle-model-of-extjs-4-components</link>
		<comments>https://blog.gmem.cc/life-cycle-model-of-extjs-4-components#comments</comments>
		<pubDate>Fri, 26 Apr 2013 07:23:00 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ExtJS]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=4942</guid>
		<description><![CDATA[<p>ExtJS组件的生命周期可以整体上划分为三个部分：初始化、渲染、销毁。生命周期由Ext.Component类控制。 初始化（Initialization） 这一阶段通常执行的很快，因为都是在JavaScript代码内完成的。注意：组件不需要经过渲染阶段即可被销毁。 该阶段整体流程图如下： 各步骤说明如下： 应用配置项：在实例化组件的时候，我们可以传递一个配置对象（包含若干配置项），其中包含新组件必要的参数、引用 底层（Base）事件注册：Ext.Component类定义了若干核心事件，这些事件在组件的某些行为前、后触发（对应事件名称beforeXxx、afterXxx），这些行为包括：enable/disable、show/hide、render/ destroy、 state restore/state save。其中before事件处理函数如果返回false，则行为被取消，例如，如果beforeshow返回false，那么组件的show行为将不生效，即不显示。通常这些事件都由Ext.Component类本身负责触发 分配或者生成组件ID：如果没有指定ID，ExtJS会自动为组件生成 构造Pre-initComponent插件：所有在initComponent之前定义的插件被创建，该步骤允许某些插件（例如Grid编辑器）尽早的执行必要的动作 执行initComponent：这是最重要的一个扩展点。initComponent方法中会包含很多逻辑，例如： 注册子类特有的事件 引用数据存储 创建子组件 在ComponentManager中注册当前组件 底层（Base）混入类构造器调用：组件提供两个混入类提供底层功能，Ext.util.Observable为组件提供发布、监听事件的能力；Ext.state.Stateful为组件处理状态相关（state-specific）的事件 组件的resize事件作为状态事件（state event）初始化，状态事件用于保存组件的状态 初始化插件：如果提供了plugins配置，那么在此调用插件的init方法，并传入当前Component作为其参数。如果由多个插件，按照在配置中声明顺序初始化之 <a class="read-more" href="https://blog.gmem.cc/life-cycle-model-of-extjs-4-components">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/life-cycle-model-of-extjs-4-components">ExtJS 4组件的生命周期</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>ExtJS组件的生命周期可以整体上划分为三个部分：初始化、渲染、销毁。生命周期由Ext.Component类控制。</p>
<div class="blog_h2"><span class="graybg">初始化（Initialization）</span></div>
<p>这一阶段通常执行的很快，因为都是在JavaScript代码内完成的。注意：组件不需要经过渲染阶段即可被销毁。</p>
<p>该阶段整体流程图如下：</p>
<p><img class="wp-image-4947 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2013/04/init-phase.png" alt="init-phase" width="716" height="460" /></p>
<p>各步骤说明如下：</p>
<ol>
<li>应用配置项：在实例化组件的时候，我们可以传递一个配置对象（包含若干配置项），其中包含新组件必要的参数、引用</li>
<li>底层（Base）事件注册：Ext.Component类定义了若干核心事件，这些事件在组件的某些行为前、后触发（对应事件名称beforeXxx、afterXxx），这些行为包括：enable/disable、show/hide、render/ destroy、 state restore/state save。<span style="background-color: #c0c0c0;">其中before事件处理函数如果返回false，则行为被取消</span>，例如，如果beforeshow返回false，那么组件的show行为将不生效，即不显示。通常这些事件<span style="background-color: #c0c0c0;">都由Ext.Component类本身负责触发</span></li>
<li>分配或者生成组件ID：如果没有指定ID，ExtJS会自动为组件生成</li>
<li>构造Pre-initComponent插件：所有在initComponent之前定义的插件被创建，该步骤允许某些插件（例如Grid编辑器）尽早的执行必要的动作</li>
<li>执行initComponent：这是<span style="background-color: #c0c0c0;">最重要的一个扩展点</span>。initComponent方法中会包含很多逻辑，例如：
<ol>
<li>注册子类特有的事件</li>
<li>引用数据存储</li>
<li>创建子组件</li>
</ol>
</li>
<li>在ComponentManager中注册当前组件</li>
<li>底层（Base）混入类构造器调用：组件提供两个混入类提供底层功能，Ext.util.Observable为组件提供发布、监听事件的能力；Ext.state.Stateful为组件处理状态相关（state-specific）的事件</li>
<li>组件的resize事件作为状态事件（state event）初始化，状态事件用于保存组件的状态</li>
<li>初始化插件：如果提供了plugins配置，那么在此调用插件的init方法，并传入当前Component作为其参数。如果由多个插件，按照在配置中声明顺序初始化之</li>
<li>启动组件的数据加载器：如果提供了一个loader配置项，则用其创建ComponentLoader，下面是一个例子：<br />
<pre class="crayon-plain-tag">var cmp = Ext.create( 'Ext.Component', {
    renderTo : Ext.getBody(),
    tpl : '{firstName} - {lastName}', //HTML内容模板
    loader : {
        url : 'userinfo',
        autoLoad : false, //是否自动加载数据
        renderer : 'data', //使用data作为渲染器，则自动提供给模板使用
        params : {
            userId : 1
        }
    }
} );
cmp.getLoader().load();</pre>
</li>
<li>组件被渲染：如果提供renderTo、applyTo配置项，那么立即开始渲染阶段；否则，组件等待其render()方法<span style="background-color: #c0c0c0;">被其容器或者被用户代码调用</span>，不作为其他容器子组件的组件，必须手动渲染，例如：<br />
<pre class="crayon-plain-tag">someComponent.render('someDivId');</pre>
</li>
</ol>
<div class="blog_h2"><span class="graybg">渲染（Render）</span></div>
<p>这一阶段用户将得到组件的视角反馈。如果初始化阶段出现任何问题，组件将渲染错误或者根本不渲染。</p>
<p>该阶段整体流程图如下：<img class="wp-image-4952 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2013/04/render-phase.png" alt="render-phase" width="702" height="501" /></p>
<p>各步骤说明如下：</p>
<ol>
<li>触发beforerender事件：组件发布该事件，并检查所有监听器函数的返回值，如果任一返回值为false，停止渲染</li>
<li>组件对应的DOM元素被缓存：如果指定了el配置项，那么使用一个Ext.Element包围el对应的元素</li>
<li>浮动组件注册：如果组件被配置为floating，则在WindowManager中注册自身，以便启用Z-Index、焦点管理。这一步骤对Window、Menu等组件比较重要</li>
<li>设置组件所在的容器：
<ol>
<li>如果指定了renderTo配置项，则在目标元素下添加单个DIV，并在此DIV中渲染当前组件</li>
<li>如果指定了applyTo配置项，则目标元素直接作为组件的容器，目标元素将被组件完全管理</li>
</ol>
</li>
<li>调用onRender：这是一个关键的步骤，组件的所有DOM元素被插入到文档中，并且渲染到屏幕上。每个组件子类<span style="background-color: #c0c0c0;">应当首先调用其父类的onRender</span>方法，然后再执行子类特有的逻辑</li>
<li>可见性模式（Visibility mode）被配置：通过组件的hideMode配置来设置，可以是display、visibility、offsets</li>
<li>初始化overCls：如果提供了overCls配置项，则组件使其元素在mouseover、mouseout事件发生时添加、去除对应的CSS样式</li>
<li>触发render事件：在此刻，所有必须的元素已经插入到文档，并且应用了样式</li>
<li>初始化组件的内容：如果提供了contentEl、html、tpl或者data配置项，则在组件对应的DIV中渲染HTML代码</li>
<li>调用afterRender：该方法会被render自动调用，这是一个重要的模板方法包括以下职责：
<ol>
<li>配置组件的尺寸、对齐、位置，并且为HTML内容添加一些样式</li>
<li>如果组件为Resizable，则执行Resizable类的初始化</li>
<li>如果组件为draggable，则执行相应的逻辑使其可拖拽</li>
<li>如果配置了autoScroll，则设置组件的滚动条</li>
</ol>
</li>
<li>触发afterrender事件：通知监听器，所有关键的渲染操作都完成了，该事件是很好的操控组件DOM的时机</li>
<li>挂钩afterRenderEvents</li>
<li>组件的元素可以支持聚焦</li>
<li>如果组件被配置为hidden:true，则调用其hide()方法</li>
<li>如果组件被配置为disabled:true，则调用其disable()方法</li>
</ol>
<div class="blog_h2"><span class="graybg">销毁（Destruction）</span></div>
<p>销毁阶段包含重要的资源清理动作，例如：移出组件（包括子组件）的DOM元素、从ComponentManager注销组件、注销事件监听器。</p>
<p>该阶段整体流程图如下：</p>
<p><img class="aligncenter  wp-image-4958" src="https://blog.gmem.cc/wp-content/uploads/2013/04/destroy-phase.png" alt="destroy-phase" width="711" height="474" /></p>
<p>各步骤说明如下：</p>
<ol>
<li>触发beforedestroy事件：如果任何一个监听器返回false，则取消组件的销毁</li>
<li>调用beforeDestroy：组件的destroy()方法首先调用该模板方法，在这里可以完成<span style="background-color: #c0c0c0;">非组件成员</span>的销毁</li>
<li>如果当前组件是floating，则从FloatingManager中解除自身的注册</li>
<li>从父容器中移出当前组件</li>
<li>调用onDestroy，该模板方法包括以下职责：
<ol>
<li>销毁所有配置的drag-and-drop代理</li>
<li>销毁配置的Resizer</li>
<li>如果组件监视浏览器的resize事件，则解除注册该监听器</li>
<li>销毁配置的ComponentLayout、loadMask、任何浮动子组件</li>
</ol>
</li>
<li>销毁注册的插件：遍历并调用其destroy()方法</li>
<li>如果组件已经渲染，则清理Element对象（从文档中删除DOM节点）、Element的事件监听器</li>
<li>触发destroy事件：此时组件已经不存在于文档</li>
<li>从ComponentManager解除注册</li>
<li>销毁Ext.state.Stateful混入类，并且解除任何状态相关（state-specific）的组件事件</li>
<li>销毁组件的事件监听器</li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/life-cycle-model-of-extjs-4-components">ExtJS 4组件的生命周期</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/life-cycle-model-of-extjs-4-components/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>浅析ExtJS 4表单字段</title>
		<link>https://blog.gmem.cc/extjs-4-form-fields</link>
		<comments>https://blog.gmem.cc/extjs-4-form-fields#comments</comments>
		<pubDate>Thu, 11 Apr 2013 01:43:50 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ExtJS]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=5326</guid>
		<description><![CDATA[<p>基本概念 字段：又称字段，对应于原始HTML表单中的input等元素，在ExtJS中，混入了Ext.form.field.Field的任何类型都是表单字段 字段值： 数据值（data value）：与字段类型密切相关的，反映真实对象状态的值，例如对于日期字段，其数据值是JavaScript日期对象 字符串值（stringified value）：用于表单提交的，字符串形式的值 原始值（raw value）：Base类引入的概念，主要用于HTML INPUT元素，通常是一个字符串 表单字段基础类 Base、Field、Labelable三个类是ExtJS表单字段体系的核心。其中，Field定义了核心接口函数，Base是一个抽象基类，Labelable处理字段文字标签、错误信息等相关的逻辑。 Ext.form.field.Base 作为所有表单字段的抽象基类，它提供了： 默认的事件处理，包括specialkey、writeablechange两个额外事件 公共的渲染逻辑，包括错误消息、错误样式的生成与显示 其它公共基础功能，例如提供了基于XTemplate的UI生成机制 混入了Ext.form.field.Field，以支持字段值处理、表单验证 混入了Ext.form.Labelable，以支持标签、错误信息的显示 继承了Ext.Component，因而表单字段是一种组件 字段体（Field body）的HTML内容由 <a class="read-more" href="https://blog.gmem.cc/extjs-4-form-fields">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/extjs-4-form-fields">浅析ExtJS 4表单字段</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2">基本概念</div>
<ol>
<li>字段：又称字段，对应于原始HTML表单中的input等元素，在ExtJS中，混入了Ext.form.field.Field的任何类型都是表单字段</li>
<li>字段值：
<ol>
<li>数据值（data value）：与字段类型密切相关的，反映真实对象状态的值，例如对于日期字段，其数据值是JavaScript日期对象</li>
<li>字符串值（stringified value）：用于表单提交的，字符串形式的值</li>
<li>原始值（raw value）：Base类引入的概念，主要用于HTML INPUT元素，通常是一个字符串</li>
</ol>
</li>
</ol>
<div class="blog_h2"><span class="graybg">表单字段基础类</span></div>
<p>Base、Field、Labelable三个类是ExtJS表单字段体系的核心。其中，Field定义了核心接口函数，Base是一个抽象基类，Labelable处理字段文字标签、错误信息等相关的逻辑。</p>
<div class="blog_h3">Ext.form.field.Base</div>
<p>作为所有表单字段的抽象基类，它提供了：</p>
<ol>
<li>默认的事件处理，包括specialkey、writeablechange两个额外事件</li>
<li>公共的渲染逻辑，包括错误消息、错误样式的生成与显示</li>
<li>其它公共基础功能，例如提供了基于XTemplate的UI生成机制</li>
<li>混入了Ext.form.field.Field，以支持字段值处理、表单验证</li>
<li>混入了Ext.form.Labelable，以支持标签、错误信息的显示</li>
<li>继承了Ext.Component，因而表单字段是一种组件</li>
</ol>
<p>字段体（Field body）的HTML内容由 XTemplate：fieldSubTpl来定义，该模板的占位符数据由getSubTplData()方法提供，覆盖这两个成员可以定制自己的字段渲染方式。</p>
<p>Ext.form.field.Base提供的配置项/属性/方法包括：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">配置项/属性/方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>{checkChangeBuffer}</td>
<td>Number，checkChangeEvents对应的HTML事件发生时，触发checkChange()的缓冲时间，默认50ms</td>
</tr>
<tr>
<td>{checkChangeEvents}</td>
<td>String[]，在inputEl对应的HTML元素上需要侦听的原生HTML事件的列表。这些事件会导致checkChange()。默认值：<br />IE：['change', 'propertychange']<br />其他浏览器：['change', 'input', 'textInput', 'keyup', 'dragdrop']</td>
</tr>
<tr>
<td>{dirtyCls}</td>
<td>String，当脏值时使用的样式类</td>
</tr>
<tr>
<td>{fieldCls}</td>
<td>String，字段输入元素的样式类</td>
</tr>
<tr>
<td>{fieldStyle}</td>
<td>String，可选的INPUT元素的样式</td>
</tr>
<tr>
<td>{fieldSubTpl}</td>
<td>String，私有配置，提供INPUT的模板</td>
</tr>
<tr>
<td>{focusCls}</td>
<td>String，字段获得焦点时的样式</td>
</tr>
<tr>
<td>{readOnlyCls}</td>
<td>String，只读样式类，应用到字段的主要元素</td>
</tr>
<tr>
<td>{invalidCls}</td>
<td>String，字段验证失败时的样式类</td>
</tr>
<tr>
<td>{inputAttrTpl}</td>
<td>String/Array/Ext.XTemplate，用作字段INPUT元素的属性的模板，getSubTplData()是其占位符上下文 </td>
</tr>
<tr>
<td>{inputId}</td>
<td>String，用作字段INPUT元素的ID，必须全局唯一</td>
</tr>
<tr>
<td>{inputType}</td>
<td>String，用作字段INPUT元素的type属性，例如radio、text、password、file</td>
</tr>
<tr>
<td>{invalidText}</td>
<td>String，如果没有提供定制消息，验证失败时显示的文本</td>
</tr>
<tr>
<td>{msgTarget}</td>
<td> </td>
</tr>
<tr>
<td>{name}</td>
<td>String，字段的名称，默认inputId</td>
</tr>
<tr>
<td>{readOnly}</td>
<td>Boolean，用于在HTML显示为只读，会设置INPUT的readOnly=true</td>
</tr>
<tr>
<td>{tabIndex}</td>
<td>Number，用于TAB键切换顺序的索引</td>
</tr>
<tr>
<td>{validateOnBlur}</td>
<td>Boolean，是否在失去焦点时执行验证，默认true</td>
</tr>
<tr>
<td>inputEl</td>
<td>Element，字段的INPUT元素，只有在渲染（render）后可用</td>
</tr>
<tr>
<td>clearInvalid()</td>
<td>覆盖了Field混入的方法。清除无效样式和消息。</td>
</tr>
<tr>
<td>getInputId()</td>
<td>获取INPUT元素的ID</td>
</tr>
<tr>
<td>getRawValue()</td>
<td>String，获取原始值，不进行任何正常化、转换、验证</td>
</tr>
<tr>
<td>getSubTplData()</td>
<td>Object，创建并返回用于fieldSubTpl的占位符上下文对象</td>
</tr>
<tr>
<td>getSubTplMarkup()</td>
<td>覆盖Labelable混入的方法</td>
</tr>
<tr>
<td>getSubmitValue()</td>
<td>String，得到用于标准表单提交的字段值，提交时的形式为name=value，如果值为空串则name=，如果值为null则不提交</td>
</tr>
<tr>
<td>isValid()</td>
<td>覆盖了Field混入的方法。对原始值进行预处理并验证预处理的结果值</td>
</tr>
<tr>
<td>markInvalid()</td>
<td>覆盖了Field混入的方法。根据msgTarget设置来显示错误信息，设置invalidCls样式</td>
</tr>
<tr>
<td>processRawValue()</td>
<td>Object (Object value)，在准备对原始值进行转换、验证之前，进行必要的预处理。这些处理包括：去除多余的无效字符。提供了缺省适配</td>
</tr>
<tr>
<td>setFieldStyle()</td>
<td>用于设置INPUT的样式</td>
</tr>
<tr>
<td>rawToValue()</td>
<td>把原始的INPUT值转换为与特定字段类型匹配的数据值（例如对于日期字段，需要转换为Date）。该方法会被getValue()调用。提供了缺省适配</td>
</tr>
<tr>
<td>valueToRaw()</td>
<td>用于把与特定字段类型匹配的数据值转化为方便在INPUT中显示的原始值形式，每次setValue()时，均会调用该方法设置原始值</td>
</tr>
<tr>
<td>setRawValue()</td>
<td>Object (Object value)，跳过valueToRaw()、change事件检测、验证，仅进行transformRawValue()，直接设置原始值</td>
</tr>
<tr>
<td>transformRawValue()</td>
<td>Object (Object value)，在setRawValue()之前，对参数进行转换</td>
</tr>
<tr>
<td>setReadOnly()</td>
<td>设置只读状态</td>
</tr>
<tr>
<td>setValue()</td>
<td>
<p>覆盖了Field混入的方法：</p>
<ol>
<li>通过valueToRaw()将参数转换为原始值，并调用setRawValue()设置原始值</li>
<li>转调Field.setValue()方法</li>
</ol>
</td>
</tr>
<tr>
<td>getValue()</td>
<td>
<p>覆盖了Field混入的方法：</p>
<ol>
<li>获取原始值，并预处理、rawToValue()转换</li>
<li>设置value属性为上述转换结果</li>
<li>返回value</li>
</ol>
</td>
</tr>
<tr>
<td>validateValue()</td>
<td>
<p>Boolean (Object value)，调用getErrors()生成验证错误，如果存在错误，则调用 markInvalid()，根据有无错误返回false/true</p>
</td>
</tr>
<tr>
<td>⚡specialkey</td>
<td>specialkey( Ext.form.field.Base this, Ext.EventObject e, Object eOpts )<br />与导航相关的按键事件被触发（箭头、TAB、回车、ESC）时触发</td>
</tr>
<tr>
<td>⚡writeablechange</td>
<td>writeablechange( Ext.form.field.Base this, Boolean Read, Object eOpts )<br />当字段的readOnly状态改变时触发</td>
</tr>
</tbody>
</table>
<p>Ext.form.field.Base的源代码分析：</p>
<pre class="crayon-plain-tag">Ext.define('Ext.form.field.Base', {
    extend: 'Ext.Component',
    mixins: {
        labelable: 'Ext.form.Labelable',
        field: 'Ext.form.field.Field'
    },
    alias: 'widget.field',
    alternateClassName: ['Ext.form.Field', 'Ext.form.BaseField'],
    requires: ['Ext.util.DelayedTask', 'Ext.XTemplate', 'Ext.layout.component.field.Field'],
	//子类可以修改此模板以改变字段的UI，这里就是生成一个HTML的input元素。
	//注意这里叫“子模板”是因为字段HTML还有外部包装元素，外部元素的渲染是Labelable混入负责的
    fieldSubTpl: [
        '<input id="{id}" size="1" type="{type}" width="" /> name="{name}"',
			//values是XTemplate内置变量，代表当前占位符替换时的上下文对象
            ' value="{[Ext.util.Format.htmlEncode(values.value)]}"',
            ' placeholder="{placeholder}"',
            '{%if (values.maxLength !== undefined){%} maxlength="{maxLength}"{%}%}',
            ' readonly="readonly"',
            ' disabled="disabled"',
            ' tabIndex="{tabIdx}"',
            ' style="{fieldStyle}"',
        ' class="{fieldCls} {typeCls} {editableCls}" autocomplete="off"/&gt;',
        {
            disableFormats: true
        }
    ],
	//需要插入字段模板的内容
    subTplInsertions: [
        'inputAttrTpl'  //增加Input元素的属性
    ],
    inputType: 'text',
    invalidText : 'The value in this field is invalid',
    fieldCls : Ext.baseCSSPrefix + 'form-field',
    focusCls : 'form-focus',
    dirtyCls : Ext.baseCSSPrefix + 'form-dirty',
	//用于检测值变更的原始DOM事件
    checkChangeEvents: Ext.isIE &amp;&amp; (!document.documentMode || document.documentMode &lt; 9) ?
                        ['change', 'propertychange'] :
                        ['change', 'input', 'textInput', 'keyup', 'dragdrop'],
    checkChangeBuffer: 50,
    componentLayout: 'field', //组件布局的类型
    readOnly : false,
    readOnlyCls: Ext.baseCSSPrefix + 'form-readonly',
    validateOnBlur: true,
    // private
    hasFocus : false,
    baseCls: Ext.baseCSSPrefix + 'field',
    maskOnDisable: false,
    // private
    initComponent : function() {
        var me = this;
        me.callParent();
        me.subTplData = me.subTplData || {}; //初始化模板数据
        me.addEvents(
            'specialkey',
            'writeablechange'
        );
		//初始化两个混入类
        me.initLabelable();
        me.initField();
        if (!me.name) {
            me.name = me.getInputId();
        }
    },
    beforeRender: function(){
        var me = this;
        me.callParent(arguments);
        me.beforeLabelableRender(arguments);
		//如果被设置为只读，则在渲染前添加只读样式类
        if (me.readOnly) {
            me.addCls(me.readOnlyCls);
        }
    },
    getInputId: function() {
        return this.inputId || (this.inputId = this.id + '-inputEl');
    },
	//获取字段UI模板的上下文数据
    getSubTplData: function() {
        var me = this,
            type = me.inputType,
            inputId = me.getInputId(),
            data;
        
        data = Ext.apply({
            id         : inputId,
            cmpId      : me.id,
            name       : me.name || inputId,
            disabled   : me.disabled,
            readOnly   : me.readOnly,
            value      : me.getRawValue(),
            type       : type,
            fieldCls   : me.fieldCls,
            fieldStyle : me.getFieldStyle(),
            tabIdx     : me.tabIndex,
            typeCls    : Ext.baseCSSPrefix + 'form-' + (type === 'password' ? 'text' : type)
        }, me.subTplData);
        me.getInsertionRenderData(data, me.subTplInsertions);
        return data;
    },
    afterFirstLayout: function() {
        this.callParent();
        var el = this.inputEl;
        if (el) {
            el.selectable();
        }
    },
    applyRenderSelectors: function() {
        var me = this;

        me.callParent();
		//inputEl属性不一定总是存在子类可以不渲染inputEl
        me.inputEl = me.el.getById(me.getInputId());
    },
	//覆盖Labelable的方法，得到完成占位符替换的字段UI模板，对应的HTML会被插入外部模板（默认来自Labelable）的bodyEl占位符中
    getSubTplMarkup: function() {
        return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
    },
	//覆盖AbstractComponent混入的Renderable的方法，决定组件如何混入
    initRenderTpl: function() {
        var me = this;
		//如果没有定义自己的渲染模板，则使用从Labelable混入的渲染模板
        if (!me.hasOwnProperty('renderTpl')) {
            me.renderTpl = me.getTpl('labelableRenderTpl');
        }
        return me.callParent();
    },
	//覆盖AbstractComponent混入的Renderable的方法，初始化渲染时的上下文数据
    initRenderData: function() {
		//把来自Labelable的上下文数据合并进去
        return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
    },
    setFieldStyle: function(style) {
        var me = this,
            inputEl = me.inputEl;
        if (inputEl) {
            inputEl.applyStyles(style);
        }
        me.fieldStyle = style;
    },
    getFieldStyle: function() {
        return 'width:100%;' + (Ext.isObject(this.fieldStyle) ? Ext.DomHelper.generateStyles(this.fieldStyle) : this.fieldStyle ||'');
    },
    // private
    onRender : function() {
        var me = this;
        me.callParent(arguments);
		//渲染Labelable、错误信息
        me.onLabelableRender();
        me.renderActiveError();
    },
    getFocusEl: function() {
        return this.inputEl;
    },
    isFileUpload: function() {
        return this.inputType === 'file';
    },
    extractFileInput: function() {
        var me = this,
            fileInput = me.isFileUpload() ? me.inputEl.dom : null,
            clone;
        if (fileInput) {
            clone = fileInput.cloneNode(true);
            fileInput.parentNode.replaceChild(clone, fileInput);
            me.inputEl = Ext.get(clone);
        }
        return fileInput;
    },
    // private 覆盖Field的方法，与getSubmitValue()逻辑一致
    getSubmitData: function() {
        var me = this,
            data = null,
            val;
        if (!me.disabled &amp;&amp; me.submitValue &amp;&amp; !me.isFileUpload()) {
            val = me.getSubmitValue();
            if (val !== null) {
                data = {};
                data[me.getName()] = val;
            }
        }
        return data;
    },
    getSubmitValue: function() {
        return this.processRawValue(this.getRawValue());
    },
	//获得原始值，如果字段有inputEl，则使用其HTML属性value，否则使用成员变量rawValue
    getRawValue: function() {
        var me = this,
            v = (me.inputEl ? me.inputEl.getValue() : Ext.value(me.rawValue, ''));
        me.rawValue = v;
        return v;
    },
	//设置原始值，transformRawValue()方法的唯一意义就是在设置原始值时对入参进行转换
    setRawValue: function(value) {
        var me = this;
        value = Ext.value(me.transformRawValue(value), '');
        me.rawValue = value;
        if (me.inputEl) {
            me.inputEl.dom.value = value;
        }
        return value;
    },
    transformRawValue: function(value) {
        return value;
    },
	//缺省实现就是将数据值转换为字符串
    valueToRaw: function(value) {
        return '' + Ext.value(value, '');
    },
	//缺省实现认为原始值与数据值一致
    rawToValue: function(rawValue) {
        return rawValue;
    },
	//原始值预处理
    processRawValue: function(value) {
        return value;
    },
	//在Ext.form.field.Base类层次下，原始值（rawValue）是中心，而不是接口Field定义的数据值（value）
	//数据值是根据原始值导出的
	//该方法具有边际效应，即获取数据值的时候也更新了它
    getValue: function() {
        var me = this,
            val = me.rawToValue(me.processRawValue(me.getRawValue()));
        me.value = val;
        return val;
    },
	//设置值之前先设置原始值
    setValue: function(value) {
        var me = this;
        me.setRawValue(me.valueToRaw(value));
        return me.mixins.field.setValue.call(me, value);
    },
    onBoxReady: function() {
        var me = this;
        me.callParent();
        
        if (me.setReadOnlyOnBoxReady) {
            me.setReadOnly(me.readOnly);
        }
            
    },
    //private
    onDisable: function() {
        var me = this,
            inputEl = me.inputEl;
            
        me.callParent();
        if (inputEl) {
            inputEl.dom.disabled = true;
            if (me.hasActiveError()) {
                me.clearInvalid();
                me.needsValidateOnEnable = true;
            }
        }
    },
    //private
    onEnable: function() {
        var me = this,
            inputEl = me.inputEl;
            
        me.callParent();
        if (inputEl) {
            inputEl.dom.disabled = false;
            if (me.needsValidateOnEnable) {
                delete me.needsValidateOnEnable;
                //启用后立即验证以触发错误
                me.forceValidation = true;
                me.isValid();
                delete me.forceValidation;
            }
        }
    },

    setReadOnly: function(readOnly) {
        var me = this,
            inputEl = me.inputEl;
        readOnly = !!readOnly;
        me[readOnly ? 'addCls' : 'removeCls'](me.readOnlyCls);
        me.readOnly = readOnly;
        if (inputEl) {
            inputEl.dom.readOnly = readOnly;
        } else if (me.rendering) {
            me.setReadOnlyOnBoxReady = true;
        }
        me.fireEvent('writeablechange', me, readOnly);
    },
    // private
    fireKey: function(e){
        if(e.isSpecialKey()){
            this.fireEvent('specialkey', this, new Ext.EventObjectImpl(e));
        }
    },
    //由Renderable在afterRender之后调用，可以完成必要的事件监听器注册
    initEvents : function(){
        var me = this,
            inputEl = me.inputEl,
            onChangeTask,
            onChangeEvent,
            events = me.checkChangeEvents,
            e,
            eLen   = events.length,
            event;
        if (me.inEditor) {
            me.onBlur = Ext.Function.createBuffered(me.onBlur, 10);
        }
        if (inputEl) {
            me.mon(inputEl, Ext.EventManager.getKeyEvent(), me.fireKey,  me);
            //用于实时监听值改变
            onChangeTask = new Ext.util.DelayedTask(me.checkChange, me);
            me.onChangeEvent = onChangeEvent = function() {
                onChangeTask.delay(me.checkChangeBuffer);
            };
            for (e = 0; e &lt; eLen; e++) {
                event = events[e];

                if (event === 'propertychange') {
                    me.usesPropertychange = true;
                }
                me.mon(inputEl, event, onChangeEvent);
            }
        }
        me.callParent();
    },
	//执行组件布局
    doComponentLayout: function() {
        var me = this,
            inputEl = me.inputEl,
            usesPropertychange = me.usesPropertychange,
            ename = 'propertychange',
            onChangeEvent = me.onChangeEvent;
        if (usesPropertychange) {
            me.mun(inputEl, ename, onChangeEvent);
        }
        me.callParent(arguments);
        if (usesPropertychange) {
            me.mon(inputEl, ename, onChangeEvent);
        }
    },
    onDirtyChange: function(isDirty) {
		//注意这种写法，比if-else结构清爽
        this[isDirty ? 'addCls' : 'removeCls'](this.dirtyCls);
    },
    isValid : function() {
        var me = this,
            disabled = me.disabled,
            validate = me.forceValidation || !disabled;
        return validate ? me.validateValue(me.processRawValue(me.getRawValue())) : disabled;
    },
    validateValue: function(value) {
        var me = this,
            errors = me.getErrors(value), //来自Field混入的getErrors()是数据验证的核心
            isValid = Ext.isEmpty(errors);
        if (!me.preventMark) {
            if (isValid) {
                me.clearInvalid();
            } else {
                me.markInvalid(errors);
            }
        }
        return isValid;
    },
    markInvalid : function(errors) {
        var me = this,
            oldMsg = me.getActiveError();
        me.setActiveErrors(Ext.Array.from(errors));
        if (oldMsg !== me.getActiveError()) {
            me.updateLayout();
        }
    },
    clearInvalid : function() {
        var me = this,
            hadError = me.hasActiveError();
        me.unsetActiveError();
        if (hadError) {
            me.updateLayout();
        }
    },
    renderActiveError: function() {
        var me = this,
            hasError = me.hasActiveError();
        if (me.inputEl) {
            me.inputEl[hasError ? 'addCls' : 'removeCls'](me.invalidCls + '-field');
        }
        me.mixins.labelable.renderActiveError.call(me);
    },
    getActionEl: function() {
        return this.inputEl || this.el;
    }
});</pre>
<div class="blog_h3">Ext.form.field.Field</div>
<p>但凡混入了该类的ExtJS类型，都可以视作“表单字段”。该混入为表单字段提供了行为、状态的公共接口：：</p>
<ol>
<li>字段值的读写方法：包括基于字符串、对象的值读写，可以返回值或者键值对</li>
<li>用于跟踪字段值、验证值、脏值状态变化的事件与方法</li>
<li>触发验证、脏值检查的方法</li>
<li>错误消息，样式的读写方法</li>
</ol>
<p>直接混入了该类的类型包括：</p>
<ol>
<li>Ext.form.CheckboxGroup（Ext.form.RadioGroup是其直接子类）</li>
<li>Ext.form.field.Base</li>
<li>Ext.form.field.HtmlEditor</li>
<li>Ext.ux.form.MultiSelect</li>
</ol>
<p>需要自定义表单字段时，通常不需要直接使用该混入，最好是继承Ext.form.field.Base。</p>
<p>Ext.form.field.Field提供的配置项/属性/方法包括：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">配置项/属性/方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>{disabled}</td>
<td>Boolean，该字段是否禁用，禁用的字段不会被提交（Ext.form.Basic.sumbit）</td>
</tr>
<tr>
<td>{name}</td>
<td>String，该字段的名称，在提交（Ext.form.Basic.sumbit）时，将作为请求参数名</td>
</tr>
<tr>
<td>{submitValue}</td>
<td>Boolean，设置为false则不会提交该字段，即使disabled=false</td>
</tr>
<tr>
<td>{validateOnChange}</td>
<td>Boolean，该字段的值发生变化时，是否立即执行验证。如果验证结果发生改变，发布validitychange事件。即使设置为false，也不代表完全禁用验证，表单提交时可以执行验证</td>
</tr>
<tr>
<td>{value}</td>
<td>Object，该字段的初始值</td>
</tr>
<tr>
<td>isFormField</td>
<td>Boolean，总是真，是否表单字段</td>
</tr>
<tr>
<td>originalValue</td>
<td>Object，字段的原始值，要么是配置项提供的，要么是trackResetOnLoad=true的表单的load方法提供的</td>
</tr>
<tr>
<td>batchChanges(fn)</td>
<td>在函数里执行若干可能导致字段值改变的逻辑，防止过多的change事件被触发</td>
</tr>
<tr>
<td>beforeReset()</td>
<td>模板方法，在重置前触发</td>
</tr>
<tr>
<td>checkChange()</td>
<td>
<p>检查值是否发生变化（从上一次检查至今），如果变化，则：</p>
<ol>
<li>触发change事件</li>
<li>如果validateOnChange=true，执行验证</li>
<li>检查脏值状态是否变化，如果是，触发dirtychange事件</li>
</ol>
</td>
</tr>
<tr>
<td>checkDirty()</td>
<td>
<p>检查字段的脏值状态是否发生变化（从上一次检查至今），如果变化，则发布dirtychange</p>
</td>
</tr>
<tr>
<td>clearInvalid()</td>
<td>
<p>子类必须事先该方法。清除字段的无效值样式、消息</p>
</td>
</tr>
<tr>
<td>str[] getErrors(value)</td>
<td>
<p>运行该字段的验证器（Validators）并得到任何错误的消息数组。一般不需要手工调用，在验证过程中会内部调用。可以传递一个值，如果不传递，则使用当前字段值</p>
</td>
</tr>
<tr>
<td>obj getModelData()</td>
<td>
<p>得到用于设置到Ext.data.Model的字段值。该方法由Ext.form.Basic.updateRecord调用。</p>
<p>该方法通常返回具有单个属性的对象：{字段名:字段数据值}，某些高级字段可能返回具有多个属性的对象，返回值会被设置到Model对应的字段中。该方法返回的值可能为经验证。</p>
</td>
</tr>
<tr>
<td>obj getSubmitData()</td>
<td>
<p>得到用于标准表单提交的字段值。</p>
<p>该方法通常返回具有单个属性的对象：{字段名:字段字面值}，某些高级字段可能返回具有多个属性的对象。该方法返回的值可能为经验证。</p>
</td>
</tr>
<tr>
<td>obj getValue()</td>
<td>
<p>获取字段当前的“数据”值，值的类型取决于字段的类型，例如对于Ext.form.field.Date会返回一个日期对象</p>
</td>
</tr>
<tr>
<td>initField()</td>
<td>
<p>在当前字段实例上初始化当前混入。字段必须在初始化过程中调用该方法</p>
</td>
</tr>
<tr>
<td>initValue()</td>
<td>
<p>根据初始化配置项来设定字段值</p>
</td>
</tr>
<tr>
<td>isDirty()</td>
<td>
<p>字段值是否想对于originalValue发生变化。如果disabled则一直为false</p>
</td>
</tr>
<tr>
<td>isEqual(val1,val2)</td>
<td>
<p>子类可以覆盖该方法，用于判断两个值是否在逻辑上相等</p>
</td>
</tr>
<tr>
<td>isFileUpload()</td>
<td>
<p>判断当前字段是不是文件上传，对于文件上传，表单将使用特殊的Ajax技术来处理之</p>
</td>
</tr>
<tr>
<td>HtmlEl extractFileInput()</td>
<td>
<p>如果isFileUpload()=true，该方法有意义。返回持有用户输入的input type=file HTML元素</p>
</td>
</tr>
<tr>
<td>isValid()</td>
<td>
<p>通过执行验证当前值，返回字段值的有效性。该方法不会导致 validitychange事件的触发。注意disabled字段总返回true</p>
<p>子类覆盖该方法时，应当注意不要有边际效应，例如导致错误信息、样式的显示</p>
</td>
</tr>
<tr>
<td>validate()</td>
<td>
<p>通过执行验证当前值，返回字段值的有效性。该方法可能触发validitychange事件。注意disabled字段总返回true</p>
<p>子类覆盖该方法时，可以有边际效应，例如导致错误信息、样式的显示</p>
</td>
</tr>
<tr>
<td>markInvalid(str[] errs)</td>
<td>
<p>子类需要实现该方法。关联一个/多个错误消息到该字段，并且渲染这些消息。该方法与值的有效性无关</p>
</td>
</tr>
<tr>
<td>reset()</td>
<td>
<p>清除任何验证消息，把值设置为初始加载的值</p>
</td>
</tr>
<tr>
<td>resetOriginalValue()</td>
<td>
<p>重置初始值为当前值，由trackResetOnLoad=true的lExt.form.Basic.setValues()方法调用</p>
</td>
</tr>
<tr>
<td>setValue(obj)</td>
<td>
<p>设置字段的值，并且执行change检测、有效性验证</p>
</td>
</tr>
<tr>
<td>transformOriginalValue(val)</td>
<td>
<p>子类可以覆盖该方法，字段初始化时，会自动使用该方法转换配置传来的值</p>
</td>
</tr>
<tr>
<td>⚡change</td>
<td>
<p>change(this, Object newValue, Object oldValue, Object eOpts )</p>
<p>由setValue()方法触发，如果值发生了变化</p>
</td>
</tr>
<tr>
<td>⚡dirtychange</td>
<td>
<p>dirtychange( this, Boolean isDirty, Object eOpts )</p>
<p>当dirtry状态发生变化时，触发该事件</p>
</td>
</tr>
<tr>
<td>⚡validitychange</td>
<td>
<p>validitychange( this, Boolean isValid, Object eOpts )</p>
<p>当有效性状态发生变化时，触发该事件</p>
</td>
</tr>
</tbody>
</table>
<p>Ext.form.field.Field的源代码分析，比较简单，主要关心字段的读、写、脏、验这四个方面，不包含任何UI相关的逻辑：</p>
<pre class="crayon-plain-tag">Ext.define('Ext.form.field.Field', {
    isFormField : true,
    disabled : false,
    submitValue: true,
    validateOnChange: true,
    suspendCheckChange: 0,
    /**
     * 字段在初始化时必须调用该方法
     */
    initField: function() {
        //添加基础事件
        this.addEvents(
            'change',
            'validitychange',
            'dirtychange'
        );
        this.initValue();
    },
    /**
     * 初始化字段值
     */
    initValue: function() {
        var me = this;
        me.value = me.transformOriginalValue(me.value);//对配置项提供的值进行转换
        me.originalValue = me.lastValue = me.value; //设置原始值
        me.suspendCheckChange++; //有很多这样的计数器，来判断是否允许触发change事件
        me.setValue(me.value);
        me.suspendCheckChange--;
    },

    setValue: function(value) {
        var me = this;
        me.value = value;
        me.checkChange(); //检查是否需要触发change事件
        return me;
    },
    //覆写此方法，会影响change事件的触发
    isEqual: function(value1, value2) {
        return String(value1) === String(value2);
    },

    isEqualAsString: function(value1, value2){
        return String(Ext.value(value1, '')) === String(Ext.value(value2, ''));
    },

    getSubmitData: function() {
        var me = this,
            data = null;
        if (!me.disabled &amp;&amp; me.submitValue &amp;&amp; !me.isFileUpload()) {
            data = {};
            data[me.getName()] = '' + me.getValue();//默认的字符串转换
        }
        return data;//如果禁用，将返回null
    },
    getModelData: function() {
        var me = this,
            data = null;
        if (!me.disabled &amp;&amp; !me.isFileUpload()) {
            data = {};
            data[me.getName()] = me.getValue();
        }
        return data;//如果禁用，将返回null
    },
    reset : function(){
        var me = this;

        me.beforeReset();
        me.setValue(me.originalValue);
        me.clearInvalid();
        //恢复原始的验证状态
        delete me.wasValid;
    },
    resetOriginalValue: function() {
        this.originalValue = this.getValue();//直接设置当前值为原始值
        this.checkDirty();
    },
    checkChange: function() {
        if (!this.suspendCheckChange) {//如果没有暂停值变化检测
            var me = this,
                newVal = me.getValue(),
                oldVal = me.lastValue;
            if (!me.isEqual(newVal, oldVal) &amp;&amp; !me.isDestroyed) {
                me.lastValue = newVal;
                me.fireEvent('change', me, newVal, oldVal);
                me.onChange(newVal, oldVal);
            }
        }
    },
    //私有方法
    onChange: function(newVal, oldVal) {
        if (this.validateOnChange) {
            this.validate();
        }
        this.checkDirty();
    },

    isDirty : function() {
        var me = this;
        return !me.disabled &amp;&amp; !me.isEqual(me.getValue(), me.originalValue);
    },

    checkDirty: function() {
        var me = this,
            isDirty = me.isDirty();
        if (isDirty !== me.wasDirty) {
            me.fireEvent('dirtychange', me, isDirty);
            me.onDirtyChange(isDirty);
            me.wasDirty = isDirty;
        }
    },
    /*验证的底层都是调用getErrors()*/
    isValid : function() {
        var me = this;
        return me.disabled || Ext.isEmpty(me.getErrors());
    },

    validate : function() {
        var me = this,
            isValid = me.isValid();
        if (isValid !== me.wasValid) {
            me.wasValid = isValid;
            me.fireEvent('validitychange', me, isValid);
        }
        return isValid;
    },

    batchChanges: function(fn) {
        try {
            this.suspendCheckChange++;
            fn();
        } catch(e){
            throw e;
        } finally {
            this.suspendCheckChange--;
        }
        this.checkChange();
    },

    isFileUpload: function() {
        return false;
    },
    
    /*下面都是缺省适配 */
    extractFileInput: function() {
        return null;
    },

    markInvalid: Ext.emptyFn, //添加字段的无效值样式、消息

    clearInvalid: Ext.emptyFn, //清除字段的无效值样式、消息
    
    beforeReset: Ext.emptyFn, //模板方法
        
    transformOriginalValue: function(value){
        return value;
    },
    getName: function() {
        return this.name;
    },
    getValue: function() {
        return this.value;
    },
    onDirtyChange: Ext.emptyFn,

    getErrors: function(value) {
        return [];
    }
});</pre>
<div class="blog_h3">Ext.form.Labelable</div>
<p>该混入类可以为组件（通常用于表单字段）添加标签、错误消息。Ext.form.FieldContainer、Ext.form.field.Base、Ext.form.field.HtmlEditor混入了该类，通常不需要直接使用该混入类。</p>
<p>使用该混入的组件应当使用Field组件布局（或其衍生形式）以便确定标签、消息的位置大小，在组件初始化阶段（initComponent）应当调用initLabelable进行该混入的初始化。</p>
<p>Ext.form.Labelable提供的配置项/属性/方法包括：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">配置项/属性/方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>{activeError}</td>
<td>String，如果指定该配置项，在组件渲染后立即显示错误信息。渲染后可以使用 setActiveError/unsetActiveError方法修改</td>
</tr>
<tr>
<td>{activeErrorsTpl}</td>
<td>String/String[]/Ext.XTemplate，传递给setActiveErrors()的模板，用来格式化一组错误消息为HTML片段</td>
</tr>
<tr>
<td>{afterBodyEl}</td>
<td>String/Array/Ext.XTemplate，可选的用于插入表单字段包含input的元素之后的模板，上下文为AbstractComponent.renderData</td>
</tr>
<tr>
<td>{afterLabelTextTpl}</td>
<td>String/Array/Ext.XTemplate，可选的用于插入表单字段标签文本（Label Text）之后的模板，上下文为AbstractComponent.renderData</td>
</tr>
<tr>
<td>{afterLabelTpl}</td>
<td>String/Array/Ext.XTemplate，可选的用于插入表单字段标签元素（Label Element）之后的模板，上下文为AbstractComponent.renderData</td>
</tr>
<tr>
<td>{afterSubTpl}</td>
<td>String/Array/Ext.XTemplate，可选的用于插入表单字段getSubTplMarkup()标记之后的模板，上下文为AbstractComponent.renderData</td>
</tr>
<tr>
<td>{autoFitErrors}</td>
<td>Boolean，调整组件大小（与标签无关），为side、under错误消息提供空间</td>
</tr>
<tr>
<td>{baseBodyCls}</td>
<td>String，应用到字段体内容元素的样式，默认“x-form-item-body”</td>
</tr>
<tr>
<td>{beforeBodyEl}</td>
<td rowspan="4">参考afterXxx部分的配置</td>
</tr>
<tr>
<td>{beforeLabelTextTpl}</td>
</tr>
<tr>
<td>{beforeLabelTpl}</td>
</tr>
<tr>
<td>{beforeSubTpl}</td>
</tr>
<tr>
<td>{errorMsgCls}</td>
<td>String，应用到错误消息元素的样式</td>
</tr>
<tr>
<td>{fieldBodyCls}</td>
<td>String，额外（baseBodyCls）字段体内容元素样式</td>
</tr>
<tr>
<td>{fieldLabel}</td>
<td>String，标签内容</td>
</tr>
<tr>
<td>{formItemCls}</td>
<td>String，用于最外层元素的样式类，指示参与表单的字段布局</td>
</tr>
<tr>
<td>{hideEmptyLabel }</td>
<td>Boolean，如果fieldLabel为空，是否自动隐藏，默认true</td>
</tr>
<tr>
<td>{hideLabel}</td>
<td>Boolean，是否隐藏标签</td>
</tr>
<tr>
<td>{invalidCls}</td>
<td>String，当验证失败时，应用到组件的样式，默认“x-form-invalid”</td>
</tr>
<tr>
<td>{labelAlign}</td>
<td>String，控制标签的位置与对齐：left、top、right </td>
</tr>
<tr>
<td>{labelAttrTpl}</td>
<td>String/Array/Ext.XTemplate，可选的、作为属性插入到标签对应元素的HTML属性的模板</td>
</tr>
<tr>
<td>{labelCls}</td>
<td>String，标签的样式，默认“x-form-item-label”</td>
</tr>
<tr>
<td>{labelClsExtra}</td>
<td>String，标签的额外样式</td>
</tr>
<tr>
<td>{labelPad}</td>
<td>Number，标签与input之间的补白，默认5像素 </td>
</tr>
<tr>
<td>{labelSeparator}</td>
<td>String，添加到标签尾部的文字，默认为冒号（:） </td>
</tr>
<tr>
<td>{labelStyle}</td>
<td>String，应用到标签的样式文本 </td>
</tr>
<tr>
<td>{labelWidth}</td>
<td>Number，对于left、right位置的标签，用于指定标签宽度，默认100像素</td>
</tr>
<tr>
<td>{labelableRenderTpl}</td>
<td>
<p>String/Array/Ext.XTemplate，私有。表单字段装饰的渲染模板，使用该混入的组件应当事先逻辑，把该渲染模板包含在其renderTpl中。并且实现getSubTplMarkup()方法以生成字段体的内容(field body content)。</p>
<p>默认模板使用HTML表格（table）来组织label 、InputField、sideErrorEl </p>
</td>
</tr>
<tr>
<td>{msgTarget}</td>
<td>String，错误信息应当显示的位置。支持以下值：<br />qtip　默认值，当用户鼠标悬停字段时显示错误信息<br />title　显示为底层HTML输入的title属性<br />under　在字段下面显示一个块状区域，其中包含错误信息<br />side　在字段右侧显示一个图标，鼠标悬停时显示错误信息<br />none　不显示错误信息<br />[element id]　错误信息渲染为id对应HTML元素的innerHTML</td>
</tr>
<tr>
<td>{preventMark}</td>
<td>Boolean，设置为真，禁止显示任何错误信息</td>
</tr>
<tr>
<td>bodyEl</td>
<td>Element，包裹字段内容的div元素，仅在渲染后可用</td>
</tr>
<tr>
<td>errorEl</td>
<td>Element，包含错误信息的div元素</td>
</tr>
<tr>
<td>isFieldLabelable</td>
<td>Boolean，指示当前组件是否表单字段，总是true</td>
</tr>
<tr>
<td>labelCell</td>
<td>Element，包含标签的td元素，仅在渲染后可用</td>
</tr>
<tr>
<td>labelEl</td>
<td>Element，标签元素，仅在渲染后可用</td>
</tr>
<tr>
<td>noWrap</td>
<td>Boolean，提示布局系统高度可以立即获得</td>
</tr>
<tr>
<td>getActiveError()</td>
<td>String，获取当前错误信息，不会触发验证动作</td>
</tr>
<tr>
<td>getActiveErrors( )</td>
<td>String[]，获取当前错误信息的数组，不会触发验证动作</td>
</tr>
<tr>
<td>getFieldLabel( )</td>
<td>String，模板方法。返回当前字段的标签，默认实现仅仅是返回fieldLabel配置</td>
</tr>
<tr>
<td>getInputId( )</td>
<td>String，获取input元素的id</td>
</tr>
<tr>
<td>getLabelableRenderData()</td>
<td>Object，生成用于填充被Labelable装饰了的表单字段HTML标记模板的上下文数据</td>
</tr>
<tr>
<td>getSubTplMarkup( )</td>
<td>String，用于插入到外部模板bodyEl的模板，默认空，混入Labelable的类应当实现之</td>
</tr>
<tr>
<td>hasActiveError( )</td>
<td>Boolean，当前是否存在错误</td>
</tr>
<tr>
<td>hasVisibleLabel( )</td>
<td>Boolean，是否具有可见的标签</td>
</tr>
<tr>
<td>initLabelable( )</td>
<td>初始化该混入</td>
</tr>
<tr>
<td>renderActiveError( )</td>
<td>私有方法，渲染当前错误</td>
</tr>
<tr>
<td>setActiveError()</td>
<td>设置当前错误 </td>
</tr>
<tr>
<td>setActiveErrors()</td>
<td>设置当前错误 </td>
</tr>
<tr>
<td>setFieldDefaults()</td>
<td>应用到当前表单字段的默认配置</td>
</tr>
<tr>
<td>setFieldLabel()</td>
<td>设置标签</td>
</tr>
<tr>
<td>trimLabelSeparator()</td>
<td>返回剔除冒号的标签</td>
</tr>
<tr>
<td>unsetActiveError()</td>
<td>清除当前错误消息，仅仅是清除错误消息元素的文本和属性，需要手工调用doComponentLayou()来重新布局</td>
</tr>
<tr>
<td>⚡errorchange</td>
<td>
<p>errorchange( Ext.form.Labelable this, String error, Object eOpts )</p>
<p>当通过setActiveError()改变错误消息时触发。</p>
</td>
</tr>
</tbody>
</table>
<p>Ext.form.Labelable的源代码分析，关注的内容主要是标签、错误信息的显示和布局：</p>
<pre class="crayon-plain-tag">Ext.define("Ext.form.Labelable", {
    requires: ['Ext.XTemplate'],
    autoEl: {
        tag: 'table',
        cellpadding: 0
    },
    //描述组件子元素的数组，渲染到renderTpl中
    childEls: [
        'labelCell',
        'labelEl',
        'bodyEl',
        'sideErrorCell',
        'errorEl',
        'inputRow',
        'bottomPlaceHolder'
    ],
    //修饰了标签、错误信息后的字段的HTML标记模板
    //Ext.form.field.Base使用该模板作为其默认的组件renderTpl
    labelableRenderTpl: [
        //标签部分
        '&lt;tr id="{id}-inputRow" &lt;tpl if="inFormLayout"&gt;id="{id}"&lt;/tpl&gt;&gt;',
            '&lt;tpl if="labelOnLeft"&gt;',  //如果：左侧标签
                '&lt;td id="{id}-labelCell" style="{labelCellStyle}" {labelCellAttrs}&gt;',
                    '{beforeLabelTpl}',//子模板：标签元素之前
                                               //子模板：标签元素属性
                    '&lt;label id="{id}-labelEl" {labelAttrTpl}&lt;tpl if="inputId"&gt; for="{inputId}"&lt;/tpl&gt; class="{labelCls}"',
                        '&lt;tpl if="labelStyle"&gt; style="{labelStyle}"&lt;/tpl&gt;&gt;',
                        '{beforeLabelTextTpl}', //子模板：标签文本之前
                                               //文字标签  冒号
                        '&lt;tpl if="fieldLabel"&gt;{fieldLabel}{labelSeparator}&lt;/tpl&gt;',
                        '{afterLabelTextTpl}', //子模板：标签文本之后
                    '&lt;/label&gt;',
                    '{afterLabelTpl}',    //子模板：标签元素之后
                '&lt;/td&gt;',
            '&lt;/tpl&gt;',                   //如果结束：左侧标签

            '&lt;td class="{baseBodyCls} {fieldBodyCls}" id="{id}-bodyEl" colspan="{bodyColspan}" role="presentation"&gt;',
                '{beforeBodyEl}',       //在字段bodyEl之前的元素
                '&lt;tpl if="labelAlign==\'top\'"&gt;',  //如果：顶部标签
                    '{beforeLabelTpl}',            //子模板：标签元素之前
                    '&lt;div id="{id}-labelCell" style="{labelCellStyle}"&gt;',
                                                   //子模板：标签元素属性
                        '&lt;label id="{id}-labelEl" {labelAttrTpl}&lt;tpl if="inputId"&gt; for="{inputId}"&lt;/tpl&gt; class="{labelCls}"',
                            '&lt;tpl if="labelStyle"&gt; style="{labelStyle}"&lt;/tpl&gt;&gt;',
                            '{beforeLabelTextTpl}',//子模板：标签文本之前
                                                   //文字标签  冒号
                            '&lt;tpl if="fieldLabel"&gt;{fieldLabel}{labelSeparator}&lt;/tpl&gt;',
                            '{afterLabelTextTpl}', //子模板：标签文本之后
                        '&lt;/label&gt;',
                    '&lt;/div&gt;',
                    '{afterLabelTpl}',             //子模板：标签元素之后
                '&lt;/tpl&gt;',                          //结束如果：顶部标签

                '{beforeSubTpl}',                      //字段主体标记之前
                '{[values.$comp.getSubTplMarkup()]}',  //字段主体标记，调用字段的getSubTplMarkup()得到
                '{afterSubTpl}',                       //字段主体标记之后

            //错误消息部分
            '&lt;tpl if="msgTarget===\'side\'"&gt;',  //右侧
                '{afterBodyEl}',                      //在字段bodyEl之后的元素
                '&lt;/td&gt;',
                '&lt;td id="{id}-sideErrorCell" vAlign="{[values.labelAlign===\'top\' &amp;&amp; !values.hideLabel ? \'bottom\' : \'middle\']}" style="{[values.autoFitErrors ? \'display:none\' : \'\']}" width="{errorIconWidth}"&gt;',
                    '&lt;div id="{id}-errorEl" class="{errorMsgCls}" style="display:none;width:{errorIconWidth}px"&gt;&lt;/div&gt;',
                '&lt;/td&gt;',
            '&lt;tpl elseif="msgTarget==\'under\'"&gt;',  //下面
                '&lt;div id="{id}-errorEl" class="{errorMsgClass}" colspan="2" style="display:none"&gt;&lt;/div&gt;',
                '{afterBodyEl}',                     //在字段bodyEl之后的元素
                '&lt;/td&gt;',
            '&lt;/tpl&gt;',

        '&lt;/tr&gt;',
        {
            disableFormats: true
        }
    ],
    //当前错误信息的HTML标记模板
    activeErrorsTpl: [
        '&lt;tpl if="errors &amp;&amp; errors.length"&gt;',
            '&lt;ul&gt;&lt;tpl for="errors"&gt;&lt;li&gt;{.}&lt;/li&gt;&lt;/tpl&gt;&lt;/ul&gt;',
        '&lt;/tpl&gt;'
    ],
    isFieldLabelable: true,
    formItemCls: Ext.baseCSSPrefix + 'form-item',
    labelCls: Ext.baseCSSPrefix + 'form-item-label',
    errorMsgCls: Ext.baseCSSPrefix + 'form-error-msg',
    baseBodyCls: Ext.baseCSSPrefix + 'form-item-body',
    fieldBodyCls: '',
    clearCls: Ext.baseCSSPrefix + 'clear',
    invalidCls : Ext.baseCSSPrefix + 'form-invalid',
    fieldLabel: undefined,
    labelAlign : 'left',
    labelWidth: 100,
    labelPad : 5,
    labelSeparator : ':',
    hideLabel: false,
    hideEmptyLabel: true,
    preventMark: false,
    autoFitErrors: true,
    msgTarget: 'qtip',
    noWrap: true,
    //模板插入点成员属性声明
    labelableInsertions: [],
    isFieldLabelable: true,
    formItemCls: Ext.baseCSSPrefix + 'form-item',
    labelCls: Ext.baseCSSPrefix + 'form-item-label',
    errorMsgCls: Ext.baseCSSPrefix + 'form-error-msg',
    baseBodyCls: Ext.baseCSSPrefix + 'form-item-body',
    fieldBodyCls: '',
    clearCls: Ext.baseCSSPrefix + 'clear',
    invalidCls : Ext.baseCSSPrefix + 'form-invalid',
    fieldLabel: undefined,
    labelAlign : 'left',
    labelWidth: 100,
    labelPad : 5,
    labelSeparator : ':',
    hideLabel: false,
    hideEmptyLabel: true,
    preventMark: false,
    autoFitErrors: true,
    msgTarget: 'qtip',
    noWrap: true,
    //模板插入点成员属性声明
    labelableInsertions: [
        'beforeBodyEl',
        'afterBodyEl',
        'beforeLabelTpl',
        'afterLabelTpl',
        'beforeSubTpl',
        'afterSubTpl',
        'beforeLabelTextTpl',
        'afterLabelTextTpl',
        'labelAttrTpl'
    ],
    labelableRenderProps: [ 'allowBlank', 'id', 'labelAlign', 'fieldBodyCls', 'baseBodyCls',
                            'clearCls', 'labelSeparator', 'msgTarget' ],
    //在字段的initComponent阶段调用
    initLabelable: function() {
        var me = this,
            padding = me.padding;
        if (padding) {
            me.padding = undefined;
            me.extraMargins = Ext.Element.parseBox(padding);
        }
        me.addCls(me.formItemCls);
        me.lastActiveError = '';
        me.addEvents(
            'errorchange'
        );
    },
    trimLabelSeparator: function() {
        var me = this,
            separator = me.labelSeparator,
            label = me.fieldLabel || '',
            lastChar = label.substr(label.length - 1);
        return lastChar === separator ? label.slice(0, -1) : label;
    },
    getFieldLabel: function() {
        return this.trimLabelSeparator();
    },
    setFieldLabel: function(label){
        label = label || '';
        var me = this,
            separator = me.labelSeparator,
            labelEl = me.labelEl;
        me.fieldLabel = label;
        if (me.rendered) {
            if (Ext.isEmpty(label) &amp;&amp; me.hideEmptyLabel) {
                labelEl.parent().setDisplayed('none');
            } else {
                if (separator) {
                    label = me.trimLabelSeparator() + separator;
                }
                labelEl.update(label);
                labelEl.parent().setDisplayed('');
            }
            me.updateLayout();
        }
    },
    getInsertionRenderData: function (data, names) {
        var i = names.length,
            name, value;
        while (i--) {
            name = names[i];
            value = this[name];
            if (value) {
                if (typeof value != 'string') {
                    if (!value.isTemplate) {
                        value = Ext.XTemplate.getTpl(this, name);
                    }
                    value = value.apply(data);
                }
            }
            data[name] = value || '';
        }
        return data;
    },
    //收集渲染数据
    getLabelableRenderData: function() {
        var me = this,
            data,
            tempEl,
            topLabel = me.labelAlign === 'top';
        if (!Ext.form.Labelable.errorIconWidth) {
            Ext.form.Labelable.errorIconWidth = (tempEl = Ext.resetElement.createChild({style: 'position:absolute', cls: Ext.baseCSSPrefix + 'form-invalid-icon'})).getWidth();
            tempEl.remove();
        }
        //复制并覆盖默认值
        data = Ext.copyTo({
            inFormLayout   : me.ownerLayout &amp;&amp; me.ownerLayout.type === 'form',
            inputId        : me.getInputId(),
            labelOnLeft    : !topLabel,
            hideLabel      : !me.hasVisibleLabel(),
            fieldLabel     : me.getFieldLabel(),
            labelCellStyle : me.getLabelCellStyle(),
            labelCellAttrs : me.getLabelCellAttrs(),
            labelCls       : me.getLabelCls(),
            labelStyle     : me.getLabelStyle(),
            bodyColspan    : me.getBodyColspan(),
            externalError  : !me.autoFitErrors,
            errorMsgCls    : me.getErrorMsgCls(),
            errorIconWidth : Ext.form.Labelable.errorIconWidth
        },
        me, me.labelableRenderProps, true);
        //用于插入主模板的若干变量或者模板，模板在getInsertionRenderData完成替换
        me.getInsertionRenderData(data, me.labelableInsertions);
        return data;
    },
    beforeLabelableRender: function() {
        var me = this;
        if (me.ownerLayout) {
            me.addCls(Ext.baseCSSPrefix + me.ownerLayout.type + '-form-item');
        }
    },
    onLabelableRender: function() {
        var me = this,
            margins,
            side,
            style = {};
        if (me.extraMargins) {
            margins = me.el.getMargin();
            for (side in margins) {
                if (margins.hasOwnProperty(side)) {
                    style['margin-' + side] = (margins[side] + me.extraMargins[side]) + 'px';
                }
            }
            me.el.setStyle(style);
        }
    },
    hasVisibleLabel: function(){
        if (this.hideLabel) {
            return false;
        }
        return !(this.hideEmptyLabel &amp;&amp; !this.getFieldLabel());
    },
    getBodyColspan: function() {
        var me = this,
            result;
 
        if (me.msgTarget === 'side' &amp;&amp; (!me.autoFitErrors || me.hasActiveError())) {
            result = 1;
        } else {
            result = 2;
        }
        if (me.labelAlign !== 'top' &amp;&amp; !me.hasVisibleLabel()) {
            result++;
        }
        return result;
    },
    getLabelCls: function() {
        var labelCls = this.labelCls,
            labelClsExtra = this.labelClsExtra;
 
        if (this.labelAlign === 'top') {
            labelCls += '-top';
        }
        return labelClsExtra ? labelCls + ' ' + labelClsExtra : labelCls;
    },
    getLabelCellStyle: function() {
        var me = this,
            hideLabelCell = me.hideLabel || (!me.fieldLabel &amp;&amp; me.hideEmptyLabel);
 
        return hideLabelCell ? 'display:none;' : '';
    },
    getErrorMsgCls: function() {
        var me = this,
            hideLabelCell = (me.hideLabel || (!me.fieldLabel &amp;&amp; me.hideEmptyLabel));
        return me.errorMsgCls + (!hideLabelCell &amp;&amp; me.labelAlign === 'top' ? ' ' + Ext.baseCSSPrefix + 'lbl-top-err-icon' : '');
    },
    getLabelCellAttrs: function() {
        var me = this,
            labelAlign = me.labelAlign,
            result = '';
 
        if (labelAlign !== 'top') {
            result = 'valign="top" halign="' + labelAlign + '" width="' + (me.labelWidth + me.labelPad) + '"';
        }
        return result + ' class="' + Ext.baseCSSPrefix + 'field-label-cell"';
    },
    getLabelStyle: function(){
        var me = this,
            labelPad = me.labelPad,
            labelStyle = '';
        if (me.labelAlign !== 'top') {
            if (me.labelWidth) {
                labelStyle = 'width:' + me.labelWidth + 'px;';
            }
            labelStyle += 'margin-right:' + labelPad + 'px;';
        }
        return labelStyle + (me.labelStyle || '');
    },
    //混入该类的组件必须实现此方法，提供表单字段的input元素
    getSubTplMarkup: function() {
        return '';
    },
    getInputId: function() {
        return '';
    },
    getActiveError : function() {
        return this.activeError || '';
    },
    hasActiveError: function() {
        return !!this.getActiveError();
    },
    setActiveError: function(msg) {
        this.setActiveErrors(msg);
    },
    getActiveErrors: function() {
        return this.activeErrors || [];
    },
    setActiveErrors: function(errors) {
        errors = Ext.Array.from(errors);
        this.activeError = errors[0];
        this.activeErrors = errors;
        this.activeError = this.getTpl('activeErrorsTpl').apply({errors: errors});
        this.renderActiveError();
    },
    unsetActiveError: function() {
        delete this.activeError;
        delete this.activeErrors;
        this.renderActiveError();
    },
    renderActiveError: function() {
        var me = this,
            activeError = me.getActiveError(),
            hasError = !!activeError;
 
        if (activeError !== me.lastActiveError) {
            me.fireEvent('errorchange', me, activeError);
            me.lastActiveError = activeError;
        }
        if (me.rendered &amp;&amp; !me.isDestroyed &amp;&amp; !me.preventMark) {
            me.el[hasError ? 'addCls' : 'removeCls'](me.invalidCls);
            me.getActionEl().dom.setAttribute('aria-invalid', hasError);
            if (me.errorEl) {
                me.errorEl.dom.innerHTML = activeError;
            }
        }
    },
    setFieldDefaults: function(defaults) {
        var me = this,
            val, key;
        for (key in defaults) {
            if (defaults.hasOwnProperty(key)) {
                val = defaults[key];
 
                if (!me.hasOwnProperty(key)) {
                    me[key] = val;
                }
            }
        }
    }
});</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/extjs-4-form-fields">浅析ExtJS 4表单字段</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/extjs-4-form-fields/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ExtJS 4的容器机制</title>
		<link>https://blog.gmem.cc/extjs-4-containers</link>
		<comments>https://blog.gmem.cc/extjs-4-containers#comments</comments>
		<pubDate>Sat, 06 Apr 2013 08:42:55 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ExtJS]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=5520</guid>
		<description><![CDATA[<p>ExtJS容器简介 容器是一种特殊的组件，容器与一般组件的根本差别在于，它的内部可以包含其他组件（包括容器）作为其“子组件（items）”。容器提供添加、删除、插入子组件的方法和相关的事件。此外，容器还引入了“容器布局”，专门处理子组件的大小、位置。 ExtJS容器的初始化和渲染 在ExtJS 4的组件机制一文中，包含了对三层嵌套的面板的渲染阶段的整体分析，请参考。 本节主要分析以下几点内容： 容器（及其子组件）的初始化过程 容器渲染阶段，如何得到和处理渲染树 渲染树如何委托布局来渲染子组件 考虑下面的例子： [crayon-69d9b17a03754232630857/] 这是包含两个Component（b0,b1）的Container（c0），使用Auto布局。下面是最终渲染效果和DOM结构： 容器的初始化 在容器c0的初始化（主要是initComponent）阶段，包含以下重要逻辑： 调用getLayout()初始化布局对象 使用静态函数Ext.layout.Layout.create()创建布局对象，由于未指定布局方式，创建为Ext.layout.container.Auto 调用AbstractContainer.setLayout()设置容器与布局的关联性。注意布局和容器是一对一的关联关系 调用initItems()初始化子组件 initItems()调用AbstractContainer.add() add()调用prepareItems()，把b0、b1添加为子组件，并完成子组件的初始化阶段： 调用lookupComponent()查找组件：如果子组件存在于ComponentManager中，则获取之，否则构造之 在本例中需要构造，调用子组件的constructor、initComponent <a class="read-more" href="https://blog.gmem.cc/extjs-4-containers">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/extjs-4-containers">ExtJS 4的容器机制</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">ExtJS容器简介</span></div>
<p>容器是一种特殊的组件，容器与一般组件的根本差别在于，它的内部可以包含其他组件（包括容器）作为其“子组件（items）”。容器提供添加、删除、插入子组件的方法和相关的事件。此外，容器还引入了“容器布局”，专门处理子组件的大小、位置。</p>
<div class="blog_h2"><span class="graybg">ExtJS容器的初始化和渲染</span></div>
<p>在<a href="/extjs-4-components#render-process-of-panel">ExtJS 4的组件机制</a>一文中，包含了对三层嵌套的面板的渲染阶段的整体分析，请参考。</p>
<p>本节主要分析以下几点内容：</p>
<ol>
<li>容器（及其子组件）的初始化过程</li>
<li>容器渲染阶段，如何得到和处理渲染树</li>
<li>渲染树如何委托布局来渲染子组件</li>
</ol>
<p>考虑下面的例子：</p>
<pre class="crayon-plain-tag">Ext.create( 'Ext.container.Container', {
    renderTo : Ext.getBody(),
    id : 'c0',
    width : 250,
    height : 250,
    style : {
        backgroundColor : '#f88'
    },
    html:'This is a container',
    items : [
        {
            id : 'c0-b0',
            xtype : 'box',
            width : 100,
            height : 100,
            style : {
                backgroundColor : '#8f8'
            }
        }, {
            id : 'c0-b1',
            xtype : 'box',
            width : 100,
            height : 100,
            style : {
                backgroundColor : '#88f'
            }
        }
    ]
} );</pre>
<p>这是包含两个Component（b0,b1）的Container（c0），使用Auto布局。下面是最终渲染效果和DOM结构：</p>
<p><img class="aligncenter size-full wp-image-5571" src="https://blog.gmem.cc/wp-content/uploads/2013/04/c0b0b1.png" alt="c0b0b1" width="95%" /></p>
<div class="blog_h3"><span class="graybg">容器的初始化</span></div>
<p>在容器c0的初始化（主要是initComponent）阶段，包含以下重要逻辑：</p>
<ol>
<li>调用getLayout()初始化布局对象
<ol>
<li>使用静态函数Ext.layout.Layout.create()创建布局对象，由于未指定布局方式，创建为Ext.layout.container.Auto</li>
<li>调用AbstractContainer.setLayout()设置容器与布局的关联性。注意<span style="background-color: #c0c0c0;">布局和容器是一对一</span>的关联关系</li>
</ol>
</li>
<li>调用initItems()初始化子组件
<ol>
<li>initItems()调用AbstractContainer.add()</li>
<li>add()调用prepareItems()，把b0、b1添加为子组件，并完成子组件的初始化阶段：
<ol>
<li>调用lookupComponent()查找组件：如果子组件存在于ComponentManager中，则获取之，否则构造之</li>
<li>在本例中需要构造，调用子组件的constructor、initComponent
<ol>
<li>如果子组件也是容器（本例不是），则又是一个递归的过程，直到最底层的子代组件被初始化</li>
</ol>
</li>
</ol>
</li>
<li>add()遍历所有被初始化完毕的子组件，执行：
<ol>
<li>对于浮动组件，转移到floatingItems集合中，并调用子组件onAdded()模板</li>
<li>对于非浮动组件，存放在items集合中，分别：
<ol>
<li>触发beforeadd事件、调用onBeforeAdd模板，只要任一返回false，取消添加</li>
<li>调用子组件onAdded()，导致子组件added事件触发</li>
<li>调用容器onAdd()</li>
<li>调用布局onAdd()</li>
<li>发布add事件</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li>由于配置了renderTo，触发渲染阶段开始</li>
</ol>
<div class="blog_h3"><span class="graybg">容器的渲染</span></div>
<p>如ExtJS 4的组件机制一文所分析，渲染阶段会自上而下的onRender，并自下而上的完成afterRender，完成整个组件层次的渲染。渲染的核心是<span style="background-color: #c0c0c0;">渲染树</span>，渲染树包括tpl属性，是<span style="background-color: #c0c0c0;">一个XTemplate模板</span>，以及<span style="background-color: #c0c0c0;">从Layout等对象获取过来控制渲染细节的若干函数</span>。</p>
<p>渲染树tpl.html是模板内容，它看起来很简单，下表列出不同容器tree.tpl.html：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">容器</td>
<td style="text-align: center;">tree.tpl.html</td>
</tr>
</thead>
<tbody>
<tr>
<td> Container</td>
<td>由AbstractContainer.renderTpl提供：<br />
<pre class="crayon-plain-tag">{%this.renderContainer(out,values)%}</pre>
</td>
</tr>
<tr>
<td> Panel</td>
<td>由AbstractPanel.renderTpl提供，核心仍然是renderContainer，但是还包括dockedItems的渲染逻辑：<br />
<pre class="crayon-plain-tag">{% this.renderDockedItems(out,values,0); %}
&lt;div id="{id}-body" 
     class="{baseCls}-body
            &lt;tpl if="bodyCls"&gt; {bodyCls}&lt;/tpl&gt; {baseCls}-body-{ui}
            &lt;tpl if="uiCls"&gt;
                &lt;tpl for="uiCls"&gt; 
                    {parent.baseCls}-body-{parent.ui}-{.}
                &lt;/tpl&gt;
            &lt;/tpl&gt;"
      &lt;tpl if="bodyStyle"&gt; 
      style="{bodyStyle}"
      &lt;/tpl&gt;
&gt;
    {%this.renderContainer(out,values);%}
&lt;/div&gt;
{% this.renderDockedItems(out,values,1); %}</pre>
</td>
</tr>
</tbody>
</table>
<p>在填充渲染树模板过程中，通过调用renderContainer()，容器的渲染行为会最终委托给布局对象，而布局也是使用模板机制进行渲染，下表列出常见布局使用的模板：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 布局</td>
<td style="text-align: center;">renderTpl </td>
</tr>
</thead>
<tbody>
<tr>
<td>Container</td>
<td>由Ext.layout.container.Container提供：<br />
<pre class="crayon-plain-tag">{%this.renderBody(out,values)%}</pre>
</td>
</tr>
<tr>
<td>Auto</td>
<td>由Ext.layout.container.Auto提供：<br />
<pre class="crayon-plain-tag">{%this.renderBody(out,values)%}
&lt;div id="{ownerId}-clearEl" class="x-clear" role="presentation"&gt;
&lt;/div&gt;</pre>
</td>
</tr>
</tbody>
</table>
<p>可以看到，renderContainer()方法是容器渲染（生成DOM结构）的核心，那么这个函数从何而来？</p>
<p><strong><span style="background-color: #c0c0c0;">渲染树tpl模板成员函数的来源</span></strong></p>
<p>tree.tpl是在渲染调用链：render() - getRenderTree() - getElConfig()中生成的：</p>
<ol>
<li>如果容器使用模拟的圆角外框（注意，如果支持CSS3则绝不会通过图片去模拟圆角外框效果），调用initFramingTpl()
<ol>
<li>获取Renderable.frameTpl或者frameTplTable</li>
<li>调用Renderable.setupFramingTpl()对frameTpl进行预处理：
<ol>
<li>把模板成员函数applyRenderTpl关联到this.doApplyRenderTpl</li>
<li>把模板成员函数renderDockedItems关联到this.doRenderFramingDockedItems</li>
</ol>
</li>
<li>frameTpl中包含对applyRenderTpl()的调用代码，这段代码的前后则是圆角外框效果的HTML</li>
<li>applyRenderTpl()就是doApplyRenderTpl()，后者则转调initRenderTpl()</li>
<li>至此，可以看到initRenderTpl()是核心所在</li>
</ol>
</li>
<li>如果容器不使用模拟的圆角外框，直接调用initRenderTpl()
<ol>
<li>调用getTpl()把上表中AbstractContainer.renderTpl获取作为模板</li>
<li>调用setupRenderTpl()
<ol>
<li>获取容器布局对象</li>
<li>调用Renderable覆盖版本：设置tpl.renderBody=tpl.renderContent=this.doRenderContent</li>
<li>调用layout.setupRenderTpl
<ol>
<li>tpl.renderBody = layout.doRenderBody</li>
<li>tpl.renderContainer = layout.doRenderContainer</li>
<li>tpl.renderItems = layout.doRenderItems</li>
<li>tpl.renderPadder = layout.doRenderPadder</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
<p>自此，渲染树的所有成员变量，成员函数的来源均已分析清楚。</p>
<p><span style="background-color: #c0c0c0;"><strong>渲染树插入DOM的具体过程</strong></span></p>
<p>明确渲染树之后，render()方法会调用Ext.DomHelper()把渲染树插入到DOM结构中，细节如下：<strong>➁</strong></p>
<ol>
<li>获取c0的渲染树后，Renderable.render()调用Ext.DomHelper.append()进行DOM插入</li>
<li>调用 Ext.DomHelper.insert() </li>
<li>调用Ext.DomHelper.markup()，调用generateMarkup()生成渲染树的HTML字符串，具体如下：
<ol>
<li>输出c0的封装元素：&lt;tree.tag，即<pre class="crayon-plain-tag">&lt;div</pre> </li>
<li>遍历tree的属性，判断哪些需要作为封装元素的属性
<ol>
<li>cls作为属性，输出：<pre class="crayon-plain-tag">class="x-container x-container-default"</pre> </li>
<li>style作为属性，输出：<pre class="crayon-plain-tag">style="background-color:#f88;width:250px;height:250px;"</pre></li>
<li>id作为属性，输出：<pre class="crayon-plain-tag">id="c0"</pre> </li>
</ol>
</li>
<li>输出 <pre class="crayon-plain-tag">&gt;</pre> ，关闭封装元素的开始标签</li>
<li>调用tree.tpl.applyOut，填充模板并输出：
<ol>
<li>调用renderContainer()，这一步是容器渲染核心的起点</li>
<li>上一步即调用Auto布局父类layout.container.Container.doRenderContainer()
<ol>
<li>由于this指针的问题，从模板数据中取得$comp.layout，即当前布局对象</li>
<li>获取布局的渲染模板lt，调用layout.getRenderTpl()，结果如上表Auto</li>
<li>调用layout.owner.setupRenderTpl()对lt进行预处理，owner即c0
<ol>
<li>设置lt.renderBody=lt.renderContent=c0.doRenderContent</li>
<li>调用layout.setupRenderTpl()，设置：
<ol>
<li>lt.renderBody = layout.doRenderBody</li>
<li>lt.renderContainer = layout.doRenderContainer</li>
<li>lt.renderItems = layout.doRenderItems</li>
<li>lt.renderPadder = layout.doRenderPadder</li>
</ol>
</li>
</ol>
</li>
<li>以上3步完成布局渲染模板的初始化后，调用layout.getRenderData()初始化渲染上下文：
<ol>
<li>$comp = c0</li>
<li>$layout = c0.layout</li>
<li>ownerId = c.id</li>
</ol>
</li>
<li>执行布局渲染模板的applyOut()
<ol>
<li>调用lt.renderBody()，即layout.doRenderBody()
<ol>
<li>调用lt.renderItems()，即layout.doRenderItems()渲染子组件   <strong>➀</strong></li>
<li>调用lt.renderContent()，即c0.doRenderContent()渲染容器内容</li>
</ol>
</li>
<li>输出：<pre class="crayon-plain-tag">&lt;div id="c0-clearEl" class="x-clear" role="presentation"&gt;&lt;/div&gt;</pre></li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li>输出c0的封装元素的关闭标签：<pre class="crayon-plain-tag">&lt;/div&gt;</pre> </li>
</ol>
</li>
<li>Renderable.render()调用wrapPrimaryEl()，至此，c0.getEl()不再返回undefined</li>
<li>调用Renderable.finishRender()，完成渲染过程，细节内容请参考：<a href="/extjs-4-components#render-process-of-panel">ExtJS 4的组件机制</a></li>
</ol>
<p><strong>布局渲染子组件的过程</strong></p>
<p>如上一节<strong>➀</strong>，布局对象调用doRenderItems来完整子组件的渲染，这是合理的，因为容器内子组件的排列、大小、位置就是由布局对象负责的。</p>
<p>子组件渲染具体过程如下：</p>
<ol>
<li>doRenderItems()调用layout.getRenderTree()获取布局的渲染树
<ol>
<li>调用Ext.layout.Layout.getItemsRenderTree()，遍历每个子组件（b0、b1）：
<ol>
<li>对组件调用layout.configureItem(item)，进行布局的预处理</li>
<li>对每个组件调用item.getRenderTree()，即获取子组件本身的渲染树</li>
<li>返回子组件渲染树的数组</li>
</ol>
</li>
</ol>
</li>
<li>doRenderItems()调用DomHelper.generateMarkup()生成子组件的DOM结构，如果子组件本身是容器，这里会有个递归处理过程，类似 <strong>➁</strong></li>
<li>输出：<br />
<pre class="crayon-plain-tag">&lt;div class="x-component x-component-default" style="background-color:#8f8;width:100px;height:100px;" id="c0-b0"&gt;&lt;/div&gt;
&lt;div class="x-component x-component-default" style="background-color:#88f;width:100px;height:100px;" id="c0-b1"&gt;&lt;/div&gt;</pre>
</li>
</ol>
<p><strong>容器内容的渲染过程</strong></p>
<p>Container类没有对Renderable.doRenderContent()进行覆盖，因此调用的就是原始版本：</p>
<ol>
<li>把c0的html配置附加到输出<pre class="crayon-plain-tag">This is a container</pre> </li>
<li>如果c0配置了tpl，把该模板填充并输出</li>
</ol>
<div class="blog_h2"><span class="graybg">ExtJS容器基础类</span></div>
<div class="blog_h3"><span class="graybg">Ext.container.AbstractContainer</span></div>
<p>该私有类为Sencha的RIA框架产品线提供了公共的逻辑，包括以下成员：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">配置项/属性/方法 </td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>{activeItem}</td>
<td>String/Number，需要在容器渲染后处于“激活”状态的子组件ID或者索引。该配置只对那种每次显示一个子组件的布局有意义（例如Card）</td>
</tr>
<tr>
<td>{autoDestroy}</td>
<td>Boolean = true，如果为true，容器会销毁所有从中移除的子组件</td>
</tr>
<tr>
<td>{bubbleEvents}</td>
<td>String[] = ["add", "remove"]，支持冒泡的事件</td>
</tr>
<tr>
<td>{defaultType}</td>
<td>String = 'panel'，子组件默认的xtype</td>
</tr>
<tr>
<td>{defaults}</td>
<td>
<p>Object/Function，传递给子组件的默认配置，通过items配置、add/insert方法加入的子组件，自动获得这些配置</p>
<p>如果传递参数，将把scope设置为当前容器，被添加组件作为第1参数调用，其结果作为config使用</p>
</td>
</tr>
<tr>
<td>{detachOnRemove}</td>
<td>Boolean = true，如果设置为true，把所有移除的组件放到detachedBody，仅在移除尚未销毁的子组件（注意autoDestroy配置）时可用</td>
</tr>
<tr>
<td>{items}</td>
<td>
<p>Object/Object[]，一个或者多个需要被添加到容器的子组件，每个Object必须是Component实例或者Component配置对象（需要指定xtype）</p>
<p>除非指定layout，否则子组件只是逐个的被渲染到它们的封装元素中，不做任何位置大小计算。</p>
<p>注意：ExtJS使用延迟渲染机制，子组件只在必要的时候才会渲染，子组件会在第一次显示时完成布局</p>
</td>
</tr>
<tr>
<td>{layout }</td>
<td>
<p>String/Object，指定容器的布局方式。如果不指定，自动使用Ext.layout.container.Auto</p>
<p>可用的布局方式包括：card/fit/hbox/vbox/anchor/table等</p>
</td>
</tr>
<tr>
<td>{suspendLayout }</td>
<td>Boolean = false，是否暂停布局</td>
</tr>
<tr>
<td>items</td>
<td>Ext.util.AbstractMixedCollection，包含所有子组件的混合集合对象</td>
</tr>
<tr>
<td>add()</td>
<td>
<p>Ext.Component[]/Ext.Component ( Ext.Component[]/Ext.Component... component ) </p>
<p>添加一个或者多个子组件到容器。在添加前会触发beforeadd事件，在添加后悔触发add事件。</p>
<p>注意：</p>
<ol>
<li>如果add()时，容器已经渲染完成，容器会把子组件渲染到它的内容区域，并且可能重新计算布局</li>
<li>如果有若干组件需要添加，应当传入数组一起添加，防止重复进行布局降低性能</li>
<li>由BorderLayout布局管理的直接子组件，不支持add或者remove</li>
</ol>
</td>
</tr>
<tr>
<td>insert()</td>
<td>
<p>Ext.Component( Number index, Ext.Component component )，在指定位置插入子组件</p>
</td>
</tr>
<tr>
<td>move()</td>
<td>
<p>Ext.Component( Number fromIdx, Number toIdx ) ，在容器内部移动组件</p>
</td>
</tr>
<tr>
<td>cascade()</td>
<td>Ext.Container ( Function fn, [Object scope], [Array args] )，向下递归的遍历所有子组件，执行指定的函数，任何一次执行返回false则停止遍历</td>
</tr>
<tr>
<td>doLayout()</td>
<td>Ext.container.Container ( )，强制重新计算布局，ExtJS内部通常使用该方法刷新布局</td>
</tr>
<tr>
<td>remove()</td>
<td>Ext.Component ( Ext.Component/String component, [Boolean autoDestroy] )。移除子组件，在移除前触发 beforeremove，移除后触发remove</td>
</tr>
<tr>
<td>removeAll()</td>
<td>Ext.Component[] ( [Boolean autoDestroy] ) 。移除所有子组件</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>组件查询方法  </strong></td>
</tr>
<tr>
<td>child()</td>
<td>Ext.Component ( [String selector] )，返回匹配选择器的第一个子组件</td>
</tr>
<tr>
<td>down()</td>
<td>Ext.Component  ( [String selector] )，返回匹配选择器的第一个后代</td>
</tr>
<tr>
<td>getComponent()</td>
<td>Ext.Component ( String/Number comp ) ，根据id、itemId或者索引来获取直接子组件</td>
</tr>
<tr>
<td>isAncestor()</td>
<td>Boolean ( Ext.Component possibleDescendant )，判断当前容器是不是目标组件的祖先</td>
</tr>
<tr>
<td>query()</td>
<td>Ext.Component[] ( [String selector] ) ，根据选择器查询匹配的子代</td>
</tr>
<tr>
<td>queryBy()</td>
<td>Ext.Component[] ( Function fn, [Object scope] )，根据过滤器函数查询子代</td>
</tr>
<tr>
<td>queryById()</td>
<td>Ext.Component ( String id ) ，根据id、itemId查询子代，返回第一个匹配的条目</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>模板方法</strong>  </td>
</tr>
<tr>
<td>afterLayout() </td>
<td>void ( Ext.layout.container.Container layout ) 当容器已经完成其子组件的布局（包括必要的子组件渲染）时调用</td>
</tr>
<tr>
<td>beforeLayout( )</td>
<td>Boolean ()，在容器布局前调用，如果返回false阻止布局发生</td>
</tr>
<tr>
<td>onAdd()</td>
<td>void ( Ext.Component component, Number position )，在组件被添加后调用</td>
</tr>
<tr>
<td>onBeforeAdd()</td>
<td>Boolean ( Ext.Component item )，在添加组件前调用，返回false阻止添加</td>
</tr>
<tr>
<td>onRemove()</td>
<td>void ( Ext.Component component, Boolean autoDestroy )，在组件被移除后调用</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>事件</strong></td>
</tr>
<tr>
<td>⚡add</td>
<td>void ( Ext.container.Container this, Ext.Component component, Number index, Object eOpts )，子组件被添加或者插入进来时触发</td>
</tr>
<tr>
<td>⚡afterlayout</td>
<td>void ( Ext.container.Container this, Ext.layout.container.Container layout, Object eOpts )，当容器布局执行完毕后触发</td>
</tr>
<tr>
<td>⚡beforeadd</td>
<td>Boolean ( Ext.container.Container this, Ext.Component component, Number index, Object eOpts )，在添加或者插入子组件前触发，任一监听器返回false导致停止添加</td>
</tr>
<tr>
<td>⚡beforeremove</td>
<td>Boolean ( Ext.container.Container this, Ext.Component component, Object eOpts )，在移除子组件前触发，任一监听器返回false导致停止移除</td>
</tr>
<tr>
<td>⚡remove</td>
<td>void ( Ext.container.Container this, Ext.Component component, Object eOpts ) ，在子组件移除后触发</td>
</tr>
</tbody>
</table>
<p>AbstractContainer类的源代码分析如下：</p>
<pre class="crayon-plain-tag">Ext.define('Ext.container.AbstractContainer', {
    renderTpl: '{%this.renderContainer(out,values)%}',
    suspendLayout : false,
    autoDestroy : true,
    defaultType: 'panel',
    detachOnRemove: true,
    isContainer : true,
    layoutCounter : 0,
    baseCls: Ext.baseCSSPrefix + 'container',
    bubbleEvents: ['add', 'remove'],
    initComponent : function(){
        var me = this;
        //初始化事件
        me.addEvents(
            'afterlayout',
            'beforeadd',
            'beforeremove',
            'add',
            'remove'
        );
        me.callParent();//调用覆盖版本
        me.getLayout(); //初始化布局
        me.initItems(); //初始化子组件，合并为数组并调用add()
    },
    initItems : function() {
        var me = this, items = me.items;
        me.items = new Ext.util.AbstractMixedCollection(false, me.getComponentId);
        if (items) {
            if (!Ext.isArray(items)) {
                items = [items];
            }
            me.add(items);
        }
    },
    //渲染当前容器、及其子组件
    //该方法直接使用了Renderable的版本
    //子组件如何渲染，容器并不知道，所以委托其布局对象去渲染
    //详细参考《容器的渲染过程》一节
    render : function(){this.callParents(arguments);},
    //聚焦元素
    getFocusEl: function() {
        return this.getTargetEl();
    },
    //该方法会被finishRender调用，完成子组件/子元素的渲染
    finishRenderChildren: function () {
        this.callParent(); //覆盖版本完成了子元素的渲染，属于组件布局系统
        var layout = this.getLayout();
        if (layout) {
            layout.finishRender(); //容器布局系统，完成子组件的渲染
        }
    },
    //生命周期模板方法
    beforeRender: function () {
        var me = this, layout = me.getLayout();
        me.callParent();
        if (!layout.initialized) {
            //如果布局尚未初始化，初始化之
            layout.initLayout();
        }
    },
    //初始化渲染模板，对当前类的renderTpl预处理
    setupRenderTpl: function (renderTpl) {
        var layout = this.getLayout();
        this.callParent(arguments);
        layout.setupRenderTpl(renderTpl);
    },

    //设置容器的布局
    setLayout : function(layout) {
        var currentLayout = this.layout;
        if (currentLayout &amp;&amp; currentLayout.isLayout &amp;&amp; currentLayout != layout) {
            currentLayout.setOwner(null); //解除与当前正在使用的布局的关联
        }
        //设置新布局的双向关联
        this.layout = layout;
        layout.setOwner(this);
    },
    //初始化当前容器关联的布局对象
    getLayout : function() {
        var me = this;
        if (!me.layout || !me.layout.isLayout) {
            me.setLayout(Ext.layout.Layout.create(me.layout, me.self.prototype.layout || 'autocontainer'));
        }
        return me.layout;
    },
    //就是调用updateLayout()，不一定会flush
    doLayout : function() {
        this.updateLayout();
        return this;
    },
    
    afterLayout : function(layout) {
        var me = this;
        ++me.layoutCounter;
        if (me.hasListeners.afterlayout) {
            me.fireEvent('afterlayout', me, layout);
        }
    },

    //准备子组件
    prepareItems : function(items, applyDefaults) {
        if (Ext.isArray(items)) {
            items = items.slice(); //浅拷贝数组
        } else {
            items = [items];
        }
        var me = this,
            i = 0,
            len = items.length,
            item;
        //遍历处理子组件
        for (; i &lt; len; i++) {
            item = items[i];
            if (item == null) {
                Ext.Array.erase(items, i, 1);
                --i;
                --len;
            } else {
                if (applyDefaults) {
                    item = this.applyDefaults(item);//应用默认配置
                }
                //告诉子组件，它在容器的初始化阶段就被包含在items中
                item.isContained = me;
                items[i] = me.lookupComponent(item); //查找或者创建子组件
                delete item.isContained;
            }
        }

        return items;
    },

    //如果在组件管理器中存在，则查找，否则创建
    lookupComponent : function(comp) {
        return (typeof comp == 'string') ? Ext.ComponentManager.get(comp)
                                         : Ext.ComponentManager.create(comp, this.defaultType);
    },

    add : function() {
        var me = this,
            args = Ext.Array.slice(arguments),
            index = (typeof args[0] == 'number') ? args.shift() : -1,
            layout = me.getLayout(),
            addingArray, items, i, length, item, pos, ret;

        if (args.length == 1 &amp;&amp; Ext.isArray(args[0])) {
            items = args[0];
            addingArray = true;
        } else {
            items = args;
        }
        ret = items = me.prepareItems(items, true); //准备子组件，并应用缺省配置
        length = items.length;
        if (me.rendered) {
            Ext.suspendLayouts(); //如果容器已经渲染，要在添加期间暂停布局
        }
        if (!addingArray &amp;&amp; length == 1) {
            ret = items[0];
        }
        for (i = 0; i &lt; length; i++) {
            pos = (index &lt; 0) ? me.items.length : (index + i);
            if (item.floating) {
                //浮动组件的处理
                me.floatingItems = me.floatingItems || new Ext.util.MixedCollection();
                me.floatingItems.add(item);
                item.onAdded(me, pos);
            } else if ((!me.hasListeners.beforeadd || me.fireEvent('beforeadd', me, item, pos) !== false) &amp;&amp; me.onBeforeAdd(item) !== false) {
                //普通组件的处理
                me.items.insert(pos, item);
                item.onAdded(me, pos);
                me.onAdd(item, pos);
                layout.onAdd(item, pos);
                if (me.hasListeners.add) {
                    me.fireEvent('add', me, item, pos); //发布事件
                }
            }
        }
        //更新（计算）布局
        me.updateLayout();
        if (me.rendered) {
            //恢复布局系统
            Ext.resumeLayouts(true);
        }
        return ret;
    },
    //子组件相关模板方法   
    onAdd : Ext.emptyFn,
    onRemove : Ext.emptyFn,
    onBeforeAdd : function(item) {
        var me = this,
            border = item.border;
        if (item.ownerCt &amp;&amp; item.ownerCt !== me) {
            //如果组件原来就在某个容器中，自动将其移除
            //因为有这个逻辑，实际使用时不必先移除，再添加
            item.ownerCt.remove(item, false);
        }
        if (me.border === false || me.border === 0) {
            item.border = Ext.isDefined(border) &amp;&amp; border !== false &amp;&amp; border !== 0;
        }
    },
    //移动子组件的位置并重新布局
    move : function(fromIdx, toIdx) {
        var items = this.items,
            item;
        item = items.removeAt(fromIdx);
        if (item === false) {
            return false;
        }
        items.insert(toIdx, item);
        this.doLayout();
        return item;
    },
    remove : function(comp, autoDestroy) {
        var me = this,
            c = me.getComponent(comp);
        //before模板方法调用、事件发布
        if (c &amp;&amp; (!me.hasListeners.beforeremove || me.fireEvent('beforeremove', me, c) !== false)) {
            me.doRemove(c, autoDestroy);//执行实际移除逻辑
            if (me.hasListeners.remove) {
                me.fireEvent('remove', me, c); //移除事件
            }
            //如果容器本身正在销毁过程中，则不需要更新布局
            if (!me.destroying) {
                me.doLayout();
            }
        }
        return c;
    },
    doRemove : function(component, autoDestroy) {
        var me = this,
            layout = me.layout,
            hasLayout = layout &amp;&amp; me.rendered,
            destroying = autoDestroy === true || (autoDestroy !== false &amp;&amp; me.autoDestroy);

        autoDestroy = autoDestroy === true || (autoDestroy !== false &amp;&amp; me.autoDestroy);
        me.items.remove(component); //从items中剔除
        if (hasLayout) {
            if (layout.running) {
                //取消正在进行的布局
                Ext.AbstractComponent.cancelLayout(component, destroying);
            }
            layout.onRemove(component, destroying); //模板方法
        }

        component.onRemoved(destroying);//模板方法
        me.onRemove(component, destroying);//模板方法
        if (destroying) {
            component.destroy();//可选的，销毁子组件
        }
        else {
            if (hasLayout) {
                layout.afterRemove(component);       
            }
            if (me.detachOnRemove &amp;&amp; component.rendered) {
                //把子组件转移到detachedBody
                Ext.getDetachedBody().appendChild(component.getEl());
            }
        }
    },
    //移除所有子组件
    removeAll : function(autoDestroy) {
        var me = this,
            removeItems = me.items.items.slice(),
            items = [],
            i = 0,
            len = removeItems.length,
            item;
        me.suspendLayouts();
        for (; i &lt; len; i++) {
            item = removeItems[i];
            me.remove(item, autoDestroy);
            if (item.ownerCt !== me) {
                items.push(item);
            }
        }
        me.resumeLayouts(!!len);
        return items;
    },
    //启用容器
    enable: function() {
        this.callParent(arguments);

        var itemsToDisable = this.getChildItemsToDisable(),
            length         = itemsToDisable.length,
            item, i;

        for (i = 0; i &lt; length; i++) {
            item = itemsToDisable[i];

            if (item.resetDisable) {
                item.enable();
            }
        }

        return this;
    },
    //禁用容器
    disable: function() {
        this.callParent(arguments);

        var itemsToDisable = this.getChildItemsToDisable(),
            length         = itemsToDisable.length,
            item, i;

        for (i = 0; i &lt; length; i++) {
            item = itemsToDisable[i];

            if (item.resetDisable !== false &amp;&amp; !item.disabled) {
                item.disable();
                item.resetDisable = true;
            }
        }
        return this;
    },
    //布局前模板方法，返回false禁止布局
    beforeLayout: function() {
        return true;
    },
    //容器销毁前模板方法
    beforeDestroy : function() {
        var me = this,
            items = me.items,
            c;
        //销毁前首先移除、销毁子组件
        if (items) {
            while ((c = items.first())) {
                me.doRemove(c, true);
            }
        }
        //销毁容器关联的布局对象
        Ext.destroy(
            me.layout
        );
        me.callParent();
    }
});</pre>
<div class="blog_h3"><span class="graybg">Ext.container.Container</span></div>
<p>该类的包含的逻辑很少，提供以下成员：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">配置项/属性/方法 </td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>{anchorSize}</td>
<td>Number/Object，要么是数字，要么是{width:,height:}形式的对象，在容器使用AnchorLayout 时该配置有意义。默认情况下，AnchorLayout会基于容器本身大小进行子组件的anchor计算，如果指定anchorSize，则以anchorSize为基准计算</td>
</tr>
<tr>
<td>getChildByElement()</td>
<td> Ext.Component ( Ext.Element/HTMLElement/String el, Boolean deep )，返回包含指定元素的子组件</td>
</tr>
</tbody>
</table>
<p>Container类的源代码分析如下：</p>
<pre class="crayon-plain-tag">Ext.define('Ext.container.Container', {
    extend: 'Ext.container.AbstractContainer',
    alias: 'widget.container',
    alternateClassName: 'Ext.Container',
    fireHierarchyEvent: function (ename) {
        //使hierarchyEventSource对象发布事件，并且把当前对象作为事件参数
        this.hierarchyEventSource.fireEvent(ename, this);
    },
    //生命周期模板方法：以下三个方法都是通过hierarchyEventSource对象发布相应的事件
    afterHide: function() {
        this.callParent(arguments);
        this.fireHierarchyEvent('hide');
    },
    afterShow: function(){
        this.callParent(arguments);
        this.fireHierarchyEvent('show');
    },
    onAdded: function() {
        this.callParent(arguments);
        if (this.hierarchyEventSource.hasListeners.added) {
            this.fireHierarchyEvent('added');
        }
    },
    //寻找“直接”包含某个元素的子组件
    getChildByElement: function(el, deep) {
        var item,
            itemEl,
            i = 0,
            it = this.getRefItems(),
            ln = it.length;

        el = Ext.getDom(el);
        for (; i &lt; ln; i++) {
            item = it[i];
            itemEl = item.getEl();
            if (itemEl &amp;&amp; ((itemEl.dom === el) || itemEl.contains(el))) {
                return (deep &amp;&amp; item.getChildByElement) ? item.getChildByElement(el, deep) : item;
            }
        }
        return null;
    }
}, function () {
    this.hierarchyEventSource = this.prototype.hierarchyEventSource = new Ext.util.Observable({ events: {
        //支持的事件
        hide: true,
        show: true,
        collapse: true,
        expand: true,
        added: true
    }});
});</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/extjs-4-containers">ExtJS 4的容器机制</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/extjs-4-containers/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ExtJS 4的组件机制</title>
		<link>https://blog.gmem.cc/extjs-4-components</link>
		<comments>https://blog.gmem.cc/extjs-4-components#comments</comments>
		<pubDate>Fri, 05 Apr 2013 05:48:31 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ExtJS]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=5428</guid>
		<description><![CDATA[<p>ExtJS组件基础类 组件、容器是ExtJS组件机制的基础，后者是前者的子类型。 Ext.util.Renderable 这是一个ExtJS框架的私有混入，定义了组件渲染的核心逻辑，对于下面这个包含三层嵌套的Panel： [crayon-69d9b17a042b1090014383/] Renderable定义其渲染过程如下： ContainerA被初始化时转调父类构造器，在AbstractComponent.constructor()中，由于配置了renderTo，调用render()方法，进入渲染阶段 render()开始渲染ContainerA，首先禁用布局系统，然后调用getRenderTree()，获取“渲染树”，这个所谓的渲染树就是一个DomHelper规格对象 getRenderTree()触发事件beforerender、调用beforeRender()模板方法，然后调用getElConfig()获取渲染树对象 根据是否配置外框（Window、Tip必须有外框），getElConfig()会生成不同的结果：要么调用initFramingTpl()，要么调用initRenderTpl()生成一个模板，并置入渲染树：{tag : 'div', tpl: template}的tpl属性 从细节上看，initRenderTpl()获取当前类（Container）的renderTpl属性作为tpl，该模板具有以下成员：  属性  说明 html 容器的HTML结构，整体形式如下： [crayon-69d9b17a042b7020887919/] {%this.renderContainer(out,values);%} [crayon-69d9b17a042b9520146204/] <a class="read-more" href="https://blog.gmem.cc/extjs-4-components">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/extjs-4-components">ExtJS 4的组件机制</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">ExtJS组件基础类</span></div>
<p>组件、容器是ExtJS组件机制的基础，后者是前者的子类型。</p>
<div class="blog_h3"><span class="graybg">Ext.util.Renderable</span></div>
<p>这是一个ExtJS框架的私有混入，定义了组件渲染的核心逻辑，对于下面这个包含三层嵌套的Panel：</p>
<pre class="crayon-plain-tag">Ext.onReady( function()
{
    Ext.createByAlias( 'widget.panel', {
        id : 'ContainerA',
        layout : 'hbox',
        width : 500,
        height : 500,
        renderTo : Ext.getBody(),
        items : [
            {
                id : 'ContainerB',
                xtype : 'container',
                width : 250,
                height : 250,
                layout : 'fit',
                items : [
                    {
                        id : 'ComponentA',
                        html : 'Hello World !'
                    }
                ]
            }
        ]
    } );
} )</pre>
<p id="render-process-of-panel">Renderable定义其渲染过程如下：</p>
<ol>
<li>ContainerA被初始化时转调父类构造器，在AbstractComponent.constructor()中，由于配置了renderTo，调用render()方法，进入渲染阶段</li>
<li>render()开始渲染ContainerA，首先禁用布局系统，然后调用getRenderTree()，获取“渲染树”，这个所谓的渲染树就是一个DomHelper规格对象</li>
<li>getRenderTree()触发事件beforerender、调用beforeRender()模板方法，然后调用getElConfig()获取渲染树对象</li>
<li>根据是否配置外框（Window、Tip必须有外框），getElConfig()会生成不同的结果：要么调用initFramingTpl()，要么调用initRenderTpl()生成一个模板，并置入渲染树：{tag : 'div', tpl: template}的tpl属性
<ol>
<li>从细节上看，initRenderTpl()获取当前类（Container）的renderTpl属性作为tpl，该模板具有以下成员：<br />
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 150px; text-align: center;"><strong> 属性</strong></td>
<td style="text-align: center;"><strong> 说明</strong></td>
</tr>
<tr>
<td>html</td>
<td>容器的HTML结构，整体形式如下：<br />
<pre class="crayon-plain-tag">{% this.renderDockedItems(out,values,0); %}</pre></p>
<div>{%this.renderContainer(out,values);%}</div>
<pre class="crayon-plain-tag">{% this.renderDockedItems(out,values,1); %}</pre>
</td>
</tr>
<tr>
<td>initialConfig</td>
<td>传入的初始配置对象</td>
</tr>
<tr>
<td>renderBody</td>
<td rowspan="6">
<p>可供在模板中调用的一系列方法<br />这些xxx方法都是在：
<ol>
<li>Renderable.setupFramingTpl()</li>
<li>AbstractContainer.setupRenderTpl()</li>
</ol>
<p>等方法中，与容器、布局类的doXxx进行关联得到的。之所以要和布局类的doXxx关联，是因为布局才知道子组件如何渲染</p>
</td>
</tr>
<tr>
<td>renderContainer</td>
</tr>
<tr>
<td>renderContent</td>
</tr>
<tr>
<td>renderDockedItems</td>
</tr>
<tr>
<td>renderItems</td>
</tr>
<tr>
<td>renderPadder</td>
</tr>
</tbody>
</table>
</li>
<li>如果配置了autoEl，替换渲染树的tag属性</li>
<li>调用initRenderData生成tpl的填充上下文（tplData），并提供一些默认值</li>
<li>最终返回的渲染树形式如下（符合DomHelper规格约定）：<br />
<pre class="crayon-plain-tag">{
    tag: "div",
    cls: "", 
    style: {
        height: "500px"
        width: "500px"
    }, 
    id: "ContainerA"
    tpl: {
        html: "..."
        initialConfig: {}
        renderBody: function (out, renderData) {...}
        renderContainer: function (out, renderData) {...}
        renderContent: function (out, renderData) {...}
        renderDockedItems: function (out, renderData, after) {...}
        renderItems: function (out, renderData) {...}
        renderPadder: function (out, renderData) {...}
    }, 
    tplData : {
        $comp: '当前组件'
        baseCls: "x-panel"
        bodyCls: "x-panel-body-default"
        bodyStyle: ""
        componentCls: "x-panel"
        frame: undefined
        id: "ContainerA"
        ui: "default"
        uiCls: []
    }
}</pre>
</li>
</ol>
</li>
<li>render()得到渲染树后，使用DomHelper将其插入到文档中，一旦插入，<span style="background-color: #c0c0c0;">整个组件（包括所有后代组件）的DOM结构就都生成了</span>，结果如下：<img class="aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2013/04/extjs-render.png" alt="extjs-render" width="95%" /></li>
<li>插入DOM完毕后，render()设置ContainerA的el为新生成的HTML元素ContainerA</li>
<li>render()调用finishRender()完成渲染阶段，在此阶段，会自上而下的递归调用所有子代组件的onRender()，然后自下而上的递归完成所有子代组件的afterRender()调用，需要注意的是，子代的onRender()完全处于父代的afterRender()调用的内部
<ol>
<li>调用onRender()
<ol>
<li>设置renderSelectors为成员变量</li>
<li>清除正在渲染状态：rendering，设置rendered=true</li>
<li>记住lastBox={width:500,height:500}</li>
</ol>
</li>
<li>设置targetEl的overflow样式；设置封装元素的visibilityMode</li>
<li>如果有存在监听器，则发布render事件</li>
<li>调用afterRender()，该方法最终会触发布局，主要逻辑位于Rendererable中
<ol>
<li>调用finishRenderChildren()完成子代的渲染
<ol>
<li><span style="background-color: #c0c0c0;">组件布局渲染</span>：调用Renderable.finishRenderChildren()渲染内部结构
<ol>
<li>获取组件布局getComponentLayout()，这里是Ext.layout.component.Dock</li>
<li>调用Dock.finishRender(),该方法会获取dockedItems，并调用Ext.layout.Layout.finishRenderItems()逐个渲染</li>
</ol>
</li>
<li><span style="background-color: #c0c0c0;">容器布局渲染</span>：调用AbstractContainer.finishRenderChildren()渲染子组件
<ol>
<li>获取容器布局getLayout()，这里是Ext.layout.container.HBox</li>
<li>调用HBox.finishRender()，后者转调父类（Ext.layout.container.Container）版本</li>
<li>Container.finishRender()获取渲染目标（targetEl）和渲染子组件<span style="color: #222222;">（仅一个ContainerB）</span></li>
<li>调用Container.finishRenderItems(target, items)完成子组件渲染</li>
<li>转调Ext.layout.Layout.finishRenderItems()，逻辑类似7.4.1.1
<ol>
<li>调用ContainerB的Renderable.finishRender，这一小节是类似于7的递归调用
<ol>
<li>子组件ContainerB的el属性被设置，不再为空</li>
<li>调用ContainerB的onRender模板方法，逻辑类似7.1，lastBox为250</li>
<li>调用ContainerB.afterRender()
<ol>
<li>finishRenderChildren()</li>
<li>完成组件布局渲染（Dock）</li>
<li>完成容器布局渲染（Fit），继续递归用
<ol>
<li>ComponentA.finishRender()
<ol>
<li>ComponentA.onRender()</li>
<li>ComponentA.afterRender()
<ol>
<li>……一直递归到最底层组件……</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li>由于不是顶层容器，不进行updateLayout()</li>
</ol>
</li>
<li>如果有存在监听器，则发布afterrender事件</li>
<li>初始化事件：initEvents()</li>
<li>如果配置为隐藏，那么调用getEl().hide()</li>
</ol>
</li>
</ol>
</li>
<li>对于每一个item，调用afterRenderItem(item)</li>
</ol>
</li>
</ol>
</li>
<li>为targetEl添加CSS类styleHtmlCls、增减样式</li>
<li>由于当前容器是顶层的，调用AbstractComponent.updateLayout()触发布局
<ol>
<li>如果当前组件是隐藏的，调用cancelLayout()取消布局</li>
<li>否则判断是否布局根，如果不是，什么都不做</li>
<li>由于ContainerA的布局未被暂停，调用静态函数Ext.AbstractComponent.updateLayout执行实际布局逻辑。这里可以看到，AbstractComponent类对象是全局的布局管理对象，updateLayout会把参数指定的组件放入某个队列，并且进行复杂的布局计算
<ol>
<li>如果当前正在运行布局，则把ContainerA放入running的无效队列</li>
<li>如果当前没有运行布局，则把ContainerA放入pending的无效队列，这里匹配这一条
<ol>
<li>如果当前布局系统没有被暂停，则立即刷新布局flushLayouts()</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li>如果有存在监听器，则发布afterrender事件</li>
<li>初始化事件，调用AbstractComponenet.initEvents()。initEvent()包含若干在组件渲染完毕后，需要初始化的事件监听器
<ol>
<li>如果具有afterRenderEvents，则把这些监听器注册为组件子元素的受管监听器，这些事件属于DOM事件</li>
<li>为当前组件添加焦点监听器（addFocusListener）</li>
</ol>
</li>
<li>如果配置为隐藏，那么调用getEl().hide()</li>
</ol>
</li>
<li>render()启用布局系统，转调AbstractComponent.resumeLayouts()静态函数
<ol>
<li>刷出布局，调用flushLayouts()</li>
<li>覆写pendingLayouts的runComplete方法防止死循环</li>
<li>调用pendingLayouts（类型Ext.layout.Context）的run()方法
<ol>
<li>调用Context.flushInvalidates()
<ol>
<li>转调Context.invalidate()，使相关的组件、容器布局失效</li>
</ol>
</li>
<li>调用Context.flush()</li>
<li>调用Context.runComplete()</li>
</ol>
</li>
</ol>
</li>
</ol>
<p>Renderable类包含以下成员：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">配置项/属性/方法 </td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>applyRenderSelectors( )</td>
<td>根据renderSelectors、childEls的配置，设置组件对其内部元素的引用</td>
</tr>
<tr>
<td>doApplyRenderTpl()</td>
<td>
<p>void ( Object out, Object values )，由XTemplate调用，在framing结构中插入当前组件的内部结构。</p>
<p>如果使用了framing，一个自动生成的模板代替renderTpl作为getElConfig()的主模板</p>
</td>
</tr>
<tr>
<td>doAutoRender()</td>
<td>执行自动渲染（autoRender）。浮动组件可能具有自己的ownerCt来管理ZIndex，虽然他们总是被渲染到document.body</td>
</tr>
<tr>
<td>ensureAttachedToBody()</td>
<td>void ( [Boolean runLayout] )。确保当前组件已经被附加到document.body。如果当前组件被渲染到Ext.getDetachedBody()，该方法可以使之添加到document.body，所有配置的位置被还原</td>
</tr>
<tr>
<td>finishRender()</td>
<td>void ( Number containerIdx )。访问者模式。自上而下的访问组件树的每一个节点，调用其onRender()方法</td>
</tr>
<tr>
<td>getFrameInfo()</td>
<td>根据组件性质、配置获取/计算圆角外框的样式信息</td>
</tr>
<tr>
<td>getFrameTpl()</td>
<td>void ( Object table )，得到圆角外框的模板</td>
</tr>
<tr>
<td>getInsertPosition()</td>
<td>HTMLElement ( String/Number/Ext.dom.Element/HTMLElement position )，返回一个可以insertBefore的Element</td>
</tr>
<tr>
<td>initFrame()</td>
<td>初始化圆角外框模板</td>
</tr>
<tr>
<td>initRenderData()</td>
<td>Object ()，初始化renderData以供renderTpl使用</td>
</tr>
<tr>
<td>initRenderTpl( )</td>
<td>Ext.XTemplate()，初始化renderTpl</td>
</tr>
<tr>
<td>render()</td>
<td>
<p>void ( [Ext.Element/HTMLElement/String container], [String/Number position] )，把组件渲染到给定的HTML元素</p>
<p>如果当前组件被容器管理，不需要调用该方法，因为容器的子组件是在容器第一次布局时，由容器的布局管理器负责渲染的。如果添加一个新子组件时，容器已经渲染，可能需要调用容器的doLayout()来强迫新的、未渲染的组件进行渲染。</p>
<p>当创建复杂的组件UI结构时，需要注意子组件的大小、位置是由父容器的布局管理器维护的。</p>
<p>如果父容器不提供layout配置，那么缺省的布局管理器被使用，这种情况下，只是依次渲染子组件，不会管理其大小和位置。</p>
</td>
</tr>
<tr>
<td>setupFramingTpl()</td>
<td>void ( Object frameTpl )，创建framing模板，该模板包裹renderTpl</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2">  <strong>模板方法  </strong></td>
</tr>
<tr>
<td>afterRender()</td>
<td>
<p>在渲染完成后执行额外逻辑，在此时组件的元素已经生成，并且应用了配置的样式，包括CSS、可见性等</p>
</td>
</tr>
<tr>
<td>onRender()</td>
<td>
<p>void( Ext.core.Element parentNode, Number containerIdx )，当组件的DOM结构被创建后，调用该方法。在此刻，组件（及其所有后代）的DOM结构已经存在，但是尚未被布局（尺寸/位置）。子类必须先调用父类的onRender版本，才能访问DOM结构。</p>
<p>parentNode：当前组件封装元素的元素；containerIdx：当前组件在父容器的索引</p>
</td>
</tr>
</tbody>
</table>
<p>Ext.util.Renderable的代码分析如下：</p>
<pre class="crayon-plain-tag">Ext.define('Ext.util.Renderable', {
    requires: [
        'Ext.dom.Element'
    ],
    //外框：在组件外围制造圆角矩形效果
    frameCls: Ext.baseCSSPrefix + 'frame',
    frameIdRegex: /[\-]frame\d+[TMB][LCR]$/,
    frameElementCls: {
        tl: [],
        tc: [],
        tr: [],
        ml: [],
        mc: [],
        mr: [],
        bl: [],
        bc: [],
        br: []
    },
    frameElNames: ['TL','TC','TR','ML','MC','MR','BL','BC','BR'],
    //基于DIV的外框模板
    frameTpl: [
        '{%this.renderDockedItems(out,values,0);%}',//渲染停靠的子组件
        '&lt;tpl if="top"&gt;',
            '&lt;tpl if="left"&gt;&lt;div id="{fgid}TL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-tl&lt;/tpl&gt;" style="background-position: {tl}; padding-left: {frameWidth}px" role="presentation"&gt;&lt;/tpl&gt;',
                '&lt;tpl if="right"&gt;&lt;div id="{fgid}TR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-tr&lt;/tpl&gt;" style="background-position: {tr}; padding-right: {frameWidth}px" role="presentation"&gt;&lt;/tpl&gt;',
                    '&lt;div id="{fgid}TC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-tc&lt;/tpl&gt;" style="background-position: {tc}; height: {frameWidth}px" role="presentation"&gt;&lt;/div&gt;',
                '&lt;tpl if="right"&gt;&lt;/div&gt;&lt;/tpl&gt;',
            '&lt;tpl if="left"&gt;&lt;/div&gt;&lt;/tpl&gt;',
        '&lt;/tpl&gt;',
        '&lt;tpl if="left"&gt;&lt;div id="{fgid}ML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-ml&lt;/tpl&gt;" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"&gt;&lt;/tpl&gt;',
            '&lt;tpl if="right"&gt;&lt;div id="{fgid}MR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-mr&lt;/tpl&gt;" style="background-position: {mr}; padding-right: {frameWidth}px" role="presentation"&gt;&lt;/tpl&gt;',
                '&lt;div id="{fgid}MC" class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-mc&lt;/tpl&gt;" role="presentation"&gt;',
                    '{%this.applyRenderTpl(out, values)%}', //渲染主体内容
                '&lt;/div&gt;',
            '&lt;tpl if="right"&gt;&lt;/div&gt;&lt;/tpl&gt;',
        '&lt;tpl if="left"&gt;&lt;/div&gt;&lt;/tpl&gt;',
        '&lt;tpl if="bottom"&gt;',
            '&lt;tpl if="left"&gt;&lt;div id="{fgid}BL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-bl&lt;/tpl&gt;" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"&gt;&lt;/tpl&gt;',
                '&lt;tpl if="right"&gt;&lt;div id="{fgid}BR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-br&lt;/tpl&gt;" style="background-position: {br}; padding-right: {frameWidth}px" role="presentation"&gt;&lt;/tpl&gt;',
                    '&lt;div id="{fgid}BC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-bc&lt;/tpl&gt;" style="background-position: {bc}; height: {frameWidth}px" role="presentation"&gt;&lt;/div&gt;',
                '&lt;tpl if="right"&gt;&lt;/div&gt;&lt;/tpl&gt;',
            '&lt;tpl if="left"&gt;&lt;/div&gt;&lt;/tpl&gt;',
        '&lt;/tpl&gt;',
        '{%this.renderDockedItems(out,values,1);%}'//渲染停靠的子组件
    ],
    //基于TABLE的外框模板
    frameTableTpl: [
        '{%this.renderDockedItems(out,values,0);%}',
        '&lt;table&gt;&lt;tbody&gt;',
            '&lt;tpl if="top"&gt;',
                '&lt;tr&gt;',
                    '&lt;tpl if="left"&gt;&lt;td id="{fgid}TL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-tl&lt;/tpl&gt;" style="background-position: {tl}; padding-left:{frameWidth}px" role="presentation"&gt;&lt;/td&gt;&lt;/tpl&gt;',
                    '&lt;td id="{fgid}TC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-tc&lt;/tpl&gt;" style="background-position: {tc}; height: {frameWidth}px" role="presentation"&gt;&lt;/td&gt;',
                    '&lt;tpl if="right"&gt;&lt;td id="{fgid}TR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-tr&lt;/tpl&gt;" style="background-position: {tr}; padding-left: {frameWidth}px" role="presentation"&gt;&lt;/td&gt;&lt;/tpl&gt;',
                '&lt;/tr&gt;',
            '&lt;/tpl&gt;',
            '&lt;tr&gt;',
                '&lt;tpl if="left"&gt;&lt;td id="{fgid}ML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-ml&lt;/tpl&gt;" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"&gt;&lt;/td&gt;&lt;/tpl&gt;',
                '&lt;td id="{fgid}MC" class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-mc&lt;/tpl&gt;" style="background-position: 0 0;" role="presentation"&gt;',
                    '{%this.applyRenderTpl(out, values)%}',
                '&lt;/td&gt;',
                '&lt;tpl if="right"&gt;&lt;td id="{fgid}MR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-mr&lt;/tpl&gt;" style="background-position: {mr}; padding-left: {frameWidth}px" role="presentation"&gt;&lt;/td&gt;&lt;/tpl&gt;',
            '&lt;/tr&gt;',
            '&lt;tpl if="bottom"&gt;',
                '&lt;tr&gt;',
                    '&lt;tpl if="left"&gt;&lt;td id="{fgid}BL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-bl&lt;/tpl&gt;" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"&gt;&lt;/td&gt;&lt;/tpl&gt;',
                    '&lt;td id="{fgid}BC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-bc&lt;/tpl&gt;" style="background-position: {bc}; height: {frameWidth}px" role="presentation"&gt;&lt;/td&gt;',
                    '&lt;tpl if="right"&gt;&lt;td id="{fgid}BR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br&lt;tpl for="uiCls"&gt; {parent.baseCls}-{parent.ui}-{.}-br&lt;/tpl&gt;" style="background-position: {br}; padding-left: {frameWidth}px" role="presentation"&gt;&lt;/td&gt;&lt;/tpl&gt;',
                '&lt;/tr&gt;',
            '&lt;/tpl&gt;',
        '&lt;/tbody&gt;&lt;/table&gt;',
        '{%this.renderDockedItems(out,values,1);%}'
    ],
    //执行渲染，生成组件的全部HTML并插入到DOM
    //container是渲染到的HTML父节点
    render: function(container, position) {
        var me = this,
            el = me.el &amp;&amp; (me.el = Ext.get(me.el)),
            vetoed,
            tree,
            nextSibling;
        //暂停所有布局
        Ext.suspendLayouts();
        //确保父节点container的引用已经获取
        container = me.initContainer(container);
        //得到下一个兄弟HTML节点，在其前面插入
        nextSibling = me.getInsertPosition(position);
        //对于顶层组件，渲染开始时el是不存在的
        if (!el) {
            //如果当前组件的封装元素还不存在
            //getRenderTree将会发布beforerender事件、调用beforeRender模板方法
            //然后调用getElConfig()获得当前组件（包括全部子组件）完整的DomHelper规格对象
            tree = me.getRenderTree();
            if (me.ownerLayout &amp;&amp; me.ownerLayout.transformItemRenderTree) {
                tree = me.ownerLayout.transformItemRenderTree(tree);
            }
            // 注意：如果beforeRender返回false，则tree不生成
            if (tree) {
                //插入当前组件到文档中
                if (nextSibling) {
                    el = Ext.DomHelper.insertBefore(nextSibling, tree);
                } else {
                    el = Ext.DomHelper.append(container, tree);
                }
                //设置this.el变量
                me.wrapPrimaryEl(el);
            }
        } else {
            //如果当前组件的封装元素已经存在
            if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) {
                me.initStyles(el);
                if (me.allowDomMove !== false) {
                    if (nextSibling) {
                        container.dom.insertBefore(el.dom, nextSibling);
                    } else {
                        container.dom.appendChild(el.dom);
                    }
                }
            } else {
                vetoed = true;//非顶层组件为true
            }
        }
        //如果当前组件是顶层组件，则自上而下的递归整棵组件树（子代组件的finishRender被递归调用）
        //访问每个组件的onRender()方法
        if (el &amp;&amp; !vetoed) {
            me.finishRender(position);
        }
        //恢复布局系统，如果组件的parentNode在document.body下，立即刷新所有未决的布局
        Ext.resumeLayouts(!container.isDetachedBody);
    },
    //获取组件的parentNode
    initContainer: function(container) {
        var me = this;
        //如果没有指定container，则自动获取el的parentNode
        if (!container &amp;&amp; me.el) {
            container = me.el.dom.parentNode;
            me.allowDomMove = false;
        }
        me.container = container.dom ? container : Ext.get(container);
        return me.container;
    },
    //所谓渲染树就是一个DomHelper规格对象
    getRenderTree: function() {
        var me = this;
        //发布beforerender事件
        if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) {
            //如果beforerender事件句柄没有返回false，则调用beforeRender()模板方法
            me.beforeRender();
            //该标记可以让布局管理器的finishRenderItems、afterFinishRenderItems指导哪些items需要处理
            me.rendering = true;
            //如果当前组件的封装元素以及存在，则返回一个代理元素
            if (me.el) {
                //由于这里正在生成一棵“渲染树”，因此使用一个代理元素（proxy el）在生成的目标DOM的精确位置进行占位
                //在finishRender阶段，代理元素会被真实元素替换
                return {
                    tag: 'div',
                    id: (me.$pid = Ext.id())
                };
            }
            //否则，生成一个代表当前组件（包含子全部子组件）的完整的DomHelper规格对象
            return me.getElConfig();
        }
        return null;
    },
    //生成当前组件完整的DomHelper规格
    getElConfig : function() {
        var me = this,
            autoEl = me.autoEl,
            frameInfo = me.getFrameInfo(),
            //规格对象
            config = {
                tag: 'div',//默认标签是DIV
                //如果具有组件外框效果，则使用frameTpl包裹renderTpl，否则直接使用initRenderTpl
                tpl: frameInfo ? me.initFramingTpl(frameInfo.table) : me.initRenderTpl()
            },
            i, frameElNames, len, suffix, frameGenId;
        //初始化样式
        me.initStyles(me.protoEl);
        me.protoEl.writeTo(config);
        me.protoEl.flush();
        //根据autoEl的配置更新config
        if (Ext.isString(autoEl)) {
            config.tag = autoEl;
        } else {
            Ext.apply(config, autoEl);
        }
        config.id = me.id;
        //何时不为true？
        if (config.tpl) {
            if (frameInfo) {
                frameElNames = me.frameElNames;
                len = frameElNames.length;
                frameGenId = me.id + '-frame1';
                me.frameGenId = 1;
                config.tplData = Ext.apply({}, {
                    $comp:      me, //把当前组件作为上下文
                    fgid:       frameGenId,
                    ui:         me.ui,
                    uiCls:      me.uiCls,
                    frameCls:   me.frameCls,
                    baseCls:    me.baseCls,
                    frameWidth: frameInfo.maxWidth,
                    top:        !!frameInfo.top,
                    left:       !!frameInfo.left,
                    right:      !!frameInfo.right,
                    bottom:     !!frameInfo.bottom,
                    renderData: me.initRenderData()
                }, me.getFramePositions(frameInfo));
                //设置外框子元素与组件的关联（作为成员变量）
                for (i = 0; i &lt; len; i++) {
                    suffix = frameElNames[i];
                    me.addChildEls({ name: 'frame' + suffix, id: frameGenId + suffix });
                }
                //对于面板，必须具有frameBody子元素
                me.addChildEls({
                    name: 'frameBody',
                    id: frameGenId + 'MC'
                });
            } else {
                config.tplData = me.initRenderData();
            }
        }
        return config;
    },
    //生命周期模板方法：渲染之后
    afterRender : function() {
        var me = this,
            data = {},
            protoEl = me.protoEl,
            target = me.getTargetEl(),
            item;
        //首先，递归的调用子组件的afterRender()
        me.finishRenderChildren();
        //为目标元素(el或者frameBody)添加样式
        if (me.styleHtmlContent) {
            target.addCls(me.styleHtmlCls);
        }
        //把protoEl的样式信息（包括style和class）写到对象data
        protoEl.writeTo(data);
        //添加、删除样式
        item = data.removed;
        if (item) {
            target.removeCls(item);
        }
        item = data.cls;
        if (item.length) {
            target.addCls(item);
        }
        item = data.style;
        if (data.style) {
            target.setStyle(item);
        }
        //protoEl没有利用价值了
        me.protoEl = null;
        //如果当前是最外层组件或容器，立即更新布局
        if (!me.ownerCt) {
            me.updateLayout();
        }
    },
    //第一次布局后执行
    afterFirstLayout : function(width, height) {
        var me = this,
            hasX = Ext.isDefined(me.x),
            hasY = Ext.isDefined(me.y),
            pos, xy;
        //设置组件的位置
        if (me.floating &amp;&amp; (!hasX || !hasY)) {
            if (me.floatParent) {
                pos = me.floatParent.getTargetEl().getViewRegion();
                xy = me.el.getAlignToXY(me.floatParent.getTargetEl(), 'c-c');
                pos.left = xy[0] - pos.left;
                pos.top =  xy[1] - pos.top;
            } else {
                xy = me.el.getAlignToXY(me.container, 'c-c');
                pos = me.container.translatePoints(xy[0], xy[1]);
            }
            me.x = hasX ? me.x : pos.left;
            me.y = hasY ? me.y : pos.top;
            hasX = hasY = true;
        }
 
        if (hasX || hasY) {
            me.setPosition(me.x, me.y);
        }
        //调用模板方法并发布事件
        me.onBoxReady(width, height);
        if (me.hasListeners.boxready) {
            me.fireEvent('boxready', me, width, height);
        }
    },
    //第一次布局后的模板方法，缺省适配
    onBoxReady: Ext.emptyFn,
    //根据renderSelectors、childEls的配置，设置组件对其内部元素的引用
    applyRenderSelectors: function() {
        var me = this,
            selectors = me.renderSelectors,
            el = me.el,
            dom = el.dom,
            selector;
 
        me.applyChildEls(el);
        if (selectors) {
            for (selector in selectors) {
                if (selectors.hasOwnProperty(selector) &amp;&amp; selectors[selector]) {
                    me[selector] = Ext.get(Ext.DomQuery.selectNode(selectors[selector], dom));
                }
            }
        }
    },
    //生命周期模板方法：渲染之前
    beforeRender: function () {
        var me = this,
            target = me.getTargetEl(),
            layout = me.getComponentLayout();
        //如果当前组件是诸如Window、Tip之类alwaysFramed的组件，那么设置frame为true
        me.frame = me.frame || me.alwaysFramed;
        //初始化组件布局管理器
        if (!layout.initialized) {
            layout.initLayout();
        }
        //设置targetEl的overflow样式，如果目前targetEl尚不存在，则在finishRender后会设置
        if (target) {
            target.setStyle(me.getOverflowStyle());
            me.overflowStyleSet = true;
        }
        //设置主题样式
        me.setUI(me.ui);
        if (me.disabled) {
            //静默的禁用组件
            me.disable(true);
        }
    },
    doApplyRenderTpl: function(out, values) {
        //注意，该方法的this指向frameTpl
        var me = values.$comp,
            tpl;
        if (!me.rendered) {
            //初始化renderTpl
            tpl = me.initRenderTpl();
            //把renderTpl的填充结果添加到输出中
            tpl.applyOut(values.renderData, out);
        }
    },
    //初始化renderTpl
    initRenderTpl: function() {
        var tpl = this.getTpl('renderTpl');//得到renderTpl作为模板
        if (tpl &amp;&amp; !tpl.renderContent) {
            this.setupRenderTpl(tpl);
        }
        return tpl;
    },
    //把renderTpl的renderBody、renderContent设值为doRenderContent函数
    setupRenderTpl: function (renderTpl) {
        renderTpl.renderBody = renderTpl.renderContent = this.doRenderContent;
    },
    doRenderContent: function (out, renderData) {
        //注意，该方法的this指向frameTpl
        var me = renderData.$comp;
        if (me.html) {
            //如果指定了html配置，把它输出到out
            Ext.DomHelper.generateMarkup(me.html, out);
            delete me.html;//删除配置
        }
        if (me.tpl) {
            //如果指定了tpl，将其填充结果输出到out
            if (!me.tpl.isTemplate) {
                me.tpl = new Ext.XTemplate(me.tpl);
            }
            if (me.data) {
                me.tpl.applyOut(me.data, out);
                delete me.data;
            }
        }
    },
    //执行自动渲染
    doAutoRender: function() {
        var me = this;
        if (!me.rendered) {
            if (me.floating) { //浮动组件渲染到body元素
                me.render(document.body);
            } else { //如果配置了渲染目标元素，则渲染到目标；否则渲染到body
                me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender);
            }
        }
    },
    
    doRenderFramingDockedItems: function (out, renderData, after) {
        //注意，该方法作为frameTpl的成员函数调用，this指向frameTpl
        var me = renderData.$comp;
        //大部分组件没有停靠子组件，所以检查其是否具有doRenderDockedItems()函数
        if (!me.rendered &amp;&amp; me.doRenderDockedItems) {
            renderData.renderData.$skipDockedItems = true;
            //调用渲染停靠子组件的函数
            me.doRenderDockedItems.call(this, out, renderData, after);
        }
    },
    //完成该组件的渲染，所有子组件的onRender、afterRender会被调用，所有组件布局、容器布局的计算都会完成
    finishRender: function(containerIdx) {
        var me = this,
            tpl, data, contentEl, el, pre, hide;
        //如果尚未渲染，执行渲染（插入DOM）
        if (!me.el || me.$pid) {
            if (me.container) {
                el = me.container.getById(me.id, true);
            } else {
                el = Ext.getDom(me.id);
            }
            if (!me.el) {
                me.wrapPrimaryEl(el);
            } else {
                delete me.$pid;
                if (!me.el.dom) {
                    me.wrapPrimaryEl(me.el);
                }
                el.parentNode.insertBefore(me.el.dom, el);
                Ext.removeNode(el); //
            }
        } else if (!me.rendering) {
            tpl = me.initRenderTpl();
            if (tpl) {
                data = me.initRenderData();
                tpl.insertFirst(me.getTargetEl(), data);
            }
        }
        //否则，目前正在渲染
        if (!me.container) {
            me.container = Ext.get(me.el.dom.parentNode);
        }
        if (me.ctCls) {
            me.container.addCls(me.ctCls);
        }
        //调用onRender模板方法
        me.onRender(me.container, containerIdx);
        if (!me.overflowStyleSet) {
            me.getTargetEl().setStyle(me.getOverflowStyle());
        }
        me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
        if (me.overCls) {
            me.el.hover(me.addOverCls, me.removeOverCls, me);
        }
        //发布render事件
        if (me.hasListeners.render) {
            me.fireEvent('render', me);
        }
        if (me.contentEl) {
            pre = Ext.baseCSSPrefix;
            hide = pre + 'hide-';
            contentEl = Ext.get(me.contentEl);
            contentEl.removeCls([pre+'hidden', hide+'display', hide+'offsets', hide+'nosize']);
            me.getTargetEl().appendChild(contentEl.dom);
        }
        //调用afterRender模板方法，在这里会完成所有子组件的渲染
        me.afterRender();
        if (me.hasListeners.afterrender) {
            me.fireEvent('afterrender', me);
        }
        //初始化事件
        me.initEvents();
        //如果配置为隐藏，则隐藏组件
        if (me.hidden) {
            me.el.hide();
        }
    },
    //渲染子代，这里的默认实现是处理组件布局
    //对于容器类组件，还要处理容器布局
    finishRenderChildren: function () {
        var layout = this.getComponentLayout(); //得到组件布局
        layout.finishRender(); //由布局完成所有子代的渲染
    },
 
    //使用TABLE或者DIV模式获得外框的模板
    initFramingTpl: function(table) {
        var tpl = table ? this.getTpl('frameTableTpl') : this.getTpl('frameTpl');
 
        if (tpl &amp;&amp; !tpl.applyRenderTpl) {
            this.setupFramingTpl(tpl);
        }
 
        return tpl;
    },
    //为外框模板添加一些成员函数
    setupFramingTpl: function(frameTpl) {
        frameTpl.applyRenderTpl = this.doApplyRenderTpl;
        frameTpl.renderDockedItems = this.doRenderFramingDockedItems;
    },
    //在配置的renderData基础上加上额外的属性
    initRenderData: function() {
        var me = this;
        return Ext.apply({
            $comp: me,
            id: me.id,
            ui: me.ui,
            uiCls: me.uiCls,
            baseCls: me.baseCls,
            componentCls: me.componentCls,
            frame: me.frame
        }, me.renderData);
    },
    //生命周期模板方法
    onRender: function(parentNode, containerIdx) {
        var me = this,
            x = me.x,
            y = me.y,
            lastBox, width, height,
            el = me.el,
            body = Ext.getBody().dom;
        if (Ext.scopeResetCSS &amp;&amp; !me.ownerCt) {
            if (el.dom === body) {
                el.parent().addCls(Ext.resetCls);
            }
            else {
                // Floaters rendered into the body can all be bumped into the common reset element
                if (me.floating &amp;&amp; me.el.dom.parentNode === body) {
                    Ext.resetElement.appendChild(me.el);
                }
                else {
                    me.resetEl = el.wrap(Ext.resetElementSpec, false, Ext.supports.CSS3LinearGradient ? undefined : '*');
                }
            }
        }
        me.applyRenderSelectors(); //处理子元素的引用，包括renderSelectors、childEls
        //设置组件状态为已渲染
        delete me.rendering;
        me.rendered = true;
        //记住最初的尺寸
        lastBox = null;
        if (x !== undefined) {
            lastBox = lastBox || {};
            lastBox.x = x;
        }
        if (y !== undefined) {
            lastBox = lastBox || {};
            lastBox.y = y;
        }
        if (!me.getFrameInfo() &amp;&amp; Ext.isBorderBox) {
            width = me.width;
            height = me.height;
 
            if (typeof width == 'number') {
                lastBox = lastBox || {};
                lastBox.width = width;
            }
            if (typeof height == 'number') {
                lastBox = lastBox || {};
                lastBox.height = height;
            }
        }
        me.lastBox = me.el.lastBox = lastBox;
    },
    //确保组件被附加到document.body内部，而不是detachedBody
    ensureAttachedToBody: function (runLayout) {
        var comp = this,
            body;
 
        while (comp.ownerCt) {
            comp = comp.ownerCt;
        }
 
        if (comp.container.isDetachedBody) {
            comp.container = body = Ext.resetElement;
            body.appendChild(comp.el.dom);
            if (runLayout) {
                comp.updateLayout();
            }
            if (typeof comp.x == 'number' || typeof comp.y == 'number') {
                comp.setPosition(comp.x, comp.y);
            }
        }
    },
    wrapPrimaryEl: function (dom) {
        this.el = Ext.get(dom, true);
    },
    //初始化圆角外框
    initFrame : function() {
        if (Ext.supports.CSS3BorderRadius || !this.frame) {
            return;
        }
 
        var me = this,
            frameInfo = me.getFrameInfo(),
            frameWidth, frameTpl, frameGenId,
            i,
            frameElNames = me.frameElNames,
            len = frameElNames.length,
            suffix;
 
        if (frameInfo) {
            frameWidth = frameInfo.maxWidth;
            frameTpl = me.getFrameTpl(frameInfo.table);
            me.frameGenId = frameGenId = (me.frameGenId || 0) + 1;
            frameGenId = me.id + '-frame' + frameGenId;
            frameTpl.insertFirst(me.el, Ext.apply({
                $comp:      me,
                fgid:       frameGenId,
                ui:         me.ui,
                uiCls:      me.uiCls,
                frameCls:   me.frameCls,
                baseCls:    me.baseCls,
                frameWidth: frameWidth,
                top:        !!frameInfo.top,
                left:       !!frameInfo.left,
                right:      !!frameInfo.right,
                bottom:     !!frameInfo.bottom
            }, me.getFramePositions(frameInfo)));
 
            me.frameBody = me.el.down('.' + me.frameCls + '-mc');
            me.removeChildEls(function (c) {
                return c.id &amp;&amp; me.frameIdRegex.test(c.id);
            });
            for (i = 0; i &lt; len; i++) {
                suffix = frameElNames[i];
                me['frame' + suffix] = me.el.getById(frameGenId + suffix);
            }
        }
    },
    //更新圆角外框
    updateFrame: function() {
        if (Ext.supports.CSS3BorderRadius || !this.frame) {
            return;
        }
        var me = this,
            wasTable = this.frameSize &amp;&amp; this.frameSize.table,
            oldFrameTL = this.frameTL,
            oldFrameBL = this.frameBL,
            oldFrameML = this.frameML,
            oldFrameMC = this.frameMC,
            newMCClassName;
        this.initFrame();
        if (oldFrameMC) {
            if (me.frame) {
                newMCClassName = this.frameMC.dom.className;
                oldFrameMC.insertAfter(this.frameMC);
                this.frameMC.remove();
                this.frameBody = this.frameMC = oldFrameMC;
                oldFrameMC.dom.className = newMCClassName;
                if (wasTable) {
                    me.el.query('&gt; table')[1].remove();
                }
                else {
                    if (oldFrameTL) {
                        oldFrameTL.remove();
                    }
                    if (oldFrameBL) {
                        oldFrameBL.remove();
                    }
                    if (oldFrameML) {
                        oldFrameML.remove();
                    }
                }
            }
        }
        else if (me.frame) {
            this.applyRenderSelectors();
        }
    },
    //得到组件圆角外框的信息
    getFrameInfo: function() {
        //如果浏览器支持CSS3圆角边框，或者组件不使用外框，则返回，不去读取CSS样式
        if (Ext.supports.CSS3BorderRadius || !this.frame) {
            return false;
        }
        var me = this,
            frameInfoCache = me.frameInfoCache,
            el = me.el || me.protoEl,
            cls = el.dom ? el.dom.className : el.classList.join(' '),
            frameInfo = frameInfoCache[cls],//缓存的外框信息
            styleEl, left, top, info;
        //如果找不到缓存的外框信息        
        if (frameInfo == null) {
            //创建一个在屏幕可见范围外的承载样式的元素
            styleEl = Ext.fly(me.getStyleProxy(cls), 'frame-style-el');
            //设置背景图片偏移
            left = styleEl.getStyle('background-position-x');
            top = styleEl.getStyle('background-position-y');
            if (!left &amp;&amp; !top) {
                info = styleEl.getStyle('background-position').split(' ');
                left = info[0];
                top = info[1];
            }
            //计算样式
            frameInfo = me.calculateFrame(left, top);
 
            if (frameInfo) {
                //确保不使用背景图片
                el.setStyle('background-image', 'none');
            }
            //加入缓存
            frameInfoCache[cls] = frameInfo;
        }
        me.frame = !!frameInfo;
        me.frameSize = frameInfo;
        return frameInfo;
    },
    //返回一个在屏幕可视范围外的基于cls样式的DIV
    getStyleProxy: function(cls) {
        var result = this.styleProxyEl || (Ext.AbstractComponent.prototype.styleProxyEl = Ext.resetElement.createChild({
                style: {
                    position: 'absolute',
                    top: '-10000px'
                }
            }, null, true));
 
        result.className = cls;
        return result;
    },
    //计算组件外框样式
    calculateFrame: function(left, top){
        if (!(parseInt(left, 10) &gt;= 1000000 &amp;&amp; parseInt(top, 10) &gt;= 1000000)) {
            return false;
        }
        var max = Math.max,
            tl = parseInt(left.substr(3, 2), 10),
            tr = parseInt(left.substr(5, 2), 10),
            br = parseInt(top.substr(3, 2), 10),
            bl = parseInt(top.substr(5, 2), 10),
            frameInfo = {
                table: left.substr(0, 3) == '110',
                vertical: top.substr(0, 3) == '110',
                top:    max(tl, tr),
                right:  max(tr, br),
                bottom: max(bl, br),
                left:   max(tl, bl)
            };
        frameInfo.maxWidth = max(frameInfo.top, frameInfo.right, frameInfo.bottom, frameInfo.left);
        frameInfo.width = frameInfo.left + frameInfo.right;
        frameInfo.height = frameInfo.top + frameInfo.bottom;
        return frameInfo;
    },
    //获取圆角外框的位置信息
    getFramePositions: function(frameInfo) {
        var me = this,
            frameWidth = frameInfo.maxWidth,
            dock = me.dock,
            positions, tc, bc, ml, mr;
        if (frameInfo.vertical) {
            tc = '0 -' + (frameWidth * 0) + 'px';
            bc = '0 -' + (frameWidth * 1) + 'px';
 
            if (dock &amp;&amp; dock == "right") {
                tc = 'right -' + (frameWidth * 0) + 'px';
                bc = 'right -' + (frameWidth * 1) + 'px';
            }
            positions = {
                tl: '0 -' + (frameWidth * 0) + 'px',
                tr: '0 -' + (frameWidth * 1) + 'px',
                bl: '0 -' + (frameWidth * 2) + 'px',
                br: '0 -' + (frameWidth * 3) + 'px',
                ml: '-' + (frameWidth * 1) + 'px 0',
                mr: 'right 0',
                tc: tc,
                bc: bc
            };
        } else {
            ml = '-' + (frameWidth * 0) + 'px 0';
            mr = 'right 0';
            if (dock &amp;&amp; dock == "bottom") {
                ml = 'left bottom';
                mr = 'right bottom';
            }
            positions = {
                tl: '0 -' + (frameWidth * 2) + 'px',
                tr: 'right -' + (frameWidth * 3) + 'px',
                bl: '0 -' + (frameWidth * 4) + 'px',
                br: 'right -' + (frameWidth * 5) + 'px',
                ml: ml,
                mr: mr,
                tc: '0 -' + (frameWidth * 0) + 'px',
                bc: '0 -' + (frameWidth * 1) + 'px'
            };
        }
        return positions;
    },
    //获取圆角外框的XTemplate
    getFrameTpl : function(table) {
        return this.getTpl(table ? 'frameTableTpl' : 'frameTpl');
    },
    frameInfoCache: {}
});</pre>
<div class="blog_h3"><span class="graybg">Ext.AbstractComponent</span></div>
<p>私有类Ext.AbstractComponent为Sencha的RIA应用框架（例如Sencha Touch、ExtJS）提供了共享的功能，是ExtJS组件机制的基础。它提供了以下方面的功能：</p>
<ol>
<li>规定了ExtJS组件的生命周期模型</li>
<li>提供了若干供扩展的模板方法，可以扩展ExtJS组件生命周期的各阶段，或者阻止默认生命周期行为的发生</li>
<li>规定了组件HTML内容的、基于模板的渲染机制</li>
<li>提供了各种设置组件UI样式的方法，包括CSS样式类、补白、边距、边框、可见性等</li>
<li>提供了组件布局机制</li>
<li>混入了：
<ol>
<li>Ext.util.Renderable，保护组件渲染阶段的核心逻辑</li>
<li>Ext.util.Observable，实现了观察者模式，是ExtJS事件机制的基础</li>
</ol>
</li>
</ol>
<p>AbstractComponent类包含以下成员：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">配置项/属性/方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>{autoEl}</td>
<td>
<p>String/Object，标签名或者DomHelper元素规格对象，用来创建表示该组件的封装元素（encapsulating element，即顶级元素）。该元素后续可以通过getEl()获得<br />对于Ext.Component、Ext.container.Container，默认值是div。复杂的组件通过指定renderTpls来生成元素</p>
<p>示例：</p>
<pre class="crayon-plain-tag">Ext.create('Ext.Component', {
    id: "widget",
    autoEl: {
        tag: 'h3',
        html: '&lt;span&gt;Content&lt;/span&gt;'
    }
});
/* 输出内容 */
&lt;h3 id="widget"&gt;
    &lt;span&gt;Content&lt;/span&gt;
&lt;/h3&gt;</pre>
</td>
</tr>
<tr>
<td>{contentEl}</td>
<td>
<p>String，指定一个既有的HTML元素或者其id，作为当前组件的内容元素。用于把已有的DOM节点移动到组件的布局元素内部
<p>可以用x-hidden、x-hide-display样式防止渲染前的闪烁</p>
</td>
</tr>
<tr>
<td>{html}</td>
<td>
<p>String/Object，HTML片段或者DomHelper元素规格对象，用作组件布局元素的内容。该HTML内容在组件被渲染后（render事件发布后）可用。在contentEl被插入前该HTML的内容被插入组件体</p>
<p>示例：</p>
<pre class="crayon-plain-tag">Ext.create('Ext.Component', {
    id: "widget",
    html: '&lt;span&gt;My Content&lt;/span&gt;'
});
/* 输出内容 */
&lt;div id="widget"&gt;
    &lt;span&gt;Content&lt;/span&gt;
&lt;/div&gt; </pre>
</td>
</tr>
<tr>
<td>{autoRender}</td>
<td>Boolean/String/HTMLElement/Ext.Element，可以控制非浮动组件的自动渲染（在第一次show被调用时进行渲染），替代renderTo配置。对于浮动组件该配置无效，总是true</td>
</tr>
<tr>
<td>{renderTo}</td>
<td>String/HTMLElement/Ext.Element，指定组件需要渲染到的HTML元素</td>
</tr>
<tr>
<td>{autoShow}</td>
<td>Boolean，如果为真，组件创建后立即显示。仅用于使用autoRender的组件、浮动组件。对于浮动组件，如果该选项为false，即使作为某个容器的子组件，也不会自动渲染。</td>
</tr>
<tr>
<td>{baseCls}</td>
<td>String，应用到组件封装元素的样式类，默认“x-component”</td>
</tr>
<tr>
<td>{cls}</td>
<td>String，添加到组件封装元素的额外样式类</td>
</tr>
<tr>
<td>{componentCls}</td>
<td>String，添加到组件根元素的样式类</td>
</tr>
<tr>
<td>{disabledCls}</td>
<td>String，组件被禁用时的样式</td>
</tr>
<tr>
<td>{overCls}</td>
<td>String，鼠标悬停组件时的额外样式类</td>
</tr>
<tr>
<td>{styleHtmlCls}</td>
<td>String，styleHtmlContent配置为true时，用于指定内容目标（content target）的样式</td>
</tr>
<tr>
<td>{border}</td>
<td>Number/String/Boolean，指定组件边框大小，支持类似10 5 3 10的CSS格式</td>
</tr>
<tr>
<td>{margin}</td>
<td>Number/String/Boolean，指定组件外边距的大小，支持类似10 5 3 10的CSS格式</td>
</tr>
<tr>
<td>{padding}</td>
<td>Number/String/Boolean，指定组件内补白的大小，支持类似10 5 3 10的CSS格式</td>
</tr>
<tr>
<td>{childEls}</td>
<td>Object[]，描述组件子元素（不是子组件）的数组，数组的每一个元素是包含3个属性的对象：
<ol>
<li>name 子元素在组件中的属性名</li>
<li>itemId 子元素的唯一标识，ExtJS自动将其与组件ID连接，因此不用担心唯一性</li>
<li>id 子元素的ID</li>
</ol>
<p>如果数组元素是字符串而不是对象，等价于：{ name: m, itemId: m }
<p>举例：</p>
<pre class="crayon-plain-tag">Ext.create('Ext.Component', {
    renderTpl: [
        '&lt;h1 id="{id}-title"&gt;{title}&lt;/h1&gt;'
    ],
    renderData: {
        title: "Error"
    },
    childEls: ["title"],
    listeners: {
        afterrender: function(cmp){
            //在渲染完毕后，{id}-title元素自动作为当前组件的实例变量
            cmp.title.setStyle({color: "red"});
        }
    }
});</pre>
</td>
</tr>
<tr>
<td>{componentLayout}</td>
<td>
<p>String/Object，组件布局。组件内部元素的大小、位置是由组件的布局管理器来负责的，当组件本身的大小发生改变时，组件布局管理器更新这些元素的大小和位置。
<p>通常不需要初始化组件实例时指定该配置，具体组件类通常内置了组件布局方式</p>
<p>Ext.Component提供的默认组件布局，仅仅是将组件封装元素的宽高通过setSize()进行复制</p>
</td>
</tr>
<tr>
<td>{data}</td>
<td>
<p>Object，用于更新组件内容区域（content  area）的tpl模板的初始上下文。要使用创建组件时的配置对象作为上下文，可以：</p>
<pre class="crayon-plain-tag">constructor: function ( config ) {
    var me = this;
    me.data = config;
    me.callParent( config );
}</pre>
</td>
</tr>
<tr>
<td>{tpl}</td>
<td>
<p>Ext.XTemplate/Ext.Template/String/String[]，与data、tplWriteMode联用，更新组件的<span style="background-color: #99cc00;">内容区域</span>
<p>如果<span style="background-color: #c0c0c0;">不同时指定data，导致tpl中的内容不渲染</span>，示例：</p>
<pre class="crayon-plain-tag">var widget = Ext.create('Ext.Component', {
    id: "widget",
    tpl: '&lt;span&gt;Hello {name}&lt;/span&gt;',
    data: {name: 'Alex'}
});
/* 输出内容 */
&lt;div id="widget"&gt;
    &lt;span&gt;Hello Alex&lt;/span&gt;
&lt;/div&gt;</pre>
<p>使用tpl时，可利用的强大特性是模板中的占位符可以动态的被替换：</p>
<pre class="crayon-plain-tag"># 警告：update导致子元素被清空并重建，不是Angular那种风格
widget.update({name: 'Alex Wong'});</pre>
<p>上面的语句导致HTML内容变为 <pre class="crayon-plain-tag">&lt;span&gt;Hello Alex Wong&lt;/span&gt;</pre> </p>
</td>
</tr>
<tr>
<td>{tplWriteMode}</td>
<td>
<p>String，更新组件内容区域（content area）使用Ext.(X)Template的哪个函数？默认overwrite</p>
</td>
</tr>
<tr>
<td>{renderData}</td>
<td>
<p>Object，用于renderTpl模板的上下文，包含以下内置属性：id、ui、uiCls、baseCls、componentCls、frame</p>
</td>
</tr>
<tr>
<td>{renderTpl}</td>
<td>
<p>Ext.XTemplate/String/String[]，一个XTemplate，用于创建<span style="background-color: #99cc00;">组件封装元素的内部结构</span>。</p>
<p>对于Ext.Component、Ext.container.Container，通常不需要指定该配置，如果该配置为null表示不渲染内部结构（getEl的得到的是空的没有子元素的元素）</p>
</td>
</tr>
<tr>
<td>{renderSelectors}</td>
<td>
<p>Object，包含DomQuery选择器的、用于识别组件子元素的对象。当组件内部结构通过renderTpl渲染后，该对象被遍历，找到的元素被作为当前组件的属性，该配置的功能与childEls类似，但是更加灵活，举例：</p>
<pre class="crayon-plain-tag">Ext.create( 'Ext.Component', {
    renderTpl : [
        '&lt;h1 class="title"&gt;{title}&lt;/h1&gt;', '&lt;p&gt;{desc}&lt;/p&gt;'
    ],
    renderData : {
        title : "Error",
        desc : "Something went wrong"
    },
    renderSelectors : {
        titleEl : 'h1.title', //CSS选择器
        descEl : 'p'
    },
    listeners : {
        afterrender : function( cmp )
        {
            //在渲染完毕后，h1.title元素自动作为当前组件的实例变量
            cmp.titleEl.setStyle( {
                color : "red"
            } );
        }
    }
} );</pre>
</td>
</tr>
<tr>
<td>{disabled}</td>
<td>
<p>Boolean，用于禁用组件
</td>
</tr>
<tr>
<td>{draggable}</td>
<td>
<p>Boolean，用于支持组件的拖拽</p>
</td>
</tr>
<tr>
<td>{floating}</td>
<td>
<p>Boolean，用于指示组件为浮动，即独立出文档布局并使用绝对定位。该属性为true的组件称为浮动组件。</p>
<p>浮动组件的z-index由某个ZIndexManager的实例管理：</p>
<ol>
<li>如果仅仅将组件渲染到DOM（而不是容器内），则 WindowManager管理其z-index</li>
<li>如果将组件渲染到容器内，其上溯到的第一个floating祖先容器的ZIndexManager负责管理组件的z-index</li>
</ol>
</td>
</tr>
<tr>
<td>{frame}</td>
<td>
<p>Boolean，如果指定为true，在渲染阶段自动诸如一个外框元素到组件外围，提供一个圆角矩形框的视觉效果。仅仅用于诸如IE9-的遗留浏览器</p>
</td>
</tr>
<tr>
<td>{hidden}</td>
<td>Boolean，组件是否是隐藏的</td>
</tr>
<tr>
<td>{height}</td>
<td>Number，组件的高度，像素</td>
</tr>
<tr>
<td>{maxHeight}</td>
<td>Number，组件允许的最大高度</td>
</tr>
<tr>
<td>{minHeight}</td>
<td>Number，组件允许的最小高度</td>
</tr>
<tr>
<td>{width}</td>
<td>Number，组件的宽度，像素</td>
</tr>
<tr>
<td>{maxWidth}</td>
<td>Number，组件允许的最大宽度</td>
</tr>
<tr>
<td>{minWidth}</td>
<td>Number，组件允许的最小宽度</td>
</tr>
<tr>
<td>{hideMode} </td>
<td>
<p>String，隐藏的方式：</p>
<ol>
<li>display，使用display: none进行隐藏。该隐藏方式导致组件的尺寸为0</li>
<li>visibility，使用visibility: hidden方式隐藏</li>
<li>offsets，通过绝对定位将其移出文档可视区域，该隐藏方式保持组件尺寸不变</li>
</ol>
</td>
</tr>
<tr>
<td>{id}</td>
<td>
<p>String，组件的唯一标识</p>
</td>
</tr>
<tr>
<td>{itemId}</td>
<td>
<p>String，条目标识，可以在不使用id的情况下用于获取组件（通过Container.getComponent），该属性在组件兄弟组件之间保持唯一性</p>
</td>
</tr>
<tr>
<td>{loader}</td>
<td>
<p>Ext.ComponentLoader/Object，用于远程加载组件内容的加载器对象</p>
</td>
</tr>
<tr>
<td>{plugins}</td>
<td>
<p>Object/Object[]，使用的组件插件，插件用于提供定制的功能。对于插件只有一个要求：存在一个init方法，接受当前组件作为其参数</p>
</td>
</tr>
<tr>
<td>{shrinkWrap}</td>
<td>
<p>Boolean/Number，各值的意义：</p>
<ol>
<li>如果值为0，宽、高都不依赖于内容，等价于false</li>
<li>如果值为1，宽依赖于内容（shrinkwrap），高不</li>
<li>如果值为2，高依赖于内容（shrinkwrap），宽不。这是默认值</li>
<li>如果值为3，宽、高都依赖于内容，等价于true</li>
</ol>
</td>
</tr>
<tr>
<td>{style}</td>
<td>String/Object，设置组件元素的样式，参数必须满足Ext.Element.applyStyles()的参数要求</td>
</tr>
<tr>
<td>{styleHtmlContent}</td>
<td>Boolean，如果设置为true，自动为组件内容目标（content target，对于panel来说是body）添加样式，</td>
</tr>
<tr>
<td>{ui}</td>
<td>String，用于定制ExtJS组件的主题</td>
</tr>
<tr>
<td>{xtype}</td>
<td>Sring，组件的别名，用于延迟创建</td>
</tr>
<tr>
<td>_isLayoutRoot</td>
<td>protected Boolean，设置该值为true，可以导致isLayoutRoot()返回true</td>
</tr>
<tr>
<td>autoGenId</td>
<td>private Boolean，指示组件标识是自动生成而不是手工指定的</td>
</tr>
<tr>
<td>componentLayoutCounter</td>
<td>private Number，该组件的组件布局被调用的次数</td>
</tr>
<tr>
<td>draggable</td>
<td>readonly Boolean，是否可拖拽</td>
</tr>
<tr>
<td>frameSize</td>
<td>readonly Object，圆角外框的尺寸，参frame配置</td>
</tr>
<tr>
<td>isComponent</td>
<td>Boolean，用于Duck类型识别，判断一个对象是否组件</td>
</tr>
<tr>
<td>maskOnDisable</td>
<td>Boolean，当禁用时，是否显示一个半透明遮罩。FieldContainer, FieldSet, Field, Button, Tab等组件覆盖此标记，以提供定制的禁用逻辑</td>
</tr>
<tr>
<td>ownerCt</td>
<td>readonly Ext.Container，组件所在容器</td>
</tr>
<tr>
<td>rendered </td>
<td>readonly Boolean，指示组件是否已经渲染</td>
</tr>
<tr>
<td>addCls()</td>
<td>Ext.Component ( String/String[] cls )，添加样式到组件的顶级元素</td>
</tr>
<tr>
<td>removeCls</td>
<td>Ext.Component ( String/String[] cls ) ，从顶级元素移除样式</td>
</tr>
<tr>
<td>addClsWithUI()</td>
<td>void ( String/String[] classes, Object skip )，添加样式到uiCls数组，该方法会同时调用addUIClsToElement把样式应用到组件所有元素</td>
</tr>
<tr>
<td>removeClsWithUI</td>
<td>void ( String/String[] cls )</td>
</tr>
<tr>
<td>addFocusListener()</td>
<td>private，如果元素具有focusEl，则添加focus监听器</td>
</tr>
<tr>
<td>addPropertyToState()</td>
<td>Boolean ( Object state, String propName, [String value] )，如果属性值与默认/配置值不一样，将其存放到状态对象state中</td>
</tr>
<tr>
<td>constructPlugins( ) </td>
<td>private，返回完整创建的插件实例数组</td>
</tr>
<tr>
<td>convertPosition()</td>
<td>privaet void( Object pos, Object withUnits )，把数字形式的位置添加px后缀</td>
</tr>
<tr>
<td>destroy()</td>
<td>销毁组件</td>
</tr>
<tr>
<td>disable()</td>
<td>void ( [Boolean silent] )，禁用组件，参数用于避免disable事件的触发</td>
</tr>
<tr>
<td>doComponentLayout()</td>
<td>Ext.container.Container()，执行组件布局，当组件内容改变，需要调整子元素大小位置时，应当调用该方法，返回this</td>
</tr>
<tr>
<td>enable()</td>
<td>void ( [Boolean silent] )，启用组件，参数用于避免enable事件的触发</td>
</tr>
<tr>
<td>getBubbleTarget( )</td>
<td>Ext.container.Container()，返回事件冒泡的目标 </td>
</tr>
<tr>
<td>getEl()</td>
<td>Ext.dom.Element()，返回组件的顶层元素</td>
</tr>
<tr>
<td>getFocusEl()</td>
<td>private，获取该组件用于持有焦点的元素，默认返回null</td>
</tr>
<tr>
<td>getHeight( )</td>
<td>Number()，获取元素的高度，类似的方法包括getWidth()、getSize( ) </td>
</tr>
<tr>
<td>getId( )  </td>
<td>String()，获取组件ID</td>
</tr>
<tr>
<td>getItemId( )</td>
<td>String()，获取分配给组件的itemId， 如果没有返回id</td>
</tr>
<tr>
<td>getPlugin()</td>
<td>Ext.AbstractPlugin (String pluginId)，根据id获取插件对象</td>
</tr>
<tr>
<td>getSizeModel()</td>
<td>Object ( Object ownerCtSizeModel ) 返回一个用于描述组件宽高如何被管理的SizeModel对象</td>
</tr>
<tr>
<td>getState( )</td>
<td>Object()，获取组件状态对象，默认实现返回flex, anchor, width, height, collapsed等尺寸相关的属性</td>
</tr>
<tr>
<td>getTargetEl()</td>
<td>private，用于确定在何处插入html、contentEl、items</td>
</tr>
<tr>
<td>hasCls()</td>
<td>Boolean (String cls)，判断指定的CSS样式类是否存在于组件的元素</td>
</tr>
<tr>
<td>hasUICls()</td>
<td>Boolean (String cls)，判断当前是否应用了指定的uiCls</td>
</tr>
<tr>
<td>isDisabled( )</td>
<td>组件是否被禁用</td>
</tr>
<tr>
<td>isDraggable( )</td>
<td>组件是否可以作为拖拽的源</td>
</tr>
<tr>
<td>isDroppable( ) </td>
<td>组件是否可以作为拖拽的目标</td>
</tr>
<tr>
<td>isFloating( )</td>
<td>是否浮动组件</td>
</tr>
<tr>
<td>isHidden( )</td>
<td>组件是否被隐藏</td>
</tr>
<tr>
<td>isLayoutRoot( )</td>
<td>protected Boolean()，组件是否是布局的根。如果组件可以在没有父容器的配合/不对父容器产生影响的情况下执行布局，则可以返回true</td>
</tr>
<tr>
<td>isLayoutSuspended( )</td>
<td>当前组件的布局是否被暂停</td>
</tr>
<tr>
<td>isVisible()</td>
<td>组件是否可见</td>
</tr>
<tr>
<td>isXType</td>
<td>Boolean ( String xtype, [Boolean shallow] )，判断当前组件是否是指定xtype的实例，如果shallow=true，则必须精确匹配，否则当前组件可以是xtype的子类型</td>
</tr>
<tr>
<td>on()</td>
<td>addListener()的别名</td>
</tr>
<tr>
<td>registerFloatingItem</td>
<td>void ( Object cmp )，由Component.doAutoRender方法，使用当前组件的ZIndexManager管理cmp</td>
</tr>
<tr>
<td>setBorder()</td>
<td>void ( String/Number border )，设置边框</td>
</tr>
<tr>
<td>setDisabled()</td>
<td>void ( Boolean disabled )，设置禁用状态</td>
</tr>
<tr>
<td>setDocked()</td>
<td>Ext.Component( Object dock, [Boolean layoutParent] ) ，设置当前组件在其父容器的停靠位置，仅当该组件是父容器dockedItems的一部分时有效</td>
</tr>
<tr>
<td>setHeight()</td>
<td>Ext.Component( Number height )，设置高度，类似还有setWidth()、setSize()</td>
</tr>
<tr>
<td>setPosition</td>
<td>Ext.Component( Number left, Number top, [Boolean/Object animate] )，设置组件的位置</td>
</tr>
<tr>
<td>setUI()</td>
<td>设置使用的主题样式</td>
</tr>
<tr>
<td>setVisible()</td>
<td>设置组件可见性</td>
</tr>
<tr>
<td>update()</td>
<td>
<p>void ( String/Object htmlOrData, [Boolean loadScripts], [Function callback] )，更新组件的内容区域</p>
<p>可以导致组件内容模板中的占位符自动更新</p>
</td>
</tr>
<tr>
<td>updateLayout()</td>
<td>void ( Object options )，更新组件的布局，如果该操作影响ownerCt，则转调ownerCt的updateLayout()，否则仅仅调用该组件的updateLayout()本身</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>静态方法</strong></td>
</tr>
<tr>
<td>flushLayouts( )</td>
<td>执行所有因为 suspendLayouts而暂停的布局</td>
</tr>
<tr>
<td>resumeLayouts()</td>
<td>void ( [Boolean flush] )，恢复整个框架的布局活动</td>
</tr>
<tr>
<td>suspendLayouts( )</td>
<td>暂停整个框架的布局活动</td>
</tr>
<tr>
<td>updateLayout()</td>
<td>void ( Ext.Component comp, [Boolean defer] )，更新某个组件的布局</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>组件查询方法</strong>  </td>
</tr>
<tr>
<td>is()</td>
<td>Boolean ( String selector )，检测当前组件是否与选择器匹配</td>
</tr>
<tr>
<td>isDescendantOf()</td>
<td>Boolean ( Ext.Container container ) ，检测当前组件是否为指定容器的后代</td>
</tr>
<tr>
<td>nextNode()</td>
<td>Ext.Component ( [String selector] ) ，获取组件树结构中的下一个（匹配选择器的）组件（按树遍历顺序）</td>
</tr>
<tr>
<td>nextSibling()</td>
<td>Ext.Component ( [String selector] )，获取组件的下一个（匹配选择器的）兄弟组件。别名：next</td>
</tr>
<tr>
<td>previousNode()</td>
<td>Ext.Component ( [String selector] )，获取组件树结构中的上一个（匹配选择器的）组件（按树遍历顺序）</td>
</tr>
<tr>
<td>previousSibling()</td>
<td>Ext.Component ( [String selector] )，获取组件的上一个（匹配选择器的）兄弟组件。别名：prev</td>
</tr>
<tr>
<td>up()</td>
<td>Ext.container.Container( [String selector] )，根据选择器向上查询容器</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>模板方法  </strong></td>
</tr>
<tr>
<td>afterComponentLayout()</td>
<td>void ( Number width, Number height, Number oldWidth, Number oldHeight )，当组件布局被执行后，由布局系统调用</td>
</tr>
<tr>
<td>afterSetPosition() </td>
<td>void ( Number x, Number y )，当组件的位置被设置了，该方法被调用</td>
</tr>
<tr>
<td>beforeComponentLayout()</td>
<td>Boolean ( Number adjWidth, Number adjHeight )，当组件布局被执行前调用，返回false导致不进行组件布局</td>
</tr>
<tr>
<td>beforeDestroy()</td>
<td>在组件被销毁前调用</td>
</tr>
<tr>
<td>beforeSetPosition()</td>
<td>private void ( Object x, Object y, Object animate )，在设置组件位置前调用</td>
</tr>
<tr>
<td>onAdded()</td>
<td>void ( Ext.container.Container container, Number pos )，该方法用于感知组件被加入的容器，会触发一个add事件。对父容器的引用建立在添加阶段，而不是渲染阶段。在此方法被调用时，组件被置于父容器的items中</td>
</tr>
<tr>
<td>onDisable( )</td>
<td>添加额外的禁用逻辑，在调用被覆盖版本后，组件禁用</td>
</tr>
<tr>
<td>onEnable( )</td>
<td>添加额外的启用逻辑，在调用被覆盖版本后，组件启用</td>
</tr>
<tr>
<td>onPosition()</td>
<td>void ( Number x, Number y )，组件被移动后调用，默认实现时空的</td>
</tr>
<tr>
<td>onRemoved()</td>
<td>void ( Boolean destroying )，用于感知组件被从父容器中移除，会发布一个removed事件。在此方法被调用时，组件已经移出父容器的items，但是尚未destroy，调用覆盖版本后，ownerCt、refOwner 置空</td>
</tr>
<tr>
<td>onResize( )</td>
<td>添加额外的改变大小逻辑</td>
</tr>
<tr>
<td>⚡activate</td>
<td>void ( Ext.Component this, Object eOpts )，组件被视觉上激活后触发</td>
</tr>
<tr>
<td>⚡added</td>
<td>void ( Ext.Component this, Ext.container.Container container, Number pos, Object eOpts )，组件被添加到容器后触发 </td>
</tr>
<tr>
<td>⚡afterrender </td>
<td>void ( Ext.Component this, Object eOpts )，在组件被渲染之后触发，此时afterRender()已经调用</td>
</tr>
<tr>
<td>⚡beforeactivate</td>
<td>Boolean ( Ext.Component this, Object eOpts )，组件激活前触发，返回false阻止激活 </td>
</tr>
<tr>
<td>⚡beforedeactivate</td>
<td>Boolean ( Ext.Component this, Object eOpts )，组件去激活前触发，返回false阻止去激活</td>
</tr>
<tr>
<td>⚡beforedestroy</td>
<td>Boolean ( Ext.Component this, Object eOpts )，组件销毁前触发，返回false阻止销毁</td>
</tr>
<tr>
<td>⚡beforehide</td>
<td>Boolean ( Ext.Component this, Object eOpts )，组件渲染前触发，返回false阻止渲染</td>
</tr>
<tr>
<td>⚡beforeshow</td>
<td>Boolean ( Ext.Component this, Object eOpts )，组件显示前触发，返回false阻止显示 </td>
</tr>
<tr>
<td>⚡blur</td>
<td>void( Ext.Component this, Ext.EventObject The, Object eOpts )，组件失去焦点时触发 </td>
</tr>
<tr>
<td>⚡boxready </td>
<td>void( Ext.Component this, Number width, Number height, Object eOpts )，组件在第一次以初始尺寸布局后发布一次 </td>
</tr>
<tr>
<td>⚡deactivate</td>
<td>void ( Ext.Component this, Object eOpts )，组件在视觉上去激活时触发 </td>
</tr>
<tr>
<td>⚡destroy</td>
<td>void ( Ext.Component this, Object eOpts )，组件被销毁后触发 </td>
</tr>
<tr>
<td>⚡disable</td>
<td>void ( Ext.Component this, Object eOpts )，组件禁用后触发 </td>
</tr>
<tr>
<td>⚡enable</td>
<td>void ( Ext.Component this, Object eOpts )，组件启用后触发 </td>
</tr>
<tr>
<td>⚡focus </td>
<td>void ( Ext.Component this, Ext.EventObject The, Object eOpts ) 组件接收到焦点后触发 </td>
</tr>
<tr>
<td>⚡hide</td>
<td>void ( Ext.Component this, Object eOpts )，组件被隐藏后触发 </td>
</tr>
<tr>
<td>⚡move</td>
<td>void ( Ext.Component this, Number x, Number y, Object eOpts )，组件的位置改变后触发 </td>
</tr>
<tr>
<td>⚡removed </td>
<td>void ( Ext.Component this, Ext.container.Container ownerCt, Object eOpts )，组件从容器中移除后触发</td>
</tr>
<tr>
<td>⚡render </td>
<td>void ( Ext.Component this, Object eOpts )，当组件的HTML标记被渲染到DOM后触发，注意组件不一定展示（show） </td>
</tr>
<tr>
<td>⚡resize</td>
<td>void ( Ext.Component this, Number width, Number height, Number oldWidth, Number oldHeight, Object eOpts )，当组件的大小被改变时触发。注意：组件第一次使用其初始大小布局后，不会触发该事件，此情况可以使用boxready代替该事件 </td>
</tr>
<tr>
<td>⚡show </td>
<td>void ( Ext.Component this, Object eOpts )，当调用show()方法后，当前组件展示，该事件被触发</td>
</tr>
</tbody>
</table>
<p>Ext.AbstractComponent的源代码分析如下：</p>
<pre class="crayon-plain-tag">Ext.define('Ext.AbstractComponent', {
    statics: {
        AUTO_ID: 1000,
        pendingLayouts: null,
        layoutSuspendCount: 0,
        cancelLayout: function(comp, isDestroying) {
            //context为布局上下文，Ext.layout.Context的实例
            var context = this.runningLayoutContext || this.pendingLayouts;
            if (context) {
                context.cancelComponent(comp, false, isDestroying);
            }
        },
        flushLayouts: function () {
            var me = this,
                context = me.pendingLayouts;
            //invalidQueue是布局失效的顶层布局组件的队列，队列里的组件不会存在具有父子/前后代关系的组件
            if (context &amp;&amp; context.invalidQueue.length) {
                me.pendingLayouts = null;
                me.runningLayoutContext = context; //正在执行的布局上下文
                //覆盖上下文的刷新布局方法
                Ext.override(context, {
                    runComplete: function () {
                        //在通过runComplete调用finishedLayout()前必须清空布局队列
                        //因为finishedLayout()会调用afterComponentLayout，而可能导致重新进入doLayout或doComponentLayout
                        me.runningLayoutContext = null;
                        return this.callParent(); 
                    }
                });
                //执行布局计算，此方法只能在布局上下文上调用一次
                context.run();
            }
        },
        //递减暂停布局计数器，如果为0，则刷新布局
        resumeLayouts: function (flush) {
            if (this.layoutSuspendCount &amp;&amp; ! --this.layoutSuspendCount) {
                if (flush) {
                    this.flushLayouts();
                }
            }
        },
        //递增暂停布局计数器
        suspendLayouts: function () {
            ++this.layoutSuspendCount;
        },
        //更新组件的布局
        updateLayout: function (comp, defer) {
            var me = this,
                running = me.runningLayoutContext,
                pending;
            //在running或者pending布局的下一次周期中排队
            if (running) {
                running.queueInvalidate(comp);
            } else {
                pending = me.pendingLayouts || (me.pendingLayouts = new Ext.layout.Context());
                pending.queueInvalidate(comp);
                if (!defer &amp;&amp; !me.layoutSuspendCount &amp;&amp; !comp.isLayoutSuspended()) {
                    me.flushLayouts();
                }
            }
        }
    },
    isComponent: true,
    getAutoId: function() {
        this.autoGenId = true;
        return ++Ext.AbstractComponent.AUTO_ID;
    },
    deferLayouts: false,
    autoGenId: false,
    //默认的渲染模板
    renderTpl: '{%this.renderContent(out,values)%}',
    frameSize: { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 },
    tplWriteMode: 'overwrite',
    baseCls: Ext.baseCSSPrefix + 'component',
    disabledCls: Ext.baseCSSPrefix + 'item-disabled',
    ui: 'default',
    uiCls: [],
    hidden: false,
    disabled: false,
    draggable: false,
    floating: false,
    hideMode: 'display',
    styleHtmlContent: false,
    styleHtmlCls: Ext.baseCSSPrefix + 'html',
    autoShow: false,
    autoRender: false,
    // @private
    allowDomMove: true,
    rendered: false,
    componentLayoutCounter: 0,
    shrinkWrap: 2,
    weight: 0,
    maskOnDisable: true,
    _isLayoutRoot: false,
    //组件构造器，规定了组件的生命周期
    constructor : function(config) {
        var me = this,
            i, len, xhooks;
        //配置项作为属性，应用到当前对象
        if (config) {
            Ext.apply(me, config);
            //xhooks用于动态的覆盖一个实例的成员，而不是类的
            xhooks = me.xhooks;
            if (xhooks) {
                delete me.xhooks;
                Ext.override(me, xhooks);
            }
        } else {
            config = {};
        }
        //初始配置
        me.initialConfig = config;
        //构造混入
        me.mixins.elementCt.constructor.call(me);
        me.addEvents(
            'beforeactivate',
            'activate',
            'beforedeactivate',
            'deactivate',
            'added',
            'disable',
            'enable',
            'beforeshow',
            'show',
            'beforehide',
            'hide',
            'removed',
            'beforerender',
            'render',
            'afterrender',
            'boxready',
            'beforedestroy',
            'destroy',
            'resize',
            'move',
            'focus',
            'blur'
        );
        //触发以生成ID
        me.getId();
        //生成protoEl属性，Ext.util.ProtoElement用于在渲染前管理Element的属性，例如class和style
        me.setupProtoEl();
        if (me.cls) {
            me.initialCls = me.cls;
            me.protoEl.addCls(me.cls);
        }
        if (me.style) {
            me.initialStyle = me.style;
            me.protoEl.setStyle(me.style);
        }
        //受管的监听器列表
        me.mons = [];
        me.renderData = me.renderData || {};
        me.renderSelectors = me.renderSelectors || {};
        //如果配置了插件，则在此执行所有插件的构造
        if (me.plugins) {
            me.plugins = me.constructPlugins();
        }
        if (!me.hasListeners) {
            me.hasListeners = new me.HasListeners();
        }
        //生命周期模板方法：初始化组件
        me.initComponent();
        //在组件管理器中注册自己
        Ext.ComponentManager.register(me);
        //构造混入类
        me.mixins.observable.constructor.call(me);
        me.mixins.state.constructor.call(me, config);
        //resize作为状态事件，resize将导致组件状态被保存
        this.addStateEvents('resize');
        //如果配置了插件，则在此依次初始化插件
        if (me.plugins) {
            for (i = 0, len = me.plugins.length; i &lt; len; i++) {
                me.plugins[i] = me.initPlugin(me.plugins[i]);
            }
        }
        //初始化用于远程加载组件内容的加载器
        me.loader = me.getLoader();

        //生命周期的第二阶段：渲染
        //容器中的子组件，其渲染阶段被父容器控制
        //如果配置了renderTo，那么立即执行渲染
        if (me.renderTo) {
            me.render(me.renderTo);
        }
        //如果配置了autoShow，并且不处于容器内，那么立即执行展示
        if (me.autoShow &amp;&amp; !me.isContained) {
            me.show();
        }
        if (Ext.isDefined(me.disabledClass)) {
            if (Ext.isDefined(Ext.global.console)) {
                Ext.global.console.warn('Ext.Component: disabledClass has been deprecated. Please use disabledCls.');
            }
            me.disabledCls = me.disabledClass;
            delete me.disabledClass;
        }
    },

    initComponent: function () {
        //再一次调用了constructPlugins()，可以让子类在自己的initComponent()里面添加新的插件配置
        this.plugins = this.constructPlugins();
        //设置组件的初始大小
        this.setSize(this.width, this.height);
    },
    //获取组件的状态，例如大小、位置等信息，子类可以扩展以添加任何定制的、需要持久化的信息
    getState: function() {
        var me = this,
            state = null,
            sizeModel = me.getSizeModel();

        if (sizeModel.width.configured) {
            state = me.addPropertyToState(state, 'width');
        }
        if (sizeModel.height.configured) {
            state = me.addPropertyToState(state, 'height');
        }
        return state;
    },
    //把某个属性添加到状态中
    addPropertyToState: function (state, propName, value) {
        var me = this,
            len = arguments.length;
        if (len == 3 || me.hasOwnProperty(propName)) {
            if (len &lt; 3) {
                value = me[propName];
            }
            if (value !== me.initialConfig[propName]) {
                (state || (state = {}))[propName] = value;
            }
        }
        return state;
    },
    //如何展示组件，缺省适配
    show: Ext.emptyFn,
    //生命周期模板方法：隐藏组件时
    //缺省实现：更新布局
    onHide: function() {
        this.updateLayout({ isRoot: false });
    },
    //生命周期模板方法：显示组件时
    //缺省实现：更新布局
    onShow : function() {
        this.updateLayout({ isRoot: false });
    },
    //构造一个插件，如果已经构造，简单的返回查询对象
    constructPlugin: function(plugin) {
        
        if (plugin.ptype &amp;&amp; typeof plugin.init != 'function') {
            plugin.cmp = this;
            plugin = Ext.PluginManager.create(plugin);
        }
        else if (typeof plugin == 'string') {
            plugin = Ext.PluginManager.create({
                ptype: plugin,
                cmp: this
            });
        }
        return plugin;
    },
    //构造所有配置的插件
    constructPlugins: function() {
        var me = this,
            plugins,
            result = [],
            i, len;

        if (me.plugins) {
            plugins = Ext.isArray(me.plugins) ? me.plugins : [ me.plugins ];
            for (i = 0, len = plugins.length; i &lt; len; i++) {
                result[i] = me.constructPlugin(plugins[i]);
            }
            return result;
        }
    },
    // @private
    //初始化插件，就是调用插件的init方法并入参当前组件
    initPlugin : function(plugin) {
        plugin.init(this);

        return plugin;
    },
    updateAria: Ext.emptyFn,
    //使用当前组件作为参数组件的浮动管理器
    registerFloatingItem: function(cmp) {
        var me = this;
        if (!me.floatingDescendants) {
            me.floatingDescendants = new Ext.ZIndexManager(me);
        }
        me.floatingDescendants.register(cmp);
    },
    unregisterFloatingItem: function(cmp) {
        var me = this;
        if (me.floatingDescendants) {
            me.floatingDescendants.unregister(cmp);
        }
    },
    //布局暂停计数器
    layoutSuspendCount: 0,
    //暂停当前组件的布局
    suspendLayouts: function () {
        var me = this;
        if (!me.rendered) { //如果当前组件尚未渲染，不做任何处理
            return;
        }
        if (++me.layoutSuspendCount == 1) {
            me.suspendLayout = true;
        }
    },
    //恢复当前组件的布局
    resumeLayouts: function (flushOptions) {
        var me = this;
        if (!me.rendered) { //如果当前组件尚未渲染，不做任何处理
            return;
        }
        if (! --me.layoutSuspendCount) {
            //如果计数器为0，那么需要更新当前组件的布局
            me.suspendLayout = false;
            if (flushOptions &amp;&amp; !me.isLayoutSuspended()) {
                me.updateLayout(flushOptions);
            }
        }
    },
    //初始化事件的私有方法
    initEvents : function() {
        var me = this,
            afterRenderEvents = me.afterRenderEvents,
            el, //注意这个闭包变量 ➀ 
            property,
            //为元素添加受管的监听器
            fn = function(listeners){
                //回调函数立即使用闭包变量的实时值 ➃
                me.mon(el, listeners);
            };
        //渲染后事件映射：{元素属性:[监听器1,监听器2]}
        if (afterRenderEvents) {
            for (property in afterRenderEvents) {
                if (afterRenderEvents.hasOwnProperty(property)) {
                    el = me[property]; //闭包变量的值改变 ➁
                    if (el &amp;&amp; el.on) {
                        //执行回调函数 ➂
                        Ext.each(afterRenderEvents[property], fn);
                    }
                }
            }
        }
        me.addFocusListener();
    },
    //添加焦点监听器
    addFocusListener: function() {
        var me = this,
            focusEl = me.getFocusEl(),//得到当前组件的焦点元素
            needsTabIndex;
        //不仅仅是表单，所有容器均可能是支持获取焦点，例如面板、工具栏、窗口
        //通常，作为焦点元素的DIV元素无法接收焦点，但如果调用了FocusManager，则其非缺省的导航处理器（navigation handlers ）
        //将明确的使面板、容器或者FieldSet获取焦点
        
        //如果当前组件具有焦点元素，可能需要为其添加focus、blur事件：
        if (focusEl) {
            // getFocusEl()方法可能返回一个组件，如果作为容器的this希望把焦点管理代理给它的子代组件 
            // Window组件可以通过配置defaultFocus为它的一个按钮来实现这种代理
            if (focusEl.isComponent) {
                return focusEl.addFocusListener(); //代理
            }
            //如果焦点元素本身就是可聚焦的(focusable)，那么总是需要添加focus监听器处理组件的聚焦
            //如果焦点元素是不可聚集的，除非启用了FocusManager，否则不需要添加focus监听器
            needsTabIndex = focusEl.needsTabIndex();
            if (!me.focusListenerAdded &amp;&amp; (!needsTabIndex || Ext.FocusManager.enabled)) {
                if (needsTabIndex) {
                    focusEl.dom.tabIndex = -1;
                }
                focusEl.on({
                    focus: me.onFocus,
                    blur: me.onBlur,
                    scope: me
                });
                me.focusListenerAdded = true;
            }
        }
    },
    //获取当前组件关联的焦点元素，缺省适配
    getFocusEl: Ext.emptyFn,
    //是否可聚焦
    isFocusable: function(c) {
        var me = this,
            focusEl;
        if ((me.focusable !== false) &amp;&amp; (focusEl = me.getFocusEl()) &amp;&amp; me.rendered &amp;&amp; !me.destroying &amp;&amp; !me.isDestroyed &amp;&amp; !me.disabled &amp;&amp; me.isVisible(true)) {
            if (focusEl.isComponent) {
                return focusEl.isFocusable();
            }

            return focusEl &amp;&amp; focusEl.dom &amp;&amp; focusEl.isVisible();
        }
    },
    //聚焦前模板方法
    preFocus: Ext.emptyFn,
    //聚焦时模板方法
    onFocus: function(e) {
        var me = this,
            focusCls = me.focusCls,
            focusEl = me.getFocusEl();
        if (!me.disabled) {
            me.preFocus(e);
            if (focusCls &amp;&amp; focusEl) {
                focusEl.addCls(me.addClsWithUI(focusCls, true));//添加聚焦CSS样式
            }
            if (!me.hasFocus) {
                me.hasFocus = true;
                me.fireEvent('focus', me, e); //发布聚焦事件
            }
        }
    },
    //失焦前模板方法
    beforeBlur : Ext.emptyFn,
    //失焦时模板方法
    onBlur : function(e) {
        var me = this,
            focusCls = me.focusCls,
            focusEl = me.getFocusEl();

        if (me.destroying) {
            return;
        }

        me.beforeBlur(e);
        if (focusCls &amp;&amp; focusEl) {
            focusEl.removeCls(me.removeClsWithUI(focusCls, true));//移除聚焦CSS样式
        }
        if (me.validateOnBlur) {
            me.validate();    //执行验证
        }
        me.hasFocus = false;
        me.fireEvent('blur', me, e); //发布失焦事件
        me.postBlur(e);
    },
    //失焦后模板方法
    postBlur : Ext.emptyFn,
    getId : function() {
        return this.id || (this.id = 'ext-comp-' + (this.getAutoId()));
    },
    //得到当前组件的itemId，如果没有，则返回id
    getItemId : function() {
        return this.itemId || this.id;
    },
    //得到当前组件的封装（顶级）元素
    getEl : function() {
        return this.el;
    },
    //用于确定html、contentEl、items等HTML插入到哪边
    getTargetEl: function() {
        return this.frameBody || this.el;//如果具有frameBody，则frameBody作为targetEl，否则使用顶级元素
    },

    /**
     * 更新当前组件的内容区域（即targetEl）
     * @param {String/Object} htmlOrData 如果组件配置了tpl模板，则使用该参数作为上下文填充模板，否则作为HTML更新内如区域
     * @param {Boolean} [loadScripts=false] 仅当使用html配置时合法
     * @param {Function} [callback] 仅当使用html配置时合法，加载脚本完毕后的回调
     */
    update : function(htmlOrData, loadScripts, cb) {
        var me = this;

        if (me.tpl &amp;&amp; !Ext.isString(htmlOrData)) {
            me.data = htmlOrData;
            if (me.rendered) {
                //填充模板，并覆盖targetEl的innerHTML
                me.tpl[me.tplWriteMode](me.getTargetEl(), htmlOrData || {});
            }
        } else {
            me.html = Ext.isObject(htmlOrData) ? Ext.DomHelper.markup(htmlOrData) : htmlOrData;
            if (me.rendered) {
                //更新元素的innerHTML
                me.getTargetEl().update(me.html, loadScripts, cb);
            }
        }
        if (me.rendered) {
            //更新组件的布局
            me.updateLayout();
        }
    },
    //模板方法：第一次使用初始大小渲染后
    onBoxReady: function(){
        var me = this;
        if (me.disableOnBoxReady) {
            me.onDisable();
        } else if (me.enableOnBoxReady) {
            me.onEnable();
        }
        //初始化：可改变大小
        if (me.resizable) {
            me.initResizable(me.resizable);
        }
        //初始化：可拖拽。必须在可改变大小之后
        if (me.draggable) {
            me.initDraggable();
        }
    },
    //启用组件
    enable: function(silent) {
        var me = this;
        delete me.disableOnBoxReady;
        me.removeCls(me.disabledCls);
        if (me.rendered) {
            me.onEnable();
        } else {
            me.enableOnBoxReady = true;
        }
        me.disabled = false;
        delete me.resetDisable;
        //silent用于阻止事件触发
        if (silent !== true) {
            me.fireEvent('enable', me);
        }
        return me;
    },
    //禁用组件
    disable: function(silent) {
        var me = this;
        delete me.enableOnBoxReady;
        me.addCls(me.disabledCls);
        if (me.rendered) {
            me.onDisable();
        } else {
            me.disableOnBoxReady = true;
        }
        me.disabled = true;
        //silent用于阻止事件触发
        if (silent !== true) {
            delete me.resetDisable;
            me.fireEvent('disable', me);
        }

        return me;
    },
    //模板方法：在启用后执行
    //缺省实现：去除遮罩
    onEnable: function() {
        if (this.maskOnDisable) {
            this.el.dom.disabled = false;
            this.unmask();
        }
    },
    //模板方法：在禁用后执行
    onDisable : function() {
        var me = this,
            focusCls = me.focusCls,
            focusEl = me.getFocusEl();
        //清除聚焦
        if (focusCls &amp;&amp; focusEl) {
            focusEl.removeCls(me.removeClsWithUI(focusCls, true));
        }
        //添加遮罩
        if (me.maskOnDisable) {
            me.el.dom.disabled = true;
            me.mask();
        }
    },
    //添加遮罩
    mask: function() {
        var box = this.lastBox,
            target = this.getMaskTarget(),
            args = [];
        if (box) {
            args[2] = box.height;
        }
        target.mask.apply(target, args);
    },
    //清除遮罩
    unmask: function() {
        this.getMaskTarget().unmask();
    },
    //遮罩的目标元素
    getMaskTarget: function(){
        return this.el;
    },
    //添加监听器
    addListener : function(element, listeners, scope, options) {
        var me = this,
            fn,
            option;
        if (Ext.isString(element) &amp;&amp; (Ext.isObject(listeners) || options &amp;&amp; options.element)) {
            if (options.element) {
                fn = listeners;

                listeners = {};
                listeners[element] = fn;
                element = options.element;
                if (scope) {
                    listeners.scope = scope;
                }
                for (option in options) {
                    if (options.hasOwnProperty(option)) {
                        if (me.eventOptionsRe.test(option)) {
                            listeners[option] = options[option];
                        }
                    }
                }
            }
            if (me[element] &amp;&amp; me[element].on) {
                me.mon(me[element], listeners);
            } else {
                me.afterRenderEvents = me.afterRenderEvents || {};
                if (!me.afterRenderEvents[element]) {
                    me.afterRenderEvents[element] = [];
                }
                me.afterRenderEvents[element].push(listeners);
            }
        }
        return me.mixins.observable.addListener.apply(me, arguments);
    },
    //移除受管的事件监听器
    removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
        var me = this,
            element = managedListener.options ? managedListener.options.element : null;

        if (element) {
            element = me[element];
            if (element &amp;&amp; element.un) {
                if (isClear || (managedListener.item === item &amp;&amp; managedListener.ename === ename &amp;&amp; (!fn || managedListener.fn === fn) &amp;&amp; (!scope || managedListener.scope === scope))) {
                    element.un(managedListener.ename, managedListener.fn, managedListener.scope);
                    if (!isClear) {
                        Ext.Array.remove(me.managedListeners, managedListener);
                    }
                }
            }
        } else {
            return me.mixins.observable.removeManagedListenerItem.apply(me, arguments);
        }
    },
    //模板方法：被添加到容器后
    //缺省实现：发布added事件
    onAdded : function(container, pos) {
        var me = this;
        me.ownerCt = container;
        if (me.hasListeners.added) {
            me.fireEvent('added', me, container, pos);
        }
    },
    //模板方法：被从容器中移除后
    //缺省实现：发布removed事件
    onRemoved : function(destroying) {
        var me = this;
        if (me.hasListeners.removed) {
            me.fireEvent('removed', me, me.ownerCt);
        }
        delete me.ownerCt;     //删除父容器属性
        delete me.ownerLayout; //删除父布局属性
    },
    //生命周期模板方法：在销毁前的动作
    //返回false可以取消销毁
    beforeDestroy : Ext.emptyFn,
    //模板方法，当修改了组件大小后
    onResize : Ext.emptyFn,
    //设置组件大小
    setSize : function(width, height) {
        var me = this;
        if (width &amp;&amp; typeof width == 'object') {
            height = width.height;
            width  = width.width;
        }
        if (typeof width == 'number') {
            me.width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
        } else if (width === null) {
            delete me.width;
        }
        if (typeof height == 'number') {
            me.height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
        } else if (height === null) {
            delete me.height;
        }
        //如果尚未渲染，仅仅设置属性即可，否则：
        if (me.rendered &amp;&amp; me.isVisible()) {
            //如果改变了当前组件的大小，那么就不能作为布局的根，因为对父容器会产生影响
            //更新布局
            me.updateLayout({
                isRoot: false
            });
        }
        return me;
    },
    //判断当前组件是否布局的根    
    isLayoutRoot: function() {
        var me = this,
            ownerLayout = me.ownerLayout;
        //对于浮动组件，或者明确设置了_isLayoutRoot的组件，返回true
        if (!ownerLayout || me._isLayoutRoot || me.floating) {
            return true;
        }
        //让父布局判断当前组件是否布局根
        return ownerLayout.isItemLayoutRoot(me);
    },
    //判断布局是否被暂停
    //只要当前组件或者其任意布局祖代的布局被暂停，则返回true
    isLayoutSuspended: function () {
        var comp = this,
            ownerLayout;
        while (comp) {
            if (comp.layoutSuspendCount || comp.suspendLayout) {
                return true;
            }

            ownerLayout = comp.ownerLayout;
            if (!ownerLayout) {
                break;
            }
            comp = ownerLayout.owner;
        }

        return false;
    },
    //更新当前组件的布局
    updateLayout: function (options) {
        var me = this,
            defer,
            isRoot = options &amp;&amp; options.isRoot;

        if (!me.rendered || me.layoutSuspendCount || me.suspendLayout) {
            return;
        }
        //对于隐藏组件，取消布局
        if (me.hidden) {
            Ext.AbstractComponent.cancelLayout(me);
        } else if (typeof isRoot != 'boolean') {
            isRoot = me.isLayoutRoot();
        }
        // 如果当前组件不是布局根，且父布局关心当前组件的尺寸变化，那么父布局会统一处理
        if (isRoot || !me.ownerLayout || !me.ownerLayout.onContentChange(me)) {
            //否则，自行处理
            if (!me.isLayoutSuspended()) {
                defer = (options &amp;&amp; options.hasOwnProperty('defer')) ? options.defer : me.deferLayouts;
                Ext.AbstractComponent.updateLayout(me, defer); //调用静态函数执行布局
            }
        }
    },
    //执行组件布局，该方法开放调用，任何时候，改变组件内容均需要考虑调用
    doComponentLayout : function() {
        this.updateLayout();
        return this;
    },
    //重设布局对象
    setComponentLayout : function(layout) {
        var currentLayout = this.componentLayout;//当前布局是componentLayout属性
        if (currentLayout &amp;&amp; currentLayout.isLayout &amp;&amp; currentLayout != layout) {
            currentLayout.setOwner(null); //取消当前布局对象的关联
        }
        this.componentLayout = layout;
        layout.setOwner(this); //注意布局对象是被组件独占的
    },
    //获取当前的组件布局，如果不存在，则创建之
    getComponentLayout : function() {
        var me = this;
        if (!me.componentLayout || !me.componentLayout.isLayout) {
            me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent'));
        }
        return me.componentLayout;
    },
    //组件布局后调用的模板方法
    //缺省实现
    afterComponentLayout: function(width, height, oldWidth, oldHeight) {
        var me = this,
            floaters, len, i, floater;
        if (++me.componentLayoutCounter === 1) {
            me.afterFirstLayout(width, height);//调用第一次组件布局的模板方法
        }
        //如果浮动子组件没有显示，立即显示
        if (me.floatingItems) {
            floaters = me.floatingItems.items;
            len = floaters.length;
            for (i = 0; i &lt; len; i++) {
                floater = floaters[i];
                if (!floater.rendered &amp;&amp; floater.autoShow) {
                    floater.show();
                }
            }
        }
        //如果大小发生改变，发布resize事件
        if (me.hasListeners.resize &amp;&amp; (width !== oldWidth || height !== oldHeight)) {
            me.fireEvent('resize', me, width, height, oldWidth, oldHeight);
        }
    },
    //布局前执行的模板方法，返回false禁止布局
    beforeComponentLayout: function(width, height) {
        return true;
    },
    //设置组件的位置
    setPosition : function(x, y, animate) {
        var me = this,
            pos = me.beforeSetPosition.apply(me, arguments);//设置位置前模板方法
        if (pos &amp;&amp; me.rendered) {
            pos = me.convertPosition(pos);
            if (pos.left !== me.el.getLeft() || pos.top !== me.el.getTop()) {
                if (animate) { //可选的动画
                    me.stopAnimation();
                    me.animate(Ext.apply({
                        duration: 1000,
                        listeners: {
                            afteranimate: Ext.Function.bind(me.afterSetPosition, me, [pos.left, pos.top])
                        },
                        to: pos
                    }, animate));
                } else {
                    if (pos.left !== undefined &amp;&amp; pos.top !== undefined) {
                        me.el.setLeftTop(pos.left, pos.top);
                    } else if (pos.left !== undefined) {
                        me.el.setLeft(pos.left);
                    } else if (pos.top !==undefined) {
                        me.el.setTop(pos.top);
                    }
                    me.afterSetPosition(pos.left, pos.top);
                }
            }
        }
        return me;
    },
    //设置位置前模板
    beforeSetPosition: function (x, y, animate) {
        var pos, x0;
        if (!x || Ext.isNumber(x)) {
            pos = { x: x, y : y, anim: animate };
        } else if (Ext.isNumber(x0 = x[0])) {
            pos = { x : x0, y : x[1], anim: y };
        } else {
            pos = { x: x.x, y: x.y, anim: y };
        }
        pos.hasX = Ext.isNumber(pos.x);
        pos.hasY = Ext.isNumber(pos.y);
        this.x = pos.x;
        this.y = pos.y;
        return (pos.hasX || pos.hasY) ? pos : null;
    },
    //设置位置后模板
    //缺省实现：如果支持，触发move事件
    afterSetPosition: function(x, y) {
        var me = this;
        me.onPosition(x, y);
        if (me.hasListeners.move) {
            me.fireEvent('move', me, x, y);
        }
    },
    //组件位置变化后执行的模板方法
    onPosition: Ext.emptyFn,
    //设置宽高，就是在el元素上进行操作
    setWidth : function(width) {
        return this.setSize(width);
    },
    setHeight : function(height) {
        return this.setSize(undefined, height);
    },
    getSize : function() {
        return this.el.getSize();
    },
    getWidth : function() {
        return this.el.getWidth();
    },
    getHeight : function() {
        return this.el.getHeight();
    },
    //设置当前组件在父容器的停靠位置
    setDocked : function(dock, layoutParent) {
        var me = this;

        me.dock = dock;
        if (layoutParent &amp;&amp; me.ownerCt &amp;&amp; me.rendered) {
            me.ownerCt.updateLayout();//导致更新父容器的布局
        }
        return me;
    },
    //生命周期模板方法：销毁后执行
    //缺省实现
    onDestroy : function() {
        var me = this;
        if (me.monitorResize &amp;&amp; Ext.EventManager.resizeEvent) {
            Ext.EventManager.resizeEvent.removeListener(me.setSize, me);
        }
        //销毁组件布局、遮罩、浮动后代
        Ext.destroy(
            me.componentLayout,
            me.loadMask,
            me.floatingDescendants
        );
    },
    //销毁组件
    destroy : function() {
        var me = this,
            selectors = me.renderSelectors,
            selector,
            el;
        if (!me.isDestroyed) {
            //如果beforedestroy事件处理返回false，则不销毁
            if (!me.hasListeners.beforedestroy || me.fireEvent('beforedestroy', me) !== false) {
                me.destroying = true;
                me.beforeDestroy();//销毁前模板
                //对于浮动组件
                if (me.floating) {
                    delete me.floatParent;//清除浮动父组件引用
                    //从ZIndexManager移除自己
                    if (me.zIndexManager) {
                        me.zIndexManager.unregister(me);
                    }
                } else if (me.ownerCt &amp;&amp; me.ownerCt.remove) {
                    me.ownerCt.remove(me, false);
                }
                me.onDestroy();//销毁后模板
                Ext.destroy(me.plugins); //尝试销毁所有插件
                //发布销毁事件
                if (me.hasListeners.destroy) {
                    me.fireEvent('destroy', me);
                }
                //接触组件注册
                Ext.ComponentManager.unregister(me);
                //销毁组件状态
                me.mixins.state.destroy.call(me);
                //清除组件监听器
                me.clearListeners();
                //确保在移除所有事件监听器后，清除所有对DOM元素的引用，避免内存泄漏
                if (me.rendered) {//如果尚未渲染，任何DOM结构都未创建，无需处理
                    if (!me.preserveElOnDestroy) {
                        me.el.remove(); //删除组件的顶级元素
                    }
                    me.mixins.elementCt.destroy.call(me); //删除所有childEls
                    if (selectors) {//如果配置了renderSelectors
                        for (selector in selectors) {
                            if (selectors.hasOwnProperty(selector)) {
                                el = me[selector];
                                //对于所有作为成员变量的元素，需要解除关系
                                if (el) {
                                    delete me[selector];
                                    el.remove();
                                }
                            }
                        }
                    }
                    delete me.el; //删除顶级元素
                    delete me.frameBody; //删除组件体
                    delete me.rendered;
                }
                me.destroying = false;
                me.isDestroyed = true;
            }
        }
    }
}, /*当组件类被创建后，执行的回调，this指向新创建的类*/function() {
    var AbstractComponent = this;
    //方法别名
    AbstractComponent.createAlias({
        on: 'addListener',
        prev: 'previousSibling',
        next: 'nextSibling'
    });
    //静态方法快捷方式
    Ext.resumeLayouts = function (flush) {
        AbstractComponent.resumeLayouts(flush);
    };
    Ext.suspendLayouts = function () {
        AbstractComponent.suspendLayouts();
    };

    Ext.batchLayouts = function(fn, scope) {
        AbstractComponent.suspendLayouts();
        fn.call(scope);
        AbstractComponent.resumeLayouts(true);
    };
});</pre>
<div class="blog_h3"><span class="graybg">Ext.Component</span></div>
<p>该类是ExtJS的所有组件的基类，保护：</p>
<ol>
<li>基本的隐藏/显示逻辑</li>
<li>基本的启用/禁用逻辑</li>
<li>组件尺寸控制行为</li>
</ol>
<p>在ExtJS中，每个组件类都具有一个称为xtype的别名，可以用于组件的延迟创建。Component类提供了一下成员：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">配置项/属性/方法 </td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>{autoScroll}</td>
<td>Boolean = false。如果设置为true，那么在组件布局元素上使用overflow : 'auto' 样式，这导致必要时出现滚动条；如果设置为false，会自动剪除溢出的内容，不显示滚动条。该配置不应该与overflowX、 overflowY一起使用</td>
</tr>
<tr>
<td>{columnWidth}</td>
<td>Number/String，在Column布局中，定义当前组件的列宽度，可以设置为数字或者百分比</td>
</tr>
<tr>
<td>{region}</td>
<td>String，在Border布局中用于指定该组件所在的位置：center,north,south,east,west被支持</td>
</tr>
<tr>
<td>{flex}</td>
<td>Number，在HBox、VBox等布局中用于指定组件所占据的尺寸比例</td>
</tr>
<tr>
<td>{draggable}</td>
<td>Boolean/Object = false，覆盖AbstractComponent。设置为true，可以让浮动组件可拖拽（其封装元素作为drag handle）。亦可提供一个传递给 ComponentDragger的配置以启动拖拽：<br />
<pre class="crayon-plain-tag">Ext.create( 'Ext.Component', {
    constrain : true,
    floating : true,
    style : {
        backgroundColor : '#fff',
        border : '1px solid black'
    },
    html : '',
    draggable : {
        delegate : 'h1' //只有按在该元素上，组件才可以拖拽
    }
} ).show();</pre>
</td>
</tr>
<tr>
<td>{resizable}</td>
<td>Boolean/Object，如果设置为true，组件渲染后为其添加一个Resizer对象。亦可指定Resizer构造器需要的配置对象</td>
</tr>
<tr>
<td>{floating}</td>
<td>
<p>Boolean/Object = false，覆盖AbstractComponent。</p>
<p>如果指定为true，组件将脱离正常文档流，使用CSS绝对定位。Window、Menu等组件默认即是浮动的。通过编程方式手工render()的组件会在全局ZIndexManager（即Ext.WindowManager单例）中注册。</p>
<p><strong>作为容器子组件的浮动组件</strong></p>
<p>浮动组件会通过ownerCt链条来寻找ZIndexManager——依次上溯，直到找到一个本身是floating的父容器，浮动组件总是在它的浮动父容器上面显示，如果找不到floating的父容器，则使用全局ZIndexManager</p>
<p>如果配置为浮动组件，在渲染阶段会它寻找一个ZIndexManager来管理自己的z-index，以正确位置与其它浮动组件的层叠（Stack）关系。当浮动组件的toFront()方法被调用时，其ZIndexManager会把它放在最前面</p>
<p><span style="background-color: #c0c0c0;">浮动组件不参与容器的布局</span>，因此它不会随着容器的渲染而渲染，需要手工调用show()才会进行渲染，在渲染后ownerCt属性被删除， floatParent属性被设置，执行其祖先floating容器或者undefined</p>
</td>
</tr>
<tr>
<td>{formBind}</td>
<td>Boolean = false，如果组件位于FormPanel内，设置该配置为true导致组件的启用/禁用状态取决于表单的验证有效性</td>
</tr>
<tr>
<td>{overflowX}</td>
<td>String = 'hidden'，支持auto、scroll、hidden</td>
</tr>
<tr>
<td>{overflowY}</td>
<td>String = 'hidden'，支持auto、scroll、hidden</td>
</tr>
<tr>
<td>{resizeHandles}</td>
<td>String = 'all'，Resizer的handles配置项，仅 resizable = true时有意义</td>
</tr>
<tr>
<td>{toFrontOnShow}</td>
<td>Boolean = true，如果设置为真，在浮动组件show()时，自动带到最前面</td>
</tr>
<tr>
<td>floatParent</td>
<td>readonly Ext.Container，仅对浮动组件有意义，手工render()的浮动组件没有floatParent</td>
</tr>
<tr>
<td>zIndexManager</td>
<td>readonly Ext.ZIndexManager，仅对浮动组件有意义，渲染后被设置</td>
</tr>
<tr>
<td>zIndexParent</td>
<td>readonly Ext.Container，</td>
</tr>
<tr>
<td>bubble()</td>
<td>Ext.Component ( Function fn, [Object scope], [Array args] )。向上（getBubbleTarget）传播执行一个方法，如果任何一次执行返回false，停止传播</td>
</tr>
<tr>
<td>cancelFocus()</td>
<td>取消任何在当前组件上的延迟聚集</td>
</tr>
<tr>
<td>cloneConfig()</td>
<td>Ext.Component cloneConfig( Object overrides ) ，克隆当前组件，使用overrides 覆盖原来的配置项</td>
</tr>
<tr>
<td>focus()</td>
<td>Ext.Component ( [Boolean selectText], [Boolean/Number delay] ) ，尝试聚焦当前组件，返回聚焦的组件。可选的延迟聚焦</td>
</tr>
<tr>
<td>getBox()</td>
<td>Object ( [Boolean local] )，得到组件的度量对象，形式：{x, y, width, height}</td>
</tr>
<tr>
<td>getPosition()</td>
<td>Number[] ( [Boolean local] ) ，返回组件的位置，如果local=true，返回的位置相对于元素的offsetParent</td>
</tr>
<tr>
<td>hide()</td>
<td>
<p>Ext.Component( [String/Ext.Element/Ext.Component animateTarget], [Function callback], [Object scope] ) 。隐藏组件，根据hideMode设置隐藏模式</p>
</td>
</tr>
<tr>
<td>scrollBy()</td>
<td>
<p>void ( Number/Number[]/Object deltaX, Number/Boolean/Object deltaY, Boolean/Object animate )</p>
<p>滚动组件的targetEl，可选动画</p>
</td>
</tr>
<tr>
<td>setLoading()</td>
<td>Ext.LoadMask( Boolean/Object/String load, [Boolean targetEl] )。显示一个LoadMask。如果targetEl=true，则遮罩targetEl而不是el，对于面板，targetEl对应面板体</td>
</tr>
<tr>
<td>setOverflowXY()</td>
<td>Ext.Component ( String overflowX, String overflowY )。在组件内容元素上设置overflow样式</td>
</tr>
<tr>
<td>setPagePosition()</td>
<td>Ext.Component ( Number x, Number y, [Boolean/Object animate] )。设置组件的page XY 位置</td>
</tr>
<tr>
<td>show()</td>
<td>
<p>Ext.Component ( [String/Ext.Element animateTarget], [Function callback], [Object scope] )</p>
<p>显示当前组件，如果floating或者autoRender为true，则先render()。</p>
<p>在show()之后，浮动组件z-index被带到最前面</p>
</td>
</tr>
<tr>
<td>showAt()</td>
<td>
<p>void ( Number x, Number y, [Boolean/Object animate] )</p>
<p>在指定位置显示组件，浮动组件的x,y相对于其ownerCt（例如Menu），该方法经常用来显示上下文菜单</p>
</td>
</tr>
<tr>
<td>updateBox()</td>
<td>Ext.Component ( Object box )，设置组件el的大小位置</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>组件查询方法  </strong></td>
</tr>
<tr>
<td>findParentBy()</td>
<td>Ext.container.Container ( Function fn )。根据过滤器函数查询第一个匹配的祖先容器</td>
</tr>
<tr>
<td>findParentByType()</td>
<td>Ext.container.Container( String/Ext.Class xtype ) 。根据类型查询第一个匹配的祖先容器</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>模板方法  </strong></td>
</tr>
<tr>
<td>afterHide()</td>
<td>void ( [Function callback], [Object scope] )。当组件隐藏后调用</td>
</tr>
<tr>
<td>afterShow()</td>
<td>void ( [String/Ext.Element animateTarget], [Function callback], [Object scope] )。在组件显示后调用</td>
</tr>
<tr>
<td>beforeShow()</td>
<td>在组件显示前调用</td>
</tr>
<tr>
<td>initComponent()</td>
<td>覆盖AbstractComponent版本。该方法是组件初始化的重要阶段，所有子类应当通过覆盖此方法来提供构造逻辑（一般不通过constructor方法），任何子类的覆盖版本<span style="background-color: #c0c0c0;">必须调用父类的版本</span>。下面是生成一个动态文字按钮的例子：<br />
<pre class="crayon-plain-tag">Ext.define('DynamicButtonText', {
    extend: 'Ext.button.Button',
    initComponent: function() {
        //准备动态按钮
        this.text = new Date();
        this.renderTo = Ext.getBody();
        //调用父类构造器
        this.callParent();
    }
});
Ext.onReady(function() {
    Ext.create('DynamicButtonText');
});</pre>
</td>
</tr>
<tr>
<td>onDestroy()</td>
<td>添加销毁逻辑，在调用覆盖版本的onDestroy后，当前组件被销毁</td>
</tr>
<tr>
<td>onHide()</td>
<td>
<p>void ( [String/Ext.Element/Ext.Component animateTarget], [Function callback], [Object scope] )</p>
<p>添加隐藏逻辑，在调用覆盖版本的onHide后，当前组件被隐藏</p>
</td>
</tr>
<tr>
<td>onShow()</td>
<td>
<p>void ( [String/Ext.Element animateTarget], [Function callback], [Object scope] )</p>
<p>添加显示逻辑，在调用覆盖版本的onShow后，当前组件显示</p>
</td>
</tr>
<tr>
<td>onShowComplete()</td>
<td>
<p>void ( [Function callback], [Object scope] )</p>
<p>在afterShow()之后调用</p>
</td>
</tr>
</tbody>
</table>
<p>Ext.Component的代码分析如下：</p>
<pre class="crayon-plain-tag">Ext.define('Ext.Component', {
    alias: ['widget.component', 'widget.box'],
    extend: 'Ext.AbstractComponent',
    statics: {
        DIRECTION_TOP: 'top',
        DIRECTION_RIGHT: 'right',
        DIRECTION_BOTTOM: 'bottom',
        DIRECTION_LEFT: 'left',
        VERTICAL_DIRECTION_Re: /^(?:top|bottom)$/,
        INVALID_ID_CHARS_Re: /[\.,\s]/g
    },
    resizeHandles: 'all',
    floating: false,
    toFrontOnShow: true,
    hideMode: 'display',
    bubbleEvents: [],
    monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
    
    constructor: function(config) {
        var me = this;
        config = config || {};
        if (config.initialConfig) {
            if (config.isAction) {
                //从Ext.Action初始化
                me.baseAction = config;
            }
            config = config.initialConfig;
        }
        else if (config.tagName || config.dom || Ext.isString(config)) {
            config = {
                applyTo: config,
                id: config.id || config
            };
        }
        me.callParent([config]);//调用覆盖版本
        if (me.baseAction){
            me.baseAction.addComponent(me);
        }
    },
    initComponent: function() {
        var me = this;
        me.callParent();
        if (me.listeners) {
            me.on(me.listeners);//添加监听器
            me.listeners = null;//移除配置项
        }
        me.enableBubble(me.bubbleEvents); //启用事件冒泡
        me.mons = [];
    },
    afterRender: function() {
        var me = this;
        me.callParent();
        //设置页面位置
        if (!(me.x &amp;&amp; me.y) &amp;&amp; (me.pageX || me.pageY)) {
            me.setPagePosition(me.pageX, me.pageY);
        }
    },
    beforeRender: function () {
        var me = this,
            floating = me.floating,
            cls;
        if (floating) {
            //处理浮动组件
            me.addCls(Ext.baseCSSPrefix + 'layer');
            cls = floating.cls;
            if (cls) {
                me.addCls(cls);
            }
        }
        return me.callParent(); //调用覆盖版本
    },
    //布局后模板
    afterComponentLayout: function(){
        this.callParent(arguments);
        if (this.floating) {
            //处理浮动组件
            this.onAfterFloatLayout();
        }
    },
    //构造浮动混入
    makeFloating : function (dom) {
        this.mixins.floating.constructor.call(this, dom);
    },
    wrapPrimaryEl: function (dom) {
        if (this.floating) {
            this.makeFloating(dom);
        } else {
            this.callParent(arguments);
        }
    },
    //初始化尺寸调整器
    initResizable: function(resizable) {
        var me = this;
        resizable = Ext.apply({
            target: me,
            dynamic: false,
            constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : null),
            handles: me.resizeHandles
        }, resizable);
        resizable.target = me;
        me.resizer = new Ext.resizer.Resizer(resizable);
    },
    getDragEl: function() {
        return this.el; //被拖拽的元素
    },
    //初始化拖拽
    initDraggable: function() {
        var me = this,
            //如果拖拽目标不是封装元素本身，则从resizer.el创建一个简单组件作为拖拽目标
            dragTarget = (me.resizer &amp;&amp; me.resizer.el !== me.el) ? me.resizerComponent = new Ext.Component({
                el: me.resizer.el,
                rendered: true,
                container: me.container
            }) : me,
            ddConfig = Ext.applyIf({
                el: dragTarget.getDragEl(),
                //拖拽限制范围
                constrainTo: me.constrain ? (me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent())) : undefined
            }, me.draggable);
        if (me.constrain || me.constrainDelegate) {
            ddConfig.constrain = me.constrain;
            ddConfig.constrainDelegate = me.constrainDelegate;
        }
        //设置dd，即组件拖拽器（处理任何组件的drag行为的对象）
        me.dd = new Ext.util.ComponentDragger(dragTarget, ddConfig);
    },
    //滚动组件的targetEl
    scrollBy: function(deltaX, deltaY, animate) {
        var el;
        if ((el = this.getTargetEl()) &amp;&amp; el.dom) {
            el.scrollBy.apply(el, arguments);
        }
    },
    //设置组件位置前的模板
    beforeSetPosition: function () {
        var me = this,
            pos = me.callParent(arguments),
            adj;

        if (pos) {
            adj = me.adjustPosition(pos.x, pos.y);
            pos.x = adj.x;
            pos.y = adj.y;
        }
        return pos || null;
    },
    //设置组件位置后的模板
    afterSetPosition: function(ax, ay) {
        this.onPosition(ax, ay);
        this.fireEvent('move', this, ax, ay);
    },
    //在指定位置显示组件
    showAt: function(x, y, animate) {
        var me = this;

        if (!me.rendered &amp;&amp; (me.autoRender || me.floating)) {
            me.doAutoRender(); //如果尚未渲染，执行渲染
            me.hidden = true; //设置hidden状态，以便后续发布beforeshow、show事件
        }
        if (me.floating) {
            //对于浮动组件，设置位置
            me.setPosition(x, y, animate);
        } else {
            //否则，设置Page位置
            me.setPagePosition(x, y, animate);
        }
        me.show();
    },
    setPagePosition: function(x, y, animate) {
        var me = this,
            p,
            floatParentBox;

        if (Ext.isArray(x)) {
            y = x[1];
            x = x[0];
        }
        me.pageX = x;
        me.pageY = y;

        if (me.floating) {
            if (me.isContainedFloater()) {
                //在容器中注册的浮动组件，必须把x,y设置为相对于容器的值
                floatParentBox = me.floatParent.getTargetEl().getViewRegion();
                if (Ext.isNumber(x) &amp;&amp; Ext.isNumber(floatParentBox.left)) {
                    x -= floatParentBox.left;
                }
                if (Ext.isNumber(y) &amp;&amp; Ext.isNumber(floatParentBox.top)) {
                    y -= floatParentBox.top;
                }
            } else {
                p = me.el.translatePoints(x, y); //将文档坐标转换为页面坐标
                x = p.left;
                y = p.top;
            }

            me.setPosition(x, y, animate);
        } else {
            p = me.el.translatePoints(x, y);    //将文档坐标转换为页面坐标
            me.setPosition(p.left, p.top, animate);
        }
        return me;
    },
    //得到组件的位置
    getPosition: function(local) {
        var me = this,
            el = me.el,
            xy,
            isContainedFloater = me.isContainedFloater(),
            floatParentBox;

        //对于非浮动组件，本地位置就是相对于el的offsetParent的位置偏移量
        if ((local === true) &amp;&amp; !isContainedFloater) {
            return [el.getLocalX(), el.getLocalY()];
        }
        xy = me.el.getXY();
        //对于浮动组件，本地位置就是相对于容器的targetEl的位置偏移量
        if ((local === true) &amp;&amp; isContainedFloater) {
            floatParentBox = me.floatParent.getTargetEl().getViewRegion();
            xy[0] -= floatParentBox.left;
            xy[1] -= floatParentBox.top;
        }
        return xy;
    },
    //显示组件
    show: function(animateTarget, cb, scope) {
        var me = this,
            rendered = me.rendered;

        if (rendered &amp;&amp; me.isVisible()) {
            if (me.toFrontOnShow &amp;&amp; me.floating) {
                //如果已渲染并且是可见的，带到最前面
                me.toFront();
            }
        } else {
            //触发beforeshow，如果返回false，什么都不做
            if (me.fireEvent('beforeshow', me) !== false) {
                me.hidden = false;
                if (!rendered &amp;&amp; (me.autoRender || me.floating)) {
                    //如果尚未渲染，执行渲染
                    me.doAutoRender();
                    rendered = me.rendered;
                }
                if (rendered) {
                    //如果渲染成功
                    me.beforeShow();  //beforeShow模板
                    me.onShow.apply(me, arguments);//onShow模板
                    me.afterShow.apply(me, arguments);//afterShow模板
                }
            } else {
                me.onShowVeto();
            }
        }
        return me;
    },
    onShowVeto: Ext.emptyFn,
    //生命周期模板方法：显示前，渲染后
    beforeShow: Ext.emptyFn,
    //生命周期模板方法：显示时
    onShow: function() {
        var me = this;
        me.el.show(); //底层HTML元素的显示
        me.callParent(arguments);
        if (me.floating) {
            //处理浮动组件的尺寸
            if (me.maximized) {
                me.fitContainer();
            }
            else if (me.constrain) {
                me.doConstrain();
            }
        }
    },
    //生命周期模板方法：显示后
    afterShow: function(animateTarget, cb, scope) {
        var me = this,
            fromBox,
            toBox,
            ghostPanel;
        animateTarget = animateTarget || me.animateTarget;
        if (!me.ghost) {
            animateTarget = null;
        }
        if (animateTarget) {
            //处理动画
            animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
            toBox = me.el.getBox();
            fromBox = animateTarget.getBox();
            me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
            ghostPanel = me.ghost();
            ghostPanel.el.stopAnimation();
            ghostPanel.el.setX(-10000);
            ghostPanel.el.animate({
                from: fromBox,
                to: toBox,
                listeners: {
                    afteranimate: function() {
                        delete ghostPanel.componentLayout.lastComponentSize;
                        me.unghost();
                        me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
                        //动画完毕后，继续调用下一模板
                        me.onShowComplete(cb, scope);
                    }
                }
            });
        }
        else {
            //直接调用下一模板
            me.onShowComplete(cb, scope);
        }
    },
    //生命周期模板方法：显示（包括动画效果）完成
    onShowComplete: function(cb, scope) {
        var me = this;
        if (me.floating) {
            me.toFront();
            me.onFloatShow();
        }
        Ext.callback(cb, scope || me);
        me.fireEvent('show', me);  //发布show事件
        delete me.hiddenByLayout;
    },
    //隐藏组件
    hide: function() {
        var me = this;
        me.showOnParentShow = false;
        //触发beforehide事件，如果false什么都不做
        if (!(me.rendered &amp;&amp; !me.isVisible()) &amp;&amp; me.fireEvent('beforehide', me) !== false) {
            me.hidden = true;
            if (me.rendered) {
                //如果已经渲染过，则调用onHide模板
                me.onHide.apply(me, arguments);
            }
        }
        return me;
    },
    //生命周期模板方法：隐藏
    onHide: function(animateTarget, cb, scope) {
        var me = this,
            ghostPanel,
            toBox;
        animateTarget = animateTarget || me.animateTarget;
        if (!me.ghost) {
            animateTarget = null;
        }
        if (animateTarget) {
            //处理动画
            animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
            ghostPanel = me.ghost();
            ghostPanel.el.stopAnimation();
            toBox = animateTarget.getBox();
            toBox.width += 'px';
            toBox.height += 'px';
            ghostPanel.el.animate({
                to: toBox,
                listeners: {
                    afteranimate: function() {
                        delete ghostPanel.componentLayout.lastComponentSize;
                        ghostPanel.el.hide();
                        me.afterHide(cb, scope);//动画完毕后，继续调用下一模板
                    }
                }
            });
        }
        me.el.hide(); //隐藏组件的元素
        if (!animateTarget) {
            me.afterHide(cb, scope);//直接调用下一模板
        }
    },
    //声明周期模板方法：隐藏后
    afterHide: function(cb, scope) {
        var me = this;
        delete me.hiddenByLayout;
        Ext.AbstractComponent.prototype.onHide.call(this);
        Ext.callback(cb, scope || me);
        me.fireEvent('hide', me); //发布hide事件
    },
    //声明周期模板方法：销毁时
    onDestroy: function() {
        var me = this;
        if (me.rendered) {
            //如果已经渲染，则销毁组件附加的对象
            Ext.destroy(
                me.proxy,
                me.proxyWrap,
                me.resizer,
                me.resizerComponent
            );
        }
        delete me.focusTask; //删除聚焦任务
        me.callParent();
    },
    //处理组件聚焦
    focus: function(selectText, delay) {
        var me = this,
            focusEl,
            focusElDom,
            containerScrollTop;
        if (delay) {
            //延迟聚焦
            if (!me.focusTask) {
                me.focusTask = new Ext.util.DelayedTask(me.focus);
            }
            me.focusTask.delay(Ext.isNumber(delay) ? delay : 10, null, me, [selectText, false]);
            return me;
        }
        if (me.rendered &amp;&amp; !me.isDestroyed &amp;&amp; me.isVisible(true) &amp;&amp; (focusEl = me.getFocusEl())) {
            if (focusEl.isComponent) {
                //聚焦行为代理给子代组件
                return focusEl.focus(selectText, delay);
            }
            if ((focusElDom = focusEl.dom)) {
                if (focusEl.needsTabIndex()) {
                    focusElDom.tabIndex = -1; //设置tabIndex
                }
                if (me.floating) {
                    containerScrollTop = me.container.dom.scrollTop;
                }
                //直接使用原生HTML聚焦
                focusEl.focus();
                if (selectText === true) {
                    focusElDom.select();
                }
            }
            if (me.floating) {
                //对于浮动组件，聚集意味着位于最前
                me.toFront(true);
                if (containerScrollTop !== undefined) {
                    me.container.dom.scrollTop = containerScrollTop;
                }
            }
        }
        return me;
    },
    //取消延迟的聚焦
    cancelFocus: function() {
        var task = this.focusTask;
        if (task) {
            task.cancel();
        }
    },
    //失焦处理
    blur: function() {
        var focusEl;
        if (this.rendered &amp;&amp; (focusEl = this.getFocusEl())) {
            focusEl.blur();
        }
        return this;
    },
    //当大小改变时执行的模板方法
    onResize: Ext.emptyFn,
    //获取事件传播的目标
    getBubbleTarget: function() {
        return this.ownerCt || this.floatParent;
    },
    //得到“内容目标”
    getContentTarget: function() {
        return this.el;
    },
    //克隆组件
    cloneConfig: function(overrides) {
        overrides = overrides || {};
        var id = overrides.id || Ext.id(),
            cfg = Ext.applyIf(overrides, this.initialConfig),//获取当前组件的初始配置
            self;

        cfg.id = id;

        self = Ext.getClass(this); //获取当前组件的类型
        return new self(cfg); //调用构造器
    },
    //向上传播的执行一函数
    bubble: function(fn, scope, args) {
        var p = this;
        while (p) {
            if (fn.apply(scope || p, args || [p]) === false) {
                break;
            }
            p = p.getBubbleTarget();
        }
        return this;
    }
});</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/extjs-4-components">ExtJS 4的组件机制</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/extjs-4-components/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ExtJS 4的事件系统</title>
		<link>https://blog.gmem.cc/extjs-4-event-system</link>
		<comments>https://blog.gmem.cc/extjs-4-event-system#comments</comments>
		<pubDate>Thu, 04 Apr 2013 02:48:04 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ExtJS]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=5499</guid>
		<description><![CDATA[<p>ExtJS事件机制简介 ExtJS使用以Observable混入类为核心的观察者模式，提供了统一的事件发布、监听编程接口，把浏览器的原生事件统一抽象为EventObject。 ExtJS事件分成元素事件和组件事件两类： 元素事件：在浏览器事件机制的基础上扩展而来的，匹配不同浏览器行为的差异性，提供统一的编程接口 组件事件： 高层次的事件机制，组件事件可能由元素事件触发，也可能与之无关 除非指定监听器为延迟执行，ExtJS的事件监听器都是同步化执行的（即：fireEvent之后立即调用listener函数，而不是由浏览器“异步”的处理） 浏览器的事件机制 事件机制是用来处理无法预知何时会发生的动作的机制，比如用户单击网页链接，程序并不知道该动作何时发生，但当其发生之后应该跳转至其他页面或执行某种处理。网页中的每个HTML标签展现时都会生成DOM元素，当用户单击鼠标时，该鼠标事件就会通过操作系统事件机制传递到浏览器，接下来浏览器根据自身事件机制来判断用户单击了哪个元素， 并执行该元素已经注册的事件处理函数，然后判断其是否需要冒泡给父元素进行相关处理。  ExtJS事件基础类 Ext.util.Observable 该混入类为发布事件提供了统一的接口，子类应当具有一个events属性来包含其支持的事件，此外listeners属性则表示已经注册的监听器，Observable并不限制使用于元素或者组件，下面是该混入的简单用法示例： [crayon-69d9b17a04e74940401671/]  Observable提供了以下成员： 配置项/属性/方法  说明 {listeners} 在初始化阶段添加到当前对象的事件监听器列表，形式为：{事件名:事件处理函数}或者其它更复杂的形式，参考addListeners()方法 从ExtJS组件中直接访问元素事件，可以指定组件的某个类型为元素的属性名，例如： [crayon-69d9b17a04e7c575553477/] 某些组件通过重新封装暴露了一些元素事件，例如DataView的itemClick暴露了节点的click事件 <a class="read-more" href="https://blog.gmem.cc/extjs-4-event-system">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/extjs-4-event-system">ExtJS 4的事件系统</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">ExtJS事件机制简介</span></div>
<p>ExtJS使用以Observable混入类为核心的观察者模式，提供了统一的事件发布、监听编程接口，把浏览器的原生事件统一抽象为EventObject。</p>
<p>ExtJS事件分成<span style="background-color: #c0c0c0;">元素事件</span>和<span style="background-color: #c0c0c0;">组件事件</span>两类：</p>
<ol>
<li>元素事件：在浏览器事件机制的基础上扩展而来的，匹配不同浏览器行为的差异性，提供统一的编程接口</li>
<li>组件事件： 高层次的事件机制，组件事件可能由元素事件触发，也可能与之无关</li>
</ol>
<p>除非指定监听器为延迟执行，ExtJS的事件监听器都是同步化执行的（即：fireEvent之后立即调用listener函数，而不是由浏览器“异步”的处理）</p>
<div class="blog_h3"><span class="graybg">浏览器的事件机制</span></div>
<p>事件机制是用来处理无法预知何时会发生的动作的机制，比如用户单击网页链接，程序并不知道该动作何时发生，但当其发生之后应该跳转至其他页面或执行某种处理。网页中的每个HTML标签展现时都会生成DOM元素，当用户单击鼠标时，该鼠标事件就会通过操作系统事件机制传递到浏览器，接下来浏览器根据自身事件机制来判断用户单击了哪个元素， 并执行该元素已经注册的事件处理函数，然后判断其是否需要冒泡给父元素进行相关处理。 </p>
<div class="blog_h2"><span class="graybg">ExtJS事件基础类</span></div>
<div class="blog_h3"><span class="graybg">Ext.util.Observable</span></div>
<p>该混入类为发布事件提供了统一的接口，子类应当具有一个events属性来包含其支持的事件，此外listeners属性则表示已经注册的监听器，Observable并不限制使用于元素或者组件，下面是该混入的简单用法示例：</p>
<pre class="crayon-plain-tag">Ext.define( 'Employee', {
    mixins : {
        observable : 'Ext.util.Observable'
    },
    constructor : function( config )
    {
        //构造该混入
        this.mixins.observable.constructor.call( this, config );
        //添加支持的事件
        this.addEvents( 'fired', 'quit' );
    }
} );
var xdz = new Employee( {
    name : employeeName,
    listeners : {
        fired : function()
        {
            //默认情况下this执行触发事件的对象，即xdz
            console.log( this.name + " was fired." );
        }
    }
} );</pre>
<p> Observable提供了以下成员：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">配置项/属性/方法 </td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>{listeners}</td>
<td>
<p>在初始化阶段添加到当前对象的事件监听器列表，形式为：{事件名:事件处理函数}或者其它更复杂的形式，参考addListeners()方法</p>
<p>从ExtJS组件中直接访问元素事件，可以指定组件的某个类型为元素的属性名，例如：</p>
<pre class="crayon-plain-tag">new Ext.panel.Panel({
    listeners: {
        click: {
            element: 'el', //绑定组件封装元素的click事件
            fn: function(){ console.log('click el'); }
        },
        dblclick: {
            element: 'body',//绑定面板体元素的dblclick事件
            fn: function(){ console.log('dblclick body'); }
        }
    }
});</pre>
<p>某些组件通过重新封装暴露了一些元素事件，例如DataView的itemClick暴露了节点的click事件</p>
</td>
</tr>
<tr>
<td>hasListeners</td>
<td>该对象持有了所有具有监听器的事件名称，例如this.hasListeners.click!=undefined则意味着包含click事件监听器</td>
</tr>
<tr>
<td>isObservable</td>
<td>用于鸭子类型识别</td>
</tr>
<tr>
<td>addEvents()</td>
<td>void ( Object/String... eventNames )，指定当前类型可以触发的事件</td>
</tr>
<tr>
<td>addListener()</td>
<td>void ( String/Object eventName, [Function fn], [Object scope], [Object options] )。该方法的别名是on：<br />
<pre class="crayon-plain-tag">gp.on({
    cellClick: {fn: 'onCellClick', scope: this, single: true},
    mouseover: {fn: 'onMouseOver', scope: panel}
});</pre></p>
<p>选项可以包括：<br />Object scope　指定this指针的值<br />Number delay　在事件触发后，延迟多少毫秒异步的执行该监听器<br />Boolean single　仅在下一次事件触发后执行一次，然后就移除监听器<br />Number buffer　缓冲多久执行监听器，如果缓冲时间内同一事件再次发生，则取消前一缓冲<br />Observable target　仅事件发生在target上才执行监听器，如果是从子代冒泡上来的则不处理<br />String element　用于把元素事件绑定给组件</p>
</td>
</tr>
<tr>
<td>addManagedListener()</td>
<td>void ( Ext.util.Observable/Ext.Element item, Object/String ename, [Function fn], [Object scope], [Object opt] )。该方法的别名是mon。与on类似，但是在当前Observable被销毁时，会自动销毁监听器，防止内存泄漏</td>
</tr>
<tr>
<td>clearListeners()</td>
<td>清除包括受管监听器的所有监听器</td>
</tr>
<tr>
<td>clearManagedListeners()</td>
<td>清除所有受管监听器</td>
</tr>
<tr>
<td>enableBubble()</td>
<td>void ( String/String[] eventNames )。允许某些事件冒泡，冒泡的目标通过this.getBubbleTarget()获得。主要在组件中用于把事件冒泡给父容器</td>
</tr>
<tr>
<td>fireEvent()</td>
<td>Object ( String eventName, Object... args )。发布一个事件，args将被传递给监听器，如果任一监听器返回false，则该函数返回false</td>
</tr>
<tr>
<td>hasListener()</td>
<td>Boolean ( String eventName )。是否具有监听器，返回值指示事件是否需要发布</td>
</tr>
<tr>
<td>removeListener() </td>
<td>void ( String eventName, Function fn, [Object scope] )。该方法的别名是un。fn必须是传递给addListener的函数的引用</td>
</tr>
<tr>
<td>removeManagedListener() </td>
<td>void ( Ext.util.Observable/Ext.Element item, Object/String ename, [Function fn], [Object scope] )。该方法的别名是mun，与removeListener类似，但是仅移除受管监听器</td>
</tr>
<tr>
<td>relayEvents()</td>
<td>void ( Object origin, String[] events, [String prefix] )，转播origin触发的事件，就好像是this触发的一样：<br />
<pre class="crayon-plain-tag">this.relayEvents(this.getStore(), ['load']);</pre>
</td>
</tr>
<tr>
<td>resumeEvents()</td>
<td>恢复事件发布</td>
</tr>
<tr>
<td>suspendEvents()</td>
<td>void ( Boolean queueSuspended )，暂停事件发布。 如果参数为true，那么事件排队，等待resumeEvents()时一起发布</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>静态成员  </strong></td>
</tr>
<tr>
<td>capture() </td>
<td>void ( Ext.util.Observable o, Function fn, [Object scope] )。捕获o上触发的事件，传递给fn（参数为eventName+args），如果fn返回false，则事件不会触发</td>
</tr>
<tr>
<td>observe()</td>
<td>void ( Function c, Object listeners )。在传递的构造器c上设置可观察性，可以用于集中式事件处理：<br />
<pre class="crayon-plain-tag">Ext.util.Observable.observe(Ext.data.Connection);
//集中处理事件
Ext.data.Connection.on('beforerequest', function(con, options) {
    console.log('Ajax request made to ' + options.url);
});</pre>
</td>
</tr>
<tr>
<td>releaseCapture()</td>
<td>void( Ext.util.Observable o )。移除捕获</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Ext.EventObject</span></div>
<p>就像Ext.Element封装了DOM节点一样，单例EventObject封装了浏览器原生的事件对象，屏蔽了跨浏览器的不一致性，EventObject的简单用法如下：</p>
<pre class="crayon-plain-tag">function clickHandler( e, t )
{
    e.preventDefault(); //阻止默认行为
    var target = e.getTarget(); //事件源
}
//添加元素的事件监听器
var myDiv = Ext.get( "myDiv" );
myDiv.on( "click", clickHandler );
Ext.EventManager.on( "myDiv", 'click', clickHandler );</pre>
<p>提供了以下成员：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 180px; text-align: center;">配置项/属性/方法 </td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>(Key constants)</td>
<td>
<p>Number，用于表示KeyCode的常量。包括：</p>
<p>A-Z, CAPS_LOCK, BACKSPACE, CONTEXT_MENU, CTRL, ALT, SHIFT, DELETE, RETURN, SPACE, UP, DOWN, LEFT, RIGHT, F1-F10, ZERO-NINE, NUM_ZERO-NUM_NINE, </p>
</td>
</tr>
<tr>
<td>WHEEL_SCALE</td>
<td>Number, 鼠标中键的Δ变化因子</td>
</tr>
<tr>
<td>altKey</td>
<td>Boolean, 事件期间ALT键是否被按下</td>
</tr>
<tr>
<td>ctrlKey</td>
<td>Boolean,  事件期间CTRL键是否被按下</td>
</tr>
<tr>
<td>shiftKey</td>
<td>Boolean, 事件期间SHIFT键是否被按下</td>
</tr>
<tr>
<td>correctWheelDelta()</td>
<td>Number ( Number delta )，使用dalta除以WHEEL_SCALE</td>
</tr>
<tr>
<td>getCharCode( )</td>
<td>Number()，返回事件的Character code </td>
</tr>
<tr>
<td>getKey( )</td>
<td>Number()，返回事件的Key code </td>
</tr>
<tr>
<td>getPoint( )</td>
<td>Ext.util.Point()，返回事件发生时鼠标的坐标</td>
</tr>
<tr>
<td>getRelatedTarget()</td>
<td>HTMLElement ( [String selector], [Number/HTMLElement maxDepth], [Boolean returnEl] ) 。获取相关的目标</td>
</tr>
<tr>
<td>getTarget()</td>
<td>HTMLElement ( [String selector], [Number/HTMLElement maxDepth], [Boolean returnEl] ) 。获取事件目标</td>
</tr>
<tr>
<td>getWheelDelta()</td>
<td>Number ( ) 。获取Y方向的滚轮Δ值</td>
</tr>
<tr>
<td>getWheelDeltas</td>
<td>Object ( ) 。获取X、Y方向的滚轮Δ值，返回{x:m, y: n}形式</td>
</tr>
<tr>
<td>getXY()</td>
<td>Number[]()。获取事件发生的坐标，类似还有getX()、getY()</td>
</tr>
<tr>
<td>hasModifier()</td>
<td>Boolean ()。判断该事件发生时，是否有ALT、CTRL、SHIFT等控制键被按下</td>
</tr>
<tr>
<td>injectEvent()</td>
<td>void ( [Ext.Element/HTMLElement target] )</td>
</tr>
<tr>
<td>isNavKeyPress()</td>
<td>Boolean ()，被按下的是否是导航键</td>
</tr>
<tr>
<td>isSpecialKey( )</td>
<td>Boolean ()，被按下的是否是特殊键</td>
</tr>
<tr>
<td>preventDefault()</td>
<td>阻止浏览器处理该事件的默认行为 </td>
</tr>
<tr>
<td>stopPropagation()</td>
<td>停止事件冒泡</td>
</tr>
<tr>
<td>stopEvent()</td>
<td>停止事件。preventDefault()并且stopPropagation()</td>
</tr>
<tr>
<td>within()</td>
<td>
<p>Boolean ( String/HTMLElement/Ext.Element el, [Boolean related], [Boolean allowEl] ) : Boolean</p>
<p>判断事件的目标是不是el的子元素，示例：</p>
<p><pre class="crayon-plain-tag">//处理任何子元素的事件
Ext.getBody().on('click', function(e){
    if(e.within('some-el')){
        console.log('Clicked on a child of some-el!');
    }
});
//处理元素本身，不处理任何子元素的事件
Ext.getBody().on('click', function(e,t){
    if((t.id == 'some-el') &amp;&amp; !e.within(t, true)){
        console.log('Clicked directly on some-el!');
    }
});</pre>
</td>
</tr>
</tbody>
</table>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/extjs-4-event-system">ExtJS 4的事件系统</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/extjs-4-event-system/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ExtJS 4的MVC框架</title>
		<link>https://blog.gmem.cc/mvc-framework-of-extjs-4</link>
		<comments>https://blog.gmem.cc/mvc-framework-of-extjs-4#comments</comments>
		<pubDate>Mon, 25 Mar 2013 07:39:26 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ExtJS]]></category>
		<category><![CDATA[MVC]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=4896</guid>
		<description><![CDATA[<p>ExtJS 4引入MVC架构，可以用来创建ExtJS应用程序，与经典的Ext.onReady回调方式很不相同。 MVC架构 在开发ExtJS应用时，往往习惯于把所有JavaScript代码编写在单个文件中，生产环境下的应用通常都比较复杂，可能包括了面板、表单、表格、布局、模型、存储等多种组件的组合。大量的组件代码导致JS文件急剧膨胀，难以维护，因此很有必要根据职责的不同把代码划分到不同的文件中，提高代码的可重用性。 在ExtJS 4以前的版本下，如何组织代码由开发人员自行决定，而MVC则是ExtJS 4提供的模式化选择。在ExtJS看来，MVC代表： Model：包含一系列字段、及其数据的集合，由Model类表示，亦可使用Store类来加载、保存数据 View：可以是某种视觉组件，例如Grid、Tree、Form Controllers：包含了一些动作逻辑，例如用户点击按钮时需要执行的操作 老式的ExtJS应用 在这里我们先创建一个老式的ExtJS应用，并在后面尝试将其迁移到MVC架构下，以阐述ExtJS MVC的用法。 该应用包含一个GRID和一个详情面板，JavaScript代码全部放在app.js中： [crayon-69d9b17a052c6111584658/]  HTML代码： [crayon-69d9b17a052cc709394229/] 迁移/创建基于MVC的ExtJS应用 在这里我们使用MVC重新实现上一段中的应用。下图描述了JavaScript代码的目录结构：  我们可以看到，所有的JavaScript代码被分配在app文件夹中，它们的职责划分如下： app.js：包装整个应用程序，通常我们使用单页面方式来开发MVC应用 controller：控制器类存放目录 <a class="read-more" href="https://blog.gmem.cc/mvc-framework-of-extjs-4">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/mvc-framework-of-extjs-4">ExtJS 4的MVC框架</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>ExtJS 4引入MVC架构，可以用来创建ExtJS应用程序，与经典的Ext.onReady回调方式很不相同。</p>
<div class="blog_h2"><span class="graybg">MVC架构</span></div>
<p>在开发ExtJS应用时，往往习惯于把所有JavaScript代码编写在单个文件中，生产环境下的应用通常都比较复杂，可能包括了面板、表单、表格、布局、模型、存储等多种组件的组合。大量的组件代码导致JS文件急剧膨胀，难以维护，因此很有必要根据职责的不同把代码划分到不同的文件中，提高代码的可重用性。</p>
<p>在ExtJS 4以前的版本下，如何组织代码由开发人员自行决定，而MVC则是ExtJS 4提供的模式化选择。在ExtJS看来，MVC代表：</p>
<ol>
<li>Model：包含一系列字段、及其数据的集合，由Model类表示，亦可使用Store类来加载、保存数据</li>
<li>View：可以是某种视觉组件，例如Grid、Tree、Form</li>
<li>Controllers：包含了一些动作逻辑，例如用户点击按钮时需要执行的操作</li>
</ol>
<div class="blog_h2"><span class="graybg">老式的ExtJS应用</span></div>
<p>在这里我们先创建一个老式的ExtJS应用，并在后面尝试将其迁移到MVC架构下，以阐述ExtJS MVC的用法。</p>
<p>该应用包含一个GRID和一个详情面板，JavaScript代码全部放在app.js中：</p>
<pre class="crayon-plain-tag">Ext.onReady( function()
{
    Ext.define( 'Book', {
        extend : 'Ext.data.Model',
        fields : [
            'id', 'title', 'pages', 'numChapters', 'topic', 'publisher', 'isbn', 'isbn13'
        ]
    } );
    var store = Ext.create( 'Ext.data.Store', {
        model : 'Book',
        proxy : {
            type : 'ajax',
            url : 'data/books.json'
        }
    } );
    var grid = Ext.create( 'Ext.grid.Panel', {
        store : store,
        title : 'Books',
        columns : [
            {
                text : "Title",
                width : 120,
                dataIndex : 'title',
                sortable : true
            }, {
                text : "Pages",
                flex : 1,
                dataIndex : 'pages',
                sortable : true
            }, {
                text : "Topic",
                width : 115,
                dataIndex : 'topic',
                sortable : true
            }, {
                text : "Publisher",
                width : 100,
                dataIndex : 'publisher',
                sortable : true
            }
        ],
        viewConfig : {
            forceFit : true
        },
        region : 'center'
    } );
    var details = Ext.create( 'Ext.panel.Panel', {
        id : 'bookDetail',
        bodyPadding : 7,
        bodyStyle : "background: #ffffff;",
        html : 'Please select a book to see additional details.',
        height : 150,
        split : true,
        region : 'south'
    } );
    var bookTplMarkup = [
        '&lt;b&gt;Title:&lt;/b&gt; {title}&lt;br/&gt;',
        '&lt;b&gt;Pages:&lt;/b&gt; {pages}&lt;br/&gt;',
        '&lt;b&gt;No Chapters:&lt;/b&gt; {numChapters}&lt;br/&gt;',
        '&lt;b&gt;Topic:&lt;/b&gt; {topic}&lt;br/&gt;',
        '&lt;b&gt;Publisher:&lt;/b&gt; {publisher}&lt;br/&gt;',
        '&lt;b&gt;ISBN:&lt;/b&gt; {isbn}&lt;br/&gt;',
        '&lt;b&gt;ISBN 13:&lt;/b&gt; {isbn13}&lt;br/&gt;'
    ];
    var bookTpl = Ext.create( 'Ext.Template', bookTplMarkup );
    grid.getSelectionModel().on( 'selectionchange', function( sm, selectedRecord )
    {
        if ( selectedRecord.length )
        {
            var detailPanel = Ext.getCmp( 'bookDetail' );
            bookTpl.overwrite( detailPanel.body, selectedRecord[0].data );
        }
    } );
    Ext.create( 'Ext.container.Viewport', {
        frame : true,
        layout : 'border',
        items : [
            grid, details
        ]
    } );
    store.load();
} );</pre>
<p> HTML代码：</p>
<pre class="crayon-plain-tag">&lt;html&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /&gt;
&lt;title&gt;MVC Architecture&lt;/title&gt;
&lt;link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css" /&gt;
&lt;script type="text/javascript" src="extjs/ext-all.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="app.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<div class="blog_h2"><span class="graybg">迁移/创建基于MVC的ExtJS应用</span></div>
<p>在这里我们使用MVC重新实现上一段中的应用。下图描述了JavaScript代码的目录结构：</p>
<p><a href="/wp-content/uploads/2013/03/ExtJS-MVC.png"><img class="size-full wp-image-4906 aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2013/03/ExtJS-MVC.png" alt="ExtJS MVC" width="444" height="272" /></a></p>
<p> 我们可以看到，所有的JavaScript代码被分配在app文件夹中，它们的职责划分如下：</p>
<ol>
<li>app.js：包装整个应用程序，通常我们使用单页面方式来开发MVC应用</li>
<li>controller：控制器类存放目录</li>
<li>model：模型类存放目录</li>
<li>store：存储类存放目录</li>
<li>view：视图类存放目录</li>
<li>data：由于本示例不合服务器交互，该目录存放JSON数据</li>
</ol>
<p>为了使用动态加载，以下ExtJS SDK文件需要被引入到extjs目录下：</p>
<ol>
<li>ext-debug.js或者ext.js：用于ExtJS自举</li>
<li>resources目录：包含样式定义文件</li>
<li>src目录：包含ExtJS所有组件的源代码</li>
</ol>
<div class="blog_h3"><span class="graybg">创建MVC骨架程序</span></div>
<p>修改后的HTML代码如下：</p>
<pre class="crayon-plain-tag">&lt;head&gt;
&lt;title&gt;MVC Architecture&lt;/title&gt;
&lt;link rel="stylesheet" type="text/css" href="exts/resources/css/ext-all.css" /&gt;
&lt;script type="text/javascript" src="extjs/ext-debug.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="app.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p> app.js为MVC骨架，内容如下：</p>
<pre class="crayon-plain-tag">Ext.require( 'Ext.container.Viewport' );
Ext.application( {
    name : 'App',   //可选的应用的名称，也在全局创建了变量App，会作为所有模型、视图、存储、控制器的名字空间
    appFolder : 'app', //应用程序组件存放位置
    controllers: ['Books'], //声明该应用使用的控制器
    launch : function() //应用创建后自动执行的动作，必须覆写
    {
        Ext.create( 'Ext.container.Viewport', { 
            //所有组件均放置于该Viewport中
        } );
    }
} );</pre>
<div class="blog_h3"><span class="graybg">创建控制器类</span></div>
<pre class="crayon-plain-tag">//注意类名于目录结构限定的名字空间一致
Ext.define( 'App.controller.Books', {
    extend : 'Ext.app.Controller',
    init : function()
    {
        //在App的launch之前执行
    }
} );</pre>
<p>控制器类继承自Ext.app.Controller，控制器负责与Store、View通信，并在特定事件发生时执行对应的Action。 </p>
<p>控制器的init方法在App.launch方法之前执行，可以在这里编写所有需要在创建Viewport前执行的代码，例如：</p>
<ol>
<li>实现与视图交互的逻辑，主要是相关的事件监听</li>
<li>加载数据</li>
<li>其它业务逻辑</li>
</ol>
<div class="blog_h3"><span class="graybg">在控制器类中操控视图</span></div>
<p>修改上面编写的控制器为：</p>
<pre class="crayon-plain-tag">Ext.define( 'App.controller.Books', {
    extend : 'Ext.app.Controller',
    init : function()
    {
        this.control( {
            //对于viewport的直接子panel，监听其render事件
            'viewport &gt; panel' : {
                render : this.onPanelRendered
            }
        } );
    },
    onPanelRendered : function()
    {
    }
} );</pre>
<p>使用control方法可以注册对视图组件生命周期的监听器，在这里，我们监听Viewport子面板的render事件。 </p>
<div class="blog_h3"><span class="graybg">创建模型、存储类</span></div>
<p>模型类：</p>
<pre class="crayon-plain-tag">//注意类名于目录结构限定的名字空间一致
Ext.define( 'App.model.Book', {
    extend : 'Ext.data.Model',
    fields : [
        'id', 'title', 'pages', 'numChapters', 'topic', 'publisher', 'isbn', 'isbn13'
    ]
} );</pre>
<p>存储类：</p>
<pre class="crayon-plain-tag">Ext.define( 'App.store.Book', {
    extend : 'Ext.data.Store',
    model : 'App.model.Book',
    proxy : {
        type : 'ajax',
        url : 'data/books.json'
    }
} );</pre>
<div class="blog_h3"><span class="graybg">添加表格面板</span></div>
<pre class="crayon-plain-tag">Ext.define( 'App.view.book.Grid', {
    extend : 'Ext.grid.Panel',
    alias : 'widget.bookList',
    title : 'Books',
    initComponent : function()
    {
        this.store = 'Book';
        this.columns = [
            {
                text : "Title",
                width : 120,
                dataIndex : 'title',
                sortable : true
            }, {
                text : "Pages",
                flex : 1,
                dataIndex : 'pages',
                sortable : true
            }, {
                text : "Topic",
                width : 115,
                dataIndex : 'topic',
                sortable : true
            }, {
                text : "Publisher",
                width : 100,
                dataIndex : 'publisher',
                sortable : true
            }
        ];
        this.viewConfig = {
            forceFit : true
        };
        this.callParent( arguments );
    }
} );</pre>
<div class="blog_h3"><span class="graybg">添加详情面板</span></div>
<pre class="crayon-plain-tag">Ext.define( 'App.view.book.DetailPanel', {
    extend : 'Ext.Panel',
    alias : 'widget.detailPanel',  //xtype别名
    bookTplMarkup : [  //作为实例变量来定义，而不是全局变量
        '&lt;b&gt;Title:&lt;/b&gt; {title}&lt;br/&gt;',
        '&lt;b&gt;Pages:&lt;/b&gt; {pages}&lt;br/&gt;',
        '&lt;b&gt;No Chapters:&lt;/b&gt; {numChapters}&lt;br/&gt;',
        '&lt;b&gt;Topic:&lt;/b&gt; {topic}&lt;br/&gt;',
        '&lt;b&gt;Publisher:&lt;/b&gt; {publisher}&lt;br/&gt;',
        '&lt;b&gt;ISBN:&lt;/b&gt; {isbn}&lt;br/&gt;',
        '&lt;b&gt;ISBN 13:&lt;/b&gt; {isbn13}&lt;br/&gt;'
    ],
    startingMarkup : 'Please select a book to see additional details.',
    bodyPadding : 7,
    initComponent : function()
    {
        this.tpl = Ext.create( 'Ext.Template', this.bookTplMarkup );
        this.html = this.startingMarkup;
        this.bodyStyle = {
            background : '#ffffff'
        };
        this.callParent( arguments );
    },
    updateDetail : function( data )  //供控制器调用，更新此视图组件
    {
        this.tpl.overwrite( this.body, data );
    }
} );</pre>
<p> 同时，修改应用骨架、控制器类为最终版本：</p>
<pre class="crayon-plain-tag">Ext.require( 'Ext.container.Viewport' );
Ext.application( {
    name : 'App',
    appFolder : 'app',
    controllers : [ 'Books'],
    launch : function()
    {
        Ext.create( 'Ext.container.Viewport', {
            layout : 'fit',
            items : [
                {
                    xtype : 'panel',
                    title : 'Books',
                    items : [
                        {
                            xtype : 'booklist'
                        }, {
                            xtype : 'detailPanel'
                        }
                    ]
                }
            ]
        } );
    }
} );</pre><br />
<pre class="crayon-plain-tag">Ext.define( 'App.controller.Books', {
    extend : 'Ext.app.Controller',
    stores : [ 'Book' ],  //不需要声明完整的名称，ExtJS会自动在当前App的名字空间、约定目录下寻找
    models : [ 'Book' ],
    views : [ 'book.Grid', 'book.DetailPanel' ],  //控制器可以控制的视图的列表
    refs : [ //该配置用于外部引用控制器内的视图对象，使用Ext.ComponentQuery查询来获取视图
        {
            //变量名，可以直接使用Getter获取
            ref : 'panel',
            //组件选择符
            selector : 'detailPanel'
        }
    ],
    init : function()
    {
        this.getBookStore().load();  //getBookStore是自动生成的方法，等价于this.getStore('Book').load();
        this.control( {
            //控制ViewPort下的bookList（表格）的dataview组件
            'viewport &gt; bookList dataview' : {
                itemclick : this.bindGridToPanel
            }
        } );
    },
    bindGridToPanel : function( grid, record )
    {
        //直接使用Getter获取详情面板，并调用其方法更新视图
        this.getPanel().updateDetail( record.data );
    }
} ); </pre>
<div class="blog_h3"><span class="graybg">控制器的getter方法</span></div>
<p>声明在stores、views、models配置项中的对象，可以使用控制器类自动生成getter方法访问，这些方法包括两类：</p>
<ol>
<li>其中一类是形如getName[Store|View|Model]()的方法，其中Name是配置项中声明的组件的名称。</li>
<li>另外一些带单个形参的getModel、getStore、getView方法，使用时通过参数传递组件的名称：</li>
</ol>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> getter方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>getModel(name)</td>
<td>返回指定模型的引用，目标模型必须在models配置项中声明</td>
</tr>
<tr>
<td>getStore(name)</td>
<td>返回指定存储的引用，目标存储必须在stores配置项中声明</td>
</tr>
<tr>
<td>getView(name)</td>
<td>返回指定视图的引用，目标视图必须在views配置项中声明</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">一些MVC开发的TIPS</span></div>
<ol>
<li>创建Model、Store、View类（扩展已有UI组件）</li>
<li>在View类中不会执行任何操作（事件句柄），View只是提供方法，让控制器调用后改变视图</li>
<li>在Controller中列出其需要使用的Model、Store、View</li>
<li>注意Controller自动生成很多getter方法</li>
<li>为了方便的在Controller中获取View的引用，可以使用ref配置项</li>
<li>使用control函数声明针对View、Components的事件监听</li>
<li>注意Controller的init在App的launch之前执行</li>
</ol>
<div class="blog_h3"><span class="graybg">嵌套Model与MVC</span></div>
<p>如果为Model声明关联（associations），则必须使用关联目标的全名：</p>
<pre class="crayon-plain-tag">Ext.define( 'App.model.Author', {
    extend : 'Ext.data.Model',
    fields : [
        {
            name : 'id',
            type : 'int'
        }, {
            name : 'name',
            type : 'string'
        }
    ],
    hasMany : {
        model : 'App.model.Book',   //如果仅仅写'Book'会出错
        foreignKey : 'authorId',
        name : 'books'
    },
    proxy : {
        type : 'ajax',
        url : 'data/authors/1.json',
        reader : {
            type : 'json',
            root : 'authors'
        }
    }
} );</pre>
<div class="blog_h2"><span class="graybg">为生产环境构建应用</span></div>
<p>Sencha SDK tools是ExtJS官方提供的开发套件，它可以生成JSB3（(JSBuilder file format)）格式的JavaScript依赖关系，并且依此生成最小化的运行时JavaScript文件，便于部署。</p>
<p>使用如下的命令可以生成JSB依赖文件：</p>
<pre class="crayon-plain-tag">sencha create jsb -a index.html -p app.js
rem 亦可指向服务器上的HTML文件
sencha create jsb -a http://localhost:8080/ext-theme/index.html -p app.jsb3</pre>
<p>上述命令会生成一个app.jsb3文件，其内容如下：</p>
<pre class="crayon-plain-tag">{
    "projectName" : "Project Name",
    "licenseText" : "Copyright(c) 2011 Company Name",
    "builds" : [
        {
            "name" : "All Classes",
            "target" : "all-classes.js",
            "options" : {
                "debug" : true
            },
            "files" : [
                {
                    "path" : "extjs/src/util/",
                    "name" : "Observable.js"
                }, {
                    "path" : "extjs/src/data/",
                    "name" : "Association.js"
                }, {
                    "path" : "extjs/src/data/",
                    "name" : "Operation.js"
                }
            ]
        }, {
            "name" : "Application - Production",
            "target" : "app-all.js",
            "compress" : true,
            "files" : [
                {
                    "path" : "",
                    "name" : "app.js"
                }
            ]
        }
    ],
    "resources" : []
}</pre>
<p>然后，执行命令：</p>
<pre class="crayon-plain-tag">sencha build -p app.jsb3 -d</pre>
<p>会生成两个文件：</p>
<ol>
<li>all-classes.js：包含应用程序用到的所有ExtJS类，未压缩，易于DEBUG</li>
<li>app-all.js：包含应用程序用到的所有ExtJS类、app.js，压缩版本</li>
</ol>
<p>我们可以修改HTML，引用生成的JavaScript文件：</p>
<pre class="crayon-plain-tag">&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;MVC Architecture&lt;/title&gt;
    &lt;link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css" /&gt;
    &lt;script type="text/javascript" src="extjs/ext.js"&gt;&lt;/script&gt;
    &lt;script type="text/javascript" src="app-all.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/mvc-framework-of-extjs-4">ExtJS 4的MVC框架</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/mvc-framework-of-extjs-4/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>定制ExtJS 4主题</title>
		<link>https://blog.gmem.cc/custom-extjs-4-themes</link>
		<comments>https://blog.gmem.cc/custom-extjs-4-themes#comments</comments>
		<pubDate>Sat, 23 Mar 2013 07:08:51 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ExtJS]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=4858</guid>
		<description><![CDATA[<p>ExtJS 4主题入门 在ExtJS 3中自定义主题相当复杂，可能需要探测每一个HTML元素，找到对应的CSS代码并修改，很多ExtJS 3组件使用了图片，可能需要手工修改这些图片才能满足新主题的配色需要。 ExtJS 4使用一些CSS3的特性来便利化主题定制，它是用了Sass、Compass，这些相关的工具可以在themes目录下找到。 安装Ruby 到http://rubyinstaller.org/下载合适的版本并安装，也可以到ftp://ftp.ruby-lang.org/pub/ruby/binaries/mswin32/可以下载特定的Windows二进制版本。本文安装的是1.9.3版本，完成完毕后要设置好环境变量。 安装Sass和Compass 命令行输入： [crayon-69d9b17a05810679337384/] 创建一个ExtJS主题工程 创建一个Maven工程，名为ext-theme，把下载的ext-4.1.1-gpl重命名为extjs，放置到src/maven/webapp（以下称webapp）下，复制extjs/examples/themes/themes.js到webapp，并在同一目录新建一个HTML文档，内容如下： [crayon-69d9b17a05815029206808/] 复制webapp/extjs/resources/themes/templates/resources到webapp下，复制webapp/extjs/resources/themes/images到webapp/resources下。最终的目录结构如下图： 创建一个新主题 新建一个scss文件，内容如下： [crayon-69d9b17a05818768479204/]  进入my-ext-theme.scss所在目录，执行以下命令即可生成CSS文件，原有文件自动被覆盖： [crayon-69d9b17a0581b432854472/] 刷新浏览器，可以看到页面上的ExtJS组件均是粉红色调了。  <a class="read-more" href="https://blog.gmem.cc/custom-extjs-4-themes">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/custom-extjs-4-themes">定制ExtJS 4主题</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">ExtJS 4主题入门</span></div>
<p>在ExtJS 3中自定义主题相当复杂，可能需要探测每一个HTML元素，找到对应的CSS代码并修改，很多ExtJS 3组件使用了图片，可能需要手工修改这些图片才能满足新主题的配色需要。</p>
<p>ExtJS 4使用一些CSS3的特性来便利化主题定制，它是用了Sass、Compass，这些相关的工具可以在themes目录下找到。</p>
<div class="blog_h2"><span class="graybg">安装Ruby</span></div>
<p>到<a href="http://rubyinstaller.org/">http://rubyinstaller.org/</a>下载合适的版本并安装，也可以到<a href="ftp://ftp.ruby-lang.org/pub/ruby/binaries/mswin32/">ftp://ftp.ruby-lang.org/pub/ruby/binaries/mswin32/</a>可以下载特定的Windows二进制版本。本文安装的是1.9.3版本，完成完毕后要设置好环境变量。</p>
<div class="blog_h2"><span class="graybg">安装Sass和Compass</span></div>
<p>命令行输入：</p>
<pre class="crayon-plain-tag">gem install sass -v 3.2.5
gem install compass -v 0.12.2

rem 查看版本
compass -v
sass -v</pre>
<div class="blog_h2"><span class="graybg">创建一个ExtJS主题工程</span></div>
<p>创建一个Maven工程，名为ext-theme，把下载的ext-4.1.1-gpl重命名为extjs，放置到src/maven/webapp（以下称webapp）下，复制extjs/examples/themes/themes.js到webapp，并在同一目录新建一个HTML文档，内容如下：</p>
<pre class="crayon-plain-tag">&lt;html&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html;charset=utf-8" /&gt;
&lt;title&gt;Ext JS 4 Themes&lt;/title&gt;
&lt;!-- 下面是我们即将创建的自定义主题的样式表文件 --&gt;
&lt;link rel="stylesheet" type="text/css" href="resources/css/my-ext-theme.css" /&gt;
&lt;script type="text/javascript" src="extjs/bootstrap.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="themes.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>复制webapp/extjs/resources/themes/templates/resources到webapp下，复制webapp/extjs/resources/themes/images到webapp/resources下。最终的目录结构如下图：</p>
<p><img class="aligncenter size-full wp-image-4865" src="https://blog.gmem.cc/wp-content/uploads/2013/03/ext-theme.png" alt="ext-theme" width="532" height="289" /></p>
<div class="blog_h2"><span class="graybg">创建一个新主题</span></div>
<p>新建一个scss文件，内容如下：</p>
<pre class="crayon-plain-tag">$include-default: false;    /*创建一个紧凑的CSS，不包括全部的组件*/

/*添加自定义变量*/
$base-color: #ffaabb;    /*基础色调：粉红*/

@import 'compass';             /*需要导入的库和文件*/
@import 'ext4/default/all';    /*导入该目录下所有SCSS文件*/

/*需要包括的组件的列表*/
@include extjs-boundlist;
@include extjs-button;
@include extjs-btn-group;
@include extjs-datepicker;
@include extjs-colorpicker;
@include extjs-menu;
@include extjs-grid;
@include extjs-form;
    @include extjs-form-field;
    @include extjs-form-fieldset;
    @include extjs-form-file;
    @include extjs-form-checkboxfield;
    @include extjs-form-checkboxgroup;
    @include extjs-form-triggerfield;
    @include extjs-form-htmleditor;
@include extjs-panel;
@include extjs-qtip;
@include extjs-slider;
@include extjs-progress;
@include extjs-toolbar;
@include extjs-window;
@include extjs-messagebox;
@include extjs-tabbar;
@include extjs-tab;
@include extjs-tree;
@include extjs-drawcomponent;
@include extjs-viewport;

/*如果设置为true，那么图片的路径改为相对，即从ext-theme/resources/images里面寻找*/
$relative-image-path-for-uis: true;</pre>
<p> 进入my-ext-theme.scss所在目录，执行以下命令即可生成CSS文件，原有文件自动被覆盖：</p>
<pre class="crayon-plain-tag">cd D:\JavaEE\projects\eclipse\4.3.2\ext-theme\src\main\webapp\resources\sass
compass compile</pre>
<p>刷新浏览器，可以看到页面上的ExtJS组件均是粉红色调了。 </p>
<div class="blog_h3"><span class="graybg">变量</span></div>
<p>在目录ext-theme/extjs/resources/themes/stylesheets/ext4/default/variables中，包含若干scss文件，定义了所有可用的变量。例如_panel.scss中包含以下变量：</p>
<pre class="crayon-plain-tag">// ===============================
// ========= BASE PANEL ==========
// ===============================
/*从变量名称上很容易看出其与CSS属性的对应关系：$panel-border-radius 对应了 border-radius*/
$panel-border-radius: null !default;
$panel-border-width: 1px !default;
$panel-base-color: adjust-color($base-color, $hue: 0deg, $saturation: 0.542%, $lightness: 7.843%) !default; //#DFE8F6
$panel-border-color: adjust-color($base-color, $hue: 0deg, $saturation: 7.644%, $lightness: -8.627%) !default;</pre>
<p>如果需要修改这些变量的值，只需要在my-ext-theme.scss中进行覆盖：</p>
<pre class="crayon-plain-tag">$panel-border-radius: 3px;      //圆角边框
$panel-header-font-size: 14px;  //放大字体</pre>
<p>重新执行compass compile命令，即可看到修改后的样式。</p>
<div class="blog_h2"><span class="graybg">创建新的ExtJS组件UI</span></div>
<p>如果需要单独修改某个组件的外观，而让其它组件保留默认的样式，例如，在某些特殊场景下使用红色的Window，其它情况下使用默认的蓝色风格。ExtJS 4的某些组件支持Sass混入，可以满足这一需求。</p>
<p>在my-ext-theme.scss中添加以下代码：</p>
<pre class="crayon-plain-tag">@include extjs-window-ui(
    'custom',
    $ui-border-radius: 10px,
    $ui-border-color: darken($base-color, 40%),
    $ui-inner-border-color:darken($base-color, 30%),
    $ui-header-color: darken($base-color, 60%),
    $ui-body-border-color: darken($base-color, 30%),
    $ui-body-background-color: $window-body-background-color,
    $ui-body-color: darken($base-color, 30%),
    $ui-background-color: $base-color
);</pre>
<p>上面的代码为Window组件定义了新的外观，可以采用如下的方式引用之：</p>
<pre class="crayon-plain-tag">Ext.createWidget( 'window', {
    ui : 'custom',  /*使用外观*/
    renderTo : 'customUIWindow',
    width : 150,
    height : 150,
    title : 'Window',
    bodyPadding : 5,
    html : 'Some text here',
    collapsible : false,
    closable : false
} ).show();</pre>
<div class="blog_h2"><span class="graybg">支持遗留浏览器</span></div>
<p>遗留浏览器不支持某些CSS3的特性，ExtJS使用图片为这些浏览器模拟圆角边框、渐变背景等特效。Slice Tool可以用来创建这些图片，它是Sencha SDK Tools的一部分。下载并安装SDK后，执行以下命令：</p>
<pre class="crayon-plain-tag">cd D:\JavaEE\projects\eclipse\4.3.2\ext-theme\src\main\webapp
rem -d ExtJS 4的根目录
rem -c 通过Sass/Compass创建的CSS文件的路径，将根据此CSS生成图片
rem -o 输出图片的目录
rem -v 详细输出各图片被创建的过程
sencha slice theme -d extjs -c resources/css/my-ext-theme.css -o resources/images –v</pre>
<p>命令执行成功后，会输出Done字样。 </p>
<div class="blog_h2"><span class="graybg">处理图片</span></div>
<p>Slice Tool不能处理某些图标，这些图标需要手工使用Photoshop之类的工具处理。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/custom-extjs-4-themes">定制ExtJS 4主题</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/custom-extjs-4-themes/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>浅析ExtJS 4布局组件</title>
		<link>https://blog.gmem.cc/extjs-4-layouts</link>
		<comments>https://blog.gmem.cc/extjs-4-layouts#comments</comments>
		<pubDate>Fri, 15 Mar 2013 09:26:37 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ExtJS]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=4670</guid>
		<description><![CDATA[<p>在ExtJS中，布局决定组件的位置如何分布、尺寸如何确定。ExtJS 4对整个布局体系进行了改造，布局引擎被重新编写，提高了性能。 ExtJS 4中的布局 ExtJS 4把布局分为两类： 容器布局（Container Layout）：组织一个组件的HTML元素。包括Border Layout、Hbox Layout、Vbox Layout、Fit Layout等 组件布局（Component Layout）：决定容器中子组件的大小、位置。包括Dock Layout、Toolbar Layout、Field Layout、TriggerField Layout等 容器布局 在ExtJS 4中Auto Layout替换了ExtJS 3中的ContainerLayout，作为默认的容器布局类，如果容器没有指定布局，则自动使用Auto <a class="read-more" href="https://blog.gmem.cc/extjs-4-layouts">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/extjs-4-layouts">浅析ExtJS 4布局组件</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>在ExtJS中，布局决定组件的位置如何分布、尺寸如何确定。ExtJS 4对整个布局体系进行了改造，布局引擎被重新编写，提高了性能。</p>
<div class="blog_h2"><span class="graybg">ExtJS 4中的布局</span></div>
<p>ExtJS 4把布局分为两类：</p>
<ol>
<li>容器布局（Container Layout）：组织一个组件的HTML元素。包括Border Layout、Hbox Layout、Vbox Layout、Fit Layout等</li>
<li>组件布局（Component Layout）：决定容器中子组件的大小、位置。包括Dock Layout、Toolbar Layout、Field Layout、TriggerField Layout等</li>
</ol>
<div class="blog_h2"><span class="graybg">容器布局</span></div>
<p>在ExtJS 4中Auto Layout替换了ExtJS 3中的ContainerLayout，作为默认的容器布局类，如果容器没有指定布局，则自动使用Auto Layout。</p>
<p>Form Layout已经不再支持，并且由组件布局Field Layout（组件布局）代替。</p>
<p>容器布局的类层次如下图：</p>
<p><img class="aligncenter  wp-image-4679" src="https://blog.gmem.cc/wp-content/uploads/2013/03/layout1.png" alt="layout" width="620" height="322" /></p>
<div class="blog_h3"><span class="graybg">Auto Layout</span></div>
<p>该布局的特点：</p>
<ol>
<li>依据声明的顺序，子组件自上而下的排列</li>
<li>子组件需要显式声明宽高度</li>
<li>改变容器的大小后，子组件不跟随改变大小（即使子组件指定百分比）</li>
<li>如果容器的尺寸不够，子组件可能重叠在一起</li>
</ol>
<p>代码示例：</p>
<pre class="crayon-plain-tag">var w = Ext.create( 'Ext.window.Window', {
    title : 'Auto Layout',
    width : 200,
    height : 320,
    layout : 'auto',
    defaults : {
        bodyStyle : 'padding:15px'
    },
    items : [
        { title : 'Panel 1', html : 'Panel 1', height : 60, width : 100 }, 
        { title : 'Panel 2', html : 'Panel 2', height : 80, width : 60 }, 
        { title : 'Panel 3', html : 'Panel 3', height : 65, width : 100 }, 
        { title : 'Panel 4', html : 'Panel 4', height : 70, width : '90%'}
    ]
} );
w.show();</pre>
<p> 效果图：</p>
<p><img class="aligncenter  wp-image-4687" src="https://blog.gmem.cc/wp-content/uploads/2013/03/auto-layout.png" alt="auto-layout" width="543" height="316" /></p>
<div class="blog_h3"><span class="graybg">Anchor Layout</span></div>
<p>该布局的特点：</p>
<ol>
<li>依据声明的顺序，子组件自上而下的排列</li>
<li>子组件依据自身指定的锚定规则（anchor配置项）来确定尺寸、位置</li>
<li>父容器大小变化后，子组件的大小自动变化</li>
</ol>
<p>代码示例：</p>
<pre class="crayon-plain-tag">var w = Ext.create( 'Ext.window.Window', {
    title : 'Auto Layout',
    width : 250,
    height : 300,
    layout :'anchor',
    defaults : {
        bodyStyle : 'padding:15px'
    },
    items : [
        //正数表示宽高度占有容器的百分比
        { title : 'Panel 1', html: '100% 30%', anchor : '100% 30%'}, 
        { title : 'Panel 2', html: '80% 25%',  anchor : '80% 25%'}, 
        //负数表示宽高度 = 容器宽高度 - abs(负数)
        { title : 'Panel 3', html: '-70 20%',  anchor : '-70 20%' }, 
        //如果指定x、y，则可以指定其相对于前一个兄弟组件的位置偏移
        { title : 'Panel 4', html: '-30 25%',  anchor : '-30 25%', x : 0, y :0}
    ]
} );
w.show();</pre>
<p>效果图：</p>
<p><img class="aligncenter  wp-image-4691" src="https://blog.gmem.cc/wp-content/uploads/2013/03/anchor-layout.png" alt="anchor-layout" width="536" height="312" /></p>
<div class="blog_h3"><span class="graybg">Absolute Layout</span></div>
<p>该布局的特点：</p>
<ol>
<li>该布局继承自Anchor Layout，具有其特点</li>
<li>子组件根据x、y设置，相对于父容器的body左上角进行绝对定位</li>
</ol>
<p>代码示例：</p>
<pre class="crayon-plain-tag">var w = Ext.create( 'Ext.window.Window', {
    title : 'Absolute Layout',
    width : 250,
    height : 300,
    layout :'absolute',
    defaults : {
        bodyStyle : 'padding:15px'
    },
    items : [
        { title : 'Panel 1', html: 'x: 10; y: 10 - anchor: 80% 80%', anchor:'80% 80%', x: 10, y: 10}
    ]
} );
w.show();</pre>
<p>效果图：</p>
<p><img class="aligncenter  wp-image-4696" src="https://blog.gmem.cc/wp-content/uploads/2013/03/absolute-layout.png" alt="absolute-layout" width="534" height="311" /></p>
<div class="blog_h3"><span class="graybg">HBox Layout</span></div>
<p>该布局的特点：</p>
<ol>
<li>子组件水平的分布在容器内</li>
<li>通过flex配置项，可以指定子组件如何划分水平方向的空白区域</li>
<li>通过pack配置项，可以指定子组件如何在水平方向连续分布，支持值：start、center、end</li>
<li>通过align配置项，可以决定子组件在垂直方向如何对齐（或伸展），支持值：stretch、stretchmax、top、middle</li>
</ol>
<p>代码示例：</p>
<pre class="crayon-plain-tag">//例一：
var w = Ext.create( 'Ext.window.Window', {
    title : 'Hbox Layout',
    width : 500,
    height : 250,
    layout :{
        type : 'hbox',
        /**
         * 对齐方式，决定了纵向的高度与位置：
         * stretch ：自动伸展填满容器
         * stretchmax ：自动伸展，使所有子组件与最大高度子组件同高
         * top ：顶端对齐子组件（默认）
         * middle ：中部对齐子组件
         */
        align : 'middle'
    },
   
    items : [
        //flex用于设置子组件占有容器可用宽度（除去固定宽度组件占用的部分）的比例
        { title : 'Panel 1', html : 'flex:1', flex : 2, height : 100},
        { title : 'Panel 2', html : 'flex:2', flex : 1, height : 80},
        //该组件为固定宽度
        { title : 'Panel 3', html : 'width:100', width : 100, height : '50%'} 
    ]
} );
w.show();
//例二：
var w = Ext.create( 'Ext.window.Window', {
    title : 'Hbox Layout',
    width : 500,
    height : 250,
    layout :{
        type : 'hbox',
        /**
         * pack在任何一个子组件指定了flex时没有意义
         * start ：靠左对齐
         * center ：中间对齐
         * end ：靠右对齐
         */
        pack : 'end',
        align : 'middle',
        //为所有子组件外围添加的补白
        padding : '0 50 0 50'
    },
   
    items : [
        { title : 'Panel 1', html : 'flex:1&lt;br&gt;height : 100', height : 100},
        { title : 'Panel 2', html : 'flex:2&lt;br&gt;height : 80', height : 80},
        { title : 'Panel 3', html : 'width:100&lt;br&gt;height : 150', width : 100, height : 150} 
    ]
} );
w.show();</pre>
<p>例一的效果图：<img class="aligncenter  wp-image-4701" src="https://blog.gmem.cc/wp-content/uploads/2013/03/hbox-layout.png" alt="hbox-layout" width="485" height="457" /></p>
<p>例二的效果图：<img class="aligncenter  wp-image-4704" src="https://blog.gmem.cc/wp-content/uploads/2013/03/hbox-layout-2.png" alt="hbox-layout-2" width="478" height="450" /></p>
<div class="blog_h3"><span class="graybg">VBox Layout</span></div>
<p>该布局的特点与Hbox非常类似，只是在排列方向上不同：</p>
<ol>
<li>子组件垂直的分布在容器内</li>
<li>通过flex配置项，可以指定子组件如何划分水平方向的空白区域</li>
<li>通过pack配置项，可以指定子组件如何在垂直方向连续分布，支持值：start、center、end</li>
<li>通过align配置项，可以决定子组件在水平方向如何对齐（或伸展），支持值：stretch、stretchmax、left、center</li>
</ol>
<p>代码示例：</p>
<pre class="crayon-plain-tag">var w = Ext.create( 'Ext.window.Window', {
    title : 'Vbox Layout',
    width : 250,
    height : 500,
    layout :{
        type : 'vbox',
        pack : 'end',
        align : 'center'
    },
   
    items : [
        { title : 'Panel 1', html : 'width:100,height:100',width:100, height : 100},
        { title : 'Panel 2', html : 'width:150,height:150',width:150, height : 150},
        { title : 'Panel 3', html : 'width:120,height:120', width : 120, height : 120} 
    ]
} );
w.show();</pre>
<p>效果图：<img class="aligncenter  wp-image-4709" src="https://blog.gmem.cc/wp-content/uploads/2013/03/vbox-layout1.png" alt="vbox-layout" width="439" height="413" /></p>
<div class="blog_h3"><span class="graybg">Accordion Layout</span></div>
<p>该布局是Vbox布局的子类，其特点为：</p>
<ol>
<li>每次只显示一个子组件</li>
<li>可以展开某个处于收缩状态的子组件</li>
</ol>
<p>代码示例：</p>
<pre class="crayon-plain-tag">var w = Ext.create( 'Ext.window.Window', {
    title : 'Accordion Layout',
    width : 200,
    height : 400,
    layout : 'accordion',
    items : [
        { title : 'Panel 1', html : 'Panel 1', height : 100},
        { title : 'Panel 2', html : 'Panel 2', height : 100},
        { title : 'Panel 3', html : 'Panel 3', height : 100} 
    ]
} );
w.show();</pre>
<p>效果图：<a href="/wp-content/uploads/2013/03/accordion-layout.png"><img class="aligncenter  wp-image-4711" src="https://blog.gmem.cc/wp-content/uploads/2013/03/accordion-layout.png" alt="accordion-layout" width="431" height="406" /></a></p>
<div class="blog_h3"><span class="graybg">Table Layout</span></div>
<p>该布局的特点：</p>
<ol>
<li>把组件渲染到HTML的table元素内部</li>
<li>可以指定某个子组件跨越的行、列数</li>
</ol>
<p>代码示例：</p>
<pre class="crayon-plain-tag">var table = Ext.create( 'Ext.window.Window', {
        title : 'Table Layout',
        width : 400,
        height : 300,
        layout : {
            type : 'table',
            columns : 3,  //表格包含的列数
            tableAttrs : {  //应用到生成的table元素的属性
                style : {
                    width : '90%',
                    height : '100%'
                }
            }
        },
        defaults : {
            bodyStyle : 'padding:10px'
        },
        items : [
            {
                html : 'Cell 1',
                rowspan : 3    //跨越3行
            }, {
                html : 'Cell 2'
            }, {
                html : 'Cell 3'
            }, {
                html : 'Cell 4'
            }, {
                html : 'Cell 5'
            }, {
                html : 'Cell 6',
                colspan : 2   //跨越2列
            }, {
                html : 'Cell 7'
            }, {
                html : 'Cell 8'
            }, {
                html : 'Cell 9'
            }
        ]
    } );
    table.show();</pre>
<p>效果图：<img class="aligncenter  wp-image-4714" src="https://blog.gmem.cc/wp-content/uploads/2013/03/table-layout.png" alt="table-layout" width="372" height="446" /></p>
<div class="blog_h3"><span class="graybg">Column Layout</span></div>
<p>该布局的特点：</p>
<ol>
<li>子组件水平分布</li>
<li>容器管理子组件的宽度，可以通过columnWidth来指定宽度</li>
<li>通常用于GridPanel的列的布局</li>
</ol>
<p>代码示例：</p>
<pre class="crayon-plain-tag">var w = Ext.create( 'Ext.window.Window', {
    title : 'Accordion Layout',
    width : 400,
    height : 100,
    layout : 'column',
    items : [
        { title : 'Panel 1', html : '.25', columnWidth: .25},
        { title : 'Panel 2', html : '.25', columnWidth: .25},
        { title : 'Panel 3', html : '1/2', columnWidth: 1/2} 
    ]
} );
w.show();</pre>
<p>效果图：<img class="aligncenter  wp-image-4716" src="https://blog.gmem.cc/wp-content/uploads/2013/03/column-layout.png" alt="column-layout" width="462" height="146" /></p>
<div class="blog_h3"><span class="graybg">Fit Layout</span></div>
<p>该布局的特点：</p>
<ol>
<li>容器只能有唯一的子组件</li>
<li>该组件充满容器的全部可用空间</li>
</ol>
<p>代码示例：</p>
<pre class="crayon-plain-tag">var w = Ext.create( 'Ext.window.Window', {
    title : 'Fit Layout',
    width : 300,
    height : 350,
    layout : 'fit',
    items : [
        { title : 'Panel 1', html : 'Fit'} 
    ]
} );
w.show();</pre>
<p>效果图：<img class="aligncenter  wp-image-4719" src="https://blog.gmem.cc/wp-content/uploads/2013/03/fit-layout.png" alt="fit-layout" width="404" height="298" /></p>
<div class="blog_h3"><span class="graybg">Card Layout</span></div>
<p>该布局是Fit Layout的子类型，特点为：</p>
<ol>
<li>与Fit Layout类似，每次只能显示一个子组件</li>
<li>可以配置多个子组件</li>
<li>可以切换子组件的显示</li>
</ol>
<p>代码示例：</p>
<pre class="crayon-plain-tag">function navHandler( btn )
{
    var activeItem = card.layout.activeItem; //获取当前激活的子组件
    var active = card.items.indexOf( activeItem );
    if ( btn.id == 'nextButton' ) active += 1;
    else if ( btn.id == 'prevButton' ) active -= 1;
    card.layout.setActiveItem( active ); //通过序号激活子组件
    var prev = card.dockedItems.items[1].items.items[0];
    var next = card.dockedItems.items[1].items.items[2];
    if ( active == 0 ) prev.setDisabled( true );
    else if ( active == 1 )
    {
        prev.setDisabled( false );
        next.setDisabled( false );
    }
    else if ( active == 2 ) next.setDisabled( true );
};
var card = Ext.create( 'Ext.window.Window', {
    title : 'Card Layout',
    width : 350,
    height : 200,
    layout : 'card',
    activeItem : 0,
    defaults : {
        border : false
    },
    bbar : [
        {
            id : 'prevButton',
            text : 'Preivous Step',
            handler : navHandler,
            disabled : true
        }, '-&gt;', //该记号用于占满多余的空间
        {
            id : 'nextButton',
            text : 'Next Step',
            handler : navHandler
        }
    ],
    items : [
        {
            html : 'Step 1 of 3'
        }, {
            html : 'Step 2 of 3'
        }, {
            html : 'Step 3 of 3'
        }
    ]
} );
card.show();</pre>
<p>效果图：<img class="aligncenter size-full wp-image-4721" src="https://blog.gmem.cc/wp-content/uploads/2013/03/card-layout.png" alt="card-layout" width="366" height="217" /></p>
<div class="blog_h3"><span class="graybg">Border Layout</span></div>
<p>该布局的特点：</p>
<p>把容器划分为东西南北中五个区域：</p>
<ol>
<li>最多有五个子组件，最少一个子组件，中间区域必不可少</li>
<li>每个子组件需要指定其占据的区域（region）</li>
<li>除了中间区域，其他区域可以收缩</li>
<li>南北区域可以指定高度，东西区域可以指定宽度，剩余的区域由中间占有</li>
</ol>
<p>该布局的示意图如下：<img class="aligncenter  wp-image-4724" src="https://blog.gmem.cc/wp-content/uploads/2013/03/border-layout-0.png" alt="border-layout-0" width="361" height="264" /></p>
<p>代码示例：</p>
<pre class="crayon-plain-tag">var border = Ext.create( 'Ext.window.Window', {
    width : 700,
    height : 500,
    title : 'Border Layout',
    layout : 'border',
    defaults : {
        xtype : 'panel'
    },
    items : [
        {
            title : 'North Region is resizable',
            region : 'north', //子组件占据的区域
            height : 100,  //南北区域可以指定高度
            split : true
        }, {
            title : 'South Region is resizable',
            region : 'south',
            height : 100,
            split : true
        }, {
            title : 'West Region is collapsible',
            region : 'west',
            width : 200,  //东西区域可以指定宽度
            collapsible : true,
            layout : 'fit'
        }, {
            title : 'East Region is collapsible',
            region : 'east',
            width : 200,
            collapsible : true,
            layout : 'fit'
        }, {
            //剩余空间全部归中间区域
            title : 'Center Region',
            region : 'center',
            layout : 'fit'
        }
    ]
} );</pre>
<p>效果图：<img class="aligncenter  wp-image-4726" src="https://blog.gmem.cc/wp-content/uploads/2013/03/border-layout.png" alt="border-layout" width="575" height="413" /></p>
<div class="blog_h2"><span class="graybg">组件布局</span></div>
<p>与容器布局不同，组件布局关注组件内部结构（而不是容器的子组件）组成的大小、位置。</p>
<div class="blog_h3"><span class="graybg">Dock Layout</span></div>
<p>Dock Layout主要用于提高面板头（Header）、工具栏（Toolbar）的灵活性。</p>
<p>考虑以下包含了若干工具栏配置的面板：</p>
<pre class="crayon-plain-tag">Ext.create( 'Ext.panel.Panel', {
    collapsible : true,
    width : 400,
    renderTo : Ext.getBody(),
    title : 'Ext 4 Panel - Header',
    html : 'Panel body HTML',  //面板体的HTML内容
    //顶部工具栏
    tbar : Ext.create( 'Ext.toolbar.Toolbar', {
        items : [
            {
                type : 'button',
                text : 'Button - Top Toolbar'
            }
        ]
    } ),
    //顶部工具栏
    bbar : Ext.create( 'Ext.toolbar.Toolbar', {
        items : [
            {
                type : 'button',
                text : 'Button - Bottom Toolbar'
            }
        ]
    } ),
    //脚注工具栏
    fbar : Ext.create( 'Ext.toolbar.Toolbar', {
        items : [
            {
                type : 'button',
                text : 'Button - Footer Toolbar'
            }
        ]
    } )
} );</pre>
<p>其UI效果图如下：<img class="aligncenter size-full wp-image-4730" src="https://blog.gmem.cc/wp-content/uploads/2013/03/panel-with-bars.png" alt="panel-with-bars" width="400" height="124" /></p>
<p>从生成的HTML元素的角度来看，该面板包含一个外部包装元素（outer wrapper element）,该包装元素包含面板头、面板体包装元素两个部分，后者由顶栏、面板体元素、底栏、脚注栏几个部分组成，如下图 所示：<img class="aligncenter  wp-image-4731" src="https://blog.gmem.cc/wp-content/uploads/2013/03/panel-with-bars-el.png" alt="panel-with-bars-el" width="359" height="216" /></p>
<p>可以看到，在默认情况下，顶部工具栏、面板头、面板体、底部工具栏等的相对位置关系是固定的，ExtJS 3中，无法改变这种位置关系，ExtJS 4则包含若干改进，便于用户在多个位置摆放工具栏：</p>
<ol>
<li>面板头被抽象为组件（Ext.panel.Header），允许设置其位置为top、bottom、left、right</li>
<li>可以使用dockedItems来配置各工具栏，从而将其放在任何方向</li>
</ol>
<p>使用上述改进来配置面板头、工具栏，代码如下：</p>
<pre class="crayon-plain-tag">Ext.create( 'Ext.panel.Panel', {
    collapsible : true,
    width : 400,
    height : 400,
    border : true,
    renderTo : Ext.getBody(),
    title : 'Ext 4 Panel - Header',
    headerPosition : 'right', //面板头的位置，改在右边
    html : 'Panel Body HTML',
    dockedItems : [
        {
            xtype : 'toolbar',
            dock : 'left',  //该工具栏放在左边
            items : [
                {
                    xtype : 'button',
                    text : 'Left Toolbar'
                }
            ]
        }, {
            xtype : 'toolbar',
            dock : 'bottom',
            items : [
                {
                    xtype : 'button',
                    text : 'Bottom Toolbar'
                }
            ]
        }, {
            xtype : 'toolbar',
            dock : 'bottom',
            items : [
                {
                    xtype : 'component',
                    flex : 1
                }, {
                    xtype : 'button',
                    text : 'Footer Toolbar'
                }
            ]
        }
    ]
} );</pre>
<p> 效果图：<img class="aligncenter size-full wp-image-4732" src="https://blog.gmem.cc/wp-content/uploads/2013/03/panel-with-bars-docked.png" alt="panel-with-bars-docked" width="402" height="402" /></p>
<p>出于对ExtJS 3兼容性的考虑，ExtJS 4仍然支持tbar、bbar、fbar，并且新增了rbar、lbar两个类似的配置项。</p>
<div class="blog_h3"><span class="graybg">Toolbar Layout</span></div>
<p>面板头工具（Header Tools）在ExtJS 4中也作为组件看待，ExtJS会在面板头部显示一系列的图标，其行为需要用户自行定义：</p>
<pre class="crayon-plain-tag">Ext.create( 'Ext.panel.Panel', {
    width : 500,
    renderTo : Ext.getBody(),
    html : 'Panel',
    title : 'Tools - Header',
    tools : [
        {
            type : 'close',
            handler : function()
            {}//定义按钮的行为
        }, {
            type : 'collapse'
        }, {
            type : 'down'
        }, {
            type : 'expand'
        }, {
            type : 'gear'
        }, {
            type : 'help'
        }, {
            type : 'left'
        }, {
            type : 'maximize'
        }, {
            type : 'minimize'
        }, {
            type : 'minus'
        }, {
            type : 'next'
        }, {
            type : 'pin'
        }, {
            type : 'plus'
        }, {
            type : 'prev'
        }, {
            type : 'print'
        }, {
            type : 'refresh'
        }, {
            type : 'restore'
        }, {
            type : 'right'
        }, {
            type : 'save'
        }, {
            type : 'search'
        }, {
            type : 'toggle'
        }, {
            type : 'unpin'
        }, {
            type : 'up'
        }
    ]
} );</pre>
<p>效果图如下：<img class="aligncenter size-full wp-image-4734" src="https://blog.gmem.cc/wp-content/uploads/2013/03/panel-tools.png" alt="panel-tools" width="500" height="42" /></p>
<div class="blog_h3"><span class="graybg">Field Layout</span></div>
<p>在ExtJS 4中，FormLayout不再被支持，组织表单字段布局的职责由新引入的FieldLayout承担。ExtJS 4的表单布局具有以下特性：</p>
<ol>
<li>表单容器默认使用Anchor Layout</li>
<li>不再需要为字段指定绝对值宽度，而可以使用anchor指定百分比或者负数</li>
<li>表单中的单个字段（Ext.form.field.Field）的输入框、错误信息由字段布局（Field Layout）确定。在显示错误信息时，ExtJS自动收缩表单字段，以腾出空间。因此，用户不需要考虑如何为错误信息预留空间</li>
</ol>
<p>下面是一个ExtJS 4表单的例子：</p>
<pre class="crayon-plain-tag">//通过设置ExtJS类的原型对象的属性，可以影响所有以后创建的实例
Ext.form.Field.prototype.msgTarget = 'side';
Ext.create( 'Ext.form.Panel', {
    frame : true,
    title : 'Form',
    margin : 5,
    bodyStyle : 'padding:5px 5px 0',
    width : 350,
    renderTo : Ext.getBody(),
    fieldDefaults : {
        msgTarget : 'side',
        labelWidth : 75
    },
    defaultType : 'textfield',
    //可以指定任何布局方式
    layout : {
        type : 'vbox',
        align : 'stretch'
    },
    defaults : {
        //与ExtJS 3不同，不需要指定字段宽度，而可以使用anchor指定宽度（支持百分比、负数）
        anchor : '100%'
    },
    items : [
        {
            fieldLabel : 'First Name',
            name : 'first'
        }, {
            fieldLabel : 'Last Name',
            name : 'last'
        }
    ]
} );</pre>
<div class="blog_h3"><span class="graybg">TriggerField Layout</span></div>
<p>与Field Layout类似，该布局机制可以为Trigger字段的错误信息自动预留空间。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/extjs-4-layouts">浅析ExtJS 4布局组件</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/extjs-4-layouts/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
