前端extjs学习之旅(1)

从上周正式开始学习Extjs。刚开始看老大推荐的Extjs in action英文版,边看边敲代码,虽然都是一些简单小程序,但是看着它跑起来还是很欢脱。但是,第一次通过英文书学技术的我感到非常痛恨英语,别扭的逻辑经常把自己搞糊涂。由于书是extjs3.x的,大神建议“直截了当”,从sencha上直接学习最新技术。sencha官网的extjs4.2.1docs真的是非常优秀的教材。(好吧,我也不知道前面这一大段废话有什么用途,很少写日志,但是,作为一名程序员,和一名正在奔向一名优秀而出色的程序员的程序员,写博客是必须要有的好习惯,于是,啰里八嗦的开启这篇里程碑日记)

[size=large][b]sencha-concept:MVC Architecture[/b][/size]
这篇今天看了一天,到最后向server传输json数据失败了,于是来整理一下这篇文章学到的技术:
大型的客户端应用一直很难开发,组织和维护。当你增添更多的功能和开发者到这个项目上时,它就可能会失控。Ext JS4 带来了一个新的应用架构,不仅可以组织代码还可以减少你的代码书写量。

我们的应用架构第一次引入Models,Controller类似一种MVC的模式。计算机世界里有很多MVC架构,大多数之间都有细微的不同。以下是我们的定义:
◆Model:是一个域和它的数据的集合(例如:一个user model有用户名和密码域)。Models知道如何在数据包之间存储,并且能够通过关系被链接到其他model中。Model的工作方式与Ext JS 3 Record class非常相似, 并且通常和Stores一起使用来将数据呈现到grids和其他组件类(componets class)中。
◆View:是任何形式的组件(component)-grids,trees panels都是views.
◆Controllers:是用来放置所有app work的代码的特殊地方-无论是渲染视图,实例模型或是任何其他app逻辑。 =>就是有许多控制view行为的方法。

/*还是中国字儿好,中文才是最美丽的语言 哼~*/

在这篇指南中我们将会创建一个非常简单的应用来管理用户数据。到结束的时候你会知道怎么用Ext JS 4应用框架将简单的应用整合在一起。

应用架构和实际类与框架代码一样多为供给结构和连贯性。下面的协议包括很多重要的好处:
每一个应用都以同样的方式工作,所以你只需要学习一次。
在app之间很容易分享代码因为他们都以同样的原理工作。
你可以用我们的编译工具来创建供你的产品使用的最优化的(optimized)应用版本。

[size=large][b]File Structure[/b][/size]
Ext JS 4应用有一个统一的(unified)目录结构,即每一个app目录都是一样的。请检查一下开始手册中对一个应用的基本文件结构的详细解释。在MVClayout中,所有的类都放在app/文件夹下,该文件夹相应的(in turn)也包括从子文件夹到你的models,views,controllers和stores的命名空间。以下是这个简单的小例子的文件夹结构:
[img]http://docs.sencha.com/extjs/4.2.1/guides/application_architecture/folderStructure.png[/img]
在这个例子中,我们在一个名为'account_manager'的文件夹中囊括了整个应用。Ext JS 4 SDK中的重要文件都被放在ext-4/文件中。因此 index.html中的内容看上去像这样:

<html>
<head>
<title>Account Manager</title>

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

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

<script type="text/javascript" src="app.js"></script>
</head>
<body></body>
</html>


[size=large][b]Creating the application in app.js[/b][/size]
每一个 Ext JS 4 应用都从一个应用类实例开始。这个Application包括你的应用中所有的设置(如:app的名字),同时维护着所有应用于app上的models,views和controllers的参考(references)。一个Application同样包括一个启动方法(launch function),它可以当一切被加载完成时自动的跑起来。

让我们一起来创建一个简单的账户管理app,它会帮助我们管理用户账户。首先我们需要为应用选一个全局命名空间。所有的Ext JS 4 应用都应该之用一个单独的全局变量,带着全部应用的类嵌套(nested inside it)在他其中。通常我们想要一个简短的全局变量,所以我们将使用"AM"。

