Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   Сортировка и отбор данных в браузере (https://javascript.ru/forum/project/12620-sortirovka-i-otbor-dannykh-v-brauzere.html)

stopkran 26.10.2010 10:23

Сортировка и отбор данных в браузере
 
Примерно год назад начал работу над скриптом JS, который назвал Simple Table Sorter. Он должен был быть маленьким (200-300 строк), понятным и быстрым. Как я узнал позже, по лаконичности ему оказалось очень далеко до TinyTable Лейгебера (leigeber.com/2009/03/table-sorter/), но скорость он показывал вполне приемлемую, не хуже jquery.tablesorter.js (jSorter).

Мне хотелось добиться от скрипта большего, чем умеет jSorter. Например, изначально заложить возможность сохранения и восстановления состояния отсортированной таблицы или добавить в заголовок поля ввода для фильтрации данных. Пользователи, тестировавшие скрипт, также подстрекали к различным усовершенствованиям. Всё это привело к тому, что скрипт перерос в размере jSorter, и к версии 2.1 пришлось переименовать его в Big Table Sorter.

Большой размер получился, главным образом, оттого что я пытался учесть разные варианты решений, и многие решения оставил в двух вариантах – потому что так и не смог решить, что лучше:
  1. Использовать при отрисовке таблицы innerHTML или appendChild?
  2. Использовать картинки для стрелок, или рисовать стрелки на CSS (идею позаимствовал из SortTable.1.5.1.zip Ильи Лебедева – debugger.ru/blog/bystraja_sortirovka_tablic)?
  3. Делать ли таблицу полосатой («зебра»)?
  4. Разбивать ли на страницы (paginate)?
  5. Добавлять поля отбора всем заголовочным ячейкам, или только тем, у которых явно назначен тип сортировки?
  6. Давать ли возможность пользователям самим определять тип сортировки (удерживая при щелчке Shift или Control)?
  7. Заставлять ли разработчика явно обозначать элементы thead и tbody?

И ещё множество более мелких вопросов. Некоторые волевые решения, конечно, всё равно принимать пришлось. Например, оставить такую удобную вещь, как «подсветка» строк при наведении мыши чисто на CSS (:hover), то есть лишить этой удобной вещи пользователей Интернет Эксплорера (IE). Потому что все варианты «псевдо-hover'ов» страшно IE тормозили.

Позже обнаружилось, что фатальный тормоз в IE возникал, в частности от использования table-layout: fixed (который вообще-то в нормальных браузерах используется для ускорения загрузки больших таблиц!). Но к этому времени желание патчить скрипт под недобраузер полностью было убито кучей мелких пакостей и недоработок MS – типа невозможности перезаписать innerHTML у таблицы или наличия значения по умолчанию display: block у элемента tr.

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

Протестировать работу скрипта можно на следующих примерах:
  1. ir2.ru/test-tracker/limit202.htm – таблица в 200 строк, для старых компьютеров.
  2. ir2.ru/test-tracker/limit1003.htm – таблица в 1000 строк.
  3. ir2.ru/test-tracker/tracker2.php.htm – таблица в 500 строк, но из-за объёма текстов (500 KB) гораздо более неудобная для обработки, чем предыдущая.
  4. Для сравнения подключил к последнему большому файлу jSorter (ir2.ru/test-tracker/tracker.php.htm) и TinyTableV3 (ir2.ru/test-tracker/tracker3.php.htm).
  5. ir2.ru/test-tracker/limit3000.htm – таблица в 3000 строк (722 KB).
  6. ir2.ru/latrus.htm – Латинско-русский словарь (~14000 строк, 1МB), работает с более новой версией скрипта ir2.ru/tabsort3a.js.

Тесты на компьютере Pentium 4 (2.53GHz, Windows XP) показали, что при использовании предложенного алгоритма разбивки на страницы (paginate) работа с таблицей в 3000 строк почти не отличается от работы с таблицей в 200 строк – в «обычном браузере» (Гекко, Хром, Сафари), конечно. В особом браузере (IE) время отрисовки таблицы не зависит от реального отображения элементов и растёт пропорционально количеству строк. Ну, Опера 9.50 тоже, кажется, испытывает трудности с «рисованием».

Во всех примерах настройки можно менять, отмечая галочками элементы checkbox в начале страницы (изменения настроек чаще всего требуют перезагрузки страницы). Например, можно убрать галочку с пункта use_appendChild (активного по умолчанию), и тогда таблица будет отрисовываться с помощью замены innerHTML (что, предположительно, должно было уменьшить время отрисовки). Нет настройки, позволяющей использовать documentFragment, потому что использование documentFragment не дало никакой выгоды ни в одном браузере.

Получившийся скрипт - скорее, инструмент для дальнейших исследований (исключить лишние возможности, оптимизировать код...), но уже сейчас видно, что для эффективной работы с таблицами на стороне веб-клиента нужен комплексный подход - обязательное использование сразу трёх элементов: сортировка, отбор (фильтрация) и разбивка на страницы. При таком подходе вполне можно работать с большими (3000 строк) таблицами прямо в браузере.

Весь набор файлов, необходимых для работы Big Table Sorter'а, находится в архиве ir2.ru/BigTableSorter.zip.

P.S. Спасибо roni за подсказку убрать букву "i" из регэкспа при работе с className.

Kolyaj 26.10.2010 10:40

Цитата:

Сообщение от stopkran
Использовать при отрисовке таблицы innerHTML или appendChild?

При отрисовке во время сортировки?

Цитата:

Сообщение от stopkran
Делать ли таблицу полосатой («зебра»)?

Не надо.

Цитата:

Сообщение от stopkran
Разбивать ли на страницы (paginate)?

Это кому как надо.

Цитата:

Сообщение от stopkran
Давать ли возможность пользователям самим определять тип сортировки (удерживая при щелчке Shift или Control)?

Никто не будет держать shift или control.

stopkran 26.10.2010 11:23

Цитата:

Сообщение от Kolyaj
При отрисовке во время сортировки?

Нет. При отрисовке после сортировки! :-) Сортировать реальный ДОМ в 1000 строк нереально, сначала всё грузится в массив, там сортируется, потом вываливается обратно на экран.

