TABLE OF CONTENTS (HIDE)

Client-side UI/UX with Bootstrap, Font Awesome, Parsley JS, Fuel UX and Others

(not for dummies)

In this article, I shall describe some HTML5/CSS3/JavaScript/jQuery libraries (plug-ins) that could zest up the UI/UX of your web pages.

Bootstrap

Bootstrap (@ http://getbootstrap.com) greatly simplifies the coding of UI/UX. You can produce a professional-looking UI/UX in a short time, without writing your own HTML/CSS/JavaScript codes.

"Bootstrap is the most popular HTML, CSS, and JS framework for developing responsive, mobile first projects on the web." ...quoted from Bootstrap mother site.

The important features are:

  • Responsive web design with mobile-first approach.
  • Cross-browser support with consistency.
  • Flexible Grid system for responsive web design.
  • Extensive list of components (such as navigation bars, BreadCrumbs, Alerts) and JavaScript plug-ins (such as Modals, Carousels, and PopOvers).

Setting Up

  1. Download Bootstrap from http://getbootstrap.com.
  2. Unzip.
  3. Copy "bootstrap.min.css" into your "css" folder; and "bootstrap.min.js" into "js" folder.
  4. To use Bootstrap, include Bootstrap CSS and JS in your web pages (typically in the <head> section). You also need to include jQuery JS (as Bootstrap uses jQuery) and a <meta> tag (for responsive web design) as highlighted.
Bootstrap HTML File Template
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>YOUR TITLE HERE</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<!-- Other External CSS files -->
</head>
<body>
<h1>Hello, world!</h1>
<script src="js/jquery-1.11.2.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<!-- Other External JavaScripts -->
</body>
</html>
How It Works?
  • Place the <meta> elements first in <head> section.
  • You place the JavaScript at the end of the document, before </body> ending tag, for better responsiveness, provided these scripts are not referenced in the <head> and <body>.
  • jQuery is needed for Bootstrap. The jQuery JS must be placed before Bootstrap JS.
  • Alternatively, you can use the CDN (Content Distribution Network) for the jQuery JS and BootStrap JS and CSS. Check BootStrap mother site.
Viewport

Mobile browsers render web pages in a virtual window called viewport, which is usually wider than the screen width, so that they do not need to squeeze the page into the screen. Users can pan or zoom to see the different parts of the web page.

Modern mobile browsers support a <meta> tag to control the viewport. The property width sets the viewport's width (to the device-width in the above template); initial-scale sets the zoom level when the page is first loaded (to no zoom in the above template).

JavaScript/CSS Source Map Files (css.map and js.map)

"The .map files are for js and css files that have been minified. They are called SourceMaps. When you minify a file, it takes thousands of lines of pretty code and turns it into only a few lines of ugly code. Hopefully, when you are shipping your code to production, you are using the minified code instead of the full, unminified version. When your app is in production, and has an error, the sourcemap will help take your ugly file, and will allow you to see the original version of the code. If you didn't have the sourcemap, then any error would seem cryptic at best."

Base CSS, Components and JavaScript

Bootstrap is divided into 3 parts:

  1. Base CSS: includes global CSS settings, fundamental HTML elements (typography, code, tables, forms, buttons, and image) styled and enhanced with extensible classes, and an advanced grid system. Base CSS only requires Bootstrap CSS, and does not require the BootStrap JS (and jQuery JS).
  2. Components: include over a dozen reusable components built to provide iconography, drop-downs, input groups, navigation, alerts, and much more.
  3. JavaScript: Bring Bootstrap's components to life with over a dozen custom jQuery plugins.
Getting Started
  1. Study the "Examples" in http://getbootstrap.com/getting-started.
  2. Read the "CSS", "Components", and "JavaScript" sections, which provide detailed explanation on using Bootstrap.

Base CSS - Grid System

Bootstrap organizes the pages in containers, marked with CSS class ".container" for fixed-width and ".container-fluid" for full-width. A container consists of rows, marked with CSS class ".row". A row consists of columns. Contents are to be placed under columns.

A row consists of 12 available column-units on which you could span your columns. To markup a column, use CSS classes ".col-device-colspan". For example, "<div class='col-sm-4'>" marks a column which spans 4 out of the 12 available column-units (i.e., one-third of the row) on small (sm) devices. If more than 12 column-units are placed in a single row, the extra columns will be wrapped onto a new row.

Currently, 4 classes of devices are supported with the following window-width breakpoints:

  1. xs: Extra small devices (Phones) (width < 768px)
  2. sm: Small devices (Tablets) (768px ≤ width < 992px)
  3. md: Medium devices (Desktops) (992px ≤ width < 1200px)
  4. lg: Large devices (Desktops) (width ≥ 1200px)

For a given column, you can specify different column-width for different devices. For example, "<div class='col-xs-8 col-sm-4'>" marks a column which spans 8 column-units (two-third) for extra small (xs) devices; but 4 column-units (one-third) for small (sm) devices. Bootstrap adopts a mobile-first approach. When you declare a grid size for a particular device, it will propagate to the larger devices. For example, the "xs" setting will propagate upwards (to "sm", "md" and "lg") until it is overridden by another setting for the larger devices. However, suppose that you specify only the "md", the "lg" device will follow the setting, but columns will be stacked on smaller devices ("sm" and "md") instead of displaying in a horizontal row. We usually use "md" for this effect.

Study the examples provided in the Bootstrap's CSS section, by resizing the browser's screen to emulate different devices, and use firefox/firebug's "Layout" panel to check the width of the container elements.

Example 1: Bootstrap's Grid System
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<!-- BSGridTest.html -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Testing the Bootstrap Grid</title>
<link href="css/bootstrap-3.3.4.min.css" rel="stylesheet">
</head>
<body>
<div class="container"> <!-- also try container-fluid -->
  <h1>Testing Bootstrap Grid</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor 
     incididunt ut labore et dolore magna aliqua.</p>
  <div class="row">
    <div class="col-md-6">
      <h2><small>First Column (1/2 Page Width)</small></h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 
         tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
    <div class="col-md-3">
      <h2><small>Second Column (1/4 Page Width)</small></h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 
         tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
    <div class="col-md-3">
      <h2><small>Third Column (1/4 Page Width)</small></h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 
         tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
  </div> <!-- end of .row -->
</div> <!-- end of .container -->
</body>
</html>
  • This page has a "container" with a "row". There are 3 columns in the "row", with width of "col-md-6", "col-md-3" and "col-md-3", respectively.
  • In the mobile-first approach, the "md" (992px ≤ width < 1200px) settings apply to "lg", but NOT "sm" and "xs". For large screen, the 3 columns will be displayed in a horizontal row. But if you reduce the window's width to below the breakpoint of 992px, the columns stack on one another and occupy the entire window's width (regardless of the column-unit settings of 6, 3, 3). Try resizing the window to see the effect.
  • Try "container-fluid", which stretches the container to cover the window's full-width.
  • To design you web page for mobile devices as well as desktop, you can set the 3 columns as "col-xs-12 col-md-6", "col-xs-6 col-md-3" and "col-xs-6 col-md-3". On "xs" and "sm", the first column occupies the entire row; the 2nd and 3rd columns stack below on a new row. On "md" and "lg", the 3 columns are arranged in a row. Try it out.
  • Take note that grid system (under base CSS) only require the Bootstrap CSS. It does not need Bootstrap JS (and jQuery JS).
Example 2: Column Offset

To move a column to the right (skipping some columns), use ".col-device-offset-colspan".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<!-- BSColOffset.html -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Testing the Bootstrap Grid</title>
<link href="css/bootstrap-3.3.4.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
  <h1>Testing Bootstrap Grid</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 
     tempor incididunt ut labore et dolore magna aliqua.</p>
  <div class="row">
    <div class="col-md-4 col-md-offset-8">
      <h2><small>First Column (1/3 Page Width, Offset 2/3)</small></h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 
         eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
  </div> <!-- end of .row -->
</div> <!-- end of .container -->
</body>
</html>
  • On "md" and "lg", the column is offset by 2/3 of the window's width. On "xs" and "sm", the offset is not shown and the column occupies the entire window's width.
Example 3: Swapping Columns (Push-Right and Pull-Left)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<!-- BSColPushPull.html -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Testing the Bootstrap Grid</title>
<link href="css/bootstrap-3.3.4.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
  <h1>Testing Bootstrap Grid</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
     eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
  <div class="row">
    <div class="col-md-10 col-md-push-2">
      <h2><small>Main Column (Push Right)</small></h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
         eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
    <div class="col-md-2 col-md-pull-10">
      <h2><small>Navigation Column (Pull Left)</small></h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
         eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
    </div>
  </div> <!-- end of .row -->
</div> <!-- end of .container -->
</body>
</html>
  • On "md" and "lg", the main column (appears first in the HTML page) is pushed right, and displayed on the right; while the navigation column is pulled left and displayed on the left. In other words, the column orders are swapped.
  • On "xs" and "sm", these settings do not apply. The main column is stacked on top of the navigation column, in accordance to the sequence of the codes.
How It Works?

The Bootstrap CSS for .container, .row, .col-device-x, .col-device-pull-x, .col-device-push-x, and .col-device-offset-x are extracted as follows. Bootstrap adopts mobile-first approach (instead of desktop-first), where "xs" receives the default settings, and media queries (via @media) is used for "sm", "md", "lg", in that order, to override the default settings.

.container {
  padding-right: 15px; padding-left: 15px;
  margin-right: auto; margin-left: auto;  /* center */
}
/* Default if width < 768px (xs), unless overridden via media query */
@media (min-width: 768px) {  /* For width >= 768px (sm), unless overridden via media query */
  .container {
    width: 750px;
  }
}
@media (min-width: 992px) {  /* For width >= 992px (md), unless overridden via media query */
  .container {
    width: 970px;
  }
}
@media (min-width: 1200px) {  /* For width >= 992px (lg) */
  .container {
    width: 1170px;
  }
}
.container-fluid {
  padding-right: 15px; padding-left: 15px;
  margin-right: auto; margin-left: auto;  /* center */
}
.row {
  margin-right: -15px; margin-left: -15px;
}
 
/* All column setting */
.col-xs-1,...,.col-xs-12, .col-sm-1,...,.col-sm-12, 
.col-md-1,...,.col-md-12, .col-lg-1,...,.col-lg-12 {
  position: relative;
  min-height: 1px;
  padding-right: 15px; padding-left: 15px;
}
 
/* .col-xs-x, .col-xs-pull-x, .col-xs-push-x, .col-xs-offset-x
   Default column settings unless overridden via media queries */
.col-xs-1,...,.col-xs-12 {
  float: left;
}
/* set column width */
.col-xs-12 { width: 100%; }
.col-xs-11 { width: 91.66666667%; }
.col-xs-10 { width: 83.33333333%; }
......
.col-xs-2  { width: 16.66666667%; }
.col-xs-1  { width: 8.33333333%; }
/* pull left, add right */
.col-xs-pull-12 { right: 100%; }
.col-xs-pull-11 { right: 91.66666667%; }
......
.col-xs-pull-2  { right: 16.66666667%; }
.col-xs-pull-1  { right: 8.33333333%; }
.col-xs-pull-0  { right: auto; }
/* push right, add left */
.col-xs-push-12 { left: 100%; }
.col-xs-push-11 { left: 91.66666667%; }
......
.col-xs-push-2  { left: 16.66666667%; }
.col-xs-push-1  { left: 8.33333333%; }
.col-xs-push-0  { left: auto; }
/* column offset, add left margin */
.col-xs-offset-12 { margin-left: 100%; }
.col-xs-offset-11 { margin-left: 91.66666667%; }
......
.col-xs-offset-2  { margin-left: 16.66666667%; }
.col-xs-offset-1  { margin-left: 8.33333333%; }
.col-xs-offset-0  { margin-left: 0; }
 
/* for "sm", unless overridden via media query */
@media (min-width: 768px) {  
  /* .col-sm-x, .col-sm-pull-x, .col-sm-push-x, .col-sm-offset-x, as above "xs" */
}
 
/* for "md", unless overridden via media query */
@media (min-width: 992px) {
  /* .col-md-x, .col-md-pull-x, .col-md-push-x, .col-md-offset-x, as above "xs" */
}
 
/* for "lg" */
@media (min-width: 1200px) {
  /* .col-lg-x, .col-lg-pull-x, .col-lg-push-x, .col-lg-offset-x, as above "xs" */
}

Base CSS - Typography

Bootstrap's global default font-size is 14px, with a line-height of 1.428.

Headings <h1> to <h6>
  • <h1>: default font size of 38px, 2.70 times the default font size.
  • <h2>: default font size of 32px, 2.25 times the default font size.
  • <h3>: default font size of 24px, 1.70 times the default font size.
  • <h4>: default font size of 18px, 1.25 times the default font size.
  • <h5>: same font size (14px) as the default font size.
  • <h6>: default font size of 12px, 0.85 times the default font size.
  • Classes .h1 to .h6 are also defined. For example, <div class="h1">....</div>.
  • You can nest a <small> (or class "small") under the headings to reduce the font size to 65% (for <h1> to <h3>) and 75% (for <h4> to <h6>)
body {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 14px;
  line-height: 1.42857143;
}
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
  font-family: inherit;
  font-weight: 500;
  line-height: 1.1;
  color: inherit;
}
h1, h2, h3, .h1, .h2, .h3 {
  margin-top: 20px;
  margin-bottom: 10px;
}
h4, h5, h6, .h4, .h5, .h6 {
  margin-top: 10px;
  margin-bottom: 10px;
}
h1, .h1 { font-size: 36px; }
h2, .h2 { font-size: 30px; }
h3, .h3 { font-size: 24px; }
h4, .h4 { font-size: 18px; }
h5, .h5 { font-size: 14px; }
h6, .h6 { font-size: 12px; }
h1 small, h2 small, h3 small { font-size: 65%; }
h4 small, h5 small, h6 small { font-size: 75%; }
Lead and Small Paragraphs

