Frank
Frank

Reputation: 123

Change ordering of a array of objects with up/down buttons

I'm having the following issue:

I want in the frondend to get a list of items based on the key 'order':

<div id="app"> 
  <ul>
    <li v-for="item in sorted">
      {{item.order}} {{item.title}} 
      <button @click="changeOrderDown(item)">down</button>
      <button @click="changeOrderUp(item)">up</button>
    </li>
  </ul>
</div>

based on the JSON you see below. When you click the button I want to swap out for example order 1 -> 2 and then 2 becomes 1

items: [
      {
        title: "test", 
        order: 1
      },
      {
        title: "test2", 
        order: 2
      },
      {
        title: "test3", 
        order: 3
      }
    ]

I keep getting a duplicate key error cause i first change the first key and then the second. i resolved it to update the whole object at ones. that seems to work but still it doesn't behave the correct way.

computed: {
sorted() {
      function compare(a, b) {
          let comparison = 0;
          if (a.order > b.order) {
            comparison = 1;
          } else if (a.order < b.order) {
            comparison = -1;
          }
          return comparison;
        }
      return this.items.sort(compare)
},
  },

  methods: {
    changeOrderDown(currentItem) {
      let temp = this.items
      let old_value = parseFloat(currentItem.order)
      let new_value = parseFloat(currentItem.order) + 1;
      console.log(old_value, new_value)
      temp.filter(o => o.order === old_value)[0].order = new_value;
      temp.filter(o => o.order === new_value)[0].order = old_value;
      this.items = temp;
    },
     changeOrderUp(currentItem) {
      let temp = this.items
      let old_value = parseFloat(currentItem.order)
      let new_value = parseFloat(currentItem.order) - 1;
      console.log(old_value, new_value)
      temp.filter(o => o.order === old_value)[0].order = new_value;
      temp.filter(o => o.order === new_value)[0].order = old_value;
      this.items = temp;
    },
  }

I made a codepen down below with the code from above. this is kinda a working example but it doesn't feel right. Can someone give me a push in the right direction?

https://codepen.io/frank-derks/pen/BaQVOZV

Upvotes: 0

Views: 1145

Answers (2)

Leo Giesen
Leo Giesen

Reputation: 136

This solution is similar to Tim's, but a bit simpler and easier to follow:

<template>
  <v-app>
    <ul>
      <li v-for="(item, index) in items" :key="index">
        {{item.order}} {{item.title}} {{index}} 
        <button @click="changeOrderDown(item, index)" v-if="index != items.length-1">down</button>
        <button @click="changeOrderUp(item, index)" v-if="index != 0">up</button>
      </li>
    </ul>
  </v-app>
</template>

<script>

export default {
  name: 'App',
  data: () => ({
    items: [
      {
        title: "test1", 
        order: 1
      },
      {
        title: "test2", 
        order: 2
      },
      {
        title: "test3", 
        order: 3
      }
    ]
  }),
  methods: {
    changeOrderDown(item, index) {
      // save clicked item in temporary variable
      let temp = item
      // move the following item to the clicked element
      this.items[index] = this.items[index + 1]
      // move clicked item to destination 
      this.items[index + 1] = temp
    },
     changeOrderUp(item, index) {
       // save clicked item in temporary variable
      let temp = item
      // move the following item to the clicked element
      this.items[index] = this.items[index - 1]
      // move clicked item to destination 
      this.items[index - 1] = temp      
    },
  }
};
</script>
<template>
  <v-app>
    <ul>
      <li v-for="(item, index) in items" :key="index">
        {{item.order}} {{item.title}} {{index}} 
        <button @click="changeOrderDown(item, index)" v-if="index != items.length-1">down</button>
        <button @click="changeOrderUp(item, index)" v-if="index != 0">up</button>
      </li>
    </ul>
  </v-app>
</template>

<script>

export default {
  name: 'App',
  data: () => ({
    items: [
      {
        title: "test1", 
        order: 1
      },
      {
        title: "test2", 
        order: 2
      },
      {
        title: "test3", 
        order: 3
      }
    ]
  }),
  methods: {
    changeOrderDown(item, index) {
      // save clicked item in temporary variable
      let temp = item
      // move the following item to the clicked element
      this.items[index] = this.items[index + 1]
      // move clicked item to destination 
      this.items[index + 1] = temp
    },
     changeOrderUp(item, index) {
       // save clicked item in temporary variable
      let temp = item
      // move the following item to the clicked element
      this.items[index] = this.items[index - 1]
      // move clicked item to destination 
      this.items[index - 1] = temp      
    },
  }
};
</script>

Upvotes: 1

Tim
Tim

Reputation: 1229

Interesting challenge. Using my Vue 2 CLI sandbox app, I came up with functionality that doesn't require an 'order' property. Here is the component code:

<template>
  <div class="swap-array-objects">
    <h3>SwapArrayObjects.vue</h3>
    <div class="row">
      <div class="col-md-6">
        <table class="table table-bordered">
          <thead>
            <tr>
              <th>TITLE</th>
              <th>&nbsp;</th>
              <th>&nbsp;</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(item, index) in items" :key="index">
              <td>{{ item.title }}</td>
              <td>
                <button class="btn btn-secondary btn-sm" @click="moveUp(index)">Up</button>
              </td>
              <td>
                <button class="btn btn-secondary btn-sm" @click="moveDown(index)">Down</button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        items: [
          {
            title: 'title1'
          },
          {
            title: 'title2'
          },
          {
            title: 'title3'
          },
          {
            title: 'title4'
          },
          {
            title: 'title5'
          }
        ]
      }
    },
    methods: {
      moveUp(index) {
        if (index === 0) {
          return;
        }

        let priorIndex = index - 1;

        let itemCopy = {...this.items[index]};
        let priorItemCopy = {...this.items[priorIndex]};

        // Swap array position with prior element
        this.$set(this.items, priorIndex, itemCopy);
        this.$set(this.items, index, priorItemCopy);
      },
      moveDown(index) {
        if (index === this.items.length - 1) {
          return;
        }

        let subsequentIndex = index + 1;

        let itemCopy = {...this.items[index]};
        let subsequentItemCopy = {...this.items[subsequentIndex]};

        // Swap array positions with subsequent element
        this.$set(this.items, subsequentIndex, itemCopy);
        this.$set(this.items, index, subsequentItemCopy);
      }
    }
  }
</script>

Upvotes: 2

Related Questions