dojo_加快Dojo TreeGrid的性能

dojo

当您要在网页上显示层次结构数据时,Dojo v1.4 TreeGrid是一个有用的小部件。 但是,如果您的数据集很大, TreeGrid的性能将非常慢。 在本文中,学习如何通过自定义TreeGridQueryStore解决此问题。 本文介绍了使用两个小部件时可能遇到的问题,解释了错误的原因,然后帮助您创建解决方案。 您还可以下载本文中使用的示例代码。

Dojo网格和大数据集

当然,您可以使用网格在浏览器中有效显示相对较小的数据集。 您将获得排序,列大小调整等优点。 但是,在任何给定时间可以处理的记录数实际上都有限制,最终会导致分页结果的问题。

您可能会忘记分页功能,这些日子终于过去了。 Dojo的网格在网格滚动时使用惰性文档对象模型(DOM)渲染。 对于具有少于几百条记录的相对较小的数据集,在滚动时,惰性DOM呈现将为预加载的数据集构建DOM。 例如,如果您有100条记录,但一次只能查看20条记录,则无需滚动到21至100中的任何记录,直到滚动到该特定部分。 按列排序和相关任务可以在小型数据集中按预期方式在内存中工作; 您可以有效地用JavaScript完成任务。

对于非常大的数据集,将JavaScript用于按列排序之类的任务很快变得不切实际,甚至是不可能的。 如果数据集很大,尝试使用ItemFileReadStore在浏览器中维护它是不切实际的,该ItemFileReadStore会将所有数据加载到浏览器中。

Dojo处理大型数据集的方法简单,优雅,可归结为两点:

  • 一个dojo.data实现,可以从服务器以任意页面大小请求数据。 本文使用QueryReadStore
  • 一个能够滚动的网格,它可以按需请求和加载特定页面。

本文的其余部分将向您展示如何使用QueryReadStore构建延迟加载的树网格。

QueryReadStore,TreeGrid和TreeModel

本节对QueryReadStoreTreeGridTreeModel了非常基本的介绍。

QueryReadStore

正如dojocampus.org DOC(见说明相关信息 ), QueryReadStore是非常相似的ItemReadStore 。 他们都使用JSON作为交换格式。 不同之处在于他们查询数据的方式。 ItemReadStore从服务器进行一次获取,并处理客户端中的所有排序和筛选。 几百条记录就可以了。 但是对于成千上万的记录或Internet连接速度缓慢, ItemReadStore不太可行。

QueryReadStore对服务器进行每次排序或查询的请求,使其非常适合具有小数据窗口的大型数据集,例如dojox.grid.DataGrid 。 清单1显示了如何创建QueryReadStore

清单1.创建一个QueryReadStore
var url = "./servlet/QueryStoreServlet";
var store = new dojox.data.QueryReadStore({
    url: url,
    requestMethod: "get"
});

TreeGrid和TreeModel

树小部件(例如TreeTreeGrid )提供了分层数据的视图。 TreeGrid小部件本身仅是数据视图。 真正的强大功能来自TreeModel ,它表示Tree小部件将显示的实际分层数据。

通常,数据最终来自数据存储,但是Tree小部件与dijit.tree.Model (与树所需的特定API匹配的对象)连接。 因此,“ Tree窗口小部件可以访问各种格式的数据,例如使用数据存储项访问其父项。

TreeModel负责某些任务,例如连接到数据源,延迟加载以及从Tree小部件中查询项目和项目的层次结构。 例如,一项任务可能是为“ Tree窗口小部件获取子项。 TreeModel还负责将数据更改通知给Tree

首先,您需要定义ForestStoreModel的查询( TreeModel的实现)以返回TreeGrid的多个顶级项。 您还将为树创建模型适配器以访问商店。 您需要清单2中的参数来构造TreeModel

清单2.创建TreeModel的代码
var query = { 
    type: "PARENT" 
}; 

