专栏首页技术博客ExtJs十(ExtJs Mvc用户管理之二)

ExtJs十(ExtJs Mvc用户管理之二)

前言

为了防止意外情况,这里添加了try模块,在错误的时候会将错误信息作为Msg关键字的值返回。

而在客户端脚本,目前是没有处理错误信息的代码的,因而在这里返回的错误信息,客户端是看不到。如果要处理这样的错误信息,就要在Store的Proxy中监听exception事件。因为exception事件的回调函数是一致的,因而可以统一到一个函数中处理,就不用复制再复制了。

先切换到Index.cshtml,在Ext.ns下添加以下代码定义处理exception事件的回调:

            ExtMVCOne.ProxyException = function (proxy, response, opts) {
                Ext.Msg.alert("错误信息", opts.error);
            }

在exception事件的回调函数的第三个参数返回的是Operation对象,当success为false时,它会将Msg关键字的值复制到对象的error属性,因而直接调用该属性就可获得错误信息了。在这里要处理的意外情况其实还有很多,大家可根据API的说明完善该函数。

接着,切换到Users的Store,在proxy定义内添加listeners配置项来监听exception事件,代码如下:

        listeners: {
            exception: ExtMVCOne.ProxyException
        }

要测试exception事件是否能正常执行,在pagesize的定义代码前添加以下代码抛出一个异常:

throw new Exception("发生错误了。");

F5运行后到用户管理标签

 上一节中还有一个错误就是,CheckColumn的样式和图片没复制过来,造成最后一列的Checkbox显示不正确。在Ext JS包的examples\ux\css目录下打开CheckHeader.css文件,将文件里的全部样式定义复制到app.css中。然后修改将带背景图片的路径修改为“../images”。最后将image目录下的check.gif和uncheck.gif文件复制到scripts\app\resources\images目录下。

用户管理添加修改删除重置密码

 首先切换到用户视图脚本文件,为Grid添加一个RowEditing插件,这不难,创建RowEditing的实例,并添加到plugins就行了,代码如下:

        me.rowEditing = Ext.create('Ext.grid.plugin.RowEditing', {
            autoCancel: false,
            saveBtnText: '保存',
            cancelBtnText: '取消',
            errorsText: '错误',
            dirtyText: "你要确认或取消更改"
        });

        me.plugins = [me.rowEditing];

现在,要为用户名、电子邮件、角色和禁用列添加编辑控件了,这个只要定义好editor配置项就可以了,修改后的代码如下:

        me.columns = [
            { text: '用户名', dataIndex: 'Username', flex: 1,
                editor: { allowBlank: false }
            },
            { text: '电子邮件', dataIndex: 'Email', flex: 1 ,
                editor: { allowBlank: false, vtype: 'email' }
            },
            { text: '角色', dataIndex: 'Roles', flex: 1 ,
                editor: {
                    xtype: 'combo', store: 'Roles', multiSelect: true, allowBlank: false, editable: false,
                    emptyText: "请选择角色", forceSelection: true
                }
            },
            { xtype: "datecolumn", text: '创建时间', dataIndex: 'Created', format: "Y-m-d H:i:s", width: 150 },
            { xtype: "datecolumn", text: '最后登录时间', dataIndex: 'Created', format: "Y-m-d H:i:s", width: 150 },
            { xtype: 'checkcolumn', dataIndex: "IsApproved", text: "允许登录", winth: 150 ,
                editor: { xtype: 'checkbox', cls: 'x-grid-checkheader-editor' }
            }
        ]

代码中,用户名只是简单的不允许为空;电子邮件除了不允许为空外,还要符合电子邮件格式(vtype为email);角色则使用Combobox,数据来自之前定义的Roles Store;禁用列则使用了Checkbox控件,样式使用x-grid-checkheader-editor,这样好看点。

接着在分页工具栏添加3个按钮,其中添加用户、删除用户使用图标显示,而重置密码则直接使用文字按钮。要在按钮显示图标,最好的方式使用样式,因而,先切换到app.css,添加以下样式:

