
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
/**
* multi.js
* A user-friendly replacement for select boxes with multiple attribute enabled.
*
* Author: Fabian Lindfors
* License: MIT
*/
var multi = (function() {
// Helper function to trigger an event on an element
var trigger_event = function( type, el ) {
var e = document.createEvent( 'HTMLEvents' );
e.initEvent( type, false, true );
el.dispatchEvent( e );
};
// Toggles the target option on the select
var toggle_option = function ( select, event ) {
var option = select.options[ event.target.getAttribute( 'multi-index' ) ];
if ( option.disabled ) {
return;
}
option.selected = !option.selected;
trigger_event( 'change', select );
};
/**
*
* Sets the options of the select based on an array of options.
*
* The options parameter must be an array with all its elements implementing
* the following interface:
*
* @param {string} value Value mapped to the value attribute
* @param {string} label Value mapped to the innerText property
* @param {boolean} selected Wether the option is selected by default or not
* @param {boolean} disabled Wether the option is disabled by default or not
*
* interface SelectOption {
* value?: String;
* label: String;
* selected?: boolean;
* disabled?: boolean;
* }
*
* @param {HTMLSelectElement} $select Select element to set options.
* @param {SelectOption[]} options List of Options to be added to the select
* @param {boolean} append Wether the options will replace the current
* option set or augment it
*/
var set_options = function ( $select, options, append ) {
if ( !append ) {
$select.innerHTML = '';
}
options
.map( function ( option ) {
var $option = document.createElement( 'option' );
$option.value = option.value;
$option.innerText = option.label;
$option.disabled = !!option.disabled;
$option.selected = !!option.selected;
return $option;
} )
.forEach( function ( $option ) {
$select.appendChild( $option );
} );
return $select;
};
// Refreshes an already constructed multi.js instance
var refresh_select = function( select, settings ) {
// Clear columns
select.wrapper.selected.innerHTML = '';
select.wrapper.non_selected.innerHTML = '';
// Add headers to columns
if (settings.non_selected_header && settings.selected_header) {
var non_selected_header = document.createElement("div");
var selected_header = document.createElement("div");
non_selected_header.className = "header";
selected_header.className = "header";
non_selected_header.innerText = settings.non_selected_header;
selected_header.innerText = settings.selected_header;
select.wrapper.non_selected.appendChild(non_selected_header);
select.wrapper.selected.appendChild(selected_header);
}
// Get search value
if ( select.wrapper.search ) {
var query = select.wrapper.search.value;
}
// Loop over select options and add to the non-selected and selected columns
for ( var i = 0; i < select.options.length; i++ ) {
var option = select.options[i];
var value = option.value;
var label = option.textContent || option.innerText;
var row = document.createElement( 'a' );
row.tabIndex = 0;
row.className = 'item';
row.innerHTML = label;
row.setAttribute( 'role', 'button' );
row.setAttribute( 'data-value', value );
row.setAttribute( 'multi-index', i );
if ( option.disabled ) {
row.className += ' disabled';
}
// Add row to selected column if option selected
if ( option.selected ) {
row.className += ' selected';
var clone = row.cloneNode( true );
select.wrapper.selected.appendChild( clone );
}
// Apply search filtering
if ( !query || query && label.toLowerCase().indexOf( query.toLowerCase() ) > -1 ) {
select.wrapper.non_selected.appendChild( row );
}
}
};
var setup = function ( select, settings ) {
// Hide select
select.style.display = 'none';
select.setAttribute( 'data-multijs', true );
// Start constructing selector
var wrapper = document.createElement( 'div' );
wrapper.className = 'multi-wrapper';
// Add search bar
if ( settings.enable_search ) {
var search = document.createElement( 'input' );
search.className = 'search-input';
search.type = 'text';
search.setAttribute( 'placeholder', settings.search_placeholder );
search.addEventListener( 'input', function() {
refresh_select( select, settings );
});
wrapper.appendChild( search );
wrapper.search = search;
}
// Add columns for selected and non-selected
var non_selected = document.createElement( 'div' );
non_selected.className = 'non-selected-wrapper';
var selected = document.createElement( 'div' );
selected.className = 'selected-wrapper';
// Add click handler to toggle the selected status
wrapper.addEventListener( 'click', function ( event ) {
if ( event.target.getAttribute( 'multi-index' ) ) {
toggle_option( select, event );
}
});
// Add keyboard handler to toggle the selected status
wrapper.addEventListener( 'keypress', function ( event ) {
var is_action_key = event.keyCode === 32 || event.keyCode === 13;
var is_option = event.target.getAttribute( 'multi-index' );
if ( is_option && is_action_key ) {
// Prevent the default action to stop scrolling when space is pressed
event.preventDefault();
toggle_option( select, event );
}
});
wrapper.appendChild( non_selected );
wrapper.appendChild( selected );
wrapper.non_selected = non_selected;
wrapper.selected = selected;
select.wrapper = wrapper;
// Add multi.js wrapper after select element
select.parentNode.insertBefore( wrapper, select.nextSibling );
// Initialize selector with values from select element
refresh_select( select, settings );
// Refresh selector when select values change
select.addEventListener( 'change', function() {
refresh_select( select, settings );
});
};
// Intializes and constructs an multi.js instance
var init = function( select, settings ) {
// Check if already initalized
if ( select.dataset.multijs != null ) {
return;
}
// Make sure element is select and multiple is enabled
if ( select.nodeName != 'SELECT' || ! select.multiple ) {
return;
}
/**
* Set up settings (optional parameter) and its default values
*
* Default values:
* enable_search : true
* search_placeholder : 'Search...'
*/
settings = typeof settings !== 'undefined' ? settings : {};
settings['enable_search'] = typeof settings['enable_search'] !== 'undefined' ? settings['enable_search'] : true;
settings['search_placeholder'] = typeof settings['search_placeholder'] !== 'undefined' ? settings['search_placeholder'] : 'Search...';
/**
*
* @param {AjaxOptions} settings.ajax Options to handle the automatic
* fetch of options.
*
* @param {string} endpoint Url where the options will be fetched from
* @param {TransformFunction} transform Intermediate function that allows
* the user to transform the data returned by the endpoint. This
* function should return an array of objects compatible with the
* SelectOption interface.
* @param {boolean} append Wether the options will replace the current
* option set or augment it.
*
* interface AjaxOptions {
* endpoint: string;
* transform?: TransformFunction;
* append?: boolean;
* }
*
* @param {any} data Data returned by the endpoint
*
* @return {SelectOption[]} List of options that should match the
* structure of a SelectOption
*
* declare function TransformFunction ( data: any ): SelectOption[]
*
*/
if ( typeof settings.ajax === 'object' &&
typeof settings.ajax.endpoint === 'string' ) {
fetch( settings.ajax.endpoint )
// Transform the response to JSON
.then( function ( response ) {
return response.json();
} )
// Apply the transform function if provided
.then( function ( data ) {
return ( typeof settings.ajax.transform === 'function' ) ?
settings.ajax.transform( data ):
data;
} )
// Append the fetched options to the select and initialize the
// component
.then( function ( options ) {
select = set_options( select, options, !!settings.ajax.append );
setup( select, settings );
} )
.catch( function ( error ) {
throw error;
});
} else {
setup( select, settings );
}
};
return init;
}());
// Add jQuery wrapper if jQuery is present
if ( typeof jQuery !== 'undefined' ) {
(function($) {
$.fn.multi = function( settings ) {
settings = typeof settings !== 'undefined' ? settings : {};
return this.each( function() {
var $select = $(this);
multi( $select.get(0), settings );
});
}
})(jQuery);
}