javascript - Establecer la posición del cursor en contentEditable<div>
jquery html (8)
Estoy buscando una solución definitiva entre navegadores para establecer la posición del cursor / cursor a la última posición conocida cuando contentEditable = ''on'' <div> recupera el foco. Parece que la funcionalidad predeterminada de un div editable de contenido es mover el cursor / cursor al principio del texto en el div cada vez que hace clic en él, lo cual es indeseable.
Creo que tendría que almacenar en una variable la posición actual del cursor cuando están dejando el foco del div, y luego volver a configurar esto cuando tengan el foco adentro otra vez, pero no he podido juntarlo o encontrar un trabajo muestra de código todavía.
Si alguien tiene alguna idea, fragmentos de código o ejemplos de trabajo, me encantaría verlos.
Realmente no tengo ningún código todavía, pero esto es lo que tengo:
<script type="text/javascript">
// jQuery
$(document).ready(function() {
$(''#area'').focus(function() { .. } // focus I would imagine I need.
}
</script>
<div id="area" contentEditable="true"></div>
PD. He intentado con este recurso, pero parece que no funciona para un <div>. Tal vez solo para textarea ( Cómo mover el cursor al final de la entidad contenteditable )
Después de jugar, modifiqué la respuesta de eyelidlessness anterior y la convertí en un complemento jQuery, así que puedes hacer una de estas:
var html = "The quick brown fox";
$div.html(html);
// Select at the text "quick":
$div.setContentEditableSelection(4, 5);
// Select at the beginning of the contenteditable div:
$div.setContentEditableSelection(0);
// Select at the end of the contenteditable div:
$div.setContentEditableSelection(html.length);
Disculpe la publicación larga del código, pero puede ayudar a alguien:
$.fn.setContentEditableSelection = function(position, length) {
if (typeof(length) == "undefined") {
length = 0;
}
return this.each(function() {
var $this = $(this);
var editable = this;
var selection;
var range;
var html = $this.html();
html = html.substring(0, position) +
''<a id="cursorStart"></a>'' +
html.substring(position, position + length) +
''<a id="cursorEnd"></a>'' +
html.substring(position + length, html.length);
console.log(html);
$this.html(html);
// Populates selection and range variables
var captureSelection = function(e) {
// Don''t capture selection outside editable region
var isOrContainsAnchor = false,
isOrContainsFocus = false,
sel = window.getSelection(),
parentAnchor = sel.anchorNode,
parentFocus = sel.focusNode;
while (parentAnchor && parentAnchor != document.documentElement) {
if (parentAnchor == editable) {
isOrContainsAnchor = true;
}
parentAnchor = parentAnchor.parentNode;
}
while (parentFocus && parentFocus != document.documentElement) {
if (parentFocus == editable) {
isOrContainsFocus = true;
}
parentFocus = parentFocus.parentNode;
}
if (!isOrContainsAnchor || !isOrContainsFocus) {
return;
}
selection = window.getSelection();
// Get range (standards)
if (selection.getRangeAt !== undefined) {
range = selection.getRangeAt(0);
// Get range (Safari 2)
} else if (
document.createRange &&
selection.anchorNode &&
selection.anchorOffset &&
selection.focusNode &&
selection.focusOffset
) {
range = document.createRange();
range.setStart(selection.anchorNode, selection.anchorOffset);
range.setEnd(selection.focusNode, selection.focusOffset);
} else {
// Failure here, not handled by the rest of the script.
// Probably IE or some older browser
}
};
// Slight delay will avoid the initial selection
// (at start or of contents depending on browser) being mistaken
setTimeout(function() {
var cursorStart = document.getElementById(''cursorStart'');
var cursorEnd = document.getElementById(''cursorEnd'');
// Don''t do anything if user is creating a new selection
if (editable.className.match(//sselecting(/s|$)/)) {
if (cursorStart) {
cursorStart.parentNode.removeChild(cursorStart);
}
if (cursorEnd) {
cursorEnd.parentNode.removeChild(cursorEnd);
}
} else if (cursorStart) {
captureSelection();
range = document.createRange();
if (cursorEnd) {
range.setStartAfter(cursorStart);
range.setEndBefore(cursorEnd);
// Delete cursor markers
cursorStart.parentNode.removeChild(cursorStart);
cursorEnd.parentNode.removeChild(cursorEnd);
// Select range
selection.removeAllRanges();
selection.addRange(range);
} else {
range.selectNode(cursorStart);
// Select range
selection.removeAllRanges();
selection.addRange(range);
// Delete cursor marker
document.execCommand(''delete'', false, null);
}
}
// Register selection again
captureSelection();
}, 10);
});
};
En Firefox puede tener el texto del div en un nodo secundario ( o_div.childNodes[0]
)
var range = document.createRange();
range.setStart(o_div.childNodes[0],last_caret_pos);
range.setEnd(o_div.childNodes[0],last_caret_pos);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
Esto es compatible con los navegadores basados en estándares, pero probablemente fallará en IE. Lo estoy proporcionando como punto de partida. IE no admite DOM Range.
var editable = document.getElementById(''editable''),
selection, range;
// Populates selection and range variables
var captureSelection = function(e) {
// Don''t capture selection outside editable region
var isOrContainsAnchor = false,
isOrContainsFocus = false,
sel = window.getSelection(),
parentAnchor = sel.anchorNode,
parentFocus = sel.focusNode;
while(parentAnchor && parentAnchor != document.documentElement) {
if(parentAnchor == editable) {
isOrContainsAnchor = true;
}
parentAnchor = parentAnchor.parentNode;
}
while(parentFocus && parentFocus != document.documentElement) {
if(parentFocus == editable) {
isOrContainsFocus = true;
}
parentFocus = parentFocus.parentNode;
}
if(!isOrContainsAnchor || !isOrContainsFocus) {
return;
}
selection = window.getSelection();
// Get range (standards)
if(selection.getRangeAt !== undefined) {
range = selection.getRangeAt(0);
// Get range (Safari 2)
} else if(
document.createRange &&
selection.anchorNode &&
selection.anchorOffset &&
selection.focusNode &&
selection.focusOffset
) {
range = document.createRange();
range.setStart(selection.anchorNode, selection.anchorOffset);
range.setEnd(selection.focusNode, selection.focusOffset);
} else {
// Failure here, not handled by the rest of the script.
// Probably IE or some older browser
}
};
// Recalculate selection while typing
editable.onkeyup = captureSelection;
// Recalculate selection after clicking/drag-selecting
editable.onmousedown = function(e) {
editable.className = editable.className + '' selecting'';
};
document.onmouseup = function(e) {
if(editable.className.match(//sselecting(/s|$)/)) {
editable.className = editable.className.replace(/ selecting(/s|$)/, '''');
captureSelection();
}
};
editable.onblur = function(e) {
var cursorStart = document.createElement(''span''),
collapsed = !!range.collapsed;
cursorStart.id = ''cursorStart'';
cursorStart.appendChild(document.createTextNode(''—''));
// Insert beginning cursor marker
range.insertNode(cursorStart);
// Insert end cursor marker if any text is selected
if(!collapsed) {
var cursorEnd = document.createElement(''span'');
cursorEnd.id = ''cursorEnd'';
range.collapse();
range.insertNode(cursorEnd);
}
};
// Add callbacks to afterFocus to be called after cursor is replaced
// if you like, this would be useful for styling buttons and so on
var afterFocus = [];
editable.onfocus = function(e) {
// Slight delay will avoid the initial selection
// (at start or of contents depending on browser) being mistaken
setTimeout(function() {
var cursorStart = document.getElementById(''cursorStart''),
cursorEnd = document.getElementById(''cursorEnd'');
// Don''t do anything if user is creating a new selection
if(editable.className.match(//sselecting(/s|$)/)) {
if(cursorStart) {
cursorStart.parentNode.removeChild(cursorStart);
}
if(cursorEnd) {
cursorEnd.parentNode.removeChild(cursorEnd);
}
} else if(cursorStart) {
captureSelection();
var range = document.createRange();
if(cursorEnd) {
range.setStartAfter(cursorStart);
range.setEndBefore(cursorEnd);
// Delete cursor markers
cursorStart.parentNode.removeChild(cursorStart);
cursorEnd.parentNode.removeChild(cursorEnd);
// Select range
selection.removeAllRanges();
selection.addRange(range);
} else {
range.selectNode(cursorStart);
// Select range
selection.removeAllRanges();
selection.addRange(range);
// Delete cursor marker
document.execCommand(''delete'', false, null);
}
}
// Call callbacks here
for(var i = 0; i < afterFocus.length; i++) {
afterFocus[i]();
}
afterFocus = [];
// Register selection again
captureSelection();
}, 10);
};
Puede aprovechar selectNodeContents que es compatible con los navegadores modernos.
var el = document.getElementById(''idOfYoursContentEditable'');
var selection = window.getSelection();
var range = document.createRange();
selection.removeAllRanges();
range.selectNodeContents(el);
range.collapse(false);
selection.addRange(range);
el.focus();
Tomé la respuesta de Nico Burns y lo hice usando jQuery:
- Genérico: para cada
div contentEditable="true"
- Corta
Necesitarás jQuery 1.6 o superior:
savedRanges = new Object();
$(''div[contenteditable="true"]'').focus(function(){
var s = window.getSelection();
var t = $(''div[contenteditable="true"]'').index(this);
if (typeof(savedRanges[t]) === "undefined"){
savedRanges[t]= new Range();
} else if(s.rangeCount > 0) {
s.removeAllRanges();
s.addRange(savedRanges[t]);
}
}).bind("mouseup keyup",function(){
var t = $(''div[contenteditable="true"]'').index(this);
savedRanges[t] = window.getSelection().getRangeAt(0);
}).on("mousedown click",function(e){
if(!$(this).is(":focus")){
e.stopPropagation();
e.preventDefault();
$(this).focus();
}
});
savedRanges = new Object();
$(''div[contenteditable="true"]'').focus(function(){
var s = window.getSelection();
var t = $(''div[contenteditable="true"]'').index(this);
if (typeof(savedRanges[t]) === "undefined"){
savedRanges[t]= new Range();
} else if(s.rangeCount > 0) {
s.removeAllRanges();
s.addRange(savedRanges[t]);
}
}).bind("mouseup keyup",function(){
var t = $(''div[contenteditable="true"]'').index(this);
savedRanges[t] = window.getSelection().getRangeAt(0);
}).on("mousedown click",function(e){
if(!$(this).is(":focus")){
e.stopPropagation();
e.preventDefault();
$(this).focus();
}
});
div[contenteditable] {
padding: 1em;
font-family: Arial;
outline: 1px solid rgba(0,0,0,0.5);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contentEditable="true"></div>
<div contentEditable="true"></div>
<div contentEditable="true"></div>
Tuve una situación relacionada, donde específicamente necesité establecer la posición del cursor al FIN de una div contenteditable. No quería usar una biblioteca completa como Rangy, y muchas soluciones eran demasiado pesadas.
Al final, se me ocurrió esta sencilla función jQuery para establecer la posición de quilates al final de una div contenteditable:
$.fn.focusEnd = function() {
$(this).focus();
var tmp = $(''<span />'').appendTo($(this)),
node = tmp.get(0),
range = null,
sel = null;
if (document.selection) {
range = document.body.createTextRange();
range.moveToElementText(node);
range.select();
} else if (window.getSelection) {
range = document.createRange();
range.selectNode(node);
sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
tmp.remove();
return this;
}
La teoría es simple: añada un tramo al final del editable, selecciónelo y luego elimine el tramo, dejándonos con un cursor al final de la división. Puede adaptar esta solución para insertar el lapso donde desee, colocando el cursor en un punto específico.
El uso es simple:
$(''#editable'').focusEnd();
¡Eso es!
Actualizar
Rangy un rango de explorador cruzado y una biblioteca de selección llamada Rangy que incorpora una versión mejorada del código que publiqué a continuación. Puede usar el módulo de guardar y restaurar de selección para esta pregunta en particular, aunque estaría tentado de usar algo como la respuesta de @Nico Burns si no está haciendo otra cosa con las selecciones en su proyecto y no necesita la mayor parte de un biblioteca.
Respuesta anterior
Puede usar IERange ( http://code.google.com/p/ierange/ ) para convertir el TextRange de IE en algo así como un rango DOM y usarlo junto con algo así como el punto de partida de la falta de definición. Personalmente, solo usaría los algoritmos de IERange que hacen las conversiones Range <-> TextRange en lugar de utilizar todo. Y el objeto de selección de IE no tiene las propiedades focusNode y anchorNode, pero debería poder usar el Range / TextRange obtenido de la selección en su lugar.
Podría armar algo para hacer esto, publicaré aquí si lo hago.
EDITAR:
Creé una demostración de un script que hace esto. Funciona en todo lo que he probado hasta ahora, excepto por un error en Opera 9, que aún no he tenido tiempo de investigar. Los navegadores en los que funciona son IE 5.5, 6 y 7, Chrome 2, Firefox 2, 3 y 3.5 y Safari 4, todos en Windows.
http://www.timdown.co.uk/code/selections/
Tenga en cuenta que las selecciones se pueden hacer hacia atrás en los navegadores para que el nodo de enfoque esté al inicio de la selección y presionar la tecla de cursor derecha o izquierda moverá el cursor a una posición relativa al inicio de la selección. No creo que sea posible replicar esto al restaurar una selección, por lo que el nodo de enfoque siempre se encuentra al final de la selección.
Escribiré esto completamente en algún momento pronto.
Esta solución funciona en todos los principales navegadores:
saveSelection()
se adjunta a los eventos onmouseup
y onkeyup
del div y guarda la selección en la variable savedRange
.
restoreSelection()
se adjunta al evento onfocus
del div y savedRange
seleccionar la selección guardada en savedRange
.
Esto funciona a la perfección a menos que desee que se restaure la selección cuando el usuario también hace clic en el div (lo cual es poco intuitivo, ya que normalmente espera que el cursor vaya al lugar donde hace clic pero que el código esté completo)
Para lograr esto, los eventos onclick
y onmousedown
son cancelados por la función cancelEvent()
que es una función de navegador cruzado para cancelar el evento. La función cancelEvent()
también ejecuta la función restoreSelection()
porque cuando se cancela el evento click, el div no recibe el foco y, por lo tanto, no se selecciona nada a menos que se ejecute esta función.
La variable isInFocus
almacena si está enfocado y se cambia a onblur
"falso" y " onblur
" verdadero. Esto permite que los eventos de clic se cancelen solo si el div no está enfocado (de lo contrario, no podría cambiar la selección).
Si desea que la selección se modifique cuando se enfoca el div con un clic, y no restaura la selección con un clic (y solo cuando se le da un enfoque programático al elemento usando document.getElementById("area").focus();
o similar, simplemente elimine los eventos onclick
y onmousedown
. Las onblur
event y onDivBlur()
y cancelEvent()
también se pueden eliminar de forma segura en estas circunstancias.
Este código debería funcionar si se coloca directamente en el cuerpo de una página html si desea probarlo rápidamente:
<div id="area" style="width:300px;height:300px;" onblur="onDivBlur();" onmousedown="return cancelEvent(event);" onclick="return cancelEvent(event);" contentEditable="true" onmouseup="saveSelection();" onkeyup="saveSelection();" onfocus="restoreSelection();"></div>
<script type="text/javascript">
var savedRange,isInFocus;
function saveSelection()
{
if(window.getSelection)//non IE Browsers
{
savedRange = window.getSelection().getRangeAt(0);
}
else if(document.selection)//IE
{
savedRange = document.selection.createRange();
}
}
function restoreSelection()
{
isInFocus = true;
document.getElementById("area").focus();
if (savedRange != null) {
if (window.getSelection)//non IE and there is already a selection
{
var s = window.getSelection();
if (s.rangeCount > 0)
s.removeAllRanges();
s.addRange(savedRange);
}
else if (document.createRange)//non IE and no selection
{
window.getSelection().addRange(savedRange);
}
else if (document.selection)//IE
{
savedRange.select();
}
}
}
//this part onwards is only needed if you want to restore selection onclick
var isInFocus = false;
function onDivBlur()
{
isInFocus = false;
}
function cancelEvent(e)
{
if (isInFocus == false && savedRange != null) {
if (e && e.preventDefault) {
//alert("FF");
e.stopPropagation(); // DOM style (return false doesn''t always work in FF)
e.preventDefault();
}
else {
window.event.cancelBubble = true;//IE stopPropagation
}
restoreSelection();
return false; // false = IE style
}
}
</script>