Сайт Игоря Кононученко   Статьи

JavaScript-дерево

24 ноября 2008

Задача. Разработать галерею с неограниченной вложенностью в категориях. Категории должны поддерживать локализацию (русский и английский языки). Должна быть панель администратора. Все что можно, реализовать с помощью Аякс.

В самом начале я получил, штмл верстку галереи. Не идеально, но концепция понятна.

За прототип дерева было взято цсс-дерево из техногрета Артемия Лебедева.. Недостатки того дерева: поддерживает только один уровень вложенности, в ИЕ плохо выглядит.
Уже вырисовывается первая задача.

Создание дерева

Я подумываю об использовании дерева из библиотеки ext.js. Но решаю не внедрять библиотеку в этот проект. В проекте использую только prototype.js.

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

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

Штмл такого вида:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<ul class="ul_maincategory">
        <li class="">
            <div>
                <p>
                    <a class="sc" href="javascript:expand_or_collapse();"><span class="expanded_node" />
                    </a><a class="title" title="ARATA" href="javascript:open_item();">ARATA</a></p>
            </div>
            <ul class="ul_subcategory">
                <li class="">
                    <div>
                        <p style="left: 1.89em;">
                            <a class="sc" href="javascript:expand_or_collapse();"><span class="expanded_node" />
                            </a><a class="title" title="Подкатегория1" href="javascript:open_item();">Подкатегория1</a></p>
                    </div>
                    <ul class="ul_subcategory" style="border-left: 2px solid white; margin-left: -1px;">
                        <li class="">
                            <div>
                                <p style="left: 1.89em;">
                                    <a class="sc" href="javascript:expand_or_collapse();"><span class="expanded_node" />
                                    </a><a class="title" title="Подкатегория1" href="javascript:open_item();">Подкатегория1</a></p>
                            </div>
                            <ul class="ul_subcategory" style="border-left: 2px solid white; margin-left: -1px;">
                                <li>
                                    <div>
                                        <p>
                                            <a class="sc" href="javascript:expand_or_collapse();" /><a class="title" title="Подкатегория1"
                                                href="javascript:open_item();">Подкатегория1</a></p>
                                    </div>
                                </li>
                            </ul>
                        </li>
                    </ul>
                </li></p>
            </ul>
        </li>
        <li>
            <div>
                <p>
                    <a class="sc" href="javascript:expand_or_collapse();" /><a class="title" href="javascript:open_item();"
                        title="CATALANO">CATALANO</a></p>
            </div>
        </li>
        <li>
            <div>
                <p>
                    <a class="sc" href="javascript:expand_or_collapse();" /><a class="title" href="javascript:open_item();"
                        title="MONILE">MONILE</a></p>
            </div>
        </li>
        <li>
            <div>
                <p>
                    <a class="sc" href="javascript:expand_or_collapse();" /><a class="title" href="javascript:open_item();"
                        title="LENVAL">LENVAL</a></p>
            </div>
        </li>
        <li>
            <div>
                <p>
                    <a class="sc" href="javascript:expand_or_collapse();" /><a class="title" href="javascript:open_item();"
                        title="ROBERTO GIANNOTTI">ROBERTO GIANNOTTI</a></p>
            </div>
        </li>
    </ul>

Вырисовывается задача с помощью джаваскрипта создать тоже самое.

На вход нужно давать данные. Выбор формата очевиден.

JSON, лучше в JS нет!

Ручками создаем данные. С помощью этих данных будет отображено дерево категорий.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
var 
data={nodes:[{name:'ARATA',hint:'ARATA',url:'ARATA',expanded:false,
languages:[{language:'Русский',name:'ARATA',hint:'ARATA'},
			{language:'English',name:'ARATA',hint:'ARATA'}],
			categoryId:58,
			childNodes:[{name:Кольца,hint:Кольца,url:'arata/rings',expanded:false,
			languages:[{language:'Русский',name:Кольца,hint:Кольца},
						{language:'English',name:'Rings',hint:'Rings'}],
			categoryId:64}]},
			{name:'CATALANO',hint:'CATALANO',url:'CATALANO',expanded:false,
			languages:[{language:'Русский',name:'CATALANO',hint:'CATALANO'},
			{language:'English',name:'CATALANO',hint:'CATALANO'}],categoryId:59},
			{name:'MONILE',hint:'MONILE',url:'MONILE',expanded:false,
			languages:[{language:'Русский',name:'MONILE',hint:'MONILE'},
			{language:'English',name:'MONILE',hint:'MONILE'}],categoryId:60},
			{name:'LENVAL',hint:'LENVAL',url:'LENVAL',expanded:false,
			languages:[{language:'Русский',name:'LENVAL',hint:'LENVAL'},
			{language:'English',name:'LENVAL',hint:'LENVAL'}],categoryId:62},
			{name:'ROBERTO GIANNOTTI',hint:'ROBERTO GIANNOTTI',
			url:'ROBERTO GIANNOTTI',expanded:false,
			languages:[{language:'Русский',name:'ROBERTO GIANNOTTI',hint:'ROBERTO GIANNOTTI'},
			{language:'English',name:'ROBERTO GIANNOTTI',hint:'ROBERTO GIANNOTTI'}],categoryId:63}]};

Итак, можно заняться наконец рендерингом дерева.

Рендеринг дерева

Задача: с помощью джаваскрипта отрендерить дерево. На вход будут поступать джейсон данные, на выходе получится штмл.

В объектной структуре особо нечего выдумывать, классы те же что и везде (класс дерева и класс узла):

Tree – содержит в себе ноды, рисует дерево итд.

TreeNode – реализация логики узла.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
var Tree = Class.create();



Tree.prototype = {
    initialize:function(container,dataSource)
    {
       this.el = $(container);
       this.data= dataSource;
       this.el.className = "tree";
       this.childNodes =new Array();                 
       this.el.onclick = this.deselectAll.bindAsEventListener(this);
      this.render();
         this.selectedNode = this;
     
    },
    render:function()
    {
        this.nodesEl =createElement("ul","",this.el,"ul_maincategory");
        this.createChilds();
        this.renderChilds();
         this.updateConnectors();
              
    },
    createChilds:function()
    {
     for(var i=0;i<this.data.nodes.length;i++)
     {
       this.appendNode(new TreeNode(this.data.nodes[i],this,this));
     }
    },
   
    renderChilds:function()
    {
     for(var i=0;i<this.childNodes.length;i++)
     {
        this.childNodes[i].render();
     }
    },
    appendNode:function(child)
  {
     this.childNodes.push(child);       
  },  
  appendNodeToSelected:function(config)
  {
    var selectedNode = this.selectedNode?this.selectedNode:this;
     node = new TreeNode(config, this,selectedNode);
     if(selectedNode){
       selectedNode.appendNode(node);
       
       selectedNode.updateNodesEl();
       }
     else {this.appendNode(node);}
     node.render();
     
      this.updateConnectors();
   return node;
     
  },
  updateNodesEl:function()
  {},
  setSelectedNode:function(node)
  {
  if(this.selectedNode&&this.selectedNode!=this)  this.selectedNode.deselectNode();
       this.selectedNode = node;
       
       if(this.onSelect) this.onSelect(node);
  },
  selectFirstNode:function()
  {
  this.childNodes[0].onClickHandler();
 
  },
  
  deselectAll:function()
  {
  this.setSelectedNode(this);
  },
  
  deleteSelectedNode:function()
  {
     if(!this.selectedNode) return;
     this.selectedNode.deleteNode();
     this.updateConnectors();    
  },
  updateConnectors:function()
  {
      //update recursively  childs
    for(var i=0;i<this.childNodes.length;i++) 
    {
   
     this.childNodes[i].updateConnectors();
     
     }
  } 
}

Из полезного, метод удаления выбраного нода (deleteSelectedNode), добавление нода (appendNode), добавление нода в дети текущего узла (appendNodeToSelected).

Код нода не показываю тут, так как его много. В ноде реализованы методы редактирующие его отображение и данные.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
setTitle:function(title)
  {
     this.titleEl.innerHTML = title;
     this.data.name = title;
     this.title = title;
  },
  setHint:function(hint)
  {
     this.titleEl.title = hint;
     this.data.hint = hint;
     this.hint = hint;
  },
  setUrl:function(url)
  {
     this.data.url = url;
  },
А также код удаления:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
deleteNode:function()
  {    this.parentNode.childNodes=deleteElementFromArray(this.parentNode.childNodes,this.getIndex());
     destroyElement(this.el);
     this.el = null;
     if(this.parentNode.childNodes.length==0) {destroyElement(this.parentNode.nodesEl); 
this.parentNode.nodesEl=null;};
  if(this.parentNode!=this.tree)
     this.parentNode.updateNodesEl();
     this.tree.selectedNode = null;     
  },

В завершении

Задача выполнена, дерево отображается в ИЕ и ФФ; позволяет добавлять удалять и редактировать ноды; содержит удобное АПИ для работы.

Пример посмотреть можно здесь.

Скачать пример здесь.

Решение криптоарифметической задачи
Автоматизация простой операции
Ctrl
JavaScript обучение
Обучение верстке