4

I've been working with vue js 1.27 on a project and I need to be able to sort a list by numeric values, Alphabetic and reverse order. I've been trying this all day with not really much progress.

I've left some of my previous code in my code sample to let you know what I've already attempted.

{% extends 'base.html.twig' %}

{% block body %}

<div id="wrap">
    <h2>Select a category</h2> 
    <ul id="categorySelect">
        <li v-for="cat in categories | orderBy reverse" @click="selectCategory(cat)" class="${cat.selectedCategory == category ? 'selected' : ''}">${cat.title}</li>
    </ul>
</div>

{% endblock %}

{% block javascripts %}

    <script type="text/javascript">

        Vue.config.delimiters = ['${', '}'];

        new Vue({
        el: '#wrap',
        data: {
            //reverse: -1,
            wasClicked: true,
            selectedCategory: null,
            categories: [{
                title: 'ALL',
                category: null
            }, 
            {
                title: 'CATE',
                category: 'sport'
            }, 
            {
                title: 'DOG',
                category: 'sport'
            },
            {
                title: 'SPEED',
                category: 'sport'
            },
            {
                title: 'CAT',
                category: 'sport'
            },
            {
                title: 'SPORT',
                category: 'sport'
            },
            {
                title: 'ART',
                category: 'sport'
            },
            {
                title: 'PEOPLE',
                category: 'people'
            }, 
            {
                title: 'CAR',
                category: 'car'
            }]
        },
        filters: {
            categoryFilter: function (infoBlocs) {
                return this.wasClicked ? this.categories : {};
            },

            caseFilter: function () {
                if (this.wasClicked) {
                    return this.reverseArray();
                }
                return this.alphaSortByKey(this.categories, 'category');
            },

            reverse: function(value) {
                // slice to make a copy of array, then reverse the copy
                return value.slice().reverse();
            }
        },
        methods: {
            selectCategory: function(category) {
                //this.wasClicked =! this.wasClicked;
                //this.categories = this.alphaSortByKey(this.categories, 'category');
                // if (this.reverse) {
                //  this.categories = this.alphaSortByKey(this.categories, 'category');
                // }
                // else {
                //  this.categories = this.reverseArray();
                // }

                if (this.reverse) {
                    this.categories = this.alphaSortByKey(this.categories, 'category');
                    this.reverse = false;
                }
                else {
                    this.categories = this.reverseArray();
                    //this.reverse = true;
                }
            },

            alphaSortByKey: function (arr, key) {
                arr.sort(function (a, b) {
                    if (a[key] < b[key])
                            return -1;
                    if (a[key] > b[key])
                            return 1;
                    return 0;
                });
                return arr;
            },

            reverseArray: function () {
                return this.categories.reverse();
            },

            changeOrder: function (event) {
              var self = this;
              self.reverse = self.reverse * -1
              var newItems = self.categories.slice().sort(function (a, b) { 
                var result;
                if (a.name < b.name) {
                  result = 1
                }
                else if (a.name > b.name) {
                  result = -1
                }
                else {
                  result = 0
                }
                return result * self.reverse
              })
              newItems.forEach(function (item, index) {
                item.position = index;
              });

              this.categories = newItems;
            }
        } 
    });


    </script>

{% endblock %}
radbyx
  • 9,352
  • 21
  • 84
  • 127
TheMan68
  • 1,429
  • 6
  • 26
  • 48

3 Answers3

4

Here is a fiddle with working functionality to sort and reverse the order of your array. For reverse I just used the built in reverse() Javascript function. For the alphanumeric sort I borrowed the solution from this answer: https://stackoverflow.com/a/4340339/6913895

https://jsfiddle.net/n1tbmgo9/

Html:

<div id="wrap">
    <h2>Select a category</h2>
    <button @click="sort">
    Sort alphanumeric
    </button>
    <button @click="reverse">
    Reverse list
    </button>
    <ul id="categorySelect">
        <li v-for="cat in categories">${cat.title}</li>
    </ul>
</div>

Javascript:

Vue.config.delimiters = ['${', '}'];

new Vue({
  el: '#wrap',
  data: {
    selectedCategory: null,
    categories: [{
      title: 'ALL',
      category: null
    }, 
    {
      title: 'CATE',
      category: 'sport'
    }, 
    {
      title: 'DOG',
      category: 'sport'
    },
    {
      title: 'SPEED',
      category: 'sport'
    },
    {
      title: 'CAT',
      category: 'sport'
    },
    {
      title: 'SPORT',
      category: 'sport'
    },
    {
      title: 'ART',
      category: 'sport'
    },
    {
      title: 'PEOPLE',
      category: 'people'
    }, 
    {
      title: 'CAR',
      category: 'car'
    }]
  },
  methods: {
    sort: function () {
        this.categories.sort(this.sortAlphaNum);
    },
    reverse: function () {
        this.categories.reverse();
    },
    sortAlphaNum: function (a,b) {
      var reA = /[^a-zA-Z]/g;
      var reN = /[^0-9]/g;
      var aA = a.title.replace(reA, "");
      var bA = b.title.replace(reA, "");
      if(aA === bA) {
          var aN = parseInt(a.title.replace(reN, ""), 10);
          var bN = parseInt(b.title.replace(reN, ""), 10);
          return aN === bN ? 0 : aN > bN ? 1 : -1;
      } else {
          return aA > bA ? 1 : -1;
      }
    }
  }
});

