Manojkumar
Manojkumar

Reputation: 1361

ngBootstrap Angular 8 - Modal Resizable and Draggable

I am trying to use the bootstrap https://ng-bootstrap.github.io/#/components/modal/examples version 4 with Angular 8. I want to make modal re-sizable and draggable. I see a few examples for other versions like 3.xx but not with angular - http://jsfiddle.net/GDVdN/

Any references for Bootstrap4 with ANgular 8 - Modal resizable + draggable?.

Upvotes: 3

Views: 3387

Answers (1)

Eliseo
Eliseo

Reputation: 57971

The most closer I get is create a component that makes "resizable" any element inside.

Update: "play" with styles of "modal-dialog"

Imagine a component like

<div class="resizable" [ngStyle]="style">
  <ng-content></ng-content>
  <div class="cell-border-top"></div>
  <div class="cell-border-bottom"></div>
  <div class="cell-border-left"></div>
  <div class="cell-border-right"></div>
  <div class="cell-top-right"></div>
  <div class="cell-bottom-right"></div>
  <div class="cell-top-left"></div>
  <div class="cell-bottom-left"></div>
</div>

the .css make that the divs was a position in left, right, top, bottom and in the four corners

we listen mouseDown, and "getting" the className we can store in a variable the type of drag

export enum TypeDrag {
  Move,
  Top,
  Bottom,
  Left,
  Right,
  TopRight,
  BottomRight,
  TopLeft,
  BottomLeft
}

When mouse down we subscribe to mouseUp and to mouseMove, a mouseUp simple remove the subscription to mouseMove

The mouseMove change the style of the div to change the position and dimensions

We need indicate as Input an ElementRef to drag the "modal"

There're and addicional consideration that is that the ngb-modal place the modal changing the "margin-left" and "margin-top", so I need style the margin to 0 to a div with calssName=="modal-dialog". For this, we create a recursive function

To get the "modal-dialog" we use a recursive function

findModalContent(element:HTMLElement)
{
  return element.className=="modal-dialog"?element:
            element.parentElement?this.findModalContent(element.parentElement):
            null
}

I try to explain with comments in the code

And as usually this is the stackblitz

@Component({
  selector: 'angular-window',
  templateUrl: './angular-window.component.html',
  styleUrls: ['./angular-window.component.css']
})
export class AngularWindowComponent implements OnInit {
  rect: any;
  incr: number[] = [0, 0, 0, 0];
  nativeElement: any;
  typeDrag: TypeDrag;
  origin: any;
  onDrag: boolean = false;
  moveSubscription: any;
  //div: any;  <--remove in the updated

  classNames = [
    'cell-top',
    'cell-border-top',
    'cell-border-bottom',
    'cell-border-left',
    'cell-border-right',
    'cell-top-right',
    'cell-bottom-right',
    'cell-top-left',
    'cell-bottom-left'
  ];

  style: any = null;
  constructor(private elementRef: ElementRef) {}

  @Input() set dragHolder(value) { //the drag holder will be a 
                                   //template reference variable
                                   //we add the class "cell-top"

    value.classList.add("cell-top");
  }

  /*It's not necesary now
  //I need indicate the background-color
  @Input('background-color') backgroundColor = 'white';
  */

  ngOnInit(): void {

    //get the "modalContent"
        this.modalContent=this.findModalContent(this.elementRef.nativeElement)

    //we subscribe to mouseDown
    fromEvent(this.elementRef.nativeElement, 'mousedown')
      .pipe(
        //we filter, only get if the className of element 
        //is one of the indicate by the variable "classNames"
        //or if the className include the "cell-top"

        filter((event: MouseEvent) => {
          const classs = (event.target as any).className;
          if (classs && typeof classs === 'string') {
            const className = classs.split(' ');
            return className.indexOf("cell-top")>=0?true:
              this.classNames.indexOf(classs) >= 0;
          }
          return false;
        })
      )
      .subscribe((event: MouseEvent) => {

        this.div = this.elementRef.nativeElement.childNodes[0];
        this.rect = this.div.getBoundingClientRect();
        this.origin = { x: event.screenX, y: event.screenY };

        this.onDrag = true;

        const className = (event.target as any).className.split(' ');
        this.typeDrag =className.indexOf('cell-top')>=0?TypeDrag.Move:
         (this.classNames.indexOf(className[0])) as TypeDrag;

        //acording the typeDrag, I store in "this.incr" the move
          
        this.incr =
          this.typeDrag == TypeDrag.Move
            ? [1, 0, 1, 0]
            : this.typeDrag == TypeDrag.Top
            ? [1, -1, 0, 0]
            : this.typeDrag == TypeDrag.Bottom
            ? [0, 1, 0, 0]
            : this.typeDrag == TypeDrag.Right
            ? [0, 0, 0, 1]
            : this.typeDrag == TypeDrag.Left
            ? [0, 0, 1, -1]
            : this.typeDrag == TypeDrag.TopRight
            ? [1, -1, 0, 1]
            : this.typeDrag == TypeDrag.TopLeft
            ? [1, -1, 1, -1]
            : this.typeDrag == TypeDrag.BottomRight
            ? [0, 1, 0, 1]
            : [0, 1, 1, -1];

        this.onDrag = true;

        /*Not necesary
        //remove the "margin" in modal-dialog
        const modalContent=this.findModalContent(this.div.parentElement)
        if (modalContent)
          modalContent.style.margin=0;
        */

        //we subscribe to mouseUp    
        fromEvent(document, 'mouseup')
          .pipe(take(1))
          .subscribe(() => {
            if (this.moveSubscription) {
              this.moveSubscription.unsubscribe();
              this.moveSubscription = undefined;
              this.onDrag = false;
            }
          });

        //we subscribe to mouseMove

        if (!this.moveSubscription) {
          this.moveSubscription = fromEvent(document, 'mousemove').pipe(
            startWith({screenY:this.origin.y,screenX:this.origin.x})
          ).subscribe(
            (moveEvent: MouseEvent) => {
              const incrTop = moveEvent.screenY - this.origin.y;
              const incrLeft = moveEvent.screenX - this.origin.x;
              const width = this.rect.width + this.incr[3] * incrLeft;
              const heigth = this.rect.height + this.incr[1] * incrTop;
              /*before
              this.style = {
                position: 'absolute',
                'z-index': 1051,
                'background-color': this.backgroundColor,
                top: this.rect.top + this.incr[0] * incrTop + 'px',
                height: (heigth < 75 ? 75 : heigth) + 'px',
                left: this.rect.left + this.incr[2] * incrLeft + 'px',
                width: (width < 50 ? 50 : width) + 'px'
              };
              */
              //now:
              this.modalContent.style['max-width']=
                        (width < 50 ? 50 : width) + 'px'
              this.modalContent.style['margin-top']=
                        this.rect.top + this.incr[0] * incrTop + 'px'
              this.modalContent.style['margin-left']=
                        this.rect.left + this.incr[2] * incrLeft + 'px'
              this.style={
                 width:(width < 50 ? 50 : width-1) + 'px',
                 height:(heigth < 75 ? 75 : heigth-1) + 'px'
              }
      });
  }
}

the use is simple,e.g. (see how indicate the "dragHolder")

<ng-template #content let-modal>
  <angular-window [dragHolder]="header">
    <div class="modal-header">
      <h4 #header class="modal-title w-100" 
           id="modal-basic-title">Profile update</h4>
    </div>
    <div class="modal-body">
    </div>
    <div class="modal-footer">
    </div>
  </angular-window>
</ng-template>

NOTE: To change the cursor in dragHolder we need add the class

.cell-top {
  cursor: move;
}

Upvotes: 6

Related Questions