(Спасибо за быстрый ответ!)

Kolyaj 26.10.2010 11:51

Цитата:

Сообщение от stopkran
сначала всё грузится в массив, там сортируется, потом вываливается обратно на экран.

Ну это понятно, что на экране не сортируется. А зачем создавать заново элементы, если они уже созданы?
Общий алгоритм такой: строки tr помещаем в массив trs, массив сортируем, потом бежим по нему и для каждого элемента просто делаем tbody.appendChild(trs[i]), в результате они выстроятся в нужном порядке.

stopkran 26.10.2010 12:46

Цитата:

Сообщение от Kolyaj
и для каждого элемента просто делаем tbody.appendChild(trs[i])

Ну, может, я неграмотно выразился, но именно это я и назвал "отрисовкой". При appendChild ведь структура ДОМ меняется? И её потом надо перерисовывать?

И, кстати, насчёт создаваемых заново элементов. Как показывает упомянутый выше TinyTable, если помещать строки в массив, копируя (cloneNode), работа с ДОМ идёт гораздо быстрее. Этот фокус позволяет практически уравнять по скорости ИЕ и ФФ (без него ИЕ в несколько раз медленнее, что на таблице в 1000 строк фатально).

with-love-from-siberia 29.10.2010 10:38

Цитата:

Сообщение от stopkran
Заставлять ли разработчика явно обозначать элементы thead и tbody?

Видимо не стоит. Ведь кроме thead, tbody есть и tfoot.

vflash 29.10.2010 13:52

