Skip to content
Menu
Justin Ball
  • About
  • Privacy Policy
Justin Ball

Ember.js Lessons Learned So Far

Posted on March 15, 2014November 1, 2021

Ember.js

Lessons Learned and Stuff

Justin Ball / @jbasdf

Justin Ball

  • CTO Atomic Jolt
  • Consultant, Rubyist, Javascriptist, Cyclist, Maker, Entrepreneur
  • Purveyor of Buzzwords

What is Ember.js?

  • A framework for creating ambitious web applications.
  • Client-side MVC Framework
  • Open Source – Github Pulse
  • Single page apps are sexy
  • Ember.js makes it easy*

* Easy is exclusive of the tears shed while learning Ember.js

Why Single Page Apps?

iFrames!

Safari and IE no longer let you write a cookie to an iFrame unless the user has visited the site

Real World – LTI apps

Heads Up

  • Ember.js changes a lot
  • Ignore anything over 3 or 4 months old
  • When you choose Ember.js you are all in

Using Rails?

Ember.js Rails Gem


gem 'ember-rails'
gem 'ember-source', '1.4.0' # or the version you need
      

//= require handlebars
//= require ember
//= require ember-data
      
Why choose the gem over anything else?
Yehuda Katz

AMD?

I love require.js

fogetaboutit

Or Try Ryan Florence’s Ember Tools*

*See “Ember Select doesn’t work” on this page to learn about the dangers

Feel Good About Polluting The Global Namespace


App = Ember.Application.create({
});
      

I don’t do Rails

Try

Ember App Kit

Ember.js and AngularJS

Everything is Awesome


AngularJS from an Ember perspective


Ember.js is Magic!

Except for when it’s not

Debugging

The Bug

Assertion failed: Cannot call get with ‘id’ on an undefined object. application.js?body=1:16925
Uncaught TypeError: Cannot read property ‘__ember1375726885234_meta’ of undefined

The Code


var map = MapModel.createRecord({
  title: 'New Map'
});
map.save().then(function(){
  this.transitionTo('map', map);
}.bind(this));
      

The Cause?

Failure to call ‘transition.abort();’

Now We Know Better

Assertion failed: Cannot call get with ‘id’ on an undefined object. application.js?body=1:17079
Uncaught TypeError: Cannot read property ‘__ember1375989556474_meta’ of undefined application.js?body=1:18391
Trying to get configured shortcut getDocumentSelection.js:51
Assertion failed: Emptying a view in the inBuffer state is not allowed and should not happen under normal circumstances. Most likely there is a bug in your application. This may be due to excessive property change notifications.


{% raw %}
<li>{{#linkTo 'map.destroy' title="Delete the current map"}}<i class="icon-trash"></i> Delete{{/linkTo}}</li>
{% endraw %}
      

No we don’t. ‘map’ was null.

Now That We’re Experts

Assertion failed: Cannot call get with ‘id’ on an undefined object. ember.js?body=1:364
Uncaught TypeError: Cannot read property ‘__ember1377186615643_meta’ of undefined ember.js?body=1:1676
Assertion failed: Emptying a view in the inBuffer state is not allowed and should not happen under normal circumstances. Most likely there is a bug in your application. This may be due to excessive property change notifications. ember.js?body=1:364
Uncaught Error: You cannot modify child views while in the inBuffer state ember.js?body=1:18835

“Most likely there is a bug in your application”

Would not have guessed that

Bad Code


{% raw %}
{{#each post in Posts}}
  {{#linkTo 'posts.show' title="View post"}}{{post.name}}{{/linkTo}}
{{/each}}
{% endraw %}
      </pre>
      

Good Code


{% raw %}
{{#each post in Posts}}
  {{#linkTo 'posts.show' post title="View post"}}{{post.name}}{{/linkTo}}
{{/each}}
{% endraw %}
      </pre>
      

Forgot to include 'post'

</section> </section>

(Really) Little Things Matter


App.ContactController = Ember.ObjectController.extend({
  needs: ['selected_contacts'],
  selectedContacts: null,
  selectedContactsBinding: 'controllers.selected_contacts.content',

  isSelected: function(){
    console.log('Checking Selected for' + this.get('name'));
    return this.selectedContacts.contains(this.get('content'));
  }.property('selectedContacts@each')

});
      

It looks like the #$%#! sample code.

But isSelected never fires!!!!

I see you still use email

Bad


property('selectedContacts@each')
      

Good


property('selectedContacts.@each')
      

Multiple Ember Apps in a Rails App

Stay DRY. Share code

Setup to reuse code from 'Common'


Common
  components
  helpers
  models
  mixins
  templates
SomethingAwesome
  controllers
  helpers
  mixins
  routes
  templates
  views
Admin
  controllers
  helpers
  mixins
  routes
  templates
  views
      

The Problem

Ember won't be able to find your templates

The Solution

Override the Resolver


AppNamespace = 'SomethingAwesome';
SomethingAwesome = Ember.Application.create({

  Resolver: Ember.DefaultResolver.extend({
    resolveTemplate: function(parsedName){
      var fullNameWithoutType = parsedName.fullNameWithoutType;
      parsedName.fullNameWithoutType = AppNamespace + "/" + fullNameWithoutType;
      var result = this._super(parsedName);
      if(!result){
        parsedName.fullNameWithoutType = "common/" + fullNameWithoutType;
        result = this._super(parsedName);
      }
      return result;
    }
  })

});
      

Check out Open Assessments source for an example

Transitions

Do This Infinitely


App.ApplicationRoute = Ember.Route.extend({

  model: function(){
    return this.store.find('user', params.user_id);
  },

  afterModel: function(transition){
    this.transitionTo('anotherPlace');
  }
});
      

Check Your Target


App.ApplicationRoute = Ember.Route.extend({

  model: function(){
    return this.store.find('user', params.user_id);
  },

  afterModel: function(transition){
    if(transition.targetName == "application.index"){
      this.transitionTo('anotherPlace');
    }
  }
});
      

Choose your event names wisely

'destroy' is reserved


    {% raw %}
<a href="#" {{action 'destroy'}} class="btn">Delete</a>
    {% endraw %}
    

'destroy_node' is not


    {% raw %}
<a href="#" {{action 'destroy_node'}} class="btn">Delete</a>
    {% endraw %}
    

Set controller for route


var ThingEditRoute = Ember.Route.extend({
  controllerName: 'thing',
  renderTemplate: function(controller, model){
    // You have to pass the controller to render or it will generate a new controller
    this.render({ controller: controller, into: 'application', outlet: 'modal' });
  }
});
    

Looks easy but couldn't find the docs

Pass a Value to a View

Add a property to your view:


App.ModalView = Ember.View.extend({
  aClassName: 'modal'
});
    

Use the value in your view template:


    {% raw %}
<div {{bindAttr class="view.aClassName"}}>
  More stuff here
</div>
    {% endraw %}
    

When you use the view just set the property:


    {% raw %}
{{#view App.ModalView aClassName="wide_modal"}}
  Some great content goes here
{{/view}}
    {% endraw %}
    

Immutable Arrays

Adding parameters to 'find' results in an immutable model


App.ApplicationRoute = Ember.Route.extend({
  model: function(){
    return this.store.find('course', {user_id: user_id});
  }
});
      

Use an ArrayProxy to builid a collection that can be modified


App.ApplicationRoute = Ember.Route.extend({
  model: function(){
    var courses = Ember.ArrayProxy.create({content: []});
    this.store.find('courses', user_id: user_id}).then(function(data){
      data.forEach(function(course){
        if(!courses.contains(course)){
          courses.pushObject(course);
        }
      }.bind(this));
    }.bind(this));
    return courses;
  }
});
      

But then you have to manually keep the collection updated.

Even Better - Filters!


var filter = this.store.filter('course', function(course){
  return !course.get('user_id') == userId;
});
this.controllerFor('courses').set('model', filter);

// Load all courses into the store.
Ember.run.once(this, function(){
  this.store.find('course');
});
    

What about Ember Data?

Beta

Rapidly evolving

Not Production Ready*

* We use it in production anyway

More Ember Data

All pre-beta examples on the internet are now wrong

Let the Ember Data Transition Guide Take You to a Happy Place

1.0 Beta changed to reduce dependance on global application object

Ember Data 0.13


App.Post.find();
App.Post.find(params.post_id);
        

Ember Data 1.0.beta.1:


this.store.find('post');
this.store.find('post', params.post_id);
        

Ember Data 0.13


App.Post.createRecord();
        

Ember Data 1.0.beta.1:


this.store.createRecord('post');
        

I already have Rails models

Generate Ember.js Models Using Your Rails Schema*


namespace :ember do

  desc "Build ember models from schema"
  task :models => :environment do

    # Change these values to fit your project
    namespace = 'App' # The Ember application's namespace.

    # The directory where ember models will be written. We drop them
    # in the tmp directory since we might not want an ember model for every table in the
    # database.
    output_dir = File.join(Rails.root, "tmp/ember_models")

    schema_file = File.join(Rails.root, 'db/schema.rb')

    current = ''
    file = ''
    max = 0
    attrs = []

    File.readlines(schema_file).each do |line|

      # Stuff to ignore
      next if line.strip.blank?
      next if /#.*/.match(line)
      next if /add_index.+/.match(line)
      next if /ActiveRecord::Schema.define/.match(line)

      # Find tables in the schema
      if m = /create_table \"(.+)\".*/.match(line)
        current = "#{namespace}.#{m.captures[0].classify.singularize} = DS.Model.extend({\n"
        file = "#{m.captures[0].singularize}.js"
      elsif m = /t\.(.+)\s+"([0-9a-zA-z_]+)".*/.match(line)
        max = m.captures[1].length if m.captures[1].length > max
        attrs << m.captures
      elsif m = /end/.match(line) && current.present?
        attrs.each_with_index do |attr, i|
          spaces = ''
          type = 'string'
          if %w(integer float).include?(attr[0])
            type = 'number'
          elsif %w(datetime time date).include?(attr[0])
            type = 'date'
          elsif %w(boolean).include?(attr[0])
            type = 'boolean'
          end
          comma = ','
          if attrs.size-1 == i
            comma=''
          end
          ((max + 1) - attr[1].length).times{spaces << ' '}
          if attr[1].ends_with?('_id')
            relation = attr[1][0...(attr[1].length-3)]
            current << "  #{relation}:    #{spaces}DS.belongsTo('#{relation.camelize(:lower).singularize}'),\n"
          end
          current << "  #{attr[1]}: #{spaces}DS.attr('#{type}')#{comma}\n"
        end
        current << "});\n"
        f = File.join(output_dir, file)
        if File.exists?(f)
          puts "Ember model already exists: #{f}"
        else
          current.gsub!('_spaces_', '')
          puts "Writing Ember model: #{f}"
          File.open(f, 'w'){|f| f.write(current)}
        end

        current = ''
        file = ''
        max = 0
        attrs = []

      else
        if /end/.match(line).blank?
          puts "Don't know how to handle: #{line}"
        end
      end

    end

  end

end
    

*It's not my fault if this code nukes your site or ruins your relationships.

</section>

Ember is Full of Promises

Think Asyncronous

Make a Promise

Docs Show This


return new Promise(function(resolve, reject){
}.bind(this));
      

But Do this


return new Ember.RSVP.Promise(function(resolve, reject){
}.bind(this));
      

Original

Requirement: wait until the entire tree is loaded before transition.


App.GoogleFile = Ember.Object.extend({
});

App.ApplicationRoute = Ember.Route.extend({

  model: function(){
    var model = App.GoogleFile.create({
      id: $('meta[name="google-folder-id"]').attr('content');
      children: Ember.ArrayProxy.create({content: []})
    });
    this.loadChildren(model.get('children'));
    return model;
  },

  afterModel: function(transition){
    if(transition.targetName == "application.index"){
      this.wait(model.get('children'), function(){
        this.transitionTo('anotherPlace');
      });
    }
  },

  // Waiting for content to load using a timer.
  wait: function(children, callback){
    if(Ember.isEmpty(children)){
      Ember.run.later(this, function () {
        this.wait(children);
      }, 10);
    } else {
      callback(children);
    }
  },

  loadChildren: function(node){
    var token = $('meta[name="google-access-token"]').attr('content');
    var query = encodeURIComponent('"' + node.get('id') + '" in parents');
    $.get('https://www.googleapis.com/drive/v2/files?q=' + query + '&access_token=' + token, function(data){
      data.items.forEach(function(item){
        var f = App.GoogleFile.create({
          name: item.title,
          id: item.id,
          icon: item.iconLink,
          mime: item.mimeType,
          embed: item.embedLink,
          edit: item.alternateLink,
          children: Ember.ArrayProxy.create({content: []})
        });
        if(item.mimeType === "application/vnd.google-apps.folder"){
          this.loadChildren(f);
        }
        node.get('children').pushObject(f);
      }.bind(this));
    }.bind(this));
  }

});
      

The Refactor



App = Ember.Application.create({
});

App.GoogleFile = Ember.Object.extend({
});

App.ApplicationRoute = Ember.Route.extend({

  model: function(){
    var model = App.GoogleFile.create({
      id: $('meta[name="google-folder-id"]').attr('content');,
      children: Ember.ArrayProxy.create({content: []})
    });

    return new Ember.RSVP.Promise(function(resolve, reject){
      this.loadChildren(model, resolve, reject);
    }.bind(this));
  },

  // The afterModel won't fire until the promise is fullfilled.
  afterModel: function(transition){
    if(transition.targetName == "application.index"){
      this.transitionTo('anotherPlace');
    }
  },

  loadChildren: function(node, resolve, reject){
  var token = $('meta[name="google-access-token"]').attr('content');
    var query = encodeURIComponent('"' + node.get('id') + '" in parents');
    // Don't resolve the promise when the ajax call returns. We have to process the data and decide if we need to make more calls.
    $.get('https://www.googleapis.com/drive/v2/files?q=' + query + '&access_token=' + token, function(data){
      var promises = [];
      data.items.forEach(function(item){
        var f = App.GoogleFile.create({
          name: item.title,
          id: item.id,
          icon: item.iconLink,
          mime: item.mimeType,
          embed: item.embedLink,
          edit: item.alternateLink,
          children: Ember.ArrayProxy.create({content: []})
        });

        if(item.mimeType === "application/vnd.google-apps.folder"){
          // We need to make more ajax calls. Create a new promise which can be resposible for
          // resolving existing promises once it is fullfilled.
          var promise = new Ember.RSVP.Promise(function(resolve, reject){
            this.loadChildren(f, resolve, reject);
          }.bind(this));
          promises.push(promise);
        }
        node.get('children').pushObject(f);
      }.bind(this));

      Promise.all(promises).then(function(){
        resolve(node);
      });

    }.bind(this));
  }

});

      

Working code snippet on jsbin

You're sitting around doing nothing And...


var Adapter = DS.RESTAdapter.extend({
  ajaxError: function(jqXHR){
    if(jqXHR.status == 401){
      window.location.href = '/users/sign_in?timeout=true';
    }
    if(jqXHR){
      jqXHR.then = null;
    }
    return jqXHR;
  }
});
      

You have to warn me about these things


<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <button  type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
        <h4 class="modal-title" id="tmModalLabel">Inactivity Warning</h4>
      </div>
      <div class="modal-body">
        You will be logged out in 30 seconds.
      </div>
    </div>
  </div>
</div>
      

Component!


App.InactivityWarningComponent = Ember.Component.extend({

  active: false,
  inactiveTimeout: 12000000, // Amount of time before we redirect to the sign in screen - the session should have expired by this point. (20 minutes)
  warningTimeout: 30000,     // Amount of time the user has to perform an action before the last keep alive fires - 30 seconds
  timeout: 1170000,          // 19.5 minutes. We want to be less than the 20 minute timeout to be sure the session is renewed.

  didInsertElement: function(){
    //if($('meta[name="in-development"]').attr('content')){ return; } // Uncomment and add a meta tag to your head if you want to avoid session timeout in development
    var context = this;

    var keepActive = function(){
      if(context.active){
        // Keep the session alive
        $.ajax({
          url: "/stayin_alive"
        }).done(function(result){

          // Go inactive until the user moves the mouse or presses a key
          context.active = false;

          // The user now has another 20 minutes before the session times out
          // Restart the timer to keep the user logged in
          Ember.run.later(context, keepActive, context.timeout);

          // Set a timer to show a modal indicating the user is about to be logged out.
          Ember.run.debounce(context, context.show, context.timeout - context.warningTimeout);

          // Set a timer that will send the user to the login screen
          Ember.run.debounce(context, context.forceLogin, context.inactiveTimeout);
        });
      }
    };

    $(window).mousemove(function(e){
      context.active = true;
      // Make sure the modal is hidden. This will cause the modal to hide if the user moves the mouse or presses a key.
      // Use debounce so we don't call it over and over again since this method is called from mousemove
      Ember.run.debounce(context, context.hide, 1000);
    });

    $(window).keypress(function(e){
      context.active = true;
      // Make sure the modal is hidden. This will cause the modal to hide if the user moves the mouse or presses a key.
      context.hide();
    });

    // The user has 5 minutes before they are logged out. We need to send a keep Active before then.
    Ember.run.later(context, keepActive, context.timeout);

  },

  forceLogin: function(){
    window.location.href = '/users/sign_out?timeout=true';
  },

  show: function(){
    // Warn the user that they will be logged out if we are inactive
    if(this.active === false){
      // Start countdown timer
      this.$('.modal').modal('show');
    }
  },

  hide: function(){
    this.$('.modal').modal('hide');
  }

});
      

Ember.js Session Timeout In Depth

Monitor key events in your textfield

Monitor changes as the user types


      {% raw %}
{{ view Ember.Textfield
  class="form-control"
  placeholderBinding="controller.prompt"
  valueBinding="controller.value"
  onEvent="keyPress"
  action="typing"
}}
      {% endraw %}
      

but keyPress doesn't fire when you press the arrow keys

No problem! Make your own Textfield


App.Textfield = Ember.TextField.extend({

  init: function() {
    this._super();
    this.on("keyUp", this, this.interpretKeyEvents);
  },

  interpretKeyEvents: function(event){
    var map = TM.Textfield .KEY_EVENTS;
    var method = map[event.keyCode];
    if (method){
      return this[method](event);
    } else {
      this._super(event);
    }
  },

  arrowUp: function(event){
    this.sendAction('arrow-up', this, event);
  },

  arrowDown: function(event){
    this.sendAction('arrow-down', this, event);
  }

});

App.Textfield.KEY_EVENTS = {
  38: 'arrowUp',
  40: 'arrowDown'
};
      

Use that new code


      {% raw %}
{{ view App.Textfield
    class="form-control"
    placeholderBinding="view.prompt"
    valueBinding="view.value"
    viewName="inputField"
    arrow-up="arrowUp"
    arrow-down="arrowDown"
}}
      {% endraw %}
      

Add 'arrowUp' and 'arrowDown' to your controller and be filled with joy.

THE END

Justin Ball

justinball.com

[email protected]

@jbasdf

Download PDF

</div>

Leave a Reply Cancel reply

You must be logged in to post a comment.

Recent Posts

  • Around and Back to WordPress
  • Last Lagoon (This Year)
  • Logan Sunset
  • Grami Del
  • FanX (and Lagoon)

Recent Comments

  1. jquery ajax readystate 0 responsetext status 0 statustext error – w3toppers.com on jqXHR Returning Readystate 0 and Status 0?
  2. Change MySQL default character set to UTF-8 in my.cnf? on Upgrade to MySQL 5.5.12 and now MySQL won’t start
  3. Around and Back to WordPress – Justin Ball on Gatsby 2.0 and Forestry
  4. More Stuff You Shouldn’t Hit on a Bike – Justin Ball on Why Cyclists Shave Their Legs. The Most Disgusting Post I Will Ever Make
  5. First Real Ride on the New Trek Madone 6.9 – Justin Ball on Rode Blacksmith Fork Canyon Tonight

Archives

  • November 2021
  • October 2021
  • September 2021
  • January 2020
  • February 2018
  • January 2018
  • December 2017
  • November 2017
  • October 2017
  • February 2017
  • November 2016
  • September 2016
  • August 2016
  • May 2016
  • March 2016
  • February 2016
  • November 2015
  • September 2015
  • June 2015
  • May 2015
  • February 2015
  • January 2015
  • October 2014
  • September 2014
  • July 2014
  • June 2014
  • May 2014
  • April 2014
  • March 2014
  • February 2014
  • January 2014
  • December 2013
  • October 2013
  • September 2013
  • August 2013
  • June 2013
  • May 2013
  • April 2013
  • February 2013
  • January 2013
  • December 2012
  • October 2012
  • September 2012
  • June 2012
  • January 2012
  • December 2011
  • September 2011
  • August 2011
  • July 2011
  • June 2011
  • May 2011
  • March 2011
  • February 2011
  • January 2011
  • December 2010
  • November 2010
  • September 2010
  • August 2010
  • July 2010
  • June 2010
  • May 2010
  • March 2010
  • February 2010
  • January 2010
  • December 2009
  • November 2009
  • October 2009
  • September 2009
  • August 2009
  • July 2009
  • June 2009
  • May 2009
  • April 2009
  • March 2009
  • February 2009
  • January 2009
  • December 2008
  • November 2008
  • October 2008
  • September 2008
  • August 2008
  • July 2008
  • June 2008
  • May 2008
  • April 2008
  • March 2008
  • February 2008
  • January 2008
  • December 2007
  • November 2007
  • October 2007
  • September 2007
  • August 2007
  • July 2007
  • June 2007
  • May 2007
  • March 2007
  • February 2007
  • January 2007
  • December 2006
  • November 2006
  • October 2006
  • September 2006
  • August 2006
  • July 2006
  • June 2006
  • May 2006
  • April 2006
  • March 2006
  • December 2005
  • November 2005
  • October 2005
  • September 2005

Categories

  • 2.3.2
  • 3g
  • 3tera
  • 420
  • 51weeks
  • 64bit
  • accessibility
  • ActionView::MissingTemplate
  • activemerchant
  • ActiveRecord
  • activesalesforce
  • acts as taggable
  • acts_as_facebook_user
  • acts_as_nested_set
  • acts_as_state_machine
  • advertising
  • Affiliate Marketing
  • air quality
  • ajax
  • Alyssa
  • ama
  • amazon
  • amazon s3
  • amazon wishlist
  • amazon.com
  • ancestry
  • animal cookies
  • antshares
  • apache
  • API
  • apis
  • apollo
  • apollo client
  • apple
  • Apple Store
  • Apple Time Capsule
  • application
  • applications
  • Art
  • ASP.Net
  • assert_sent_email
  • asyncronous processing
  • Atomic Jolt
  • Aubrey
  • Authentication
  • authorize.net
  • Autumn
  • babelphish
  • back problems
  • backbone.js
  • backup software
  • backups
  • bacon
  • Battlestar Galactica
  • big companies
  • birthday.
  • bitcoin
  • black cherry vanilla coke
  • Black Smith Fork Canyon
  • blockchain
  • blog
  • Blogging
  • bluehost
  • books
  • BoomStartup
  • bread
  • buddypress
  • bug
  • bugs
  • business
  • business. mother's animal cookies
  • cache county
  • cache valley
  • California
  • Cancun
  • canvas
  • capistrano
  • Catholic Church
  • cereal
  • chauvet obey 40
  • checkbox list
  • checkboxes
  • chess
  • Chicago
  • china
  • chocolate
  • Christmas
  • Chrome
  • church
  • Cinderella
  • Cisco
  • cloud computing
  • cms
  • code generation
  • code sprint
  • coke
  • Comcast
  • commerce
  • Common Lisp
  • communities
  • Community
  • complex
  • Computers
  • conference
  • conference software
  • configuration
  • consulting
  • cookies
  • cooking
  • COSL
  • cosmos
  • count
  • courts
  • cows
  • create
  • creative commons
  • cryptocurrencies
  • cryptography
  • css animations
  • cucumber
  • currency
  • Cycling
  • database
  • dataloader
  • date
  • death
  • death ray
  • debugging
  • decentralized applications
  • dell dimension 8400
  • democray
  • deployment
  • developing
  • development
  • Devin
  • diet
  • digg
  • Digital Ocean
  • digital-photography
  • disease
  • disguise
  • disgusting
  • disney
  • disneyland
  • DiSo
  • disposable
  • DMX
  • Docker
  • domain name
  • domains
  • doom
  • dr strangelove
  • driving
  • Dryers
  • DVI
  • ec2
  • economics
  • economy
  • ecto
  • edge rails
  • Education
  • EFF
  • Egypt
  • ElasticSearch
  • elastra
  • elections
  • elixir
  • email
  • Ember
  • Ember.js
  • encoding
  • energy
  • engine yard
  • engines testing
  • engineyard
  • enterprise
  • epp
  • error
  • errors
  • ethereum
  • Event Machine
  • expercom
  • facebook
  • failure
  • Family
  • family history
  • family reunion
  • family search
  • family trip
  • Family Vacation
  • familysearch
  • familysearch.org
  • farmers market
  • fashion
  • fences
  • field trip
  • file uploads
  • Firebase
  • fireeagle
  • fix
  • flat tax
  • flowers
  • folksonomy
  • food
  • France iPad Internet access
  • free book
  • freedom
  • friendfeed
  • friends
  • fuel
  • Fun Stuff
  • funeral
  • Funny
  • funny kids
  • gadgets
  • galleries
  • gamenight
  • garden
  • gardens
  • garter snake
  • gatsby
  • gatsbyjs
  • gearsynper
  • geek
  • gelatin
  • gem
  • gems
  • gems ruby on rails
  • genealogy
  • genius
  • geocaching
  • geotagging
  • girl's camp
  • gistr
  • git
  • github
  • global
  • gmail
  • godaddy
  • Goliath
  • Google
  • google bomb
  • google docs
  • google hacks
  • Gorden B Hinckley
  • government
  • gps
  • grand master
  • grand-teton-national-park
  • graph ql
  • graphcool
  • graphql
  • graphqlsummit
  • great firewall
  • grocery
  • gross
  • group work
  • HABTM
  • Hacks
  • halloween
  • happy
  • has and belongs to many
  • has_many
  • hashgraph
  • Hawaii
  • health
  • health insurance
  • heirachy
  • Heirarchies
  • helps
  • Heroku
  • Holiday
  • home building
  • home improvement
  • home plans
  • homebrew
  • homework
  • hosting
  • house plans
  • House Stuff
  • housing
  • human rights
  • hyperledger
  • i18n
  • ice cream
  • icls2008
  • idaho
  • ideas
  • identity
  • identity_theft
  • iiw2006b
  • image
  • image processing
  • inbox
  • induglences
  • insane
  • inspiration
  • install
  • Instructure
  • Interesting
  • internet
  • Internet Explorer
  • InvalidAuthenticityToken
  • iPhone
  • jackson-hole
  • jamis buck
  • Javascript
  • JavaScript (Programming Language)
  • Javscript
  • Jenna
  • jeweler
  • jobs
  • joyent
  • jQuery
  • jungle disk
  • jurlp
  • justin ball
  • kids
  • knowledge workers
  • lambad
  • laptop case
  • launchup.org
  • lds
  • LDS church
  • learning
  • legal
  • Lego
  • legos
  • leopard
  • lesson
  • Levi Leipheimer
  • Liahona
  • library
  • life
  • lifestream
  • Links
  • litecoin
  • LMS
  • loans
  • localization
  • logan
  • Logan Canyon
  • logistics
  • logitech
  • LTI
  • lucene
  • lucene.net
  • Lucifer
  • luvfoo
  • mac
  • Mac OSX 10.6
  • Mac Ports
  • macbook
  • macbook pro
  • Maker
  • Maker Faire
  • manage
  • marginal changes
  • marion
  • marriage
  • Matt Mullenweg
  • me
  • medicine
  • Meetings
  • merb
  • Mexico
  • micro-blogging
  • microcontent
  • microformats
  • Microsoft
  • Middle East
  • migrations
  • mom
  • money
  • Monitor
  • morph
  • morph exchange
  • morphexchange
  • mortgage
  • mosso
  • motorcycle
  • mountain biking
  • Mountain West Javascript
  • Mountain West Ruby
  • mountain west ruby conference
  • mountainwestrubyconf
  • mozy
  • MRI
  • mtnwestrubyconf
  • muck
  • multi-user
  • music
  • mwjs
  • mwrc
  • mysql
  • mysql gem
  • MYTecC
  • Neat Stuff
  • neighbors
  • newgem
  • No Programming
  • node.js
  • nuclear weapons
  • nutcracker
  • Oahu
  • Oauth
  • oauth-plugin
  • Obama
  • Obie Fernandez
  • OER
  • OER Glue
  • olympic torch
  • olympics
  • omniauth
  • Open Assessments
  • open source
  • OpenContent
  • opened2007
  • OpenID
  • opensocial
  • optimism
  • ordered tree
  • oreos
  • osx
  • outdoors
  • outsourcing
  • ozmozr
  • pain
  • panasonic plasma
  • Paris
  • password recovery
  • payday lenders
  • paypal
  • pety
  • PGP
  • Phil Windley
  • photography
  • photoJAR
  • photos
  • php
  • pickle soup
  • pickup
  • piclens
  • Pictures
  • plasma tv
  • Playa Del Carmen
  • plugin
  • plugins
  • poinsettia
  • Political
  • politics
  • portablecontacts
  • PostGreSQL
  • PostGresSQL
  • poverty
  • privacy
  • problems
  • product: web
  • professional
  • Programming
  • Projects
  • prophet
  • protect_from_forgery
  • protests
  • prototype
  • psych
  • psychology
  • queue
  • rails
  • rails 2.0
  • rails conference
  • Rails I18n Textmate bundle
  • RailsConf
  • RailsConf07
  • rake
  • rant
  • react
  • react router
  • React.js
  • Reactive
  • reactjs
  • reactrouter
  • realestate
  • recipe
  • recommender
  • records
  • red green
  • redirect_to
  • regular expressions
  • relay
  • religion
  • render
  • replace
  • reputation
  • require.js
  • research
  • REST
  • restaurant
  • rFacebook
  • ridiculous
  • rightscale
  • ringside networks
  • river
  • river trail
  • robots
  • romantic
  • roomba
  • rpsec
  • rspec
  • rspec bundle
  • rss
  • ruby
  • Ruby On Rails
  • Ruby On Railst
  • ruby_on_rails
  • rvm
  • s3
  • sad
  • Salesforce
  • samsung ml1740
  • sarah sample
  • scalability
  • School
  • Science
  • scorm
  • scream
  • script.aculo.us
  • SDK
  • search
  • senate
  • SEO
  • serverless
  • servers
  • sessions
  • shopping
  • shortcodes
  • shoulda
  • sign language
  • simple
  • small business
  • snakes
  • Snelgrove
  • social graph
  • social media
  • social network dilution
  • social networking
  • social search
  • Social Software
  • socialsoftware
  • society2.0
  • soda
  • software
  • software design
  • Software Development
  • solidity
  • solo
  • soviet union
  • sovrin
  • sql
  • sql server
  • SQL Server 2005 Express
  • sql server 2008 express
  • starling
  • start ups
  • startups
  • starvation
  • stm bags
  • stm medium alley
  • storage
  • subversion
  • target
  • tax
  • Teachers Without Borders
  • tech
  • teeth whitening
  • template not foudn
  • templates
  • test-spec
  • testing
  • tests
  • textmate
  • thanksgiving point
  • The Japanese Mafia is controlling the weather
  • The Kids
  • The Plan Collection
  • The Web
  • theming skin
  • theplancollection
  • theplancollection.com
  • time
  • timr
  • tips
  • to_json
  • tools
  • Tour de France
  • transfer
  • translations
  • Travel
  • Travel, Disneyland, LA
  • trees
  • trip
  • truffles
  • tutorial
  • tutorials
  • tv
  • twitter
  • Uncategorized
  • uninsured
  • universe
  • unpack
  • unread
  • upgrades
  • uploader
  • uploads
  • user discovery
  • user interface
  • userfly
  • utah
  • utah government
  • utah senate
  • utf8
  • Vacation
  • values
  • vinegar
  • virtual hosts
  • walmart
  • warranty
  • Waste of Time
  • weather
  • Web
  • web design
  • web development
  • Web RTC
  • Web2.0
  • web2con2006
  • webservices
  • weddings
  • Wesley Connell
  • whereigo
  • wife
  • windows
  • Wired
  • wishlist
  • with
  • word press
  • Wordpress
  • work
  • workling
  • wpmu
  • xml
  • yeast
  • yellowstone
  • zentest
©2025 Justin Ball | Powered by SuperbThemes & WordPress