前端开发框架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集合有项,才显示该表格,注意if、ifnot的作用范围不包括table标记本身,而是从thead开始*@
        <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:本来想写详细点,结果发现写一大堆还不如几行代码来的清楚。文中若有错误之处,请及时告知,大家交流,共同进步。


knockoutjs补充:

  1. 若前端元素绑定的是普通属性(即非observable属性),虽然属性值的改变不会反应到前端,但是前端值的改变会改变属性值;也就是说在这两种模式下,后端都会监听前端的相关事件(如input的change事件)
  2. js中直接设置observable属性值,比如this.ItemData.IsChecked(true);,也将触发IsChecked.subscribe订阅的事件,但不会触发前端dom事件,比如checkbox的change事件
  3. 可以属性链绑定,如<span data-bind="text: BasicInfo.RoleName"></span>

 

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

目录
相关文章
|
15天前
|
JavaScript 前端开发 开发者
Vue.js 框架大揭秘:响应式系统、组件化与路由管理,震撼你的前端世界!
【8月更文挑战第27天】Vue.js是一款备受欢迎的前端JavaScript框架,以简洁、灵活和高效著称。本文将从三个方面深入探讨Vue.js:响应式系统、组件化及路由管理。响应式系统为Vue.js的核心特性,能自动追踪数据变动并更新视图。例如,通过简单示例代码展示其响应式特性:`{{ message }}`,当`message`值改变,页面随之自动更新。此外,Vue.js支持组件化设计,允许将复杂界面拆分为独立且可复用的组件,提高代码可维护性和扩展性。如创建一个包含标题与内容的简单组件,并在其他页面中重复利用。
35 3
|
4天前
|
Web App开发 前端开发 JavaScript
Web前端项目的跨平台桌面客户端打包方案之——CEF框架
Chromium Embedded Framework (CEF) 是一个基于 Google Chromium 项目的开源 Web 浏览器控件,旨在为第三方应用提供嵌入式浏览器支持。CEF 隔离了底层 Chromium 和 Blink 的复杂性,提供了稳定的产品级 API。它支持 Windows、Linux 和 Mac 平台,不仅限于 C/C++ 接口,还支持多种语言。CEF 功能强大,性能优异,广泛应用于桌面端开发,如 QQ、微信、网易云音乐等。CEF 开源且采用 BSD 授权,商业友好,装机量已超 1 亿。此外,GitHub 项目 CefDetector 可帮助检测电脑中使用 CEF
34 3
|
1月前
|
前端开发 JavaScript API
一场前端框架的“武林大会”,三大主流框架之间的性能比较!!!
一场前端框架的“武林大会”,三大主流框架之间的性能比较!!!
|
1月前
|
开发框架 前端开发 JavaScript
【Vue 3】一款开箱即用的中后台前端开发框架,开源且免费!!
【Vue 3】一款开箱即用的中后台前端开发框架,开源且免费!!
|
11天前
|
开发者 安全 UED
JSF事件监听器:解锁动态界面的秘密武器,你真的知道如何驾驭它吗?
【8月更文挑战第31天】在构建动态用户界面时,事件监听器是实现组件间通信和响应用户操作的关键机制。JavaServer Faces (JSF) 提供了完整的事件模型,通过自定义事件监听器扩展组件行为。本文详细介绍如何在 JSF 应用中创建和使用事件监听器,提升应用的交互性和响应能力。
13 0
|
11天前
|
前端开发 开发者 Apache
揭秘Apache Wicket项目结构:如何打造Web应用的钢铁长城,告别混乱代码!
【8月更文挑战第31天】Apache Wicket凭借其组件化设计深受Java Web开发者青睐。本文详细解析了Wicket项目结构,帮助你构建可维护的大型Web应用。通过示例展示了如何使用Maven管理依赖,并组织页面、组件及业务逻辑,确保代码清晰易懂。Wicket提供的页面继承、组件重用等功能进一步增强了项目的可维护性和扩展性。掌握这些技巧,能够显著提升开发效率,构建更稳定的Web应用。
33 0
|
11天前
|
前端开发 程序员 API
从后端到前端的无缝切换:一名C#程序员如何借助Blazor技术实现全栈开发的梦想——深入解析Blazor框架下的Web应用构建之旅,附带实战代码示例与项目配置技巧揭露
【8月更文挑战第31天】本文通过详细步骤和代码示例,介绍了如何利用 Blazor 构建全栈 Web 应用。从创建新的 Blazor WebAssembly 项目开始,逐步演示了前后端分离的服务架构设计,包括 REST API 的设置及 Blazor 组件的数据展示。通过整合前后端逻辑,C# 开发者能够在统一环境中实现高效且一致的全栈开发。Blazor 的引入不仅简化了 Web 应用开发流程,还为习惯于后端开发的程序员提供了进入前端世界的桥梁。
19 0
|
11天前
|
前端开发 JavaScript 中间件
【前端状态管理之道】React Context与Redux大对决:从原理到实践全面解析状态管理框架的选择与比较,帮你找到最适合的解决方案!
【8月更文挑战第31天】本文通过电子商务网站的具体案例,详细比较了React Context与Redux两种状态管理方案的优缺点。React Context作为轻量级API,适合小规模应用和少量状态共享,实现简单快捷。Redux则适用于大型复杂应用,具备严格的状态管理规则和丰富的社区支持,但配置较为繁琐。文章提供了两种方案的具体实现代码,并从适用场景、维护成本及社区支持三方面进行对比分析,帮助开发者根据项目需求选择最佳方案。
8 0
|
11天前
|
前端开发 JavaScript 测试技术
React 与前端自动化测试也太重要啦!各种测试框架助力确保应用质量,快来开启优质开发之旅!
【8月更文挑战第31天】随着前端技术的发展,React 成为了构建用户界面的热门选择。然而,随着应用复杂性的增加,确保应用质量变得至关重要。本文介绍了前端自动化测试的重要性,并详细综述了常用的测试框架如 Jest、Enzyme 和 Cypress,以及如何通过它们进行高效的 React 组件测试。通过遵循最佳实践,如编写可维护的测试用例、覆盖关键场景、集成 CI/CD 流程和进行性能测试,可以显著提高应用的稳定性和可靠性。
22 0
|
27天前
|
前端开发
前端ElementPlus框架中的几种图标按钮使用方式
本文介绍了如何在Element Plus前端框架中使用带有图标的按钮,包括设置按钮大小、图标大小、按钮类型以及如何为图标添加点击事件。
98 0
下一篇
DDNS