4

I'm using contenteditable to allow users to edit information on the webpage, but some fields have a length requirement, and I want them to know when they've hit that requirement, so I change the color of the text that will be chopped off when they hit return to red.

The issue is that when the text is changed with JavaScript, the browser moves the cursor to the front of the string. Does anyone know of a way I can prevent this behavior? As far as I can tell this this is an issue in all browsers.

Here's a JSFiddle.

$(element).keypress(function (event) {
    if ($(element).text().trim().length > maxChars) {
        $(element).html($(element).text().trim().substr(0, maxChars) +
            "<span class=red>" + $(element).text().trim().substr(maxChars) + "</span>");
    }
});
BuddhistBeast
  • 2,652
  • 2
  • 21
  • 29
Josh Sherick
  • 2,161
  • 3
  • 20
  • 37
  • 1
    Maybe this will help http://stackoverflow.com/questions/16230720/set-the-caret-position-always-to-end-in-contenteditable-div – Harsha Venkataramu Dec 23 '13 at 04:25
  • Wow, apparently I'm not as good at searching for answers as I thought. Thanks! – Josh Sherick Dec 23 '13 at 05:27
  • No prob , I am glad that it helped! – Harsha Venkataramu Dec 23 '13 at 05:35
  • 2
    http://stackoverflow.com/a/13950376/96100 will work more generally, so the caret remains in the same position rather than always going to the end (which is not what you want if you're entering text somewhere in the middle). Demo: http://jsfiddle.net/TeXG6/5/ – Tim Down Dec 23 '13 at 10:19
  • @TimDown this is what I've been trying to get it to do for the past day, thank you! For some reason that doesn't work for me in the JSFiddle (typing in the middle of the text works, but typing at the end makes the cursor jump to the from), but in my actual app it works, so it's fine. Still an issue with Firefox though where spaces get removed from the end... I'll check it out later and see what's up. – Josh Sherick Dec 23 '13 at 22:50
  • @JoshSherick: There will be issues around line breaks and possibly multiple spaces. It's a hard problem to solve completely. – Tim Down Dec 24 '13 at 09:39

2 Answers2

2

Thanks to harsha and Tim Down I finally got this to work and also fixed the issue where Firefox doesn't allow spaces, and it works quite well. The only non-expected behavior I could find was that it messes up the browser's undo history but other than that it works very well.

I've tested in every browser but IE, but I don't see why it wouldn't work in newer IE versions also. It requires use of the input event though, so it wouldn't work in older browsers. I also don't know how it handles line breaks because I strip them in my app.

First, you need this code written by Tim Down somewhere in your file.

var saveSelection, restoreSelection;
var endSpaceIndex = -1;

if (window.getSelection && document.createRange) {
    saveSelection = function(containerEl) {
        var range = window.getSelection().getRangeAt(0);
        var preSelectionRange = range.cloneRange();
        preSelectionRange.selectNodeContents(containerEl);
        preSelectionRange.setEnd(range.startContainer, range.startOffset);
        var start = preSelectionRange.toString().length;

        return {
            start: start,
            end: start + range.toString().length
        }
    };

    restoreSelection = function(containerEl, savedSel) {
        var charIndex = 0, range = document.createRange();
        range.setStart(containerEl, 0);
        range.collapse(true);
        var nodeStack = [containerEl], node, foundStart = false, stop = false;

        while (!stop && (node = nodeStack.pop())) {
            if (node.nodeType == 3) {
                var nextCharIndex = charIndex + node.length;
                if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
                    range.setStart(node, savedSel.start - charIndex);
                    foundStart = true;
                }
                if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
                    range.setEnd(node, savedSel.end - charIndex);
                    stop = true;
                }
                charIndex = nextCharIndex;
            } else {
                var i = node.childNodes.length;
                while (i--) {
                    nodeStack.push(node.childNodes[i]);
                }
            }
        }

        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }
} else if (document.selection) {
    saveSelection = function(containerEl) {
        var selectedTextRange = document.selection.createRange();
        var preSelectionTextRange = document.body.createTextRange();
        preSelectionTextRange.moveToElementText(containerEl);
        preSelectionTextRange.setEndPoint("EndToStart", selectedTextRange);
        var start = preSelectionTextRange.text.length;

        return {
            start: start,
            end: start + selectedTextRange.text.length
        }
    };

    restoreSelection = function(containerEl, savedSel) {
        var textRange = document.body.createTextRange();
        textRange.moveToElementText(containerEl);
        textRange.collapse(true);
        textRange.moveEnd("character", savedSel.end);
        textRange.moveStart("character", savedSel.start);
        textRange.select();
    };
}

Somewhere you also need the following code. It uses jQuery, so if you don't have that you'll have to get rid of the jQuery in the example below. This needs to be bound to the input event, as Firefox is very inconsistent with when it calls keyup and keypress. This is what fixes the issue with Firefox not allowing spaces. It's pretty messy. But then again, apparently so is browser support for contenteditable.

// Provided `element` is the element that you want to modify while the user changes it.
$(element).bind('input', function() {
    var savedSel = saveSelection(element);
    if (endSpaceIndex > -1 && $(element).text().substr(endSpaceIndex) != " " 
        && savedSel.end == $(element).text().length && savedSel.end == savedSel.start) {
            $(element).html($(element).text().substr(0, endSpaceIndex) + " " + $(element).text().substr(endSpaceIndex));
            endSpaceIndex = -1;
            savedSel.end = savedSel.start = $(element).text().length;
    }

    // Here, change the element however you want to. 
    // For example, I add a 'red' class around the text that will be chopped off

    restoreSelection(element, savedSel);
    var fullText = $(element).text();
    if (fullText.substr(fullText.length - 1) == " ") {
        endSpaceIndex = fullText.length - 1;
    }
}

Also I originally marked this as a duplicate, but I no longer think it's a duplicate because it fixes this issue with Firefox not allowing spaces. Hope this helps someone else!

Community
  • 1
  • 1
Josh Sherick
  • 2,161
  • 3
  • 20
  • 37
0

Maybe I can try to point you in the right direction.

First: Get the current cursor location in the text field: Get cursor position (in characters) within a text Input field

Second: Do your color change, which sends the cursor to the beginning

Third: Put the cursor back where it was: jQuery Set Cursor Position in Text Area

Community
  • 1
  • 1
Andrew
  • 18,680
  • 13
  • 103
  • 118