Angular Drag and Drop (ドラッグアンドドロップ) ディレクティブの概要
Ignite UI for Angular Drag and Drop ディレクティブは、ページの要素のドラッグを有効にします。サポートされている機能には自由自在のドラッグ、ドラッグ ハンドルの使用、ゴーストのドラッグ、アニメーション、および複数のドロップ ストラテジが含まれています。
Angular Drag and Drop の例
アイコンをドラッグ アンド ドロップして位置を変更します。
import { NgModule } from "@angular/core" ;
import { FormsModule } from "@angular/forms" ;
import { BrowserModule } from "@angular/platform-browser" ;
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { AppComponent } from "./app.component" ;
import {
IgxDragDirective,
IgxDropDirective,
IgxDragDropModule,
IgxDialogModule
} from "igniteui-angular" ;
import { IconsSampleComponent } from "./drag-drop/icons-sample/icons-sample.component" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
IconsSampleComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxDragDropModule,
IgxDialogModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー import { Component } from '@angular/core' ;
@Component ({
selector : 'app-icons-sample' ,
styleUrls : ['./icons-sample.component.scss' ],
templateUrl : './icons-sample.component.html'
})
export class IconsSampleComponent {
public dragIconId: number ;
public dropTileId: number ;
public icons = [
{
id : 0 , url : 'https://www.infragistics.com/angular-demos-lob/assets/images/drag-drop/profile.png'
},
{
id : 1 , url : 'https://www.infragistics.com/angular-demos-lob/assets/images/drag-drop/calendar.png'
},
{
id : 2 , url : 'https://www.infragistics.com/angular-demos-lob/assets/images/drag-drop/mail.png'
},
{
id : 3 , url : 'https://www.infragistics.com/angular-demos-lob/assets/images/drag-drop/photos.png'
},
{
id : 4 , url : 'https://www.infragistics.com/angular-demos-lob/assets/images/drag-drop/videos.png'
},
{
id : 5 , url : 'https://www.infragistics.com/angular-demos-lob/assets/images/drag-drop/cloud.png'
},
{
id : 6 , url : 'https://www.infragistics.com/angular-demos-lob/assets/images/drag-drop/map.png'
},
{
id : 7 , url : 'https://www.infragistics.com/angular-demos-lob/assets/images/drag-drop/contacts.png'
},
{
id : 8 , url : 'https://www.infragistics.com/angular-demos-lob/assets/images/drag-drop/chat.png'
}
];
public onIconDropped (ev ) {
ev.drag.dropFinished();
}
public onEnterHandler(ev): void {
this .dropTileId = parseInt (ev.owner.element.nativeElement.id, 10 );
if (this .dragIconId === this .dropTileId) {
return ;
}
const dragIndex = this .icons.findIndex((iconObj ) => iconObj.id === this .dragIconId);
const dropIndex = this .icons.findIndex((iconObj ) => iconObj.id === this .dropTileId);
this .swapIcons(dragIndex, dropIndex);
}
public dragStartHandler(id: number ): void {
this .dragIconId = id;
}
public dragEndHandler (dragRef: HTMLElement ) {
dragRef.style.visibility = 'visible' ;
}
public ghostCreateHandler (dragRef: HTMLElement ) {
dragRef.style.visibility = 'hidden' ;
}
private swapIcons (dragIndex: number , dropIndex: number ) {
const tempObj = this .icons[dragIndex];
this .icons.splice(dragIndex, 1 );
this .icons.splice(dropIndex, 0 , tempObj);
}
}
ts コピー <div class ="icons_wrapper" >
<ng-container *ngFor ="let icon of icons" >
<div class ="drag_container"
igxDrag
igxDrop
#dragRef
id ="{{icon.id}}"
(dropped )="onIconDropped($event)"
(dragStart )="dragStartHandler(icon.id)"
(ghostCreate )="ghostCreateHandler(dragRef)"
(dragEnd )="dragEndHandler(dragRef)"
(enter )="onEnterHandler($event)"
[ghostClass ]="'drag_ghost_class'" >
<span class ="icon_container" draggable ="false" onmousedown ="if (event.preventDefault) event.preventDefault()" >
<img [src ]="icon.url" />
</span >
</div >
</ng-container >
</div >
html コピー .icons_wrapper {
display : flex;
width : 300px ;
flex-direction : row;
flex-wrap : wrap;
margin : 20px ;
}
.drag_container {
padding : 0px ;
width : 100px ;
height : 100px ;
display :flex;
justify-content : center;
align-items : center;
position : relative;
}
.icon_container {
-moz-user-select: none;
padding : 1px ;
position : relative;
& img {
position : relative;
z-index : 2 ;
width : 100% ;
height : 100% ;
}
&:after {
content: '' ;
background : transparent;
position : absolute;
top : 50% ;
left :50% ;
width :0px ;
height :0px ;
z-index : 1 ;
box-shadow : 0px 3px 30px 20px #000 ;
}
}
.drag_ghost_class {
width : 110px ;
height : 110px ;
}
scss コピー
このサンプルが気に入りましたか? 完全な Ignite UI for Angularツールキットにアクセスして、すばやく独自のアプリの作成を開始します。無料でダウンロードできます。
Ignite UI for Angular Drag and Drop コンポーネントを使用した作業の開始
Ignite UI for Angular Drag & Drop ディレクティブを使用した作業を開始するには、Ignite UI for Angular をインストールする必要があります。既存の Angular アプリケーションで、以下のコマンドを入力します。
ng add igniteui-angular
cmd
Ignite UI for Angular については、「はじめに 」トピックをご覧ください。
次に、app.module.ts ファイルに IgxDragDropModule
をインポートします。
...
import { IgxDragDropModule } from 'igniteui-angular' ;
@NgModule ({
...
imports : [..., IgxDragDropModule],
...
})
export class AppModule {}
typescript
あるいは、16.0.0
以降、IgxDragDirective
と IgxDropDirective
をスタンドアロンの依存関係としてインポートすることも、IGX_DRAG_DROP_DIRECTIVES
トークンを使用してコンポーネントとそのすべてのサポート コンポーネントおよびディレクティブをインポートすることもできます。
import { IGX_DRAG_DROP_DIRECTIVES } from 'igniteui-angular' ;
@Component ({
selector : 'app-home' ,
template : `
<div igxDrag>Drag me</div>
<div igxDrop>Drop here</div>
` ,
styleUrls : ['home.component.scss' ],
standalone : true ,
imports : [IGX_DRAG_DROP_DIRECTIVES]
})
export class HomeComponent {}
typescript
Ignite UI for Angular Drag and Drop モジュールまたはディレクティブをインポートしたので、igxDrag
と igxDrop
ディレクティブの使用を開始できます。
Angular Drag ディレクティブの使用
Angular アプリケーション内の要素をある場所から他の場所へドラッグするには、igxDrag
ディレクティブを使用します。igxDrop
ディレクティブと組み合わせてドラッグした要素の配置やインタラクティブなアプリケーションを作成できます。
ドラッグの基本
ユーザーが 5px いずれかの方向へスワイプするとドラッグ操作を開始します。これはカスタマイズ可能であり、dragTolerance
入力を使用して変更できます。そうでない場合は、インタラクションがクリックとして見なされ、dragClick
イベントがトリガーします。
ドラッグが開始されると、dragStart
イベントがトリガーされます。実際の移動が発生しないようにするには、cancel
プロパティを true
に設定してイベントをキャンセルできます。
移動が実行される前に、ポインターの最後と次の位置を含む dragMove
イベントもトリガーされます。要素のドラッグ時に動きが検出されるたびにトリガーされます。
ユーザーがマウス/タッチをリリースした後、ドラッグ ゴースト要素が DOM から削除され、dragEnd
が発生されます。
dragMove
イベントの性質上、短い期間で何度もトリガーされる可能性があり、トリガーされたときに実行される複雑な操作のパフォーマンスの問題が発生することがあります。
ゴーストでドラッグします
igxDrag
ディレクティブは、テンプレートに追加して DOM 要素に適用できます。
<div igxDrag > Drag me</div >
html
igxDrag
ディレクティブのデフォルト動作では、ベース要素を変更せずに残し、エンドユーザーがドラッグ操作を実行した場合ゴースト要素を作成します。
ページにゴーストがレンダリングされる前に、追加しようとしているゴースト要素の情報を含む ghostCreate
イベントがトリガーされます。このイベントは、dragStart
イベントの直後にトリガーされます。dragStart
がキャンセルされた場合、ゴーストは作成されず、それに応じて ghostCreate
イベントはトリガーされません。
ゴーストが削除される直前に、ゴーストの ghostDestroy
イベントがトリガーされます。
ゴーストのカスタマイズ
デフォルトのゴースト要素は、igxDrag
が使用されるベース要素のコピーです。ghostTemplate
入力へのテンプレート参照を直接提供することでカスタマイズできます。
<div class ="email-content flexlist"
igxDrag
[ghostTemplate ]="customGhost" >
<div class ="sender" >
{{email.sender}}
</div >
<div class ="email-title" >
{{email.title}}
</div >
</div >
<ng-template #customGhost >
<div class ="dragGhost" >
<igx-icon fontSet ="material" > email</igx-icon >
Moving {{ draggedElements }} item{{ (draggedElements > 1 ? 's' : '')}}
</div >
</ng-template >
html
ゴーストなしのドラッグ
igxDrag
ディレクティブが適用されるベース要素を移動したい場合、ghost
入力を false
に設定することができます。それにより、余分なゴースト要素はレンダリングされず、要素をドラッグするときにカスタム スタイル設定を適用する必要がある場合は、ベース要素に直接適用できます。
<div igxDrag [ghost ]="false" > Drag me</div >
html
ハンドルを使用したドラッグ
デフォルトで、要素全体がそのアクションの実行に使用されるため、ドラッグする igxDrag
の子である要素を指定できます。igxDragHandle
ディレクティブを使用して実行し、igxDrag
内の複数の要素に適用できます。
使用方法
<div
igxDrag
[ghost ]="false"
[dragTolerance ]="0"
(dragMove )=onDragMove($event) >
<igx-icon igxDragHandle fontSet ="material" class ="dialogHandle" > drag_indicator</igx-icon >
<div class ="igx-dialog__window" >
</div >
</div >
html
以下のデモでは、ハンドルを使用してダイアログをドラッグします。
import { NgModule } from "@angular/core" ;
import { FormsModule } from "@angular/forms" ;
import { BrowserModule } from "@angular/platform-browser" ;
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { AppComponent } from "./app.component" ;
import {
IgxDragDropModule,
IgxDialogModule,
IgxIconModule,
IgxButtonModule,
IgxToggleModule,
NoOpScrollStrategy,
ConnectedPositioningStrategy,
GlobalPositionStrategy
} from "igniteui-angular" ;
import { DragDialogSampleComponent } from "./drag-drop/dialog-sample/drag-dialog-sample.component" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
DragDialogSampleComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxDragDropModule,
IgxDialogModule,
IgxIconModule,
IgxButtonModule,
IgxToggleModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー import { Component, ElementRef, ViewChild } from '@angular/core' ;
import {
GlobalPositionStrategy,
IgxDragDirective,
IgxDragLocation,
IgxToggleDirective,
NoOpScrollStrategy,
OverlaySettings
} from 'igniteui-angular' ;
@Component ({
selector : 'app-drag-dialog-sample' ,
templateUrl : './drag-dialog-sample.component.html' ,
styleUrls : ['./drag-dialog-sample.component.scss' ]
})
export class DragDialogSampleComponent {
@ViewChild ('toggleForm' , { static : true })
public toggleForm: IgxToggleDirective;
@ViewChild ('toggleForm' , { read : IgxDragDirective, static : true })
public toggleFormDrag: IgxDragDirective;
@ViewChild ('dialogButton' , { static : true })
public buttonElement: ElementRef;
public toggleStartPageX;
public toggleStartPageY;
private overlaySettings: OverlaySettings = {
positionStrategy : new GlobalPositionStrategy(),
scrollStrategy : new NoOpScrollStrategy(),
modal : true ,
closeOnOutsideClick : true
};
constructor ( ) { }
public openDialog ( ) {
this .toggleForm.open(this .overlaySettings);
if (!this .toggleStartPageX && !this .toggleStartPageY) {
this .toggleStartPageX = this .toggleFormDrag.pageX;
this .toggleStartPageY = this .toggleFormDrag.pageY;
}
this .toggleFormDrag.setLocation(new IgxDragLocation(this .toggleStartPageX, this .toggleStartPageY));
}
public onDragMove (e ) {
const deltaX = e.nextPageX - e.pageX;
const deltaY = e.nextPageY - e.pageY;
e.cancel = true ;
this .toggleForm.setOffset(deltaX, deltaY);
}
}
ts コピー <button #dialogButton igxButton ="raised" (click )="openDialog()" > Show Dialog</button >
<div class ="dialog"
igxToggle
#toggleForm ="toggle"
igxDrag
[ghost ]="false"
[dragTolerance ]="0"
(dragMove )=onDragMove($event) >
<igx-icon class ="dialog__icon" igxDragHandle > drag_indicator</igx-icon >
<span class ="dialog__title" > Draggable Dialog</span >
<span class ="dialog__content" > Grab the handle icon to drag.</span >
</div >
html コピー @use '../../../variables' as *;
@include dialog(
dialog-theme(
$schema : $schema
)
);
@include dialog-typography();
:host {
display : block;
margin : 16px ;
@include b(dialog) {
@extend %igx-dialog-window;
display : flex;
flex-direction : column;
max-width : rem(280px );
@include e(icon) {
position : absolute;
right : rem(24px );
top : rem(24px );
cursor : move;
}
@include e(title) {
@extend %igx-dialog-title;
}
@include e(content) {
@extend %igx-dialog-content;
}
}
}
scss コピー
アニメーション
要素がドラッグされている場合、デフォルトでニメーションは適用されません。
igxDrag
にトランジション アニメーションを適用できますが、ドラッグの終了時または要素が現在ドラッグされていないときの使用をお勧めします。これは、transitionToOrigin
および transitionTo
メソッドを使用して実現できます。
transitionToOrigin
メソッドは、その名前が示すように、現在ドラッグされている要素または要素のゴーストを、ドラッグが開始された開始位置へアニメーション化します。transitionTo
メソッドは、ページ (pageX
および pageY
など) に関連する特定の位置に要素をアニメーション化します。または、指定された要素の位置をアニメーション化します。要素が現在ドラッグされていない場合は、アニメーション化するか、ゴーストを作成して目的の位置にアニメーション化します。
両方の関数には、トランジション アニメーションをカスタマイズし、期間、タイミング関数、または遅延を設定するために設定できる引数があります。特定の開始位置が設定されている場合、そこから要素をアニメーション化します。
トランジション アニメーションが終了すると、ゴーストが作成される場合、ゴーストは削除され、igxDrag
ディレクティブは初期状態に戻ります。ゴーストが作成されていない場合、位置を維持します。いずれの場合もアニメーションの持続時間に基づいて、transitioned
イベントがトリガーされます。アニメーションが適用されていない場合、ただちにトリガーされます。
要素の変換を処理する他のアニメーションを作成できます。これは、Angular Animations または CSS Animations を使用して、ベース igxDrag
要素/そのゴーストのいずれかに他の要素と同様に実行できます。ゴーストに適用する場合は、カスタムゴ ーストを定義し、その要素にアニメーションを適用する必要があります。
ドラッグ ハンドルを使用してリスト内の要素をソートします。リスト要素のドラッグ時は、他のリスト要素がアニメーションでソートされます。
import { NgModule } from "@angular/core" ;
import { FormsModule } from "@angular/forms" ;
import { BrowserModule } from "@angular/platform-browser" ;
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { AppComponent } from "./app.component" ;
import {
IgxDragDirective,
IgxDropDirective,
IgxIconModule,
IgxListModule,
IgxDragDropModule
} from "igniteui-angular" ;
import { ListReorderSampleComponent } from "./drag-drop/list-reorder-sample/list-reorder-sample.component" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
ListReorderSampleComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxIconModule,
IgxListModule,
IgxDragDropModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー import {
Component,
ElementRef,
QueryList,
ViewChild,
ViewChildren
} from '@angular/core' ;
import {
IDragBaseEventArgs,
IDragMoveEventArgs,
IgxDragDirective,
IgxDragLocation
} from 'igniteui-angular' ;
@Component ({
selector : 'app-list-reorder-sample' ,
templateUrl : './list-reorder-sample.component.html' ,
styleUrls : ['./list-reorder-sample.component.scss' ]
})
export class ListReorderSampleComponent {
@ViewChildren ('dragDirRef' , { read : IgxDragDirective })
public dragDirs: QueryList<IgxDragDirective>;
@ViewChild ('listContainer' , { read : ElementRef })
public listContainer: ElementRef;
public employees = [
{ id : 0 , name : 'Ivan Cornejo' , title : 'Senior Product Owner' },
{ id : 1 , name : 'Amish Shiravadakar' , title : 'Business Tools Director' },
{ id : 2 , name : 'Elsi Hansdottir' , title : 'Financial Director' },
{ id : 3 , name : 'Benito Noboa' , title : 'Marketing Specialist' },
{ id : 4 , name : 'Beth Murphy' , title : 'Platform Lead for Web' }
];
public newIndex = null ;
public animationDuration = 0.3 ;
private listItemHeight = 55 ;
public getDragDirectiveRef(id: number ): IgxDragDirective {
return this .dragDirs.find((item ) => item.data.id === id);
}
public onDragStart (event: IDragBaseEventArgs, dragIndex: number ) {
this .newIndex = dragIndex;
event.owner.data.dragged = true ;
}
public onDragEnd (event: IDragBaseEventArgs, itemIndex: number ) {
if (this .newIndex !== null ) {
const moveDown = this .newIndex > itemIndex;
const prefix = moveDown ? 1 : -1 ;
const movedHeight = prefix * Math .abs(this .newIndex - itemIndex) * this .listItemHeight;
const originLocation = event.owner.originLocation;
event.owner.transitionTo(
new IgxDragLocation(originLocation.pageX, originLocation.pageY + movedHeight),
{ duration : this .animationDuration }
);
} else {
event.owner.transitionToOrigin({ duration : this .animationDuration });
}
}
public onTransitioned (event: IDragBaseEventArgs, itemIndex: number ) {
if (event.owner.data.dragged && this .newIndex != null && this .newIndex !== itemIndex) {
this .shiftElements(itemIndex, this .newIndex);
event.owner.setLocation(event.owner.originLocation);
this .newIndex = null ;
}
event.owner.data.dragged = false ;
}
public onDragMove (event: IDragMoveEventArgs, itemIndex: number ) {
const containerPosY = this .listContainer.nativeElement.getBoundingClientRect().top;
const relativePosY = event.nextPageY - containerPosY;
let newIndex = Math .floor(relativePosY / this .listItemHeight);
newIndex = newIndex < 0 ? 0 : (newIndex >= this .employees.length ? this .employees.length - 1 : newIndex);
if (newIndex === this .newIndex) {
return ;
}
const movingDown = newIndex > itemIndex;
if (movingDown && newIndex > this .newIndex ||
(!movingDown && newIndex < this .newIndex && newIndex !== itemIndex)) {
const elementToMove = this .getDragDirectiveRef(this .employees[newIndex].id);
const currentLocation = elementToMove.location;
const prefix = movingDown ? -1 : 1 ;
elementToMove.transitionTo(
new IgxDragLocation(currentLocation.pageX, currentLocation.pageY + prefix * this .listItemHeight),
{ duration : this .animationDuration }
);
} else {
const elementToMove = this .getDragDirectiveRef(this .employees[this .newIndex].id);
elementToMove.transitionToOrigin({ duration : this .animationDuration });
}
this .newIndex = newIndex;
}
private shiftElements (draggedIndex: number , targetIndex: number ) {
const movedElem = this .employees.splice(draggedIndex, 1 );
this .employees.splice(targetIndex, 0 , movedElem[0 ]);
this .dragDirs.forEach((dir ) => {
if (this .employees[targetIndex].id !== dir.data.id) {
dir.setLocation(dir.originLocation);
dir.data.shifted = false ;
}
});
}
}
ts コピー <igx-list #listContainer >
<igx-list-item *ngFor ="let employee of employees; index as targetIndex;"
#dragDirRef ="drag"
igxDrop
[igxDrag ]="{ id: employee.id, dragged: false }"
(dragStart )="onDragStart($event, targetIndex)"
(dragMove )="onDragMove($event, targetIndex)"
(dragEnd )="onDragEnd($event, targetIndex)"
(transitioned )="onTransitioned($event, targetIndex)"
[ghost ]="false"
[class.dragged ]="dragDirRef.data && dragDirRef.data.dragged" >
<h4 igxListLineTitle > {{employee.name}}</h4 >
<h6 igxListLineSubTitle > {{employee.title}}</h6 >
<igx-icon igxDragHandle igxListAction > drag_indicator</igx-icon >
</igx-list-item >
</igx-list >
html コピー @use '../../../variables' as *;
:host {
display : block;
margin : 16px ;
igx-list {
max-width : 336px ;
overflow : visible;
}
igx-list-item {
z-index : 0 ;
&.dragged {
box-shadow : elevation(8 );
z-index : 1 ;
}
}
igx-icon {
cursor : move;
user-select: none;
}
}
scss コピー
ドラッグ可能な要素を無視する
ユーザーが igxDrag をインスタンス化したメイン要素の操作可能な子を使用したい場合は、igxDragIgnore
ディレクティブを設定することにより、igxDrag からは無視され、ドラッグ アクションを実行しないようにすることができます。これにより、これらの要素は完全に操作可能になり、すべてのマウス イベントを受信します。
<div [igxDrag ]="myData" >
<span > Drag me!</span >
<igx-icon igxDragIgnore fontSet ="material" (click )="remove()" > bin</igx-icon >
</div >
html
Angular Drop ディレクティブの使用
igxDrag
ディレクティブを使用してドラッグされている要素を領域に配置する場合、igxDrop
を使用します。要素が適用される要素の境界に入ったかどうか、その後要素内でリリースされているかを決定するために使用できるイベントを提供します。
igxDrop
ディレクティブは、igxDrag
ディレクティブと同じように、任意の DOM 要素に適用できます。
デフォルトで、igxDrop
ディレクティブは DOM のドラッグされた要素の位置を変更するためのロジックを適用しません。そのため、dropStrategy
を指定するか、カスタム ロジックを適用する必要があります。ドロップ ストラテジについては、次のセクションで説明します。
ドロップ ストラテジ
igxDrop
には、Default
、Prepend
、Insert
および Append
の 4 つのドロップ ストラテジがあります。
ストラテジを適用する方法は、上記のクラスのいずれかに dropStrategy
入力を設定することです。igxDrop
はインスタンス自体を作成および管理する必要があるため、提供される値はインスタンスではなくタイプでなければなりません。
public appendStrategy = IgxAppendDropStrategy;
typescript
<div igxDrop [dropStrategy ]="appendStrategy" > </div >
html
ドロップ ストラテジのキャンセル
特定のドロップ ストラテジを使用する場合、cancel
プロパティを true に設定して dropped
イベントでその動作をキャンセルできます。Dropped
イベントは igxDrop
に固有です。igxDrop
にドロップ ストラテジを適用していない場合、イベントをキャンセルしても副作用はありません。
例:
<div igxDrag > </div >
<div igxDrop (dropped )="onDropped($event)" > </div >
html
public onDropped (event ) {
event.cancel = true ;
}
typescript
独自のドロップ ロジックを実装する場合は、dropped
イベントにバインドし、そこでロジックを実行するか、IgxDefaultDropStrategy
クラスの拡張をお勧めします。
ドラッグとドロップ要素のリンク
それぞれ igxDrag
および igxDrop
ディレクティブで dragChannel
および dropChannel
入力を使用すると、異なる要素をリンクして相互間にのみ操作できます。たとえば、特定の igxDrop
要素にドロップできるように igxDrag
要素を制約する必要があり、使用できない場合は、同じチャネルを割り当てることで簡単に実現できます。
例
<div igxDrag [dragChannel ]="['Mammals', 'Land']" > Human </div >
<div igxDrag [dragChannel ]="['Mammals', 'Water']" > Dolphin </div >
<div igxDrag [dragChannel ]="['Insects', 'Air']" > Butterfly </div >
<div igxDrag [dragChannel ]="['Insects', 'Land']" > Ant </div >
<div igxDrop [dropChannel ]="['Mammals']" > Mammals </div >
<div igxDrop [dropChannel ]="['Insects']" > Insects </div >
<div igxDrop [dropChannel ]="['Land']" > Land </div >
html
右側のメールを左側のフォルダーにドラッグします。
import { NgModule } from "@angular/core" ;
import { FormsModule } from "@angular/forms" ;
import { BrowserModule } from "@angular/platform-browser" ;
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { AppComponent } from "./app.component" ;
import {
IgxDragDirective,
IgxDropDirective,
IgxListModule,
IgxDragDropModule,
IgxIconModule,
IgxCheckboxModule
} from "igniteui-angular" ;
import { EmailSampleComponent } from "./drag-drop/email-sample/email-sample.component" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
EmailSampleComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxDragDropModule,
IgxIconModule,
IgxCheckboxModule,
IgxListModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー import { ChangeDetectorRef, Component, Input, Renderer2 } from '@angular/core' ;
@Component ({
selector : 'app-email-sample' ,
templateUrl : './email-sample.component.html' ,
styleUrls : ['./email-sample.component.scss' ]
})
export class EmailSampleComponent {
@Input ()
public ghostTemplate: any ;
public hasChecked = false ;
public draggedElements = 0 ;
public folders: any [] = [
{ icon : 'inbox' , text : 'Inbox' , dropChannel : 'inbox' },
{ icon : 'star_rate' , text : 'Starred' , dropChannel : 'starred' },
{ icon : 'error' , text : 'Important' , dropChannel : 'important' },
{ icon : 'send' , text : 'Sent' , dropChannel : 'sent' },
{ icon : 'label' , text : 'Personal' , dropChannel : 'personal' },
{ icon : 'label' , text : 'Work' , dropChannel : 'work' },
{ icon : 'label' , text : 'Finances' , dropChannel : 'finances' }
];
public emails: any [] = [
{ sender : 'Ivan Cornejo' , title : 'We have exciting news' , checked : false },
{ sender : 'Amish Shiravadakar' , title : 'RE: Document Libraries status' , checked : false },
{ sender : 'Elsi Hansdottir' , title : 'SEO Keywords' , checked : false },
{ sender : 'Benito Noboa' , title : 'Last Chance: Win an Amazon Gift Card' , checked : false }
];
constructor (
private cdr: ChangeDetectorRef,
private renderer: Renderer2
) { }
public toggleCheck(email: any , checkbox : any ): void {
this .emails.forEach(x => x.checked = false );
email.checked = true ;
checkbox.checked = true ;
}
public toggleCheckbox(email: any ): void {
email.checked = !email.checked;
}
public stopEventPropagation(event: any ): void {
event.stopPropagation();
}
public dropElement(event: any ): void {
this .emails = this .emails.filter(x => x.checked !== true );
event.dragData = {};
event.cancel = true ;
this .leaveDropZone(event);
}
public enterDropZone(event: any ): void {
this .renderer.addClass(event.owner.element.nativeElement, 'drag-enter' );
}
public leaveDropZone(event: any ): void {
this .renderer.removeClass(event.owner.element.nativeElement, 'drag-enter' );
}
public onDragStart(event: any ): void {
this .aggressiveToggle(event);
this .draggedElements = this .emails.filter(x => x.checked === true ).length;
}
public onGhostCreated(): void {
this .cdr.detectChanges();
}
private aggressiveToggle(event: any ): void {
const checkbox = event.owner.element.nativeElement.parentElement.querySelector('igx-checkbox' );
event.owner.data.checked = true ;
if (!this .nativeCheckboxChecked(checkbox)) {
checkbox.click();
}
}
private nativeCheckboxChecked(nativeElement: any ): boolean {
return nativeElement.classList.contains('igx-checkbox--checked' );
}
}
ts コピー <ng-template #customGhost >
<div class ="drag-ghost" >
<igx-icon > email</igx-icon >
<span > Move {{ draggedElements }} item{{ (draggedElements > 1 ? 's' : '')}}</span >
</div >
</ng-template >
<igx-list class ="folders" >
<igx-list-item *ngFor ="let folder of folders" igxDrop
(enter )="enterDropZone($event)"
(leave )="leaveDropZone($event)"
(dropped )="dropElement($event)"
[dropChannel ]="folder.dropChannel" >
<igx-icon igxListThumbnail > {{folder.icon}}</igx-icon >
<h4 igxListLineTitle > {{ folder.text }}</h4 >
</igx-list-item >
</igx-list >
<igx-list class ="emails" >
<igx-list-item [isHeader ]="true" > Today</igx-list-item >
<igx-list-item *ngFor ="let email of emails"
[class.email-checked ]="email.checked"
[igxDrag ]="email"
[dragChannel ]="['starred', 'important', 'personal', 'work', 'finances']"
[ghostTemplate ]="customGhost"
[ghostOffsetX ]="-5"
[ghostOffsetY ]="-5"
(dragStart )="onDragStart($event)"
(ghostCreate )="onGhostCreated()"
class ="email-content flexlist"
(click )="toggleCheck(email, checkbox)" >
<h4 igxListLineTitle > {{ email.sender }}</h4 >
<h6 igxListLineSubTitle > {{ email.title }}</h6 >
<igx-checkbox #checkbox
[(ngModel )]="email.checked"
(change )="toggleCheckbox(email)"
(pointerdown )="stopEventPropagation($event)"
(click )="stopEventPropagation($event)" >
</igx-checkbox >
</igx-list-item >
</igx-list >
html コピー @use '../../../variables' as *;
.drag-ghost {
position : relative;
display : flex;
align-items : center;
justify-content : flex-start;
width : rem(180px );
color : contrast-color($color : 'secondary' , $variant : 200 );
background-color : color($color : 'secondary' , $variant : 200 );
font-size : rem(13px );
padding : rem(8px );
border-radius : rem(4px );
igx-icon {
margin-right : rem(8px );
}
}
:host {
display : flex;
user-select: none;
font-weight : 400 ;
font-style : normal;
height : 100% ;
.folders {
width : rem(200px );
padding : 8px ;
background : color($color : 'gray' , $variant : 50 );
border : none;
igx-icon {
margin : 0 ;
}
igx-list-item {
cursor : pointer;
border-radius : 4px ;
background : transparent;
transition : background .15s $ease-out-quad ;
&.drag-enter {
background : color($color : 'secondary' , $variant : 100 );
}
&:hover {
background : color($color : 'gray' , $variant : 100 );
}
&:nth-child (5 ) {
igx-icon {
color : #af1159 ;
}
}
&:nth-child (6 ) {
igx-icon {
color : #0071bf ;
}
}
&:nth-child (7 ) {
igx-icon {
color : #4eb862 ;
}
}
}
}
.emails {
flex-grow : 1 ;
border : none;
border-radius : 0 ;
}
.email-checked {
color : contrast-color($color : 'secondary' , $variant : 100 );
background : color($color : 'secondary' , $variant : 100 );
transition : background .1s ease-out;
}
}
scss コピー
高度な設定
igxDrag
と igxDrop
両方を組み合わせて多くのさまざまな複雑なアプリケーションシ ナリオで使用できるため、次の例はかんばんボードでそれらの使用方法を示します。
ユーザーは各列のカードをソートすることができます。各カードにドロップ領域を設定することで実行されるため、別のカードがその領域に入ったことを検出し、実行時にそれらを切り替えて、ユーザーにより快適なエクスペリエンスを提供します。
列間でカードを切り替える機能はなければ、かんばんボードにはなりません。カードは、列から別の列に特定の位置で直接移動できます。以下ではダミー オブジェクトを使用して実現されているため、リリースされた場合にカードが配置される視覚的な領域が作成されます。カードのドラッグが終了するか、別の列を出るとダミーオブジェクトが削除されます。
かんばんボード内で項目を移動します。
import { NgModule } from "@angular/core" ;
import { FormsModule } from "@angular/forms" ;
import { BrowserModule } from "@angular/platform-browser" ;
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { AppComponent } from "./app.component" ;
import { KanbanSampleComponent } from "./drag-drop/kanban-sample/kanban-sample.component" ;
import {
IgxDragDirective,
IgxDropDirective,
IgxDragDropModule,
IgxDialogModule,
IgxCardModule,
IgxChipsModule
} from "igniteui-angular" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
KanbanSampleComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxDragDropModule,
IgxCardModule,
IgxChipsModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー
import { ChangeDetectorRef, Component, ElementRef, OnInit, Renderer2, ViewChild } from '@angular/core' ;
import { IDropBaseEventArgs, IDropDroppedEventArgs } from 'igniteui-angular' ;
enum state {
toDo = 'toDo' ,
inProgress = 'inProgress' ,
done = 'done'
}
interface IListItem {
id : string ;
text: string ;
state: state;
hide?: boolean ;
}
@Component ({
selector : 'app-kanban-sample' ,
templateUrl : './kanban-sample.component.html' ,
styleUrls : ['./kanban-sample.component.scss' ]
})
export class KanbanSampleComponent implements OnInit {
public toDoList: IListItem[];
public inProgressList: IListItem[];
public doneList: IListItem[];
private dragObj;
private dummyObj;
private lastDragEnterList: string ;
private currentList: string ;
constructor (private renderer: Renderer2, private cdr: ChangeDetectorRef ) { }
public ngOnInit(): void {
this .toDoList = [
{ id : 'STR-000132' , text : 'Implement chat bubble' , state : state.toDo },
{ id : 'STR-000097' , text : 'Implement sticky header' , state : state.toDo },
{ id : 'STR-000191' , text : 'Change trial days to credit' , state : state.toDo }
];
this .inProgressList = [
{ id : 'STR-000124' , text : 'Implement fback widget' , state : state.inProgress },
{ id : 'STR-000121' , text : 'Add analytics' , state : state.inProgress }
];
this .doneList = [
{ id : 'STR-000129' , text : 'Add SSL to account pages' , state : state.done }
];
this .dragObj = null ;
this .dummyObj = null ;
this .lastDragEnterList = '' ;
this .currentList = '' ;
}
public onStateContainerEnter (event: IDropBaseEventArgs ) {
if (this .currentList !== event.owner.element.nativeElement.id) {
this [this .currentList] = this [this .currentList].filter((item ) => item.id !== 'dummy' );
this .cdr.detectChanges();
this .currentList = event.owner.element.nativeElement.id;
this .dummyObj = null ;
}
this .renderer.addClass(event.owner.element.nativeElement, 'active' );
}
public onStateContainerLeave (event: IDropBaseEventArgs ) {
this .renderer.removeClass(event.owner.element.nativeElement, 'active' );
}
public dragStartHandler (event ) {
this .currentList = event.owner.element.nativeElement.dataset.state + 'List' ;
this .lastDragEnterList = this .currentList;
this .dragObj = this [this .currentList].filter((elem ) => elem.id === event.owner.element.nativeElement.id)[0 ];
}
public dragEndHandler (event ) {
this .toDoList = this .toDoList.filter((x ) => x.id !== 'dummy' );
this .inProgressList = this .inProgressList.filter((x ) => x.id !== 'dummy' );
this .doneList = this .doneList.filter((x ) => x.id !== 'dummy' );
if (this .dragObj) {
this .dragObj.hide = false ;
}
}
public onItemEnter (event: IDropBaseEventArgs ) {
const listContainer = event.owner.element.nativeElement.dataset.state;
this .renderer.addClass(this [listContainer].nativeElement, 'active' );
const currentList = event.owner.element.nativeElement.dataset.state + 'List' ;
const currentItemIndex = this [currentList].findIndex((item ) => item.id === event.owner.element.nativeElement.id);
if (this .lastDragEnterList === currentList) {
const draggedItemIndex = this [currentList].findIndex((item ) => item.id === this .dragObj.id);
this .swapTiles(draggedItemIndex, currentItemIndex, currentList);
} else {
if (!this .dummyObj) {
this .dummyObj = {id : 'dummy' , text : '' , state : event.owner.element.nativeElement.dataset.state};
const newCurrentList = [
...this[currentList].slice(0 , currentItemIndex),
this .dummyObj,
...this[currentList].slice(currentItemIndex)
];
this [currentList] = newCurrentList;
this .cdr.detectChanges();
} else {
const dummyObjIndex = this [currentList].findIndex((item ) => item.id === 'dummy' );
if (dummyObjIndex !== -1 ) {
this .swapTiles(dummyObjIndex, currentItemIndex, currentList);
}
}
}
}
public onItemLeave (event: IDropBaseEventArgs ) {
const listContainer = event.owner.element.nativeElement.dataset.state;
this .renderer.removeClass(this [listContainer].nativeElement, 'active' );
}
public onItemDropped (event: IDropDroppedEventArgs ) {
const dropListState = event.owner.element.nativeElement.id;
const dragListState = event.drag.element.nativeElement.dataset.state + 'List' ;
const dummyItemIndex = this [dropListState].findIndex((item ) => item.id === 'dummy' );
if (dropListState !== dragListState) {
this .dragObj.state = dropListState.substring(0 , dropListState.length - 4 );
this [dragListState] = this [dragListState].filter((item ) => item.id !== this .dragObj.id);
if (dummyItemIndex !== -1 ) {
this [dropListState].splice(dummyItemIndex, 1 , this .dragObj);
} else {
this [dropListState].push(this .dragObj);
}
}
this .dragObj.hide = false ;
this .dragObj = null ;
event.cancel = true ;
}
private swapTiles(currentIndex: number , targetIndex : number , itemList : string ): void {
const tempObj = this [itemList][currentIndex];
this [itemList].splice(currentIndex, 1 );
this [itemList].splice(targetIndex, 0 , tempObj);
this .cdr.detectChanges();
}
}
ts コピー <article
#toDo
igxDrop
id ="toDoList"
[attr.data-state ]="'ToDo'"
(enter )="onStateContainerEnter($event)"
(leave )="onStateContainerLeave($event)"
(dropped )="onItemDropped($event)" >
<header >
<h4 class ="state-title" > To Do</h4 >
<igx-chip >
<span class ="state-number" >
{{toDoList.length}}
</span >
</igx-chip >
</header >
<section >
<igx-card *ngFor ="let item of toDoList"
igxDrag
igxDrop
(dragStart )="dragStartHandler($event)"
(dragEnd )="dragEndHandler($event)"
(enter )="onItemEnter($event)"
(leave )="onItemLeave($event)"
[class.invisible ]="item.id === 'dummy'"
id ="{{item.id}}"
[attr.data-state ]="item.state"
(ghostCreate )="item.hide = true"
[style.visibility ]='item.hide ? "hidden" : "visible"'
[dragTolerance ]="0"
[ghostClass ]="'drag-ghost'" >
<igx-card-header >
<p igxCardHeaderTitle > {{ item.text }}</p >
</igx-card-header >
<igx-card-content >
<p > {{ item.id }}</p >
</igx-card-content >
</igx-card >
</section >
</article >
<article
#inProgress
igxDrop
id ="inProgressList"
[attr.data-state ]="'InProgress'"
(enter )="onStateContainerEnter($event)"
(leave )="onStateContainerLeave($event)"
(dropped )="onItemDropped($event)" >
<header >
<h4 class ="state-title" > In Progress</h4 >
<igx-chip >
<span class ="state-number" >
{{inProgressList.length}}
</span >
</igx-chip >
</header >
<section >
<igx-card *ngFor ="let item of inProgressList"
igxDrag
igxDrop
(dragStart )="dragStartHandler($event)"
(dragEnd )="dragEndHandler($event)"
(enter )="onItemEnter($event)"
(leave )="onItemLeave($event)"
[class.invisible ]="item.id === 'dummy'"
id ="{{item.id}}"
[attr.data-state ]="item.state"
(ghostCreate )="item.hide = true"
[style.visibility ]='item.hide ? "hidden" : "visible"'
[dragTolerance ]="0"
[ghostClass ]="'drag-ghost'" >
<igx-card-header >
<p igxCardHeaderTitle > {{ item.text }}</p >
</igx-card-header >
<igx-card-content >
<p > {{ item.id }}</p >
</igx-card-content >
</igx-card >
</section >
</article >
<article
#done
igxDrop
id ="doneList"
[attr.data-state ]="'Done'"
(enter )="onStateContainerEnter($event)"
(leave )="onStateContainerLeave($event)"
(dropped )="onItemDropped($event)" >
<header >
<h4 class ="state-title" > Done</h4 >
<igx-chip >
<span class ="state-number" >
{{doneList.length}}
</span >
</igx-chip >
</header >
<section >
<igx-card *ngFor ="let item of doneList"
igxDrag
igxDrop
(dragStart )="dragStartHandler($event)"
(dragEnd )="dragEndHandler($event)"
(enter )="onItemEnter($event)"
(leave )="onItemLeave($event)"
[class.invisible ]="item.id === 'dummy'"
id ="{{item.id}}"
[attr.data-state ]="item.state"
(ghostCreate )="item.hide = true"
[style.visibility ]='item.hide ? "hidden" : "visible"'
[dragTolerance ]="0"
[ghostClass ]="'drag-ghost'" >
<igx-card-header >
<p igxCardHeaderTitle > {{ item.text }}</p >
</igx-card-header >
<igx-card-content >
<p > {{ item.id }}</p >
</igx-card-content >
</igx-card >
</section >
</article >
html コピー @use '../../../variables' as *;
igx-card {
width : rem(288px );
height : rem(128px );
margin-bottom : rem(16px );
cursor : move;
&.drag-ghost {
transform : scale(1.05 );
box-shadow : elevation(24 );
color : color($color : 'secondary' , $variant : 300 );
}
&.invisible {
visibility : hidden !important ;
}
[igxCardHeaderTitle] {
font-size : rem(16px );
}
&:last-child {
margin-bottom : unset;
}
}
:host {
display : flex;
height : 100% ;
min-height : rem(656px );
user-select: none;
padding : rem(16px );
overflow : auto;
article {
position : relative;
display : flex;
flex-direction : column;
align-items : center;
flex : 1 0 33% ;
max-width : rem(320px );
min-width : rem(320px );
padding : 0 rem(16px );
background-color : color($color : 'gray' , $variant : 100 );
border-radius : rem(8px );
border : 1px solid color($color : 'gray' , $variant : 200 );
margin : 0 rem(12px );
overflow : auto;
header {
display : flex;
justify-content : space-between;
align-items : center;
width : 100% ;
padding : rem(8px ) 0 ;
align-self : flex-start;
.state-title {
@include type-style('subtitle-1' ) {
font-weight : 600 ;
margin : 0 ;
};
}
.state-number {
font-size : rem(12px );
font-weight : bold;
padding : 4px ;
}
}
&.active {
background-color : color($color : 'primary' , $variant : 100 );
}
}
}
scss コピー
API
リファレンス
コミュニティに参加して新しいアイデアをご提案ください。