【问题标题】:AngularJS directive to render a JSON tree as indented tableAngularJS 指令将 JSON 树呈现为缩进表
【发布时间】:2017-07-03 12:11:50
【问题描述】:

我想在 Angular 中渲染一个多列树(即树表或树网格)。我的数据是任意深度的树,看起来像这样:

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

我见过使用递归模板在 ul/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>
...
...

因此,换句话说,它只是一个平面表,其中树的深度使用多个 span 元素表示,该数字等于层次结构中的那个级别。

一般来说,我对 html/AngularJS/js 不是很好,所以我不知道这是否是呈现我的数据的最佳方式,但它看起来确实是我想要的方式。

我的问题是,使用 AngularJS 的最佳方法是什么?我知道我可以在 JavaScript 中构建一个像这样的整体 html 块,然后将其包装在自定义指令中,但我正在寻找一种更符合 Angular 精神的解决方案,以便我可以利用数据绑定等。我最终希望单元格是可编辑的,所以我不想做任何会使数据绑定混乱的事情。

我想编写一些代码来将我的树扁平化为明确包含“深度”属性的对象列表。那么也许我可以使用 ng-repeat。但我认为也许有一种使用一个或多个自定义指令的更清洁的方法

谢谢!

【问题讨论】:

  • 有什么理由需要这张桌子吗?您可以尝试扩展像 angular-json-tree 这样的指令以允许编辑
  • 我需要多列。为简洁起见,我没有在示例中展示它们。
  • 您希望通过额外的列实现什么目标? :) 用于编辑/删除/复制的按钮?如果是这样,您可能可以使用 ui-popover 或模态来管理相同的操作
  • 我希望其他列始终可见,例如 maxazan.github.io/jquery-treegrid。我将使用它们进行编辑/删除等,但那里也会显示数据。
  • 在思考/阅读了更多之后,我现在想我有一个如何做到这一点的计划。我需要的是像 ng-repeat 这样的自定义转发器,但不同之处在于它将递归地遵循“item in items”表达式,并构建一个平面表,其中每一行都包含一个“depth”属性。然后希望我可以使用嵌套在每一行中的另一个线性中继器,它使用深度属性来创建用于填充的跨度。在我真正完成它之前,我还有一些学习要做......

标签: recursion angularjs-directive flatten treetable


【解决方案1】:

我现在有一些东西可以使用。它以我在上面对我的问题的评论中描述的方式工作。回顾一下,我使用了 2 个自定义指令,它们都基于 ng-repeat。第一个构建一个平面表结构,在类属性中使用文字值来指示深度。第二个指令使用该深度信息将填充元素重复适当的次数。两个转发器都使用嵌入来最小化对 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 示例。我的不同之处在于您可以指定一个“子”属性,并且指令逻辑将检查每个项目上的该属性,如果它存在则递归地下降到它 - 尽管它将所有新元素附加到同一个父级,给我扁平结构。

这可能效率很低,并且可以使用一些优化 - 就像我提供的链接中的代码一样,它会在发生更改时重建所有内容。

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

        });
    }
    };
});

第二个有点hacky。它也是一个穿插式中继器。它在 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 模板看起来相当优雅。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-23
    • 1970-01-01
    • 2017-01-03
    • 1970-01-01
    • 1970-01-01
    • 2013-02-18
    相关资源
    最近更新 更多