Refs #5 - Progress to Filter widget
This commit is contained in:
parent
f18c905f9b
commit
f1849c2ea0
|
|
@ -9592,17 +9592,17 @@ table.table.data tr.list-tree-level-25 td.list-cell-index-1 {
|
|||
border-bottom: 1px solid #949ea6;
|
||||
}
|
||||
.control-filter a {
|
||||
color: #949ea6;
|
||||
text-decoration: none;
|
||||
color: #949ea6;
|
||||
}
|
||||
.control-filter > .filter-set {
|
||||
.control-filter > .filter-scope {
|
||||
display: inline-block;
|
||||
padding: 15px;
|
||||
}
|
||||
.control-filter > .filter-set .filter-setting {
|
||||
.control-filter > .filter-scope .filter-setting {
|
||||
display: inline-block;
|
||||
}
|
||||
.control-filter > .filter-set:after {
|
||||
.control-filter > .filter-scope:after {
|
||||
font-size: 14px;
|
||||
font-family: FontAwesome;
|
||||
font-weight: normal;
|
||||
|
|
@ -9612,7 +9612,7 @@ table.table.data tr.list-tree-level-25 td.list-cell-index-1 {
|
|||
*margin-right: .3em;
|
||||
content: "\f107";
|
||||
}
|
||||
.control-filter > .filter-set.active .filter-setting {
|
||||
.control-filter > .filter-scope.active .filter-setting {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
color: #FFF;
|
||||
|
|
@ -9621,13 +9621,23 @@ table.table.data tr.list-tree-level-25 td.list-cell-index-1 {
|
|||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.control-filter > .filter-set:hover {
|
||||
.control-filter > .filter-scope.checkbox {
|
||||
padding-left: 35px;
|
||||
}
|
||||
.control-filter > .filter-scope.checkbox,
|
||||
.control-filter > .filter-scope.checkbox label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.control-filter > .filter-scope.checkbox:after {
|
||||
content: '';
|
||||
}
|
||||
.control-filter > .filter-scope:hover {
|
||||
color: #000;
|
||||
}
|
||||
.control-filter > .filter-set:hover .filter-label {
|
||||
.control-filter > .filter-scope:hover .filter-label {
|
||||
color: #949ea6;
|
||||
}
|
||||
.control-filter > .filter-set:hover.active .filter-setting {
|
||||
.control-filter > .filter-scope:hover.active .filter-setting {
|
||||
background-color: #b32d00;
|
||||
}
|
||||
.control-filter-popover {
|
||||
|
|
@ -9689,6 +9699,19 @@ table.table.data tr.list-tree-level-25 td.list-cell-index-1 {
|
|||
*margin-right: .3em;
|
||||
content: "\f067";
|
||||
}
|
||||
.control-filter-popover .filter-items li.loading {
|
||||
padding: 7px;
|
||||
}
|
||||
.control-filter-popover .filter-items li.loading > span {
|
||||
display: block;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background-image: url(../images/loading-indicator.svg);
|
||||
background-size: 20px 20px;
|
||||
background-position: 50% 50%;
|
||||
-webkit-animation: spin 1s linear infinite;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
.control-filter-popover .filter-active-items a:before {
|
||||
font-family: FontAwesome;
|
||||
font-weight: normal;
|
||||
|
|
|
|||
|
|
@ -182,14 +182,14 @@
|
|||
this.$el.data('oc.inspectorVisible', true)
|
||||
|
||||
var displayPopover = function() {
|
||||
var offset = self.$el.data('inspector-offset')
|
||||
var offset = self.$el.data('inspector-offset')
|
||||
if (offset === undefined)
|
||||
offset = 15
|
||||
|
||||
var offsetX = self.$el.data('inspector-offset-x'),
|
||||
offsetY = self.$el.data('inspector-offset-y')
|
||||
|
||||
var placement = self.$el.data('inspector-placement')
|
||||
var offsetX = self.$el.data('inspector-offset-x'),
|
||||
offsetY = self.$el.data('inspector-offset-y')
|
||||
|
||||
var placement = self.$el.data('inspector-placement')
|
||||
if (placement === undefined)
|
||||
placement = 'bottom'
|
||||
|
||||
|
|
@ -248,8 +248,9 @@
|
|||
displayPopover()
|
||||
}
|
||||
|
||||
// Creates group nodes in the property set
|
||||
//
|
||||
/*
|
||||
* Creates group nodes in the property set
|
||||
*/
|
||||
Inspector.prototype.preprocessConfig = function() {
|
||||
var fields = [],
|
||||
result = {
|
||||
|
|
@ -707,15 +708,15 @@
|
|||
|
||||
InspectorEditorDropdown.prototype.showLoadingIndicator = function() {
|
||||
if (!Modernizr.touch)
|
||||
this.indicatorContainer.loadIndicator({'opaque': true})
|
||||
this.indicatorContainer.loadIndicator({'opaque': true})
|
||||
}
|
||||
|
||||
InspectorEditorDropdown.prototype.hideLoadingIndicator = function() {
|
||||
if (!Modernizr.touch)
|
||||
this.indicatorContainer.loadIndicator('hide')
|
||||
this.indicatorContainer.loadIndicator('hide')
|
||||
}
|
||||
|
||||
InspectorEditorDropdown.prototype.loadOptions= function() {
|
||||
InspectorEditorDropdown.prototype.loadOptions = function() {
|
||||
var $form = $(this.selector).closest('form'),
|
||||
data = this.inspector.propertyValues,
|
||||
$select = $(this.selector),
|
||||
|
|
@ -764,7 +765,7 @@
|
|||
|
||||
// INSPECTOR DATA-API
|
||||
// ==================
|
||||
|
||||
|
||||
$(document).on('click', '[data-inspectable]', function(){
|
||||
var $this = $(this),
|
||||
inspector = $this.data('oc.inspector')
|
||||
|
|
|
|||
|
|
@ -55,14 +55,14 @@
|
|||
Popover.prototype.hide = function() {
|
||||
var e = $.Event('hiding.oc.popover', {relatedTarget: this.$el})
|
||||
this.$el.trigger(e, this)
|
||||
if (e.isDefaultPrevented())
|
||||
if (e.isDefaultPrevented())
|
||||
return
|
||||
|
||||
if (this.$container) this.$container.remove()
|
||||
if (this.$overlay) this.$overlay.remove()
|
||||
|
||||
this.$overlay = false;
|
||||
this.$container = false;
|
||||
this.$overlay = false
|
||||
this.$container = false
|
||||
|
||||
this.$el.removeClass('popover-highlight')
|
||||
this.$el.data('oc.popover', null)
|
||||
|
|
@ -70,46 +70,46 @@
|
|||
|
||||
$(document).unbind('mousedown', this.docClickHandler);
|
||||
this.$el.trigger('hide.oc.popover')
|
||||
$(document).off('.oc.popover');
|
||||
$(document).off('.oc.popover')
|
||||
}
|
||||
|
||||
Popover.prototype.show = function(options) {
|
||||
var self = this;
|
||||
var self = this
|
||||
|
||||
/*
|
||||
* Trigger the show event
|
||||
*/
|
||||
var e = $.Event('showing.oc.popover', {relatedTarget: this.$el})
|
||||
this.$el.trigger(e, this)
|
||||
if (e.isDefaultPrevented())
|
||||
if (e.isDefaultPrevented())
|
||||
return
|
||||
|
||||
/*
|
||||
* Create the popover container and overlay
|
||||
*/
|
||||
|
||||
this.$container = $('<div/>')
|
||||
this.$container = $('<div />')
|
||||
.addClass('control-popover')
|
||||
.css('visibility', 'hidden')
|
||||
|
||||
if (this.options.containerClass)
|
||||
this.$container.addClass(this.options.containerClass)
|
||||
|
||||
var $content = $('<div/>').html(this.getContent())
|
||||
var $content = $('<div />').html(this.getContent())
|
||||
this.$container.append($content)
|
||||
|
||||
if (this.options.width)
|
||||
this.$container.width(this.options.width)
|
||||
|
||||
if (this.options.modal) {
|
||||
this.$overlay = $('<div/>').addClass('popover-overlay')
|
||||
this.$overlay = $('<div />').addClass('popover-overlay')
|
||||
$(document.body).append(this.$overlay)
|
||||
if (this.options.highlightModalTarget) {
|
||||
this.$el.addClass('popover-highlight')
|
||||
this.$el.blur()
|
||||
}
|
||||
} else
|
||||
} else {
|
||||
this.$overlay = false
|
||||
}
|
||||
|
||||
if (this.options.container)
|
||||
$(this.options.container).append(this.$container);
|
||||
|
|
@ -119,8 +119,7 @@
|
|||
/*
|
||||
* Determine the popover position
|
||||
*/
|
||||
|
||||
var
|
||||
var
|
||||
placement = this.calcPlacement(),
|
||||
position = this.calcPosition(placement);
|
||||
|
||||
|
|
@ -132,14 +131,12 @@
|
|||
/*
|
||||
* Display the popover
|
||||
*/
|
||||
|
||||
this.$container.css('visibility', 'visible')
|
||||
$(document.body).addClass('popover-open')
|
||||
|
||||
/*
|
||||
* Bind events
|
||||
*/
|
||||
|
||||
this.$container.on('mousedown', function(e){
|
||||
e.stopPropagation();
|
||||
})
|
||||
|
|
@ -173,12 +170,12 @@
|
|||
}
|
||||
|
||||
Popover.prototype.calcDimensions = function() {
|
||||
var
|
||||
var
|
||||
documentWidth = $(document).width(),
|
||||
documentHeight = $(document).height(),
|
||||
targetOffset = this.$el.offset(),
|
||||
targetWidth = this.$el.outerWidth(),
|
||||
targetHeight = this.$el.outerHeight();
|
||||
targetHeight = this.$el.outerHeight()
|
||||
|
||||
return {
|
||||
containerWidth: this.$container.outerWidth() + this.arrowSize,
|
||||
|
|
@ -242,24 +239,24 @@
|
|||
}
|
||||
|
||||
Popover.prototype.calcPosition = function(placement) {
|
||||
var
|
||||
var
|
||||
dimensions = this.calcDimensions(),
|
||||
result;
|
||||
result
|
||||
|
||||
switch (placement) {
|
||||
case 'left' :
|
||||
case 'left':
|
||||
var realOffset = this.options.offsetY === undefined ? this.options.offset : this.options.offsetY
|
||||
result = {x: (dimensions.targetOffset.left - dimensions.containerWidth), y: dimensions.targetOffset.top + realOffset}
|
||||
break;
|
||||
case 'top' :
|
||||
case 'top':
|
||||
var realOffset = this.options.offsetX === undefined ? this.options.offset : this.options.offsetX
|
||||
result = {x: dimensions.targetOffset.left + realOffset, y: (dimensions.targetOffset.top - dimensions.containerHeight)}
|
||||
break;
|
||||
case 'bottom' :
|
||||
case 'bottom':
|
||||
var realOffset = this.options.offsetX === undefined ? this.options.offset : this.options.offsetX
|
||||
result = {x: dimensions.targetOffset.left + realOffset, y: (dimensions.targetOffset.top + dimensions.targetHeight + this.arrowSize)}
|
||||
break;
|
||||
case 'right' :
|
||||
case 'right':
|
||||
var realOffset = this.options.offsetY === undefined ? this.options.offset : this.options.offsetY
|
||||
result = {x: (dimensions.targetOffset.left + dimensions.targetWidth + this.arrowSize), y: dimensions.targetOffset.top + realOffset}
|
||||
break;
|
||||
|
|
@ -268,14 +265,14 @@
|
|||
if (!this.options.container)
|
||||
return result
|
||||
|
||||
var
|
||||
var
|
||||
$container = $(this.options.container),
|
||||
containerOffset = $container.offset();
|
||||
containerOffset = $container.offset()
|
||||
|
||||
result.x -= containerOffset.left;
|
||||
result.y -= containerOffset.top;
|
||||
result.x -= containerOffset.left
|
||||
result.y -= containerOffset.top
|
||||
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
|
||||
Popover.prototype.onDocumentClick = function() {
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@
|
|||
border-top: 1px solid @color-filter-border;
|
||||
border-bottom: 1px solid @color-filter-border;
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: @color-filter-text;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
>.filter-set {
|
||||
> .filter-scope {
|
||||
display: inline-block;
|
||||
padding: 15px;
|
||||
.filter-label {}
|
||||
|
|
@ -37,6 +37,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.checkbox {
|
||||
padding-left: 35px;
|
||||
&, label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #000;
|
||||
.filter-label { color: @color-filter-text; }
|
||||
|
|
@ -52,7 +63,7 @@
|
|||
min-height: 36px;
|
||||
input {
|
||||
min-height: 36px;
|
||||
border: none;
|
||||
border: none;
|
||||
border-bottom: 1px solid @color-filter-border;
|
||||
background: transparent url(../images/bitmap-icons.png) no-repeat 100% -82px;
|
||||
.border-radius(0);
|
||||
|
|
@ -67,7 +78,7 @@
|
|||
text-decoration: none;
|
||||
color: @color-filter-text;
|
||||
display: block;
|
||||
padding: 7px 15px;
|
||||
padding: 7px 15px;
|
||||
|
||||
&:before {
|
||||
margin-right: 8px;
|
||||
|
|
@ -86,6 +97,19 @@
|
|||
background-color: @color-filter-items-bg;
|
||||
border-bottom: 1px solid @color-filter-border;
|
||||
a:before { .icon(@plus); }
|
||||
|
||||
li.loading {
|
||||
padding: 7px;
|
||||
> span {
|
||||
display: block;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background-image: url(../images/loading-indicator.svg);
|
||||
background-size: 20px 20px;
|
||||
background-position: 50% 50%;
|
||||
.animation(spin 1s linear infinite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-active-items {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ class ListController extends ControllerBehavior
|
|||
*/
|
||||
protected $toolbarWidgets = [];
|
||||
|
||||
/**
|
||||
* @var WidgetBase Reference to the filter widget objects.
|
||||
*/
|
||||
protected $filterWidgets = [];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
@ -174,6 +179,20 @@ class ListController extends ControllerBehavior
|
|||
$this->toolbarWidgets[$definition] = $toolbarWidget;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prepare the filter widget (optional)
|
||||
*/
|
||||
if (isset($listConfig->filter)) {
|
||||
$filterConfig = $this->makeConfig($listConfig->filter);
|
||||
$filterConfig->alias = $widget->alias . 'Filter';
|
||||
$filterWidget = $this->makeWidget('Backend\Widgets\Filter', $filterConfig);
|
||||
$filterWidget->bindToController();
|
||||
|
||||
$widget->cssClasses[] = 'list-flush';
|
||||
|
||||
$this->filterWidgets[$definition] = $filterWidget;
|
||||
}
|
||||
|
||||
return $widget;
|
||||
}
|
||||
|
||||
|
|
@ -206,6 +225,9 @@ class ListController extends ControllerBehavior
|
|||
if (isset($this->toolbarWidgets[$definition]))
|
||||
$collection[] = $this->toolbarWidgets[$definition]->render();
|
||||
|
||||
if (isset($this->filterWidgets[$definition]))
|
||||
$collection[] = $this->filterWidgets[$definition]->render();
|
||||
|
||||
$collection[] = $this->listWidgets[$definition]->render();
|
||||
|
||||
return implode(PHP_EOL, $collection);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
<?php namespace Backend\Classes;
|
||||
|
||||
use Str;
|
||||
|
||||
/**
|
||||
* Filter scope definition
|
||||
* A translation of the filter scope configuration
|
||||
*
|
||||
* @package october\backend
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class FilterScope
|
||||
{
|
||||
/**
|
||||
* @var string Scope name.
|
||||
*/
|
||||
public $scopeName;
|
||||
|
||||
/**
|
||||
* @var string A prefix to the field identifier so it can be totally unique.
|
||||
*/
|
||||
public $idPrefix;
|
||||
|
||||
/**
|
||||
* @var string Form scope label.
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* @var string Filter mode.
|
||||
*/
|
||||
public $type = 'group';
|
||||
|
||||
/**
|
||||
* @var string Filter options.
|
||||
*/
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* @var string Specifies contextual visibility of this form scope.
|
||||
*/
|
||||
public $context = null;
|
||||
|
||||
/**
|
||||
* @var bool Specify if the scope is disabled or not.
|
||||
*/
|
||||
public $disabled = false;
|
||||
|
||||
/**
|
||||
* @var string Specifies a default value for supported scopes.
|
||||
*/
|
||||
public $defaults;
|
||||
|
||||
/**
|
||||
* @var string Specifies a CSS class to attach to the scope container.
|
||||
*/
|
||||
public $cssClass;
|
||||
|
||||
/**
|
||||
* @var array Raw scope configuration.
|
||||
*/
|
||||
public $config;
|
||||
|
||||
public function __construct($scopeName, $label)
|
||||
{
|
||||
$this->scopeName = $scopeName;
|
||||
$this->label = $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a scope control rendering mode. Supported modes are:
|
||||
* - group - filter by a group of IDs. Default.
|
||||
* - checkbox - filter by a simple toggle switch.
|
||||
* @param string $type Specifies a render mode as described above
|
||||
* @param array $config A list of render mode specific config.
|
||||
*/
|
||||
public function displayAs($type, $config = [])
|
||||
{
|
||||
$this->type = strtolower($type) ?: $this->type;
|
||||
$this->config = $this->evalConfig($config);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process options and apply them to this object.
|
||||
* @param array $config
|
||||
* @return array
|
||||
*/
|
||||
protected function evalConfig($config)
|
||||
{
|
||||
if (isset($config['options'])) $this->options($config['options']);
|
||||
if (isset($config['context'])) $this->context = $config['context'];
|
||||
if (isset($config['default'])) $this->defaults = $config['default'];
|
||||
if (isset($config['cssClass'])) $this->cssClass = $config['cssClass'];
|
||||
|
||||
if (array_key_exists('disabled', $config)) $this->disabled = $config['disabled'];
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value suitable for the scope id property.
|
||||
*/
|
||||
public function getId($suffix = null)
|
||||
{
|
||||
$id = 'scope';
|
||||
$id .= '-'.$this->scopeName;
|
||||
|
||||
if ($suffix)
|
||||
$id .= '-'.$suffix;
|
||||
|
||||
if ($this->idPrefix)
|
||||
$id = $this->idPrefix . '-' . $id;
|
||||
|
||||
$id = rtrim(str_replace(['[', ']'], '-', $id), '-');
|
||||
return $id;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
<?php namespace Backend\Widgets;
|
||||
|
||||
use Event;
|
||||
use Backend\Classes\WidgetBase;
|
||||
use Backend\Classes\FilterScope;
|
||||
use System\Classes\ApplicationException;
|
||||
|
||||
/**
|
||||
* Filter Widget
|
||||
|
|
@ -16,6 +19,48 @@ class Filter extends WidgetBase
|
|||
*/
|
||||
public $defaultAlias = 'filter';
|
||||
|
||||
/**
|
||||
* @var boolean Determines if scope definitions have been created.
|
||||
*/
|
||||
protected $scopesDefined = false;
|
||||
|
||||
/**
|
||||
* @var array Collection of all scopes used in this filter.
|
||||
*/
|
||||
protected $allScopes = [];
|
||||
|
||||
/**
|
||||
* @var array Collection of all scopes models used in this filter.
|
||||
*/
|
||||
protected $scopeModels = [];
|
||||
|
||||
/**
|
||||
* @var string The context of this filter, scopes that do not belong
|
||||
* to this context will not be shown.
|
||||
*/
|
||||
protected $activeContext = null;
|
||||
|
||||
/**
|
||||
* @var array List of CSS classes to apply to the filter container element
|
||||
*/
|
||||
public $cssClasses = [];
|
||||
|
||||
/**
|
||||
* Initialize the widget, called by the constructor and free from its parameters.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->activeContext = $this->getConfig('context');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function loadAssets()
|
||||
{
|
||||
$this->addJs('js/october.filter.js', 'core');
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the widget.
|
||||
*/
|
||||
|
|
@ -30,5 +75,197 @@ class Filter extends WidgetBase
|
|||
*/
|
||||
public function prepareVars()
|
||||
{
|
||||
$this->defineFilterScopes();
|
||||
$this->vars['cssClasses'] = implode(' ', $this->cssClasses);
|
||||
$this->vars['scopes'] = $this->allScopes;
|
||||
}
|
||||
|
||||
public function onFilterGetOptions()
|
||||
{
|
||||
$this->defineFilterScopes();
|
||||
|
||||
if (!$scopeName = post('scopeName'))
|
||||
return;
|
||||
|
||||
$scope = $this->getScope($scopeName);
|
||||
|
||||
// $available = [
|
||||
// ['id' => 1, 'name' => 'Deleted'],
|
||||
// ['id' => 2, 'name' => 'Moo'],
|
||||
// ];
|
||||
|
||||
$available = $this->getAvailableOptions($scope);
|
||||
$active = $this->filterActiveOptions($available);
|
||||
|
||||
// $active = [
|
||||
// ['id' => 3, 'name' => 'Selected'],
|
||||
// ['id' => 4, 'name' => 'Bar'],
|
||||
// ];
|
||||
|
||||
$available = $this->processOptionsForAjax($available);
|
||||
$active = $this->processOptionsForAjax($active);
|
||||
|
||||
return [
|
||||
'options' => [
|
||||
'available' => $available,
|
||||
'active' => $active,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
protected function getAvailableOptions($scope)
|
||||
{
|
||||
$available = [];
|
||||
$options = $this->getOptionsFromModel($scope);
|
||||
foreach ($options as $option) {
|
||||
$available[$option->id] = $option->name;
|
||||
}
|
||||
return $available;
|
||||
}
|
||||
|
||||
protected function filterActiveOptions(&$availableOptions)
|
||||
{
|
||||
$fromSession = [1];
|
||||
|
||||
$active = [];
|
||||
foreach ($availableOptions as $id => $option) {
|
||||
if (!in_array($id, $fromSession))
|
||||
continue;
|
||||
|
||||
$active[$id] = $option;
|
||||
unset($availableOptions[$id]);
|
||||
}
|
||||
|
||||
return $active;
|
||||
}
|
||||
|
||||
protected function processOptionsForAjax($options)
|
||||
{
|
||||
$processed = [];
|
||||
foreach ($options as $id => $result) {
|
||||
$processed[] = ['id' => $id, 'name' => $result];
|
||||
}
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks at the model for defined scope items.
|
||||
*/
|
||||
protected function getOptionsFromModel($scope)
|
||||
{
|
||||
$model = $this->scopeModels[$scope->scopeName];
|
||||
return $model->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the HTML element for a scope
|
||||
*/
|
||||
public function renderScopeElement($scope)
|
||||
{
|
||||
return $this->makePartial('scope_'.$scope->type, ['scope' => $scope]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a flat array of filter scopes from the configuration.
|
||||
*/
|
||||
protected function defineFilterScopes()
|
||||
{
|
||||
if ($this->scopesDefined)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Extensibility
|
||||
*/
|
||||
Event::fire('backend.filter.extendScopesBefore', [$this]);
|
||||
$this->fireEvent('filter.extendScopesBefore');
|
||||
|
||||
/*
|
||||
* All scopes
|
||||
*/
|
||||
if (!isset($this->config->scopes) || !is_array($this->config->scopes))
|
||||
$this->config->scopes = [];
|
||||
|
||||
$this->addScopes($this->config->scopes);
|
||||
|
||||
/*
|
||||
* Extensibility
|
||||
*/
|
||||
Event::fire('backend.filter.extendScopes', [$this]);
|
||||
$this->fireEvent('filter.extendScopes');
|
||||
|
||||
$this->scopesDefined = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Programatically add scopes, used internally and for extensibility.
|
||||
*/
|
||||
public function addScopes(array $scopes)
|
||||
{
|
||||
foreach ($scopes as $name => $config) {
|
||||
|
||||
$scopeObj = $this->makeFilterScope($name, $config);
|
||||
|
||||
/*
|
||||
* Check that the filter scope matches the active context
|
||||
*/
|
||||
if ($scopeObj->context !== null) {
|
||||
$context = (is_array($scopeObj->context)) ? $scopeObj->context : [$scopeObj->context];
|
||||
if (!in_array($this->getContext(), $context))
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate scope model
|
||||
*/
|
||||
if (!isset($config['modelClass']))
|
||||
throw new ApplicationException('Missing model definition for scope');
|
||||
|
||||
$class = $config['modelClass'];
|
||||
$model = new $class;
|
||||
$this->scopeModels[$name] = $model;
|
||||
|
||||
$this->allScopes[$name] = $scopeObj;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a filter scope object from name and configuration.
|
||||
*/
|
||||
protected function makeFilterScope($name, $config)
|
||||
{
|
||||
$label = (isset($config['label'])) ? $config['label'] : null;
|
||||
$scopeType = isset($config['type']) ? $config['type'] : null;
|
||||
|
||||
$scope = new FilterScope($name, $label);
|
||||
$scope->displayAs($scopeType, $config);
|
||||
return $scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the registered scopes for the instance.
|
||||
* @return array
|
||||
*/
|
||||
public function getScopes()
|
||||
{
|
||||
return $this->allScopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specified scope object
|
||||
* @param string $scope
|
||||
* @return mixed
|
||||
*/
|
||||
public function getScope($scope)
|
||||
{
|
||||
return $this->allScopes[$scope];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the active context for displaying the filter.
|
||||
*/
|
||||
public function getContext()
|
||||
{
|
||||
return $this->activeContext;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Filter Widget
|
||||
*
|
||||
* Dependences:
|
||||
* - Nil
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
|
||||
var FilterWidget = function (element, options) {
|
||||
|
||||
var $el = this.$el = $(element);
|
||||
|
||||
this.options = options || {}
|
||||
this.scopeValues = {}
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
FilterWidget.DEFAULTS = {
|
||||
optionsHandler: null
|
||||
}
|
||||
|
||||
/*
|
||||
* Get popover template
|
||||
*/
|
||||
FilterWidget.prototype.getPopoverTemplate = function() {
|
||||
|
||||
return ' \
|
||||
<div class="control-filter-popover"> \
|
||||
<div class="filter-search"> \
|
||||
<input type="text" class="form-control" /> \
|
||||
</div> \
|
||||
<div class="filter-items"> \
|
||||
<ul> \
|
||||
{{#available}} \
|
||||
<li><a href="javascript:;" data-item-id="{{id}}">{{name}}</a></li> \
|
||||
{{/available}} \
|
||||
{{^available}} \
|
||||
<li class="loading"><span></span></li> \
|
||||
{{/available}} \
|
||||
</ul> \
|
||||
</div> \
|
||||
<div class="filter-active-items"> \
|
||||
<ul> \
|
||||
{{#active}} \
|
||||
<li><a href="javascript:;" data-item-id="{{id}}">{{name}}</a></li> \
|
||||
{{/active}} \
|
||||
</ul> \
|
||||
</div> \
|
||||
</div> \
|
||||
'
|
||||
}
|
||||
|
||||
FilterWidget.prototype.init = function() {
|
||||
var self = this
|
||||
|
||||
this.$el.on('click', 'a.filter-scope', function(){
|
||||
self.displayPopover($(this))
|
||||
})
|
||||
}
|
||||
|
||||
FilterWidget.prototype.displayPopover = function($scope) {
|
||||
var self = this,
|
||||
scopeName = $scope.data('scope-name'),
|
||||
data = this.scopeValues[scopeName]
|
||||
|
||||
if (!data)
|
||||
self.loadOptions($scope)
|
||||
|
||||
$scope.ocPopover({
|
||||
content: Mustache.render(self.getPopoverTemplate(), data),
|
||||
modal: false,
|
||||
highlightModalTarget: true,
|
||||
closeOnPageClick: true,
|
||||
placement: 'bottom'
|
||||
})
|
||||
}
|
||||
|
||||
FilterWidget.prototype.loadOptions = function($scope) {
|
||||
var $form = this.$el.closest('form'),
|
||||
self = this,
|
||||
scopeName = $scope.data('scope-name'),
|
||||
data = {
|
||||
scopeName: scopeName
|
||||
}
|
||||
|
||||
$form.request(this.options.optionsHandler, {
|
||||
data: data,
|
||||
success: function(data) {
|
||||
|
||||
if (self.scopeValues[scopeName])
|
||||
return
|
||||
|
||||
self.scopeValues[scopeName] = data.options
|
||||
|
||||
/*
|
||||
* Inject available
|
||||
*/
|
||||
if (data.options.available) {
|
||||
var container = $('.control-filter-popover .filter-items > ul').empty()
|
||||
$.each(data.options.available, function(key, obj){
|
||||
var item = $('<li />').append($('<a />').prop({
|
||||
'href': 'javascript:;',
|
||||
'data-item-id': obj.id
|
||||
}).text(obj.name))
|
||||
container.append(item)
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Inject active
|
||||
*/
|
||||
if (data.options.active) {
|
||||
var container = $('.control-filter-popover .filter-active-items > ul')
|
||||
$.each(data.options.active, function(key, obj){
|
||||
var item = $('<li />').append($('<a />').prop({
|
||||
'href': 'javascript:;',
|
||||
'data-item-id': obj.id
|
||||
}).text(obj.name))
|
||||
container.append(item)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FILTER WIDGET PLUGIN DEFINITION
|
||||
// ============================
|
||||
|
||||
var old = $.fn.filterWidget
|
||||
|
||||
$.fn.filterWidget = function (option) {
|
||||
var args = arguments,
|
||||
result
|
||||
|
||||
this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('oc.filterwidget')
|
||||
var options = $.extend({}, FilterWidget.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||
if (!data) $this.data('oc.filterwidget', (data = new FilterWidget(this, options)))
|
||||
if (typeof option == 'string') result = data[option].call($this)
|
||||
if (typeof result != 'undefined') return false
|
||||
})
|
||||
|
||||
return result ? result : this
|
||||
}
|
||||
|
||||
$.fn.filterWidget.Constructor = FilterWidget
|
||||
|
||||
// FILTER WIDGET NO CONFLICT
|
||||
// =================
|
||||
|
||||
$.fn.filterWidget.noConflict = function () {
|
||||
$.fn.filterWidget = old
|
||||
return this
|
||||
}
|
||||
|
||||
// FILTER WIDGET DATA-API
|
||||
// ==============
|
||||
|
||||
$(document).render(function(){
|
||||
$('[data-control="filterwidget"]').filterWidget();
|
||||
})
|
||||
|
||||
}(window.jQuery);
|
||||
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
<div class="control-filter">
|
||||
<div
|
||||
class="control-filter"
|
||||
data-control="filterwidget"
|
||||
data-options-handler="<?= $this->getEventHandler('onFilterGetOptions') ?>">
|
||||
|
||||
<?= $this->makePartial('filter_scopes') ?>
|
||||
|
||||
<a class="filter-set" href="javascript:;">
|
||||
<span class="filter-label">Categories:</span>
|
||||
|
|
@ -10,8 +15,12 @@
|
|||
<span class="filter-setting">2</span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div class="filter-scope checkbox custom-checkbox">
|
||||
<input type="checkbox" id="hideTranslated" name="hide_translated" value="1" />
|
||||
<label for="hideTranslated">Hide disabled</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
$('.control-filter').on('click', 'a.filter-set', function(){
|
||||
$(this).ocPopover({
|
||||
|
|
@ -35,6 +44,10 @@
|
|||
<ul>
|
||||
<li><a href="#">Deleted</a></li>
|
||||
<li><a href="#">Deployed</a></li>
|
||||
<li><a href="#">Deployed</a></li>
|
||||
<li><a href="#">Deployed</a></li>
|
||||
<li><a href="#">Deployed</a></li>
|
||||
<li><a href="#">Deployed</a></li>
|
||||
<li><a href="#">Detailed</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -46,3 +59,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
<?php foreach ($scopes as $scope): ?>
|
||||
<?= $this->renderScopeElement($scope) ?>
|
||||
<?php endforeach ?>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<!-- Checkbox scope -->
|
||||
<div
|
||||
class="filter-scope checkbox custom-checkbox"
|
||||
data-scope-name="<?= $scope->scopeName ?>">
|
||||
<input type="checkbox" id="<?= $scope->getId() ?>" />
|
||||
<label for="<?= $scope->getId() ?>"><?= e($scope->label) ?></label>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<!-- Group scope -->
|
||||
<a
|
||||
class="filter-scope"
|
||||
href="javascript:;"
|
||||
data-scope-name="<?= $scope->scopeName ?>">
|
||||
<span class="filter-label"><?= e($scope->label) ?>:</span>
|
||||
<span class="filter-setting">all</span>
|
||||
</a>
|
||||
Loading…
Reference in New Issue