DPH.AM

I like to draw, code, and build stuff.

Chromebook for Developers

Acer Chromebook

I recently purchased a Chromebook to determine if it’s possible to code effectively on it (spoiler: yes it is). I’ve actually been on the fence for a while now. There were two reasons that finally pushed me to make the purchase; I noticed the dust layer covering my Retina iPad and Kindle HDX, and I realized both tablets can be replaced with a light-weight 11” $200 laptop.

Acquiring the Chromebook was largely, as they say, for science. With that in mind, I kept track of everything I did since day one. The following is a hacker’s guide to configuring a fresh install of ChromeOS.

The essentials

Shortcuts

If you want to be efficient with your operating system, you must grok the default keyboard configuration and shortcuts. My first mistake on the Chromebook was remapping the Alt key to simulate the Ctrl key. The only benefit this added was allowing me to open new tabs the same I do on a Mac keyboard (Alt key is next to the Spacebar). I quickly realized how essential the Alt key is and how foolish I was for discarding it.

ChromeOS has a built-in shortcuts overlay which displays shortcuts on a virtual keyboard when a command key is held down. Access this menu by tapping Ctrl+Alt+/. Many other shortcuts stem from holding down the Alt key and some other key combinations. You can see other useful shortcuts in this blog post. Here are my personal favorites.

  1. Desktop Screenshot – Ctrl+switch window key
  2. Partial Screenshot – Ctrl+Shift+switch window key
  3. Task Manager – Shift+Esc
  4. Capslock – Alt+Search
  5. Mirror – Ctrl+Fullscreen

Trackpad

Why trackpads simulate the tactile click of a traditional mouse is beyond me. The first thing I do when I’m on a new Mac is change the trackpad to a touch-only gesture control – single tap triggers left click, double tap triggers right click, and three finger drag simulates mouse drag (left mouse down + mousemove). On ChromeOS, the first two are enabled by default. If you’re deranged, you can disable the single tap feature under Chrome Settings > Device > Touchpad Settings > Enable tap-to-click.

ChromeOS’s 3-finger drag gesture allows you to switch between tabs (a feature I’ve warmed up to), and there’s no way to change that gesture to mouse drag. The best thing you can get is one-finger tap drag. This option is hidden inside the “Show advanced settings…” section in the ChromeOS settings panel. Locate the “Enable tap dragging” option under the Accessibility section.

I must admit, because of Apple, inverted scrolling feels natural to me now. In fact, I experience cognitive dissonance when using the normal scrolling direction. Thankfully, this feature can be enabled by selecting “Australian scrolling” under the Touchpad Settings section.

Apps

  1. Secure Shell
    As a developer, this is the most powerful app you can arm yourself with, both for cloud and local development. More on this app below.

  2. Remote Desktop
    Dead simple to configure! It’s good to have if you always keep your desktop on. If you have multiple monitors, it will show the screens side by side. I would prefer for it to shrink down to one monitor and take the screen resolution of the client, but I don’t use it enough for it to bother me.

  3. Image Editor – Pixlr Editor
    This is as close to Photoshop as you’ll get on the web.

  4. Image touchups and filters – Pixlr Touch Up
    A.K.A instagram filters app.

  5. Lightweight Text Editor – Text
    A general purpose text editor with dark and light themes as well as color syntax support for a wide variety of formats.

  6. Fully featured Code Editor – Caret
    I have this installed and used it only once, but only because I got Vim 7.3 and my config files to work. I suspect this is the best option if you’re a Sublime Text fan.

Open App as Window

At default, all Chrome apps open in a browser tab. Right click on an app icon (either in the search panel or shelf) and you can change how the app will open. I like having dedicated windows for some of my apps. If you’re a Vim window-splittin’ ninja like me, this is an absolute must as Ctrl+w will close the current tab rather than send the window command.

Cloud development

I abandoned a ton of possessions when I left my Maryland suburban life and moved to Seattle 9 months ago. There wasn’t much of a choice; space is limited in the city and so are the things we can stuff in there. I went from a 3500 sq ft single family home to a 780 sq ft one bedroom apartment (apparently that’s a decent size by Seattle standards). It turns out this was one of the most liberating experiences of my life. Detachment from physical belongings created a mindset shift that eventually got me yearning for a world where personal computing devices are obsolete. For developers, having a cloud dev environment is the first step towards this utopian world.

Free cloud environment with Nitrous.io

There are currently plenty of great options for cloud development. Digital Ocean offers SSD cloud servers at a competitive price point. Cloud9’s free package gives you full shell/terminal access and an IDE workspace for a private project. In the end, Nitrous.IO won me over because of three reasons: a feature-heavy product, a generous freemium offering, and an incredible Chrome application.

Nitrous.io makes it easy to set up a Ruby, Python, Go, or Node.js box with just one click. Of course you have a full Linux shell and can install other packages using autoparts. They have a web IDE and terminal, allowing you to be productive anywhere you have access to a web browser. In fact, Nitrous.io’s Collaborative Code Editing service allows for synergies between you and your fellow collaborators in a way a traditional local dev environment cannot achieve.

The most relevant reason why Nitrous.io is the perfect cloud service for ChromeOS is their Chrome app. The app provides an easy way to manage your dev box and comes equipped with an IDE and terminal. And yes, opening the app in window mode gets you around Vim’s Ctrl+W issue clashing with Chrome’s close tab functionality.

If you don’t already have an account, I highly encourage you to sign up and take it for a spin. It’s free after all.

Secure Shell

Most cloud providers authenticate your connection using the ssh public key associated with your machine. Luckily the Secure Shell app gives you the option of importing a key and assigning it to a connection. Go to any Unix machine and run ssh-keygen to generate a pair of keys and store them on your Google Drive. You can now import this key pair and assign it to any number of connections in Secure Shell. If you have sudo access on the server machine, including this public key in the server’s authorized_keys file will allow you to connect without entering the password.

The Secure Shell app comes with a default font size of 15pt which was a tad big for my taste. At this font size, a vertical split in Vim does not provide the buffers with a standard 80-character column width. To fix this, open up the console on any Chrome tab (Ctrl+Shift+i) and enter term_.prefs_.set('font-size', 12). Visit the Secure Shell FAQ page for other useful preferences.

Local Dev Environment

In the early days, all the “Chromebook for Devevelopers” guides contain instructions on how to replace ChromeOS with Ubuntu. I’m currently composing and previewing this blog post off a local ruby server on my Chromebook, and I can assure you I still have ChromeOS installed.

Getting the full shell

ChromeOS ships with a shell called crosh which you can bring up by tapping Ctrl+Alt+t. It’s completely useless unless you enable developer mode and install crouton. Their Github repo contains installation instructions. If you need it spelled out for you, just follow steps 1 and 2 in this blog post.

Open Crosh in Window

Launching crosh actually activates the Secure Shell app, but for some reason it opens as a Chrome tab even though Secure Shell is set to open as a window. This wasn’t cutting it for me; as I mentioned above, the tab-close shortcut conflicts with the window operations in Vim. It turns out you can create a connection in the Secure Shell app to your local crosh shell. Just create a new connection, give it whatever label and username you want, but you must set the value in the hostname field to >crosh. There’s a Crosh Window app which automatically opens crosh in a dedicated window, but I’m partial to having all my shell connections under the same app (Secure Shell).

This gets you to the basic crosh shell, you must still type shell to enter full shell mode. At this point, you’ll save yourself a ton of time by adding the following to your .bashrc file: alias s='sudo enter-chroot'. Now jumping into your crouton environment is just a matter of sending the s command (short for start).

Locale issues

After cloning my unix configuration, I started to see warning messages related to locale. I traced it to the rbenv init call in my .zshrc. If you’re running rbenv (which you should) and have locale issues, here’s how you fix it.

  1. sudo apt-get install locales
  2. sudo locale-gen
  3. sudo dpkg-reconfigure locales

Extending your Storage

My Acer Chromebook has 16GB of flash memory, most of which are occupied by the Operating System. You only have storage issues if you’re utilizing your Chromebook for local development; Google gives you 15GB of Google Drive storage and is currently offering Chromebook users 100GB extra free of charge for a year. I have nothing in my Downloads folder – after installing node.js, ruby, python, and other essentials, I only have 8GB of storage left.

At the rate I’m going, it won’t be long until I reach the capacity of the internal storage. I set out to find the best storage option based on three requirements: adequate storage space (at least 32GB), low profile attachment, and lightning fast speed.

