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:
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:
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:
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:
12345678910111213141516
exportdefaultEmber.VirtualListView.extend({//Lazy load more models when less than _refreshAt remain//When only _threshold models left, get more data_threshold:10,_infiniteScroll:function(){varlastIndex=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 likethis.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.
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.
importEmberfrom"ember";importDSfrom'ember-data';exportdefaultDS.RESTAdapter.extend({ajax:function(url,type,hash){varbrowser,adapter;adapter=this;returnnewEmber.RSVP.Promise(function(resolve,reject){varoptions={};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/selfself.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};}});
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.
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.
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.
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.
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.
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:
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.
Turns out implementing jQuery sortable into an Ember.js project (which
persists sort changes) is easy.
View: Enable jQuery sortable
123456789101112131415161718
App.IndexView=Ember.View.extend({didInsertElement:function(){varcontroller=this.get('controller');this.$(".sortable").sortable({update:function(event,ui){//Cancel default action$(this).sortable('cancel');//Collect new placementsvarindexes={};$(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.
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’.