При обновлении страницы все предыдущие действия пользователя по сортировке повторяются программно. То есть, если пользователь сортировал 20 раз подряд разные столбцы, программа будет сортировать те же 20 столбцов в том же порядке. Кто-нибудь знает, как это можно оптимизировать?

Сортировщик HTML-таблиц, версия 1.3

Статья о сортировке HTML-таблиц была написана год назад. Так получилось, что описываемая технология заинтересовала некоторых начинающих программистов, и они стали задавать вопросы и высказывать пожелания. В результате к своему юбилею скрипт изменился до неузнаваемости: первоначальное торжественное заявление о 150-ти строках утратило силу, так как строк стало почти вдвое больше (файл tabsort1.3.js). Но главное, совершенно изменилась его сущность. Настолько, что описывать его надо заново (потому что уже и сам автор мало что стал в нём понимать).

Установка и использование

Чтобы сортировать строки любой HTML-таблицы без перезагрузки страницы с помощью скрипта tabsort1.3.js, нужно выполнить минимум два действия:

  1. Присоединить к странице с помощью <script src=... два скрипта: ir2.js и tabsort1.3.js;
  2. Добавить сортируемой таблице класснэйм sortable (<table class='sortable' ...).

Для большей наглядности и удобства пользования нужно также присоединить к таблице файл стилей tabsort.css: <link rel='stylesheet' href='static/tabsort.css' ... – будут подсвечиваться строки таблицы и будет меняться форма курсора при наведении мыши на заголовок таблицы.

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

Тип сортировки нужно назначать в HTML-коде самой таблицы – с помощью атрибута axis в ячейке заголовка:

  1. <th axis='str'> (или без явного указания типа <th>) – сортировать как строки: 1, 10, 100, 2, 3...
  2. <th axis='num'> – сортировать как числа: 1, 2, 3, 10, 100...
  3. <th axis='datru'> – рассматривать значения как русские даты: 01.10.2010, 02.10.2010, 01.11.2010...
  4. <th axis='daten'> – сортировать как английские даты: 01.10.2010, 01.11.2010, 02.10.2010...

В заголовке таблицы может быть несколько строк. Сортировщик может правильно обрабатывать такую структуру, если поместить строки заголовка в элемент thead (между тэгами <thead> и </thead>).

Настройки

Пожелания по работе скрипта были разные, и не всегда очевидно полезные, поэтому часть возможностей управляется теперь с помощью секции «Настройки» в начале кода в файле скрипта tabsort1.3.js. Настройки эти следующие:

  1. use_title. Для наглядности (подсказки пользователям, что элемент активен) заголовку сортируемой таблицы присваивается правило CSS cursor: pointer;. Это значит, при наведении мыши на заголовок курсор становится таким же, как при наведении на ссылку. При установке значения use_title равным true добавляется ещё один намёк для пользователей – всплывающая подсказка (атрибут title) «Сортировать»; после щелчка по заголовку title ячейки меняется на «Отсортировано по возрастанию» (или «по убыванию»). Неочевидно, что это надо всем и всегда (где-то подсказка может и помешать), поэтому можно убрать всплывающие подсказки, установив use_title равным false.
  2. use_cookie. При установке этого значения равным true все действия пользователя по сортировке таблиц на странице записываются в cookie. При обновлении страницы сохранённые в cookie значения считываются и все предыдущие действия пользователя повторяются программно. То есть если пользователь сортировал 20 раз подряд разные столбцы, программа при последующих открытиях страницы будет сортировать те же 20 столбцов в том же порядке. Мы не придумали другого способа сохранять состояние сортировки. Это резерв для последующей работы. Если установить use_cookie = false состояние сохраняться и восстанавливаться не будет.
  3. show_time придумали не пользователи. Это чисто авторская опция. Если установить её значение в true, после каждого сортировочного щелчка будет выскакивать сообщение о времени вычислений и времени отрисовки нового состояния таблицы.
  4. use_appendChild – вообще сюр: переключение способов отрисовки отсортированных строк таблицы. По умолчанию use_appendChild = false, это значит, новое состояние таблицы записывается в строковую переменную, значение которой потом передается свойству таблицы innerHTML. Если use_appendChild = true, строки после сортировки в новом порядке присоединяются к таблице с помощью метода DOM appendChild.

