Skip to content

Instantly share code, notes, and snippets.

@markleusink
Last active May 7, 2024 07:59
Show Gist options
  • Save markleusink/7af171d5f17e7dc9714e69965fdabab9 to your computer and use it in GitHub Desktop.
Save markleusink/7af171d5f17e7dc9714e69965fdabab9 to your computer and use it in GitHub Desktop.
Angular directive to make ngx-bootstrap modals draggable
import { Directive, ElementRef, HostListener, AfterViewInit } from '@angular/core';
/*
* Directive to add 'drag' support to Ngx Bootstrap modals (https://github.com/valor-software/ngx-bootstrap).
* Based on this library to enable drag support for an ng-bootstrap modal: https://github.com/mattxu-zz/ngb-modal-draggable
*
* Enable by adding the directive to the modal-header element, e.g.:
*
* <div class="modal-header" ngxModalDraggable> </div>
*/
@Directive({
selector: '[ngxModalDraggable]'
})
export class NgxModalDraggableDirective implements AfterViewInit {
private modalElement: HTMLElement;
private topStart: number;
private leftStart: number;
private isDraggable: boolean;
private handleElement: HTMLElement;
constructor(public element: ElementRef) {
}
ngAfterViewInit() {
let element = this.element.nativeElement;
//only make the modal header draggable
this.handleElement = this.element.nativeElement;
//change cursor on the header
this.handleElement.style.cursor = 'pointer';
//get the modal parent container element: that's the element we're going to move around
for (let level = 3; level > 0; level--) {
element = element.parentNode;
}
this.modalElement = element;
this.modalElement.style.position = 'relative';
}
@HostListener('mousedown', ['$event'])
onMouseDown(event: MouseEvent) {
if (event.button === 2 || (this.handleElement && event.target !== this.handleElement)) {
return; // prevents right click drag
}
//enable dragging
this.isDraggable = true;
//store original position
this.topStart = event.clientY - Number(this.modalElement.style.top.replace('px', ''));
this.leftStart = event.clientX - Number(this.modalElement.style.left.replace('px', ''));
event.preventDefault();
}
@HostListener('document:mouseup', ['$event'])
onMouseUp(event: MouseEvent) {
this.isDraggable = false;
}
@HostListener('document:mousemove', ['$event'])
onMouseMove(event: MouseEvent) {
if (this.isDraggable) {
//on moving the mouse, reposition the modal
this.modalElement.style.top = (event.clientY - this.topStart) + 'px';
this.modalElement.style.left = (event.clientX - this.leftStart) + 'px';
}
}
@HostListener('document:mouseleave', ['$event'])
onMouseLeave(event: MouseEvent) {
this.isDraggable = false;
}
}
@MichalJakubeczy
Copy link

MichalJakubeczy commented Feb 5, 2021

Great code. Helped a lot. I would suggest one improvement:

I have changed onMouseDown in a following way:

    public onMouseDown(event: MouseEvent) {
        if (event.button === 2 || !this.handleElement) {
            return; // prevents right click drag or initialized handleElement
        }

        if (event.target !== this.handleElement && !this.searchParentNode(<any>event.target, this.handleElement)) {
            return; // prevents dragging of other elements than children of handleElement
        }

        //enable dragging
        this.isDraggable = true;

        //store original position
        this.topStart = event.clientY - Number(this.modalElement.style.top.replace('px', ''));
        this.leftStart = event.clientX - Number(this.modalElement.style.left.replace('px', ''));
        event.preventDefault();
    }

And declare method searchParentNode:

    private searchParentNode(element: Node, tag: Node): Node {
        while (element.parentNode) {
            element = element.parentNode;
            if (element === tag) {
                return element;
            }
        }

        return null;
    }

This way you're able to activate drag also on children elements of modal-header which is useful if you put more elements than a simple text to a header.

@syedahmad9
Copy link

In case, when you have no 'modal-header' class and you have to put draggable directive to some element inside 'modal-content'. like in my case i have to put on my Title div. Use following snippet to achieve your goal.

//get the modal parent container element: that's the element we're going to move around
// for (let level = 3; level > 0; level--) {
// element = element.parentNode;
// }
element = element.closest('.modal');

@mohammedfahimullah
Copy link

Instead of using element = element.closest(.modal); We can use element = element.closest('.modal-content'); Because in my case .closest(.modal) returned the class with modal-header.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment