пятница, 21 декабря 2007 г.

Javascript Treeview

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

Немного поразмыслив я придумал такой подход:

Каждой строке таблицы присваивается 2 параметра:
level - уровень, на котором лежит узел (от 0 и выше)
state - состояние узла (open - узел открыт, closed - закрыт, end - узел является конечным )


Смотрите скриншот таблицы:




И так, первая функция для открытия и закрытия узлов. Встречайте:

  1. function changeRowState(box, RH_Panel)
  2. {
  3. elem = box.parentNode.parentNode.parentNode;

  4. var current_level = elem.getAttribute("level");
  5. var current_state = elem.getAttribute("state");
  6. var current_table = "table_body";

  7. if(RH_Panel)
  8. current_table = "RH_table_body";

  9. var row_display = "none"; // for style.display
  10. if(current_state == "closed") // if item is closed, than show all children
  11. row_display = "";


  12. var tbody = document.getElementById(current_table);
  13. var children = tbody.childNodes;

  14. var after_current = false;

  15. for(var i=0; i<children.length; i++)
  16. {
  17. if(children[i].tagName == 'TR')
  18. {
  19. if(children[i] == elem)
  20. {
  21. after_current = true;
  22. }

  23. if(after_current)
  24. {
  25. if((children[i] != elem) && (children[i].getAttribute('level') == current_level))
  26. {
  27. if(current_state == "open")
  28. {
  29. elem.setAttribute("state", "closed");
  30. box.src = "images/branch_closed.gif";
  31. }
  32. else
  33. {
  34. elem.setAttribute("state", "open");
  35. box.src = "images/branch_open.gif";
  36. }
  37. return;
  38. }

  39. if(children[i].getAttribute('level') > current_level)
  40. {

  41. children[i].style.display = row_display;
  42. }
  43. }
  44. }
  45. }

  46. if(current_state == "open")
  47. {
  48. elem.setAttribute("state", "closed");
  49. box.src = "images/branch_closed.gif";
  50. }
  51. else
  52. {
  53. elem.setAttribute("state", "open");
  54. box.src = "images/branch_open.gif";
  55. }

Эту функцию мы ставим на событие onclick для элемента <img> в строке таблицы:

<tr level="0" state="open" style="background:#99ddaa;">
<td><input type="checkbox" name="tree_items[]" /></td>
<td>
<a href="javascript:void(0)">
<img src="images/branch_open.gif" onclick="changeRowState(this);" />
</a>
<a href="action.php">&nbsp;&nbsp;<b>Web Channel (2)</b></a>
</td>
<td>56</td>
<td>2</td>
<td>3</td>
<td>10</td>
<td class="statusProduction">Published</td>
</tr>Syhi-подсветка кода


Логика очень проста: при нажатии на иконку, функция проходит по всем строчкам таблицы и изменяет атрибут display (если уровень больше). При таком подходе, в принципе, уровень вложенности может быть большим. Но для нашего проекта я сделал ограничение до 10 уровней.

Функция для изменения уровня узлов:

function changeLevel(direction)
{
var delta_level = -1;
if(direction == "down")
delta_level = 1;

var checkboxes = document.getElementsByName("tree_items[]");

for(var i=0; i<checkboxes.length; i++)
{
if(checkboxes[i].checked)
{
var row = checkboxes[i].parentNode.parentNode;
var current_level = row.getAttribute("level");

if(current_level != 0 || direction == "down") // dont move up row with level = 0
{
var td = checkboxes[i].parentNode.nextSibling;
if(td.nodeType == 3)
td = checkboxes[i].parentNode.nextSibling.nextSibling;

var prev_html = td.innerHTML;
prev_html = prev_html.slice(prev_html.indexOf("<"));
var new_level = parseInt(current_level) + parseInt(delta_level);
td.innerHTML = getSpacesForLevel(new_level) + prev_html;
row.setAttribute("level", new_level);
row.style.backgroundColor = arrColors[new_level];

var tbody = document.getElementById('table_body');
var children = tbody.childNodes;
var row_index = getRowIndex(row);

if(direction == "down")
row_index++;

for(var j=row_index; j<children.length; j++)
{
if(children[j].tagName == 'TR')
{
if(children[j].getAttribute("level") == current_level)
break;

if(children[j].getAttribute("level") > current_level)
{
var cur_level = children[j].getAttribute("level")
var td = children[j].childNodes[3];
if(td.innerHTML.indexOf("<img") == -1) // IE and Safari hack
td = children[j].childNodes[1];

var prev_html = td.innerHTML;
prev_html = prev_html.slice(prev_html.indexOf("<"));
var new_lev = parseInt(cur_level) + parseInt(delta_level);
td.innerHTML = getSpacesForLevel(new_lev) + prev_html;
children[j].setAttribute("level", new_lev);
children[j].style.backgroundColor = arrColors[new_lev];
}
}
}// for
checkAll(0); // uncheck all checkboxes
}// if
}// if checked


// AJAX request to set level
}
}Syhi-подсветка кода


А также функции дублирования:

function duplicateSingle()
{
var checkboxes = document.getElementsByName("tree_items[]");
var tbody = document.getElementById("table_body");

var len = checkboxes.length; // FireFox hack (against constant reference to collection of checkboxes)
for(var i=0; i<len; i++)
{
if(checkboxes[i].checked)
{
var row = checkboxes[i].parentNode.parentNode;
var inserted_row = tbody.appendChild(row.cloneNode(1));

var td = inserted_row.childNodes[3];
if(td.innerHTML.indexOf("<img") == -1) // IE and Safari hack
td = inserted_row.childNodes[1];

var cell_content = td.innerHTML;
var ins_pos = cell_content.indexOf("</b>");
td.innerHTML = cell_content.slice(0, ins_pos) + " Copy" + cell_content.slice(ins_pos);
}
}
checkAll(0);
}

function duplicateBranch()
{
var checkboxes = document.getElementsByName("tree_items[]");
var tbody = document.getElementById("table_body");

var len = checkboxes.length; // FireFox hack (against constant reference to collection of checkboxes)
for(var i=0; i<len; i++)
{
if(checkboxes[i].checked)
{
var row = checkboxes[i].parentNode.parentNode;
var inserted_row = tbody.appendChild(row.cloneNode(1));
var current_level = inserted_row.getAttribute("level");

var tbody = document.getElementById('table_body');
var children = tbody.childNodes;
var row_index = getRowIndex(row);


for(var j=(row_index+1); j<children.length; j++)
{
if(children[j].tagName == 'TR')
{
if(children[j].getAttribute("level") == current_level)
break;

if(children[j].getAttribute("level") > current_level)
{
tbody.appendChild(children[j].cloneNode(1));
}
}
}
}
}
checkAll(0);
}Syhi-подсветка кода


Кому нужны будут подробные объяснения, или кто сталкивался с подробной задачей - сообщите об этом в комментах.

Удачи!

Комментариев нет: