Question

I've created a running example of a content assistant in an editable area on a html document. So if the user hits ctrl and space on the keyboard a context menu appears. Currently (see demo bellow) the context menu is on the right y position (bellow the text). But it goes not along with the x-axis (if the text becomes longer the box will be sown on the beginning of the line).

Can you help me solving this problem?

Greetings, mythbu

Example code:

<!doctype html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Test</title>
<script type="text/javascript">

var iframe = null, iwindow = null, iDocument = null;

function setUpInput() {
        iframe = document.createElement( 'iframe' );
        iframe.setAttribute( 'id', 'iframe-test' );
        iframe.setAttribute( 'frameborder', 0 );
        iframe.setAttribute( 'style', 'width:100%; height:100%;border: solid 1px red;' );

        document.getElementById( "input" ).appendChild( iframe );

        iwindow   = iframe.contentWindow;
        idocument = iwindow.document;

        idocument.open();
        idocument.write("<p></p>");
        idocument.close();

        idocument.body.setAttribute( 'spellcheck', false );
        idocument.body.setAttribute( 'style', 'font-family: Consolas,serif;font-size: 0.8em;' );
        idocument.body.contentEditable = true;

        iwindow.onkeydown = function(e) {
            if (e.ctrlKey && e.keyCode == 32) {
                createSuggestObject();
                return false;
            }
            if (e.ctrlKey) return false;
        };

        iwindow.onkeypress = function(e) {if (e.ctrlKey) return false;};
}

function createSuggestObject() {
suggest = new Object();
suggest.box = document.createElement( 'div' );
suggest.box.style.position = 'absolute';
suggest.box.style.width = '120px';
suggest.box.style.overflow = 'auto';
suggest.box.style.border = '1px solid #BEC7E4';
suggest.box.style.display = 'block';
suggest.box.style.marginTop = '16px';
suggest.box.innerHTML = "Example 1";
document.body.appendChild( suggest.box )

var position = iframe.getBoundingClientRect();

var selObj = iwindow.getSelection();
var selRange = selObj.getRangeAt(0);
var p2 = selObj.anchorNode.parentNode.getBoundingClientRect();  

suggest.box.style.top = Math.round( window.scrollY + position.top + p2.top) + 'px';
suggest.box.style.left = Math.round( window.scrollX + position.left + p2.left) + 'px';

}

window.onload = function() {
    setUpInput();
};
</script>

</head>
<body>
<div id="input"></div>
</body>
</html>
Was it helpful?

Solution

Main problem with your solution was that you were using bounding rectangle of the p element (which as you assumed contained text inputed by user) as a reference to where to place suggest object.

First of all you didn't account bounding rectangle width into the position of the suggest object, so your suggestion box stayed on the left no matter how long the text was.

However, that approach would fail eventually because if the text would have more than one line (where the second line would be shorter than the first one), the width of bounding rectangle would equal length of the first line (more or less). Hence, the suggestion object would be positioned incorrectly.

My first idea of how to fix your code was to append some inline element to the text (beacon, if you will), measure it's position, remove it from the DOM and use that calculated position to properly set suggestion object.

It turned out to almost work. Almost, because as it turned out, different browsers use different methods of dealing with contentEditable line endings. Firefox for example inserts <br _moz_dirty=""/> at the end of the line, while Chrome does not. So when I tried to append my beacon element after the text it was appended after that <br/> causing incorrect position again.

Solution was to change the way of getting text node we're dealing with and insert beacon right before nextSibling of it.

Here's working example http://jsfiddle.net/MmKXS/10/

Note 1: I've removed addition of empty <p></p> element to the document of the iframe since in Chrome text inputed by user wasn't inserted into it, causing yet another problems with positioning suggest object.

Note 2: As for now my solution only works for positioning suggestion object at the end of the text, not at the cursor position as it would involve splitting textNodes, inserting the beacon, checking its position and merging textNodes again. Depending on use case you have for that code that could lead to poor performance and/or could require changing the whole approach of how to deal with positioning your suggestion object.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top