文档章节

使用avalon实现用户分组管理的介绍

limodou
 limodou
发布于 2013/10/14 13:14
字数 3912
阅读 5590
收藏 5

在我的应用中,有一块消息处理的功能,它可以按组给相应的人发送消息。为了方便使用,増加了自定义分组的功能,用户可以自行将人员分为不同的组。目前分组只支持一级,对于日常使用目前是足够了。为了方便用户操作,因此分组的所有功能设计为单页面的操作,包括:分组的创建、改名、删除,分组的展示,切換,分组用户的添加,删除,人数的动态显示。因为涉及的功能比较多,因此采用了avalon这一mvvm的框架。前端展示主要是使用bootstrap加上自已写的一些css。与后端交互使用jquery,还用到了bootstrap的对话框插件。本文主要是介绍前端的实现,后端因为是使用uliweb框架所以不会做太多涉及。

本文不是一个avalon的教程,所以很多细节可能只是一带而过,在这里只是想介绍一下我用Avalon都做了些什么,用到了什么,以及我的一些想法。所以有些内容请参照avalon的文档来阅读。

下面让我们来一步步看分组功能的实现。

整体布局

在此输入图片描述

  • 最上面是几个按钮,用来控制组的添加、组名修改和删除。
  • 下面左侧是分组的tab,可以点击进行切換。
  • 每个分组会显示一个添加按钮,当前总人员和目前所有人。每个人显示一个头像,人名。当鼠标移上去,会显示一个框和一个删除的图标,点击后可以删除此人。

avalon的引入和基本结构

因为我使用的比较简单,所以只需要引入 avalon.js 即可。从avalon的网站下载源码包,将avalon.js拷贝到你的应用的静态文件目录下,象我是放在 /static 目录下,因此在 <head> 处添加:

<script src="/static/avalon.js"></script>

然后,找一处可以添加 javascript 的地方,比如 </body> 之前,写入:

avalon.config({
    interpolate: ["{%", "%}"]
});
var model = avalon.define('Groups', function(vm){
    vm.groups = {{=json_dumps(groups)}};
    vm.items = [];
    vm.cur_id = 0;
});

请注意,上面的代码是在uliweb框架下运行的(其实和本文还是有些差异),所以有些地方你要根据实际情况进行调整。上面的代码我解释一下:

  1. avalon.config 是用来进行配置的。这里 interpolate 是可以更換 avalon 插值表达式的标签字符串的,原来它是 {{}} ,但是因为uliweb的模板使用 {{}} 来解析,所以和avalon的冲突了,因此通过avalon.config进行重设。如果你没有这个问题,这个设置可以不用管它。
  2. avalon.define('Groups', function(vm){}) 定义了一个avalon的controller的处理函数。它对应的controller的名字是 Groups, function中的vm参数是对应底层的viewmodel的实例,用它来处理controller中的变量。它的返回值保存到一个变量中,这里为 model,这样可以在controller函数之外使用。
  3. {{=json_dumps(groups)}} 这是干了什么。 vm.groups 是用来存放当前有哪些组的变量。在启动这个页面时,我们需要对groups进行初始化。其实初始化有两种方式,一种是通过ajax请求从后台获取,这样可以与后台的实现无关。另一种是在模板中直接生成。这里我采用的是第二种,因此,在后台显示这个页面时,已经准备好了groups的数据,通过uliweb的一个方法将数据转为json格式。这样做也是因为每个人的组比较少,直接在模板中生成可以减少一次请求,你可以试着改为用ajax从后台获取。
  4. vm.items 用来保存当前组的人员信息。
  5. vm.cur_id 用来保存当前组的id。

我利用了 items, cur_id来保存当前组的信息,意味着,在任意时刻,我只维护一组的用户信息。但是组的信息是全部的。因此,在切換组的时候,会向后台请求待切換组的信息,然后items和cur_id会发生变化。这样也是为了减化数据结构。一个完整的group信息可以设计为:

group = [{id:xx, name:yyy, items:[...]}, {id:xx, name:yyy, items:[...]}

不过这样层次比较多。因此我把items提出来,并且随着组的切換与后台进行数据的获取。

基本的界面代码

我是使用bootstrap作为ui,以下是框架代码:

<div class="container-fluid" ms-controller="Groups">
  <div class="row-fluid">
    <div style="margin-bottom:10px; padding-bottom:5px; border-bottom:1px solid #999;">
        <a href="#" class="btn btn-primary" ms-click="add_group">添加新分组</a>
        <a href="#" class="btn btn-info" ms-click="edit_group(cur_id)">修改分组名</a>
        <a href="#" class="btn btn-info" ms-click="del_group(cur_id)">删除分组</a>
    </div>
    <div id="tabs" class="tabbable tabs-left rounded" style="background-color:white;padding:10px;">
        <ul class="nav nav-tabs" ms-each-tab="groups">
            <li>
                <a href="#items" data-toggle="tab" ms-click="active_tab(tab.id)">{% tab.name %}</a>
            </li>
        </ul>
        <div class="tab-content" ms-if="groups.size()">
          <div class="tab-pane" id="items">
            <div>
                <a href="#" class="btn btn-primary btn-small" ms-click="add_user(cur_id)">添加人员</a>
                总人数: {%items.size()%}
            </div>
            <ul ms-each-user="items" class="unstyled group-users clearfix">
                <li>
                    <div class="body" ms-hover="show">
                        <img ms-src="user.image"/>
                        {% user.url|html %}
                        <span class="delete" ms-click="del_user(user.id, cur_id)">&times;</span>
                    </div>
                </li>
            </ul>
          </div>
        </div>
    </div>
    
  </div>
</div>

说明一下:

  1. 最外层是一个div,它有一个非常关键的属性 ms-controller="Groups" 表明它是一个avalon的controller。

  2. 整体结构就是bootstrap的tab结构,就不作过多解释了。通过 <div id="tabs" class="tabbable tabs-left"> 在class上添加tabs-left让tab显示在左侧,主要是让组太多的话,可以竖着排列。

  3. 在tabs区上面是若干的按钮,通过 ms-click 来绑定处理函数,如: add_group, edit_group(cur_id) 。其中 add_group 没有括号,当没有参数时可以简化成这样。这样需要在define中添加一个 add_group 的函数。 edit_group(cur_id) 因为有参数所以是标准的函数的写法,在define中需要添加一个带参数的处理函数。cur_id 是对应 vm.cur_id 变量,表示当前的组id。

  4. 在tabs区的ul中,添加了 ms-each-tab="groups" ,它用来控制下面子元素的循环。它是用来显示group的tab的。这里 tab 是循环变量,groups 对应 vm.groups 变量。在循环体中将生成若干的 <a> 标签 。在 <a> 标签中添加了 ms-click="active_tab(tab.id)" ,这样就绑定了click事件到 active_tab 方法上,同时传入 tab.id 这个变量。<a> 的值是 {% tab.name %} 。这是avalon中的插件表达式的写法。还记得吗?我们在前面将 {{}} 換成了 {%%} 。所以它的显示是 tab.name 的值。

  5. 因为我的tab对应的内容是动态生成的,所以我只定义了一个 <div class="tab-content" ms-if="groups.size()"> 。这里使用了 ms-if 属性,表示只有当 groups.size() 为真,即个数大于 0时才会显示出来。因此,如果groups在开始为空,这个div是不会出现的。

  6. 在每个tab的内容部分,先是有一个按钮,绑定了 add_user(cur_id) 事件,用来向指定的group中添加用户。然后在后面通过 {%items.size()%} 来显示items的个数,即当前组的总人数。这里 size() 是 avalon 为监控数组添加的一个方法,它可以随着内容条数的变化,对页面的引用进行修改。而使用 items.length 则起不到这个作用。所以这一点要注意。

  7. 下面就是显示每个组的人员信息了,所以使用了 ul>li 的结构。在ul上添加了一个ms-each的循环: ms-each-user="items" 。这样对 items 进行循环,循环变量是 user

  8. 在循环体中,添加了 ms-hover="show" 作用是当鼠标在元素上移动时,向元素添加一个class,即 show 。这样我们可以通过定义相对应的css来处理有show和无show时的样式,来显示当前鼠标所指向的元素的效果。

  9. 每个用户将显示一个头像,姓名和删除按钮,分别用:

     <img ms-src="user.image"/>
     {% user.url|html %}
     <span class="delete" ms-click="del_user(user.id, cur_id)">&times;</span>
    

来处理。 ms-src 用来引用 user 变量中的 image 属性值。 {% user.url|html %} 用来显示用户名,它其实是一个可点击的链接,所以是url。而 |html 是Avalon中定义的过滤器(在avalon中定义了一些常用的过滤器,比如对日期格式的转換等),它可以保持内容不被转义,即保留文本中的html特殊符号。如果不加,则自动转义。通过span来生成一个删除按钮,将click事件与删除用户函数相绑定,它的参数是 user.id 表示当前用户的id,和 cur_id 表示当前组。对于删除按钮,缺省的样式是不显示的。当父元素添加了 show 之后,就会显示出来。所以它的显示是通过css来控制的。

以上就是ui的详细介绍。从而我们可以了解avalon(mvvm框架)的一些特点:

  • 提供了数据模型与DOM页面结合的方法
  • 提供了UI的一些展示效果,如我用到的: ms-if, ms-hover, ms-src等
  • 提供了数据与事件的绑定
  • 提供了基本的处理逻辑,如 ms-each-xxx

通过以上的绑定,数据和展示非常好的结合在了一些。那么,当数据发生变化,会使得界面自动进行改变。所以一旦界面绑定完毕,我们下面就只要处理数据是如何变化的就可以了。

数据处理

其实Avalon的主要难点在界面,数据处理我只是举几个例子。

组的添加

先看一下添加组的示例:

vm.add_group = function(){
    var name = prompt("请输入新的收信人分组名称:");
    if (name){
        $.post('/config/messages/add_group', {'name':name})
            .success(function(data){
                if (data.success){
                    show_message(data.message);
                    vm.groups.push(data.data);
                    setTimeout(function(){
                        vm.active_tab(data.data.id);
                        $('#tabs ul a:last').tab('show');
                    }, 100);
                }else{
                    show_message(data.message, 'error');
                }
            });
    }
}
  1. 先提示用户输入组名。
  2. 然后如果输入了值,则调用 $.post 来与后台进行通讯。这里, $.post 是jquery的方法,因此我的这个示例其实还需要安装jquery,如何引入jquery我就不再说了。我发现avalon和jquery可以很好的结合,至少从我目前的使用来说是这样的。而angularjs则在这一点上要麻烦一些。avalon中,通过其它的非Avalon的方法可以直接修改vm中的数据,并会影响界面的变化;而angularjs使用jquery的方法修改了model的数据,界面不会直接变化,还要执行如$scope.apply()之类来强制刷新的方法或者使用Angularjs自带的ajax方法。所以我个人感觉在与jquery的结合上,avalon要优于angularjs。
  3. 如果成功,会调用 show_message 来显示成功的信息。这里show_message是我自已写的一个方法和avalon无关。成功后,会将返回的数据添加到vm.groups的最后。在我的应用里我定义前后段的json消息格式为: {success:boolean, message:消息, data:数据} 。因此data参数其实是这种结构,所以data.data是返回的数据。它其实是一个object,格式为 {id:xxx, name:yyy}
  4. 然后使用了setTimeout 来延迟更新tab切換。可能你要问?为什么不在push新的group之后就调用切換tab的处理呢?这是因为,数据变化之后,到界面刷新还是需要一定的时间的。avalon采用监控属性变化的方式来异步更新,所以更新数据之后马上执行,有可能早于界面的刷新,造成切換无效。采用延迟方式来刷新,是希望界面已经变化之后再执行,避免错误。那么能不能做到更精确的时间切換呢?目前还很困难,因为异步的原因,无法得到准确的时机。所以象angularjs也存在同样的问题。对于我来说,我是通过$scope.apply()来强制刷新,而Avalonjs中好象没有这样的用法。所以采用了延迟执行。100毫秒对于我这个简单的应用来说足够了。不过如果有更好的方式希望能告诉我。在延迟执行中,先调用了 active_tab 来切換数据,然后调用bootstrap的方法来切換tab。其实,延迟执行主要是因为在一系列的串行处理中,夹杂了异步处理(自动刷新)导制,而这个异步处理目前没有办法以得到它的一些状态。

切換当前tab

先看代码:

vm.active_tab = function(id){
    vm.cur_id = 0;
    vm.items = [];
    if(id){
        vm.cur_id = id;
    }else{
        if (vm.groups.length>0){
            vm.cur_id = vm.groups[0].id;
        }
    }
    if(vm.cur_id>0){
        $.get('/config/messages/get_group/'+vm.cur_id)
            .success(function(data, status, headers, config){
                vm.items = data;
                orderBy(vm.items, 'email');
            });
    }
}
  1. 先对 cur_iditems 进行初始化。主要是为了保证旧的数组不会遗留。对于象数组这样的结构,我曾经发现,如果不清空直接修改的话,有可能以前的数据会遗留下来,这个是avalon为了效率这样的处理。所以清空就保证了没有遗留。不知道这点以后有可能可能变化。这样反正也没有错。
  2. 然后修改 cur_id 。如果没有给出id参数,则自动取vm.groups中的第一个id。
  3. 如果 cur_id > 0 ,则通过 $.post() 与后台通讯,获得当前group的所有用户,这里没有采用 {success:boolean, message:消息, data:数据} 的格式,而是直接返回一个数组。所以是 vm.items = data。得到数据后,对它进行了一个排序,根据 邮件地址 。这里 orderBy是我单独写的一个方法, 不是avalon提供的。

修改组

vm.edit_group = function(group_id){
    var name = prompt("请输入新的收信人分组名称:");
    if (name){
        $.post('/config/messages/edit_group/'+group_id, {'name':name})
            .success(function(data){
                if (data.success){
                    for(var i=0;i<vm.groups.length;i++){
                        if(vm.groups[i].id === group_id)
                            vm.groups[i]['name'] = name;
                    }
                }else{
                    show_message(data.message, 'error');
                }
            });
    }
}

这里主要说一下调用后台的处理成功后,如何修改vm.groups的数据。对vm.groups进行循环,比较group的id是否等于修改的组的id,即上面的 if(vm.groups[i].id === group_id) ,如果相等,则替換 name : vm.groups[i]['name'] = name 。这样当数据一变,界面自然跟着变化。

删除组

vm.del_group = function(group_id){
    var ret = confirm("你确定要删除此分组吗?");
    if (ret){
        $.post('/config/messages/del_group', {'id':group_id})
            .success(function(data){
                if(data.success){
                    show_message(data.message);
                    var i=0;
                    for(;i<vm.groups.length;i++){
                        if(group_id === vm.groups[i].id){
                            vm.groups.splice(i, 1);
                            break;
                        }
                    }
                    vm.active_tab();
                    $('#tabs ul a:first').tab('show');
                }
                else{
                    show_message(data.message, 'error');
                }
            });
    }
}

最主要的就是如何修改数据,就是上面的 vm.groups.splice(i, 1); ,将删除数组中找到的索引下标的值。

总结

通过avalon可以大大减化前端界面的开发,基本上我们只要考虑:如何展示界面,如何处理数据。当数据变化时,Avalon为你搞定一切。当前目前还是有些理想。而且我上面的例子还是非常简单,许多avalon的功能都没有用到,比如include, router等。使用Avalon之后,大量琐碎的处理都没有了,比如:添加,修改,删除之后对DOM元素的处理,当人员变化时对DOM元素的处理,以及人员总数的处理。如果只是简单使用jquery,除了后台通讯的处理,我们还需要大量的代码来处理这些细节。当然,我们也可以封装出若干当数据变化时更新界面的函数,但是没有直接使用Avalon这样顺畅。avalon目前还在快速发展中,还可能存在不足,所以要在使用中比较仔细,了解它能做什么,不能做什么,怎么作是正确的,这样才能比较好的使用它。随着它的不断完善,我想它会越来越好用。

© 著作权归作者所有

limodou

limodou

粉丝 244
博文 92
码字总数 36993
作品 6
西城
技术主管
私信 提问
avalonJS-源码阅读(三) VMODEL

avalon dom小结 看过前面三篇文章后,应该会对avalon关于dom的处理有个大体的理念。这里再理一遍:avalon通过手动触发scan函数来遍历dom。然后根据确定VMODELS的作用域,接下来便是处理用户代...

lost_o0
2014/05/05
2.3K
0
mooshroom/MDEditor

MDEditor--markdown编辑器 直接上网址碳素云-markdown编辑器 功能介绍 MDEditor是一款前端实现markdown编辑器的模块,目前功能包含: markdown格式文本的实时编译(同时支持GFM语法) 编辑模...

mooshroom
2015/03/05
0
0
迷你MVVM框架 avalonjs 1.2 发布

avalon1.2 带来了许多新特性,让开发更轻松!详见如下: 升级路由系统与分页组件。 对ms-duplex的绑定值进行增强,以前只能prop或prop.prop2,现在可以prop["xxx"]、prop[prop2]。换言之,添...

qinerg
2014/02/21
15.2K
21
MV* 框架 与 DOM操作为主 JS库 的案例对比

最近分别使用 Zepto 和 Avalon框架写了个 SPA项目,贴出来讨论下 JS DOM操作为主 JS库 与 MV* 框架的对比 案例(MV* 框架 与 DOM操作 JS库 实例对比) 购物车页面 JS业务逻辑(忽略 Ajax请求...

子凡
09/29
0
0
前端 MVVM 框架--Avalon

avalon是一个功能强大,体积小巧的MVVM框架。它遵循“操作数据即操作DOM”的理念,让你在代码里基本见不到一点DOM操作代码。DOM操作全部在绑定后,交给框架处理。相当后端有了ORM一样,不用你...

qinerg
2013/07/18
38.1K
9

没有更多内容

加载失败,请刷新页面

加载更多

springboot 403 问题

添加WebAppConfigurer 配置 @Configuration@EnableAutoConfigurationpublic class WebAppConfigurer extends WebMvcConfigurerAdapter { public WebAppConfigurer() { } ......

布袋和尚_爱吃鱼
4分钟前
1
0
Python自动更换壁纸爬虫与tkinter结合

直接上代码 import ctypesimport timeimport requestsimport osfrom threading import Threadfrom tkinter import Tk, Label, Button,Entry,StringVar,messagebox# '放到AppData\Roami......

物种起源-达尔文
4分钟前
1
0
Postgresql Study 笔记

Postgresql 安装 Windows, MAC Install Postgresql 下载地址: https://www.enterprisedb.com/downloads/postgres-postgresql-downloads Linux Install sudo apt-get update sudo apt-get in......

slagga
6分钟前
1
0
layer.open 打开新页面传参问题

如图所示,点击出售,把A页面的数据传到弹框上面,因为弹框比较复杂,所以使用引入一个新页面。 A.html a.js B.html b.js 1、第一种方案 sellInte: function (){ var obj = document.g...

木九天
9分钟前
1
0
沙龙报名 | 区块链数据服务技术应用实践

京东云是国内首家提供区块链数据在线分析服务产品的公司,也是行业内首家对区块链数据服务进行开源的公司。 本次沙龙是京东云BDS开源后,首次在深圳举办线下沙龙,我们将邀请京东云BDS团队核...

京东云技术新知
10分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部