Сортировка таблицы средствами JavaScript

Эта статья может быть полезна владельцам сайтов, на которых представлены списки в виде таблицы. Если к таким страницам применять серверную сортировку, то пользователь, особенно находящийся на коммутируемом доступе, может отказаться от повторной загрузки большого объма информации и покинуть страницу, что, конечно же, нежелательно.

Нижеследующий код предлагает некоторую альтернативу серверному решению, сортируя таблицу на компьютере клиента, но только при том условии, что браузер клиента поддерживает объектную модель документа (DOM) и в нем не отключена поддержка JavaScript.

Итак, создаем страницу, содержащую наш код.

var maxSize = 1;
var temp1, temp2, temp3, temp4, temp5, temp6;
var txt = new Array();

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

function fullArray(years, books, authors){
   this.years = years;
   this.books = books;
   this.authors = authors;
  }

Далее, заполняем наш массив данными, которые будут отображены в таблице. Первую строку мы резервируем для заголовка, она не будет подлежать сортировке. Если в вашей таблице отсутствует заголовок, код сортировки следует изменить. В качестве аргументов мы передаем функции наши данные.

txt[0] = new fullArray("Год", "Книга", "Автор");
txt[1] = new fullArray("1959", "Фрейд", "Жан-Поль Сартр");
txt[2] = new fullArray("1940", "Подростки", "Джером Сэлинджер");
txt[3] = new fullArray("1946", "Пена дней", "Борис Виан");
txt[4] = new fullArray("1948", "Осадное положение", "Альбер Камю");
txt[5] = new fullArray("1899", "Об иноческой жизни", "Райнер Рильке");
txt[6] = new fullArray("1849", "Аннабель Ли", "Эдгар По");
txt[7] = new fullArray("1917", "Дагон", "Говард Лавкрафт");
txt[8] = new fullArray("1915", "Процесс", "Франц Кафка");

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

function createTable(){
   var counter2 = 0;
   var getIDs = document.getElementById("testremover");  
   //  В самом начале функции мы получаем ссылки 
   //  на уже существующие элементы нашей страницы.  
   //  В теле документа, как вы увидите это позднее, 
   //  обязательно должны присутствовать теги <table id="test">
   //  <tbody id="testremover">. Если тег <tbody> отсутствует,
   //  значит уже было произведено удаление тела таблицы, 
   //  и необходимо создать новый тег.
   if(!getIDs){
    var getIDTest = document.getElementById("test");
    var crtID = document.createElement("tbody");
    crtID.id = "testremover";
    getIDTest.appendChild(crtID); 
    getIDs = document.getElementById("testremover");
   }    
  //Создав <tbody> и присвоив ему указанный id
  //(безусловно, имя id может быть любым), 
  //мы уже создали остов нашей таблицы.

  for(var counter=0; counter<=txt.length-1; counter++){
   var crtTr = document.createElement("tr");  
  //Первый цикл определяет количество строк, 
  //после него указана только одна переменная, 
  //она создает элемент "tr", в который будет добавлена 
  //информация из следующего цикла.
   for(var count=0; count<=2; count++){
  //Логическое условие второго цикла представляет 
  //собой количество аргументов массива, 
  //проще говоря количество его столбцов. 
    counter2++;
    var crtTd = document.createElement("td"); 
    if(counter2 == 1){
     var crtTxtNd = document.createTextNode(txt[counter].years);
    }  
    else if(counter2 == 2){
     var crtTxtNd = document.createTextNode(txt[counter].books);
    }
    else{
     var crtTxtNd = document.createTextNode(txt[counter].authors);
     counter2 = 0;
    }
    crtTd.appendChild(crtTxtNd);
    crtTr.appendChild(crtTd);
    getIDs.appendChild(crtTr);
   }
  }
 }

Затем мы создаем ячейку (элемент "td") и проверяем переменную counter2, которую мы объявили в начале функции, на соответствие условию. Это необходимо для создания текстовых узлов по порядку. В конце цикла добавляем дочерний текстовый узел в элемент "td", в элемент "tr" добавляем элемент "td", с уже содержащимся в нем текстовым узлом, а сам элемент "tr" мы включаем в тело таблицы (созданный тег <tbody>).

Далее представлена универсальная функция сортировки

function sortArrYears(){
   for(counter=1; counter < txt.length-1; counter++){
    // Вначале определяется первый цикл, 
    // его переменная counter будет определять порядковый
    // номер первого массива взятого для сравнения.
    maxSize++
    for(count=maxSize; count <= txt.length-1; count++){
     //  Переменная maxSize обеспечивает такое значение 
     //  для переменной второго цикла, которое
     //  всегда будет больше на единицу переменной counter. 
     //  То есть, мы будем сравнивать 
     //  первый и второй массив, затем первый и третий... 
     //  второй и третий, и т. д.  
     for(var counts=0; counts <= 100; counts++){
      // Следующий, третий цикл, инициализирует переменную counts 
      // нулем и ставит ей логическое условие <= 100.
      // Число сто условно, это количество сравниваемых букв или 
      // цифр в цикле (вряд-ли найдется такая фраза, 
      // в которой будут дважды повторяться одни и те же слова 
      // в одинаковом порядке, впрочем, если такое возможно, 
      //    то условие можно увеличивать вплоть до 9007199254740992).
      if(txt[counter].years.charCodeAt(counts) 
          < txt[count].years.charCodeAt(counts)){
       temp1 = txt[counter].years;
       temp2 = txt[counter].books;
       temp3 = txt[counter].authors;
       temp4 = txt[count].years;
       temp5 = txt[count].books;
       temp6 = txt[count].authors;
       txt[count].years = temp1;
       txt[count].books = temp2;
       txt[count].authors = temp3;
       txt[counter].years = temp4;
       txt[counter].books = temp5;
       txt[counter].authors = temp6;
       break
      }

      // Затем начинается сравнение. 
      // Если порядковый символ массива с номером counter и столбцом,
      // по которому мы сортируем ( if (txt[counter].years) меньше 
      // (или больше, вы вправе менять эти условия), то значения массивов 
      // меняются, и цикл, который определяет char'овский код
      // сравниваемых символов, прерывается. 
      // Счетчик второго массива изменяется на единицу, 
      // и проверка начинается по новой.
     else if(txt[counter].years.charCodeAt(counts) 
            == txt[count].years.charCodeAt(counts)){
      continue
     }
     //Если значения равны, внутренний цикл увеличивает значение на единицу 
     //и сравнивает уже вторые символы и т. д.
     else{
          break
     }
    // В противном случае, значение считается отвечающим условию сортировки
    // и внутренний цикл завершается.
     }
    }
   }
   maxSize=1
   createTable()
}

Cразу оговорюсь, код символа 'ё' меньше остальных символов кириллицы, из-за чего эта функция всегда будет помещать этот символ в начало, или же конец массива, в зависимости от выбранного направления сортировки.

Итак, что же можно изменить в функции сортировки, чтобы она сортировала не только года, как в примере, а любые столбцы? Для этого, после указания массивов, пишем наименования столбцов. Например, txt[counter].years.charCodeAt(counts) можно заменить на txt[counter].books.charCodeAt(counts), теперь вы сравниваете наименования книг. Очень важно, чтобы правая часть условия содержала то же имя столбца, что и левая, иначе вы будете сравнивать различные аргументы. Например, книги с авторами. Далее, устанавливая логическое сравнение в '<' или '>', мы меняем порядок сортировки с возрастания на убывание.

К сожалению, нельзя передавать аргументы прямо в функцию (sortArrYears(args)), это вызовет ошибку при его подстановке. Поэтому надо создавать столько функций сортировки, сколько вариантов мы хотим предоставить пользователю. Естественно, эти функции должны иметь уникальное имя. Выбор между этими сортировками представлен в следующей функции.

function allocator(){
   var a = document.sorter.Selector.value
   var GetRem = document.getElementById("test");
   var cloneGetRem = document.getElementById("testremover");
   GetRem.removeChild(cloneGetRem);
   // Удалив тело таблицы, мы проверяем селекторы выпадающего списка,
   // и, в соответствии с результатами проверки, 
   // вызываем необходимую нам функцию сортировки. 
   // Как ее изменить указано выше. 
   if(a == "Z-A"){
    sortByName()
   }
   else if(a == "A-Z"){
    sortByNameReverse()
   }
   else if(a == "D-D"){
    sortArrYears()
   }
   else{
    sortArrYearsReverse()
   }
  }

Остальная часть документа может выглядеть приблизительно так:

<form name="sorter">
<label for="Selector">Сортировать</label> <select name="Selector" id="Selector">
<option value="Z-A">По книге: от большего к меньшему
<option value="A-Z">По книге: от меньшего к большему
<option value="D-D">По годам: от большего к меньшему
<option value="both">По годам: от меньшего к большему
</select>
<input type="button" value="Сортировать" onClick="allocator()">
</form>

<table border="1" id="test">
<tbody id="testremover">
</tbody>
</table>

.