Last active
January 2, 2016 19:29
-
-
Save Deraen/8350730 to your computer and use it in GitHub Desktop.
AngularJS Selectize directive, autocomplete. CHECK https://gist.github.com/Deraen/9811993 FOR BETTER VERSION!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
angular.module('frontendApp') | |
.directive('autocomplete', function($timeout, $compile) { | |
// No use using ng-model or anything as we are only | |
// interested in CHANGE EVENTS not about current value | |
// infact, <autocomplete ng-model="foo"> should work as autocomplete is replaced with input element | |
// => change-callback is optional | |
return { | |
restrict: 'E', | |
replace: true, | |
template: | |
'<div class="input-group">' + | |
' <span class="input-group-addon"><span class="glyphicon glyphicon-search"></span></span>' + | |
' <input/>' + | |
'</div>', | |
link: function(scope, el, attrs, ctrls, transcludeFn) { | |
var filter = scope.$eval(attrs.filter); | |
var selectedCb = scope.$eval(attrs.cb); | |
// attrs.load contains a function which should return a promise | |
// or a Array which contains the data for autocomplete | |
var load = scope.$eval(attrs.load); | |
var map = scope.$eval(attrs.map); | |
var loaded = false; | |
var opts = { | |
maxItems: 1, | |
valueField: '_id', | |
labelField: 'name', | |
sortField: 'name', | |
searchField: ['name'], | |
// Exec load even without query | |
preload: true, | |
load: function(q, cb) { | |
// We do all filtering on client side, no need to request data again | |
if (loaded) return; | |
if (_.isFunction(load)) { | |
load().success(function(data) { | |
if (_.isFunction(map)) { | |
data = _.map(data, map); | |
} | |
cb(data); | |
loaded = true; | |
}).error(function() { | |
cb(); | |
}); | |
} else if (_.isArray(load)) { | |
if (_.isFunction(map)) { | |
load = _.map(load, map); | |
} | |
cb(load); | |
} else { | |
cb(); | |
} | |
}, | |
onChange: function(val) { | |
if (_.isFunction(selectedCb)) { | |
selectedCb(val); | |
// Deselect | |
selectize.clear(); | |
// Unfocus field, because after clear dropdown is closed but if clicking | |
// again on field dropdown wont open as field is already active. | |
// This way user can click on field and dropdown will open. | |
selectize.blur(); | |
} | |
}, | |
render: {}, | |
}; | |
// Couldn't devise another way... | |
// $compile can't really be used as it creates A ELEMENT | |
// which will have data bound and real values inserted later | |
// and render functions have to return A STRING | |
var templates = { | |
skill: { | |
option: | |
'<div>' + | |
' <div class="image"><img src="/api/images/skill/<%= d._id %>" class="skill-logo-small"/></div>' + | |
' <span class="name"><%= d.name %></span>' + | |
' <span class="tags"><% _.each(d.tags, function(tag) { %><span class="tag"><%= tag.name %></span><% }); %></span>' + | |
'</div>', | |
}, | |
project: { | |
option: | |
'<div>' + | |
' <span class="name"><%= d.name %></span>' + | |
// Argh | |
'<% if (d.begin) { %><span class="d.begin"><%= moment(d.begin).format("DD.MM.YYYY") %></span> <% if (d.ended) { %> - <span class="ended"><%= moment(d.ended).format("DD.MM.YYYY") %><% } %></span><% } %>' + | |
'</div>', | |
}, | |
user: { | |
option: | |
'<div class="user">' + | |
' <span class="name"><%= d.name %></span>' + | |
' <span class="title"><%= d.title %></span>' + | |
'</div>', | |
}, | |
}; | |
if (templates[attrs.template] && templates[attrs.template].option) { | |
var option = _.template(templates[attrs.template].option); | |
opts.render.option = function(item) { | |
return option({d: item}); | |
}; | |
} | |
// Score func | |
var scores = { | |
skill: function(search) { | |
var score = this.getScoreFunction(search); | |
return function(item) { | |
var scoreTags = this.sifter.getScoreFunction(search, { | |
fields: 'name', | |
conjunction: this.settings.searchConjunction, | |
}); | |
var tagsSum = _.reduce(item.tags, function(sum, tag) { | |
return sum + scoreTags(tag); | |
}, 0); | |
return 10 * score(item) + tagsSum; | |
}.bind(this); | |
}.bind(this), | |
}; | |
if (scores[attrs.score]) { | |
opts.score = scores[scope[attrs.score]]; | |
} | |
// Init | |
var $select = $('input', el).selectize(opts); | |
var selectize = $select[0].selectize; | |
// WARNING: Monkey-patch selectize.search function | |
// Use custom filter function to hide options which we don't want to display | |
// If this breaks in future version of Selectize.js, check search function: | |
// https://github.com/brianreavis/selectize.js/blob/master/src/selectize.js#L873 (or somewhere) | |
// and try to mimic what it does with hideSelected | |
var orgSearch = selectize.search.bind(selectize); | |
selectize.search = function(query) { | |
// Disable query cache - who needs performance? | |
// It would break complete after item is deleted from list (filter needs to run again) | |
delete this.lastQuery; | |
var result = orgSearch(query); | |
if (_.isFunction(filter)) { | |
var i; | |
for (i = result.items.length - 1; i >= 0; i--) { | |
var item = this.options[result.items[i].id]; | |
if (!filter(item)) { | |
result.items.splice(i, 1); | |
} | |
} | |
} | |
return result; | |
}.bind(selectize); | |
} | |
}; | |
}) | |
.directive('editableComplete', function(editableDirectiveFactory) { | |
return editableDirectiveFactory({ | |
directiveName: 'editableComplete', | |
inputTpl: '<autocomplete class="form-control"/>', | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Selectize less doesn't seem to support bootstrap large inputs, this might work | |
.autocomplete { | |
.selectize-control { | |
height: @input-height-large; | |
} | |
.selectize-input { | |
.input-lg; | |
} | |
.input-group { | |
.selectize-control:not(:first-child):not(:last-child) .selectize-input { | |
border-radius: 0; | |
} | |
.selectize-control:last-child .selectize-input { | |
.border-right-radius(0); | |
} | |
.selectize-control:first-child .selectize-input { | |
.border-left-radius(0); | |
} | |
} | |
input { | |
font-size: 18px; | |
width: auto !important; | |
} | |
// Template specific | |
.image { | |
display: inline-block; | |
width: 60px; | |
} | |
.tags { | |
margin-left: 20px; | |
.tag { | |
.skill-tag; | |
font-size: 12px; | |
opacity: 0.7; | |
} | |
} | |
.user { | |
.name { | |
font-weight: bold; | |
} | |
.title { | |
margin-left: 20px; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment