2017-02-15 23 views
0

Я хочу визуализировать дерево с несколькими столбцами (т. Е. Дерево или дерево) в Angular. Мои данные дерево произвольной глубины, и выглядит примерно так:Директива AngularJS для рендеринга дерева JSON в виде отступов таблицы

[ 
    {name: "node 1", 
    type: "folder", 
    children: {[ 
       {name:"node 1-1", type:file}, 
       {name:"node 1-2", type:file}, 
       {name:"node 1-3", type:file}, 
       ]} 
    }, 
    ... 
    ... 
] 

Я видел примеры, которые используют рекурсивные шаблоны, чтобы сделать это в уль/li дерева, но мне нужно несколько столбцов, так что я думаю Мне нужен стол. До сих пор лучший HTML я придумал, чтобы сделать приведенную выше структуру данных будет выглядеть примерно так:

<tr> 
    <td> 
    node 1 
    </td> 
    <td> 
    folder 
    </td> 
</tr> 
<tr> 
    <td> 
    <span class="treetable-padding"/> 
    node 1-1 
    </td> 
    <td> 
    file 
    </td> 
</tr> 
... 
... 

Так, другими словами, это просто плоский стол, где глубина дерева представлена ​​с использованием ряда пролетных элементов , причем это число равно этому уровню в иерархии.

Я не очень разбираюсь в html/AngularJS/js в целом, поэтому не знаю, является ли это лучшим способом визуализации моих данных, но он, конечно, выглядит так, как я хочу.

Мой вопрос: что это лучший способ сделать это с помощью AngularJS? Я знаю, что могу построить монолитный кусок html, подобный этому, в JavaScript, а затем обернуть это в пользовательскую директиву, но я ищу решение, которое больше соответствует духу Angular, чтобы я мог использовать привязку данных и т. Д. Я в конечном итоге хочу, чтобы ячейки были доступны для редактирования, поэтому я не хочу делать ничего, что может привести к сбою данных.

Я подумал о написании кода для сглаживания моего дерева в список объектов, которые явно содержат атрибут «глубина». Тогда, возможно, я мог бы использовать ng-repeat. Но я подумал, что, возможно, есть более чистый способ использования одной или нескольких пользовательских директив

Спасибо!

+0

По какой-либо причине это должен быть стол? вы можете попробовать распространить директиву как [angular-json-tree] (https://github.com/awendland/angular-json-tree), чтобы разрешить редактирование – haxxxton

+0

Мне нужно несколько столбцов. Я не показывал их в моем примере для краткости. –

+0

Что вы хотите достичь с помощью дополнительных столбцов? :) для редактирования/удаления/дублирования? вы, вероятно, могли бы работать с ui-popover или modal, если так – haxxxton

ответ

0

У меня есть что-то, что работает сейчас. Он работает так, как я описал в своем комментарии к моему вопросу выше. Просто для того, чтобы повторить, я использую 2 пользовательских директивы, оба из которых основаны на ng-repeat. Первая построит структуру плоской таблицы с буквальными значениями в атрибуте class, чтобы указать глубину. Вторая директива использует эту информацию глубины, чтобы повторить элемент заполнения на соответствующее количество раз. Оба повторителя используют переход, чтобы минимизировать ограничения/зависимости от html. Единственное предостережение в том, что второму повторителю нужна информация о глубине, чтобы быть где-то в ее родословной внутри DOM.

Вот что шаблон HTML выглядит для создания дерева стол структуры я описал в моем вопросе:

<table class="treetable" style="width: 40%;"> 
    <tr class="treetable-node" my-repeat="item in things" 
    my-repeat-children="children"> 
<td> 
    <span my-repeat-padding class="treetable-node-indent"></span> 
    {{ item.name }} 
</td> 
<td> 
    {{ item.type }} 
</td> 
    </tr> 
</table> 

я на основе моего пользовательского повторитель на this примере. Что отличает меня, так это то, что вы можете указать свойство «children», а логика директивы будет проверять это свойство на каждом элементе и рекурсивно переходить в него, если он есть, - хотя он присоединяет все новые элементы к одному и тому же родителю, плоская структура.

Это, вероятно, довольно неэффективно и может использовать некоторую оптимизацию - как и код в ссылке, которую я предоставил, она восстанавливает все, когда есть изменения.

myApp.directive("myRepeat", function($parse) { 
    return { 
    restrict: "A", 
    replace: true, 
    transclude: "element", 
    link: function (scope, element, attrs, controller, transclude) { 
     match = attrs.myRepeat.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/); 
     itemName = match[1]; 
     collectionName = match[2]; 
     childCollectionName = attrs["myRepeatChildren"]; 
     parentElement = element.parent(); 
     elements = []; 

     scope.$watchCollection(collectionName, function(collection) { 
     var i, block, childScope; 
     if (elements.length > 0) { 
      for(i=0; i<elements.length; i++) { 
      elements[i].el.remove(); 
      elements[i].scope.$destroy(); 
      }; 
      elements = []; 
     } 

     var buildHtml = function(parent, itemList, depth=0) { 
      for (var i=0; i<itemList.length; i++) { 
      childScope = scope.$new(); 
      childScope[itemName] = itemList[i]; 

      transclude(childScope, function(clone) { 
       parentElement.append(clone); 
       block = {}; 
       block.el = clone; 
       block.scope = childScope; 
       block.el.addClass("depth-" + depth); 
       elements.push(block); 
      }); 

      /*Recurse if this item has children, 
       adding the sub-elements to the same 
       parent so we end up with a flat list*/ 
      if(childCollectionName in itemList[i]) { 
       buildHtml(parentElement, 
         itemList[i][childCollectionName], 
         depth+1); 
      } 

      } 
     } 
     for (i=0; i<collection.length; i++) { 
      buildHtml(parentElement, collection); 
     } 

     }); 
    } 
    }; 
}); 

Второй - немного взломанный. Это также трансивер-повторитель. Он ищет вверх в DOM для атрибута класса глубины, анализирует значение глубины и добавляет себя много раз в родительский. Таким образом, он полностью зависит от этого класса глубины, заданного первой директивой. Вероятно, есть лучшие способы сделать это. Я также не стал настраивать часы или что-то в этом роде, поскольку это чисто косметическое.

myApp.directive("myRepeatPadding", function($parse) { 
    return { 
    restrict: "A", 
    replace: true, 
    transclude: "element", 
    terminal: true, 
    link: function (scope, element, attrs, controller, transclude) { 
     var getDepth = function(element) { 
     classes = element.attr("class"); 
     if (classes) { 
      match = classes.match(/depth-([\d+])/); 
      if(match.length > 0) { 
      return match[1]; 
      } else { 
      return getDepth(element.parent()); 
      } 
     } else { 
      return getDepth(element.parent()); 
     } 


     } 
     depth = getDepth(element); 
     for (var i=0; i<depth; i++) { 
     transclude(scope, function(clone) { 
      element.parent().prepend(clone); 
      block = {}; 
      block.el = clone; 
      block.scope = scope; 
      elements.push(block); 
     }); 
     } 
    } 
    }; 
}); 

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