All Projects → kristianmandrup → vue2-dragula-demo

kristianmandrup / vue2-dragula-demo

Licence: other
Vue2 demo app for vue-dragula plugin

Programming Languages

javascript
184084 projects - #8 most used programming language
Vue
7211 projects
CSS
56736 projects
HTML
75241 projects

Projects that are alternatives of or similar to vue2-dragula-demo

Easy Dnd
A drag and drop implementation for Vue.js 2 https://codesandbox.io/s/easy-dnd-demo-9mbij https://codesandbox.io/s/easy-dnd-demo-2-xnqbz
Stars: ✭ 202 (+162.34%)
Mutual labels:  dnd
DM-Operations-Center
The DM Operations Center is a collection of rules and tools for 5th edition Dungeons & Dragons dungeon masters that use the advanced Virtual Tabletop application Encounter+
Stars: ✭ 27 (-64.94%)
Mutual labels:  dnd
slate-react-dnd-plugin
No description or website provided.
Stars: ✭ 35 (-54.55%)
Mutual labels:  dnd
dnddata
Weekly updated dataset of D&D characters submitted to https://oganm.com/shiny/printSheetApp and https://oganm.com/shiny/interactiveSheet. A superset of characters used in oganm/dndstats
Stars: ✭ 91 (+18.18%)
Mutual labels:  dnd
DicePP
基于nonebot和go-cqhttp的骰子机器人
Stars: ✭ 17 (-77.92%)
Mutual labels:  dnd
vue3-smooth-dnd
Vue3 wrapper components for smooth-dnd
Stars: ✭ 92 (+19.48%)
Mutual labels:  dnd
Adventurerscodex
Experience the next step in tabletop RPGs
Stars: ✭ 167 (+116.88%)
Mutual labels:  dnd
PolyDiceGenerator
A customizable Polyhedral Dice Generator for OpenSCAD.
Stars: ✭ 63 (-18.18%)
Mutual labels:  dnd
character-overlay
Web App for adding an OBS overlay with character information such as name, picture, and health for your favorite role-playing game.
Stars: ✭ 17 (-77.92%)
Mutual labels:  dnd
dnd
Beautiful and accessible drag and drop for lists with React.
Stars: ✭ 271 (+251.95%)
Mutual labels:  dnd
react-native-dnd-board
A drag and drop Kanban board for React Native.
Stars: ✭ 41 (-46.75%)
Mutual labels:  dnd
this-is-your-life
An angular app character backstories based on the Xanathar's Guide to Everything 'This Is Your Life' tables.
Stars: ✭ 36 (-53.25%)
Mutual labels:  dnd
dndstats
Statistics of DnD characters submitted to https://oganm.com/shiny/printSheetApp and https://oganm.com/shiny/interactiveSheet. A larger dataset is at https://github.com/oganm/dnddata
Stars: ✭ 37 (-51.95%)
Mutual labels:  dnd
Planarally
A companion tool for when you travel into the planes.
Stars: ✭ 242 (+214.29%)
Mutual labels:  dnd
awesome-5e-srd
A compilation of all the cool things people make with the D&D 5e SRD API 🔮✨
Stars: ✭ 20 (-74.03%)
Mutual labels:  dnd
React Dragtastic
A simple drag and drop library for React which uses the more stable mouseDown/mouseUp event pattern instead of the problematic HTML5 drag and drop API
Stars: ✭ 181 (+135.06%)
Mutual labels:  dnd
Project-GW
A TTRPG conversion of the Guild Wars Games.
Stars: ✭ 23 (-70.13%)
Mutual labels:  dnd
Dungeoneer
A game master helper tool, includes a virtual tabletop, initiative tracker, combat tracker and homebrew management for Dungeons and Dragons 5e.
Stars: ✭ 106 (+37.66%)
Mutual labels:  dnd
withinbot
Within's Discord Bot
Stars: ✭ 18 (-76.62%)
Mutual labels:  dnd
TheMiniIndex
Crowd-sourced library of 3d models (minis, terrain, scatter, etc.) for D&D, Pathfinder, and other tabletop games.
Stars: ✭ 17 (-77.92%)
Mutual labels:  dnd

Dragula for Vue2 via vue-dragula

A Vue.js demo app which demonstrates how to use Dragula with Vue 2 for drag and drop. Includes Time Travel demo (undo/redo) in Named service example.

Build Setup

# install dependencies
npm install

# serve with hot reload at localhost:8080
npm start

# build for production with minification
npm run build

# run unit tests
npm run unit

# run e2e tests
npm run e2e

# run all tests
npm test

For detailed explanation on how things work, checkout the guide and docs for vue-loader.

TODO

  • transitions

Transitions

We would very much like to add support for Vue transitions and transition groups as per discussions in this issue

Here is a jsFiddle Demo: single list with transitions

We might need to use Vue.set to explicitly notify Vue that the underlying array has changed and thus activate the transition effect or change the way we update the Array in ModelManager. Please experiment!

Development

