JQuery Events

JQuery Events

What is JQuery Events

When you write JavaScript in the browser, you’re writing event-driven code. Most of your code will be executed when something happens, such as having content slide in when a user clicks a link. Even though you might not have realized it, you’ve already written some event-based code:

$(function() {

});

 

You’ve been writing code that runs when the document is ready, as previously explained. This is an event that you’re attaching code to. It is also known as binding. Keeping the JavaScript in a separate document and binding it to the HTML document is good idea for a few reasons. The first is that it makes editing easier. Another reason is that it keeps people from injecting code into the HTML document and overriding your code.

 

So far you have bound some code to run on a certain event. In this blog, you will look at the type of events and, at the end of the blog, use your new knowledge to build an accordion. An accordion is a great way to include a lot of information in a small amount of room. It works by taking paragraphs of text—each under a heading—and showing only one section of text at a time. The user can navigate by clicking each heading to reveal the text below it. 

 

There are a lot of events in the browser that you can bind to. If you can think of an event, it’s almost certain that you can bind to that event with jQuery. This blog introduces the following events:

  • click: Clicking an elements such as a button
  • hover: Interacting with an element via the mouse; in pure JavaScript, known as mouseenter or mouseleave
  • submit: Submitting a form
  • trigger: Making an event happen
  • off: Removing an event

 

The most popular is the click event. A lot of older tutorials you’ll see on the Web will tell you to use methods such as click() to bind code to a click event, like so:

$("div").click(function() {

alert("clicked");

});

 

This was the recommended way of doing it. However, there is an updated syntax for binding code to events. Note Functions that are bound to an event are often referred to as event handlers. Of course, the methods like click(), hover(), and so on all still work, but it’s recommended to use the current API, which features primarily just one method—on()—to do the event binding for you.

 

The issue for people picking up jQuery is that the on() method can be pretty overwhelming, especially at the beginning. Because it does the job of a lot of other methods, it comes across as fairly complex. In practice though, it’s not half as bad as you might think. Here’s code to bind a click handler pre–jQuery 1.7 compared to the 1.7 way of doing things:

$("div").click(function() {
alert("hello");
});
$("div").on("click", function() {
alert("hello");
});

 

There’s not much complexity here. Instead of using individual methods for all the different events, you pass the event name in as the first parameter and then the event handler in as the second.

 

Now for a quick history lesson. The reason for this change was simple: previously there were a huge number of methods, all focused on event binding. There were individual event handlers like click(), hover(), and so on. Then there were more methods for general event binding, such as bind(), live(), and delegate().

 

As you can imagine, this got complicated and required a lot of explaining. Those methods all still exist in jQuery, but it’s highly advised that you switch to just using on(). That’s the approach that we’re taking in this blog. on() is incredibly powerful, so much so that it will take this blog and the next to fully cover everything you need to know about events.

 

Popular Events

events

Now that you know how to bind events, it’s time to examine some of the ones I tend to use most often in day-to-day development. The most obvious is the click event, which you have already seen. This is the event you are likely going to use more than any other.

 

Another popular event is hover. Now, hover isn’t actually an event, but it’s shorthand for binding two functions at once—one to the mouseenter event, which is executed when the mouse hovers over the element in question, and one for the mouseleave event, which is when the mouse stops hovering over the element.

 

If you want to bind to a hover event, you can use the hover() method, which takes two functions:

 style="margin:0;width:923px;height:170px">$("div").hover(function() {
alert("hovered in");
}, function() {
alert("hovered out");
});
If you would rather use the new on() method, you have to use the mouseenter and mouseleave events:
$("div").on("mouseenter", function() {
alert("hovered over");
}).on("mouseleave", function() {
alert("hovered out");
});

 

By taking advantage of chaining, you can simply bind the mouseleave immediately after binding the mouseenter function.

However, there are multiple times when you will find yourself wanting to run code whenever the user hovers in or out. For example, often you might want to run some code to reset margins, stop animations, and so on. If this is the case, on() allows you to bind the same function to multiple events. Simply pass them into the on() method as a space-delimited string:

$("div").on("mouseenter mouseleave", function() { alert("hovered on or out");

});


You can bind to as many events as you want in one go:

$("div").on("mouseenter mouseleave click dblclick", function() { alert("hovered on or out, clicked or double clicked");

});

 

jQuery doesn’t care how many events you bind to; it will do it. Obviously, this is impractical and there are not many times where you’d want to do this, but it’s good to know. Sometimes you may want to run the same code on mouse on or mouse out, for example. Binding multiple events presents a question: how do you know which event triggered the function? It’s a good question—and one we’ll answer shortly in this blog.

 

Back to the subject of events, you might have noticed that the previous binding example just introduced another event: double-click, which is named dblclick. That wraps up the important mouse events that you need to know, for now. The next blog will go over a few that we are skipping for the moment. As a recap, the main mouse events you need to be aware of are:

  • click
  • mouseenter
  • mouseleave
  • dblclick

 

Another important part of jQuery’s events are the form events. jQuery makes enhancing forms using JavaScript—such as custom validation—really straightforward. For additional security, it is also important to have validation on the server side. JavaScript can help with making sure an e-mail address is formatted correctly, but it does not know what is happing in your database.

 

A large part of jQuery’s simplicity comes down to the events you’re able to hook into. The main one is submit, which is fired when a form is submitted. You don’t have to bind this event to the Submit button on a form, but to the form itself. For example, with this HTML:

<form action="/some/url" method="get">
<label>Enter your first name: </label>
<input type="text" name="first_name">
<input type="submit" name="submit" value="Submit"> </form>
you could run code when the form is submitted just by binding to the submit element on the form:
$("form").on("submit", function() {
alert("you just submitted the form!");
});

 

For dealing with events on individual inputs, the two events you will use most often are focus and blur, which are exact opposites of each other. The focus event is fired when an element has focus. The most obvious example is when the user clicks an input box or starts typing in it. At that moment, the element has focus and the focus event is fired.

 

When the user moves on, clicking another element or just off that element, the blur method is fired. Think of focus and blur as being a little like mouseenter and mouseleave in how they work. The most important difference is that focus and blur can be triggered in more ways than just via a mouse.

 

They can also be triggered via the keyboard when the user tabs through a form. Thus, for events to be fired based on an input element being active, never use mouseenter or mouseleave. Always use focus and blur.

$("input").on("focus", function() {

alert("you’re focused on an input");

}).on("blur", function() {

alert("this input just lost focus");

});

 

Interacting with the Element

Interacting with the Element

When an element fires an event, one of the things you’ll frequently need to do is perform actions with the element that’s been interacted with. Perhaps you want to hide it when it’s clicked, or slowly fade it in or out. Within the event handler, you have access to the current element through the this keyword.

 

You’ve already seen the this keyword previously in your animation callbacks, and it works the same way for events. When the event is fired, the this keyword is bound to the element that fired the event.

 

Be aware, the this keyword is not set to a jQuery object with the element in, but only to the DOM element reference. To get a jQuery reference, simply pass it into the jQuery object:

$("div").on("click", function() {

alert($(this).attr("class"));
});
If you’re going to reference the element multiple times, it’s best to get a jQuery reference to it and then save that to a variable:
$("div").on("click", function() {
var t = $(this);
alert(t.attr("class"));
});

 

In this case we are calling the variable t, but there are a few different conventions. A lot of people will go for the variable name that; others go for $this, $t, or self. It doesn’t matter what you call it really—just make sure that it’s sensible and consistent. There’s nothing worse than coming back to your code to see you’ve used different variable names in different places!

 

Triggering Events

Triggering Events

Sometimes you might want to manually trigger an event. Perhaps you’ve got a link that enables the user to fill out a form, and when it’s clicked you’d like to fire the submit event on a form. jQuery has the trigger() method to do this for us:

$("a").on("click", function() {

$("form").trigger("submit");

});

 

