A Software Engineer Learns HTML5 JavaScript and Jquery Dane Cameron PDF
A Software Engineer Learns HTML5 JavaScript and Jquery Dane Cameron PDF
CoverDesignStudio.com
Cisdal
[email protected]
www.cisdal.com
Table of Contents
Introduction
About this book
A Brief Overview of Web Applications
HTML5 Markup Language
Javascript Fundamentals
jQuery
Debugging
Moving to a Web Server
Building the Web Application
Managing Client-side Data
Tidying up the Web Application
Offline Web Applications
Working with Files
Web Workers
AJAX
Server Sent Events and Web Sockets
Error Handling
Conclusion
Appendix A: Cascading Style Sheets
Appendix B: Recommended Libraries
Preface
JavaScript (and its frameworks such as
jQuery) and HTML (along with its
style sheet language CSS) have
become a ubiquitous presence in
software development. Due to their
monopoly position in web browsers,
and the fact web browsers have spread
from PCs to phones, tablets and TVs;
this pervasiveness will continue to
grow and grow.
Despite their success, many software
engineers are apprehensive about
JavaScript and HTML. This
apprehensiveness is not completely
unfounded; both JavaScript and HTML
were rushed in their early years, and
driven by commercial rather than
engineering interests. As a result, many
dubious features crept into these
languages. Due to backwards
compatibility concerns, most of these
features still remain.
Introduction
Im not going to second guess your
motivations for reading this book but
I would like to tell you how I arrived
at writing it.
I was a software engineer with 15
years experience writing large-scale,
enterprise Java applications. Typically
these applications performed system
integration functions, rather than enduser functionality.
I then joined a team writing a complex
web application from scratch. This
web application had a number of
requirements I had not encountered
before:
It had to run inside the latest version
of all major browsers.
It had to be capable of running
without network connectivity for
periods of time, and therefore needed
to store reasonably large amounts of
Conclusion
The conclusion you can draw from this
chapter is that HTML5 and JavaScript
are perfectly placed to be the driving
force powering the applications most
users interact with on a daily basis.
http://www.w3schools.com/ provide
basic introductions to both HTML and
JavaScript.
The exercises in this book can be
performed on any computer with
access to the following:
Ctrl+F5 on Windows
Many of the examples in this book,
particularly in the early chapters, can
be run directly using a JavaScript
interpreter. All major browsers now
provide development tools offering
direct access to a JavaScript
interpreter.
In order to access the Chrome
interpreter, simply open Chrome, and
type:
Command+Option+i on OSX
F12 or Ctrl+Shift+I on Windows
Alternatively, you can right click
anywhere on any web page and choose
Inspect Element:
Conventions
One unfortunate aspect of eBooks is
that it is difficult to format computer
code effectively on all devices, since
the width of the screen can change
from device to device. Ideally code
will be presented in a distinct, fixedwidth font; however this is not
possible on all eBook readers. Where
supported, all code in this book will
utilize the following font:
This is code
>2+2
Feedback
I would love to hear your feedback
(positive and negative) on this book.
Please email me at
[email protected].
//
It
will
be
important
throughout this book that you
understand the difference
between an asynchronous and
a synchronous API. Although
this book will offer many
examples,
the
basic
difference between the two
is:
A synchronous API waits
for a response, and blocks
everything
else
from
happening in the application
until
that
received.
response
is
HTML5
HTML5 can be a confusing term.
HTML5 includes a specification of a
markup language for creating
documents that can be rendered in web
browsers. As a markup language,
HTML5 both extends and rationalises
earlier versions of HTML and
XHTML.
As part of its extension of HTML,
HTML5 offers a new set of tags to web
site developers. Many of these tags are
designed to provide greater
descriptive power to HTML. For
instance, HTML5 contains header and
footer tags. These tags do not allow
pages to do or look any different than
they did previously, and are one of the
less interesting aspects of HTML5; we
will examine a subset of these new
tags in this book.
HTML5 also contains new tags to
support audio and video, and a canvas
//
//
standards.
JavaScript
JavaScript is the only language
natively supported in virtually all web
browsers in existence. JavaScript first
appeared in 1995 in an early version
of the Netscape Navigator browser,
and quickly migrated to Internet
Explorer. JavaScript is essential for
adding dynamic and interactive
features to a web application.
//
JQuery
jQuery is a JavaScript library designed
to simplify the process of writing
JavaScript applications within web
browsers.
Due to the document-centric nature of
web pages, JavaScript is routinely
responsible for selecting elements
within the document (the internal
representation of a document inside the
browser is referred to as the Document
Object Model), manipulating these
elements, or reacting to events
triggered by these elements. JavaScript
natively supports this functionality
through the Document Object Model
API, which is also included in the
HTML5 specification. jQuery
essentially provides an elegant
wrapper around the Document Object
Model API.
The heart of jQuery is a selector
engine. jQuery accepts selection
Page structure
It is often good to start with the
simplest possible example, for
HTML5 that would be:
<!DOCTYPE html>
hello world!!!
//
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Task list</title>
<link rel="stylesheet" type="text/css"
href="styles/tasks.css" media="screen" />
</head>
<body>
<header>
<span>Task list</span>
</header>
<main>
<section>
<form>
<div>
<label>Task</label> <input type="text"
required="required"
name="task" class="large"
placeholder="Breakfast at Tiffanys" />
</div>
<div>
<label>Required by</label> <input
type="date" required="required"
name="requiredBy" />
</div>
<div>
<label>Category</label> <select
name="category">
<option
value="Personal">Personal</option>
<option value="Work">Work
</option>
</select>
</div>
<nav>
<a href="#">Save task</a> <a
href="#">Clear task</a>
</nav>
</form>
</section>
<section>
<table id="tblTasks">
<colgroup>
<col width="50%">
<col width="25%">
<col width="25%">
</colgroup>
<thead>
<tr>
<th>Name</th>
<th>Due</th>
<th>Category</th>
</tr>
</thead>
<tbody>
<tr>
<td>Return library books</td>
<td><time datetime="2013-1014">2013-10-14</time></td>
<td>Personal</td>
</tr>
<tr class="even">
<td>Perform project demo to
stakeholders</td>
<td><time datetime="2013-1014">2013-10-14</time></td>
<td>Work</td>
</tr>
<tr>
<td>Meet friends for dinner</td>
<td><time datetime="2013-1014">2013-10-14</time></td>
<td>Personal</td>
</tr>
</tbody>
</table>
<nav>
<a href="#">Add task</a>
</nav>
</section>
</main>
<footer>You have 3 tasks</footer>
</body>
</html>
margin: 0;
font-size: 12px;
}
header {
width:100%;
height:80px;
background:#d1e0e1;
color: #333;
font-weight: bold;
font-size: 20px;
text-align:center;
line-height: 80px;
}
footer {
width:100%;
height:60px;
background:#d1e0e1;
font-size: 12px;
text-align:center;
line-height: 80px;
margin-top:30px;
}
table, th, td
{
border: 1px solid #888;
}
section {
margin:20px 0 0 20px;
}
table {
width:90%;
border-collapse:collapse;
}
thead {
line-height: 30px;
}
thead th {
background: #53777a;
color: #fff;
font-size: 12px;
font-weight: bold;
text-align:center;
}
td {
font-size: 11px;
line-height: 25px;
padding-left: 10px;
}
.even {
background-color: #f8f8f8;
}
nav {
margin:15px 0 10px 0;
nav a {
background: #53777a;
color: #fff;
width: 80px;
text-decoration: none;
border: 1px solid #5b5b5b;
font-size: 13px;
text-align: center;
padding:5px 10px;
}
label {
display: block;
padding: 8px 0 8px 0;
color: #333;
}
input {
border-radius: 3px;
height: 24px;
border: 1px solid #AAA;
padding: 0 7px;
}
input.large {
width: 400px;
}
select {
border: 1px solid #AAA;
overflow: hidden;
margin-right: 15px;
width: 200px;
}
.required {
color: red;
}
.not {
display:none;
}
.rowHighlight {
font-weight:bold;
}
label.error {
color: red;
font-weight:bold;
}
.overdue {
background: #F7DCE5;
}
.warning {
background: #F7F7DC;
}
.taskCompleted {
text-decoration: line-through;
}
//
The DOCTYPE is an
important signal to browsers
and allows them to interpret
the content of the document in
the context of the rules
or
<!DOCTYPE html PUBLIC "-//W3C//DTD
XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
//
//
Tags
Since the advent of CSS, web
designers have been relying on the div
and span tags to layout content. These
tags have no visual meaning; rather
they act as containers for other
elements. These containers can then be
positioned anywhere on the screen
using CSS.
The only difference between a span
and a div is that spans are inline
elements (they can be placed vertically
adjacent to other elements) whereas
divs are block elements, and therefore
are positioned on their own line in the
document.
//
Microformats
If you look at the time tags in the
document you will see the following:
<time datetime="2013-10-14">2013-10-14</time>
Or
<time datetime="2013-10-14">October 2014</time>
HTML5 Forms
There are a number of additions in
HTML5 to forms, and the input fields
they contain. Form elements such as
input fields and select boxes had not
really changed since the early versions
of HTML. Not only does HTML5
introduce a new set of input types, it
includes a large set of attributes for
customising form inputs, along with
native validation of form fields.
New Input Types
If you look at the input type of the field
named requiredBy you will notice
that its type is set to date:
<input type="date"
required="required"name="requiredBy"/>
//
type="date"
required
name="requiredBy"/>
to
it
of
is
required="false"
color
datedatetime
datetime-local
month
range
search
time
week
In reality most of these types cannot be
relied on in most browsers. This is a
familiar problem for developers of
web applications, so it is worth
stopping to discuss the concept of
polyfills, which are a common
solution to this problem.
A polyfill is an elegant solution to the
problem that some features are
supported by a subset of browsers. If a
feature is natively supported in a
particular browser, the polyfill does
nothing. If the feature is not supported,
JavaScript Fundamentals
This section will provide an in-depth
overview of JavaScript, including its
strengths and weaknesses. The
intention of this chapter is to provide
you a strong understanding of
JavaScript fundamentals, and to focus
on how the language should be used,
rather than how it can be used.
JavaScript is a particularly flexible
language, and does not enforce much
discipline or structure on its users. In
addition, JavaScript contains a number
of features than can only truly be
regarded as bugs. These remain in the
language principally due to backwards
compatibility concerns.
In order to write large-scale web
applications it is necessary to harness
the strengths of JavaScript, while at the
same time avoiding the pitfalls that can
easily confront software engineers and
programmers who do not understand
Types
Any understanding of JavaScript
begins with an understanding of its
data types, and how these types are
used.
JavaScript has the following data
types:
String
Number
Boolean
Null
Undefined
Object
Each of these will be outlined in the
sections below.
Strings
Strings are series of characters
enclosed in either single or double
quotes:
> "hello world"
"hello world"
"hello world"
"Hello world"
//
"string
"e"
> s.substr(6)
"world"
> s.toUpperCase()
"HELLO WORLD"
11
//
An immutable object is an
object that cannot be changed
after it is first created. Even
when these appear to change,
as in the examples below, in
reality a new object is being
created.
"Hello world"
"Hello world"
> s == t
true
"Hello worldtest"
"Hello worldtest"
"Hello world"
Numbers
The number type is used to represent
both integer and floating-point values;
in fact all numbers are 64 bit floatingpoint numbers in JavaScript:
> n = 6.827
6.827
> typeof n
"number"
> n2 = 6
> typeof n2
"number"
0.3333333333333333
NaN
> typeof a
"number"
Infinity
> b == Number.POSITIVE_INFINITY
true
> c = -6/0
-Infinity
> c == Number.NEGATIVE_INFINITY
true
> Math.round(3.22)
Booleans
true
> typeof t
"boolean"
> f = false
false
true
Null
Null is a data type that has a single
value: null.
> n = null
null
"object"
> typeof q
"number"
> q = null
null
> typeof q
"object"
Undefined
The undefined data type is returned
when you access a property on an
"undefined"
Objects
Other than the types outlined above, all
data types in JavaScript are objects:
this includes arrays, functions, dates
and user defined objects. Objects will
be discussed at length in the sections
below.
Truthy and Falsey Values
Now that you have an understanding of
the JavaScript data types, the next thing
to understand is that some values for
these types evaluate to true, and some
evaluate to false. For instance, the
true
true
> 5 == "5"
true
false
true
false
false
false
false
false
//
false
!!"hello"
true
Dynamic Typing
Finally, it is worth reiterating that
JavaScript is a dynamically typed
language.
Languages such as Java and C++ are
statically typed languages. In statically
typed languages, all variables are
assigned a type at compile time, and
this type cannot be changed.
//
"11"
//
Objects
JavaScript also supports objects; in
fact, most values in JavaScript
applications will be objects.
JavaScript also supports syntax for
defining classes that objects can be
instantiated from. This may lead you to
think JavaScript is a conventional
object orientated language this would
be a mistake.
In classical object orientated languages
such as Java and C#, classes must be
defined before objects can be
instantiated from them. It is never
possible to have an object that is not a
type of a specific class.
Classes are static templates that
contain definitions of the properties
and methods that objects will contain.
During program execution, instances of
these classes are created: these are
called objects. All objects instantiated
from the same class have the same
//
"object"
//
"John"
"John"
//
> obj.age
33
> obj2.age = 28
//
An alternative approach is to
include the properties and
methods inside the {}
separating properties/methods
with
commas,
and
properties/methods from their
values with colons:
obj2 = {
firstName: 'Albert',
lastName: 'Jones',
age: 28,
increaseAge: function() {
this.age++;
}
}
newObj[key] = clone(obj[key]);
}
return newObj;
}
//
"Jim"
"Duffey"
> obj3.age = 42
> obj3.increaseAge()
43
<firstName>John</firstName>
<lastName>Smith</lastName>
<age>32</age>
<address>
<city>Los Angeles</city>
<postCode>90245</postCode>
</address>
</person>
"
{"firstName":"John","lastName":"Smith","age":32,"addre
{"city":"Los Angeles","postCode":90245}}"
//
This
process
is
only
//
Prototypes
We will return now to our discussion
of objects in JavaScript.
The clone function that we wrote
above was our first attempt at code
reuse. It allowed us to reuse or extend
an object that we had already written.
One problem discussed with the clone
function was that it took all the
properties from the object it was
cloning, including properties that were
specific to the cloned instance, such as
firstName and lastName.
JavaScript provides a more elegant
mechanism for extending objects
called prototypes. In fact, JavaScript
itself is considered a prototype-based
language.
If you declare an empty object, you
may think that it contains no properties
at all:
> obj = {}
"[object Object]"
"I am an object"
> obj2.toString()
"[object Object]"
> typeof a
"object"
"1,2,3,4,5"
[5, 4, 3, 2, 1]
> a.pop()
> a.push(6)
true
> a.contains(6)
false
//
},
increaseAge: function() {
this.age++;
}
}
"undefined undefined"
//
John
> window.lastName
Smith
//
Programmers
who
have
experience with other object
orientated languages are
always initially drawn to
constructor functions. They
provide a certain familiarity,
and appear to provide a class
based
typing
system.
Programmers
are
then
invariably annoyed when
these classes do not provide
the same features they are
used to with classes in other
languages.
> p1.age = 34
//
function a() {
return
{a:1};
}
"John Smith"
Functional Programming
Understanding JavaScript begins with
the realisation that it is a prototypebased object orientated language. The
next phase in understanding JavaScript
comes from realising that it is also a
functional-orientated programming
language.
//
There
is
no
standard
definition for what makes a
programming language a
functional
programming
language. This section will
highlight the aspects of
JavaScript that make it a
functional
programming
language without addressing
arguments against considering
it a functional language.
//
Following
you may
typeof an
array: it is
object.
this precedence,
expect that the
array would be
not, it will return
Hello World
false
> f2(10)
true
[2, 4, 6, 8, 10]
//
[2, 4, 6, 8, 10]
//
//
http://static.googleusercontent.com
osdi04.pdf
Total is 1
Current value is 3
Total is 3
Current value is 4
Total is 6
Current value is 5
Total is 10
15
220
Function Arguments
JavaScript does not check that the
arguments passed to a function match
the signature of the function. For
instance:
You can pass more arguments to a
function than it expects; in this case the
extra arguments will be ignored.
You can pass fewer arguments to a
function than it expects; in this case the
arguments are assigned the value of
undefined.
//
parameter
lists
(or
signatures). The compiler
then determines the correct
version to invoke based on
the parameters provided.
In order to achieve this in
JavaScript the functions must
be given different names,
otherwise JavaScript cannot
determine the correct version
to invoke.
37
Closures
Closures are another of the most
important features of JavaScript. When
we continue the development of the
sample web application we will make
extensive use of closures.
Closures can be a difficult concept to
explain, so it is useful to learn about
them through examples.
Consider the following code:
> function f() {
var i = 0;
return ++i;
}
//
> f()
1
> f()
return function() {
return ++i;
};
}
> incrementer()
> incrementer()
> incrementer2()
> incrementer2()
> obj1.increment()
> obj1.increment()
21
NaN
closure-based incrementer is a
representation of a design pattern. A
design pattern is a commonly used,
reusable approach to solving a
common problem. This approach is
referred to as the module design
pattern.
The module design pattern is also
commonly implemented by returning an
object rather than a function. This
allows the object to use private
variables for its private properties and
functions, while still exposing a series
of public properties and methods. The
functionality above can be rewritten as
follows:
> function createIncrementer() {
var i = 0;
return {
increment: function() {
return ++i;
}
};
> obj2.increment()
> obj2.increment()
> obj2.i = 10
10
> obj2.increment()
> obj2.increment()
10
> getNumber()
10
10
//
written as follows:
> function getNumber() {
this.num = 10;
return this.num;
}
//
20
> obj.add(10)
30
return this.num;
}
return helper(num2);
}
}
NaN
code.
The common way to avoid this issue is
as follows:
> obj = { num: 10,
add: function(num2) {
var that = this;
helper = function(num2) {
that.num += num2;
return that.num;
}
return helper(num2);
}
}
30
70
70
}
}
undefined
> testGlobal()
Exception Handling
We will discuss approaches to
implementing application wide
exception handling later in this book.
For now, it is worth emphasising that
JavaScript does support Java-style
exception handling, but without the
benefits provided by static typing.
Any code can throw an exception
without declaring that it will throw that
exception. Any data type can be thrown
as an exception, although it is common
practice to throw an object with a code
and a message. This function throws an
exception if it is passed an even
number:
> function dontLikeEven(num) {
if (num % 2 == 0) {
throw {code: 'even_number',
message: 'This function cannot be called with
even numbers'
};
}
an even number:
> passNumber(2)
Threading
Unlike most languages, JavaScript
does not offer programmers an
approach for utilizing multiple threads.
Within the browser environment, all
JavaScript code executes on a single
thread, and this is also the thread that
the browser utilizes to update the
window. This means that if you make
changes to the DOM, and then continue
to utilize the thread to perform any
computation, your DOM changes will
not be applied until you finish this
processing.
Browsers do allow documents in
different tabs to utilize different
threads: this is possible because the
tabs do not interact with one another.
The browser manages the single thread
with an event queue. Each time an
event occurs, be it a browser initiated
event such as resizing the window, a
user initiated even such as clicking a
limitations.
JavaScript does provide a mechanism
to alleviate some of the issues inherent
in the single thread model with the
setTimeout function defined in the
language. The setTimeout function
allows code to execute at a delayed
interval.
In order to see setTimeout in action,
we will first define a function that we
wish to execute at a delayed interval:
> writer = function() {
for (var i = 0; i < 10; i++) {
console.log('Row '+i);
}
}
Next we will ask the browser thread to
execute this with a delay of 5000
milliseconds:
> setTimeout(writer, 5000)
Conclusion
JavaScript is best described as a
multi-paradigm language: it supports
the following programming paradigms:
Functional programming
Imperative programming
Classical object orientated
programming
Prototype-based programming
Due to its multi-paradigm nature,
JavaScript is enormously flexible. The
danger with JavaScripts multiparadigm nature is that it allows
programmers to see in it what they
want, without taking into account
which of these paradigms are
JavaScripts true strengths.
This chapter has presented the case
that JavaScript should be best thought
of as a prototype-based, functional
programming language.
jQuery
Understanding jQuery begins with an
understanding of JavaScript.
Sometimes jQuery is treated as an
independent language that can be used
instead of JavaScript. Although jQuery
contains many features associated with
programming languages, it is best
thought of as a library that enhances
JavaScript in specific situations. For
this reason, JavaScript has been
introduced before jQuery.
Before understanding jQuery however,
it is worth taking a brief look at the
Document Object Model (DOM), since
jQuery is primarily a library for
dealing with the DOM in a browser
independent manner.
following:
> document
//
<script
src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jq
</script>
Selection
The first thing we are going to do with
jQuery is select elements from the
document.
Selecting elements obviously has no
intrinsic value in its own right; we are
eventually going to do something with
these elements. A jQuery selection will
return zero or more elements from the
document. More specifically, the
selection will return zero or more
jQuery wrapped DOM elements from
the document. This distinction will be
important in later sections.
In order to perform a selection, simply
include the selection criteria inside the
following construct:
$('<selection criteria>')
Pseudo classes.
All five approaches will be briefly
outlined below.
The first way to select an element is by
its type. For instance, if we want to
select all the table elements in the
document, we can use the following
syntax:
> $('table')
or
<tr data-priority="medium">
> $('[data-priority="high"]')
//
//
It is possible to apply
specific styles to every
second row purely through
CSS:
tr:nth-child(even) {
background-color: #f8f8f8;
}
//
in a selection.
:not(selection) finds all elements
that do not match the selection.
:checked finds radio buttons or
check boxes that are checked.
:selected finds options in select
boxes that are selected.
:contains(text) finds elements that
contain a given piece of text.
:empty finds all elements that have
no children.
:focus finds the element that currently
has focus.
:last finds the last element in a set.
In addition, filters can simplify the
process of selecting input elements.
Since most input fields use the element
type of input, it is necessary to also
query on the attribute type. jQuery
contains the following filters for
finding specific types of input field:
:hidden
:text
:checkbox
:password
:radio
:checkbox
:file
:button
for instance, this selects all text input
fields:
> $(':text')
One of the nice features about jQuery
filters is that you can actually write
your own. For instance, jQuery does
not provide a filter for finding all the
date input types. We can therefore add
a custom filter to our code-base as
follows:
> jQuery.expr[':'].date = function(elem) {
return jQuery(elem).is( "input" ) && $( elem
//
> $('[type="text"]')
$('tr:first')
Traversal
Selecting a set of elements from the
DOM is an important step, but is only
the first step in the process. Once a set
of elements has been identified a
common second step is to traverse
from these elements to another set of
elements.
A traversal function always starts with
the results of a selection, and then
performs a traversal operation on these
elements to return a new set of
elements.
A common traversal requirement is to
traverse from an element, or set of
elements, to their siblings. For
instance, in tasks.html input fields and
their labels are siblings because they
share the same parent.
Sibling-based queries can be
performed through the siblings
function. If we want to find all siblings
for the select box with the name of
//
Manipulation
The previous two sections on selection
and traversal have shown you how a
set of elements can be selected from
the document. This section is going to
introduce you to the ways you can
manipulate documents once you have a
set of elements to work with.
If you look at the tasks.html document
you will see that the second row in the
table has a class called even assigned
to it. The basic idea is that every
second row in the table will be given
the even class, and this will change the
background colour of the row to make
the table easier to read.
As mentioned previously, a real world
application would be better off
performing this functionality with CSS,
but performing this with jQuery
provides a nice introduction to DOM
manipulation.
Before beginning this section, remove
> $('[required="required"]').prev('label')
//
Events
The next aspect of jQuery to
understand is events. When writing
web applications, the application will
need to respond to events initiated by
the user.
These events may be any of the
following:
Clicking a link or button.
Double clicking a button or link.
Changing the value in a text field.
Pressing a particular key inside an
input field.
Selecting an option in a select list.
Focusing on an input field.
Hovering the mouse over an element.
In addition to these user-initiated
events, the application may wish to
respond to browser-initiated events
such as:
media="screen" />
<script src="scripts/jquery-2.0.3.js"></script>
</head>
<body>
<header>
<span>Task list</span>
</header>
<main>
<section id="taskCreation"
class="not">
<form>
<div>
<label>Task</label> <input type="text"
required="required"
name="task" class="large"
placeholder="Breakfast at Tiffanys" />
</div>
<div>
<label>Required by</label> <input
type="date" required="required"
name="requiredBy" />
</div>
<div>
<label>Category</label> <select
name="category">
<option
value="Personal">Personal</option>
<option
value="Work">Work</option>
</select>
</div>
<nav>
<a href="#">Save task</a> <a
href="#">Clear task</a>
</nav>
</form>
</section>
<section>
<table id="tblTasks">
<colgroup>
<col width="50%">
<col width="25%">
<col width="25%">
</colgroup>
<thead>
<tr>
<th>Name</th>
<th>Due</th>
<th>Category</th>
</tr>
</thead>
<tbody>
<tr>
<td>Return library books</td>
<td><time datetime="2013-1014">2013-10-14</time></td>
<td>Personal</td>
</tr>
<tr>
<td>Perform project demo to
stakeholders</td>
<td><time datetime="2013-1014">2013-10-14</time></td>
<td>Work</td>
</tr>
<tr>
<td>Meet friends for dinner</td>
<td><time datetime="2013-1014">2013-10-14</time></td>
<td>Personal</td>
</tr>
</tbody>
</table>
<nav>
<a href="#" id="btnAddTask">Add
task</a>
</nav>
</section>
</main>
<footer>You have 3 tasks</footer>
</body>
<script>
$('[required="required"]').prev('label').append(
'<span>*</span>').children(
'span').addClass('required');
$('tbody tr:even').addClass('even');
$('#btnAddTask').click(function(evt) {
evt.preventDefault();
$('#taskCreation').removeClass('not');
});
</script>
</html>
function btnAddClicked(evt) {
$(evt.target).preventDefault();
$('#taskCreation').removeClass('not');
}
$('#btnAddTask').click(btnAddClicked);
//
//
//
//
$('#btnAddTask').click(function(evt) {
evt.preventDefault();
$('#taskCreation').removeClass('not');
});
$('tbody tr').click(function(evt) {
$(evt.target).closest('td').siblings( ).andSelf(
).toggleClass( 'rowHighlight');
});
});
//
console.log('index: '+i);
console.log('value: '+v)
});
(function($) {
$.fn.extend({
toObject: function() {
var result = {}
$.each(this.serializeArray(), function(i, v) {
result[v.name] = v.value;
});
return result;
}
});
})(jQuery);
},
fromObject: function(obj) {
}
});
})(jQuery);
$(v).val('');
}
});
}
});
})(jQuery);
http://ajax.aspnetcdn.com/ajax/jquery.tem
//
https://developers.google.com/spe
javascript
<script
src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jq
</script>
<script src="scripts/jquery-tmpl.js"></script>
</head>
//
//
object.
The template will generate a block of
HTML, which we will then append to
the table body. Add the following code
to the script section of tasks.html
alongside the other event handlers:
$('#saveTask').click(function(evt) {
evt.preventDefault();
var task = $('form').toObject();
$('#taskRow').tmpl(task).appendTo($('#tblTasks
tbody'));
});
<script src="scripts/jquery-tmpl.js"></script>
</head>
<body>
<header>
<span>Task list</span>
</header>
<main>
<section id="taskCreation" class="not">
<form>
<div>
<label>Task</label>
<input type="text" required="required"
name="task" class="large"
placeholder="Breakfast at Tiffanys" />
</div>
<div>
<label>Required by</label>
<input type="date" required="required"
name="requiredBy" />
</div>
<div>
<label>Category</label>
<select name="category">
<option
value="Personal">Personal</option>
<option value="Work">Work</option>
</select>
</div>
<nav>
<a href="#" id="saveTask">Save task</a>
<a href="#">Clear task</a>
</nav>
</form>
</section>
<section>
<table id="tblTasks">
<colgroup>
<col width="40%">
<col width="15%">
<col width="15%">
<col width="30%">
</colgroup>
<thead>
<tr>
<th>Name</th>
<th>Due</th>
<th>Category</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<nav>
<a href="#" id="btnAddTask">Add task</a>
</nav>
</section>
</main>
<footer>You have 3 tasks</footer>
</body>
<script>
$(document).ready(function() {
$('[required="required"]').prev('label').append(
'<span>*</span>').children(
'span').addClass('required');
$('tbody tr:even').addClass('even');
$('#btnAddTask').click(function(evt) {
evt.preventDefault();
$('#taskCreation').removeClass('not');
});
$('tbody tr').click(function(evt) {
$(evt.target).closest('td' ).siblings( ).andSelf(
).toggleClass( 'rowHighlight');
});
$('#saveTask').click(function(evt) {
evt.preventDefault();
var task = $('form').toObject();
$('#taskRow').tmpl(task).appendTo(
$('#tblTasks tbody'));
});
});
(function($) {
$.fn.extend({
toObject: function() {
var result = {}
$.each(this.serializeArray(), function(i, v) {
result[v.name] = v.value;
});
return result;
},
fromObject: function(obj) {
$.each(this.find(':input'), function(i,v) {
var name = $(v).attr('name');
if (obj[name]) {
$(v).val(obj[name]);
} else {
$(v).val('');
}
});
}
});
})(jQuery);
</script>
</td>
</tr>
</script>
</html>
Conclusion
This chapter has introduced all the
important aspects of jQuery that deal
with DOM manipulation. As has been
demonstrated, jQuery provides an
elegant and concise set of function
calls for selecting, traversing and
manipulating the DOM, along with
adding event listeners.
Although jQuery does not do anything
that could not be done natively with the
DOM API (or other libraries such as
Dojo for that matter), jQuery provides
the following benefits:
It takes care of any quirks that may
exist in different versions of web
browsers.
It uses the same basic set of selectors
as CSS, meaning anyone who
understands CSS can quickly adapt to
jQuery.
It provides a concise and intuitive set
Debugging
Now that we have some working code,
it is worth taking a step back to look at
the development tools that can help
when writing JavaScript based web
applications.
Probably the most important tool for
development purposes is a debugger.
Debuggers allow a software engineer
to examine an application while it is
running, and therefore analyse the
cause of any problems.
In order to start the debugger, first
open the tasks.html page in Chrome.
Then use one of the following
approaches to open the development
tools:
Command+Option+i on OSX
F12 or Ctrl+Shift+I on Windows
Once the familiar console is open,
click on the sources tab:
to
$('#btnAddTask').click(function(evt) {
evt.callUnknownFunction();
$('#taskCreation').removeClass('not');
});
> evt.callUnknownFunction();
OSX
Download the OSX installer from:
http://cesanta.com/downloads.html
Once this has downloaded, perform the
following steps:
1. Double click on the DMG file and
drag it to applications.
2. Open the finder, and go to the
Applications folder.
3. Double click on Mongoose.
The Mongoose application can now be
configured via the icon in the taskbar at
the top of the screen:
Windows
Download the Windows executable
(no install required) installer from:
http://cesanta.com/downloads.html
Once this downloads, perform the
following steps:
1. Copy the exe file to the same
directory that contains tasks.html.
2. Double click on the executable to
start Mongoose.
The Mongoose application can now be
configured via the icon in the taskbar at
the bottom of the screen (although no
configuration is required in this case):
<title>Task list</title>
<link rel="stylesheet" type="text/css"
href="styles/tasks.css"
media="screen" />
<script src="scripts/jquery-2.0.3.js"></script>
<script src="scripts/jquery-tmpl.js"></script>
<script src="scripts/jquery-serialization.js">
</script>
<script src="scripts/tasks-controller.js">
</script>
</head>
<body>
<header>
<span>Task list</span>
</header>
<main id="taskPage">
<section id="taskCreation" class="not">
<form id="taskForm">
<div>
<label>Task</label> <input type="text"
required="required"
name="task" class="large"
placeholder="Breakfast at Tiffanys" />
</div>
<div>
<label>Required by</label> <input
type="date" required="required"
name="requiredBy" />
</div>
<div>
<label>Category</label> <select
name="category">
<option
value="Personal">Personal</option>
<option value="Work">Work</option>
</select>
</div>
<nav>
<a href="#" id="saveTask">Save
task</a>
<a href="#" id="clearTask">Clear
task</a>
</nav>
</form>
</section>
<section>
<table id="tblTasks">
<colgroup>
<col width="40%">
<col width="15%">
<col width="15%">
<col width="30%">
</colgroup>
<thead>
<tr>
<th>Name</th>
<th>Due</th>
<th>Category</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<nav>
<a href="#" id="btnAddTask">Add task</a>
</nav>
</section>
</main>
<footer>You have 3 tasks</footer>
</body>
<script>
$(document).ready(function() {
$('[required="required"]').prev('label').append(
'<span>*</span>').children(
'span').addClass('required');
$('tbody tr:even').addClass('even');
$('#btnAddTask').click(function(evt) {
evt.preventDefault();
$('#taskCreation').removeClass('not');
});
$('tbody tr').click(function(evt) {
$(evt.target).closest('td' ).siblings( ).andSelf(
).toggleClass( 'rowHighlight');
});
$(evt.target).parents('tr').remove();
});
$('#saveTask').click(function(evt) {
evt.preventDefault();
var task = $('form').toObject();
$('#taskRow').tmpl(task).appendTo(
$('#tblTasks tbody'));
});
});
</script>
</html>
}
}
}();
This code is implementing exactly the
same pattern as was implemented
earlier with the createIncrementer
function. The only difference here is
that we have not named the function
that is returning the object: it is an
anonymous function that we are
executing immediately by adding () to
the end of the line.
return {
init : function(page) {
if (!initialised) {
taskPage = page;
$(taskPage).find( '[required="required"]'
).prev('label').append( '<span>*</span>').children(
'span').addClass('required');
$(taskPage).find('tbody tr:even').addClass(
'even');
initialised = true;
}
}
}
}();
Validation
The next feature that we will add is an
essential feature of any form based
web application: form validation.
JavaScript has been used to perform
field validation since it first appeared
in browsers; in fact it was probably the
feature JavaScript was used for the
most in its early years.
Client side validation remains an
important use of JavaScript, but as
mentioned earlier in the book, HTML5
now contains a specification for form
validation based on form attributes.
This validation is intended to remove
the need for JavaScript validation.
Although it may not be obvious, this
has actually been implemented by
default by adding the required
attributes to our input fields. If you
press enter in the Task field without
entering any input you should see the
following:
http://ajax.aspnetcdn.com/ajax/jquery.va
I have downloaded version 1.11.1 and
added it to my scripts folder under the
name jquery.validate.js. I have also
added it to the head section of the web
page:
<head>
<meta charset="utf-8">
<title>Task list</title>
<link rel="stylesheet" type="text/css"
href="styles/tasks.css"
media="screen" />
<script src="scripts/jquery-2.0.3.js"></script>
<script src="scripts/jquery-tmpl.js"></script>
<script src="scripts/jquery.validate.js">
</script>
<script src="scripts/jquery-serialization.js"></script>
<script src="scripts/tasks-controller.js"></script>
</head>
if ($(taskPage).find('form').valid()) {
var task = $('form').toObject();
$('#taskRow').tmpl(task ).appendTo($(taskPage
).find( '#tblTasks tbody'));
}
});
Conclusion
The web application is still far from
complete at this stage, but the basic
project structure is now in place.
Before adding additional functionality
we are going to pause and begin
implementing the functionality to store
the application's data and resources
inside the browser.
of data:
1. They are included on each request to
the server, so unless you want the
entire task list to be included on each
request, cookies are not a good
solution.
2. The maximum size of each cookie is
approximately 5 kilobytes.
3. The maximum number of cookies
that a single domain can store is 20
(this can be browser dependent). This
means that the total amount of cookie
storage available to a domain is less
than 100 kilobytes.
It has therefore been evident for some
time that HTML needs some form of
offline data storage if it is to allow for
rich and dynamic web applications that
can exist independent of a web server.
Even applications that are continually
connected to a web server can
significantly improve user experiences
if they could cache larger quantities of
/**
* The client must call this to initialize a specific
object type in the storage engine.
* If the storage engine supports the object type the
successCallback will be invoked with a null value.
* It should be possible to call this method multiple
times, and the same result will be returned each
time.
* If the errorCallback is invoked then the object type
cannot be stored.
*
/**
* This can be used to find all the objects for a
specific type.
* If there are no objects found for that type this will
return an empty array.
*
* @param {String} type The type of object that
should be searched for.
* @param {Function} successCallback The callback
that will be invoked after the query completes. This
will be passed an array of objects conforming to the
requested type.
* @param {Function} errorCallback The callback
that will be invoked on error scenarios.
*/
function findAll(type, successCallback,
errorCallback)
/**
* This will return an object with a specific id for a
specific type.
* If no object is found this will return null
*
* @param {String} type The type of object that
should be searched for.
* @param {String|number} id The unique ID of the
object
* @param {Function} successCallback The callback
that will be invoked after the query completes. This
will be passed an object conforming to the requested
type or null.
* @param {Function} errorCallback The callback
that will be invoked on error scenarios.
*/
function findById(type, id, successCallback,
errorCallback)
/**
/*
* This will delete an object with a specific id for a
specific type.
* If no object exists with that id, the error callback
will be invoked.
* If an object is deleted this function will return the
id of the deleted object to the successCallback
*
* @param {String} type The type of object that will
be deleted.
* @param {String|number} id The unique id of the
object.
* @param {Function} successCallback The callback
that will be invoked after the object has been deleted
from the storage engine. This will be passed the
unique id of the deleted object.
* @param {Function} errorCallback The callback
that will be invoked on error scenarios.
*/
function delete(type, id, successCallback,
errorCallback)
/**
* This can be used for querying objects based on a
property value.
/*
* This will be called in all success scenarios.
* @param {any} result The success result, as
documented on individual method calls.
*/
function succssCallback(result)
/*
* This will be called in all failure scenarios.
* @param {String} errorCode The type of exception
* @param {String} errorMessage A human
readable version of the error message.
*/
function errorCallback(errorCode,
errorMessage)
> localStorage.getItem('item1')
"[object Object]"
//
Due to the fact JavaScript uses UTF16, the amount of character data it can
store in 5MB is approximately half the
amount as could have been stored with
UTF-8 (assuming we are using mainly
characters from the Latin alphabet).
Effectively this reduces the size of
Web storage to 2.5MB: this is still far
better than cookies, but is a limitation
we need to bear in mind when
//
http://rosettacode.org/wiki/LZW_
//
http://www.broofa.com/2008/09/j
uuid-function
}
},
initObjectStore : function(type, successCallback,
errorCallback) {
if (!initialized) {
errorCallback('storage_api_not_initialized',
'The storage engine has not been initialized');
} else if (!localStorage.getItem(type)) {
localStorage.setItem(type,
JSON.stringify({}));
}
initializedObjectStores[type] = true;
successCallback(null);
},
save : function(type, obj, successCallback,
errorCallback) {
},
findAll : function(type, successCallback,
errorCallback) {
},
delete : function(type, id, successCallback,
errorCallback) {
},
},
function(errorCode, errorMessage) {
console.log(errorCode+':'+errorMessage);
}
);
"{}"
//
code:
storageEngine.init(function() {
storageEngine.initObjectStore('task', function() {
}, errorLogger)
}, errorLogger);
obj.id = $.now();
}
var savedTypeString = localStorage.getItem(type);
var storageItem = JSON.parse(savedTypeString);
storageItem[obj.id] = obj;
localStorage.setItem(type,
JSON.stringify(storageItem));
successCallback(obj);
}
if (!initialized) {
errorCallback('storage_api_not_initialized', 'The
storage engine has not been initialized');
} else if (!initializedObjectStores[type]) {
errorCallback(store_not_initialized', 'The object
store '+type+' has not been initialized');
}
var result = [];
var storageItem = getStorageObject(type);
$.each(storageItem, function(i, v) {
result.push(v);
});
successCallback(result);
}
})
}
}
to
to this:
$(taskPage).find('#tblTasks tbody').on('click',
'.deleteRow',
function(evt) {
storageEngine.delete('task',
$(evt.target).data().taskId, function() {
$(evt.target).parents('tr').remove();
}, errorLogger);
}
);
This will allow us to serialize and deserialize all properties of the task, not
just the visible properties.
Next, change the edit button in the
template from this:
<a href="#">Edit</a>
to this:
<a href="#" class="editRow" data-taskid="${id}">Edit</a>
}, errorLogger);
}
);
tasksController.loadTasks();
$(':input').val('');
$(taskPage).find('#taskCreation'
).addClass( 'not');
}, errorLogger);
}
});
//
evil"
if (window.localStorage) {
initialized = true;
successCallback(null);
} else {
errorCallback('storage_api_not_supported',
'The web storage api is not supported');
}
},
initObjectStore : function(type, successCallback,
errorCallback) {
if (!initialized) {
errorCallback('storage_api_not_initialized',
'The storage engine has not been initialized');
} else if (!localStorage.getItem(type)) {
localStorage.setItem(type, JSON.stringify({}));
}
initializedObjectStores[type] = true;
successCallback(null);
},
save: function(type, obj, successCallback,
errorCallback) {
if (!initialized) {
errorCallback('storage_api_not_initialized',
errorCallback("object_not_found","The object
requested could not be found");
}
},
findByProperty : function(type, propertyName,
propertyValue, successCallback, errorCallback) {
if (!initialized) {
errorCallback('storage_api_not_initialized',
'The storage engine has not been initialized');
} else if (!initializedObjectStores[type]) {
errorCallback('store_not_initialized', 'The
object store '+type+' has not been initialized');
}
var result = [];
var storageItem = getStorageObject(type);
$.each(storageItem, function(i, v) {
if (v[propertyName] === propertyValue) {
result.push(v);
}
});
successCallback(result);
},
findById : function (type, id, successCallback,
errorCallback) {
if (!initialized) {
errorCallback('storage_api_not_initialized',
'The storage engine has not been initialized');
} else if (!initializedObjectStores[type]) {
errorCallback('store_not_initialized', 'The
object store '+type+' has not been initialized');
}
var storageItem = getStorageObject(type);
var result = storageItem[id];
successCallback(result);
}
}
}();
IndexedDB Implementation
We will now implement the storage
engine using the IndexedDB API. Due
to the fact we are using the same API
we will not need to change anything
else in our application. The taskscontroller.js does not care how the
storage engine is implemented, only
that the API behaves as advertised.
IndexedDB is a more sophisticated
API than Web storage. Although it is an
object database rather than a classic
relational database, it incorporates
concepts that are familiar to those who
have worked with relational databases,
such as transactions and indexes.
IndexedDB always operates with
asynchronous callbacks, but by default
it does still use the main browser
thread. It is also possible for browser
vendors to implement the IndexedDB
API with Web Workers (a subject that
will be introduced in later chapters),
errorCallback) {
},
save : function(type, obj, successCallback,
errorCallback) {
},
findAll : function(type, successCallback,
errorCallback) {
},
delete : function(type, id, successCallback,
errorCallback) {
},
findByProperty : function(type, propertyName,
propertyValue, successCallback, errorCallback) {
},
findById : function (type, id, successCallback,
errorCallback) {
}
}
}();
1)
request.onsuccess = function(event) {
database = request.result;
successCallback(null);
}
request.onerror = function(event) {
errorCallback('storage_not_initalized', 'It is
not possible to initialized storage');
}
} else {
errorCallback('storage_api_not_supported',
'The web storage api is not supported');
}
},
initObjectStore : function(type, successCallback,
errorCallback) {
},
save : function(type, obj, successCallback,
errorCallback) {
},
findAll : function(type, successCallback,
errorCallback) {
},
delete : function(type, id, successCallback,
errorCallback) {
},
findByProperty : function(type, propertyName,
propertyValue, successCallback, errorCallback) {
},
findById : function (type, id, successCallback,
errorCallback) {
}
}
}();
//
if (!database) {
errorCallback( 'storage_api_not_initialized', 'The
storage engine has not been initialized');
}
var exists = false;
$.each(database.objectStoreNames, function(i, v) {
if (v == type) {
exists = true;
}
});
if (exists) {
successCallback(null);
} else {
var version = database.version+1;
database.close();
var request =
indexedDB.open(window.location.hostname+'DB',
version);
request.onsuccess = function(event) {
successCallback(null);
}
request.onerror = function(event) {
tasksController.loadTasks();
});
if (!database) {
errorCallback('storage_api_not_initialized', 'The
storage engine has not been initialized');
}
if (!obj.id) {
delete obj.id ;
} else {
obj.id = parseInt(obj.id)
}
var tx = database.transaction([type], "readwrite");
tx.oncomplete = function(event) {
successCallback(obj);
};
tx.onerror = function(event) {
errorCallback('transaction_error', 'It is not
possible to store the object');
};
var objectStore = tx.objectStore(type);
var request = objectStore.put(obj);
request.onsuccess = function(event) {
obj.id = event.target.result
}
request.onerror = function(event) {
errorCallback('object_not_stored', 'It is not
possible to store the object');
};
}
//
};
tx.onerror = function(event) {
errorCallback('transaction_error', 'It is not possible
to store the object');
};
//
function(event) {
var cursor = event.target.result;
if (cursor) {
result.push(cursor.value);
cursor.continue();
} else {
successCallback(result);
}
};
request.onerror = function(event) {
errorCallback('object_not_stored', 'It is not
possible to locate the requested object');
};
}
Indexes can either be unique or nonunique. The index for the category
property on the task object would need
to be non-unique, since many tasks can
have the same category.
With that in place we could search for
all tasks with a specific category by
first obtaining a reference to the index
from the object store:
var index = objectStore.index("category");
if (cursor.value[propertyName] ==
propertyValue) {
result.push(cursor.value);
}
cursor.continue();
} else {
successCallback(result);
}
};
}
database = request.result;
successCallback(null);
}
request.onerror = function(event) {
errorCallback('storage_not_initalized', 'It is
not possible to initialized storage');
}
} else {
errorCallback('storage_api_not_supported',
'The web storage api is not supported');
}
},
initObjectStore : function(type, successCallback,
errorCallback) {
if (!database) {
errorCallback('storage_api_not_initialized',
'The storage engine has not been initialized');
}
var exists = false;
$.each(database.objectStoreNames, function(i,
v) {
if (v == type) {
exists = true;
}
});
if (exists) {
successCallback(null);
} else {
var version = database.version+1;
database.close();
var request =
indexedDB.open(window.location.hostname+'DB',
version);
request.onsuccess = function(event) {
successCallback(null);
}
request.onerror = function(event) {
errorCallback('storage_not_initalized', 'It is
not possible to initialized storage');
}
request.onupgradeneeded = function(event) {
database = event.target.result;
var objectStore =
database.createObjectStore(type, { keyPath: "id",
autoIncrement: true });
}
}
},
save : function(type, obj, successCallback,
errorCallback) {
if (!database) {
errorCallback('storage_api_not_initialized',
'The storage engine has not been initialized');
}
if (!obj.id) {
delete obj.id ;
} else {
obj.id = parseInt(obj.id)
}
var tx = database.transaction([type],
"readwrite");
tx.oncomplete = function(event) {
successCallback(obj);
};
tx.onerror = function(event) {
errorCallback('transaction_error', 'It is not
possible to store the object');
};
var objectStore = tx.objectStore(type);
cursor.continue();
} else {
successCallback(result);
}
};
},
delete : function(type, id, successCallback,
errorCallback) {
var obj = {};
obj.id = id;
var tx = database.transaction([type],
"readwrite");
tx.oncomplete = function(event) {
successCallback(id);
};
tx.onerror = function(event) {
console.log(event);
errorCallback('transaction_error', 'It is not
possible to store the object');
};
var objectStore = tx.objectStore(type);
var request = objectStore.delete(id);
request.onsuccess = function(event) {
}
request.onerror = function(event) {
errorCallback('object_not_stored', 'It is not
possible to delete the object');
};
},
findByProperty : function(type, propertyName,
propertyValue, successCallback, errorCallback) {
if (!database) {
errorCallback('storage_api_not_initialized',
'The storage engine has not been initialized');
}
var result = [];
var tx = database.transaction(type);
var objectStore = tx.objectStore(type);
objectStore.openCursor().onsuccess =
function(event) {
var cursor = event.target.result;
if (cursor) {
if (cursor.value[propertyName] ==
propertyValue) {
result.push(cursor.value);
}
cursor.continue();
} else {
successCallback(result);
}
};
},
findById : function (type, id, successCallback,
errorCallback) {
if (!database) {
errorCallback('storage_api_not_initialized',
'The storage engine has not been initialized');
}
var tx = database.transaction([type]);
var objectStore = tx.objectStore(type);
var request = objectStore.get(id);
request.onsuccess = function(event) {
successCallback(event.target.result);
}
request.onerror = function(event) {
errorCallback('object_not_stored', 'It is not
possible to locate the requested object');
};
}
}
}();
and
if (window.indexedDB) {
//
with this:
if (window.indexedDB) {
$.getScript( "scripts/tasks-indexeddb.js" )
.done(function( script, textStatus ) {
$(document).ready(function() {
tasksController.init($('#taskPage'), function() {
tasksController.loadTasks();
});
})
})
.fail(function( jqxhr, settings, exception ) {
console.log( 'Failed to load indexed db script' );
});
} else if (window.localStorage) {
$.getScript( "scripts/tasks-webstorage.js" )
.done(function( script, textStatus ) {
$(document).ready(function() {
tasksController.init($('#taskPage'), function() {
tasksController.loadTasks();
});
})
})
.fail(function( jqxhr, settings, exception ) {
console.log( 'Failed to load web storage script'
);
});
}
})
.fail(function( jqxhr, settings, exception ) {
console.log( 'Failed to load web storage script' );
});
}
to this:
<footer>You have <span id="taskCount"></span>
tasks</footer>
function taskCountChanged() {
var count = $(taskPage).find( '#tblTasks
tbody tr').length;
$('footer').find('#taskCount').text(count);
}
});
taskCountChanged();
}, errorLogger);
}
Clear Task
Next we want to implement the clear
task functionality. This should be
invoked when the Clear Task button
is clicked, but should also be invoked
after a save.
There is a very convenient way to
implement the clear task functionality.
Add the following code to the private
section of tasks-controller.js:
function clearTask() {
$(taskPage).find('form').fromObject({});
}
registered:
$(taskPage).find('#clearTask').click(function(evt) {
evt.preventDefault();
clearTask();
});
to the following:
clearTask();
Overdue tasks
The next feature we will add is
functionality to render the background
of rows to show tasks that are due in
the next 2 days in orange, and tasks that
are overdue in a light red colour.
Before doing so, you may have noticed
that this functionality to highlight rows
no longer works:
$(taskPage).find('tbody tr').click(function(evt) {
$(evt.target).closest('td').siblings().andSelf().toggleCla
});
$(evt.target).closest('td').siblings().andSelf().toggleCla
});
.warning {
background: #F7F7DC;
}
//
> (2).months().ago()
> (3).days().fromNow();
if (due.compareTo(Date.today()) < 0) {
$(row).addClass("overdue");
} else if (due.compareTo((2).days().fromNow())
<= 0) {
$(row).addClass("warning");
}
});
}
Completing tasks
Next we will implement the Complete
task functionality. If a task is
completed we want to render the text
with a strike through it. This will be
achieved with the following class from
tasks.css:
.taskCompleted {
text-decoration: line-through;
}
<td>${task}</td>
<td><time datetime="${requiredBy}">
${requiredBy}</time></td>
<td>${category}</td>
<td>
<nav>
<a href="#" class="editRow" data-taskid="${id}">Edit</a>
<a href="#" class="completeRow" datatask-id="${id}">Complete</a>
<a href="#" class="deleteRow" data-taskid="${id}">Delete</a>
</nav>
</td>
</tr>
</script>
storageEngine.findById('task',
$(evt.target).data().taskId, function(task) {
task.complete = true;
storageEngine.save('task', task, function() {
tasksController.loadTasks();
},errorLogger);
}, errorLogger);
});
taskCountChanged();
renderTable();
});
}, errorLogger);
}
true}}class="taskCompleted"{{/if}}>${category}
</td>
<td>
<nav>
{{if complete != true}}
<a href="#" class="editRow" data-taskid="${id}">Edit</a>
<a href="#" class="completeRow" datatask-id="${id}">Complete</a>
{{/if}}
<a href="#" class="deleteRow" data-taskid="${id}">Delete</a>
</nav>
</td>
</tr>
</script>
as follows:
We can still delete a completed task,
but we can no longer edit or complete
them.
Sorting tasks
The final change we will make is to
sort tasks so that the ones due earliest
are sorted first.
We will again use the date library for
parsing and comparing dates, but we
will use the standard sort method
available on arrays to perform the
sorting.
The sort method accepts a comparator
as an argument. A comparator is a
function that can compare any two
elements in an array and determine
which ranks higher. A comparator
should return a value less than 1 to
indicate the first element is higher, 0 if
they are equal, or a number greater
than 1 if the second element ranks
higher. Date objects already have a
compareTo method that performs this
task.
The sort method will compare the
relevant items in the array to determine
renderTable();
});
}, errorLogger);
}
Conclusion
This completes the basic requirements
for the tasks web applications. In the
next few chapters we will incorporate
features from more advanced HTML5
APIs to allow offline storage of the
web application, and the use of files in
the application.
environment):
CACHE MANIFEST
tasks.html
scripts/date.js
scripts/jquery-2.0.3.js
scripts/jquery-serialization.js
scripts/jquery-tmpl.js
scripts/jquery.validate.js
scripts/tasks-controller.js
scripts/tasks-indexeddb.js
scripts/tasks-webstorage.js
styles/tasks.css
FALLBACK:
NETWORK:
section is as follows:
NETWORK:
*
FALLBACK:
NETWORK:
*
for example:
scripts/tasks-indexeddb.js?_=1379463013652
window.applicationCache.addEventListener('download
function(e) {});
window.applicationCache.addEventListener('progress'
function(e) {});
window.applicationCache.addEventListener(updaterea
function(e) {});
revised application:
window.location.reload();
selected.
HTML5 includes several APIs for
interacting with the users file-system.
The most ambitious of these is the File
System and File Writer APIs. These
specifications have been proposed by
Google and are currently only
supported in Chrome. They allow a
web application to read files, create
directories and write files in a virtual
file-system.
The word virtual is the key here.
Each origin will be given its own
virtual file-system that it has full
control over, but this will be
partitioned from both the users real
file-system, and the file-system of any
pages loaded from any other origin. In
addition, depending on how the API is
implemented, the virtual file-system
may not exist as a set of files and
directories on the users file-system (it
could be implemented as a binary
database).
As a result of the other storage options
we have examined in earlier chapters,
these APIs are of limited use, and you
may be advised to continue using the
other storage options we have
discussed.
The other major file related API is the
FileReader API. This is a simpler, but
in many ways more interesting API. It
relies on the HTML file form field, but
once a file is selected it provides the
developer access to the file contents in
JavaScript. This means it is possible to
perform local parsing of a file without
ever submitting it to the web server.
This has several advantages:
1. The user can load files when they
are offline (provided the web
application has been made to work
offline using the other techniques
outlined in this book).
function loadFromCSV(event) {
var reader = new FileReader();
reader.onload = function(evt) {
console.log(evt.target.result);
};
reader.onerror = function(evt) {
errorLogger('cannot_read_file', 'The file specified
cannot be read');
};
reader.readAsText(event.target.files[0]);
}
more stable.
In this scenario we will use this
library:
https://code.google.com/p/jquery-csv/
Download the latest version and add it
to the scripts folder of the application.
I am using version 0.71.
Next, remember to include the import
in the page at the end of the head
section of tasks.htmk, and also
remember to add a reference to the file
in tasks.appcache, since we want this
library to be available offline.
<script src="scripts/jquery.csv-0.71.js"></script>
function loadFromCSV(event) {
var reader = new FileReader();
reader.onload = function(evt) {
var contents = evt.target.result;
var lines = contents.split('\n');
var tasks = [];
$.each(lines, function(indx, val) {
if (indx >= 1 && val) {
var task = loadTask(val);
if (task) {
tasks.push(task);
}
}
});
};
reader.onerror = function(evt) {
errorLogger('cannot_read_file', 'The file specified
cannot be read');
};
reader.readAsText(event.target.files[0]);
}
if (!obj.id) {
obj.id = $.now();
}
storageItem[obj.id] = obj;
localStorage.setItem(type,
JSON.stringify(storageItem));
});
successCallback(objs);
}
function() {
tasksController.loadTasks();
},errorLogger);
};
reader.onerror = function(evt) {
errorLogger('cannot_read_file', 'The file specified
cannot be read');
};
reader.readAsText(event.target.files[0]);
}
Web Workers
Earlier in this book we discussed how
JavaScript utilizes a single thread to
perform all processing, and that this is
the same thread the browser uses to
render the web page. Although there
are rudimentary ways of controlling
when a piece of code executes using
setTimeout, there is no way to execute
JavaScript on a seperate thread, or in
multiple threads concurrently.
Most modern computers, and even
most smart phones, are capable of
executing multiple threads concurrently
since most devices now contain
multiple processors or multiple cores.
In addition, most software engineers
expect multithreading libraries to be
built into modern languages.
There are very good reasons why
JavaScript is limited to a single thread,
for instance, to prevent multiple
threads attempted to update the same
function loadTask(csvTask) {
var tokens = $.csv.toArray(csvTask);
if (tokens.length == 3) {
var task = {};
task.task = tokens[0];
task.requiredBy = tokens[1];
task.category = tokens[2];
return task;
}
return null;
}
function loadTask(csvTask) {
var tokens = csvTask.split(',');
if (tokens.length == 3) {
var task = {};
task.task = tokens[0];
task.requiredBy = tokens[1];
task.category = tokens[2];
return task;
}
return null;
}
worker.postMessage(contents);
};
reader.onerror = function(evt) {
errorLogger('cannot_read_file', 'The file specified
cannot be read');
};
reader.readAsText(event.target.files[0]);
}
port.start();
}, false);
Conclusion
Web Workers offer interesting
potentials to Web Applications: the key
is finding the appropriate use-cases.
The most viable use-cases are
scenarios where the foreground thread
can continue to be utilized while
additional code is executed on the
background thread. The foreground
thread may be doing something as
trivial as updating a progress bar for
the user: but the benefit of Web
Workers is that they will not prevent
this happening since they are not
hogging the foreground thread.
The other main use-case for Web
Workers is algorithms that can
naturally be multi-threaded. In these
cases we can spawn multiple Web
Workers, and have each of them
complete a portion of the algorithm. On
devices with multiple cores or
processors this is likely to lead to
AJAX
The web application we have written
in this book is largely independent of
its web server. The web server is
responsible for serving resources, and
providing an origin for APIs that
implement single-origin policies, but
beyond that it is not playing any
meaningful role, and in fact, once the
application is loaded it can exist
independent of the web server.
Most real world web applications
need to send or receive data to a web
server at some point in their lifecycles.
For instance, users may wish to
synchronize their task list in our
application between multiple devices.
There are two ways a web application
can interact with a web server. The
most common approach is to perform
HTTP POST or GET to the server, and
load the HTTP response into the
browser.
//
In fact, XMLHttpRequest is
not dependent on HTTP
either; it is supported with
other protocols such as FTP.
In
many
respects
XMLHttpRequest is the worst
named object in the history of
JavaScript.
console.log(data);
}
});
beyond.
If you inspect this object in the
debugger it looks like this:
promises.
promise = $.ajax({
type : "GET",
url : "/server/tasks.json",
cache : false
});
promise.done(function(data) {
console.log(data);
});
promise.fail(function() {
console.log('A failure occurred');
});
url : "/server/tasks.json",
});
promise2= $.ajax({
url : "/server/tasks.json",
});
$.when(promise1, promise2).done(function(data1,
data2) {
console.log(data1[0]);
console.log(data2[0]);
console.log("Both requests have completed");
});
promise.fail(function() {
console.log('A failure occurred');
});
getTasks : function() {
var deferred = $.Deferred();
if (tasks) {
deferred.resolve(tasks);
return deferred.promise();
} else {
var promise1 = $.ajax({
url : "/server/tasks.json",
});
promise1.done(function(data) {
tasks = data;
setTimeout(function()
{deferred.resolve(tasks)}, 5000);
})
return deferred.promise();
}
}
}
}();
Web Sockets
As discussed above, Server Sent
Events continue to utilize standard
HTTP requests and responses, but
allow the server (rather than the client)
to initiate requests.
Web sockets on the other hand are an
API for real-time, bi-directional
communication between the client and
server using a TCP based protocol.
HTTP does use TCP as the underlying
transport protocol, but HTTP is
designed for larger payloads, and is
not intended to be conversational.
Web Sockets are designed to allow the
client and server to be as chatty with
each other as they like by imposing
minimal overhead on each message
sent and received. In addition, Web
Socket connections are full duplex: so
it is possible to send and receive data
simultaneously on the same connection.
Although Web Sockets uses a distinct
Error Handling
Throughout this book we have
developed a web application. There is
one notable step that we have missed
in this development however: a
strategy for handling errors.
Errors can occur in a web application
for many reasons, but it is worth
subdividing errors into two categories:
1. Errors that can be anticipated. An
example of this is an AJAX call that
fails because the user is not connected
to the Internet. We will call these
exceptions, since they are exceptions
to the rule.
2. Errors that are not anticipated.
These errors normally arise from
programming bugs, such as accessing
an element in an array that does not
exist, or accessing a property on an
undefined object. We will call these
errors, since they are errors in the
implementation of the software rather
Detecting Errors
The first step in implementing an error
handling strategy is detecting the fact
that errors have occurred. The first
type of error can be handled by the
try/catch block within functions, or via
registered callbacks on asynchronous
APIs. All APIs that utilize
asynchronous callbacks differentiate
between success and failure scenarios,
and we have also seen jQuery
promises provide direct support for
this.
The second type of exception can be
handled with try/catch blocks, but in
general should not be. By definition,
these errors should not occur, therefore
you should prevent them from
occurring rather than handling them.
For instance, consider a scenario
where we have been passed an object
that may be undefined, and we wish to
access a property on it. We could write
this as follows:
function accept(obj) {
try {
console.log(obj.property1);
} catch (e){}
}
Handling Errors
Now that we have detected errors, the
next step is deciding what to do with
them. It is important that you, as the
application programmer, understand
that these errors have occurred. Unlike
with a server however, there is no
centralized log file containing all the
errors that have occurred.
In the case of exceptions, the
requirements should specify what to do
if an exception occurs. For instance, if
an AJAX call cannot be performed:
Should the user be informed?
Should we try to carry on the
best we can without the data
from the AJAX call?
Should we prevent the user
performing any other actions on
the web page?
The answers to these questions depend
on the requirements. Detecting the
exception is the first step, but detection
Conclusion
When assessing technology for new
projects I like to apply a 10-year rule:
what will people think of this
technology in 10 years time?
Will the software continue to function
as-is on the devices commonly used
in 10-years time?
Will the software continue to function
on existing devices, but need to be
rewritten for new devices?
Will the software need to be
upgraded (and maybe rewritten) to
continue its relevance?
Will the software be obsolete and
need to be discarded?
Will anyone even remember this
technology?
The 10-year horizon seems sensible
for me. Vendors make a huge
investment in the software they
Monopoly Power
HTML and JavaScript are in a
particularly strong position due to their
monopoly position in the web browser,
and the fact that web browsers exist
for virtually all consumer computing
devices (from phones, to games
consoles, to tablets and laptops, to
PCs).
Until recently Micorsoft Windows held
such a dominant position in operating
systems that it made sense for vendors
to produce applications that worked
only in Microsoft Windows. Thanks to
the explosion of hand-held devices,
and the resurgence of Apple, this is no
longer the case. Many people now use
2 or 3 distinct operating systems on a
daily basis:
A Microsoft Windows PC at work
An Adnroid mobile phone
An OSX laptop at home
them.
The End
I hope this book has convinced you not
only the value and utility of HTML5,
JavaScript and jQuery; but also their
underlying beauty and elegance. The
future is remarkably bright for these
languages, and the software engineers
who harness their power. I wish you
all the best in your endeavours, and
hope this book has provided the
foundations you need to write your
own web applications.
I would like to thank you for choosing
to read this book. As an independent
publisher of Software Engineering
books, reviews from readers are an
important tool both to improve our
books, and spread the word to new
readers. If possible, please take the
time to review this book on Amazon,
or email me directly at
[email protected].
//
Selecting elements
CSS consists of a set of stylistic
properties that should be applied to a
set of elements. The following is an
example of two properties being
applied to all elements of type footer:
footer {
font-size: 12px;
text-align:center;
}
}
.myfooter {
background:blue;
}
# footer1 {
font-size:12pt;
}
color:red;
}
Positioning Elements
In order to fully understand the
position an element will take on
screen, and the way it interacts with
adjacent elements, it is also necessary
to take into account the value of the
display property.
This can be one of 4 primary values:
inline: this is the default for span, a
and td elements.
block: this is the default for most
other elements, including div, p and
most other elements.
inline-block: this is only the default
for the img element, but is commonly
assigned to other elements in CSS.
none: the element should be hidden
from view.
It is possible to override the display
property of any elements as follows:
.myclass {
display:inline-block;
}
<html>
<head>
<style>
header {
height:100px;
background: blue;
}
.menu {
float:left;
width:20%;
height:400px;
background: red;
}
.content {
float:right;
height:400px;
width:80%;
background: green;
}
footer {
clear:both;
height: 100px;
background: orange;
}
</style>
</head>
<body>
<header>
Header
</header>
<div class="menu">
This is the menu
</div>
<div class="content">
This is the content
</div>
<footer>
Footer
</footer>
</body>
</html>
//
Inheritance
The final fundamental aspect to
understanding CSS is understanding
when styles from the parent are
inherited by their children and when
they are not. For instance, consider the
following HTML:
<div>
<span>this is text</span>
<span>this is more text</span>
</div>
Debugging
Chrome makes it relatively simple to
debug elements, and dynamically add
styles. It is possible to right click on
any element and select Inspect
Element. This will load the element
into the Elements tab of the
developer tools, and show the CSS
properties that have been applied to
the element.
Conclusion
The goal of this chapter is to provide
you the fundamentals of CSS. Like
many of the other web languages in this
book, CSS appears simple, but without
understanding the fundamentals it can
be enormously frustrating.
Appendix B: Recommended
Libraries
This chapter will provide a brief
introduction to some of the JavaScript
libraries I have found useful when
developing web applications.
Underscore
If you ever find yourself asking why
doesnt JavaScript have a function to
do x?, chances are it will exist in the
Underscore library. Underscore
contains a wide array of utility
functions for managing arrays, objects
and functions, along with templating
functionality similar to that used with
jQuery Template.
The library, along with its API can be
found here:
http://underscorejs.org/
jQuery UI
jQuery UI contains a wide range of UI
widgets, ranging from progress bars, to
dialogs, to drag and drop based lists.
The library itself is reasonably large,
but the capabilities of the widgets are
extensive.
It is also possible to selectively
include widgets from the library in
your applications.
The project can be found here:
http://jqueryui.com/
Datatable
One important widget not included in
jQuery UI is a table. It is common for
web applications to require tables
supporting of sorting, filtering and
pagination.
The Datatable library provides a
wrapper over top of regular HTML
tables, and converts them into the rich
and dynamic tables programmers are
used to in desktop GUI toolkits:
https://datatables.net/
D3
D3 is a library for producing data
driven documents. Charts and graphs
are examples of data driven
documents, but D3 can produce data
bound documents of amazing
complexity and elegance.
A full set of demos can be found at the
project home page:
http://d3js.org/
Log4j JS
This library provides a logging API for
JavaScript similar to the log4j (and
related) library popular with Java.
This API allows you to write custom
appenders to dictate logging strategies,
and provides an AJAX appender to
send logs to the server.
http://log4js.berlios.de/