var treeModel = new dijit.tree.ForestStoreModel({ 
    store: store, // the data store that this model connects to 
    query: query, // filter multiple top level items 
    rootId: "$root$", 
    rootLabel: "ROOT", 
    childrenAttrs: ["children"], // children attributes used in data store. 
    /* 
      For efficiency reasons, Tree doesn't want to query for the children 
      of an item until it needs to display them. It doesn't want to query 
      for children just to see if it should draw an expando (+) icon or not. 
      So we set "deferItemLoadingUntilExpand" to true. 
    */ 
    deferItemLoadingUntilExpand: true 
});

QueryReadStore,TreeGrid和TreeModel之间的关系

要了解如何使用TreeGrid ,请记住以下相互配合的树组件:

  • QueryReadStore负责从服务器获取数据。
  • ForestTreeModel负责从QueryReadStore查询项目的层次结构,并通知TreeGrid更新。
  • TreeGrid仅负责显示数据和处理用户事件。

图1显示了这三个之间的关系。 清单3显示了如何创建树。

图1. QueryReadStore,TreeGrid和TreeModel之间的关系
Flowcart显示了从服务器到用户事件的步骤,并以queryreadstore,foresttreemodel和treegrid作为步骤。
清单3.创建一棵树
// define the column layout for the tree grid. 
var layout = [ 
    { name: "Name", field: "name", width: "20%" }, 
    { name: "Age", field: "age", width: "auto" }, 
    { name: "Position", field: "position", width: "auto" }, 
    { name: "Telephone", field: "telephone", width: "auto" } 
]; 

// tree grid 
var treegrid = new dojox.grid.TreeGrid({ 
    treeModel: treeModel, 
    structure: layout, // define columns layout 
    /* 
      A 0-based index of the cell in which to place the actual expando (+) 
      icon. Here we define the "Name" column as the expando column. 
    */ 
    expandoCell: 0, 
    defaultOpen: false, 
    columnReordering: true, 
    rowsPerPage: 20, 
    sortChildItems: true, 
    canSort: function(sortInfo) { 
        return true; 
    } 
}, "treegrid"); 

treegrid.startup();

QueryReadStore如何与TreeGrid一起使用

本节探讨如何在TreeGrid渲染数据,使用QueryReadStore排序以及扩展父节点。

渲染图

QueryReadStore将向服务器发送顶级节点的请求。 存储对服务器的第一个请求将是对顶级节点(根的子代)的请求。 此dojo.data请求遵循特定的JSON格式。

树状网格将使用提供给模型的查询来发出初始请求,然后商店会将其与目标结合起来。 清单4显示了请求格式,REST URL模式以及服务器对请求的响应。 在示例中,请求是针对REST模式的。

清单4.查询请求和服务器响应
// query 
{ 
  query: {type: "PARENT"}, 
  start: 0, 
  count: 20 //rowsPerPage attribute of the tree grid 
} 

// request sample 
url?type=PARENT&start=0&count=20 

// server response 
{ 
  "identifier": "id", 
  "label": "name", 
  "items": [ 
    { "id": "id_0", 
      "name": "Edith Barney", 
      "age": 39, 
      "position": "Marketing Manager", 
      "telephone": 69000044 
      "children": true, 
      "type": "PARENT" 
    }, 
    { "id": "id_1", 
      "name": "Herbert Jeames", 
      "age": 43, 
      "position": "Brand Executive Manager", 
      "telephone": 69000077, 
      "type": "Child" 
    }, 
    ... 
  ], 
  "numRows": 10000 // total records in the grid 
}

项目"Edith Barney"的类型为"PARENT""Herbert Jeames"项目的类型为"Child" 。 树模型中的查询为{type: "PARENT"},因此树网格最初将仅显示"Edith Barney"记录。

您实际上不需要包括孩子; children属性的存在将向Tree指示该节点是可扩展的,并且将包含一个扩展图标。 项目"Edith Barney"children属性不是一个假值,因此树模型会将其视为带有孩子的节点。 树形网格中的Edith Barney行中将有一个加号(+)。 图2显示了结果。

图2.创建的TreeGrid的渲染结果
显示姓名,年龄,职务,职位和电话号码的列,并带有+号(由多个名称表示)。

现在,您已经在TreeGrid呈现了数据,现在您将要验证TreeGrid功能(如排序和扩展父节点)是否可以与QueryReadStore一起正常使用。

排序

要执行排序功能,请单击“名称”列的标题,然后按名称升序排序。 以升序或降序对其他列进行排序是相同的。

清单5.排序
// the tree model will pass a request to the query store like this: 
{ 
  query: {type: "PARENT"}, 
  start: 0, 
  count: 20, 
  sort: { 
    attribute: "name", 
    descending: false 
  } 
} 

// The query store will request server like this: 
url?type=PARENT&start=0&count=20&sort=name

图3显示了排序结果。

图3.排序结果
显示姓名,年龄,职务,职位和电话号码的列,带有+的几个名称,按字母顺序排列。

扩展父节点

对于大型树数据集,您可能只想为树的可见节点加载必要的数据。 当用户扩展节点时,它将开始加载该节点的子级。 理想情况下,您只想针对每个扩展发出一个HTTP请求即可获得最佳性能。

当用户单击示例中的节点之一时,树将要求商店加载项目,并且商店将请求资源。 但是可能会发生奇怪的事情,例如在我们的例子中,加号图标消失而子行未显示。

图4.扩展无法正常工作
显示姓名,年龄,职务,职位和电话号码的列。一行没有+号,也不会扩展。

那么,为什么它不起作用?

查看清单6中的TreeGridTreeModel的源代码,以了解扩展子行的工作方式。

清单6. TreeGrid和TreeModel如何扩展子行
//TreeGrid.setOpen
setOpen: function(open){
    ...

    treeModel.getChildren(itm, function(items){
        d._loadedChildren = true;
        d._setOpen(open);
    });

    ...
} 
//TreeModel.getChilren
getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete,
/*function*/ onError){
    // summary:
    // Calls onComplete() with array of child items of given parent item, all loaded.

    var store = this.store;
    if(!store.isItemLoaded(parentItem)){
    // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand mode,
    // so we will load it and just return the children (without loading each child item) 
        var getChildren = dojo.hitch(this, arguments.callee);
        store.loadItem({
            item: parentItem,
            onItem: function(parentItem){
                getChildren(parentItem, onComplete, onError);
            },
            onError: onError
        });
        return; 
    }
    // get children of specified item
    var childItems = [];
    for(var i=0; i<this.childrenAttrs.length; i++){
        var vals = store.getValues(parentItem, this.childrenAttrs[i]);
        childItems = childItems.concat(vals);
    }
}

树模型将要求商店首先加载父项。 父项未完全加载,因为其children属性不是列出该项所有子级的数组。

但是,这种情况将以某种方式通过store.isItemLoaded(parentItem)检查逻辑,并且您有childItems=[true] 。 由于true不是有效的项目,因此树状网格将跳过此无效的子项目,这不会导致任何事情发生。 查看清单7中store.isItemLoaded方法的代码,以了解发生了什么。

清单7. store.isItemLoaded
isItemLoaded: function(/* anything */ something){
    // Currently we don't have any state that tells if an item is loaded or not
    // if the item exists it's also loaded.
    // This might change when we start working with refs inside items ...
    return this.isItem(something);
}

此方法中的注释提供了解释。 QueryReadStore目前不支持部分加载项目。

自定义QueryReadStore以支持部分加载

在本节中,您将自定义QueryReadStoreTreeGrid以实现部分加载并修复回归错误。

您可以下载所有自定义的代码。 这是一个在Eclipse中运行的Web项目,易于配置。

创建CustomQueryStore

若要使树形网格的扩展功能与QueryReadStore一起QueryReadStore ,必须自定义QueryReadStore以使其与部分加载的项目一起使用。 首先,您必须扩展该类以添加缺少的方法。 如清单8所示,只需将CustomQueryStore子类QueryReadStoreCustomQueryStore

清单8.子类QueryReadStore
/* treegrid-demo\WebContent\script\dojo-1.4.3\demo\data\CustomQueryStore.js */ 

dojo.provide("demo.data.CustomQueryStore"); 
dojo.require("dojox.data.QueryReadStore"); 

dojo.declare("demo.data.CustomQueryStore", dojox.data.QueryReadStore, { 
    /* @Override */ 
    isItemLoaded: function(/* anything */ something) { 
        // TODO 
    } 
});

下一步是更改检查项目是否已加载的规则,如清单9所示。

清单9. isItemLoaded方法
/* treegrid-demo\WebContent\script\dojo-1.4.3\demo\grid\CustomTreeGrid.js */ 

/* @Override isItemLoaded method */ 
isItemLoaded: function(/* anything */ something) { 
    // Currently we have item["children"] as a state that tells if an item is 
    // loaded or not. 
    // if item["children"] === true, means the item is not loaded. 
    var isLoaded = false; 

    if (this.isItem(something)) { 
        var children = this.getValue(something, "children"); 
        if (children === true) { 
            // need to lazy loading children 
            isLoaded = false; 
        } else { 
            isLoaded = true; 
        } 
    } 

    return isLoaded; 
}

QueryReadStore没有loadItem方法,因此您将创建它。 此方法将向服务器请求一个列出该项目所有子项的数组。

清单10.覆盖loadItem,getValues和setValues
/* treegrid-demo\WebContent\script\dojo-1.4.3\demo\grid\CustomTreeGrid.js */ 

/* @Override loadItem method */ 
loadItem: function(/* object */ args) { 
    if (this.isItemLoaded(args.item)) { 
        return; 
    } 

    var item = args.item; 
    var scope = args.scope || dojo.global; 
    var sort = args.sort || null; 
    var onItem = args.onItem; 
    var onError = args.onError; 

    if (dojo.isArray(item)) { 
        item = item[0]; 
    } 

    // load children 
    var children = this.getValue(item, "children"); 

    // load children 
    if (children === true) { 
        var serverQuery = {}; 

        // "parent" param 
        var itemId = this.getValue(item, "id"); 
        serverQuery["parent"] = itemId; 

        // "sort" param 
        if (sort) { 
            var attribute = sort.attribute; 
            var descending = sort.descending; 
            serverQuery["sort"] = (descending ? "-" : "") + attribute; 
        } 

        // ajax request 
        var _self = this; 

        var xhrData = { 
            url: this.url, 
            handleAs: "json", 
            content: serverQuery 
        }; 

        var xhrFunc = (this.requestMethod.toLowerCase() === "post") ? 
                       dojo.xhrPost : dojo.xhrGet; 
        var deferred = xhrFunc(xhrData); 

        // onError callback 
             deferred.addErrback(function(error) { 
            if (args.onError) { 
                args.onError.call(scope, error); 
            } 
        }); 

        // onLoad callback 
        deferred.addCallback(function(data) { 
            if (!data) { 
                return; 
            } 

            if (dojo.isArray(data)) { 
                var children = data; 

                var parentItemId = itemId; 
                var childItems = []; 

                dojo.forEach(children, function(childData) { 
                    // build child item 
                    var childItem = {}; 
                    childItem.i = childData; 
                    childItem.r = this; 

                    childItems.push(childItem); 
                }, _self); 

                _self.setValue(item, "children", childItems); 
            } 

            if (args.onItem) { 
                args.onItem.call(scope, item); 
            } 
        }); 
    } 
} 

/* @Override geValues method */ 
getValues: function(item, attribute) { 
    //  summary: 
    //      See dojo.data.api.Read.getValues() 

    this._assertIsItem(item); 
    if (this.hasAttribute(item, attribute)) { 
        return item.i[attribute] || []; 
    } 

    return []; // Array 
} 

/* @Override seValue method */ 
setValue: function(/* item */ item, /* attribute-name-string */ attribute, 
                   /* almost anything */ value) { 
    // summary: See dojo.data.api.Write.set() 

    // Check for valid arguments 
    this._assertIsItem(item); 
    this._assert(dojo.isString(attribute)); 
    this._assert(typeof value !== "undefined"); 

    var success = false; 
    var _item = item.i; 
    _item[attribute] = value; 
    success = true; 

    return success; // boolean 
}

