Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to keep sortable arrays and components in order in Vue.js

I'm using Vue.js and Dragula to make a drag and drop tile page. Each tile contains its own set of data, so each tile is a Vue component.

The problem is that once I drag and drop one of the tiles, the DOM elements and the data array in the Vue instance fall out of sync and start to cause problems. Just dragging and dropping doesn't create a problem, but once I drag and drop something and then try to delete it, everything goes wrong.

Here's a fiddle: https://jsfiddle.net/wfjawvnL/13/

Here's my HTML, with the component template:

<body>
  <div id="app">
    <div id="draggable" class="wrapper">
      <template v-for="(index, item) in list">
        <block :id="index" :index="index" :item="item" :name="item.name">
        </block>
      </template>
    </div>
    <div class="footer">
      <pre>{{ $data | json }}</pre>
    </div>
  </div>
</body>

<template id="block-template">
  <div :id="index" :class="[name, 'block']">
    <div class="text name">{{ name }}</div>
    <div>Index: {{ index }}</div>
    <span class="delete" v-on:click="removeItem(item)">&#x2717;</span>
  </div>
</template> 

Here's my Vue instance:

var vm = new Vue({
    el: '#app',

        data: {
        list: [
        {name: 'item1'},
        {name: 'item2'},
        {name: 'item3'},
        {name: 'item4'},
        {name: 'item5'}
      ]
    },

    methods: {
        reorder: function (element, sibling) {
            var children = element.parentNode.querySelectorAll(".block");

            var length = this.list.length;

            var ids = [];

            for (var i = 0; i < length; i++) {
                if (children[i].id) {
                    ids.push(children[i].id);
                }

                children[i].id = i;
            }

            var vm = this;

            var newArr = ids.map(function (id) {
                return vm.list[id];
            });

            this.list = newArr;
        }
    }
}); 

Here's the component:

Vue.component('block', {
    template: '#block-template',  

    props: ['index', 'name', 'item'],

    methods: {
        removeItem: function (item) {
           vm.list.$remove(item);
        }
    }
});

And I'm calling Dragula like this:

dragula([document.getElementById('draggable')]).on('drop', function (el, target, source, sibling) {
    vm.reorder(el, sibling);
});

This fiddle works fine but doesn't use components: https://jsfiddle.net/z2s83yfL/1/

like image 934
revolt_101 Avatar asked Mar 14 '16 14:03

revolt_101


People also ask

How do I sort alphabetically in Vue?

The ListBox supports sorting of available items in the alphabetical order that can be either ascending or descending. This can achieved using sortOrder property. Sort order can be None , Ascending or Descending .

What are the 3 parts of a component in Vue?

Components in Vue are composed of three parts; a template (which is like HTML), styles and JavaScript. These can be split into multiple files or the same .

What are components used for in Vue?

Components are reusable Vue instances with custom HTML elements. Components can be reused as many times as you want or used in another component, making it a child component. Data, computed, watch, and methods can be used in a Vue component.


1 Answers

I had two problems. First, I was binding the array index and using it as an id. The index was being changed, which was also causing the id to change. Second, using v-for on a template tag was causing some issues. I changed the tags to divs and it now works.

Working fiddle: https://jsfiddle.net/wfjawvnL/18/

Vue stuff:

Vue.component('block', {
    template: '#block-template',  

    props: ['name', 'item'],

    methods: {
        removeItem: function (item) {
           vm.list.$remove(item);
        }
    }
});

var vm = new Vue({
    el: '#app',

        data: {
        list: [
        {name: 'item1'},
        {name: 'item2'},
        {name: 'item3'},
        {name: 'item4'},
        {name: 'item5'}
      ]
    },

    ready: function() {
        var self = this;
        var from = null;
        var drake = dragula([document.querySelector('#draggable')]);

        drake.on('drag', function(element, source) {
        var index = [].indexOf.call(element.parentNode.children, element);

        from = index;
        });

        drake.on('drop', function(element, target, source, sibling) {
        var index = [].indexOf.call(element.parentNode.children, element);

        self.list.splice(index, 0, self.list.splice(from, 1)[0]);
        });
    }
});

HTML stuff:

<body>
  <div id="app">
    <div id="draggable" class="wrapper">
      <div class="wrapper" v-for="item in list">
        <block :item="item" :name="item.name">
        </block>
      </div>
    </div>
    <pre>{{ $data | json }}</pre>
  </div>
</body>

<template id="block-template">
  <div :class="[name, 'block']">
    <div class="text name" v-on:click="removeItem(item)">{{ name }}</div>
    <span class="delete" v-on:click="removeItem(item)">&#x2717;</span>
  </div>
</template>
like image 172
revolt_101 Avatar answered Oct 29 '22 06:10

revolt_101