ember-cli-select-picker
Version:
A bootstrap 3 compatible select component
340 lines (309 loc) • 10.6 kB
JavaScript
import Ember from 'ember';
// Ember Addons need to be coded as if Ember.EXTEND_PROTOTYPES = false
// Because of this we need to make our own proxy functions to apply as one offs
// to native arrays.
const emberArrayFunc = function(method) {
return function(ctx, ...args) {
let enumeral = Ember.A(ctx);
Ember.assert(
`Ember.Enumerable has no method ${method}`,
Ember.typeOf(enumeral[method]) === 'function'
);
let result = enumeral[method](...args);
if (Ember.typeOf(result) === 'array') {
return Ember.A(result);
} else {
return result;
}
};
};
const _contains = emberArrayFunc(
// Backwards compatability for Ember < 2.x
Ember.Enumerable.keys().indexOf('includes') !== -1 ?
'includes' :
'contains'
);
const _mapBy = emberArrayFunc('mapBy');
const _filterBy = emberArrayFunc('filterBy');
const _findBy = emberArrayFunc('findBy');
const _uniq = emberArrayFunc('uniq');
const _compact = emberArrayFunc('compact');
const selectOneOf = function(someSelected,
allSelected,
noneSelected) {
return Ember.computed(
'hasSelectedItems', 'allItemsSelected',
function() {
if (this.get('allItemsSelected')) {
return allSelected.call(this);
} else if (this.get('hasSelectedItems')) {
return someSelected.call(this);
} else {
return noneSelected.call(this);
}
}
);
};
const selectOneOfValue = function(someSelectedValue,
allSelectedValue,
noneSelectedValue) {
return selectOneOf(
function() { return someSelectedValue; },
function() { return allSelectedValue; },
function() { return noneSelectedValue; }
);
};
const selectOneOfProperty = function(someSelectedKey,
allSelectedKey,
noneSelectedKey) {
return selectOneOf(
function() { return this.get(someSelectedKey); },
function() { return this.get(allSelectedKey); },
function() { return this.get(noneSelectedKey); }
);
};
const isAdvancedSearch = function(liveSearch) {
return (
Ember.typeOf(liveSearch) === 'string' &&
liveSearch.toLowerCase() === 'advanced'
);
};
export default Ember.Mixin.create({
liveSearch: false,
showDropdown: false,
promptMessage: 'Please select an option',
prompt: Ember.computed.bool('promptMessage'),
showNativePrompt: Ember.computed(
'multiple', 'prompt',
function() {
return !this.get('multiple') && Ember.isPresent(this.get('prompt'));
}
),
menuButtonId: Ember.computed(
'elementId',
function() {
return this.get('elementId') + '-dropdown-menu';
}
),
selectionAsArray: function() {
return Ember.makeArray(this.get('selection'));
},
contentList: Ember.computed(
'selection.[]', 'content.[]', 'optionGroupPath',
'optionLabelPath', 'optionValuePath', 'searchFilter',
function() {
// Ember.Select does not include the content prefix for optionGroupPath
var groupPath = this.get('optionGroupPath');
// Ember.Select expects optionLabelPath and optionValuePath to have a
// `content.` prefix
var labelPath = this.contentPathName('optionLabelPath');
var valuePath = this.contentPathName('optionValuePath');
// selection is either an object or an array of object depending on the
// value of the multiple property. Ember.Select maintains the value
// property.
var selection = this.selectionAsArray().map(function(item) {
return valuePath ? Ember.get(item, valuePath) : item;
});
var searchMatcher = this.makeSearchMatcher();
var result = _compact(Ember.makeArray(this.get('content'))
.map(function(item, index) {
const label = labelPath ? Ember.get(item, labelPath) : item;
const value = valuePath ? Ember.get(item, valuePath) : item;
const group = groupPath ? Ember.get(item, groupPath) : null;
if (searchMatcher(group) || searchMatcher(label)) {
return Ember.Object.create({
item: item,
itemId: index,
group: group,
label: label,
value: value,
selected: _contains(selection, value)
});
} else {
return null;
}
}));
if (Ember.isPresent(result)) {
result.set('firstObject.first', true);
}
return result;
}
),
nestedGroupContentList: Ember.computed(
'contentList.[].group',
function() {
const contentList = this.get('contentList');
const groups = _uniq(_mapBy(contentList, 'group'));
const results = Ember.A();
groups.forEach(function(group) {
results.pushObject(Ember.Object.create({
name: group,
items: _filterBy(contentList, 'group', group)
}));
});
return results;
}
),
contentPathName: function(pathName) {
return this.getWithDefault(pathName, '').substr(8);
},
getByContentPath: function(obj, pathName) {
return Ember.get(obj, this.contentPathName(pathName));
},
selectedContentList: Ember.computed.filterBy('contentList', 'selected'),
unselectedContentList: Ember.computed.setDiff('contentList', 'selectedContentList'),
hasSelectedItems: Ember.computed.gt('selection.length', 0),
allItemsSelected: Ember.computed(
'selection.length', 'content.length',
function() {
return Ember.isEqual(this.get('selection.length'), this.get('content.length'));
}
),
glyphiconClass: selectOneOfValue('glyphicon-minus', 'glyphicon-ok', ''),
selectAllNoneLabel: selectOneOfProperty('selectNoneLabel', 'selectNoneLabel', 'selectAllLabel'),
makeSearchMatcher: function () {
var searchFilter = this.get('searchFilter');
// item can be null, string, or SafeString.
// SafeString does not have toLowerCase() so use toString() to
// normalize it.
if (Ember.isEmpty(searchFilter)) {
return function () {
return true; // Show all
};
} else if (isAdvancedSearch(this.get('liveSearch'))) {
searchFilter = new RegExp(searchFilter.split('').join('.*'), 'i');
return function (item) {
if (Ember.isNone(item)) {
return false;
} else {
return searchFilter.test(item.toString());
}
};
} else {
searchFilter = searchFilter.toLowerCase();
return function (item) {
if (Ember.isNone(item)) {
return false;
} else {
return item.toString().toLowerCase().indexOf(searchFilter) >= 0;
}
};
}
},
selectionLabels: Ember.computed.mapBy('selectedContentList', 'label'),
selectionSummary: Ember.computed(
'selectionLabels.[]', 'nothingSelectedMessage', 'multipleSelectedMessage',
'summaryMessage', 'summaryMessageKey',
function() {
var selection = this.get('selectionLabels');
var count = selection.get('length');
var messageKey = this.get('summaryMessageKey');
var message = this.get('summaryMessage');
if (Ember.I18n && Ember.isPresent(messageKey)) {
// TODO: Allow an enablePrompt="false" feature
if (count === 0) {
return this.get('nothingSelectedMessage');
}
var item = selection.get('firstObject');
var translation = Ember.I18n.t(messageKey, {
count: count,
item: item,
list: selection.join(', ')
});
// I18n is returning a string that's been escaped, we don't want the
// string to get escaped again.
return Ember.String.htmlSafe(translation);
} else if (Ember.isPresent(message)) {
return message;
} else {
switch (count) {
case 0:
return this.get('nothingSelectedMessage');
case 1:
return selection.get('firstObject');
default:
return Ember.String.fmt(
this.get('multipleSelectedMessage'),
count,
selection.get('firstObject'),
selection.join(', ')
);
}
}
}
),
clearSearchDisabled: Ember.computed.empty('searchFilter'),
toggleSelection: function(value) {
var selection = Ember.A(this.get('selection'));
if (_contains(selection, value)) {
selection.removeObject(value);
} else {
selection.pushObject(value);
}
this.set('selection', selection);
},
selectAnItem: function(selected) {
if (!this.get('disabled')) {
if (this.get('multiple')) {
this.set('keepDropdownOpen', true);
this.toggleSelection(selected.get('item'));
} else {
this.setProperties({
// TODO: value will be removed in the future
value: selected.get('value'),
selection: selected.get('item')
});
}
}
},
sendChangeAction: function() {
const changeAction = Ember.get(this, 'attrs.action');
if (changeAction) {
changeAction(this.get('selection'));
}
},
actions: {
selectItem(selected) {
if (this.get('disabled')) { return true; }
this.selectAnItem(selected);
this.sendChangeAction();
return false;
},
selectAllNone(listName) {
if (this.get('disabled')) { return true; }
this.get(listName).forEach(Ember.run.bind(this, this.selectAnItem));
this.sendChangeAction();
return false;
},
selectByValue() {
if (this.get('disabled')) { return true; }
const hasPrompt = Ember.isPresent(this.get('prompt'));
const contentList = this.get('contentList');
const selectedValues = Ember.makeArray(this.$('select').val());
if (this.get('multiple')) {
this.set('selection', contentList.filter(function(item) {
return selectedValues.indexOf(item.get('value')) !== -1;
}));
} else if (hasPrompt && Ember.isEmpty(selectedValues[0])) {
this.setProperties({value: null, selection: null});
} else {
this.send('selectItem', _findBy(contentList, 'value', selectedValues[0]));
}
this.sendChangeAction();
},
toggleSelectAllNone() {
var listName;
if (this.get('hasSelectedItems')) {
listName = 'selectedContentList';
} else {
listName = 'unselectedContentList';
}
this.send('selectAllNone', listName);
return false;
},
clearFilter() {
this.set('searchFilter', null);
return false;
}
}
});