Skip to content

Instantly share code, notes, and snippets.

@evilmarty
Last active December 14, 2015 13:39
Show Gist options
  • Save evilmarty/5094991 to your computer and use it in GitHub Desktop.
Save evilmarty/5094991 to your computer and use it in GitHub Desktop.
Ember-backed autocomplete

In my journey in figuring out the Ember pattern, this is my attempt at trying to create an Ember-only autocomplete field. There were a few outcomes I wanted out of this, a part from being the Ember-way:

  • Work with any data source
  • Easily templatable results
  • Use only Ember constructs

All are welcome to use this, I'm just after feedback at this point.

Zalevent.Autocomplete = Ember.ContainerView.extend({
content: null,
value: null,
valuePath: '',
selected: null,
isDropdownVisible: false,
template: Ember.Handlebars.compile('{{view.content}}'),
classNames: 'autocomplete',
childViews: ['inputView', 'dropdownView'],
emptyView: null,
inputView: Ember.TextField.extend({
value: function(key, value) {
var parentView = this.get('parentView'),
valuePath;
if (arguments.length === 2) {
return value;
} else {
valuePath = parentView.get('valuePath').replace(/^content\.?/, '');
if (valuePath) { valuePath = '.' + valuePath; }
return parentView.get('value' + valuePath);
}
}.property('parentView.value', 'parentView.valuePath'),
keyUp: function(e) {
var parentView = this.get('parentView');
// Only trigger search when it's not a special key. Having this
// triggered when value changes gives us false positives as to
// the user's true intensions.
if (!parentView.constructor.KEY_EVENTS[e.keyCode]) {
parentView.trigger('search', this.get('value'));
}
}
}),
dropdownView: Ember.CollectionView.extend({
classNames: 'dropdown',
tagName: 'ul',
contentBinding: 'parentView.content',
selectedBinding: 'parentView.selected',
templateBinding: 'parentView.template',
isVisibleBinding: 'parentView.isDropdownVisible',
emptyViewBinding: 'parentView.emptyView',
itemViewClass: Ember.View.extend({
tagName: 'li',
templateBinding: 'parentView.template',
classNameBindings: ['selected'],
selected: function() {
var content = this.get('content'),
value = this.get('parentView.selected');
return content === value;
}.property('parentView.selected'),
click: function() {
var parentView = this.get('parentView.parentView'),
content = this.get('content');
if (parentView) {
parentView.trigger('select', content);
}
}
})
}),
keyDown: function(e) {
var map = this.constructor.KEY_EVENTS,
method = map[e.keyCode];
if (method && Ember.typeOf(this[method]) === 'function') {
e.preventDefault();
this[method](e);
}
},
focusIn: function() {
this.show();
},
focusOut: function() {
setTimeout(Ember.$.proxy(this, 'hide'), 200);
},
select: function(value) {
this.set('value', value).hide();
},
search: function(term) {
var controller = this.get('controller');
if (term) {
controller.send('search', term, this);
}
},
confirm: function() {
var selected = this.get('selected');
this.select(selected);
},
clear: function() {
this.setProperties({
value: null,
selected: null
}).hide();
},
next: function() {
return this._move(+1, this.get('content.firstObject'));
},
prev: function() {
return this._move(-1, this.get('content.lastObject'));
},
show: function() {
this.set('isDropdownVisible', true);
return this;
},
hide: function() {
this.set('isDropdownVisible', false);
return this;
},
_move: function(dir, def) {
var selected = this.get('selected'),
content = this.get('content'),
index = content.indexOf(selected);
if (index !== -1) {
selected = content.objectAt(index + dir);
} else {
selected = def;
}
this.set('selected', selected).show();
return selected;
},
contentDidChange: function() {
this.show();
}.observes('content')
});
Zalevent.Autocomplete.KEY_EVENTS = {
38: 'prev',
40: 'next',
27: 'clear',
13: 'confirm'
};
AutocompleteController = Ember.Controller.extend({
search: function(term, context) {
var results = [
Ember.Object.create({name: 'Bison'}),
Ember.Object.create({name: 'Vega'}),
];
context.set('content', results);
}
});
{{#view Autocomplete valueBinding="person" controllerBinding="controllers.autocomplete"}}
{{content.name}}
{{/view}}
@mszoernyi
Copy link

where do you initiate the AutoCompleteController to pass it to your view?

@onamission
Copy link

@evilmarty, Thanks for this widget, but I can't seem to get it to work. Since I am new to Ember, I might need a little more hand-holding. When I copied and pasted exactly as is, I got an error: "Uncaught ReferenceError: Zalevent is not defined ". So, I changed the name of the object to what I am using for my application object [App] and now no matter what I do I get an error like this: "Uncaught Error: assertion failed: Unable to find view at path 'Autocomplete' "

Any help would be appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment