Simple Image Popouts

In-page popouts are a powerful tool you can use to make your website feel more like a desktop application. There are a lot of great libraries with powerful, full-featured in-page popouts, and if you want the kitchen sink, I suggest you use one like Yuis Container. However, sometimes you just want something simple, that works out-of-the-box, requiring little code and next to no setup. For those times I have created a Simple Image Popout widget.

Example 1: Simple Image Popout JavaScript

var ImagePopout = function(bd, src, emap) {

	// Module Constant Variables

	var STYLES_DIV = position: absolute; top: 0; left: 0; overflow: visible; height: 0; z-index: 100,
		STYLES_IMG = position: absolute; top: 0; left: 0; display: none;;

	// Module Local Variables

	var F = function() {},
		that = null,
		img = new Image(),
		nextNode = bd.firstChild;
	img.src = src;

	// find first non-text
	while (nextNode.nodeValue) {
		nextNode = nextNode.nextSibling;
	}

	// Libary Functions

	// will pass in the evet returned by addListener, and expects to return an object where x is the left position of event and y is the top position of event
	var getEventPos = function(e) {
		return {x: YAHOO.util.Event.getPageX(e), y: YAHOO.util.Event.getPageY(e)};
	};
	var Event = YAHOO.util.Event;
	var addListener = Event.addListener; // expects params : function(element, targetEvent, callbackFunction)
	var viewportSize = function() { // expects to return an object where x is the viewport with and y is the viewport height
		return {x: YAHOO.util.Dom.getViewportWidth(), y: YAHOO.util.Dom.getViewportHeight()};
	};
	var $ = YAHOO.util.Dom.get;

	// Module Dom Variables

	var dom = {
		div: bd.insertBefore(document.createElement(div), nextNode),
		img: $(document.createElement(img))
	};

	dom.img.src = img.src;
	dom.div.setAttribute(style, STYLES_DIV);
	dom.img.setAttribute(style, STYLES_IMG);

	dom.div.appendChild(dom.img);

	// Public Methods

	F.prototype = {

		/**
		 * Hides the popout
		 */
		hide: function() {
			dom.img.style.display = none;
		},

		/**
		 * Shows the popout
		 */
		show: function() {
			var wsize = viewportSize(),
				left = (wsize.x - 800) / 2,
				top = (wsize.y - 400) / 2;

			if (left < 0) {left = 0;}
			if (top < 0) {top = 0;}

			dom.img.style.left = left + px;
			dom.img.style.top = top + px;
			dom.img.style.display = block;
		}
	};

	that = new F();

	// attach the click event to the popout
	addListener.call(Event, dom.img, click, function(e) {
		var epos = getEventPos(e),
			x = epos.x,
			y = epos.y,
			coords = YAHOO.util.Dom.getRegion(dom.img),
			ox = coords.left,
			oy = coords.top;

		// iterate on the emaps, to see if event is valid
		for (var i = 0, j = emap.length; i < j; i += 1) {
			var l = emap[i];

			// inside coordinates
			if (ox + l.left < x && oy + l.top < y && ox + l.right > x && oy + l.bottom > y) {
				l.fn(e);
			}
		}
	});

	return that;
};

This widget handles the position, displaying, and interaction of an image file that you want to use as a popout. Simply create the perfect image of your static popout, in Photoshop, and include it in your project. To use this widget, call as follows:

Example 2: Calling ImagePopout

var popup = null;

var fn = function() {
	popup.hide();
};

var eventRegion1 = {
	top: 60,
	right: 190,
	bottom: 80,
	left: 90,
	fn: fn
};

var eventRegion2 = {
	top: 110,
	right: 330,
	bottom: 130,
	left: 230,
	fn: fn
};

var eventRegion3 = {
	top: 175,
	right: 145,
	bottom: 195,
	left: 45,
	fn: fn
};

var emap = [
	eventRegion1, eventRegion2, eventRegion3
];

var popup = new ImagePopout(referenceToBodyTag, urlToPopupSource, emap);

When you call ImagePopout, you need to pass a reference to the body tag and the src to your image file. ImagePopout will create a new, absolutely-positioned node as the first element in the body and insert a new image tag, with your src, into that element. The constant styles are those that I have found work best, in most browsers, for positioning and hiding the popout. The public Functions on a new ImagePopout are hide and show, where show positions the popout in the center of the viewport, or top-left if the veiwport is too small. Lastly, the image has an onclick listener, which will iterate through the event regions that you passed in the emaps variable, firing the appropriate callback Function.

The mouse click point of the event is compared to the position of the popout plus any coordinates you have provided, and when the mouse event occurs inside one of your regions, then the provided callback Function is fired. In Example 2, eventRegion1 could be some textual link that triggers a new page to open, and perhaps the eventRegion2 is an X in the top right corner that closes the popout. emaps is a collection of as many event regions as you desire, and will work even if you pass in an Array of length 0 (although you probably shouldnt). Also, you may have overlapping regions and multiple callback Functions for the same point (say you want all clicks to close the popout, but you want specific clicks to also trigger another callback).

I have built this on YUI, but have also made it easy to use your own library by setting Library methods to local Functions. Simply, update the shortcut methods that I have used with the ones of your library (you may need to wrap them as I did with getEventPos and viewportSize to return the values in the right format).

Some improvements that you might think about are: a page mask, so that the page is not interact-able when the popout is visible; hiding the page select tags when viewed in IE 6 and lower, as they will most likely bleed through the image; changing the cursor to pointer when hovering over a link; and passing a config property to allow better positioning and styling of the popout.

……………………….

Revised March 30, 2008
As it turns out I didnt properly test the library function shortcuts with YUI. By assigning YAHOO.util.Event.addListener to addListener it breaks the scope of the execution of addListener, so referencing this inside the Function does not work. To fix this, we need to use the call method of the addListener Function and pass in the scope of the object containing the addListener method (in this case YAHOO.util.Event).

I have also added a test page to illustrate how this technqiue working.