ExtJS4教程

1     Extjs初步

1.1     搭建ExtJS可视化开发环境:

假设您的机器已经安装Eclipse,Tomcat和Sql Server(或MySql),如果没有请参阅其他相关资料。

安装ExtJS应用的可视化开发环境:

可以从http://www.putdb.com获取,或直接从http://www.putdb.com/zh_cn.html下载,并按提示或以下步骤进行安装:

1、安装任意类型的一款 JAVA Web 应用服务器(如 Tomcat)和数据库(如 MySql),并且在Web应用服务器中创建一个 JNDI 链接串,以访问所创建的数据库。

2、解压缩下载的安装包 webbuilder.zip,并把 wb 目录部署到 Web 应用服务器。例如,您使用 Tomcat, 那么可直接复制 wb 目录至Tomcat的 webapps/wb目录。

3、打开浏览器并访问 [http://ip:port/wb],使用安装向导来完成WebBuilder 的安装。例如,访问http://localhost:8080/wb

4、使用管理员帐户登录到系统,管理员默认用户名称和密码均为 admin。

1.2     测试例子

开始...

Webroot目录下建立hellowword.js 输入如下内容:

Ext.application({

    name: 'HelloExt',

    launch: function() {

        Ext.create('Ext.container.Viewport'{

            layout: 'fit',

            items: [

                {

                    title: 'Hello Ext',

                    html : 'Hello! Welcome to Ext JS.'

                }

            ]

        });

    }

});

再建立一个helloword.html,输入如下内容

<html>

<head>

    <title>Hello Ext</title>

    <link rel="stylesheet" type="text/css" href="resources/css/ext-all.css">

    <script type="text/javascript" src="ext-all.js"></script>

    <script type="text/javascript" src="HelloExt.js"></script>

</head>

<body></body>

</html>

Html文件中只引入了一个css和2个js文件,注意引用路径和你建立文件路径是否能匹配,如果路径没有问题的话,打开浏览器输入

http://localhost:8080/Ext4/helloworld.html您将会看到浏览器里显示一个panel,标题是Hello Ext,内容Hello! Welcome to Ext JS.,如果没有,请查看是否有路径不匹配。

其他:

在ExtJS里最常用的,应该就是Ext.onReady和Ext.application这两个个方法了,而且它也可能是你学习ExtJS所接触的第一个方法,这个方法在当前的DOM加载完毕后自动调用,保证页面内的所有元素都能被Script所引用.可以尝试在这个方法中添加一条语句,看看页面打开后是什么反映

(先建立js和html文件,将如下代码加入js文件中,html文件相应引入对应的js文件, 本文档所有示例代码均如此方式运行以下不再重复)

Ext.onReady(function() {

alert('hello world!');

});

上面的代码将在页面加载完毕后弹出一对话框,打印出'hello world!'字样.

获取元素

还有一个常用的方法,就是获取页面上的元素了,ExtJS提供了一个get方法,可以根据ID取到页面上的元素:

var myDiv = Ext.get('myDiv');

会取到页面上ID为'myDiv'的元素.如果使用Element.dom的方法,则可以直接操作底层的DOM节点,Ext.get返回的则是一个Element对象.

在不能使用这种方式来获取多个DOM的节点,或是要获取一些ID不一致,但又有相同特征的时候,可以通过选择器来进行获取,比如要获取页面上所有的

标签,则可以使用:

var ps = Ext.select('p');

这样你就可以对所要获取的元素进行操作了,select()方法返回的是Ext.CompositeElement对象,可以通过其中的each()方法对其所包含的节点进行遍历:

 

ps.each(function(el) {

el.highlight();

});

当然,如果你要是对获取的所有元素进行相同的操作,可以直接应用于CompositeElement对象上,如:

ps.highlight();

或是:

Ext.select('p').highlight();

当然,select参数还可以更复杂一些,其中可以包括W3C Css3Dom选取器,基本的XPath,HTML属性等,详细情况,可以查看DomQuery API的文档,来了解细节.

事件响应

获取到了元素,则可能会对一些元素的事件进行一些处理,比如获取一个按钮,我们为它添加一个单击事件的响应:

复制代码 代码如下:

Ext.onReady(function() {

Ext.get('myButton').on('click', function() {

alert('You clicked the button!');

});

});

 

 

当然,你可以把事件的响应加到通过select()方法获取到的元素上:

复制代码 代码如下:

Ext.select('p').on('click', function() {

alert('You clicked a paragraph!');

});

 

 

Widgets

ExtJS还提供了丰富的UI库来供大家使用.

 

2     Extjs4布局详解

2.1     Fit布局

在Fit布局中,子元素将自动填满整个父容器。注意:在fit布局下,对其子元素设置宽度是无效的。如果在fit布局中放置了多个组件,则只会显示第一个子元素

在Fit布局中,子元素将自动填满整个父容器。注意:在fit布局下,对其子元素设置宽度是无效的。如果在fit布局中放置了多个组件,则只会显示第一个子元素。典型的案例就是当客户要求一个window或panel中放置一个GRID组件,grid组件的大小会随着父容器的大小改变而改变。

示例代码:

Ext.application({

    name: 'HelloExt',

    launch: function() {

        Ext.create('Ext.container.Viewport'{

            layout: 'fit',

            items: [

                {

                    title: 'Hello Ext',

                    html : 'Hello! Welcome to Ext JS.'

                }

            ]

        });

    }

});

 

2.2     Border布局

border布局:border布局也称边界布局,他将页面分隔为west,east,south,north,center这五个部分,我们需要在在其items中指定使用region参数为其子元素指定具体位置。

border布局:border布局也称边界布局,他将页面分隔为west,east,south,north,center这五个部分,我们需要在在其items中指定使用region参数为其子元素指定具体位置。

注意:north和south部分只能设置高度(height),west和east部分只能设置宽度(width)。north south west east区域变大,center区域就变小了。

参数 split:true 可以调整除了center四个区域的大小。

参数 collapsible:true 将激活折叠功能,title必须设置,因为折叠按钮是出现标题部分的。

center 区域是必须使用的,而且center 区域不允许折叠。Center区域会自动填充其他区域的剩余空间。尤其在Extjs4.0中,当指定布局为border时,没有指定center区域时,会出现报错信息。

示例代码:

 

Ext.application({

    name:"HelloExt",

    launch:function () {

        Ext.create('Ext.panel.Panel'{

            width: 1024,

            height: 720,

            layout: 'border',

            items: [{

                region: 'south',

                xtype: 'panel',

                height: 20,

                split: false,

                html: '欢迎登录!',

                margins: '0 5 5 5'

            },{

                title: 'West Region is collapsible',

                region:'west',

                xtype: 'panel',

                margins: '5 0 0 5',

                width: 200,

                collapsible: true,

                id: 'west-region-container',

                layout: 'fit'

            },{

                title: 'Center Region',

                region: 'center',

                xtype: 'panel',

                layout: 'fit',

                margins: '5 5 0 0',

                html:'Extjs4中,center区域必须指定,否则会报错。'

            }],

            renderTo: Ext.getBody()

    });

    }

});

 

 

 

2.3     Accordion布局

accordion布局:accordion布局也称手风琴布局,在accordion布局下,在任何时间里,只有一个面板处于激活状态。其中每个面边都支持展开和折叠。

 

accordion布局:accordion布局也称手风琴布局,在accordion布局下,在任何时间里,只有一个面板处于激活状态。其中每个面边都支持展开和折叠。注意:只有Ext.Panels 和所有Ext.panel.Panel 子项,才可以使用accordion布局。

示例代码:

 

Ext.application({

    name:"HelloExt",

    launch:function () {

        Ext.create('Ext.panel.Panel'{

        title: 'Accordion Layout',    

        width: 300,     

        height: 300,

        x:20,

        y:20,

        layout:'accordion',    

        defaults: {

            bodyStyle: 'padding:15px'

        },

        layoutConfig: {       

            titleCollapse: false,        

            animate: true,        

            activeOnTop: true     

        },    

        items: [{        

            title: 'Panel 1',        

            html: 'Panel content!'     

        },{        

            title: 'Panel 2',        

            html: 'Panel content!'     

        },{

            title: 'Panel 3',        

            html: 'Panel content!'    

        }],     

        renderTo: Ext.getBody()

    });

}});

 

 

2.4     Card布局

Card布局:这种布局用来管理多个子组件,并且在任何时刻只能显示一个子组件。这种布局最常用的情况是向导模式,也就是我们所说的分布提交。

 

Card布局:这种布局用来管理多个子组件,并且在任何时刻只能显示一个子组件。这种布局最常用的情况是向导模式,也就是我们所说的分布提交。Card布局可以使用layout:'card'来创建。注意:由于此布局本身不提供分步导航功能,所以需要用户自己开发该功能。由于只有一个面板处于显示状态,那么在初始时,我们可以使用setActiveItem功能来指定某一个面板的显示。当要显示下一个面板或者上一个面板的时候,我们可以使用getNext()或getPrev()来得到下一个或上一个面板。然后使用setDisabled方法来设置面板的显示。另外,如果面板中显示的是FORM布局,我们在点击下一个面板的时候,处理FORM中提交的元素,通过AJAX将表单中的内容保存到数据库中或者SESSION中。

下面的示例代码展示了一个基本的Card布局,布局中并没有包含form元素,具体情况,还要根据实际情况进行处理:

Ext.application({

    name: 'HelloExt',

    launch: function() {

        var navigate = function(panel, direction){     

            var layout = panel.getLayout();    

            layout[direction]();     

            Ext.getCmp('move-prev').setDisabled(!layout.getPrev());     

            Ext.getCmp('move-next').setDisabled(!layout.getNext());

        }

        Ext.create('Ext.panel.Panel'{    

            title: 'Card布局示例',    

            width: 300,     

            height: 202,    

            layout: 'card',     

            activeItem: 0,

            x:30,

            y:60,

            bodyStyle: 'padding:15px',    

            defaults: {border: false},

            bbar: [{             

                id: 'move-prev',            

                text: 'Back',             

                handler: function(btn) {

                    navigate(btn.up("panel"), "prev");            

                },

                disabled: true

            },

            '->',

            {

                id: 'move-next',            

                text: 'Next',             

                handler: function(btn) {

                    navigate(btn.up("panel"), "next");

                }

            }],

            items: [{

                id: 'card-0',

                html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'     

            },

            {

                id: 'card-1',

                html: '<p>Step 2 of 3</p>'

            },

            {

                id: 'card-2',

                html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'     

            }],    

            renderTo: Ext.getBody()

        });

    }

});

 

2.5     Anchor布局

anchor布局将使组件固定于父容器的某一个位置,使用anchor布局的子组件尺寸相对于容器的尺寸,即父容器容器的大小发生变化时,使用anchor布局的组件会根据规定的规则重新渲染位置和大小。

 

anchor布局将使组件固定于父容器的某一个位置,使用anchor布局的子组件尺寸相对于容器的尺寸,即父容器容器的大小发生变化时,使用anchor布局的组件会根据规定的规则重新渲染位置和大小。

AnchorLayout布局没有任何的直接配置选项(继承的除外),然而在使用AnchorLayout布局时,其子组件都有一个anchor属性,用来配置此子组件在父容器中所处的位置。

anchor属性为一组字符串,可以使用百分比或者是-数字来表示。配置字符串使用空格隔开,例如

anchor:'75% 25%',表示宽度为父容器的75%,高度为父容器的25%

anchor:'-300 -200',表示组件相对于父容器右边距为300,相对于父容器的底部位200

anchor:'-250 20%',混合模式,表示组件党对于如容器右边为250,高度为父容器的20%

示例代码:

Ext.application({

    name: 'HelloExt',

    launch: function() {

        Ext.create('Ext.Panel'{    

            width: 500,    

            height: 400,     

            title: "Anchor布局",    

            layout: 'anchor',

            x:60,

            y:80,

            renderTo: Ext.getBody(),    

            items: [{        

                xtype: 'panel',         

                title: '75% Width and 25% Height',        

                anchor: '75% 25%'    

            },{         

                xtype: 'panel',        

                title: 'Offset -300 Width & -200 Height',         

                anchor: '-300 -200'         

            },{        

                xtype: 'panel',         

                title: 'Mixed Offset and Percent',        

                anchor: '-250 30%'    

            }]

        });

    }

});

 

2.6     Absolute布局

Absolute布局继承Ext.layout.container.Anchor 布局方式,并增加了X/Y配置选项对子组件进行定位,Absolute布局的目的是为了扩展布局的属性,使得布局更容易使用

 

Absolute布局继承Ext.layout.container.Anchor 布局方式,并增加了X/Y配置选项对子组件进行定位,Absolute布局的目的是为了扩展布局的属性,使得布局更容易使用。

Ext.application({

    name:"HelloExt",

    launch:function () {

        Ext.create('Ext.form.Panel'{    

            title: 'Absolute布局',    

            width: 300,     

            height: 275,

            x:20,

            y:90,

            layout:'absolute',    

            defaultType: 'textfield',    

            items: [{

                x: 10,        

                y: 10,         

                xtype:'label',        

                text: 'Send To:'    

            },{

                x: 80,         

                y: 10,        

                name: 'to',         

                anchor:'90%'    

            },{        

                x: 10,         

                y: 40,        

                xtype:'label',        

                text: 'Subject:'    

            },{         

                x: 80,        

                y: 40,        

                name: 'subject',         

                anchor: '90%'

            },{        

                x:0,         

                y: 80,        

                xtype: 'textareafield',        

                name: 'msg',         

                anchor: '100% 100%'

            }],     

            renderTo: Ext.getBody()

        });

    }

});

 

 

 

2.7     Column布局

Column布局一般被称为列布局,这种布局的目的是为了创建一个多列的格式。其中每列的宽度,可以为其指定一个百分比或者是一个固定的宽度。

 

Column布局一般被称为列布局,这种布局的目的是为了创建一个多列的格式。其中每列的宽度,可以为其指定一个百分比或者是一个固定的宽度。

Column布局没有直接的配置选项(继承的除外),但Column布局支持一个columnWidth属性,在布局过程中,使用columnWidth指定每个面板的宽度。

注意:使用Column布局布局时,其子面板的所有columnWidth值加起来必须介于0~1之间或者是所占百分比。他们的总和应该是1。

另外,如果任何子面板没有指定columnWidth值,那么它将占满剩余的空间。
 

示例代码:

 

Ext.application({

    name:"HelloExt",

    launch:function () {

        Ext.create('Ext.panel.Panel'{         

            title: 'Column Layout - 按比例',        

            width: 350,         

            height: 250,

            x:20,

            y:100,   

            layout:'column',    

            items: [{             

                title: 'Column 1',            

                columnWidth: .25        

            },{             

                title: 'Column 2',            

                columnWidth: .55        

            },{             

                title: 'Column 3',            

                columnWidth: .20        

            }],         

            renderTo: Ext.getBody()    

        });

    }

});

 

 

3     Extjs4文档阅读

ExtJS4使用新的类机制进行了大量的重构。为了支撑新的架构,ext4几乎重写了每一个类,因此最好先好好的理解一下新的架构,再开始编码。

本文适合想在extjs4中扩展现有类或者创建新类的开发者。其实,不管是想扩展还是使用,都建议您仔细阅读一下(如果E文好的,建议您还是阅读英文原文。链接地址是:http://docs.sencha.com/ext-js/4-0/#/guide/ )。文章共分4个部分,建议每一部分都仔细研究下,对之后的开发工作,会有意想不到的好处。

 

3.1     系统类(class system)

Api文档路径:http://localhost/Ext4/docs/index.html#!/guide/class_system

第一部分:概述。说明了强大的类机制的必要性

第二部分:编码规范。讨论类、方法、属性、变量和文件命名

第三部分:DIY。详细的编码示例

第四部分:错误处理和调试。提供一些有用的调试和异常处理技巧

3.1.1  概述


ExtJS4拥有超过300个的类.迄今为止,我们的社区拥有超过20万来自世界各地,使用不同后台语言的开发者.要在这种规模的框架上提供具有以下特点的架构,需要面临巨大的挑战:

1、简单易学。

2,快速开发、调试简单、部署容易。

3,良好的结构、可扩展性和可维护性。

3.1.2  编码和规范


*在所有类、命名空间(namespace)和文件名中使用一致的命名约定,有助于保持代码的良好结构和可读性。

1) Classes

类名只能包含字母和数字。允许包含数字,但是大部分情况下不建议使用,除非这些数字是专业术语的一部分。不要使用下划线,连字符等非数字字母符号。例如:

MyCompany.useful_util.Debug_Toolbar is discouraged

MyCompany.util.Base64 is acceptable

类名应该包含在使用点号分隔的命名空间中。至少,要有一个顶级命名空间。例如:

MyCompany.data.CoolProxyMyCompany.Application

顶级命名空间和实际的类名应使用驼峰命名(CamelCased),其他则为小写。例如:

MyCompany.form.action.AutoLoad

不是Sencha开发的类(即不是Ext自带的)不要使用Ext做为顶级命名空间。缩写也要遵守以上的驼峰式命名约定。例如:

Ext.data.JsonProxy 代替 Ext.data.JSONProxy

MyCompany.util.HtmlParser代替MyCompary.parser.HTMLParser

MyCompany.server.Http代替MyCompany.server.HTTP

2) 代码文件类名对应类所在的文件(包括文件名)。因此,每个文件应该只包含一个类(类名和文件名一样)。例如:

Ext.util.Observable  存放在 path/to/src/Ext/util/Observable.js
Ext.form.action.Submit   存放在 path/to/src/Ext/form/action/Submit.js
MyCompany.chart.axis.Numeric   存放在 path/to/src/MyCompany/chart/axis/Numeric.js

path/to/src是你的应用所在目录。所有类都应该在这个通用根目录下,并且使用适当的命名空间以利于开发、维护和部署。

3)方法和变量

•和类命名一样,方法和变量也只能包含字母和数字。数字同样是允许但不建议,除非属于专业术语。不要使用下划线,连字符等任何非字母数字符号。

•方法和变量名一样使用驼峰式命名,缩写也一样。

•举例

•合适的方法名:
                   encodeUsingMd5() getHtml() 代替 getHTML()

                   getJsonResponse() 代替getJSONResponse()

                   parseXmlContent() 代替parseXMLContent()

•合适的变量名:
                   var isGoodName

                   var base64Encoder

                   var xmlReader

                   var httpServer

4)属性

  • 类属性名称遵循以上的变量和方法命名约定.除非是静态的常量.
  • 类的静态属性常量应该全部大写。例如:
    • Ext.MessageBox.YES = "Yes"
    • Ext.MessageBox.NO = "No"
    • MyCompany.alien.Math.PI = "4.13"

 

3.1.3  DIY亲自动手(示例代码)


3.1.3.1 声明

3.1.3.1.1             Extjs4之前的方式

如果你曾经使用过旧版本的extjs,那么你肯定熟悉使用Ext.extend来创建一个类:

  1: var MyWindow=Ext.extend(Object,{...});

这个方法很容易从现有的类中继承创建新的类.相比直接继承,我们没有好用的API用于类创建的其他方面,诸如:配置、静态方法、混入(Mixins)。呆会我们再来详细的重新审视这些方面。现在,让我们来看看另一个例子:

1: My.cool.Window = Ext.extend(Ext.Window, { ... });

在这个例子中,我们创建我们的新类,继承Ext.Window,放在命名空间中。我们有两个问题要解决:

1,在我们访问My.cool的Window属性之前,My.cool必须是一个已有的对象.

2,Ext.Window必须在引用之前加载.

第一个问题通常使用Ext.namespace(别名Ext.ns)来解决.该方法递归创建(如果该对象不存在)这些对象依赖.比较繁琐枯燥的部分是你必须在Ext.extend之前执行Ext.ns来创建它们.

   1: Ext.ns('My.cool');
   2: My.cool.Window = Ext.extend(Ext.Window, { ... });

第二个问题不好解决,因为Ext.Window可能直接或间接的依赖于许多其他的类,依赖的类可能还依赖其它类...出于这个原因,在ext4之前,我们通常引入整个ext-all.js,即使是我们只需要其中的一小部分.