function cmpRowVerse(a, b) {
 a = a[global.sortcell], b = b[global.sortcell];
 try { 
  return (a <= b) ? (a == b) ? 0 : 1 : -1;
 }
 catch(e){return 0;}
}


тут не стоит использовать try catch. он сильно замедляет код и не работает.

Kolyaj 29.10.2010 13:53

А зачем тут try-catch вообще? Операции сравнения ошибку не бросят.

vflash 29.10.2010 14:03

function hasClass(obj, c) {
 //http://ir2.ru/javascript-if.aspx
 if (!c || !obj) return false
 var re = new RegExp('(\\s+|^)' + c + '(\\s+|$)', 'ig')
 if (typeof obj == "string") obj = {className: obj}
 return (re.test(obj.className)) ? re : false
}

а это просто шедевр. извините

stopkran 30.10.2010 09:11

Цитата:

Сообщение от with-love-from-siberia (Сообщение 76439)
Видимо не стоит. Ведь кроме thead, tbody есть и tfoot.

Где логика?:-) Если уж вы используете tfoot, про tbody наверняка не забудете. Сейчас скрипт при отсутствии thead сам создаёт этот элемент из первой строчки таблицы. Вопрос в том, сэкономить ли на этом участке кода...

stopkran 30.10.2010 09:22

Цитата:

Сообщение от Kolyaj (Сообщение 76466)
А зачем тут try-catch вообще? Операции сравнения ошибку не бросят.

Это остаточное явление. Когда-то в одном из вариантов функции (может быть, при return (a-b)?) ошибки в ИЕ были. Сейчас try-catch уже нету (в последней версии: tabsort3a.js, рабочий пример в 14000 строк: latrus.htm).

stopkran 30.10.2010 09:34

Цитата:

Сообщение от vflash (Сообщение 76468)
а это просто шедевр. извините

Извинить не могу:-). Ирония была бы более-менее оправданна, если бы вы хотя бы про addClass() писали. Это же тайное оружие пролетариата: hasClass() ведь вызывается из addClass(), зачем же два раза ДОМ дёргать, если можно передать для анализа строку? Вы говорите, try-catch (которого, кстати, в последней версии уже нет) замедляет работу. Лишний раз извлекать className элемента - тоже замедляет (особенно с учётом того, что это делается в цикле раз 500 или даже 14000, как в примере latrus.htm).

vflash 30.10.2010 10:03

function hasClass(obj, c) {
 //http://ir2.ru/javascript-if.aspx
 if (!c || !obj) return false   // можно просто return 
 var re = new RegExp('(\\s+|^)' + c + '(\\s+|$)', 'ig') // каждый рас создается новый обьект регулярного выражения. а если с=".*" или пуста "" ?  имя класса регистрозависимо.
 if (typeof obj == "string") obj = {className: obj} // зачем создаете еше один обьект. это дешево конечно но зачем.
 return (re.test(obj.className)) ? re : false // зачем возвращать регулярное выражение ? 
}


вот еше, найдите сами.
function getCookie(name) {
 var value=new RegExp("(^|;)\\s*"+name+"\\s*\\=\\s*([^;]+)($|;)","i").exec(unescape(document.cookie));
 return value && value[2];
}

stopkran 30.10.2010 11:18

Цитата:

Сообщение от vflash (Сообщение 76602)
return false // можно просто return

Можно. Это, наверное, больше вопрос стиля.

Цитата:

Сообщение от vflash (Сообщение 76602)
// каждый рас создается новый обьект регулярного выражения

Думал уже об этом. Но как вынести регэксп из функции, если там используется переменная (c)? Если подскажете решение, буду благодарен.

Цитата:

Сообщение от vflash (Сообщение 76602)
а если с=".*" или пуста "" ?

Если с=".*", не знаю (это в каком месте HTML или CSS кода такое возможно?). Ну, наверное, будет сообщение отладчика "Правило проигнорировано из-за плохого селектора". Если с="" (пуста) будет "return false" (строка 3).

Цитата:

Сообщение от vflash (Сообщение 76602)
имя класса регистрозависимо.

.ть, просмотрел! Не вы первый носом тычете. В "библиотеке" http://ir2.ru/ir2.js исправил, а саму библиотеку из экономии решил к сортировщику не подключать (скопировал туда нужные функции).

Цитата:

Сообщение от vflash (Сообщение 76602)
if (typeof obj == "string") obj = {className: obj} // зачем создаете еше один обьект. это дешево конечно но зачем.

А как? Я допускаю на входе и строку, и объект. Дальше надо проверять
re.test(obj.className)
. Если там всё-таки строка (у obj нету className), условия что ли использовать? Так:
if (typeof obj == "string") re.test(obj.className) else re.test(obj)
? Ну, вероятно, можно как-нибудь в "новом стиле":
re.test(obj && obj.className)
, но это проверять нужно... Руки дойдут - подумаю.

Цитата:

Сообщение от vflash (Сообщение 76602)
return (re.test(obj.className)) ? re : false // зачем возвращать регулярное выражение ?

Всё из той же экономии. В других библиотеках то, что вам не понравилось ("Каждый раз создаётся новый регэксп"), происходит дважды: в hasClass и в addClass (или delClass).

Не знаю, что вам не нравится в getCookie(). Не могу догадаться. Пока они нормально соотносятся с моими же setCookie() и это работает во всех браузерах...

vflash 30.10.2010 18:31

Цитата:

с=""
эт конечно с потолка взял пример, чтобы указать на ошибку. вообше тут правильно разбивать в массив и потом искать obj.className.split(/\s+/).indexOf(c) . а на практике в начале и конец добавляют пробелы и ишут уже используя (" "+obj.className+" ").indexOf(" "+c+" ") !== -1 , это правда не по стандарту но зато быстро.

в getCookie unescape делать нужно для значения а не для всей куки

stopkran 02.11.2010 04:37

Цитата:

Сообщение от vflash
и ишут уже используя (" "+obj.className+" ").indexOf(" "+c+" ") !== -1 , это правда не по стандарту

Отчего же это "не по стандарту"? Мне понравилось. Сам давно делаю так на PHP, а тут, видимо, инерция помешала (образцы из чужих библиотек). Теперь сделал hasClass именно так; но для остальных функций (удаление, замена класса) регэксп всё-таки нужен: tabsort3b.js

stopkran 02.11.2010 04:53

Цитата:

Сообщение от vflash
в getCookie unescape делать нужно для значения а не для всей куки

Я бы с удовольствием, но только не знаю, как получить значение куки без регэкспа, в котором допускаются русские буквы. Что ли сначала делать escape(регэксп), потом получить значение, а потом unescape? Или тоже всё через indexOf, как в jQuery?

stopkran 03.12.2010 07:09

обновление скрипта
 
Появилась новая версия Сортировщика HTML таблиц - http://ir2.ru/tabsort3.js

Там можно работать "в режиме словаря" (отметить в секции настройки is_dict = true), при этом скрипт будет создавать нечто вроде индексов, позволяющих находить (по целому слову) строку в таблице с очень высокой скоростью.

Пример - http://ir2.ru/latrus.htm (Латинско-русский словарь, 14000 слов).

Ну, и всякие paginate улучшены.

stopkran 09.12.2010 09:03

Simple Table Sorter
 
Готова версия 0 скрипта Simple Table Sorter. Пример: http://ir2.ru/SimpleTableSorter.zip (13 KB). В нём всего 57 строк:

(function(){
 var a_re = /[cdu]\_\d+\_[cdu]/, a_color = 1

 var hc = function (s, c) {return (" " + s + " ").indexOf(" " + c + " ") !== -1},
 ac = function (e, c) {var s = e.className; if (!hc(s, c)) e.className += " " + c}

 prepTabs = function (t){
  var el, th, cs, c, cell, axis, ts = (t && t.className) ? [t] : document.getElementsByTagName("table")
  for (var e in ts) {
   el = ts[e]
   if (!hc(el.className, "sortable")) continue
   if (!el.tHead) {
    th = document.createElement("thead")
    th.appendChild(el.rows[0])
    el.appendChild(th)
   }
   th = el.tHead
   ac(th, "c_0_c")
   th.title = "Сортировать"
   th.onclick = clicktab
   el.sorted = NaN
  }
 }

 var clicktab = function (e) {
  e = e || window.event
  var obj = e.target || e.srcElement
  while (!obj.tagName.match(/^(th|td)$/i)) obj = obj.parentNode
  var i = obj.cellIndex, t = obj.parentNode
  while (!t.tagName.match(/^table$/i)) t = t.parentNode

  var cn = obj.className, verse = /d\_\d+\_d/.test(cn),
  dir = (verse) ? "u" : "d", new_cls = dir + "_" + a_color + "_" + dir
  if (a_color < 8) a_color++
  if (a_re.test(cn)) obj.className = cn.replace(a_re, new_cls)
  else obj.className = new_cls

  var j = 0, tb = t.tBodies[0], rows = tb.rows, l = rows.length, c, v, vi
  if (i !== t.sorted) {
   t.sarr = []
   for (j; j < l; j++) {
    c = rows[j].cells[i]
    v = (c) ? (c.innerHTML.replace(/\<[^<>]+?\>/g, "")) : ""
    vi = Math.round(100 * parseFloat(v)).toString()
    if (!isNaN(vi)) while (vi.length < 10) vi = "0" + vi
    else vi = v
    t.sarr[j] = [vi, rows[j]]
   }
  }
  t.sarr = (verse) ? t.sarr.reverse() : t.sarr.sort()
  t.sorted = i

  for (j = 0; j < l; j++) tb.appendChild(t.sarr[j][1])
  obj.title = "Отсортировано по " + ((verse) ? "убыванию" : "возрастанию")
 }

 window.onload = prepTabs
})()

x-yuri 09.12.2010 10:35

Цитата:

Сообщение от stopkran
Делать ли таблицу полосатой («зебра»)?

Цитата:

Сообщение от Kolyaj
Не надо.

Kolyaj, а почему?

Kolyaj 09.12.2010 11:19

x-yuri,
как-то у тебя цитаты перепутаны :)

Зебра мало чем помогает воспринимать таблицу, при этом реализация её зачастую неудобна, особенно, если таблица потом ещё и сортируется.

Реально помогает воспринимать таблицу подсвечивание строки под указателем мыши. А зебра... ну строки как путались, так и путаются, только они ещё цветные. Нормально свёрстанная таблица, в которой строки не налезают друг на друга, а столбцы не сливаются, и без зебры будет нормально читаться.

Впрочем это всё только моё мнение :)

x-yuri 09.12.2010 11:50

Цитата:

Сообщение от Kolyaj
как-то у тебя цитаты перепутаны

не, строки у меня сначала были перепутаны. Но я заметил. А вот то, что авторы перепутаны - это ты заметил :)

Цитата:

Сообщение от Kolyaj
Зебра мало чем помогает воспринимать таблицу

хм, но почему? Ведь, зебра - это когда указатель мыши находится над всеми строками, которые через одну :) Разве что можно сказать, что зебра актуальнее в печатных изданиях, потому что там нету указателя мыши. А в компьютерных - "не надо решать за пользователя, какую захочет строку подсветить, ту и подсветит" :) надо будет попробовать...

Kolyaj 09.12.2010 11:59

Цитата:

Сообщение от x-yuri
Ведь, зебра - это когда указатель мыши находится над всеми строками, которые через одну

А зачем нам все строки, которые через одну? В каждый конкретный момент времени нас интересует только одна строчка, вот её и надо подсвечивать.

В печатных изданиях есть палец :)


Часовой пояс GMT +3, время: 23:49.