2017-02-15 3 views
0

Ich möchte einen mehrspaltigen Baum (d. H. Baumtabelle oder Baumgitter) in Angular rendern. Meine Daten ist ein Baum von beliebiger Tiefe und sieht etwa so aus:AngularJS-Anweisung zum Rendern eines JSON-Baums als eingerückte Tabelle

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

I Beispiele gesehen haben, die rekursive Vorlagen verwenden, dies in einem ul/li Baum zu machen, aber ich brauche mehrere Spalten, so dass ich denke, Ich brauche einen Tisch. Bisher ist die beste HTML ich mit kommen habe die oben genannte Datenstruktur machen würde in etwa so aussehen:

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

Also, mit anderen Worten, es ist nur ein flacher Tisch, wo die Baumtiefe dargestellt wird, eine Anzahl von Spannelementen , wobei diese Zahl der Ebene in der Hierarchie entspricht.

Ich bin nicht gut mit html/AngularJS/js im Allgemeinen, also weiß ich nicht, ob dies der beste Weg ist, meine Daten zu rendern, aber es sieht sicherlich so aus, wie ich es möchte.

Meine Frage ist, was ist der beste Weg, dies mit AngularJS zu tun? Ich weiß, dass ich einen monolithischen HTML-Block wie diesen in JavaScript erstellen und dann in eine benutzerdefinierte Direktive einfügen kann, aber ich suche nach einer Lösung, die mehr im Sinne von Angular ist, so dass ich die Datenbindung usw. nutzen kann. Ich möchte schließlich, dass die Zellen editierbar sind, also möchte ich nichts tun, was die Datenbindung unordentlich macht.

Ich dachte daran, Code zu schreiben, um meinen Baum in eine Liste von Objekten einzubetten, die explizit ein Attribut "Tiefe" enthalten. Dann könnte ich vielleicht ng-repeat verwenden. Aber ich dachte, vielleicht gibt es einen saubereren Weg mit einer oder mehreren benutzerdefinierten Direktiven

Vielen Dank!

+0

aus irgendeinem Grund muss dies eine Tabelle sein? Sie könnten versuchen, eine Direktive wie [angular-json-tree] (https://github.com/awendland/angular-json-tree) zu erweitern, um die Bearbeitung zu ermöglichen. – haxxxton

+0

Ich brauche mehrere Spalten. Ich habe sie in meinem Beispiel der Kürze halber nicht gezeigt. –

+0

Was möchten Sie mit den zusätzlichen Säulen erreichen? :) Tasten zum Bearbeiten/Löschen/Duplizieren? du könntest wahrscheinlich dasselbe mit ui-popovers oder einem modalen handhaben, wenn so – haxxxton

Antwort

0

Ich habe etwas, das jetzt funktioniert. Es funktioniert so, wie ich es in meinem Kommentar zu meiner Frage oben beschrieben habe. Zur Erinnerung, ich verwende 2 benutzerdefinierte Direktiven, die beide auf ng-repeat basieren. Der erste erstellt eine flache Tabellenstruktur mit Literalwerten im Klassenattribut, um die Tiefe anzuzeigen. Die zweite Anweisung verwendet diese Tiefeninformationen, um das Padding-Element die entsprechende Anzahl von Wiederholungen zu wiederholen. Beide Repeater verwenden die Transclusion, um Beschränkungen/Abhängigkeiten von HTML zu minimieren. Der einzige Vorbehalt ist, dass der zweite Repeater die Tiefeninformationen benötigt, um irgendwo innerhalb seiner DOM-Herkunft zu sein.

Hier ist, was die HTML-Vorlage sieht aus wie für eine Baum Tabelle der Struktur produziert ich in meiner Frage beschrieben:

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

ich anhand meines benutzerdefinierten Repeater auf this Beispiel. Was meins anders macht, ist, dass Sie eine "children" -Eigenschaft angeben können und die Direktivenlogik nach dieser Eigenschaft für jedes Element sucht und rekursiv in sie absteigt, wenn sie dort ist - obwohl sie alle neuen Elemente an das gleiche übergeordnete Element anfügt und mir den flache Struktur.

Es ist wahrscheinlich ziemlich ineffizient, und könnte einige Optimierung verwenden - wie der Code in der Verbindung, die ich zur Verfügung stellte, baut es alles auf, wenn es eine Änderung gibt.

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

     }); 
    } 
    }; 
}); 

Die zweite ist ein bisschen hacky. Es ist auch ein Transceiver. Es sucht im DOM nach dem Attribut für die Tiefenklasse nach oben, analysiert den Tiefenwert und fügt sich selbst oft in das Elternobjekt ein. Es hängt also vollständig von der Tiefenklasse ab, die von der ersten Direktive festgelegt wurde. Es gibt wahrscheinlich bessere Möglichkeiten, dies zu tun. Ich habe mir auch keine Mühe gemacht, eine Uhr oder ähnliches aufzustellen, da dies rein kosmetischer Natur ist.

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

Es ist bei weitem nicht perfekt und ich werde einige Zeit damit verbringen müssen, es zu verbessern. Ich bin mir nicht einmal sicher, ob der HTML-Code, den ich produziere, der beste Weg ist, um das gewünschte Aussehen zu erhalten - aber es macht so, wie ich es möchte, während die HTML-Vorlage einigermaßen elegant aussieht.

Verwandte Themen