3.1.3.1.2             Extjs4新的方式

在Extjs4中,你只需要使用一个方法就可以解决这些问题:Ext.define.以下是它的基本语法:

1: Ext.define(className, members, onClassCreated);

className: 类名

members:代表类成员的对象字面量(键值对,json)

onClassCreated: 可选的回调函数,在所有依赖都加载完毕,并且类本身建立后触发.由于类创建的新的异步特性,这个回调函数在很多情况下都很有用.这些在第四节中将进一步讨论

例如:

·                                        Ext.define('My.sample.Person', {
        name: 'Unknown',     
        constructor: function(name) {
            if (name) {
                this.name = name;
            }     
            return this;
      }, 
       eat: function(foodType) {
          alert(this.name + " is eating: " + foodType);
   
           return this;
      }
  });   
   var aaron = Ext.create('My.sample.Person', 'Aaron');
      aaron.eat("Salad"); 

程序执行结果会弹出alert窗口显示"Aaronis eating: Salad".

·    注意我们使用Ext.create()方法创建了My.sample.Person类的一个新实例.我们也可以使用新的关键字(new My.sample.Person())来创建.然而,建议养成始终用Ext.create来创建类示例的习惯,因为它允许你利用动态加载的优势.更多关于动态加载信息,请看入门指南:入门指南

3.1.3.2 配置

在ExtJS 4 ,我们引入了一个专门的配置属性,用于提供在类创建前的预处理功能.特性包括:

  • 配置完全封装其他类成员
  • getter和setter.如果类没有定义这些方法,在创建类时将自动生成配置的属性的getter和setter方法。
  • 同样的,每个配置的属性自动生成一个apply方法.自动生成的setter方法内部在设置值之前调用apply方法.如果你要在设置值之前自定义自己的逻辑,那就重载apply方法.如果apply没有返回值,则setter不会设置值.看下面applyTitle的例子:
·                                        Ext.define('My.own.Window', {
     /** @readonly */
      isWindow: true,   
       config: {
           title: 'Title Here',    
           bottomBar: {
               enabled: true,
             height: 50,
              resizable: false
          }
      },   
       constructor: function(config) {
          this.initConfig(config);   
           return this;
      },
   
       applyTitle: function(title) {
          if (!Ext.isString(title) || title.length === 0) {
              alert('Error: Title must be a valid non-empty string');
          }
          else {
              return title;
          }
      },
   
       applyBottomBar: function(bottomBar) {
          if (bottomBar && bottomBar.enabled) {
              if (!this.bottomBar) {
                  return Ext.create('My.own.WindowBottomBar', bottomBar);
              }
              else {
                  this.bottomBar.setConfig(bottomBar);
              }
          }
      }
  });

以下是它的用法:

var myWindow = Ext.create('My.own.Window', {
    title: 'Hello World',
    bottomBar: {
        height: 60
    }
});
 
alert(myWindow.getTitle()); // alerts "Hello World"
 
myWindow.setTitle('Something New');
 
alert(myWindow.getTitle()); // alerts "Something New"
 
myWindow.setTitle(null); // alerts "Error: Title must be a valid non-empty string"
 
