Highlight Search Term in Ember

A few months ago I built contacts search into morse.io, which is an Ember project.

Given a contact search was matched by either first name, last name or email address, I wanted to highlight the matched term (e.g. the reason a given contact was returned).

Thus, here is a simple way to highlight matched search terms in Ember.

1: Perform search, pass searchQuery to each result

Naturally, you first need to perform the search query. No changes to how you would normally query:

1
2
3
  store.find('contact', {search: query}).then(function(contacts) {
    //do something
  });

When rendering each result, pass the searchQuery to each result/component. In the context of morse.io, it looks a little like this:

{{contact-card
    action="selectContact"
    contact=contact
    query=searchQuery}}

2: Register a helper to highlight the result

I kept this simple, wrap each matched term in a highight span.

1
2
3
4
5
6
7
8
import Ember from "ember";

export default Ember.Handlebars.makeBoundHelper(function(value, options) {
    var query = options.hash.query;
    var regex = new RegExp(query, "gi");
    var formattedTag = value.replace(regex, "<span class='highlight'>$&</span>");
    return new Handlebars.SafeString(formattedTag);
});

3: Glue time

There is only one functional component left. Instead of placing contact.name and contact.email in the component, let the helper wrap matching query terms in highlight spans.

{{highlight-term contact.name query=query}}

4: Style at will

I like to shade the matching terms with a light yellow:

1
2
3
.highlighted {
  background-color: #F8F2C0;
}

Rapid Prototyping (/w Ember) Presentation

I was at FITC April 12-14 hanging out and doing a few presentations. On 12th I did a panel on web components, and on the 13th spoke at length on how I use Ember to build rapid prototypes.

The Rapid Prototyping video is now live. Take a peek, please forgive the sound:

Infinite Scroll With Ember ListView

Ember ListView is really great. It provides incremental rendering for large lists, and is a little like Collection Views in iOS. Only on-screen objects are reflected in the DOM, vs. each having their own node, and when users scroll the on-screen nodes are updated. You should be using ListView for any large index views.

Out of the box ListView does not support infinite scrolling, and I needed a quick solution for a prototype.

Enter my slightly hacky but workable solution. I’m assuming you know how to subclass ListView, which is required for any modifications. See the docs if you don’t.

Within the view, insert the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default Ember.VirtualListView.extend({
  //Lazy load more models when less than _refreshAt remain
  //When only _threshold models left, get more data
  _threshold: 10,

  _infiniteScroll: function() {
    var lastIndex      = this.get('_lastEndingIndex'),
        refreshAt      = this.get('_threshold'),
        contentLength  = this.get('content.length');

    if (contentLength - lastIndex < refreshAt) {
      //Get more items, add a 'getting more' wheel if you like
      this.get('controller').send('getMore');
    }
  }.on('scrollYChanged'),
});

ListView exposes two important pieces us:

1) it exposes a scrollYChanged event when a user scrolls; and

2) _lastEndingIndex, while private, updates with the last model accessed. For example, if the 8th model in an array of 10 is being rendered on-screen, _lastEndingIndex would be 8.

The solution is pretty simple. Pick a reload threshold (e.g. at only 5 models left, go and get more) and check on scrollY events.

Creating an Ember Data Adapter for Firefox Extensions

Ember.js works pretty great for browser extensions. Ember Data Adapters (notably the RESTAdapter and ActiveModelAdapter) rely on AJAX requests, which are not allowed in Firefox extensions / content scripts. Instead, one must use the Request API.

Once you have a handle on the Request API and Ember Promises this is not too prohibitive. You should also note that ‘self’ is reserved in Firefox extensions, and provides access to the add on environment.

In production, I tend to have a Browser JS Object (with type “chrome” or “firefox”) so my Ember code requires no modifications for either environment.

First, you must handle HTTP Requests within your local module (probably main.js), not much different to how we normally would in a Firefox addon.

This implementation:

  • attaches a content script;
  • listens for a ‘http-req’ message from our content script;
  • makes a HTTP Request via the Request module; and
  • on success/failure, emits the response data over our shared port (see the port documentation)), with the original url for identification.

Firefox Addon: main.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
var pageMod = require("sdk/page-mod");
var Request = require("sdk/request").Request;

pageMod.PageMod({
  onAttach: function(port) {
    portListeners(port);
  }
});

function portListeners(port) {
  port.port.on("http-req", function(options) {
    var req = Request({
      url: options.url,
      content: options.data,
      onComplete: function (response) {
        port.port.emit(options.url, response.status, response.json);
      }
    });

    switch (options.type) {
      case "GET":
        req.get();
      case "PUT":
        req.put();
      case "POST":
        req.post();
      case "DELETE":
        req.delete();
    }
  });
}

In our Ember adapter, all we need to do is override the ajax function to return an Ember Promise. The Ember promise:

  • emits a ‘http-req’ message; and
  • listens for response data (a message over the same port, identified by the original url).

Ember: adapters/firefox-extension-adapter.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
import Ember from "ember";
import DS from 'ember-data';

export default DS.RESTAdapter.extend({
  ajax: function(url, type, hash) {
    var browser, adapter;
    adapter = this;

    return new Ember.RSVP.Promise(function(resolve, reject) {
      var options = {};
      options.url = url;
      options.type = type;
      options.data = hash.data;

      //self is reserved in firefox ext, refers to the addon
      //see https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/self
      self.port.on(url, function(status, json) {
        if (status === 200) {
          Ember.run(null, resolve, json);
        } else {
          Ember.run(null, reject, adapter.firefoxError(status));
        }
      });

      self.port.emit("http-req", options);
    });
  },

  firefoxError: function(error) {
    return {status: status};
  }
});

Using Ember.js With a Rails 4 or Sinatra Backend / Api

I’ve been using Ember.js for nearly a year now. Things have changed a lot for the better. One of the hardest bits about learning Ember is realizing most guides are out-dated (the framework evolves quickly) and may no longer recommended best practice. An example would be any guides which use views (hint: components instead) – and there are probaby some of this very blog with views.

One learning curve was making Ember Data work with my Rails 4 api. If you are a Sinatra / ActiveModel lover, you can probably follow the same steps.

A high level of overview:

1) Set Ember adapter to DS.ActiveModelAdapter;
2) Install ActiveModel::Serializers & setup routes;
3) Setup Serializers; and
4) Handle controller actions.


1: Set Ember adapter to DS.ActiveModelAdapter

The first and only Ember step is changing your adapters to DS.ActiveModelAdapter, vs DS.RestAdapter or whichever you are using.

From the Ember docs:

The ActiveModelAdapter is a subclass of the RESTAdapter designed to integrate with a JSON API that uses an underscored naming convention instead of camelCasing.

tldr; Use ActiveModelAdapter for Rails backends, and you won’t have to do much work at all!

Adapter Tips:

  • Namespacing your API (i.e. v1) will save you headaches later;
  • If Ember & Rails model names differ, consider the pathForType method.

2: Install ActiveModel::Serializers & setup routes

From the docs:

ActiveModel::Serializers brings convention over configuration to your JSON generation. AMS does this through two components: serializers and adapters. Serializers describe which attributes and relationships should be serialized. Adapters describe how attributes and relationships should be serialized.

In short, we will use ActiveModel::Serializers to avoid manual hash mangling. That’s never a good idea.

Add the following to your Gemfile and run bundle install:

1
gem "active_model_serializers"

Setup Routes:

Ember expects your API to follow restful conventions. You are a good developer, and see little need to deviate from this pattern.

To config/routes.rb, add:

1
resources :model_name

3: Setup Serializers

A mostly correct ActiveModel::Serializer tldr is they define how a model will be reflected as JSON.

All of your serializers should be placed in /app/serializers/model_name.rb.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app/serializers/my_model_serializer.rb

class MyModelSerializer < ActiveModel::Serializer
  embed :ids, include: true

  attributes :id,
             :user_id,
             :name

  def name
    return object.title
  end

end

Serializer Tips:

  • Within a serializer, access your model as “object” i.e. “object.name”;
  • Attributes define keys which will be reflected in JSON; and
  • You can define methods as attriutes to return custom or sanitized values.

4: Handle Controller Actions

This is where the magic happens. An example controller (intentionally long hand for learners):

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
class ModelController < ApplicationController

  def index
    models = current_user.models

    render json: models, each_serializer: ModelSerializer
  end

  def create
    model = current_user.models.create(model_params)

    if model.valid?
      render json: model, serialzier: ModelSerializer
    else
      render status: 500, nothing: true
    end
  end

  def update
    model = ClientToken.find(params[:id])

    if model.update_attributes(model_params)
      render json: model

      #You can specify add'l keys i.e.
      #render json: model,
      #       serializer: DifferentSerializer, 
      #       root: 'different_model'
    else
      render status: 500, nothing: true
    end
  end

  def destroy
    model = Model.find(params[:id])

    if model.destroy
      render status: 200, nothing: true
    else
      render status: 500, nothing: true
    end
  end

  private
    def model_params
      params.require(:model).permit(:params)
    end
end

Controller Tips:

  • When rendering json, set ‘root’ to change the root key name;
  • Rendering an object in a 200 response (i.e. after save) means any properties updated on the backend will also be reflected in Ember.

That is all

With not a lot of work, you have a pretty speedy implementation which persists your data, is easy to test, and lets you focus on more important things.

Ember is fun.

Bitcoin Thoughts

Disclaimer: These are thoughts only. I claim no qualifications.

I really like Bitcoin. I find it interesting intellectually and going long, I think it’s ++. I’ve been watching it for a few years, and even own some coin for experimental purposes. I’d like to see Bitcoin succeed.