Since I already have a 32 GB class 10 MicroSD card lying around, I went with a low-profile MicroSD adapter. It doesn’t sit as flushed in the SD card reader as I would’ve liked. However, it also doesn’t stick out far enough to worry me about accidentally breaking it in half the same way I worry about standard-size SD cards (photo). If you’re taking the SD slot route, here’s an alias that will save you a ton of time: alias sd='cd /media/removable/SD\ Card'. I keep the adapter glued to the SD slot and having an alias to jump directly to the SD card directory has been invaluable.

A note about low-profile MicroSD readers: there are plenty of Raspberry Pi MicroSD adapters smaller than what I’m using. Beware of the bulge from the MicroSD slot on these adapters, they will not fit in a standard SD card slot.

Another option if you already have a MicroSD card is a tiny USB 2.0 card reader. This is the smallest MicroSD card reader I found during my search. Many critical reviews claim that the reader stops working after a number of insertions. With our use case, the USB device is treated like a permanent fixture and will endure much fewer physical stress.

If you don’t have a MicroSD card handy, the most reliable and affordable storage option is the SanDisk Cruzer Fit. The MicroSD card I’m using has a 30MB/s transfer rating, but I’d be lucky to achieve 15MB/s reads. According to this review, the customer ran a simple test on a 1GB file and got 4.10MB/s write and 16.5MB/s read. This transfer rate should be more than enough to handle standard development.

Most Chromebooks are equipped with a USB 3.0 port, it’s a shame that the low-profile USB 3.0 flash drive market is so sparse. The best option I found is a line from Patriot with an overwhelming number of reviews about the device overheating. If ~15MB/s read is inadequate, it’s worth giving this USB 3.0 drive a try.

What’s next?

Although I included a whole section on setting up a local development environment, it’s largely meant to show the Chromebook’s capabilities. Using a Chromebook for local development is counter intuitive. Move your development environment, better yet your entire digital life, to the cloud. If you encounter friction along the way, don’t look at it as a hindrance but an opportunity to solve real problems with your programming ability.

Getting Things Done With Evernote

I read Getting Things Done by David Allen about 6 months ago at the suggestion of a coworker and it has been absolutely vital to my daily workflow. I won’t go into details about why GTD is so helpful in increasing productivity; it’s a short book and you should definitely read it if you haven’t already. Instead, I want to focus on how to effectively apply the methodologies using a free application called Evernote.

Why I started using Evernote

After learning that Omnifocus was the de facto application for GTD and being a compulsive buyer, I wasted no time paying $39.99 for a copy from the Mac Appstore (it now costs $79.99). At the time, my work machine was an Apple MacBook Pro, my home/entertainment machine was a Windows 7 desktop, and my mobile device was an Android-powered HTC MyTouch 4G (I currently have a Retina MacBook Pro, a hackintosh tower, and a Galaxy Note II, but that’s besides the point). I quickly became frustrated with Omnifocus; I didn’t have an iPhone so I couldn’t take Omnifocus on the go, I couldn’t use it on my home computer since I was using Windows, and it didn’t make sense for me to pay another fee just to have it on my iPad. I was already using Evernote to store short web clippings so I did a quick google search for “GTD with Evernote” and a particular thread caught my attention. I borrowed some ideas from the discussions and crafted a workflow that worked for me.

What’s so great about Evernote

It’s Free

Well… they have a freemium business model. You can upgrade your account to premium for $5/month or $45/year. Fortunately, larger upload size, work collaboration, note history, and priority support are not necessary for implementing a GTD workflow.

It’s Flexible

Evernote is not a task/todo list manager, it’s a feature-heavy cloud-storage note application. The features I enjoy the most are the ability to add multiple tags to a note, attach images to a note, create audio/video notes, and generate a publicly-accessable html document from a note.

It’s on every platform imaginable

There’s a Windows and a Mac client. There’s even an open-source third party app for linux called Everpad. And of course, you can also download it on the Android Play Store and the iOS AppStore.

It’s completely open!

If you’re a developer and want to write third-party apps, Evernote has an API. There’s a nodejs package, ruby gem, and python package to help you get started on your project. Also, check out Evernote Trunk to see all the cool third-party apps.

What my workflow look like

Start with a GTD notebook

Evernote allows organizing notes based on different notebooks. I personally keep only one notebook and rely heavily on tags. However, this isn’t a strict requirement; If you’re already using Evernote and have existing notebooks, just make a new one and name it “GTD”. Every single thing that needs to be written down no matter how big or small can be worded as getting something done. By this logic, one should only need a single notebook consisting of notes about GTD.

Creating a new item

There is only one thing to keep in mind when creating a new activity; be comfortable with an empty note. Smaller tasks such as calling someone to make an appointment or picking up something from the grocery store should only warrant text in the note’s title. However, the great thing about using a note which comprises of a title and body rather than just a single line of text representing the task or goal is that it’s possible use the body block to store more information about an activity. Utilize this extra field to store information about a long term goals such as learning a new language or an information-heavy task such as refinancing a home. Take advantage of other core features as well. If your task is to call someone or pick up clothes from the cleaners and since Evernote supports photo uploads, it’s much more efficient to take a photo of the business card or receipt than to manually type in the phone number or tracking ID.

Tagging your notes

The minimum required tags are:

  1. !Today
  2. #Next
  3. ~Basket
  4. &Done

Whenever you come up with a task/goal, create a note and add a ~Basket tag to it. Visit notes with this tag often and replace the ~Basket tag with the #Next tag when you plan to tackle it within the next few days. Items you plan to resolve the same day should have the !Today tag. After accomplishing a task, If you like to archive, add the &Done tag to the note, otherwise either delete the note or remove all tags in the above list from it.

I chose these symbols because the old Evernote desktop app would list all the tags in the sidebar in a certain order. These particular symbols would ensure that the four tags will appear at the top of the tag list. Even though the Evernote app now has a customizable “shortcuts” section in the sidebar, having unique symbols for easy tagging is still invaluable. Just type the first character symbol and hit tab, Evernote will automatically select the first/only matching tag.

Keep in mind you can and should have multiple tags outside of the above four, just make sure they don’t have a character precedence clash. I often tag notes with a location or activity context such as @shopping, @home, @phone, @work, etc. I also have a unique first character for tags I use the most such as $Programming (the dollar sign has some history with programming languages) and ?Reading (a question mark seemed appropriate for reading and learning).

Getting things done is a slightly different process for each person, and Evernote gives plenty of room for personal implementation. The someday tag is popular and is suggested in the GTD book, but I prefer tags such as one week out and one month out because it forces me to continually make sure I check my backlog and move tasks along. At the end of the day, you’re successful as long as you’re getting things done efficiently.

Oh and by the way, this blog entry was written in Evernote with the title “write a blog entry about GTD using Evernote”. I can remove the !Today tag now.

Backbone Models With RequireJS in Node

I’ve been working on a very data-intensive, client-facing application at work for a few months now. The application is a WYSIWYG editor which requires outputting some markup and parsing the LESS tree to be inserted onto the page as a user makes changes. Naturally, I used Backbone to help structure the models. At the end of the day, I needed to burn out and persist the markup and LESS files to the server as well.

One of the main reasons I chose Node, and I think this is the same reason for most people, is because I wanted the same consistent code in the client as well as the server. Since I already created Backbone models describing my data, it was a no brainer to use these models on the server. Normally, this is extremely simple, but lets face it, who doesn’t write large client applications these days without a JavaScript module loader? The problem is that node already implements the CommonJS pattern but RequireJS uses the AMD API. However, since require has a node module, it wasn’t too tricky using this to require other client-side modules.

The following is a sample Backbone model wrapped in RequireJS’s define call. If this file is being loaded on the server, the define function will not exist; as you can see, resolving this is as simple as setting define to require('amdefine')(module). I specifically included references to both LESS and jQuery to illustrate how these libraries can be used appropriately inside models and how to resolve conflicts when loading it on the server. You can find comments inside the code.

public/src/models/Template.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
// if 'define' is not a function, this file is being requested from the server
if(typeof define !== 'function') {
  define = require('amdefine')(module);
}

define([
  'underscore',
  'backbone',
  'less',
  'jquery',
  'some_collection'
], function(_, Backbone, less, $, SomeCollection) {

  // In the next file, you will notice requirejs's sever
  // configuration points the 'less' module to a null object
  // this allows us to know if we're on the server and
  // reset references accordingly
  if(!less) less = require('requirejs').nodeRequire('less');
  var lessParser = new (less.Parser)();

  // If the model needs to output HTML markup, it's useful to
  // include jQuery to perform node and attribute changes
  // cheerio wraps strings and implements jQuery's API
  if(!$) $ = require('cheerio');

  return Backbone.Model.extend({

    // this model may have dependencies on other models/collections
    // after we resolve the define call, these files will automatically
    // be loaded properly without having to make them CommonJS-compatible
    initialize: function() {
      this.someCollection = new SomeCollection();
    },

    markup: function() {
      var $node = $('<div/>').html(this.get('contents'));
      // do some crazy manipulations here
      return $node.html();
    },

    css: function(fn) {
      lessParser.parse(this.get('bar'), function(e, tree) {
        if(typeof fn === 'function') fn(tree.toCSS());
      });
    },

    data: function() {
      return {
        slug: this.get('id'),
        foo: this.someCollection.toJSON()
      }
    }

  });

});

Now in your server file, you just need to require the requirejs module and configure the paths. Notice less and jquery both point to a file called empty – all this file contains is define(null);. As stated above, this is to let you know that the file is being included on the server and you need to make the right reference adjustments. You’ll also likely want to turn this file into a CommonJS module. To do this, just export the object first; after you’ve successfully used requirejs on the server to load your Backbone model, attach methods/objects to the exported object that has the Backbone model as a dependency.

compile.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
var fs = require('fs');
var requirejs = require('requirejs');

requirejs.config({
  nodeRequire: require,
  bareUrl: 'public/src',
  paths: {
    'less': '../lib/empty',
    'jquery': '../lib/empty',
    'backbone': '../lib/backbone',
    'underscore': '../lib/underscore'
  }
});

function Compiler() {
  this.initialize.apply(this, arguments);
}

// Export this object first
module.exports.Compiler = Compiler;


requirejs(['models/Template'], function(Template) {

  // successfully loaded the Backbone model, now we can
  // set methods to Compiler that has model dependency
  Compiler.prototype = {

    initialize: function(someObject) {
      this.template = new Template(someObject);
    },

    compile: function() {
      var dataString = JSON.stringify(this.theme.data(), undefined, 4);
      fs.writeFile('some_json_file.json', dataString, function(err) {
        if(err) throw err;
      });

      fs.writeFile('some_template_file.dust', this.theme.markup(), function(err) {
        if(err) throw err;
      });
    }

  };

});

Driving an RC Car With Node.js

Here’s a video of an RC car I rigged that’s controlled using the raspberry pi’s GPIO headers.

I know JavaScript is a terrible language to write autonomous cars in – it’s single threaded and has a terrible timer. Your accuracy is limited to ms and even then it’s not precise because of the single threaded nature.

That said, I wrote a node.js raspberry pi gpio library (you can run “npm install gpio” to install) and wanted to use it. I was already planning to make an autonomous car from an existing RC car with my Arduino board and decided to throw a raspberry pi on it for kicks, and I wanted to prove that my node gpio package works.

The node-rc library I used in the video can be found here

I like that rpi [can] have an entire operating system and I can ssh into it. I have a wireless usb dongle set up on the Pi and I had this lying around. I might build the autonomous car using the rpi in C or python or something.


edit

A few people have been asking about the RC car so here goes…

I connected the gpio headers directly to the chip on the car, not the remote (the rpi needs to be on the car so I can connect sensors to it later). You can’t connect it directly to the motor as many people on this board have said – the pi can’t produce a constant current to drive the motors.

The nice thing about working with an RC car is that the motor driver is built in for you. Google the RX-2B datasheet; I’ve taken apart 3 RC cars so far and they all use the same chip. In general, pin 2 is ground, 6 is right, 7 is left, 10 is reverse, and 11 is forward. Connect the Pi’s ground to pin 2, then whatever gpio header you want to the corresponding pin. Word of advice, some cars have bigger chips than others. The Targets near me sells New Bright and Jada (brand) RC cars; the New Bright ones had such a small chip that it was impossible to solder anything on. The Jada was much easier to solder but had a terrible set of wheels and turning mechanism. I actually replaced the whole board on my New Bright car with the one from Jada (just connect the right wires to the motor and it’s fine).

Note: you can remove the chip altogether and solder your wire directly into where the hole used to be, but this will disable the remote controller. If you choose to keep the chip, in your software, remember to change the direction to “in” before using the remote.

Don’t Mix Spaces and Tabs

I personally love spaces, but I only prefer spaces over tabs because it’s not tabstop-dependent. There are editors (and code hosts such as github) out there using different tabstops and not intelligent enough to interpret tabs as different whitespace characters depending on the number of tabs/spaces used on the line right above it.

For that reason, what you see as…

1
2
3
4
/**
 * @string1      String      Hey I'm a string
 * @boolean1     Boolean     And I line up with the guy above me
 */

may look like..

1
2
3
4
/**
 * @string1                String              Hey I'm a string
 * @boolean1     Boolean      Uh oh, I don't line up =(
 */

There are worse examples, I promise. The point is my tabstop value may be different than yours and my editor may not be as intelliJent as yours, so try to play nice. When you’re working on a project with other people, using JUST spaces or JUST tabs should never be a topic of discussion. The only way I’ve found so far that can allow us to respect everyone’s coding standards and play nice with github is…. dun dun dun..

Use tabs at the beginning of a line and spaces everywhere else

Alot of developers seem to have adopted this, heck even JSHint at default warns you about this (kinda). If you’re OCD like me and prefer your code to line up nicely, that’s fine, but please don’t mix spaces and tabs. After your first non-tab character in a line, try to resist hitting space, then tabbing a bunch of times just to line something up.

If you’re a Vim user, here’s a great script that will automatically use spaces instead of tabs when you try to tab after a non-white character. It will even line up with the indentation above using spaces! Just throw in your vimrc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function VaryTabs()
  if &expandtab
    return "\<Tab>"
  else
    let nonwhite = matchend(getline('.'),'\S')
    if nonwhite < 0 || col('.') <= nonwhite
      return "\<Tab>"
    else
      let pos = virtcol('.')
      let num = pos + &tabstop
      let num = num - (num % &tabstop) - pos +1
      return repeat(" ",num)
    endif
  endif
endfunction
inoremap <Tab> <C-R>=VaryTabs()<CR>

Vim Editing Mode in Your Zshell

Note: I got the huge chunk of the code from this blog post.

If you’re a VIM lover like me and using zsh for your shell, there’s a simple command to enable vim mode on your command prompt.

1
set -o vi

It works fine other than the fact that I usually had no idea if I’m in normal or command mode. If you want to read about the hows, following the link above. Below is the zsh script I use to indicate my editing mode on my prompt.

1
2
3
4
5
6
7
VIMODE=">> "
function zle-keymap-select {
    VIMODE="${${KEYMAP/(main|viins)/>> }/(vicmd)/}${${KEYMAP/vicmd/<< }/(main|viins)/}"
    zle reset-prompt
}
zle -N zle-keymap-select
set -o vi

Now you can place $VIMODE in your zsh prompt declaration. >> will display for normal mode and << will appear for command mode. Of course you can easily change that to whatever you prefer.

Bind and Trigger

Bind and trigger in JavaScript is one of those design patterns that are widely used but not discussed anywhere outside of the scope of the framework that utilizes it. A google search for “bind and trigger” brings up mostly references to jQuery discussions, and their context seems to revolve around attaching event handlers to DOM nodes rather than implementing it in your own application.

Javascript is an event-driven language. As such, it’s common practice to pass in function callbacks to be triggered by an event. For instance, an AJAX request requires a function callback since the callstack will finish long before the request is completed. Passing in a function callback is the only way to guarantee that your function will get called after the request has completed and with the correct data from the request. But what if you want multiple callbacks to happen after the request is done? Or if you’re building an application which accepts plugins; how do you allow these plugins to hook into events triggered from the core application? This is where the bind and trigger paradigm really shines.

Sidenote. There are plenty of frameworks out there like jQuery and Spine which have a very complete implementation of the bind and trigger paradigm. Mine is very incomplete for the sake of explaining the model.

In this example, we will build a Button Class which allows you to create an anchor element.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Button = function(param) {
   var text, href, anchor;

   if(typeof param !== "object") {
      // ok, seriously?
      return false;
   }
   anchor = document.createElement('a');
   anchor.innerHTML = param.text;
   anchor.setAttribute('href', param.href);

   this.anchor = anchor;
   this.param = param;
};

var button = new Button({
   text: "Link Text",
   href: "http://dph.am"
});

Now lets assume that when a button is appended to the page, you want a callback to happen. This is pretty common in an element design pattern where anchors are the elements and your application wants to proxy the button’s append event. This is typically done two ways; through the method’s parameter callback and Class initialize parameter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Callback from initialized parameter
Button.prototype.appendTo = function(container) {
   container.appendChild(this.anchor);
   if(typeof this.param.onAppend === "function") {
      this.param.onAppend(container);
   }
};
var button = new Button({
   text: "Link Text",
   href: "http://dph.am",
   onAppend: function(container) {
      console.log("Anchor was appended to", container);
   }
});
button.appendTo(document.body);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Callback from method parameter
Button.prototype.appendTo = function(el, callback) {
   el.appendChild(this.anchor);
   if(typeof callback === "function") {
      callback();
   }
};
var button = new Button({
   text: "Link Text",
   href: "http://dph.am"
});
button.appendTo(document.body, function(container){
   console.log("Anchor was appended to", container);
}

These pattern brings up a few problems.

  1. For the first method, callback must be defined when initializing. You might not know what that callback is the when you initialize the Button class.
  2. For the second method, you must define the callback every time you call button.appendTo(). This is a bit tedious.
  3. Only one callback can happen!

The bind and trigger pattern essentially solves all these problems. You can attach the callback anytime you want, you only need to attach it one time, and best of all, you can “bind” a number of callbacks to be “triggered” later. To do that, we extend our prototype.

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
var Button = function(param) {
   // place the following line in your class init block
   this.events = {};
};

// Bind method
Button.prototype.bind = function(event, callback) {
   if(typeof event === "string") {
      if(typeof this.events[event] !== "object") {
         // This event type doesn't exist, create a stack for it
         this.events[event] = [];
      }

      if(typeof callback === "function") {
         // Push this callback into the event's stack
         this.events[event].push(callback);
      }
   }
};

// Trigger method
Button.prototype.trigger = function(event, param) {
   // Making sure the event type stack exists
   if(typeof event === "string" &&
      typeof this.events[event] === "object" &&
         this.events[event] instanceof Array) {

      // Loop through event's stack
      for(var i=0, len=this.events[event].length; i<len; i++) {
         if(typeof this.events[event][i] === "function") {
            // Fires callback with given parameter
            this.events[event][i](param);
         }
      }
   }
};

// Update appendTo method to trigger an "onAppend" event
Button.prototype.appendTo = function(container) {
   container.appendChild(this.anchor);
   this.trigger("onAppend", container);
};

// Now create a button instance and bind to the "onAppend" event
var button = new Button({
   text: "Link Text",
   href: "http://dph.am"
});
button.bind("onAppend", function(container) {
   console.log("Callback 1 fired, button appended to", container);
});

// In fact, you can bind multiple callbacks to that event
button.bind("onAppend", function(container) {
   console.log("Callback 2 fired, button appended to", container);
});

// Now when you call the appendTo() method
// all the "onAppend" callbacks will get triggered
button.appendTo(document.body);

Again, this is a very bare implementation. In practice, one would define an unbind method so that certain bounded callbacks can be unbind and maybe performing a call or apply on each callbacks to give them the instance’s context.

Firefox Outline Overflow Fix

Firefox has a rendering bug with the outline css attribute where the outline will wrap not the element but the overflow elements from its descendants. Below is a comparison of how outline renders in Firefox vs other browsers.

This is a screenshot of a container with 2px blue border, 2px red outline, and an image inside with a -30px left and top margin. As you can see, in Firefox, the outline is around the container as well as the overflowing image.

The following is a fix for it.

1
2
3
4
5
6
7
8
9
.container { position: relative; }
.container:before {
   content: '';
   margin: 0; padding: 0;
   position: absolute; z-index: -1;
   top: 0; left: 0;
   width: 100%; height: 100%;
   outline: 1px solid #ff0000;
}

We create an extra container using the :before pseudo-selector with absolute position set at 0 0 and 100% width and height. We then place an outline around this element, effectively forcing the outline to wrap only the container. The container itself must have a position relative, absolute, or fixed for the :before selector to be positioned properly.

If you’re a sass guy, here’s the mixin for it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@mixin outline($definition) {
   position: relative;
   &:before {
      content: '';
      margin: 0; padding: 0;
      position: absolute; z-index: -1;
      top: 0; left: 0;
      width: 100%; height: 100%;
      outline: $definition;
   }
}

.container {
   @include outline(1px solid #ff0000);
}