Проблемы с созданием системы автозаполнения в Facebook - PullRequest
1 голос
/ 16 сентября 2011

Я делаю систему упоминаний наподобие той, что в Facebook, сейчас я добился этого с помощью текстовой области, но теперь я хочу, чтобы она работала с contenteditable div, чтобы я мог форматировать текст внутри.что теперь происходит, когда вы нажимаете «@» и букву, которую он ищет в массиве, результаты, содержащие эту букву, с помощью автозаполнения JQuery-UI, когда вы выбираете результат, он отображает результат в div, но теперь я хочувыделите упоминание как Facebook, поэтому я поставил:

<b style="background: #somecolor"></b>

Итак, упоминание выглядело так:

<b style="background: #somecolor">Mention</b>

пока все хорошо, но когда я хочу продолжать писать, весь новый текст, который я написал, идет внутри <b></b>, выглядя так:

<b style="background: #somecolor">Mention all my new text</b>

Вместо <b style="background: #somecolor">Mention</b> all my new text

Итак, весь новый текст был выделен, вот картинка:

http://dev.frinki.com/question/1.png

Итак, это код JavaScript, который я использую

$("#Conversacion").bind("keydown", function(event) {
            if (event.keyCode === $.ui.keyCode.TAB && $(this).data("autocomplete").menu.active) {
                event.preventDefault();
            }           

        }).autocomplete({
            minLength: 0,
            source: function(request, response) {
                var term = request.term,
                    results = [];
                    console.log(term);
                if (term.indexOf("@") >= 0) {
                    term = extractLast(request.term);
                    if (term.length > 0) {
                        results = $.ui.autocomplete.filter(availableTagsFollowing, term);
                    } else {
                        //results = ['Start typing...'];
                    }
                }
                response(results);
            },
            focus: function() {
                // prevent value inserted on focus
                return false;
            },
            select: function(event, ui) {
                var terms = split(this.innerHTML);
                // remove the current input
                terms.pop();
                // add the selected item
                terms.push('<b style="background:#E0FFC5">' + ui.item.label + '</b>');
                this.innerHTML = terms.join("");

                $('#mentionsHidden').val($('#mentionsHidden').val() + '@['+ui.item.value+':'+ui.item.label+']');
                mentionsString = $('#mentionsHidden').val();
                return false;
            }

        });

А это HTML-код

<div name="Descripcion" id="Conversacion" class="Conversacion" contenteditable="true"></div>

Это изображение Chrome Inspector:

http://dev.frinki.com/question/2.png

Я посмотрел в Facebook, чтобы увидеть, как это делается, это фотография инспектора Chrome в Facebook:

3.png (тот же домени папку с двумя другими изображениями, извините за это, это новый пользователь, и я не могу опубликовать более 2 ссылок)

Кажется, что Facebook, когда вы выбираете человека, которого нужно упомянуть, создает новую строку вдиапазон, который он использует, поэтому текст отделен от <b></b>.

Ну, я надеюсь, что кто-то может мне помочь.

Кстати, мне ужасно жаль моего английского,Я из Южной Америки.

Ответы [ 2 ]

1 голос
/ 16 сентября 2011

Я просто перефразирую / обобщаю вашу идею: ваш div доступен для редактирования, и пользователь вводит его. При некоторых условиях вы представляете список, который пользователь может выбрать. Как только выбор сделан, содержимое div изменяется, чтобы содержать выбранное значение в некотором элементе оболочки. После этого пользователь печатает, но символы вставляются в оболочку, а не после оболочки, верно?

Причиной нежелательного поведения является позиция фокуса. Технически говоря, существует ряд выбранного контента, который заменяется набранными символами пользователя. Этот диапазон часто невидим, потому что он охватывает ровно 0 символов. Он охватывает некоторый контент (и, возможно, несколько элементов), когда мышь используется для выделения текста, например.

Теперь давайте предположим следующий HTML: <b>One</b><b>Two</>. Возможно, пустой диапазон находится между тегами b. Когда вводится следующий символ A , его необходимо вставить в документ - но даже если известен диапазон / курсор / позиция курсора, есть 3 возможных назначения:

  • В конце первого b: <b>OneA</b><b>Two</b>
  • Между тегами: <b>One</b>A<b>Two</b>
  • В начале второго b: <b>One</b><b>ATwo</b>

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

