AngularJS - Как сделать перетаскиваемое дерево?

Я хочу создать древовидную структуру, где пользователь может перетаскивать листья. У меня есть отправная точка следующим образом:

HTML

<div ng:controller="controller">
  <ul ui-sortable ng-model="items" ui-options="{connectWith: '.item'}" class="item">
    <li ng-repeat="item in items" class="item">
      {{ item.name }}
      <ul ui-sortable ng-model="item.children" ui-options="{connectWith: '.item'}" class="item">
        <li ng-repeat="item in item.children" class="item">{{ item.name }}</li>
      </ul>
    </li>
  </ul>

  <pre>{{ items | json }}</pre>
</div>

<script src="http://code.angularjs.org/1.0.2/angular.min.js"></script>
<script src="https://raw.github.com/angular-ui/angular-ui/master/build/angular-ui.min.js"></script>

CoffeeScript

myapp = angular.module 'myapp', ['ui']

myapp.controller 'controller', ($scope) ->

    $scope.items = [
      {id: 1, name: 'Item 1', children: [
        {id: 5, name: 'SubItem 1.1', children: [
          {id: 11, name: 'SubItem 1.1.1', children: []},
          {id: 12, name: 'SubItem 1.1.2', children: []}
        ]},
        {id: 6, name: 'SubItem 1.2', children: []}
      ]},
      {id: 2, name: 'Item 2', children: [
        {id: 7, name: 'SubItem 2.1', children: []},
        {id: 8, name: 'SubItem 2.2', children: []}
        {id: 9, name: 'SubItem 2.3', children: []}
      ]},
      {id: 3, name: 'Item 3', children: [
        {id: 10, name: 'SubItem 3.1', children: []}
      ]}
    ]

angular.bootstrap document, ['myapp']

Код также находится в этом JSFiddle: http://jsfiddle.net/bESrf/1/

В моем "реальном" коде вместо одного уровня для детей я извлек второй <ul> в шаблон и отображал его рекурсивно, что прекрасно работает, но я не смог найти способ сделать это в JSFiddle.

Что было бы лучшим способом сделать это рекурсивно и все же позволить перетаскивание, которое изменило бы массив объектов и подобъектов, представленных ng-моделью?

3 ответа

Решение

Посмотрите на этот пример: http://jsfiddle.net/furf/EJGHX/

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

Вам нужно будет использовать несколько вещей:

  1. ezTree директива - визуализировать дерево
  2. Вложенный подключаемый модуль Manuele J Sarfatti для jQuery UI
  3. (необязательно) uiNestedSortable директива - включить nestedSortable из вашего шаблона.
  4. код контроллера для обновления вашей модели - обратитесь к $scope.update

С использованием ezTree директива

Учитывая рекурсивную структуру данных:

$scope.data = {
  children: [{
    text: 'I want to create a tree like structure...',
    children: [{
      text: 'Take a look at this example...',
      children: []
    }]
  }]
};

Этот шаблон создаст дерево:

<ol>
  <li ez-tree="child in data.children at ol">
    <div>{{item.text}}</div>
    <ol></ol>
  </li>
</ol>

ez-tree выражение должно быть написано как item in collection at selector где item это повторяющийся ребенок (аля ng-repeat), collection является коллекцией корневого уровня, и selector CSS-селектор для узла внутри шаблона, где директива должна повторяться Имя свойства терминала в коллекции, в данном случае children будет использоваться для рекурсии дерева, в этом случае child.children, Это может быть переписано, чтобы быть настраиваемым, но я оставлю это как упражнение для читателя.

С помощью uiNestedSortable директива

<ol ui-nested-sortable="{ listType: 'ol', items: 'li', doNotClear: true }"
  ui-nested-sortable-stop="update($event, $ui)">
</ol>

ui-nested-sortable атрибут должен содержать конфигурацию JSON для плагина nestedSortable. Плагин требует, чтобы вы указали listType а также items, Мое решение требует, чтобы doNotClear быть true, Назначьте обратные вызовы для событий, используя ui-nested-sortable-*eventName*, Моя директива предоставляет необязательные аргументы $ event и $ ui для обратных вызовов. Обратитесь к документации nestedSortable для других опций.

Обновление вашей модели

Есть более чем один способ снять кожу с этой кошки. Вот мой. В событии stop он извлекает дочернее свойство области действия элемента, чтобы определить, какой объект был перемещен, дочернее свойство родительской области действия элемента, чтобы определить назначение объекта, и положение элемента, чтобы определить положение объекта. в пункте назначения. Затем он обходит структуру данных, удаляет объект из исходного положения и вставляет его в новое положение.

$scope.update = function (event, ui) {

  var root = event.target,
    item = ui.item,
    parent = item.parent(),
    target = (parent[0] === root) ? $scope.data : parent.scope().child,
    child = item.scope().child,
    index = item.index();

  target.children || (target.children = []);

  function walk(target, child) {
    var children = target.children,
      i;
    if (children) {
      i = children.length;
      while (i--) {
        if (children[i] === child) {
          return children.splice(i, 1);
        } else {
          walk(children[i], child)
        }
      }
    }
  }
  walk($scope.data, child);

  target.children.splice(index, 0, child);
};

Попробуйте Angular-NestedSortable, это плагин Angularjs, который может сортировать вложенные списки и связывать данные, и ему не нужно зависеть от jQuery. https://github.com/jimliu/Angular-NestedSortable

Небольшое редактирование скрипки от furf, чтобы она работала в IE.

IE выдает ошибку на insertNode, когда второй аргумент имеет значение null, поэтому в этом случае вместо него используется appendNode.

http://jsfiddle.net/michieljoris/VmtfR/

if (!cursor) parentNode.appendChild(cached.element);
else parentNode.insertBefore(cached.element, cursor);

Плагин Nested Sortable встроен в js, потому что IE выдает несоответствие типов MIME при включении из github.

Другие вопросы по тегам