Initial WIP on implementing dependsOn support for filter scopes.
Still need to resolve an issue where if the slave filter has values set when the master filter updates, thus triggering a change of the available options to the slave, the original values are still set on the slave but not actually visible in the popup as options because they're no longer valid options. To fix this we'll need the ability to get the browser to refresh the slave filter's selected values (count icon basically since it already forces the options popup to refresh) when its masters update; while at the same rechecking the slave's scope values set on the server to ensure that they're all valid and there aren't values left over from the previous request that are no longer valid but are still being applied to the query.
This commit is contained in:
parent
85fadbfef3
commit
70107c6376
|
|
@ -51,6 +51,11 @@ class FilterScope
|
|||
*/
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* @var array Other scope names this scope depends on, when the other scopes are modified, this scope will update.
|
||||
*/
|
||||
public $dependsOn;
|
||||
|
||||
/**
|
||||
* @var string Specifies contextual visibility of this form scope.
|
||||
*/
|
||||
|
|
@ -113,33 +118,32 @@ class FilterScope
|
|||
*/
|
||||
protected function evalConfig($config)
|
||||
{
|
||||
if (isset($config['options'])) {
|
||||
$this->options = $config['options'];
|
||||
if ($config === null) {
|
||||
$config = [];
|
||||
}
|
||||
if (isset($config['context'])) {
|
||||
$this->context = $config['context'];
|
||||
}
|
||||
if (isset($config['default'])) {
|
||||
$this->defaults = $config['default'];
|
||||
}
|
||||
if (isset($config['conditions'])) {
|
||||
$this->conditions = $config['conditions'];
|
||||
}
|
||||
if (isset($config['scope'])) {
|
||||
$this->scope = $config['scope'];
|
||||
}
|
||||
if (isset($config['cssClass'])) {
|
||||
$this->cssClass = $config['cssClass'];
|
||||
}
|
||||
if (isset($config['nameFrom'])) {
|
||||
$this->nameFrom = $config['nameFrom'];
|
||||
}
|
||||
if (isset($config['descriptionFrom'])) {
|
||||
$this->descriptionFrom = $config['descriptionFrom'];
|
||||
}
|
||||
if (array_key_exists('disabled', $config)) {
|
||||
$this->disabled = $config['disabled'];
|
||||
|
||||
/*
|
||||
* Standard config:property values
|
||||
*/
|
||||
$applyConfigValues = [
|
||||
'options',
|
||||
'dependsOn',
|
||||
'context',
|
||||
'default',
|
||||
'conditions',
|
||||
'scope',
|
||||
'cssClass',
|
||||
'nameFrom',
|
||||
'descriptionFrom',
|
||||
'disabled',
|
||||
];
|
||||
|
||||
foreach ($applyConfigValues as $value) {
|
||||
if (array_key_exists($value, $config)) {
|
||||
$this->{$value} = $config[$value];
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -171,6 +171,22 @@ class Filter extends WidgetBase
|
|||
return $this->makePartial('scope_'.$scope->type, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a HTML encoded value containing the other scopes this scope depends on
|
||||
* @param \Backend\Classes\FilterScope $scope
|
||||
* @return string
|
||||
*/
|
||||
protected function getScopeDepends($scope)
|
||||
{
|
||||
if (!$scope->dependsOn) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$dependsOn = is_array($scope->dependsOn) ? $scope->dependsOn : [$scope->dependsOn];
|
||||
$dependsOn = htmlspecialchars(json_encode($dependsOn), ENT_QUOTES, 'UTF-8');
|
||||
return $dependsOn;
|
||||
}
|
||||
|
||||
//
|
||||
// AJAX
|
||||
//
|
||||
|
|
@ -295,7 +311,14 @@ class Filter extends WidgetBase
|
|||
$scope = $this->getScope($scopeName);
|
||||
$activeKeys = $scope->value ? array_keys($scope->value) : [];
|
||||
$available = $this->getAvailableOptions($scope, $searchQuery);
|
||||
$active = $searchQuery ? [] : $this->filterActiveOptions($activeKeys, $available);
|
||||
|
||||
if ($searchQuery) {
|
||||
$active = [];
|
||||
} else {
|
||||
// Ensure that only valid values are set on the current scope
|
||||
$active = $this->filterActiveOptions($activeKeys, $available);
|
||||
$this->setScopeValue($scope, array_keys($active));
|
||||
}
|
||||
|
||||
return [
|
||||
'scopeName' => $scopeName,
|
||||
|
|
@ -426,7 +449,11 @@ class Filter extends WidgetBase
|
|||
]));
|
||||
}
|
||||
|
||||
$options = $model->$methodName();
|
||||
if (!empty($scope->dependsOn)) {
|
||||
$options = $model->$methodName($this->getScopes());
|
||||
} else {
|
||||
$options = $model->$methodName();
|
||||
}
|
||||
}
|
||||
elseif (!is_array($options)) {
|
||||
$options = [];
|
||||
|
|
@ -644,6 +671,12 @@ class Filter extends WidgetBase
|
|||
/*
|
||||
* Set scope value
|
||||
*/
|
||||
if ($scope->type === 'group') {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
$scope->value = $this->getScopeValue($scope, @$config['default']);
|
||||
|
||||
return $scope;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
<a
|
||||
class="filter-scope <?= $scope->value ? 'active' : '' ?>"
|
||||
href="javascript:;"
|
||||
data-scope-name="<?= $scope->scopeName ?>">
|
||||
data-scope-name="<?= $scope->scopeName ?>"
|
||||
<?php if ($depends = $this->getScopeDepends($scope)): ?>data-scope-depends="<?= $depends ?>"<?php endif ?>
|
||||
>
|
||||
<span class="filter-label"><?= e(trans($scope->label)) ?>:</span>
|
||||
<span class="filter-setting"><?= $scope->value ? count($scope->value) : e(trans('backend::lang.filter.all')) ?></span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
+function ($) { "use strict";
|
||||
|
||||
var FilterWidget = function (element, options) {
|
||||
|
||||
this.$el = $(element);
|
||||
|
||||
this.options = options || {}
|
||||
|
|
@ -28,6 +27,12 @@
|
|||
this.activeScopeName = null
|
||||
this.isActiveScopeDirty = false
|
||||
|
||||
/*
|
||||
* Throttle dependency updating
|
||||
*/
|
||||
this.dependantUpdateInterval = 300
|
||||
this.dependantUpdateTimers = {}
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
|
|
@ -89,6 +94,9 @@
|
|||
FilterWidget.prototype.init = function() {
|
||||
var self = this
|
||||
|
||||
this.bindDependants()
|
||||
|
||||
// Setup event handler on type: checkbox scopes
|
||||
this.$el.on('change', '.filter-scope input[type="checkbox"]', function(){
|
||||
var $scope = $(this).closest('.filter-scope')
|
||||
|
||||
|
|
@ -100,12 +108,14 @@
|
|||
}
|
||||
})
|
||||
|
||||
// Apply classes to type: checkbox scopes that are active from the server
|
||||
$('.filter-scope input[type="checkbox"]', this.$el).each(function() {
|
||||
$(this)
|
||||
.closest('.filter-scope')
|
||||
.toggleClass('active', $(this).is(':checked'))
|
||||
})
|
||||
|
||||
// Setup click handler on type: group scopes
|
||||
this.$el.on('click', 'a.filter-scope', function(){
|
||||
var $scope = $(this),
|
||||
scopeName = $scope.data('scope-name')
|
||||
|
|
@ -120,6 +130,7 @@
|
|||
$scope.addClass('filter-scope-open')
|
||||
})
|
||||
|
||||
// Setup event handlers on type: group scopes' controls
|
||||
this.$el.on('show.oc.popover', 'a.filter-scope', function(event){
|
||||
self.focusSearch()
|
||||
|
||||
|
|
@ -144,9 +155,9 @@
|
|||
e.preventDefault()
|
||||
self.filterScope(true)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
// Setup event handler to apply selected options when closing the type: group scope popup
|
||||
this.$el.on('hide.oc.popover', 'a.filter-scope', function(){
|
||||
var $scope = $(this)
|
||||
self.pushOptions(self.activeScopeName)
|
||||
|
|
@ -158,6 +169,62 @@
|
|||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Bind dependant scopes
|
||||
*/
|
||||
FilterWidget.prototype.bindDependants = function() {
|
||||
if (!$('[data-scope-depends]', this.$el).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this,
|
||||
scopeMap = {},
|
||||
scopeElements = this.$el.find('.filter-scope')
|
||||
|
||||
/*
|
||||
* Map master and slave scope
|
||||
*/
|
||||
scopeElements.filter('[data-scope-depends]').each(function() {
|
||||
var name = $(this).data('scope-name'),
|
||||
depends = $(this).data('scope-depends')
|
||||
|
||||
$.each(depends, function(index, depend){
|
||||
if (!scopeMap[depend]) {
|
||||
scopeMap[depend] = { scopes: [] }
|
||||
}
|
||||
|
||||
scopeMap[depend].scopes.push(name)
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
* When a master is updated, refresh its slaves
|
||||
*/
|
||||
$.each(scopeMap, function(scopeName, toRefresh){
|
||||
scopeElements.filter('[data-scope-name="'+scopeName+'"]')
|
||||
.on('change.oc.filterScope', $.proxy(self.onRefreshDependants, self, scopeName, toRefresh))
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Refresh a dependancy scope
|
||||
* Uses a throttle to prevent duplicate calls and click spamming
|
||||
*/
|
||||
FilterWidget.prototype.onRefreshDependants = function(scopeName, toRefresh) {
|
||||
var self = this,
|
||||
scopeElements = this.$el.find('.filter-scope')
|
||||
|
||||
if (this.dependantUpdateTimers[scopeName] !== undefined) {
|
||||
window.clearTimeout(this.dependantUpdateTimers[scopeName])
|
||||
}
|
||||
|
||||
this.dependantUpdateTimers[scopeName] = window.setTimeout(function() {
|
||||
$.each(toRefresh.scopes, function (index, dependantScope) {
|
||||
self.scopeValues[dependantScope] = null
|
||||
})
|
||||
}, this.dependantUpdateInterval)
|
||||
}
|
||||
|
||||
FilterWidget.prototype.focusSearch = function() {
|
||||
if (Modernizr.touchevents)
|
||||
return
|
||||
|
|
@ -369,7 +436,7 @@
|
|||
var items = $('#controlFilterPopover .filter-active-items > ul'),
|
||||
buttonContainer = $('#controlFilterPopover .filter-buttons')
|
||||
|
||||
if(data) {
|
||||
if (data) {
|
||||
data.active.length > 0 ? buttonContainer.show() : buttonContainer.hide()
|
||||
} else {
|
||||
items.children().length > 0 ? buttonContainer.show() : buttonContainer.hide()
|
||||
|
|
@ -383,16 +450,21 @@
|
|||
if (!this.isActiveScopeDirty || !this.options.updateHandler)
|
||||
return
|
||||
|
||||
var data = {
|
||||
var self = this,
|
||||
data = {
|
||||
scopeName: scopeName,
|
||||
options: this.scopeValues[scopeName]
|
||||
}
|
||||
|
||||
$.oc.stripeLoadIndicator.show()
|
||||
|
||||
this.$el.request(this.options.updateHandler, {
|
||||
data: data
|
||||
}).always(function(){
|
||||
}).always(function () {
|
||||
$.oc.stripeLoadIndicator.hide()
|
||||
}).done(function () {
|
||||
// Trigger dependsOn updates on successful requests
|
||||
self.$el.find('[data-scope-name="'+scopeName+'"]').trigger('change.oc.filterScope')
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue