@excelwebzone/symfony-admin-ui
Version:
Symfony Admin UI is a simple set of UI behaviors and components used with your [symfony-admin](https://github.com/excelwebzone/symfony-admin-bundle) application.
570 lines (456 loc) • 18.6 kB
JavaScript
import $ from 'jquery';
import _ from 'underscore';
import moment from 'moment';
import toaster from '../../lib/utils/toaster';
import axios from '../../lib/utils/axios_utils';
import { withoutEmpty } from '../../lib/utils/form_parsing';
import { getParameterValues } from '../../lib/utils/url_utility';
import { dateRanges, getDateRange, initTagsPicker, initDateRangePicker } from './filter_range';
const filterItemLI = (id, title, filters) => `
<li class="option-list-item option-list-item-filter option-list-item-has-overflow js-filter-item" data-id="${id}" data-filter='${filters}'>
<div class="option-list-item-right-content">
<div class="option-list-item-hover-content">
<i class="ledger-icons ledger-icon ledger-icon-lock"></i>
</div>
</div>
<div class="option-list-item-text">${title}</div>
</li>
`;
export default class Filter {
constructor(containerEl) {
this.currentFilter = {};
this.filters = '{}';
this.isLoading = false;
this.compareCheck = true;
this.initDomElements(containerEl);
this.bindEvents();
for (let picker of this.$form.find('.js-daterangepicker')) {
initDateRangePicker(picker);
}
for (let picker of this.$form.find('.js-tagspicker')) {
initTagsPicker(picker);
}
// @hack: remove "empty" unselected values
for (let checkbox of this.$form.find('input.checkbox-input')) {
$(checkbox).prev().remove();
}
}
initDomElements(containerEl) {
this.$container = containerEl ? $(containerEl) : $('body');
this.$form = this.$container.find('.js-filter-form > form');
this.$searchField = this.$container.find('.list-search .input-text');
this.$clearButton = this.$container.find('.js-filter-clear-button');
this.$saveButton = this.$container.find('.js-filter-save-button');
this.$updateButton = this.$container.find('.js-filter-update-button');
this.$counter = this.$container.find('.js-filter-counter');
this.$noPrivateFilters = this.$container.find('.js-no-private-filters');
this.$noPublicFilters = this.$container.find('.js-no-public-filters');
}
bindEvents() {
this.$clearButton.on('click', () => this.clearFilter());
this.$updateButton.on('click', (e) => this.updateActiveFilter(e));
this.$saveButton.on('modal:shown', (e, modal) => this.createNew(e, modal));
this.$searchField.on('keydown', _.debounce(() => this.setSearchKeyword(), 1300));
this.$container.on('click', '.js-filter-item .js-filter-item-default', (e) => this.setDefaultFilter(e));
this.$container.on('modal:shown', '.js-filter-item .js-filter-item-update', (e, modal) => this.editFilter(e, modal));
this.$container.on('modal:shown', '.js-filter-item .js-filter-item-delete', (e, modal) => this.deleteFilter(e, modal));
}
getActiveFilter() {
return this.$container.find('.js-filter-active .js-filter-item');
}
preloadFilters(filters) {
if (!filters) {
return;
}
if (this.getActiveFilter().data('id') === 'preload') {
this.getActiveFilter().attr('data-filter', filters);
this.getActiveFilter().data('filter', JSON.parse(filters));
this.getActiveFilter().click();
return;
}
// show all options
for (let element of this.$container.find('.js-filter-list .js-filter-item')) {
$(element).show();
}
this.$container.find('.js-filter-active .js-filter-item').replaceWith(
filterItemLI(
'preload',
'Preload Search',
filters
)
);
this.getActiveFilter().click();
}
clearFilter() {
if (this.getActiveFilter().data('id') === 'preload') {
this.loadDefaultFilter();
} else {
this.loadActiveFilter();
}
}
loadActiveFilter() {
this.getActiveFilter().click();
this.$container.find(`.js-filter-list .js-filter-item[data-id="${this.getActiveFilter().data('id')}"]`).hide();
}
loadDefaultFilter() {
this.$container.find('.js-filter-list .js-filter-item.is-default').click();
}
toggleNoFilters(filterId) {
const selector = filterId
? `li:not(.is-hidden):not([data-id=${filterId}])`
: 'li:not(.is-hidden)';
let $nextFilter = this.$noPrivateFilters.nextAll(selector);
if ($nextFilter.length === 0 || $($nextFilter[0]).hasClass('js-public-filters')) {
this.$noPrivateFilters.show();
} else {
this.$noPrivateFilters.hide();
}
$nextFilter = this.$noPublicFilters.nextAll(selector);
if ($nextFilter.length === 0) {
this.$noPublicFilters.show();
} else {
this.$noPublicFilters.hide();
}
}
setDefaultFilter(e) {
const $currentTarget = $(e.currentTarget);
const $filterItem = $currentTarget.closest('.js-filter-item');
const url = $currentTarget.data('endpoint');
axios.post(url)
.then(({ data }) => {
this.$container.find('.js-filter-item .js-filter-item-default')
.removeClass('ledger-icon-star')
.addClass('ledger-icon-star-outline')
.parent()
.show();
this.$container.find('.js-filter-item.is-default').removeClass('is-default');
this.$container.find('.js-filter-item .js-default-item-icon').remove();
for (let item of this.$container.find(`.js-filter-item[data-id="${$filterItem.data('id')}"]`)) {
$(item).addClass('is-default');
$(item).find('.js-filter-item-default')
.addClass('ledger-icon-star')
.removeClass('ledger-icon-star-outline')
.parent()
.hide();
$(item).find('.option-list-item-right-content').prepend('<i class="ledger-icons ledger-icon-star js-default-item-icon"></i>');
}
toaster('Default Filter changed to ' + $filterItem.find('.option-list-item-text').text());
});
e.stopPropagation();
}
cleanFilterJSON(filters) {
filters = JSON.parse(filters);
for (let field of this.$form.data('ignore-fields') || []) {
delete filters[field];
}
return JSON.stringify(filters);
}
createNew(e, modal) {
$(modal).find('[id$="_params"]').val(this.cleanFilterJSON(this.filters));
$(modal).find('[id$="_report"]').val(this.$saveButton.data('report'));
$(modal).find('[id$="_section"]').val(this.$saveButton.data('section'));
$(modal).on('modal:hidden', (e, data) => {
$(modal).off('modal:hidden');
const li = filterItemLI(
data.id,
data.label,
data.params
);
if (data.isPublic || false) {
this.$noPublicFilters.hide();
this.$container.find('.js-filter-list>ul').append(li);
} else {
this.$noPrivateFilters.hide();
$(li).insertAfter(this.$noPrivateFilters);
}
this.$container.find(`.js-filter-item[data-id="${data.id}"]`).click();
});
}
updateActiveFilter(e) {
const $currentTarget = $(e.currentTarget);
const id = this.getActiveFilter().data('id');
const url = $currentTarget.data('endpoint').replace('__ID__', id);
const filters = this.cleanFilterJSON(this.filters);
axios.put(url, { params: filters })
.then(({ data }) => {
this.$clearButton.hide();
this.$saveButton.hide();
this.$updateButton.hide();
this.currentFilter = JSON.parse(filters);
for (let item of this.$container.find(`.js-filter-item[data-id="${id}"]`)) {
$(item).attr('data-filter', filters);
$(item).data('filter', this.currentFilter);
}
toaster(data.label + ' updated');
});
}
editFilter(e, modal) {
const $currentTarget = $(e.currentTarget);
const $filterItem = $currentTarget.closest('.js-filter-item');
$(modal).on('modal:hidden', (e, data) => {
$(modal).off('modal:hidden');
$filterItem.attr('data-filter', JSON.stringify(data.params));
$filterItem.data('filter', data.params);
$filterItem.find('.option-list-item-text').html(data.label);
});
}
deleteFilter(e, modal) {
$(modal).on('modal:hidden', (e, data) => {
$(modal).off('modal:hidden');
// delete all items
this.$container.find(`.js-filter-item[data-id="${data.id}"]`).remove();
// mark "public" default as default
if (this.$container.find('.js-filter-item.is-default').length === 0) {
for (let item of this.$container.find('.js-filter-item.is-public-default')) {
$(item).addClass('is-default');
$(item).find('.js-filter-item-default')
.addClass('ledger-icon-star')
.removeClass('ledger-icon-star-outline')
.parent()
.hide();
$(item).find('.option-list-item-right-content').prepend('<i class="ledger-icons ledger-icon-star js-default-item-icon"></i>');
}
}
this.toggleNoFilters(data.id);
if (this.getActiveFilter().length === 0) {
this.$container.find('.js-filter-active>ul').replaceWith('<li class="js-filter-item"></li>');
this.loadDefaultFilter();
}
});
}
setSearchKeyword() {
this.$form.find(this.$searchField.data('filter-field')).val(this.$searchField.val()).trigger('change');
}
setCurrentFilter(e) {
if ($(e.target).hasClass('js-entity-modal')
|| $(e.target).hasClass('js-entity-modal-template')
) {
return false;
}
const $filterItem = $(e.currentTarget);
const $activeItem = this.getActiveFilter();
const $hiddenItem = this.$container.find(`.js-filter-list .js-filter-item[data-id="${$activeItem.data('id')}"]`);
// @hack: show before moving
$filterItem.show().removeClass('is-hidden');
$activeItem.replaceWith($filterItem.clone());
$hiddenItem.show().removeClass('is-hidden');
$filterItem.hide().addClass('is-hidden');
if ($filterItem.data('id') === $hiddenItem.data('id')) {
$hiddenItem.hide().addClass('is-hidden');
}
// style as active
this.getActiveFilter().addClass('option-list-item-active');
this.currentFilter = $filterItem.data('filter') || {};
// get url filters
let values = [];
try {
values = JSON.parse(getParameterValues('filters'));
} catch (e) {}
// override fields
for (let field of this.$form.data('ignore-fields') || []) {
if (typeof values[field] !== 'undefined') {
this.currentFilter[field] = values[field];
}
}
this.loadFilters();
this.toggleNoFilters();
return true;
}
loadFilters() {
this.isLoading = true;
this.$form[0].reset();
// force resetting special fields
this.$searchField.val('');
this.$container.find(`input[name="${this.$form.prop('name')}[search]"]`, this.$form).val('');
for (let element of this.$form.find('input[type="hidden"]:not([id$="_token"])')) {
$(element).val('');
}
for (let element of this.$form.find('input.checkbox-input')) {
$(element).prop('checked', false);
}
// convert selected filter into this.$form.serializeArray()
let params = {};
for (let [key, value] of Object.entries(this.currentFilter)) {
let fieldName = `${this.$form.prop('name')}[${key}]`;
if (_.isArray(value)) {
params[`${fieldName}[]`] = value;
} else if (_.isObject(value)) {
for (let [k, v] of Object.entries(value)) {
params[`${fieldName}[${k}]`] = v;
}
} else {
params[fieldName] = value;
}
}
// populate form
this.$form.deserialize(params);
// mark dropdown as empty
this.$form.find('.dropdown').removeClass('has-value');
// reset regular dropdown
for (let element of this.$container.find('.dropdown.js-select-dropdown', this.$form)) {
$(element).find('.dropdown-reset').hide();
if ($(element).find('.dropdown-text')) {
$(element).find('.dropdown-text').addClass('dropdown-placeholder').html($(element).data('placeholder') || '');
}
}
// show and unselect all options
this.$form.find('.dropdown.js-select-dropdown,.dropdown.js-select-dropdown-multiple')
.find('.option-list-item')
.removeClass('is-selected is-highlighted')
.show();
// remove all preselected tags
this.$form.find('.dropdown-tags').html('');
// force "onload" event (update sync dropdown)
this.$form.find('.dropdown').find('input[type=hidden],select').trigger('onload');
// auto select option items
for (let option of this.$container.find('select option:selected', this.$form)) {
$(option).closest('.dropdown').find(`.option-list-item[data-value="${$(option).val()}"]`).click();
}
// populate search keyword
if (params[`${this.$form.prop('name')}[search]`]) {
this.$searchField.val(params[`${this.$form.prop('name')}[search]`]);
}
// update data range selections
for (let element of this.$container.find('.js-autocomplete-dropdown', this.$form)) {
const selectedVal = $(element).find('input[type=hidden]').val();
if (selectedVal.length) {
axios.get($(element).data('endpoint'), {
params: {
id: selectedVal.split('|')
}
})
.then(({ data }) => {
for (let [key, value] of Object.entries(data.options)) {
if ($(element).hasClass('js-select-dropdown-multiple')) {
$(element).find('.dropdown-tags').append(`
<div class="tag tag-interactive">
<div class="tag-display-name">${value.label ? value.label : value}</div>
<span class="tag-action" data-value="${key}">
<i class="ledger-icons ledger-icon-close-circle"></i>
</span>
</div>
`);
if ($(element).find('.dropdown-tags .tag').length) {
$(element).addClass('has-value');
}
} else if (key === selectedVal) {
$(element).addClass('has-value');
$(element).find('.dropdown-reset').show();
$(element).find('.dropdown-text').removeClass('dropdown-placeholder').html(value.label ? value.label : value);
}
}
});
} else {
$(element).find('.dropdown-reset').click();
}
}
// update data range selections
for (let element of this.$container.find('.js-daterangepicker', this.$form)) {
$(element).find('.filter-range-label>a').html('Select Date Range');
const unitVal = this.$container.find('#' + $(element).data('filter-field') + '_unit').val();
let fromVal = this.$container.find('#' + $(element).data('filter-field') + '_from').val();
let toVal = this.$container.find('#' + $(element).data('filter-field') + '_to').val();
if (_.isEmpty(unitVal) && _.isEmpty(fromVal) && _.isEmpty(toVal)) {
continue;
}
if (!_.isEmpty(unitVal)) {
const dateRange = getDateRange(unitVal);
if (dateRange) {
fromVal = dateRange[0].format('YYYY-MM-DD');
toVal = dateRange[1].format('YYYY-MM-DD');
}
}
const start = moment(fromVal);
const end = moment(toVal);
let label = null;
for (let [key, value] of Object.entries(dateRanges)) {
if (value[0].isSame(start, 'day') && value[1].isSame(end, 'day')) {
label = key;
}
}
$(element).find('.filter-range-label>a').html(label || start.format('YYYY-MM-DD') + ' to ' + end.format('YYYY-MM-DD'));
if (!label && start.isSame(end, 'day')) {
$(element).find('.filter-range-label>a').html(start.format('YYYY-MM-DD'));
}
}
// update tags selections
for (let element of this.$container.find('.js-tagspicker', this.$form)) {
$(element).find('.filter-range-label>a').html('Select Tags');
const val = this.$container.find('#' + $(element).data('filter-field')).val();
if (_.isEmpty(val)) {
continue;
}
$(element).find('.filter-range-label>a').html(val.split('|').length + ' Tags');
}
this.isLoading = false;
}
getData() {
// exit while loading filters
if (this.isLoading) {
return;
}
// set parameters to post
let params = this.$form.serializeObject();
params = params[this.$form.prop('name')];
params = withoutEmpty(params);
// get multi autocomplete field names
const acFields = [];
for (let element of this.$container.find('.js-autocomplete-dropdown.js-select-dropdown-multiple', this.$form)) {
acFields.push($(element).find('input[type=hidden]').prop('id').substring(this.$form.prop('name').length + 1));
}
// @hack: remove _token (not required)
delete params._token;
this.filters = JSON.stringify(params);
params = JSON.parse(this.cleanFilterJSON(this.filters));
if (this.compareCheck) {
for (let element of this.$container.find('.js-filter-list .js-filter-item:not(.option-list-item-active):not(.is-hidden)')) {
if (_.isEqual(params, $(element).data('filter'))) {
this.compareCheck = false;
$(element).click();
return;
}
}
}
this.compareCheck = true;
const isSameFilter = _.isEqual(params, this.currentFilter) && this.getActiveFilter().data('id') !== 'preload';
// reset all label counts
this.$container.find('.filter-label>span').text('');
// show total filter selected
let counter = 0;
for (let [key, value] of Object.entries(params)) {
let total = _.isArray(value) ? value.length : 1;
if (acFields.indexOf(key) !== -1) {
total = value.split('|').length;
}
counter += total;
const $label = this.$container.find(`[name^="${this.$form.prop('name')}[${key}]"]`).closest('.filter').find('.filter-label>span');
if ($label) {
if (total) {
$label.text(`(${total})`);
} else {
$label.text('');
}
}
}
this.$counter.text(counter).show();
if (counter === 0) {
this.$counter.hide();
}
// toggle buttons
this.$clearButton.toggle(!isSameFilter);
this.$saveButton.toggle(!isSameFilter);
this.$updateButton.toggle(!isSameFilter && !this.getActiveFilter().hasClass('is-locked'));
if (this.$updateButton.is(':visible')) {
this.$saveButton
.removeClass('btn-primary w-100')
.addClass('btn-flat-primary')
.text('Save New Filter');
} else {
this.$saveButton
.removeClass('btn-flat-primary')
.addClass('btn-primary w-100')
.text('Save Filter');
}
return this.filters;
}
}