zoukankan      html  css  js  c++  java
  • 前端开发框架Bootstrap和KnockoutJS

    前端开发框架Bootstrap和KnockoutJS

    江湖中那场异常惨烈的厮杀,如今都快被人遗忘了。当年,所有的武林同道为了同一个敌人都拼尽了全力,为数不多的幸存者心灰意冷,隐姓埋名,远赴他乡,他们将唯一的希望寄托给时间。少年子弟江湖老,红颜少女的鬓边也有了白发。多年以后,听闻那个魔头也不久于人世,他们欣欣然回乡,却发现当初殚精竭虑研究出来对付敌人的招数全无用处,曾经受人尊敬的大侠现在被称为——新手 or 菜鸟。月下小酌,孤独的他们对着夜空举起酒杯,吼一声:“走你,IE6!”

    -----------------------------------------------------割--------------------------------------------------------------------

    Bootstrap是一个前端框架,解放Web开发者的好东东,展现出的UI非常高端大气上档次,理论上可以不用写一行css。只要在标签中加上合适的属性即可。请参看Bootstrap中文文档,这是3.0版本。

    KnockoutJS是一个JavaScript实现的MVVM框架。非常棒。比如列表数据项增减后,不需要重新刷新整个控件片段或自己写JS增删节点,只要预先定义模板和符合其语法定义的属性即可。简单的说,我们只需要关注数据的存取。官网文档

    Bootstrap负责UI,KnockoutJS负责数据绑定,两者相得益彰,Web前端必备利器。

    我们来做一个简单的例子展示一下它们的威力。

    要搁以前,实现类似功能,可以有两个选择:a)直接操作DOM,够喝一壶,一般喜欢展现技术同学的首选;b)借助各种拉风的重量级JS框架,比如extjs,使用它们的API以减少工作量,不过这些框架的学习曲线也挺扭曲。当然本文所说的两个框架也涉及到各自的JS类库,but,提供给开发人员的使用方式是完全不同的,后者更松散(废话,两个当然比一个松散)、灵活,且是基于特性声明的方式,个人表示相当不错。下面就让我们开始码吧。

    首先搭个初步的框架:

    复制代码
    <div id="divAuthManage" class="row" style="margin-top: 30px;">
        <div class="col-md-4 col-sm-4 col-xs-6">
            <div>
                <div class="input-group">
                    <span class="input-group-addon">用户名</span>
                    <input id="inputUserName" type="text" class="form-control" />
                    <span class="btn btn-primary input-group-btn">添加</span>
                </div>
                <div id="divWaring" class="alert alert-warning" style="display: none;"></div>
            </div>
            <table class="table table-bordered table-hover" style="margin-top: 20px;">
                <thead>
                    <tr>
                        <th>用户ID</th>
                        <th>用户名</th>
                        <th style="text-align: center;">删除</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </div>
        <div class="col-md-8 col-sm-8 col-xs-12">
            @foreach (AreaElement area in Model.Areas)
            {
                <div class="panel panel-default">
                    <div class="panel-heading">
    
                        @{if (area.Sites.Count == 0)
                          {
                            <label class="checkbox">
                                <input type="checkbox" value="@area.Code" />
                                @area.Name
                            </label>
                          }
                          else
                          {
                            @area.Name
                          }
                        }
    
                    </div>
                    @if (area.Sites.Count > 0)
                    {
                        <div class="panel-body">
                            @foreach (SiteElement site in area.Sites)
                            {
                                <label class="checkbox-inline">
                                    <input type="checkbox" value="@site.Code" />@site.Name
                                </label>
                            }
                        </div>
                    }
                </div>
            }
            <p class="text-right">
                <button type="button" class="btn btn-default">保存</button>
            </p>
        </div>
    </div>
    复制代码

    这里就用到了bootstrap,如果一个元素使用了相应的class,它就会呈现bootstrap中预定义的样式。bootstrap还提供了data-xxx属性,这是用来以声明方式使用组件,这里没有涉及。now,界面如下:

    图中标注了需要改进的两个地方,此时先不考虑。我们现在要先把数据从后台取出,以及其它的一些操作,于是引进KnockoutJS。接触过WPF的都知道ViewModel的概念,说白了就是将前端分为UI和交互逻辑,ViewModel就负责交互逻辑,knockoutJS也有这个东西。结合例子具体来看:

    复制代码
    window.adApp.authManageViewModel = (function (ko) {
        var userList = ko.observableArray(),
            error = ko.observable(),
            addUser = function (username) {
                this.clearError();
                if (!username) {
                    error("请输入用户名.");
                }
                else {
                    this._ajaxRequest("post", '/api/UserAuthority/AddUser', { "": username }, function (data) {
                        if (!data.IsSucceed)
                            this.error(data.Message);
                        else {
                            var user = new User(data.Data);
                            this.userList.unshift(user);
                        }
                    });
                }
            },
            deleteUser = function (user) {
                this._ajaxRequest("delete", '/api/UserAuthority/DeleteUser', { "": user.userid }, function (data) {
                    if (!data.IsSucceed)
                        this.error(data.Message);
                    else {
                        this.userList.remove(user);
                    }
                });
            },
            getUsers = function () {
                this._ajaxRequest("get", '/api/UserAuthority/GetUsers', null, function (data) {
                    this.userList.removeAll();
                    for (var i = 0; i < data.length; i++) {
                        userList.push(new User(data[i]));
                    }
                });
            },
            selectUser = function (user) {
                for (var i = 0; i < userList().length; i++) {
                    userList()[i].selected(false);
                }
                user.selected(true);
                this._ajaxRequest("get", '/api/UserAuthority/GetAccessNavItems', { userid: user.userid }, function (data) {
                    user.navitems.removeAll();
                    for (var i = 0; i < data.length; i++) {
                        user.navitems.push(data[i]);
                    }
                });
            },
            clearError = function () { error(""); };
    
        var viewmodel = {
            userList: userList,
            error: error,
            _ajaxRequest: ajaxRequest,
            addUser: addUser,
            deleteUser: deleteUser,
            clearError: clearError,
            _getUsers: getUsers,
            selectUser: selectUser,
            currentUser: ko.computed(function () {
                for (var i = 0; i < userList().length; i++) {
                    if (userList()[i].selected()) {
                        return userList()[i];
                    }
                }
                return null;
            })
        };
        viewmodel._getUsers();
        return viewmodel;
    
        function ajaxRequest(type, url, data, callback) { // Ajax helper
            this.clearError();
            $.ajax({
                url: url,
                data: data,
                type: type,
                dataType: "json",
                context: this,//指定this为当前对象viewmodel
                success: callback,
                error: function () {
                    this.error("服务器错误.");
                }
            });
        }
    })(ko);
    
    // Initiate the Knockout bindings
    ko.applyBindings(window.adApp.authManageViewModel);
    复制代码

    window.adApp.authManageViewModel就是ViewModel,它包含了两个属性(UserList为用户集合,error为提示信息,准确的命名应该类似msg,懒得改了)和若干函数(和服务端交互)。ko.applyBindings将该ViewModel绑定到页面。上述代码还涉及到两个类型:

    复制代码
    function NavItem(data) {
        var self = this;
        data = data || {};
    
        // Persisted properties
        self.code = data.code;
        self.name = data.name;
    }
    
    function User(data) {
        var self = this;
        data = data || {};
    
        // Persisted properties
        self.userid = data.userid;
        self.username = data.username;
        data.navitems = data.navitems || [];
        self.navitems = ko.observableArray(data.navitems);
    
        self.selected = ko.observable(false);
    }
    User.prototype.updateNavs = function () {
        var user = this;
        window.adApp.authManageViewModel._ajaxRequest(
            "put", '/api/UserAuthority/UpdateNavItems?userid=' + user.userid, { "": user.navitems()}, function (data) {
                if (!data.IsSucceed)
                    this.error(data.Message);
                else {
                    this.error("保存成功!");
                }
            });
    }
    复制代码

    现在页面代码如下:

    复制代码
    <div id="divAuthManage" class="row" style="margin-top: 30px;">
        <div class="col-md-4 col-sm-4 col-xs-6">
            <div>
                <div class="input-group">
                    <span class="input-group-addon">用户名</span>
                    @*data-bind="input: clearError" 不支持input绑定,so换用自定义绑定,or采用event绑定如下*@
                    <input id="inputUserName" type="text" class="form-control" data-bind="event: { input: clearError }" />
                    <span class="btn btn-primary input-group-btn" data-bind="click: function (data, event) { addUser(inputUserName.value) }">添加</span>
                </div>
                <div id="divWaring" class="alert alert-warning" style="display: none;" data-bind="animVisible: error"></div>
            </div>
            @*如果userList集合有项,才显示该表格*@
            <table data-bind="if: userList().length > 0" class="table table-bordered table-hover" style="margin-top: 20px;">
                <thead>
                    <tr>
                        <th>用户ID</th>
                        <th>用户名</th>
                        <th style="text-align: center;">删除</th>
                    </tr>
                </thead>
                <tbody data-bind="foreach: userList">
                    <tr data-bind="css: { success: selected }, click: function (data, event) { $parent.selectUser($data) }">
                        <td><span data-bind="text: userid"></span></td>
                        <td><span data-bind="text: username"></span></td>
                        <td style="text-align: center;">
                            <button type="button" class="btn btn-default btn-xs" data-bind="click: function (data, event) { $parent.deleteUser($data) }, clickBubble: false">
                                <span class="glyphicon glyphicon-remove"></span>
                            </button>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
        @*将下面div的绑定对象设为currentUser,如果currentUser为空,则该div中的内容不会显示*@
        <div class="col-md-8 col-sm-8 col-xs-12" data-bind="with: currentUser">
            @foreach (AreaElement area in Model.Areas)
            {
                <div class="panel panel-default">
                    <div class="panel-heading">
    
                        @{if (area.Sites.Count == 0)
                          {
                            <label class="checkbox">
                                <input type="checkbox" value="@area.Code" data-bind="checked: navitems" />
                                @area.Name
                            </label>
                          }
                          else
                          {
                            @area.Name
                          }
                        }
    
                    </div>
                    @if (area.Sites.Count > 0)
                    {
                        <div class="panel-body">
                            @foreach (SiteElement site in area.Sites)
                            {
                                <label class="checkbox-inline">
                                    <input type="checkbox" value="@site.Code" data-bind="checked: navitems" />@site.Name
                                </label>
                            }
                        </div>
                    }
                </div>
            }
            <p class="text-right">
                <button type="button" class="btn btn-default" data-bind="click: updateNavs">保存</button>
            </p>
        </div>
    </div>
    复制代码

    代码中加了很多data-bind属性,作用不言自明。需要注意的是所谓自定义绑定。当绑定的值变动了,希望执行额外的逻辑(和c#中的事件相似),就会用到。这里,当error的值有变动,为空时提示面板隐藏,否则显示:

    <div id="divWaring" class="alert alert-warning" style="display: none;" data-bind="animVisible: error"></div>

    animVisible就是一个自定义绑定,定义如下:

    复制代码
    ko.bindingHandlers.animVisible = {
        update: function (elem, valueAccessor) {
            var error = ko.unwrap(valueAccessor());
            if (error) {
                elem.innerText = error;
                $(elem).show(300);
            }
            else {            
                $(elem).hide(300);
                elem.innerText = "";
            }
        }
    };
    复制代码

    OK,接下来,当点击表格的每一行,currentUser就会自动计算得到(ko.computed),并反馈到界面,绑定了该字段的div内部的相应节点的状态也会相应变化(checkbox)。保存啥的就不说了。综上所述,除了必要的与后台交互的代码,涉及到操作UI和DOM节点,我们不需要写一行JS,很爽很舒服。

    ps:本来想写详细点,结果发现写一大堆还不如几行代码来的清楚。文中若有错误之处,请及时告知,大家交流,共同进步。

    转载请注明本文出处:http://www.cnblogs.com/newton/p/3328058.html

     
     
     
    标签: HTML5CSS3javascript
  • 相关阅读:
    33.数组声明方式(var构造函数) 、检测数组类型、数组的属性(封装好的就一个length)、数组的方法
    31.this指向(写出调用链,找最近对象) this的默认绑定 隐式绑定 显示绑定(call(绑定对象) apply(绑定对象) 当括号内没放绑定对象的时候恢复默认绑定) bind
    31.
    30.函数作用域链 (GO AO 也叫词法作用域链)、 调用栈、调用栈涉及this绑定
    29.包装类(构造函数) 包装类作用及调用栈
    916. Word Subsets
    246. Strobogrammatic Number
    445. Add Two Numbers II
    2. Add Two Numbers
    341. Flatten Nested List Iterator
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3333565.html
Copyright © 2011-2022 走看看