zoukankan      html  css  js  c++  java
  • Angular 自定义拖拽指令

    指令

    组件是一种带模版的指令。指令是超级。

    结构型指令(改变布局)和属性型指令(改变外观和行为)。

    Renderer2和ElementRef

    Angular不提倡直接操作DOM

    对于DOM的操作应该通过Renderer2进行。

    ElementRef是指向DOM元素的引用

    拖拽指令实例

    1、新建directive module

    $ ng g m directive
    CREATE src/app/directive/directive.module.ts (193 bytes)

    2, 在指令文件夹下的drag-drop文件夹里新建一个drag和一个drop

    $ ng g d directive/drag-drop/drag
    $ ng g d directive/drag-drop/drop

    3,改一下selector

    selector: '[appDrag]'改为
    selector: '[app-draggable]'
    selector: '[appDrop]'改为
    selector: '[app-droppable]'

    4,在SharedModule中导入DirectiveModule

    import { NgModule } from "@angular/core";
    import { CommonModule } from "@angular/common";
    import { MaterialModule } from "../material/material.module";
    import { ConfirmDialogComponent } from "./confirm-dialog/confirm-dialog.component";
    import { DirectiveModule } from '../directive/directive.module';
    
    @NgModule({
      imports: [CommonModule, MaterialModule, DirectiveModule],
      exports: [CommonModule, MaterialModule, DirectiveModule],
      declarations: [ConfirmDialogComponent],
      entryComponents: [ConfirmDialogComponent]
    })
    export class SharedModule { }

    5,把DirectiveModule中多余的CommonDodule删掉,然后把Drag和Drop两个指令导出

    import { NgModule } from '@angular/core';
    import { DragDirective } from './drag-drop/drag.directive';
    import { DropDirective } from './drag-drop/drop.directive';
    
    @NgModule({
      declarations: [DragDirective, DropDirective],
      exports: [DragDirective, DropDirective],
    })
    export class DirectiveModule { }

    6,drag指令

    使用@HostListener监听dragstart事件和dragend事件。

    使用ElementRef获取元素。

    使用Renderer2修改样式。

    draggedClass是一个输入型参数。所以selector 为selector: '[app-draggable][draggedClass]'。

    app-draggable设置为true就可以拖拽,为false不可拖拽。

      private _isDraggble = false;
    
      set isDraggable(value){
        this._isDraggble=value;
      }
      get isDraggable(){
        return this._isDraggble;
      }

    给set方法加上@Input(app-draggable)。 这样在调用app-draggable= "true"的时候会调用set方法。

    import { Directive, HostListener, Host, ElementRef, Renderer2, Input } from '@angular/core';
    
    @Directive({
      selector: '[app-draggable][draggedClass]'
    })
    export class DragDirective {
    
      private _isDraggble = false;
    
      @Input('app-draggable')
      set isDraggable(value:boolean){
        this._isDraggble=value;
        this.rd.setAttribute(this.el.nativeElement,'draggable',`${value}`);
      }
      get isDraggable(){
        return this._isDraggble;
      }
      @Input()
      draggedClass:string;
      constructor(private el:ElementRef, private rd:Renderer2) { }
    
      @HostListener('dragstart', ['$event'])
      ondragstart(ev:Event){
        //判断drag元素是不是指令应用的元素发起的
        if(this.el.nativeElement===ev.target){
          this.rd.addClass(this.el.nativeElement, this.draggedClass);//往el上增加一个class
        }
    
      }
    
      @HostListener('dragend', ['$event'])
      ondragend(ev:Event){
        if(this.el.nativeElement===ev.target){
          this.rd.removeClass(this.el.nativeElement, this.draggedClass);
        }
      }
    }

    在task-item组件中使用

    //红色dash虚线半透明
    .drag-start
    { opacity: 0.5; border: #ff525b dashed 2px; }
    <mat-list-item class="container" [@item]="widerPriority" [ngClass]="{
      'priority-normal':item.priority===3,
      'priority-important':item.priority===2,
      'priority-emergency':item.priority===1
    }"
      [app-draggable]="true"
      [draggedClass]=" 'drag-start' "
      (click)="onItemClick()">
      ......
    </mat-list-item>

    7,drop指令

    drop要监听dragenter,dragover,dragleave,drop四个事件。

    需要改变自己的css。即拖放区域的css。变暗。

    import { Directive, HostListener, ElementRef, Renderer2, Input  } from '@angular/core';
    
    @Directive({
      selector: '[app-droppable][dragEnterClass]'
    })
    export class DropDirective {
    
      @Input()
      dragEnterClass:string;
      constructor(private el:ElementRef, private rd:Renderer2) { }
    
      @HostListener('dragenter', ['$event'])
      onDragEnter(ev:Event){
        //判断drag元素是不是指令应用的元素发起的
        if(this.el.nativeElement===ev.target){
          this.rd.addClass(this.el.nativeElement, this.dragEnterClass);//往el上增加一个class
        }
    
      }
    
      @HostListener('dragover', ['$event'])
      onDragOver(ev:Event){
        //判断drag元素是不是指令应用的元素发起的
        if(this.el.nativeElement===ev.target){
          
        }
    
      }
    
      @HostListener('dragleave', ['$event'])
      onDragLeave(ev:Event){
        //判断drag元素是不是指令应用的元素发起的
        if(this.el.nativeElement===ev.target){
          this.rd.removeClass(this.el.nativeElement, this.dragEnterClass);//往el上增加一个class
        }
    
      }
    
      @HostListener('drop', ['$event'])
      onDrop(ev:Event){
        //判断drag元素是不是指令应用的元素发起的
        if(this.el.nativeElement===ev.target){
          this.rd.removeClass(this.el.nativeElement, this.dragEnterClass);//往el上增加一个class
        }
    
      }
    }
    View Code

    在task-home中使用drop指令

    .drag-enter{
      background-color: dimgray;
    }
      <app-task-list *ngFor="let list of lists" 
      class="list-container"
      app-droppable="true"
      [dragEnterClass]=" 'drag-enter' "
      >
        ......
      </app-task-list>

     至此,问题是不能多重拖拽(list-item和list都能拖拽,区分不开。)和携带数据。

    8、解决多重拖拽和携带数据的问题

    创建一个service

    $ ng g s directive/drag-drop

    在DirectiveModule中providers里声明一下

    import { NgModule } from '@angular/core';
    import { DragDirective } from './drag-drop/drag.directive';
    import { DropDirective } from './drag-drop/drop.directive';
    import { DragDropService } from './drag-drop.service';
    
    @NgModule({
      declarations: [DragDirective, DropDirective],
      exports: [DragDirective, DropDirective],
      providers:[DragDropService]
    })
    export class DirectiveModule { }

    drag-drop.service.ts

    import { Injectable } from '@angular/core';
    import { Observable,BehaviorSubject } from 'rxjs';
    
    //数据结构
    export interface DragData{
      tag:string; //多重拖拽的话是哪一级拖拽,用户自己保证唯一性,不能重复
      data:any; //传递的数据
    }
    
    
    @Injectable({
      providedIn: 'root'
    })
    export class DragDropService {
      //用BehaviorSubject总能记住上一次的值
      private _dragData = new BehaviorSubject<DragData>(null);
    
      setDragData(data:DragData){
        this._dragData.next(data);
      }
    
      getDragData():Observable<DragData>{
        return this._dragData.asObservable();
      }
    
      clearDragData(){
        this._dragData.next(null);
      }
      constructor() { }
    }

    9,更新drag

    在drag的时候需要多定义一个属性dragTag,

    constructor中注入新的DragDropService,

    在开始拖拽的时候就给service set上数据,这里需要多定义一个dragData。import { Directive, HostListener, Host, ElementRef, Renderer2, Input } from '@angular/core';import { DragDropService } from '../drag-drop.service';

    
    @Directive({
      selector: '[app-draggable][dragTag][dragData][draggedClass]'
    })
    export class DragDirective {
    
      private _isDraggble = false;
    
      @Input('app-draggable')
      set isDraggable(value:boolean){
        this._isDraggble=value;
        this.rd.setAttribute(this.el.nativeElement,'draggable',`${value}`);
      }
      get isDraggable(){
        return this._isDraggble;
      }
      @Input()
      draggedClass:string;
    //多定义一个dragTag @Input() dragTag:string;
    //给DragDropservice传递的数据 @Input() dragData:any constructor( private el:ElementRef, private rd:Renderer2,
    //注入DragDropService private service:DragDropService) { } @HostListener(
    'dragstart', ['$event']) ondragstart(ev:Event){ //判断drag元素是不是指令应用的元素发起的 if(this.el.nativeElement===ev.target){ this.rd.addClass(this.el.nativeElement, this.draggedClass);//往el上增加一个class
    //进入时候给service传递上数据
    this.service.setDragData({tag:this.dragTag,data:this.dragData}); } } @HostListener('dragend', ['$event']) ondragend(ev:Event){ if(this.el.nativeElement===ev.target){ this.rd.removeClass(this.el.nativeElement, this.draggedClass); } } }

    10,更新drop

    对于drop来讲,它的tags是一个数组,而不是字符串了。

    因为拖放放的区域,可能会支持多个拖的区域

    所以放的时候原来的判断都有问题,首先需要判断这个拖拽是不是你能够接收的。

    建立一个私有的data$,在constructor里订阅data。

    drop指令还需要一个Output,因为需要什么时候drop。

    import { Directive, HostListener, ElementRef, Renderer2, Input, Output, EventEmitter  } from '@angular/core';
    import { DragDropService, DragData } from '../drag-drop.service';
    import { take } from 'rxjs/operators';
    
    @Directive({
      selector: '[app-droppable][dropTags][dragEnterClass]'
    })
    export class DropDirective {
    
      @Output()
      dropped = new EventEmitter<DragData>();
      @Input()
      dragEnterClass:string;
      @Input()
      dropTags:string[] = [];
      private data$;
    
      constructor(
        private el:ElementRef, 
        private rd:Renderer2,
        private service:DragDropService) {
          this.data$ = this.service.getDragData().pipe(
            take(1));
         }
    
      // @HostListener('dragenter', ['$event'])
      // onDragEnter(ev:Event){
      //   //判断drag元素是不是指令应用的元素发起的
      //   if(this.el.nativeElement===ev.target){
      //     this.rd.addClass(this.el.nativeElement, this.dragEnterClass);//往el上增加一个class
      //   }
      // }
      @HostListener('dragenter', ['$event'])
      onDragEnter(ev:Event){
        //判断drag元素是不是指令应用的元素发起的
        if(this.el.nativeElement===ev.target){
          this.data$.subscribe(dragData=>{
            if(this.dropTags.indexOf(dragData.tag)>-1){
              this.rd.addClass(this.el.nativeElement, this.dragEnterClass);//往el上增加一个class
            }
          });
        }
      }
    
    
      // @HostListener('dragover', ['$event'])
      // onDragOver(ev:Event){
      //   //判断drag元素是不是指令应用的元素发起的
      //   if(this.el.nativeElement===ev.target){ 
      //   }
      // }
      //dragover允许进行data transfer的一些特效
      @HostListener('dragover', ['$event'])
      onDragOver(ev:Event){
        //需要支持多级拖拽,所以要防止事件冒泡
        ev.preventDefault(); 
        ev.stopPropagation();
        //判断drag元素是不是指令应用的元素发起的
        if(this.el.nativeElement===ev.target){ 
          this.data$.subscribe(dragData=>{
            if(this.dropTags.indexOf(dragData.tag)>-1){
              this.rd.setProperty(ev,'dataTransfer.effectAllowed','all');
              this.rd.setProperty(ev,'dataTransfer.fropEffect','move');
            }else{
              this.rd.setProperty(ev,'dataTransfer.effectAllowed','none');
              this.rd.setProperty(ev,'dataTransfer.dropEffect','none');
            }
          });
        }
      }
    
      @HostListener('dragleave', ['$event'])
      onDragLeave(ev:Event){
        ev.preventDefault();
        ev.stopPropagation();
        //判断drag元素是不是指令应用的元素发起的
        if(this.el.nativeElement===ev.target){
          this.data$.subscribe(dragData=>{
            if(this.dropTags.indexOf(dragData.tag)>-1){
              this.rd.removeClass(this.el.nativeElement, this.dragEnterClass);//往el上增加一个class
            }
          });
        }
    
      }
    
      @HostListener('drop', ['$event'])
      onDrop(ev:Event){
        ev.preventDefault();
        ev.stopPropagation();
        //判断drag元素是不是指令应用的元素发起的
        if(this.el.nativeElement===ev.target){
          this.data$.subscribe(dragData => {
            if(this.dropTags.indexOf(dragData.tag)>-1){
              this.rd.removeClass(this.el.nativeElement, this.dragEnterClass);//往el上增加一个class
              this.dropped.emit(dragData);//drop的时候把dragData发射出去
              this.service.clearDragData(); //drop的时候把data clear掉,否则会影响下一次拖拽
            }
          });
        }
    
      }
    }

    11,改造模版调用

    对于taskItem

    <mat-list-item class="container" [@item]="widerPriority" [ngClass]="{
      'priority-normal':item.priority===3,
      'priority-important':item.priority===2,
      'priority-emergency':item.priority===1
    }"
      [app-draggable]= "true"
      [dragTag]= "'task-item'"
      [draggedClass]=" 'drag-start' "
      [dragData]="item"
      (click)= "onItemClick()">

    对于taskHome

    既能drag又能drop

    此外还要处理一个dropped事件

    <div class="task-list">
      <app-task-list *ngFor="let list of lists" 
      class="list-container"
      app-droppable="true"
      [dropTags]="['task-item','task-list']"
      [dragEnterClass]=" 'drag-enter' "
      [app-draggable]="true"
      [dragTag]=" 'task-list' "
      [draggedClass]=" 'drag-start' "
      [dragData]="list"
      (dropped)="handleMove($event,list)"
      >
      handleMove(srcData,List){
        switch (srcData.tag) {
          case 'task-item':
            console.log('handling item');
            break;
          case 'task-list':
            console.log('handling list');
          default:
            break;
        }
      }

     最终效果:

  • 相关阅读:
    关于HTTP协议,一篇就够了
    jvm在什么情况下会执行GC
    为什么我们做分布式使用Redis?
    linux入门系列
    linux学习笔记-13.进程控制
    linux学习笔记-12.输入输出重定向及管道
    app获取自己的签名
    安卓给微信公众号发消息
    微信扫码下载apk
    设备通道开启关闭状态
  • 原文地址:https://www.cnblogs.com/starof/p/10662027.html
Copyright © 2011-2022 走看看