Cursorpositie op inhoud instellenBewerkbaar <div>

Ik ben op zoek naar een definitieve, cross-browser oplossing om de cursor/caret-positie in te stellen op de laatst bekende positie wanneer een contentEditable=’on’ <div> herwint de focus. Het lijkt erop dat de standaardfunctionaliteit van een inhoud bewerkbare div is om het caret/cursor naar het begin van de tekst in de div te verplaatsen elke keer dat u erop klikt, wat ongewenst is.

Ik geloof dat ik in een variabele de huidige cursorpositie zou moeten opslaan wanneer ze de focus van de div verlaten, en deze vervolgens opnieuw moet instellen wanneer ze weer focus binnenin hebben, maar ik heb niet kunnen samenstellen, of vind al een werkend codevoorbeeld.

Als iemand ideeën, werkende codefragmenten of voorbeelden heeft, zou ik die graag zien.

Ik heb nog niet echt een code, maar dit is wat ik wel heb:

<script type="text/javascript">
// jQuery
$(document).ready(function() {
   $('#area').focus(function() { .. }  // focus I would imagine I need.
}
</script>
<div id="area" contentEditable="true"></div>

PS. Ik heb deze bron geprobeerd, maar het lijkt erop dat het niet werkt voor een <div>. Misschien alleen voor textarea (Hoe de cursor naar het einde van een contenteditable entiteit te verplaatsen )


Antwoord 1, autoriteit 100%

Deze oplossing werkt in alle belangrijke browsers:

saveSelection()is gekoppeld aan de gebeurtenissen onmouseupen onkeyupvan de div en slaat de selectie op in de variabele savedRange.

restoreSelection()is gekoppeld aan de gebeurtenis onfocusvan de div en selecteert de selectie die is opgeslagen in savedRangeopnieuw.

Dit werkt perfect, tenzij je wilt dat de selectie wordt hersteld wanneer de gebruiker ook op de div klikt (wat een beetje niet intuïtief is omdat je normaal gesproken verwacht dat de cursor gaat waar je klikt, maar code bevat voor de volledigheid)

Om dit te bereiken worden de gebeurtenissen onclicken onmousedowngeannuleerd door de functie cancelEvent(), een cross-browserfunctie om de gebeurtenis te annuleren . De functie cancelEvent()voert ook de functie restoreSelection()uit, omdat als de gebeurtenis click wordt geannuleerd, de div geen focus krijgt en daarom helemaal niets wordt geselecteerd, tenzij dit functioneert wordt uitgevoerd.

De variabele isInFocusslaat op of deze in focus is en wordt gewijzigd in “false” onbluren “true” onfocus. Hierdoor kunnen klikgebeurtenissen alleen worden geannuleerd als de div niet in focus is (anders zou u de selectie helemaal niet kunnen wijzigen).

Als u wilt dat de selectie wordt gewijzigd wanneer de div wordt gefocust door een klik, en de selectie niet herstelt onclick(en alleen wanneer de focus programmatisch aan het element wordt gegeven met behulp van document.getElementById("area").focus();of iets dergelijks, verwijder dan gewoon de gebeurtenissen onclicken onmousedown. De onblurevent en de functies onDivBlur()en cancelEvent()kunnen in deze omstandigheden ook veilig worden verwijderd.

Deze code zou moeten werken als deze rechtstreeks in de hoofdtekst van een html-pagina wordt geplaatst als u deze snel wilt testen:

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

Antwoord 2, autoriteit 58%

Dit is compatibel met de op standaarden gebaseerde browsers, maar zal waarschijnlijk mislukken in IE. Ik geef het als uitgangspunt. IE ondersteunt geen DOM-bereik.

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

Antwoord 3, Autoriteit 20%

update

Ik heb een Cross-Browser-assortiment en selectiebibliotheek geschreven genaamd RANGY die een Verbeterde versie van de code die ik hieronder heb gepost. U kunt de selecteren opslaan en herstellen Module voor deze specifieke vraag, hoewel ik ‘D wordt verleid om iets te gebruiken als @nico burns’s antwoord Als u niets anders doet met selecties in uw project en het bulk van een bibliotheek niet nodig hebt.

Vorig antwoord

U kunt Ierange gebruiken (http://code.google.com/p/ ierange / ) om te converteren, dwz’s TextRange in iets als een DOM-assortiment en gebruik het in combinatie met iets als het startpunt van het ooglidloosheid. Persoonlijk zou ik alleen de algoritmen van Ierange gebruiken die het bereik en LT; – & GT; Textrange conversies in plaats van het hele ding te gebruiken. En IE’s selectieobject heeft niet de focusnode- en ankerode-eigenschappen, maar u moet gewoon het bereik / Textange verkregen uit de selectie gebruiken.

Ik zou iets samen kunnen doen om dit te doen, zal hier teruggeven als en wanneer ik het doe.

EDIT:

Ik heb een demo gemaakt van een script dat dit doet. Het werkt in alles wat ik het tot nu toe heb geprobeerd, behalve voor een bug in Opera 9, die ik nog geen tijd heb gehad om nog in te kijken. Browsers Het werkt erin zijn IE 5.5, 6 en 7, Chrome 2, Firefox 2, 3 en 3.5 en Safari 4, allemaal op Windows.

http://www.timdown.co.uk/code/selections/

Merk op dat selecties in browsers kunnen worden gemaakt, zodat het focusknooppunt aan het begin is van de selectie en de rechter- of linker Cursor-toets beweegt de verzachting naar een positie ten opzichte van het begin van de selectie. Ik denk niet dat het mogelijk is om dit te repliceren bij het herstellen van een selectie, dus het focusknooppunt is altijd aan het einde van de selectie.

Ik zal dit binnenkort volledig opschrijven.


Antwoord 4, Autoriteit 16%

Ik had een gerelateerde situatie, waar ik specifiek nodig had om de cursorpositie in te stellen aan het einde van een contentedable div. Ik wilde niet een volwaardige bibliotheek zoals Rangy gebruiken, en veel oplossingen waren veel te zwaargewicht.

Uiteindelijk bedacht ik met deze eenvoudige jQuery-functie om de karatpositie in te stellen tot het einde van een Concentred, DIV:

$.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;
}

De theorie is eenvoudig: voeg een spanaanslag toe aan het einde van de bewerkbare, selecteer deze en verwijder vervolgens de spanning – waardoor we ons achterlaten met een cursor aan het einde van de div. U kunt deze oplossing aanpassen om de span te plaatsen waar u maar wilt, waardoor de cursor op een specifieke plek wordt geplaatst.

Gebruik is eenvoudig:

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

Dat is het!


Antwoord 5, Autoriteit 7%

Ik nam het antwoord van Nico Burns en maakte het met JQuery:

  • Generic: voor elke div contentEditable="true"
  • korter

u hebt jQuery 1.6 of hoger nodig:

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

toon codefragment


Antwoord 6, Autoriteit 5%

Na het spelen van rond te spelen, heb ik het antwoord van de ooglidloos gewijzigd en maakte het een jQuery-plug-in, zodat je een van deze kunt doen:

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

excuseer de lange codepost, maar het kan iemand helpen:

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

Antwoord 7, autoriteit 3%

U kunt gebruikmaken van selectNodeContentsdie wordt ondersteund door moderne browsers.

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

Antwoord 8

In Firefox heeft u mogelijk de tekst van de div in een onderliggende node (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);

Other episodes