Draggable Bootstrap modal with AngularJS in TypeScript

by

This short post will demonstrate how to make a Bootstrap Modal draggable. I will use the UI Bootstrap library as a base for the modal construction and add a custom directive to make it draggable. I will write the directive in TypeScript, so if you are unfamiliar with this, check it out at the TypeScript website. I will not go in to details on the modal instantiation part here, just the draggable directive.

I’m going to be using the good old Angular Directive. As I want my draggable directive to be available as an attribute, I can not make use of the simpler component construct that was included in AngularJS 1.5, so we stick to a standard directive.

Here is a CodePen showing it in action.

See the Pen Draggable Bootstrap Modal with AngularJS in TypeScript by Bjørn Sørensen (@Crevil) on CodePen.light

The draggable directive

The directive needs to handle the following flow of events.

  1. A drag is initiated with a mousedown event
  2. The modal is dragged around with mousemove events
  3. The drag is stopped with a mouseup event

Further more, it needs to remember where the modal is relative to its initial position and calculate the new position subtracting the position of the initiating click. I also wan’t to specify a CSS tag on the “handle” element where the drag can be initiated, e.g. <div draggable=".header"></div>. You might have noticed that the modals header is the handle in the CodePen. Let’s get to it.

First I create a basic TypeScript class to hold all the logic. In the constructor I initialize the two positions and get a reference to the wrapping modal frame. I also modify this elements CSS property position to relative to make it moveable. At last I check to see if a handle selector is supplied. If not I use the whole element as a handle. At last I add a move pointer to the handle and attached the mouse down event listener. The interface  IPosition holds the x and y properties and  IDraggableScope is extending  ng.IScope to hold the handle property.

I’ve created a couple of helper methods to set the positions, as I will need to do this several places.

Next up is the actual drag handling. As shown in line 41, I want to call the method beginDrag when a mouse down event is fired. This method should save the cursor position and setup event listeners for the mousemove and mouseup events on the document. Notice that I save the references to the event listeners on two private fields on the class for later use.

On the  drag method that is fired on every mousemove event. This method should calculate the new position of the modal, save it and move the modal element.

At last, on a mouseup event I need to detach the event listeners. This is done in the endDrag method.

This is it. Well, almost. This class is of no use, if we don’t register it with the directive in Angular. Notice line 8 here where I get the attribute value as a string.

The modal markup

I can now use this directive in my modal markup like this.

Complete example

Here is the complete TypeScript directive to show you the grand overview. You could all so head over to our GitHub and watch to full source. It’s right here on GitHub.

Let me know what you think in the comments below or reach out on @Bjorn_Sorensen.

  • rd

    Great post! Unfortunately the modal dialog may be dragged outside the client area which causes the browser to show scroll bars.
    Any quick idea of how to fix it, so the dialog can only be dragged within the client area?

    • I can’t come up with anything better than limiting the position coordinates according to the screen size.
      To do this properly you need to know the modal size as well.
      Some calculation in the beginDrag method could then set the limits according to the initial position of the drag event.
      Let me know it you come up with something from this and I will add it to the tutorial.
      Otherwise I might get my hands dirty and try doing something my self. 🙂
      // Bjørn

      • rd

        Meanwhile I’ve used a much easier solution using the Draggable Widget from jQuery UI.

        The HTML snippet/sample:

        myTitle
        myBody
        myFooter

        Using the open method on the IModalService, I get “modalInstance” as result.

        modalInstance.rendered.then(() => {
        const content = $(“.my-Container”).parent(“.modal-content”);
        const bounds = content[0].getBoundingClientRect();

        (content as any).draggable({
        cursor : “move”,
        handle : “.modal-header”,
        drag : (event, ui) =>
        {
        // Don’t allow to move the modal window outside the visible boundary.
        let x = ui.position.left;
        let y = ui.position.top;

        if (x < 0 && bounds.left < -x)
        x = -bounds.left;

        if (y < 0 && bounds.top 0 && (bounds.right + x) > window.innerWidth)
        x = window.innerWidth – bounds.right;

        if (y > 0 && (bounds.bottom + y) > window.innerHeight)
        y = window.innerHeight – bounds.bottom;

        ui.position.left = x;
        ui.position.top = y;
        }
        });
        });