.user-add{
     background:url("../images/user-add-16.png")!important;
}
.user-delete{
     background:url("../images/user-delete-16.png")!important;
}

复制后,切换回用户视图脚本文件,在分页工具栏定义中加入items配置项来添加按钮,代码如下:

            items: [
                '-',
                { iconCls: "user-add", tooltip: '添加用户', id: "buttonUserAdd" },
                { iconCls: "user-delete", tooltip: '删除用户', id: "buttonUserDelete", disabled: true },
                '-',
               { text: "重置密码", id: "buttonUserResetPassword", disabled: true }
            ]

注意,按钮全部都添加了id,目的就是在控制器中使用id查找按钮。删除用户和重置密码默认状态为disabled状态,只有在Grid选择行后才会启用。

然后可以F5运行,然后点击Grid数据行,效果如下

 现在,要在控制器完成各种视图操作了。切换到Users控制器,先添加一些引用来获取视图和按钮,代码如下:

    refs: [
        { ref: "UserPanel", selector: "#userPanel" },
        { ref: "UserView", selector: "#usersView" },
        { ref: "ButtonUserAdd", selector: "#buttonUserAdd" },
        { ref: "ButtonUserDelete", selector: "#buttonUserDelete" },
        { ref: "ButtonUserResetPassword", selector: "#buttonUserResetPassword" },
    ],

这样,就可通过get方法获得各控件了。

首先来完成选择一条记录后,启用删除和重置密码按钮。实现相当简单,利用引用,使用getUserView返回用户视图后,调用on方法绑定selectionchange事件就行了。当然也可以在control方法内定义,不过笔者感觉再用id获取一次对象,有点多余。在panel.add这句下加入以下代码:

me.getUserView().on("selectionchange", me.onUserSelect, me);

在onUserSelect方法内,利用get方法返回两个按钮后,调用对象的setDisabled方法设置其开启状态就可以了,代码如下:

    onUserSelect:function(model, rs){
        var me=this,
        length=rs.length;
       me.getButtonUserDelete().setDisabled(length==0);
       me.getButtonUserResetPassword().setDisabled(length == 0);
    },

事件selectionchange会在第二个参数以数组形式返回所有选择的记录,只要数组的长度不为0,就启用按钮的,为0则禁用按钮。

接着完成添加操作,在绑定selectionchange事件的代码下添加以下代码绑定click事件:

me.getButtonUserAdd().on("click", me.onAddUser, me);

在onAddUser方法内,要做的操作是先调用cancelEdit取消当前编辑操作,以避免在编辑过程中单击了添加按钮出现问题。接着在Store中添加一条记录。最后调用startEdit方法进入编辑状态。具体代码如下:

   onAddUser: function () {
       var me = this
       edit = me.getUserView().plugins[0],
       User = me.getUserModel();
       edit.cancelEdit();
       me.getUsersStore().insert(0, new User);
       edit.startEdit(0, 0);
   },

代码中,getUserModel是定义models配置项时自动生成的方法,可返回模型。同理,getUsersStore方法也是自动生成的,用于返回Store。要注意plugins中的索引,因为当前示例只有一个插件,因而使用0就可以返回RowEditing实例了,如果有多个插件,要注意索引值。

接下来要考虑怎么保存数据了,在RowEditing有一个Edit事件,它会在编辑完成后触发,非常适合用来进行数据保存操作。而且该事件会把事件冒泡到Grid,因而在Grid绑定该事件就行了,代码如下:

me.getUserView().on("edit",me.onEditcomplete, me);

在onEditcomplete方法内,调用Store的sync方法将数据同步到服务器就行了,代码如下:

   onEditcomplete: function (editor, e) {
       var me = this;
       me.getUsersStore().sync({
           success: function (e, opt) {
               var me = this;
               me.getUsersStore().commitChanges();
           },
           failure: function (e, opt) {
               var me = this, msg = "";
               me.getUsersStore().rejectChanges()
               Ext.Msg.alert("错误", e.exceptions[0].error);
           },
           scope: me
       });
   }