This can be useful in certain situations; however, if you find yourself doing it frequently, there’s a chance you might want to rethink your code. You shouldn’t be continuously triggering artificial events, but there may be times when it’s useful. For example, if you are working on some code that allows the user to click links to navigate through a set of images, it’s a good idea to make that work with the arrow buttons on a keyboard, too. Thus, you might want the navigational link to have a click event triggered when you detect that the arrow keys have been clicked.

 

Unbinding from Events

Just as you have on() for binding to events, you have off() for unbinding from events. In its simplest form, it’s used like so:

$("div").off();

 

That will unbind all events from every div. You can also pass in an event as the first parameter to unbind all events of that type. The following code unbinds all click events from paragraphs, so clicking a paragraph does nothing:

 

$("p").on("click", function() {
alert("click " + http://this.id);
});
$("p").off("click");
It’s also possible to unbind just a specific function. Looking at the following code, see if you can figure out what will happen when you click a paragraph:
$(function() {
var clickEvent = function() {
alert("clickEvent");
};
$("p").on("click", function() {
alert("click");
}).on("click", clickEvent);
$("p").off("click", clickEvent);
});

 

  • Which one of the following do you think will happen?
  • You get two alerts: one saying “clickEvent” and the other saying “click”
  • You get just one alert saying “click”
  • You get just one alert saying “clickEvent”

 

If you guessed the middle option, you’re correct. When you bind the function stored in the variable clickEvent to the event, you can unbind only that function by passing it, along with an event type, into the off() method.

 

You won’t find yourself using the off() method too frequently, but as with a lot of the things you’ve seen so far in this blog, there are places where it comes in handy. Maybe you only want to allow a button to be clicked a certain number of times, in which case you would keep count of the number of clicks and use off() when the counter reaches the maximum that you want to allow.

 

The Event Object

The Event Object

Earlier we said that binding multiple events presents a question: How do you know which event triggered the function? And now you’re going to find out the answer.

 

Whenever you bind an event to a function and that function is then triggered, jQuery passes what’s known as the event object. This object contains a lot of information about the event. To get access to this, just make your event handler take one parameter as an argument. jQuery then passes the event object into this function, and you can get at it through the argument that you denoted your function should take. For example:

$(function() {


$("p").on("click", function(event) {

console.log(event);

});

});

 

As you’ve seen, you don’t have to do this. If you’ve no interest in the event object, don’t add the argument. JavaScript doesn’t give an error if you pass a function an argument that it doesn’t accept. The event object contains a lot of properties. The results will be the same in all browsers. Here’s the output when logging it to the Google Chrome console:

 

altKey: false
attrChange: undefined
attrName: undefined
bubbles: true
button: 0
buttons: undefined
cancelable: true
clientX: 21
clientY: 54
ctrlKey: false
currentTarget: HTMLParagraphElement
data: undefined
delegateTarget: HTMLParagraphElement
eventPhase: 2
fromElement: null
handleObj: Object
isDefaultPrevented: function ba(){return!1}
jQuery18008258051469456404: true
metaKey: false
offsetX: 13
offsetY: 4
originalEvent: MouseEvent
pageX: 21
pageY: 54
relatedNode: undefined
relatedTarget: null
screenX: 21
screenY: 148
shiftKey: false
class='lazy' data-srcElement: HTMLParagraphElement
target: HTMLParagraphElement
timeStamp: 1348853095547
toElement: HTMLParagraphElement
type: "click"
view: Window
which: 1

 

There’s an awful lot of stuff there—a lot of which you won’t ever care about most of the time. In the following code, we’ve picked out a few key attributes that are worth knowing. A few of these attributes you will use, but not until a bit later in the blog.

 

Remember the question posed earlier about how to find out which event was fired? The event object contains a type property, which does just this. This is useful for binding a function to the hover event:

$(function() {

$("div").on("hover", function(event) {
if(event.type === "mouseenter") {
$(this).css("background", "blue");
} else { $(this).css("background", "red");
}
});
});

 

You know that when you bind to "hover", it will fire on mouseenter or mouseleave (“hover” is just a handy shortcut that jQuery provides), so all you have to do is a simple statement within the event handler to determine the type. The preceding code will make the div turn blue when you hover your mouse over it and red when you leave it.

 

You can use the pageX and pageY properties to get the position of the mouse when the event fired, relative to the top-left edge of the document window.

$(function() {

$("div").on("click", function(event) {

alert("Your mouse is at X " + event.pageX + " and at Y " + event.pageY); });

});

 

This will bring up an alert box whenever the link is clicked, showing the coordinates of the mouse pointer.

We’ll cover more of these properties in greater detail later. For now, it’s time to build something!

 

Building an Accordion

Building an Accordion

Up until now, the code that we’ve asked you to write has been small and typically used to show a small feature. This time, you’re going to pull together what you’ve learned in the past few blogs and build a basic accordion. Once you study events in further details in the next blog, you will visit this code again and improve it.

Start a new project and create the usual structure, an index.html file containing nothing but

<!DOCTYPE html>
<html>
<head>
<title>blog 05, Accordion</title>
<script class='lazy' data-src=jquery.js></script>
<script class='lazy' data-src=app.js></script>
<link rel=stylesheet href=style.css />
</head>
<body>
</body>
</html>
Include the jQuery source and an app.js file that simply contains
$(function() {
});

 

You’ll also need to do a bit of basic CSS styling, so add a blank style.css. You’re going to use a basic HTML structure of headings and paragraphs, contained within a div:

<div id="accordion">
<h2>Heading</h2>
<p>Thesis Scientist</p>
<h2>Heading 2</h2>
<p>Thesis Scientist.</p>
<h2>Heading 3</h2>
<p>Thesis Scientist</p>
</div>
The first thing you need to do is style your accordion. This isn’t a blog on CSS, so the details aren’t important, but all you’re doing here is adding structure to the HTML with some simple rules:
#accordion {
width: 500px;
border: 1px solid black;
}
#accordion h2 {
padding: 5px;
margin: 0;
background: #ddd;
}
#accordion p {
padding: 0 5px;
}

 

Now you need to hide all the paragraphs except for the first. This could be done with CSS, but when working on anything like this, you need to make sure the content is still accessible with JavaScript turned off. Thus, you hide the content with JavaScript, so if a user does not have it enabled, he or she can still see the content.

 

Head over to app.js, which at this point should just contain the code that binds a function to the ready event. The first thing to do is the initial setup, so get all the headers and paragraphs stored in variables:

var headings = $("h2");

var paragraphs = $("p");

 

Now you want to hide all but the first paragraph, traversing the DOM, and one of the methods briefly mentioned was not(), a filter method. You can use this to filter your selection down to all but the first paragraph, and hide the rest:

$(function() {

var headings = $("h2");

var paragraphs = $("p");

paragraphs.not(":first").hide();

});

 

Now you can work on the code to run when a header is clicked. You need to do the following:

Hide the currently visible paragraph.

Show the paragraph that’s immediately after the header that was clicked.

 

But you should only do this if the header isn’t the currently active header—else you’d be hiding and showing the same paragraph. When a header is clicked, the first thing to do is check whether the paragraph underneath it is visible. If it is, then you don’t need to do anything. That’s established like so:

headings.on("click", function() {

var t = $(this);

if(t.next().is(":visible")) {

return;

}

});

 

After binding the click handler and storing the value of $(this) in the variable t, you then need a way to access the paragraph. blog 4 covered the next() method.

 

It is used to get the element that immediately follows the current one in the DOM. Based on your HTML, this will always be the relevant paragraph to the header that was clicked. You then need to use the is() method. It is passed a selector, in this case ":visible", and will return true if the element matches the selector, and false if it does not.

 

If it does match the selector, it means that it’s visible, so all you do is return. Using the return keyword causes the function to stop execution at that point, and no further code in that function will be run. This is a great way to stop running the code if you’re running it on an element that you don’t need to.

 

If t.next().is(":visible") returns false, you know that you need to show that paragraph and hide the others. Rather than specifically hide the visible paragraph, in this instance it’s much easier to hide them all, and then show only the one you need:

$(function() {
var headings = $("h2");
var paragraphs = $("p");
paragraphs.not(":first").hide();
headings.on("click", function() {
var t = $(this);
if(t.next().is(":visible")) {
return;
}
paragraphs.hide();
t.next().show();
});
});

 

If you refresh the page and click a header, you’ll see the paragraph appear and the others vanish. You’re done!

 

Actually, you’re not quite done yet because there’s an improvement you could make. Within the click handler, you have referenced t.next() twice. It’s much neater to save t.next() to a variable and then reference that:

 

$(function() {
var headings = $("h2");
var paragraphs = $("p");
paragraphs.not(":first").hide();
headings.on("click", function() {
var t = $(this);
var tPara = t.next();
if(www.ThesisScientist.com(":visible")) {
return;
}
paragraphs.hide();
tPara.show();
});
});

 

Also, it would be nice if you had some animation here, so make the paragraphs slide in and out of view, rather than just appearing and hiding instantly. This is really straightforward—simply change the last two lines within the event handler:

 

$(function() {
var headings = $("h2");
var paragraphs = $("p");
paragraphs.not(":first").hide();
headings.on("click", function() {
var t = $(this);
var tPara = t.next();
if(www.ThesisScientist.com(":visible")) {
return;
}
paragraphs.slideUp("normal");
tPara.slideDown("normal");
});
});

Now you should get some nicely animated paragraphs as you click the headers. Congratulations, you’ve just built your first jQuery accordion!

 

More Events

events

At the end of the last blog, you ended up with a respectable accordion, which was the first major bit of jQuery you’d embarked on. You might not have realized it, but the code you wrote could be tidied up. In this blog, you’ll round out your knowledge of events by looking at the more advanced features, including:

  • Event delegation
  • Event propagation
  • Preventing default behavior
  • Creating your own events

 

Once you’ve covered these features, you’ll take another look at your accordion and do some refactoring to improve the code and make it more efficient. 

 

Event Delegation

Event Delegation

Imagine that you have 100 paragraphs on a page and you want something to happen every time a user clicks one of them. Knowing what you do about jQuery, you would probably, quite reasonably, write something like this:

$("p").on("click", function() {

//do something here

});

 

And this would work fine, but it is terribly inefficient. Why? This code makes the browser loop over every single paragraph individually and bind an event handler to it. That means it has to bind 100 individual event handlers 100 times to 100 individual paragraphs. When you’re writing code and you notice that a browser has to do something multiple times in quick succession, it’s time to start thinking about whether there’s a nicer way to write the code to avoid it.

 

There’s also another problem with this code. Imagine that the user can add new content on your page, which is then saved to your system through some back-end system. This means the user might be adding new paragraphs to the page, and you still want the event handler that you bound earlier to work on these newly inserted paragraphs. In the following code, if you insert a new paragraph to the DOM and click it, will you see the alert box?

$("p").on("click", function() {

alert("Hello World");

});

// insert new paragraph code here

 

The answer is no, clicking the new paragraph will not show the alert. Consider why this might be. When you run $("p"), it selects all current paragraph elements on the page. This is not a “live” selector that updates whenever you insert new content. It selects elements in the DOM at the time but does not update itself. So there are now have two problems to solve:

 

  • \1.\ How can you run a function whenever a paragraph is clicked, but still bind it efficiently when there are a lot of paragraphs?
  • \2.\ How can you make it so any new paragraphs inserted into the DOM also run the code when they are clicked?

 

The answer, as you might have guessed, is event delegation. It means that instead of binding the event to each paragraph individually, you bind the event to a parent element of all the paragraphs and let it delegate the event to the paragraph. This sounds more complicated than it is. Here’s an explanation of how it works:

 

  • The click event is bound to a parent element of all your paragraphs (keep it simple and use the body element for this example).
  • When the body element detects an event of the type you bound (click, in this case), it checks to see if the click happened on a paragraph.
  • If the click happened, the body element fires. This is where the delegation happens.

 

The major advantage of binding this way is that you have to bind one handler to one element and do it just once. That’s a lot better than doing it 100 times over, as you did previously. And because this event is bound to a parent element, and not to the paragraphs, this will work for any new paragraphs you insert!

 

Let’s look at how you do this in practice. As always, set up an index.html file that includes jQuery and a blank app.js. You can add some styling if you like, but that’s optional. Within the body, add paragraphs.

<!DOCTYPE html>
<html>
<head>
<title>blog 06, Exercise 01</title>
<script class='lazy' data-src=jquery.js></script>
<script class='lazy' data-src=app.js></script>
</head>
<body>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
<p>Paragraph 3</p>
<p>Paragraph 4</p>
<p>Paragraph 5</p>
</body>
</html>
Head into app.js and add this:
$(function() {
$("p").on("click", function() {
alert("Hello World");
});
$("<p />", {
text: "Paragraph 6"
}).appendTo("body");
});

 

Notice how you bind the click event as you did in the previous blog, and then insert a new paragraph. If you open index.html in a browser and click the paragraphs, you’ll see that the first five (the ones that existed initially) all give you the alert, but clicking the sixth (the paragraph you inserted after binding the click event) doesn’t give you the alert. Use event delegation to fix that. The change is brilliantly simple:

$(function() {
$("body").on("click", "p", function() {
alert("Hello World");
});
$("<p />", {
text: "Paragraph 6"
}).appendTo("body");
});
The key (and in fact, only) change is this line:
$("body").on("click", "p", function() {...});
Previously, when you used the on() method, you used it in the form:
$(selector).on(event, function() {...});
When used in this form, the event is bound to the selector; however, you can also use it in this form:
$(selector).on(event, delegateSelector, function() {...});

 

When it is used in this form, the event is still bound to the selector, but it will delegate it to any elements that match the delegateSelector that are children of it. If you make that change and refresh the page in the browser, clicking the sixth paragraph will work as desired and display the alert.

 

What you’re doing is binding the click event to the body and telling it to delegate it to all paragraphs within it. This means it doesn’t care when the paragraph first existed—as long as it’s within the body and is clicked, the event will fire.

 

You shouldn’t go over the top when it comes to deciding when to delegate. The key rule here is common sense: if you’re binding an event to just one element, there’s no point in delegating because you don’t gain anything. The rule we recommend is not to delegate when you’re binding to more than a few elements, perhaps anything over five.

 

In reality, it’s not going to matter much if you don’t delegate. The performance gains are minimal for a small number of links, but it’s still something you should do. Even if the optimizations you make are small, they are still worth making.

 

Event Propagation

Event Propagation

Event propagation is something that can cause people new to JavaScript events a bit of trouble, so it’s best explained with an example before diving into it. Imagine that you have a div, and within that div, there’s a heading. You want to run one piece of code when the div is clicked, and another when the heading is clicked. Easy, right? Take a look. As always, you’ve got your index.html:

<!DOCTYPE html>
<html>
<head>
<title>blog 06, Exercise 02</title>
<script class='lazy' data-src=jquery.js></script>
<script class='lazy' data-src=app.js></script>
<link rel=stylesheet href=style.css />
</head>
<body>
<div>
<h5>Hello</h5>
</div>
</body>
</html>
Here’s your app.js:
$(function() {
});
Also include the jQuery source in index.html and add a quick bit of CSS in style.css to style things so that it’s easier to see what’s going on:
div {
width: 500px;
height: 500px;
background: red;
padding: 20px;
}
h5 {
border: 1px solid black;
background: white;
width: 300px;
font-size: 20px;
top: 20px;
}

 

This gives you a very basic page. Now write the code to alert a message when you click the header, and alert a different message when you click the div:

$(function() {
$("h5").on("click", function() {
alert("header");
});
$("div").on("click", function() {
alert("div");
});
});

 

Refresh your browser and click the div. You’ll see the correct alert, "div". Now click the header. You will see two alerts pop up—one with the text “header” and the other with "div". What just happened there? You guessed it: you just saw event propagation in action. You may have heard of the phrase event bubbling. Event propagation is the same as event bubbling; they are simply two terms meaning the same thing.

 

When an event is fired on an element in the browser, it’s not just fired on that element, but every element that is a parent of it. When you click the heading in your example, you also click the div. The heading is within the div, which means you didn’t just click the headingbut the div, too. You also registered a click event on the parent of the div, which in this case is the body.

 

That’s why you get two alert boxes when you click the heading—because you’ve also clicked the div. While most events (including the ones you’ll work with most often) propagate, not all of them do. The Wikipedia page on DOM events (DOM events - Wikipedia) has a handy table showing all DOM events and whether they propagate.

 

When Should I Worry About Event Propagation

Event Propagation

Typically, the only time you need to worry about event propagation is when you are binding an event to both an element and the element’s parent. Most of the time, event propagation is something you won’t have to worry about—demonstrated by the fact that it was not mentioned in the first five blogs of the blog. If it caused the average developer more issues, it would have come up much earlier.

 

Luckily, there is a way to stop event propagation. You’ll recall that earlier you learned information about an event by passing an event object into your event handler functions, like so:

$("div").on("click", function(event) {...});

 

While the event object contains a lot of information about the event, it also contains methods that you can use. One of those methods is stopPropagation(). Here’s what the jQuery API (http://api.jquery. com/event.stopPropagation/) has to say about it: “Prevents the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event.”

 

Therefore, you can pass in an event object, call stopPropagation(), and solve the issue you have with your code. Look at the change required:

 

$(function() {
$("h5").on("click", function(event) {
alert("header");
event.stopPropagation();
});
$("div").on("click", function() {
alert("div");
});
});

 

When you click the header now, you will only see one alert that contains the text “header”, as desired. Just because you can do something, doesn’t mean you should—and that rings true with preventing propagation. Unless the propagation of an event is causing an issue, don’t prevent it. In practice, propagation rarely causes any problems.

 

Preventing Default Behavior

Default Behavior

Sometimes when you bind to an event, you need to stop the browser from performing the default action attached to that event. For example, when an anchor element is clicked, the default browser behavior is to follow that link.

 

Sometimes you’re going to want to override this. Perhaps you want the link to appear in a pop-up window, so decide to bind to the event and implement your pop-up code. Let’s see how you might do this. Here’s an index.html file with a link in it:

<!DOCTYPE html>

<html>
<head>
<title>blog 06, Exercise 04</title>
<script class='lazy' data-src=jquery.js></script>
<script class='lazy' data-src=app.js></script>
<link rel=stylesheet href=style.css />
</head>
<body>
<div>
<a href=Thesis>Thesis</a>
</div>
</body>
</html>

 

That link is within a div, and as a simplified example, suppose that when the link is clicked, you’d like the background of the div to change to blue, and then nothing more to happen. The first attempt at this would probably look like so:

$(function() {

$("a").on("click", function() {

$("div").css("background", "blue");

});

});

 

If you try that in your browser, you will see the div turn blue for a split second before you are taken to the Thesis web site. So, while it is executing your code, it’s then immediately whisking the user off to another site, rendering your code useless.

 

On the event object, along with stopPropagation(), there’s also preventDefault(), and you can probably figure out what that one does. You use it just as you did with stopPropagation()—pass in an event object to the event handler and then call preventDefault() on that object:

$(function() {

$("a").on("click", function(event) {

event.preventDefault();

$("div").css("background", "blue");

});

});

 

It’s important to note that it does not matter where in the event handler you call preventDefault() (and the same goes for stopPropagation()). Some people like to call it at the end, some at the beginning, and some in the middle. Usually it is put and the end, and we can explain why.

 

If you call preventDefault() at the very start of the event handler, it immediately prevents the browser’s default action from occurring. If some other code in the event handler causes an error, two things have happened:

  • The default browser event didn’t fire because you called preventDefault() on the very first line.
  • The JavaScript you bound to the event didn’t fire because there was an error.
  • Now imagine that you had the call to preventDefault() at the end, and some of your JavaScript in the event handler function errored.

 

  • Your JavaScript wouldn’t fire because there was an error.
  • That error would mean that preventDefault() wasn’t called, so the browser’s default behavior would happen.

 

Having the browser’s default behavior happen when your JavaScript errors is usually a good thing—it doesn’t leave the user’s browser totally broken (or at least, it won’t seem that way to them).

 

A Note on return false; In a lot of tutorials, you will see the use of return false; in handlers:

$(function() {

$("a").on("click", function() {

$("div").css("background", "blue");

return false;

});

});

 

Making a handler return Boolean false has the effect of stopping the default event action from being called and stopping the event from propagating. In essence, it effectively is a shortcut for calling stopPropagation() and preventDefault(). As previously explained, most of the time you actually don’t want to call stopPropagation(), so we strongly advise avoiding return false, and instead use preventDefault().

 

Your Own Events

Your Own Events

A seldom-used but very useful feature of jQuery’s events is the ability to trigger and bind to your own custom events. You might be reading this thinking, “Why?”. This section explains why you would want to use this feature.

 

When you have a complex web site with multiple events firing, your code can get a bit messy. Suppose that when the user clicked a button, you had to perform a few distinct actions. Perhaps you had to update the title, change the background color, and a few other things. You could add all of these within the one event handler for the click event for this button, but soon things will get messy.

 

And then you realize that one of these bits of functionality—perhaps changing the background color—needs to happen either when the user clicks the button or hovers over a certain element.

 

From here, your code is going to get messy, quickly. You can’t copy and paste the same code into two different event handlers because that would be sloppy. You could pull the code into a function, which wouldn’t be too bad. Or, you could create a custom event—and have the best of both worlds. If you’re still not convinced, just hold tight and hopefully the following example will win you over.

 

As always, create a new folder for this example, which has an index.html:

<!DOCTYPE html>

<html>
<head>
<title>blog 06, Exercise 05</title>
<script class='lazy' data-src=jquery.js></script>
<script class='lazy' data-src=app.js></script>
<link rel=stylesheet type=text/css href=style.css />
</head>
<body>
<div>
<h5>Click Me</h5>
<h5>Or Me</h5>
</div>
</body>
</html>
Create an empty app.js file and a style sheet to which you can add basic styling:
div {
width: 500px;
height: 500px;
background-color: red;
padding: 20px;
}
h5 {
border: 1px solid black;
background: white;
width: 300px;
font-size: 20px;
top: 20px;
}

 

It’s also got a local copy of the jQuery source. After that quick bit of CSS styling.

Now imagine that you need to change the background color of the div whenever either of those headers is clicked. The following triggers a custom event when those headers are clicked:

 

$(function() {
$("h5").on("click", function() {
$("div").trigger("bgchange");
});
});
An event has to be triggered on an element, so this triggers it on the div. Now you can bind a function to that event just as you would for any other event:
$(function() {
$("h5").on("click", function() {
$("div").trigger("bgchange");
});
$("div").on("bgchange", function() {
var t = $(this);
t.css("background-color", "blue");
});
});

 

The beauty of custom events is that they give you a neat way to package up your code and keep as much of it separate as possible. If your code can be purely event driven, that’s a good thing. Rather than having lots of code interacting with other functions, simply trigger and bind to custom events. It will make your life easier. It also allows you to assign meaningful names to the events you create, keeping your code easy to follow and maintain.

 

The Accordion Take 2

The Accordion Take 2

Building on the knowledge covered in this blog, it’s time to revisit the JavaScript you wrote for the accordion and see if you can improve it. Here it is again:

$(function() {

var headings = $("h2");
var paragraphs = $("p");
paragraphs.not(":first").hide();
headings.on("click", function() {
var t = $(this);
var tPara = t.next();
if(www.ThesisScientist.com(":visible")) {
return;
}
paragraphs.slideUp("normal");
tPara.slideDown("normal");
});
});
Seeing as this accordion could grow into many more headings than the three you have right now, switch from binding directly to the click event on the heading to delegating:
$(function() {
var accordion = $("#accordion");
var headings = $("h2");
var paragraphs = $("p");
paragraphs.not(":first").hide();
accordion.on("click", "h2", function() {
var t = $(this);
var tPara = t.next();
if(www.ThesisScientist.com(":visible")) {
return;
}
paragraphs.slideUp("normal");
tPara.slideDown("normal");
});
});

 

Note the line added to save the accordion to a variable. Although you only reference it once, it’s something you could easily find yourself referencing again, so there’s not much harm done in saving it to a variable. You then switch the line that binds the event to use the delegation syntax covered in this blog.

 

That still works great, but clicking the header might not be the only way to show a particular paragraph. Next, you’re going to make the paragraphs slide down when a custom event is triggered on them, and then make clicking the header trigger that event.

Write the code that will make the paragraph slide down when it detects the event:

 

accordion.on("showParagraph", "p", function() { paragraphs.slideUp("normal"); $(this).slideDown("normal");
});
Again, you use delegation for this, just as you did with the headings. Remember, custom events are handled just like regular events.
You can then rewrite the event handler for clicking the headings like so:
accordion.on("click", "h2", function() {
var t = $(this);
var tPara = t.next();
if(!www.ThesisScientist.com(":visible")) {
tPara.trigger("showParagraph");
}
});

 

You’ll notice this is now far simpler and easier to read. Within the click handler, check to see if !tPara. is(":visible") is true (note the exclamation mark at the beginning), and if it is, you then trigger the showParagraph event on the paragraph that you need to show. That leaves your entire code looking like the following:

$(function() {
var accordion = $("#accordion");
var headings = $("h2");
var paragraphs = $("p");
paragraphs.not(":first").hide();
accordion.on("click", "h2", function() {
var t = $(this);
var tPara = t.next();
if(!www.ThesisScientist.com(":visible")) {
tPara.trigger("showParagraph");
}
});
accordion.on("showParagraph", "p", function() { paragraphs.slideUp("normal"); $(this).slideDown("normal");
});
});

 

This might not seem easier. In fact, your code is a bit easier, but it’s also more efficient, thanks to delegation, and it is easy to add other ways to trigger the correct paragraph sliding down. If you wanted to trigger a paragraph sliding down from another method, all you have to do is trigger the event on it. It’s simple and extensible.

 

Animation

animation

The subject of animation has been broached multiple times in this blog so far, but only the very basic bits. You’ve done some fading and sliding, but nothing more—until now. jQuery has a fully-featured animation library, but it comes with its quirks: sometimes things don’t entirely happen as you might expect. This blog will cover those “gotchas” and a lot more, including:

 

  • jQuery’s animate() method, which allows you to animate a large number of properties.
  • More jQuery convenience methods, such as fadeOut(), slideIn(), and so on.
  • jQuery’s animation queue, which dictates how and when animations are run. They are not always run as you might expect.
  • Common mistakes beginners make with animation—and how to avoid these mistakes.
  • Enhancing the animation on your accordion so that it is less buggy.

 

The infamous jQuery project: an image slider.

This is going to be a fairly heavy blog. The image slider will combine everything you’ve learned so far. Let’s get started!

 

The animate( ) Method

The animate() method can be used to animate a number of properties on an element over a period of time.

 

Basic Usage

The jQuery API has a succinct explanation of which properties can be animated:

All animated properties should be animated to a single numeric value, except as noted; most properties that are non-numeric cannot be animated using basic jQuery functionality (for example, width, height, or left can be animated but background-color cannot be, unless the jQuery.Color plugin is used).

 

Property values are treated as a number of pixels unless otherwise specified. The units em and % can be specified where applicable. (jQuery API Documentation)

 

Plug-ins are available to animate complex properties such as color, but you’ll usually animate something that has a distinct value, such as a width or height. The basic use of animate() has a very similar syntax to the css() method:

animate({

property: value,

property2: value2

}, 500);

 

More often than not, that’s the form you’ll use with the method. It takes an object of key-value pairs that relate properties to values.

The second argument is the duration in time. As with the convenience methods, this can be fast, normal, or slow—which are shortcuts for 200, 400, and 600 milliseconds, respectively. Otherwise, specify a value in milliseconds. If you don’t specify a duration, 400 seconds (or “normal”) is used as the default.

 

You are also able to pass in another argument, a callback function, which you have already used a few times in this blog. When working with animations, a callback function is a function that is executed once the animation ends.

You might be wondering why you can’t simply call animate() and then run some code like this:

$("div").animate({ "height": 50 }, 500);

$("p").text("animation finished");

 

The reasoning behind this comes down to how jQuery deals with animation. It doesn’t just run the animation and then move to the next line. It gets the animation started, and then moves to the next line, while the animation is in progress—hence the need for using callbacks. You will examine this in more detail shortly. Adding a callback function is really simple:

animate({
property: value,
property2: value2
}, 500, function() {
console.log("finished");
});

 

Within the callback function, the value of this will refer to the DOM element that has just been animated. If you wanted to use jQuery methods on that object, you’d simply pass it through to jQuery, as follows: $(this).

 

Easing

easing

Animations in jQuery support easing functions, which specify the speed that the animation should run at different points within an animation. For example, you might choose to have the animation move quickly for all but the last few moments, where you might slow it down so that it eases into its final position.

 

By default, jQuery supports just two easing methods: linear and swing. Swing is the default, so if you don’t name a particular easing method, swing will be used.

 

This graph was taken from James Padolsey’s jQuery Easing; Illustrated (https://j11y.io/demos/ jquery/easing/), which is a great web site for viewing all the different easing effects. You can see that there is a subtle difference between the two: linear goes at a constant rate, while swing starts slowly and then speeds up before easing down again.

 

Further easing effects are built into the jQuery UI (user interface) project (jQuery UI). The jQuery UI project is a set of jQuery plug-ins for common UI components, but it also contains extra jQuery add-ons, including a set of easing effects. Later, you will use some of the extra easing functions, but for now, look at how you can animate with the linear function, rather than the default swing:

 

$("div").animate({
"height": 500
}, 500, "linear");
The easing method to use simply goes as the third argument into the animate() function. This is demonstrated by the jQuery documentation for animate():
animate( properties [, duration] [, easing] [, complete] )

 

This shows that the animate() method can take up to four arguments. The arguments in square brackets are optional. jQuery is clever enough to figure out the arguments that you pass in and those that you don’t. If you want to pass in a callback function, as well as the easing function to use, simply add it as the last argument:

$("div").animate({

"height": 500

}, 500, "linear", function() {

console.log("finished!");

});

 

Passing in Two Objects

Things can get a little confusing when passing in so many arguments, so you can pass in a second object for the other arguments:

$("div").animate({
"height": 500
}, {
"duration": 500,
"easing": "linear",
"complete": function() { console.log("finished!"); } });

 

Within the second object, there’s no need to define the arguments in any order. Passing them in by name makes it a bit easier to see what’s going on. Some prefer this, others don’t. It tends to be a personal preference.

 

Animation Shortcuts

Animation Shortcuts

The majority of the time, you’ll be animating a value—height, width, opacity, or otherwise. I most commonly animate to a specific height, perhaps to show extra information when a user hovers over a link or when making content slide in for visual effect:

$("div").animate({ "height": 300 }, 500);

 

Often, however, you’ll commonly be animating to a height relative to the element’s original height. You might need to expand an element to show more text, or if the element contains a text area to allow input, the user might need to expand the text box to accommodate the message. jQuery lets you animate like this:

$("div").animate({ "height": "+=200px" }, 500);

 

This animates the div to 200 pixels more than it started with. You can also use "-=" to animate it to 200 less than it started with.

Of course, you’re not limited to pixel animation. jQuery presumes that you’re animating by pixels, but you can animate by ems, percentage, or any other valid unit. Just specify it, like so:

$("div").animate({ "height" : "+=10%" }, 500);

 

Now that you’re more comfortable with animation methods, take a look at some of the more popular convenience methods and what they animate.

 

More Convenience Methods

You’ve met the most common convenience methods, but before you dive further into animation, it’s a good idea to review them—so that you know what they actually do and that you’re comfortable using them. This shouldn’t take long because most convenience methods follow the same pattern and they can be called in two ways:

  • methodName(duration, callback);
  • methodName(duration, easing, callback);

 

All three arguments are optional. All the upcoming methods follow this pattern, unless mentioned otherwise.

Note Convenience methods also set the element’s display property. For example, when fadeOut() runs, it animates the opacity down to 0 and then sets the display property to "none". However animate() does not do this. It will only animate what you ask it to, and nothing more.

 

Fading

fading

The fade methods are used to fade elements by animating their opacity property. They are as follows:

  • fadeIn();
  • fadeOut();
  • fadeToggle();
  • fadeTo()

 

The only method that varies is fadeTo(). It takes an extra required parameter, which is the opacity value to fade an element. This is given as a number between 0 (transparent) and 1 (fully opaque). Because the opacity is the second argument, this means that the duration must be also provided:

$("div").fadeTo(500, 0.5);

This is a bit unintuitive and may trip you up a couple of times, but you’ll soon get used to it.

 

Sliding

sliding

The slide methods mirror the fade methods, with the one omission being no matching “slideTo” method. This is because it’s incredibly rare that you would want to animate an element to a height that’s not either 0 or its initial height. Remember, if none of the convenience methods do exactly what you need, simply use animate(). These methods are only handy wrappers around the animate() method in order to provide shortcuts to common functionality.

  • slideUp();
  • slideDown();
  • slideToggle();

 

Note If you ever find yourself checking whether an element is visible or not before sliding/fading it in, use the toggle methods to save yourself some work.

 

Sliding and Fading

jQuery also has three lesser-used methods to show and hide elements:

  • show()
  • hide()
  • toggle()

 

Called without any arguments, they show or hide an element instantly. However, if you pass in any arguments, they turn into animations that animate the width, height, and opacity simultaneously. The methods take the same arguments as the slide and fade methods: either a duration and a callback, or a duration, an easing, and a callback.

 

The Animation Queue

When you run multiple animations on a single element, they are not all run at the same time but are added to jQuery’s animation queue. You can see this in action through an example.

Create a new index.html file and fill it with the following:

<!DOCTYPE html>

<html>
<head>
<title>blog 07, Exercise 01</title>
<script class='lazy' data-src=jquery.js></script>
<script class='lazy' data-src=app.js></script>
<link rel=stylesheet type=text/css href=style.css />
</head>
<body>
<div id=box>
box!
</div>
</body>
</html>
Style the div in order to make it easier to see what’s going on:
#box {
width: 500px;
height: 500px;
background: blue;
}
Then add the following code to app.js:
$(function() {
$("div")
.animate({ "height" : 300 })
.fadeOut()
.show(500)
.animate({ "width" : 100 })
.css("background", "red");
});

 

Open index.html in a browser. You will see the background of the div change to red before the animations finish. This is because jQuery queues animations to run one after the other.

 

The browser only has one thread, which means it can only run one bit of code at a time. Applications with multiple threads are able to run multiple chunks of code at different times, meaning they can do more than one thing at once. Being multithreaded allows tasks to be executed asynchronously, rather than synchronously.

 

In the browser, this isn’t possible. If a bit of code runs for a long time, the user isn’t able to use the browser because that bit of code runs and blocks the thread. To get around this, jQuery does some workarounds with its animations to ensure that they are nonblocking so that the user is able to interact with the page while the animations run. When you call animate()—or any method that calls animate()—that animation is added to a queue.

 

This queue is a first in/first out (FIFO) queue, which means animations are added to the queue and then run in the order they were added. Once an animation ends, it will trigger the next animation in the queue, if it exists. This is why the div’s background changes to red very quickly. The first animation starts to run, and then all the others are added to the queue, meaning that the call to the css() method actually happens almost as soon as the first animation starts.

 

jQuery performs animation through a series of setTimeout() calls. setTimeout() is a JavaScript method that runs code after a defined time interval. When you run code to animate a div’s opacity from 1 to 0, it actually makes a large number of very small changes to the opacity over time to emulate an animation. There’s no actual fading occurring. It’s just very quickly changing the opacity by a small amount to give the illusion of animation.

 

A Common Problem

risk

A common issue with this queue is the build-up of animations. Next, you’ll build some code that will animate a div every time a header is clicked so that you can see this in action.

 

Create an index.html file with the following:

<!DOCTYPE html>
<html>
<head>
<title>blog 07, Exercise 02</title>
<script class='lazy' data-src=jquery.js></script>
<script class='lazy' data-src=app.js></script>
<link rel=stylesheet type=text/css href=style.css />
</head>
<body>
<h5>Animate</h5>
<div id=box>
box!
</div>
</body>
</html>
Add some styling in style.css:
#box {
width: 500px;
height: 500px;
background: blue;
}
And JavaScript in app.js:
$(function() {
$("h5").on("click", function() {
$("div").fadeToggle(500);
});
});

 

