Javascript Rollover Rollout Events

Javascript has mouseover and mouseout events. Flash has these, but they also have rollover and rollout events. There is a difference, and it can be painful developing Javascript components without the rollover and rollout events. So I put together a little script that provides them for us.

Problem

You want to perform some action when the cursor rolls onto and off of an HTML element. When you use the mouseover/mouseout events, you get a mouseout and immediately another mouseover when the cursor is over a child element. Technically the mouse is still over the parent element, why does the mouseout happen? What the… you want it to work like CSS :hover. The way you’d think it SHOULD work. You figure, maybe it’s just you’re catching because bubbling child events, so you return out of your listener if the target does not equal your element. This removes the second mouseover, but you still get a mouseout when hovering over a child element.

Solution

Flash’s solution was to build in a non-bubbling rollover/rollout event that works like you’d expect. I’ve never used a mouseover/mouseout in Flash since these new events came about. So, assuming you’ve got a modern standards-compliant browser (tested on Safari/Chrome & Firefox), this little bit of code will provide rollover/rollout events for your HTML and is non-intrusive. It saves a lot of headache and works great.

Updated: The code did not previously take into account siblings, only ancestors. Fixed to find the common ancestor.

(function() {
	
	function listener(event) {
		var child = event.relatedTarget;
		var ancestor = event.target;
		// cancel if the relatedTarget is a child of the target
		while (child) {
			if (child.parentNode == ancestor) return;
			child = child.parentNode;
		}
		
		// dispatch for the child and each parentNode except the common ancestor
		ancestor = event.target.parentNode;
		var ancestors = [];
		while (ancestor) {
			ancestors.push(ancestor);
			ancestor = ancestor.parentNode;
		}
		ancestor = event.relatedTarget;
		while (ancestor) {
			if (ancestors.indexOf(ancestor) != -1) break;
			ancestor = ancestor.parentNode;
		}
		child = event.target;
		while (child) {
			var mouseEvent = document.createEvent('MouseEvents');
			mouseEvent.initEvent(event.type.replace('mouse', 'roll'),
					false, // does not bubble
					event.cancelable,
					event.view,
					event.detail, event.screenX, event.screenY,
					event.ctrlKey, event.altKey, event.metaKey, event.button,
					event.relatedTarget);
			child.dispatchEvent(mouseEvent);
			child = child.parentNode;
			if (child == ancestor) break;
		}
	}
	
	// setup the rollover/out events for components to use
	document.addEventListener('mouseover', listener, false);
	document.addEventListener('mouseout', listener, false);
})();

I hope this helps someone having a rough time with Javascript mouseovers.

Javascript Data Binding

Simpli5 has been coming along nicely as I’ve been able to put time into it. I’m very excited to announce data-binding.

Data Binding

Data binding is a technique we have and use in Flash quite a bit that allows one property to stay in sync with another. If obj.x is bound to obj.y then whenever obj.x is changed, obj.y will automatically update to the same value. I’ve built my own data binding frameworks in ActionScript 3 and I wanted it for my Javascript work as well.

Since Simpli5 has a base-line of HTML5, I was easily able to create data binding in Javascript using implicit getters and setters. Any property on an object can be bound. My binding supports one-way, two-way, and bind-setters, and it allows deep binding.

One-way binding propagates the changes of the source object’s property to the target object’s property. Whenever the source’s property changes, the target’s property updates. If the target’s property is changed however, nothing will happen to the source.

Two-way binding keeps the properties of the source and target in sync. When first bound, the source’s property will be the value used, but if either the source’s property is changed or the target’s property is changed the other will update to stay in sync.

Deep binding is being able to bind the property of a property of the source. As an example, obj.x would be shallow binding where obj.x.y.z would be deep binding.

Binding Usage

The following shows how binding can be used.

var obj1 = { name: 'Bob' };
var obj2 = { name: 'Henry' };

