May 27

stylesheet.onload: or lack thereof

For a version of stylesheet.onload that works cross domain, please see: When is a stylesheet really loaded?by @stoyanstefanov

Loading stylesheets into your page dynamically can be a good way of reducing your initial page download size. You may want to load a print stylesheet in only when a user initiates a print action, you may want to allow your users to change the theme or skin of your site/ app or before displaying some content loaded in dynamically via AJAX.

There’s only one caveat, you need to know that the stylesheet has actually loaded before you start the print action, or display your content to avoid any mishaps or foucs. This is easy when using AJAX or inserting a script or image node into the page. Unfortunately though, most browsers have not implemented the same functionality for the humble link node.

Funnily enough, Internet Explorer supports the onreadystatechange event and Opera supports the onload event for link nodes. This still leaves us with a large gap of unsupported browsers and the IE and Opera only tell us that the stylesheet has completed loading and not whether it was loaded successfully. So, what are our options then?

  1. Don’t support this kind of functionality
  2. Use AJAX to load in the stylesheet’s content, create a style node and insert the XHR responseText into the style node.
  3. Dynamically create a link node, load the stylesheet in and use setTimeout to run a callback after a abitrary amount of time has elapsed (e.g. 250ms), and hope it all works out.

None of these options really seems viable, although option 2 is probably the nicest of the 3. There is one more option though: option 4. Oooohhhh…

Option 4: The bestest option so far.

Even though there is no event to listen to, we do have a way we can check a stylesheet has loaded in the remaining browsers: by checking it’s cssRules length.

In W3C compliant browsers the link node will have a sheet property which, in itself, contains a cssRules property, which, as you can probably guess, is a list of all the CSS rules in the style sheet. If the style sheet is loaded, and not empty, then:

link_node.sheet.cssRules.length > 0;

As you can expect, Internet Explorer, has a slightly different syntax:

link_node.styleSheet.rules.length > 0;

So using this little piece of knowledge and a little setInterval/ setTimeout magic we can create a script that allows us to load style sheet into the page dynamically and then fire a callback when it has completed.

With option 4, we can create a function that loads a style sheet in any browser and fires a callback when it is has loaded. The callback will tell us if it was successfull or unsuccessfull (it would only be unsuccessful if the file did not exist or the style sheet had no rules in it).

The basics

The most basic implementation of this can be done in a mesely 30 lines of — framework independent — JavaScript code:

function loadStyleSheet( path, fn, scope ) {
   var head = document.getElementsByTagName( 'head' )[0], // reference to document.head for appending/ removing link nodes
       link = document.createElement( 'link' );           // create the link node
   link.setAttribute( 'href', path );
   link.setAttribute( 'rel', 'stylesheet' );
   link.setAttribute( 'type', 'text/css' );

   var sheet, cssRules;
// get the correct properties to check for depending on the browser
   if ( 'sheet' in link ) {
      sheet = 'sheet'; cssRules = 'cssRules';
   }
   else {
      sheet = 'styleSheet'; cssRules = 'rules';
   }

   var interval_id = setInterval( function() {                    // start checking whether the style sheet has successfully loaded
          try {
             if ( link[sheet] && link[sheet][cssRules].length ) { // SUCCESS! our style sheet has loaded
                clearInterval( interval_id );                     // clear the counters
                clearTimeout( timeout_id );
                fn.call( scope || window, true, link );           // fire the callback with success == true
             }
          } catch( e ) {} finally {}
       }, 10 ),                                                   // how often to check if the stylesheet is loaded
       timeout_id = setTimeout( function() {       // start counting down till fail
          clearInterval( interval_id );            // clear the counters
          clearTimeout( timeout_id );
          head.removeChild( link );                // since the style sheet didn't load, remove the link node from the DOM
          fn.call( scope || window, false, link ); // fire the callback with success == false
       }, 15000 );                                 // how long to wait before failing

   head.appendChild( link );  // insert the link node into the DOM and start loading the style sheet

   return link; // return the link node;
}

This would allow you to load a style sheet with an onload callback function like this:

loadStyleSheet( '/path/to/my/stylesheet.css', function( success, link ) {
   if ( success ) {
      // code to execute if the style sheet was loaded successfully
   }
   else {
      // code to execute if the style sheet failed to successfully
   }
} );

Or if you want to your callback to maintain its scope/ context, you could do something kind of like this:

loadStyleSheet( '/path/to/my/stylesheet.css', this.onComplete, this );

If you want to see it in action I’ve created a test page. I’ve also created a nicer version of loadStyleSheet which:

  • Allows you to add multiple callbacks per stylesheet
  • Uses onreadystatechange and onload events for browsers that support them; and
  • Does not try and load the same stylesheet if it is still in the DOM — if you try and load the same style sheet twice it will simply fire the callback you passed, with success == true.

It has it’s own test page test page. loadStyleSheet.js is about 1kb gzipped.


Apr 12

pseudocode + diagrams = ui interactions on a silver platter

I mentioned in a previous post how I’d been working on the drag and drop module of thud for about 2 weeks. It was definitely challenging, though now that I’ve done it, I think I could do it again in about 2 or 3 days.

Making something on a page draggable without using the new HTML5 attributes and events is the easy part. What are the tricky parts? Setting the coordinates of the drag image correctly when the drag element is in a positioned element, scrolling correctly and getting the snap to grid correct. It was a little frustrating, but I got there in the end. Thanks to pseudocode. This post is mostly to do with implementing drag functionality. However, it was pseudocode and drawing diagrams that saved me, hence the title.

I first “learnt” (about) pseudocode in high school — I use the term loosely, because I didn’t really learn much in high school, except about how to be a yes man and how everyone in the class was going to fail. I failed at both failing and being a yes man — computer studies class. I didn’t really get it then and thought it was a waste of time, never looking at it again until recently.

However, after failing to figure out some of the major errors I was encountering I needed to get back to basics and so I turned to pen and paper. I didn’t follow the exact pseudocode syntax, I don’t think you really need to. As long as you understand what is going on and can use what you write in your program. Mine is my own special recipe of pseudocode and JavaScript.

I thought I would add post about it, in case anyone else finds it useful.

Finding and setting the drag image’s current left and top positions

The logic for finding and setting the drag image’s current left and top position also includes the logic to snap the drag image to a grid, constrain it to specific dimensions, keep it a specific amount of pixels from the mouse pointer (offset) and/ or limit it to been dragged horizontally or vertically.

Glossary

I’ve put this at the top so it’s easier to see while reading the rest. Assuming you’re lucky enough to have a big monitor.

propertydescription
xThe position the mouse cursor is at, relative to the page. i.e. the event’s pageX value.
coordinatesThe bottom, height, left, offsetLeft, offsetTop, right, scrollHeight, scrollLeft, scrollTop, scrollWidth, top & width properties of an html element or the document.
orientationA value, either horizontal, vertical or null, and if set is used to limit the drag_image to only moving in either direction but not both.
drag_elementThe element we want to drag.
drag_imageThe element that will be following the cursor while the drag operation is taking place. This could be the drag_element itself, a clone of the drag_element or a completely bespoke element.
scroll_elementThe element to scroll if scrolling is allowed and the drag_image is at the boundary of the viewable_area.
viewable_areaThe coordinates of the area currently visible in the scroll_element (this will generally be the document).
snapAs in snap to grid. The number of pixels to snap the drag_image to. If this is less than 2 it will be ignored.
gridA predefined array of all the values the drag_image should snap to within the boundaries of the scroll_element. This is worked out as:
( new Array( Math.ceil( scroll_element.scrollWidth / snap ) + 2 ) ).map( function( value, index ) { return index * snap; } );
e.g. If the scrollWidth was 1024px and the snap is 16px, the grid array would be: [16, 32, 48, …, 1024, 1040]
The reason the final grid value is larger than the scroll_element’s boundary is that we want to be able to position the drag_image at the scroll_element’s boundary.
To figure out the vertical grid, we use scrollHeight instead of scrollWidth.
constrain_coordinatesthe bottom, left, right, & top coordinates to limit the drag_image’s movements within.
offsetthe number of pixels to keep the drag_image from the mouse pointer.
scrollXthe amount of pixels the viewable_area was scrolled left or right.
aggregateOffsetLeftThis is worked out as:
var aggregateOffsetLeft = 0, el = drag_element;
do { aggregateOffsetLeft += el.offsetLeft; } while ( el = el.offsetParent );
aggregateOffsetTopThis is worked out as above, except instead of offsetLeft we aggregate offsetTop.
Finding and setting the drag image’s left (x) position
IF orientation != vertical // if we are only allowing vertical movement, don't worry about calculating the horizontal
   IF viewable_area WAS SCROLLED BY AMOUNT scrollX // check to see if the scrolling function has scrolled horizontally
      viewable_area.left += scrollX // if so we need to adjust the viewable_area properties to reflect this
      viewable_area.right += scrollX

   x = x - drag_image.aggregateOffsetLeft // we don't want ot use the -= operator here as it will change the original x value

   IF x >= viewable_area.right // this makes sure the drag_image is always visible to the user dragging it
      IF viewable_area.right // 20 is the standard vertical scroll bar width
      ELSE
         x = scroll_element.scrollWidth - drag_image.width - |offset| // |offset| if you don't know is the absolute value of offset so |8| == |-8| == 8
   ELSE IF x // closestGridPosition returns the number in the grid array that x is between. 
                                                   // e.g. if snap == 16px and x is between 16 and 32 it will return 16, 
                                                   // if x is between 32 and 48 it will return 32, etc.
   IF CONSTRAIN TO COORDINATES
      IF constrain_coordinates.left && x  constrain_coordinates.right
         x = constrain_coordinates.right

   drag_image.left = x + offset

