Getting Started
Welcome to our first post in talking about Alloy. I'm going to jump right in, but the only piece of info I want to cover beforehand, and you'll see me do it in every post, is the idea of "sandboxes". Because AlloyUI is built on top of YUI3, it has the concept of the sandbox. What this means is simply a callback where you run your code.
The way it's constructed is that you declare the packages that you want to use, and then, inside of your sandbox, you use them.
The benefit to this is that it allows your code to run as lean as possible, and loading only what it needs, without having to load a lot of stuff on the page first.
How do you create a sandbox?
Simple:
AUI().use(function(A) { // Your code goes here });
Let's look at that real quick.
AUI()
is a function call, and you attach a .use
on it. Inside of that .use()
, you can pass 1-n number of arguments, but the last argument *must always be a function*.
You'll notice that the callback gets an argument passed to it called "A". That A is *the* Alloy object. It's where all of Alloy objects and classes are stored.
Most of the time you'll be setting up your sandbox using at least one or two packages. Here's how that would look like using the event and node packages:
AUI().use('event', 'node', function(A) {
// Your code goes here
});
When you see me write code samples where I do something like:
A.one('body');
assume that I am inside of the sandbox.
Working with elements and events
The most common task you're most likely to come across in web development is working with elements on the page, and doing something with them. AlloyUI, because it's built on top of YUI3, has two objects for working with elements on the page, Node and NodeList. These two objects are closely related, and in fact, you can think of them as almost the same. The only difference is that Node is a single item, and NodeList is a collection of Nodes.
There are two methods you can use to get elements on the page, and each will return something different depending on the method.
The methods are called A.one()
and A.all()
.
A.one()
will return a Node object if it finds the element on the page (null otherwise), and A.all()
will always return a NodeList (if it doesn't match any elements, it will return an empty collection).
Here's a few examples of how A.one()
would be used:
var el_1 = A.one('#myCustomElement1');
var el_2 = A.one('.custom-element');
var el_3 = A.one('input[type=checkbox]');
Notice how A.one()
will accept selector strings that are not just for ID elements? What if there are multiple elements matching the selector? It will just return the first one. This is useful in many situations, and has an impact on performance.
If the selector cannot be matched on the page, then A.one()
will return null. This means that in order to operate on the Node element, you have to first do an if check on the variable then do the work.
For instance:
var el_1 = A.one('#myCustomElement1');
if(el_1) {
el_1.setStyle('height', 50);
}
This can seem a bit verbose to some people, so it could be avoided if you wish. You could write the above like so:
A.all('#myCustomElement1').setStyle('height', 50);
without risk of throwing an error.
So why do I prefer A.one()
? Mainly because of performance. A.one()
will run about 2-4x faster to grab the element, but it also helps me write out clearer code (I know that if I'm not updating a block of code it's because it didn't find an element, whereas trying to debug long chains of code is a nightmare).
But both methods are there for you.
What kind of selectors are available? By default, anything in CSS2, which covers 98% of the cases and most of the selectors I've needed to write.
However, there is a CSS3 module, and if you need to do something like:
A.all('.custom-element > div:nth-of-type(even)')
, just add the "selector-css3" module to your sandbox, and the selectors are available to you.
It's pretty rare that we've actually *needed* these selectors, though, but again, they're there if you need them.
That covers the basics on getting the elements, what about doing something with them?
So, let's cover some common tasks:
Setting styles
I'm going to grab my element:
var nodeObject = A.one('#myElement');
Setting a background color:
nodeObject.setStyle('backgroundColor', '#f00'); //Sets the background color to red
Setting a border
nodeObject.setStyle('border', '5px solid #0c0'); //Sets a large green border
But what if I want to set multiple styles all at once? Just simply use the setStyles method (notice the "s" on the end of the name?).
nodeObject.setStyles({
height: 200,
width: 400
});
You can also get the current style for an element by doing something like:
nodeObject.getStyle('border');
One common task I think we've all done is to try to position a box somewhere on a page? Usually we'll just set the styles on the element, including the positioning.
For instance, let's say we wanted to move something to exactly 100px from the left, and 200px from the top.
Usually we might do something like:
nodeObject.setStyles({
left: 100,
position: 'absolute'
top: 200
});
But then, what happens if it's inside of a positioned container? It will be relative to the container, then your offset will be off.
Instead, here's how you would do it now:
nodeObject.setXY([100, 200])
And it will automatically calculate the parents positioning for you guaranteeing that it's at the spot absolutely on the page that you want it. Much shorter code and much more accurate.
But what is really cool that is related to this is, often times you just want to center an item on the page absolutely. Here's how you would do it:
nodeObject.center();
Working with class names
All of the most convenient ways of working with class names, and then some, are here:
nodeObject.addClass('custom-class');
nodeObject.removeClass('custom-class');
nodeObject.toggleClass('custom-class');
nodeObject.replaceClass('custom-class', 'new-class');
nodeObject.hasClass('custom-class');
nodeObject.radioClass('custom-class');
In that last line, radioClass()
will remove the class name from all of the sibling elements, and add it only to the current item, similar to how a radio button would behave.
Manipulating elements
Appending a new element to the nodeObject:
nodeObject.append('<span>New Text</span>');
Appending the nodeObject to another element already on the page:
nodeObject.appendTo('body');
Updating the innerHTML of an element:
nodeObject.html('<b>new text</b>');
Removing an element:
nodeObject.remove();
Creating a brand new node from scratch:
var newNodeObject = A.Node.create('<div id="myOtherElement">Test</div>');
Moving up and down the elements
Often you need to jump around to different elements relative to the current one you're on (for instance, to find a parent of a current item or a child/children).
Finding the first parent of nodeObject with the class name of .custom-parent:
nodeObject.ancestor('.custom-parent');
Finding the first child with the class name of .custom-child:
nodeObject.one('.custom-child');
Finding all children with the class name of .custom-child:
nodeObject.all('.custom-child');
It's interesting to note that most of the methods that are on Nodes are also on NodeList. The ones that aren't are usually just the getters where it wouldn't make sense for a collection of items to return the data from any one item.
Meaning this: it makes sense to have a collection, like nodeListObject, which contains 5 div elements, and when you call nodeListObject.setStyle()
for that style to be applied to all 5 elements, or if you call nodeListObject.append('<b>test</b>')
for it to append a new b element to every item.
But it doesn't make much sense to do: nodeListObject.getStyle('backgroundColor')
. What should it return? The first item in the collection? The last item?
And since it's insanely easy to do this instead:
nodeListObject.item(0).getStyle('backgroundColor')
it just makes more sense not to add the methods onto the NodeList to avoid confusion when getting data out of an element.
Getting properties
Now, here comes a really interesting part. Since nodeObject is a wrapped element, you can't just do nodeObject.id to get the id or nodeObject.parentNode. If you tried that, it would return undefined.
Instead, we do nodeObject.get('id')
or nodeObject.get('parentNode')
.
Here's what is REALLY cool about using the getter: nodeObject.get('parentNode')
will return another wrapped Node object, and if it's a collection, like nodeObject.get('childNodes')
, it will be a wrapped NodeList object.
So all of the DOM properties are available.
EVEN cooler:
get will accept a dot (.) separated list of properties and traverse it for you. So let's say you know you have an item exactly three parents up, and want to set the background color to red:
nodeObject.get('parentNode.parentNode.parentNode').setStyle('backgroundColor', '#f00');
Interaction time
We've touched on how to wrangle the elements on the page. What about adding an event to it, such as doing something when a user interacts with it?
It's actually pretty simple. Every Node and NodeList has a method called on()
that let's you, appropriately enough, do something "on" that event.
Let's say I want to alert "Hello" when a user clicks on the nodeObject
nodeObject.on('click', function(event){
alert('hello');
});
Or let's say we want to add a border when a user first moves their mouse over an item:
nodeObject.on('mouseenter', function(event){
this.setStyle('border', '5px solid #555');
});
Notice how the "this" object is used without wrapping it? It's automatically wrapped for you to be a Node object, which is incredibly convenient.
But what if, on the off chance, you *must* get the original DOM object. You can do nodeObject.getDOM()
and it will return you the underlying DOM element.
This also applies to NodeList objects as well.
So if you do A.all('div').getDOM()
it will return you an array of plain DOM elements.
What if about if you need to remove an event?
Let's say you do this:
nodeObject.on('click', myFunc);
you can detach the event by simply doing:
nodeObject.detach('click', myFunc);
or you could even just remove all events by not passing a second argument, like this:
nodeObject.detach('click');
What if you want to do some work on document ready?
You can do:
A.on('domready', function(event){
// More work here
});
Now, there are times when you want to both load some modules and fire your callback on DOM ready, so here is how you would do that in Alloy:
AUI().ready('event', 'node', function(A){
// This code will fire on DOM ready
// and when this modules are ready
});
Here's an interesting example. Let's say you want to listen on the node for only a specific key combination. For instance, you want to only fire the event when the user presses the escape key, but only when holding down the shift key.
Here's how you would listen to it:
nodeObject.on('key', function(event){
// escape + shift has been pressed on this node
}, 'down:27+shift');
Now here's another use case some might be curious about. What if you want to prevent the default behavior of an event, for instance, if you want to stop a link's href from being followed?
nodeObject.on('click', function(event){
event.preventDefault();
});
In Javascript, events bubble, which mean that by default, an even on one element also happens on every element that contain it, so if you click on a link, it will also fire an event on the body element as well.
You can stop your event from bubbling though, here's how:
nodeObject.on('click', function(event){
event.stopPropagation();
});
You might notice that these are the same methods that exist in the W3C specification, but they're normalized to work the same in all browsers.
But there's also a shortcut if you want to just preventDefault and stopPropagation, which is like so:
nodeObject.on('click', function(event){
event.halt();
});
Event delegation
Speaking of event bubbling, built into Alloy is event delegation. Event delegation is a technique that let's you can attach one event to a container but have it fire only on the children elements.
Imagine you have a list, and a lot of LI elements inside of it. You could add a new event listener for each element, but as your list grows, the number of listeners will also grow, as well as memory consumption.
And let's say you add elements via ajax, it's a pain to have to reattach events after every update as well.
So let's say go through an example. Let's say that we have this HTML:
<ul id="myList"><li>Test</li></ul>
Here's how we would use delegation:
var myList = A.one('#myList');
myList.delegate('click', function(event){
alert(event.currentTarget.html());
}, 'li');
Notice a few things. One, we're calling a method called delegate, but it's very similar to "on". In fact, the only difference to the "on" method is that the third parameter is a selector that we will test to make sure the element matches before firing the function.
But also notice that we're referencing event.currentTarget. This is a property that will always point to the element that you are currently listening for, even inside of the "on" method, so I recommend using it.
But now that we've added our event, if you click on the list item, it will alert the contents of that item. Now let's try this:
myList.append('<li>Test 2</li>');
It will add another list item, and when you click on this new item, it will alert "Test 2", without having to reattach the event.
Conclusion
Hopefully this helps show you some of the helpful ways you can work with elements on your page, and help get you up to speed.