-
Notifications
You must be signed in to change notification settings - Fork 161
Drag&Drop Directives Specification
- Overview
- User Stories
- Functionality
-
API
- 4.1. Directives
- 4.2. Classes
- 4.3. Interfaces
- Assumptions and Limitations
-
Test Scenarios
- 6.1. Manual
- 6.2. Automation
Version | User | Date | Notes |
---|---|---|---|
0.1 | Svetoslav Krastev | Jun 20, 2019 | Initial draft |
0.2 | Svetoslav Krastev | Jun 27, 2019 | Updated draft |
0.3 | Svetoslav Krastev | Aug 12, 2019 | Updated API |
- Stefan Ivanov | Date:
- Radoslav Karaivanov | Date:
- Martin Pavlov | Date:
- Konstantin Dinev | Date:
IgxDrag
provides a way to move an element by click and dragging it around. In combination with igxDrop
directive that specifies where element can be put it allows an element to be moved from one place to another.
Elaborate more on the multi-faceted use cases
As a developer, I want to:
- easily define an element can be dragged by using a directive on any element.
- easily define a drop area where a dragged element can be dropped on any element.
- specify if element that can be dragged and moved freely.
- specify if a ghost element should be rendered after dragging begins representing the original element and the original element should keep its original position.
- easily use a custom ghost element that is created from the drag directive after dragging starts.
- provide a way to set where the default/custom ghost should be rendered (what it's parent element should be).
- ? dynamically update the custom ghost based on my provided template
- have elements inside dragged elements that should not trigger dragging and can be interacted with.
- be able to listen to events when the drag has started/ended, when clicked or when the preview element is being created.
- be able to listen to events when element has been dropped onto a drop area.
- provide specific data that the
igxDrag
carries so when a specific element is dropped it can be distinguished. - set new position in the DOM for the
igxDrag
instanced element and have animation that transitions to that new location. - have ability to customize the animations applied to the
igxDrag
element when interacting with it
As an end user, I want to:
- drag an element around freely and it keeping its position on release.
- drag an element around and it rendering a ghost element that is placed under the pointer when dragging around.
- drag an element from one area to another area by both possible ways of dragging.
- use either mouse or touch interaction to drag an element.
- have a clear indication when the dragging has started.
- be able to click the draggable elements.
- have some room of error when clicking on an element so it doesn't start dragging immediately.
- have smooth transition animations if dragged elements keep their original position or have specific new ones that don't align with the exact drop location
The igxDrag
directive can be instantiated on any type of element. It can be used on its own without depending on the igxDrop
. It should provide enough functionality so the user could determine where it has been released and so implements a custom logic.
Specific data can be stored inside the igxDrag
for various purposes like identifying it among other draggable elements and etc. It can be specified by assigning it on the initialization tag [igxDrag]
or by using the data input where it is stored:
<div [igxDrag]="myData">Drag me!</div>
By default the dragging will not start immediately in order to provide some room for error as well as not interrupt if the user wants to click the element instead. The tolerance for it is 5px
in any direction and if it is exceeded then the dragging would start. This can be configured using the dragTolerance
input.
- Interactable children elements
When the user wants to have interactable children of the main element that has igxDrag instanced, he can set the igxDragIgnore directive to them in order for them to be ignored by the igxDrag and not perform any dragging action. This will leave these element to be fully interactable and will receive all mouse events.
<div [igxDrag]="myData">
<span>Drag me!</span>
<igx-icon igxDragIgnore fontSet="material" (click)="remove()">bin</igx-icon>
</div>
The ghost
input is set to true
by default which means that the base element the igxDrag
directive is initialized will keep its position and a ghost will be rendered under the user pointer once the dragging starts. While still holding and moving the ghost created will move along the user pointer instead of the base element.
-
Customizing the ghost
The ghost element by default is a copy of the base element the
igxDrag
is used on. It can be customized by providing a template reference to theghostTemplate
input directly. The template itself can be position anyway, since the only thing provided is reference to it. It can be done the following way:<div [igxDrag]="'Dolphin'" [ghostTemplate]="customGhost"> Drag me! </div> <ng-template #customGhost> <div>I can fly!</div> </ng-template>
-
Customizing the base
Since when using a ghost element leaves us with the base element being still rendered at its original location we can hide it by setting applying custom visibility style when dragging starts or by completely replacing its content using
ngIf
.Hiding the base element:
<div [igxDrag]="'Dolphin'" [ngStyle]="{ 'visibility': dragged ? 'hidden' : 'visible' }"> Drag me! </div>
Customizing the base content:
<div [igxDrag]="'Dolphin'" [ngStyle]="{ 'visibility': dragged ? 'hidden' : 'visible' }"> <div *ngIf="dragged; else originTemplate">Drag me!</div> <ng-template #originTemplate>Origin!</ng-template> </div>
If renderGhost
input is set to false the dragging logic for the igxDrag
provides dragging ability for the initialized element itself. This means that it can freely move an element around by click and hold and when released it will keep its position where it was dragged.
The user can specify an element that is a child of the igxDrag
by which to drag since by default the whole element is used to perform that action. It can be done using the igxDragHandle
directive and can be applied to multiple elements inside the igxDrag
.
When multiple channels are applied to an igxDrag
and one of them matches one of applied channels to an igxDrop
, then all events and applied behaviors would be executed when that element is dropped.
Example:
<div igxDrag>
<div igxDragHandle>X</div>
Drag me!
</div>
Using the dragChannel
and dropChannel
input on respectively igxDrag
and igxDrop
directives the user can link different elements to interact only between each other. For example if an igxDrag
element needs to be constrained so it can be dropped on specific igxDrop
element and not all available this can easily be achieved by assigning them same channel.
When assigning either single or multiple channels using an array, each channel is compared using the
Strict Equality
comparison.
Example:
<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>
As displayed above only Human
and Dolphin
can be dropped in the 'Mammals' class but not in the 'Insects' class, where only the Butterfly
and Bee
can be dropped. Same for the 'Land' drop area where only Ant
and Human
can be dropped.
By default when an element is being dragged there are no animations applied.
The user can apply transition animation to the igxDrag
any time, but it is advised to use it when dragging ends or the element is not currently dragged. This can be achieved using the transitionToOrigin
and the transitionTo
methods.
The transitionToOrigin
method as the name suggest animates the currently dragged element or its ghost to the start position where the dragging began. The transitionTo
method animates the element to a specific location relative to the page (i.e. pageX
and pageY
) or to the position of a specified element. If the element is not being currently dragged it will animate anyway or create ghost and animate it to the desired position.
Both function have arguments that the user can set to customize the transition animation and set duration, timing function or delay. If specific start location is set it will animate the element starting from there.
When the transition animation ends if a ghost is created it will be removed and the igxDrag
directive will return to its initial state or if no ghost is created it will keep its position. In both cases then the transitioned
event will be triggered depending on how long the animation lasts. If no animation is applied it will triggered instantly.
If the user want to have other types of animations that involve element transformations he can do that like any other element either using the Angular Animations or straight CSS Animations to either the base igxDrag
element or its ghost. If he wants to apply them to the ghost he would need to define a custom ghost and apply animations to that element.
For achieving a drop functionality with the igxDrag
directive the igxDrop
directive should be used. It can be applied on any kind of element and it specifies an area where the igxDrag
can be dropped.
By default the igxDrop
does not apply any logic to the dragged element when it is dropped onto it. The user could choose between a few different drop strategies if he would like the igxDrop
to perform some action or he could implement his own drop logic using the provided onDragDrop
events.
The igxDrop
comes with 4 separate drop strategies and each is defined a separate class that has specific functionality:
-
The
Default
strategy does not perform any action when an element is dropped onto an IgxDrop element and is implemented as a class namedIgxDefaultDropStrategy
. -
As the names suggest the first
Append
strategy inserts the dropped element as a last child and is implemented as a class namedIgxAppendDropStrategy
. -
The
Prepend
strategy inserts the dropped element as first child and is implemented as a class namedIgxPrependDropStrategy
. -
The
Insert
strategy inserts the dragged element at the dropped position. If there is a child under the element when it was dropped, theigxDrag
instanced element will be inserted at that child's index. It is implemented as a class namedIgxInsertDropStrategy
The way a strategy can be applied is by setting the dropStrategy
input to one of the listed classes above. The value provided has to be e type and not an instance, since the igxDrop
has to create the instance itself.
Example:
TypeScript:
public insertStrategy = IgxInsertDropStrategy;
HTML:
<div igxDrop [dropStrategy]="insertStrategy"></div>
When using a specific drop strategy, its behavior can be canceled in the onDrop
or onDragDrop
events by setting the cancel property to true. The onDrop
event is specific to the igxDrag
and the onDragDrop
event to the igxDrop
. If the user does not have drop strategy applied to the igxDrop
canceling the event would have no side effects.
Example:
HTML
<div igxDrag></div>
<!-- ... -->
<div igxDrop (dropped)="onDropped($event)"></div>
TypeScript
public onDropped(event) {
event.cancel = true;
}
If the user would like to implement its own drop logic it can easily be done by binding to dropped
and executing their logic when the event is triggered or extending the default drop strategy.
If the user decides that he want to use transition animations when dropping an element he can do that by using transition animations that can be applied to the igxDrag
by calling the transitionToOrigin
or transitionTo
methods whenever he wants. Preferably that should be done when dragging of an element ends or when it is dropped onto a igxDrop
instanced element.
Example:
HTML
<div>Products:</div>
<div #productsContainer>
<div *ngFor="let product of availableProducts; let i = index"
[igxDrag]="{index: i}"
(dragEnd)="onDragEnd($event)"
(transitioned)="onDragAnimationEnd($event)">
{{product}
</div>
<div>
<div>Basket:</div>
<div igxDrop (dropped)="onDragDropped($event)">
<div *ngFor="let product of basketProducts">{{product}}</div>
</div>
TypeScript
public availableProducts = ["milk", "cheese", "banana"];
public basketProducts = [];
public onDragEnd(event) {
event.owner.transitionToOrigin();
}
public onDragDropped(event) {
event.drag.transitionTo(event.dropDirective.element);
}
public onDragAnimationEnd(event) {
const removeIndex = event.owner.data.index;
const removedElem = availableProducts.splice(removeIndex, 1);
basketProducts.push(removedElem);
}
-
Inputs
Name Description Type Default value data
Sets information to be stored in the directive. any undefined dragTolerance
The amount of pixes the user need to move before the dragging starts and the preview is rendered. number 5 dragDirection
Specifies if the element should be draggable only in one direction. DragDirection DragDirection.BOTH dragChannel
Specifies channel or multiple channels to which the element is linked to and can interact with only those igxDrop
elements in those channelsnumber | string | number[] | string[] undefined renderGhost
Sets if the when the dragging of the element start a ghost should be rendered under the pointer and the original element kept where it is positioned or not. boolean true ghostTemplate
A custom template for the ghost element that completely replaces the default one. TempalteRef undefined ghostHost
Sets the element to which the drag ghost element will be appended to. By default it's set to null and the ghost element is appended to the body. ElementRef undefined ghostOffsetX
Sets the offset position of the ghost element relative to the mouse horizontally. number undefined ghostOffsetY
Sets the offset position of the ghost element relative to the mouse vertically. number undefined Outputs
Name Description Cancelable Parameters dragStart
Event triggered when any movement starts. true IDragBaseEventArgs
dragMove
Event triggered for every frame where the igxDrag
element has been dragged.true IDragMoveEventArgs
dragEnd
Event triggered when the user releases the element area that is not inside an igxDrop
. This is triggered before any animation starts.false IDragBaseEventArgs
click
Even triggered when the user performs a click and not dragging. This is the native event. false MouseEvent transitioned
Event triggered after any movement of the drag element has ended. This is triggered after all animations have ended and before the ghost is removed. false IDragBaseEventArgs
ghostCreate
Event triggered right before the ghost element is created false IDragGhostBaseEventArgs
ghostDestroy
Event triggered right before the ghost element is destroyed false IDragGhostBaseEventArgs
Properties
Name Description Type location
Gets the current location of the element relative to the page. If ghost is enabled it will get the location of the ghost, if the user is not currently dragging it will return the location of the base element. IgxDragLocation
originLocation
Gets the origin location of the element before dragging started. If ghost is enabled it will get the location of the base. IgxDragLocation
Methods
Name Description Parameters Return Type setLocation
Sets new location for the igxDrag directive. When ghost is enable and it is not rendered it will be ignored. newLocation?:
IgxDragLocation
void transitionToOrigin
Animates the element from its current location to its initial position. If it was not moved or no start location is specified nothing would happen . customTransitionArgs?: IDragCustomTransitionArgs
,startLocation?:
IgxDragLocation
,void transitionTo
Animates the element from its current location to specific location or DOM element. If it was not moved or no start location is specified nothing would happen. target:
IgxDragLocation
|ElementRef, customTransitionArgs?:IDragCustomTransitionArgs
,startLocation?:
IgxDragLocation
void -
Inputs
Name Description Type Default value data
Sets information to be stored in the directive. any undefined dropChannel
Specifies channel or multiple channels to which the element is linked to and can interact with only those igxDrag
elements in those channelsnumber | string | number[] | string[] undefined dropStrategy
Sets a drop strategy that should be applied once an element is dropped into the current igxDrop
element.class reference IgxDefaultDropStrategy Outputs
Name Description Cancelable Type enter
Event triggered once an IgxDrag
instanced element enters the boundaries of the drop area. Similar to MouseEnter.false IDropBaseEventArgs
over
Event triggered when an IgxDrag
instanced element moves inside the boundaries of the drop area similar to MouseOver.false IDropBaseEventArgs
leave
Event triggered once an IgxDrag
instanced element leaves the boundaries of the drop area. Similar to MouseLeave.false IDropBaseEventArgs
dropped
Event triggered once an IgxDrag
instanced element inside the drop area is released.true IDropDragDropEventArgs
-
Name Description Type pageX
The far left location of the drag element relative to the page horizontally. number pageY
The far top location of the drag element relative to the page vertically. number
-
Name Description Type duration
Specifies how many seconds or milliseconds the animation takes to complete. number timingFunction
Specifies the speed curve for the animation effect. string delay
Specifies a delay (in seconds) for the animation to start. number -
Name Description Type originalEvent
The original event that starting this interaction. PointerEvent/MouseEvent/TouchEvent owner
The owner that triggered this event. In this case the igxDrag
.IgxDragDirective startX
The start pageX position of pointer that initiated the drag. number startY
The start pageY position of pointer that initiated the drag. number pageX
The current pageX position of pointer that initiated the drag. number pageY
The current pageY position of pointer that initiated the drag. number -
Extends
IDragBaseEventArgs
.Name Description Type cancel
Property specifying if the default logic which that event is related should be canceled boolean -
Extends
IDragStartEventArgs
.Name Description Type cancel
Property specifying if the default logic which that event is related should be canceled boolean newPageX
The new pageX position of the pointer that the igxDrag will use. It can be overridden. number newPageY
The new pageX position of the pointer that the igxDrag will use. It can be overridden. number -
Name Description Type owner
Null or the owner directive that was used to specify custom ghost. IgxDragGhostDirective
dragDirective
The owner igxDrag
directive that created/destroyed the ghost.IgxDragDirective
-
Name Description Type originalEvent
The original event that caused the interaction. PointerEvent/MouseEvent/TouchEvent owner
The owner element that triggered this event. In this case the igxDrop
.IgxDropDirective
dragDirective
The owner igxDrag
directive of the element being dragged over the drop area.IgxDragDirective
startX
The start pageX position of pointer that initiated the drag. number startY
The start pageY position of pointer that initiated the drag. number pageX
The current pageX position of pointer that performs the dragging. number pageY
The current pageY position of pointer that performs the dragging. number offsetX
The current offset of the pointer relative to the pageX position of the igxDrop
.number offsetY
The current offset of the pointer relative to the pageY position of the igxDrop
.number -
Extends
IDropBaseEventArgs
Name Description Type cancel
Specifies if the default drop logic related to the event should be canceled. boolean
Assumptions | Limitation Notes |
---|---|
Drag/Drop on iOS 11.0 and earlier | Due to missing implementation of vital document functions that the IgxDrag uses in iOS 11.0 and the combination of IgxDrag with IgxDrop would not work. IgxDrag on its own should still work. |
- Should correctly initialize drag and drop directives
- Should create drag ghost element and trigger
onGhostCreate
/onGhostDestroy
. - Should not create drag ghost element when the dragged amount is less than
dragTolerance
. - Should trigger dragStart/dragMove/dragEnd events in that order.
- Should trigger igxDrag
onDragEnter
/onDragLeave
events when it enters and leaves igxDrop area. -
- Should trigger igxDrag
onDrop
event when it is dropped onto and igxDrop area.
- Should trigger igxDrag
- Should position ghost at the same position relative to the mouse when drag started.
- Should position ghost relative to the mouse using offsetX and offsetY correctly.
- Should position ghost at the same position relative to the mouse when drag started when host is defined.
- Should allow customizing of ghost element by passing template reference and position it correctly.
- Should position custom ghost relative to the mouse using
offsetX
andoffsetY
correctly. - Should correctly move igxDrag element when ghost is disabled and trigger dragStart/dragMove/dragEnd events.
- Should prevent dragging if it does not exceed dragTolerance and ghost is disabled.
- Should correctly apply dragTolerance of 0 when it is set to 0 and ghost is disabled.
- Should correctly set location using setLocation() method when ghost is disabled.
- Should trigger onEnter/onDrop/onLeave events when element is dropped inside igxDrop element
-
- Should trigger onEnter/onDrop/onLeave events when element is dropped inside and is linked with it.
-
- Should not trigger onEnter/onDrop/onLeave events when element is dropped inside and is not linked with it.
-
- Should not perform any action by default when an element is dropped inside.
-
- Should put dropped element as a first child when
Prepend
drop strategy is used.
- Should put dropped element as a first child when
-
- Should put dropped element as a last child when
Append
drop strategy is used.
- Should put dropped element as a last child when
-
- Should put dropped element as a second child when
Insert
drop strategy is used and element is dropped over the second child already in the igxDrop area.
- Should put dropped element as a second child when
-
- Should cancel drop strategy when the
onDrop
event is canceled.
- Should cancel drop strategy when the