#  Neos Filtering

Package that contains common abstract base logic for filters built for Neos packages.

## Architectural overview

On an architectural level a filter is a tree of filter items - each item knows how to
render itself (if it should be visible to the end-user) and how to build an expression
that is applied to a query.

The following is an example for a filter tree:

```
                      (ROOT)
                    /        \
   Section "categories"      Section "Properties"
     /                         /        \
 Checkbox List        Checkbox List    Group: OR
                                        /      \
                            Range: Duration    Select: Difficulty
```

Each filter item has what we call a 'state' - an array / object / value defining the current
value for the item. If the item is a search field then the state would be the search string,
if the item is a list of checkbox then the state is an array of the selected options.

### `FilterItem`
Every filter item must always implement the `FilterItem` interface. The `FilterItemCommon` trait
exists to provide a common implementation of the interface.

### `QueryBoundInterface`
If a filter item conveys information through the query string (e.g. adding `?category=123-abc` to it)
then it is called query-bound. All query-bound filter items must implement this interface and may
use the `HasQueryString` trait as a default implementation.

### `TitledFilterItem`
If a filter item has a title - then this is the interface to implement. Use the `HasTitle` trait
for a default implementation.

### `CompositeFilterItem`
If a filter item can contain children items in the tree, then we call it a composite filter item.
All composite filter items must implement the `CompositeFilterItem` interface.

### `DataSourcedFilterItem`
If a filter item uses data provided by a neos data-source then we call it data-sourced. All
data-sourced items must implement the `DataSourcedItemInterface`. The `HasDataSource` trait
provides a common implementation of the interface.

### `RangedFilterItem`
If a filter item has upper and lower bounds for possible values (e.g. numbers between 0 - 100)
then it is called a ranged filter item. All ranged filter item must implement this interface.
Similar to the data sources that already exist in the Neos core this package adds the concept of a
range source: A range source provides these bounds to a filter item.

## Available items
The following predefined filter items ship with this package.

### `CheckboxList`
Renders a list of checkboxes defined by a data source.

### `Select`
Renders a single checkbox defined by a data source.

### `Range`
Renders a range slider with minimum and maximum values defined by a range source.

### `Group`
Does nothing itself: Renders it's children and can be used as a logical element to combine
children with `AND` or `OR` without having any visual impact.

### `Section`
Similar in behaviour to `Group` but has visual impact: Sections are used to group up common elements
into a common visual format (e.g. tabs or filter drop downs). Sections should only be used in one layer
of the filter.

### `DirectEquals`
Meta item: Renders to nothing, has no option for user interaction but only defines a fixed condition to
the query.

### `Root`
Meta item: Root of the filter. Interacts directly with database queries and handles mapping of query string
values to filter items.

## Building a filter

Of course, one could start building a filter tree like this by hand, but that's quite uncomfortable.
In order to quickly get started with a filter the `DefaultFitlerFactory` helps build filters:
With it a filter can be built from a configuration array that will most likely come from YAML-based
settings.

```php
/**
 * @var DefaultFilterFactory
 * @Flow\Inject()
 */
protected $filterFactory;

public function indexAction() {
     $filter = $this->filterFactory->createFilterFromSettings('Newland.Toubiz.Poi.Neos.filters.0');
     // ...
}
```

### Generic configuration options
The only common required option is `type`: It defines what exactly a filter item is.
This can be either a predefined string or a fully qualified class name of a filter item.
Depending on the type a couple more configuration options are available.

Additionally, a default state (that is being used when not overridden otherwise) can be declared
using the `defaultState` option.

The `override` option can be used to specify how the filter item behaves when overriden while
being created (e.g. from backend properties).

While not being available for every item, most filter items that change the current database query
directly may also have a `databaseColumn` option. This option defines which database column / DQL
column is being referred to. Note, that whatever the queried entity is is always referred to as 
`entity` in the DQL query.

```yaml
# ---
type: 'select'
# ---
type: 'Newland\Toubiz\Poi\Neos\Filter\ArticleSearchField'
defaultState: 'Bad Krozingen'
databaseColumn: 'entity.title'
override:
    property: 'preselectedTitle'
    hideIfOverriden: true
```


### Composite items
Composite filter items have a `children` option that must contain a dictionary of more filter items as
well as a `combine` option which can contain either `AND` or `OR` - defining how the children are
combined with each other.


```yaml
type: 'group'
combine: 'AND'
children:
    myFilterGroup:
        type: 'select'
        options: # ...
    myOtherFilterGroup:
        type: 'checkbox_list' # ...
```

If you use preconfigured filters you can unset children by setting the value of child to `~`:

```yaml
Project:
    filter:
        children:
            myFilterGroup:
                type: 'select'
                options: #...
            myOtherFilterGroup: ~
```

### Titled items
Titled filter items have the `title` and `titleRaw` options in order to define a title. The `title`
option defines where to find a language label to use as a title, the `titleRaw` defines a direct string
to use a title. For international websites the former is always preferred.

```yaml
titleRaw: 'This string is used directly'
# ---
title:
    id: 'filter.title.foo'
    source: 'Views/Filter'
    package: 'Newland.Toubiz.Poi.Neos'
```

### Data Sourced items
If an item is data sourced then the `dataSource` and `dataSourceArguments` options are available which
work the same as their neos counterparts:

```yaml
dataSource: 'newland-toubiz-poi-neos-categories'
dataSourceArguments:
    articleType: 2
```

The built-in data source `newland-neosfiltering-direct` can be used to specify options directly in
configuration:

```yaml
dataSource: 'newland-neosfiltering-direct'
dataSourceArguments:
    i18n:
        package: 'Newland.Toubiz.Poi.Neos'
        source: 'Views/Filter/Attributes'
    options:
         # Gets label from `foo` language label
        - value: 'foo'

         # Custom language label id
        - value: 'bar'
          i18n: { id: 'title.bar' }

         # Fully custom language resolution
        - value: 'baz'
          i18n:
              package: 'Newland.Toubiz.Poi.Neos'
              source: 'Views/Filter'
              id: 'attributes.title.baz'
```

### Range sourced items
Similar to data sourced items ranged items have `rangeSource` and `rangeSourceArguments` options.
In contrast to data sourced items however, the `rangeSource` must be a fully qualified class name
of the range source class.

```yaml
rangeSource: 'Newland\NeosFiltering\RangeSource\SimpleDatabaseRangeSource'
rangeSourceArguments:
    class: 'Newland\Toubiz\Sync\Neos\Domain\Model\Article'
    alias: 'article'
    column: 'article.someColumn'
```

If fixed bounds should be declared the `range` option can be used instead:

```yaml
range:
    min: 0
    max: 100
```

### Root item
The root item is the starting point of the filter: It is the top most filter item which is being
rendered to a page and which gets to interact directly with the database query.

The only special configuration on the root level is `join`: It can be used to define how other
entities are joined to the query. The join aliases can then be used in children items.

```yaml
combine: 'AND'
join:
    - column: 'entity.categories'
      alias: 'categories'
children:
    categories:
        type: 'select'
        databaseColumn: 'categories'
        dataSource: 'newland-toubiz-poi-neos-categories'
```
