Let’s start working on our web application, Save The Child. This web application will contain a form for donations to sick children and an embedded video player, will integrate with Google Maps, will have charts, and more. The goal is for you to gradually build all the functionality of this web application while we explain each step so that you can understand why we are building it the way we do. By the end of this chapter, you’ll have the web design and the first prototype of Save The Child.
The proliferation of mobile devices and web applications requires new skills for development of what used to be boring-looking enterprise applications. In the past, design of the user interface (UI) of most enterprise applications was done by developers to the best of their artistic abilities: a couple of buttons here, and a grid there, on a gray background. Business users were happy because they did not know any better. The application allowed users to process business data—what else was there to wish for? Enterprise business users used to be happy with any UI, as long as the application helped them to take care of their business.
But today’s business users are spoiled by nice-looking consumer-facing applications, and more often than not, new development starts by inviting a web designer to create a prototype of the future application. For example, we’ve seen some excellent (from the UI perspective) functional specifications for boring financial applications made by professional designers. Business users are slowly but surely becoming more demanding in the area of UI design solutions. The trend is clear: a developer’s art does not cut it anymore.
In enterprise IT shops, web design is usually done by a professional web designer. Software developers are not overly familiar with the tools that web designers are using. But to make this book useful even for smaller shops that can’t afford professional web design, we illustrate the process of design and prototyping of the UI of a web application.
Our web designer—let’s call him Jerry—is ready to start working on the mockup (a.k.a. wireframes); this is a set of images depicting various views of the future Save The Child application. We expect him to deliver images with comments that briefly explain what should change in a view if a user takes certain actions (for example, clicks a button). You can also think of an application’s UI as a set of states, and the user’s action results in your application transitioning from one state to another. As nerds and mathematicians say, the UI of your application is a finite state machine, which at any given point in time is in one of a finite number of states (for example, in the view state Donate Form or Auction).
While starting work on the design of a new web application, keep in mind that some users likely will access it from mobile devices. Will the proposed UI look good on mobile devices with smaller screens? Some people suggest using a so-called Mobile First approach, which means that from the very early stages of web application development, you should do the following:
-
Ensure that your web application (design and layout) looks good on smaller screens.
-
Differentiate the content to be shown on large versus small screens (start with small screens and enhance for the larger ones).
-
Test your application on slow (3G-like) networks and minimize the "weight" of the landing page.
-
Decide on an approach: should you use responsive web design (see [responsive_design]), HTML5 mobile frameworks, native, or hybrid (see [hybrid_mobile_apps])?
-
If you are planning to use geographical location services, decide on the API to be used for mobile devices, but don’t forget about desktops, too.
Note
|
Users of iOS and Android devices are used to being able to find the closest restaurant or gas station based on their current location. Did you know that this location feature can be available on desktops, too? Google Maps is just one of the services that can find the location of a user’s desktop based on its IP address, WiFi router’s ID, or proximity to cell towers. Zeroing in on your device might not be as precise as with a smartphone’s GPS, but it might be good enough. So, why not plan on adding this feature to all versions of your web application? Finding the closest charity event or a local child in need can be done by knowing an approximate location of your desktop computer. |
Let’s consider pointing devices. At the time of this writing, the vast majority of desktop users work with pixel-perfect mouse pointers or track pads. Smartphone or tablet users work with their fingers. One finger touch can cover a square comprising roughly 100 pixels. CNN’s site, for example, shows lots of news links located very close to one another on the screen. A finger might cover more than one link, and Android devices offer you a larger pop-up, allowing you to select the link you really wanted to touch. Having a Mobile First state of mind doesn’t mean that CNN needs to keep a larger distance between links for all the users. However, it does mean that CNN should foresee the issues or innovate using the features offered by modern mobile devices.
[responsive_design] covers the responsive web design techniques that allow us to create UIs for web applications that automatically re-allocate screen content based on the size of the display on the user’s device. Although this chapter is about the desktop version of the Save The Child web application, its screen will consist of several rectangular areas that can be allocated differently (or even hidden) on smartphones or tablets.
Note
|
Before writing this book, we discussed how our application should look and work on mobile devices. But strictly speaking, because the work on multiple chapters was done in parallel, this was not a Mobile First approach. |
Tip
|
Consider reading [sencha_touch] now to better understand what you will need to deal with when developing web applications that look good on desktop monitors as well as on mobile screens. Understanding responsive design principles will help you in communications with your web designer. |
One of the constraints that mobile users have is the relatively slow speed of the mobile Internet. This means that even though your desktop users will use fast LAN connection lines, your web application has to be modularized so that only a minimal number of modules has to be loaded initially. Often, mobile providers charge users based on the amount of consumed data, too.
The chances are slim that desktop users will lose an Internet connection for a long period of time. On the other hand, mobile users might stay in an area with no connection or a spotty one. In this case, the Mobile First thinking can lead to introducing an offline mode with limited functionality.
Thinking up front of the minimal content to be displayed on small mobile screens might force you to change the design of desktop web pages, too. In our sample Save The Child application, we need to make sure that there is enough space for the Donate Now button even on the smallest devices.
Visualize a project owner talking to our web designer, Jerry, in a cafeteria, and Jerry is drawing sketches of the future website on a napkin. Well, in the 21st century, he’s using an electronic napkin, so to speak—an excellent prototyping tool called Balsamiq Mockups. This easy-to-use program gives you a working area where you create a mockup of your future web application by dragging and dropping the required UI components from the toolbar onto the image of the web page (see The working area of Balsamiq Mockups).
If you can’t find the required image in Balsamiq’s library, add your own by dragging and dropping it onto the top toolbar. For example, the mockup in [responsive_design] uses our images of the iPhone that we’ve added to Balsamiq assets.
Tip
|
If you prefer using free tools, consider using MockFlow. |
When the prototype is done, it can be saved as an image and sent to the project owner. Another option is to export the Balsamiq project into XML, and if both the project owner and web designer have Balsamiq installed, they can work on the prototype in collaboration. For example, the designer exports the current state of the project, the owner imports it and makes corrections or comments, and then exports it again and sends it back to the designer.
During the first meeting, Jerry talks to the project owner about the required functionality and then creates the UI to be implemented by web developers. The artifacts produced by a designer vary depending on the qualifications of that designer. For instance, a set of images might represent different states of the UI with little callouts explaining the navigation of the application. If the web designer is familiar with HTML and CSS, developers might get a working prototype in the form of HTML and CSS files, and this is exactly what Jerry will create by the end of this chapter.
Our project owner says to Jerry: "The Save The Child web application should allow people to make donations to the children. Users should be able to find these children by specifying a geographical area on the map. The application should include a video player and display statistics about donors and recipients. The application should include an online auction, with proceeds going to the charity. We’ll start working on the desktop version first, but your future mockup should include three versions of the UI, supporting desktops, tablets, and smartphones."
After the meeting, Jerry launches Balsamiq and begins to work. He decides that the main window will consist of four areas laid out vertically:
-
The header with the logo and several navigation buttons
-
The main area with the Donate section plus the video player
-
The area with statistics, and charts
-
The footer with several housekeeping links plus the icons for Twitter and Facebook
The first deliverable of our web designer (see Figures and ) depicts two states of the UI: before and after clicking the Donate Now button. The web designer suggests that on the button click, the video player turn into a small button revealing the donation form.
The project owner suggests that turning the video into a Donate Now button might not be the best idea. We shouldn’t forget that the main goal of this application is collecting donations, so they decide to keep the user’s attention on the Donate area and move the video player to the lower portion of the window.
Next, they review the mockups of the authorization routine. The view states in this process can be:
-
Not Logged On
-
The Login Form
-
Wrong ID/Password
-
Forgot Password
-
Successfully Logged On
The web designer’s mockups of some of these states are shown in Figures and .
The latter shows different UI states should the user decide to log in. The project owner reviews the mockups and returns them to Jerry with some comments. The project owner wants to make sure that the user doesn’t have to log on to the application to access the website. The process of making donations has to be as easy as possible, and forcing the donor to log on might scare some people away, so the project owner leaves the comment shown in The user hasn’t clicked the Login button.
This is enough of a design for us to build a working prototype of the app and start getting feedback from business users. In the real world, when a prospective client (including business users from your enterprise) approaches you, asking for a project estimate, provide a document with a detailed work breakdown and screenshots made by Balsamiq or a similar tool.
We are lucky, because Jerry knows HTML and CSS. He’s ready to turn the still mock-ups into the first working prototype. It will use only hardcoded data, but the layout of the site will be done in CSS and will use HTML5 markup. He’ll design this application as a single-page application (SPA).
An SPA is an architectural approach that doesn’t require the user to go through multiple pages to navigate a site. The user enters the URL in the browser, which brings up the web page that remains open on the screen until the user stops working with that application. A portion of the user’s screen might change as the user navigates the application, new data might come in via Ajax techniques (see [using_ajax_and_json]), or new DOM elements might need to be created during runtime, but the main page itself isn’t reloaded. This allows building so-called fat client applications that can remember the state. Besides, most likely your HTML5 application will use a JavaScript framework, which in SPA is loaded only once, when the home page is created by the browser.
Have you ever seen a monitor of a trader working for a Wall Street firm? Traders usually have three or four large monitors, but let’s look at just one of them. Imagine a busy screen with lots and lots of fluctuating data grouped in dedicated areas of the window. This screen shows the constantly changing prices from financial markets, orders placed by the trader to buy or sell products, and notifications on completed trades. If this were a web application, it would live on the same web page. There would be no menus to open other windows.
The price of an Apple share was $590.45 just a second ago, and now it’s $590.60. How can this be done technically? Here’s one possibility: Every second, an Ajax call is made to the remote server providing current stock prices, and the JavaScript code finds in the DOM the HTML element responsible for rendering the price and then modifies its value with the latest price.
Have you seen a web page showing the content of a Google Gmail input box? It looks like a table with rows representing the sender, subject, and date of each email’s arrival. Suddenly, you see a new row in bold on top of the list—a new email came in. How was this done technically? A new object(s) was created and inserted into a DOM tree. No page changes, no need for the user to refresh the browser’s page. An undercover Ajax call gets the data, and JavaScript changes the DOM. The content of DOM changed, and the user sees an updated value.
The authors of this book use WebStorm IDE 7 from JetBrains for developing real-world projects. [appendix_c] explains how to run code samples in WebStorm.
This chapter includes lots of code samples illustrating how the UI is gradually being built. We’ve created a number of small web applications. Each of them can be run independently. Just download and open in the WebStorm (or any other) IDE the directory containing samples from Mocking Up the Save The Child Application. After that, you’ll be able to run each of these examples by right-clicking index.html in WebStorm and choosing Open in Browser.
Note
|
We assume that the users of our Save The Child application work with modern versions of web browsers (two years old or newer). Real-world web developers need to find workarounds to the unsupported CSS or HTML5 features in old browsers, but modern IDEs generate HTML5 boilerplate code that include large CSS files providing different solutions to older browsers. JavaScript frameworks implement workarounds (a.k.a., polyfills) for features unsupported by old browsers, too, so we don’t want to clutter the text by providing several versions of the code just to make book samples work in outdated browsers. This is especially important when developing enterprise apps for situations in which the majority of users are locked in a particular version of an older web browser. |
In this section, you’ll see several projects that show how the static mockup will turn into a working prototype with the help of HTML, CSS, and JavaScript. Because Jerry, the designer, decided to have four separate areas on the page, he created the HTML file index.html that has the tag <header>
with the navigation tag <nav>
, two <div>
tags for the middle sections of the page, and a <footer>
, as shown in The first version of the home page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Save The Child | Home Page</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<div id="main-container">
<header>
<h1>Save The Child</h1>
<nav>
<ul>
<li>
<a href="javascript:void(0)">Who we are</a>
</li>
<li>
<a href="javascript:void(0)">What we do</a>
</li>
<li>
<a href="javascript:void(0)">Way to give</a>
</li>
<li>
<a href="javascript:void(0)">How we work</a>
</li>
</ul>
</nav>
</header>
<div id="main" role="main">
<section>
Donate section and Video Player go here
</section>
<section>
Locate The Child, stats and tab folder go here
</section>
</div>
<footer>
<section id="temp-project-name-container">
<b>project 01</b>: This is the page footer
</section>
</footer>
</div>
</body>
</html>
Note that this HTML file uses the <link>
tag to include the CSS file shown in The file styles.css. Because there is no content yet for the navigation links to open, we use the syntax href="javascript:void(0)
to create a live link that doesn’t load any page, which is fine in prototyping stage.
/* Navigation menu */
nav {
float: right
}
nav ul li {
list-style: none;
float: left;
}
nav ul li a {
display: block;
padding: 7px 12px;
}
/* Main content
#main-container is a wrapper for all page content
*/
#main-container {
width: 980px;
margin: 0 auto;
}
div#main {
clear: both;
}
/* Footer */
footer {
/* Set background color just to make the footer standout*/
background: #eee;
height: 20px;
}
footer #temp-project-name-container {
float: left;
}
This CSS controls not only the styles of the page content, but also the page layout. The <nav>
section should be pushed to the right. If an unordered list is placed inside the <nav>
section, it should be left-aligned. The width of the HTML container with the ID main-container
should be 980 pixels, and it has to be automatically centered. The footer will be 20 pixels high and should have a gray background. The first version of our web page is shown in Working prototype, take 1: Getting Started. Run index.html from project-01-get-started.
Tip
|
In [responsive_design], you’ll see how to create web pages with more flexible layouts that don’t require specifying absolute sizes in pixels. |
The next version of our prototype is more interesting and contains a lot more code. The CSS file will become fancier, and the layout of the four page sections will properly divide the screen real estate. We’ll add a logo and a nicely styled Login button to the top of the page. This version of the code will also introduce some JavaScript that supports user authorization. Run project-02-login, and you’ll see a window similar to Working prototype, take 2: Login.
This project has several directories to keep JavaScript, images, CSS, and fonts separate. We’ll talk about special icon fonts later in this section, but first things first: let’s take a close look at the HTML code in The second version of the home page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Save The Child | Home Page</title>
<link rel="stylesheet" href="assets/css/styles.css">
</head>
<body>
<div id="main-container">
<header>
<h1 id="logo"><a href="javascript:void(0)">Save The Child</a></h1>
<nav id="top-nav">
<ul>
<li id="login">
<div id="authorized">
<span class="icon-user authorized-icon"></span>
<span id="user-authorized">admin</span>
<br/>
<a id="profile-link" href="javascript:void(0);">profile</a> |
<a id="logout-link" href="javascript:void(0);">logout</a>
</div>
<form id="login-form">
<span class="icon-user login-form-icons"></span>
<input id="username" name="username" type="text"
placeholder="username" autocomplete="off" />
<span class="icon-locked login-form-icons"></span>
<input id="password" name="password"
type="password" placeholder="password"/>
</form>
<a id="login-submit" href="javascript:void(0)">login
<span class="icon-enter"></span> </a>
<div id="login-link" class="show-form">login
<span class="icon-enter"></span></div>
<div class="clearfix"></div>
</li>
<li id="top-menu-items">
<ul>
<li>
<a href="javascript:void(0)">Who We Are</a>
</li>
<li>
<a href="javascript:void(0)">What We Do</a>
</li>
<li>
<a href="javascript:void(0)">Where We Work</a>
</li>
<li>
<a href="javascript:void(0)">Way To Give</a>
</li>
</ul>
</li>
</ul>
</nav>
</header>
<div id="main" role="main">
<section id="main-top-section">
<br/>
Main content. Top section.
</section>
<section id="main-bottom-section">
Main content. Bottom section.
</section>
</div>
<footer>
<section id="temp-project-name-container">
<b>This is the footer</b>
</section>
</footer>
</div>
<script src="assets/js/main.js"></script>
</body>
</html>
Usually, the logos on multipage websites are clickable—they bring up the home page. That’s why Jerry placed the anchor tag in the logo section. But we are planning to build a single-page application, so having a clickable logo won’t be needed.
Run this project in WebStorm and click the Login button; you’ll see that it reacts. But looking at the login-related <a>
tags in the <header>
section, you’ll find nothing but href="javascript:void(0)"
. So why does the button react? Read the code in main.js shown in The JavaScript code of the home page, and you’ll find the line loginLink.addEventListener('click', showLoginForm, false);
that invokes the callback showLoginForm()
. That’s why the Login button reacts. This seems confusing because the anchor component was used here just for styling purposes. In this example, a better solution would be to replace the anchor tag <a id="login-link" class="show-form" href="javascript:void(0)">
with another component that doesn’t make the code confusing—for example, <div id="login-link" class="show-form">
.
Note
|
We do not want to build web applications the old way wherein a server-side program prepares and sends UI fragments to the client. The server and the client send each other only the data. If the server is not available, we can use the local storage (the offline mode) or mock up data on the client. |
Now let’s examine the JavaScript code located in main.js. This code self-invokes the anonymous function, which creates an object-encapsulated namespace stc (short for Save The Child). This avoids polluting the global namespace. If we wanted to expose anything from this closure to the global namespace, we could have done this via the variable stc
, as described in the section "Closures" in the bonus online chapter. See The JavaScript code of the home page.
// global namespace ssc
var stc = (function() {
// Encapsulated variables
// Find login section elements (1)
// You can use document.getQuerySelector() here
// instead of getElementByID ()
var loginLink = document.getElementById("login-link");
var loginForm = document.getElementById("login-form");
var loginSubmit = document.getElementById('login-submit');
var logoutLink = document.getElementById('logout-link');
var profileLink = document.getElementById('profile-link');
var authorizedSection = document.getElementById("authorized");
var userName = document.getElementById('username');
var userPassword = document.getElementById('password');
// Register event listeners (2)
loginLink.addEventListener('click', showLoginForm, false);
loginSubmit.addEventListener('click', logIn, false);
logoutLink.addEventListener('click', logOut, false);
profileLink.addEventListener('click', getProfile, false);
function showLoginForm() {
loginLink.style.display = "none"; (3)
loginForm.style.display = "block";
loginSubmit.style.display = "block";
}
function showAuthorizedSection() {
authorizedSection.style.display = "block";
loginForm.style.display = "none";
loginSubmit.style.display = "none";
}
function logIn() {
//check credentials
var userNameValue = userName.value;
var userNameValueLength = userName.value.length;
var userPasswordValue = userPassword.value;
var userPasswordLength = userPassword.value.length;
if (userNameValueLength == 0 || userPasswordLength == 0) {
if (userNameValueLength == 0) {
console.log("username can't be empty");
}
if (userPasswordLength == 0) {
console.log("password can't be empty");
}
} else if (userNameValue != 'admin' ||
userPasswordValue != '1234') {
console.log('username or password is invalid');
} else if (userNameValue == 'admin' &&
userPasswordValue == '1234') {
showAuthorizedSection(); (4)
}
}
function logOut() {
userName.value = '';
userPassword.value = '';
authorizedSection.style.display = "none";
loginLink.style.display = "block";
}
function getProfile() {
console.log('Profile link clicked');
}
})();
-
Query the DOM to get references to login-related HTML elements.
-
Register event listeners for the clickable login elements.
-
To make a DOM element invisible, set its
style.display="none"
. Hide the Login button and show the login form having two input fields for entering the user ID and the password. -
If the user is admin and the password is 1234, hide the
loginForm
and make the top corner of the page look as in After successful login.
Warning
|
We keep the user ID and password in this code just for illustration purposes. Never do this in your applications. Authentication has to be done in a secure way on the server side. |
We recommend placing the <script>
tag with your JavaScript at the end of your HTML file as in our index.html shown previously. If you move the line <script src="js/main.js"></script>
to the top of the <body>
section and rerun index.html, the screen will look like Working prototype, take 2: Login, but clicking Login won’t display the login form as it should. Why? Registering of the event listeners in the script main.js failed because the DOM components (login-link
, login-form
, and others) were not created by the time this script was running. Open Firebug, Chrome Developer Tools, or any other debugging tool, and you’ll see an error on the console that will look similar to the following:
__TypeError: loginLink is null loginLink.addEventListener('click', showLoginForm, false);__
Of course, in many cases, your JavaScript code could have tested whether the DOM elements exist before using them, but in this particular sample, it’s just easier to put the script at the end of the HTML file. Another solution is to load the JavaScript code located in main.js in a separate handler function that would run only when the window’s load
event, which is dispatched by the browser, indicates that the DOM is ready: window.addEventListener('load', function() {…}
. You’ll see how to do this in the next version of main.js.
Now that we have reviewed the HTML and JavaScript code, let’s spend a little more time with the CSS that supports the page shown in Working prototype, take 2: Login. The difference between the screenshots shown in Working prototype, take 1: Getting Started and Working prototype, take 2: Login is substantial. First, the upper-left image is nowhere to be found in index.html. Open the styles.css file and you’ll see the line background: url(../img/logo.png) no-repeat;
in the header h1#logo
section.
The page layout is also specified in the file styles.css. In this version, the size of each section is specified in pixels (px), which won’t make your page fluid and easily resizable. For example, the HTML element with id="main-top-section"
is styled like this:
#main-top-section {
width: 100%;
height: 320px;
margin-top: 18px;
}
Jerry styled the main section to take the entire width of the browser’s window and to be 320 pixels tall. If you keep in mind the Mobile First mantra, this might not be the best approach because 320 pixels means a different size (in inches) on the displays with different screen density. For example, 320 pixels on the iPhone 5 with Retina display will look a lot smaller than 320 pixels on the iPhone 4. You might want to consider switching from px
to em
units: 1 em is equal to the current font height, 2 em means twice the size, and so forth. You can read more about creating scalable style sheets with em units at link:http://bit.ly/1lJnSUL.
What looks like a Login button in Working prototype, take 2: Login is not a button, but a styled div
element. Initially, it was a clickable anchor <a>
, and we’ve explained this change right after The file styles.css. The CSS fragment supporting the Login button looks like this:
li#login input {
width: 122px;
padding: 4px;
border: 1px solid #ddd;
border-radius: 2px;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
}
The border-radius
element rounds the corners of the HTML element to which it’s applied. But why do we repeat it three times with the additional prefixes -moz-
and -webkit-
? These are CSS vendor prefixes, which allow web browser vendors to implement experimental CSS properties that haven’t been standardized yet. For example, -webkit-
is the prefix for all WebKit-based browsers: Chrome, Safari, Android, and iOS. Microsoft uses -ms-
for Internet Explorer, and Opera uses -o-
. These prefixes are temporary measures, which make the CSS files heavier than they need to be. The time will come when the CSS3 standard properties will be implemented by all browser vendors, and you won’t need to use these prefixes.
As a matter of fact, unless you want this code to work in the very old versions of Firefox, you can remove the line -moz-border-radius: 2px;
from our styles.css because Mozilla has implemented the property border-radius
in most of its browsers. You can find a list of CSS properties with the corresponding vendor prefixes in this list maintained by Peter Beverloo.
The footer section comes next. Run the project called project-03-footer and you’ll see a new version of the Save The Child page with the bottom portion that looks like The footer section. The footer section shows several icons linking to Facebook, Google Plus, Twitter, RSS feed, and email.
The HTML section of our first prototype is shown in The footer section’s HTML. At this point, it has a number of <a>
tags, which have the dummy references href="javascript:void(0)"
that don’t redirect the user to any of these social sites.
Each of the preceding anchors is styled using vector graphics icon fonts that we’ve selected and downloaded from link:http://icomoon.io/app. Vector graphics images are being redrawn using vectors (strokes)—as opposed to raster graphics, which are predrawn in certain resolution images. Raster graphics can give you boxy, pixelated images if the size of the image needs to be increased. We use vector images for our footer section that are treated as fonts. They will look as good as the originals on any screen size, and you can change their properties (for example, color) as easily as you would with any other font. The images that you see in The footer section are located in the fonts directory of project-03-footer. The IcoMoon web application will generate the fonts for you based on your selection and you’ll get a sample HTML file, fonts, and CSS to be used with your application. Our icon fonts section in styles.css will look like Icon fonts in CSS.
/* Icon Fonts */
@font-face {
font-family: 'icomoon';
src:url('../fonts/icomoon.eot');
src:url('../fonts/icomoon.eot?#iefix') format('embedded-opentype'),
url('../fonts/icomoon.svg#icomoon') format('svg'),
url('../fonts/icomoon.woff') format('woff'),
url('../fonts/icomoon.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
The section with the Donate Now button and the donation form will be located in the top portion of the page, directly below the navigation area. Initially, the page opens with the background image of a sick but smiley boy on the right and a large Donate Now button on the left. The image shown in The initial view of the Donate section is taken from a large collection of photos at the iStockphoto website. We’re also using two more background images here: one with the flowers, and the other with the sun and clouds. You can find the references to these images in the styles.css file. Run project-04-donation and you’ll see the new version of our Save The Child page that will look like The initial view of the Donate section.
Lorem Ipsum is a dummy text widely used in printing, typesetting, and web design. It’s used as a placeholder to indicate the text areas that should be filled with real content later. You can read about it at Lipsum. The donate section before clicking Donate Now shows what the HTML fragment supporting The initial view of the Donate section looks like (no CSS is shown for brevity).
<div id="donation-address">
<p class="donation-address">
Lorem ipsum dolor sit amet, consectetur e magna aliqua.
Nostrud exercitation ullamco laboris nisi ut aliquip ex
ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident.
</p>
<button class="donate-button" id="donate-btn">
<span class="donate-button-header">Donate Now</span>
<br/>
<span class="donate-2nd-line">Children can't wait</span>
</button>
</div>
Clicking the Donate Now button should reveal the form where the user can enter a name, address, and donation amount. Instead of opening a pop-up window, we’ll just change the content on the left revealing the form, and move the Donate Now button to the right. After clicking the Donate Now button shows how the top portion of our page will look after the user clicks the Donate Now button.
The HTML of this donation is shown in The donate section after clicking the Donate Now button. When the user clicks the Donate Now button, the content of the form should be sent to PayPal or any other payment processing system.
<div id="donate-form-container">
<h3>Make a donation today</h3>
<form name="_xclick" action="https://www.paypal.com/cgi-bin/webscr" method="post">
<div class="donation-form-section">
<label class="donation-heading">Please select or enter
<br/> donation amount</label>
<input type="radio" name = "amount" id= "d10" value = "10"/>
<label for = "d10">10</label>
<br/>
<input type="radio" name = "amount" id = "d20" value="20" />
<label for = "d20">20</label>
<br/>
<input type="radio" name = "amount" id="d50" checked="checked" value="50" />
<label for="d50">50</label>
<br/>
<input type="radio" name = "amount" id="d100" value="100" />
<label for="d100">100</label>
<br/>
<input type="radio" name = "amount" id="d200" value="200" />
<label for="d200">200</label>
<label class="donation-heading">Other amount</label>
<input id="customAmount" name="amount" value=""
type="text" autocomplete="off" />
</div>
<div class="donation-form-section">
<label class="donation-heading">Donor information</label>
<input type="text" id="full_name" name="full_name"
placeholder="full name *" required>
<input type="email" id="email_addr" name="email_addr"
placeholder="email *" required>
<input type="text" id="street_address" name="street_address"
placeholder="address">
<input type="text" id="city" name="scty" placeholder="city">
<input type="text" id="zip" name="zip" placeholder="zip/postal code">
<select name="state">
<option value="" selected="selected"> - State - </option>
<option value="AL">Alabama</option>
<option value="WY">Wyoming</option>
</select>
<select name="country">
<option value="" selected="selected"> - Country - </option>
<option value="United States">United States</option>
<option value="Zimbabwe">Zimbabwe</option>
</select>
</div>
<div class="donation-form-section make-payment">
<h4>We accept Paypal payments</h4>
<p>
Your payment will processed securely by <b>PayPal</b>.
PayPal employ industry-leading encryption and fraud prevention tools.
Your financial information is never divulged to us.
</p>
<button type="submit" class="donate-button donate-button-submit">
<span class="donate-button-header">Donate Now</span>
<br/>
<span class="donate-2nd-line">Children can't wait</span>
</button>
<a id="donate-later-link" href="javascript:void(0);">I'll donate later
<span class="icon-cancel"></span></a>
</div>
</form>
</div>
The JavaScript code supporting the UI transformations related to the Donate Now button is shown next. It’s the code snippet from main.js from project-04-donation. Clicking the Donate Now button invokes the event handler showDonationForm()
, which simply hides <div id="donation-address">
with Lorem Ipsum and displays the donation form:
<form name="_xclick" action="https://www.paypal.com/cgi-bin/webscr"
method="post">">.
When the form field loses focus or after the user clicks the Submit button, the data from the form _xclick
must be validated and sent to PayPal. If the user clicks "I’ll donate later," the code hides the form and shows the Lorem Ipsum from the <div id="donation-address">
again.
Note
|
Not including proper form validation is a sign of a rookie developer. This can easily irritate users. Instead of showing error messages like "Please include only numbers in the phone number field," use regular expressions to programmatically strip nondigits away (read more about these in Regular Expressions. |
Two select
drop-downs in the preceding code contain hardcoded values of all states and countries. For brevity, we’ve listed just a couple of entries in each. In [using_ajax_and_json], we’ll populate these drop-downs by using external data in JavaScript Object Notation (JSON) format.
Tip
|
Don’t show all the countries in the drop-down unless your application is global. If the majority of users live in France, display France at the top of the list, and not Afghanistan (the first country in alphabetical order). |
The first version of event handlers is an extract of the JavaScript file main.js provided by Jerry. This code contains function handlers that process user clicks in the Donate section.
(function() {
var donateBotton = document.getElementById('donate-button');
var donationAddress = document.getElementById('donation-address');
var customAmount = document.getElementById('customAmount');
var donateForm = document.forms['_xclick'];
var donateLaterLink = document.getElementById('donate-later-link');
var checkedInd = 2;
function showDonationForm() {
donationAddress.style.display = "none";
donateFormContainer.style.display = "block";
}
// Register the event listeners
donateBotton.addEventListener('click', showDonationForm, false);
customAmount.addEventListener('focus', onCustomAmountFocus, false);
donateLaterLink.addEventListener('click', donateLater, false);
customAmount.addEventListener('blur', onCustomAmountBlur, false);
// Uncheck selected radio buttons if the custom amount was chosen
function onCustomAmountFocus() {
for (var i = 0; i < donateForm.length; i++) {
if (donateForm[i].type == 'radio') {
donateForm[i].onclick = function() {
customAmount.value = '';
}
}
if (donateForm[i].type == 'radio' && donateForm[i].checked) {
checkedInd = i;
donateForm[i].checked = false;
}
}
}
function onCustomAmountBlur() {
if (isNan(customAmount.value)) {
// The user haven't entered valid number for other amount
donateForm[checkedInd].checked = true;
}
}
function donateLater(){
donationAddress.style.display = "block";
donateFormContainer.style.display = "none";
}
})();
This code contains an example of an inefficient loop that assigns a click event handler to each radio button should the user click any radio button after visiting the Other Amount field. This reflects Jerry’s understanding of how to reset the value of the customAmount variable. Jerry was not familiar with the capture phase of the events that can intercept the click event on the level of the radio buttons and simply reset the value of customAmount
regardless of which specific radio button is clicked.
Let’s improve the code from the previous section. The idea, as shown in The event handler for the Reset button, is to intercept the click event during the capture phase (see the section "DOM Events" in the bonus online chapter) and if the Event.target
is any radio button, perform customAmount.value = '';
.
The code of onCustomAmountFocus()
doesn’t need to assign function handlers to the radio buttons any longer, as shown in The Custom Amount field gets focus.
function onCustomAmountFocus() {
for (var i = 0; i < donateForm.length; i++) {
if (donateForm[i].type == 'radio' && donateForm[i].checked) {
checkedInd = i;
donateForm[i].checked = false;
}
}
}
In the Donate section, we started working with event handlers. You’ll see many more examples of event processing throughout the book.
In this section, we’ll add a video player to our Save The Child application. The goal is to play a short animation encouraging kids to fight the disease. We’ve hired a professional animation artist, Yuri, who has started working on the animation. Meanwhile, let’s take care of embedding the video player showing any sample video file.
Let’s run the project called project-05-html5-video to see the video playing, and after that, we’ll review the code. The new version of the Save The Child app should look like The video player is embedded. Users will see an embedded video player on the right that can play the video located in the assets/media folder of the project project-05-html5-video.
Let’s see how index.html has changed since its previous version. The bottom part of the main section includes the <video>
tag. In the past, videos in web pages were played predominantly by the browser’s Flash Player plug-in (even older popular plug-ins included RealPlayer, Media Player, and QuickTime). For example, you could have used the HTML tag <embed src="myvideo.swf" height="300" width="300">, and if the user’s browser supports Flash Player, that’s all you need for basic video play. Although there were plenty of open source video players, creation of the enterprise-grade video player for Flash videos became an important skill for some software developers. For example, HBO, an American cable network, offers an advanced multifeatured video player embedded into link:http://www.hbogo.com for its subscribers.
In today’s world, most modern mobile web browsers don’t support Flash Player, and video content providers prefer broadcasting videos in formats that are supported by all browsers and can be embedded into web pages by using the standard HTML5 element <video>
(see its current working draft).
The HTML container for the video element illustrates how we’ve embedded the video into the bottom portion of our web page (index.html). It includes two <source>
elements, which allows us to provide alternative media resources. If the web browser supports playing video specified in the first <source>
element, it’ll ignore the other versions of the media. For example, the following code offers two versions of the video file: intro.mp4 (in H.264/MPEG-4 format natively supported by Safari and Internet Explorer) and intro.webm (WebM format for Firefox, Chrome, and Opera).
<section id="main-bottom-section">
<div id="video-container">
<video controls poster="assets/media/intro.jpg"
width="390px" height="240" preload="metadata">
<source src="assets/media/intro.mp4" type="video/mp4">
<source src="assets/media/intro.webm" type="video/webm">
<p>Sorry, your browser doesn't support video</p>
</video>
<h3>Video header goes here</h3>
<h5><a href="javascript:void(0);">More videos</a></h5>
</div>
</section>
The Boolean property controls
asks the web browser to display the video player with controls (the Play/Pause buttons, the full-screen mode, and so forth). You can also control the playback programmatically in JavaScript. The poster
property of the <video>
tag specifies the image to display as a placeholder for the video—this is the image you see in The video player is embedded. In our case, preload=metadata instructs the web browser to preload just the first frame of the video and its metadata. Should we use preload="auto", the video would start loading in the background as soon as the web page was loaded, unless the user’s browser doesn’t allow it (for example, Safari on iOS) in order to save bandwidth.
All major web browsers released in 2011 and later (including Internet Explorer 9) come with their own embedded video players that support the <video> element. It’s great that your code doesn’t depend on the support of Flash Player, but browsers' video players look different.
If neither .mp4 nor .webm files can be played, the content in the <p>
tag displays the fallback message "Sorry, your browser doesn’t support video." If you need to support older web browsers that don’t support HTML5 video, but support Flash Player, you can replace this <p>
tag with the <object>
and <embed>
tags that embed another media file that Flash Player understands. Finally, if you believe that some users might have browsers that support neither the <video>
tag nor Flash Player, just add links to the files listed in the <source>
tags right after the closing </video>
tag.
Another way to include videos in your web application is by uploading them to YouTube first and then embedding them into your web page. This provides several benefits:
-
The videos are hosted on Google’s servers and use their bandwidth.
-
The users either can watch the video as a part of your application’s web page or, by clicking the YouTube logo on the status bar of the video player, can continue watching the video from its original YouTube URL.
-
YouTube streams videos in compressed form, and the user can watch as the bytes come in. The video doesn’t have to be fully preloaded to the user’s device.
-
YouTube stores videos in several formats and automatically selects the best one based on the user’s web browser (user agent).
-
The HTML code to embed a YouTube video is generated for you by clicking the Share and then the Embed link under the video itself.
-
You can enrich your web application by incorporating extensive video libraries via the YouTube Data API. You can create fine-tuned searches to retrieve channels, playlists, and videos; manage subscriptions; and authorize user requests.
-
Your users can save the YouTube videos on their local drive by using free web browser add-ons such as the DownloadHelper extension for Firefox or RealDownloader.
Embedding a YouTube video into your HTML page is simple. Find the page with the video on YouTube and click the links Share and Embed located right under the video. Then select the size of your video player and HTTPS encryption if needed (see [intro_to_security] on web security for reasoning). When this is done, copy the generated iFrame
section into your page.
Open the file index.html in project-06-YouTube-video and you’ll see the code that replaces the <video>
tag of the previous project. It should look like The HTML container for the YouTube video.
<section id="main-bottom-section">
<div id="video-container">
<div id="video-container">
<iframe
src="http://www.youtube.com/embed/VGZcerOhCuo?wmode=transparent&hd=1&vq=hd720"
frameborder="0" width="390" height="240"></iframe>
<h3>Video header goes here</h3>
<h5><a href="javascript:void(0);">More videos</a></h5>
</div>
</div>
</section>
Note that the initial size of our video player is 390×240 pixels. The <iframe>
wraps the URL of the video, which in this example ends with parameters hd=1
and vq=hd720
. This is how you can force YouTube to load video in HD quality. Run project-06-YouTube-video and you will see a web page that looks like The YouTube player is embedded.
Now let’s do yet another experiment. Enter the URL of our video directly in your web browser and then turn on Firebug or Chrome Developer Tools as explained in the bonus online chapter. We used Firebug under the Mac OS and selected the Net tab. Then, the HTML Response looks like HTTP Response object from YouTube. YouTube recognizes that this web browser is capable of playing Flash content (FLASH_UPGRADE) and picks QuickTime as a fallback (QUICKTIME_FALLBACK).
Tip
|
YouTube offers an Opt-In Trial of HTML5 video, which allows the users to request playing most of the videos using HTML 5 video (even those recorded for Flash Player). Try to experiment on your own and see if YouTube streams HTML5 videos in your browser. |
Our brief introduction to embedding videos in HTML is over. Let’s keep adding new features to the Save The Child web application. This time, we’ll become familiar with the HTML5 Geolocation API.
HTML5 includes a Geolocation API that allows you to programmatically determine the latitude and longitude of a user’s device. Most people are accustomed to the non-Web GPS applications in cars or mobile devices that display maps and calculate distances based on the current coordinates of the user’s device or motor vehicle. But why do we need a Geolocation API in a desktop web application?
The goal of this section is to demonstrate a very practical feature: finding registered Save The Child events based on the user’s location. This way, users of this application not only can donate, but can participate in such an event or even find children in need of assistance in a particular geographical area. In this chapter, you’ll just learn the basics of the HTML5 Geolocation API, but we’ll continue improving the location feature of the Save The Child application in the next chapter.
Tip
|
The World Wide Web Consortium (W3C) has published a proposed recommendation of the Geolocation API Specification, which can become a part of the HTML5 spec soon. |
Does your old desktop computer have GPS hardware? Most likely it doesn’t. But its location can be calculated with varying degrees of accuracy. If your desktop computer is connected to a network, it has an IP address or your local WiFi router might have a Service Set Identifier (SSID) given by the router vendor or your Internet provider. Therefore, the location of your desktop computer is not a secret, unless you change the SSID of your WiFi router. Highly populated areas have more WiFi routers and cell towers, so the accuracy increases. In any case, properly designed applications must always ask the user’s permission to use the current location of a computer or other connected device.
Note
|
GPS signals are not always available. However, various location services can help identify the position of a device. For example, Google, Apple, Microsoft, Skyhook, and other companies use publicly broadcast WiFi data from a wireless access point. Google Location Server uses a Media Access Control (MAC) address to identify any device connected to a network. |
Every web browser has a global object window
, which includes the navigator
object containing information about the user’s browser. If the browser’s navigator
object includes the property geolocation
, geolocation services are available. Although the Geolocation API allows you to get just a coordinate of your device and report the accuracy of this location, most applications use this information with some user-friendly UI; for example, mapping software. In this section, our goal is to demonstrate the following:
-
How to use the Geolocation API
-
How to integrate the Geolocation API with Google Maps
-
How to detect whether the web browser supports geolocation services
Note
|
To respect people’s privacy, web browsers will always ask for permission to use the Geolocation API unless the user changes the settings on the browser to always allow it. |
The next version of our application is called project-07-basic-geolocation, where we simply assume that the web browser supports geolocation. The Save The Child page will get a new container in the middle of the bottom main section, where we are planning to display the map of the current user’s location. But for now, we’ll show just the coordinates: latitude, longitude, and the accuracy. Initially, the map container is empty, but we’ll populate it from the JavaScript code as soon as the position of the computer is located:
<div id="map-container">
</div>
Finding coordinates with navigator.geolocation from main.js makes a call to the navigator.geolocation
object to get the current position of the user’s computer. In many code samples, we’ll use console.log()
to print debug data in the web browser’s console.
var mapContainer = document.getElementById('map-container'); (1)
function successGeoData(position) {
var successMessage = "We found your position!"; (2)
successMessage += '\n Latitude = ' + position.coords.latitude;
successMessage += '\n Longitude = ' + position.coords.longitude;
successMessage += '\n Accuracy = ' + position.coords.accuracy +
console.log(successMessage);
var successMessageHTML = successMessage.replace(/\n/g, '<br />');
var currentContent = mapContainer.innerHTML;
mapContainer.innerHTML = currentContent + "<br />"
+ successMessageHTML; (3)
}
function failGeoData(error) { (4)
console.log('error code = ' + error.code);
switch(error.code) {
case error.POSITION_UNAVALABLE:
errorMessage = "Can't get the location";
break;
case error.PERMISSION_DENIED:
errorMessage = "The user doesn't want to share location";
break;
case error.TIMEOUT:
errorMessage = "Timeout - Finding location takes too long";
break;
case error.UNKNOWN_ERROR:
errorMessage = "Unknown error: " + error.code;
break;
}
console.log(errorMessage);
mapContainer.innerHTML = errorMessage;
}
if (navigator.geolocation) {
var startMessage = 'Your browser supports geolocation API :)';
console.log(startMessage);
mapContainer.innerHTML = startMessage;
console.log('Checking your position...');
mapContainer.innerHTML = startMessage + '<br />Checking your position...';
navigator.geolocation.getCurrentPosition(successGeoData,
failGeoData, (5)
{maximumAge : 60000,
enableHighAccuracy : true, (6)
timeout : 5000
}
);
} else {
mapContainer.innerHTML ='Your browser does not support geolocation';
}
-
Get a reference to the DOM element
map-container
to be used for showing the results. -
The function handler to be called in case of the successful discovery of the computer’s coordinates. If this function is called, it will get a
position
object as an argument. -
Display the retrieved data on the web page (see The latitude and longitude are displayed).
-
This is the error-handler callback.
-
Invoke the method getCurrentPosition(), passing it two callback functions as arguments (for success and failure) and an object with optional parameters for this invocation.
-
Optional parameters: accept the cached value if not older than 60 seconds, retrieve the best possible results, and don’t wait for results for more than 5 seconds. You might not always want the best possible results, to lower the response time and the power consumption.
If you run project-07-basic-geolocation, the browser will show a pop-up (it can be located under the toolbar) asking a question similar to "Would you like to share your location with 127.0.01?" Allow this sharing and you’ll see a web page, which will include the information about your computer’s location, similar to The latitude and longitude are displayed.
Tip
|
If you don’t see the question asking permission to share your location, check the privacy settings of your web browser; most likely you’ve allowed using your location at some time in the past. |
Tip
|
If you want to monitor the position as it changes (the device is moving), use geolocation.watchPosition() , which implements an internal timer and checks the position. To stop monitoring the position, use geolocation.clearWatch() .
|
Knowing the device coordinates is important, but let’s make the location information more presentable by feeding the device coordinates to the Google Maps API. In this version of Save The Child, we’ll replace the gray rectangle from The latitude and longitude are displayed with the Google Maps container. We want to show a familiar map fragment with a pin pointing at the location of the user’s web browser. To follow our show-and-tell style, let’s see it working first. Run project-08-geolocation-maps, and you’ll see a map with your current location, as shown in Showing your current location.
Now comes the "tell" part. First, take a look at the bottom of the index.html file. It loads Google’s JavaScript library with its Map API (sensor=false
means that we are not using a sensor-like GPS locator):
<script src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
In the past, Google required developers to obtain an API key and include it in the URL. Although some of Google’s tutorials still mention the API key, it’s no longer a must.
Note
|
An alternative way of adding the <script> section to an HTML page is by creating a <script> element. This gives you the flexibility of postponing the decision about which JavaScript to load. For example: var myScript=document.createElement("script");
myScript.src="http://......somelibrary.js";
document.body.appendChild(myScript); |
Our main.js will invoke the function for Google’s library as needed. The code that finds the location of your device is almost the same as in Geolocation Basics. We’ve replaced the call to geolocation.watchPosition()
so that this program can modify the position if your computer, tablet, or a mobile phone is moving. We store the returned value of watchPosition()
in the variable watcherID
in case you decide to stop watching the position of the device by calling clearWatch(watcherID)
. Also, we lower the value of the maximumAge
option so the program will update the UI more frequently, which is important if you are running this program while in motion (Integrating geolocation with the mapping software).
(function() {
var locationUI = document.getElementById('location-ui');
var locationMap = document.getElementById('location-map');
var watcherID;
function successGeoData(position) {
var successMessage = "We found your position!";
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
successMessage += '\n Latitude = ' + latitude;
successMessage += '\n Longitude = ' + longitude;
successMessage += '\n Accuracy = ' + position.coords.accuracy
+ ' meters';
console.log(successMessage);
// Turn the geolocation position into a LatLng object.
var locationCoordinates =
new google.maps.LatLng(latitude, longitude); (1)
var mapOptions = {
center : locationCoordinates,
zoom : 12,
mapTypeId : google.maps.MapTypeId.ROADMAP, (2)
mapTypeControlOptions : {
style : google.maps.MapTypeControlStyle.DROPDOWN_MENU,
position : google.maps.ControlPosition.TOP_RIGHT
}
};
// Create the map
var map = new google.maps.Map(locationMap, mapOptions); (3)
// set the marker and info window
var contentString = '<div id="info-window-content">' +
'We have located you using HTML5 Geolocation.</div>';
var infowindow = new google.maps.InfoWindow({ (4)
content : contentString,
maxWidth : 160
});
var marker = new google.maps.Marker({ (5)
position : locationCoordinates,
map : map,
title : "Your current location"
});
google.maps.event.addListener(marker, 'click', (6)
function() {
infowindow.open(map, marker);
}
);
// When the map is loaded show the message and
// remove event handler after the first "idle" event
google.maps.event.addListenerOnce(map, 'idle', function(){
locationUI.innerHTML = "Your current location";
})
}
// error handler
function failGeoData(error) {
clearWatch(watcherID);
//the error processing code is omitted for brevity
}
if (navigator.geolocation) {
var startMessage =
'Browser supports geolocation API. Checking your location...';
console.log(startMessage);
var currentContent = locationUI.innerHTML;
locationUI.innerHTML = currentContent +' '+startMessage;
watcherID = navigator.geolocation.watchPosition(successGeoData, (7)
failGeoData, {
maximumAge : 1000,
enableHighAccuracy : true,
timeout : 5000
});
} else {
console.log('browser does not support geolocation :(');
}
})();
-
The Google API represents a point in geographical coordinates (latitude and longitude) as a
LatLng
object, which we instantiate here. -
The object google.maps.MapOptions is an object that allows you to specify various parameters of the map to be created. In particular, the map type can be one of the following: HYBRID, ROADMAP, SATELLITE, TERRAIN. We’ve chosen
ROADMAP
, which displays a normal street map. -
The function constructor
google.maps.Map
takes two arguments: the HTML container where the map has to be rendered and theMapOption
as parameters of the map. -
Create an overlay box that will show the content describing the location (for example, a restaurant name) on the map. You can do it programmatically by calling
InfoWindow.open()
. -
Place a marker on the specified position on the map.
-
Show the overlay box when the user clicks the marker on the map.
-
Invoke the method
watchPosition()
to find the current position of the user’s computer.
This is a pretty basic example of integrating geolocation with the mapping software. The Google Maps API consists of dozens JavaScript objects and supports various events that allow you to build interactive and engaging web pages that include maps. Refer to the Google Maps JavaScript API Reference for the complete list of available parameters (properties) of all objects used in project-08-geolocation-maps and more. [using_ajax_and_json] presents a more advanced example of using Google Maps; we’ll read the JSON data stream containing coordinates of the children so the donors can find them based on the specified postal code.
Tip
|
For a great illustration of using Google Maps, look at the PadMapper web application. We use it for finding rental apartments in Manhattan. |
Now we’ll learn how to use the detection features offered by a JavaScript library called Modernizr. This is a must-have feature-detection library that helps your application figure out whether the user’s browser supports certain HTML5/CSS3 features. Review the code of index.html from project-08-1-modernizr-geolocation-maps. Note that index.html includes two <script>
sections. The Modernizr’s JavaScript is loaded first, whereas our own main.js is loaded at the end of the <body>
section:
<!DOCTYPE html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8">
<title>Save The Child | Home Page</title>
<link rel="stylesheet" href="assets/css/styles.css">
<script src="js/libs/modernizr-2.5.3.min.js"></script>
</head>
<body>
!-- Most of the HTML markup is omitted for brevity --!
<script src="js/main.js"></script>
</body>
</html>
Modernizr is an open source JavaScript library that helps your script to determine whether the required HTML or CSS features are supported by the user’s browser. Instead of maintaining a complex cross-browser feature matrix to see if, say, border-radius
is supported in the user’s version of Firefox, the Modernizer queries the <html>
elements to see what’s supported and what’s not.
Note the fragment at the top of index.html: <html class="no-js" lang="en">
. For Modernizr to work, your HTML root element has to include the class named no-js
. On page load, Modernizr replaces the no-js
class with its extended version that lists all detected features; those that are not supported are labeled with the prefix no-
. Run index.html from project-08-1-modernizr-geolocation-maps in Chrome and you’ll see in the Developer Tools panel that the values of the class
property of the html
element are different now. You can see in Modernizr changed the HTML’s class property that our version of Chrome doesn’t support touch events (no-touch
) or flexbox (no-flexbox
).
For example, there is a new way to do page layouts, using the so-called CSS Flexible Box Layout module. This feature is not widely supported yet, and as you can see in Modernizr changed the HTML’s class property, our web browser doesn’t support it at the time of this writing. If the CSS file of your application implements two class selectors, .flexbox
and .no-flexbox
, the browsers that support flexible boxes will use the former, and the older browsers will use the latter.
When Modernizr loads, it creates a new JavaScript object window.Modernizr
with lots of Boolean properties indicating whether a certain feature is supported. Add the Modernizr
object as a watch expression in the Chrome Developer Tools panel and see which properties have the false
value (see window.Modernizr object).
Hence, your JavaScript code can test whether certain features are supported.
What if Modernizer detects that a certain feature is not supported by a user’s older browser? You can include polyfills in your code that replicate the required functionality. You can write such a polyfill on your own or pick one from the collection at Modernizr’s GitHub repository.
Tip
|
Addy Osmani published The Developer’s Guide To Writing Cross-Browser JavaScript Polyfills. |
The Development version of Modernizr is only 42 KB in size and can detect lots of features. But you can make it even smaller by configuring the detection of only selected features. Just visit Modernizr and click the red Production button that enables you to configure the build specifically for your application. For example, if you’re interested in just detecting the HTML5 video support, the size of the generated Modernizr library will be reduced to under 2 KB.
Let’s review the relevant code from project-08-1-modernizr-geolocation-maps that illustrates the use of Modernizr (see Using the Modernizr loader). In particular, Modernizr allows you to load one or the other JavaScript code block based on the result of some tests.
Note
|
Actually, the Modernizr loader internally utilizes a tiny (under 2 KB) resource loader library, yepnope.js, which can load both JavaScript and CSS. This library is integrated in Modernizr, but we just wanted to give proper recognition to yepnope.js, which you can use as an independent resource loader, too. |
(function() {
Modernizr.load({
test: Modernizr.geolocation,
yep: ['js/get-native-geo-data.js','https://www.google.com/jsapi'],
nope: ['js/get-geo-data-by-ip.js','https://www.google.com/jsapi'],
complete : function () {
google.load("maps", "3",
{other_params: "sensor=false", 'callback':init});
}
});
})();
The preceding code invokes the function load()
, which can take different arguments. In our example, the argument is a specially prepared object with four properties: test, yep, nope, and complete. The load()
function will test the value of Modernizr.geolocation
and if it’s true, it’ll load the scripts listed in the yep
property. Otherwise, it will load the code listed in the nope
array. The code in get-native-geo-data.js gets the user’s location the same way as was done earlier in Integration with Google Maps.
Now let’s consider the nope case. The code of get-geo-data-by-ip.js has to offer an alternative way of getting the location of browsers that don’t support the HTML5 Geolocation API. We found the GeoIP JavaScript API offered by MaxMind. Its service returns country, region, city, latitude, and longitude, which can serve as a good illustration of how a workaround of a nonsupported feature can be implemented. The code in get-geo-data-by-ip.js (see Reporting nonimplemented features) is simple for now.
function init(){
var locationMap = document.getElementById('location-map');
locationMap.innerHTML="Your browser does not support HTML5 geolocation API.";
// The code to get the location by IP from http://j.maxmind.com/app/geoip.js
// will go here. Then we'll pass the latitude and longitude values to
// Google Maps API for drawing the map.
}
Most likely your browser supports the HTML5 Geolocation API, and you’ll see the map created by the script get-native-geo-data.js. But if you want to test nonsupported geolocation (the nope branch), either try this code in the older browser or change the test condition to look like this: Modernizr.fakegeolocation,
.
Google has several JavaScript APIs—for example, Maps, Search, Feed, and Earth. Any of these APIs can be loaded by the Google AJAX Loader google.load()
. This is a more generic way of loading any APIs compared to loading maps from maps.googleapis.com/maps/api, as shown in the previous section on integrating geolocation and maps. The process of loading the Google code with the Google Ajax loader consists of two steps:
-
Load the concrete module API, specifying its name, version, and optional parameters. In our example, we are loading the Maps API of version 3, passing an object with two properties:
sensor=false
and the name of the callback function to invoke right after the mapping API completes loading,'callback':init
.
Tip
|
If you want to test your web page in a specific older version of a particular web browser, you can find distributions at oldapps.com. For example, you can find all the old versions of Firefox for Mac OS and for Windows. |
We’ve prepared a couple of more examples to showcase the features of Google Maps. The working examples are included in the code accompanying this book, and we provide brief explanations in this section.
The chapter’s code samples include the project-09-map-and-search Webstorm project, which is an example of an address search using Google Maps. Searching by address shows a fragment of the Save The Child page after we’ve entered the address 26 Broadway ny ny in the search field. You can do a search by city or zip code, too. This can be a useful feature if you want to allow users to search for children living in a particular geographical area so their donations could be directed to specific people.
Our implementation of the search is shown in Finding map location by address, a code fragment from main.js. It uses geocoding, which is the process of converting an address into geographic coordinates (latitude and longitude). If the address is found, the code places a marker on the map.
var geocoder = new google.maps.Geocoder();
function getMapByAddress() {
var newaddress = document.getElementById('newaddress').value;
geocoder.geocode( (1)
{'address' : newaddress,
'country' : 'USA'
},
function(results, status) { (2)
console.log('status = ' + status);
if (status == google.maps.GeocoderStatus.OK) {
var latitude = results[0].geometry.location.lat(); (3)
var longitude = results[0].geometry.location.lng();
var formattedAddress = results[0].formatted_address;
console.log('latitude = ' + latitude +
' longitude = ' + longitude);
console.log('formatted_address = ' + formattedAddress);
var message = '<b>Address</b>: ' + formattedAddress;
foundInfo.innerHTML = message;
var locationCoordinates =
new google.maps.LatLng(latitude, longitude); (4)
showMap(locationCoordinates, locationMap);
} else if (status == google.maps.GeocoderStatus.ZERO_RESULTS) { (5)
console.log('geocode was successful but returned no results. ' +
'This may occur if the geocode was passed a nonexistent ' +
'address or a latlng in a remote location.');
} else if (status == google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
console.log('You are over our quota of requests.');
} else if (status == google.maps.GeocoderStatus.REQUEST_DENIED) {
console.log('Your request was denied, ' +
'generally because of lack of a sensor parameter.');
} else if (status == google.maps.GeocoderStatus.INVALID_REQUEST) {
console.log('Invalid request. ' +
'The query (address or latlng) is missing.');
}
});
}
-
Initiate request to the
Gecoder
object, providing theGeocodeRequest
object with the address and a function to process the results. Because the request to the Google server is asynchronous, the function is a callback. -
When the callback is invoked, it will get an array with results.
-
Get the latitude and longitude from the result.
-
Prepare the LatLng object and give it to the mapping API for rendering.
-
Process errors.
The Geocoding API is simple and free to use until your application reaches a certain number of requests. Refer to the Google Geocoding API documentation for more details. If your application is getting the status code OVER_QUERY_LIMIT
, you need to contact the Google Maps API for Business sales team for information on licensing options.
Our designer, Jerry, has yet another idea: show multiple markers on the map to reflect several donation campaigns and charity events that are going on at various locations. If we display this information on the Save The Child page, more people might donate or participate in other ways. We’ve just learned how to do an address search on the map, and if the application has access to data about charity events, we can display them as the markers on the map. Run project-10-maps-multi-markers and you’ll see a map with multiple markers, as shown in Multiple markers on the map.
The JavaScript fragment in Displaying a map with multiple markers displays the map with multiple markers. In this example, the data is hardcoded in the array charityEvents
. In [using_ajax_and_json], we modify this example to get the data from a file in JSON form. The for loop creates a marker for each of the events listed in the array charityEvents
. Each element of this array is also an array containing the name of the city and state, the latitude and longitude, and the title of the charity event. You can have any other attributes stored in such an array and display them when the user clicks a particular marker in an overlay by calling InfoWindow.open()
.
(function() {
var locationUI = document.getElementById('location-ui');
var locationMap = document.getElementById('location-map');
var charityEvents = [['Chicago, Il', 41.87, -87.62, 'Giving Hand'],
['New York, NY', 40.71, -74.00, 'Lawyers for Children'],
['Dallas, TX', 32.80, -96.76, 'Mothers of Asthmatics '],
['Miami, FL', 25.78, -80.22, 'Friends of Blind Kids'],
['Miami, FL', 25.78, -80.22, 'A Place Called Home'],
['Fargo, ND', 46.87, -96.78, 'Marathon for Survivors']
];
var mapOptions = {
center : new google.maps.LatLng(46.87, -96.78),
zoom : 3,
mapTypeId : google.maps.MapTypeId.ROADMAP,
mapTypeControlOptions : {
style : google.maps.MapTypeControlStyle.DROPDOWN_MENU,
position : google.maps.ControlPosition.TOP_RIGHT
}
};
var map = new google.maps.Map(locationMap, mapOptions);
var infowindow = new google.maps.InfoWindow();
var marker, i;
// JavaScript forEach() function is deprecated,
// hence using a regular for loop
for ( i = 0; i < charityEvents.length; i++) {
marker = new google.maps.Marker({
position : new google.maps.LatLng(charityEvents[i][1],
charityEvents[i][2]),
map : map
});
google.maps.event.addListener(marker, 'click', (function(marker, i) {
return function() {
var content = charityEvents[i][0] + '<br/>' + charityEvents[i][3];
infowindow.setContent(content);
infowindow.open(map, marker);
}
})(marker, i));
google.maps.event.addListenerOnce(map, 'idle', function(){
locationUI.innerHTML = "Donation campaigns and charity events.";
})
}
})();
This chapter described the process of mocking up our future website by our web designer, Jerry, who went a lot further than creating images with short descriptions. Jerry created a working prototype of the Save The Child page. Keep in mind that Jerry and his fellow web designers like creating good-looking web pages.
But we web developers need to worry about other things, like making web pages responsive and lightweight. The first thing you need to do after receiving the prototype from Jerry is run it through Google Developer Tools or Firebug (see the section "Debugging JavaScript in Web Browsers" in the online bonus chapter for details) and measure the total size of the resources being downloaded from the server. If your application loads 1 MB or more worth of images, ask Jerry to review the images and minimize their size.
The chances are that you don’t need to download all the JavaScript code at once. We discuss modularization of large applications in [modularizing_javascript_projects].
The next phase of improving this prototype is to remove the hardcoded data from the code and place it into external files. The next chapter covers the JSON data format and how to fill our single-page application with this data by using a set of techniques called Ajax.