Garry Smith - Big Nerd Ranch Tue, 19 Oct 2021 17:46:25 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 Ember.JS and Electron: Build Desktop Apps with Web Technologies https://bignerdranch.com/blog/ember-js-and-electron-build-desktop-apps-with-web-technologies/ https://bignerdranch.com/blog/ember-js-and-electron-build-desktop-apps-with-web-technologies/#respond Tue, 14 Feb 2017 10:00:53 +0000 https://nerdranchighq.wpengine.com/blog/ember-js-and-electron-build-desktop-apps-with-web-technologies/ In this blog post, we'll create a desktop file-management app using Electron and Ember.JS. Electron provides the cross-platform development environment, while EmberJS gives us organization and convention.

The post Ember.JS and Electron: Build Desktop Apps with Web Technologies appeared first on Big Nerd Ranch.

]]>

Building cross-platform desktop and mobile apps with web technologies is not a new topic (in fact, we talked about hybrid apps on the blog just yesterday). There are many different flavors that have arisen over the years, allowing you to build and deploy on a myriad of platforms using a single codebase. Each have their own varying benefits, drawbacks and community support. Here are just a few:

Electron has a straightforward API and is incredibly easy to set up. With a single code base, you can write apps for macOS, Windows and Linux. On the web framework side, we have been using Ember.JS at Big Nerd Ranch for a while now on client projects. We even teach it at our front-end bootcamps and it has its own chapter in our new book. Together, Electron and Ember can make a powerful team. You can build desktop apps while utilizing the power of Ember’s organization and conventions as well as use modern JavaScript with Babel.

You might recognize some of these apps—they’re all built with Electron!

Setting Up Ember-Electron

  1. If you don’t have ember-cli installed yet, run npm install -g ember-cli (2.11.0-beta.4 version at writing)
  2. ember new electron-playground
  3. cd electron-playground
  4. ember install ember-electron

It will add an electron.js file to your app folder. You can modify many of the Electron specific settings and events in this file, including the default window size of your app.

mainWindow = new BrowserWindow({
    width: 800,
    height: 600
});

Since 800×600 is annoyingly small for an app, we want to change this line to:

const {width, height} = electron.screen.getPrimaryDisplay().workAreaSize;
mainWindow = new BrowserWindow({width, height});

This will detect your primary display’s size and open the app to that size.

ember-electron will also add the following to your package.json file.

"ember-electron": {
  "WHAT IS THIS?": "Please see the README.md",
  "copy-files": [
    "electron.js",
    "package.json"
  ],
  "name": null,
  "platform": null,
  "arch": null,
  "version": null,
  "app-bundle-id": null,
  "app-category-type": null,
  "app-copyright": null,
  "app-version": null,
  ...
}

These properties are for platform packaging settings for electron-packager which is used by ember-electron. I won’t go into what they do, but for a full list and other useful info visit the ember-electron repo.

One configuration step: to run your app both in a browser and on the desktop, change the locationType in your environment.js to locationType: process.env.EMBER_CLI_ELECTRON ? 'hash' : 'auto',

To launch your app in desktop mode, run: ember electron in the terminal.

Ember Inspector

You can use the developer console along with the Ember Inspector directly inside of the desktop app by hitting cmd+option+i (Mac) or ctrl+shift+i (PC)

File Management/File Viewer App

I set out to create a simple file management app that would highlight Ember’s strengths, like routing conventions and reusable components, and leverage Electron’s integration with native desktop features. It proved to be an interesting project that could easily be extended with much more functionality.

A full-featured app would support file deletion, drag-and-drop, etc. But for the sake of brevity for this blog post, we’re going to build out the base app which will allow us to navigate, view and open files. I may cover additional features in supplemental blog posts.

Bootstrapping the project

There is some groundwork we need to lay before diving into the code. Let’s create the required routes, components and utilities in one fell swoop. Run the following commands in your terminal.

ember g route application
ember g route files
ember g component nav-bar
ember g component side-bar
ember g component file-manager
ember g component file-list
ember g component folder-breadcrumbs
ember g service read-directory
ember g util format-filepath

Install the junk npm package, it will be used to filter out all junk files like “.DS_Store”, etc.

npm install --save junk

You can style the app however you want but for this example I just modified a pre-made Bootstrap theme that fits well with what we’re building. Include the Bootstrap CDN to your app/index.html.

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">

Add the following to your app/styles/app.css file.

/*
 * Base structure
 */

/* Move down content because we have a fixed navbar that is 50px tall */

body {
  padding-top: 50px;
}

/*
 * Typography
*/

h1 {
  margin-bottom: 20px;
  padding-bottom: 9px;
  border-bottom: 1px solid #eee;
}

/*
 * Sidebar
*/

.sidebar {
  position: fixed;
  top: 98px;
  bottom: 0;
  left: 0;
  z-index: 1000;
  padding: 20px;
  overflow-x: hidden;
  overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
  border-right: 1px solid #eee;
}

/* Sidebar navigation */
.sidebar {
  padding-left: 0;
  padding-right: 0;
}

.sidebar .nav {
  margin-bottom: 20px;
}

.sidebar .nav-item {
  width: 100%;
}

.sidebar .nav-item + .nav-item {
  margin-left: 0;
}

.sidebar .nav-link {
  border-radius: 0;
}

.folder-icon {
  width: 2rem;
  height: 2rem;
  background: url('folder-icon.png') no-repeat center;
  background-size: 100% 100%;
}

.breadcrumb {
  position: fixed;
  top: 51px;
  z-index: 1000;
  width: 100%;
}