To help improve the plugin, please do the following:

  • fork vue2-dragula and clone it to local disk
  • from within the root of /vue2-dragula run npm link to make a symbolic global link to this package
  • from the root of this demo app, run npm link vue2-dragula to install the vue2-dragula module via the symbolic link

When you make changes to the plugin, make sure you run npm run build in order to compile it to /dist.

You can also set up a watcher to auto-build on every change.

Design

components

  • Home brief overview of the examples
  • GlobalService use of global app service
  • NamedServices named services with copy: true
  • DragEffects drag effects on a named service
  • CustomModelManager immutable model manager with time travel

router

The app is configured with a router which have the following components mounted:

  • / : home
  • /global : global
  • /named : named
  • /effects : effects

To add your own example page

Add a route in routes/index and your example component in /components. Register the component in /components/index.js and update the main navigation in App.vue with a link to your example route.

Bugs and issues

Please report bugs or issues

Using v-dragula directive

  • v-dragula directive on an element must point to an underlying data model (Array) in the VM.
  • service attribute specifies a registered DragulaService
  • drake attribute to use a specific named drake configuration registered on the service

Global app service example

If you don't specify a service the global application level dragula service $dragula.$service will be used

<div class="wrapper">
  <div class="container" v-dragula="colOne" drake="first">
    <div v-for="text in colOne" :key="text" @click="onClick">{{text}} [click me]</div>
  </div>
  <div class="container" v-dragula="colTwo" drake="first">
    <div v-for="text in colTwo" :key="text">{{text}}</div>
  </div>
</div>

Named services

DOM element containers can be configured to use specific named services:

<div class="wrapper">
  <div class="container" v-dragula="colOne" service="first" drake="a">...</div>
  <div class="container" v-dragula="colTwp" service="first" drake="b">...</div>
  <div class="container" v-dragula="colTwo" service="first" drake="b">...</div>
  <div class="container" v-dragula="stocks" service="second" drake="a">...</div>
</div>

Every service has a default drake with default a dragula configuration. You can use the default drake by not setting the drake attribute.

<div class="wrapper">
  <div class="container" v-dragula="colOne" service="first">...</div>
  <div class="container" v-dragula="colTwp" service="first">...</div>
  <div class="container" v-dragula="colTwo" service="first" drake="b">...</div>
  <div class="container" v-dragula="stocks" service="second">...</div>
</div>

Custom Model Manager with Time Travel

Time travel uses the following classes

  • ImmutableModelManager
  • TimeMachine
  • ActionManager

ImmutableModelManager uses seamless-immutable which contains Immutable data structures for JavaScript which are backwards-compatible with normal JS Arrays and Objects.

Implements basic Time Travel with undo/redo back and forward in model history. Play with it and have fun!

The difference for the immutable collections is that methods which would mutate the collection, like push, set, unshift or splice instead return a new immutable collection. Methods which return new arrays like slice or concat also return new immutable collections.

The local VM should maintain a history of transactions that can be undone.

An action consists of:

  • dragIndex index in source model
  • dropIndex index in target model
  • sourceModel source list (or model manager that manages a list, ie. a model)
  • targetModel destination list
  • transitModel item (or list) in transition from source to target

These params are also grouped for the insertAt event:

models: {
  source,
  target,
  transit
},
indexes: {
  drag,
  drop
},
elements: {
  source, // container
  target, // container
  drop // element being dropped/inserted
}

The event handlers insertAt and dropModel can be used to manage the action history. insertAt is the best candidate, as it has access to all the action information.

'effects:insertAt': ({indexes, models, elements}) => {

},

'effects:dropModel': ({name, el, source, target, dragIndex, dropIndex, sourceModel}) => {
}

The ImmModelManager contains all the history methods/tracking but we need to use this in the VM itself. Both the sourceModel and targetModel have a history, so we can undo both and update the VM models to reflect it. The VM/drake model references are encapsulated by the ImmModelManager as modelRef for both source and target models.

ImmModelManager uses a TimeMachine to manage history and handle time transitions. The key method is timeTravel method shown here, which sets the modelRef via updateModelRef(). timeTravel is used internally by both undo and redo. Note that updateModelRef is also called internally by insertAt and removeAt to ensure modelRef is always in sync.

  timeTravel (index) {
    this.log('timeTravel to', index)
    this.model = this.history[index]
    this.updateModelRef()
    return this
  }

The actionManager can be used to manage the done and undone actions on the containers (and models) of the VM.

  created () {
    // ...

    this.actionManager = new actionManager({
      logging: true
    })
    // ...
  }

You can add an onUndo and onRedo handler as follows:

this.actionManager.onUndo((action) => {
  let { models, indexes, elements } = action
  log('onUndo', action, models, indexes, elements)
  // ...
})

In the example we hook the actionManager to some VM methods

  methods: {
    undo () {
      this.actionManager.undo()
    },
    redo () {
      this.actionManager.redo()
    },
    act (action) {
      this.actionManager.act(action)
    }
  },

The insertAt event handler performs a given action via the VM act method.

  'effects:insertAt': ({indexes, models, elements}) => {
    this.act({
      name,
      models,
      indexes
    })

  },

