• CMS:文章管理之视图(3)


    要想让Combobox列表显示像树,只要按Ext JS树的形式组织显示文本就行了。因而先用Firebug分析一下Ext JS树的格式就可以了。在浏览器打开Ext JS示例中的Check Tree示例,依次展开Grocery List和Ebergy Foods节点,会看到图53的效果。

    图53 Check Tree示例的效果

    因为Combobox中返回的是一个完整的树,其节点全部是展开的,因而可以知道,展开后的节点的图标是模拟效果所需的。将Firebug切换到HTML面板,然后单击选择页面元素的按钮,选择展开后的那个黑色小三角图标,在Firebug中会看到如图54中的效果。


    图54 Firebug中黑色小三角图标的HTML代码


    从图54可以看到,Grocery List节点的构成是2个图标加上文字。现在要做的就是把两个图标的HTML代码复制出来。接着选择Coffee节点,会看到它是由3个空白图标、1个叶子图标、1个复选框和文字构成的,这里面的空白图标和叶子图标也是需要的,复制出来。

    从书中可以知道,这些图标src中的图片都是空白图片,起占位符的作用,实际显示的是它们的背景图片。(这也说明,这个在IE旧版本中是没有效果的)

    在HTML面板中,选中这些图片,然后在右边的样式中就可看到它们的样式了,例如如图55那样,选择叶子图标,会看它主要由2个样式决定显示方式。第1个就是顶部的img的样式,这个很重要,如果没有该样式,图标和文字就不能对齐,会上下错位。第2个样式“x-tree-icon-leaf”了,它用来显示背景图片。

    图55 叶子图标的样式

    笔者测试过,直接保留样式和整个结构搬过去并不行,还是要重新定义一下样式。因为Ext.view.BoundList的每个列表行的起始样式是x-boundlist-item,因而从这个样式开始重新定义一下样式就行了。切换到app.css,添加以下样式:

    .x-boundlist-item span

    {

        line-height:19px;

    }

    .x-boundlist-item img

    {

       display:inline;

       vertical-align: top;

    }

    .x-boundlist-item .x-tree-elbow-plus

    {

       background-image:url("../../../extjs/resources/themes/images/default/tree/arrows.gif");

       background-position: -16px 0;    

        16px;

    }

    .x-boundlist-item .x-tree-icon-parent {

       background-image:url("../../../extjs/resources/themes/images/default/tree/folder-open.gif");

        16px;

    }

    .x-boundlist-item .x-tree-icon-leaf

    {

       background-image: url("../../../extjs/resources/themes/images/default/tree/leaf.gif");

        16px;

    }

    第1个样式是为文字对齐用的,如果不套一个span,直接设置x-boundlist-item的line-height,会影响其它的Combobox,因而这里套一个span来对齐文字。第2个样式是用来定义图标显示方式的;第3个用来显示展开的小三角图标;第4个用来显示打开的文件夹图标;第6个用来显示叶子图标,也就是在图55看到的样式。

    现在切换到Category控制器,先新增几个私有变量来指定图片,代码如下:

    private string blank = "<imgclass='x-tree-elbow'src='data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='>";

    private string folder = "<imgclass='x-tree-icon x-tree-icon-parent ' src='data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='>";

    private string leaf = "<imgclass='x-tree-icon x-tree-icon-leaf'src='data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='>";

    private string plus = "<img class='x-tree-elbow-plusx-tree-expander'src='data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='>";

    重载一个writeNode方法来生成新的数据结构,代码如下:

    private JObject writeNode(int id, string text, boolisLeaf, int level)

    {

        stringoffset= "";

       if(level>0)

        {

           offset = string.Join(string.Empty, Enumerable.Repeat(blank,level).ToArray());

        }

        stringlistText = "<span>" + offset + (isLeaf ? blank + leaf : plus+folder)+text +"</span>";

        JObjectjo = new JObject

        {

            newJProperty("id",id),

            newJProperty("text",text),

            newJProperty("listText",listText)

        };

        returnjo;

    }

    因为两个writeNode参数相同,且参数类型也一样,因而第2个方法把3和4参数对调了一下。参数level的作用就是用来生成空白图标的。

    Enumerable对象的Repeat方法可生成一个重复值的队列,非常方便。把队列转换为数组,就可通过字符串的Join方法将数组转换为字符串了。

    用来显示的listText,先套一个span,然后加上偏移量,也就是空白图标。如果是叶子,就加多一个空白图标,再加上叶子图标。如果不是叶子,就加一个展开的小三角图标和打开的文件夹图标。最后加上文本。

    CategoryCombo提交的是All方法,现在完成All方法,代码如下:

    public JObject All()

    {

        boolsuccess = false;

        stringmsg = "";

        JArray ja= new JArray();

        int total= 0;

        try

        {

           ja.Add(writeNode(-1,"文章类别",false,0));

           ja.Add(writeNode(10000, "未分类", true, 1));

            var q = dc.T_Category.Where(m =>m.State == 0 & m.CategoryId!=10000).OrderBy(m => m.FullPath);

           foreach (var c in q)

            {

               bool leaf = c.Childs.Count() > 0 ? false : true;

               ja.Add(writeNode(c.CategoryId, c.Title, leaf,(int)c.Hierarchylevel+1));

            }

           success = true;

        }

        catch(Exception e)

        {

            msg =e.Message;

        }

        returnHelper.MyFunction.WriteJObjectResult(success, total, msg, ja);

    }

    还是重复得不能再重复的代码。因为有全路径,因而通过FullPath排序就可直接把树排列好了。这里还是要先把“未分类”排除在外,让它显示在前面。因为要把根目录显示出来,因而要多添加一个“文章分类”作为根。也因为根目录才是第1层,而Hierarchylevel是以没有父id的列为根的,因而要加1。

    生成一下解决方案,然后展开父类选择,就可看到如图56的效果。笔者认为这还行。

    图56 类似树的下拉选择效果

    定义父类的Combobox时,忘记加上查询功能了,只要添加配置项queryMode,值为local就行了。因为是必选的,因而,还要加上forceSelection配置项,值为true。

    现在来完成保存按钮和重置按钮的操作。先完成重置按钮的,这个简单,调用表单的reset方法就行了,代码如下:

    onReset: function () {

        var me =this;

       me.form.getForm().reset();

    },

    最后完成保存按钮,主要就是做表单提交,这个问题不大,直接调用表单的submit提交就行了,提交地址会在窗口显示前进行设置。现在要考虑的是提交成功后,怎么处理树的更新?新增的话比较简单,先检查它的父节点是否已经显示,如果还没有,就直接通过fullpath找到最顶层的根节点,然后展开它的全部子节点。如果已经存在,且已经展开,则调用appendChild方法追加就行了;如果存在,但没展开,则展开该节点就行了。

    编辑操作比较复杂,很难处理,除非模仿树的拖放操作做相应的处理,这里就不做研究了,直接刷新Store。或者一种替代方式是编辑时禁止修改父类,只允许通过拖放方式改变父类,这里也不再做研究了,有兴趣自己做一下。

    现在,先把提交过程写出来,再考虑刷新问题,代码如下:

    onSave: function () {

        var me = this,

                f= me.form.getForm();

        if(f.isValid()) {

           f.submit({

                //waitMsg:"正在保存,请等待……",

                //waitTitle:"正在保存",

               success: function (form, action) {

                   var me = this;

               },

                failure: SimpleCMS.FormSubmitFailure,

               scope: me

            });

        }

    }

    因为Ext JS表单提交的错误返回方式是雷同的,因而可以统一对这些错误进行处理,因而在这里会看到failure配置项的值为 “SimpleCMS.FormSubmitFailure”。这个当时在登录窗口时没做,现在可以完成它了。在Index.html文件中,添加以下代码:

    SimpleCMS.FormSubmitFailure = function(form,action) {

        if(action.failureType === "connect") {

           Ext.Msg.alert('错误',

            '状态:' + action.response.status + ': ' +

           action.response.statusText);

           return;

        }

        if(action.result) {

            if(action.result.Msg)

               Ext.Msg.alert('错误',action.result.Msg);

        }

    }

    好了,现在考虑提交成功后的处理,首先是要区分现在的状态是编辑状态还是添加状态,这个可通过CategoryId的值来判断,值大于10000的肯定是编辑状态,除非新建模型的时候,把该值设置为了大于10000的值,这个,估计只有傻瓜才会这样干。

    新增的时候,根据服务器端返回的数据值,在父节点下添加一个新节点就行了。这里还有个技术问题,就是要找到TreeStore才能找到父节点,这是个难题。还是用老办法,在控制器的init方法内,为窗口添加一个store属性,并指向TreeStroe好了。因为还需要用到视图展开节点,因而,还要添加属性view,指向TreeView,具体修改代码如下:

    var me = this,

        win =SimpleCMS.view.Content.CategoryEdit,

        panel =me.getContentPanel();

    me.view = Ext.widget("contentview");

    panel.add(me.view);

    win.view = me.view.down("treeview");

    win.store = me.getCategoriesTreeStore();

    现在来完成success中的代码,代码如下:

    success: function (form, action) {

        var me =this,

           values = form.getValues(),

            data= action.result.data[0];

        if(values.CategoryId > 10000) {

           me.store.load();

        } else {

            if(data.parentId) {

               var parentNode = me.store.getNodeById(data.parentId);

               if (parentNode) {

                   if (parentNode.isExpanded()) {

                       parentNode.appendChild(data);

                   } else {

                       parentNode.expand();

                   }

                }else {

                   parentNode = me.store.getNodeById(data.fullpath.substr(1, 5));

                   me.view.expand(parentNode, true);

                }

            }else{

                me.store.getRootNode().appendChild(data);

           }

        }

       me.form.down("combobox").store.load();

       me.close();

    },

    代码先调用getValues方法返回表单中所有字段的值,然后判断CategoryId的值是否大于10000,如果是,说明是编辑状态,直接刷新树。否则,是新增状态。然后根据返回的数据,判断父id是否存在,如果不存在,说明是顶层节点,直接将新节点追加到根节点。如果父id存在,则调用getNodeById方法获取节点,如果返回null,说明节点还没展开,则通过fullpath取得顶层节点,然后调用视图的expand方法展开顶层节点的所有节点。如果父节点存在,且已展开,就追加方式追加节点,否则展开节点。

    现在来完成onCategoryAdd方法,在这里要做的就是设置表单的提交地址,设置窗口的标题,利用loadRecord方法为表单加载一个新记录,最后显示窗口,具体代码如下:

    onCategoryAdd: function () {

        var me = this,

            win =SimpleCMS.view.Content.CategoryEdit,

            model= me.getCategoryModel();

       win.form.getForm().url = "/Category/Add";

       win.setTitle("新增文章类别");

       win.form.loadRecord(new model);

       win.show();

    },

    因为是表单提交,因而可以利用MVC的模型认证进行服务器端验证,这形式不错,省了很多功夫。首先要做的是在Models目录创建一个模型。在Models目录添加一个名称为CategoryModel的类,在类内添加以下属性定义就行了:

    public class CategoryModel

    {

        publicint CategoryId { get; set; }

       [Required]

       [Display(Name = "父类")]

        publicint ParentId { get; set; }

       [Display(Name = "题图")]

       [StringLength(255)]

        publicstring Image { get; set; }

       [Display(Name = "排序序数")]

        publicint SortOrder { get; set; }

       [Required]

       [Display(Name = "标题")]

       [StringLength(255)]

        publicstring Title { get; set; }

        [Display(Name= "内容")]

       [StringLength(8000)]

        publicstring Content { get; set; }

    }

    以上不懂的,可以网上搜索一下,资料很多,学MVC必须会的。

    接着切换到Category控制器,添加Add方法,代码结构有点类似登录时的代码,具体代码如下:

    [HttpPost]

    [AjaxAuthorize(Roles = "普通用户,系统管理员")]

    public JObject Add(CategoryModel model)

    {

        boolsuccess = false;

        JObjecterrors = new JObject();

        if(ModelState.IsValid)

        {

            try

            {

               T_Category rec = new T_Category

                {

                   Content = model.Content,

                   Created = DateTime.Now,

                    Image = model.Image,

                   SortOrder = model.SortOrder,

                   Title = model.Title

               };

               if (dc.T_Category.Select(m => m.CategoryId).Contains(model.ParentId))

                {

                   rec.ParentId=model.ParentId;

                }

               dc.T_Category.AddObject(rec);

               dc.SaveChanges();

               return MyFunction.WriteJObjectResult(true, 0, "", new JArray(

                   new JObject (

                       new JProperty("id",rec.CategoryId),

                       new JProperty("text", rec.Title),

                       new JProperty("parentId", rec.ParentId),

                       new JProperty("fullpath", rec.ParentId==null ? "" :rec.Parent.FullPath)

                   )

               ));

            }

           catch(Exception e)

            {

               return MyFunction.WriteJObjectResult(false, 0, e.Message, null);

            }

        }

        else

        {

           MyFunction.ModelStateToJObject(ModelState, errors);

        }

        returnMyFunction.WriteJObjectResult(success, errors);

    }

    特性HTTP说明Add方法只接收Post提交的数据,提交后的数据会字段转换为CategoryModel模型的实例。模型验证如果有错误,处理方法和登录时一样。如果没有验证错误,则新增一个T_Category实例,父类不能直接加进新建实例中,要判断在数据库中是否有该记录,如果没有,说明是顶层节点,要保持ParentId的值为null。保存数据,就返回一个包含新增数据的对象。因为新增后,没那么快更新记录的fullpath(通过存储过程更新的),因而,这里fullpath需要用到它的父类的fullpath。如果ParentId为null,说明是顶层节点,直接返回空值就行。

    生成一下解决方案,就可以测试了,在这里就不进行测试了。

    说明:经测试,如果Combobox设置了配置项forceSelection为true,则存在相同标题的类别时,提交后的类别均为最靠前的那个类别,与实际选择不符。造成这个问题的原因是在Combobox无论任何状态下,在验证时,都会搜索一遍在列表中是否存在与显示文本相同的记录,找到后就将它设置为选择值了,也就改变了原有的值。因而,不建议使用该配置项。

  • 相关阅读:
    网络通信2
    linux下使用shell脚本输出带颜色字体
    CentOS7 yum方式安装MySQL5.7
    Prometheus 基于文件的服务发现
    k8s容器探针
    kuberntes部署metallb LoadBalancer负载均衡
    [kubernetes]-namespace 处于Terminating状态的处理方法
    Kubernetes角色访问控制RBAC和权限规则
    k8s 关联pvc到特定的pv
    k8s创建kubeconfig文件
  • 原文地址:https://www.cnblogs.com/muyuge/p/6333733.html
Copyright © 2020-2023  润新知