2017-02-15 1 views
0

Je souhaite rendre un arbre multi-colonnes (c'est-à-dire une table arborescente ou une grille arborescente) en Angulaire. Mes données est un arbre de profondeur arbitraire, et ressemble à ceci:Directive AngularJS pour le rendu d'une arborescence JSON en tant que table indentée

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

J'ai vu des exemples qui utilisent des modèles récursifs pour rendre ce dans un arbre ul/li, mais je besoin de plusieurs colonnes, donc je pense J'ai besoin d'une table. Jusqu'à présent, le meilleur HTML que je suis venu avec pour rendre la structure de données ci-dessus ressemblerait à quelque chose comme:

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

Donc, en d'autres termes, il est juste une table plate où la profondeur de l'arbre est représenté en utilisant un certain nombre d'éléments de portée , ce nombre étant égal à ce niveau dans la hiérarchie.

Je ne suis pas génial avec html/AngularJS/js en général, donc je ne sais pas si c'est la meilleure façon de rendre mes données, mais cela ressemble certainement à ce que je veux.

Ma question est, quelle est la meilleure façon de le faire en utilisant AngularJS? Je sais que je peux construire un morceau de code html monolithique comme celui-ci en JavaScript et ensuite l'intégrer dans une directive personnalisée, mais je cherche une solution qui soit plus dans l'esprit d'Angular, pour que je profite de la liaison de données etc. En fin de compte, je veux que les cellules soient modifiables, donc je ne veux rien faire qui rendrait la liaison de données désordonnée. J'ai pensé à écrire du code pour aplatir mon arbre dans une liste d'objets qui contiennent explicitement un attribut "profondeur". Alors peut-être que je pourrais utiliser ng-repeat. Mais je pensais qu'il y avait peut-être un moyen plus propre d'utiliser une ou plusieurs directives personnalisées

Merci!

+0

quelle raison cela doit-il être une table? vous pouvez essayer d'étendre une directive comme [angular-json-tree] (https://github.com/awendland/angular-json-tree) pour permettre l'édition – haxxxton

+0

J'ai besoin de plusieurs colonnes. Je ne les ai pas montrés dans mon exemple pour la brièveté. –

+0

Que cherchez-vous à réaliser avec les colonnes supplémentaires? :) boutons pour éditer/supprimer/dupliquer? vous pouvez probablement gérer la même chose avec ui-popover ou un modal si oui – haxxxton

Répondre

0

J'ai quelque chose qui fonctionne maintenant. Cela fonctionne de la manière que j'ai décrite dans mon commentaire sur ma question ci-dessus. Juste pour récapituler, j'utilise 2 directives personnalisées, toutes deux basées sur ng-repeat. Le premier construit une structure de table plate avec des valeurs littérales dans l'attribut de classe pour indiquer la profondeur. La seconde directive utilise cette information de profondeur pour répéter l'élément de remplissage le nombre approprié de fois. Les deux répéteurs utilisent la transclusion pour minimiser les restrictions/dépendances sur le code HTML. Le seul inconvénient est que le deuxième répéteur a besoin de l'information de profondeur pour être quelque part dans son ascendance dans le DOM.

Voici ce que le modèle HTML ressemble pour produire un arbre table de la structure que j'ai décrit dans ma question:

<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> 

I sur la base de mon répéteur personnalisé sur this exemple. Ce qui rend le mien différent, c'est que vous pouvez spécifier une propriété "children" et la logique de la directive vérifiera cette propriété sur chaque élément, et descendra récursivement dedans si elle est là - bien qu'elle attache tous les nouveaux éléments au même parent, me donnant le structure plate.

Il est probablement assez inefficace, et pourrait utiliser une certaine optimisation - comme le code dans le lien que j'ai fourni, il reconstruit tout quand il y a un changement.

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); 
     } 

     }); 
    } 
    }; 
}); 

La seconde est un peu hacky. C'est aussi un répéteur de transclusion. Il recherche vers le haut dans le DOM pour l'attribut de classe de profondeur, analyse la valeur de profondeur, et s'y ajoute plusieurs fois dans le parent. Donc, cela dépend totalement de la classe de profondeur définie par la première directive. Il y a probablement de meilleures façons de le faire. Je n'ai pas non plus pris la peine de mettre en place une montre ou quelque chose comme ça, car c'est purement cosmétique.

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); 
     }); 
     } 
    } 
    }; 
}); 

C'est loin d'être parfait et je vais devoir passer du temps à l'améliorer. Je ne suis même pas sûr que le HTML que je produise est le meilleur moyen d'obtenir l'apparence que je veux - mais il rend le rendu que je veux tout en gardant le modèle HTML raisonnablement élégant.