UNPKG

15.2 kBMarkdownView Raw
1Official **Angular** wrapper for [`dragula`](https://github.com/bevacqua/dragula).
2
3[![npm version](https://badge.fury.io/js/ng2-dragula.svg)](http://badge.fury.io/js/ng2-dragula) [![npm downloads](https://img.shields.io/npm/dm/ng2-dragula.svg)](https://npmjs.org/ng2-dragula)
4[![Build Status](https://travis-ci.org/valor-software/ng2-dragula.svg?branch=master)](https://travis-ci.org/valor-software/ng2-dragula)
5[![codecov](https://codecov.io/gh/valor-software/ng2-dragula/branch/master/graph/badge.svg)](https://codecov.io/gh/valor-software/ng2-dragula)
6
7![Logo](https://raw.githubusercontent.com/bevacqua/dragula/master/resources/logo.png)
8
9> Drag and drop so simple it hurts
10
11
12# Demo
13
14Try out the [demo](http://valor-software.github.io/ng2-dragula/index.html)!
15
16[//]: # (Or play with [this starter in your browser][stackblitz] on StackBlitz.)
17
18[stackblitz]: https://stackblitz.com/edit/ng2-dragula-base?file=src/app/app.component.html
19
20![Demo](https://raw.githubusercontent.com/bevacqua/dragula/master/resources/demo.png)
21
22* [Install](#install)
23* [Setup](#setup)
24* [Usage](#usage)
25 * [Directive](#directive)
26 * [Grouping containers](#grouping-containers)
27 * [Saving changes to arrays with dragulaModel](#saving-changes-to-arrays-with-dragulamodel)
28 * [DragulaService](#dragulaservice)
29 * [Drake options](#drake-options)
30 * [Events](#events)
31 * [Special events for ng2-dragula](#special-events-for-ng2-dragula)
32* [Classic Blunders](#classic-blunders)
33* [Development](#development)
34
35# Dependencies
36
37Latest version available for each version of Angular
38
39| ng2-dragula | Angular |
40| ---------- |---------|
41| 2.1.1 | <= 9.x |
42| current | 16.x.x |
43
44# Install
45
46You can get it on npm.
47
48```sh
49npm install ng2-dragula
50# or
51yarn add ng2-dragula
52```
53
54# Setup
55
56### 1. Important: add the following line to your `polyfills.ts`:
57
58```ts
59(window as any).global = window;
60```
61
62This is a temporary workaround for
63[#849](https://github.com/valor-software/ng2-dragula/issues/849), while upstream
64dragula still relies on `global`.
65
66### 2. Add `DragulaModule.forRoot()` to your application module.
67
68```typescript
69import { DragulaModule } from 'ng2-dragula';
70@NgModule({
71 imports: [
72 ...,
73 DragulaModule.forRoot()
74 ],
75})
76export class AppModule { }
77```
78
79On any child modules (like lazy loaded route modules), just use `DragulaModule`.
80
81### 3. Add the CSS to your project
82
83You'll also need to add Dragula's CSS stylesheet `dragula.css` to your
84application (e.g. in `styles.scss`). The following is **slightly better** than
85`node_modules/dragula/dist/dragula.css` (it includes `pointer-events: none`
86([#508](https://github.com/valor-software/ng2-dragula/issues/508)) and
87[this fix](https://github.com/bevacqua/dragula/issues/373)),
88but you may wish to make your own modifications.
89
90```scss
91/* in-flight clone */
92.gu-mirror {
93 position: fixed !important;
94 margin: 0 !important;
95 z-index: 9999 !important;
96 opacity: 0.8;
97 -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
98 filter: alpha(opacity=80);
99 pointer-events: none;
100}
101/* high-performance display:none; helper */
102.gu-hide {
103 left: -9999px !important;
104}
105/* added to mirrorContainer (default = body) while dragging */
106.gu-unselectable {
107 -webkit-user-select: none !important;
108 -moz-user-select: none !important;
109 -ms-user-select: none !important;
110 user-select: none !important;
111}
112/* added to the source element while its mirror is dragged */
113.gu-transit {
114 opacity: 0.2;
115 -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
116 filter: alpha(opacity=20);
117}
118```
119
120### Then you're ready to go
121
122Here's a super quick sample to get you started:
123
124```typescript
125@Component({
126 selector: "sample",
127 template:`
128 <div>
129 <div class="wrapper">
130 <div class="container" dragula="DRAGULA_FACTS">
131 <div>You can move these elements between these two containers</div>
132 <div>Moving them anywhere else isn't quite possible</div>
133 <div>There's also the possibility of moving elements around in the same container, changing their position</div>
134 </div>
135 <div class="container" dragula="DRAGULA_FACTS">
136 <div>This is the default use case. You only need to specify the containers you want to use</div>
137 <div>More interactive use cases lie ahead</div>
138 <div>Make sure to check out the <a href="https://github.com/bevacqua/dragula#readme">documentation on GitHub!</a></div>
139 </div>
140 </div>
141 </div>
142 `
143})
144class Sample {}
145```
146
147# Usage
148
149This package isn't very different from `dragula` itself. I'll mark the
150differences here, but please refer to the documentation for
151[dragula](https://github.com/bevacqua/dragula) if you need to learn more about
152`dragula` itself.
153
154## Directive
155
156There's a `dragula` directive that makes a container's direct children
157draggable. You must supply a string. Both syntaxes, `dragula="VAMPIRES"` or
158`[dragula]="'VAMPIRES'"`, work equally well.
159
160```html
161<ul dragula="VAMPIRES">
162 <li>Dracula</li>
163 <li>Kurz</li>
164 <li>Vladislav</li>
165 <li>Deacon</li>
166</ul>
167```
168
169## Grouping containers
170
171You can group containers together by giving them the same group name. When you
172do, the children of each container can be dragged to any container in the same
173group.
174
175```html
176<div dragula="VAMPIRES">
177 <!-- vamps in here -->
178</div>
179<div dragula="VAMPIRES">
180 <!-- vamps in here -->
181</div>
182
183<div dragula="ZOMBIES">
184 <!-- but zombies in here! -->
185</div>
186```
187
188If you want to make sure you are using the same type string in different places,
189use the `[dragula]` syntax to pass a string variable from your component:
190
191```html
192<div [dragula]="Vampires"></div>
193<div [dragula]="Vampires"></div>
194```
195```ts
196class MyComponent {
197 Vampires = "VAMPIRES";
198}
199```
200
201## Saving changes to arrays with `[(dragulaModel)]`
202
203If your container's children are rendered using `ngFor`, you may wish to read what your users have done. If you provide the same array to the `[(dragulaModel)]` attribute on the **container** element, any changes will be synced back to the array.
204
205**NOTE: v2 changes the behaviour of [dragulaModel]. It no longer mutates the arrays you give it, but will shallow clone them and give you the results.** Use two-way binding with `[(dragulaModel)]="..."`, or use the DragulaService `dropModel` and `removeModel` events to save the new models produced.
206
207```html
208<ul dragula="VAMPIRES" [(dragulaModel)]="vampires">
209 <li *ngFor="let vamp of vampires">
210 {{ vamp.name }} likes {{ vamp.favouriteColor }}
211 </li>
212</ul>
213```
214
215You do not, of course, *have* to sync the changes back. The `[(dragulaModel)]` syntax is equivalent to:
216
217```html
218<ul dragula="VAMPIRES" [dragulaModel]="vampires" (dragulaModelChange)="vampires = $event">
219 ...
220</ul>
221```
222
223Note: **DO NOT** put any other elements inside the container. The library relies
224on having the index of a DOM element inside a container mapping directly to
225their associated items in the array. Everything will be messed up if you do
226this.
227
228On top of the normal Dragula events, when `[(dragulaModel)]` is provided, there are two extra events: `dropModel` and `removeModel`. Further details are available under `Events`
229
230## Drake options
231
232If you need to configure the `drake` _(there's exactly one `drake` per `group`)_, you can use the `DragulaService`.
233
234```ts
235import { DragulaService } from 'ng2-dragula';
236
237class ConfigExample {
238 constructor(private dragulaService: DragulaService) {
239 dragulaService.createGroup("VAMPIRES", {
240 removeOnSpill: true
241 });
242 }
243}
244```
245
246See below for more info on options.
247
248## `DragulaService`
249
250This service exposes a few different methods with which you can interact with `dragula`.
251
252### `dragulaService.createGroup(name, options)`
253
254NOTE: formerly known as `setOptions()`
255
256Creates a group named `name`, with an
257[options](#dragulaoptions) object.
258
259### `dragulaService.find(name: string)`
260
261Returns a `Group` named `name`, if there is one. A `Group` contains the following
262properties.
263
264- `name` is the name that identifies the group
265- `drake` is the raw `drake` instance itself
266- `options` is the options object used to create the drake. Modifying it won't
267 do anything useful.
268
269### `dragulaService.destroy(name)`
270
271Destroys a `Group` named `name` and its associated `drake` instance. Silently
272returns if the group does not exist.
273
274### DragulaOptions
275
276Refer to the documentation for
277[dragula](https://github.com/bevacqua/dragula#readme) to learn more about the
278native options.
279
280All of the native options work with ng2-dragula. However, there is one addition:
281
282#### `copyItem: <T>(item: T) => T`
283
284When you have:
285
286* `[(dragulaModel)]`
287* `copy` is `true` or a *function that returns true*
288
289... ng2-dragula will have to create a clone of the JS object you picked up. In
290previous versions of `ng2-dragula`, there was a terribly buggy,
291one-size-fits-all clone function. From v2 onwards, you **MUST** provide your own
292`copyItem` function.
293
294If you have a simple object with no nested values, it could be as simple as:
295
296```ts
297{
298 copy: ...,
299 copyItem: (item: MyType) => ({ ...item })
300}
301```
302
303There is a complete example using a `Person` class on the demo page.
304
305## Events
306
307Whenever a `drake` instance is created with the `dragula` directive, there are
308several events you can subscribe to via `DragulaService`. Each event emits
309a typed object, which you can use to get information about what happened.
310
311Refer to [the Drake events
312documentation](https://github.com/bevacqua/dragula#drakeon-events) for more
313information about the different events available. Each event follows this
314format:
315
316```yml
317Event named: 'drag'
318
319Native dragula:
320 Use: drake.on('drag', listener)
321 Listener arguments: (el, source)
322
323ng2-dragula:
324 Method: DragulaService.drag(groupName?: string): Observable<...>
325 Observable of: { name: string; el: Element; source: Element; }
326```
327
328Each supports an optional parameter, `groupName?: string`, which filters events
329to the group you're interested in. This is usually better than getting all
330groups in one observable.
331
332The sample below illustrates how you can use destructuring to pull values from
333the event, and unsubscribe when your component is destroyed.
334
335```html
336<div dragula="VAMPIRES"></div>
337```
338
339```ts
340import { Subscription } from 'rxjs';
341import { DragulaService } from 'ng2-dragula';
342
343export class MyComponent {
344 // RxJS Subscription is an excellent API for managing many unsubscribe calls.
345 // See note below about unsubscribing.
346 subs = new Subscription();
347
348 constructor(private dragulaService: DragulaService) {
349
350 // These will get events limited to the VAMPIRES group.
351
352 this.subs.add(this.dragulaService.drag("VAMPIRES")
353 .subscribe(({ name, el, source }) => {
354 // ...
355 })
356 );
357 this.subs.add(this.dragulaService.drop("VAMPIRES")
358 .subscribe(({ name, el, target, source, sibling }) => {
359 // ...
360 })
361 );
362 // some events have lots of properties, just pick the ones you need
363 this.subs.add(this.dragulaService.dropModel("VAMPIRES")
364 // WHOA
365 // .subscribe(({ name, el, target, source, sibling, sourceModel, targetModel, item }) => {
366 .subscribe(({ sourceModel, targetModel, item }) => {
367 // ...
368 })
369 );
370
371 // You can also get all events, not limited to a particular group
372 this.subs.add(this.dragulaService.drop()
373 .subscribe(({ name, el, target, source, sibling }) => {
374 // ...
375 })
376 );
377 }
378
379 ngOnDestroy() {
380 // destroy all the subscriptions at once
381 this.subs.unsubscribe();
382 }
383}
384
385```
386
387**NOTE: You should always unsubscribe each time you listen to an event.** This
388is especially true for a component, which should tear itself down completely in
389`ngOnDestroy`, including any subscriptions. It might not be necessary if you
390have a global singleton service (which is never destroyed) doing the
391subscribing.
392
393You can also engineer your use of events to avoid subscribing in the first
394place:
395
396```ts
397import { merge } from 'rxjs';
398import { mapTo, startWith } from 'rxjs/operators';
399
400dragStart$ = this.dragulaService.drag("VAMPIRES").pipe(mapTo(true));
401dragEnd$ = this.dragulaService.dragend("VAMPIRES").pipe(mapTo(false));
402isDragging$ = merge(dragStart$, dragEnd$).pipe(startWith(false));
403
404// html: [class.dragging]="isDragging$ | async"
405```
406
407## Special Events for `ng2-dragula`
408
409The `dropModel(name?: string)` and `removeModel(name?: string)` events are only active when you have supplied `[dragulaModel]`.
410
411| Event Name | Listener Arguments | Event Description |
412| :-------------- | :-------------------------: | :--------------------------------------------------------------------------------------- |
413| dropModel | { type, el, target, source, item, sourceModel, targetModel, sourceIndex, targetIndex } | same as normal drop, but with updated models + the item that was dropped |
414| removeModel | { type, el, container, source, item, sourceModel, sourceIndex } | same as normal remove, but with updated model + the item that got removed |
415
416# Classic Blunders
417
418There are a number of very common issues filed against this repo. You will be
419mocked terribly if you file a bug and it turns out you made one of these
420blunders and it wasn't a bug at all.
421
422### 1. Do not put `[dragula]` or `[(dragulaModel)]` on the same element as `*ngFor`.
423
424**WRONG:**
425
426```html
427<div class="container">
428 <div *ngFor="let x of list"
429 dragula="WRONG" [(dragulaModel)]="list">...</div>
430</div>
431```
432
433**RIGHT:**
434
435```html
436<div class="container" dragula="RIGHT" [(dragulaModel)]="list">
437 <div *ngFor="let x of list">...</div>
438</div>
439```
440
441### 2. Do not add any child elements that aren't meant to be draggable
442
443**WRONG:**
444```html
445<div class="container" dragula="WRONG" [(dragulaModel)]="list">
446 <h2>WRONG: This header will mess up everything, and you will
447 get really weird bugs on drop</h2>
448 <div *ngFor="let x of list">...</div>
449</div>
450```
451
452**RIGHT:**
453```html
454<h2>This header will not be draggable or affect drags at all.</h2>
455<div class="container" dragula="RIGHT" [(dragulaModel)]="list">
456 <div *ngFor="let x of list">...</div>
457</div>
458```
459
460# Alternatives
461
462There are hundreds of other libraries that do this. Some notable ones:
463
464- [@angular-skyhook](https://cormacrelf.github.io/angular-skyhook/examples/), specifically with the sortable. Also by me (@cormacrelf).
465- [@angular/cdk/drag-drop](https://material.angular.io/cdk/categories) -- no documentation yet, but will presumably be well-supported.
466
467# Development
468
469- You must use Yarn >= 1.3. It includes the 'workspaces' feature.
470- Please use [Conventional Commits](https://conventionalcommits.org/) in your commit messages.
471
472#### setup
473
474```sh
475yarn
476yarn workspace ng2-dragula build
477```
478
479#### run tests
480
481```sh
482yarn workspace ng2-dragula test
483# or
484yarn workspace ng2-dragula test:headless
485```
486
487#### run demo server
488
489```sh
490# listens for changes in the library and rebuilds on save
491yarn watch
492# runs demo server
493yarn workspace demo start
494```
495
496#### Publishing a new version
497
498```
499yarn lerna publish
500```
501
502## Credits
503
504- `v1`: Nathan Walker (@NathanWalker)
505- `v1.x`: Dmitriy Shekhovtsov (@valorkin)
506- `v2`: Cormac Relf (@cormacrelf)
507