Skip to content

Drag&Drop Directives Specification

Svetoslav Krastev edited this page Apr 8, 2020 · 49 revisions

IgxDrag/IgxDrop Directives Specification

Contents

  1. Overview
  2. User Stories
  3. Functionality
  4. API
  5. Assumptions and Limitations
  6. Test Scenarios
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>

Dragging with ghost element

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 the ghostTemplate 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>

Dragging without ghost

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.

Dragging using a handle

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>

Linking Drag to Drop element

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.

Animations

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.

Drop Strategies

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 named IgxDefaultDropStrategy.

  • As the names suggest the first Append strategy inserts the dropped element as a last child and is implemented as a class named IgxAppendDropStrategy.

  • The Prepend strategy inserts the dropped element as first child and is implemented as a class named IgxPrependDropStrategy.

  • The Insert strategy inserts the dragged element at the dropped position. If there is a child under the element when it was dropped, the igxDrag instanced element will be inserted at that child's index. It is implemented as a class named IgxInsertDropStrategy

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>

Canceling a Drop Strategy

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.

Animations

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);
}
  • IgxDrag

    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 channels number | 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
  • IgxDrop

    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 channels number | 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
  • IgxDragLocation

    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
  • IDragCustomTransitionArgs

    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
  • IDragBaseEventArgs

    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
  • IDragStartEventArgs

    Extends IDragBaseEventArgs.

    Name Description Type
    cancel Property specifying if the default logic which that event is related should be canceled boolean
  • IDragMoveEventArgs

    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
  • IDragGhostBaseEventArgs

    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
  • IDropBaseEventArgs

    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
  • IDropDragDropEventArgs

    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.

IdxDrag

IgxDrop (combined with igxDrag)

IdxDrag

  • 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 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 and offsetY 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.

IgxDrop

  • 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 last child when Append drop strategy is used.
    • 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 cancel drop strategy when the onDrop event is canceled.
Clone this wiki locally