views:

273

answers:

3

I need to position an absolute positioned button
next to user's selected text. just like what IE8 does internally.

I'm binding a jquery event mouseup to the Document, and get
the selected text. but I'm currently out of ideas how to know where the
actual selection is positioned, without wrapping it in some element,
because selection of text can be across several elements, and it would mess
the structure if I would wrap it.

+1  A: 

You should probably insert an absolutely position element at the end of the 'range.' This works differently in different browsers, so your best bet might be to sniff.

And since you asked: this is how the new york times does it in their 'altClickToSearch.js' file:

function insertButton() {

selectionButton = new Element(
        'span', {
          'className':'nytd_selection_button',
          'id':'nytd_selection_button',
          'title':'Lookup Word',
          'style': 'margin:-20px 0 0 -20px; position:absolute; background:url(http://graphics8.nytimes.com/images/global/word_reference/ref_bubble.png);width:25px;height:29px;cursor:pointer;_background-image: none;filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="http://graphics8.nytimes.com/images/global/word_reference/ref_bubble.png", sizingMethod="image");'
        }
    )

if (Prototype.Browser.IE) {
  var tmp = new Element('div');
  tmp.appendChild(selectionButton);
  newRange = selection.duplicate();
  newRange.setEndPoint( "StartToEnd", selection);
  newRange.pasteHTML(tmp.innerHTML);
  selectionButton = $('nytd_selection_button');
}
else {
  var range = selection.getRangeAt(0);
  newRange = document.createRange();
  newRange.setStart(selection.focusNode, range.endOffset);
  newRange.insertNode(selectionButton);
}
}
Alex Sexton
Thanks, yes they appear to just insert it at that specific point in the selection instead of absolute to the Document itself. thanks!
vsync
There's a needless browser sniff in there. You can just check for the objects/methods you need. Also, the selection object in older WebKits (Safari 2, maybe 3, I'm a bit hazy on this and I don't have all the relevant browsers easily to hand) don't have a `getRangeAt` method.
Tim Down
+1  A: 

You could position a marker span at the end of the selection, get its coordinates using jQuery, place your button at those coordinates and remove the marker span.

The following should get you started:

var markSelection = (function() {
    var markerTextChar = "\ufeff";
    var markerTextCharEntity = "";

    var markerEl, markerId = "sel_" + new Date().getTime() + "_" + Math.random().toString().substr(2);

    var selectionEl;

    return function() {
        var sel, range;

        if (document.selection && document.selection.createRange) {
            // Clone the TextRange and collapse
            range = document.selection.createRange().duplicate();
            range.collapse(false);

            // Create the marker element containing a single invisible character by creating literal HTML and insert it
            range.pasteHTML('<span id="' + markerId + '">' + markerTextCharEntity + '</span>');
            markerEl = document.getElementById(markerId);
        } else if (window.getSelection) {
            sel = window.getSelection();

            if (sel.getRangeAt) {
                range = sel.getRangeAt(0).cloneRange();
            } else {
                // Older WebKit doesn't have getRangeAt
                range.setStart(sel.anchorNode, sel.anchorOffset);
                range.setEnd(sel.focusNode, sel.focusOffset);

                // Handle the case when the selection was selected backwards (from the end to the start in the
                // document)
                if (range.collapsed !== sel.isCollapsed) {
                    range.setStart(sel.focusNode, sel.focusOffset);
                    range.setEnd(sel.anchorNode, sel.anchorOffset);
                }
            }

            range.collapse(false);

            // Create the marker element containing a single invisible character using DOM methods and insert it
            markerEl = document.createElement("span");
            markerEl.id = markerId;
            markerEl.appendChild( document.createTextNode(markerTextChar) );
            range.insertNode(markerEl);
        }

        if (markerEl) {
            // Lazily create element to be placed next to the selection
            if (!selectionEl) {
                selectionEl = document.createElement("div");
                selectionEl.style.border = "solid darkblue 1px";
                selectionEl.style.backgroundColor = "lightgoldenrodyellow";
                selectionEl.innerHTML = "&lt;- selection";
                selectionEl.style.position = "absolute";

                document.body.appendChild(selectionEl);
            }

            // Move the button into place.
            // Substitute your jQuery stuff in here
            selectionEl.style.left = markerEl.offsetLeft + "px";
            selectionEl.style.top = markerEl.offsetTop + "px";

            markerEl.parentNode.removeChild(markerEl);
        }
    };
})();
Tim Down
A: 

If you pull any annoying crap like disabling my normal right-click menu (as the New York Times does), I'll set my browsing filter to indiscriminately shoot holes in your script.

When offering your users helpful features, offer them politely in a link- don't mess with their browsers' behavior!

Bystander