Javascript фильтр таблиц

Javascript фильтр таблиц

Наконец, я нашел время и хочу представить вашему вниманию свой новый скрипт, который позволяет осуществить фильтрацию данных в html-таблицах.

Скрипт поддерживает следующие типы фильтров:

  • Текстовое поле
  • Выпадающий список
  • Радио-кнопки
  • Чекбоксы

Скрипт имеет малый размер и достаточно прост в подключении, а так же неплохо комбинируется со скриптом сортировщиком таблиц скачать его вместе с примерами подключения и настройки можно в моем репозитории: bitbucket.org

Фильтр таблицы демо:

Символы Текст Цифры Цифры Текст
A B C - 1 2 3
BАрбуз23Фанат
BСтрелок12Арба
CФанат31Стрелок
CСтрелок21Фантомас
BСтрелок12Арбуз
CФанат33Стрелок
AАрбуз22Арбуз
AФанат11Стрелочник
CФанат33Арбуз
BФанат23Фантик
CСтрелок11Арбуз
CФанат32Стрелка

Концептуально скрипт состоит из двух частей: объектов-фильтров filterTable.Filter и собственно из функции filterTable( ... ), которая привязывает эти объекты-фильтры к html-таблице.

Объект-фильтр имеет следующий конструктор:

        /**
        * Объект фильтр.
        * @param HTMLInputElement | HTMLSelect HTMLElementRef | [] - Ссылка,  или массив ссылок
        *                 на html-элементы, служащие фильтрами.
        * @param Function callback - ф-ция обратного вызова. Вызывается когда скрипт
        * производит валидацию содержимого ячейки. Ф-ция вызывается для каждой строки таблицы, для
        * каждой ячейки столбца, для которого назначен фильтр.
        * Функции будут переданы 3 параметра: callback(value, filters, i) где:
        *      String value - значение ячейки таблицы, проверяемой на момент вызова ф-ции
        *      HTMLElements[] filters - массив HTML-элементов назначенных фильтрами для проверяемого столбца.
        *      Number i - индекс элемента фильтра в массиве filters который является
        *                 валидатором для текущего вызова. Т.е. filters[i] внутри ф-ции
        *                 обратного вызова будет содержать элемент, с которым провзаимодействовал
        *                 пользователь, в результате чего был запущен процесс валидации.
        * @param String eventName - название события привязанного к фильтру, по которому будет
        *      запускаться валидация (onkeyup | onclick | onblur | onchange и т.п.)
        * @constructor
        */
        filterTable.Filter = function (HTMLElementRef, callback, eventName)
Первый аргумент: HTMLElement HTMLElementRef

- ссылка на html-элемент-фильтр, полученный, например при помощи document.getElementById, или массив таких ссылок.

Второй аргумент: Function callback

- функция: callback(value, filters, i) где:
String value - значение ячейки таблицы, проверяемой на момент вызова ф-ции
HTMLElements[] filters - массив HTML-элементов назначенных фильтрами для проверяемого столбца.
Number i - индекс элемента фильтра в массиве filters который является валидатором для текущего вызова. Т.е. filters[i] внутри ф-ции обратного вызова будет содержать элемент, с которым провзаимодействовал пользователь, в результате чего был запущен процесс валидации. Функция должна возвращать true, или false в зависимости от того проходит фильтрацию пришедшее значение value при установленном значении фильтра filters[i] согласно вашей задумке, или нет.

Третий аргумент: String eventName

- название события привязанного к фильтру, по которому будет запускаться валидация (onkeyup | onclick | onblur | onchange и т.п.) onchange - значение по-умолчанию

Сама же функция filterTable имеет следующую сигнатуру:

/**
 * Привязать фильтры к таблице.
 * @param HTMLTableSectionElement HTMLTBodyRef - ссылка на элемент <tbody> таблицы
 * @param Object filters - объект-конфигурация фильтров: { N : FILTER[, N : FILTER] }
 *
 *  Где:
 *      NUM - это натуральное число - номер столбца таблицы, обслуживаемого
 *          фильтром. Этот номер может принимать значения от 0 до кол-во
 *          столбцов таблицы - 1. Номера можно задавать не по порядку.
 *
 *      FILTER - это ссылка на HTML-элемент представляющий собой элемент
 *          HTML-формы и имеющий атрибут value (select в том числе), либо
 *          объект типа tableKit.Filter
 */
filterTable(HTMLTBodyRef, aFilters)

Выглядит достаточно запутанно, но давайте разберем на примере. Для начала нам необходим html - каркас таблицы. Заметьте, что фильтры - это просто элементы html-формы они кстати имеют уникальные атрибуты id по которым мы их будем выбирать для передачи в конструктор filterTable.Filter

<table>
    <thead>
    <tr>
        <th>Символы</th>
        <th>Текст</th>
        <th>Цифры</th>
        <th>Цифры</th>
        <th>Текст</th>
    </tr>
    <!-- Это строка содержит фильтры //-->
    <tr>
        <td>
            <input type="checkbox" id="charA" value="A" />A
            <input type="checkbox" id="charB" value="B" />B
            <input type="checkbox" id="charC" value="C" />C
        </td>
        <td>
            <input id="text" />
        </td>
        <td>
            <select id="digits">
                <option value="">---</option>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
            </select>
        </td>
        <td>
            <input type="radio" name="digit" id="radioZ" value="" checked="true" />-
            <input type="radio" name="digit" id="radioA" value="1" />1
            <input type="radio" name="digit" id="radioB" value="2" />2
            <input type="radio" name="digit" id="radioC" value="3" />3
        </td>
        <td>
            <input id="regexp" />
        </td>
    </tr>
    </thead>
    <!-- А вот на этот элемент нам нужно получить ссылку для передачи в filterTable //-->
    <tbody id="target">
    <tr>
        <td>B</td><td>Арбуз</td><td>2</td><td>3</td><td>Фанат</td>
    </tr>
    <tr>
        <td>B</td><td>Стрелок</td><td>1</td><td>2</td><td>Арба</td>
    </tr>
    <tr>
        <td>C</td><td>Фанат</td><td>3</td><td>1</td><td>Стрелок</td>
    </tr>
    <tr>
        <td>C</td><td>Стрелок</td><td>2</td><td>1</td><td>Фантомас</td>
    </tr>
    <tr>
        <td>B</td><td>Стрелок</td><td>1</td><td>2</td><td>Арбуз</td>
    </tr>
    <tr>
        <td>C</td><td>Фанат</td><td>3</td><td>3</td><td>Стрелок</td>
    </tr>
    <tr>
        <td>A</td><td>Арбуз</td><td>2</td><td>2</td><td>Арбуз</td>
    </tr>
    <tr>
        <td>A</td><td>Фанат</td><td>1</td><td>1</td><td>Стрелочник</td>
    </tr>
    <tr>
        <td>C</td><td>Фанат</td><td>3</td><td>3</td><td>Арбуз</td>
    </tr>
    <tr>
        <td>B</td><td>Фанат</td><td>2</td><td>3</td><td>Фантик</td>
    </tr>
    <tr>
        <td>C</td><td>Стрелок</td><td>1</td><td>1</td><td>Арбуз</td>
    </tr>
    <tr>
        <td>C</td><td>Фанат</td><td>3</td><td>2</td><td>Стрелка</td>
    </tr>
    </tbody>
</table>
Стоит отметить, что такие типы фильтров, как текстовое поле, или выпадающий список - являются для скрипта "родными" и для того чтобы их реализовать не требуется даже прибегать к вызову filterTable.Filter - достаточно просто передать ссылку на сам html-элемент.

Итак, каркас у нас есть. Фильтры в нём есть. Осталось все это связать воедино. Но давайте для начала рассмотрим простейший вариант подключения, и разберем лишь фильтры для 2-го и 3-го столбцов, потому, как там используются текстовое поле и выпадающий список значений, которые скрипт-фильтр понимает "нативно" без нужды создания filterTable.Filter Я нарочно закомментировал элементы 0, 3, 4 и пока обозначил их реализацию как "..." что бы преждевременно не отпугнуть слабонервных :)

Обратите внимание, что второй аргумент ф-ции filterTable( ..., {...} ) - это объект-конфигурация фильтров, у которого свойства имеют имена-цифры, начиная от 0 и до КОЛ-ВО_СТОЛБЦОВ_ТАБЛИЦЫ-1 Значением каждого такого свойства должен стать фильтр:

filterTable(
    /* Ссылка на элемент <tbody> таблицы */
    document.getElementById("target"),

    /* Объект-конфигурация фильтров: */
    {
        /* Фильтр для первого столбца чекбоксы:
        0: ..., */

        /* Фильтр для второго столбца текстовое поле - только точное совпадение: */
        1: document.getElementById("text"),

        /* Фильтр для третьего столбца выпадающий список: */
        2: document.getElementById("digits"),

        /* Фильтр для четвертого столбца радио кнопки:
        3: ... ,

        /* Фильтр для пятого столбца Постепенный ввод слова:
        4: ... */
    }
);

Если кому то понадобится весь функционал, как показанный в демо примере, например фильтры с радио-кнопками, или фильтры с чекбоксами, то подключение становится немного сложнее потому, как по сути это фильтры, состоящие из набора html-элементов, и при валидации нужно проверять значения всего набора такого фильтра. Тут в действие вступают callback-функции. Ниже в листинге я постарался подробно прокомментировать этот момент.

Опять же обратите внимание, что для текстового поля и выпадающего списка достаточно просто передать ссылку на html-элемент. А так же обратите внимание на подключение последнего фильтра. Вы спросите почему - ведь в последнем столбце фильтр - это текстовое поле! Да это так, но если вы внимательно поработали с демо примером, то заметили, что первый фильтр - тестовое поле срабатывает только после того как вы ввели значение полностью и нажали кнопку "Enter", или кликнули в любое другое место страницы т.е. фильтр срабатывает по дефолтному событию "onchange"! А вот фильтр-текстовое- поле последнего столбца - срабатывает моментально как только вы вводите какой-либо символ. Вот для реализации этого поведения пришлось создавать фильтр по всем правилам с filterTable.Filter а так же понадобилось задавать callback функцию и плюс к этому необходимо было передать имя события "onkeyup" по которому будет инициироваться процесс фильтрации. Вот так. Надеюсь я вас не запутал окончательно.

filterTable(
    /* Ссылка на элемент <tbody> таблицы */
    document.getElementById("target"),

    /* Объект-конфигурация фильтров: */
    {
        /* Фильтр для первого столбца чекбоксы: */
        0: new filterTable.Filter([ /* Элементы фильтра */
                document.getElementById("charA"),
                document.getElementById("charB"),
                document.getElementById("charC")
            ],
            /* Коллбэк ф-ция валидации */
            function (value, filters, i) {
                /* Если чекбокс не отмечен - его значение не учавствует
                 в валидации поэтому мы обязаны вернуть true */
                if (false === filters[i].checked) return true;
                /* Далее, при проверке, мы должны одновременно проверять
                 значения всех элементов набора при условии чекбокс
                 отмечен */
                return filters[0].checked && filters[0].value === value ||
                        filters[1].checked && filters[1].value === value ||
                        filters[2].checked && filters[2].value === value;
            }
        ),

        /* Фильтр для второго столбца текстовое поле - только точное совпадение: */
        1: document.getElementById("text"),

        /* Фильтр для третьего столбца выпадающий список: */
        2: document.getElementById("digits"),

        /* Фильтр для четвертого столбца радио кнопки: */
        3: new filterTable.Filter([/* Элеменеты фильтра */
                document.getElementById("radioZ"),
                document.getElementById("radioA"),
                document.getElementById("radioB"),
                document.getElementById("radioC")
            ],
            /* Коллбэк ф-ция валидации */
            function (value, filters, i) {
                /* В filters[0] - у нас радио кнопка "Не выбрано", если она
                 установлена фильтр не участвует в валидации и мы
                 обязаны вернуть true */
                if (true === filters[0].checked) return true;
                /* Если какая то радио-кнопка отмечена и содержимое проверяемой
                 ячейки совпало то вернем true */
                return filters[1].checked && filters[1].value === value ||
                        filters[2].checked && filters[2].value === value ||
                        filters[3].checked && filters[3].value === value;
            }
        ),

        /* Фильтр для пятого столбца Постепенный ввод слова: */
        4: new filterTable.Filter(document.getElementById("regexp"),
            /* Коллбэк ф-ция валидации */
            function (value, filters, i) {
                return value.indexOf(filters[i].value) === 0;
            },
            /* Будем вызывать валидацию по событию onkeyup фильтра */
            "onkeyup"
        )
    }
);

Собственно все, жду от вас отзывов и предложений и надеюсь вам пригодится мой труд. Ну, и напоследок привожу полный листинг скрипта.

/**
 * Привязать фильтры к таблице.
 * @param HTMLTableSectionElement HTMLTBodyRef - ссылка на элемент &lt;tbody&gt; таблицы
 * @param Object filters - объект-конфигурация фильтров: { N : FILTER[, N : FILTER] }
 *
 *  Где:
 *      NUM - это натуральное число - номер столбца таблицы, обслуживаемого
 *          фильтром. Этот номер может принимать значения от 0 до кол-во
 *          столбцов таблицы - 1. Номера можно задавать не по порядку.
 *
 *      FILTER - это ссылка на HTML-элемент представляющий собой элемент
 *          HTML-формы и имеющий атрибут value (select в том числе), либо
 *          объект типа tableKit.Filter
 */
var filterTable = function (HTMLTBodyRef, aFilters) {
    var rows = HTMLTBodyRef.getElementsByTagName("TR"),
        filters = {}, n,
        walkThrough = function (rows) {
            var tr, i, f;
            for (i = 0; i < rows.length; i += 1) {
                tr = rows.item(i);
                for(f in filters) {
                    if (filters.hasOwnProperty(f)) {
                        if (false === filters[f].validate(tr.children[f].innerText) ) {
                            tr.style.display = "none"; break;
                        } else {
                            tr.style.display = "";
                        }
                    }
                }
            }
        };
    for(n in aFilters) {
        if (aFilters.hasOwnProperty(n)) {
            if (aFilters[n] instanceof filterTable.Filter) {
                filters[n] = aFilters[n];
            } else {
                filters[n] = new filterTable.Filter(aFilters[n]);
            }
            filters[n]._setAction("onchange", function () {walkThrough(rows);});
        }
    }
}

/**
 * Объект фильтр.
 * @param HTMLInputElement | HTMLSelect HTMLElementRef | [] - Ссылка,  или массив ссылок
 *                 на html-элементы, служащие фильтрами.
 * @param Function callback - ф-ция обратного вызова. Вызывается когда скрипт
 * производит валидацию содержимого ячейки. Ф-ция вызывается для каждой строки таблицы, для
 * каждой ячейки столбца, для которого назначен фильтр.
 * Функции будут переданы 3 параметра: callback(value, filters, i) где:
 *      String value - значение ячейки таблицы, проверяемой на момент вызова ф-ции
 *      HTMLElements[] filters - массив HTML-элементов назначенных фильтрами для проверяемого столбца.
 *      Number i - индекс элемента фильтра в массиве filters который является
 *                 валидатором для текущего вызова. Т.е. filters[i] внутри ф-ции
 *                 обратного вызова будет содержать элемент, с которым провзаимодействовал
 *                 пользователь, в результате чего был запущен процесс валидации.
 * @param String eventName - название события привязанного к фильтру, по которому будет
 *      запускаться валидация (onkeyup | onclick | onblur | onchange и т.п.)
 * @constructor
 */
filterTable.Filter = function (HTMLElementRef, callback, eventName) {
    /* Если ф-цию вызвали не как конструктор фиксим этот момент: */
    if (!(this instanceof arguments.callee)) {
        return new arguments.callee(HTMLElementRef, callback, eventName);
    }

    /* Выравниваем пришедший аргумент к массиву */
    this.filters = {}.toString.call(HTMLElementRef) == "[object Array]" ? HTMLElementRef : [HTMLElementRef];

    /**
     * Шаблонный метод вызывается для каждой строки таблицы, для соответствующей
     * ячейки. Номер ячейки задается в объекте-конфигурации фильтров ф-ции
     * filterTable (См. параметр 2 ф-ции tableFilter )
     * @param String cellValue - строковое значение ячейки
     * @returns {boolean}
     */
    this.validate = function (cellValue) {
        for (var i = 0; i < this.filters.length; i += 1) {
            if ( false === this.__validate(cellValue, this.filters[i], i) ) {
                return false;
            }
        }
    }

    this.__validate = function (cellValue, filter, i) {
        /* Если фильтр был создан явно и явно указана функция валидации: */
        if (typeof callback !== "undefined") {
            return callback(cellValue, this.filters, i);
        }
        /* Если в фильтр напихали пробелов,  или другой непечатной фигни - удаляем: */
        filter.value = filter.value.replace(/^\s+$/g, "");
        /* "Фильтр содержит значение и оно совпало со значением ячейки" */
        return !filter.value || filter.value == cellValue;
    }

    this._setAction = function (anEventName, callback) {
        for (var i = 0; i < this.filters.length; i += 1) {
            this.filters[i][eventName||anEventName] = callback;
        }
    }
};

Фильтр диапазона значений "ОТ" и "ДО"

По просьбе трудящихся, показываю, как можно реализовать фильтр, который позволит выбирать диапазоны значений цифр "от" и "до". В заголовке столбца, которому требуется этот фильтр пропишем код, который являет собой два выпадающих списка. Соответственно: первый - это значение "от", а второй это значение "до":

<table>
    <thead>
    <tr>
        ...
        <td>
            ОТ
            <select id="digits-from">
                <option value="">---</option>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
            </select>
            ДО
            <select id="digits-to">
                <option value="">---</option>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
            </select>
        </td>
        ...
    </tr>
    </thead>
    <tbody id="target">
    ...
    </tbody>
</table>

А в вызове скрипта прописываем фильтр (для третьего столбца) для краткости я сократил листинг оставив только определение фильтра "от" и "до":

filterTable(
    /* Ссылка на элемент <tbody> таблицы */
    document.getElementById("target"),

    /* Объект-конфигурация фильтров: */
    {
        /* Фильтр для первого столбца чекбоксы: */
        0: ... ,

        /* Фильтр для второго столбца текстовое поле - только точное совпадение: */
        1: ... ,

        /* ФИЛЬТР диапазон значений ОТ и ДО */
        2: new filterTable.Filter([
                document.getElementById("digits-from"),
                document.getElementById("digits-to")
            ],
            function (value, filters, i) {
                var accept = true;
                value = parseInt(value,10)
                if (filters[0].value) {
                    accept = (value >= parseInt(filters[0].value,10));
                }
                if (accept && filters[1].value) {
                    accept = (value <= parseInt(filters[1].value,10));
                }
                return accept;
            }
        ),

        /* Фильтр для четвертого столбца радио кнопки: */
        3: ... ,

        /* Фильтр для пятого столбца Постепенный ввод слова: */
        4: ...
    }
);

Примеры фильтров таблиц без учета регистра

Фильтр не чувствительный к регистру для точного совпадения (можно заменять им стандартный)

new filterTable.Filter(
        document.getElementById("text"),
        function (value, filters, i) {
            var empty_filter = filters[i].value == '',
                match_value  = value.toLowerCase() == filters[i].value.toLowerCase();
            return  empty_filter || match_value;
        }
)

Фильтр не чувствительный к регистру для постепенного ввода слова

new filterTable.Filter(document.getElementById("regexp"),
    /* Коллбэк ф-ция
        валидации */
    function (value, filters, i) {
        var c_value = value.toLowerCase(),
            f_value = filters[i].value.toLowerCase();
        return c_value.indexOf(f_value) === 0;
    },
    /* Будем вызывать
        валидацию по событию
        onkeyup фильтра */
    "onkeyup"
)

В принципе не чувствительность к регистру можно встроить и в сам скрипт фильтра, но пока нет времени думаю в будущем переписать этот скрипт с учетом всех ваших пожеланий тогда и сделаем эту фичу там. Как говорится: оставайтесь с нами ;)

Добавить комментарий


Защитный код
Обновить



Кто на сайте
Сейчас 174 гостей онлайн