You can apply CSS class "lead" for a lead paragraph, with bigger font size and line-height so as to stand out among others. To reduce the font size, apply nested <small> or class "small".

.lead {
  margin-bottom: 20px;
  font-size: 16px;
  font-weight: 300;
  line-height: 1.4;
}
@media (min-width: 768px) {  /* "sm", "md", "lg" */
  .lead { font-size: 21px; }
}
small, .small { font-size: 85%; }
Inline Text Element Markups
  • <strong>, <b>: bold
  • <mark>: highlight text
  • <sup>, <sub>: superscript and subscript.
  • <small>: smaller font size. Note that <big> is not supported in HTML5.
  • <em>, <i>, <u>, <del>, <ins>, <s>: No new rules in Bootstrap's CSS, but supported.
strong, b { font-weight: bold; }
mark { color: #000; background: #ff0; }  /* black on yellow */
small { font-size: 80%; }
sub, sup { position: relative; font-size: 75%; line-height: 0; vertical-align: baseline; } sup { top: -.5em; } sub { bottom: -.25em; }
Text Alignment, Transformation and Color

Use Bootstrap's CSS classes "text-center", "text-right", "text-right", "text-justify" and "text-nowrap" for text alignment.

.text-left    { text-align: left; }
.text-right   { text-align: right; }
.text-center  { text-align: center; }
.text-justify { text-align: justify; }
.text-nowrap  { white-space: nowrap; }

Use Bootstrap's CSS classes "text-lowercase", "text-uppercase" and "text-capitalize" for text transformation.

.text-lowercase  { text-transform: lowercase; }
.text-uppercase  { text-transform: uppercase; }
.text-capitalize { text-transform: capitalize; }

Use Bootstrap's CSS classes "text-primary", "text-info", "text-muted", "text-success", "text-warning", "text-danger" for various text colors, as shown:

.text-primary { color: #337ab7; }
.text-info    { color: #31708f; }
.text-muted   { color: #777;    }
.text-success { color: #3c763d; }
.text-warning { color: #8a6d3b; }
.text-danger  { color: #a94442; }

Similarly, you can set the various background colors/effects, as follows:

.bg-primary { color: #fff; background-color: #337ab7; }
.bg-info    { background-color: #d9edf7; }
.bg-success { background-color: #dff0d8; }
.bg-warning { background-color: #fcf8e3; }
.bg-danger  { background-color: #f2dede; }
Lists
  • Ordered Lists (<ol>, <li>), unordered lists (<ul>, <li>), and definition list (<dl>, <dt>, <dd>) are supported.
  • To remove the bullets or numbering, apply class "list-unstyled".
  • For inline list, apply class "list-inline".

Program Codes

  • <code>: program code in monospace font
  • <kbd>: keyboard (or user) input
  • <pre>:
  • <var>: variable
  • <samp>: sample output

Base CSS - Tables

  • Mark a table with class "table". i.e., <table class='table'>.
  • To add zebra-strips to the rows in <tbody>, use <table class='table table-stripped'>.
  • To show the borders, use <table class='table table-bordered'>.
  • To highlight the pointed (hover) row, use <table class='table table-hover'>.
  • To compact the table, use <table class='table table-condensed'>.
  • To color a row, use classes "active" (hover state), "info" (blue), "success" (green), "warning" (orange), and "danger" (red), on <tr>, <th> or <td>.
  • For responsive table, use <table class='table table-responsive'>, which makes the table scroll horizontally on small devices (under 768px).

Refer to the examples in Bootstrap.

Base CSS - Buttons

  • It is preferable to use <button type=button|submit|reset> element for button, instead of <input type="submit|reset|button> or <a>.
  • Add class "btn", i.e., <button class="btn">.
  • To style a button, use classes "btn-default", "btn-primary", "btn-info", "btn-success", "btn-warning", "btn-danger" or "btn-link".
  • To change the size of button, use classes "btn-lg" (bigger than "btn-default"), "btn-sm", "btn-xs" (smaller than "btn-default").
  • To span the full-width (i.e., block button), add "btn-block".
  • To disable a button, add "disabled".
  • To activate a button programmatically, add "active".

Base CSS - Form

Read the CSS's "form" section, which is clearly presented with many examples.

Bootstrap supports 3 kinds of forms: normal (no additional classes), inline (marked with class "form-inline"), and horizontally tabulated (marked with class "form-horizontal").

  • Add "form-control" class to input elements such as <input>, <textarea> and <select>; Use "help-block" class for help text; Use "form-control-static" for plain-text control.
  • For text input, wrap the <label> and text in "form-group" class, using a <div>. For checkbox and radio, wrap the label and checkbox/radio under "checkbox", "checkbox-inline", "radio", "radio-inline".

For example,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<!DOCTYPE html>
<!-- BSFormBasic.html -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Testing the Bootstrap Form Control</title>
<link href="css/bootstrap-3.3.4.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
  <h1>Testing Bootstrap Form</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
     eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
 
  <form>
    <!-- Wrap the <label> and <input> under <div class="form-group"> -->
    <div class="form-group">
      <label for="email">Email address</label>
      <!-- Add "form-control" to input control -->
      <input type="email" class="form-control" id="email" placeholder="Enter your email">
    </div>
    <div class="form-group">
      <label for="password">Password</label>
      <input type="password" class="form-control" id="password" placeholder="Enter your password">
      <p class="help-block">Password shall be 6-20 characters</p>
    </div>
 
    <fieldset class="checkbox">
      <legend>Choose your color:</legend>
      <!-- Wrap label and checkbox under "checkbox" or "checkbox-inline" -->
      <label class="checkbox-inline">
        <input type="checkbox" name="color" value="red"> Red
      </label>
      <label class="checkbox-inline">
        <input type="checkbox" name="color" value="green"> Green
      </label>
      <label class="checkbox-inline">
        <input type="checkbox" name="color" value="blue"> Blue
      </label>
    </fieldset>
 
    <fieldset class="radio">
      <legend>Choose your gender:</legend>
      <label class="radio-inline">
        <input type="radio" name="gender" value="m"> Male
      </label>
      <label class="radio-inline">
        <input type="radio" name="gender" value="f"> Female
      </label>
    </fieldset>
 
    <button type="submit" class="btn btn-default">Submit</button>
  </form>
</div> <!-- end of .container -->
</body>
</html>
Horizontally Tabulated Form

To tabulate label and control, use <form class='form-horizontal'> (which arrange a "form-group" in a row). Set the column with of label and control via "col-md-n" class. Push columns to the right via "col-md-offset-n" class.

Login Form

Study the "Sign-in Page" example. To control the width of the form, you can set its "max-width" (as used in the "signin.css"):

<form class="form-signin">
......
</form >
/* signin.css */
.form-signin {
  max-width: 330px;
  padding: 15px;
  margin: 0 auto;
}
......

Base CSS - Images

Bootstrap provides 3 image formatting classes: "img-rounded", "img-circle", "img-thumbnail".

Base CSS - Helper Classes

  • Carets (for drop-down menu): <span class="caret"></span>
  • Center block: <div class="center-block">...</div>
  • Float and Clear: use classes "pull-left", "pull-right", "clearfix".
  • Show and Hide: use classes "show" and "hidden".

Component - Navigation Bar

Read the "Components"'s "Nav" and "NavBar" sections, which are clearly presented with many examples.

Study all the "Navbars in action" examples.

Example

[TODO]

Bootstrap JavaScript Plugins

Tooltips

Tooltips are used to provide hints for icons, links, and buttons, which are displayed when you place your mouse pointer over them.

In HTML5, you can attach a tooltip to an element via the "title" attribute. Bootstrap JavaScript Tooltips plugin allows you to place the tooltips on top, bottom, left or right of the component.

Popovers

Popovers are used for housing secondary information for any element by adding small overlays of content.

Accordion

Collapsible panels

ScrollSpy

The navigation menu gets highlighted based on the scroll position.

Modals

A modal is a special dialog box that provides crucial information to the users. It is usually displayed on top of a "faded" and "disabled" background.

Carousels

Slide show of images which are displayed in a cyclic manner.

Bootstrap Extensions

Open Source Themes and Templates
More Plugins
Editors
Utilities
  • Bootlint Online (@ http://www.bootlint.com/): Checks for several common HTML mistakes in webpages that are using Bootstrap in a fairly "vanilla" way.

Icon Libraries

Placing some nice icons in your web pages greatly zests up your UI.

Font Awesome

Font Awesome (@ http://fortawesome.github.io/Font-Awesome) provides a nice set of over 400 scalable vector icons that can be easily included into your web pages, and customized for size, color, etc.

Installation and Setup
  1. Download from http://fortawesome.github.io/Font-Awesome and unzip.
  2. Copy the "font-awesome.min.css" into your "css" folder; copy the "fonts" folders too.
  3. To use Font Awesome, simply include the Font Awesome's CSS in your page:
    <link rel="stylesheet" href="css/font-awesome.min.css">
Getting Started

Read the "Get Started" section @ http://fortawesome.github.io/Font-Awesome/get-started, followed by the "Examples" section.

Using Font Awesome is straight-forward:

  • To include a particular icon, simply write "<i class='fa fa-icon-name'></i>" or more syntactically correct "<span class='fa fa-icon-name'></span>". You can check the icon-name from the "Icons" section.
  • To size up the icon, add class name "fa-lg" (33% larger), "fa-2x", "fa-3x", "fa-3x", "fa-4x" or fa-5x.
  • To set the icon to fixed-width for proper alignment (e.g., in tables and list), add class name "fa-fw".
  • To set the icon in list, add class name "fa-ul" to <ul> and "fa-li" to <li>.
  • More: Read the Examples.

You can use Font Awesome together with Bootstrap. Font Awesome has a larger set of icons which suits many web pages.

How It Works?

In the past, web designers are limited to a few web-safe fonts that are available on most computers. CSS3 adds support for web fonts, that can be downloaded together with the style sheets. This is carried out via the @font-face at-rule.

For example, the "font-awesome.css" begins with:

@font-face {
  font-family: 'FontAwesome';
  src: url('../fonts/fontawesome-webfont.eot?v=4.3.0');  /* IE9 */
  src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'),  /* IE6-IE8 */ 
       url('../fonts/fontawesome-webfont.woff2?v=4.3.0') format('woff2'),  /* Super Modern Browsers */
       url('../fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'),  /* Pretty Modern Browsers */
       url('../fonts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'),  /* Safari, Android, iOS */
       url('../fonts/fontawesome-webfont.svg?v=4.3.0#fontawesomeregular') format('svg');  /* Legacy iOS */
  font-weight: normal;
  font-style: normal;
}

where:

  • font-family: define the font-family name, which can be used in the font-family property of your style rule, e.g., body { font-family: name }
  • src: font file location, can be expressed in url() for remote font file, or local() for local font. The url's will be tried in sequence, until one is found to be suitable for the browser. For Font Awesome, the font files must be kept in the "fonts" directory, at the same level as "css".

To include a icon, we are required to use <i class='fa fa-icon-name'></i>. The style rules for .fa and .fa-send (as an example of an icon) are as follows:

.fa {
  display: inline-block;
  font: normal normal normal 14px/1 FontAwesome;
        /* font-style font-variant font-weight font-size/line-height font-family */
  font-size: inherit;
  text-rendering: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transform: translate(0, 0);
}
 
.fa-send:before {
  content: "\f1d8";
}
  • The font-family refers to the custom web font.
  • The :before (or ::before in CSS3) pseudo-element selector inserts the content value before the innerHTML of the element (i.e., <i>), which is a Unicode defined for the icon in the font files. (The ::before and ::after are the only CSS rules that can modify the contents of the HTML document!) Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen readers do not read off random characters that represent icons.
Font file Format
  • EOF (Embedded Open Type): A proprietary file standard, created by Microsoft, supported only by IE (≤9). No other browsers support EOT.
  • WOFF/WOFF2 (Web Open Font Format): Load faster because they use a compressed version of OTF and TTF. WOFF2 is the next generation that boasts better compression. This seems to be the winner. Supported by all newer browsers (IE ≥9, FireFox, Chrome, etc.).
  • OTF (OpenType Font) and TTF (TrueType Font): Traditional font formats used in desktops.
  • SVG (Scalable Vector Graphics); old mobile version of Safari and Android.

Take note that Font Awesome provides all 6 font formats (WOFF2/WOFF, EOT, OTF, TTF, and SVG), so as to support all browsers and devices. WOFF2 has the smallest file size.

CSS Pre-processors (less and scss)

CSS rules can be written with CSS preprocessor, which lets you write styles using programming constructs (such as variables) and then compile into the final CSS.

Font Awesome uses less and scss. Read "less\font-awesome.less" and "scss\font-awesome.scss".

Bootstrap's Glyphicons

Bootstrap includes 200 glyphs from Glyphicon Halflings set. In Bootstrap 3, these icon are also scalable.

Installation and Setup
  1. No additional download as it is included in Bootstrap.
  2. Copy the "fonts" folders to your web app, at the same directory level as "css".
  3. To use Glyphicon, simply include the Bootstrap CSS in your page:
    <link rel="stylesheet" href="css/bootstrap.min.css">
Getting Started

Using Glyphicons is straight-forward: To include a particular icon, simply write "<span class='glyphicon glyphicon-icon-name'></span>". You can check the icon-name from http://getbootstrap.com/components.

How It works?

Refer to "How it works" section for "Font Awesome".

Bootstrap glyphicon includes 5 font formats: EOT, WOFF/WOFF2, TTF, SVG. The relevant CSS rules are:

@font-face {
  font-family: 'Glyphicons Halflings';   /* define the font-family name */
  src: url('../fonts/glyphicons-halflings-regular.eot');   /* IE9 */
  src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),  /* IE6-8 */
       url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'),  /* Modern Browsers */
       url('../fonts/glyphicons-halflings-regular.woff') format('woff'), 
       url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), 
       url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
}
 
.glyphicon {
  position: relative;
  top: 1px;
  display: inline-block;
  font-family: 'Glyphicons Halflings';
  font-style: normal;
  font-weight: normal;
  line-height: 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}  

/* An icon example */ 
.glyphicon-asterisk:before {
  content: "\2a";
}

Parsley JS for Form/Field Input Validation

Parsley (@ http://parsleyjs.org), is an excellent JavaScript/jQuery library for form input validation.

Setting Up

  1. Download the "parsley.zip" from http://parsleyjs.org/doc/download.html. Unzip.
  2. Copy the JavaScript files to your JavaScript folder, e.g., "js"; and CSS file to your CSS folder, e.g., "css"
  3. Include the jQuery JS, Parsley JS, and Parsley CSS and in your web pages. Take note that Parsley uses jQuery.
    <script src="js/jquery-1.11.2.min.js"></script>
    <script src="js/parsley.min.js"></script>
    <link rel="stylesheet" href="css/parsley.css">

Getting Started

You MUST read the "Examples" and "Documentation" at the Parsley mother site.

I shall present some examples instead of repeating the information.

Example 1: Form Validation using HTML5 (for Comparison)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<!-- H5FormCompare.html -->
<html lang="en">
<head>
<meta charset="utf-8">
<title>HTML5 Form Validation</title>
</head>
<body>
<form>
  <label>Name:
    <input type="text" name="name" autofocus
         required pattern="^\w{4,32}$"></label>
  <label>Email:
    <input type="email" name="email" required></label><br>
  <input type="submit">
</form>
</body>
</html>
How It works?
  • For the first <input type="text"> field, HTML5 inspects attributes "required" and "pattern" to carry out form input validation. In our example, the "pattern" is assigned a regex which matches 4-32 word characters.
  • For the second <input type="email"> field, HTML5 inspects attribute "required" to validate this field.
  • HTML5 validates form input by default. To disable form validation, use <form novalidate>.

Try it out! Observe the bubble error messages "Please fill out this field.", "Please match the requested format.", and "Please enter an email address.". Also observe that validation is first done during the "onchange" (i.e., when you push Enter or click outside the text field).

Example 2: Form Validation Using Parsley

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<!-- ParsleyFormValidation.html -->
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test Parsley Form Validation</title>
<link rel="stylesheet" href="css/parsley-2.0.7.css">
<script src="js/jquery-1.11.2.min.js"></script>
<script src="js/parsley-2.0.7.min.js"></script>
</head>
 
<body>
<form data-parsley-validate >
  <label>Name:
  <input type="text" name="name" autofocus
         required data-parsley-trigger="change"
         pattern="^\w{4,32}$"
         data-parsley-pattern-message="Invalid name (4-32 characters)!"
         data-parsley-required-message="Name is required!"></label>
  <label>Email:
  <input type="email" name="email"
         required data-parsley-trigger="keyup"></label><br>
  <input type="submit">
</form>
</body>
</html>
How It works?
  • Parsley added custom "data-*" attributes, in the form of "data-parsley-*" to the form elements.
  • You do NOT need to write any JavaScript codes?!
  • We include "data-parsley-validate" in the <form> tag to bind Parsley validation to the form.
  • For the first <input type="text"> field, Parsley uses HTML5's attributes "required" and "pattern" for validation. In our example, the "pattern" is assigned a regex matching 4-32 word characters. We also override the default error messages for the "required" and "pattern" fields, via the "data-parsley-required-message" and "data-parsley-pattern-message", respectively. The data-parsley-trigger="change" specifies that validation is triggered upon an onchange event (i.e., pushing Enter or Tab key, or click outside the field).
  • For the second <input type="email"> field, Parsley uses type="email" to match for a valid email. The data-parsley-trigger="keyup" specifies that validation is triggered upon an onkeyup event (i.e., whenever you pushes a key).

Try out this form! It works only if you can see the overridden error messages. Otherwise, use FireFox/Firebug to debug your script!

Notes
  • Even though you specified data-parsley-trigger="keyup", Parsley does not carry out validation with less than 4 characters ("Do not assault your users with error messages too soon!")
  • When a field is detected as invalid, further checks are done on each keystroke, to quickly remove the error message when the field input is valid. Hence, the "data-parsley-trigger" setting is only applicable for the initial detection.
  • The "submit" button will not work, unless all the fields are valid.

Example: Field Validation (without Form) with Parsley Ex. 1

Suppose that we wish to validate an input field, which is not wrapped inside a <form> element.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<!-- ParsleyFieldEx1.html -->
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test Parsley Field Validation</title>
<link rel="stylesheet" href="css/parsley-2.0.7.css">
<script src="js/jquery-1.11.2.min.js"></script>
<script src="js/parsley-2.0.7.min.js"></script>
<script>
$(document).ready( function() {
   // Bind all <input> elements for Parsley validation
   $('input').parsley();
});
</script>
</head>
 
<body>
  <input type="text" name="name" placeholder="Enter your name"
      required pattern="^\w{4,32}$"
      data-parsley-pattern-message="Invalid name!"
      data-parsley-required-message="Name is required!"
      data-parsley-trigger="change">
  <input type="email" name="email" placeholder="Enter your email"
      required data-parsley-trigger="change">
</body>
</html>
How It works?
  • The document contains two <input> elements which is not wrapped inside a <form> (for whatsoever reasons such as tabulating the <input> in rows).
  • In the first <input type="text">, we specify the validation criteria via the "required" and "pattern" attributes; and the error messages via "data-parsley-pattern-message" and "data-parsley-pattern-message". We also set "data-parsley-trigger='change'".
  • In the jQuery script, we bind the <input> elements to Parsley to perform validation at "data-parsley-trigger='change'".

Example: Field Validation (without Form) with Parsley Ex. 2

Further to the above example, suppose that we also wish to carry out processing for the input elements, if it is validated, upon onchange.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<!-- ParsleyFieldEx2.html -->
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test Parsley Field Validation</title>
<link rel="stylesheet" href="css/parsley-2.0.7.css">
<script src="js/jquery-1.11.2.min.js"></script>
<script src="js/parsley-2.0.7.min.js"></script>
<script>
$(document).ready( function() {
   // Suppose that you need to process the <input> elements upon onchange
   //   instead of purely validation.
   // No 'data-parsley-trigger="change"' in the <input> elements.
   // But bind an onchange handler to the <input>'s which triggers
   //   Parsley validation before processing.
   $('input').change( function(event) {
      event.preventDefault();
      if ($(this).parsley().validate() === true) {
         alert("OK and Processed!");
      }
   });
});
</script>
</head>
 
<body>
  <input type="text" name="name" placeholder="Enter your name"
         required pattern="^\w{4,32}$"
         data-parsley-pattern-message="Invalid name!"
         data-parsley-required-message="Name is required!">
         <!-- No data-parsley-trigger -->
  <input type="email" name="email" placeholder="Enter your email" required>
</body>
</html>
How It Work?
  • Instead of binding the <input> elements to Parsley to perform validation at "data-parsley-trigger='change'", we remove "data-parsley-trigger='change'" from the <input> elements, but bind an onchange handler to the <input>'s.
  • The onchange handler invokes ".parsley.validate()" to trigger Parsley validation, which returns true if valid, but something else if not valid (hence, we need to compare with true).
  • The processing operations shall follow once the input is validated.
Notes
  • Parsley also provides a function called ".parsley.isValid()" which triggers Parsley validation (like ".parsley.validate()"), but does not affect the UI nor show the error messages.

Example: Field Validation (without Form) with Parsley Ex. 3

Further to the above examples, suppose that we wish to validate all the input fields, but perform processing via a button if all inputs are valid.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<!-- ParsleyFieldEx3.html -->
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test Parsley Field Validation</title>
<link rel="stylesheet" href="css/parsley-2.0.7.css">
<script src="js/jquery-1.11.2.min.js"></script>
<script src="js/parsley-2.0.7.min.js"></script>
<script>
$(document).ready( function() {
   // Bind the <input> elements for Parsley validation
   // triggered at data-parsley-trigger="change".
   $('input').parsley();
 
   // Bind an onclick handler to the <button>
   // to trigger Parsley validation on all fields upon click.
   $('button').click( function(event) {
      event.preventDefault();
      // Validate all input fields.
      var isValid = true;
      $('input').each( function() {
         if ($(this).parsley().validate() !== true) isValid = false;
      });
      if (isValid) {
         alert("OK and Processed!");
      }
   });
});
</script>
</head>
 
<body>
  <input type="text" name="name" id="name" placeholder="Enter your name"
         required pattern="^\w{4,32}$"
         data-parsley-pattern-message="Invalid name!"
         data-parsley-required-message="Name is required!"
         data-parsley-trigger="change">
  <input type="email" name="email" id="email" placeholder="Enter your email"
         required data-parsley-trigger="change">
  <button type="button">Send</button>
</body>
</html>
How It Works?
  • We validate all the input fields by binding the <input> elements to Parsley to perform validation at "data-parsley-trigger='change'", as in the first example.
  • We bind an onclick handler to the <button>, which invokes ".parsley.validate()" on all the <input> fields before processing the button. No special attributes for <button> are needed.

Fuel UX

Fuel UX extends Bootstrap with additional lightweight JavaScript controls for your webapps, such as Datepicker, Scheduler, Repeater and Wizard. It also styles controls such as checkbox, radio, combobox, infinite scroll, select. The mother site is http://getfuelux.com/index.html.

Firebug/Web Developer Tools seem unable to debug Fuel UX?! But Chrome is working.

Datepicker

Note: Too much codes, try jQuery UI Datepicker (@ https://jqueryui.com/datepicker/) instead.

I re-arrange the examples provided by Fuel UX for easier understanding.

Example 1: Without JavaScript

Wrap the control under a <div> with class="datapicker" and custom data attribute data-initialize="datapicker", as highlighted.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fuel UX Datepicker (No JavaScript)</title>
<link href="css/bootstrap-3.3.4.min.css" rel="stylesheet">
<link href="css/fuelux-3.6.3.min.css" rel="stylesheet">
<script src="js/jquery-1.11.2.min.js"></script>
<script src="js/bootstrap-3.3.4.min.js"></script>
<script src="js/fuelux-3.6.3.min.js"></script>
</head>
 
<body class="fuelux">
<div class="container">
  <h1>Fuel UX Datepicker (No JavaScript)</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
     eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
 
  <div class="datepicker" data-initialize="datepicker" id="myDatepicker">
    <div class="input-group">
      <input type="text" class="form-control" id="myDatepickerInput">
      <div class="input-group-btn">
        <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
          <span class="glyphicon glyphicon-calendar"></span>
          <span class="sr-only">Toggle Calendar</span>
        </button>
        <div class="dropdown-menu dropdown-menu-right datepicker-calendar-wrapper" role="menu">
          <div class="datepicker-calendar">
            <div class="datepicker-calendar-header">
              <button type="button" class="prev">
                <span class="glyphicon glyphicon-chevron-left"></span>
                <span class="sr-only">Previous Month</span></button>
              <button type="button" class="next">
                <span class="glyphicon glyphicon-chevron-right">
                </span><span class="sr-only">Next Month</span></button>
              <button type="button" class="title">
                <span class="month">
                  <span data-month="0">January</span>
                  <span data-month="1">February</span>
                  <span data-month="2">March</span>
                  <span data-month="3">April</span>
                  <span data-month="4">May</span>
                  <span data-month="5">June</span>
                  <span data-month="6">July</span>
                  <span data-month="7">August</span>
                  <span data-month="8">September</span>
                  <span data-month="9">October</span>
                  <span data-month="10">November</span>
                  <span data-month="11">December</span>
                </span>
                <span class="year"></span>
              </button>
            </div>
            <table class="datepicker-calendar-days">
              <thead>
                <tr>
                  <th>Su</th>
                  <th>Mo</th>
                  <th>Tu</th>
                  <th>We</th>
                  <th>Th</th>
                  <th>Fr</th>
                  <th>Sa</th>
                </tr>
              </thead>
              <tbody></tbody>
            </table>
            <div class="datepicker-calendar-footer">
              <button type="button" class="datepicker-today">Today</button>
            </div>
          </div>
          <div class="datepicker-wheels" aria-hidden="true">
            <div class="datepicker-wheels-month">
              <h2 class="header">Month</h2>
              <ul>
                <li data-month="0"><button type="button">Jan</button></li>
                <li data-month="1"><button type="button">Feb</button></li>
                <li data-month="2"><button type="button">Mar</button></li>
                <li data-month="3"><button type="button">Apr</button></li>
                <li data-month="4"><button type="button">May</button></li>
                <li data-month="5"><button type="button">Jun</button></li>
                <li data-month="6"><button type="button">Jul</button></li>
                <li data-month="7"><button type="button">Aug</button></li>
                <li data-month="8"><button type="button">Sep</button></li>
                <li data-month="9"><button type="button">Oct</button></li>
                <li data-month="10"><button type="button">Nov</button></li>
                <li data-month="11"><button type="button">Dec</button></li>
              </ul>
            </div>
            <div class="datepicker-wheels-year">
              <h2 class="header">Year</h2>
              <ul></ul>
            </div>
            <div class="datepicker-wheels-footer clearfix">
              <button type="button" class="btn datepicker-wheels-back">
                <span class="glyphicon glyphicon-arrow-left"></span>
                <span class="sr-only">Return to Calendar</span></button>
              <button type="button" class="btn datepicker-wheels-select">Select
                <span class="sr-only">Month and Year</span></button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div> <!-- end of .datepicker -->
</div> <!-- end of .container -->
</body>
</html>
Example 2: With JavaScript

Instead of using the custom data attribute "data-initialize" to initialize Datepicker, we invoke JavaScript method .datepicker({ init-object }), in this example. We also change the date format to MMMM-YY-DD (MySQL Date Format), but this requires Moment JS (@ http://momentjs.com/). In addition, we created some buttons to illustrate the various JavaScript methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fuel UX Datepicker (No JavaScript)</title>
<link href="css/bootstrap-3.3.4.min.css" rel="stylesheet">
<link href="css/fuelux-3.6.3.min.css" rel="stylesheet">
<script src="js/jquery-1.11.2.min.js"></script>
<script src="js/bootstrap-3.3.4.min.js"></script>
<script src="js/moment-with-locales-2.10.3.min.js"></script>
<script src="js/fuelux-3.6.3.min.js"></script>
<script>
$(document).ready( function() {
   /* initialize datepicker */
   $('#myDatepicker').datepicker({
      allowPastDates: true,
      momentConfig: {          /* require moment.js */
         culture: 'en',
         format: 'YYYY-MM-DD'  /* in MySQL Date format */
      }
   });
 
   /* event handlers */
   $('#myDatepicker').on('changed.fu.datepicker', function (event, data) {
      console.log('datepicker change event fired');
   });
 
   $('#myDatepicker').on('inputParsingFailed.fu.datepicker', function () {
      console.log('datepicker inputParsingFailed event fired');
   });
 
   /* for the buttons */
   $('#btnDatepickerEnable').on('click', function () {
      $('#myDatepicker').datepicker('enable');
   });
 
   $('#btnDatepickerDisable').on('click', function () {
      $('#myDatepicker').datepicker('disable');
   });
 
   jQuery('#btnDatepickerLogFormattedDate').on('click', function () {
      console.log($('#myDatepicker').datepicker('getFormattedDate'));
   });
 
   $('#btnDatepickerLogDateObj').on('click', function () {
      console.log($('#myDatepicker').datepicker('getDate'));
   });
 
   $('#btnDatepickerSetDate').on('click', function () {
      var futureDate = new Date(+new Date() + (7 * 24 * 60 * 60 * 1000));
      $('#myDatepicker').datepicker('setDate', futureDate);
      console.log($('#datepicker').datepicker('getDate'));
   });
 
   $('#btnDatepickerDestroy').on('click', function () {
      var markup = $('#myDatepicker').datepicker('destroy');
      console.log(markup);
      $(this).closest('.section').append(markup);
   });
});
</script>
 
</head>
 
 
<body class="fuelux">
<div class="container">
  <h1>Fuel UX Datepicker (No JavaScript)</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
     eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
 
  <div class="datepicker" id="myDatepicker">
    <div class="input-group">
      <input type="text" class="form-control" id="myDatepickerInput">
      <div class="input-group-btn">
        <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
          <span class="glyphicon glyphicon-calendar"></span>
          <span class="sr-only">Toggle Calendar</span>
        </button>
        <div class="dropdown-menu dropdown-menu-right datepicker-calendar-wrapper" role="menu">
          <div class="datepicker-calendar">
            <div class="datepicker-calendar-header">
              <button type="button" class="prev">
                <span class="glyphicon glyphicon-chevron-left"></span>
                <span class="sr-only">Previous Month</span></button>
              <button type="button" class="next">
                <span class="glyphicon glyphicon-chevron-right">
                </span><span class="sr-only">Next Month</span></button>
              <button type="button" class="title">
                <span class="month">
                  <span data-month="0">January</span>
                  <span data-month="1">February</span>
                  <span data-month="2">March</span>
                  <span data-month="3">April</span>
                  <span data-month="4">May</span>
                  <span data-month="5">June</span>
                  <span data-month="6">July</span>
                  <span data-month="7">August</span>
                  <span data-month="8">September</span>
                  <span data-month="9">October</span>
                  <span data-month="10">November</span>
                  <span data-month="11">December</span>
                </span>
                <span class="year"></span>
              </button>
            </div>
            <table class="datepicker-calendar-days">
              <thead>
                <tr>
                  <th>Su</th>
                  <th>Mo</th>
                  <th>Tu</th>
                  <th>We</th>
                  <th>Th</th>
                  <th>Fr</th>
                  <th>Sa</th>
                </tr>
              </thead>
              <tbody></tbody>
            </table>
            <div class="datepicker-calendar-footer">
              <button type="button" class="datepicker-today">Today</button>
            </div>
          </div>
          <div class="datepicker-wheels" aria-hidden="true">
            <div class="datepicker-wheels-month">
              <h2 class="header">Month</h2>
              <ul>
                <li data-month="0"><button type="button">Jan</button></li>
                <li data-month="1"><button type="button">Feb</button></li>
                <li data-month="2"><button type="button">Mar</button></li>
                <li data-month="3"><button type="button">Apr</button></li>
                <li data-month="4"><button type="button">May</button></li>
                <li data-month="5"><button type="button">Jun</button></li>
                <li data-month="6"><button type="button">Jul</button></li>
                <li data-month="7"><button type="button">Aug</button></li>
                <li data-month="8"><button type="button">Sep</button></li>
                <li data-month="9"><button type="button">Oct</button></li>
                <li data-month="10"><button type="button">Nov</button></li>
                <li data-month="11"><button type="button">Dec</button></li>
              </ul>
            </div>
            <div class="datepicker-wheels-year">
              <h2 class="header">Year</h2>
              <ul></ul>
            </div>
            <div class="datepicker-wheels-footer clearfix">
              <button type="button" class="btn datepicker-wheels-back">
                <span class="glyphicon glyphicon-arrow-left"></span>
                <span class="sr-only">Return to Calendar</span></button>
              <button type="button" class="btn datepicker-wheels-select">Select
                <span class="sr-only">Month and Year</span></button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div> <!-- end of .datepicker -->
 
  <br>
  <h5 id="datepicker-examples-sample-methods">Sample Methods</h5>
  <div class="btn-group">
    <button class="btn btn-default" id="btnDatepickerEnable">enable</button>
    <button class="btn btn-default" id="btnDatepickerDisable">disable</button>
    <button class="btn btn-default" id="btnDatepickerLogFormattedDate">log formatted date</button>
    <button class="btn btn-default" id="btnDatepickerLogDateObj">log date object</button>
  </div>
  <div class="btn-group">
    <button class="btn btn-default" id="btnDatepickerSetDate">set date 7 days ahead (will log new value)</button>
    <button class="btn btn-default" id="btnDatepickerDestroy">destroy and append</button>
  </div>
 
</div> <!-- end of .container -->
</body>
</html>

Repeater

References:

  1. Fuel UX Repeater Tutorial @ http://fuelux-tutorials.herokuapp.com/repeater/.
  2. Fuel UX Control "Repeater" @ http://getfuelux.com/javascript.html#repeater.
  3. Fuel UX Extension "Repeater List View" and "Repeater Thumbnail View" @ http://getfuelux.com/extensions.html#bundled-extensions-list.
Example 1: Basic Static DataSource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
<!DOCTYPE html>
<!-- FuelRepeaterBasic.html -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fuel UX Repeater</title>
<link href="css/bootstrap-3.3.4.min.css" rel="stylesheet">
<link href="css/fuelux-3.6.3.min.css" rel="stylesheet">
<script src="js/jquery-1.11.2.min.js"></script>
<script src="js/bootstrap-3.3.4.min.js"></script>
<script src="js/fuelux-3.6.3.min.js"></script>
 
<script>
$(document).ready( function() {
   // initialize the repeater
   $('#myRepeater').repeater({
      dataSource: myDataSource   /* bind the dataSource */
   });
});
 
// Define the columns (in array of column objects)
var columns = [
   { 'label'   : 'Name',   // Column header label
     'property': 'name',   // bind to items' key
     'sortable': true      // is the column sortable?
   },
   { 'label'   : 'Description',
     'property': 'desc',
     'sortable': false
   },
   { 'label'   : 'Status',
     'property': 'status',
     'sortable': true
   },
   { 'label'   : 'Category',
     'property': 'cat',
     'sortable': true
   }
];
 
// Define the data items (rows) (hardcoded here)
// The key should match the columns' property defined above
var items = [
   { "name": "Name 1",  "desc": "Desc 1",  "status": "draft",    "cat":"A" },
   { "name": "Name 2",  "desc": "Desc 2",  "status": "active",   "cat":"C" },
   { "name": "Name 3",  "desc": "Desc 3",  "status": "archived", "cat":"B" },
   { "name": "Name 4",  "desc": "Desc 4",  "status": "active",   "cat":"C" },
   { "name": "Name 5",  "desc": "Desc 5",  "status": "archived", "cat":"D" },
   { "name": "Name 6",  "desc": "Desc 6",  "status": "archived", "cat":"C" },
   { "name": "Name 7",  "desc": "Desc 7",  "status": "active",   "cat":"D" },
   { "name": "Name 8",  "desc": "Desc 8",  "status": "draft",    "cat":"C" },
   { "name": "Name 9",  "desc": "Desc 9",  "status": "draft",    "cat":"A" },
   { "name": "Name 10", "desc": "Desc 10", "status": "draft",    "cat":"B" },
   { "name": "Name 11", "desc": "Desc 11", "status": "draft",    "cat":"C" },
   { "name": "Name 12", "desc": "Desc 12", "status": "archived", "cat":"C" },
   { "name": "Name 13", "desc": "Desc 13", "status": "active",   "cat":"D" },
   { "name": "Name 14", "desc": "Desc 14", "status": "archived", "cat":"A" },
   { "name": "Name 15", "desc": "Desc 15", "status": "draft",    "cat":"B" },
   { "name": "Name 16", "desc": "Desc 16", "status": "archived", "cat":"C" },
   { "name": "Name 17", "desc": "Desc 17", "status": "active",   "cat":"C" },
   { "name": "Name 18", "desc": "Desc 18", "status": "archived", "cat":"A" },
   { "name": "Name 19", "desc": "Desc 19", "status": "active",   "cat":"C" },
   { "name": "Name 20", "desc": "Desc 20", "status": "draft",    "cat":"D" },
   { "name": "Name 21", "desc": "Desc 21", "status": "draft",    "cat":"B" }
];
 
/* Call back to render the desired page */
function myDataSource(options, callback) {
   // Prepare displayData for the callback function
   var pageIndex = options.pageIndex;  // page index, starting from 0
   var pageSize = options.pageSize;    // number of items per page
   var totalItems = items.length;      // total number of items
   var totalPages = Math.ceil(totalItems / pageSize);  // number of pages
   var startIndex = (pageIndex * pageSize) + 1; // start index (from 0) for desired page
   var endIndex = (startIndex + pageSize) - 1;  // end index for desired page
   if (endIndex > totalItems) {
      endIndex = totalItems;  // adjust for last page
   }
   var rows = items.slice(startIndex - 1, endIndex);  // slice items for the desired page
 
   // Set the displayData object for the callback function
   var displayData = {
      'page'    : pageIndex,
      'pages'   : totalPages,
      'count'   : totalItems,
      'start'   : startIndex,
      'end'     : endIndex,
      'columns' : columns,
      'items'   : rows
   };
 
   // Pass the displayData back to the repeater to render
   callback(displayData);
}
</script>
 
</head>
<body class="fuelux">
<div class="container">
  <h1>Fuel UX Repeater Basic</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
     eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
 
  <!-- Repeater -->
  <div class="repeater" id="myRepeater">
    <div class="repeater-header">
      <div class="repeater-header-left">
        <div class="repeater-search">
          <div class="search disabled input-group">
            <input type="search" class="form-control" placeholder="Search">
            <span class="input-group-btn">
              <button class="btn btn-default" type="button">
                <span class="glyphicon glyphicon-search"></span>
                <span class="sr-only">Search</span>
              </button>
            </span>
          </div>
        </div>
      </div> <!-- end of .repeater-header-left -->
      <div class="repeater-header-right">
        <span>Filter Status: </span>
        <div class="btn-group selectlist disabled repeater-filters">
          <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
            <span class="selected-label"> </span>
            <span class="caret"></span>
            <span class="sr-only">Toggle Filters</span>
          </button>
          <ul class="dropdown-menu pull-right" role="menu">
            <li data-value="all" data-property="all" data-selected="true">
              <a href="#">All</a></li>
            <li class="divider"></li>
            <li data-value="draft" data-property="status"><a href="#">Draft</a></li>
            <li data-value="archived" data-property="status"><a href="#">Archived</a></li>
            <li data-value="active" data-property="status"><a href="#">Active</a></li>
          </ul>
          <input class="hidden hidden-field" name="filterSelection"
            readonly="readonly" aria-hidden="true" type="text">
        </div>
      </div> <!-- end of .repeater-header-right -->
    </div>  <!-- end of .repeater-header -->
 
    <div class="repeater-viewport">
      <div class="repeater-canvas"></div>
      <div class="loader repeater-loader"></div>
    </div>
 
    <div class="repeater-footer">
      <div class="repeater-footer-left">
        <div class="repeater-itemization">
          <span>
            <span class="repeater-start"></span> -
            <span class="repeater-end"></span> of
            <span class="repeater-count"></span> items</span>
          <div class="btn-group selectlist dropup" data-resize="auto">
            <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
              <span class="selected-label"> </span>
              <span class="caret"></span>
              <span class="sr-only">Toggle Dropdown</span>
            </button>
            <ul class="dropdown-menu" role="menu">
              <li data-value="10" data-selected="true"><a href="#">10</a></li>
              <li data-value="25"><a href="#">25</a></li>
              <li data-value="50"><a href="#">50</a></li>
            </ul>
            <input class="hidden hidden-field" name="itemsPerPage"
                   readonly="readonly" aria-hidden="true" type="text">
          </div>
          <span>Per Page</span>
        </div>
      </div> <!-- end of .repeater-footer-left -->
      <div class="repeater-footer-right">
        <div class="repeater-pagination">
          <button type="button" class="btn btn-default btn-sm repeater-prev">
            <span class="glyphicon glyphicon-chevron-left"></span>
            <span class="sr-only">Previous Page</span>
          </button>
          <label class="page-label" id="myPageLabel">Page</label>
          <div class="repeater-primaryPaging active">
            <div class="input-group input-append dropdown combobox dropup">
              <input type="text" class="form-control" aria-labelledby="myPageLabel">
              <div class="input-group-btn">
                <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
                  <span class="caret"></span>
                  <span class="sr-only">Toggle Dropdown</span>
                </button>
                <ul class="dropdown-menu dropdown-menu-right"></ul>
              </div>
            </div>
          </div>
          <input type="text" class="form-control repeater-secondaryPaging"
                 aria-labelledby="myPageLabel">
          <span>of <span class="repeater-pages"></span></span>
          <button type="button" class="btn btn-default btn-sm repeater-next">
            <span class="glyphicon glyphicon-chevron-right"></span>
            <span class="sr-only">Next Page</span>
          </button>
        </div>
      </div> <!-- end of .repeater-footer-right -->
    </div> <!-- end of .repeater-footer -->
  </div> <!-- end of .repeater -->
 
</div> <!-- end of .container -->
</body>
</html>
How It Works?

To initialize the Repeater, invoke JavaScript method .repeater(initOptions). The argument initOptions is an object, which shall contain a key dataSource, with a dataSource function as value.

// Initialize the repeater
$('#myRepeater').repeater({'dataSource': myDataSource});
 
// Call back to obtain display data for the current page
function myDataSource(options, callback) {
   ......
};

The dataSource function is called back by the fuel UX engine to obtain the display data, prior to rendering the current page. It passes back 2 arguments: an options object and a callback function.

In options object, Fuel UX gathers the options selected by the users for the desired page to be displayed (e.g., page number to display, search string, filter value, sort column, etc). They are:

  • options.pageIndex (number): the desired page index, obtained from .repeater-pagination. The page index begins at 0.
  • options.pageSize (number): number of items per page, obtained from .repeater-itemization.

From options.pageIndex, options.pageSize and the total number of items, we could setup the displayData of the desired page for the callback function, as follows:

var pageIndex = options.pageIndex;   // page index, starting from 0
var pageSize = options.pageSize;     // number of items per page
var totalItems = items.length;       // Total number of items
var totalPages = Math.ceil(totalItems / pageSize);  // number of pages
var startIndex = (pageIndex * pageSize) + 1;  // start index of the desired page, this index starts from 1
var endIndex = (startIndex + pageSize) - 1;   // end index for the desired page
if (endIndex > totalItems) {
   endIndex = totalItems;  // last page? 
}
var rows = items.slice(startIndex - 1, endIndex); // items for desired page

The options object also includes these keys for searching, filtering and sorting on column:

  • options.search (string): the search string, obtained from .repeater-search.
  • options.filter (object): filtering value, obtained from .repeater-filter. The options.filter.property and options.filter.value provides the property (column) and the filter value to be used.
  • options.sortProperty (string) and options.sortDirection (string): specifies the sorting property (column) and direction ('asc' or 'desc').

Fuel UX supports two types of views: List view and Thumbnail view.

  • options.view (string): the view option, obtained from .repeater-view.

Searching, filtering and sorting options require custom processing, to be described in latter examples.

At the end of the dataSource function, we invoke the callback function with a displayData object as argument for rendering the desired page. The displayData object has these keys:

  • count (number): total number of items.
  • page (number): current page index (starting from 0).
  • pages (number): total number of pages.
  • start (number): start index for the current page (starting from 1).
  • end (number): end index for the current page.
  • columns (array of column objects): to provide the columns definition. Each column object could have these keys:
    • label (string): column heading label.
    • property (string): bind to this property of the items (to be described below).
    • sortable (boolean): sortable on this column (only can sort on one column).
    • sortDirection (boolean): 'asc' or 'desc'.
    • className (string): value for the class attribute for CSS styling. Multiple classes are separated by space.
    • width (string or number): column width (for styling).
    The common column representation is {'label':xxx, 'property':xxx, 'sortable': true|false}.
  • items (array of item objects): rows for the current page. Each item object represents a row of data. The item object's keys are bound to the column's property; but you are free to provide fewer properties (no display for that column) or more properties (for custom rendering). The items (rows) shall already be processed for search, filter, column sort; and shall be display in the order presented.

In this example, the search, filter, column sort are not functioning, as we did not handle them.

Example 2: Searching, Data Filtering and Column Sorting

You can obtain:

  • the search string from options.search.
  • the filter object from options.filter.property and options.filter.value. we use a special string of 'all' for all items (coded under repeater-filters).
  • the sort column from options.sortProperty and options.sortDirection ('asc'|'desc').

To implement searching, filtering, and column sorting, we need to write more codes. Replace the JavaScript of the previous example by the followings, and include the Underscore JS (@ http://underscorejs.org/).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
......
<script src="js/underscore-1.8.3.min.js"></script>
<script src="js/fuelux-3.6.3.min.js"></script>
 
<script>
$(document).ready( function() {
   // initialize the repeater
   $('#myRepeater').repeater({
      dataSource: myDataSource   /* bind the dataSource */
   });
});
 
// Define the columns (in array of column objects)
var columns = [
   { 'label'   : 'Name',   // Column header label
     'property': 'name',   // bind to items' key
     'sortable': true      // is the column sortable?
   },
   { 'label'   : 'Description',
     'property': 'desc',
     'sortable': false
   },
   { 'label'   : 'Status',
     'property': 'status',
     'sortable': true
   },
   { 'label'   : 'Category',
     'property': 'cat',
     'sortable': true
   }
];
 
// Define the data items (rows) (hardcoded here)
// The key should match the columns' property defined above
var items = [
   { "name": "Name 1",  "desc": "Desc 1",  "status": "draft",    "cat":"A" },
   { "name": "Name 2",  "desc": "Desc 2",  "status": "active",   "cat":"C" },
   { "name": "Name 3",  "desc": "Desc 3",  "status": "archived", "cat":"B" },
   { "name": "Name 4",  "desc": "Desc 4",  "status": "active",   "cat":"C" },
   { "name": "Name 5",  "desc": "Desc 5",  "status": "archived", "cat":"D" },
   { "name": "Name 6",  "desc": "Desc 6",  "status": "archived", "cat":"C" },
   { "name": "Name 7",  "desc": "Desc 7",  "status": "active",   "cat":"D" },
   { "name": "Name 8",  "desc": "Desc 8",  "status": "draft",    "cat":"C" },
   { "name": "Name 9",  "desc": "Desc 9",  "status": "draft",    "cat":"A" },
   { "name": "Name 10", "desc": "Desc 10", "status": "draft",    "cat":"B" },
   { "name": "Name 11", "desc": "Desc 11", "status": "draft",    "cat":"C" },
   { "name": "Name 12", "desc": "Desc 12", "status": "archived", "cat":"C" },
   { "name": "Name 13", "desc": "Desc 13", "status": "active",   "cat":"D" },
   { "name": "Name 14", "desc": "Desc 14", "status": "archived", "cat":"A" },
   { "name": "Name 15", "desc": "Desc 15", "status": "draft",    "cat":"B" },
   { "name": "Name 16", "desc": "Desc 16", "status": "archived", "cat":"C" },
   { "name": "Name 17", "desc": "Desc 17", "status": "active",   "cat":"C" },
   { "name": "Name 18", "desc": "Desc 18", "status": "archived", "cat":"A" },
   { "name": "Name 19", "desc": "Desc 19", "status": "active",   "cat":"C" },
   { "name": "Name 20", "desc": "Desc 20", "status": "draft",    "cat":"D" },
   { "name": "Name 21", "desc": "Desc 21", "status": "draft",    "cat":"B" }
];
 
// Maintain the current filter option.
// No need to invoke dataFilter() if the option is the same.
var currentFilterOptions = {
   filterVaue: '',
   filterProperty: '',
   sortProperty: '',
   sortDirection: '',
   search: ''
};
var currentItems = [];
 
/* Call back to render the desired page */
function myDataSource(options, callback) {
   // Run filtering, searching and column sorting on items
   var filteredItems = dataFilter(options);
 
   // Prepare displayData for the callback function
   var pageIndex = options.pageIndex;  // page index, starting from 0
   var pageSize = options.pageSize;    // number of items per page
   var totalItems = filteredItems.length;      // total number of items
   var totalPages = Math.ceil(totalItems / pageSize);  // number of pages
   var startIndex = (pageIndex * pageSize) + 1; // start index (from 0) for desired page
   var endIndex = (startIndex + pageSize) - 1;  // end index for desired page
   if (endIndex > totalItems) {
      endIndex = totalItems;  // adjust for last page
   }
   var rows = filteredItems.slice(startIndex - 1, endIndex);  // slice items for the desired page
 
   // Set the displayData object for the callback function
   var displayData = {
      'page'    : pageIndex,
      'pages'   : totalPages,
      'count'   : totalItems,
      'start'   : startIndex,
      'end'     : endIndex,
      'columns' : columns,
      'items'   : rows
   };
 
   // Pass the displayData back to the repeater to render
   callback(displayData);
}
 
/*
 * Helper to handle filter, search and column sort. Operate directly on items.
 * Need Underscore JS.
 * Filter value in options.filter.property and options.filter.value
 * Search string in options.search.
 * Sort column in options.sortProperty and options.sortDirection.
 * Use global variables items, currentItems, currentFilterOptions.
 */
function dataFilter(options) {
   // Do nothing if there is no change in any of the filter options
   if ((currentFilterOptions.filterVaue === options.filter.value)
       && (currentFilterOptions.filterProperty === options.filter.property)
       && (currentFilterOptions.sortProperty === options.sortProperty)
       && (currentFilterOptions.sortDirection === options.sortDirection)
       && (currentFilterOptions.search === options.search)) {
      return currentItems;
   }
 
   currentItems = $.extend([], items);  // clone items to filter
 
   // Handle filter first
   var filterRegex = new RegExp(options.filter.value, 'i');
      // Explicitly make a regex object instead of just using String.search()
      // to avoid confusion with FuelUX search() and options.search
   if (!filterRegex.test('all')) {  // no filtering for the special value 'all'
      currentItems = _.filter(currentItems, function (item) {
         var isFilterMatch = filterRegex.test(item[options.filter.property]);
         return isFilterMatch;
      });
   }
 
   // Handle search next (search within the filtered rows)
   if (options.search) {  // non-empty
      var searchRegex = new RegExp(options.search, 'i');
      currentItems = _.filter(currentItems, function (item) {
         // collapse all item property values to a single string to make
         //   matching on it easier to manage
         var itemLine = _.reduce(_.values(_.omit(item)), function (finalText, currentText) {
            return finalText + " " + currentText;
         });
         var isSearchMatch = searchRegex.test(itemLine);
         return isSearchMatch;
      });
   }
 
   // Hanlde column sort last
   if (options.sortProperty) {
      currentItems = _.sortBy(currentItems, function (item) {
         return item[options.sortProperty];
      });
      if (options.sortDirection === 'desc') {
         currentItems.reverse();
      }
   }
 
   // Update the current filter option
   currentFilterOptions.filterVaue = options.filter.value;
   currentFilterOptions.filterProperty = options.filter.property;
   currentFilterOptions.sortProperty = options.sortProperty;
   currentFilterOptions.sortDirection = options.sortDirection;
   currentFilterOptions.search = options.search;
 
   return currentItems;
};
</script>

We used Underscore JS (@ http://underscorejs.org/) to filter and sort the items, before setting up the displayData.

[TODO] More explanation

A limitation of repeater is it can only filter on one criterion, NOT multiple criteria. Also, you can only sort on one column, NOT nested columns.

Example 3: Retrieving Data Rows from Server via an Ajax JSON response.

In this example, if the data has not been loaded, we send an Ajax request to do a database query, and return the results in JSON for rendering. Otherwise, we will skip the Ajax request.

SQL Script

We use the following SQL script to create our database.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
use test;
 
drop table if exists RepeaterData;
create table RepeaterData (
   name varchar(20),
   `desc` varchar(40),
   status varchar(10),
   cat varchar(10),
   primary key (name)
);
 
insert into RepeaterData values
   ( 'Name 1',  'Desc 1',  'draft',    'A' ),
   ( 'Name 2',  'Desc 2',  'active',   'C' ),
   ( 'Name 3',  'Desc 3',  'archived', 'B' ),
   ( 'Name 4',  'Desc 4',  'active',   'C' ),
   ( 'Name 5',  'Desc 5',  'archived', 'D' ),
   ( 'Name 6',  'Desc 6',  'archived', 'C' ),
   ( 'Name 7',  'Desc 7',  'active',   'D' ),
   ( 'Name 8',  'Desc 8',  'draft',    'C' ),
   ( 'Name 9',  'Desc 9',  'draft',    'A' ),
   ( 'Name 10', 'Desc 10', 'draft',    'B' ),
   ( 'Name 11', 'Desc 11', 'draft',    'C' ),
   ( 'Name 12', 'Desc 12', 'archived', 'C' ),
   ( 'Name 13', 'Desc 13', 'active',   'D' ),
   ( 'Name 14', 'Desc 14', 'archived', 'A' ),
   ( 'Name 15', 'Desc 15', 'draft',    'B' ),
   ( 'Name 16', 'Desc 16', 'archived', 'C' ),
   ( 'Name 17', 'Desc 17', 'active',   'C' ),
   ( 'Name 18', 'Desc 18', 'archived', 'A' ),
   ( 'Name 19', 'Desc 19', 'active',   'C' ),
   ( 'Name 20', 'Desc 20', 'draft',    'D' ),
   ( 'Name 21', 'Desc 21', 'draft',    'B' );
 
select * from RepeaterData;
Server-side PHP Script FuelRepeaterGetData.php

This PHP script returns all the row as a JSON object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
/**
 * Retrieving data for Fuel UX Repeater
 */
// Define the MySQL database parameters.
$DB_HOST = 'localhost'; // MySQL server hostname
$DB_PORT = '3306';      // MySQL server port number (default 3306)
$DB_NAME = 'test';      // MySQL database name
$DB_USER = 'xxxx';      // MySQL username
$DB_PASS = 'xxxx';      // password
 
try {
   // Create a PDO database connection to MySQL server
   $pdo = new PDO("mysql:host=$DB_HOST;port=$DB_PORT;dbname=$DB_NAME", $DB_USER, $DB_PASS);
   $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Set error mode to exception
 
   // Get all records.
   $stmt = $pdo->prepare('SELECT * FROM RepeaterData');
   $stmt->execute();
   $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
   $jsonResult = json_encode($results);
   echo $jsonResult;
 
   // Close the database connection (optional).
   $pdo = NULL;
 
} catch (PDOException $e) {
   $fileName = basename($e->getFile(), ".php"); // Filename that trigger the exception
   $lineNumber = $e->getLine();         // Line number that triggers the exception
   die("[$fileName][$lineNumber] Database error: " . $e->getMessage() . '<br>');
}
?>
HTML File

Replace the script in the earlier example with the followings, and include the Underscore JS.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
......
<script src="js/underscore-1.8.3.min.js"></script>
<script src="js/fuelux-3.6.3.min.js"></script>
 
<script>
$(document).ready( function() {
   // initialize the repeater
   $('#myRepeater').repeater({
      dataSource: myDataSource   /* bind the dataSource */
   });
});
 
// Define the columns (in array of column objects)
var columns = [
   { 'label'   : 'Name',   // Column header label
     'property': 'name',   // bind to items' key
     'sortable': true      // is the column sortable?
   },
   { 'label'   : 'Description',
     'property': 'desc',
     'sortable': false
   },
   { 'label'   : 'Status',
     'property': 'status',
     'sortable': true
   },
   { 'label'   : 'Category',
     'property': 'cat',
     'sortable': true
   }
];
 
// Data rows. Init to null to be loaded from database.
var items = null;
 
// Maintain the current filter option.
// No need to invoke dataFilter() if the option is the same.
var currentFilterOptions = {
   filterVaue: '',
   filterProperty: '',
   sortProperty: '',
   sortDirection: '',
   search: ''
};
var currentItems = [];
 
/* Call back to render the desired page */
function myDataSource(options, callback) {
   if (items === null) {
      /*
       * Data rows not populated yet.
       * Send an Ajax Request to Database Server, which returns a JSON response.
       * You can also load the items in ready().
       */
      $.ajax({
         'url'     : 'FuelRepeaterGetData.php',
         'type'    : 'post',
         'dataType': 'json'   // Response as JSON
      })
         .done( function(responseText) {
            items = responseText;  // populate global items
            myDataSourceHelper(options, callback);
         });
 
   } else {
      myDataSourceHelper(options, callback);
   }
}
 
/*
 * The items shall already have been populated.
 * Handle search, filter, sort; and prepare the displayData.
 */
function myDataSourceHelper(options, callback) {
   // Run filtering, searching and column sorting on items
   var filteredItems = dataFilter(options);
 
   // Prepare displayData for the callback function
   var pageIndex = options.pageIndex;  // page index, starting from 0
   var pageSize = options.pageSize;    // number of items per page
   var totalItems = filteredItems.length;      // total number of items
   var totalPages = Math.ceil(totalItems / pageSize);  // number of pages
   var startIndex = (pageIndex * pageSize) + 1; // start index (from 0) for desired page
   var endIndex = (startIndex + pageSize) - 1;  // end index for desired page
   if (endIndex > totalItems) {
      endIndex = totalItems;  // adjust for last page
   }
   var rows = filteredItems.slice(startIndex - 1, endIndex);  // slice items for the desired page
 
   // Set the displayData object for the callback function
   var displayData = {
      'page'    : pageIndex,
      'pages'   : totalPages,
      'count'   : totalItems,
      'start'   : startIndex,
      'end'     : endIndex,
      'columns' : columns,
      'items'   : rows
   };
 
   // Pass the displayData back to the repeater to render
   callback(displayData);
}
 
/*
 * Helper to handle filter, search and column sort. Operate directly on items.
 * Need Underscore JS.
 * Filter value in options.filter.property and options.filter.value
 * Search string in options.search.
 * Sort column in options.sortProperty and options.sortDirection.
 */
function dataFilter(options) {
   // Do nothing if there is no change in any of the filter options
   if ((currentFilterOptions.filterVaue === options.filter.value)
       && (currentFilterOptions.filterProperty === options.filter.property)
       && (currentFilterOptions.sortProperty === options.sortProperty)
       && (currentFilterOptions.sortDirection === options.sortDirection)
       && (currentFilterOptions.search === options.search)) {
      return currentItems;
   }
 
   currentItems = $.extend([], items);  // clone items to filter
 
   // Handle filter first
   var filterRegex = new RegExp(options.filter.value, 'i');
      // Explicitly make a regex object instead of just using String.search()
      // to avoid confusion with FuelUX search() and options.search
   if (!filterRegex.test('all')) {  // no filtering for the special value 'all'
      currentItems = _.filter(currentItems, function (item) {
         var isFilterMatch = filterRegex.test(item[options.filter.property]);
         return isFilterMatch;
      });
   }
 
   // Handle search next (search within the filtered rows)
   if (options.search) {  // non-empty
      var searchRegex = new RegExp(options.search, 'i');
      currentItems = _.filter(currentItems, function (item) {
         // collapse all item property values to a single string to make
         //   matching on it easier to manage
         var itemLine = _.reduce(_.values(_.omit(item)), function (finalText, currentText) {
            return finalText + " " + currentText;
         });
         var isSearchMatch = searchRegex.test(itemLine);
         return isSearchMatch;
      });
   }
 
   // Hanlde column sort last
   if (options.sortProperty) {
      currentItems = _.sortBy(currentItems, function (item) {
         return item[options.sortProperty];
      });
      if (options.sortDirection === 'desc') {
         currentItems.reverse();
      }
   }
 
   // Update the current filter option
   currentFilterOptions.filterVaue = options.filter.value;
   currentFilterOptions.filterProperty = options.filter.property;
   currentFilterOptions.sortProperty = options.sortProperty;
   currentFilterOptions.sortDirection = options.sortDirection;
   currentFilterOptions.search = options.search;
 
   return currentItems;
};
</script>

[TODO] Client-side filtering vs. Server-side filtering?

Example 4: Custom Column Cell Rendering

To perform custom column cell rendering, such as injecting markup or combining multiple properties, you can write a custom column rendering function and register the function during initialization of repeater:

$(document).ready( function() {
   // initialize the repeater
   $('#myRepeater').repeater({
      // register dataSource
      dataSource: myDataSource,
      // Setup custom column cell renderer for the list view
      list_columnRendered: myColumnRenderer
   });
});
          
// Call back after rendering each table cell
function myColumnRenderer(helpers, callback) {
   ......
   ......
   callback();  // continue rendering
}

The Fuel UX repeater calls this function after rendering each table cell within a row. It passes two arguments: a helpers object and a callback function:

  • helpers.rowData: All key/value data from the current item/row in the dataSource.
  • helpers.columnAttr: The property specified by dataSource.columns of the current column/cell.
  • helpers.container: jQuery element of the current tr or table row for manipulating the row.
  • helpers.item: jQuery element of the current td or table cell for manipulating the current cell.
  • callback: Call this function to continue rendering the view.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
......
<script src="js/underscore-1.8.3.min.js"></script>
<script src="js/fuelux-3.6.3.min.js"></script>
 
<script>
$(document).ready( function() {
   // initialize the repeater
   $('#myRepeater').repeater({
      dataSource: myDataSource,   /* bind the dataSource */
      list_columnRendered: myColumnRenderer  /* custom column renderer */
   });
});
 
/*
 * Define the columns
 * We shall combine the name and desc under one column;
 *  style the status column; do nothing to category;
 *  add a new column for controls (checkbox, button)
 */
var columns = [
   { 'label'   : 'Name/Description', // combine name and desc
     'property': 'name',
     'sortable': true
   },
   { 'label'   : 'Status',
     'property': 'status',
     'sortable': true
   },
   { 'label'   : 'Category',
     'property': 'cat',
     'sortable': true
   },
   { 'label'   : 'Action',  // new column of controls
     'property': 'action'
   }
];
 
// Define the data items (rows) (hardcoded here)
var items = [
   { "name": "Name 1",  "desc": "Desc 1",  "status": "draft",    "cat":"A" },
   { "name": "Name 2",  "desc": "Desc 2",  "status": "active",   "cat":"C" },
   { "name": "Name 3",  "desc": "Desc 3",  "status": "archived", "cat":"B" },
   { "name": "Name 4",  "desc": "Desc 4",  "status": "active",   "cat":"C" },
   { "name": "Name 5",  "desc": "Desc 5",  "status": "archived", "cat":"D" },
   { "name": "Name 6",  "desc": "Desc 6",  "status": "archived", "cat":"C" },
   { "name": "Name 7",  "desc": "Desc 7",  "status": "active",   "cat":"D" },
   { "name": "Name 8",  "desc": "Desc 8",  "status": "draft",    "cat":"C" },
   { "name": "Name 9",  "desc": "Desc 9",  "status": "draft",    "cat":"A" },
   { "name": "Name 10", "desc": "Desc 10", "status": "draft",    "cat":"B" },
   { "name": "Name 11", "desc": "Desc 11", "status": "draft",    "cat":"C" },
   { "name": "Name 12", "desc": "Desc 12", "status": "archived", "cat":"C" },
   { "name": "Name 13", "desc": "Desc 13", "status": "active",   "cat":"D" },
   { "name": "Name 14", "desc": "Desc 14", "status": "archived", "cat":"A" },
   { "name": "Name 15", "desc": "Desc 15", "status": "draft",    "cat":"B" },
   { "name": "Name 16", "desc": "Desc 16", "status": "archived", "cat":"C" },
   { "name": "Name 17", "desc": "Desc 17", "status": "active",   "cat":"C" },
   { "name": "Name 18", "desc": "Desc 18", "status": "archived", "cat":"A" },
   { "name": "Name 19", "desc": "Desc 19", "status": "active",   "cat":"C" },
   { "name": "Name 20", "desc": "Desc 20", "status": "draft",    "cat":"D" },
   { "name": "Name 21", "desc": "Desc 21", "status": "draft",    "cat":"B" }
];
 
// Maintain the current filter option.
// No need to invoke dataFilter() if the option is the same.
var currentFilterOptions = {
   filterVaue: '',
   filterProperty: '',
   sortProperty: '',
   sortDirection: '',
   search: ''
};
var currentItems = [];
 
/* Call back to render the desired page */
function myDataSource(options, callback) {
   // Run filtering, searching and column sorting on items
   var filteredItems = dataFilter(options);
 
   // Prepare displayData for the callback function
   var pageIndex = options.pageIndex;  // page index, starting from 0
   var pageSize = options.pageSize;    // number of items per page
   var totalItems = filteredItems.length;      // total number of items
   var totalPages = Math.ceil(totalItems / pageSize);  // number of pages
   var startIndex = (pageIndex * pageSize) + 1; // start index (from 0) for desired page
   var endIndex = (startIndex + pageSize) - 1;  // end index for desired page
   if (endIndex > totalItems) {
      endIndex = totalItems;  // adjust for last page
   }
   var rows = filteredItems.slice(startIndex - 1, endIndex);  // slice items for the desired page
 
   // Set the displayData object for the callback function
   var displayData = {
      'page'    : pageIndex,
      'pages'   : totalPages,
      'count'   : totalItems,
      'start'   : startIndex,
      'end'     : endIndex,
      'columns' : columns,
      'items'   : rows
   };
 
   // Pass the displayData back to the repeater to render
   callback(displayData);
}
 
/*
 * Handle filter, search and column sort. Operate directly on items.
 * Need Underscore JS.
 * Filter value in options.filter.property and options.filter.value
 * Search string in options.search.
 * Sort column in options.sortProperty and options.sortDirection.
 */
function dataFilter(options) {
   // Do nothing if there is no change in any of the filter options
   if ((currentFilterOptions.filterVaue === options.filter.value)
       && (currentFilterOptions.filterProperty === options.filter.property)
       && (currentFilterOptions.sortProperty === options.sortProperty)
       && (currentFilterOptions.sortDirection === options.sortDirection)
       && (currentFilterOptions.search === options.search)) {
      return currentItems;
   }
 
   currentItems = $.extend([], items);  // clone items to filter
 
   // Handle filter first
   var filterRegex = new RegExp(options.filter.value, 'i');
      // Explicitly make a regex object instead of just using String.search()
      // to avoid confusion with FuelUX search() and options.search
   if (!filterRegex.test('all')) {  // no filtering for the special value 'all'
      currentItems = _.filter(currentItems, function (item) {
         var isFilterMatch = filterRegex.test(item[options.filter.property]);
         return isFilterMatch;
      });
   }
 
   // Handle search next (search within the filtered rows)
   if (options.search) {  // non-empty
      var searchRegex = new RegExp(options.search, 'i');
      currentItems = _.filter(currentItems, function (item) {
         // collapse all item property values to a single string to make
         //   matching on it easier to manage
         var itemLine = _.reduce(_.values(_.omit(item)), function (finalText, currentText) {
            return finalText + " " + currentText;
         });
         var isSearchMatch = searchRegex.test(itemLine);
         return isSearchMatch;
      });
   }
 
   // Hanlde column sort last
   if (options.sortProperty) {
      currentItems = _.sortBy(currentItems, function (item) {
         return item[options.sortProperty];
      });
      if (options.sortDirection === 'desc') {
         currentItems.reverse();
      }
   }
 
   // Update the current filter option
   currentFilterOptions.filterVaue = options.filter.value;
   currentFilterOptions.filterProperty = options.filter.property;
   currentFilterOptions.sortProperty = options.sortProperty;
   currentFilterOptions.sortDirection = options.sortDirection;
   currentFilterOptions.search = options.search;
 
   return currentItems;
};
 
/*
 * Custom column (cell) renderer.
 * Call back after rendering each cell.
 * We combine the name and description properties under one column;
 *  style the status; and populate Action column with controls.
 */
function myColumnRenderer(helpers, callback) {
   var column = helpers.columnAttr;   /* dataSource.columns.property */
 
   // get all the data for the entire row
   var rowData = helpers.rowData;     /* row data in key-value pair */
   var customMarkup = '';
 
   // Override the output for specific columns.
   switch(column) {
      case 'name':
         // let's combine name and description into a single column
         customMarkup = '<a href="#">' + rowData.name
               + '</a><div class="small text-muted">' + rowData.desc + '</div>';
         break;
 
      case 'status':
         // let's change the text color based on status
         var color = '#000';
         switch(helpers.item.text()) {
            case 'draft':
               color = '#4EB1CB';
               break
            case 'active':
               color = '#4AB471';
               break;
            case 'archived':
               color = '#CF5C60';
               break;
         }
         customMarkup = '<span style="color:' + color + '">'
               + helpers.item.text() + '</span>';
         break;
 
      case 'action':
         // Add a checkbox with name=xxx
         customMarkup = '<input type="checkbox" name="name" value="'
               + rowData.name + '">';
         // Add a trash action button
         customMarkup += '&nbsp;&nbsp;&nbsp;<button class="btn btn-xs btn-danger" type="botton"><span class="glyphicon  glyphicon-trash" ></span></button>';
         // customMarkup += '</div>';
         break;
 
      default:
         // otherwise, just use the existing text value
         customMarkup = helpers.item.text();
         break;
   }
 
   helpers.item.html(customMarkup);
 
   callback(); /* continue rendering */
}
</script>
Example 5: List View and Thumbnail View

[TODO]

Wizard

[TODO]

AMD and RequireJS

RequireJS (@ http://requirejs.org/) is a JavaScript file and module loader. It implements the AMD (Asynchronous Module Definition) specification.

The AMD API (@ https://github.com/amdjs/amdjs-api/blob/master/AMD.md) specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded. This is particularly well suited for the browser environment where synchronous loading of modules incurs performance, usability, debugging, and cross-domain access problems. The API has only a function called define(), with the following signature:

define(id?, dependencies?, factory);

The define() function is used to define a module, with its dependencies:

  • id: the ID of the module being defined. Optional and default to the filename. We often leave it to the default. For example, if the JavaScript filename is "foo.js", the default ID is "foo" (without the ".js").
  • dependencies: array of dependencies module IDs, e.g., ["jquery", "underscore"]. Optional.
  • factory: a function that should be executed to instantiate the module or an object.

Defining Modules

Example 1: Define a module as an object, without dependencies
/* Filename is "js/amdModObj.js" */
define({
   k1: 'v1',
   k2: 'v2',
   k3: 'v3'
});
   // id is "amdModObj" by default (without ".js")
   // No dependencies
   // The factory produces an object, which can be referenced in other modules as "amdModObj"
   // (e.g., the values are "amdModObj.k1", "amdModObj.k2")
Example 2: Define a module as a function, without dependencies
/* Filename is "js/amdModFun.js" */
define( function() {
   return function(n1, n2) {
      return n1 + n2;  // Simply add the arguments
   };
});
   // id is "amdModObj" by default (without ".js")
   // No dependencies
   // The factory produces a function, which can be referenced in other modules as "amdModFun()"
   // (e.g., "amdModFun(1, 2)")
Example 3: Define a module as an object, with dependencies

In this example, we shall define a module, that depends on jQuery and Underscore. We list the dependencies module IDs in an array. We then invoke the factory function, which takes the dependencies modules as arguments. In this case, we use $ and _ as the argument names. This module returns an object, with a key sayHello mapping to the sayHello function.

/* Filename is "js/amdModDep.js" */
define(['jquery', 'underscore'], function($, _) {
  var sayHello = function(message) {
    var template = _.template("Hello, <%= message %>");
          // Create an underscore template
    $(".message").html(template({message: message}));
          // Use jQuery to select an element and show the message
  };
  return {
    sayHello: sayHello   // return an object
  };
});
   // id is "amdModDep" by default (without ".js").
   // Depends on modules with ID of 'jquery' and 'underscore'.
   // The factory produces an object, which can be referenced in other modules as "amdModDep".
   //   You can invoke the function via amdModDep.sayHello('testing').

Using the Defined Modules

Example 1: Using amdModObj.js and amdModFun.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<!-- amdUseMod1.html -->
<html lang="en">
<head>
<meta charset="utf-8">
<title>Testing AMD Modules</title>
<!-- Only need to load the Require JS -->
<script src="js/require-2.1.17.min.js"></script>
<script>
// Configure the Module IDs and their path.
// The baseUrl is at this HTML file.
require.config({
   paths: {
      'amdModObj': 'js/amdModObj',  // Without the ".js"
      'amdModFun': 'js/amdModFun'
   },
});
 
// Using modules 'amdModObj' and 'amdModFun'
require(['amdModObj', 'amdModFun'], function(amdModObj, amdModFun) {
   console.log(amdModObj.k3);
   console.log(amdModFun(1, 2));
});
</script>
</head>
<body>
<p>Hello World</p>
</body>
</html>

In this example, we use amdModObj.js and amdModFun.js created earlier.

  1. We only need to load the Require JS, not the other scripts.
  2. We use the require.config() to configure the Module IDs and their paths. Take note that in the script, the baseUrl is at this HTML file. We provide relative path relative to the baseUrl. The file extension ".js" is to be omitted.
  3. To use the modules, we invoke the require() function, which has similar syntax as define(). We list the modules used as dependencies in an array. We then invoke the factory function, which takes the modules as arguments.
Example 2: Factoring out the main.js

Instead of place the script in the HTML, we move the script to "js/amdMain.js" (or typically named "js/app.js" or "js/main.js"). Require JS invokes the main script via attribute data-main="js/amdMain". Take note that in the require.config(), the baseUrl is changed to the main script folder.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Filename is "amdMain.js"  */
// Configure the Module IDs and their path
require.config({
   paths: {
      'amdModObj': 'amdModObj',  // baseUrl is at js/amdMain.js. No ".js"
      'amdModFun': 'amdModFun'
   },
});
 
// Using modules 'amdModObj' and 'amdModFun'
require(['amdModObj', 'amdModFun'], function(amdModObj, amdModFun) {
   console.log(amdModObj.k3);
   console.log(amdModFun(1, 2));
});

The HTML file is:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Testing AMD Modules</title>
<script data-main="js/amdMain" src="js/require-2.1.17.min.js"></script>
</head>
<body>
<p>Hello World</p>
</body>
</html>
Example 3: Using the Module with dependencies on jQuery and Underscore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Testing AMD Modules</title>
<script src="js/require-2.1.17.min.js"></script>
<script>
// Configure the Module IDs and their path
require.config({
   paths: {
      'jquery': 'js/jquery-1.11.2.min',   // without the ".js". Can use the remote CDN too.
      'underscore': 'js/underscore-1.8.3.min',
      'amdModDep' : 'js/amdModDep'
   },
});
 
// Using modules 'amdModDep', which depends on 'jquery' and 'underscore'.
require(['amdModDep'], function(amdModDep) {
   amdModDep.sayHello('testing');
});
</script>
</head>
<body>
<h2>Testing AMD and RequireJS</h2>
<p class="message">&nbsp;</p>
</body>
</html>

Benefits: The module, as well as its dependencies, are loaded only if "require", asynchronously.

You can also use remote CDN for the modules, e.g.,

require.config({
  paths: {
    "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min",   // CDN
    "underscore": "js/underscore"  // Local
  }
});

More Examples

Example 1: jQuery

If an AMD loader is detected, jQuery registers itself as an AMD module, via the following codes at the end of the jquery.js:

// Register as a named AMD module, since jQuery can be concatenated with other
// files that may use define, but not via a proper concatenation script that
// understands anonymous AMD modules. A named AMD is safest and most robust
// way to register. Lowercase jquery is used because AMD module names are
// derived from file names, and jQuery is normally delivered in a lowercase
// file name. Do this after creating the global so that if an AMD module wants
// to call noConflict to hide this version of jQuery, it will work.

// Note that for maximum portability, libraries that are not jQuery should
// declare themselves as anonymous modules, and avoid setting a global if an
// AMD loader is present. jQuery is a special case. For more information, see
// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon

if ( typeof define === "function" && define.amd ) {
   define( "jquery", [], function() {
      return jQuery;
   });
}
Example 2: Fuel UX
<script src="//cdn.jsdelivr.net/requirejs/2.1.11/require.js"></script>
<script>
  requirejs.config({
    paths: {
      'bootstrap': '//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min',
      'fuelux': '//www.fuelcdn.com/fuelux/3.6.3/js/fuelux.min',
      'jquery': '//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery',
      // Moment.js is optional
      'moment': '//cdn.jsdelivr.net/momentjs/2.6.0/lang-all.min'
    },
    // Bootstrap is a "browser globals" script :-(
    shim: { 'bootstrap': { deps: ['jquery'] } }
  });
  // Require all.js or include individual files as needed
  require(['jquery', 'bootstrap', 'fuelux'], function($){});
</script>

[TODO] Study Fuel UX examples, which uses require JS extensively.

jQuery UI

jQuery UI (@ https://jqueryui.com/) is a jQuery add-on library for building User Interface. "jQuery UI is a curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery JavaScript Library. Whether you're building highly interactive web applications or you just need to add a date picker to a form control, jQuery UI is the perfect choice."

jQuery UI can co-exist with BootStrap.

Datepicker

See example and API @ https://jqueryui.com/datepicker/.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>jQuery UI Datepicker</title>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.min.css">
<script  src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="http://code.jquery.com/ui/1.11.4/jquery-ui.min.js"></script>
<script>
$( function() {
   $( "#datepicker" ).datepicker({
      dateFormat: "yy-mm-dd"   // MySQL date format
   });
});
</script>
</head>
<body>
<p>Date: <input type="text" id="datepicker"></p>
</body>
</html>

Underscore JS

"Underscore provides over 100 functions that support both your favorite workaday functional helpers: map, filter, invoke - as well as more specialized goodies: function binding, javascript templating, creating quick indexes, deep equality testing, and so on."

The mother site is http://underscorejs.org/.

Moment JS

"Parse, validate, manipulate, and display dates in JavaScript."

The mother site is http://momentjs.com/.

REFERENCES & RESOURCES

Bootstrap
  1. Bootstrap mother site @ http://getbootstrap.com/.
  2. Aravind Shenoy and Ulrich Sossou, "Learning Bootstrap", Packt Publishing, 2014.
Font Awesome
  1. Font Awesome mother site @ http://fortawesome.github.io/Font-Awesome/.
Parsley JS
  1. Parsley mother site @ http://parsleyjs.org/.
Fuel UX
  1. Fuel UX mother site @ http://getfuelux.com/index.html.
AMD and Require JS
  1. AMD API and Examples @ https://github.com/amdjs/amdjs-api/blob/master/AMD.md.
  2. Require JS API @ http://requirejs.org/docs/api.html.
  3. Rakhitha Nimesh, Understanding RequireJS for Effective JavaScript Module Loading @ http://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/.
  4. Introduction to RequireJS @ http://javascriptplayground.com/blog/2012/07/requirejs-amd-tutorial-introduction/.