Skip to content

Persistent/default filters #13499

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

Merged
merged 2 commits into from
Aug 2, 2023
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Element slideouts now show their sidebar content full-screen for elements without a field layout, rather than having an empty body. ([#13056](https://github.com/craftcms/cms/pull/13056), [#13053](https://github.com/craftcms/cms/issues/13053))
- Relational fields no longer track the previously-selected element(s) when something outside the field is clicked on. ([#13123](https://github.com/craftcms/cms/issues/13123))
- Element indexes now use field layouts’ overridden field labels, if all field layouts associated with an element source use the same label. ([#8903](https://github.com/craftcms/cms/discussions/8903))
- Element indexes now track souces’ filters in the URL, so they can be sharable and persisted when navigating back to the index page via the browser history. ([#13499](https://github.com/craftcms/cms/pull/13499))
- Improved the styling and max height of Selectize inputs. ([#13065](https://github.com/craftcms/cms/discussions/13065), [#13176](https://github.com/craftcms/cms/pull/13176))
- Selectize inputs now support click-and-drag selection. ([#13273](https://github.com/craftcms/cms/discussions/13273))
- Selectize single-select inputs now automatically select the current value on focus. ([#13273](https://github.com/craftcms/cms/discussions/13273))
Expand Down Expand Up @@ -42,6 +43,7 @@
- When applying a draft, the canonical elements’ `getDirtyAttributes()` and `getDirtyFields()` methods now return the attribute names and field handles that were modified on the draft for save events. ([#12967](https://github.com/craftcms/cms/issues/12967))
- Admin tables can be configured to pass custom query params to the data endpoint. ([#13416](https://github.com/craftcms/cms/pull/13416))
- Admin tables can now be programatically reloaded. ([#13416](https://github.com/craftcms/cms/pull/13416))
- Native element sources can now define a `defaultFilter` key, which defines the default filter condition that should be applied when the source is selected. ([#13499](https://github.com/craftcms/cms/pull/13499))
- Added `craft\addresses\SubdivisionRepository`. ([#13361](https://github.com/craftcms/cms/pull/13361))
- Added `craft\base\Element::thumbSvg()`. ([#13262](https://github.com/craftcms/cms/pull/13262))
- Added `craft\base\ElementInterface::getThumbHtml()`.
Expand Down
2 changes: 2 additions & 0 deletions src/base/ElementInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ public static function statuses(): array;
* the attribute. (Optional)
* - **`defaultSort`** – A string identifying the sort attribute that should be selected by default, or an array where
* the first value identifies the sort attribute, and the second determines which direction to sort by. (Optional)
* - **`defaultFilter`** – An element condition instance or config, which should be used by default when the source
* is first selected.
* - **`hasThumbs`** – A bool that defines whether this source supports Thumbs View. (Use your element’s
* [[getThumbUrl()]] method to define your elements’ thumb URL.) (Optional)
* - **`structureId`** – The ID of the Structure that contains the elements in this source. If set, Structure View
Expand Down
36 changes: 30 additions & 6 deletions src/controllers/ElementIndexesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use craft\elements\db\ElementQueryInterface;
use craft\elements\exporters\Raw;
use craft\events\ElementActionEvent;
use craft\helpers\Component;
use craft\helpers\Cp;
use craft\helpers\ElementHelper;
use craft\services\ElementSources;
Expand Down Expand Up @@ -418,7 +419,24 @@ public function actionFilterHud(): Response
/** @phpstan-var class-string<ElementInterface>|ElementInterface $elementType */
$elementType = $this->elementType();
$id = $this->request->getRequiredBodyParam('id');
$condition = $elementType::createCondition();
$conditionConfig = $this->request->getBodyParam('conditionConfig');
$serialized = $this->request->getBodyParam('serialized');

$conditionsService = Craft::$app->getConditions();

if ($conditionConfig) {
$conditionConfig = Component::cleanseConfig($conditionConfig);
/** @var ElementConditionInterface $condition */
$condition = $conditionsService->createCondition($conditionConfig);
} elseif ($serialized) {
parse_str($serialized, $conditionConfig);
/** @var ElementConditionInterface $condition */
$condition = $conditionsService->createCondition($conditionConfig['condition']);
} else {
/** @var ElementConditionInterface $condition */
$condition = $elementType::createCondition();
}

$condition->mainTag = 'div';
$condition->id = $id;
$condition->addRuleLabel = Craft::t('app', 'Add a filter');
Expand All @@ -429,7 +447,7 @@ public function actionFilterHud(): Response
$condition->sourceKey = $this->sourceKey;
} else {
/** @var ElementConditionInterface $sourceCondition */
$sourceCondition = Craft::$app->getConditions()->createCondition($this->source['condition']);
$sourceCondition = $conditionsService->createCondition($this->source['condition']);
$condition->queryParams = [];
foreach ($sourceCondition->getConditionRules() as $rule) {
/** @var ElementConditionRuleInterface $rule */
Expand Down Expand Up @@ -595,11 +613,17 @@ protected function elementQuery(): ElementQueryInterface
}

// Override with the custom filters
$filterConditionStr = $this->request->getBodyParam('filters');
if ($filterConditionStr) {
parse_str($filterConditionStr, $filterConditionConfig);
$filterConditionConfig = $this->request->getBodyParam('filterConfig');
if (!$filterConditionConfig) {
$filterConditionStr = $this->request->getBodyParam('filters');
if ($filterConditionStr) {
parse_str($filterConditionStr, $filterConditionConfig);
$filterConditionConfig = $filterConditionConfig['condition'];
}
}
if ($filterConditionConfig) {
/** @var ElementConditionInterface $filterCondition */
$filterCondition = $conditionsService->createCondition($filterConditionConfig['condition']);
$filterCondition = $conditionsService->createCondition(Component::cleanseConfig($filterConditionConfig));
$filterCondition->modifyQuery($query);
}

Expand Down
25 changes: 17 additions & 8 deletions src/services/ElementSources.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace craft\services;

use Craft;
use craft\base\conditions\ConditionInterface;
use craft\base\ElementInterface;
use craft\base\PreviewableFieldInterface;
use craft\base\SortableFieldInterface;
Expand Down Expand Up @@ -366,17 +367,25 @@ private function _nativeSources(string $elementType, string $context): array
/** @phpstan-var class-string<ElementInterface>|ElementInterface $elementType */
$sources = $elementType::sources($context);
$normalized = [];

foreach ($sources as $source) {
if (isset($source['type'])) {
$normalized[] = $source;
} elseif (array_key_exists('heading', $source)) {
$source['type'] = self::TYPE_HEADING;
$normalized[] = $source;
} elseif (isset($source['key'])) {
$source['type'] = self::TYPE_NATIVE;
$normalized[] = $source;
if (!isset($source['type'])) {
if (array_key_exists('heading', $source)) {
$source['type'] = self::TYPE_HEADING;
} elseif (isset($source['key'])) {
$source['type'] = self::TYPE_NATIVE;
} else {
continue;
}
}

if (isset($source['defaultFilter']) && $source['defaultFilter'] instanceof ConditionInterface) {
$source['defaultFilter'] = $source['defaultFilter']->getConfig();
}

$normalized[] = $source;
}

return $normalized;
}

Expand Down
1 change: 1 addition & 0 deletions src/templates/_elements/sources.twig
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
sites: (source.sites ?? false) ? source.sites|join(',') : false,
'override-status': (source.criteria.status ?? false) ? true : false,
disabled: source.disabled ?? false,
'default-filter': source.defaultFilter ?? false,
}|merge(source.data ?? {}),
html: _self.sourceInnerHtml(source)
}) }}
Expand Down
2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/cp.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/cp/dist/cp.js.map

Large diffs are not rendered by default.

111 changes: 92 additions & 19 deletions src/web/assets/cp/src/js/BaseElementIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,24 @@ Craft.BaseElementIndex = Garnish.Base.extend(
this.searchText = queryParams.search;
}

// Respect the initial filters
// ---------------------------------------------------------------------

if (queryParams.filters) {
this.createFilterHud({
showOnInit: false,
serialized: queryParams.filters,
});
} else if (
this.$source.data('default-filter') &&
!this.filterHudExists()
) {
this.createFilterHud({
showOnInit: false,
conditionConfig: this.$source.data('default-filter'),
});
}

// Select the default sort attribute/direction
// ---------------------------------------------------------------------

Expand Down Expand Up @@ -1371,8 +1389,11 @@ Craft.BaseElementIndex = Garnish.Base.extend(
if (
this.filterHuds[this.siteId] &&
this.filterHuds[this.siteId][this.sourceKey] &&
this.filterHuds[this.siteId][this.sourceKey].serialized
(this.filterHuds[this.siteId][this.sourceKey].conditionConfig ||
this.filterHuds[this.siteId][this.sourceKey].serialized)
) {
params.filterConfig =
this.filterHuds[this.siteId][this.sourceKey].conditionConfig;
params.filters =
this.filterHuds[this.siteId][this.sourceKey].serialized;
}
Expand Down Expand Up @@ -3226,25 +3247,43 @@ Craft.BaseElementIndex = Garnish.Base.extend(
}
},

filterHudExists: function () {
return (
this.filterHuds[this.siteId] &&
this.filterHuds[this.siteId][this.sourceKey]
);
},

showFilterHud: function () {
if (!this.filterHuds[this.siteId]) {
this.filterHuds[this.siteId] = {};
}
if (!this.filterHuds[this.siteId][this.sourceKey]) {
this.filterHuds[this.siteId][this.sourceKey] = new FilterHud(
this,
this.sourceKey,
this.siteId
);
this.updateFilterBtn();
if (!this.filterHudExists()) {
this.createFilterHud();
} else {
this.filterHuds[this.siteId][this.sourceKey].show();
}
},

createFilterHud: function (settings) {
if (!this.filterHuds[this.siteId]) {
this.filterHuds[this.siteId] = {};
}

this.filterHuds[this.siteId][this.sourceKey] = new FilterHud(
this,
this.sourceKey,
this.siteId,
settings
);

this.updateFilterBtn();
},

updateFilterBtn: function () {
this.$filterBtn.removeClass('active');

if (this.settings.context === 'index') {
Craft.setQueryParam('filters', null);
}

if (
this.filterHuds[this.siteId] &&
this.filterHuds[this.siteId][this.sourceKey]
Expand All @@ -3261,11 +3300,15 @@ Craft.BaseElementIndex = Garnish.Base.extend(
: 'false'
);

if (
this.filterHuds[this.siteId][this.sourceKey].showing ||
this.filterHuds[this.siteId][this.sourceKey].hasRules()
) {
if (this.filterHuds[this.siteId][this.sourceKey].isActive) {
this.$filterBtn.addClass('active');

if (this.settings.context === 'index') {
Craft.setQueryParam(
'filters',
this.filterHuds[this.siteId][this.sourceKey].serialized
);
}
}
} else {
this.$filterBtn.attr('aria-controls', null);
Expand Down Expand Up @@ -3738,16 +3781,31 @@ const FilterHud = Garnish.HUD.extend({
siteId: null,
id: null,
loading: true,
conditionConfig: null,
serialized: null,
$clearBtn: null,
cleared: false,

init: function (elementIndex, sourceKey, siteId) {
get isActive() {
return this.showing || this.conditionConfig || this.serialized;
},

init: function (elementIndex, sourceKey, siteId, settings) {
this.elementIndex = elementIndex;
this.sourceKey = sourceKey;
this.siteId = siteId;
this.id = `filter-${Math.floor(Math.random() * 1000000000)}`;

if (settings) {
if (settings.conditionConfig) {
this.conditionConfig = settings.conditionConfig;
delete settings.conditionConfig;
} else if (settings.serialized) {
this.serialized = settings.serialized;
delete settings.serialized;
}
}

const $loadingContent = $('<div/>')
.append(
$('<div/>', {
Expand All @@ -3762,9 +3820,16 @@ const FilterHud = Garnish.HUD.extend({
})
);

this.base(this.elementIndex.$filterBtn, $loadingContent, {
hudClass: 'hud element-filter-hud loading',
});
this.base(
this.elementIndex.$filterBtn,
$loadingContent,
Object.assign(
{
hudClass: 'hud element-filter-hud loading',
},
settings
)
);

this.$hud.attr({
id: this.id,
Expand All @@ -3784,6 +3849,8 @@ const FilterHud = Garnish.HUD.extend({
elementType: this.elementIndex.elementType,
source: this.sourceKey,
condition: this.elementIndex.settings.condition,
conditionConfig: this.conditionConfig,
serialized: this.serialized,
id: `${this.id}-filters`,
},
})
Expand Down Expand Up @@ -3825,6 +3892,12 @@ const FilterHud = Garnish.HUD.extend({
this.updateSizeAndPosition(true);
});
this.setFocus();

if (this.conditionConfig) {
// conditionConfig => serialized
this.conditionConfig = null;
this.serialized = this.serialize();
}
})
.catch(() => {
Craft.cp.displayError(Craft.t('app', 'A server error occurred.'));
Expand Down
2 changes: 1 addition & 1 deletion src/web/assets/garnish/dist/garnish.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/web/assets/garnish/dist/garnish.js.map

Large diffs are not rendered by default.

15 changes: 10 additions & 5 deletions src/web/assets/garnish/src/HUD.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,6 @@ export default Base.extend(
this.$hud.css('position', 'absolute');
}

// Hide the HUD until it gets positioned
this.$hud.css('opacity', 0);
this.show();
this.$hud.css('opacity', 1);

this.addListener(this.$body, 'submit', '_handleSubmit');

if (this.settings.hideOnShadeClick) {
Expand Down Expand Up @@ -147,6 +142,15 @@ export default Base.extend(
this.$nextFocusableElement.focus();
}
});

if (this.settings.showOnInit) {
// Hide the HUD until it gets positioned
this.$hud.css('opacity', 0);
this.show();
this.$hud.css('opacity', 1);
} else {
this.$hud.appendTo(Garnish.$bod).hide();
}
},

/**
Expand Down Expand Up @@ -654,6 +658,7 @@ export default Base.extend(
onHide: $.noop,
onSubmit: $.noop,
closeBtn: null,
showOnInit: true,
closeOtherHUDs: true,
hideOnEsc: true,
hideOnShadeClick: true,
Expand Down