infojune/resources/sass/materialize/js/chips.js

482 lines
12 KiB
JavaScript

(function($) {
'use strict';
let _defaults = {
data: [],
placeholder: '',
secondaryPlaceholder: '',
autocompleteOptions: {},
limit: Infinity,
onChipAdd: null,
onChipSelect: null,
onChipDelete: null
};
/**
* @typedef {Object} chip
* @property {String} tag chip tag string
* @property {String} [image] chip avatar image string
*/
/**
* @class
*
*/
class Chips extends Component {
/**
* Construct Chips instance and set up overlay
* @constructor
* @param {Element} el
* @param {Object} options
*/
constructor(el, options) {
super(Chips, el, options);
this.el.M_Chips = this;
/**
* Options for the modal
* @member Chips#options
* @prop {Array} data
* @prop {String} placeholder
* @prop {String} secondaryPlaceholder
* @prop {Object} autocompleteOptions
*/
this.options = $.extend({}, Chips.defaults, options);
this.$el.addClass('chips input-field');
this.chipsData = [];
this.$chips = $();
this._setupInput();
this.hasAutocomplete = Object.keys(this.options.autocompleteOptions).length > 0;
// Set input id
if (!this.$input.attr('id')) {
this.$input.attr('id', M.guid());
}
// Render initial chips
if (this.options.data.length) {
this.chipsData = this.options.data;
this._renderChips(this.chipsData);
}
// Setup autocomplete if needed
if (this.hasAutocomplete) {
this._setupAutocomplete();
}
this._setPlaceholder();
this._setupLabel();
this._setupEventHandlers();
}
static get defaults() {
return _defaults;
}
static init(els, options) {
return super.init(this, els, options);
}
/**
* Get Instance
*/
static getInstance(el) {
let domElem = !!el.jquery ? el[0] : el;
return domElem.M_Chips;
}
/**
* Get Chips Data
*/
getData() {
return this.chipsData;
}
/**
* Teardown component
*/
destroy() {
this._removeEventHandlers();
this.$chips.remove();
this.el.M_Chips = undefined;
}
/**
* Setup Event Handlers
*/
_setupEventHandlers() {
this._handleChipClickBound = this._handleChipClick.bind(this);
this._handleInputKeydownBound = this._handleInputKeydown.bind(this);
this._handleInputFocusBound = this._handleInputFocus.bind(this);
this._handleInputBlurBound = this._handleInputBlur.bind(this);
this.el.addEventListener('click', this._handleChipClickBound);
document.addEventListener('keydown', Chips._handleChipsKeydown);
document.addEventListener('keyup', Chips._handleChipsKeyup);
this.el.addEventListener('blur', Chips._handleChipsBlur, true);
this.$input[0].addEventListener('focus', this._handleInputFocusBound);
this.$input[0].addEventListener('blur', this._handleInputBlurBound);
this.$input[0].addEventListener('keydown', this._handleInputKeydownBound);
}
/**
* Remove Event Handlers
*/
_removeEventHandlers() {
this.el.removeEventListener('click', this._handleChipClickBound);
document.removeEventListener('keydown', Chips._handleChipsKeydown);
document.removeEventListener('keyup', Chips._handleChipsKeyup);
this.el.removeEventListener('blur', Chips._handleChipsBlur, true);
this.$input[0].removeEventListener('focus', this._handleInputFocusBound);
this.$input[0].removeEventListener('blur', this._handleInputBlurBound);
this.$input[0].removeEventListener('keydown', this._handleInputKeydownBound);
}
/**
* Handle Chip Click
* @param {Event} e
*/
_handleChipClick(e) {
let $chip = $(e.target).closest('.chip');
let clickedClose = $(e.target).is('.close');
if ($chip.length) {
let index = $chip.index();
if (clickedClose) {
// delete chip
this.deleteChip(index);
this.$input[0].focus();
} else {
// select chip
this.selectChip(index);
}
// Default handle click to focus on input
} else {
this.$input[0].focus();
}
}
/**
* Handle Chips Keydown
* @param {Event} e
*/
static _handleChipsKeydown(e) {
Chips._keydown = true;
let $chips = $(e.target).closest('.chips');
let chipsKeydown = e.target && $chips.length;
// Don't handle keydown inputs on input and textarea
if ($(e.target).is('input, textarea') || !chipsKeydown) {
return;
}
let currChips = $chips[0].M_Chips;
// backspace and delete
if (e.keyCode === 8 || e.keyCode === 46) {
e.preventDefault();
let selectIndex = currChips.chipsData.length;
if (currChips._selectedChip) {
let index = currChips._selectedChip.index();
currChips.deleteChip(index);
currChips._selectedChip = null;
// Make sure selectIndex doesn't go negative
selectIndex = Math.max(index - 1, 0);
}
if (currChips.chipsData.length) {
currChips.selectChip(selectIndex);
}
// left arrow key
} else if (e.keyCode === 37) {
if (currChips._selectedChip) {
let selectIndex = currChips._selectedChip.index() - 1;
if (selectIndex < 0) {
return;
}
currChips.selectChip(selectIndex);
}
// right arrow key
} else if (e.keyCode === 39) {
if (currChips._selectedChip) {
let selectIndex = currChips._selectedChip.index() + 1;
if (selectIndex >= currChips.chipsData.length) {
currChips.$input[0].focus();
} else {
currChips.selectChip(selectIndex);
}
}
}
}
/**
* Handle Chips Keyup
* @param {Event} e
*/
static _handleChipsKeyup(e) {
Chips._keydown = false;
}
/**
* Handle Chips Blur
* @param {Event} e
*/
static _handleChipsBlur(e) {
if (!Chips._keydown) {
let $chips = $(e.target).closest('.chips');
let currChips = $chips[0].M_Chips;
currChips._selectedChip = null;
}
}
/**
* Handle Input Focus
*/
_handleInputFocus() {
this.$el.addClass('focus');
}
/**
* Handle Input Blur
*/
_handleInputBlur() {
this.$el.removeClass('focus');
}
/**
* Handle Input Keydown
* @param {Event} e
*/
_handleInputKeydown(e) {
Chips._keydown = true;
// enter
if (e.keyCode === 13) {
// Override enter if autocompleting.
if (this.hasAutocomplete && this.autocomplete && this.autocomplete.isOpen) {
return;
}
e.preventDefault();
this.addChip({
tag: this.$input[0].value
});
this.$input[0].value = '';
// delete or left
} else if (
(e.keyCode === 8 || e.keyCode === 37) &&
this.$input[0].value === '' &&
this.chipsData.length
) {
e.preventDefault();
this.selectChip(this.chipsData.length - 1);
}
}
/**
* Render Chip
* @param {chip} chip
* @return {Element}
*/
_renderChip(chip) {
if (!chip.tag) {
return;
}
let renderedChip = document.createElement('div');
let closeIcon = document.createElement('i');
renderedChip.classList.add('chip');
renderedChip.textContent = chip.tag;
renderedChip.setAttribute('tabindex', 0);
$(closeIcon).addClass('material-icons close');
closeIcon.textContent = 'close';
// attach image if needed
if (chip.image) {
let img = document.createElement('img');
img.setAttribute('src', chip.image);
renderedChip.insertBefore(img, renderedChip.firstChild);
}
renderedChip.appendChild(closeIcon);
return renderedChip;
}
/**
* Render Chips
*/
_renderChips() {
this.$chips.remove();
for (let i = 0; i < this.chipsData.length; i++) {
let chipEl = this._renderChip(this.chipsData[i]);
this.$el.append(chipEl);
this.$chips.add(chipEl);
}
// move input to end
this.$el.append(this.$input[0]);
}
/**
* Setup Autocomplete
*/
_setupAutocomplete() {
this.options.autocompleteOptions.onAutocomplete = (val) => {
this.addChip({
tag: val
});
this.$input[0].value = '';
this.$input[0].focus();
};
this.autocomplete = M.Autocomplete.init(this.$input[0], this.options.autocompleteOptions);
}
/**
* Setup Input
*/
_setupInput() {
this.$input = this.$el.find('input');
if (!this.$input.length) {
this.$input = $('<input></input>');
this.$el.append(this.$input);
}
this.$input.addClass('input');
}
/**
* Setup Label
*/
_setupLabel() {
this.$label = this.$el.find('label');
if (this.$label.length) {
this.$label.setAttribute('for', this.$input.attr('id'));
}
}
/**
* Set placeholder
*/
_setPlaceholder() {
if (this.chipsData !== undefined && !this.chipsData.length && this.options.placeholder) {
$(this.$input).prop('placeholder', this.options.placeholder);
} else if (
(this.chipsData === undefined || !!this.chipsData.length) &&
this.options.secondaryPlaceholder
) {
$(this.$input).prop('placeholder', this.options.secondaryPlaceholder);
}
}
/**
* Check if chip is valid
* @param {chip} chip
*/
_isValid(chip) {
if (chip.hasOwnProperty('tag') && chip.tag !== '') {
let exists = false;
for (let i = 0; i < this.chipsData.length; i++) {
if (this.chipsData[i].tag === chip.tag) {
exists = true;
break;
}
}
return !exists;
}
return false;
}
/**
* Add chip
* @param {chip} chip
*/
addChip(chip) {
if (!this._isValid(chip) || this.chipsData.length >= this.options.limit) {
return;
}
let renderedChip = this._renderChip(chip);
this.$chips.add(renderedChip);
this.chipsData.push(chip);
$(this.$input).before(renderedChip);
this._setPlaceholder();
// fire chipAdd callback
if (typeof this.options.onChipAdd === 'function') {
this.options.onChipAdd.call(this, this.$el, renderedChip);
}
}
/**
* Delete chip
* @param {Number} chip
*/
deleteChip(chipIndex) {
let $chip = this.$chips.eq(chipIndex);
this.$chips.eq(chipIndex).remove();
this.$chips = this.$chips.filter(function(el) {
return $(el).index() >= 0;
});
this.chipsData.splice(chipIndex, 1);
this._setPlaceholder();
// fire chipDelete callback
if (typeof this.options.onChipDelete === 'function') {
this.options.onChipDelete.call(this, this.$el, $chip[0]);
}
}
/**
* Select chip
* @param {Number} chip
*/
selectChip(chipIndex) {
let $chip = this.$chips.eq(chipIndex);
this._selectedChip = $chip;
$chip[0].focus();
// fire chipSelect callback
if (typeof this.options.onChipSelect === 'function') {
this.options.onChipSelect.call(this, this.$el, $chip[0]);
}
}
}
/**
* @static
* @memberof Chips
*/
Chips._keydown = false;
M.Chips = Chips;
if (M.jQueryLoaded) {
M.initializeJqueryWrapper(Chips, 'chips', 'M_Chips');
}
$(document).ready(function() {
// Handle removal of static chips.
$(document.body).on('click', '.chip .close', function() {
let $chips = $(this).closest('.chips');
if ($chips.length && $chips[0].M_Chips) {
return;
}
$(this)
.closest('.chip')
.remove();
});
});
})(cash);