The built in reverse() function is straightforward so I will elaborate on the sortAlphaNum() sort function. That function is passed into the sort() function and must return either 1, 0, or -1 to indicate if the objects passed in should be moved in a particular direction in the array.

The variables reA and reN are regexes to identify alphabetic and numeric characters, respectively.

First the function removes all alphabet characters from the titles of the two objects passed in and compares them for equality.
var aA = a.title.replace(reA, ""); and var bA = b.title.replace(reA, "");

If they are not equal then it means we have alphabet characters (as opposed to just numeric input) and we can sort them accordingly. return aA > bA ? 1 : -1;

If the titles with alphabet characters stripped are equal (if(aA === bA)) then we remove numeric digits from the object titles (leaving non-numeric characters). var aN = parseInt(a.title.replace(reN, ""), 10); var bN = parseInt(b.title.replace(reN, ""), 10);

Then we compare the resulting variables and return the appropriate sorting value (1, 0, -1). return aN === bN ? 0 : aN > bN ? 1 : -1;

Community
  • 1
  • 1
mrogers
  • 1,187
  • 2
  • 15
  • 22
  • Thank you for your help I will try this as soon as I can get some time at work today and I will be back to mark as answered if it works for me. Thank you – TheMan68 Dec 20 '16 at 03:45
  • Your a life saver. Thank you so much – TheMan68 Dec 20 '16 at 03:50
  • Hi there. I'm just implementing your code now and I have to say, It works great but I've got not a clue how it's working. Do you think you'd be able to explain it any for clarity? Thank you very much – TheMan68 Dec 22 '16 at 08:28
  • Brilliant. You've been a massive help. Thank you. I wish there was a way to give you a higher rating. – TheMan68 Dec 23 '16 at 03:15
  • Hi there. I was looking through my questions and i actually have something which is a slight add on to this question, so I thought I'd drop by and ask. Right now I'm using the '| filterBy search' (search is a text input v-model) in the v-for loop, But today I noticed that it only filters based on the items that is currently paginated. So is there a way that I can get it to filter the whole array when I search? Thank you for you help once again – TheMan68 Feb 03 '17 at 07:53
  • I'd suggest opening a new question with your relevant code for that issue. I'll keep an eye open for it. – mrogers Feb 03 '17 at 17:23
  • Thank you very much. But I did some playing around with it and I actually got a custom filter working after all. Ill update my question to show my changes for other people – TheMan68 Feb 06 '17 at 03:28
0

create a computed property where you manually sort it and then loop for that instead the data prop

  • Thank you for your reply. I will be trying the answer above. And if I don't have any luck then ill look into your comments. – TheMan68 Dec 20 '16 at 03:46
0

There is my List Component implementation with client-side order implementation:

<template>
  <div>
    <table class="table table-bordered" v-if="data.length">
      <thead>
        <tr>
          <th v-for="(colValue, colKey) in cols" :key="colKey">
            <a @click="sort(colKey)" href="javascript:void(0)">
              {{colValue}}
              <icon :name="(sortColumn === colKey) ? (sortAsc ? 'sort-down' : 'sort-up') : 'sort'"></icon>
            </a>
          </th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="row in data" :key="row.id">
          <td v-for="(colValue, colKey) in cols" :key="row.id + colKey">{{row[colKey]}}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
import _ from 'lodash';
import apiServer from '@/utils/apiServer'; // 

export default {
  name: 'List',

  data() {
    return {
      data: [],
      sortColumn: '',
      sortAsc: true
    };
  },

  props: {
    cols: {
      type: Object,
      required: true
    },
    apiEndpoint: {
      type: String,
      required: true
    }
  }

  created() {
    this.fetchData();
  },

  watch: {
    '$route': 'fetchData'
  },

  methods: {
    async fetchData() {
      const response = await apiServer.get(this.apiEndpoint);
      this.data = response.data;
    },

    sort(colKey) {
      this.data = _.sortBy(this.data, [colKey]);

      if (this.sortColumn === colKey) {
        if (!this.sortAsc) {
          this.data = _.reverse(this.data);
        }

        this.sortAsc = !this.sortAsc;
      } else {
        this.sortAsc = false;
      }

      this.sortColumn = colKey;
    }
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
</style>

Usage example

<template>
  <div>
    <h1>Orders</h1>

    <List :cols="cols" api-endpoint="/orders" title="Orders" />
  </div>
</template>

<script>
import List from '@/components/List.vue';

export default {
  name: 'OrderList',
  components: { List },

  data() {
    return {
      cols: {
        id: 'Id',
        title: 'Title',
        created_at: 'Created at'
      }
    };
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
</style>
Eduardo Cuomo
  • 17,828
  • 6
  • 117
  • 94