Tuesday, March 11, 2014

Scroll to selection using rangy

Rangy, like ierange before it, is great for reliably selecting text programmatically across browsers. It uses the W3C Range model and applies it to all modern browsers, greatly simplifying a task that is not covered in jQuery. But, what rangy does not do is provide a way to scroll to a selection. In cases where the selected text is off-screen, it is necessary to scroll the document down so the user can see it. A selection does not have a bounding box in rangy, so there is no way to tell where it is. The only way to get that information is to enclose it in a node, and then position that node in the centre of the screen. Provided that the node-wrapping is possible this method will suffice:

function scrollToSelection( sel )
{
    var node = document.createElement("span");
    sel.surroundContents(node);
    var scrollDist = node.offsetTop 
        - (window.innerHeight+node.offsetHeight)/2;
    if ( scrollDist > 0 )
        window.scrollBy(0,scrollDist);
}

If the selection covers parts of various elements you could wrap each separate text-node in a <span>, then compute the bounding box of the entire thing. But for my purposes the above suffices.

Tuesday, March 4, 2014

Drupal add module javascript for a specific page

If like me you prefer to split your interface into modules, and to call each of those modules on separate pages, then one problem you are likely to encounter is the use of jQuery init functions ($(document).ready(function() etc.) in each of those modules. For example, I had separate javascript for a biography page, and for the homepage. Each called its own jQuery init function, and each was incompatible with the other. When loading the biography page I looked for a certain element and then embedded the timeline and biography data fetched from the server into it. This allowed me to leverage existing javascript libraries and to reuse interface code. The drawback, though, is that in Drupal all modules are loaded for each page, and if they contain any javascript it will be executed on page load. Likewise any css files specified in the module's .info file will also be loaded, even though they are not required. Not only is this very inefficient, but it will lead to clashes, as each javascript init function looks for its specific features and fails. So to make it work better and faster you must detect the current page and only load css stylesheets and javascript functions as required. Something like this, in the mymodule.module file will work:

This way both the javascript and css only get loaded when needed, so long as you don't use module names that can occur elsewhere in request uris.

So the moral of the story is: "don't load javascript or css files via a .info file, because they will get loaded on each and every page of your Drupal installation".