On line 5, we have the following code x = x - drag_image.aggregateOffsetLeft. This was a tricky part, it took me about 2 days to realise I was using the wrong property here.

As I mentioned before making something draggable is easy. In it’s simplest form you can set the left and top position of the drag_image to the mousemove event’s pageX and pageY values. Consider the following diagram:

diagram: dragging within the viewport

If our drag_element is a child — and not a descendant of a positioned element — of the body tag and you don’t need to scroll the page, then setting the drag_image’s left and top styles to the event’s pageX and pageY values is ok. However, consider the next diagram:

diagram: dragging within a positioned element

Basically, the element we want to drag is a child of positioned_element in the diagram. Its left and top values are relative to this element. So setting the left and top values to the event’s pageX and pageY values, would position the drag_image in the wrong location on the page, as its position is relative to positioned_element, NOT the viewport.

If we want to drag the drag_image out of the positioned_element, then we need a way of figuring out the drag_image’s left and top values, relative to positioned_element.

In the example above, positioned_element is 100px from the left and top of the document, the drag_image is currently 100px from the left and top of the positioned_element and we want to drag the drag_image to 10px from the left and top of the document, the new coordinates for the drag_image would need to be set to -90px from the left and top of positioned_element.

This is worked out as, 10px from the left of the viewport minus the positioned_element’s 100px from the left of the viewport, equals -90px.

Finding and setting the drag image’s top (y) position

I was going to put the pseudocode for this in, but it’s exactly the same as the above, only we replace the left (x) properties, with the corresponding top (y) properties:

left (x) propertytop (y) property
xy
verticalhorizontal
scrollXscrollY
viewable_area.leftviewable_area.top
viewable_area.rightviewable_area.bottom
drag_image.heightdrag_image.width
drag_image.leftdrag_image.top
drag_image.aggregateOffsetLeftdrag_image.aggregateOffsetTop
scroll_element.scrollWidthscroll_element.scrollHeight
constrain_coordinates.leftconstrain_coordinates.top
constrain_coordinates.rightconstrain_coordinates.bottom

Scrolling when the drag image is at a boundary point

The tricky part here is working the scroll sensitivity into the equation. We could adjust the viewable_area boundary of the scroll_element to incorporate the scroll_sensitivity, so that when the drag_image reaches this new boundary we can start scrolling.

However, as the viewable area will then change we will need to readjust the boundary each time we scroll. We need to create a boundary — I like to think of it as a force field — around the drag_image. The easiest way to envision this, I’ve found, is using the following diagram:

diagram: scrolling an element while the drag_image is at its boundary

Using this method, we can create a boundary — force field — around the drag_image the size of the scroll_sensitivity amount. When this drag_boundary's bottom, left, right, top — or a combination of those — is greater — or less than, depending on the direction of the drag — the boundary of the viewable_area we can initiate scrolling in the specific direction. Figuring out the drag_boundary is defined in the glossary below, so I won't repeat it here.

Glossary

Apart from what’s already above, we also have these properties:

propertydescription
scroll_sensitivityThe distance from the scroll_element’s boundary points to initiate scrolling.
drag_boundaryThis is an object representing the drag_image dimensions plus the scroll_sensitivity. This is worked out as:
var drag_boundary = {
   bottom : drag_image.height + drag_image.aggregateOffsetTop + scroll_sensitivity,
   left   : drag_image.aggregateOffsetLeft - scroll_sensitivity,
   right  : drag_image.width + drag_image.aggregateOffsetLeft + scroll_sensitivity,
   top    : drag_image.aggregateOffsetTop - scroll_sensitivity
};
scroll_amountThe amount in pixels to scroll by if scrolling is required.
scrollXIf scrolling is required, then this will be set to either positive scroll_amount (to scroll down/ right) or negative scroll_amount (to scroll up/ left).
Scrolling horizontally
IF orientation != vertical
   IF drag_boundary.right > viewable_area.right && drag_boundary.right  scroll_element.scrollLeft + scroll_sensitivity
      scrollX = scroll_amount
   ELSE IF drag_boundary.left = 0
      scrollX = -scroll_amount

   IF scrollX IS A NUMBER
      scroll_element.scrollLeft += scrollX
Scrolling vertically

Again, apart from what is already above, we swap the corresponding properties:

left (x) propertytop (y) property
drag_boundary.leftdrag_boundary.top
drag_boundary.rightdrag_boundary.bottom
scroll_element.scrollLeftscroll_element.scrollTop

Conclusion

Using pseudocode and diagrams, we can clearly visualise user interface interactions and this in turn will help us write clean and efficient code.

Obviously, project time constraints are an issue and a lot of development teams are pushed to get something live rather than something — of quality — that works. This type of careful planning often takes a back seat to showing working code for your next sprint so you can rush through the next chunk of work.

Having the opportunity to take time off, to focus on development with an emphasis on quality is an amazing experience. It’s going to be hard if I have to go back!


Apr 6

the dragging and droppings

For the past two weeks I’ve been working on the drag and drop module for thud. Even though I had read PPK’s: The HTML5 drag and drop disaster. I thought, “Wow! I can use the new HTML5 drag and drop events to build my drag and drop module!” WRONG!!!

I won’t go into everything he’s already talked about in his post, it’s brilliant, and if you haven’t read it you absolutely should. I would like to, however, extend the list of grievances:

  • Setting a drag image — the html snippet or image you see following the cursor — is convoluted. What if I wanted to use the item I clicked on rather than a copy of it? There isn’t a method in place for that.
  • Scrolling the document instead of exiting the browser window. Currently dragging allows you to drag outside of the browser window. What if i want to restrict the dragging to within the current document and scroll the document in the direction i am dragging when i hit the edge?
  • Constraining the drag to specific coordinates in the document (e.g. { bottom : 400, left : 10, right : 400, top : 10 }).
  • Limiting the drag to only horizontal or vertical directions.
  • Snap to grid dragging (e.g. a 16px snap would only move the drag image in increments of 16px in any direction).

These options are all available in the drag and drop implementations of various frameworks I’ve worked used — MooTools, Ext, Scriptaculous, etc… — and are there for good reason, we need them to give the user a rich experience when building UI components!

My feeling is I will need 2 different types of drag and drop in thud. One will be for restricting UI components to a page (non-html5 implementation); the other will be for dragging objects from external sources to the document and vice versa (html5 implementation). WAH! :’(


Apr 3

wtf!? new commodore 64!

commodore c4 revamped

I was looking for some design inspiration today when I happened to see this link in my search results.

Seeing the Commodore 64 again made me think how it was similar to the Apple iMac, only it needs a monitor instead of a keyboard. Then I realised, the Commodore 64 was doing this from its beginnings. However, as I’m writing this, I also realised that Apple has also been doing the whole computer in the monitor thing since 1983 with the introduction of the Apple Lisa and then the following year with the first Macintosh.

I’m wondering though if you can combine the two to get some awesome processing power and two machines using the same monitor without any loss of space. Would probably require at least 3 rolls of scotch tape though.


Hackers and Painters


Page 1 of 4