Pregunta

Estoy buscando una solución definitiva para varios navegadores para establecer la posición del cursor/carácter en la última posición conocida cuando contentEditable='on' <div> recupera el foco.Parece que la funcionalidad predeterminada de un div de contenido editable es mover el cursor/carácter al principio del texto en el div cada vez que hace clic en él, lo cual no es deseable.

Creo que tendría que almacenar en una variable la posición actual del cursor cuando salen del foco del div, y luego restablecer esto cuando vuelvan a tener el foco dentro, pero no he podido armarlo ni encontrar un método que funcione. muestra de código todavía.

Si alguien tiene alguna idea, fragmentos de código de trabajo o muestras, estaré encantado de 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.Probé este recurso pero parece que no funciona para un <div>.Quizás sólo para el área de texto (Cómo mover el cursor al final de una entidad contenta)

¿Fue útil?

Solución

Esto es compatible con los navegadores basados ​​en estándares, pero probablemente fallará en IE.Lo proporciono como punto de partida.IE no es compatible con el rango DOM.

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);
};

Otros consejos

Esta solución funciona en todos los navegadores:

saveSelection() se une a los eventos onmouseup y onkeyup del div y guarda la selección a la savedRange variable.

restoreSelection() se une al evento onfocus del div y vuelve a seleccionar la selección guardada en savedRange.

Esto funciona perfectamente a menos que quiera que la selección se restaura cuando el usuario hace clic en la también de div (que es un poco unintuitative como normalmente se espera que el cursor para ir donde haga clic pero el código incluido para exhaustividad)

Para lograr esto, los eventos onclick y onmousedown se cancelan por el cancelEvent() función que es una función de navegador de la Cruz de cancelar el evento. La función cancelEvent() también se ejecuta la función restoreSelection() porque como el evento click se cancela el div no recibe atención y por lo tanto no hay nada seleccionado en absoluto a menos que estas funciones se ejecutan.

Las tiendas isInFocus variables si está en foco y se cambia a onblur "falsa" y onfocus "verdadero". Esto permite que los eventos de clic de suspensión sólo será posible si el div no está en el foco (de lo contrario no sería capaz de cambiar la selección en absoluto).

Si desea que la selección se cambian cuando el div se enfoca mediante un clic, y no restaurar el onclick selección (y sólo cuando se da el foco al elemento programtically usando document.getElementById("area").focus(); o similar, a continuación, basta con quitar los eventos onclick y onmousedown . El evento onblur y las funciones onDivBlur() y cancelEvent() pueden ser eliminados de manera segura también en estas circunstancias.

Este código debería funcionar si se cae directamente en el cuerpo de una página HTML si desea probar 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>

Actualizar

He escrito una biblioteca gama y selección multi-navegador llamado Rangy que incorpora una versión mejorada del código que publica a continuación. Se puede utilizar el href="http://code.google.com/p/rangy/wiki/SelectionSaveRestoreModule" rel="nofollow guardar y restaurar módulo para esta pregunta en particular, aunque 'd tener la tentación de utilizar algo como @Nico de Burns respuesta Si usted no está haciendo otra cosa con selecciones en su proyecto y no es necesario el grueso de una biblioteca.

respuesta anterior

Puede utilizar IERange ( http://code.google.com/p/ ierange / ) para convertir TextRange de IE en algo así como un rango DOM y usarlo en conjunción con algo así como el punto de partida de eyelidlessness. En lo personal sólo lo usaría los algoritmos de IERange que hacen el rango <-> conversiones TextRange en lugar de utilizar toda la cosa. Y el objeto la selección de IE no tiene las propiedades focusNode y anchorNode pero usted debe ser capaz de simplemente utilizar la radio / TextRange obtenido a partir de la selección en su lugar.

Yo podría poner algo juntos para hacer esto, va a publicar de nuevo aquí, siempre y cuando lo haga.

EDIT:

He creado una demo de un script que hace esto. Funciona en todo lo que he intentado en la medida excepto por un error en Opera 9, que no he tenido tiempo para estudiar aún. Navegadores funciona en IE son 5,5, 6 y 7, Chrome 2, Firefox 2, 3 y 3.5, y Safari 4, todo 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 foco está en el inicio de la selección y golpear a la derecha o la tecla de cursor izquierda se mueve el cursor a una posición en relación con el inicio de la selección. Creo que no es posible replicar este al restaurar una selección, por lo que el nodo objetivo es siempre al final de la selección.

Voy a escribir esto totalmente en algún momento pronto.

Yo tenía una situación relacionada, en la que específicamente Necesario para ajustar la posición del cursor hasta el final de un div contenteditable. No quería utilizar una biblioteca de pleno derecho como Rangy, y muchas soluciones eran demasiado pesados.

Al final, se me ocurrió con esta simple función de jQuery para establecer la posición quilates hasta el final de un 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: agregar un lapso hasta el final del editable, seleccionarlo, y luego retire el lapso - que nos deja con un cursor al final de la div. Se pueden adaptar esta solución para insertar el lapso donde quieras, poniendo el cursor en un punto específico.

El uso es simple:

$('#editable').focusEnd();

Eso es todo!

Tomé la respuesta de Nico Burns y lo hizo usando jQuery:

  • Genérico: Por cada div contentEditable="true"
  • Shorter

que necesitará 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>

Después de jugar respuesta en torno He modificado eyelidlessness' arriba y convertido en un plugin de jQuery por lo que sólo puede hacer uno de los siguientes:

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);

Con permiso del poste código largo, 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);
    });
};

Puede aprovechar selectNodeContents que se apoya por 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();

En Firefox que podría tener el texto de la div en un nodo hijo (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);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top