前言

  在上一次主要是搭建Ext环境,本次课程主要是通过Ext组件来实现登录。

开始动手

 1.在解决方案资源管理器中选择Scripts\ExtJS\ux目录,单击右键选择添加,新建项,在弹出窗口中选择Jscript文件,并将名称修改为login.js(以后的项目的可直接将该文件复制到该目录)

ExtJs二(实现登录)_配置项

这里要注意,文件名不能用类的全名做文件名,因为动态加载会根据类名自动找到目录并加载文件,类名中最后一个小数点后的名称就是文件名,例如,登录窗口的类全称为Ext.ux.Login,而login就是文件名。

如果想要在脚本中使用ExtJS的提示信息,可将书附带的资源包中的Ext.js文件复制到ExtJS目录中,复制后,在解决方案资源管理器将Ext.js拖到到login.js文件中,就会生成以下代码: 

/// <reference path="../Ext.js" />


ExtJs二(实现登录)_验证码_02

2.现在,先把类的定义写好,包括父类、单例模式、窗口标题、宽度和高度。窗口的标题为“Ext Js MVC登录窗口”。宽度和高度暂定为400,到时候再调整。最终代码如下(Login.js中):

Ext.define("Ext.ux.Login", {
extend: "Ext.window.Window",
singleton: true,
title: 'ExtJs Mvc登录窗口',
width: 400,
height: 400
});


3.要考虑窗口应该包含那些配置项了,窗口应是模态的,不能关闭,不能调整大小,关闭模式为隐藏,隐藏模式为偏移等,因而加入以下代码:

modal: true,
closable: false,
resizable: false,
closeAction: 'hide',
hideMode: 'offsets',


4.在窗口的initComponent方法内定义登录用的控件了。一般的登录窗口都包含用户名、密码和验证码3个文本输入框,还包含有显示验证码的图片、登录和重置按钮。因而需要用到的ExtJS控件包括表单面板、图片、工具栏、按钮和文本字段。下面要做的是先定义好表单,在扩展内加入以下代码:

initComponent: function () {
var me = this;
me.form = Ext.create(Ext.form.Panel, {

});
me.callParent(arguments);
}


代码中,me的作用是将外部作用域中的this对象保存为本地变量,这样的好处包括,一是,如果this是window等全局变量,就可以将全局变量变成本地变量,提高访问效率,二是可以让闭包访问该对象。这写法在Ext JS文件中始终贯穿其中,本着拿来主义的精神,好东西应该学一下。

注意create方法中的对象名称,笔者并没有使用字符串,这样就可以直接使用对象,而不需要再去转换表中找对象,可以提高速度。

调用callParent方法是必须的,不然组件运行会出问题,达不到预期效果。

5.来定义表单配置项了,代码如下:

border: false,
bodyPadding: 5,
bodyStyle: "background:#DFE9F6",


代码中,第一句表示不要边框,如果喜欢带有边框的表单,可以把这项去掉或者修改为true。第二句表示将表单面板向内压缩5像素,这样表单内的组件就不会和窗口的内边框粘在一起,这个可根据个人喜好设置。第三句的作用就是让表单面板的背景颜色和窗口融合在一起,而不是默认的白色,这还是个人喜好问题。

6.接着加入表单面板的提交地址,这里定为Account/Login,就是Account控制器的Login方法,代码如下:

url: "Account/Login",


7.因为表单内使用的都是文本字段,因而可以统一做一些定义,如标签宽度为80,标签的分隔符为中文冒号,锚固为0,都不允许为空等,代码如下:

defaultType: "textfield",
fieldDefaults: {
labelWidth: 80,
labelSeparator: ":",
anchor: "0",
allowBlank: false
},


8.接下来是定义字段了,这个简单,因为默认设置已经定义了几个配置项,因而余下的就只有字段标签和名称。验证码特殊点,必须是6位字符,代码如下:

items: [
{
fieldLabel: "用户名", name: "UserName"
},
{
fieldLabel: "密码", name: "Password", inputType: "password"
},
{
fieldLabel: "验证码", name: "Vcode", minLength: 6, maxLength: 6
}
}


9.现在要考虑怎么显示验证码图片,如果直接在表单内加入Image控件,会很难控制图片的位置,因为最好的方式是先套一个容器。因为Img对象的实例在刷新图片的时候还要用到,因而最好用一个属性来指向对象实例,这样就可以通过该属性在类的内部访问到实例了。在创建表单的前面添加以下创建Img对象实例的代码:

me.image = Ext.create(Ext.Img, {
src: "/VerifyCode"
});


 千万不要在创建表单后面创建,不然在表单内插入图片的时候就找不到对象了。

代码中,验证码图片将VerifyCode控制器生成,这个暂时放下,会在后面讨论。

10.还要实现的是单击图片刷新验证码,但是查API发现Img对象居然没单击事件。没关系,在4.1版本的Ext JS中,修改了事件的定义方式,可以直接为对象生成的HTML元素绑定事件了,只要在监听事件中加入element配置项就行了,这相当方法。因而可在创建Img实例的配置对象中加入以下代码:

listeners: {
click: me.onRefrehImage,
element: "el",
scope: me
}


代码中,element配置项中的el就表示要在对象生成的HTML元素中绑定事件,绑定事件为click事件,事件将调用onRefrehImage方法。方法只是简单的刷新图片,因而使用Img对象的setSrc方法就可以,使用以下代码顺便完成onRefrehImage方法:

onRefrehImage: function () {
this.image.setSrc("/VerifyCode?_dc=" + (new Date()).getTime());
}


代码很简单,使用setSrc方法刷新图片的src就行了,加上时间戳可防止显示缓存图片。

 好了,可以在表单items里加入验证码图片了,代码如下:

{
xtype: "container", height: 80, anchor: "-5", layout: "fit",
items: [me.image]
}


 从代码可以看到,使用容器的作用就是可以使用fit布局来限制图片的尺寸,这样布局就容易多了。

还要加入一段提示信息,告知用户验证码不区分大小写,且如果看不清楚验证码图片,可单击图片刷新验证码,代码如下:

{
xtype: "container", anchor: "-5", html: "**验证码不区分大小写,如果看不清楚验证码,可单击图片刷新验证码。"
}


 11.表单余下的就是添加登录和重置按钮了,代码如下:

dockedItems: [{
xtype: 'toolbar', dock: 'bottom', ui: 'footer', layout: { pack: "center" },
items: [
{ text: "登录", width: 80, disabled: true, formBind: true, handler: me.onLogin, scope: me },
{ text: "重置", width: 80, handler: me.onReset, scope: me }
]
}]


在这里使用了dockedItems配置项,目的一是因为介绍Ext JS 4的新功能,二是因为使用这个确实挺方便。代码中定义了一个工具栏,停靠位置由dock配置项决定,在这里是底部(bottom),工具栏的样式使用了ui配置项定义的footer,也就是原来窗口的底部页脚工具栏,工具栏的布局将使用居中对齐方式。

登录按钮预设为禁用的。formBind配置的作用是只有在表单内输入符合要求时才能使用该按钮,这个设计在Ext JS4也是新加入的,很方便,不再需要自己去写代码实现这个了。登录按钮将调用onLogin方法。重置按钮很简单,只是简单的调用onReset方法。

余下要完成的是onLogin和onReset方法。先来完成简单onReset方法,基本功能就是重置表单,并将焦点移动到第一个文本字段,也就是用户名那里,还要刷新验证码,代码如下:

onReset: function () {
var me = this;
me.form.getForm().reset();
if (me.form.items.items[0]) {
me.form.items.items[0].focus(true, 10);
}
me.onRefrehImage();
}


代码中要注意的是获取表单中第一个文本字段的代码,因为表单在实例化后,items属性指向的是MixedCollection实例,因为要在其items内才能找到文本自动对象。

 接着完成的是onLogin方法,难度也不大, 就是先调用isValid方法,验证表单是否符合提交要求,然后调用submit方法提交。其实不调用isValid也行,因为登录按钮只要在isValid为true时才能用。代码如下:

onLogin: function () {
var me = this,
f = me.form.getForm();
if (f.isValid()) {
f.submit({
//waitMsg: "正在登录,请等待……",
//waitTitle: "正在登录",
success: function (form, action) {
window.location.reload();
},
failure: function () {
},
scope: me
});
}
}


登录成功(success配置项)后,会刷新一下页面,让页面写入验证信息到Cookie。当然,也可以跳转到另外一页,不过笔者认为不如这样来得简便,这个稍后会说到。

登录失败(failure配置项),只写了一个空函数的目的是因为表单的提交返回的数据格式是一样的,处理方式也一样,因而可使用同一个函数进行处理,但是还没写到,因而先保留一个空函数。

最后,别忘了将表单加入窗口的items里,这个必须放在调用callParent之前,不如不会初始化表单,代码如下:

me.items = [me.form]


至此,登录窗口就暂时写好了。

以下是完整的Login.js代码:

/// <reference path="../Ext.js" />

Ext.define("Ext.ux.Login", {
extend: "Ext.window.Window",
singleton: true,
title: 'ExtJs Mvc登录窗口',
width: 450,
height: 300,
modal: true,
closable: false,
resizable: false,
closeAction: 'hide',
hideMode: 'offsets',

initComponent: function () {
var me = this;

me.image = Ext.create(Ext.Img, {
style: "cursor:pointer ",
src: "/VerifyCode",
listeners: {
click: me.onRefrehImage,
element: "el",
scope: me
}
});

me.form = Ext.create(Ext.form.Panel, {
border: false,
bodyPadding: 5,
bodyStyle: "background:#DFE9F6",
url: "Account/Login",
defaultType: "textfield",
fieldDefaults: {
labelWidth: 80,
labelSeparator: ":",
anchor: "0",
allowBlank: false
},
items: [
{
fieldLabel: "用户名", name: "UserName"
},
{
fieldLabel: "密码", name: "Password", inputType: "password"
},
{
fieldLabel: "验证码", name: "Vcode", minLength: 6, maxLength: 6
},
{
xtype: "container", height: 80, anchor: "-5", layout: "fit",
items: [me.image]
},
{
xtype: "container", anchor: "-5", html: "**验证码不区分大小写,如果看不清楚验证码,可单击图片刷新验证码。"
}
],
dockedItems: [{
xtype: 'toolbar', dock: 'bottom', ui: 'footer', layout: { pack: "center" },
items: [
{ text: "登录", width: 80, disabled: true, formBind: true, handler: me.onLogin, scope: me },
{ text: "重置", width: 80, handler: me.onReset, scope: me }
]
}]
});

me.items = [me.form]

me.callParent(arguments);

},

onRefrehImage: function () {
this.image.setSrc("/VerifyCode?_dc=" + (new Date()).getTime());
},

onReset: function () {
var me = this;
me.form.getForm().reset();
if (me.form.items.items[0]) {
me.form.items.items[0].focus(true, 10);
}
me.onRefrehImage();
},

onLogin: function () {
var me = this,
f = me.form.getForm();
if (f.isValid()) {
f.submit({
//waitMsg: "正在登录,请等待……",
//waitTitle: "正在登录",
success: function (form, action) {
window.location.reload();
},
failure: function () {
},
scope: me
});
}
}


});