tabsort1.3.js – что внутри

Вначале всё было хорошо: пользователь для сортировки щёлкает по ячейке таблицы – следовательно нужна функция, которая срабатывает по событию onclickclickTab() (1). Для сортировки такого сложного объекта, как таблица требуется ещё одна функция (которая, собственно, и производит сортировку) – cmpRow() (2). Ну, и нужно проверить, есть ли на странице таблицы, пригодные для сортировки (с класснэйм sortable), что делает функция prepTabs() (3). Так было в первой версии Сортировщика – три функции и 150 строк (с комментариями).

Первое осложнение вызвала система сохранения сортировки: функцию сортировки понадобилось вызывать не только по событию onclick, но и по событию «загрузка страницы» (если установлена опция use_cookie). Так появилась четвёртая функция – sortTab() (4), выделенная в отдельное прозводство из clickTab(). Ну, и, соответственно, потянулись глобальные переменные настроек: use_cookie, use_title... Дальше – больше. Закончилось так:

1. Страница открывается, функция prepTabs():

  1. ищет подходящие таблицы;
  2. если нет элемента thead, создаёт его и запихивает в него первую строку таблицы;
  3. навешивает на thead таблицы по событию click функцию clickTab();
  4. ищет cookie с именем, состоящим из url и номера таблицы (в NodeList, полученном с помощью getElementsByTagName);
  5. извлекает из куки номера сортированных раньше столбцов с типом сортировки и сортирует функцией sortTab() таблицу в порядке столбцов, полученных из куки;
  6. запускает отрисовку полученного состояния таблицы (функция draw()).
  7. Если в куки нет записей об истории сортировки текущей таблицы, просто раскрашивает таблицу в полосатую «зебру».

2. Пользователь щёлкает по ячейке заголовка, функция clickTab():

  1. проверяет тип сортировки, заданный пользователем – были ли нажаты клавиши Shift (тип: число), Ctrl (тип: русская дата), Ctrl+Alt (тип: английская дата). Если были, указания автора таблицы в атрибуте axis игнорируются (и вместо сортировки может получиться бардак).
  2. Если при щелчке были нажаты клавиши Ctrl+Alt+Shift, уничтожает историю сортировки (удаляет куки) и перегружает страницу.
  3. Передаёт объект «Ячейка» (по которой щёлкнули) и тип сортировки в следующую функцию – sortTab().

3. Функция sortTab():

  1. Если не получен тип сортировки «свыше», проверяет axis переданной ячейки; если там ничего нет, назначает тип сортировки «Строка».
  2. Проверяет, существует ли непустой массив сортировки global[k].rarr, если нет, создаёт массив (функцией makeSortArr()). Значение k извлекается из класснэйм таблицы функцией extractTid().
  3. Проверяет, не щёлкнул ли пользователь по той же ячейке: если да, то использует для сортировки метод reverse); иначе создаёт новый массив и сортирует его с помощью метода sort() и функции cmpRow().
  4. Проверяет значение параметра dont_draw и рисует отсортированный массив в таблицу (функция draw()). НЕ рисует таблицу (параметр dont_draw = true) при вызове из функции prepTabs() (она сама вызывает функцию draw()).

Идеология ускорения

Есть ещё несколько мелких функций, назначение которых более-менее очевидно. Интереснее другое: как повысить скорость работы Сортировщика? Поиск в сети приводит к статьям, описывающим различные алгоритмы самой функции сортировки. Встроенная в Javascript функция – sort(). Но люди пишут о том, что придумали более быстрые функции. Дальше другие люди активно обсуждают выигрыш в миллисекундах, даваемый тем или иным хитрым методом (Хоара, «истинный» n log n и проч.), и на этом всё заканчивается.

На практике же основную проблему сортировки представляют не расчёты, а прорисовка HTML-элементов (работа с DOM): голые расчёты (сортировка массива) в общем случае занимают в десятки раз меньше времени, чем создание массива из элементов HTML и отображение отсортированного массива обратно на страницу. В разных браузерах, конечно, по-разному. Больше всех традиционно тормозит с DOM Firefox (ФФ), с расчётами – Интернет Эксплорер (ИЕ). В ИЕ8 скорость расчётов существенно увеличилась, но природу не обманешь (если программа из ГовноСофта, это всё равно где-нибудь вылезет): теперь в ИЕ скорость работы с DOM стала значительно хуже.

