2

我目前正在使用 slickgrid 实现树视图。

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

我想要做的是获得一个搜索过滤器,类似于示例中的过滤器,但它适用于分支以及父母。例如,如果一棵树看起来像这样:

-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

抱歉,我没有任何代码要显示,我没有任何地方。有任何想法吗?谢谢

4

2 回答 2

2

更新:

本周我不得不改进我一年多前编写的代码,并且经过大量测试,这就是我最终得到的结果。这种方法比旧方法快很多,我的意思是很多!使用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用于父索引。我不太确定这是否真的提高了性能,但它消除了setData函数中嵌套循环的需要。

在这里真正发挥作用的是uniq_fast,它接受一个数组并删除其中的所有重复项。该方法是此答案中的众多功能之一remove-duplicates-from-javascript-array

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;
}

所以,这个问题是很久以前提出的,但如果有人正在寻找这个问题的答案,请使用上面的代码。它很快,但是对代码的任何改进都会非常受欢迎!

编辑结束

旧答案(这很慢):

首先,您必须创建一个与 dataView 一起使用的过滤器函数。只要您键入内容,dataView 就会调用您的函数。将为 dataView 中的每一行调用该函数,并将该行作为item参数传递。返回 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;
}

我目前正在研究这个,所以我还没有真正费心改进代码。据我所知,它正在工作,但您可能需要调整filterrealFilter中的一些内容才能使其按预期工作。我今天写了这个,所以它没有在开发阶段进行更多的测试。

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

于 2015-01-22T16:55:37.897 回答
1

我个人使用了分组示例,并且我还帮助使它成为了多列(嵌套)分组,并且使用该分组,它完全符合您的要求……因此,不要使用您所说的那个,我认为它主要是制作的仅用于缩进,您应该使用这一交互式分组和聚合。

该示例不包括搜索,但很容易添加它,就像在我的项目中一样。是的,父组永远不会消失。在多分组的示例中,选择 50k 行,然后单击“按持续时间分组,然后按努力驱动然后百分比”,您将看到一个不错的 3 列分组:) 复制它,添加搜索栏,它应该可以工作

于 2013-05-30T16:59:50.893 回答