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;
 | |
| });
 |