UNPKG

@atlassian/aui

Version:

Atlassian User Interface library

344 lines (304 loc) 10.5 kB
import $ from './jquery'; import './dropdown2'; import './tooltip'; import { I18n } from './i18n'; import amdify from './internal/amdify'; import skate from './internal/skate'; import uid from './unique-id'; var templates = { dropdown: function (items) { function createSection() { return $('<div class="aui-dropdown2-section">'); } // clear items section var $clearItemsSection = createSection(); $('<button />') .attr({ 'type': 'button', 'data-aui-checkbox-multiselect-clear': '', 'class': 'aui-button aui-button-link', }) .text(I18n.getText('aui.checkboxmultiselect.clear.selected')) .appendTo($clearItemsSection); // list of items var $section = createSection(); var $itemList = $('<ul />').appendTo($section); $.each(items, function (idx, item) { var $li = $('<li />') .attr({ class: item.styleClass || '', }) .appendTo($itemList); var $a = $('<a />') .text(item.label) .attr('data-value', item.value) .addClass('aui-dropdown2-checkbox aui-dropdown2-interactive') .appendTo($li); if (item.icon) { $('<span />') .addClass('aui-icon') .css('backgroundImage', 'url(' + item.icon + ')")') .appendTo($a); } if (item.selected) { $a.addClass('aui-dropdown2-checked'); } }); return $('<div />').append($clearItemsSection).append($section).html(); }, furniture: function (name, optionsHtml) { var dropdownId = name + '-dropdown'; var $select = $('<select />') .attr({ name: name, multiple: 'multiple', }) .html(optionsHtml); var $dropdown = $('<div>').attr({ id: dropdownId, class: 'aui-checkbox-multiselect-dropdown aui-dropdown2', }); var $button = $('<button />').attr({ 'class': 'aui-checkbox-multiselect-btn aui-button aui-dropdown2-trigger', 'type': 'button', 'aria-owns': dropdownId, 'aria-haspopup': true, }); return $('<div />').append($select).append($button).append($dropdown).html(); }, }; /** * Handles when user clicks an item in the dropdown list. Either selects or unselects the corresponding * option in the <select>. * @private */ function handleDropdownSelection(e) { var $a = $(e.target); var value = $a.attr('data-value'); updateOption(this, value, $a.hasClass('aui-dropdown2-checked')); } /** * Selects or unselects the <option> corresponding the given value. * @private * @param component - Checkbox MultiSelect web component * @param value - value of option to update * @param {Boolean} selected - select or unselect it. */ function updateOption(component, value, selected) { var $toUpdate = component.$select.find('option').filter(function () { var $this = $(this); return $this.attr('value') === value && $this.prop('selected') !== selected; }); if ($toUpdate.length) { $toUpdate.prop('selected', selected); component.$select.trigger('change'); } } /** * Enables 'clear all' button if there are any selected <option>s, otherwise disables it. * @private */ function updateClearAll(component) { component.$dropdown.find('[data-aui-checkbox-multiselect-clear]').prop('disabled', function () { return getSelectedDescriptors(component).length < 1; }); } /** * Gets button title used for tipsy. Is blank when dropdown is open so we don't get tipsy hanging over options. * @private * @param component * @returns {string} */ function getButtonTitle(component) { return component.$dropdown[0].hasAttribute('hidden') ? '' : getSelectedLabels(component).join(', '); } /** * Converts a jQuery collection of <option> elements into an object that describes them. * @param {jQuery} $options * @returns {Array<Object>} * @private */ function mapOptionDescriptors($options) { return $options.map(function () { var $option = $(this); return { value: $option.val(), label: $option.text(), icon: $option.data('icon'), styleClass: $option.data('styleClass'), title: $option.attr('title'), disabled: $option.attr('disabled'), selected: $option.prop('selected'), }; }); } /** * Gets label for when nothing is selected * @returns {string} * @private */ function getImplicitAllLabel(component) { return $(component).data('allLabel') || 'All'; } /** * Renders dropdown with list of items representing the selected or unselect state of the <option>s in <select> * @private */ function renderDropdown(component) { component.$dropdown.html(templates.dropdown(getDescriptors(component))); updateClearAll(component); } /** * Renders button with the selected <option>'s innerText in a comma seperated list. If nothing is selected 'All' * is displayed. * @private */ function renderButton(component) { var selectedLabels = getSelectedLabels(component); var label = isImplicitAll(component) ? getImplicitAllLabel(component) : selectedLabels.join(', '); component.$btn.text(label); } /** * Gets descriptor for selected options, the descriptor is an object that contains meta information about * the option, such as value, label, icon etc. * @private * @returns Array<Object> */ function getDescriptors(component) { return mapOptionDescriptors(component.getOptions()); } /** * Gets descriptor for selected options, the descriptor is an object that contains meta information about * the option, such as value, label, icon etc. * @private * @returns Array<Object> */ function getSelectedDescriptors(component) { return mapOptionDescriptors(component.getSelectedOptions()); } /** * Gets the innerText of the selected options * @private * @returns Array<String> */ function getSelectedLabels(component) { return $.map(getSelectedDescriptors(component), function (descriptor) { return descriptor.label; }); } /** * If nothing is selected, we take this to mean that everything is selected. * @returns Boolean */ function isImplicitAll(component) { return getSelectedDescriptors(component).length === 0; } const CheckboxMultiselectEl = skate('aui-checkbox-multiselect', { attached: function (component) { // This used to be template logic, however, it breaks tests if we // keep it there after starting to use native custom elements. This // should be refactored. // // Ideally we should be templating the element within the "template" // hook which will ensure it's templated prior to calling the // "attached" callback. var name = component.getAttribute('name') || uid('aui-checkbox-multiselect-'); component.innerHTML = templates.furniture(name, component.innerHTML); component.$select = $('select', component).on('change', function () { renderButton(component); updateClearAll(component); }); component.$dropdown = $('.aui-checkbox-multiselect-dropdown', component) .on('aui-dropdown2-item-check', handleDropdownSelection.bind(component)) .on('aui-dropdown2-item-uncheck', handleDropdownSelection.bind(component)) .on( 'click', 'button[data-aui-checkbox-multiselect-clear]', component.deselectAllOptions.bind(component) ); component.$btn = $('.aui-checkbox-multiselect-btn', component).tooltip({ title: function () { return getButtonTitle(component); }, }); renderButton(component); renderDropdown(component); }, prototype: { /** * Gets all options regardless of selected or unselected * @returns {jQuery} */ getOptions: function () { return this.$select.find('option'); }, /** * Gets all selected options * @returns {jQuery} */ getSelectedOptions: function () { return this.$select.find('option:selected'); }, /** * Sets <option> elements matching given value to selected */ selectOption: function (value) { updateOption(this, value, true); }, /** * Sets <option> elements matching given value to unselected */ unselectOption: function (value) { updateOption(this, value, false); }, /** * Gets value of <select> * @returns Array */ getValue: function () { return this.$select.val(); }, /** * Unchecks all items in the dropdown and in the <select> */ deselectAllOptions: function () { this.$select.val([]).trigger('change'); this.$dropdown .find('.aui-dropdown2-checked,.checked') .removeClass('aui-dropdown2-checked checked'); }, /** * Adds an option to the <select> * @param descriptor */ addOption: function (descriptor) { $('<option />') .attr({ value: descriptor.value, icon: descriptor.icon, disabled: descriptor.disabled, selected: descriptor.selected, title: descriptor.title, }) .text(descriptor.label) .appendTo(this.$select); renderButton(this); renderDropdown(this); }, /** * Removes options matching value from <select> * @param value */ removeOption: function (value) { this.$select.find("[value='" + value + "']").remove(); renderButton(this); renderDropdown(this); }, }, }); amdify('aui/checkbox-multiselect', CheckboxMultiselectEl); export default CheckboxMultiselectEl;