На чём и где можно сэкономить время «реальной» сортировки таблицы?

  1. Главный момент был найден последним: это способ отображения отсортированного массива на странице. Можно добавлять к новой таблице отсортированные строки по одной, используя метод appendChid; а можно выгрузить массив в строковую переменную и присвоить свойству таблицы innerHTML значение этой переменной. В ИЕ при использовании второго метода время экономится в 5-10 раз. А подлость в том, что именно в ИЕ у таблицы свойство innerHTML реализовано криво! ГуглоЯн на вопрос «what's wrong with IE innerHTML?» выдаёт нечто невразумительное о том, следует ли пользоваться свойством innerHTML вообще. Одна англостраница сообщила, что решить проблему поможет некий священный текст на msdn. Ну, и священный текст чётко и ясно сообщил: у элементов table, thead, tbody, ... (куча других) свойство innerHTML доступно только для чтения!
  2. Другое узкое место – способ создания массива. Можно делать массив из ссылок на элементы таблицы row, а можно «копировать» строки в массив. Понятно, что ссылки будут работать быстрее. Но из-за кривого innerHTML и тут возникла проблема – таблицу пришлось полностью перезаписывать, а значит, «ссылочный» массив, потеряв связь с элементами таблицы, в момент перезаписи будет обнуляться. Вот тут и пришлось делать выбор: создавать ссылочный массив заново или использовать постоянный? Это оказалось несложно: лишнее создание «ссылочного» массива в ИЕ получилось на порядок быстрее создания «постоянного» массива из копий элементов.
  3. Какой должна быть пользовательская функция сортировки, тоже не всё равно. Об этом уже говорилось в первой статье о сортировке таблицы. Функция должна быть предельно проста, единственное её назначение (кроме собственно сортировки) – выбрать нужный «столбец» массива; всё остальное (в частности, приведение данных к нужному типу) можно и нужно делать в процессе создания массива из строк таблицы.

Результат работы

Тестировалась таблица в 1000 строк: limit1001.htm.

На медленном компьютере в Опере 9.50 браузер не успевает... ну, короче, обманывает себя и людей: Опера, как всегда, поспешно сообщает, что всё уже загрузилось, и сама начинает в это верить – и запускает Javascript, а у ячейки ЕЩЁ НЕТ свойства cellIndex (во дают)! Баг обходится таким же тупым способом (против лома...): вызывается alert, и пока пользователь думает и нажимает кнопку, подгружаются недостающие куски DOM'а.

На быстром компьютере ИЕ8 с использованием appendChild (вместо innerHTML) почему-то сортирует (вернее, рисует) большую таблицу в течение 220000 мс (то есть несколько минут). Хотя ИЕ6 на медленном компьютере работает вполне предсказуемо. Продублировал проверку на таблице в 200 строк – 6 секунд. Видимо, последние могзи говно микрософтеров ушли на повышение скорости расчётов Javascript, а научить свой говнобраузер рисовать DOM уже ума не хватило.

Safari и Google Chrom показывают просто чудовищную скорость сортировки и отображения большой таблицы, причём, независимо от метода отображения (appendChild или innerHTML).

Ну, современные Опера и ФФ, как всегда, где-то посередине (Опера прикидывается в разы быстрее, но врёт, конечно, зараза).

Лучше всего, наверное, проверять скорость на таблице в 200 строк: limit200.htm. Совсем маленький файл демонстрирует разные заголовки таблиц – thead2.htm. Скрипт tabsort1.3.js можно скачать и использовать, с ним вместе нужен также файл ir2.js, в котором хранятся некоторые нужные для работы функции (типа addLoadEvent, addClass, deselect...).

D.M., admin

Комментарии

AndreyT 09.10.2012 02:11:07

А не подскажите каким условием можно задать автоматическую сортировку таблицы по ID от большего к меньшем.

Алекс 04.03.2012 20:48:22

Здравствуйте. Можно ли заставить скрипт сортировать таблицу по заданному столбцу ПРИ ЗАГРУЗКЕ СТРАНИЦЫ? Не нашел что-то где об этом написано (может плохо искал).

лесник 05.03.2012 21:52:30