Bind.property(obj1, 'name', obj2, 'name');

alert(obj2.name); // will be Bob
obj1.name = 'Patricia'; // the binding makes obj2.name update as well
alert(obj2.name); // will be Patricia

var obj1 = { name: 'Bob', address: { zip: 12345 } };
var obj2 = { name: 'Henry', address: { zip: 56789 } };

Bind.property(obj1, 'address.zip', obj2, 'address.zip', true); // the last param (true) means a two way bind

alert(obj2.address.zip); // 12345

obj2.address.zip = 'unknown';
alert(obj1.address.zip); // will be 'unknown' because we are 2-way binding
obj1.address.zip = 12345;
alert(obj2.address.zip); // will be 12345 again, same as obj1

Binding added to Template

I’ve added a new method to Template called createBound(). The Template.create() method will give you back an HTML element created from the template’s HTML, replacing anything in curly braces with the evaluated javascript equivalent. With Template.createBound() you get back an HTML element which continues to update its attributes or text content as properties in the data or element change. Perhaps the best way to explain is to show some code.

var template = new Template('<button><img src="{this.src}" alt="{this.label}"/> Say {this.label}</button>');
var btn = template.createBound();
alert(btn.outerHTML); // <button><img src="" alt=""/> Say 

btn.label = 'Hello World';
btn.src = 'images/hello_world.png';

alert(btn.outerHTML); // <button><img src="images/hello_world.png" alt="Hello World"/> Say Hello World</button>

I’ve made it the component creation method of course, so now the properties on your components can update the HTML directly. And it isn’t setting the innerHTML of the component, which would replace all the sub-elements and destroy any listeners that were on the old ones. It’s binding smartly so that attributes and text get updated carefully.

Limitations

There are properties on elements that binding will not work the way you’d expect. Things like element.id, element.firstChild, img.src, et cetera, which the browser treats in a special way but are not getters and setters (as far as the javascript can tell anyway), these cannot be bound to reliably. They cannot be the source of a bind, but they could be the target of a one-way (non-deep) bind just fine.

Testing

I’ve started adding unit tests to the framework, especially since data binding can be such a tricky thing. I’ve got tests around the binding and the templates right now and feel confident that they are working pretty well. I’m using js-test-driver. I’ve also added a maven build file for the tests and for compressing the library into a single file and a compressed file.

The Simpli5 project is being hosted on Github: http://github.com/jacwright/simpli5.

Simpli5, an HTML5 Javascript Framework

I’ve started putting together a new HTML5 Javascript framework that is only compatible with HTML5 browsers. I’m able to have a much smaller library that way, and I’m able to do stuff I couldn’t otherwise. Like…

Optimization

Because I’m using built-in Javascript methods (written in C) for many things I do, I am taking advantage of the speed of modern HTML5 browsers, without the bloat of code that makes up for missing features. Array.prototype.slice is used to convert NodeLists and Argument objects into arrays with $.toArray(); querySelector and querySelectorAll are used to quickly find the first or all matches for a given selector string. The built-in Array.prototype.forEach is used for iterating over arrays.

HTMLElement.prototype

I can add methods to the prototype of Node and HTMLElement, giving all elements in my document functionality that I feel are common enough to be needed.

element.on('click', callback, false) // same as element.addEventListener(...) but shorter
element.addClass('my-class-name'); // hasClass, removeClass, toggleClass
element.findFirst('div .red'); element.find('li');
element.html(); element.html('<b>Hi!</b>');

And much much more.

simpl5 Element Wrapper

I’ve also create a jQuery-like class (called simpli5) which is basically an Array (uses all the native Array methods like concat, splice, forEach, map, etc.) and forwards all my calls I specify onto the elements in the array.

simpli5('#my-div').toggleClass('blue');
$('a').on('click', function(event) {
	event.preventDefault(); // real event object, don't need to fake it with a compatible browser
});

Class Model and EventDispatcher

I like the class model a lot. It is actually compatible with earlier browsers as well. And I do use getters/setters now that I can. I also created an EventDispatcher class that provides the addEventListener/removeEventListener/dispatchEvent, and the shortcut methods on/un.

var Foo = new Class({
	extend: EventDispatcher,
	
	init: function(name) {
		this._name = name;
	},
	
	get name() {
		return this._name;
	},
	set name(value) {
		if (this._name == value) return;
		var oldName = this._name;
		this._name = value;
		this.dispatchEvent(new PropertyChangeEvent('name', oldName, value));
	}
});

var Bar = new Class({
	extend: Foo,
	init: function() {
		// I've found this to be the simplest most optimized (least hacky) way for overrides
		// even though the syntax with a string isn't ideal. And I've looked around.
		this.callSuper('init', 'Bar');
	},
	// override only the getter to make it read-only
	get name() {
		return this._name;
	}
});

Component Model

The part I’m most excited about is my component model. It allows you to make DOM elements become new classes. I can take a common <button id="myButton"></button> (HTMLButtonElement) element and make it become my ToggleButton component. You will even get a true when doing: document.getElementById(‘myButton’) instanceof ToggleButton. The following is a simple Button component.

var Button = new Component({
	extend: HTMLButtonElement, // be sure to extend the type you are becoming
	// the default template, true == cached (thanks ExtJS)
	// this component will become the top-level element in the template
	template: new Template('<button></button>', true),
	init: function(label) {
		if (label) this.label = label;
	},
	get label() {
		return this.text();
	},
	set label(value) {
		this.text(value);
	}
});

// create a new button and add it into the document
var button = new Button();
button.label = "Hello World";
document.body.prepend(button);

// turn all existing <button></button> elements into Button components
document.find('button').make(Button);

Just the Beginning

This is just the start. I’ve been borrowing ideas and code from libraries such as jQuery, mootools, prototype, and Extjs. The library is still only partial with many missing features, but it will mature, and the beginning I’ve got has been quite exciting.

CSS3 Buttons

Using a handful of CSS3 styles implemented by Webkit (Safari and Chrome) and Firefox browsers, I’ve recreated buttons which I previously did with images, all in some simple CSS. I used the following styles:

  • Background-image gradients
  • Border radius
  • Text shadow
  • Box shadow
  • Custom fonts

The page is shown here in an iframe: If you have a keen eye you’ll see I’m using some borders on the top and bottom to simulate bevels, I’m using a custom font, Sansation, courtesy of dafont.com, and the text shadows are cast upward when hoving over the button. Best part is, I’m using no javascript, and whenever I change the text of the buttons, I don’t have to re-export images with the correct text. Won’t the world

buy levitra oral jellyhttp://pharmacy7days-online.com/http://cialis7days-pharmacy.com/desyrel-price.php

be a lovely place when all browsers support the full HTML5/CSS3 spec?

New Track: HTML5

I’ve picked up a side job using HTML5’s canvas element, and the project at work turned HTML5 for various non-Apple reasons. I’ve been hesitant to post about it because I firmly feel that Flash will continue to flourish in the future, and HTML5 is simply a new and better HTML4/XHTML.

I will continue to be involved in the Reflex project and in the Flash community, but since I am doing a lot of HTML5 work in the next few months, I will be talking about the cool stuff I am learning and doing. I’ve done a lot of HTML/CSS/JS, but now that I am tasked to only worry about HTML5, and not worry about being compatible with non-HTML5, the world has become a brighter place. A lot brighter.

To get you started, here’s a great overview on some of the new HTML5 features. There is some cool stuff, especially the Worker (which Flash doesn’t have…yet) for a type of multi-threading. But the best feature for me is the CSS3 spec, allowing me to have scale-9 borders, use negative margins and inline-block without worrying about browser compatibility, and in general not need to create a ton of HTML markup just for my design. More coming soon.