2010-05-09 2 views
10

Я работаю над плагином jQuery, который позволит вам делать теги стиля @username, например Facebook, в поле ввода статуса.Tag-like autocompletion и перемещение курсора/курсора в контентных элементах

Моя проблема в том, что даже после нескольких часов исследований и экспериментов кажется ДЕЙСТВИТЕЛЬНО трудно просто перемещать каретку. Мне удалось ввести тег <a> с чьим-то именем, но поместив каретку после того, как он выглядит как наука о ракетах, особенно если она предполагается работать во всех браузерах.

И я даже не смотрел на замену набранного @username текста с тегом еще, а не только путем инъекций, как я делаю сейчас ... лол

Там тонна вопросов о работе с contenteditable здесь, в Stack Overflow, и я думаю, что я прочитал их все, но они действительно не покрывают должным образом то, что мне нужно. Таким образом, любая информация, которую любой может предоставить, была бы большой :)

+0

ли вы когда-нибудь найти дополнительные объяснения? Я отправил аналогичный вопрос на http://stackoverflow.com/questions/3764273/jquery-facebook-like-autosuggest-triggered-by и http://stackoverflow.com/questions/3972014/get-caret-position-in- contenteditable-div, но не повезло ... – Bertvan

+0

Я, безусловно, сочувствую отсутствию помощи в контентной работе! В последнее время мне пришлось много работать. –

ответ

1

Как вы говорите, вы уже можете вставить тег в каретку, я собираюсь начать с этого момента. Первое, что нужно сделать, - дать вашему тегу идентификатор, когда вы его вставляете. После этого вы должны иметь что-то вроде этого:

<div contenteditable='true' id='status'>I went shopping with <a href='#' id='atagid'>Jane</a></div>

Вот функция, которая должна поместить курсор сразу после тега.

function setCursorAfterA() 
{ 
    var atag = document.getElementById("atagid"); 
    var parentdiv = document.getElementById("status"); 
    var range,selection; 
    if(window.getSelection) //FF,Chrome,Opera,Safari,IE9+ 
    { 
     parentdiv.appendChild(document.createTextNode(""));//FF wont allow cursor to be placed directly between <a> tag and the end of the div, so a space is added at the end (this can be trimmed later) 
     range = document.createRange();//create range object (like an invisible selection) 
     range.setEndAfter(atag);//set end of range selection to just after the <a> tag 
     range.setStartAfter(atag);//set start of range selection to just after the <a> tag 
     selection = window.getSelection();//get selection object (list of current selections/ranges) 
     selection.removeAllRanges();//remove any current selections (FF can have more than one) 
     parentdiv.focus();//Focuses contenteditable div (necessary for opera) 
     selection.addRange(range);//add our range object to the selection list (make our range visible) 
    } 
    else if(document.selection)//IE 8 and lower 
    { 
     range = document.body.createRange();//create a "Text Range" object (like an invisible selection) 
     range.moveToElementText(atag);//select the contents of the a tag (i.e. "Jane") 
     range.collapse(false);//collapse selection to end of range (between "e" and "</a>"). 
     while(range.parentElement() == atag)//while ranges cursor is still inside <a> tag 
     { 
      range.move("character",1);//move cursor 1 character to the right 
     } 
     range.move("character",-1);//move cursor 1 character to the left 
     range.select()//move the actual cursor to the position of the ranges cursor 
    } 
    /*OPTIONAL: 
    atag.id = ""; //remove id from a tag 
    */ 
} 

EDIT: Испытано и фиксированный сценарий. Он определенно работает в IE6, chrome 8, firefox 4 и opera 11. У вас нет других браузеров для проверки, но он не использует какие-либо функции, которые недавно изменились, поэтому он должен работать во всем, что поддерживает contenteditable.

Эта кнопка удобна для тестирования: <input type='button' onclick='setCursorAfterA()' value='Place Cursor After &lt;a/&gt; tag' >

Нико

5

