Автозаполнение с помощью Firebase

Как использовать Firebase для базового автоматического завершения/предварительного просмотра текста?

Например, представьте себе блог, поддерживаемый Firebase, где блоггер может помечать сообщения тегами. Поскольку блоггер помещает новый пост, было бы полезно, если бы они могли видеть все существующие в настоящее время теги, которые соответствовали первым нажатиям клавиш, которые они ввели. Поэтому, если "блог", "черный", "пылающие седла" и "бульдоги" были тегами, если пользователь набирает "bl", они получают первые три, но не "бульдоги".

Моя первоначальная мысль заключалась в том, что мы можем установить тег с приоритетом тега и использовать startAt, чтобы наш запрос выглядел примерно так:

fb.child('tags').startAt('bl').limitToFirst(5).once('value', function(snap) {
  console.log(snap.val()) 
});

Но это также вернет "бульдог" как один из результатов (а не конец света, но не самый лучший). Использование startAt ('bl'). EndAt ('bl') не возвращает результатов. Есть ли другой способ сделать это?

(Я знаю, что один из вариантов заключается в том, что мы можем использовать сервер поиска, например ElasticSearch, для - см. https://www.firebase.com/blog/2014-01-02-queries-part-two.html - но я хотел бы сохранить как можно больше в Firebase.)

Edit

Как предложил Като, вот конкретный пример. У нас есть 20 000 пользователей, их имена хранятся как таковые:

/users/$userId/name

Часто пользователи будут искать другого пользователя по имени. Когда пользователь ищет своего собеседника, мы хотели бы, чтобы выпадающий список заполнил список пользователей, имена которых начинаются с букв, которые вводил поисковик. Поэтому, если бы я набрал "Ja", я бы ожидал увидеть "Джейка Хеллера", "Джейк Гилленхаал", "Джек Донахи" и т.д. В раскрывающемся списке.

Ответ 1

Как вдохновлено комментариями Kato - одним из способов решения этой проблемы является установка приоритета в поле, которое вы хотите найти для своего автозаполнения, и использовать функции startAt(), limit() и фильтрации на стороне клиента только для возврата результаты, которые вы хотите. Вы хотите убедиться, что приоритет и поисковый запрос имеют нижний регистр, поскольку Firebase чувствителен к регистру.

Это грубый пример, демонстрирующий это, используя пример пользователей, который я изложил в вопросе:

Для поиска "ja" , если все пользователи имеют свой приоритет, установленный в нижней строке имени пользователя:

fb.child('users').
  startAt('ja'). // The user-inputted search
  limitToFirst(20).
  once('value', function(snap) {
    for(key in snap.val()){
      if(snap.val()[key].indexOf('ja') === 0) {
        console.log(snap.val()[key];
      }
    }
});

Это должно возвращать только имена, которые на самом деле начинаются с "ja" (даже если Firebase фактически возвращает имена по алфавиту после "ja" ).

Я предпочитаю использовать limitToFirst (20), чтобы размер ответа был небольшим, и потому, что реально, вам никогда не понадобится больше 20 для раскрывающегося списка автозаполнения. Вероятно, есть более эффективные способы фильтрации, но это должно хотя бы продемонстрировать концепцию.

Надеюсь, это поможет кому-то! И вполне возможно, что у парней Firebase есть лучший ответ.

(Обратите внимание, что это очень ограниченно - если кто-то ищет фамилию, он не вернет то, что они ищут. Следовательно, лучший ответ - это, вероятно, использование бэкэнда поиска с чем-то вроде Kato Flashlight.)

Ответ 2

Я знаю, что это старая тема, но она по-прежнему актуальна. На основании ответа Нила выше, вы более легко выполняете следующие действия:

fb.child('tags').startAt(queryString).endAt(queryString + '\uf8ff').limit(5)

См. Firebase Получение данных.

Символ\uf8ff, используемый в запросе выше, является очень высокой точкой кода в диапазоне Unicode. Потому что это происходит после большинства регулярных символов в Unicode, запрос соответствует всем значениям, начинающимся с queryString.

Ответ 3

Мне кажется, что существует гораздо более простой и элегантный способ достижения этой цели, чем фильтрация на стороне клиента или взлома Elastic.

Преобразуя ключ поиска в его значение "Юникод" и сохраняя его как приоритет, вы можете искать по startAt() и endAt() путем увеличения значения на единицу.

var start = "ABA";

var pad = "AAAAAAAAAA";
start += pad.substring(0, pad.length - start.length);

var blob = new Blob([start]);

var reader = new FileReader();
reader.onload = function(e) {
    var typedArray = new Uint8Array(e.target.result);
    var array = Array.prototype.slice.call(typedArray);
    var priority = parseInt(array.join(""));
    console.log("Priority of", start, "is:", priority);
}
reader.readAsArrayBuffer(blob);

Затем вы можете ограничить приоритет поиска клавишей "ABB", увеличив последний символ charCode на один и выполнив одно и то же преобразование:

var limit = String.fromCharCode(start.charCodeAt(start.length -1) +1);
limit = start.substring(0, start.length -1) +limit;

"ABA..." на "ABB..." заканчивается приоритетами:

Начало: 6566 65 65656565650000

Конец: 6566 66 65656565650000

Simples!

Ответ 4

На основе ответа Джейка и Мэт обновленная версия для sdk 3.1. ".limit" больше не работает:

firebaseDb.ref('users')
    .orderByChild('name')
    .startAt(query)
    .endAt(`${query}\uf8ff`)
    .limitToFirst(5)
    .on('child_added', (child) => {
      console.log(
          {
            id: child.key,
            name: child.val().name
          }
      )
    })