Source: ressource/serviceRessourceAutocomplete.js


'use strict'

const config = require('./config')
const { toAscii } = require('sesajstools')
const excluded = ['a', 'd', 'dans', 'de', 'du', 'et', 'est', 'l', 'le', 'la', 'par', 'un', 'une']

/**
 * Service qui renvoie la liste des filters qui matchent un pattern
 * Ça compare le pattern avec toutes les valeurs définies en conf,
 * pour tous les champs à valeurs imposées
 * @service $ressourceAutocomplete
 */
module.exports = function (component) {
  component.service('$ressourceAutocomplete', function () {
    // passe en minuscule, désaccentue et remplace les char non lettre/chiffre/espace par une espace
    const sanitize = (value) => toAscii(value.toLowerCase()).replace(/[^a-z0-9 ]/g, ' ')

    // on construit un objet dont les props sont la liste des valeurs connues,
    // et leur valeur la liste des searchFilter possibles
    const knownValues = {}
    // on passe en revue tous les champs à valeurs controlées
    Object.entries(config.listes).forEach(([prop, liste]) => {
      Object.entries(liste).forEach(([key, value]) => {
        sanitize(value)
          // on découpe en mots
          .split(' ')
          // on vire les mots à exclure
          .filter(v => !excluded.includes(v))
          .forEach((valueToIndex) => {
            if (!valueToIndex) return
            if (!knownValues[valueToIndex]) knownValues[valueToIndex] = []
            // on ajoute sous la forme d'un filter de recherche
            knownValues[valueToIndex].push({ index: prop, value: key })
          })
      })
    })

    // on liste toutes les entrées à partir de 2 caractères (ça fait un objet avec bcp d'entrées
    // mais toutes les valeurs sont des refs à un objet existant, pas si lourd en RAM),
    // ça permet de construire cet objet une seule fois au chargement du module
    const patternToFilters = {}
    Object.entries(knownValues).forEach(([value, filters]) => {
      patternToFilters[value] = filters
      if (value.length < 3) return // pas d'autre pattern que la valeur
      // sinon on enregistre aussi tous les patterns avec moins de lettres (3 min)
      let i = 2
      while (i++ < value.length) {
        const pattern = value.substr(0, i) // démarre à une longueur 3
        if (patternToFilters[pattern]) {
          // Évite les valeurs multiples avec un Set
          patternToFilters[pattern] = [...new Set([].concat(...[patternToFilters[pattern], filters]))]
        } else {
          patternToFilters[pattern] = filters
        }
      }
    })

    /**
     * @typedef searchFilter
     * @type Object
     * @property {string} index La propriété de Ressource sur laquelle filtrer
     * @property {string|number} value La valeur à filtrer
     */
    /**
     * Retourne les filtres de recherche qui peuvent correspondre à ce pattern
     * (on nettoie et ne prend que le premier mot)
     * @memberOf $ressourceAutocomplete
     * @param {string} pattern
     * @return {searchFilter[]}
     */
    const getFilters = (pattern) => patternToFilters[sanitize(pattern).replace(/ .*/, '')] || []

    return {
      getFilters
    }
  })
}