If you run this and click the heading, you will see the div fade out. Another click will fade it back in. Now try clicking the heading multiple times—really quickly. You’ll see the queue in action. The animations will build up, and when you stop clicking, the animations still run, creating the lag effect. If you want to avoid this effect, you need a way of clearing the queue every time a new animation is run. Thankfully, the jQuery developers thought of this, too, and provided the stop() method (jQuery API Documentation).

 

The stop() method lets you clear all queued animations. To avoid a buildup, you can clear the current queue before adding a new animation to it, meaning you’ll never be in a situation where you have the lag effect.

First, try changing your app.js file to the following:

$(function() {

$("h5").on("click", function() {

$("div").stop().fadeToggle(500);

});

});

 

Try clicking the header multiple times. This doesn’t quite do what you want. As the jQuery API explains:

“When .stop() is called on an element, the currently-running animation (if any) is immediately stopped.”

 

If you click it lots of times, the div stops being animated at a random point in the animation. When stop() is called with no arguments, the current animation immediately stops and the next one in the queue starts. Chances are that you’ll want to finish the current animation before clearing the queue.

 

As of jQuery 1.7, the stop() method takes three optional arguments: queue, clearQueue, and jumpToEnd, the last two of which are Boolean values defaulting to false.

 

The first argument is a string that represents the name of the queue that holds the animations you want to stop. If you pass in true as the second argument, jQuery will clear the entire animation queue, getting rid of the backlog. If you pass in true for the third argument, jQuery will immediately skip to the end of the animation. So if the div is midway through fading in when stop() is called with true as the second argument, the div will immediately become fully faded in.

 

You need a combination of both of these:

$(function() {

$("h5").on("click", function() {

$("div").stop(true, true).fadeToggle(500);

});

});

 

Run the page in a browser and click frequently. As you click, you clear the queue and end the current animation, leading to no buildup and thus preventing the lag. In any complex sliding functionality, you’ll most likely use stop(true, true).

 

This is also why callbacks are so important in animations—they are the only way to be certain that the code within them will only run when an animation is complete. When you call stop() with no arguments, the callback function is not called. A callback function is only called when an animation has finished.

 

If you call stop with the second argument as true—making the animation complete immediately when stop() is called, the callback is called because the animation finished, even if it had to do it quicker than expected.

 

A similar method to stop() is finish(). Added in jQuery 1.9, it finishes all currently running animations and removes everything in the queue. The one big difference between the methods is that whereas stop() sets the values of the current animation and removes everything that is in queue, finish() sets all the properties of the queued animations to the end values.

 

Fixing Your Accordion

fix

The preceding problem is one your accordion suffers from. Here’s the index.html:

<!DOCTYPE html>

<html>
<head>
<title>blog 06, Accordion</title>
<script class='lazy' data-src=jquery.js></script>
<script class='lazy' data-src=app.js></script>
<link rel=stylesheet type=text/css href=style.css />
</head>
<body>
<div id=accordion>
<h2>Heading</h2>
<p>Thesis Scientist.</p>
<h2>Heading 2</h2>
<p>Thesis Scientist.</p>
<h2>Heading 3</h2>
<p>Thesis Scientist 3.</p>
</div>
</body>
</html>
And some quick styling in style.css:
#accordion {
width: 500px;
border: 1px solid black;
}
#accordion h2 {
padding: 5px;
margin: 0;
background: #ddd;
}
#accordion p {
padding: 0 5px;
}
And here’s the JavaScript guilty of the lag problem:
$(function() {
var accordion = $("#accordion");
var headings = $("h2");
var paragraphs = $("p");
paragraphs.not(":first").hide();
accordion.on("click", "h2", function() {
var t = $(this);
var tPara = t.next();
if(!www.ThesisScientist.com(":visible")) {
tPara.trigger("showParagraph");
}
});
accordion.on("showParagraph", "p", function() { paragraphs.slideUp("normal"); $(this).slideDown("normal");
});
});

 

Due to the gaps between the headers, it’s actually very difficult to click enough times to cause any huge lag because you don’t animate a paragraph that’s already visible. This means that it’s tough to click different headings to cause the lag, because clicking the same heading doesn’t do anything once that section is visible. Fix any possibility of the “lag” effect by changing the function bound to the showParagraph event you trigger:

accordion.on("showParagraph", "p", function() { paragraphs.stop(true, true).slideUp("normal"); $(this).stop(true, true).slideDown("normal");

});

 

Next, try out a couple of the different easing options. As explained earlier, most of these exist in jQuery UI, so you need to include it to gain access to them. However, it would be a huge waste of space to include the entire jQuery UI library just for its easing functions. Thankfully, the web site lets you put together a custom build with only the bits that you need (Download Builder).

 

The only thing you need to tick is Effects Core. Don’t worry about ticking any other boxes or filling out the Theme at the bottom of the page. Once you click the Download button, you get a zip file. Once you extract the zip file. The file you need (as of this writing) is called jquery-ui-1.12.1.custom.min.zip. Unzip the file and use the “jquery-ui-min.js”. This is the custom build, but minified for you. Copy it into your project folder and rename it to something shorter, such as simply jqueryui.js.

 

Go into index.html and edit the top, adding a link to include jquery-ui-min.js file. Make sure you do so after the jQuery source because jQuery UI, unsurprisingly, depends on jQuery:

 

<script class='lazy' data-src="jquery.js"></script>


<script class='lazy' data-src="jqueryui.js"></script>

<script class='lazy' data-src="app.js"></script>

<link rel="stylesheet" type="text/css" href="style.css" />

 

To view all the easing options now available to you, check out the jQuery UI documentation page (jQuery UI API Documentation). Give "easeInBack" a go:

accordion.on("showParagraph", "p", function() { paragraphs.stop(true, true).slideUp("normal", "easeInBack"); $(this).stop(true, true).slideDown("normal", "easeInBack");
});
Feel free to try out a few and find your favorite. In the end, we settled on "easeInCirc":
accordion.on("showParagraph", "p", function() { paragraphs.stop(true, true).slideUp(1000, "easeInCirc"); $(this).stop(true, true).slideDown(1000, "easeInCirc");
});

 

This code has a lot of duplication. The two lines look very similar, and if you want to change the duration or easing, you have to change it on two lines. That’s never a good sign. It’s times like this that you should look to abstract out into a utility function:

var animateAccordion = function(elem, duration, easing) { paragraphs.stop(true, true).slideUp(duration, easing); $(elem).stop(true, true).slideDown(duration, easing);

}

 

The function takes three arguments: elem, which refers to the element you want to slide down, and duration and easing. It then does the same animations as before, sliding all the paragraphs up and the active element down. This really tidies the event handler:

accordion.on("showParagraph", "p", function() { animateAccordion(this, 600, "easeInCirc");

});

It might not look like much of a change, but making things as easy as possible is really important in our opinion. We tend to refer to these small functions as “utility functions.” They save a lot of time and typing in the long run—and you’ll almost certainly find uses for them in future projects.

 

The Image Slider

Image Slider

It’s time to take a first pass at building an image slider. Before you dive into the code, you need to think about how you need it to work. You’ll also have to do a little CSS work before you can get into the JavaScript. The list of images will be represented as an unordered list. You then style the images so that they are laid out horizontally.

 

You make the list as wide as it needs to be to accommodate them all. Next, this unordered list sits within a div that is only as wide as one image and has its overflow property set to hidden. This way, only the current image is shown. You then manipulate the margin of the unordered list to animate the images.

 

It’s all pretty straightforward. It just sounds worse than it is!

Start with the initial setup. Create a new directory and drop the jQuery source file in. You’ll also want index.html, style.css, and app.js files.

Add the following to your index.html:

<!DOCTYPE html>
<html>
<head>
<title>blog 07 Slider</title>
<script class='lazy' data-src=jquery.js></script>
<script class='lazy' data-src=app.js></script>
<link rel=stylesheet type=text/css href=style.css />
</head>
<body>
<div id=slider>
<ul>
<li><img class='lazy' data-src="

" alt="Random Image" /></li> <li><img class='lazy' data-src="

" alt="Random Image" /></li> <li><img class='lazy' data-src="

" alt="Random Image" /></li> <li><img class='lazy' data-src=" https://unsplash.it/300/300/?random " alt="Random Image" /></li> <li><img class='lazy' data-src="

" alt="Random Image" /></li>
</ul>
</div>
</body>
</html>

 

We’re using the rather awesome Unsplash It web site (Lorem Picsum) to provide the placeholder images. They are represented within list items. You need to style these, so add the following to your style.css:

 

#slider {
width: 300px;
height: 300px;
}
#slider ul {
list-style: none;
width: 1500px;
height: 300px;
margin: 0;
padding: 0;
}
#slider li {
float: left;
width: 300px;
height: 300px;
}
If you look at the page, you should see all of your images in a line going off the page.
You can fix that by adding overflow: hidden; to the #slider div, as shown next. When you do that, you get just one random image, as expected.
#slider {
width: 300px;
overflow: hidden;
height: 400px;
}
Finally, add buttons to let the user navigate forward and back through your slider. Add these just after the closing tag for the unordered list:
<span class="button back">Back</span>
<span class="button next">Next</span>
And then style them:
.button {
font-family: Arial, sans-serif;
font-size: 14px;
display: block;
padding: 6px;
border: 1px solid #ccc;
margin: 10px 0 0 0;
}
.back {
float: left;
}
.next {
float: right;
}
Also give the body a bit of padding, just to move the slider in slightly so that it’s easier to see:
body {
padding: 50px;
}

 

All that styling leaves you ready to JavaScript. 

Whenever you’re tackling a problem that’s fairly complex, consider listing out all the functionality that you need, and then implement it, one bit at a time. Here’s what you need to do:

  • When the Back button is clicked, animate the unordered list to increase its margin by 300 pixels (the width of one image).
  • When the Next button is clicked, animate the unordered list to decrease the margin by 300 pixels.
  • Disable the Back button if at the first image.
  • Disable the Next button if at the last image.

 

Once you’ve done all that, you will look at adding functionality that is more complex. The preceding is plenty to keep you going, however. So, let’s get started!

 

First, store some variables that you’ll no doubt have to refer to:

$(function() {

var sliderWrapper = $("#slider");

var sliderList = sliderWrapper.children("ul"); var sliderItems = sliderList.children("li"); var buttons = sliderWrapper.children(".button");

});

 

Next, make a function that will animate your slider. It will take two arguments: the direction in which to animate, which is either "+" or "-", and the duration the animation should take. Here’s an example:

var animateSlider = function(direction, duration) { if(direction === "+") {
sliderList.animate({
"margin-left" : "+=300px"
}, duration);
} else { sliderList.animate({
"margin-left" : "-=300px"
}, duration);
}
};

 

It’s a pretty simple function that just animates the margin up or down by 300 pixels, depending on whether the direction argument is "+" or "-". There’s certainly room for some refactoring here—there’s a lot of duplicated code—but the focus now is to get a basic implementation working, and then revisit the code.

 

Now that you have this function, you need to run it when the buttons are clicked. That’s also simple:

buttons.on("click", function() {


if($(this).hasClass("back")) {

animateSlider("+", 1000);

} else { animateSlider("-", 1000);

};

});

 

If you refresh the page, you have a working slider in place! You’ve only got your first two bullet points done, but congratulations—you have implemented a basic image slider! That wasn’t so bad, really. Before you move to the next two bullet points, do some refactoring, particularly in the animateSlider() method:

 

var animateSlider = function(direction, duration) { if(direction == "+") {

sliderList.animate({
"margin-left" : "+=300px"
}, duration);
} else { sliderList.animate({
"margin-left" : "-=300px" }, duration);
}
};
The duplication here is horrible. You only want to have the call to animate appear once. It turns out there’s an easy way to fix this:
var animateSlider = function(direction, duration) { sliderList.animate({
"margin-left" : direction + "=300px"
}, duration);
};
As you pass in the direction, you just append the "=300px" to the direction variable, which will give you either "+=300px" or "-=300px", which is exactly what you need. That’s much better.
Now take look at the click event handler on the buttons:
buttons.on("click", function() {
if($(this).hasClass("back")) {
animateSlider("+", 1000);
} else {
animateSlider("-", 1000);
};
});
Again, calling animateSlider() twice is messy. There’s a nice solution here to trim the event handler down to just a one-liner:
buttons.on("click", function() {
animateSlider(($(this).hasClass("back") ? "+" : "-"), 1000);
});
Here you’ve used a ternary operator. Take a moment to study it a bit more closely:
($(this).hasClass("back") ? "+" : "-")
This is simply a syntactical shortcut for
if($(this).hasClass("back")) { return "+" } else { return "-" }

 

The bit to the left of the question mark is evaluated to either true or false. If it’s true, the item after the question mark is returned. If it’s false, the bit after the colon is returned. So here, if the button has a class of "back", it will return "+"; but if it does not have that class, it will return "-". Be careful here. Although you’re going back, you are actually animating the margin positively—even though it seems counterintuitive at first.

 

This leaves your entire slider looking much tidier. All that functionality in just 16 lines of code!

$(function() {

var sliderWrapper = $("#slider");
var sliderList = sliderWrapper.children("ul"); var sliderItems = sliderList.children("li"); var buttons = sliderWrapper.children(".button");
var animateSlider = function(direction, duration) { sliderList.animate({
"margin-left" : direction + "=300px"
}, duration);
};
buttons.on("click", function() {
animateSlider(($(this).hasClass("back") ? "+" : "-"), 1000);
});
});

The new problem is that you can click either button infinitely, and the margin will still be animated, leaving a blank space where the image should be.

 

If the margin of the unordered list is set to 0, it means you are at the first image, and hence the Back button should be disabled. If the margin is set to –1200 pixels, you are at the last image. This value is simply the width of an image, multiplied by the number of images you have. First, write a helper function to tell you if the slider is at the beginning:

var isAtStart = function() {

return parseInt(sliderList.css("margin-left"), 10) === 0;

};

 

This uses a new JavaScript method called parseInt(), which you have not yet seen. It takes a string and turns it into an integer. For the string "300px", it will return the integer 300. The second parameter that it takes is the radix of the string. This is optional, but it’s highly recommended that you use it to guarantee expected results.

 

More often than not, you’ll use base 10. If the margin is 0, you’re at the start; if it’s not 0, you’re not at the start, so you can simply return parseInt(sliderList.css("margin-left"), 10) == 0 as the result. It gets evaluated to either true or false.

 

Now rework the event handler. Here’s an example of how to do it:

buttons.on("click", function() {
var $this = $(this);
var isBackBtn = $this.hasClass("back");
if(isBackBtn && isAtStart()) {
return;
}
animateSlider(( isBackBtn ? "+" : "-"), 1000); });

 

This stores the result of $this.hasClass("back") because it’s probable that you’ll reference it at least twice. Then, if isBackBtn is true and isAtStart() is also true, you simply return, which returns and stops any further execution of the event handler. This ensures that the Back button doesn’t work when you get to the beginning.

 

Next, do the same for when the user clicks the Back button when at the end of the slider:

var isAtEnd = function() {
var imageWidth = sliderItems.first().width();
var imageCount = sliderItems.length;
var maxMargin = -1 * (imageWidth * (imageCount-1));
return parseInt(sliderList.css("margin-left"), 10) === maxMargin;
}

 

You have to do a bit more work here. First, calculate the width of an image by getting the width of an individual list item. The maximum margin is the width of an item multiplied by the number of images, minus one. This is because at a margin of 0, the first image is displayed; so at –300 pixels, it’s showing the second image, not the first. You return if the slider margin is indeed the maximum margin. Your event handler becomes.

buttons.on("click", function() {

var $this = $(this);
var isBackBtn = $this.hasClass("back");
if(isBackBtn && isAtStart()) {
return;
}
if(!isBackBtn && isAtEnd()) {
return;
}
animateSlider(( isBackBtn ? "+" : "-"), 1000); });
But you can merge those conditionals together using the or (||) operator:
buttons.on("click", function() {
var $this = $(this);
var isBackBtn = $this.hasClass("back");
if( (isBackBtn && isAtStart()) || (!isBackBtn && isAtEnd()) ) { return; } animateSlider(( isBackBtn ? "+" : "-"), 1000);
});

 

Note that this also brings up the return statement and the braces onto the same line, simply because having just the word “return” on its own line seems like a foolish waste of space. Your four bullet points are done—all in 31 lines of JavaScript:

 

$(function() {
var sliderWrapper = $("#slider");
var sliderList = sliderWrapper.children("ul"); var sliderItems = sliderList.children("li"); var buttons = sliderWrapper.children(".button");
var animateSlider = function(direction, duration) { sliderList.animate({
"margin-left" : direction + "=300px"
}, duration);
};
var isAtStart = function() {
return parseInt(sliderList.css("margin-left"), 10) === 0;
};
var isAtEnd = function() {
var imageWidth = sliderItems.first().width();
var imageCount = sliderItems.length;
var maxMargin = -1 * (imageWidth * (imageCount-1));
return parseInt(sliderList.css("margin-left"), 10) === maxMargin;
}
buttons.on("click", function() {
var $this = $(this);
var isBackBtn = $this.hasClass("back");
if( (isBackBtn && isAtStart()) || (!isBackBtn && isAtEnd()) ) { return; } animateSlider(( isBackBtn ? "+" : "-"), 1000);
});
});
And there you have the basic JavaScript slider.

 

There’s one final thing to cover before ending this blog: the animation lag problem. You also want to be able to pass in a callback to the animateSlider() function, because when you improve this slider later (you’ll turn it into a plug-in), it might come in handy:

var animateSlider = function(direction, duration, callback) { sliderList.stop(true, true).animate({

"margin-left" : direction + "=300px"

}, duration, callback);

};

 

All you need to do is call stop(true, true), which causes it to empty the animation queue and to immediately get to the end of the currently running animation before starting the next. Using a callback is easy: you just make your animateSlider() method take the argument and pass it through to the animate() method. If you don’t need to use a callback, you don’t have to pass one in. jQuery will see that the callback is undefined and will not try to execute it.

 

If you click the Next button a couple of times in succession, you’ll see it’s possible for you to click enough that it scrolls past the end. Why is this? It is because the isAtEnd() method only returns true if the margin is exactly –1200. But if you click the Next button during an animation, the margin is somewhere between –900 and –1200. So you actually want to check that the margin is less than (negative values, remember) –900, which is the imageWidth * (imageCount - 2):

 

var isAtEnd = function() {
var imageWidth = sliderItems.first().width();
var imageCount = sliderItems.length;
var maxMargin = -1 * (imageWidth * (imageCount-2));
return parseInt(sliderList.css("margin-left"), 10) < maxMargin;
}
That fixes that issue, but the Back button has similar problems. Again, you just need to check that the margin is greater than –300 pixels, rather than it being exactly zero.
var isAtStart = function() {
return parseInt(sliderList.css("margin-left"), 10) > -300;
};
Now you have a slider that is far more robust.