如果同步操作,服务器返回success为true,则调用commitChanges方法确认修改,如果失败,则调用rejectChanges方法取消修改,并显示错误信息。错误信息的处理与上文的处理差不多,只是返回的对象不同,自己根据需要做好定义就行了。

还有考虑用户取消编辑时,要调用rejectChanges方法取消更改,这个通过监听canceledit事件就可以实现,代码如下:

me.getUserView().on("canceledit", me.onCancelEdit, me);

方法onCancelEdit的代码如下:

   onCancelEdit: function () {
       var me = this;
       me.getUsersStore().rejectChanges();
   }

现在要在服务器端完成用户的添加功能了,这里要注意的是数据的提交方式。如果不清楚,可在页面单击添加按钮,然后单击保存按钮,在Firebug中就可以看到如图28所示的提交数据。 在服务器端的处理过程就是通过data提取数据,然后转换为JSON数组,从数组中把数据提取出来。对于添加操作,调用Membership的CreateUser方法添加用户就可以了,具体代码如下:

public JObject Add()
        {
            bool success = false;
            string msg = "";
            JArray ja = null;
            try
            {
                string data = Request["data"] ?? "";
                if (string.IsNullOrEmpty(data))
                {
                    msg = "错误的提交数据。";
                }
                else
                {
                    ja = JArray.Parse(data);
                    if (ja.Count > 0)
                    {
                        JObject jo = (JObject)ja[0];
                        MembershipCreateStatus createStatus;
                        MembershipUser user = Membership.CreateUser((string)jo["Username"], "123456", (string)jo["Email"], null, null, (bool)jo["IsApproved"], out createStatus);
                        if (createStatus == MembershipCreateStatus.Success)
                        {
                            success = true;
                            string[] roles = ((JArray)jo["Roles"]).Values<string>().ToArray<string>();
                            jo["id"] = user.ProviderUserKey.ToString();
                            jo["Created"] = user.CreationDate.ToString("yyyy-MM-dd hh:mm:ss");
                            jo["LastLoginDate"] = user.LastLoginDate.ToString("yyyy-MM-dd hh:mm:ss");
                            Roles.AddUserToRoles(user.UserName, roles);
                        }
                        else
                        {
                            msg = MyFunction.ErrorCodeToString(createStatus);
                        }
                    }
                    else
                    {
                        msg = "错误的提交数据。";
                    }
                }
            }
            catch (Exception e)
            {
                msg = e.Message;
            }
            return MyFunction.WriteJObjectResult(success, 0, msg, ja);
        }

还要在相应的MyFunction类中添加方法

        public static string ErrorCodeToString(MembershipCreateStatus createStatus)
        {
            // See http://go.microsoft.com/fwlink/?LinkID=177550 for
            // a full list of status codes.
            switch (createStatus)
            {
                case MembershipCreateStatus.DuplicateUserName:
                    return "当前用户名已被注册,请使用其他用户名。";

                case MembershipCreateStatus.DuplicateEmail:
                    return "当前电子邮件已被注册,请使用其他电子邮件。";

                case MembershipCreateStatus.InvalidPassword:
                    return "密码错误,请输入正确的密码。";

                case MembershipCreateStatus.InvalidEmail:
                    return "电子邮件地址错误,请重新输入。";

                case MembershipCreateStatus.InvalidAnswer:
                    return "The password retrieval answer provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidQuestion:
                    return "The password retrieval question provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidUserName:
                    return "用户名错误,请输入正确的用户名。";

                case MembershipCreateStatus.ProviderError:
                    return "系统错误,请联系管理员。";

                case MembershipCreateStatus.UserRejected:
                    return "当前请求已被取消,请重新输入并再次尝试提交。如果还存在问题,请与管理员联系。";

                default:
                    return "未知错误,请重新输入并再次尝试提交。如果问题依然存在,请与管理员联系。";
            }
        }

编辑用户的操作过程与添加操作雷同,不过Membership不允许修改用户名,因为最好在编辑的时候,不允许用户修改用户名。这个需要在RowEditing进入编辑之前来控制编辑控件的状态,也就是在beforeedit事件中实现。先在Grid绑定beforeedit事件,代码如下:

me.getUserView().on("beforeedit", me.onBeforeEdit, me);

在onBeforeEdit方法内,要对用户名的编辑组件进行操作,因而要为它添加一个引用,那就要先为编辑控件添加一个id,在用户视图脚本文件内,为用户名的编辑控件添加一个id:

            { text: '用户名', dataIndex: 'Username', flex: 1,
                editor: { allowBlank: false, id: "editorUserName" }
            },

然后在控制器中引用:

现在完成onBeforeEdit方法,代码如下:

   onBeforeEdit: function (editor, e) {
       var me = this;
       if (e.record.data.id) {
           me.getEditorUsername().setDisabled(true);
       } 
       else {
           me.getEditorUsername().setDisabled(false);
       }
   }

编辑的数据都有id,而新增的数据id为null,因而通过判断id就可判断是编辑数据还是新增数据,从而可控制用户名是否允许编辑了。这也是为什么在添加数据后必须返回正确的id的一个重要原因。

现在可完成服务器端的编辑操作代码了,具体代码如下:

public JObject Edit()
        {
            bool success = false;
            string msg = "";
            JArray ja = null;
            try
            {
                string data = Request["data"] ?? "";
                if (string.IsNullOrEmpty(data))
                {
                    msg = "错误的提交数据。";
                }
                else
                {
                    ja = JArray.Parse(data);
                    if (ja.Count > 0)
                    {
                        JObject jo = (JObject)ja[0];
                        Guid id = Guid.Parse((string)jo["id"]);
                        MembershipUser user = Membership.GetUser(id);
                        if (user != null)
                        {
                            user.Email = (string)jo["Email"];
                            user.IsApproved = (bool)jo["IsApproved"];
                            Membership.UpdateUser(user);
                            string[] roles = ((JArray)jo["Roles"]).Values<string>().ToArray<string>();
                            string[] oldroles = Roles.GetRolesForUser(user.UserName);
                            string[] rolelist = new string[] { "普通用户", "系统管理员" };
                            foreach (var c in rolelist)
                            {
                                if (roles.Contains(c))
                                {
                                    if (!oldroles.Contains(c))
                                    {
                                        Roles.AddUserToRole(user.UserName, c);
                                    }
                                }
                                else
                                {
                                    if (oldroles.Contains(c))
                                    {
                                        Roles.RemoveUserFromRole(user.UserName, c);
                                    }
                                }
                            }
                            success = true;
                        }
                        else
                        {
                            msg = "要修改的用户不存在或已被删除。";
                        }
                    }
                    else
                    {
                        msg = "错误的提交数据。";
                    }
                }
            }
            catch (Exception e)
            {
                msg = e.Message;
            }
            return MyFunction.WriteJObjectResult(success, 0, msg, ja);
        }

 现在来完成删除功能。目前的Grid,一次只能选择一行,也就是说,一次只能删除一行,不太方便,因而要设置成使用复选框选择,并允许多选的。在用户视图脚本文件中,添加以下配置项实现这个:

    selType: "checkboxmodel",
    selModel: { checkOnly: false, mode: "MULTI" },

打开浏览器刷新

删除用户的方式有2种,一种是先使用remove方法在Store中删除记录,然后调用sync方法同步,一种是提取选择行的id,然后通过Ajax方式提交到服务器进行删除,确认后再在客户端刷新页面。第一种方式必须在proxy的api定义中定义destroy配置项,之前的代码中已经定义了,因而本示例将使用该方式。第二种方式如果也定义了destroy配置项,就千万别用remove删除Store的记录,不然在添加或编辑的时候,调用sync方法进行同步的时候会把删除记录的数据一起提交的。