myWindow.setBottomBar({ height: 100 }); // Bottom bar's height is changed to 100
 

3.1.3.3 Statics

静态成员可以使用statics配置项来定义

Ext.define('Computer', {
    statics: {
        instanceCount: 0,
        factory: function(brand) {
            // 'this' in static methods refer to the class itself
            return new this({brand: brand});
        }
    },
 
    config: {
        brand: null
    },
 
    constructor: function(config) {
        this.initConfig(config);
 
        // the 'self' property of an instance refers to its class
        this.self.instanceCount ++;
 
        return this;
    }
});
 
var dellComputer = Computer.factory('Dell');
var appleComputer = Computer.factory('Mac');
 
alert(appleComputer.getBrand()); // using the auto-generated getter to get the value of a config property. Alerts "Mac"
 
alert(Computer.instanceCount); // Alerts "2"
 

3.1.3.4 错误处理&调试

Extjs 4包含一些有用的特性用于调试和错误处理.你可以使用Ext.getDisplayName()来显示任意方法的名字.这对显示抛出异常的类和方法非常有用.

 

throw new Error('['+ Ext.getDisplayName(arguments.callee) +'] Some message here');

当使用Ext.define()定义的类中的方法抛出异常后.你将在调用堆栈中看到类和方法名(如果你使用webkit).例如,以下是chrome浏览器的效果:

javascript是一种类无关(原文:classless)、基于原型的语言。因此javascript最强大的特点是灵活。同样的事情可以使用不同的方式,不同的编码风格和技巧去完成。这个特点也会带来一些不可预测的风险。如果没有统一的编码规范,javascript代码将很难理解、维护和复用。

相反的,基于类的编程语言拥有较为流行的面向对象模型,强类型、内建的封装机制和强制的编码约束等特点。通过强制开发人员遵守一些大的原则来使代码的行为更容易被理解,以及提高可扩展性(这里不明白,javascript这类动态语言不是更容易扩展么?)和可伸缩性。但是,这类语言没有javascript的灵活性

 

3.2     MVC应用模式

一直想写一些Extjs4 MVC的东西,但由于自己的英文水平足够媲美小学5年纪的学生,所以在找了一些比我强一点的网友+机器翻译,总结出了以下这篇文章。但个人强烈建议去看英文原版(http://localhost/Ext4/docs/index.html#!/guide/application_architecture)。本段代码示例建议使用firefox firebug插件配合使用,ie无法加在console.log对象。

那么,我们开始吧!

对于Extjs来说,大客户端程序一直很难写,当你为大客户端程序添加更多的功能和项目的时候,项目的体积往往迅速增长。这样的大客户端程序很难组织和维持 ,所以,Extjs4配备了一个新的应用程序体系结构,它能结构化你的代码,那就是Extjs4 MVC。

Extjs4 MVC有别于其他MVC架构,Extjs有他自己定义:

1、Model是一个Field以及他的Data的集合,Modes知道如何通过Stores来表示数据,以能用于网格和其他组件。模型的工作很像Extjs3的记录集(Record class),通常通过数据加载器(Stores)渲染至网格(grid)和其他组件上边。

2、View:用以装载任何类型的组件—grid、tree和panel等等。

3、Controller—用来放使得app工作的代码,例如 render views , instantiating Models 或者其他应用逻辑。

本篇文章,我们将创建一个非常简单的应用程序,即用户数据管理,最后,你就会知道如何利用Extjs4 MVC去创建简单应用程序。Extjs4 MVC应用程序架构提供应用程序的结构性和一致性。这样的模式带来了一些重要的好处:

1、 每个应用程序的工作方式相同,我们只需要学习一次。

2、 应用程序之间的代码共享很容易,应为他们所有的工作方式都相同

3、 你可以使用EXTJS提供的构建工具创建你应用程序的优化版本。

既然是介绍Extjs4 MVC,那么,我们开始创建这个应用。

3.2.1  文件结构(File Structure): 

Extjs4 MVC的应用程序和别的应用程序一样都遵循一个统一的目录结构。在MVC布局中,所有的类放在应用程序文件夹,它的子文件夹中包含您的命名空间,模型,视图,控制器和存储器。下面来通过简单的例子来看下怎样应用。

在这个例子中,我们将整个应用程序放到一个名为”account_manager”的文件夹下,”account_manager”文件夹中的结构如上图。现在编辑index.html,内容如下:

 

<html>
<head>
    <title>Account Manager</title>
 
    <link rel="stylesheet" type="text/css" href="ext-4.0/resources/css/ext-all.css">
 
    <script type="text/javascript" src="ext-4.0/ext-debug.js"></script>
 
    <script type="text/javascript" src="app.js"></script>
</head>
<body></body>
</html>

3.2.2 创建app.js文件(Creating the application)

所有Extjs4MVC应用从Ext.application的一个实例开始,应用程序中应包含全局设置、以及应用程序所使用的模型(model),视图(view)和控制器(controllers),一个应用程序还应该包含一个发射功能(launch function)。

现在来创建一个简单的账户管理应用。首先,需要选择一个命名空间(所有extjs4应用应该使用一个单一的全局变来来作为命名空间)。暂时,使用”AM”来作为命名空间。

Ext.application({
    name: 'AM',
 
    appFolder: 'app',
 
    launch: function() {
        Ext.create('Ext.container.Viewport', {
            layout: 'fit',
            items: [
                {
                    xtype: 'panel',
                    title: 'Users',
                    html : 'List of users will go here'
                }
            ]
        });
    }
});

 

Ext.application({
    name: 'AM',
 
    appFolder: 'app',
 
    launch: function() {
        Ext.create('Ext.container.Viewport', {
            layout: 'fit',
            items: [
                {
                    xtype: 'panel',
                    title: 'Users',
                    html : 'List of users will go here'
                }
            ]
        });
    }
});

以上代码,做了如下几件事。首先,调用Ext.application创建一个应用程序类的实例,设置了一个”AM”的命名空间,他将作为整个应用的全局变量,也将作为Ext.Loader的命名空间,然后通过appFolder来指定配置选项设置相应的路径。最后,创建了一个简单的发射功能,这里仅仅创建了一个Viewport,其中包含一个panel,使其充满整个窗口。

 

3.2.3 定义一个控制器(Defining a Controller)

控制器是整个应用程序的关键,他负责监听事件,并对某些时间做出相应的动作。现在我们创建一个控制器,将其命名为Users.js,其路径是app/controller/Users.js。然后,我们为Users.js添加如下代码:

Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',
 
    init: function() {
        console.log('Initialized Users! This happens before the Application launch function is called');
    }
});

 

Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',
 
    init: function() {
        console.log('Initialized Users! This happens before the Application launch function is called');
    }
});

完成之后,我们将创建好的控制器添加到程序配置文件:app.js中:

Ext.application({
    ...
 
    controllers: [
        'Users'
    ],
 
    ...
});

当我们访问index.html时,用户控制器(Users.js)自动加载,因为我们在上面的app.js中的定义中指定了。Init函数一个最棒的地方是控制器与视图的交互,这里的交互是指控制功能,因为他很容易就可以监听到视图类的事件处理函数,并采取相应的措施,并及时渲染相关信息到面板上来。

编写Users.js:

Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',
 
    init: function() {
        this.control({
            'viewport > panel': {
                render: this.onPanelRendered
            }
        });
    },
 
    onPanelRendered: function() {
        console.log('The panel was rendered');
    }
});
Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',
 
    init: function() {
        this.control({
            'viewport > panel': {
                render: this.onPanelRendered
            }
        });
    },
 
    onPanelRendered: function() {
        console.log('The panel was rendered');
    }
});

在Users.js中,init函数使用this.control来负责监听,由于使用了新的ComponentQuery引擎,所以可以快速方便的找到页面上组件的引用(viewport > panel),这有些类似CSS选择器,通过匹配,快速的找到相匹配的组件。

在上面的init函数中,我们使用viewport> panel,来找到app.js中Viewport下的panel组件,然后,我们提供了一个对象的处理函数(this. onPanelRendered,注意,这里的对象是this,他的处理函数是onPanelRendered)。整个效果是,只要符合触发render事件的任何组件,那么onPanelRendered函数将被调用。当运行我们的应用程序,我们将看到以下内容。

 

3.2.4  定义一个视图(Defining a View)

到目前为止,应用程序只有几行,也只有两个文件,app.js和app/controller/Users.js。现在我们来增加一个grid,来显示整个系统中的所有用户。作为视图组件的一个子类,我们创建一个新的文件,并把他放到app/view/user目录下。命名为List.js。整个路径是这样的。app/view/user/List.js,下面,我们写List.js的代码: 

Ext.define('AM.view.user.List' ,{
    extend: 'Ext.grid.Panel',
    alias : 'widget.userlist',
 
    title : 'All Users',
 
    initComponent: function() {
        this.store = {
            fields: ['name', 'email'],
            data  : [
                {name: 'Ed',    email: 'ed@sencha.com'},
                {name: 'Tommy', email: 'tommy@sencha.com'}
            ]
        };
 
        this.columns = [
            {header: 'Name',  dataIndex: 'name',  flex: 1},
            {header: 'Email', dataIndex: 'email', flex: 1}
        ];
 
        this.callParent(arguments);
    }
});
Ext.define('AM.view.user.List' ,{
    extend: 'Ext.grid.Panel', 
    alias : 'widget.userlist', 
    title : 'All Users', 
    initComponent: function() {
        this.store = { 
            fields: ['name', 'email'],
            data  : [
                 {name: 'Ed',    email: 'ed@sencha.com'}, 
                {name: 'Tommy', email: 'tommy@sencha.com'} 
           ] 
        };
        this.columns = [
             {header: 'Name',  dataIndex: 'name',  flex: 1}, 
            {header: 'Email', dataIndex: 'email', flex: 1}
        ];
         this.callParent(arguments);
    }});

我们创建好的这个类,只是一个非常普通的类,并没有任何意义,为了能让我们更好的使用这个定义好的类,所以我们使用alias来定义一个别名,这个时候,我们的类可以使用Ext.create()和Ext.widget()创建,在其他组件的子组件中,也可以使用xtype来创建。

Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',
 
    views: [
        'user.List'
    ],
 
    init: ...
 
    onPanelRendered: ...
});

 

修改Users.js,增加views属性,修改app.js中的launch方法,将List渲染到Viewport。

Ext.application({
    ...
 
    launch: function() {
        Ext.create('Ext.container.Viewport', {
            layout: 'fit',
            items: {
                xtype: 'userlist'
            }
        });
    }
});

 

Ext.application({
    ...    launch: function() {
         Ext.create('Ext.container.Viewport', { 
           layout: 'fit', 
            items: {
                xtype: 'userlist' 
           } 
       });
 }});

看到这里,也许会有人开始抓狂了,这个user.List到底是怎么来的,为什么要这样写?如果你开始感到疑惑,那么不妨看看Ext.Loader是如何工作的(参见文档其他部分),在看过Ext.Loader之后,你就会明白了,User.List就是app/view/user下的List.js文件。为什么Ext要从view下找呢?因为我们在控制器中定了views: ['user.List']。这就是Extjs动态加载的强大之处,具体Ext.Loader,请看本站的其他文章,你就会明白了。当我们刷新页面。

3.2.5  控制网格(Controlling the grid)

要注意的是,onPanelRendered功能仍然是被调用的。这是因为gird匹配'viewport > panel'。
然后我们添加一个监听,当我们双击grid中的行,就可以编辑用户。

 

Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',
 
    views: [
        'user.List'
    ],
 
    init: function() {
        this.control({
            'userlist': {
                itemdblclick: this.editUser
            }
        });
    },
 
    editUser: function(grid, record) {
        console.log('Double clicked on ' + record.get('name'));
    }
});

 

Ext.define('AM.controller.Users', {

extend: 'Ext.app.Controller',

views: ['user.List'],

init: function() {

       this.control({

              'userlist': {

               itemdblclick: this.editUser

              }

        });

},

editUser: function(grid, record) {

        console.log('Double clicked on '+record.get('name'));

}

});

这里,我们修改了ComponentQuery的选择('userlist')和事件的名称('itemdblclick')和处理函数('editUser')。

如果要实现真正编辑用户,那么我们需要一个真正用于用户编辑window,接着,创建一个JS文件。其路径是:app/view/user/Edit.js,代码是这样的:

Ext.define('AM.view.user.Edit', {
    extend: 'Ext.window.Window',
    alias : 'widget.useredit',
 
    title : 'Edit User',
    layout: 'fit',
    autoShow: true,
 
    initComponent: function() {
        this.items = [
            {
                xtype: 'form',
                items: [
                    {
                        xtype: 'textfield',
                        name : 'name',
                        fieldLabel: 'Name'
                    },
                    {
                        xtype: 'textfield',
                        name : 'email',
                        fieldLabel: 'Email'
                    }
                ]
            }
        ];
 
        this.buttons = [
            {
                text: 'Save',
                action: 'save'
            },
            {
                text: 'Cancel',
                scope: this,
                handler: this.close
            }
        ];
 
        this.callParent(arguments);
    }
});

Ext.define('AM.view.user.Edit', {

extend: 'Ext.window.Window',

    alias : 'widget.useredit',

title : 'Edit User',

layout: 'fit',

    autoShow: true,

initComponent: function() {

        this.items = [{

                xtype:'form',

                items: [{

                       xtype: 'textfield',

                            name:'name',

                       fieldLabel: 'Name'

                    },

                {

                       xtype: 'textfield',

                        name: 'email',

                       fieldLabel: 'Email'

                 }]

          }];

        this.buttons = [{

                text:'Save',

            action: 'save'

         },

            {

            text: 'Cancel',

            scope: this,

                handler:this.close

       }];

       this.callParent(arguments);

    }

});

 

我们定义了一个子类,继承Ext.window.Window,然后使用initComponent创建了一个表单和两个按钮,表单中,两个字段分别装载用户名和电子邮件。接下来,我们修改视图控制器,使其可以载入用户数据。

Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',
 
    views: [
        'user.List',
        'user.Edit'
    ],
 
    init: ...
 
    editUser: function(grid, record) {
        var view = Ext.widget('useredit');
 
        view.down('form').loadRecord(record);
    }
});

 

Ext.define('AM.controller.Users', {

extend: 'Ext.app.Controller',

views: [

        'user.List',

    'user.Edit'

],

init: ...

    editUser: function(grid,record) {

        var view =Ext.widget('useredit');

           view.down('form').loadRecord(record);

}

});

首先,我们创建的视图,使用便捷的方法Ext.widget,这是相当于Ext.create('widget.useredit“)。然后我们利用ComponentQuery快速获取编辑用户的形式引用。在Ext JS4的每个组件有一个down函数,可以使用它快速的找到任何他的子组件。当双击Grid中的行,我们可以看到如下图所示:

3.2.6  创建Model 和 Store

 

 

Ext.define('AM.store.Users', {
    extend: 'Ext.data.Store',
    fields: ['name', 'email'],
    data: [
        {name: 'Ed',    email: 'ed@sencha.com'},
        {name: 'Tommy', email: 'tommy@sencha.com'}
    ]
});
 

 

Ext.define('AM.store.Users', {

extend: 'Ext.data.Store',

    fields: ['name','email'],

data: [

        {name: 'Ed',    email: 'ed@sencha.com'},

        {name: 'Tommy',email: 'tommy@sencha.com'}

]

});

接下来修改两个文件:

Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',
    stores: [
        'Users'
    ],
    ...
});

 

Ext.define('AM.controller.Users', {

extend: 'Ext.app.Controller',

    stores: [        'Users'    ],

    ...

});

修改app/view/user/List.js,使其引用Users

 

Ext.define('AM.view.user.List' ,{
    extend: 'Ext.grid.Panel',
    alias : 'widget.userlist',
 
    //we no longer define the Users store in the `initComponent` method
    store: 'Users',
 
    ...
});
 

 

Ext.define('AM.view.user.List' ,{

extend: 'Ext.grid.Panel',

    alias :'widget.userlist',

    //we no longer definethe Users store in the `initComponent` method

    store: 'Users',   

...

});

现在,我们定义的用户控制器已经能顺利的加载数据了,到目前,我们所定义的Store已经足够使用了,但是Extjs4提供了一个强大的Ext.data.Model类,不如,我们利用它来重构下我们Store,创建app/model/User.js
 

Ext.define('AM.model.User', {
    extend: 'Ext.data.Model',
    fields: ['name', 'email']
});
 

 

Ext.define('AM.model.User', {

extend: 'Ext.data.Model',

    fields: ['name','email']

});

创建好模型之后,我们将他引入用户控制:

//the Users controller will make sure that the User model is included on the page and available to our app
Ext.define('AM.controller.Users', {
    extend: 'Ext.app.Controller',
    stores: ['Users'],
    models: ['User'],
    ...
});
 
// we now reference the Model instead of defining fields inline
Ext.define('AM.store.Users', {
    extend: 'Ext.data.Store',
    model: 'AM.model.User',
 
    data: [
        {name: 'Ed',    email: 'ed@sencha.com'},
        {name: 'Tommy', email: 'tommy@sencha.com'}
    ]
});

 

 

//the Users controller will make sure that the User model isincluded on the page and available to our app

Ext.define('AM.controller.Users', {

extend: 'Ext.app.Controller',

    stores: ['Users'],

models: ['User'],

...

});

// we now reference the Model instead of defining fields inline

Ext.define('AM.store.Users', {

extend: 'Ext.data.Store',

    model: 'AM.model.User',

data: [

        {name: 'Ed',    email: 'ed@sencha.com'},

        {name: 'Tommy',email: 'tommy@sencha.com'}

]

});

完成上面的代码后,刷新页面,看到的结果和以前的一样。

 

3.2.7  通过model保存数据

现在双击Grid中的行,会弹出编辑用户的window,实现Save来保存用户数据,我们需要修改init函数。

Ext.define('AM.controller.Users', {
    init: function() {
        this.control({
            'viewport > userlist': {
                itemdblclick: this.editUser
            },
            'useredit button[action=save]': {
                click: this.updateUser
            }
        });
    },
 
    updateUser: function(button) {
        console.log('clicked the Save button');
    }
});

 

 

Ext.define('AM.controller.Users', {

init: function() {

        this.control({

            'viewport >userlist': {

               itemdblclick: this.editUser

        },

            'usereditbutton[action=save]': {

                click:this.updateUser

           }

      });

},

updateUser: function(button) {

        console.log('clickedthe Save button');

}

});

 

在this.control中,我们增加了一个选择项,'usereditbutton[action=save]',当ComponentQuery找到符合的组件(button按钮,并且action动作为save),给他增加一个方法click,事件为updateUser。如图:

上图中,可以看到正确的Click事件,在updateUser函数中,需要一个正式的逻辑,来完成用户数据的更新。

updateUser: function(button) {
    var win    = button.up('window'),
        form   = win.down('form'),
        record = form.getRecord(),
        values = form.getValues();
 
    record.set(values);
    win.close();
}
 

 

updateUser: function(button) {

var win   =button.up('window'),

        form   = win.down('form'),

    record =form.getRecord(),

        values=form.getValues();

    record.set(values);

   win.close();

}

这里,当我们点击Save按钮,我们将按钮本身传入函数updateUser,这时,我们使用button.up('window')来获取用户window的引用,然后使用win.down('form')来获取表单的引用,然后获取表单的记录,获取表单中的值,再设置记录为新的值,最后关闭window。
修改数据后点击Save,更新完成。

实际应用过程中,我们需要将数据保存到服务器,所以,需要修改文件,达到想要的目的。

3.2.8  将数据保存到服务器

这很容易做到。使用AJAX来实现即可。

Ext.define('AM.store.Users', {
    extend: 'Ext.data.Store',
    model: 'AM.model.User',
    autoLoad: true,
 
    proxy: {
        type: 'ajax',
        url: 'data/users.json',
        reader: {
            type: 'json',
            root: 'users',
            successProperty: 'success'
        }
    }
});
 

Ext.define('AM.store.Users', {

    extend:'Ext.data.Store',

      model: 'AM.model.User',

    autoLoad: true,

    proxy: {

        type: 'ajax',

          url: 'data/users.json',

        reader: {

            type: 'json',

           root: 'users',

            successProperty:'success'       

  }

    }

});

在AM.store.Users,移除data,用一个代理(proxy)取代它,用代理的方式来加载和保存数据。在Extjs4中,代理的方式有AJAX,JSON-P 和 HTML5 localStorage,这里使用AJAX代理。数据从data/users.json中得到。

我们还用reader来解析数据,还指定了successProperty配置。具体请查看Json Reader,把以前的数据复制到users.json中。得到如下形式:
 

{
    success: true,
    users: [
        {id: 1, name: 'Ed',    email: 'ed@sencha.com'},
        {id: 2, name: 'Tommy', email: 'tommy@sencha.com'}
    ]
}

 

{    success: true,    users: [

        {id: 1, name:'Ed',    email: 'ed@sencha.com'},

        {id: 2, name:'Tommy', email: 'tommy@sencha.com'}  

 ]}

到这里,唯一的变化是将Stroe的autoLoad设置为了true,这要求Stroe的代理要自动加载数据,当刷新页面,将得到和之前一样的效果。

最后一件事情,是将修改的数据发送到服务器,对于本例,服务端只用了一个静态的JSON,所以我们不会看到有任何变化,但至少可以确定这样做是可行的,相信服务端处理数据能力,大家都应该有。本例用,做个小小的变化,即新的代理中,使用api来更新一个新的URL。

proxy: {
    type: 'ajax',
    api: {
        read: 'data/users.json',
        update: 'data/updateUsers.json'
    },
    reader: {
        type: 'json',
        root: 'users',
        successProperty: 'success'
    }
}
 



proxy: {

    type: 'ajax',

    api: {

        read:'data/users.json',

            update:'data/updateUsers.json'

    },

   reader: {

        type: 'json',

            root: 'users',

        successProperty:'success'   

}

}

 

再来看看是如何运行的,我们还在读取users.json中的数据,而任何更新都将发送到updateUsers.json,在updateUsers.json中,创建一个虚拟的回应,从而让我们知道事件确实已经发生。updateUsers.json只包含了{"success": true}。而我们唯一要做的事情是服务的同步编辑。在updateUser函数增加这样一个功能。

updateUser: function(button) {
    var win    = button.up('window'),
        form   = win.down('form'),
        record = form.getRecord(),
        values = form.getValues();
 
    record.set(values);
    win.close();
    this.getUsersStore().sync();
}
 

updateUser: function(button) {

        var win    = button.up('window'),

        form   = win.down('form'),

        record =form.getRecord(),

        values =form.getValues();

record.set(values);

    win.close();

       this.getUsersStore().sync();

}

现在,运行这个完整的例子,当双击grid中的某一样并进行编辑,点击Save按钮后,得到正确的请求和回应。 至此,整个MVC模式全部完成,下一节,将讨论控制器(Controller)和模式(patterns),而这些,可以使应用代码更少,更容易维护。

posted on 2013-09-15 11:54 Chen Jie 阅读(2335) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航:
 
<2013年9月>
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345

导航

统计

常用链接

留言簿

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