Skip to content

Instantly share code, notes, and snippets.

@StephanHoyer
Created May 20, 2015 10:07
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save StephanHoyer/ea6d1e9c5ea7a9225639 to your computer and use it in GitHub Desktop.
Save StephanHoyer/ea6d1e9c5ea7a9225639 to your computer and use it in GitHub Desktop.
Select2-like mithril input
'use strict';
var m = require('mithril');
var icons = require('client/utils/icons');
var remove = require('lodash/array/remove');
var transform = require('lodash/object/transform');
var last = require('lodash/array/last');
var code = require('yields-keycode');
function sameAs(filterA) {
return function (filterB) {
return filterA.type === filterB.type && filterA.label === filterB.label;
};
}
function not(fn) {
return function(obj) {
return !fn(obj);
};
}
function emptyArrayFn() {
return [];
}
function minMax(a, b, c) {
return Math.max(a, Math.min(b, c));
}
module.exports = {
controller: function (options) {
var scope = {};
options = options || {};
options.getSuggestions = options.getSuggestions || emptyArrayFn;
options.getCurrentFilters = options.getCurrentFilters || emptyArrayFn;
scope.currentFilters = options.getCurrentFilters();
scope.inputValue = '';
function exists(filter) {
return scope.currentFilters.some(sameAs(filter));
}
function updateSuggestions() {
scope.suggestions = options.getSuggestions(scope.inputValue).filter(not(exists));
}
updateSuggestions();
scope.oninput = function (event) {
scope.inputValue = event.target.value;
delete scope.selectedSuggestion;
delete scope.selectedFilter;
updateSuggestions();
};
scope.add = function (filter) {
return function () {
if (!exists(filter)) {
scope.currentFilters.push(filter);
options.onAddFilter && options.onAddFilter(filter);
scope.inputValue = '';
updateSuggestions();
}
};
};
scope.remove = function (filter) {
return function () {
remove(scope.currentFilters, filter);
options.onRemoveFilter && options.onRemoveFilter(filter);
updateSuggestions();
};
};
function createFromInput() {
var filter = {
type: 'text',
label: scope.inputValue,
value: scope.inputValue
};
if (scope.selectedSuggestion) {
filter = scope.selectedSuggestion;
} else if (scope.inputValue === '') {
return;
}
scope.add(filter)();
}
function removeOrSelect() {
if (scope.inputValue !== '') {
return true;
}
if (scope.selectedFilter) {
scope.remove(scope.selectedFilter)();
delete scope.selectedFilter;
} else {
scope.selectedFilter = last(scope.currentFilters);
}
}
function selectFromCurrent(event) {
if (scope.inputValue !== '') {
return;
}
if (scope.selectedFilter) {
var currentIndex = scope.currentFilters.indexOf(scope.selectedFilter);
var newIndex = currentIndex + (event.keyCode === code('left') ? -1 : 1);
if (newIndex === scope.currentFilters.length) {
delete scope.selectedFilter;
} else {
newIndex = minMax(0, newIndex, scope.currentFilters.length - 1);
scope.selectedFilter = scope.currentFilters[newIndex];
}
} else if (event.keyCode === code('left')) {
scope.selectedFilter = last(scope.currentFilters);
}
}
function selectFromSuggestion(event) {
if (scope.selectedSuggestion) {
var currentIndex = scope.suggestions.indexOf(scope.selectedSuggestion);
var newIndex = currentIndex + (event.keyCode === code('up') ? -1 : 1);
if (newIndex === scope.suggestions.length) {
delete scope.selectedSuggestion;
} else {
newIndex = minMax(0, newIndex, scope.suggestions.length - 1);
scope.selectedSuggestion = scope.suggestions[newIndex];
}
} else if (event.keyCode === code('down')) {
scope.selectedSuggestion = scope.suggestions[0];
}
}
var actions = transform({
enter: createFromInput,
backspace: removeOrSelect,
del: removeOrSelect,
left: selectFromCurrent,
right: selectFromCurrent,
up: selectFromSuggestion,
down: selectFromSuggestion
}, function(actions, fn, name) {
actions[code(name)] = fn;
});
scope.onkeydown = function (event) {
if (actions[event.keyCode]) {
return actions[event.keyCode](event);
}
};
return scope;
},
view: function (scope, options) {
return m('.filterInput', [
icons('search'),
m('ul.currentFilters', scope.currentFilters.map(function (filter) {
return m('li.filter' + (scope.selectedFilter === filter ? '.selected' : ''), [
filter.label,
icons('remove', {
onclick: scope.remove(filter)
})
]);
})),
m('.form', [
m('input', {
placeholder: options && options.placeholder || '',
oninput: scope.oninput,
onkeydown: scope.onkeydown,
value: scope.inputValue
}),
m('ul.suggestions', scope.suggestions.map(function (suggestion) {
return m('li' + (scope.selectedSuggestion === suggestion ? '.selected' : ''), {
onclick: scope.add(suggestion)
}, suggestion.label);
}))
])
]);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment