Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update filter js #8230

Open
wants to merge 4 commits into
base: 4.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 0 additions & 128 deletions assets/js/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const Admin = {
shared_setup(subject) {
Admin.read_config();
Admin.log('[core|shared_setup] Register services on', subject);
Admin.add_filters(subject);
Admin.setup_select2(subject);
Admin.setup_icheck(subject);
Admin.setup_checkbox_range_selection(subject);
Expand Down Expand Up @@ -256,115 +255,6 @@ const Admin = {
return event.target;
},

add_filters(subject) {
Admin.log('[core|add_filters] configure filters on', subject);

function updateCounter() {
const count = jQuery('a.sonata-toggle-filter .fa-check-square', subject).length;

jQuery('.sonata-filter-count', subject).text(count);
}

jQuery('a.sonata-toggle-filter', subject).on('click', (event) => {
event.preventDefault();
event.stopPropagation();

if (jQuery(event.target).attr('sonata-filter') === 'false') {
return;
}

Admin.log(
'[core|add_filters] handle filter container: ',
jQuery(event.target).attr('filter-container')
);

const filtersContainer = jQuery(`#${jQuery(event.currentTarget).attr('filter-container')}`);

if (jQuery('div[sonata-filter="true"]:visible', filtersContainer).length === 0) {
jQuery(filtersContainer).slideDown();
}

const targetSelector = jQuery(event.currentTarget).attr('filter-target');
const target = jQuery(`div[id="${targetSelector}"]`, filtersContainer);
const filterToggler = jQuery('i', `.sonata-toggle-filter[filter-target="${targetSelector}"]`);
if (jQuery(target).is(':visible')) {
filterToggler
.filter(':not(.fa-minus-circle)')
.removeClass('fa-check-square')
.addClass('fa-square');
target.hide();
} else {
filterToggler
.filter(':not(.fa-minus-circle)')
.removeClass('fa-square')
.addClass('fa-check-square');
target.show();
}

if (jQuery('div[sonata-filter="true"]:visible', filtersContainer).length > 0) {
jQuery(filtersContainer).slideDown();
} else {
jQuery(filtersContainer).slideUp();
}

updateCounter();
});

jQuery('.sonata-filter-form', subject).on('submit', (event) => {
const $form = jQuery(event.target);
$form.find('[sonata-filter="true"]:hidden :input').val('');

if (!event.target.dataset.defaultValues) {
return;
}

const defaults = Admin.convert_query_string_to_object(
jQuery.param({ filter: JSON.parse(event.target.dataset.defaultValues) })
);

// Keep only changed values
$form.find('[name*=filter]').each((index, element) => {
const defaultValue = element.multiple ? [] : '';
const defaultElementValue = defaults[element.name] || defaultValue;
const elementValue = jQuery(element).val() || defaultValue;

if (JSON.stringify(defaultElementValue) === JSON.stringify(elementValue)) {
element.removeAttribute('name');
} else if (element.multiple && JSON.stringify(elementValue) === '[]') {
// Empty array values will not be submitted, but we need to override
// the default value provided by `AdminInterface::getDefaultFilterParameters()`.
// So we change the empty select to an empty input in order to have a submitted value.
// @see https://github.com/sonata-project/SonataAdminBundle/issues/7547

// We remove the `[]` from the select name in order to generate the input name
const name = element.name.substring(0, element.name.length - 2);
$form.append(`<input name="${name}" type="hidden" value="">`);
element.removeAttribute('name');
}
});

// Simulate a reset if no value is different from the default ones.
if ($form.find('[name*=filter]').length === 0) {
$form.append('<input name="filters" type="hidden" value="reset">');
}
});

/* Advanced filters */
if (
jQuery('.advanced-filter :input:visible', subject).filter(function filterWithoutValue() {
return jQuery(this).val();
}).length === 0
) {
jQuery('.advanced-filter').hide();
}

jQuery('[data-toggle="advanced-filter"]', subject).on('click', () => {
jQuery('.advanced-filter').toggle();
});

updateCounter();
},

setup_collection_counter(subject) {
Admin.log('[core|setup_collection_counter] setup collection counter', subject);

Expand Down Expand Up @@ -807,24 +697,6 @@ const Admin = {
});
},

convert_query_string_to_object(str) {
return str.split('&').reduce((accumulator, keyValue) => {
const key = decodeURIComponent(keyValue.split('=')[0]);
const val = keyValue.split('=')[1];

if (key.endsWith('[]')) {
if (!Object.prototype.hasOwnProperty.call(accumulator, key)) {
accumulator[key] = [];
}
accumulator[key].push(val);
} else {
accumulator[key] = val;
}

return accumulator;
}, {});
},

/**
* Remember open tab after refreshing page.
*/
Expand Down
105 changes: 105 additions & 0 deletions assets/js/controllers/filter_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*!
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import qs from 'qs';
import { Controller } from '@hotwired/stimulus';
import { controlValue, convertQueryStringToObject } from '../utils';

export default class extends Controller {
static targets = ['form', 'group', 'advanced', 'submitter'];
static outlets = ['filter-list'];
static values = {
defaultValues: Object,
};

connect() {
const withAdvanced = this.advancedTargets.find((advanced) => !advanced.hidden) !== undefined;
this.advancedTargets.forEach((advanced) => {
advanced.hidden = !withAdvanced;
});
}

prepareSubmit() {
const defaults = convertQueryStringToObject(
qs.stringify({
filter: this.defaultValuesValue,
})
);

const changed = [];
this.formElements.forEach((element) => {
const defaultValue = element.multiple ? [] : '';
const defaultElementValue = defaults[element.name] || defaultValue;
const elementValue = controlValue(element) || defaultValue;

if (element.closest('[hidden]')) {
element.removeAttribute('name');
} else if (JSON.stringify(defaultElementValue) === JSON.stringify(elementValue)) {
element.removeAttribute('name');
} else if (element.multiple && JSON.stringify(elementValue) === '[]') {
// Empty array values will not be submitted, but we need to override
// the default value provided by `AdminInterface::getDefaultFilterParameters()`.
// So we change the empty select to an empty input in order to have a submitted value.
// @see https://github.com/sonata-project/SonataAdminBundle/issues/7547

// We remove the `[]` from the select name in order to generate the input name
const name = element.name.substring(0, element.name.length - 2);
const input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = '';

this.formTarget.appendChild(input);
element.removeAttribute('name');
} else {
changed.push(element);
}
});

if (changed.length === 0) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'filters';
input.value = 'reset';

this.formTarget.appendChild(input);
}

this.submitterTarget.disabled = true;
}

toggleAdvanced() {
this.advancedTargets.forEach((advanced) => {
advanced.hidden = !advanced.hidden;
});
}

toggleFilter(id, state) {
const group = this.groupTargets.find((el) => id === el.id);
if (group) {
group.hidden = !state;
this.element.hidden = !this.visibleGroups.length;
}
}

hideFilter({ params }) {
this.toggleFilter(params.id, false);
this.filterListOutlet.disable(params.id);
}

get visibleGroups() {
return this.groupTargets.filter((group) => !group.hidden);
}

get formElements() {
return Array.from(this.formTarget.elements)
.filter((tag) => ['select', 'textarea', 'input'].includes(tag.tagName.toLowerCase()))
.filter((element) => element.name.includes('filter'));
}
}
47 changes: 47 additions & 0 deletions assets/js/controllers/filter_list_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*!
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { Controller } from '@hotwired/stimulus';

export default class extends Controller {
static targets = ['counter', 'field'];
static outlets = ['filter'];
static classes = ['active'];

connect() {
this.updateCounter();
}

updateCounter() {
this.counterTarget.innerHTML = this.enabledFields.length;
}

disable(id) {
const field = this.fieldTargets.find((el) => id === el.dataset.filter);
if (field) {
field.classList.remove(this.activeClass);
this.updateCounter();
}
}

toggle(event) {
const field = event.target;
const state = field.classList.contains(this.activeClass);
field.classList.toggle(this.activeClass, !state);

this.filterOutlet.toggleFilter(field.dataset.filter, !state);
this.updateCounter();
}

get enabledFields() {
return this.fieldTargets.filter((field) => {
return field.classList.contains(this.activeClass);
});
}
}
37 changes: 37 additions & 0 deletions assets/js/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*!
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

export function controlValue(el) {
if (el.options && el.multiple) {
// prettier-ignore
return el.options
.filter((option) => option.selected)
.map((option) => option.value);
}

return el.value;
}

export function convertQueryStringToObject(str) {
return str.split('&').reduce((accumulator, keyValue) => {
const key = decodeURIComponent(keyValue.split('=')[0]);
const val = keyValue.split('=')[1];

if (key.endsWith('[]')) {
if (!Object.prototype.hasOwnProperty.call(accumulator, key)) {
accumulator[key] = [];
}
accumulator[key].push(val);
} else {
accumulator[key] = val;
}

return accumulator;
}, {});
}
10 changes: 10 additions & 0 deletions assets/scss/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -563,3 +563,13 @@ div.sonata-filters-box div.form-group {
.sonata-search-result-show {
display: block;
}

.sonata-toggle-filter {
i::before {
content: '\f0c8'; // fa-square
}

&.active i::before {
content: '\f14a'; // fa-check-square
}
}
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"symfony/security-core": "^6.4 || ^7.1",
"symfony/security-csrf": "^6.4 || ^7.1",
"symfony/serializer": "^6.4 || ^7.1",
"symfony/stimulus-bundle": "^2.22",
"symfony/string": "^6.4 || ^7.1",
"symfony/translation": "^6.4 || ^7.1",
"symfony/translation-contracts": "^2.3 || ^3.0",
Expand Down
Loading
Loading