mirror of
https://github.com/Choices-js/Choices.git
synced 2024-05-17 13:06:34 +02:00
Set choices directly via public function + callback on change
This commit is contained in:
parent
e667b61bf2
commit
ca39e30684
12
README.md
12
README.md
|
@ -70,6 +70,7 @@ A lightweight, configurable select box/text input plugin. Similar to Select2 and
|
|||
callbackOnInit: () => {},
|
||||
callbackOnAddItem: (id, value, passedInput) => {},
|
||||
callbackOnRemoveItem: (id, value, passedInput) => {},
|
||||
callbackOnChange: (value, passedInput) => {},
|
||||
callbackOnRender: () => {},
|
||||
});
|
||||
</script>
|
||||
|
@ -292,14 +293,21 @@ classNames: {
|
|||
|
||||
<strong>Input types affected:</strong> `text`, `select-one`, `select-multiple`
|
||||
|
||||
<strong>Usage:</strong> Function to run each time an item is added.
|
||||
<strong>Usage:</strong> Function to run each time an item is added (programmatically or by the user).
|
||||
|
||||
### callbackOnRemoveItem
|
||||
<strong>Type:</strong> `Function` <strong>Default:</strong>`(id, value, passedInput) => {}`
|
||||
|
||||
<strong>Input types affected:</strong> `text`, `select-one`, `select-multiple`
|
||||
|
||||
<strong>Usage:</strong> Function to run each time an item is removed.
|
||||
<strong>Usage:</strong> Function to run each time an item is removed (programmatically or by the user).
|
||||
|
||||
### callbackOnChange
|
||||
<strong>Type:</strong> `Function` <strong>Default:</strong>`(value, passedInput) => {}`
|
||||
|
||||
<strong>Input types affected:</strong> `text`, `select-one`, `select-multiple`
|
||||
|
||||
<strong>Usage:</strong> Function to run each time an item is added/removed by a user.
|
||||
|
||||
|
||||
## Methods
|
||||
|
|
4
assets/scripts/dist/choices.min.js
vendored
4
assets/scripts/dist/choices.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -70,7 +70,7 @@ export class Choices {
|
|||
callbackOnInit: () => {},
|
||||
callbackOnAddItem: (id, value, passedInput) => {},
|
||||
callbackOnRemoveItem: (id, value, passedInput) => {},
|
||||
callbackOnRender: (state) => {},
|
||||
callbackOnChange: (value, passedInput) => {},
|
||||
};
|
||||
|
||||
// Merge options with user options
|
||||
|
@ -177,6 +177,8 @@ export class Choices {
|
|||
* @public
|
||||
*/
|
||||
destroy() {
|
||||
this._removeEventListeners();
|
||||
|
||||
this.passedElement.classList.remove(this.config.classNames.input, this.config.classNames.hiddenState);
|
||||
this.passedElement.tabIndex = '';
|
||||
this.passedElement.removeAttribute('style', 'display:none;');
|
||||
|
@ -188,8 +190,6 @@ export class Choices {
|
|||
this.userConfig = null;
|
||||
this.config = null;
|
||||
this.store = null;
|
||||
|
||||
this._removeEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -368,28 +368,59 @@ export class Choices {
|
|||
* @public
|
||||
*/
|
||||
setValue(args) {
|
||||
// Convert args to an itterable array
|
||||
const values = [...args];
|
||||
if(this.initialised === true) {
|
||||
// Convert args to an itterable array
|
||||
const values = [...args];
|
||||
|
||||
values.forEach((item, index) => {
|
||||
if(isType('Object', item)) {
|
||||
if(!item.value) return;
|
||||
// If we are dealing with a select input, we need to create an option first
|
||||
// that is then selected. For text inputs we can just add items normally.
|
||||
if(this.passedElement.type !== 'text') {
|
||||
this._addChoice(true, false, item.value, item.label, -1);
|
||||
} else {
|
||||
this._addItem(item.value, item.label, item.id);
|
||||
values.forEach((item, index) => {
|
||||
if(isType('Object', item)) {
|
||||
if(!item.value) return;
|
||||
// If we are dealing with a select input, we need to create an option first
|
||||
// that is then selected. For text inputs we can just add items normally.
|
||||
if(this.passedElement.type !== 'text') {
|
||||
this._addChoice(true, false, item.value, item.label, -1);
|
||||
} else {
|
||||
this._addItem(item.value, item.label, item.id);
|
||||
}
|
||||
} else if(isType('String', item)) {
|
||||
if(this.passedElement.type !== 'text') {
|
||||
this._addChoice(true, false, item, item, -1);
|
||||
} else {
|
||||
this._addItem(item);
|
||||
}
|
||||
}
|
||||
} else if(isType('String', item)) {
|
||||
if(this.passedElement.type !== 'text') {
|
||||
this._addChoice(true, false, item, item, -1);
|
||||
} else {
|
||||
this._addItem(item);
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct populate choices
|
||||
* @param {Array} choices - Choices to insert
|
||||
* @param {string} value - Name of 'value' property
|
||||
* @param {string} label - Name of 'label' property
|
||||
* @return {Object} Class instance
|
||||
* @public
|
||||
*/
|
||||
setChoices(choices, value, label){
|
||||
if(this.initialised === true) {
|
||||
if(this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple') {
|
||||
if(!isType('Array', choices) || !value) return;
|
||||
|
||||
if(choices && choices.length) {
|
||||
this.containerOuter.classList.remove(this.config.classNames.loadingState);
|
||||
choices.forEach((result, index) => {
|
||||
// Select first choice in list if single select input
|
||||
if(index === 0 && this.passedElement.type === 'select-one') {
|
||||
this._addChoice(true, false, result[value], result[label]);
|
||||
} else {
|
||||
this._addChoice(false, false, result[value], result[label]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -465,6 +496,25 @@ export class Choices {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call change callback
|
||||
* @param {string} value - last added/deleted/selected value
|
||||
* @return
|
||||
* @private
|
||||
*/
|
||||
_triggerChange(value) {
|
||||
if(!value) return;
|
||||
// Run callback if it is a function
|
||||
if(this.config.callbackOnChange){
|
||||
const callback = this.config.callbackOnChange;
|
||||
if(isType('Function', callback)) {
|
||||
callback(value, this.passedElement);
|
||||
} else {
|
||||
console.error('callbackOnChange: Callback is not a function');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process enter key event
|
||||
* @param {Array} activeItems Items that are currently active
|
||||
|
@ -502,6 +552,7 @@ export class Choices {
|
|||
if(canAddItem) {
|
||||
this.toggleDropdown();
|
||||
this._addItem(value);
|
||||
this._triggerChange(value);
|
||||
this.clearInput(this.passedElement);
|
||||
}
|
||||
}
|
||||
|
@ -523,6 +574,7 @@ export class Choices {
|
|||
if(this.config.editItems && !hasHighlightedItems && lastItem) {
|
||||
this.input.value = lastItem.value;
|
||||
this._removeItem(lastItem);
|
||||
this._triggerChange(lastItem.value);
|
||||
} else {
|
||||
if(!hasHighlightedItems) { this.highlightItem(lastItem); }
|
||||
this.removeHighlightedItems();
|
||||
|
@ -589,6 +641,7 @@ export class Choices {
|
|||
const label = highlighted.innerHTML;
|
||||
const id = highlighted.getAttribute('data-id');
|
||||
this._addItem(value, label, id);
|
||||
this._triggerChange(value);
|
||||
this.clearInput(this.passedElement);
|
||||
|
||||
if(this.passedElement.type === 'select-one') {
|
||||
|
@ -778,6 +831,7 @@ export class Choices {
|
|||
const itemId = e.target.parentNode.getAttribute('data-id');
|
||||
const itemToRemove = activeItems.find((item) => item.id === parseInt(itemId));
|
||||
this._removeItem(itemToRemove);
|
||||
this._triggerChange(itemToRemove.value);
|
||||
}
|
||||
} else if(e.target.hasAttribute('data-item')) {
|
||||
// If we are clicking on an item
|
||||
|
@ -803,6 +857,7 @@ export class Choices {
|
|||
|
||||
if(!choice.selected && !choice.disabled) {
|
||||
this._addItem(choice.value, choice.label, choice.id);
|
||||
this._triggerChange(choice.value);
|
||||
if(this.passedElement.type === 'select-one') {
|
||||
this.input.value = "";
|
||||
this.isSearching = false;
|
||||
|
@ -872,7 +927,13 @@ export class Choices {
|
|||
if(e.target === this.input && !hasActiveDropdown) {
|
||||
this.containerOuter.classList.add(this.config.classNames.focusState);
|
||||
if(this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple'){
|
||||
this.showDropdown();
|
||||
this.showDropdown();
|
||||
}
|
||||
} else if(this.passedElement.type === 'select-one' && e.target === this.containerOuter && !hasActiveDropdown) {
|
||||
this.containerOuter.classList.add(this.config.classNames.focusState);
|
||||
this.showDropdown();
|
||||
if(this.config.search) {
|
||||
this.input.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -885,9 +946,15 @@ export class Choices {
|
|||
*/
|
||||
_onBlur(e) {
|
||||
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
||||
if(e.target === this.input && !hasActiveDropdown) {
|
||||
|
||||
// If the blurred element is this input
|
||||
if(e.target === this.input) {
|
||||
// Remove the focus state
|
||||
this.containerOuter.classList.remove(this.config.classNames.focusState);
|
||||
} else {
|
||||
}
|
||||
|
||||
// Close the dropdown if there is one
|
||||
if(hasActiveDropdown) {
|
||||
this.hideDropdown();
|
||||
}
|
||||
}
|
||||
|
@ -1139,7 +1206,15 @@ export class Choices {
|
|||
const classNames = this.config.classNames;
|
||||
const templates = {
|
||||
containerOuter: () => {
|
||||
return strToEl(`<div class="${ classNames.containerOuter }" data-type="${ this.passedElement.type }"></div>`);
|
||||
if(this.passedElement.type === 'select-one') {
|
||||
return strToEl(`
|
||||
<div class="${ classNames.containerOuter }" data-type="${ this.passedElement.type }" tabindex="0"></div>
|
||||
`);
|
||||
} else {
|
||||
return strToEl(`
|
||||
<div class="${ classNames.containerOuter }" data-type="${ this.passedElement.type }"></div>
|
||||
`);
|
||||
}
|
||||
},
|
||||
containerInner: () => {
|
||||
return strToEl(`<div class="${ classNames.containerInner }"></div>`);
|
||||
|
@ -1430,8 +1505,8 @@ export class Choices {
|
|||
} else if(activeChoices.length >= 1) {
|
||||
choiceListFragment = this.renderChoices(activeChoices, choiceListFragment);
|
||||
}
|
||||
|
||||
if(choiceListFragment.childNodes) {
|
||||
|
||||
if(choiceListFragment.childNodes && choiceListFragment.childNodes.length > 0) {
|
||||
// If we actually have anything to add to our dropdown
|
||||
// append it and highlight the first choice
|
||||
this.choiceList.appendChild(choiceListFragment);
|
||||
|
@ -1462,14 +1537,6 @@ export class Choices {
|
|||
}
|
||||
}
|
||||
|
||||
if(this.config.callbackOnRender){
|
||||
if(isType('Function', this.config.callbackOnRender)) {
|
||||
this.config.callbackOnRender(this.currentState);
|
||||
} else {
|
||||
console.error('callbackOnRender: Callback is not a function');
|
||||
}
|
||||
}
|
||||
|
||||
this.prevState = this.currentState;
|
||||
}
|
||||
}
|
||||
|
@ -1485,6 +1552,10 @@ export class Choices {
|
|||
document.addEventListener('mousedown', this._onMouseDown);
|
||||
document.addEventListener('mouseover', this._onMouseOver);
|
||||
|
||||
if(this.passedElement.type && this.passedElement.type === 'select-one') {
|
||||
this.containerOuter.addEventListener('focus', this._onFocus);
|
||||
}
|
||||
|
||||
this.input.addEventListener('input', this._onInput);
|
||||
this.input.addEventListener('paste', this._onPaste);
|
||||
this.input.addEventListener('focus', this._onFocus);
|
||||
|
@ -1501,6 +1572,10 @@ export class Choices {
|
|||
document.removeEventListener('keydown', this._onKeyDown);
|
||||
document.removeEventListener('mousedown', this._onMouseDown);
|
||||
document.removeEventListener('mouseover', this._onMouseOver);
|
||||
|
||||
if(this.passedElement.type && this.passedElement.type === 'select-one') {
|
||||
this.containerOuter.removeEventListener('focus', this._onFocus);
|
||||
}
|
||||
|
||||
this.input.removeEventListener('input', this._onInput);
|
||||
this.input.removeEventListener('paste', this._onPaste);
|
||||
|
|
|
@ -189,7 +189,7 @@
|
|||
var choices10 = new Choices('#choices-10', {
|
||||
placeholder: true,
|
||||
placeholderValue: 'Pick an Strokes record',
|
||||
callbackOnRender: function(state) { console.log(state) }
|
||||
callbackOnChange: function(value, passedInput) { console.log(value) }
|
||||
}).ajax(function(callback) {
|
||||
fetch('https://api.discogs.com/artists/55980/releases?token=QBRmstCkwXEvCjTclCpumbtNwvVkEzGAdELXyRyW')
|
||||
.then(function(response) {
|
||||
|
@ -242,6 +242,12 @@
|
|||
],
|
||||
});
|
||||
|
||||
choices15.setChoices([
|
||||
{value: 'Four', label: 'Label Four'},
|
||||
{value: 'Five', label: 'Label Five'},
|
||||
{value: 'Six', label: 'Label Six'},
|
||||
], 'value', 'label');
|
||||
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -45,6 +45,7 @@ describe('Choices', function() {
|
|||
|
||||
it('should have config options', function() {
|
||||
expect(this.choices.config.items).toEqual(jasmine.any(Array));
|
||||
expect(this.choices.config.choices).toEqual(jasmine.any(Array));
|
||||
expect(this.choices.config.maxItemCount).toEqual(jasmine.any(Number));
|
||||
expect(this.choices.config.addItems).toEqual(jasmine.any(Boolean));
|
||||
expect(this.choices.config.removeItems).toEqual(jasmine.any(Boolean));
|
||||
|
@ -64,7 +65,7 @@ describe('Choices', function() {
|
|||
expect(this.choices.config.callbackOnInit).toEqual(jasmine.any(Function));
|
||||
expect(this.choices.config.callbackOnAddItem).toEqual(jasmine.any(Function));
|
||||
expect(this.choices.config.callbackOnRemoveItem).toEqual(jasmine.any(Function));
|
||||
expect(this.choices.config.callbackOnRender).toEqual(jasmine.any(Function));
|
||||
expect(this.choices.config.callbackOnChange).toEqual(jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('should expose public methods', function() {
|
||||
|
@ -156,7 +157,6 @@ describe('Choices', function() {
|
|||
beforeEach(function() {
|
||||
this.input = document.createElement('select');
|
||||
this.input.className = 'js-choices';
|
||||
this.input.multiple = false;
|
||||
this.input.placeholder = 'Placeholder text';
|
||||
|
||||
for (let i = 1; i < 4; i++) {
|
||||
|
@ -219,6 +219,28 @@ describe('Choices', function() {
|
|||
expect(this.choices.currentState.items.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should trigger a change callback on selection', function() {
|
||||
spyOn(this.choices.config, 'callbackOnChange');
|
||||
this.choices.input.focus();
|
||||
|
||||
// Key down to second choice
|
||||
this.choices._onKeyDown({
|
||||
target: this.choices.input,
|
||||
keyCode: 40,
|
||||
ctrlKey: false,
|
||||
preventDefault: () => {}
|
||||
});
|
||||
|
||||
// Key down to select choice
|
||||
this.choices._onKeyDown({
|
||||
target: this.choices.input,
|
||||
keyCode: 13,
|
||||
ctrlKey: false
|
||||
});
|
||||
|
||||
expect(this.choices.config.callbackOnChange).toHaveBeenCalledWith(jasmine.any(String), jasmine.any(HTMLElement));
|
||||
});
|
||||
|
||||
it('should filter choices when searching', function() {
|
||||
this.choices.input.focus();
|
||||
this.choices.input.value = 'Value 3';
|
||||
|
@ -240,7 +262,7 @@ describe('Choices', function() {
|
|||
beforeEach(function() {
|
||||
this.input = document.createElement('select');
|
||||
this.input.className = 'js-choices';
|
||||
this.input.multiple = true;
|
||||
this.input.setAttribute('multiple', '');
|
||||
|
||||
for (let i = 1; i < 4; i++) {
|
||||
const option = document.createElement('option');
|
||||
|
@ -264,7 +286,7 @@ describe('Choices', function() {
|
|||
{value: 'Two', label: 'Label Two', disabled: true},
|
||||
{value: 'Three', label: 'Label Three'},
|
||||
],
|
||||
});;
|
||||
});
|
||||
});
|
||||
|
||||
it('should add any pre-defined values', function() {
|
||||
|
|
Loading…
Reference in a new issue