/** * Angular nestable 0.0.1 * Copyright (c) 2014 Kamil Pekala * https://github.com/kamilkp/ng-nestable */ /** * Sample output HTML
*/ /** * Sample model object [ { item: {}, children: [] }, { item: {}, children: [ { item: {}, children: [] } ] }, { item: {}, children: [] } ] */ ;(function(window, document, angular, undefined){ angular.module('ng-nestable', []) .provider('$nestable', function(){ var modelName = '$item'; var defaultOptions = {}; this.$get = function(){ return { modelName: modelName, defaultOptions: defaultOptions }; }; /** * Method to set model variable for nestable elements * @param {[string]} value */ this.modelName = function(value){ modelName = value; }; /** * Method to set default nestable options * @param {[object]} value * You can change the follow options: maxDepth : number of levels an item can be nested (default 5) group : group ID to allow dragging between lists (default 0) listNodeName : The HTML element to create for lists (default 'ol') itemNodeName : The HTML element to create for list items (default 'li') rootClass : The class of the root element .nestable() was used on (default 'dd') listClass : The class of all list elements (default 'dd-list') itemClass : The class of all list item elements (default 'dd-item') dragClass : The class applied to the list element that is being dragged (default 'dd-dragel') handleClass : The class of the content element inside each list item (default 'dd-handle') collapsedClass : The class applied to lists that have been collapsed (default 'dd-collapsed') placeClass : The class of the placeholder element (default 'dd-placeholder') emptyClass : The class used for empty list placeholder elements (default 'dd-empty') expandBtnHTML : The HTML text used to generate a list item expand button (default '') collapseBtnHTML : The HTML text used to generate a list item collapse button (default '') */ this.defaultOptions = function(value){ defaultOptions = value; }; }) .directive('ngNestable', ['$compile', '$nestable', function($compile, $nestable){ return { restrict: 'A', require: 'ngModel', compile: function(element){ var itemTemplate = element.html(); element.empty(); return function($scope, $element, $attrs, $ngModel){ var options = $.extend( {}, $nestable.defaultOptions, $scope.$eval($attrs.ngNestable) ); $scope.$watchCollection(function(){ return $ngModel.$modelValue; }, function(model){ if(model){ /** * we are running the formatters here instead of watching on $viewValue because our model is an Array * and angularjs ngModel watcher watches for "shallow" changes and otherwise the possible formatters wouldn't * get executed */ model = runFormatters(model, $ngModel); // TODO: optimize as rebuilding is not necessary here var root = buildNestableHtml(model, itemTemplate); $element.empty().append(root); $compile(root)($scope); root.nestable(options); root.on('change', function(){ $ngModel.$setViewValue(root.nestable('serialize')); $scope && $scope.$root && $scope.$root.$$phase || $scope.$apply(); }); } }); }; }, controller: angular.noop }; function buildNestableHtml(model, tpl){ var root = $('
'); var rootList = $('
    ').appendTo(root); model.forEach(function f(item){ var list = Array.prototype.slice.call(arguments).slice(-1)[0]; if(!(list instanceof $)) list = rootList; var listItem = $('
  1. '); var listElement = $('
    '); listElement.append(tpl).appendTo(listItem); list.append(listItem); listItem.data('item', item.item); if(isArray(item.children) && item.children.length > 0){ var subRoot = $('
      ').appendTo(listItem); item.children.forEach(function(item){ f.apply(this, Array.prototype.slice.call(arguments).concat([subRoot])); }); } }); return root; } function isArray(arr){ return Object.prototype.toString.call(arr) === '[object Array]'; } function runFormatters(value, ctrl){ var formatters = ctrl.$formatters, idx = formatters.length; ctrl.$modelValue = value; while(idx--) { value = formatters[idx](value); } return value; } }]) .directive('ngNestableItem', ['$nestable', function($nestable){ return { scope: true, require: '^ngNestable', link: function($scope, $element){ $scope[$nestable.modelName] = $element.parent().data('item'); } }; }]); })(window, document, window.angular);