The template includes buttons to trigger undo and redo of those actions via the actionManager.

  <div class="actions">
    <button @click="undo">undo</button>
    <button @click="redo">redo</button>
    <button @click="setRandom">generate</button>
  </div>

Notice

If you check the log, you will see that for TimeMachine [...] set modelRef it sets the VM model containers back to their original on undo but the UI doesn't reflect this (Array pointer) update. What to do to make the UI respond to this change!?

v-for="text in colOne" needs to be forced to re-iterate somehow, see Vue2 list rendering. and see sorting

"To work around this problem we need to add a unique identifier to our array items, and then bind this identifier to key property in our HTML."

Vue wraps an observed array’s mutation methods so they will also trigger view updates.

Vue implements some smart heuristics to maximize DOM element reuse, so replacing an array with another array containing overlapping objects is a very efficient operation.

See also list caveats

  timeTravel (index) {
    this.log('timeTravel to', index)
    this.model = this.history[index]

    // this.modelRef = mutable
    // this.log('set modelRef', this.modelRef, this.model)
    this.modelRef.splice(0, this.modelRef.length)
    for (let item of this.model) {
      this.modelRef.push(item)
    }

    return this
  }

Let us know if you know/find a better, simpler or more efficient way to correctly trigger Vue2 to notice that the Array has been updated and update the VDOM + re-iterate the v-for in the template/view.

You can experiment in setRandom of the VM which uses the same strategy.

Dragula Service pre-configuration

Important Always pre-configure named services with drakes in the created life cycle hook method of the VM.

created () {
  let myService = this.$dragula.createService({
    name: 'my-service',
    drakes: {
      first: {
        copy: true,
      }
    }
  })

  let otherService = this.$dragula.createService({
    name: 'other-service',
    drake: {
      // default drake config
    }
  })

  myService.on({
    drop: (el, container) => {
      console.log('drop: ', el, container)
    }
    ...
  })
}

Styling

Add handles

.handle {
    padding: 0 5px;
    margin-right: 5px;
    background-color: rgba(0, 0, 0, 0.4);
    cursor: move;
}

Add a black border effect on :hover over draggable child elements of a drake container

[drake] >:hover {
  border: 2px solid black
}

UX effects via event handlers

Add/Remove DOM element style classes as UX effects for drag'n drop events. Here using classList

service.on({
  accepts: (drake, el, target) => {
    log('accepts: ', el, target)
    return true // target !== document.getElementById(left)
  },
  drag: (drake, el, container) => {
    log('drag: ', 'el:', el, 'c:', container)
    log('classList', el.classList)
    el.classList.remove('ex-moved')
  },
  drop: (drake, el, container) => {
    log('drop: ', el, container)
    log('classList', el.classList)
    el.classList.add('ex-moved')
  },
  over: (drake, el, container) => {
    log('over: ', el, container)
    log('classList', el.classList)
    el.classList.add('ex-over')
  },
  out: (drake, el, container) => {
    log('out: ', el, container)
    log('classList', el.classList)
    el.classList.remove('ex-over')
  }
})

Sample effects styling

@keyframes fadeIn {
  to {
    opacity: 1;
  }
}

.ex-moved {
  animation: fadeIn .5s ease-in 1 forwards;
  border: 2px solid yellow;
  padding: 2px
}

.ex-over {
  animation: fadeIn .5s ease-in 1 forwards;
  border: 4px solid green;
  padding: 2px
}

Note that assets/styles.css contains most of the styling used, primarily this part of interest:

.container .ex-moved {
  background-color: #e74c3c;
}
.container.ex-over {
  background-color: rgba(255, 255, 255, 0.3);
}
.handle {
  padding: 0 5px;
  margin-right: 5px;
  background-color: rgba(0, 0, 0, 0.4);
  cursor: move;
}

Tip: Please add more examples showcasing dynamic styling and transition effects to better visualize the drag and drop actions/events ;)

Configuring dragula options

Dragula includes loads of options you can use to fine tune the Dnd behaviour.

dragula(containers, {
  isContainer: function (el) {
    return false; // only elements in drake.containers will be taken into account
  },
  moves: function (el, source, handle, sibling) {
    return true; // elements are always draggable by default
  },
  accepts: function (el, target, source, sibling) {
    return true; // elements can be dropped in any of the `containers` by default
  },
  invalid: function (el, handle) {
    return false; // don't prevent any drags from initiating by default
  },
  direction: 'vertical',             // Y axis is considered when determining where an element would be dropped
  copy: false,                       // elements are moved by default, not copied
  copySortSource: false,             // elements in copy-source containers can be reordered
  revertOnSpill: false,              // spilling will put the element back where it was dragged from, if this is true
  removeOnSpill: false,              // spilling will `.remove` the element, if this is true
  mirrorContainer: document.body,    // set the element that gets mirror elements appended
  ignoreInputTextSelection: true     // allows users to select input text, see details below
});

Let us know if this demo helps you and what you build with this example as your foundation.

Feel free to improve it or come with suggestions for new features etc :)

Enjoy!!!

License

MIT

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].