Archive for the ‘javascript’ Category

Using CSS3 :target selectors

Friday, March 14th, 2008

This is a quick post to present a JavaScript that provides CSS3 :target pseudo selector like functionality for browsers that do not provide native :target support.

function() {
    var cssClass = 'target';

    // Initialise targeted fragments if any.
    var target = document.getElementById(window.location.hash.slice(1));
    if (target) {
        target.className += ' ' + cssClass;
    }

    // Click event handler function generator. This is passed a "frag" argument
    //   that is a reference to the target element.
    var reCSS = new RegExp("\\b" + cssClass + "\\b");
    function newTargetClickHandler(frag) {
        return function() {
            if (target) {
                target.className = target.className.replace(reCSS, '');
            }
            target = frag;
            target.className += ' ' + cssClass;
        }
    }

    // Run thru <a> elements and add an onclick handler to those that reference
    //   document fragments within the current page.
    var reURI = new RegExp('^' + window.location.href.match(/^[^#]+/)[0] + '#([^#]+)$');
    var elems = document.getElementsByTagName('a');
    for (var i=0; i<elems.length; i++) {
        if (elems[i].href && elems[i].href.match(reURI)) {
            elems[i].onclick = newTargetClickHandler(document.getElementById(RegExp.$1));
        }
    }
};

When the user clicks on a link that points to a fragment within the current page, this code will add a CSS class of “target” to the targeted element, and will remove the same “target” class from any previously targeted element.

Originally I was using the CSS3 :target pseudo selector and the .target class selector together in the same rule set, but I found that Opera (9.26) would not apply the rules presumably because it considered it invalid.

/* THIS DOES NOT WORK! */
h2:target,
h2.target {
    color: red;
}

I’m not sure if this is correct behavior, but regardless of cause, I found I had to use a duplicate set of CSS rules; one for both modes of selection.

h2:target {
    color: red;
}
h2.target {
    color: red;
}

And basically… that’s it! You can see it in action on this test page, and I hope to add a follow up post soon…. time allowing.

You download the the JavaScript here:
Full-fat script with white-space and comments [1621 bytes]
Minified script [1101 bytes]

Suckerfish menus with hide delay

Saturday, December 8th, 2007

You’re using Suckerfish Dropdown menus but you would like to prevent the dropdown menus from disappearing the moment the users mouse steps 1 pixel outside the dropdown?

This question is especially relevant with multi-level dropdown menus; multi-level menus and laptop track-pads are something I’ll never get used to!

The code presented below is designed to be a drop-in replacement for the standard suckerfish Javascript. The original code was only required to make MSIE work correctly, the replacement code targets all browsers. If Javascript has been disabled by the user, then the CSS will function as before.

sfHover = function() {
	var timeout = 600;
	var cssClass = "sfhover";

	var queue = [];
	var reCSS = new RegExp("\\b" + cssClass + "\\b");
	var sfEls = document.getElementById("nav").getElementsByTagName("li");
	for (var i=0; i<sfEls.length; i++) {

		// mouseover and mouseout handlers for regular mouse based interface.
		sfEls[i].onmouseover = function() {
			queueFlush();
			this.className += " " + cssClass;
		}
		sfEls[i].onmouseout = function() {
			queue.push([setTimeout(queueTimeout, timeout), this]);
		}

		// focus and blur handlers for keyboard based navigation.
		sfEls[i].onfocus = function() {
			queueFlush();
			this.className += " " + cssClass;
		}
		sfEls[i].onblur = function() {
			queue.push([setTimeout(queueTimeout, timeout), this]);
		}

		// click event handler needed for tablet type interfaces (e.g. Apple iPhone).
		sfEls[i].onclick = function(e) {
			if (this.className.search(reCSS) == -1) {
				// CSS not set, so clear all sibling (and decendants) menus, and then set CSS on this menu...
				var elems = this.parentNode.getElementsByTagName("li");
				for (var i=0; i<elems.length; i++) {
					elems[i].className = elems[i].className.replace(reCSS, "");
				}
				this.className += " " + cssClass;
			} else {
				// CSS already set, so clear all decendant menus and then this menu...
				var elems = this.getElementsByTagName("li");
				for (var i=0; i<elems.length; i++) {
					elems[i].className = elems[i].className.replace(reCSS, "");
				}
				this.className = this.className.replace(reCSS, "");
			}
			if (e && e.stopPropagation)
				e.stopPropagation();
			else
				window.event.cancelBubble = true;
		}
	}

	queueFlush = function () {
		while (queue.length) {
			clearTimeout(queue[0][0]);
			queueTimeout();
		}
	}

	queueTimeout = function() {
		if (queue.length) {
			var el = queue.shift()[1];
			el.className = el.className.replace(reCSS, "");
		}
	}
}
addLoadEvent(sfHover);

The code also includes handlers for focus and blur events, to allow site visitors to tab through the menus without using a mouse.

And finally, a click event handler is added to parent menus so that they can be opened on devices that don’t support hover events such as the iPhone/iPod Touch. This might cause a problem if your parent menu item is also a link however!

Oh… and finally finally, the above code calls the an addLoadEvent() function to force sfHover to run on page load. If you don’t already have a similar function, here’s one you can use:

function addLoadEvent(func) {
	var oldonload = window.onload;
	if (typeof window.onload != 'function') {
		window.onload = func;
	} else {
		window.onload = function() {
			oldonload();
			func();
		}
	}
}

UPDATE 2008-05-19: I have uploaded a very basic example page here, and the JavaScript is available for download here.