Search code examples
javascriptsortingecmascript-2016

How to sort alphabetically an array of object depending on name and a query string?


I have the following function:

const sort = (query) => (choiceA, choiceB) => choiceA.name.toLowerCase().localeCompare(choiceB.name.toLowerCase()),

It is used as follow:

newSearches = newSearches.sort(sort(query));

For example, in this example, tarascon should be first result, etc...

const query = 't';

const choices = [
  {
    "name": "Marseille 1er arrondissement"
  },
  {
    "name": "Marseille 2ème arrondissement"
  },
  {
    "name": "Marseille 3ème arrondissement"
  },
  {
    "name": "Marseille 4ème arrondissement"
  },
  {
    "name": "Marseille 5ème arrondissement"
  },
  {
    "name": "Marseille 6ème arrondissement"
  },
  {
    "name": "Marseille 7ème arrondissement"
  },
  {
    "name": "Marseille 8ème arrondissement"
  },
  {
    "name": "Marseille 9ème arrondissement"
  },
  {
    "name": "Marseille 10ème arrondissement"
  },
  {
    "name": "Marseille 11ème arrondissement"
  },
  {
    "name": "Marseille 12ème arrondissement"
  },
  {
    "name": "Marseille 13ème arrondissement"
  },
  {
    "name": "Marseille 14ème arrondissement"
  },
  {
    "name": "Marseille 15ème arrondissement"
  },
  {
    "name": "Marseille 16ème arrondissement"
  },
  {
    "name": "Le Tholonet"
  },
  {
    "name": "Saint-Marc-Jaumegarde"
  },
  {
    "name": "Beaurecueil"
  },
  {
    "name": "Saint-Antonin-sur-Bayon"
  },
  {
    "name": "Aix-en-Provence"
  },
  {
    "name": "Saint-Étienne-du-Grès"
  },
  {
    "name": "Mas-Blanc-des-Alpilles"
  },
  {
    "name": "Mimet"
  },
  {
    "name": "Simiane-Collongue"
  },
  {
    "name": "Port-de-Bouc"
  },
  {
    "name": "Coudoux"
  },
  {
    "name": "La Destrousse"
  },
  {
    "name": "Lamanon"
  },
  {
    "name": "Puyloubier"
  },
  {
    "name": "Saint-Paul-lès-Durance"
  },
  {
    "name": "Vernègues"
  },
  {
    "name": "Saint-Savournin"
  },
  {
    "name": "Gardanne"
  },
  {
    "name": "Aurons"
  },
  {
    "name": "Ventabren"
  },
  {
    "name": "Peypin"
  },
  {
    "name": "Vauvenargues"
  },
  {
    "name": "Vitrolles"
  },
  {
    "name": "Berre-l'Étang"
  },
  {
    "name": "Miramas"
  },
  {
    "name": "Tarascon"
  },
  {
    "name": "Les Pennes-Mirabeau"
  },
  {
    "name": "Gignac-la-Nerthe"
  },
  {
    "name": "Allauch"
  },
  {
    "name": "Arles"
  },
  {
    "name": "Saint-Rémy-de-Provence"
  },
  {
    "name": "Châteauneuf-les-Martigues"
  },
  {
    "name": "Port-Saint-Louis-du-Rhône"
  },
  {
    "name": "Septèmes-les-Vallons"
  },
  {
    "name": "Cornillon-Confoux"
  },
  {
    "name": "Saint-Chamas"
  },
  {
    "name": "Cassis"
  },
  {
    "name": "Fos-sur-Mer"
  },
  {
    "name": "Salon-de-Provence"
  },
  {
    "name": "Saint-Martin-de-Crau"
  },
  {
    "name": "Bouc-Bel-Air"
  },
  {
    "name": "La Barben"
  },
  {
    "name": "Pélissanne"
  },
  {
    "name": "Rognac"
  },
  {
    "name": "Charleval"
  },
  {
    "name": "Roquevaire"
  },
  {
    "name": "Mallemort"
  },
  {
    "name": "Plan-de-Cuques"
  },
  {
    "name": "Auriol"
  },
  {
    "name": "Aubagne"
  },
  {
    "name": "Lambesc"
  },
  {
    "name": "Gémenos"
  },
  {
    "name": "Eyguières"
  },
  {
    "name": "Grans"
  },
  {
    "name": "Saintes-Maries-de-la-Mer"
  },
  {
    "name": "Carnoux-en-Provence"
  },
  {
    "name": "Cabriès"
  },
  {
    "name": "Jouques"
  },
  {
    "name": "Martigues"
  },
  {
    "name": "Éguilles"
  },
  {
    "name": "Paradou"
  },
  {
    "name": "Maussane-les-Alpilles"
  },
  {
    "name": "Les Baux-de-Provence"
  },
  {
    "name": "Trets"
  },
  {
    "name": "Sénas"
  },
  {
    "name": "La Fare-les-Oliviers"
  },
  {
    "name": "Meyreuil"
  },
  {
    "name": "La Ciotat"
  },
  {
    "name": "Ceyreste"
  },
  {
    "name": "Saint-Estève-Janson"
  },
  {
    "name": "Le Puy-Sainte-Réparade"
  },
  {
    "name": "Carry-le-Rouet"
  },
  {
    "name": "La Roque-d'Anthéron"
  },
  {
    "name": "Meyrargues"
  },
  {
    "name": "Lançon-Provence"
  },
  {
    "name": "Marignane"
  },
  {
    "name": "Fuveau"
  },
  {
    "name": "La Bouilladisse"
  },
  {
    "name": "Belcodène"
  },
  {
    "name": "Saint-Victoret"
  },
  {
    "name": "Le Rove"
  },
  {
    "name": "Saint-Cannat"
  },
  {
    "name": "Venelles"
  },
  {
    "name": "Cuges-les-Pins"
  },
  {
    "name": "Riboux"
  },
  {
    "name": "Peynier"
  },
  {
    "name": "Châteauneuf-le-Rouge"
  },
  {
    "name": "Rousset"
  },
  {
    "name": "Istres"
  },
  {
    "name": "Ensuès-la-Redonne"
  },
  {
    "name": "La Penne-sur-Huveaune"
  },
  {
    "name": "Roquefort-la-Bédoule"
  },
  {
    "name": "Rognes"
  },
  {
    "name": "Gréasque"
  },
  {
    "name": "Peyrolles-en-Provence"
  },
  {
    "name": "Velaux"
  },
  {
    "name": "Mouriès"
  },
  {
    "name": "Saint-Mitre-les-Remparts"
  },
  {
    "name": "Aureille"
  },
  {
    "name": "Cadolive"
  },
  {
    "name": "Sausset-les-Pins"
  },
  {
    "name": "Alleins"
  },
  {
    "name": "Fontvieille"
  },
  {
    "name": "Fourques"
  },
  {
    "name": "Artigues"
  },
  {
    "name": "Rians"
  },
  {
    "name": "Plan-d'Aups-Sainte-Baume"
  },
  {
    "name": "Saint-Zacharie"
  },
  {
    "name": "Nans-les-Pins"
  },
  {
    "name": "Pourrières"
  },
  {
    "name": "Pertuis"
  },
  {
    "name": "Mirabeau"
  },
  {
    "name": "La Bastidonne"
  },
  {
    "name": "Beaumont-de-Pertuis"
  },
  {
    "name": "Puyvert"
  },
  {
    "name": "Cucuron"
  },
  {
    "name": "Lourmarin"
  },
  {
    "name": "Cadenet"
  },
  {
    "name": "Vaugines"
  },
  {
    "name": "Vitrolles-en-Lubéron"
  },
  {
    "name": "Grambois"
  },
  {
    "name": "Cabrières-d'Aigues"
  },
  {
    "name": "Peypin-d'Aigues"
  },
  {
    "name": "Sannes"
  },
  {
    "name": "Ansouis"
  },
  {
    "name": "La Bastide-des-Jourdans"
  },
  {
    "name": "La Tour-d'Aigues"
  },
  {
    "name": "La Motte-d'Aigues"
  },
  {
    "name": "Lauris"
  },
  {
    "name": "Villelaure"
  },
  {
    "name": "Saint-Martin-de-la-Brasque"
  }
];


