3

私はAngularが初めてで、どうやって物事を行うかについて頭を悩ませようとしています。

REST API サービスへの ajax 呼び出しによってページの読み込み時に入力されるリストを作成しようとしています。そのリストの要素は、それらの要素の下にある入力されたサブリストをクリックすると、同じサービスへの ajax 呼び出しを起動します。深さ n まで同様です。

リストの初期設定は簡単です。コントローラーが ajax 呼び出しを行い、JSON オブジェクトを取得してスコープに割り当て、DOM を ng-repeat ディレクティブで処理します。その後のサブリストの読み込みに問題があります。

jQuery では、必要なパラメーターを取得し、JSON 出力を取得して HTML に解析し、イベントを発生させた要素の後にその HTML を追加する、適切に分類された各要素に関連付けられた関数を onClick 経由でクリックします。これは直接 DOM 操作であるため、Angular の異端です。

私はすでにこの質問をここで見ましたが、この「Angularの方法」のようなものを実装する方法はまだよくわかりません。

ヘルプ?

編集:再帰ディレクティブを作成することで、この問題を解決しました。ここからの手順: http://jsfiddle.net/alalonde/NZum5/light/ .

コード:

    var myApp = angular.module('myApp',[]);

    myApp.directive('uiTree', function() {
      return {
        template: '<ul class="uiTree"><ui-tree-node ng-repeat="node in tree"></ui-tree-node></ul>',
        replace: true,
        transclude: true,
        restrict: 'E',
        scope: {
          tree: '=ngModel',
          attrNodeId: "@",
          loadFn: '=',
          expandTo: '=',
          selectedId: '='
        },
        controller: function($scope, $element, $attrs) {
            $scope.loadFnName = $attrs.loadFn;
          // this seems like an egregious hack, but it is necessary for recursively-generated
          // trees to have access to the loader function
          if($scope.$parent.loadFn)
            $scope.loadFn = $scope.$parent.loadFn;

          // TODO expandTo shouldn't be two-way, currently we're copying it
          if($scope.expandTo && $scope.expandTo.length) {
            $scope.expansionNodes = angular.copy($scope.expandTo);
            var arrExpandTo = $scope.expansionNodes.split(",");
            $scope.nextExpandTo = arrExpandTo.shift();
            $scope.expansionNodes = arrExpandTo.join(",");
          }
        }
      };
    })
    .directive('uiTreeNode', ['$compile', '$timeout', function($compile, $timeout) {
      return { 
        restrict: 'E',
        replace: true,
        template: '<li>' + 
          '<div class="node" data-node-id="{{ nodeId() }}">' +
            '<a class="icon" ng-click="toggleNode(nodeId())""></a>' +
            '<a ng-hide="selectedId" ng-href="#/assets/{{ nodeId() }}">{{ node.name }}</a>' +
            '<span ng-show="selectedId" ng-class="css()" ng-click="setSelected(node)">' + 
                '{{ node.name }}</span>' +
          '</div>' +
        '</li>',
        link: function(scope, elm, attrs) {
          scope.nodeId = function(node) {
            var localNode = node || scope.node;
            return localNode[scope.attrNodeId];          
          };
          scope.toggleNode = function(nodeId) {
            var isVisible = elm.children(".uiTree:visible").length > 0;
            var childrenTree = elm.children(".uiTree");
            if(isVisible) {
              scope.$emit('nodeCollapsed', nodeId);
            } else if(nodeId) {
              scope.$emit('nodeExpanded', nodeId);
            }
            if(!isVisible && scope.loadFn && childrenTree.length === 0) {
              // load the children asynchronously
              var callback = function(arrChildren) {
                scope.node.children = arrChildren;
                scope.appendChildren();
                elm.find("a.icon i").show();
                elm.find("a.icon img").remove();
                scope.toggleNode(); // show it
              };
              var promiseOrNodes = scope.loadFn(nodeId, callback);
              if(promiseOrNodes && promiseOrNodes.then) {
                promiseOrNodes.then(callback);
              } else {
                  $timeout(function() {
                      callback(promiseOrNodes);
                  }, 0);
              }
              elm.find("a.icon i").hide();
              var imgUrl = "http://www.efsa.europa.eu/efsa_rep/repository/images/ajax-loader.gif";
              elm.find("a.icon").append('<img src="' + imgUrl + '" width="18" height="18">');
            } else {
              childrenTree.toggle(!isVisible);
              elm.find("a.icon i").toggleClass("icon-chevron-right");
              elm.find("a.icon i").toggleClass("icon-chevron-down");
            }
          };

          scope.appendChildren = function() {
            // Add children by $compiling and doing a new ui-tree directive
            // We need the load-fn attribute in there if it has been provided
            var childrenHtml = '<ui-tree ng-model="node.children" attr-node-id="' + 
                scope.attrNodeId + '"';
            if(scope.loadFn) {
              childrenHtml += ' load-fn="' + scope.loadFnName + '"';
            }
            // pass along all the variables
            if(scope.expansionNodes) {
              childrenHtml += ' expand-to="expansionNodes"';
            }
            if(scope.selectedId) {
              childrenHtml += ' selected-id="selectedId"';
            }
            childrenHtml += ' style="display: none"></ui-tree>';
            return elm.append($compile(childrenHtml)(scope));
          };

          scope.css = function() {
            return { 
              nodeLabel: true,
              selected: scope.selectedId && scope.nodeId() === scope.selectedId
            };
          };
          // emit an event up the scope.  Then, from the scope above this tree, a "selectNode"
          // event is expected to be broadcasted downwards to each node in the tree.
          // TODO this needs to be re-thought such that the controller doesn't need to manually
          // broadcast "selectNode" from outside of the directive scope.
          scope.setSelected = function(node) {
            scope.$emit("nodeSelected", node);
          };
          scope.$on("selectNode", function(event, node) {
            scope.selectedId = scope.nodeId(node);
          });

          if(scope.node.hasChildren) {
            elm.find("a.icon").append('<i class="icon-chevron-right"></i>');
          }

          if(scope.nextExpandTo && scope.nodeId() == parseInt(scope.nextExpandTo, 10)) {
            scope.toggleNode(scope.nodeId());
          }
        }
      };
    }]);

    function MyCtrl($scope, $timeout) {
        $scope.assets = [
            { assetId: 1, name: "parent 1", hasChildren: true},
            { assetId: 2, name: "parent 2", hasChildren: false}
        ];
        $scope.selected = {name: "child 111"};
        $scope.hierarchy = "1,11";
        $scope.loadChildren = function(nodeId) {
            return [
                {assetId: parseInt(nodeId + "1"), name: "child " + nodeId + "1", hasChildren: true}, 
                {assetId: parseInt(nodeId + "2"), name: "child " + nodeId + "2"}
            ];
        }
        $scope.$on("nodeSelected", function(event, node) {
            $scope.selected = node;
            $scope.$broadcast("selectNode", node);
        });
    }

テンプレート:

    <div ng-controller="MyCtrl">
      <ui-tree ng-model="assets" load-fn="loadChildren" expand-to="hierarchy" selected-id="111" attr-node-id="assetId"></ui-tree>
        <div>selected: {{ selected.name }}</div> 
    </div>
4

1 に答える 1

1

これは、私が自分で使用するためにプロトタイプを作成したソリューションです。

https://embed.plnkr.co/PYVpWYrduDpLlsvto0wR/

リンクを更新しました

于 2013-11-18T15:32:52.807 に答える