Slickgrid Treeview Search

В настоящее время я реализую древовидное представление с помощью 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

Извините, у меня нет кода, чтобы показать, у меня нет никуда. Любые идеи? Спасибо


person Captastic    schedule 30.05.2013    source источник


Ответы (2)


ОБНОВЛЕНИЕ:

Мне пришлось улучшить код, который я написал более года назад на этой неделе, и после большого количества тестов это то, что я в итоге получил. Этот метод намного быстрее, чем старый, и я имею в виду много! Протестировано с глубиной узла 5 узлов и 5100 строк, эта подготовка данных занимает около 1,3 секунды, но если вам не нужен поиск без учета регистра, удаление toLowerCase займет половину этого времени. примерно до 600 мс. Когда поисковые строки подготовлены, поиск происходит мгновенно.

Это из нашей функции 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 - для видимой.

Глядя на пример дерева, функция фильтра выглядит следующим образом

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

В своей первой попытке сделать это я попытался манипулировать родительским элементом, чтобы он не был скрыт. Проблема в том, что я не знаю, как его показать, и проблема также в том, что вы не знаете, в каком порядке строки будут отфильтрованы (если родительская строка будет отфильтрована последней. , свойство parent имеет значение null)

Я отказался от этой мысли и попытался работать с элементом, переданным в метод, так как это было задумано. Способ сделать это при работе с базовыми древовидными структурами родительский / дочерний - использовать рекурсию.

Мое решение

Для начала создайте функцию, которая содержит всю фильтрацию и возвращает true или false. Я использовал фиксированную строку заголовка для быстрых фильтров в качестве base, а затем добавил к ней свои собственные правила. Это действительно урезанная версия моей функции 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;
}

В настоящее время я работаю над этим, поэтому я еще не особо удосужился улучшить код. Насколько я знаю, он работает, но вам, возможно, придется настроить некоторые вещи в filter и realFilter, чтобы заставить его работать так, как вы ожидаете. Я написал это сегодня, поэтому он не тестировался больше, чем на этапе разработки.

Примечание. Если вы хотите использовать другой ввод для поиска, вы можете просто использовать $ .keyup () в этом поле, а затем передать данные в фильтр заголовка. Таким образом, вы получаете все возможности для использования фильтров на уровне столбцов, даже если вы не хотите их использовать в данном конкретном случае.

person Binke    schedule 22.01.2015

лично я использую пример группировки, и я также помог сделать его многоколоночную (вложенную) группировку, и с этим он делает именно то, что вы ищете ... Итак, вместо того, чтобы использовать тот, который вы сказали, который, я думаю, в основном сделан только для отступов вы должны использовать это Интерактивное группирование и агрегирование.

Пример не включает поиск, но его легко добавить, как и в моем проекте. И да, родительская группа никогда не уходит. В примере для множественной группировки выберите 50 тыс. Строк, а затем нажмите «Группировать по продолжительности, затем по усилиям, затем по процентам», вы увидите красивую группировку из 3 столбцов :) Скопируйте ее, добавьте панель поиска, и она должна работать

person ghiscoding    schedule 30.05.2013
comment
Спасибо за ответ. Да, я согласен, что группировка по нескольким столбцам - хорошее решение. Фактически, я использовал его в другом месте в рамках проекта. К сожалению, это требование не подошло ему, так как у меня есть различные уровни группировки и до 8 уровней подразделов, поэтому я не думаю, что группировка также будет работать. Возможно, slickgrid здесь не подходящее решение, если я тоже хочу иметь возможность искать. Я все равно буду пытаться. Спасибо за помощь - person Captastic; 31.05.2013
comment
Хм, ладно, но я все еще не знаю, почему ты сказал, что это может быть неправильное решение? Я пытался искать в мультигруппе, и родитель всегда остается там, разве это не то, что вы хотите на самом деле? Если я не полностью понял твои потребности - person ghiscoding; 31.05.2013