const sort = (query) => (choiceA, choiceB) => !isNaN(query) || (
    choiceA.name.toLowerCase().localeCompare(choiceB.name.toLowerCase()) && query.indexOf(choiceA.name.toLowerCase()) === -1
);

const filter = (query) => (choice) => choice.name.toLowerCase().includes(query.toLowerCase());

document.getElementById('search').addEventListener('click', () => {
   const q = document.getElementById('query').value;
   const newSearches = choices.filter(filter(q)).sort(sort(q));
   document.getElementById('result').innerHTML = JSON.stringify(newSearches.map(s => s.name), null, 2);
}, true);
#result {
  display: block;
  width: 100vw;
}
<input id="query" type="text" />
<button id="search" type="button">search<button>

<pre id="result"></pre>

enter code hereSo far, this will sort alphabetically just by comparing the names, regardless of the query, how can I update it in order to sort it alphabetically depending on the query providing?


Solution

  • You could change it to something like this:

    const sort = (query) => (choiceA, choiceB) => {
      const name1 = choiceA.name.toLowerCase(),
            name2 = choiceB.name.toLowerCase(),
            q = query.toLowerCase();
    
      return (name2.startsWith(q) - name1.startsWith(q)) 
              || (name1.localeCompare(name2))
    };
    

    First take the difference between

    name2.startsWith(q) - name1.startsWith(q)
    

    startsWith returns a boolean and they coerced to 0 or 1 when they are used in a mathematical context

    true - false === 1
    false - true === -1
    true - true === 0
    

    If name1 starts with the query but name2 doesn't, then it becomes false - true which is -1. So, choiceA is prioritized compared to choiceB

    If both start with query or both don't start with query, then the the subtraction returns 0. This is a falsy value. So, in this case, the || operator will check the second condition which will sort it alphabetically.

    Here's a snippet:

    const choices=[{name:"Marseille 1er arrondissement"},{name:"Marseille 2ème arrondissement"},{name:"Marseille 3ème arrondissement"},{name:"Marseille 4ème arrondissement"},{name:"Marseille 5ème arrondissement"},{name:"Marseille 6ème arrondissement"},{name:"Marseille 7ème arrondissement"},{name:"Marseille 8ème arrondissement"},{name:"Marseille 9ème arrondissement"},{name:"Marseille 10ème arrondissement"},{name:"Marseille 11ème arrondissement"},{name:"Marseille 12ème arrondissement"},{name:"Marseille 13ème arrondissement"},{name:"Marseille 14ème arrondissement"},{name:"Marseille 15ème arrondissement"},{name:"Marseille 16ème arrondissement"},{name:"Le Tholonet"},{name:"Saint-Marc-Jaumegarde"},{name:"Beaurecueil"},{name:"Saint-Antonin-sur-Bayon"},{name:"Aix-en-Provence"},{name:"Saint-Étienne-du-Grès"},{name:"Mas-Blanc-des-Alpilles"},{name:"Mimet"},{name:"Simiane-Collongue"},{name:"Port-de-Bouc"},{name:"Coudoux"},{name:"La Destrousse"},{name:"Lamanon"},{name:"Puyloubier"},{name:"Saint-Paul-lès-Durance"},{name:"Vernègues"},{name:"Saint-Savournin"},{name:"Gardanne"},{name:"Aurons"},{name:"Ventabren"},{name:"Peypin"},{name:"Vauvenargues"},{name:"Vitrolles"},{name:"Berre-l'Étang"},{name:"Miramas"},{name:"Tarascon"},{name:"Les Pennes-Mirabeau"},{name:"Gignac-la-Nerthe"},{name:"Allauch"},{name:"Arles"},{name:"Saint-Rémy-de-Provence"},{name:"Châteauneuf-les-Martigues"},{name:"Port-Saint-Louis-du-Rhône"},{name:"Septèmes-les-Vallons"},{name:"Cornillon-Confoux"},{name:"Saint-Chamas"},{name:"Cassis"},{name:"Fos-sur-Mer"},{name:"Salon-de-Provence"},{name:"Saint-Martin-de-Crau"},{name:"Bouc-Bel-Air"},{name:"La Barben"},{name:"Pélissanne"},{name:"Rognac"},{name:"Charleval"},{name:"Roquevaire"},{name:"Mallemort"},{name:"Plan-de-Cuques"},{name:"Auriol"},{name:"Aubagne"},{name:"Lambesc"},{name:"Gémenos"},{name:"Eyguières"},{name:"Grans"},{name:"Saintes-Maries-de-la-Mer"},{name:"Carnoux-en-Provence"},{name:"Cabriès"},{name:"Jouques"},{name:"Martigues"},{name:"Éguilles"},{name:"Paradou"},{name:"Maussane-les-Alpilles"},{name:"Les Baux-de-Provence"},{name:"Trets"},{name:"Sénas"},{name:"La Fare-les-Oliviers"},{name:"Meyreuil"},{name:"La Ciotat"},{name:"Ceyreste"},{name:"Saint-Estève-Janson"},{name:"Le Puy-Sainte-Réparade"},{name:"Carry-le-Rouet"},{name:"La Roque-d'Anthéron"},{name:"Meyrargues"},{name:"Lançon-Provence"},{name:"Marignane"},{name:"Fuveau"},{name:"La Bouilladisse"},{name:"Belcodène"},{name:"Saint-Victoret"},{name:"Le Rove"},{name:"Saint-Cannat"},{name:"Venelles"},{name:"Cuges-les-Pins"},{name:"Riboux"},{name:"Peynier"},{name:"Châteauneuf-le-Rouge"},{name:"Rousset"},{name:"Istres"},{name:"Ensuès-la-Redonne"},{name:"La Penne-sur-Huveaune"},{name:"Roquefort-la-Bédoule"},{name:"Rognes"},{name:"Gréasque"},{name:"Peyrolles-en-Provence"},{name:"Velaux"},{name:"Mouriès"},{name:"Saint-Mitre-les-Remparts"},{name:"Aureille"},{name:"Cadolive"},{name:"Sausset-les-Pins"},{name:"Alleins"},{name:"Fontvieille"},{name:"Fourques"},{name:"Artigues"},{name:"Rians"},{name:"Plan-d'Aups-Sainte-Baume"},{name:"Saint-Zacharie"},{name:"Nans-les-Pins"},{name:"Pourrières"},{name:"Pertuis"},{name:"Mirabeau"},{name:"La Bastidonne"},{name:"Beaumont-de-Pertuis"},{name:"Puyvert"},{name:"Cucuron"},{name:"Lourmarin"},{name:"Cadenet"},{name:"Vaugines"},{name:"Vitrolles-en-Lubéron"},{name:"Grambois"},{name:"Cabrières-d'Aigues"},{name:"Peypin-d'Aigues"},{name:"Sannes"},{name:"Ansouis"},{name:"La Bastide-des-Jourdans"},{name:"La Tour-d'Aigues"},{name:"La Motte-d'Aigues"},{name:"Lauris"},{name:"Villelaure"},{name:"Saint-Martin-de-la-Brasque"}];
    
    const sort = (query) => (choiceA, choiceB) => {
      const name1 = choiceA.name.toLowerCase(),
            name2 = choiceB.name.toLowerCase(),
            q = query.toLowerCase();
            
      return (name2.startsWith(q) - name1.startsWith(q)) 
              || (name1.localeCompare(name2))
    };
    
    const filter = (query) => (choice) => choice.name.toLowerCase().includes(query.toLowerCase());
    
    document.getElementById('search').addEventListener('click', () => {
      const q = document.getElementById('query').value;
      const newSearches = choices.filter(filter(q)).sort(sort(q));
      document.getElementById('result').innerHTML = JSON.stringify(newSearches.map(s => s.name), null, 2);
    }, true);
    #result {
      display: block;
      width: 100vw;
    }
    <input id="query" type="text" />
    <button id="search" type="button">search<button>
    
    <pre id="result"></pre>