Здравствуйте, Алекс! Теоретически всё возможно. Система развёрнутых визуальных настроек описана в статье tabsort004.aspx (рабочий пример см. на стр.: http://ir2.ru/static/sort0.03/table404.htm).

лесник 01.10.2010 19:19:05

Проблема «утечки мозгов» при работе Сортировщика в ИЕ локализована: фатальный тормоз возникает при использовании «зебры» (раскрашивании строк таблицы в чередующиеся цвета). Причём, раньше всё более-менее работало, а соломинка, переломившая спину Осла, вот откуда: MrTopa справедливо указал на неточность раскраски и предложил вполне разумное решение – удалять предыдущий цвет перед добавлением нового (после сортировки). Так вот, это лишнее действие (удаление предыдущего класснейма) и оказалось для ИЕ фатальным.

Решение – убрать «зебру». Точнее, оставить это на усмотрение веб-мастера: в секцию «Настройки» добавляется параметр use_zebra и предупреждение о том, что назначение true этому параметру вместе с предыдущим (use_appendChild) может подвесить ИЕ.

Да, и такое решение всё меняет: теперь (без зебры) ИЕ6 сортирует таблицу в 4 раза быстрее с включённым use_appendChild (=true).

upd 15:19 02.10.2010

Ещё иллюзия. В ИЕ вроде бы можно управлять CSS с помощью каких-то там «behavior» и «expression». Поскольку я довольно смутно представляю, что это и как, обратился с вопросом к ПС, и, ткнув в первую попавшую ссылку (http://www.lysenka.net/web/6/, Andrei Lysenka), нашёл и протестировал решение. Всё равно в большой таблице (limit1001.htm) получилась жопа, но вы можете попробовать сами (доверять нельзя никому), добавив в начало файла tabsort1.3.js следующий код:

лесник 04.10.2010 23:47:12

Никто ничего не пишет, значит, настало время заняться стрелками (когда коту делать нечего). С самого начала было ясно, что стрелки делать придётся, просто не было повода. Хотел делать как обычно – картинкой (треугольничком), но при анализе скрипта конкурента (http://debugger.ru/blog/bystraja_sortirovka_tablic) обнаружил стрелки-треугольнички и заинтересовался, как они сделаны. Начал искать символы, что-то вроде &#9632;, а их нет. Идея оказалась гениально простой – треугольники сделаны из border!

Действительно, можно было бы и самому догадаться. Ведь что такое border? Это четыре доски по краям прямоугольника. Как доски могут крепиться друг к другу? Если концы прямоугольные, неизбежно возникнет вопрос, какая [доска] главнее – правая или нижняя (что в W3C недопустимо). Поэтому в местах скрепления доски режут под углом 45 градусов. Такая конструкция содержит в себе форму треугольника, которую вполне можно извлечь, правильно обработав напильником.

Пришлось позаимствовать идею практически полностью. В благодарность помещаю на сайт таблицу, работающую на SortTable.1.5.1 (для сравнения с собственными примерами): sorttable.htm. Ну, а вот и мои примеры со скриптом tabsort1.4.js: thead3.htm, limit201.htm, limit1002.htm (1000 строк). В версии 1.4 кроме добавленных стрелок изменилось следующее: используется только один файл tabsort1.4.js (библиотека ir2.js не нужна). Ну, и повысилась необходимость файла стилей tabsort.css (раздел «arrows»).

лесник 11.10.2010 20:22:02

Более правильная версия сейчас постоянно находится по адресу tabsort2.1.js. Трудно (и всё меньше понятно, зачем) синхронизировать с tabsort1.4, поэтому tabsort1.4.js больше, видимо, не будет поддерживаться. Скрипт сортировки дорабатывается теперь только вместе с фильтрацией данных.

Тестовые таблицы: limit202.htm, thead4.htm, limit1003.htm. Сравнение с jquery.tablesorter.js в двух файлах на базе списка торрентов: test-tracker/tracker.php.htm (jquery.tablesorter.js) и test-tracker/tracker2.php.htm (tabsort2.1.js - наш, о котором речь в статье).

test-tracker/tracker3.php.htm - испытание ещё одного сортировщика, самого маленького в мире и очень классного во многих отношениях (http://www.leigeber.com/2009/11/advanced-javascript-table-sorter/)

Ну, и проверка работы на таблице в 3000 строк: test-tracker/limit3000.htm