Grab selected text in Iframe and highlight it using Rangy


I’ve been recently working on some project wherein I wanted to grab the selected text and highlight it. The best approach in my opinion would be put markers around selected text and calculate their position starting from <body> tag.

Failed Attempt

In this approach, I decided to take advantage of Range Object to grab selected text and wrap it in markers in order to highlight it. This works pretty well until we select texts from different ranges. This is the code I’ve used, Notice I’m wrapping the selected text by <chucknorris> tag which acts as a marker in this case but unfortunately range.surroundContents() method does not allow to wrap texts of different ranges and returns an error.

Uncaught Error: BAD_BOUNDARYPOINTS_ERR: DOM Range Exception 1

function grabSelectedText() {
  var t = '',
  window = $('#iframe')[0].contentWindow.window,
  document = $('#iframe')[0].contentWindow.document,
  chucknorris = document.createElement("chucknorris");

  if (window.getSelection) {
    t = window.getSelection();
  } else if (document.getSelection) {
    t = document.getSelection();
  } else if (document.selection) {
    t = document.selection.createRange().text;
  }

  var range = t.getRangeAt(0).cloneRange();
  range.surroundContents(chucknorris); // Does not work for different ranges
  t.removeAllRanges();
  t.addRange(range);

  return t;
}

Range differs when selected texts belong to different HTML tags. For example, If we select and wrap Bold Italic text from <strong>Bold </strong><em>Italic</em> string, we’ll have 2 different ranges because both the words wrapped inside different tags but for individual words, it will work.
Demo (keep your Firebug console open to see errors)

Successful Attempt

After googling a lot, I’d come across Rangy – an awesome cross-browser range and selection library in Javascript. It provides a simple standards-based API for performing common DOM Range and Selection tasks in all major browsers. So, above function changes to below:

getSelectedText: function () {
  var window = $('#iframe')[0].contentWindow.window, 
    selection = rangy.getSelection($('#iframe')[0]);

  markerApplier = rangy.createCssClassApplier(null, {
    elementTagName: 'chucknorris'
  });
  markerApplier.toggleSelection(window);

  return selection; 
}

Usage

I’ve written a wrapper named Rangee on top of Rangy to simply grab and highlight the selected text.

  // For Iframe's content and Storing the selection details in localStorage
  var objSel = rangee.init('iframe', iframe.contentWindow.window || iframe.contentDocument.defaultView).grabSelection();
  selection1Storage.push(objSel);
  localStorage['selection1'] = JSON.stringify(selection1Storage);  

  // For parent window and Storing the selection details in localStorage
  var objSel = rangee.init('div', window).grabSelection();
  selection2Storage.push(objSel);
  localStorage['selection2'] = JSON.stringify(selection2Storage);

  // Highlighting previous selection (stored in localStorage)
  rangee.init('iframe', iframe.contentWindow.window || iframe.contentDocument.defaultView);
  rangee.highlight(JSON.parse(localStorage['selection1']) );
  rangee.init('div', window);
  rangee.highlight(JSON.parse(localStorage['selection2']) );

Final Demo

View Demo

References

http://code.google.com/p/rangy/
http://stackoverflow.com/q/11537032/415057

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.
Advertisement

How to load PDF file into a webpage


I was recently working on a pdfViewer project wherein I wanted to render PDF file into a webpage and place some interactive elements (i.e. audio, video or a link) on top of it. During my research for existing JS libraries or plugins to simplify my task, I landed up making a categorized list of available tools which somewhat helped me to accomplish my job and also to know their approach to solve this problem on the web.

  • Having Adobe Acrobat or Built-In PDF viewer Dependencies: 
    •  pdfobject.com (http://pdfobject.com/): is an easy-to-use method for dynamically embedding PDF files into HTML documents. It uses JavaScript to generate and inject a standards friendly <object> element into your HTML file.
    • Using <object> or <iframe> tags : You can simply load any PDF file into an iframe or object tag which will be rendered by the PDF viewer plugin installed in your browser. It simply obviates the need of using pdfobject.js library.
  • Implementing custom PDF viewer in JavaScript:
    • HTML5 version of Flexpaper (http://flexpaper.devaldi.com/demo/) : For this to work, you have to convert entire PDF file into images using `convert` imageMagick tool (http://goo.gl/m8S9B) in Linux and rest of the things can be taken care by the viewer itself. In addition to this, it also supports custom zooming, text searching and selection, hand tool, layout options etc but unfortunately its paid.
    • Mozilla pdf.js (https://github.com/mozilla/pdf.js) : I’m really excited about this but for now it fails to render complicated PDFs (including complex tabular data). I hope that this project one day would replace the native or Adobe PDF plugin in the browser.
    • Trapeza (http://trapeze.xyrka.com/) : Faces problem while rendering complex PDFs like pdf.js.
  • For publicly available PDFs: 
    • Google Docs Viewer (https://docs.google.com/viewer) :  It’s a perfect choice (it is free) only If you have your PDFs publicly available and you believe that Google does no evil :-P.

Unfortunately, none of the above worked for me because rendering a PDF file into a webpage was not the only requirement I had. Iframe also works differently in different browsers which means Safari does not allow you to overlay other html elements on top of it but chrome does though they share the same rendering engine (webkit). And luckily bgIframe shim JavaScript plugin helps you get rid of z-index issues in IE.

Finally, ping me If something is missing in the list.  Sigh!

If you found this article useful in anyway, feel free to donate me and receive my dilettante painting as a token of appreciation for your donation.