65.9K
CodeProject 正在变化。 阅读更多。
Home

SLGrid - jQuery/Knockout 客户端组件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (4投票s)

2014年10月13日

MIT

3分钟阅读

viewsIcon

21096

downloadIcon

527

SLGrid - 用于 CRUD 操作的 jQuery/Knockout 客户端组件

在线尝试

引言

SLGrid 是一个 jQuery/Knockout 客户端组件。它可以用于任何 Web 开发平台:ASP.NET/MVC、PHP、Java 等等,并且可以使用任何数据库系统。它具有标准的网格功能,例如排序、分页和过滤。SLGrid 使用 Knockout,它允许每个网格单元格可选地包含 Knockout 小部件,例如:DropDown、日期选择器等,这些小部件绑定到 JavaScript 对象的列(字段)。许多 Knockout 小部件,例如:MoneyPercentageImage 等,可以在网上的 public 库中找到。对于 CRUD 操作,SLGrid 生成模板,这些模板应用于 Bootstrap 弹出框。

背景

SLGrid 是一个客户端组件,它使用 Ajax 调用与任何数据库系统通信。它使用自定义数据库适配器,例如:内存适配器、Ajax 适配器、LighStreamer 适配器等。

SLGrid 使用 Knockout,它通过声明性绑定将 UI 的各个部分连接到数据模型。
由于声明性绑定,相同的实体模型(文档)可以绑定到不同的视图,例如

  • 行视图
  • 行编辑视图
  • 表单视图
  • 表单编辑视图

这就是为什么数据行需要从网格中分离出来,并且数据行位于 ListView 中。

SLGrid 使用 Knockout,具有以下优点

  • 网格单元格的更新速度非常快,因为网格单元格中包含的 DOM 元素已经绑定到 JavaScript 字段(列)。每次更改时无需 getDocumentById()
  • 我们修改 JavaScript 数组:添加/更新/删除行,Knockout 会在网格中反映视觉变化。
  • 每个单元格都可以包含可重用的小部件

将此库用作启动点,并根据您的需要对其进行自定义。例如,当用户单击行编辑按钮时,有几种显示详细信息表单的方法

  • 在 Bootstrap 弹出框中显示表单
  • 在行下面的展开面板中显示表单
  • 在单独的页面中显示表单(使用编码的 URL 返回)
  • ...

SLGrid 使用 JavaScript 的面向对象(继承)编程和一些设计模式,例如:模块模式等。用于 CRUD 功能的大量代码位于基类中。对于具有许多实体(管理模块)的 CRUD 操作非常有用。

JavaScript 原型继承

    var SLEntity = function () {
        this.whichTemplate = function() {
            return this.displayMode == "edit" ? "person-edit-template" : "person-view-template"
        }
    }

    var Person = function (mode) {
        // instance properties
        // method whichTemplate(), in base class SLEntity, uses displayMode property
        this.displayMode = mode;
    }

    //  Person extends SLEntity
    Person.prototype = new SLEntity();

    var person =  new Person("edit");
    alert(person.whichTemplate());

模块模式

模块模式克服了对象字面量的局限性,为变量和函数提供了隐私,同时公开了 public API。在我们的例子中,只有 PersonList.viewModel 可以从外部访问。

    function SLGridViewModel(configuration) {
        this.itemsAtPage = ko.observableArray([]);
        ...
    }

    var PersonList = (function (DB) {
        var db = DB;

        var StorePerson  = function (data) { 
            db.StorePerson(data) 
        }

        var UpdatePerson = function (data) { 
            db.UpdatePerson(data) 
        }

        function GridViewModel() {
            ...
        }

        GridViewModel.prototype = new SLGridViewModel();

        return {
            viewModel: new GridViewModel()
        }

    })(new PersonDB());      

Using the Code

核心功能,从定义到规则和业务逻辑,都位于实体类中,例如 Person

定义类:Person、PersonList、PersonDB

通常,这些类由 SLGen 生成器生成为单独的 js 文件。

     var Person = function (data) {
        this.PersonId = ko.observable(data.PersonId || 0)
        this.Name = ko.observable(data.Name || "")

        this.TwitterName = ko.observable(data.TwitterName || "")

        this.CityId = ko.observable(data.CityId || 0)
        this.chosenCity = ko.computed(function () {
            return CityList.getCity(this.CityId())
        }, this);

        // instance properties, functionality is into the base (Entity) class
        this.isNew = ko.observable(data.isNew || false);
        this.rowDisplayMode = ko.observable(data.isNew ? "rowAdd" : Person.DEFAULTS.rowDisplayMode);
    }

    //  Person EXTENDS SLEntity
    Person.prototype = new SLEntity();

    Person.prototype.PersonId = ko.observable(0)
        .extend({
            primaryKey: true,
            //headerText: "Id",
            formLabel: "Id",
            width: "100px",
            defaultValue: function () { return this.getNextId() }
        });

    Person.prototype.Name = ko.observable("")
        .extend({
            headerText: "Name",
            formLabel: "Name",
            presentation: "bsPopoverLink", // view form bootstrap popover
            width: "200px",
            defaultValue: "",
            required: true,
            minLength: 2,
            pattern: { message: 'Please, start with uppercase !', params: '^([A-Z])+' }
        });

    Person.prototype.TwitterName = ko.observable("")
        .extend({
            headerText: "Twitter",
            formLabel: "Twitter name",
            width: "auto", // one of the columns with take rest of row space
            defaultValue: ""
        });

    Person.prototype.CityId = ko.observable(0)
        .extend({
            defaultValue: 101
        });

    Person.prototype.chosenCity = ko.observable()
        .extend({
            headerText: "City",
            formLabel: "City",
            width: "200px",
            presentation: "bsSelectCity"
            //presentation: "bsTypeaheadCity"
        }); 

将视图模型添加到全局视图模型

        var globalViewModel = {
            ...
            cityList: CityList.viewModel,
            personList: PersonList.viewModel
        }

        $(document).ready(function () {
            ko.applyBindings(globalViewModel);
        });

使用默认的 EntityListTemplate 或提供您的 PersonListTemplate

    < script type="text/html" id="EntityListTemplate">
        <div style="float:left">
            <h4 data-bind="text:listHeader"></h4> 
        </div>

        <div style="clear:both"></div>

        <div style="width:100%"> 
            <div data-bind="SLGrid: $data" /> 
            <div>
                <div style="float:left">
                    <button class="btn btn-primary btn-xs" 
                        data-bind="click: add, enable: canAdd, text:textAdd"></button>
                </div>
                <div style="float:right;" data-bind="SLGridPager: $data">
                </div>
                <div style="float:right; margin-right:20px;">
                    <span data-bind='text: nItems'></span> 
                    <span data-bind='text:textPager'></span>
                    <span data-bind="visible:maxPageIndex()>=itemsOnPager()">, 
                    <span data-bind='text:maxPageIndex()+1'></span> pages</span>
                </div>
                <div style="clear:both"></div>
            </div>
        </div>
    </script>

将模板绑定到 PersonList.viewModel

    <div data-bind="template: { 
                name: 'EntityListTemplate',
                data : personList, 
                afterRender: personList.afterRender }" >
    </div>

Knockout 小部件

Knockout 支持创建自定义绑定。这可以控制 observables 如何与 DOM 元素交互,并为您提供很大的灵活性,以一种易于重用的方式封装复杂的行为。

SLGrid 单元格可以包含 Knockout 小部件,例如:DropDown、日期选择器等,以显示显示和编辑模式下的列(字段)值。这是一个显示以“.”分隔千位的 float 值的 Widget 示例。

ko.bindingHandlers.bsMoney = {

    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var mode = viewModel.rowDisplayMode()
        var inRow = allBindings.get("inRow");
        var isViewMode = inRow && viewModel.isRowViewMode() || !inRow && viewModel.isViewMode()

        if (!isViewMode) {
            var colName = $(element).data("field");
            $(element).html("<input style='text-align:right' 
            class='form-control' data-field='" + colName 
            + "' data-bind=\"value: " + colName + 
            "().toFixed(2), valueUpdate:'keyup'\">")
        }
    },

    update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var value = ko.utils.unwrapObservable(valueAccessor())
        var mode = viewModel.rowDisplayMode()
        var inRow = allBindings.get("inRow");
        var isViewMode = inRow && viewModel.isRowViewMode() || !inRow && viewModel.isViewMode()

        if (isViewMode) {
            var tokens = value.toFixed(2).replace('-', '').split('.');
            var s = '$' + $.map(tokens[0].split('').reverse(), function (elm, i) {
                return [(i % 3 === 0 && i > 0 ? ',' : ''), elm];
            }).reverse().join('') + '.' + tokens[1];
            var markup = value < 0 ? '-' + s : s;
            $(element).html(markup)
        }
    }
}

可以使用属性 presentation:"bsMoney" 轻松地将该小部件应用于字段。

    Person.prototype.Amount = ko.observable()
        .extend({
            headerText: "Amount",
            formLabel: "Amount",
            defaultValue: 0,
            presentation: "bsMoney",
            align : 'right'
        });

历史

  • 2014 年 10 月 13 日:初始版本
© . All rights reserved.