Clauses
Refine is a paid package. To purchase, head to hammerstone.dev.
In addition to attributes, a basic building block of conditions is "clauses".
The clause is the part of the condition that links the attribute to the data. Let's break that down a bit.
If you have a condition that allows your users to filter based on employee name, then your end users will end up with filters that include criteria like:
- Name equals "Aaron"
- Name starts with "A"
- Name does not contain "test"
The pattern that you can see here is [attribute] [clause] [value]
.
@TODO Insert screenshot of frontend with attribute clause and value identified.
In the examples above, the attribute
is "Name". The values are "Aaron", "Jo", and "test'. The clauses are the words that link the attributes
to the values
: "equals", "starts with", and "does not contain".
Default Clauses
Every standard condition that uses clauses defines its own set of reasonable defaults for you to use without having to change anything. However, there may be certain conditions where you don't want particular clauses showing up. We've provided a number of methods to make that easier for you.
Excluding Clauses
If you would like to exclude certain clauses, you can do so by calling the withoutClauses
method.
TextCondition::make('name') ->withoutClauses([ TextCondition::CLAUSE_SET, TextCondition::CLAUSE_NOT_SET ]);
This will prevent the clauses from being sent to the frontend and shown to your end users. This also prevents them from being valid choices on the backend, if your users were to try to sneak them in.
Including Only Certain Clauses
Instead of excluding clauses, if you'd prefer to strictly include specific clauses, the onlyClauses
method is available to you.
TextCondition::make('name') ->onlyClauses([ TextCondition::CLAUSE_SET, TextCondition::CLAUSE_NOT_SET ]);
With the configuration above, your end users would only be able to choose between the two clauses we've explicitly allowed, the Is Set
and Is Not Set
clauses.
Adding Clauses Back
If you've excluded clauses that you need to later re-add, you can do that with the withClauses
method.
$condition = TextCondition::make('name') ->withoutClauses([ TextCondition::CLAUSE_SET, TextCondition::CLAUSE_NOT_SET ]); // Later on... $condition->withClauses([ TextCondition::CLAUSE_SET,]);
You can chain withClauses
, withoutClauses
, and onlyClauses
in any order that you want, but whatever you call last takes the highest priority.
Constants
Every standard condition uses clauses, which are represented by class level constants to ease development. We keep the string representations of the clauses as compact as possible so that stabilized filters can be as small as possible.
Any time you need to use a clause, it's recommended that you use the constant:
// This is good...TextCondition::make('name') ->onlyClauses([ TextCondition::CLAUSE_SET, TextCondition::CLAUSE_NOT_SET ]); // This is bad...TextCondition::make('name') ->onlyClauses([ 'st', 'nst' ]);
Displays
Every clause has "id" and a "display" attributes. The id is the value that the frontend and backend communicate via, while the display value is the value you can show to your end users.
For example, for the clause TextCondition::CLAUSE_STARTS_WITH
the id is 'sw'
and the display is 'Starts With'
by default.
In many cases, you may want to have different display values than the ones we've provided for you. You can use the remapClauseDisplays
method to accomplish that.
TextCondition::make('name') ->remapClauseDisplays([ TextCondition::CLAUSE_STARTS_WITH => 'begins with', TextCondition::CLAUSE_CONTAINS => 'includes', ]);
In some cases, you may want to remap the displays for every instance of a condition. If your application is not in English, then you would want to change all the displays to the language of your application.
You can accomplish this with the static $defaultClauseDisplayMap
.
// In a service provider... TextCondition::$defaultClauseDisplayMap = [ TextCondition::CLAUSE_EQUALS => 'équivaut à', TextCondition::CLAUSE_DOESNT_EQUAL => 'n\'est pas égal', TextCondition::CLAUSE_STARTS_WITH => 'commence avec', TextCondition::CLAUSE_ENDS_WITH => 'se termine par', TextCondition::CLAUSE_DOESNT_START_WITH => 'ne commence pas par', TextCondition::CLAUSE_DOESNT_END_WITH => 'ne commence pas par', TextCondition::CLAUSE_CONTAINS => 'contient', TextCondition::CLAUSE_DOESNT_CONTAIN => 'ne contient pas', TextCondition::CLAUSE_SET => 'est réglé', TextCondition::CLAUSE_NOT_SET => 'n\'est pas défini',];
Now every TextCondition you use will have French displays instead of the standard English ones.
If you have both a defaultClauseDisplayMap
and also call remapClauseDisplays
, the latter will take precedence.
Custom Clauses
We've provided a decent number of clauses out of the box for you, but there are undoubtedly going to be times you want to add custom clauses specific to your application.
To add custom clauses, you would first need to create a custom condition. In this example, we're going to add a "Is This Year" clause to the Date Condition.
Every condition that uses clauses has an empty method called customClauses
that you can use in your extended class.
class DateCondition extends BaseDateCondition{ const CLAUSE_THIS_YEAR = 'ty'; protected function customClauses() { return [ Clause::make(self::CLAUSE_THIS_YEAR, 'Is This Year') ]; }}
The
Clause
class is a simple helper class that provides a few quality-of-life methods that you can use.
By extending the Date Condition and populating the custom clauses method, you now have a new clause of "Is This Year" that your users can choose from. Of course you'll need to apply this condition differently, because the Date Condition won't know how to handle this custom condition. (You can read all about extending conditions here.)
In the case of the Date Condition, we're going to hook into the modifyDatesAndClause
. We'll first test to make sure we're dealing with a custom clause by checking isCustomClause
, if not we'll return what we were given.
If we are dealing with our custom clause, we're going to set date1
to the beginning of the year, date2
to the end, and then change the clause the standard CLAUSE_BETWEEN
:
class DateCondition extends BaseDateCondition{ const CLAUSE_THIS_YEAR = 'ty'; protected function customClauses() { return [ Clause::make(self::CLAUSE_THIS_YEAR, 'Is This Year') ]; } protected function modifyDatesAndClause($date1, $date2, $clause) { if (!$this->isCustomClause($clause)) { return [$date1, $date2, $clause]; } return [ now()->startOfYear(), now()->endOfYear(), DateCondition::CLAUSE_BETWEEN ]; }}
Each condition handles the application of custom clauses a little bit differently, so you'll have to check the condition you're extending for the best place to hook in.
As a final note: all of your custom clauses work with the standard clause operations, meaning you can reference them in the withClauses
, withoutClauses
, onlyClauses
, remapDisplay
, etc. methods.
Clause Validation Rules
If your clause requires custom validation rules, you can add rules that will only be checked when the user has chosen your clause.
Let's say that you are building a custom clause that lets users filter for dates that are within a week of a given date. In that scenario, you'd want to validate that the date was populated.
class DateCondition extends BaseDateCondition{ const CLAUSE_WITHIN_A_WEEK = 'wtin_week'; protected function customClauses() { return [ Clause::make(self::CLAUSE_WITHIN_A_WEEK, 'Is Within a Week of') ->rules([ 'date1' => ['required'] ]) ]; }}
When the user chooses your custom clause, we'll validate that date1
is present. (The base Date Condition also has a rule to validate that date1
is a valid date when it is required.)
If you're just adding required fields, there is a shorthand requiresInputs
method you can call:
class DateCondition extends BaseDateCondition{ const CLAUSE_WITHIN_A_WEEK = 'wtin_week'; protected function customClauses() { return [ Clause::make(self::CLAUSE_WITHIN_A_WEEK, 'Is Within a Week of') ->requiresInputs('date1') ]; }}