Теперь есть довольно сложная система диапазонов , чтобы указать, какой подход использовать для размещения нового контента. См. Введение в диапазон и , вопрос об установке диапазона для примеров.

Основная идея для решения вашей проблемы - создать какой-то пустой контент после вновь вставленного контента. Таким образом, заканчивая <b style="background: #somecolor">Mention</b>|. Обратите внимание, что вместо символа канала | вам нужно разместить пустой текстовый узел, но это не может быть отображено в коде HTML. DOM способен обрабатывать не только сериализованный код HTML / HTML! Затем вам нужно будет выбрать содержимое пустого текстового узла. Следующий символ затем заменит пустой текстовый узел (диапазон, выбирающий пустое содержимое внутри текстового узла, будет заменен, и, таким образом, символ будет вставлен в текстовый узел, чтобы быть более точным). В результате вы получите <b style="background: #somecolor">Mention</b>A, предполагая, что введенный символ был A .

Для реализации идеи предлагаю:

  • Избегайте использования innerHTML и используйте вместо этого методы DOM. innerHTML не может создавать пустые текстовые узлы.
  • Внимательно прочитайте документацию по Диапазон объектной модели документа .

Я подготовил jsFiddle , чтобы показать базовую реализацию. Обратите внимание, что это быстрый взлом и не поддерживает ни диапазоны, охватывающие несколько элементов, ни пустых родителей. В этом примере все заглавные буквы помещаются в тег b, которого должно быть достаточно для начала работы.

// Get the current selection and range
var selection = window.getSelection();
var range = selection.getRangeAt(0);

var textNode = range.startContainer;    
var text = textNode.data;
// Create a new node to hold the text in front of the range
var textBeforeAddedContent = document.createTextNode(text.substr(0, range.startOffset));
// Update current node to remove everything which goes into the just create node and the content to be replaced
textNode.data = text.substr(range.endOffset);
// Put stuff in front of the range into the document. Unless a non-empty selection was made the content looks now exactly as before calling this code
textNode.parentNode.insertBefore(textBeforeAddedContent, textNode);

// Place the new content in between the text nodes
var wrapper = document.createElement('b');
wrapper.appendChild(document.createTextNode(letter));
textNode.parentNode.insertBefore(wrapper, textNode);

// You might want to set the new range explicitly here and add an empty text node wherever newly typed letters shall go
// Set the new selection explicitly
range.setStart(textNode, 0);
range.setEnd(textNode, 0);
selection.addRange(range);

В Chrome происходит сбой для выборок нулевой длины, начиная со смещения 0, и вместо этого устанавливает выбор нулевой длины в конец предыдущего дочернего элемента. Уродливым хаком для поддержки Chrome может быть вставка невидимого символа и его удаление после вставки следующего символа.

Проблемы Chrome не должны иметь большого значения для исходного вопроса, потому что может быть допустимым вставить пробел после элемента-обертки. Начальное смещение диапазона будет тогда установлено в 1 для вставки нового содержимого после пробела.

0 голосов
/ 03 июля 2016

Я опоздал на пару лет, но, учитывая, что не похоже, что вы нашли надежное, совместимое с браузером решение вашей проблемы, я решил вмешаться.

Проще говоря, вам не нужно уходить от своей реализации textarea, чтобы выполнить то, что вы пытаетесь сделать .

Вы можете просто использовать div, идентичный по размеру и положению и помещенный под textarea, который содержит версию содержимого textarea со строкой каждого упоминания, заключенной в элемент span , Вы можете отформатировать строки упоминания через эти промежутки. Чтобы эта пара элементов выглядела как отдельный элемент, фон textarea, а также текст, рамка и контур "теневого элемента div" должны быть прозрачными.

Изучите Упомяните , если вы хотите получить представление о том, как это сделать в существующем плагине, который предоставляет функциональность, которую вы пытаетесь эмулировать. Он хорошо структурирован, легко отслеживается и обильно комментируется, поэтому у вас не должно возникнуть проблем с пониманием того, как принять такую ​​технику. Это также поддерживается вашим по-настоящему :).

...