2010-05-09 6 views
10

Estoy trabajando en un complemento de jQuery que le permitirá hacer etiquetas de estilo @username, como Facebook lo hace en su cuadro de entrada de actualización de estado.Autocompletado de etiquetas y movimiento de cursor/cursor en elementos contenteditable

Mi problema es que, incluso después de horas de investigación y experimentación, parece REALMENTE difícil mover el cursor. Me las arreglé para inyectar la etiqueta <a> con el nombre de alguien, pero colocar el símbolo de intercalación después parece una ciencia de cohetes, especialmente si se supone que funciona en todos los navegadores.

Y ni siquiera he mirado en la sustitución de la @username texto escrito con el tag, en lugar de inyectarlo como yo estoy haciendo ahora ... lol

Hay un montón de preguntas acerca de cómo trabajar con contento aquí en Stack Overflow, y creo que los he leído todos, pero realmente no cubren adecuadamente lo que necesito. Así que cualquier información adicional que cualquiera pueda proporcionar sería genial :)

+0

¿Alguna vez encontrar una explicación más detallada? Publiqué una pregunta similar en http://stackoverflow.com/questions/3764273/jquery-facebook-like-autosuggest-triggered-by y http://stackoverflow.com/questions/3972014/get-caret-position-in- contenteditable-div pero sin suerte ... – Bertvan

+0

¡Ciertamente puedo simpatizar con la falta de ayuda en contenteditable! Tuve que trabajar mucho en esto recientemente. –

Respuesta

1

Como dices ya puedes insertar una etiqueta en el símbolo de intercalación, voy a comenzar desde allí. Lo primero que debe hacer es darle a su etiqueta una identificación cuando la inserta. A continuación, debe tener algo como esto:

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

Aquí es una función que debe colocar el cursor justo después de la etiqueta.

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: Probado y guión fijo. Definitivamente funciona en IE6, Chrome 8, Firefox 4 y opera 11. No tienes otros navegadores disponibles para probar, pero no usa ninguna función que haya cambiado recientemente, por lo que debería funcionar en cualquier cosa que admita contenteditable.

Este botón es muy útil para la prueba: <input type='button' onclick='setCursorAfterA()' value='Place Cursor After &lt;a/&gt; tag' >

Nico

5

Usted podría utilizar my Rangy library, que trata con cierto éxito para normalizar rango de navegador y de selección de las implementaciones. Si usted ha logrado insertar el <a> como usted dice y lo tienes en una variable llamada aElement, puede hacer lo siguiente:

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

+1 para la biblioteca, esto será de gran ayuda – Bertvan

+0

Su pregunta me ha interesado y he estado escribiendo un código genérico para hacer este tipo de cosas. Publicaré pronto. –

3

Me interesé en esto, así que ha escrito el inicio punto para una solución completa. Lo siguiente usa mi Rangy library con su selection save/restore module para guardar y restaurar la selección y normalizar los problemas del navegador cruzado. Rodea todo el texto coincidente (@ lo que sea en este caso) con un elemento de enlace y coloca la selección donde había estado previamente. Esto se activa después de que no ha habido actividad del teclado por un segundo. Debería ser bastante reutilizable.

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

eso es genial. Estoy tratando de extender esto utilizando rangos para resaltar las URL a medida que escribe. Sin embargo, parece que hay una serie de problemas: una vez que se detecta el enlace, no se puede editar, y establecer contentEditable en true dentro de la etiqueta A se rompe también. ¿Hay un mejor enfoque para esto? –

+0

@MattRoberts: No creo que haya un enfoque fundamentalmente mejor, solo mejor, una implementación más cuidadosa. Esto fue pensado como un punto de partida realmente. –

+0

Gracias Tim. Me preguntaba si su nuevo módulo de resaltado era adecuado para este tipo de tarea. –

Cuestiones relacionadas