现在切换到Users控制器的脚本,为删除按钮绑定单击事件,代码如下:

me.getButtonUserDelete().on("click", me.onDeleteUser, me);

在onDeleteUser方法内,要先从Store的选择模型获取选择的记录。如果有选择记录,则先提示用户是否真的删除用户。确认后,调用remove方法删除记录,并调用sync方法同步数据,如果成功,调用commitChanges方法确认修改,否则调用rejectChanges方法取消修改。具体代码如下:

   onDeleteUser: function () {
       var me = this,
       sm = me.getUserView().getSelectionModel();
       if (sm.getCount() > 0) {
           var rs = sm.getSelection();
           content = ["确定删除以下用户?"];
           for (var i = 0; ln = rs.length, i < ln; i++) {
               content.push(rs[i].data.Username);
           }
           Ext.Msg.confirm("删除用户", content.join("<br/>"), function (btn) {
               if (btn == "yes") {
                   varme = this,
                   rs = me.getUserView().getSelectionModel().getSelection()
                   store = me.getUsersStore();
                   store.remove(rs);
                   store.sync({
                       success: function (e, opt) {
                           var me = this;
                           me.getUsersStore().commitChanges();
                       },
                       failure: function (e, opt) {
                           var me = this;
                           me.getUsersStore().rejectChanges()
                           Ext.Msg.alert("发生错误", e.exceptions[0].error);
                       },
                       scope: me
                   });
               }
           }, me)
       } 
       else {
           Ext.Msg.alert('删除用户', '请选择要删除的用户。');
       }
   }

代码中,使用了数组content来组合确认信息。当用户确认后,就调用remove方法,并调用sync方法。

现在来完成服务器端代码。因为sync方法提交数据的方式是固定的,因而提取删除数据的方式与添加和编辑操作的一样,需要从data中提取数据,然后使用parse方法转换为JArray。余下的工作就是从JArray中提取出删除数据的JObject,通过id或Username去删除用户了,具体代码如下:

        public JObject Delete()
        {
            string msg = "";
            bool success = false;
            JArray ja = null;
            string data = Request["data"] ?? "";
            if (string.IsNullOrEmpty(data))
            {
                msg = "错误的提交数据。";
            }
            try
            {
                ja = JArray.Parse(data);
                if (ja.Count > 0)
                {
                    foreach (JObject jo in ja)
                    {
                        Membership.DeleteUser((string)jo["Username"]);
                    }
                    success = true;
                }
                else
                {
                    msg = "错误的提交数据。";
                }
            }
            catch (Exception e)
            {
                msg = e.Message;
            }
            return MyFunction.WriteJObjectResult(success, 0, msg, ja);
        }

这里要注意的是,和添加编辑操作一样,尽管success返回true,还是要把删除的数据返回。因为删除不需要改变原有的数据,因而直接将ja返回就行了。

还有2个问题要自己考虑清楚。第1个是删除后是否提示用户已删除记录,如果需要,在sync方法内的回调函数success内加入提示信息就可以了。第2个问题是,因为删除数据后,Grid内的数据会减少,是否需要刷新页面?

最后一个功能重置密码与删除用户差不多,也是从选择模型获取选择记录。不过,这次,不能用sync同步,只能通过提取id,然后使用Ajax方法提交数据了。先为重置密码按钮绑定单击事件,代码如下:

me.getButtonUserResetPassword().on("click", me.onResetPassword, me);

接着完成onResetPassword方法,代码如下:

   onResetPassword: function () {
       var me = this,
           rs = me.getUserView().getSelectionModel().getSelection();
       if (rs.length > 0) {
           var idList = [];
           for (var i = rs.length - 1; i >= 0; i--) {
               idList.push(rs[i].data.id);
           }
           Ext.Ajax.request({
               params: { id: idList },
               url: '/Users/ResetPassword',
               scripts: true,
               scope: me,
               success: function (response, opt) {
                   var obj = Ext.JSON.decode(response.responseText);
                   if (obj) {
                       if (obj.success) {
                           Ext.Msg.alert("提示信息", "重置密码成功");
                           return;
                       } else {
                           Ext.Msg.alert("错误", obj.Msg);
                       }
                   }
               },
               failure: function (response, options) {
                   Ext.Msg.alert("错误", "重置密码失败!<br>错误信息:" + response.responseText);
               }
           });
       }
   }

代码中,没有进行确认,如果需要,可自行添加。因为Ajax提交不是根据返回的success值来调用success方法或failure方法的,只要不是页面错误,都会执行回调函数success方法,因而要自己根据返回的数据,调用decode方法将数据转换为对象,然后根据success的值来做处理。

下面,完成服务器端的ResetPassword方法。在这里,有个麻烦,就是ChangePassword方法需要使用旧密码才能修改新买,而ResetPassword方法会随机生成一个密码,都不能直接将密码设置为123456,因而,需要先用ResetPassword方法重置密码后,然后利用这个重置的密码,调用ChangePassword方法将密码修改为123456,具体代码如下:

        public JObject ResetPassword()
        {
            bool success = false;
            string msg = "";
            string idList = Request["id"] ?? "";
            try
            {
                string[] ids = idList.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var c in ids)
                {
                    Guid id = Guid.Parse(c);
                    MembershipUser user = Membership.GetUser(id);
                    if (user != null)
                    {
                        string pwd = user.ResetPassword();
                        user.ChangePassword(pwd, "123456");
                    }
                }
                success = true;
            }
            catch (Exception e)
            {
                msg = e.Message;
            }
            return MyFunction.WriteJObjectResult(success, 0, msg, null);
        }

现在用户管理的基本功能(添加、删除、修改、重置密码)功能都实现了。

示例代码下载链接http://files.cnblogs.com/aehyok/ExtJsUserViewTwo.zip

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • ExtJs十四(ExtJs Mvc图片管理之四)

    现在来实现排序的问题。要实现远程排序,首先要清楚的是排序信息是如何提交到服务器的,而这个,利用FireBug相当简单。

    aehyok
  • ExtJs二(实现登录)

      在上一次http://www.cnblogs.com/aehyok/archive/2013/04/17/3025957.html主要是搭建Ext环境,本次...

    aehyok
  • ExtJs二(实现登录)

      在上一次http://www.cnblogs.com/aehyok/archive/2013/04/17/3025957.html主要是搭建Ext环境,本次...

    aehyok
  • APICloud开发者进阶之路|[ 新手教程 ] 原生js 会话列表滑动删除置顶效果

    完整地址:https://community.apicloud.com/bbs/thread-87977-1-1.html

    APICloud
  • DeDecms织梦程序设置当天发布文档日期以红色显示

    建站需要使用cms系统,大部分情况下个人草根站长或者小公司是无法自行开发cms系统,目前国内有很多种免费的cms建站系统,那么使用织梦CMS建站的时候,会碰到特...

    用户2472688
  • 自定义组件——BottomSelectView

      BottomSelectView是一个常用的底部选择器。多用于门户页面,与Fragment联动,可以快速搭建一个可以切换Fragment的首页组件。   ...

    饮水思源为名
  • EXTJS7 combobox 下拉加载数据源码

    路过君
  • ExtJs十四(ExtJs Mvc图片管理之四)

    现在来实现排序的问题。要实现远程排序,首先要清楚的是排序信息是如何提交到服务器的,而这个,利用FireBug相当简单。

    aehyok
  • [-Flutter自组篇-] Flutter中四十行代码能做什么?

    张风捷特烈
  • PHP怎么获取二维数组之间的差值

    前几天写了一个获取思否某标签下文章的采集Api,为了不想重复推送,就加了一个新旧文件比较的操作,其实就是两个数组合并取差值

    沈唁

扫码关注云+社区

领取腾讯云代金券