Search code examples
angularjsjsplumb

Angular bind draggable behaviour to element in directive the angular way


I'm using Angular and JSPlumb, I want to bind the draggable behaviour from jsplumb to my element in the directive (using element in the link function).

Current I do something like this (http://jsfiddle.net/r8epahbt/):

// In the controller I define a method to get the elements
// via jquery and then make them draggable
function MyCtrl($scope, $http, $log, $timeout) {
    $scope.makeWidgetsDraggable = function() {
        // ISSUE: I have to use jQuery here, how can I do it the Angular way?
        // I only want to make the "current" element draggable (it's wasteful to do ALL .widgets)
        // How can I make only THIS element (could be passed from directive below) draggable
        jsPlumb.draggable($('#canvas .widget'), { // Do this $('#canvas .widget') - the Angular Way
            containment: "parent"
        });
    };
}

// When the value of $scope.items changes, we call scope.makeWidgetDraggable
// which will get ALL widgets and make them draggable.
// I only want to make the newly created widget draggable
myApp.directive("widgetTemplate", function($parse, $timeout) {
    //...
    link: function (scope, element, attrs) {
        // Watch the `items` for change, if so (item added)
        // make the new element(s) draggable
        scope.$watch('items', function() {
            $timeout(function() {
                // [ISSUE] - This method uses jQuery to get `this` element (and all other elements)
                // How can I do this the `angular way` - I want to make `this` element draggable 
                // (the one that is being rendered by this directive)
                scope.makeWidgetsDraggable();

                // I want to do something like this:
                // But it Gives error: TypeError: Cannot read property 'offsetLeft' of undefined
                /*jsPlumb.draggable(element, {
                    containment: "parent"
                });*/
            }); // $timeout
        }); // $watch
    },// link
    //...
}

I thought something like this should work (in the link function in the directive):

// Gives me error (through JSPlumb Library):
// TypeError: Cannot read property 'offsetLeft' of undefined
jsPlumb.draggable(element, {
    containment: "parent"
});

I've made a working JSFiddle, if anyone could take a look I'd really appreciate it.

http://jsfiddle.net/r8epahbt/


So basically, I want to find a better way of doing line 11 (in the fiddle)

// I want to remove this jquery selector
jsPlumb.draggable($('#canvas .widget'),...)

// and do it the `Angular Way`
jsPlumb.draggable(element, ...)
// Doesn't work, gives me error:
// TypeError: Cannot read property 'offsetLeft' of undefined

Solution

  • You could move the $scope.makeWidgetsDraggable function to be part of the directive's link, and drop the controller from being coupled to the directive, since you're already passing the items via the directive's scope with =:

    Directive

    myApp.directive("widgetTemplate", function ($parse, $timeout) {
        return {
            restrict: "E",
            templateUrl: "widgetTemplate",
            replace: true,
            scope: {
                items: "="
            },
            link: function (scope, element, attrs) {
    
                scope.makeWidgetsDraggable = function () {
                    // ISSUE: I have to use jQuery here, how can I do it the Angular way?
                    // I only want to make the "current" element draggable (it's wasteful to do ALL .widgets)
                    // How can I make only THIS element (could be passed from directive line 46) draggable
                    jsPlumb.draggable(element, { // Do this $('#canvas .widget') - the Angular Way
                        containment: "parent"
                    });
                };
    
    
                // Watch the `items` for change, if so (item added), make the new element(s) draggable
                scope.$watch('items', function () {
                    $timeout(function () {
                        // [ISSUE] - This method uses jQuery to get `this` element
                        // How can I do this the `angular way` - I want to make `this` element draggable 
                        // (the one that is being rendered by this directive)
                        scope.makeWidgetsDraggable();
    
                        // I want to do something like this:
                        // But it Gives error: TypeError: Cannot read property 'offsetLeft' of undefined
                        /*jsPlumb.draggable(element, {
                            containment: "parent"
                        });*/
                    }); // $timeout
                }); // $watch
            }, // link
        }
    });
    

    Here's your working fiddle with the changes: http://jsfiddle.net/r8epahbt/1/

    EDIT

    Seems like I posted a bit earlier before and the solution wasn't working 100%, but I'm leaving that answer in case it might help someone else in a similar situation.

    My second suggestion, after seeing how element was pointing to a DOM comment, is to pass a single item to the directive, and have 1 directive instance per item. This way you don't have to watch the items for any changes and you don't need any timeouts:

    Change in view:

    <div id="canvas" class="canvas">
        <div class="absolute widget" ng-repeat="item in items" id="widget{{$index}}" data-wId="{{$index}}">
            <widget-template item="item"></widget-template>
        </div>
    </div>       
    

    Notice I removed the <span ng-init="fakeAjaxCall"></span> and moved it over to the controller.

    Directive

    myApp.directive("widgetTemplate", function ($parse, $timeout) {
        return {
            restrict: "E",
            templateUrl: "widgetTemplate",
            replace: true,
            scope: {
                item: "="
            },
            link: function (scope, element, attrs) {
                jsPlumb.draggable(element.parent(), { 
                    containment: "parent"
                }); 
    
                //no need to watch for items added since an instance of the directive is used for each element.
    
            }, // link
        }
    });
    

    Finally here's the updated jsfiddle, this time I made double sure it works. http://jsfiddle.net/r8epahbt/10/