光滑网格树视图搜索

Slickgrid Treeview Search

本文关键字:视图 图搜索 网格      更新时间:2023-09-26

我目前正在使用slikgrid实现一个树视图。

我的代码基本上是基于这个例子。

我想做的是得到一个搜索过滤器,类似于一个在例子中,但它的工作在分支以及父母。例如,如果树看起来像这样:

-Parent 1
  -branch 1
   -sub_branch 1
  -branch 2
-Parent 2
  -branch 1
  -branch 2

然后搜索数字'1'它应该显示如下:

-Parent 1
  -branch 1
   -sub_branch 1
  -branch 2
-Parent 2
  -branch 1

而不是:

-Parent 1

对不起,我没有任何代码要展示,我没有得到任何地方。什么好主意吗?由于

更新:

我不得不改进我在一年前的这个星期写的代码,经过大量的测试,这就是我最终得到的。这种方法比旧方法快得多,而且我的意思是快得多!在节点深度为5个节点和5100行的情况下,该数据准备大约需要1.3s,但是如果您不需要不区分大小写的搜索,删除toLowerCase将使时间减半,大约为600ms。当准备好搜索字符串时,搜索是即时的。

这是来自我们的setData函数,我们准备数据

var self = this,
    searchProperty = "name";
//if it's a tree grid, we need to manipulate the data for us to search it
if (self.options.treeGrid) {
    //createing index prop for faster get
    createParentIndex(items);
    for (var i = 0; i < items.length; i++) {
        items[i]._searchArr = [items[i][searchProperty]];
        var item = items[i];
        if (item.parent != null) {
            var parent = items[item.parentIdx];
            while (parent) {
                parent._searchArr.push.apply(
                    parent._searchArr, uniq_fast(item._searchArr)
                    );
                item = parent;
                parent = items[item.parentIdx];
            }
        }
    }
    //constructing strings to search
    //for case insensitive (.toLowerCase()) this loop is twice as slow (1152ms instead of 560ms for 5100rows) .toLowerCase();
    for (var i = 0; i < items.length; i++) {
        items[i]._search = items[i]._searchArr.join("/").toLowerCase(); 
        items[i]._searchArr = null;
    }
    //now all we need to do in our filter is to check indexOf _search property
}
在上面的代码中,我使用了一些函数。第一个创建了两个属性,一个用于它在数组中的位置,第二个parentIdx用于parents索引。我不确定这是否真的提高了性能,但是它消除了在setData函数中嵌套循环的需要。

这里真正起作用的是uniq_fast,它接受一个数组并删除其中的所有重复项。该方法是这个答案中的众多函数之一。

function createParentIndex(items) {
    for (var i = 0; i < items.length; i++) {
        items[i].idx = i; //own index
        if (items[i].parent != null) {
            for (var j = 0; j < items.length; j++) {
                if (items[i].parent === items[j].id) {
                    items[i].parentIdx = j; //parents index
                    break;
                }
            }
        }
    }
}
function uniq_fast(a) {
    var seen = {};
    var out = [];
    var len = a.length;
    var j = 0;
    for (var i = 0; i < len; i++) {
        var item = a[i];
        if (seen[item] !== 1) {
            seen[item] = 1;
            out[j++] = item;
        }
    }
    return out;
}

现在,所有这些数据的准备,我们的过滤器函数实际上变得非常小,易于处理。对每个项目调用过滤器函数,因为现在每个项目上都有_search属性,所以我们只需检查它。如果没有应用过滤器,我们需要确保不显示封闭节点

function treeFilter(item, args) {
    var columnFilters = args.columnFilters;
    var propCount = 0;
    for (var columnId in columnFilters) {
        if (columnId !== undefined && columnFilters[columnId] !== "") {
            propCount++;
            if (item._search === undefined || item._search.indexOf(columnFilters[columnId]) === -1) {
                return false;
            } else {
                item._collapsed = false;
            }
        }
    }
    if (propCount === 0) {
        if (item.parent != null) {
            var dataView = args.grid.getData();
            var parent = dataView.getItemById(item.parent);
            while (parent) {
                if (parent._collapsed) {
                    return false;
                }
                parent = dataView.getItemById(parent.parent);
            }
        }
    }     
    return true;
}
所以,这个问题很久以前就被问到了,但是如果有人正在寻找这个问题的答案,请使用上面的代码。它很快,但是对代码的任何改进都是值得赞赏的!

END OF EDIT

旧答案(这是非常慢的):

首先,您必须创建一个与dataView一起使用的筛选函数。只要您键入一些内容,dataView就会调用您的函数。将对dataView中的每一行调用该函数,并将该行作为参数传递。返回false表示该行应该隐藏,返回true表示该行可见。

查看Tree示例,过滤器函数如下所示

function myFilter(item, args) {
  if (item["percentComplete"] < percentCompleteThreshold) {
    return false;
  }
  if (searchString != "" && item["title"].indexOf(searchString) == -1) {
    return false;
  }
  if (item.parent != null) {
    var parent = data[item.parent];
    while (parent) {
      if (parent._collapsed || (parent["percentComplete"] < percentCompleteThreshold) || (searchString != "" && parent["title"].indexOf(searchString) == -1)) {
        return false;
      }
      parent = data[parent.parent];
    }
  }
  return true;
}

在我第一次尝试这样做时,我试图操纵父元素,使它不应该被隐藏。问题是,我不知道如何取消隐藏它,问题也是,你不知道在哪个顺序行将被过滤(如果父行是最后被过滤,属性是null)

我放弃了这个想法,并尝试处理传递到方法中的项,因为这就是它的意图。当使用基本的父/子树结构时,可以使用递归

我的解决方案

首先,创建一个保存所有过滤并返回truefalse的函数。我使用固定的标题行作为快速过滤器的基础,然后添加我自己的规则。这是我的realFilter函数的简化版本,所以你可能需要稍微调整一下。

function realFilter(item, args) {
    var columnFilters = args.columnFilters;
    var grid = args.grid;
    var returnValue = false;
    for (var columnId in columnFilters) {
        if (columnId !== undefined && columnFilters[columnId] !== "") {
            returnValue = true;
            var c = grid.getColumns()[grid.getColumnIndex(columnId)];
            if (item[c.field].toString().toLowerCase().indexOf(
                columnFilters[columnId].toString().toLowerCase()) == -1) { //if true, don't show this post
                returnValue = false;
            }
        }
    }
    return returnValue;
}
其次,是使用递归函数的时候了。如果你不熟悉它们的工作原理,这是一个棘手的部分。
//returns true if a child was found that passed the realFilter
function checkParentForChildren(parent, allItems, args) { 
    var foundChild = false;
    for (var i = 0; i < allItems.length; i++) {
        if (allItems[i].parent == parent.id) {
            if (realFilter(allItems[i], args) == false && foundChild == false) //if the child do not pass realFilter && no child have been found yet for this row 
                foundChild = checkParentForChildren(allItems[i], allItems, args);
            else
                return true;
        }
    }
    return foundChild;
}

最后,我们实现了原来的滤波函数。这是由slickgrid调用的函数,应该注册到dataView

//registration of the filter
dataView.setFilter(filter);
//the base filter function
function filter(item, args) {
    var allRows = args.grid.getData().getItems();
    var columnFilters = args.columnFilters;
    var grid = args.grid;
    var checkForChildren = false;
    for (var i = 0; i < allRows.length; i++) {
        if (allRows[i].parent == item.id) {
            checkForChildren = true;
            break;
        }
    }
    for (var columnId in columnFilters) {
        if (columnId !== undefined && columnFilters[columnId] !== "") {
            var c = grid.getColumns()[grid.getColumnIndex(columnId)];
            var searchString = columnFilters[columnId].toLowerCase().trim();
            if (c != undefined) {
                if (item[c.field] == null || item[c.field] == undefined) {
                    return false;
                }
                else { 
                    var returnValue = true;
                    if (checkForChildren) {
                        returnValue = checkParentForChildren(item, allRows, args);
                        if(!returnValue)
                            returnValue = realFilter(item, args);
                    }
                    else
                        returnValue = realFilter(item, args);
                    if (item.parent != null && returnValue == true) {
                        var dataViewData = args.grid.getData().getItems();
                        var parent = dataViewData[item.parent];
                        while (parent) {
                            if (parent._collapsed) {
                                parent._collapsed = false;
                            }
                            parent = dataViewData[parent.parent];
                        }
                    }
                    return returnValue;
                }
            }
        }
    }
    if (item.parent != null) {
        var dataViewData = args.grid.getData().getItems();
        var parent = dataViewData[item.parent];
        while (parent) {
            if (parent._collapsed) {
                return false;
            }
            parent = dataViewData[parent.parent];
        }
    }
    return true;
}

我目前正在处理这个问题,所以我还没有真正费心去改进代码。据我所知,它正在工作,但你可能不得不调整一些东西在过滤器realFilter让它像你期望的那样工作。我今天写了这篇文章,所以它的测试时间不会超过开发阶段。

注意:如果您想使用另一个输入为您的搜索,您可以只使用$.keyup()字段,然后将数据传递给标题过滤器。通过这种方式,您可以获得使用列级过滤器的所有功能,即使在这种特殊情况下您不想使用它们。

我个人使用分组的例子,我也帮助使它多列(嵌套)分组,它做的正是你正在寻找的…所以不要用你说的那个,我认为它主要是用来缩进的,你应该用这个交互式分组和聚合。

示例不包括搜索,但很容易添加它,就像在我的项目。是的,父母群体永远不会消失。在多列分组的例子中,选择50k行,然后点击"按持续时间分组,然后按努力驱动,然后按百分比分组,你会看到一个漂亮的3列分组:)复制它,添加搜索栏,应该可以了

编辑

我增加了对过滤树数据的支持,最近还增加了对聚合器功能的树总数的支持。您可以看到这些slikgrid - universal示例(我创建的)

  • 示例5
  • 例子6

还请注意,在我的其他项目

中可以使用完全相同的功能。
  • Angular-Slickgrid
  • Aurelia-Slickgrid
  • Slickgrid-React