Handling composition

This fiddle shows how one could handle composition events on a contenteditable element

by lddubeau

HTML

<p id="edit" contenteditable="true">1234</p>
<p id="log"></p>
<p id="composing"></p>
<input id="field"></input>

CSS

#field {
    position: fixed;
    z-index: -1;
    height: 0px;
    width: 0px;
    outline: none;
}
#field:focus {
    outline: none;
}
#edit {
    background-color: white;
}
}

JavaScript

//
// The method used here is to redirect keyboard events to a hidden
// field. This field is invisible because #edit has a background color. 
// Turn that color off to see the field. The event handlers on the 
// field then motify the contenteditable element which is supposed to // be edited. The composition events are handled so that the edited 
// text shows the state of composition as it happens.
//
// It is important that the field be moved to the caret position so 
// that it shows a possible list of choice. Like when chinese is input
// for instance.

$(function () {
    var $edit = $("#edit");
    var edit = $edit[0];
    var $log = $("#log");
    var $field = $("#field");
    var caret = 0;
    var caret_html = "<span>_</span>";
    var $composing = $("#composing");
    var composing = false;
    var composition_data = "";
    var composition_data_start = 0;

    function moveCaret() {
        $edit.children("span").remove();
        var text = edit.innerHTML;
        if (composing) {
            text = text.slice(0, composition_data_start) + composition_data + text.slice(composition_data_start);
        }
        edit.innerHTML = text.slice(0, caret) + caret_html + text.slice(caret);
        var $span = $edit.children("span");
        var pos = $span.offset();
        $field.css("top", pos.top);
        $field.css("left", pos.left);
    }

    moveCaret();

    function removeCompositionData() {
        if (!composing) return;
        $edit.children("span").remove();
        var text = edit.innerHTML;
        edit.innerHTML = text.slice(0, composition_data_start) + text.slice(composition_data_start + composition_data.length);
    }

    $field.on("compositionstart", function (ev) {
        composing = true;
        composition_data = ev.originalEvent.data;
        composition_data_start = caret;
        caret = composition_data_start + composition_data.length;
        moveCaret();
    });
    $field.on("compositionupdate", function (ev) {
       ...