Вы можете использовать my Rangy library, который пытается с некоторым успехом нормализуют реализации диапазона браузера и выбора. Если вам удалось вставить <a>, как вы говорите, и вы получите его в переменной с именем aElement, вы можете сделать следующее:

var range = rangy.createRange(); 
range.setStartAfter(aElement); 
range.collapse(true); 
var sel = rangy.getSelection(); 
sel.removeAllRanges(); 
sel.addRange(range); 
+1

+1 для библиотеки, это будет большой помощью – Bertvan

+0

Ваш вопрос заинтересовал меня, и я пишу какой-то общий код для такого рода вещей. Я скоро отправлю сообщение. –

3

я заинтересован в этом, так что я написал начальный точка для полного решения. Следующее использует мой Rangy library с его selection save/restore module для сохранения и восстановления выбора и нормализации проблем с кросс-браузером. Он окружает весь соответствующий текст (@ в любом случае в этом случае) с элементом ссылки и позиционирует выбор, где он был ранее. Это срабатывает после того, как на клавиатуре не было активности клавиатуры в течение одной секунды. Это должно быть вполне многоразовым.

function createLink(matchedTextNode) { 
    var el = document.createElement("a"); 
    el.style.backgroundColor = "yellow"; 
    el.style.padding = "2px"; 
    el.contentEditable = false; 
    var matchedName = matchedTextNode.data.slice(1); // Remove the leading @ 
    el.href = "http://www.example.com/?name=" + matchedName; 
    matchedTextNode.data = matchedName; 
    el.appendChild(matchedTextNode); 
    return el; 
} 

function shouldLinkifyContents(el) { 
    return el.tagName != "A"; 
} 

function surroundInElement(el, regex, surrounderCreateFunc, shouldSurroundFunc) { 
    var child = el.lastChild; 
    while (child) { 
     if (child.nodeType == 1 && shouldSurroundFunc(el)) { 
      surroundInElement(child, regex, surrounderCreateFunc, shouldSurroundFunc); 
     } else if (child.nodeType == 3) { 
      surroundMatchingText(child, regex, surrounderCreateFunc); 
     } 
     child = child.previousSibling; 
    } 
} 

function surroundMatchingText(textNode, regex, surrounderCreateFunc) { 
    var parent = textNode.parentNode; 
    var result, surroundingNode, matchedTextNode, matchLength, matchedText; 
    while (textNode && (result = regex.exec(textNode.data))) { 
     matchedTextNode = textNode.splitText(result.index); 
     matchedText = result[0]; 
     matchLength = matchedText.length; 
     textNode = (matchedTextNode.length > matchLength) ? 
      matchedTextNode.splitText(matchLength) : null; 
     surroundingNode = surrounderCreateFunc(matchedTextNode.cloneNode(true)); 
     parent.insertBefore(surroundingNode, matchedTextNode); 
     parent.removeChild(matchedTextNode); 
    } 
} 

function updateLinks() { 
    var el = document.getElementById("editable"); 
    var savedSelection = rangy.saveSelection(); 
    surroundInElement(el, /@\w+/, createLink, shouldLinkifyContents); 
    rangy.restoreSelection(savedSelection); 
} 

var keyTimer = null, keyDelay = 1000; 

function keyUpLinkifyHandler() { 
    if (keyTimer) { 
     window.clearTimeout(keyTimer); 
    } 
    keyTimer = window.setTimeout(function() { 
     updateLinks(); 
     keyTimer = null; 
    }, keyDelay); 
} 

HTML:

<p contenteditable="true" id="editable" onkeyup="keyUpLinkifyHandler()"> 
    Some editable content for @someone or other 
</p> 
+0

это здорово. Я пытаюсь расширить это использование rangy, чтобы выделить URL-адреса при вводе. Однако, похоже, существует ряд проблем - после обнаружения связи он не может быть отредактирован, а установка contentEditable в true в тегах A теги также ломает ситуацию. Есть ли лучший подход для этого? –

+0

@ MattRoberts: Я не думаю, что есть принципиально лучший подход, только лучше, более тщательная реализация. Это было задумано как отправная точка. –

+0

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

Смежные вопросы