But there is a short term thought I struggle with. Many positive news cycles involve new vendors (recently Microsoft) taking the coin, with the thought being increases in transaction volume will increase the market price of Bitcoins (more people using it == higher price).

Yet most holders are seemingly speculators, expecting the value to go up in the most obnoxious of ways. Why would I spend an asset I expect to 10x in the coming years in lieu of cash, which probably won’t 10x? Not much.

Most of the times I went to Bitcoin events (admittedly not recently) I had a similar feeling. Mostly people who could explain what a Blockchain was, but in no technical detail. They could explain how transactions work, but with no real depth.

Bitcoin is a very young piece of technology, and there is an over-exposure of fake technologists. They just want to ride the rocket ship.

Long, I think Bitcoin is ++. An ongoing crash will mean more developers may purchase, who have incentive to improve the technology. When developers don’t own Bitcoins they will probably build less Bitcoin things.

Disable Invasive Safari Popups

I finally updated to Yosemite. One of the more annoying features is a persistent notification to ‘Try Safari’ which can not be dismissed, the only options being to Try Safari or receive the notification later. This notification generally appears when opening Chrome or Firefox when Safari is not the default browser.

I could not disable this advertisement in the Notifications Center.

I don’t want to try Safari. I like using Chrome, and sometimes Firefox. I also build a product where most of the traction is a Chrome extension (morse.io), which is a pretty good reason to continue not using Safari.

It turns out I’m not alone. Thankfully Aeyoun already has a solution on StackOverflow here and his blog, which involves running the following three lines on terminal:

1
2
3
defaults write com.apple.coreservices.uiagent CSUIHasSafariBeenLaunched -bool YES
defaults write com.apple.coreservices.uiagent CSUIRecommendSafariNextNotificationDate -date 2050-01-01T00:00:00Z
defaults write com.apple.coreservices.uiagent CSUILastOSVersionWhereSafariRecommendationWasMade -float 10.99

I’ll skip over the implications of invasive ad notificatons which can’t be disabled without programming knowledge.

Not cool, Apple. Really not cool.

Ports 21, 554 & 7070 Open?

This weekend, I was deploying a new server (thanks Mina). Once deployed, I confirmed that iptables only allowed 21/80/443 traffic, and confirmed:

1
netstat -ntlp | grep LISTEN

Sweet! Everything worked. A remote nmap -Pn $serverIp confirmed we were all good.

A few hours and some tinkering later (it was a long weekend, after all), I re-ran nmap from my home network. Imagine my surprise to see ports 21, 554 & 7070 were open! iptables -L confirmed that my default policy was DROP, and I confirmed there were no services running on these ports.

Coronary time.

I tethered and re-ran nmap. The ports now showed as closed. Infact, they only showed as open when running the test from my home network, which runs an Apple Airport router.

After googling and using Web Wayback Machine to read (now removed!) Apple support discussions, it turns out Apple Routers try and help. They help you by not even checking to see if a connection can even be obtained. Instead, the connection is reported as granted, and any follow up requests are passed along.

I managed to confirm the ports were actually closed, contary to the ‘help’ offered. If you are wondering why ports 21, 554 & 7070 are sometimes open, hopefully this post sets you at ease.

Drag & Drop Sorting With Ember.js & jQuery Sortable

Turns out implementing jQuery sortable into an Ember.js project (which persists sort changes) is easy.

View: Enable jQuery sortable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
App.IndexView = Ember.View.extend({
  didInsertElement: function() {
    var controller = this.get('controller');
    this.$(".sortable").sortable({
      update: function(event, ui) {
        //Cancel default action
        $(this).sortable('cancel');

        //Collect new placements
        var indexes = {};
        $(this).find('.sorted-item').each(function(index) {
          indexes[$(this).data('id')] =index;
        });
        controller.updateSort(indexes);
      }
    });
  }
});

First, we use the didInsertElement event (called when the view has been inserted into the DOM (link)), to bind sortable.

On sortable update, we want to list all .sortable-item’s and find their new position. Pending on your App structure, this may be a DOM iterator or something much cleaner.

Now, all we need to do is persist the sorted changes.

Controller: Persist sort changes

1
2
3
4
5
6
7
8
9
10
11
App.IndexController = Ember.ArrayController.extend({
  updateSort: function(indexes) {
    this.beginPropertyChanges();
      this.forEach(function(model) {
        var pos = indexes[model.get('id')];
        model.set('position', pos);
        model.save();
      }, this);
    this.endPropertyChanges();
  }
});

Very simple (again, your implementation will probably be tigher when no written for a blog). Wrapping multiple changes in beginPropertyChange and endPropertyChanges simply defers notifications until all changes are finished (link).

All we need to do is iterate each model in the ArrayController, and update the position property based on the new indexes. In this case, that property is titled ‘position’.

Go Websockets With Nginx Web Server

I just pushed a gist of my test nginx/Go websockets config.