Bootstrap 4 Tooltips with Aurelia CLI

This post shows how to use Bootstrap 4 Tooltips in an Aurelia project, using the Aurelia CLI.

aurelia-bootstrap4-tooltip

Create a new project using the Aurelia CLI. Choose the default options.

au new

Install Bootstrap 4, jQuery, and Tether.

npm install bootstrap@4.0.0-alpha.6 --save
npm install jquery@3.2.1 --save
nom install tether@1.4.0 --save

The commands above download the libraries into node_modules. The –save option lists them as dependencies in package.json.

Now we have to tell Aurelia to include these libraries when bundling our project. Aurelia CLI creates two javascript files, app-bundle.js and vendor-bundle.js. We can configure what goes in these bundles in aurelia.json.

Open aurelia.json, found in the aurelia_project directory. Locate the section in the file that configures vendor-bundle.js. Add the Tether library to the prepend array. Add jQuery and Bootstrap to the dependencies array. For some reason the Tether library doesn’t work as a dependency (I’ll update this post if I figure out why). The changes look like this:

"bundles": [
    {
    "name": "app-bundle.js",
    "source": [
        "[**/*.js]",
        "**/*.{css,html}"
    ]
    },
    {
    "name": "vendor-bundle.js",
    "prepend": [
        "node_modules/bluebird/js/browser/bluebird.core.js",
        "node_modules/aurelia-cli/lib/resources/scripts/configure-bluebird.js",
        "node_modules/tether/dist/js/tether.min.js",
        "node_modules/requirejs/require.js"
    ],
    "dependencies": [
        "jquery",
        {
        "name": "bootstrap",
        "path": "../node_modules/bootstrap/dist",
        "main": "js/bootstrap.min",
        "deps": ["jquery"],
        "resources": [
            "css/bootstrap.css"
        ]
        },
        "aurelia-binding",

Add a Bootstrap Tooltip Button to app.html. Also import the bootstrap css using a require element.

<template>
  <require from="bootstrap/css/bootstrap.css"></require>
  <button type="button" class="btn btn-secondary" data-toggle="tooltip"data-placement="bottom" data-html="true" title.bind="message">
  Button with Tooltip
  </button>
</template>

Import bootstrap in app.js. When the view is attached to the DOM, enable tooltips using the tooltip method that bootstrap has added to the jQuery $ global variable.

import 'bootstrap';
export class App {
  constructor() {
    this.message = 'Hello World!';
  }
  attached() {
    $('[data-toggle="tooltip"]').tooltip()
  }
}

Run the project:

au run

The tooltip appears when mousing over the button. View a demo here. Download the source files.

Links:
- Getting Started with Aurelia CLI and Boostrap
- How to add Tether in Aurelia-CLI to use with Bootstrap 4

Posted in Uncategorized | Leave a comment

Statistics Page with Aurelia and Bootstrap 4

I really like how the New York Times displays interesting statistics about their business on their corporate website, www.nytco.com. The statistics are overlaid on photographs. They change when the page is refreshed. In this post we’ll use Aurelia and Bootstrap 4 to create something similar.

statistics-aurelia-bootstrap

Start by installing the Aurelia CLI using npm and create a new project. Choose the default options. By default Babel is used for transpiling modern javascript to ES5.

npm install aurelia-cli -g
au new

Add Bootstrap 4 by linking to the CDN files in the header of index.html.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Stats</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script>
  </head>

  <body aurelia-app="main">
    <script src="scripts/vendor-bundle.js" data-main="aurelia-bootstrapper"></script>
  </body>
</html>

Now update app.html, under the src directory, with the code below. This file is the view that will display the statistics. The view contains a template which contains a repeater.

<template>
  <require from="css/main.css"></require>
  <div class="container">
    <div class="row">
      <div class="col">
        <div id="stats-container" css="background-image:url('${theme.backgroundImage}')">
          <ul class="list-unstyled">
            <li repeat.for="stat of stats" class="h2">
              <span css="color:${theme.color}">${stat.number}</span> ${stat.description}
            </li>
          </ul>
        </div>
      </div>
    </div>
  </div>
</template>

Templates work with view-models. By convention views and view-models in Aurelia have the same name. So our view-model for app.html is app.js. Update app.js to look like this:

export class App {
  constructor() {
    this.themes = [
      { color: '#FFBE0B', backgroundImage: './images/1.jpeg' },
      { color: '#E7F6CC', backgroundImage: './images/2.jpeg' },
      { color: '#AD2100', backgroundImage: './images/3.jpeg' },
      { color: '#CA8A3F', backgroundImage: './images/4.jpeg' },
      { color: '#EFD133', backgroundImage: './images/5.jpeg' },
      { color: '#8A3909', backgroundImage: './images/6.jpeg' }
    ]
    this.theme = this.getRandomTheme();
    this.allStats = [
      { number: '7,600,000', description: 'subs are served at Subway every day' },
      { number: '7,500', description: 'varieties of apples are grown throughout the world' },
      { number: '2,500', description: 'varieties of apples are grown in the United States' },
      { number: '671,396,071', description: 'cups of coffee were sold by Starbucks in 2017' },
      { number: '58,301', description: 'fusce cursus nisl in massa pulvinar' },
      { number: '8,645', description: 'nam aliquet, dui sed sagittis lobortis' },
      { number: '3,580', description: 'vivamus enim ligula, finibus vel purus non'},
      { number: '26,924', description: 'integer et erat neque' },
      { number: '14,597', description: 'vivamus mi sem, cursus in nisi vel' },
      { number: '876', description: 'ut mollis dui in ornare aliquam'},
      { number: '168', description: 'fusce auctor tristique elit, nec lacinia augue'},
    ]
    this.stats = this.shuffle(this.allStats).slice(0, 7)
  }

  getRandomTheme() {
    return this.themes[Math.floor(Math.random() * this.themes.length)];
  }

  shuffle(array) { //https://github.com/coolaj86/knuth-shuffle
    var currentIndex = array.length, temporaryValue, randomIndex;
    while (0 !== currentIndex) {
      randomIndex = Math.floor(Math.random() * currentIndex);
      currentIndex -= 1;
      temporaryValue = array[currentIndex];
      array[currentIndex] = array[randomIndex];
      array[randomIndex] = temporaryValue;
    }
    return array;
  }
}

When the view-model is created it’s constructor() is called and the theme and stats arrays are instantiated. The repeater in our template loops over these arrays and uses databinding to set the text color and display the background image, numbers, and descriptions.

Now we need to create a directory for the images. Create this directory at the top level of the project. The images are from lorempixel.

statistics-examples-files

Create a directory called css in the src directory. Add main.css containing this code:

/* Extra Extra Small Devices, Phones */ 
@media only screen and (min-width : 0px) {
    #stats-container {
        background-size: cover;
        margin-top: 2em;
    }
    #stats-container ul {
        font-size: 2rem;
        color: white;
        padding: 1em;
    }
    #stats-container li {
        padding: 0 0 1em 0;
    }
    #stats-container span {
        font-weight: bold;
    }
}

/* Extra Small Devices, Phones */ 
@media only screen and (min-width : 544px) {}

/* Small Devices, Tablets */
@media only screen and (min-width : 768px) {}

/* Medium Devices, Desktops */
@media only screen and (min-width : 992px) {
    #stats-container ul {
        padding: 1.5em 2em;
    }
}

/* Large Devices, Wide Screens */
@media only screen and (min-width : 1200px) {}

Test the project using the run command:

au run

Finally, build the project using the build command. The files needed for deployment are index.html, the images directory, and the scripts directory.

au build --env prod

View a demo here. Download the source files.

Links:
- Bootstrap 4 Grid system
- Aurelia

Posted in Uncategorized | Leave a comment

SVG Background Image with PNG Fallback using Modernizr

In this example Modernizr is used to detect SVG support. CSS is then used to display the image of a bike (PNG or SVG) as the background for a DIV. SVG is a scalable vector graphic, it will always be sharp no matter how it’s re-sized. PNG is a raster graphic, it can become pixelated if re-sized.

bike

Visit the Modernizr download page and select SVG and Add CSS Classes options. Generate the code and download the file.

modernizr-svg-arrow

By adding this Modernizr script to the page we get either the svg or no-svg class name added to the html tag. Here’s how it looks in the console:

modernizr-body-js-svg

We can utilize these classes with CSS selectors to fallback to the PNG image if SVG is not supported.

<!DOCTYPE html>
<html>
<head>
	<title>SVG Background Image with PNG Fallback using Modernizr</title>
	<style type="text/css">
		#bike {
			width: 792px;
			height: 612px;
		}
		/* Use the PNG if SVG is not supported */
		.no-svg #bike {
			background-image: url(bike.png);
		}
		/* Use the SVG if supported */
		.svg #bike {
			background-image: url(bike.svg);
		}
		/* Hide the SVG description if using PNG */
		.no-svg #svg-description {
			display: none;
		}
		/* Hide the PNG description if using SVG */
		.svg #png-description {
			display: none;
		}
	</style>
	<script type="text/javascript" src="modernizr.custom.65829.js"></script>
</head>
<body>
<div id="bike"></div>
<p id="svg-description">This bike is a scalable vector graphic.</p>
<p id="png-description">This bike is a raster graphic.</p>
</body>
</html>

View a demo here. Download the source files.

Modernizr SVG Test

To determine how Modernizr is testing for SVG support we can look at the non-minified version of the code. The sections involved in SVG testing are:

ns = {'svg': 'http://www.w3.org/2000/svg'}
...
tests['svg'] = function() {
    return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect;
};

The createElementNS method creates an element node given a namespace URI and a qualified name.

Let’s look closer at the comparison operator:

!!document.createElementNS

In older browsers document.createElementNS may be undefined because this method doesn’t exist. In JavaScript undefined is a falsey value. Therefore the not(!) of undefined is true. The not of true is false. So if a browser doesn’t support createElementNS this statement returns false.

In newer browsers createElementNS exists. Therefore document.createElementNS will return the function. A function isn’t falsey, therefore the statement returns true.

!!document.createElementNS(ns.svg, ‘svg’).createSVGRect

Here a SVGSVGElement element called svg is created in the ‘http://www.w3.org/2000/svg’ namespace. Then the createsSVGRect method is called. If the method exists the statement returns true. If the method is undefined the statement returns false.

Combining the two operands means that Modernizr verifies that a SVGSVGElement can be created and that it has a method called createsSVGRect.

Links:
- Bicycle Vector Image
- Namespaces Crash Course
- Namespaces in HTML5

Posted in Uncategorized | Leave a comment

Load AngularJS after Office Initialized

This JavaScript code snippet shows how to load an AngularJS application after Office has been initialized (or some other event has occurred). The key point here is that you need to do a manual bootstrap because the DOMContentLoaded event will have already been fired.

Office.initialize = function () {

    //load angular app after office has been initialized
    $.getScript('services.js', function () {
        $.getScript('app.js', function () {
            $.getScript('controllers.js', function () {

                //manually start angular application
                angular.bootstrap($('#container'), ['myApp']);

            });
        });
    });

}

Links:
- Angular Bootstrap
- JavaScript API for Office (v1.1)
- jQuery.getScript()

Posted in Uncategorized | 2 Comments