scinote-web/app/assets/javascripts/sitewide/dropdown_selector.js

417 lines
14 KiB
JavaScript
Raw Normal View History

2019-08-07 16:25:58 +08:00
/* global PerfectScrollbar activePSB PerfectSb */
/* eslint-disable no-unused-vars, no-use-before-define */
2019-08-02 21:57:41 +08:00
var dropdownSelector = (function() {
2019-08-07 16:25:58 +08:00
// /////////////////////
// Support functions //
// ////////////////////
// Change direction of dropdown depends of container position
function updateDropdownDirection(selector, container) {
var windowHeight = $(window).height();
var containerPosition = container[0].getBoundingClientRect().top;
var containerHeight = container[0].getBoundingClientRect().height;
var containerWidth = container[0].getBoundingClientRect().width;
var bottomSpace = windowHeight - containerPosition - containerHeight;
if (bottomSpace < 280) {
container.addClass('inverse');
container.find('.dropdown-container').css('max-height', `${(containerPosition - 82)}px`)
.css('margin-bottom', `${(containerPosition * -1)}px`)
.css('width', `${containerWidth}px`);
} else {
container.removeClass('inverse');
container.find('.dropdown-container').css('max-height', `${(bottomSpace - 32)}px`)
.css('width', '');
}
}
// Get data in JSON from field
function getCurrentData(container) {
return JSON.parse(container.find('.data-field').val());
}
// Save data to the field
function updateCurrentData(container, data) {
container.find('.data-field').val(JSON.stringify(data));
}
// Search filter for non-ajax data
function filterOptions(selector, container, options) {
var customFilter = selector.data('config').localFilter;
2019-08-07 16:25:58 +08:00
var searchQuery = container.find('.seacrh-field').val();
var data = customFilter ? customFilter(options) : options;
if (searchQuery.length === 0) return data;
return $.grep(data, (n) => {
2019-08-07 16:25:58 +08:00
return n.label.toLowerCase().includes(searchQuery.toLowerCase());
});
}
// Check if all options selected, for non ajax data
function allOptionsSelected(selector, container) {
return JSON.parse(container.find('.data-field').val()).length === selector.find('option').length && !(selector.data('ajax-url'));
}
// Update dropdown selection, based on save data
function refreshDropdownSelection(selector, container) {
container.find('.dropdown-option, .dropdown-group').removeClass('select');
$.each(getCurrentData(container), function(i, selectedOption) {
container.find(`.dropdown-option[data-value="${selectedOption.value}"][data-group="${selectedOption.group}"]`)
.addClass('select');
});
if (selector.data('select-by-group')) {
$.each(container.find('.dropdown-group'), function(gi, group) {
if ($(group).find('.dropdown-option').length === $(group).find('.dropdown-option.select').length) {
$(group).addClass('select');
}
});
}
}
function disableDropdown(selector, container, mode) {
var searchFieldValue = container.find('.seacrh-field');
if (mode) {
updateCurrentData(container, []);
updateTags(selector, container, { skipChange: true });
searchFieldValue.attr('placeholder', selector.data('disable-placeholder'));
container.addClass('disabled').removeClass('open')
.find('.seacrh-field').prop('disabled', true);
} else {
container.removeClass('disabled')
.find('.seacrh-field').prop('disabled', false);
updateTags(selector, container, { skipChange: true });
}
}
2019-08-07 16:25:58 +08:00
// //////////////////////
// Private functions ///
// /////////////////////
// Initialization of dropdown
function generateDropdown(selector, config = {}) {
var selectElement = $(selector);
2019-08-05 23:21:58 +08:00
var optionContainer;
2019-08-07 16:25:58 +08:00
var ps;
var dropdownContainer;
if (selectElement.length === 0) return;
dropdownContainer = selectElement.after('<div class="dropdown-selector-container"></div>').next();
2019-08-06 21:25:52 +08:00
2019-08-07 16:25:58 +08:00
selectElement.data('config', config);
2019-08-06 21:25:52 +08:00
2019-08-02 21:57:41 +08:00
$(`
<div class="dropdown-container"></div>
2019-08-06 21:25:52 +08:00
<div class="input-field">
<input type="text" class="seacrh-field" placeholder="${selectElement.data('placeholder')}"></input>
</div>
<input type="hidden" class="data-field" value="[]">
2019-08-07 16:25:58 +08:00
`).appendTo(dropdownContainer);
2019-08-02 21:57:41 +08:00
2019-08-07 16:25:58 +08:00
if (selectElement.data('select-all-button')) {
2019-08-06 21:25:52 +08:00
$(`<div class="dropdown-select-all btn">${selectElement.data('select-all-button')}</div>`)
.appendTo(dropdownContainer.find('.dropdown-container'))
.click(() => {
if (allOptionsSelected(selectElement, dropdownContainer) || selectElement.data('ajax-url')) {
2019-08-07 16:25:58 +08:00
dropdownContainer.find('.dropdown-group, .dropdown-option').removeClass('select');
2019-08-06 21:25:52 +08:00
} else {
2019-08-07 16:25:58 +08:00
dropdownContainer.find('.dropdown-group, .dropdown-option').addClass('select');
2019-08-06 21:25:52 +08:00
}
2019-08-07 16:25:58 +08:00
saveData(selectElement, dropdownContainer);
});
2019-08-06 21:25:52 +08:00
}
dropdownContainer.find('.seacrh-field').keyup(() => {
2019-08-07 16:25:58 +08:00
loadData(selectElement, dropdownContainer);
2019-08-06 21:25:52 +08:00
}).click((e) =>{
2019-08-07 16:25:58 +08:00
e.stopPropagation();
if (dropdownContainer.hasClass('open')) {
loadData(selectElement, dropdownContainer);
2019-08-06 21:25:52 +08:00
} else {
2019-08-07 16:25:58 +08:00
dropdownContainer.find('.input-field').click();
2019-08-06 21:25:52 +08:00
}
2019-08-07 16:25:58 +08:00
});
2019-08-06 21:25:52 +08:00
2019-08-07 16:25:58 +08:00
ps = new PerfectScrollbar(dropdownContainer.find('.dropdown-container')[0]);
activePSB.push(ps);
2019-08-02 21:57:41 +08:00
2019-08-07 16:25:58 +08:00
optionContainer = dropdownContainer.find('.dropdown-container');
2019-08-05 23:21:58 +08:00
2019-08-02 21:57:41 +08:00
dropdownContainer.find('.input-field').click(() => {
if (dropdownContainer.hasClass('disabled')) return;
2019-08-07 16:25:58 +08:00
$('.dropdown-selector-container').removeClass('active');
dropdownContainer.addClass('active');
$('.dropdown-selector-container:not(.active)').removeClass('open');
optionContainer.scrollTo(0);
dropdownContainer.toggleClass('open');
if (dropdownContainer.hasClass('open')) {
loadData(selectElement, dropdownContainer);
updateDropdownDirection(selectElement, dropdownContainer);
2019-08-02 21:57:41 +08:00
}
2019-08-07 16:25:58 +08:00
});
$(window).resize(function() { updateDropdownDirection(selectElement, dropdownContainer); });
$(window).click(() => { dropdownContainer.removeClass('open'); });
dropdownContainer.click((e) => { e.stopPropagation(); });
2019-08-02 21:57:41 +08:00
2019-08-07 16:25:58 +08:00
selectElement.css('display', 'none');
if (selectElement.data('disable-on-load')) disableDropdown(selectElement, dropdownContainer, true);
2019-08-07 16:25:58 +08:00
updateDropdownDirection(selectElement, dropdownContainer);
2019-08-02 21:57:41 +08:00
}
2019-08-07 16:25:58 +08:00
// Load data to dropdown list
2019-08-06 21:25:52 +08:00
function loadData(selector, container, ajaxData = null) {
2019-08-07 16:25:58 +08:00
var data;
2019-08-06 21:25:52 +08:00
if (ajaxData) {
2019-08-07 16:25:58 +08:00
data = ajaxData;
2019-08-06 21:25:52 +08:00
} else {
2019-08-07 16:25:58 +08:00
data = dataSource(selector, container);
2019-08-06 21:25:52 +08:00
}
2019-08-07 16:25:58 +08:00
// Draw option object
2019-08-06 21:25:52 +08:00
function drawOption(option, group = null) {
2019-08-05 23:21:58 +08:00
return $(`
<div class="dropdown-option checkbox-icon"
2019-08-06 21:25:52 +08:00
data-label="${option.label}"
data-group="${group ? group.value : ''}"
2019-08-05 23:21:58 +08:00
data-value="${option.value}">
2019-08-06 21:25:52 +08:00
${option.label}
2019-08-05 23:21:58 +08:00
</div>"
2019-08-07 16:25:58 +08:00
`);
2019-08-05 23:21:58 +08:00
}
2019-08-07 16:25:58 +08:00
// Draw group object
2019-08-05 23:21:58 +08:00
function drawGroup(group) {
return $(`
<div class="dropdown-group">
<div class="group-name checkbox-icon">${group.label}</div>
</div>
2019-08-07 16:25:58 +08:00
`);
2019-08-05 23:21:58 +08:00
}
2019-08-07 16:25:58 +08:00
// Click action for option object
2019-08-05 23:21:58 +08:00
function clickOption() {
2019-08-07 16:25:58 +08:00
$(this).toggleClass('select');
saveData(selector, container);
2019-08-05 23:21:58 +08:00
}
2019-08-06 21:25:52 +08:00
2019-08-07 16:25:58 +08:00
container.find('.dropdown-group, .dropdown-option').remove();
if (!data || !data.length) return;
2019-08-02 21:57:41 +08:00
2019-08-07 16:25:58 +08:00
if (selector.data('select-by-group')) {
2019-08-06 21:25:52 +08:00
$.each(data, function(gi, group) {
2019-08-07 16:25:58 +08:00
var groupElement = drawGroup(group);
2019-08-06 21:25:52 +08:00
$.each(group.options, function(oi, option) {
2019-08-07 16:25:58 +08:00
var optionElement = drawOption(option, group);
optionElement.click(clickOption);
optionElement.appendTo(groupElement);
});
2019-08-02 21:57:41 +08:00
groupElement.find('.group-name').click(function() {
2019-08-07 16:25:58 +08:00
var groupContainer = $(this).parent();
if (groupContainer.toggleClass('select').hasClass('select')) {
groupContainer.find('.dropdown-option').addClass('select');
} else {
groupContainer.find('.dropdown-option').removeClass('select');
}
saveData(selector, container);
});
groupElement.appendTo(container.find('.dropdown-container'));
});
2019-08-02 21:57:41 +08:00
} else {
2019-08-06 21:25:52 +08:00
$.each(data, function(oi, option) {
2019-08-07 16:25:58 +08:00
var optionElement = drawOption(option);
optionElement.click(clickOption);
optionElement.appendTo(container.find('.dropdown-container'));
});
2019-08-02 21:57:41 +08:00
}
2019-08-07 16:25:58 +08:00
PerfectSb().update_all();
refreshDropdownSelection(selector, container);
2019-08-02 21:57:41 +08:00
}
2019-08-07 16:25:58 +08:00
// Save data to local field
2019-08-05 23:21:58 +08:00
function saveData(selector, container) {
2019-08-07 16:25:58 +08:00
var selectArray = getCurrentData(container);
function findOption(options, option) {
return options.findIndex(x => (x.value === option.dataset.value
&& x.group === option.dataset.group));
}
container.find('.seacrh-field').val('');
$.each(container.find('.dropdown-container .dropdown-option'), function(oi, option) {
2019-08-06 21:25:52 +08:00
var alreadySelected;
var toDelete;
var newOption;
2019-08-07 16:25:58 +08:00
if ($(option).hasClass('select')) {
alreadySelected = findOption(selectArray, option);
if (alreadySelected === -1) {
2019-08-06 21:25:52 +08:00
newOption = {
label: option.dataset.label,
value: option.dataset.value,
group: option.dataset.group
2019-08-07 16:25:58 +08:00
};
selectArray.push(newOption);
2019-08-06 21:25:52 +08:00
}
} else {
2019-08-07 16:25:58 +08:00
toDelete = findOption(selectArray, option);
if (toDelete >= 0) selectArray.splice(toDelete, 1);
2019-08-06 21:25:52 +08:00
}
2019-08-07 16:25:58 +08:00
});
updateCurrentData(container, selectArray);
updateTags(selector, container);
2019-08-05 23:21:58 +08:00
}
2019-08-07 16:25:58 +08:00
// Refresh tags in input field
function updateTags(selector, container, config = {}) {
2019-08-07 16:25:58 +08:00
var selectedOptions = getCurrentData(container);
var searchFieldValue = container.find('.seacrh-field');
2019-08-05 23:21:58 +08:00
2019-08-07 16:25:58 +08:00
// Draw tag and assign event
2019-08-06 21:25:52 +08:00
function drawTag(data) {
2019-08-07 16:25:58 +08:00
var customLabel = selector.data('config').tagLabel;
2019-08-05 23:21:58 +08:00
var tag = $(`<div class="ds-tags" >
2019-08-06 21:25:52 +08:00
<div class="tag-label"
data-ds-tag-group="${data.group}"
data-ds-tag-id=${data.value}>
${customLabel ? customLabel(data) : data.label}
</div>
2019-08-05 23:21:58 +08:00
<i class="fas fa-times"></i>
2019-08-07 16:25:58 +08:00
</div>`).insertBefore(container.find('.input-field .seacrh-field'));
2019-08-05 23:21:58 +08:00
2019-08-07 16:25:58 +08:00
tag.click((e) => { e.stopPropagation(); });
2019-08-05 23:21:58 +08:00
tag.find('.fa-times').click(function(e) {
2019-08-07 16:25:58 +08:00
var tagLabel = $(this).prev();
var toDelete;
e.stopPropagation();
tagLabel.addClass('closing');
2019-08-06 21:25:52 +08:00
setTimeout(() => {
if (selector.data('combine-tags')) {
2019-08-07 16:25:58 +08:00
container.find('.data-field').val('[]');
updateTags(selector, container);
2019-08-06 21:25:52 +08:00
} else {
2019-08-07 16:25:58 +08:00
selectedOptions = getCurrentData(container);
toDelete = selectedOptions.findIndex(x => (String(x.value) === String(tagLabel.data('ds-tag-id'))
&& String(x.group) === String(tagLabel.data('ds-tag-group'))
));
selectedOptions.splice(toDelete, 1);
updateCurrentData(container, selectedOptions);
updateTags(selector, container);
2019-08-06 21:25:52 +08:00
}
2019-08-07 16:25:58 +08:00
}, 150);
2019-08-05 23:21:58 +08:00
});
}
2019-08-07 16:25:58 +08:00
container.find('.ds-tags').remove();
if (selector.data('combine-tags')) {
2019-08-05 23:21:58 +08:00
if (selectedOptions.length === 1) {
2019-08-07 16:25:58 +08:00
drawTag({ label: selectedOptions[0].label, value: selectedOptions[0].value });
2019-08-05 23:21:58 +08:00
} else if (allOptionsSelected(selector, container)) {
2019-08-07 16:25:58 +08:00
drawTag({ label: selector.data('select-multiple-all-selected'), value: 0 });
2019-08-05 23:21:58 +08:00
} else if (selectedOptions.length > 1) {
2019-08-07 16:25:58 +08:00
drawTag({ label: `${selectedOptions.length} ${selector.data('select-multiple-name')}`, value: 0 });
2019-08-05 23:21:58 +08:00
}
} else {
2019-08-06 21:25:52 +08:00
$.each(selectedOptions, (ti, tag) => {
2019-08-07 16:25:58 +08:00
drawTag(tag);
});
2019-08-06 21:25:52 +08:00
}
2019-08-07 16:25:58 +08:00
searchFieldValue.attr('placeholder',
2019-08-06 21:25:52 +08:00
selectedOptions.length > 0 ? '' : selector.data('placeholder'));
2019-08-05 23:21:58 +08:00
if (!selector.data('combine-tags')) {
2019-08-07 16:25:58 +08:00
container.find('.ds-tags').addClass('stretch');
2019-08-06 21:25:52 +08:00
} else {
2019-08-07 16:25:58 +08:00
container.find('.ds-tags').removeClass('stretch');
2019-08-06 21:25:52 +08:00
}
2019-08-07 16:25:58 +08:00
updateDropdownDirection(selector, container);
refreshDropdownSelection(selector, container);
if (selector.data('config').onChange && !config.skipChange) {
2019-08-07 16:25:58 +08:00
selector.data('config').onChange();
}
2019-08-06 21:25:52 +08:00
}
2019-08-07 16:25:58 +08:00
// Convert local data or ajax data to same format
function dataSource(selector, container) {
2019-08-06 21:25:52 +08:00
var result = [];
var groups;
var options;
var defaultParams;
var customParams;
var ajaxParams;
2019-08-07 16:25:58 +08:00
if (selector.data('ajax-url')) {
defaultParams = { query: container.find('.seacrh-field').val() };
customParams = selector.data('config').ajaxParams;
ajaxParams = customParams ? customParams(defaultParams) : defaultParams;
2019-08-06 21:25:52 +08:00
$.get(selector.data('ajax-url'), ajaxParams, (data) => {
2019-08-07 16:25:58 +08:00
loadData(selector, container, data);
});
} else if (selector.data('select-by-group')) {
groups = selector.find('optgroup');
$.each(groups, (gi, group) => {
var groupElement = { label: group.label, value: group.label, options: [] };
var groupOptions = filterOptions(selector, container, $(group).find('option'));
2019-08-07 16:25:58 +08:00
$.each(groupOptions, function(oi, option) {
groupElement.options.push({ label: option.innerHTML, value: option.value });
});
if (groupElement.options.length > 0) result.push(groupElement);
});
} else {
options = filterOptions(selector, container, selector.find('option'));
2019-08-07 16:25:58 +08:00
$.each(options, function(oi, option) {
result.push({ label: option.innerHTML, value: option.value });
});
2019-08-06 21:25:52 +08:00
}
2019-08-07 16:25:58 +08:00
return result;
2019-08-05 23:21:58 +08:00
}
2019-08-07 16:25:58 +08:00
// ////////////////////
// Public functions ///
// ////////////////////
2019-08-02 21:57:41 +08:00
return {
2019-08-07 16:25:58 +08:00
// Dropdown initialization
2019-08-06 21:25:52 +08:00
init: (selector, config) => {
2019-08-07 16:25:58 +08:00
generateDropdown(selector, config);
2019-08-02 21:57:41 +08:00
},
2019-08-07 16:25:58 +08:00
// Update dropdown position
updateDropdownDirection: (selector) => {
if ($(selector).length === 0) return false;
2019-08-07 16:25:58 +08:00
updateDropdownDirection($(selector), $(selector).next());
2019-08-06 21:25:52 +08:00
},
2019-08-07 16:25:58 +08:00
// Get only values
2019-08-06 21:25:52 +08:00
getValues: (selector) => {
if ($(selector).length === 0) return false;
2019-08-07 16:25:58 +08:00
return $.map(getCurrentData($(selector).next()), (v) => {
return v.value;
});
},
// Get all data
getData: (selector) => {
if ($(selector).length === 0) return false;
2019-08-07 16:25:58 +08:00
return getCurrentData($(selector).next());
},
setData: (selector, data) => {
if ($(selector).length === 0) return false;
updateCurrentData($(selector).next(), data);
refreshDropdownSelection($(selector), $(selector).next());
updateTags($(selector), $(selector).next());
},
disableSelector: (selector, mode) => {
if ($(selector).length === 0) return false;
disableDropdown($(selector), $(selector).next(), mode);
2019-08-02 21:57:41 +08:00
}
};
2019-08-07 16:25:58 +08:00
}());