369 lines
11 KiB
JavaScript
Executable File
369 lines
11 KiB
JavaScript
Executable File
/**
|
|
* Copyright © Magento, Inc. All rights reserved.
|
|
* See COPYING.txt for license details.
|
|
*/
|
|
|
|
define([
|
|
'underscore',
|
|
'jquery',
|
|
'text!mage/multiselect.html',
|
|
'Magento_Ui/js/modal/alert',
|
|
'jquery-ui-modules/widget',
|
|
'jquery/editableMultiselect/js/jquery.multiselect'
|
|
], function (_, $, searchTemplate, alert) {
|
|
'use strict';
|
|
|
|
$.widget('mage.multiselect2', {
|
|
options: {
|
|
mselectContainer: 'section.mselect-list',
|
|
mselectItemsWrapperClass: 'mselect-items-wrapper',
|
|
mselectCheckedClass: 'mselect-checked',
|
|
containerClass: 'paginated',
|
|
searchInputClass: 'admin__action-multiselect-search',
|
|
selectedItemsCountClass: 'admin__action-multiselect-items-selected',
|
|
currentPage: 1,
|
|
lastAppendValue: 0,
|
|
updateDelay: 1000,
|
|
optionsLoaded: false
|
|
},
|
|
|
|
/** @inheritdoc */
|
|
_create: function () {
|
|
$.fn.multiselect.call(this.element, this.options);
|
|
},
|
|
|
|
/** @inheritdoc */
|
|
_init: function () {
|
|
this.domElement = this.element.get(0);
|
|
|
|
this.$container = $(this.options.mselectContainer);
|
|
this.$wrapper = this.$container.find('.' + this.options.mselectItemsWrapperClass);
|
|
this.$item = this.$wrapper.find('div').first();
|
|
this.selectedValues = [];
|
|
this.values = {};
|
|
|
|
this.$container.addClass(this.options.containerClass).prepend(searchTemplate);
|
|
this.$input = this.$container.find('.' + this.options.searchInputClass);
|
|
this.$selectedCounter = this.$container.find('.' + this.options.selectedItemsCountClass);
|
|
this.filter = '';
|
|
|
|
if (this.domElement.options.length) {
|
|
this._setLastAppendOption(this.domElement.options[this.domElement.options.length - 1].value);
|
|
}
|
|
|
|
this._initElement();
|
|
this._events();
|
|
},
|
|
|
|
/**
|
|
* Leave only saved/selected options in select element.
|
|
*
|
|
* @private
|
|
*/
|
|
_initElement: function () {
|
|
this.element.empty();
|
|
_.each(this.options.selectedValues, function (value) {
|
|
this._createSelectedOption({
|
|
value: value,
|
|
label: value
|
|
});
|
|
}, this);
|
|
},
|
|
|
|
/**
|
|
* Attach required events.
|
|
*
|
|
* @private
|
|
*/
|
|
_events: function () {
|
|
var onKeyUp = _.debounce(this.onKeyUp, this.options.updateDelay);
|
|
|
|
_.bindAll(this, 'onScroll', 'onCheck', 'onOptionsChange');
|
|
|
|
this.$wrapper.on('scroll', this.onScroll);
|
|
this.$wrapper.on('change.mselectCheck', '[type=checkbox]', this.onCheck);
|
|
this.$input.on('keyup', _.bind(onKeyUp, this));
|
|
this.element.on('change.hiddenSelect', this.onOptionsChange);
|
|
},
|
|
|
|
/**
|
|
* Behaves multiselect scroll.
|
|
*/
|
|
onScroll: function () {
|
|
var height = this.$wrapper.height(),
|
|
scrollHeight = this.$wrapper.prop('scrollHeight'),
|
|
scrollTop = Math.ceil(this.$wrapper.prop('scrollTop'));
|
|
|
|
if (!this.options.optionsLoaded && scrollHeight - height <= scrollTop) {
|
|
this.loadOptions();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Behaves keyup event on input search
|
|
*/
|
|
onKeyUp: function () {
|
|
if (this.getSearchCriteria() === this.filter) {
|
|
return false;
|
|
}
|
|
|
|
this.setFilter();
|
|
this.clearMultiselectOptions();
|
|
this.setCurrentPage(0);
|
|
this.loadOptions();
|
|
},
|
|
|
|
/**
|
|
* Callback for select change event
|
|
*/
|
|
onOptionsChange: function () {
|
|
this.selectedValues = _.map(this.domElement.options, function (option) {
|
|
this.values[option.value] = true;
|
|
|
|
return option.value;
|
|
}, this);
|
|
|
|
this._updateSelectedCounter();
|
|
},
|
|
|
|
/**
|
|
* Overrides native check behaviour.
|
|
*
|
|
* @param {Event} event
|
|
*/
|
|
onCheck: function (event) {
|
|
var checkbox = event.target,
|
|
option = {
|
|
value: checkbox.value,
|
|
label: $(checkbox).parent('label').text()
|
|
};
|
|
|
|
checkbox.checked ? this._createSelectedOption(option) : this._removeSelectedOption(option);
|
|
event.stopPropagation();
|
|
},
|
|
|
|
/**
|
|
* Show error message.
|
|
*
|
|
* @param {String} message
|
|
*/
|
|
onError: function (message) {
|
|
alert({
|
|
content: message
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Updates current filter state.
|
|
*/
|
|
setFilter: function () {
|
|
this.filter = this.getSearchCriteria() || '';
|
|
},
|
|
|
|
/**
|
|
* Reads search input value.
|
|
*
|
|
* @return {String}
|
|
*/
|
|
getSearchCriteria: function () {
|
|
return this.$input.val().trim();
|
|
},
|
|
|
|
/**
|
|
* Load options data.
|
|
*/
|
|
loadOptions: function () {
|
|
var nextPage = this.getCurrentPage() + 1;
|
|
|
|
this.$wrapper.trigger('processStart');
|
|
this.$input.prop('disabled', true);
|
|
|
|
$.get(this.options.nextPageUrl, {
|
|
p: nextPage,
|
|
s: this.filter
|
|
})
|
|
.done(function (response) {
|
|
if (response.success) {
|
|
this.appendOptions(response.result);
|
|
this.setCurrentPage(nextPage);
|
|
} else {
|
|
this.onError(response.errorMessage);
|
|
}
|
|
}.bind(this))
|
|
.always(function () {
|
|
this.$wrapper.trigger('processStop');
|
|
this.$input.prop('disabled', false);
|
|
|
|
if (this.filter) {
|
|
this.$input.focus();
|
|
}
|
|
}.bind(this));
|
|
},
|
|
|
|
/**
|
|
* Append loaded options
|
|
*
|
|
* @param {Array} options
|
|
*/
|
|
appendOptions: function (options) {
|
|
var divOptions = [];
|
|
|
|
if (!options.length) {
|
|
return false;
|
|
}
|
|
|
|
if (this.isOptionsLoaded(options)) {
|
|
return;
|
|
}
|
|
|
|
options.forEach(function (option) {
|
|
if (!this.values[option.value]) {
|
|
this.values[option.value] = true;
|
|
option.selected = this._isOptionSelected(option);
|
|
divOptions.push(this._createMultiSelectOption(option));
|
|
this._setLastAppendOption(option.value);
|
|
}
|
|
}, this);
|
|
|
|
this.$wrapper.append(divOptions);
|
|
},
|
|
|
|
/**
|
|
* Clear multiselect options
|
|
*/
|
|
clearMultiselectOptions: function () {
|
|
this._setLastAppendOption(0);
|
|
this.values = {};
|
|
this.$wrapper.empty();
|
|
},
|
|
|
|
/**
|
|
* Checks if all options are already loaded
|
|
*
|
|
* @return {Boolean}
|
|
*/
|
|
isOptionsLoaded: function (options) {
|
|
this.options.optionsLoaded = this.options.lastAppendValue === options[options.length - 1].value;
|
|
|
|
return this.options.optionsLoaded;
|
|
},
|
|
|
|
/**
|
|
* Setter for current page.
|
|
*
|
|
* @param {Number} page
|
|
*/
|
|
setCurrentPage: function (page) {
|
|
this.options.currentPage = page;
|
|
},
|
|
|
|
/**
|
|
* Getter for current page.
|
|
*
|
|
* @return {Number}
|
|
*/
|
|
getCurrentPage: function () {
|
|
return this.options.currentPage;
|
|
},
|
|
|
|
/**
|
|
* Creates new selected option for select element
|
|
*
|
|
* @param {Object} option - option object
|
|
* @param {String} option.value - option value
|
|
* @param {String} option.label - option label
|
|
* @private
|
|
*/
|
|
_createSelectedOption: function (option) {
|
|
var selectOption = new Option(option.label, option.value, false, true);
|
|
|
|
this.element.append(selectOption);
|
|
this.selectedValues.push(option.value);
|
|
this._updateSelectedCounter();
|
|
|
|
return selectOption;
|
|
},
|
|
|
|
/**
|
|
* Remove passed option from select element
|
|
*
|
|
* @param {Object} option - option object
|
|
* @param {String} option.value - option value
|
|
* @param {String} option.label - option label
|
|
* @return {Object} option
|
|
* @private
|
|
*/
|
|
_removeSelectedOption: function (option) {
|
|
var unselectedOption = _.findWhere(this.domElement.options, {
|
|
value: option.value
|
|
});
|
|
|
|
if (!_.isUndefined(unselectedOption)) {
|
|
this.domElement.remove(unselectedOption.index);
|
|
this.selectedValues.splice(_.indexOf(this.selectedValues, option.value), 1);
|
|
this._updateSelectedCounter();
|
|
}
|
|
|
|
return unselectedOption;
|
|
},
|
|
|
|
/**
|
|
* Creates new DIV option for multiselect widget
|
|
*
|
|
* @param {Object} option - option object
|
|
* @param {String} option.value - option value
|
|
* @param {String} option.label - option label
|
|
* @param {Boolean} option.selected - is option selected
|
|
* @private
|
|
*/
|
|
_createMultiSelectOption: function (option) {
|
|
var item = this.$item.clone(),
|
|
checkbox = item.find('input'),
|
|
isSelected = !!option.selected;
|
|
|
|
checkbox.val(option.value)
|
|
.prop('checked', isSelected)
|
|
.toggleClass(this.options.mselectCheckedClass, isSelected);
|
|
|
|
item.find('label > span').text(option.label);
|
|
|
|
return item;
|
|
},
|
|
|
|
/**
|
|
* Checks if passed option should be selected
|
|
*
|
|
* @param {Object} option - option object
|
|
* @param {String} option.value - option value
|
|
* @param {String} option.label - option label
|
|
* @param {Boolean} option.selected - is option selected
|
|
* @return {Boolean}
|
|
* @private
|
|
*/
|
|
_isOptionSelected: function (option) {
|
|
return !!~this.selectedValues.indexOf(option.value);
|
|
},
|
|
|
|
/**
|
|
* Saves last added option value.
|
|
*
|
|
* @param {Number} value
|
|
* @private
|
|
*/
|
|
_setLastAppendOption: function (value) {
|
|
this.options.lastAppendValue = value;
|
|
},
|
|
|
|
/**
|
|
* Updates counter of selected items.
|
|
*
|
|
* @private
|
|
*/
|
|
_updateSelectedCounter: function () {
|
|
this.$selectedCounter.text(this.selectedValues.length);
|
|
}
|
|
});
|
|
|
|
return $.mage.multiselect2;
|
|
});
|