现在,该示例具有一个新的CustomQueryStore 。 替换JavaScript中的QueryReadStore ,如清单11所示。

清单11.使用CustomQueryStore发送请求
var url = "./servlet/QueryStoreServlet";
var store = new demo.data.CustomReadStore({
    url: url,
    requestMethod: "get"
});

该代码返回图5所示的结果。

图5. customQueryStore返回的结果
列显示名称,年龄,职务,职位和电话号码,雇员显示在经理下方的子行中。

自定义TreeGrid修复回归错误

现在该检查其他功能,以确保在应用CustomQueryStore之后没有回归错误。

首先展开一行,然后单击“名称”标题以执行排序。 如图6所示,发生错误。

图6.对CustomQueryStore进行排序时发生错误
第一行的屏幕截图,显示“对不起,发生错误”。

在调查之后,您发现树状网格将始终保持expando函数的状态。 在执行排序操作之前,将展开第四行,并且树形网格会记住它。 排序后,项目有所不同,但树状网格仍将尝试打开第四行的expando功能。 发生错误,因为现在第四行的项目没有任何子代。 当树状网格执行排序查询时,您需要清除expando状态。

就像您自定义QueryReadStore ,您也需要自定义TreeGrid 。 然后,使用CustomTreeGrid替换TreeGrid应该可以解决排序缺陷。

清单12.自定义TreeGrid
dojo.provide("demo.grid.CustomTreeGrid");
dojo.require("dojox.grid.TreeGrid");
dojo.declare("demo.grid.CustomTreeGrid", dojox.grid.TreeGrid, {
    /* @Override */
    sort: function() {
        this.closeExpando();

        this.inherited(arguments);
    },

    closeExpando: function(identities) {
        if (identities) {
            if (dojo.isArray(identities)) {
                // close multiple expando
                dojo.forEach(identities, function(identity) {
                    this._closeExpando(identity);
                }, this);
            } else {
                // close single expando
                var identity = identities;
                this._closeExpando(identity);
            }
        } else {
            // close all expando
            var expandoCell = this.getCell(this.expandoCell);
            for (var identity in expandoCell.openStates) {
                this._closeExpando(identity);
            }
        }
    },

    _closeExpando: function(identity) {
        var expandoCell = this.getCell(this.expandoCell);

        if (expandoCell.openStates.hasOwnProperty(identity) === true) {
            var open = expandoCell.openStates[identity] || false;
            if (open === true) {
                // clean up expando cache
                this._cleanupExpandoCache(null, identity, null);
            }
        }
    }

});

在一起, TreeGridQueryReadStore是延迟加载数据的强大组合。 无需大型的前期数据传输,就可以显示大型的,广泛的分层数据。 您可以利用QueryReadStore的部分加载支持来对每个扩展使用单个请求执行延迟加载。

性能比较

在本文中,我们比较了使用ItemFileReadStoreQueryReadStoreTreeGrid性能。 图7和表1显示,定制的QueryStore和网格仅使用ItemFileReadStore和基本TreeGrid所用时间的约1/30。

图7. ItemFileReadStore和CustomQueryStore之间的性能比较
图表显示条形图,其中itemfilereadstore的读数为1400,queryreadstore的读数小于100
表1.性能比较
二手数据存储 服务器数据加载时间(秒) 网格发布时间(秒)
ItemFileReadStore 1.45 12.65 14.1
CustomQueryStore 0.14 0.37 0.51

摘要

在本文中,您学习了如何创建自定义解决方案来提高Dojo TreeGrid加载大型数据的性能。 您探索了QueryReadStoreTreeModelTreeGrid如何QueryReadStore工作以获取和呈现数据。 定制QueryStoreTreeGrid可以QueryStore解决Dojo当前不支持大数据的问题。 文章中的结果表明,该解决方案的性能大约是ItemFileReadStoreTreeGrid用时间的1/30。


翻译自: https://www.ibm.com/developerworks/web/library/wa-dojotreegrid/index.html

dojo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值