.file-list {
  margin-top: 45px;
}

The Code

We first need to create the basic layout for our app: a nav-bar, side-bar, breadcrumbs and a file viewer.

Update the app/templates/application.hbs to:

<div class="content">
  {{nav-bar}}
  <div class="container-fluid">
    {{outlet}}
  </div>
</div>

Update the app/templates/components/nav-bar.hbs to:

<nav class="navbar navbar-toggleable-md navbar-inverse bg-primary fixed-top">

  <a class="navbar-brand" href="#">{{appName}}</a>

  <div class="collapse navbar-collapse" id="navbarSupportedContent">
    <ul class="navbar-nav mr-auto">
      <li class="nav-item">
        {{#link-to "files" "root" class="nav-link"}}File Management{{/link-to}}
      </li>
      <li class="nav-item">
        <a class="nav-link disabled" href="#">Network Monitor</a>
      </li>
    </ul>
  </div>
</nav>

You’ll notice a custom property binding in the nav-bar, {{appName}}, which should include the user’s computer username. We can define this property in the nav-bar component as a property. Add it to your app/components/nav-bar.js.

import Ember from 'ember';
const os = require('os');

export default Ember.Component.extend({
  appName: `Desktop for ${os.userInfo().username}`
});

Since Electron gives us the ability to use all of Node’s capabilities, we can use its modules directly inside our EmberJS code.

In the above code sample, we are using the os module in Node.js to retrieve information on your system’s operating system and its current logged-in user, and then displaying the username on the nav-bar.

Now let’s start working on the actual file-management page of the app. At the beginning of this section we generated an Ember route for files. We need to update its entry in the app/router.js file.

Router.map(function() {
  this.route('files', { path: '/files/:file_path' });
});

As you can see, we added a path with a parameter for :file_path. If this were an app hosted on the web, this route would be your URL path, but in this case it is primarily acting as an application state. It will keep track of the physical folder path that we are currently browsing in the app.

The beauty of this is that we are still using the conventions and magic that the EmberJS framework provides. Our code is nice and organized and we are delegating the task of routing and model retrieval to Ember.

If you run the app now, it should look something like this (with your username instead of garry).

Local username in Nav-bar

Before we set up the route, we need to add a little utility to help us create navigable breadcrumbs on top of the file-list. Update app/utils/format-filepath.js.

import Ember from 'ember';

export function formatFilePath(filePath) {
  var parts = filePath
              .replace(//g, '/')
              .split('/')
              .filter(Boolean);

  var link = '';
  return parts.map((part) => {
    link += `/${part}`;
    return { path: link, name: part };
  });
}

This utility breaks down a file path string and separates it into an array of objects, each with their own path and name, making it much easier later on to create navigable links with them.

Now let’s update our route to fetch the directory and file data whenever the route’s file path changes. Update app/routes/files.js.

import Ember from 'ember';
import { formatFilePath } from '../utils/format-filepath';

export default Ember.Route.extend({
  readDirectory: Ember.inject.service(),
  model(params) {
    const filePath = params.file_path === 'root' ? process.env['HOME'] : params.file_path;
    let sideBarDirectory = this.get('readDirectory').path();
    let currentDirectory = this.get('readDirectory').path(filePath);

    return Ember.RSVP.hash({
      sideBarDirectory,
      currentDirectory,
      filePath: formatFilePath(filePath)
    });
  }
});

We are injecting a service called readDirectory which we will code next, and passing it a file path to retrieve or in the case of the sideBarDirectory retrieving the default root file path. The sidebar will be like the sidebar for your native desktop file manager such as the ‘Favorites’ on Mac or ‘Quick Access’ on Windows. The folders in the sidebar are static and won’t change when you’re browsing through folders. The currentDirectory will update whenever you click on a new folder and drill down to see its contents.

Finally, we are using Ember’s RSVP promise module to forward the model to the view only when all promises in the hash have resolved.

The readDirectory service below does the heavy lifting of pulling the required information about the local files and folders as well as mapping extensions to a category and converting file size to a more human readable format. Update the app/services/read-directory.js.

import Ember from 'ember';
const fs = require('fs');
const path = require('path');
const junk = require('junk');
const Promise = Ember.RSVP.Promise;
const computed = Ember.computed;

const map = {
  'directory': ['directory'],
  'compressed': ['zip', 'rar', 'gz', '7z'],
  'text': ['txt', 'md', 'pages', ''],
  'image': ['jpg', 'jpge', 'png', 'gif', 'bmp'],
  'pdf': ['pdf'],
  'css': ['css'],
  'html': ['html'],
  'word': ['doc', 'docx'],
  'powerpoint': ['ppt', 'pptx'],
  'video': ['mkv', 'avi', 'rmvb']
};

var FileProxy = Ember.ObjectProxy.extend({
  fileType: computed('fileExt', function() {
    var ext = this.get('fileExt');
    return Object.keys(map).find(type => map[type].includes(ext));
  }),
  isDirectory: computed('fileType', function() {
    return this.get('fileType') === 'directory';
  }),
  icon: computed('fileType', function() {
    if (this.get('fileType') === 'directory') {
      return 'assets/folder-icon.png';
    }
  })
});

var humanFileSize = size => {
  var i = Math.floor( Math.log(size) / Math.log(1024) );
  return ( size / Math.pow(1024, i) ).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
}

const rootPath = process.env['HOME'];

export default Ember.Service.extend({
  path(dir = rootPath) {
    var callback = (resolve, reject) => {
      fs.readdir(dir, (error, files) => {
        if (error) {
          window.alert(error.message);
          return reject(error);
        }
        // Filter out all junk files and files that start with '.'
        var filteredFiles = files.filter(file => junk.not(file) && file[0] !== '.');
        var fileObjects = filteredFiles.map(file => {
          let filePath = path.join(dir, file);
          let fileStat = fs.statSync(filePath);
          let fileSize = fileStat.size ? humanFileSize(fileStat.size) : '';
          // Directories do not have an extension, hardcode it as 'directory'
          let fileExt = fileStat.isDirectory() ? 'directory' : path.extname(filePath).substr(1);
          let parsedPath = path.parse(filePath);

          let opts = {
            filePath,
            fileExt,
            fileSize,
            ...fileStat,
            ...parsedPath
          };
          return new FileProxy(opts);
        });
        resolve(fileObjects);
      });
    }
    return new Promise(callback);
  }
});

The map array converts some common file extensions to a category. If you want to use a more exhaustive list of extensions, the mimetypes npm package is pretty useful. But a simple map in this case will do fine. In summary, the readDirectory service uses the fs “file system” Node module to read the contents of a filepath that is passed to it. It then maps the properties of those contents to a custom FileProxy object which contains three computed properties that behave according to what type of file it is. All of this is contained within an Ember Promise that is consumed by the files route and forwarded via model to the view.

Now that we have the route and model set up with, we can start working on the templates. Add the file-manager component to the files template and pass the model to it.

{{file-manager model=model}}

Now that we have the model in the file-manager, we can start to display the data on the sidebar and file-list. Update the app/templates/components/file-manager.hbs:

{{folder-breadcrumbs filePath=model.filePath}}
<div class="row">
  {{side-bar model=model.sideBarDirectory}}
  <main class="col-sm-9 offset-sm-3 col-md-10 offset-md-2 pt-3">
    {{file-list files=model.currentDirectory}}
  </main>
</div>

This component includes the folder-breadcrumbs, sidebar and file-list components.

The breadcrumbs:

<div class="row">
  <nav class="breadcrumb">
      {{#each filePath as |pathObj|}}
        {{#link-to 'files' pathObj.path class="breadcrumb-item" }}{{pathObj.name}}{{/link-to}}
      {{/each}}
  </nav>
</div>

The sidebar:

<div class="container">
  <div class="row">
    <nav class="col-sm-3 col-md-2 hidden-xs-down bg-faded sidebar">
      <ul class="nav nav-pills flex-column">
        {{#each model as |file|}}
          {{#if file.isDirectory }}
          <li class="nav-item">
            {{#link-to "files" file.filePath class="nav-link"}}{{file.base}}{{/link-to}}
          </li>
          {{/if}}
        {{/each}}
      </ul>
    </nav>
  </div>
</div>

And finally our file-list component.

<div class="table-responsive">
  <table class="table table-striped table-hover table-sm">
    <thead class="thead-default">
      <tr>
        <th></th>
        <th>Name</th>
        <th>Type</th>
        <th>Size</th>
        <th>Last Modified</th>
      </tr>
    </thead>
    <tbody>
      {{#each files as |file|}}
      <tr>
        {{#if file.isDirectory}}
        <td><div class="folder-icon"></div></td>
        <td>{{#link-to "files" file.filePath class="nav-link"}}{{file.base}}{{/link-to}}</td>
        {{else}}
        <td></td>
        <td><a href="" {{action "openFile" file.filePath}}>{{file.base}}</a></td>
        {{/if}}
        <td>{{file.fileType}}</td>
        <td>{{file.fileSize}}</td>
        <td>{{file.mtime}}</td>
      </tr>
      {{/each}}
    </tbody>
  </table>
</div>

It loops through each file and folder and renders a table row. If it is a folder, it displays a folder icon. If it’s a file it makes the file name an anchor link with an action to open the file in whatever default application you have set for that file extension. That action is handled in app/components/file-list.js

import Ember from 'ember';
const { shell } = require('electron');

export default Ember.Component.extend({
  classNameBindings: [':file-list'],
  actions: {
    openFile(file) {
      shell.openItem(file);
    }
  }
});

This component action uses the shell module provided by Electron’s API. It provides you with some handy functions such as shell.moveItemToTrash(fullPath), shell.showItemInFolder(fullPath), and more.

That’s it! If this is your very first desktop app, congratulations! If not, then… well it’s still pretty cool, right? Your app should look something like this:

File Viewer App Screenshot

Happy coding! If you or someone you know needs an Electron and/or Ember.JS app developed, let us know! We’ll push buttons on the keyboard for you!

The post Ember.JS and Electron: Build Desktop Apps with Web Technologies appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/ember-js-and-electron-build-desktop-apps-with-web-technologies/feed/ 0
Music Visualization with D3.js https://bignerdranch.com/blog/music-visualization-with-d3-js/ https://bignerdranch.com/blog/music-visualization-with-d3-js/#respond Thu, 21 May 2015 09:53:37 +0000 https://nerdranchighq.wpengine.com/blog/music-visualization-with-d3-js/

In my previous post, we created a bar chart using D3.js.

The post Music Visualization with D3.js appeared first on Big Nerd Ranch.

]]>

In my previous post, we created a bar chart using D3.js.

While brainstorming ideas on how to elaborate on my first post, I had a flashback to music visualizers in WinAmp where shapes would beautifully expand and contract with the frequency of the music. I thought this would be a great opportunity for me to learn the Web Audio API as well as display what D3 can do. You can view the finished visualizer here.

Audio Visualizer with D3 and Web Audio API

First, let’s add our HTML <audio> source and controls.

<audio id="audioElement" src="./audio/Odesza - Above The Middle.mp3"></audio>
<div>
  <button onclick="document.getElementById('audioElement').play()">Play the Audio</button>
  <button onclick="document.getElementById('audioElement').pause()">Pause the Audio</button>
  <button onclick="document.getElementById('audioElement').volume+=0.1">Increase Volume</button>
  <button onclick="document.getElementById('audioElement').volume-=0.1">Decrease Volume</button>
</div>

Point the source of the <audio> element to your audio file. In this case, I created an audio folder and added an mp3 of Odesza’s song “Above The Middle,” available for free from their website. But if you think my taste in music is horrendous, feel free to use your own!

If you were to open up your index.html now, you should be able to play, pause and increase/decrease the volume of your audio file. All you have to do now is slap a 500 page Terms of Service agreement on it and you got yourself an app!

Eh, not quite.

To be able to make a visualization, we need to grab the data from the song and then interpret it. Introducing the…

Web Audio API

The Web Audio API gives you access to control, modify and interpret audio within the browser. I was surprised by how much data you can retrieve about an audio file—and there are endless possibilities for modification. You can grab the frequency data, time info, create an oscilloscope or a voice modulator and much more. Once you have the streaming frequency data, you could make whatever visualization you can imagine! It’s pretty exciting for me (and hopefully for you, too).

For a full explanation on the Web Audio API, MDN has some pretty great documentation. But think of it as the middle-man between an audio input and output, effectivaly hijacking your audio through a context and network of nodes before it reaches its output (i.e., you speakers).

var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var audioElement = document.getElementById('audioElement');
var audioSrc = audioCtx.createMediaElementSource(audioElement);
var analyser = audioCtx.createAnalyser();

// Bind our analyser to the media element source.
audioSrc.connect(analyser);
audioSrc.connect(audioCtx.destination);

Here we are creating our AudioContext, getting our <audio> element, turning it into a MediaElementAudioSourceNode so that we can manipulate the audio from its source, and creating our AnalyserNode so that we can retrieve frequency data. Finally, we use the audioSrc.connect() method to connect the output of our <audio> element to the input of our analyser, and then connect to the audioCtx.destination (our speakers).

Updating The Chart

We need to make a few changes to the D3 code from my first blog post.

var frequencyData = new Uint8Array(200);

var svgHeight = '300';
var svgWidth = '1200';
var barPadding = '1';

function createSvg(parent, height, width) {
  return d3.select(parent).append('svg').attr('height', height).attr('width', width);
}

var svg = createSvg('body', svgHeight, svgWidth);

// Create our initial D3 chart.
svg.selectAll('rect')
   .data(frequencyData)
   .enter()
   .append('rect')
   .attr('x', function (d, i) {
      return i * (svgWidth / frequencyData.length);
   })
   .attr('width', svgWidth / frequencyData.length - barPadding);

The frequencyData array will be where we will copy our audio frequency data to. I also increased the size of the svg to accomodate the larger amount of data points. Lastly, I removed the rectangle’s height and Y attributes, because they will be blank until the song is played, and we are only concerned with adding 200 rectangles and setting their x and width values at initial screen load.

Streaming Frequency Data

Now comes the fun part: constantly streaming audio data to the browser and dynamically updating the D3 bar chart.

// Continuously loop and update chart with frequency data.
function renderChart() {
   requestAnimationFrame(renderChart);

   // Copy frequency data to frequencyData array.
   analyser.getByteFrequencyData(frequencyData);

   // Update d3 chart with new data.
   svg.selectAll('rect')
      .data(frequencyData)
      .attr('y', function(d) {
         return svgHeight - d;
      })
      .attr('height', function(d) {
         return d;
      })
      .attr('fill', function(d) {
         return 'rgb(0, 0, ' + d + ')';
      });
}

// Run the loop
renderChart();

Let’s break it down. renderChart() will loop continuously and update our frequencyData and chart. It will loop because window.requestAnimationFrame(renderChart) tells the browser to run renderChart() again before repainting the screen.

Most importantly, we need to get the audio data. analyser.getByteFrequencyData(frequencyData); will use our attached AnalyserNode to grab the frequency data of the audio and copy it to the Uint8Array frequencyData that we created earlier. If you were to set the length of the frequencyData array to analyser.frequencyBinCount, it would default to 1024. I opted to limit the length to 200 to make the chart a little easier to see. If you want data on the entire audio spectrum, feel free to use the total length, but 200 gives us a pretty good sample to work with.

If you were to put a console.log(frequencyData) inside the renderChart() function and looked at the output, you’ll see a Matrix-like stream of numbers.

Web Audio API data stream

With that data, you can make any visualization you want! For now, let’s bind the streaming data to the D3 chart. Since we just need to update the data on our chart, there’s no need to .append() anything; we just need to update the data, y, height and fill values. Since the range of frequency data is 0-255, it’s perfect for using as the rgb value. This will make the color a brighter blue for frequencies that are higher.

That’s it! You can view the source code here, and the finished demo here.

The post Music Visualization with D3.js appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/music-visualization-with-d3-js/feed/ 0
Create data-driven documents with D3.js https://bignerdranch.com/blog/create-data-driven-documents-with-d3-js/ https://bignerdranch.com/blog/create-data-driven-documents-with-d3-js/#respond Fri, 13 Feb 2015 09:53:37 +0000 https://nerdranchighq.wpengine.com/blog/create-data-driven-documents-with-d3-js/

If your data needs to portray a message and be easily understood, a visualization can really help you get your point across. Enter D3.js. This JavaScript framework is not new, but it is pretty shiny, and helps you present data in a consumable and understandable format by binding data to the Document Object Model (DOM).

The post Create data-driven documents with D3.js appeared first on Big Nerd Ranch.

]]>

If your data needs to portray a message and be easily understood, a visualization can really help you get your point across. Enter D3.js. This JavaScript framework is not new, but it is pretty shiny, and helps you present data in a consumable and understandable format by binding data to the Document Object Model (DOM).

With D3, there are countless possibilities. For example, D3 can help you chart your business’s quarterly financial results, visualize the relationships between species of salamanders or make a nifty music frequency visualizer.

Getting Started with D3.js

If you’re like me, you’ll take a look at the some of the more involved examples and your eyes will gloss over with awe and initimidation. Don’t sweat it. Like everything else, you should start with an introduction. Today we will be creating a simple bar chart.

First, create your index.html file.

	<!DOCTYPE html>
	<html>
	  <head>
	    <meta charset="utf-8" />
	    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
	    <title>Big Nerd Ranch D3 Introduction</title>
	    <link rel="stylesheet" href="application.css" />
	  </head>

	  <body>
	    <div id="graph"></div>
	    <script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
	    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
	    <script src="app.js"></script>
	  </body>
	</html>

Note: We will be using CDNs for jQuery and d3 but if you plan on working offline, make sure to download the libs to your project folder.

Next, create the app.js file in the same directory.

	$(document).ready(function () {

	});

Creating the SVG

Enough with the setup—let’s start writing some code. You can use DIVs to create your graphs, but we’re going to be using an SVG (Scalable Vector Graphic) and appending <rect> (rectangle) elements to it. First, lets create a little helper function in our app.js file to create the SVG.

	$(document).ready(function () {

		var svgHeight = 100;
		var svgWidth = 600;
		var barPadding = 1;

		function createSvg(parent, height, width) {
			return d3.select(parent)
					 .append('svg')
					 .attr('height', height)
					 .attr('width', width);
		}

		var graph = createSvg('#graph', svgHeight, svgWidth);

	});

The svgHeight and svgWidth variables will be the height and width of our SVG in pixels. The createSvg function accepts a parent DOM element selector that the SVG will be appended to (in our case, the <div id="graph"></div> element), along with its height and width. The d3.select(parent) will tell the D3 framework to select a DOM element to be acted upon. If you’re familiar with jQuery or CSS selectors, this is primarily the same thing. You can pass select() an ID or class name with the appropriate pretext, or simply any HTML element such as d3.select('body'). Once D3 selects that element, it returns a reference to that element which allows us to modify that element by chaining methods, in this case appending an SVG and setting attributes.

So step-by-step, our createSvg() method first selects the parent element we passed to it, then creates a new SVG element and appends it within the parent, then sets the height and width attributes of the new SVG element. Finally, since the last method in the chain .attr() is referencing the SVG element, the return statement in createSVG() is returning a reference to the SVG element. Pulling everything together, var graph = createSvg('#graph', svgHeight, svgWidth); creates the SVG and then stores the reference to the SVG element in the graph variable.

Building a Graph

Before we create our bar graph, we need some data to bind to it. D3 accepts many different formats of data, including JSON, GeoJSON and even CSV files, but for this intro, we will just be using a JavaScript array of numbers.

Add the following variable to your app.js:

var dataset = [12, 19, 8, 17, 22, 9, 15, 12, 22, 25, 17, 12, 25, 16];

Now let’s create the bars in our bar graph.

	graph.selectAll('rect')
	   .data(dataset)
	   .enter()
	   .append('rect')
	   .attr('width', svgWidth / dataset.length - barPadding)
	   .attr('height', function (d) {
	   		return d * 4;
	   })
	   .attr('x', function (d, i) {
	   		return i * (svgWidth / dataset.length);
	   })
	   .attr('y', function (d) {
	   		return svgHeight - (d * 4); // Align the bars to the bottom of the SVG.
	   });

d3 Introduction Bar Chart

Boom. You now have a bar graph worthy of visualizing team synergy at your next board meeting.

Let’s break down what this code snippet is doing.

	graph.selectAll('rect')
	   .data(dataset)
	   .enter()
	   .append('rect')

graph is referencing our SVG variable that we created earlier, and selectAll('rect') is saying, “Please find all the elements within our SVG.” Since there are currently none, D3 will prepare an empty array of element references.

Next, the .data(dataset) method binds our array of numbers to those references. Third, .enter() tells D3 to look at the data and create element references based on that data. Since there are 14 numbers in our dataset, D3 will create 14 element references.

Finally, .append('rect') appends those new elements to the DOM.

Without setting the height, width, and X and Y values, the SVG will look blank even though there are elements inside of it.

Since the .attr() methods are modifying an array of DOM references on the method chain, they will be invoked for each item in the array. This means you can gain access to each item’s value in the array by using an anonymous function. Let’s take a look at the last four methods:

	.attr('width', svgWidth / dataset.length - barPadding)
	.attr('height', function (d) {
		return d * 4;
	})
	.attr('x', function (d, i) {
		return i * (svgWidth / dataset.length);
	})
	.attr('y', function (d) {
		return svgHeight - (d * 4); // Align the bars to the bottom of the SVG.
	});

Laying it All out

The width of each bar in the graph is being set to a relative value based on how many items are in the array and the width of the SVG, so that the bar graph will take up the full width of the SVG. The height is using an anonymous function to gain access to the item’s value d and multiplying it by four, just to make the bars a little easier to see.

The X coordinate value is each bar’s position left-to-right on the SVG. We can gain access to each bar’s index in the array by adding a parameter i to the anonymous function. We want to space out the bars evenly, so set the X coordinate to be i * (svgWidth / dataset.length). The Y coordinate is a little different, since SVG rects use the top left corner as the point of origin, which is a little weird. If we didn’t provide a value for the Y coordinate, our bars would be upside down. That’s why we have to set the Y value to be the difference between the heights of the SVG and our bar.

Our example here was simple, but you can do a lot of cool things with D3! Go check out their examples and start building.

The post Create data-driven documents with D3.js appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/create-data-driven-documents-with-d3-js/feed/ 0
Maintaining and Organizing UI Dependencies with Knockout.JS: Part 2 https://bignerdranch.com/blog/maintaining-and-organizing-ui-dependencies-with-knockout-js-part-2/ https://bignerdranch.com/blog/maintaining-and-organizing-ui-dependencies-with-knockout-js-part-2/#respond Thu, 12 Sep 2013 18:08:18 +0000 https://nerdranchighq.wpengine.com/blog/maintaining-and-organizing-ui-dependencies-with-knockout-js-part-2/

In Part 1 of Maintaining and Organizing UI Dependencies with Knockout.JS, we discussed the three types of observables that Knockout provides: observables, observable arrays and computed observables. We also built a small demo app and explained some of the library’s features such as templating, using different bindings like “foreach” and “visible,” adding input to an array and creating models.

The post Maintaining and Organizing UI Dependencies with Knockout.JS: Part 2 appeared first on Big Nerd Ranch.

]]>

In Part 1 of Maintaining and Organizing UI Dependencies with Knockout.JS, we discussed the three types of observables that Knockout provides: observables, observable arrays and computed observables. We also built a small demo app and explained some of the library’s features such as templating, using different bindings like “foreach” and “visible,” adding input to an array and creating models.

The infamous JavaScript “this” conundrum

If you are experienced with JavaScript, you know about the ongoing war between scope, context and dealing with the this keyword. Previously, we used this on properties this.studentsName = ko.observable('') to refer to the ViewModel object itself. That was all well and good because we didn’t have to worry about tracking what this meant in a nested function within studentsName.

The problem arises when our ViewModel starts becoming more complex. Say, for example, our computed observable function this.challengeCompletedCount uses a nested internal function. Internal functions create a new, separate context with a new this variable. The new this variable is completely different from the one outside of our internal function. If we were to leave the code like so:

function courseViewModel(notes, challenges) {
  this.studentsName = ko.observable('Garry Smith');
  //... etc.
  this.challengeCompletedCount = ko.computed(function () {
    return ((ko.utils.arrayFilter(this.challenges(), function (challenge) {
      return challenge.completed();
    }).length / this.challenges().length) * 100).toFixed(2) + ' %';
  });
}

It would not work, because this.challenges() is looking for a .challenges method belonging to a new this variable created by the inner function. Put another way, it is trying to find an observable array within the context of the computed’s function scope, which doesn’t exist. This is why we used the .bind(this) on this.challengeCompletedCount in the Part 1 code so that we could bind the ViewModel’s value of this into the computed’s function. You may feel like this is weird. It is, but stay calm and code on.

The popular convention of working with this is to save its value into a variable at the ViewModel’s level so that you don’t have to worry about it being redefined.

function courseViewModel(notes, challenges) {
  var self = this;
  self.studentsName = ko.observable('Garry Smith');
  //... etc.
  self.challengeCompletedCount = ko.computed(function () {
    return ((ko.utils.arrayFilter(self.challenges(), function (challenge) {
        return challenge.completed();
    }).length / self.challenges().length) * 100).toFixed(0) + ' %';
  });
}

Knockout.JS’s Style and CSS binding.

Let’s go into the more enjoyable and less quirky part of JavaScript. As mentioned in the previous post, Knockout provides many useful binding attributes. The Style and CSS bindings allow you to visually represent changes in the data to the user. Maybe you want to apply different CSS classes to the same element under different situations, perhaps changing the background color of a validation box to red when a user inputs an invalid password. In our case, to show a validation message when adding a “Note.”

self.addNote = function () {
    var note = self.noteToAdd();
    if (note) {
        self.notes.push(new Note(note));
        self.noteToAdd("");
        self.validation("success", true, "Wicked good job!");
    }
    else {
         self.validation("fail", true, "Oh shux! There was no note text.");
    }
};
self.validationStatus = ko.observable();
self.displayValidation = ko.observable();
self.validationText = ko.observable();
self.validation = function (status, displayValidation, text) {
    self.validationStatus(status);
    self.displayValidation(displayValidation);
    self.validationText(text);
};

This checks to see if the user inputted a value into the note box and displays a message accordingly. The code below sets the font color of the validation text based on the value of validationStatus(), green for successful and red for failure. This is a simple implementation, but you can use any CSS properties you want in the style binding.

<div data-bind="text: validationText,
  style: { color: validationStatus() === 'success' ? 'green' : 'red' },
  visible: displayValidation">
</div>

Alternatively, you can bind CSS classes to an element using the css: binding. Below, we are applying the Bootstrap CSS class alert-success if the validation status is success, and alert-danger if not.

<div class="alert" data-bind="text: validationText,
  css: {
        'alert-success': validationStatus() === 'success',
        'alert-danger': !(validationStatus() === 'success')
       },
  visible: displayValidation">
</div>

Depending on where you feel comfortable putting code logic, you can also bind the observable to the CSS binding and put the logic in the JavaScript so that your HTML is simply css: validationStatus

Rejoice! Asynchronous Module Definition is here!

So far, our little app has been easy to maintain with only one ViewModel and a couple of models, but projects inevitably get larger and more complex. Code becomes quickly cumbersome and hard to maintain, not to mention prone to global variable conflictons. To truly leverage the perks of using an MVVM (model-view-viewmodel) or MVC pattern, you must separate your code into logical modules and decouple the layers. It may take some additional time at the onset of a project, but it will save you and your team headaches (and quite possibly an _Anchorman-_style street brawl) in the future. So unless tridents and hand grenades are your thing, I suggest using RequireJS to modularize your code.

Instead of having all of your Knockout code in your HTML file, you can break it down into separate JavaScript files, under their own respective folder paths such as ..ModelsChallengeModel.js and ..ViewModelsCourseViewModel.js and then tie their dependencies together using RequireJS’s define() syntax. RequireJS will do all the heavy lifting, loading each file and their dependencies in correct order.

define(['knockout-2.3.0', 'challengeModel'], function(ko, challengeModel) {
  function courseViewModel(notes, challenges) {
    var self = this;
    self.studentsName = ko.observable('Garry Smith');
    self.studentsAge = ko.observable(25);
    //etc.
  }
  return courseViewModel;
});

We are wrapping the ViewModel code in a define() which tells RequireJS what dependencies to load for this module, in this case, the Knockout library and our challenge model.

For more information on how to set up RequireJS in your project, please visit their documentation.

That’s it for now folks! We’ve covered some of the key aspects of KnockoutJS, including observable types, templating, built-in bindings and helper functions. We’ve also dabbled in project architecture, separating models and viewmodels, and tying together file dependencies with RequireJS, making code easier to maintain.

The code for this post can be viewed and run on jsFiddle, with exception to the RequireJS code.

The post Maintaining and Organizing UI Dependencies with Knockout.JS: Part 2 appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/maintaining-and-organizing-ui-dependencies-with-knockout-js-part-2/feed/ 0
Maintaining and Organizing UI Dependencies with Knockout.JS: Part 1 https://bignerdranch.com/blog/maintaining-and-organizing-ui-dependencies-with-knockout-js-part-1/ https://bignerdranch.com/blog/maintaining-and-organizing-ui-dependencies-with-knockout-js-part-1/#respond Tue, 13 Aug 2013 07:23:50 +0000 https://nerdranchighq.wpengine.com/blog/maintaining-and-organizing-ui-dependencies-with-knockout-js-part-1/

Why use Knockout.JS?

The post Maintaining and Organizing UI Dependencies with Knockout.JS: Part 1 appeared first on Big Nerd Ranch.

]]>

Why use Knockout.JS?

We all know that the larger a project gets, the harder it is to maintain and organize UI dependencies. “What if the user does this, or this? That needs to change that and that.” This can be tough to deal with, especially when a different developer comes onto the project and doesn’t quite understand how you manually set up these dependencies.

Among the plethora of different Javascript libraries that help UI developers create responsive UIs, Knockout.JS has been a pleasure to work with. It knows what it needs to do to help you but also when to get out of the developer’s way.

Knockout aims to help with this by providing the developer with a way to “make elegant dependency tracking, declarative bindings, and be trivially extensible.” Knockout uses the MVVM (model-view-viewmodel) pattern, so if you’re familiar with any other MV* pattern, it should be easy to pick up on. The pattern allows developers to keep the logic in the Javascript and the HTML5 view is left solely to render the logic. What I’ve enjoyed the most about using Knockout.JS is that it makes the web page reactive to the user, like a native desktop app, easily.

Observables

The heart and soul of Knockout are what’s called “observables.” The best description comes from Knockout’s documentation: “Observables are special JavaScript objects that can notify subscribers about changes, and can automatically detect dependencies.” What this means is that if, for example, a user changes his/her age on the UI, this change will automatically update that property on the ViewModel and automatically update the View and whatever dependencies may be on Age, without having to save or post back to the server. Let’s look at the ViewModel code:

function courseViewModel() {
    this.studentsName = ko.observable('');
    this.studentsAge = ko.observable();
};
ko.applyBindings(new courseViewModel());

The ko.applyBindings(new courseViewModel()); kicks everything off and binds your ViewModel to the View. Let’s bind the student’s age to the View.

<div data-bind='text: studentsAge'></div>

As you can assume, this binds the value of student’s age to the text value of the div element. To change this value live, add an input element: <input data-bind="value: studentsAge" />
Since this is an observable, it will notify all subscribers that there has been a change in value of studentsAge. Right now, the only subscriber is the div element that we created before and its text will be updated whenever you change the value of the input box.

Observable Arrays

Knockout also handles collections gracefully, allowing you to add, remove and manipulate lists on the fly. Say Big Nerd Ranch instructors want to be able to add notes to an individual student, maybe describing a certain course concept he/she needs to reiterate to that student the next day. We can do that easily:

//JavaScript
function courseViewModel() {
    this.studentsName = ko.observable('');
    this.studentsAge = ko.observable();
    this.notes = ko.observableArray([
      { dateTime: new Date(), text: ""}
    ]);
};
ko.applyBindings(new courseViewModel());
//HTML
<ul data-bind="foreach: notes">
  <li>
    <div data-bind="text: dateTime"></div> : <div data-bind="text: text"></div>
  </li>
<ul>

The data-bind="foreach: notes" loops through the notes array and renders list items for each item including the date/time it was added and its text.

Knockout also gives you the ability to create templates that can be used to render HTML for a single element or for each element in an array. Templates can help organize your code and can be pretty powerful. Let’s refactor the HTML above:

<h5>Notes:</h5>
//Where the template is actually rendered
<div data-bind="template: { name: 'notes-template', foreach: notes }"></div>
//What is rendered
<script type="text/html" id="notes-template">
  <div class="well well-small">
    <h5 data-bind="text: dateTime"></h5>
    <div data-bind="text: text"></div>
  </div>
</script>

The template script tag requires type="text/html" so that the browser does not mistake it as JavaScript. Knockout also supports the Underscore template engine and the jquery.tmpl engine, each with its own syntax. Check out Knockout’s documentation for more information.

There are lots of different bindings to make your UI responsive to what data is being sent to the UI. If there are no notes for an individual student, you can bind to the “visible” property of an element such as the “Notes:” heading title like so: <h5 data-bind="visible: notes().length > 0">Notes:</h5> This will hide the header if there are no notes. We will talk more about some of Knockout’s bindings later on.

Right now, we have no way of adding notes to the collection, so let’s remedy that. Add the following to your HTML View:

<form data-bind="submit: addNote">
  <div class="form-group">
    <label>Add Note:</label>
    <textarea data-bind="value: noteToAdd" class="form-control"></textarea>
    <br />
    <button class="btn btn-primary" type="submit">Add</button>
  </div>
</form>

We’re telling the View to monitor a “submit” event, triggered when the user clicks the submit button, and run the “addNote” procedure when it detects that event has occured. The ViewModel will grab the text value from the user’s input (noteToAdd) and push that to our notes array. This can be achieved easily by adding the following to our ViewModel:

this.noteToAdd = ko.observable();
this.addNote = function () {
  var note = this.noteToAdd().trim();
  if (note) {
    this.notes.push({ dateTime: new Date(), text: note });
    this.noteToAdd("");
  }
};

The if (note) { is a simple validation, checking to see if the user had typed anything into the textbox. If so, it adds that note to the array. Knockout makes doing more advanced validation and reporting results to the user a fairly trivial task for developers. We will go over validation later on.

Now on to computed observables, the last observable type in your toolbox.

Computed Observables

Computed Observables come in handy when tracking the dependencies on one or more observables. Whenever one of the dependent observables changes, it re-evaluates the computed observable’s value. The usual example is a “full name” situation, where a user changes the value of their first name or last name and the full name property combines the two values.

We’re going to do something different. Let’s make a feature that allows instructors to track how many assignments or challenges the student has completed and show a percentage of overall completion. First, create a “Challenges” model that sets up the structure for creating challenge objects. This is a best practice for fully utilizing the power of the MVVM (model-view-viewmodel) pattern. It separates your code into logical parts and makes handling and binding JSON data easier later on.

var Challenge = function (challengeText, challengeCompleted) {
  this.text = ko.observable(challengeText);
  this.completed = ko.observable(challengeCompleted);
}

And then add “challenges” to your ViewModel constructor: function courseViewModel(challenges) { This will allow us to insert and parse a custom array or JSON data into the ViewModel.

Add the following to your ViewModel:

this.challenges = ko.observableArray(
  ko.utils.arrayMap(challenges, function(challenge) {
    return new Challenge(challenge.challengeText, challenge.completed);
  })
);

What this is doing is mapping data from the array that we inserted through the ViewModel’s constructor to an observableArray of “Challenge” objects (with their respective observable properties). Knockout comes with numerous helper functions like the ko.utils.arrayMap that make situations like these easier. You can always still use jQuery or UnderscoreJS to do the the same stuff, it’s up to you.

Add some default data to your ViewModel like so:

var challenges = [
  {"challengeText": "Chapter 1 Challenge", "completed": false},
  {"challengeText": "Chapter 2 Challenge", "completed": false},
  {"challengeText": "Chapter 3 Challenge", "completed": false},
  {"challengeText": "Chapter 4 Challenge", "completed": false},
  {"challengeText": "Chapter 5 Challenge", "completed": false},
  {"challengeText": "Chapter 6 Challenge", "completed": false}
];
var viewModel = new courseViewModel(challenges);
ko.applyBindings(viewModel);

This data would normally come from an Ajax request, but for demo purposes, let’s hardcode it for now. Now on to the computed observable:

//JavaScript -- Add to ViewModel
this.challengeCompletedCount = ko.computed(function () {
  return ((ko.utils.arrayFilter(this.challenges(), function (challenge) {
    return challenge.completed();
  }).length / this.challenges().length) * 100).toFixed(2) + ' %';
}.bind(this));

While this is technically only dependent on one observable, it is computing two different inputs: the number of completed assignments and the number of assignments, and computing that into one output.

Finally, bind it to the HTML view:

//HTML
<h5 data-bind="visible: challenges().length > 0">Challenges:</h5>
<div data-bind="template: { name: 'challenges-template', foreach: challenges }"></div>
<script type="text/html" id="challenges-template">
  <div class="checkbox">
    <label>
      <input type="checkbox" data-bind="checked: completed"> <div data-bind="text: text"></div>
    </label>
  </div>
</script>
<h5>Completed: <span data-bind="text: challengeCompletedCount"></span></h5>

We’re done! If you have spent any time with front-end development, you will appreciate how simple Knockout makes handling dependencies, with little regard to how or where your data is coming from.

I hope you learned something and enjoy working with KnockoutJS as much as I do. I strongly encourage you to check out the Knockout.JS documentation and go through their live tutorials.

You can see my full code and demo for this tutorial on jsFiddle.

The post Maintaining and Organizing UI Dependencies with Knockout.JS: Part 1 appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/maintaining-and-organizing-ui-dependencies-with-knockout-js-part-1/feed/ 0