简体   繁体   中英

Vue/Javascript - Sort array of objects based on its existence in another array

I have two arrays, array1 has all objects and array2 has filtered objects based on a search string.

Currently I am rendering array2 (which will contain all objects from array1 when the search string is empty, but will return only filtered objects if search string isn't empty) to the user but I would like to show all objects and style the filtered ones (those which match the search) differently and keep those matching the search query in the top of the array/list and if I even in addition to that could also sort those matched objects alphabetically I would love to.

Here is how I am filtering based on the search query:

export default {
    name: "RegionSelector",
  
    data: () => ({
      searchRegionTextValue: "",
  
      regions: [
        {
          country: "USA",
          flag: "flag-en-us",
          name: "United States",
          language: "English",
        },
        {
          country: "UK",
          flag: "flag-en-gb",
          name: "United Kingdom",
          language: "English",
        },
        {
          country: "DE",
          flag: "flag-de",
          name: "Germany",
          language: " German",
        },
      ],
    }),
  
    methods: {
      // Used in my v-for in the template to style non-matched results differently
      checkRegion(region) {
        var isInArray =
          this.filteredRegions.find(function(el) {
            return el === region;
          }) !== undefined;
        return isInArray;
      },
  
    computed: {
      filteredRegions() {
        function compare(a, b) {
          if (a.name < b.name) return -1;
          if (a.name > b.name) return 1;
  
          return 0;
        }
  
        let regions = this.regions.filter((region) => {
          return (
            region.name
              .toLowerCase()
              .indexOf(this.searchRegionTextValue.toLowerCase()) != -1 ||
            region.language
              .toLowerCase()
              .indexOf(this.searchRegionTextValue.toLowerCase()) != -1
          );
        });
  
        regions.sort(compare);
  
        return regions;
      },
    },
};

And in my template I am rendering them this way (to show all objects but style them differently):

<div v-for="region in regions">
    <span
    :class="checkRegion(region) ? 'matched-query' : 'unmatched-query'">
        {{region.name}}
    </span>
</div>

How can I implement the sorting as mentioned above?

Use the spread operator to append the original array to the result of the filtered array like this

[...filteredRegions(), ...regions]

This adds the matches to the beginning of the array, next we remove duplicates from the array, and we can do that just by wrapping the new Set() around it, just like this

const newRegions = new Set([...filteredRegions(), ...regions])

You can convert it to a plain array just like this.

const newRegions = [...new Set([...filteredRegions(), ...regions])]

See Example code below:

const regions = [
  {
    country: "USA",
    flag: "flag-en-us",
    name: "United States",
    language: "English",
  },
  {
    country: "UK",
    flag: "flag-en-gb",
    name: "United Kingdom",
    language: "English",
  },
  {
    country: "DE",
    flag: "flag-de",
    name: "Germany",
    language: " German",
  },
  {
    country: "NG",
    flag: "flag-ng",
    name: "Nigeria",
    language: "English",
  },
]

function compare(a, b) {
  if (a.name < b.name) return -1;
  if (a.name > b.name) return 1;

  return 0;
}

function filteredRegions(query = 'Nigeria') {
  let regions = regions.filter((region) => {
    return (
      region.name
        .toLowerCase()
        .indexOf(query.toLowerCase()) != -1 || region.language
        .toLowerCase()
        .indexOf(query.toLowerCase()) != -1
    );
  });

  regions.sort(compare);

  return regions;
}

let result = [...new Set([...filteredRegions(), ...regions])]

You can introduce a second computed method which contain the un-matched objects and do another for loop for that array

Or

'might be' more efficiently, introduce another property to flag the object as matched object, and also use that property for the sorting logic

computed: {
  newregions() {
    function compare(a, b) {
      if (a.matched && !b.matched) return -1;
      if (!a.matched && b.matched) return 1;
      if (a.matched && b.matched) {
        if (a.name < b.name) return -1;
        if (a.name > b.name) return 1;
      }

      return 0;
    }

    let regions = this.regions.map((region) => ({
      ...region,
      matched: region.name
          .toLowerCase()
          .indexOf(this.searchRegionTextValue.toLowerCase()) != -1 ||
        region.language
          .toLowerCase()
          .indexOf(this.searchRegionTextValue.toLowerCase()) != -1
    }));

    regions.sort(compare);

    return regions;
  },
},

and then on your template

<div v-for="region in newregions">
    <span
    :class="region.matched ? 'matched-query' : 'unmatched-query'">
        {{region.name}}
    </span>
</div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM