@atlassian/aui
Version:
Atlassian User Interface library
341 lines (299 loc) • 10.4 kB
JavaScript
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;