Ext.application({
requires: ['Ext.container.Viewport'],
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'
}
]
});
}
});

有几件事情要在这里说一下。首先我们借助(invoked)Ext.application来为给予名字为'AM'的命名空间(?)创建一个新的Application class实例,这个自动为我们建立一个全局AM变量,并且将命名空间注册到 Ext.Loader上同时通过 appFolder 配置选项(config option)完成'app'路径绑定设置。我们同样提供一个简单的启动方法仅仅创建了将会充满全屏的一个包括单独Panel的Viewport。
[img]http://docs.sencha.com/extjs/4.2.1/guides/application_architecture/panelView.png[/img]

[size=large][b]Defining a Controller[/b][/size]

Controllers是将application连接到一起的粘合剂。他们所有要做的就是监听所有事件(通常来自views)和采取一些行动。接着我们的账户管理应用,让我们来建立一个controller。创建一个叫做app/controller/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');
    }
});

现在让我们将新创建的Users controller添加的应用配置文件app.js中:

Ext.application({
...

controllers: [
'Users'
],

...
});

当我们通过访问index.html加载我们的应用到浏览器,Users controller是被自动加载的(应为我们在上面的Application定义中指定出来了),并且它的init方法正是在Application的启动方法之前被调用l
init方法是一个很棒的地方来定义你的controller和view如何交互,同时常常被用来连接(conjunction)另一个Controller方法-control. control方法使监听到你的view类上的事件变得容易,并且用一个handler方法采取一些措施。让我们更新一下我们的Users controller来告诉我们panel何时被渲染:

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');
}
});

我们已经更新了init方法来使用this.control来家里我们application上的views的监听器。control方法用到了组件查询机制(ComponentQuery engine)来方便快捷的得到页面上的组件的引用。如果你对组件查询还不太熟悉,确保查看一下组件查询文件得到完整解释。简而言之,他允许我们通过类似css选择器的方式找到每一个匹配的组件并传到页面上。

在我们上面的init方法我们提供了'viewport > panel',翻译过来就是“给我找到每一个是一个Viewport直接子类的Panel”。我们然后提供了一个对象映射(maps)事件名称(就是这里的render)到handler方法。整体的效果是,无论什么时候任何组件和我们的选择器匹配时都会开启一个render事件,我们的onPanelRendered方法被调用。

当我们跑一下这个应用时我看到如下效果:
[img]http://docs.sencha.com/extjs/4.2.1/guides/application_architecture/firstControllerListener.png[/img]
虽然这不是最令人兴奋的应用,但是它展示了从管理代码开始是多么容易。让我们来通过加一个网格(grid)使app更饱满。

[size=large][b]Defining a View[/b][/size]
直到现在我们的应用才之后几行并且仅包含在两个文件中-app.js和app/controller/Users.js。既然我们想要添加一个网格来展示系统中所有用户,就是时候更好的管理我们的逻辑,开始使用views。

一个View只不过是一个组件,通常被定义成一个Ext JS组件的子类。我们现在要通过新建一个叫做app/view/user/List.js 文件建立我们的Users grid,并且写入以下内容:

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);
}
});

我们的View class不过是一个普通类。这里我们正好继承了Grid Component并且建立了一个别名,因此我们可以把它用作一个xtype(更多的时候)。我们也传入了store配置和网格应该渲染的列。

下一步我们需要把这个view加入到我们的Users controller。因为我们用特殊的'widget'格式设置一个换名,我们现在可以用'userlist'作为一个xtype,就好像我们之前用过的'panel'。

Ext.define('AM.controller.Users', {
extend: 'Ext.app.Controller',

views: [
'user.List'
],

init: ...

onPanelRendered: ...
});

然后通过修改app.js中的启动方法将它渲染到主viewport中:

Ext.application({
...

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

唯一的另一件事要强调的是我们在views array中指定的'user.List'。这告诉应用自动去加载那个文件因此当我们启动时可以用它。这个应用使用Ext JS 4的新动态加载系统,自动的从服务器获取文件(pull this file from the server)。以下是我们现在刷新网页会看到的效果:
[img]http://docs.sencha.com/extjs/4.2.1/guides/application_architecture/firstView.png[/img]


[size=large][b]Controlling the grid[/b][/size]
注意我们的onPanelRendered方法是一直被调用的。这个是因为我们的网格类一直和'view > panel'选择器匹配。其中的原因是我们的类继承自Grid,其也继承自Panel。

这是,我们为这个选择器添加的监听器实际上将会为每一个是viewport的直接子类的Panel或Panel的子类所调用,所以让我们稍微收紧使用我们新的xtype。让我们以在网格的行上双击代替,这样我们可以之后编辑那个User:

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'),事件的名字(to 'itemdblclick'),handler方法名字(to 'editUser')。现在我们登出我们刚刚双击的User名:
[img]http://docs.sencha.com/extjs/4.2.1/guides/application_architecture/doubleClickHandler.png[/img]
登入控制台非常顺利但是我们很想要编辑我们的Users。让我们现在就做,从一个 app/view/user/Edit.js中新的view开始:

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);
}
});

再一次我们定义一个已经存在的component的子类-这次是Ext.window.Window.再一次我们使用initComponent来指定复杂对象items 和 buttons。我们用一个'fit'layout和一个表格作为单独的item,他们包括可编辑姓名和邮箱地址的域。最终我们创建了两个按钮,一个用于关闭窗口,另一个用于保存我们的改变。

所有我们要做的是将view加入的controller中,渲染它并且在其中加载User:

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方法创建view,它等同于Ext.create('widget.useredit').。然后我们再一次促使ComponentQuery改变(leveraged)来快速得到一个引用来编辑窗口的表格。每一个在Ext JS 4中的组件都有一个down方法,用来接受一个ComponentQuery选择器来快速找到任何一个子组件(component)。

现在在我们的网格中双击一行会产生如下效果:
[img]http://docs.sencha.com/extjs/4.2.1/guides/application_architecture/loadedForm.png[/img]


[size=large][b]Creating a Model and a Store[/b][/size]
既然我们已经有了编辑表格,是时候开始编辑我们的users和保存那些改变了。在我们这么做之前,我们应该对我们的代码稍作重构(refactor)。

此刻AM.view.user.List组件创建一个内联Store。这个工作的很好但是我们想要能够引用在应用中其他地方的Store因而可以更新那里的数据。我们将通过把Store放入他自己的文件- app/store/Users.js开始:

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'}
]
});

现在我们将要做两个小的改变-首先我们要让我们的Users controller加载时包括这个Store:

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

然后我们将用id更新app/view/user/List.js来简单的引用Store:

Ext.define('AM.view.user.List' ,{
extend: 'Ext.grid.Panel',
alias: 'widget.userlist',
title: 'All Users',

// we no longer define the Users store in the `initComponent` method
store: 'Users',

initComponent: function() {

this.columns = [
...
});

通过包括stores我们的Users controller关心它的定义,他们是自动加载到页面上并且被给予一个存储id,这使他们在我们的views引用变得真的很容易。(通过简单的配置store:在这个例子里是'Users')。

此时我们已经定义了我们的域('name'和'fields')内联在store中。这个运行的足够好但是在Ext JS 4中我们有一个强大的Ext.data.Model类,我们喜欢当它编辑我们的Users是利用它。我们将完成这部分通过重构我们的Store来使用一个Model,我们将向app/model/User.js写入:

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

这些就是我们需要做的来定义我们的Model.现在我们将要更新我们的Store来引用Model名字而不是提供内联域:

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'}
]
});

我们将要询问Users controller来返回一个引用到User model上:

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

我们的重构将要使下一个奔赴更容易但是不应该影响当前应用的行为。如果我们现在重新加载页面,在行上双击我们看到编辑用户窗口仍然如期待一样的显示。现在是时候完成编辑方法了:
[img]http://docs.sencha.com/extjs/4.2.1/guides/application_architecture/loadedForm.png[/img]


[size=large][b]Saving data with the Model[/b][/size]
既然我们让我们的用户网格加载数据并打开一个编辑窗口,当我们双击每一行,我们想要保存用户做的修改。在上面定义的Edit User窗口,包括一个表格(有带有名字和邮箱的域),和一个保存按钮。首先让我们更新我们的controller的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');
}
...
});

我们第二次将ComponentQuery选择器加入我们的this.control调用-这一次是 'useredit button[action=save]'。它和第一个选择器以同样的原理工作-它使用'useredit' xtype ,我们在之前定义过的来聚焦到我们的编辑用户窗口,并且然后寻找任何带有'save'行为的按钮,这使得给我们提供了一个锁定按钮的简单的途径。

我们能够满足我们自己,当我们点击保存按钮updateUser方法被调用。:
[img]http://docs.sencha.com/extjs/4.2.1/guides/application_architecture/saveHandler.png[/img]
既然我们已经看到我们的handler被正确的绑定到保存按钮的点击时间,那么让我们为updateUser方法添加真正的逻辑。在这个方法中我们需要从表格中获取数据,用它更新我们的User,然后将它保存回上面建好的Users store中。让我们看一下我们应该怎样做:

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

record.set(values);
win.close();
}

让我们分解一下现在发生的事情。我们的点击事件通过用户点击给我们了一个对按钮的引用,但是我们真正想要从表格中获得的是包括数据和窗口本身。为了让事情快速起效,我们将仅再次使用ComponentQuery,首先使用button.up('window')来得到一个参考用户编辑窗口,然后使用win.down('form')来的到一个表格。

在这之后,我们简单的取回记录,这记录是目前载入表格中的并且将用户输入的任何内容更新到表格中。最后,我们关闭窗口来将关注点回到网格。这就是我们将要看到的我们的app再次跑起来的效果,将名字域改成'Ed Spencer'并保存。
[img]http://docs.sencha.com/extjs/4.2.1/guides/application_architecture/updatedGridRecord.png[/img]


[size=large][b]Saving to the server[/b][/size]
非常方便。让我们现在通过让它和我们的服务器端交互来把它完成。现在我们正在努力将两个User记录编写到Users Store中,是哦一让我们开始阅读那些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'
}
}
});

这里我们去掉了'data'属性并把它替换为一个Proxy。Proxies在Ext JS 4 中是从一个Store或一个Model加载和保存数据的一种方法。有proxies for AJAX, JSON-P and HTML5 localStorage among others. 这里我们已经使用了一个简单的AJAX proxy,我们已经告知它从'data/users.json'地址载入数据。

我们也将一个 Reader和 Proxy关联了起来。reader主要负责将服务器端响应解码到一种Store可以理解的格式。这是我们使用一个 JSON Reader和指定一个根目录和 successProperty配置参数。最后我们将创建我们的 data/users.json文件,并且将之前的数据粘进去:

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

唯一的一个我们对Store进行的其他的改变是将autoLoad设置为true,这意味着Store将询问它的Proxy来立即加载数据。如果我们现在刷新页面,我们将会看到和之前同样的结果,除了现在我们不再硬编码到我们的应用中。

最后,我们想要做的是将我们的改变传回服务器。举个例子,我们正在在服务器端使用一个静态JSON文件,所以我们不会看到任何数据库的改变但是我们至少能够核实每一件事都被正确的连接在一起。第一我们将对我们的新proxy做一点小改变来告诉它将更新内容发送到一个不同的url:

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文件刚好包括{"success": true}。由于它通过一个http端口指令更新,你可能不得不闯进一个空文件来避免接收一个404错误。

另一个我们需要做的改变是告诉我们的Store在编辑之后经自己同步,这个通过我们在 updateUser方法中额外增加一行,它现在看上去是这样:

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

record.set(values);
win.close();
// synchronize the store after editing the record
this.getUsersStore().sync();
}

现在我们能够跑完我们的完整示例,并确保一切都在工作。我们将编辑一行,点击保存按钮,会看到请就被正确的发送到了 updateUser.json中。
[img]http://docs.sencha.com/extjs/4.2.1/guides/application_architecture/postUpdatesToServer.png[/img]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值