在应用中使用Ext Loader
原文:http://www.sencha.com/blog/using-ext-loader-for-your-application/
ExtJS 4.0是一个使用新的依赖系统的类加载系统。这两个强大的新功能允许你创建大量允许浏览器按需下载脚本代码的应用。
今天,我们将通过建立一个小的使用新的类加载系统的应用程序来熟悉一下依赖管理系统。同时,我们将讨论Ext加载系统的各种配置项。
在开始之前,我们先来看看将要实现的结果。这样做,可使我们确定需要扩展那些类。
应用会包括互相绑定的GridPanel和FormPanel,名称分别为UserGridPanel和UserFormPanel。UserGridPanel的操作需要创建一个模型和Store。UserGridPanel和UserFormPanel将被渲染到一个名称为UserEditorWindow的窗口,它扩张自ExtJS的Window类。所有这些类都会在命名空间MyApp下。
在开始编码前,首先要确定目录结构,以下是使用命名空间组织的文件夹:
从上图可以看到,MyApp目录已经按照命名空间进拆分成几个目录。在完成开发的时候,我们的应用将会有一个如下图所示的内部依赖运行模型。
(尽管应用的目录构成很象ExtJS 4 MVC架构,事实上示例并没有使用它 )
现在开始编写index.html文件,这里需要包含应用需要的启动文件和应用的根文件(app.js)。
1 | DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd" > |
2 | < html > |
3 | < head > |
4 | < title > Ext 4 Loader < /title > |
5 | < link rel = " stylesheet " type = " text/css " href = " js/ext-4.0.1/resources/css/ext-all.css " / > |
6 | |
7 | < script type = " text/javascript " src = " js/MyApp/app.js " > |
8 | < /head > |
9 | < body > |
10 | < /body > |
11 | < /html > |
index.html文件中需要使用link标记包含ExtJS 4的样式文件。包含ext-debug.js文件的javascript标记可能要修改多次,ext-all-debug.js文件是开发调试用的,而ext-all.js则是在发布产品时使用的。
这里有几个选择,每个选择都有优缺点。
以下是这些文件的说明:
ext-all-debug-w-comments.js:带注释的的完整调试版本。文件比较大,加载时间比较长。
ext-all-debug.js : 不带注释的完整调试版本。文件也比较大,但很适合调试。
ext-all.js ;压缩后的完整版本,文件比较小。使用该版本调试很困难,因此一般在发布产品时才使用。
ext-debug.js : 该文件只包含ExtJS基础架构和空的结构。使用该文件,可以实现ExtJS类文件的远程加载,而且提供了很好的调试体验,不过代价是相当的慢。
ext.js : ext-debug.js的压缩版本。
我们的index.html将使用ext-debug.js文件,这是实现动态加载所需的最低要求。最后,我们将展示如何使用ext-all版本获取最好的结果。
由于UserGridPanel 类要求模型和Store,因而,要先定义编写这些支持类。现在开始编写模型和Store:
1 | Ext . define ( ' MyApp . models . UserModel ' , { |
2 | extend : ' Ext . data . Model ' , |
3 | fields : [ |
4 | ' firstName ' , |
5 | ' lastName ' , |
6 | ' dob ' , |
7 | ' userName ' |
8 | ] |
9 | } ) ; |
以上代码扩展自Ext.data.Model,将创建UserModel 类。因为扩展自Ext.data.Model类,ExtJS会自动加载它,并在它加载后创建UserModel类。
下一步,要创建扩展自Ext.data.Store的UserStore 类:
1 | Ext . define ( ' MyApp . stores . UserStore ' , { |
2 | extend : ' Ext . data . Store ' , |
3 | singleton : true , |
4 | requires : [ ' MyApp . models . UserModel ' ] , |
5 | model : ' MyApp . models . UserModel ' , |
6 | constructor : function ( ) { |
7 | this . callParent ( arguments ) ; |
8 | this . loadData ( [ |
9 | { |
10 | firstName : ' Louis ' , |
11 | lastName : ' Dobbs ' , |
12 | dob : ' 12 / 21 / 34 ' , |
13 | userName : ' ldobbs ' |
14 | } , |
15 | { |
16 | firstName : ' Sam ' , |
17 | lastName : ' Hart ' , |
18 | dob : ' 03 / 23 / 54 ' , |
19 | userName : ' shart ' |
20 | } , |
21 | { |
22 | firstName : ' Nancy ' , |
23 | lastName : ' Garcia ' , |
24 | dob : ' 01 / 18 / 24 ' , |
25 | userName : ' ngarcia ' |
26 | } |
27 | ] ) ; |
28 | } |
29 | } ) ; |
当创建单件模式的UserStore 时,需要在UserStore原型添加一个requires关键字,它会在类实例化前,为ExtJS提供一个类的请求列表。在这个示例,列表中只有UserModel 一个请求类。
(实际上, 在Store的原型中定义了model为UserModel 类,ExtJS就会自动加载它。在requires关键字中列出的目的,是希望你的代码能自文档化(self-documenting),从而提醒你,UserModel 类是必须的 )
好了,UserGridPanel视图需要的基类已经创建了,现在可以创建UserGridPanel类了:
1 | Ext . define ( ' MyApp . views . UsersGridPanel ' , { |
2 | extend : ' Ext . grid . Panel ' , |
3 | alias : ' widget . UsersGridPanel ' , |
4 | requires : [ ' MyApp . stores . UserStore ' ] , |
5 | initComponent : function ( ) { |
6 | this . store = MyApp . stores . UserStore ; |
7 | this . columns = this . buildColumns ( ) ; |
8 | this . callParent ( ) ; |
9 | } , |
10 | buildColumns : function ( ) { |
11 | return [ |
12 | { |
13 | header : ' First Name ' , |
14 | dataIndex : ' firstName ' , |
15 | width : 70 |
16 | } , |
17 | { |
18 | header : ' Last Name ' , |
19 | dataIndex : ' lastName ' , |
20 | width : 70 |
21 | } , |
22 | { |
23 | header : ' DOB ' , |
24 | dataIndex : ' dob ' , |
25 | width : 70 |
26 | } , |
27 | { |
28 | header : ' Login ' , |
29 | dataIndex : ' userName ' , |
30 | width : 70 |
31 | } |
32 | ] ; |
33 | } |
34 | } ) ; |
在上面代码中,要注意requires 关键字,看它是怎么增加UserStore 为请求类的。刚才,我们为GridPanel扩展和Store扩展配置了一个直接的依赖关系。
下一步,我们要创建FormPanel扩展:
1 | Ext . define ( ' MyApp . views . UserFormPanel ' , { |
2 | extend : ' Ext . form . Panel ' , |
3 | alias : ' widget . UserFormPanel ' , |
4 | bodyStyle : ' padding : 10px ; background - color : #DCE5F0 ; ' |
5 | + ' border - left : none ; ' , |
6 | defaultType : ' textfield ' , |
7 | defaults : { |
8 | anchor : ' - 10 ' , |
9 | labelWidth : 70 |
10 | } , |
11 | initComponent : function ( ) { |
12 | this . items = this . buildItems ( ) ; |
13 | this . callParent ( ) ; |
14 | } , |
15 | buildItems : function ( ) { |
16 | return [ |
17 | { |
18 | fieldLabel : ' First Name ' , |
19 | name : ' firstName ' |
20 | } , |
21 | { |
22 | fieldLabel : ' Last Name ' , |
23 | name : ' lastName ' |
24 | } , |
25 | { |
26 | fieldLabel : ' DOB ' , |
27 | name : ' dob ' |
28 | } , |
29 | { |
30 | fieldLabel : ' User Name ' , |
31 | name : ' userName ' |
32 | } |
33 | ] ; |
34 | } |
35 | } ) ; |
因为UserForm 不需要从服务器端请求任何类,因而不需要添加requires定义。
应用快完成了,现在需要创建UserEditorWindow类和运行应用的app.js。以下是UserEditorWindow类的代码。因为要将Grid和表单绑定在一起,因而类代码有点长,请见谅:
1 | Ext . define ( ' MyApp . views . UserEditorWindow ' , { |
2 | extend : ' Ext . Window ' , |
3 | requires : [ ' MyApp . views . UsersGridPanel ' , ' MyApp . views . UserFormPanel ' ] , |
4 | height : 200 , |
5 | width : 550 , |
6 | border : false , |
7 | layout : { |
8 | type : ' hbox ' , |
9 | align : ' stretch ' |
10 | } , |
11 | initComponent : function ( ) { |
12 | this . items = this . buildItems ( ) ; |
13 | this . buttons = this . buildButtons ( ) ; |
14 | this . callParent ( ) ; |
15 | this . on ( ' afterrender ' , this . onAfterRenderLoadForm , this ) ; |
16 | } , |
17 | buildItems : function ( ) { |
18 | return [ |
19 | { |
20 | xtype : ' UsersGridPanel ' , |
21 | width : 280 , |
22 | itemId : ' userGrid ' , |
23 | listeners : { |
24 | scope : this , |
25 | itemclick : this . onGridItemClick |
26 | } |
27 | } , |
28 | { |
29 | xtype : ' UserFormPanel ' , |
30 | itemId : ' userForm ' , |
31 | flex : 1 |
32 | } |
33 | ] ; |
34 | } , |
35 | buildButtons : function ( ) { |
36 | return [ |
37 | { |
38 | text : ' Save ' , |
39 | scope : this , |
40 | handler : this . onSaveBtn |
41 | } , |
42 | { |
43 | text : ' New ' , |
44 | scope : this , |
45 | handler : this . onNewBtn |
46 | } |
47 | ] ; |
48 | } , |
49 | onGridItemClick : function ( view , record ) { |
50 | var formPanel = this . getComponent ( ' userForm ' ) ; |
51 | formPanel . loadRecord ( record ) |
52 | } , |
53 | onSaveBtn : function ( ) { |
54 | var gridPanel = this . getComponent ( ' userGrid ' ) , |
55 | gridStore = gridPanel . getStore ( ) , |
56 | formPanel = this . getComponent ( ' userForm ' ) , |
57 | basicForm = formPanel . getForm ( ) , |
58 | currentRec = basicForm . getRecord ( ) , |
59 | formData = basicForm . getValues ( ) , |
60 | storeIndex = gridStore . indexOf ( currentRec ) , |
61 | key ; |
62 | // loop through the record and set values |
63 | currentRec . beginEdit ( ) ; |
64 | for ( key in formData ) { |
65 | currentRec . set ( key , formData [ key ] ) ; |
66 | } |
67 | currentRec . endEdit ( ) ; |
68 | currentRec . commit ( ) ; |
69 | // Add and select |
70 | if ( storeIndex = = - 1 ) { |
71 | gridStore . add ( currentRec ) ; |
72 | gridPanel . getSelectionModel ( ) . select ( currentRec ) |
73 | } |
74 | } , |
75 | onNewBtn : function ( ) { |
76 | var gridPanel = this . getComponent ( ' userGrid ' ) , |
77 | formPanel = this . getComponent ( ' userForm ' ) , |
78 | newModel = Ext . ModelManager . create ( { } , |
79 | ' MyApp . models . UserModel ' ) ; |
80 | gridPanel . getSelectionModel ( ) . clearSelections ( ) ; |
81 | formPanel . getForm ( ) . loadRecord ( newModel ) |
82 | } , |
83 | onAfterRenderLoadForm : function ( ) { |
84 | this . onNewBtn ( ) ; |
85 | } |
86 | } ) ; |
UserEditorWindow 的代码包含了许多东西用来管理UserGridPanel和UserFormPanel类的整个绑定的声明周期。为了指示ExtJS在创建该类前加载这两个类,必须在requires列表里列出它们。
现在完成最后一个文件app.js。为了最大限度地提高我们的学习,将有3次修改要做。首先从最简单配置开始,然后逐步添加。
1 | Ext . Loader . setPath ( ' MyApp ' , ' js / MyApp ' ) ; |
2 | Ext . onReady ( function ( ) { |
3 | Ext . create ( ' MyApp . views . UserEditorWindow ' ) . show ( ) ; |
4 | } ) ; |
首先,app.js会在ExtJS添加MyApp命名空间的路径,这可通过调用Ext.loader.setPath方法实现,方法的第1个参数是命名空间,然后是加载文件与页面的相对路径。
下一步,调用Ext.OnReady方法,传递一个包含Ext.create的匿名函数。Ext.create会在ExtJS 4.0初始化之后执行,以字符串形式传递的UserEditorWindow 类会被实例化。因为不需要指向实例和希望立即显示它,因而在后面串接了show方法的调用。
如果你打开这个页面(http://moduscreate.com/senchaarticles/01/pass1.html ),你会看到UI渲染,但很慢,并且ExtJS会在Firebug中显示以下警告信息:
ExtJS提示我们没有使用加载系统最优化的方式。这是第二步要讨论的问题。然后,这是一个好的学习机会,要好好理由。
我们需要配置Firebug在控制台中显示XHR请求,以便在控制台中看到所有请求,而不需要切换到网络面板。这样,我们不单可以观察到类依赖系统的工作情况,还可以从所有ExtJS类加载的文件中通过过滤方式找到我们要求这样的文件。
在Firebug控制台过滤输入框中输入“User”,你会看到下图所示的结果。
从图中可以看到,UserEditorWindow类第一个被加载,接着请求UserGridPanel。UserGridPanel 要求UserStore和UserModel类。最后加载UserFormPanel 类。
我刚才提到,ExtJS提示了我们没有使用加载系统最优化的方式。这是因为依赖是在Ext.OnReady触发加载之后通过同步XHR请求确定的,而这不是有效的方式且难于调试。
未来修正这个问题,可以修改app.js指示ExtJS先加载我们定义的类,这样即可提供性能又便于调试:
1 | Ext . Loader . setPath ( ' MyApp ' , ' js / MyApp ' ) ; |
2 | Ext . require ( ' MyApp . views . UserEditorWindow ' ) ; |
3 | Ext . onReady ( function ( ) { |
4 | Ext . create ( ' MyApp . views . UserEditorWindow ' ) . show ( ) ; |
5 | } ) ; |
为了快速加载我们定义的类和避免调试信息,可简单的在Ext.onReady前调用Ext.require,只是ExtJS请求UserEditorWindow类。这将会让ExtJS在文档HEAD标记内注入一个script标记,运行资源在Ext.OnReady前加载。
查看http://moduscreate.com/senchaarticles/01/pass2.html 可看到它是如何工作地。在页面加载后,你会注意到ExtJS没有在控制台显示警告信息了。
我们所做的是让ExtJS框架和应用类延迟加载。虽然这样做调试很好,但是对于需要快速调试的情况,页面渲染时间会让你感到痛苦。为什么?
原因很简单,因为这需要加载许多资源文件。在示例中,ExtJS发送了193个Javascript资源请求到web服务器,还有部分是在缓存中的:
我们创建了6个Javascript文件(5个类文件和app.js),这意味着加载要求的ExtJS文件有187个请求。当你在本地做开发的时候,这个方案可行,但不是最理想的和效果最好的。
解决这个问题,我们可以使用折中方案,通过ext-all-debug加载ExtJS框架,动态加载我们的类文件。要实现这个,需要修改两个文件。
首先,需要修改Index.html,使用ext-all-debug.js替换ext.debug.js。
1 | < script type = " text/javascript " src = " js/ext-4.0.1/ext-all-debug.js " > < / script > |
接着,修改app.js,开启Ext.Loader:
1 | ( function ( ) { |
2 | Ext . Loader . setConfig ( { |
3 | enabled : true , |
4 | paths : { |
5 | MyApp : ' js / MyApp ' |
6 | } |
7 | } ) ; |
8 | |
9 | Ext . require ( ' MyApp . views . UserEditorWindow ' ) ; |
10 | |
11 | Ext . onReady ( function ( ) { |
12 | Ext . create ( ' MyApp . views . UserEditorWindow ' ) . show ( ) ; |
13 | } ) ; |
14 | } ) ( ) ; |
通过调用Loader.setConfig可开启Ext.Loader,需要传递一个匿名对象,它的eanbled属性设置为true,而命名空间设置为路径映射。
通过编辑app.js,在本地开发环境下,应用将会在1秒内完成加载和渲染。
源代码下载地址:http://moduscreate.com/senchaarticles/01/files.zip
作者:
Jay Garcia
Author of Ext JS in Action
and Sencha Touch in Action
, Jay Garcia has been an evangelist of Sencha-based JavaScript frameworks since 2006. Jay is also Co-Founder and CTO of Modus Create
, a digital agency focused on leveraging top talent to develop high quality Sencha-based applications. Modus Create is a Sencha Premier partner.