Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can i apply a draggable directive to bootstrap modal using angularJS?

I am using bootstrap modal in my Angular Application, it works fine. I need to make it draggable and resizeable, so i have defined a directive. The issue now is it getting applied to the content inside the modal window, hence the modal window becomes transparent.

how can i assign the draggable directive to the modal window when opening the window? Here is the code,

HTML:

<div ng-controller="CustomWidgetCtrl">
    <div class="box-header-btns pull-right" style="top:10px" >
        <a title="settings" ng-click="openSettings(widget)"><i class="glyphicon glyphicon-cog"></i></a>
</div>
</div>

App.js:

var routerApp = angular.module('DiginRt',  ['ui.bootstrap','ngRoute']);
routerApp.controller('CustomWidgetCtrl', ['$scope', '$modal',
  function($scope, $modal) {

    $scope.openSettings = function(widget) {
          $modal.open({
            scope: $scope,
            templateUrl: 'chart_settings.html',
            controller: 'chartSettingsCtrl',        
            resolve: {
              widget: function() {
                return widget;
              }
            }
          });
        };
    }
    ])

Chart Settings is another HTML page. Here is my Draggable directive.

UPDATE:

I have update the issue with Plunker

Issue: enter image description here

like image 708
Sajeetharan Avatar asked Jan 14 '15 11:01

Sajeetharan


3 Answers

I couldn't find a way to add the directive to the modal opened by ui-bootstrap, as it wraps the template with a modal-dialog..

So what i did is setting the events for drag to the modal-dialog itself(not the directive) using the following code.

I know it is not the best practice to add events to another element inside a directive but not a bad practice as well in cases like these, where you cant set a directive directly to the element.

the reason doing this is because ui-bootstrap doesnt provide a way to add a directive to the modal-dialog on modal.open function

here is the code to be put at the start of the directive:

element= angular.element(document.getElementsByClassName("modal-dialog"));

and the plunkr

like image 95
Naeem Shaikh Avatar answered Nov 16 '22 13:11

Naeem Shaikh


I upvoted @Naeem_Shaikh's answer which is basically an improvement on the standard angular directive.

However, there is another problem with the standard angular draggable directive that I have been able to fix.

The standard directive imposes draggability on the whole dialog. On the one hand, this is what we want. We want to drag the whole dialog. But this has unfortunate side effect that clicks in various edit fields in the dialog do not work: the default behavior is prevented! Somehow buttons have been coded in bootstrap to overcome this but not text edit fields. I'm guessing that the authors weren't considering my use case. But dialogs are more than just buttons!

It's tricky since it is the whole dialog that needs to be dragged, but you only want drags initiated by clicks in the header "hotspot". In other words, the hotspot to initiate dragging is a subset of the area to be dragged.

Naeem's fix enabled me to get it to work so that only clicks in the header initiated drags. Without his fix, the coordinate conversion was getting confused.

function clickedWithinHeader(event) {
    var target = event.currentTarget;
    var hotspot = null;
    var hotspots = target.getElementsByClassName("modal-header");
    if (hotspots.length > 0) {
        hotspot = hotspots.item(0);
    }
    if (hotspot !== null) {
        var eY = event.clientY;
        var tOT = target.offsetTop;
        var y = eY - tOT;
        var hH = hotspot.offsetHeight;
        // since the header occupies the full width across the top
        // no need to check X.  Note that this assumes the header
        // is on the top, which should be a safe assumption
        var within = (y <= hH);
        return within;
    } else {
        return true;
    }
}


// Draggable directive from: http://docs.angularjs.org/guide/compiler
// Modified so that only clicks in the dialog header trigger drag behavior.
// Otherwise clicking on dialog widgets did not give them focus.
angular.module('drag', []).directive('draggable', function($document) {
"use strict";
return function(scope, element) {
    var startX = 0, startY = 0, x = 0, y = 0;
    element= angular.element(document.getElementsByClassName("modal-dialog"));
    element.css({
        position : 'fixed',
        cursor : 'move',
    });
    element.on('mousedown', function(event) {
//      // OK, where did they touch?  Only want to do this
//      // when they clicked on the header.
        if (!clickedWithinHeader(event)) {
            return;
        }
        // Prevent default dragging of selected content
        event.preventDefault();
        ...

Note that the clickedWithinHeader() logic only works with Naeem's improvement. There may be a better way to do it, but this works. Only clicks in the header initiate dragging and clicks elsewhere do what they're supposed to do.

However, that wasn't the whole answer since the standard directive also imposed the move cursor over the whole dialog which is very disconcerting, even if, or especially if you can't drag where it appears.

Removing that from the element.css in the directive:

element.css({
    position : 'fixed',
});

and tying the move cursor only to the modal header using CSS

.modal-header 
{
    cursor: move;
}

provides a complete solution.

like image 38
Steve Cohen Avatar answered Nov 16 '22 14:11

Steve Cohen


I incorporated the various code fragments here and came up with a simpler solution. This one doesn't rely on detecting if the click event happened in the modal-header, because the mousedown event is bound to the header specifically.

This solution also doesn't rely on searching the entire DOM for the modal-dialog class, as it searches the parents for the modal-dialog element. This will allow you to have multiple dialogs on screen and only one be draggable.

I also created a gist that has checking to prevent the dialog from being moved outside the visible bounds.

(function (angular) {
    "use strict";

    angular.module('MyModule')
        .directive('modalDraggable', ['$document', modalDraggable]);

    function modalDraggable($document) {
        return function (scope, element) {
            var startX = 0,
                startY = 0,
                x = 0,
                y = 0;

            var draggable = angular.element(element.parents('.modal-dialog')[0]);

            draggable.find('.modal-header')
                .css('cursor', 'move')
                .on('mousedown', function (event) {
                    // Prevent default dragging of selected content
                    event.preventDefault();
                    startX = event.screenX - x;
                    startY = event.screenY - y;

                    $document.on('mousemove', mousemove);
                    $document.on('mouseup', mouseup);
                });

            function mousemove(event) {
                y = event.screenY - startY;
                x = event.screenX - startX;

                draggable.css({
                    top: y + 'px',
                    left: x + 'px'
                });
            }

            function mouseup() {
                $document.unbind('mousemove', mousemove);
                $document.unbind('mouseup', mouseup);
            }
        };
    }
}(window.angular));
like image 2
Dan Morphis Avatar answered Nov 16 '22 12:11

Dan Morphis