ng-nestable icon indicating copy to clipboard operation
ng-nestable copied to clipboard

model change handler is not executed after sorting or nesting items

Open zahycs opened this issue 10 years ago • 6 comments

Hello after nesting or sorting items by dragging them ,I'm not able to add more items to ng-nestable,

i.e after this handler get executed , the model will never change , so the html is not rebuilt root.on('change', function () { $ngModel.$setViewValue(root.nestable('serialize')); $scope && $scope.$root && $scope.$root.$$phase || $scope.$apply(); }

Thanks

zahycs avatar Aug 18 '15 21:08 zahycs

I'm having the same problem. Did you end up fixing it yourself?

malevolm avatar Nov 04 '15 21:11 malevolm

Hello @malevolm

I tried to figure out how did I solve it , but I could not remember , but the version is working fine for me.

Please note that I've added a handle to the dragable element you may need to include this class in your CSS file

.dd3-item > button { margin-left: 40px; } .dd3-handle { position: absolute; margin: 0; left: 0; top: 0; cursor: pointer; width: 40px; text-indent: 100%; white-space: nowrap; overflow: hidden; border: 1px solid #ebebeb; background: #fff; border-top-right-radius: 0; border-bottom-right-radius: 0; } .dd3-handle:before { content: '≡'; display: block; position: absolute; left: 0; top: 10px; width: 100%; text-align: center; text-indent: 0; color: #ccc; font-size: 20px; font-weight: normal; } .dd3-handle:hover { background: #f7f7f7; }

/**
 * Angular nestable 0.0.1
 * Copyright (c) 2014 Kamil Pekala
 * https://github.com/kamilkp/ng-nestable
 */

/**
 * Sample output HTML
    <div class="dd">
        <ol class="dd-list">
            <li class="dd-item" data-id="1">
                <!-- item element -->
            </li>
            <li class="dd-item" data-id="2">
                <!-- item element -->
            </li>
            <li class="dd-item" data-id="3">
                <!-- item element -->
                <ol class="dd-list">
                    <li class="dd-item" data-id="4">
                        <!-- item element -->
                    </li>
                    <li class="dd-item" data-id="5">
                        <!-- item element -->
                    </li>
                </ol>
            </li>
        </ol>
    </div>
 */

/**
 * 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 '<button data-action="expand">Expand></button>')
                collapseBtnHTML : The HTML text used to generate a list item collapse button (default '<button data-action="collapse">Collapse</button>')

             */
            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(
                            { maxDepth: 2 },
                            $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 = $('<div class="dd"></div>');
                var rootList = $('<ol class="dd-list"></ol>').appendTo(root);
                model.forEach(function f(item){
                    var list = Array.prototype.slice.call(arguments).slice(-1)[0];
                    if(!(list instanceof $)) list = rootList;

                    var listItem = $('<li class="dd-item dd3-item"></li>');
                    var listHandle = $('<div  class="dd-handle dd3-handle">Drag</div>')
                    var listElement = $('<div ng-nestable-item class="dd3-content"></div>');
                    listHandle.appendTo(listItem);
                    listElement.append(tpl).appendTo(listItem);
                    list.append(listItem);
                    listItem.data('item', item.item);
                    if(isArray(item.children) && item.children.length > 0){
                        var subRoot = $('<ol class="dd-list"></ol>').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);

zahycs avatar Nov 05 '15 04:11 zahycs

I just changed $watchCollection to $watch and passed the third parameter as true.

AbdoDabbas avatar Jan 08 '16 21:01 AbdoDabbas

I tried both of ways. but nothing changed :( does anyone have absolute answer?

duylddev avatar Aug 25 '16 02:08 duylddev

You will notice above if(model && element.is(':empty') is now if (model) to allow all changes to run the formatters once you have updated the array.

cation avatar Dec 05 '16 11:12 cation

Is there an official solution?

softwarenacho avatar Aug 24 '17 17:08 softwarenacho