diff --git a/resources/views/columns/actions.blade.php b/resources/views/columns/actions.blade.php
deleted file mode 100644
index 38d2ac524..000000000
--- a/resources/views/columns/actions.blade.php
+++ /dev/null
@@ -1,3 +0,0 @@
-
- {{ $label }}
- |
diff --git a/resources/views/columns/cells/row-actions.blade.php b/resources/views/columns/cells/row-actions.blade.php
deleted file mode 100644
index 4383598c8..000000000
--- a/resources/views/columns/cells/row-actions.blade.php
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
- @can('update', $model)
-
-
-
- @endcan
- @can('delete', $model)
-
- @endcan
-
- |
diff --git a/resources/views/columns/cells/row-select.blade.php b/resources/views/columns/cells/row-select.blade.php
deleted file mode 100644
index 9773c9b3b..000000000
--- a/resources/views/columns/cells/row-select.blade.php
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- |
diff --git a/resources/views/columns/column.blade.php b/resources/views/columns/column.blade.php
deleted file mode 100644
index ac79028bf..000000000
--- a/resources/views/columns/column.blade.php
+++ /dev/null
@@ -1,12 +0,0 @@
-
- @if($sortable)
-
- @else
- {{ $label }}
- @endif
- |
diff --git a/resources/views/columns/select-all.blade.php b/resources/views/columns/select-all.blade.php
deleted file mode 100644
index dd5a64283..000000000
--- a/resources/views/columns/select-all.blade.php
+++ /dev/null
@@ -1,6 +0,0 @@
-
- {{ __('Select') }}
-
- |
diff --git a/resources/views/columns/cells/cell.blade.php b/resources/views/resources/table/cell.blade.php
similarity index 100%
rename from resources/views/columns/cells/cell.blade.php
rename to resources/views/resources/table/cell.blade.php
diff --git a/resources/views/resources/table/column.blade.php b/resources/views/resources/table/column.blade.php
new file mode 100644
index 000000000..846d03bc4
--- /dev/null
+++ b/resources/views/resources/table/column.blade.php
@@ -0,0 +1,30 @@
+
+ @if($sortable)
+
+ @else
+ {{ $label }}
+ @endif
+ |
diff --git a/resources/views/resources/table/table.blade.php b/resources/views/resources/table/table.blade.php
index c4da2becb..13aad74c6 100644
--- a/resources/views/resources/table/table.blade.php
+++ b/resources/views/resources/table/table.blade.php
@@ -11,17 +11,58 @@
- @foreach($columns as $column)
- @include($column['template'], $column)
+ @if(! empty($actions))
+
+ {{ __('Select') }}
+
+ |
+ @endif
+ @foreach($data[0]['fields'] as $column)
+ @include('root::resources.table.column', $column)
@endforeach
+
+ {{ __('Actions') }}
+ |
@foreach($data as $row)
- @foreach($row['cells'] as $cell)
- @include($cell['template'], $cell)
+ @if(! empty($actions))
+
+
+ |
+ @endif
+ @foreach($row['fields'] as $cell)
+ @include('root::resources.table.cell', $cell)
@endforeach
+
+
+ @can('update', $row['model'])
+
+
+
+ @endcan
+ @can('delete', $row['model'])
+
+ @endcan
+
+ |
@endforeach
diff --git a/src/Actions/Action.php b/src/Actions/Action.php
index 28265af8f..55386ee7f 100644
--- a/src/Actions/Action.php
+++ b/src/Actions/Action.php
@@ -10,6 +10,7 @@
use Cone\Root\Traits\Authorizable;
use Cone\Root\Traits\Makeable;
use Cone\Root\Traits\RegistersRoutes;
+use Cone\Root\Traits\ResolvesVisibility;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
@@ -28,6 +29,7 @@ abstract class Action implements Arrayable, Form, JsonSerializable
use Authorizable;
use HasAttributes;
use Makeable;
+ use ResolvesVisibility;
use RegistersRoutes {
RegistersRoutes::registerRoutes as __registerRoutes;
}
@@ -219,12 +221,12 @@ public function toArray(): array
/**
* {@inheritdoc}
*/
- public function toTableComponent(Request $request): array
+ public function toForm(Request $request): array
{
return array_merge($this->toArray(), [
'open' => $this->errors($request)->isNotEmpty(),
'fields' => $this->resolveFields($request)
- ->mapToFormComponents($request, $this->query->getModel()),
+ ->mapToInputs($request, $this->query->getModel()),
]);
}
}
diff --git a/src/Actions/Actions.php b/src/Actions/Actions.php
index 98ef80f8e..37f8bb35f 100644
--- a/src/Actions/Actions.php
+++ b/src/Actions/Actions.php
@@ -3,6 +3,7 @@
namespace Cone\Root\Actions;
use Cone\Root\Traits\RegistersRoutes;
+use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Routing\Router;
use Illuminate\Support\Arr;
@@ -22,12 +23,28 @@ public function register(array|Action $actions): static
return $this;
}
+ /**
+ * Filter the actions that are available for the current request and model.
+ */
+ public function authorized(Request $request, Model $model = null): static
+ {
+ return $this->filter->authorized($request, $model)->values();
+ }
+
+ /**
+ * Filter the actions that are visible in the given context.
+ */
+ public function visible(string|array $context): static
+ {
+ return $this->filter->visible($context)->values();
+ }
+
/**
* Map the action to table components.
*/
- public function mapToTableComponents(Request $request): array
+ public function mapToForms(Request $request): array
{
- return $this->map->toTableComponent($request)->all();
+ return $this->map->toForm($request)->all();
}
/**
diff --git a/src/Columns/Column.php b/src/Columns/Column.php
deleted file mode 100644
index d78142eb2..000000000
--- a/src/Columns/Column.php
+++ /dev/null
@@ -1,193 +0,0 @@
-label = $label;
- $this->modelAttribute = $modelAttribute ?: Str::of($label)->lower()->snake()->value();
- }
-
- /**
- * Get the model attribute.
- */
- public function getModelAttribute(): string
- {
- return $this->modelAttribute;
- }
-
- /**
- * Get the Blade template.
- */
- public function getTemplate(): string
- {
- return $this->template;
- }
-
- /**
- * Set the sortable attribute.
- */
- public function sortable(bool|Closure $value = true): static
- {
- $this->sortable = $value;
-
- return $this;
- }
-
- /**
- * Determine if the field is sortable.
- */
- public function isSortable(): bool
- {
- if ($this->sortable instanceof Closure) {
- return call_user_func($this->sortable);
- }
-
- return $this->sortable;
- }
-
- /**
- * Get the sort URL.
- */
- public function getSortUrl(Request $request): ?string
- {
- if (! $this->isSortable()) {
- return null;
- }
-
- return match ($request->input('sort.order', 'asc')) {
- 'asc' => $request->fullUrlWithQuery(['sort' => ['order' => 'desc', 'sort' => $this->getModelAttribute()]]),
- default => $request->fullUrlWithQuery(['sort' => ['order' => 'asc', 'by' => $this->getModelAttribute()]]),
- };
- }
-
- /**
- * Set the searachable attribute.
- */
- public function searchable(bool|Closure $value = true): static
- {
- $this->searchable = $value;
-
- return $this;
- }
-
- /**
- * Determine if the field is searchable.
- */
- public function isSearchable(): bool
- {
- if ($this->searchable instanceof Closure) {
- return call_user_func($this->searchable);
- }
-
- return $this->searchable;
- }
-
- /**
- * Set the search query resolver.
- */
- public function searchWithQuery(Closure $callback): static
- {
- $this->searchQueryResolver = $callback;
-
- return $this;
- }
-
- /**
- * Resolve the search query.
- */
- public function resolveSearchQuery(Request $request, Builder $query, mixed $value): Builder
- {
- return is_null($this->searchQueryResolver)
- ? $query
- : call_user_func_array($this->searchQueryResolver, [$request, $query, $value]);
- }
-
- /**
- * * Convert the column to a table head.
- */
- public function toHead(Request $request): array
- {
- return [
- 'attribute' => $this->modelAttribute,
- 'label' => $this->label,
- 'sortable' => $this->isSortable(),
- 'sortUrl' => $this->getSortUrl($request),
- 'template' => 'root::columns.column',
- ];
- }
-
- /**
- * Convert the column to a cell.
- */
- public function toCell(Request $request, Model $model): array
- {
- return [
- 'attrs' => $this->newAttributeBag(),
- 'formattedValue' => $this->resolveFormat($request, $model),
- 'model' => $model,
- 'template' => $this->getTemplate(),
- 'value' => $this->resolveValue($request, $model),
- ];
- }
-}
diff --git a/src/Columns/Columns.php b/src/Columns/Columns.php
deleted file mode 100644
index 47116efbb..000000000
--- a/src/Columns/Columns.php
+++ /dev/null
@@ -1,55 +0,0 @@
-push($column);
- }
-
- return $this;
- }
-
- /**
- * Filter the searchable columns.
- */
- public function searchable(): Collection
- {
- return $this->filter->isSearchable();
- }
-
- /**
- * Filter the sortable columns.
- */
- public function sortable(): Collection
- {
- return $this->filter->isSortable();
- }
-
- /**
- * Map the columns to cells for the given model.
- */
- public function mapToHeads(Request $request): array
- {
- return $this->map->toHead($request)->all();
- }
-
- /**
- * Map the columns to cells for the given model.
- */
- public function mapToCells(Request $request, Model $model): array
- {
- return $this->map->toCell($request, $model)->all();
- }
-}
diff --git a/src/Columns/Relation.php b/src/Columns/Relation.php
deleted file mode 100644
index 251c4e3a7..000000000
--- a/src/Columns/Relation.php
+++ /dev/null
@@ -1,52 +0,0 @@
-searchableRelationAttributes = (array) $attributes;
-
- return $this;
- }
-
- /**
- * Set the sortable relation attribute.
- */
- public function sortBy(string $attribute): static
- {
- $this->sortableRelationAttribute = $attribute;
-
- return $this;
- }
-
- /**
- * Get the serachable relation attributes.
- */
- public function getSearchableRelationAttributes(): array
- {
- return $this->searchableRelationAttributes;
- }
-
- /**
- * Get the sortable relation attribute.
- */
- public function getSortableRelationAttribute(): string
- {
- return $this->sortableRelationAttribute;
- }
-}
diff --git a/src/Columns/RowActions.php b/src/Columns/RowActions.php
deleted file mode 100644
index 9ed53713c..000000000
--- a/src/Columns/RowActions.php
+++ /dev/null
@@ -1,23 +0,0 @@
- 'root::columns.actions',
- ]);
- }
-}
diff --git a/src/Columns/RowSelect.php b/src/Columns/RowSelect.php
deleted file mode 100644
index 5b91862ca..000000000
--- a/src/Columns/RowSelect.php
+++ /dev/null
@@ -1,23 +0,0 @@
- 'root::columns.select-all',
- ]);
- }
-}
diff --git a/src/Fields/BelongsToMany.php b/src/Fields/BelongsToMany.php
index 13267f3c4..1d4e7efc8 100644
--- a/src/Fields/BelongsToMany.php
+++ b/src/Fields/BelongsToMany.php
@@ -162,7 +162,7 @@ public function toOption(Request $request, Model $model, Model $related): array
$option['fields'] = is_null($this->pivotFieldsResolver)
? []
- : call_user_func_array($this->pivotFieldsResolver, [$request, $model, $related])->mapToFormComponents($request, $model);
+ : call_user_func_array($this->pivotFieldsResolver, [$request, $model, $related])->mapToInputs($request, $model);
return $option;
}
diff --git a/src/Fields/Dropdown.php b/src/Fields/Dropdown.php
index 458fc1bcc..e38adf6e1 100644
--- a/src/Fields/Dropdown.php
+++ b/src/Fields/Dropdown.php
@@ -27,9 +27,9 @@ public function __construct(string $label, string $modelAttribute = null)
/**
* {@inheritdoc}
*/
- public function toFormComponent(Request $request, Model $model): array
+ public function toInput(Request $request, Model $model): array
{
- $data = parent::toFormComponent($request, $model);
+ $data = parent::toInput($request, $model);
return array_merge($data, [
'options' => array_map(static function (array $option): array {
diff --git a/src/Fields/Editor.php b/src/Fields/Editor.php
index 3b1729043..0119d8f11 100644
--- a/src/Fields/Editor.php
+++ b/src/Fields/Editor.php
@@ -168,10 +168,10 @@ public function toArray(): array
/**
* {@inheritdoc}
*/
- public function toFormComponent(Request $request, Model $model): array
+ public function toInput(Request $request, Model $model): array
{
- return array_merge(parent::toFormComponent($request, $model), [
- 'media' => $this->media?->toFormComponent($request, $model),
+ return array_merge(parent::toInput($request, $model), [
+ 'media' => $this->media?->toInput($request, $model),
]);
}
}
diff --git a/src/Fields/Field.php b/src/Fields/Field.php
index b6aeca16a..dce6f015f 100644
--- a/src/Fields/Field.php
+++ b/src/Fields/Field.php
@@ -3,11 +3,13 @@
namespace Cone\Root\Fields;
use Closure;
+use Cone\Root\Traits\Authorizable;
use Cone\Root\Traits\HasAttributes;
use Cone\Root\Traits\Makeable;
-use Cone\Root\Traits\ResolvesModelValue;
+use Cone\Root\Traits\ResolvesVisibility;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\MessageBag;
+use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
@@ -18,10 +20,11 @@
abstract class Field implements Arrayable, JsonSerializable
{
+ use Authorizable;
use Conditionable;
use HasAttributes;
use Makeable;
- use ResolvesModelValue;
+ use ResolvesVisibility;
/**
* The Blade template.
@@ -33,6 +36,16 @@ abstract class Field implements Arrayable, JsonSerializable
*/
protected ?Closure $hydrateResolver = null;
+ /**
+ * The format resolver callback.
+ */
+ protected ?Closure $formatResolver = null;
+
+ /**
+ * The value resolver callback.
+ */
+ protected ?Closure $valueResolver = null;
+
/**
* The errors resolver callback.
*/
@@ -87,6 +100,21 @@ abstract class Field implements Arrayable, JsonSerializable
*/
protected bool $hydrated = false;
+ /**
+ * Indicates if the field is sortable.
+ */
+ protected bool|Closure $sortable = false;
+
+ /**
+ * Indicates if the field is searchable.
+ */
+ protected bool|Closure $searchable = false;
+
+ /**
+ * The search query resolver callback.
+ */
+ protected ?Closure $searchQueryResolver = null;
+
/**
* Create a new field instance.
*/
@@ -234,6 +262,80 @@ public function suffix(string $value): static
return $this;
}
+ /**
+ * Set the sortable attribute.
+ */
+ public function sortable(bool|Closure $value = true): static
+ {
+ $this->sortable = $value;
+
+ return $this;
+ }
+
+ /**
+ * Determine if the field is sortable.
+ */
+ public function isSortable(): bool
+ {
+ if ($this->sortable instanceof Closure) {
+ return call_user_func($this->sortable);
+ }
+
+ return $this->sortable;
+ }
+
+ /**
+ * Set the searachable attribute.
+ */
+ public function searchable(bool|Closure $value = true): static
+ {
+ $this->searchable = $value;
+
+ return $this;
+ }
+
+ /**
+ * Determine if the field is searchable.
+ */
+ public function isSearchable(): bool
+ {
+ if ($this->searchable instanceof Closure) {
+ return call_user_func($this->searchable);
+ }
+
+ return $this->searchable;
+ }
+
+ /**
+ * Set the search query resolver.
+ */
+ public function searchWithQuery(Closure $callback): static
+ {
+ $this->searchQueryResolver = $callback;
+
+ return $this;
+ }
+
+ /**
+ * Resolve the serach query.
+ */
+ public function resolveSearchQuery(Request $request, Builder $query, mixed $value): Builder
+ {
+ return is_null($this->searchQueryResolver)
+ ? $query
+ : call_user_func_array($this->searchQueryResolver, [$request, $query, $value]);
+ }
+
+ /**
+ * Set the value resolver.
+ */
+ public function value(Closure $callback): static
+ {
+ $this->valueResolver = $callback;
+
+ return $this;
+ }
+
/**
* Resolve the value.
*/
@@ -278,6 +380,38 @@ public function withoutOldValue(): mixed
return $this->withOldValue(false);
}
+ /**
+ * Get the default value from the model.
+ */
+ public function getValue(Model $model): mixed
+ {
+ return $model->getAttribute($this->getModelAttribute());
+ }
+
+ /**
+ * Set the format resolver.
+ */
+ public function format(Closure $callback): static
+ {
+ $this->formatResolver = $callback;
+
+ return $this;
+ }
+
+ /**
+ * Format the value.
+ */
+ public function resolveFormat(Request $request, Model $model): mixed
+ {
+ $value = $this->resolveValue($request, $model);
+
+ if (is_null($this->formatResolver)) {
+ return $value;
+ }
+
+ return call_user_func_array($this->formatResolver, [$request, $model, $value]);
+ }
+
/**
* Persist the request value on the model.
*/
@@ -405,21 +539,33 @@ public function toArray(): array
'prefix' => $this->prefix,
'suffix' => $this->suffix,
'template' => $this->getTemplate(),
+ 'searchable' => $this->isSearchable(),
+ 'sortable' => $this->isSortable(),
];
}
/**
* Get the form component data.
*/
- public function toFormComponent(Request $request, Model $model): array
+ public function toDisplay(Request $request, Model $model): array
{
return array_merge($this->toArray(), [
+ 'value' => $this->resolveValue($request, $model),
+ 'formattedValue' => $this->resolveFormat($request, $model),
+ ]);
+ }
+
+ /**
+ * Get the form component data.
+ */
+ public function toInput(Request $request, Model $model): array
+ {
+ return array_merge($this->toDisplay($request, $model), [
'attrs' => $this->newAttributeBag()->class([
'form-control--invalid' => $this->invalid($request),
]),
'error' => $this->error($request),
'invalid' => $this->invalid($request),
- 'value' => $this->resolveValue($request, $model),
]);
}
diff --git a/src/Fields/Fields.php b/src/Fields/Fields.php
index f456d3357..c1398fc03 100644
--- a/src/Fields/Fields.php
+++ b/src/Fields/Fields.php
@@ -35,6 +35,38 @@ public function persist(Request $request, Model $model): void
});
}
+ /**
+ * Filter the fields that are available for the current request and model.
+ */
+ public function authorized(Request $request, Model $model = null): static
+ {
+ return $this->filter->authorized($request, $model)->values();
+ }
+
+ /**
+ * Filter the fields that are visible in the given context.
+ */
+ public function visible(string|array $context): static
+ {
+ return $this->filter->visible($context)->values();
+ }
+
+ /**
+ * Filter the searchable fields.
+ */
+ public function searchable(): static
+ {
+ return $this->filter->isSearchable();
+ }
+
+ /**
+ * Filter the sortable fields.
+ */
+ public function sortable(): static
+ {
+ return $this->filter->isSortable();
+ }
+
/**
* Map the fields to validate.
*/
@@ -46,11 +78,19 @@ public function mapToValidate(Request $request, Model $model): array
}
/**
- * Map the field to form components.
+ * Map the fields to displayable data.
+ */
+ public function mapToDisplay(Request $request, Model $model): array
+ {
+ return $this->map->toDisplay($request, $model)->all();
+ }
+
+ /**
+ * Map the fields to form inputs.
*/
- public function mapToFormComponents(Request $request, Model $model): array
+ public function mapToInputs(Request $request, Model $model): array
{
- return $this->map->toFormComponent($request, $model)->all();
+ return $this->map->toInput($request, $model)->all();
}
/**
diff --git a/src/Fields/Fieldset.php b/src/Fields/Fieldset.php
index b5f37b0c1..985c5cf0e 100644
--- a/src/Fields/Fieldset.php
+++ b/src/Fields/Fieldset.php
@@ -77,10 +77,10 @@ public function invalid(Request $request): bool
/**
* {@inheritdoc}
*/
- public function toFormComponent(Request $request, Model $model): array
+ public function toInput(Request $request, Model $model): array
{
- return array_merge(parent::toFormComponent($request, $model), [
- 'fields' => $this->resolveFields($request)->mapToFormComponents($request, $model),
+ return array_merge(parent::toInput($request, $model), [
+ 'fields' => $this->resolveFields($request)->mapToInputs($request, $model),
]);
}
diff --git a/src/Fields/File.php b/src/Fields/File.php
index d8f847183..c6713fb97 100644
--- a/src/Fields/File.php
+++ b/src/Fields/File.php
@@ -247,9 +247,9 @@ public function toOption(Request $request, Model $model, Model $related): array
/**
* {@inheritdoc}
*/
- public function toFormComponent(Request $request, Model $model): array
+ public function toInput(Request $request, Model $model): array
{
- return array_merge(parent::toFormComponent($request, $model), [
+ return array_merge(parent::toInput($request, $model), [
'options' => $this->resolveOptions($request, $model),
]);
}
diff --git a/src/Columns/ID.php b/src/Fields/ID.php
similarity index 60%
rename from src/Columns/ID.php
rename to src/Fields/ID.php
index cf5a37d29..fee71343e 100644
--- a/src/Columns/ID.php
+++ b/src/Fields/ID.php
@@ -1,16 +1,14 @@
sortable();
}
}
diff --git a/src/Fields/Media.php b/src/Fields/Media.php
index b15ac2273..85e936c4e 100644
--- a/src/Fields/Media.php
+++ b/src/Fields/Media.php
@@ -186,9 +186,9 @@ public function routes(Router $router): void
/**
* {@inheritdoc}
*/
- public function toFormComponent(Request $request, Model $model): array
+ public function toInput(Request $request, Model $model): array
{
- $data = parent::toFormComponent($request, $model);
+ $data = parent::toInput($request, $model);
return array_merge($data, [
'modalKey' => $this->getModalKey(),
diff --git a/src/Fields/Meta.php b/src/Fields/Meta.php
index 4e39ac09b..2d0e77034 100644
--- a/src/Fields/Meta.php
+++ b/src/Fields/Meta.php
@@ -252,9 +252,9 @@ public function toArray(): array
/**
* {@inheritdoc}
*/
- public function toFormComponent(Request $request, Model $model): array
+ public function toInput(Request $request, Model $model): array
{
- return $this->field->toFormComponent($request, $model);
+ return $this->field->toInput($request, $model);
}
/**
diff --git a/src/Fields/Relation.php b/src/Fields/Relation.php
index 5f88b7584..92749e839 100644
--- a/src/Fields/Relation.php
+++ b/src/Fields/Relation.php
@@ -17,6 +17,16 @@ abstract class Relation extends Field
*/
protected Closure|string $relation;
+ /**
+ * The searchable columns.
+ */
+ protected array $searchableColumns = ['id'];
+
+ /**
+ * The sortable column.
+ */
+ protected string $sortableColumn = 'id';
+
/**
* Indicates if the field should be nullable.
*/
@@ -113,6 +123,42 @@ public function isNullable(): bool
return $this->nullable;
}
+ /**
+ * Set the searachable attribute.
+ */
+ public function searchable(bool|Closure $value = true, array $columns = ['id']): static
+ {
+ $this->searchableColumns = $columns;
+
+ return parent::searchable($value);
+ }
+
+ /**
+ * Get the searchable columns.
+ */
+ public function getSearchableColumns(): array
+ {
+ return $this->searchableColumns;
+ }
+
+ /**
+ * Set the sortable attribute.
+ */
+ public function sortable(bool|Closure $value = true, string $column = 'id'): static
+ {
+ $this->sortableColumn = $column;
+
+ return parent::sortable($value);
+ }
+
+ /**
+ * Get the sortable columns.
+ */
+ public function getSortableColumn(): string
+ {
+ return $this->sortableColumn;
+ }
+
/**
* Set the display resolver.
*/
@@ -242,9 +288,9 @@ public function toOption(Request $request, Model $model, Model $related): array
/**
* {@inheritdoc}
*/
- public function toFormComponent(Request $request, Model $model): array
+ public function toInput(Request $request, Model $model): array
{
- return array_merge(parent::toFormComponent($request, $model), [
+ return array_merge(parent::toInput($request, $model), [
'nullable' => $this->isNullable(),
'options' => $this->resolveOptions($request, $model),
]);
diff --git a/src/Fields/Repeater.php b/src/Fields/Repeater.php
index e40119a3f..cadb80082 100644
--- a/src/Fields/Repeater.php
+++ b/src/Fields/Repeater.php
@@ -173,7 +173,7 @@ public function buildOption(Request $request, Model $model): array
{
$option = $this->toOption($request, $model, $this->newTemporaryModel([]));
- $option['fields'] = $option['fields']->mapToFormComponents($request, $model);
+ $option['fields'] = $option['fields']->mapToInputs($request, $model);
$option['html'] = View::make('root::fields.repeater-option', $option)->render();
@@ -218,13 +218,13 @@ public function toOption(Request $request, Model $model, Model $tmpModel): array
/**
* {@inheritdoc}
*/
- public function toFormComponent(Request $request, Model $model): array
+ public function toInput(Request $request, Model $model): array
{
- return array_merge(parent::toFormComponent($request, $model), [
+ return array_merge(parent::toInput($request, $model), [
'addNewLabel' => $this->getAddNewOptionLabel(),
'max' => $this->max,
'options' => array_map(static function (array $option) use ($request, $model): array {
- $option['fields'] = $option['fields']->mapToFormComponents($request, $model);
+ $option['fields'] = $option['fields']->mapToInputs($request, $model);
return array_merge($option, [
'html' => View::make('root::fields.repeater-option', $option)->render(),
diff --git a/src/Fields/Select.php b/src/Fields/Select.php
index 35e6f76bd..f52037d20 100644
--- a/src/Fields/Select.php
+++ b/src/Fields/Select.php
@@ -107,9 +107,9 @@ public function newOption(mixed $value, string $label): Option
/**
* {@inheritdoc}
*/
- public function toFormComponent(Request $request, Model $model): array
+ public function toInput(Request $request, Model $model): array
{
- return array_merge(parent::toFormComponent($request, $model), [
+ return array_merge(parent::toInput($request, $model), [
'nullable' => $this->isNullable(),
'options' => $this->resolveOptions($request, $model),
]);
diff --git a/src/Fields/Slug.php b/src/Fields/Slug.php
index c71084c88..ae9434bf9 100644
--- a/src/Fields/Slug.php
+++ b/src/Fields/Slug.php
@@ -185,9 +185,9 @@ static function (array $match): string {
/**
* {@inheritdoc}
*/
- public function toFormComponent(Request $request, Model $model): array
+ public function toInput(Request $request, Model $model): array
{
- return array_merge(parent::toFormComponent($request, $model), [
+ return array_merge(parent::toInput($request, $model), [
'help' => $this->help ?: __('Leave it empty for auto-generated slug.'),
]);
}
diff --git a/src/Filters/Filters.php b/src/Filters/Filters.php
index 75fd7e9e3..7af8b743e 100644
--- a/src/Filters/Filters.php
+++ b/src/Filters/Filters.php
@@ -21,6 +21,14 @@ public function register(array|Filter $filters): static
return $this;
}
+ /**
+ * Filter the filters that are available for the given request.
+ */
+ public function authorized(Request $request): static
+ {
+ return $this->filter->authorized($request)->values();
+ }
+
/**
* Apply the filters on the query.
*/
diff --git a/src/Filters/Search.php b/src/Filters/Search.php
index 8d22a2b10..7e378d03d 100644
--- a/src/Filters/Search.php
+++ b/src/Filters/Search.php
@@ -2,25 +2,25 @@
namespace Cone\Root\Filters;
-use Cone\Root\Columns\Column;
-use Cone\Root\Columns\Columns;
-use Cone\Root\Columns\Relation;
+use Cone\Root\Fields\Field;
+use Cone\Root\Fields\Fields;
+use Cone\Root\Fields\Relation;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class Search extends RenderableFilter
{
/**
- * The searchable columns.
+ * The searchable fields.
*/
- protected Columns $columns;
+ protected Fields $fields;
/**
* Create a new filter instance.
*/
- public function __construct(Columns $columns)
+ public function __construct(Fields $fields)
{
- $this->columns = $columns;
+ $this->fields = $fields;
}
/**
@@ -28,9 +28,9 @@ public function __construct(Columns $columns)
*/
public function apply(Request $request, Builder $query, mixed $value): Builder
{
- $attributes = $this->columns->mapWithKeys(static function (Column $column): array {
+ $attributes = $this->fields->mapWithKeys(static function (Field $field): array {
return [
- $column->getModelAttribute() => $column instanceof Relation ? $column->getSearchableRelationAttributes() : null,
+ $field->getModelAttribute() => $field instanceof Relation ? $field->getSearchableColumns() : null,
];
})->all();
@@ -39,15 +39,15 @@ public function apply(Request $request, Builder $query, mixed $value): Builder
}
return $query->where(static function (Builder $query) use ($attributes, $value): void {
- foreach ($attributes as $attribute => $columns) {
+ foreach ($attributes as $attribute => $fields) {
$operator = array_key_first($attributes) === $attribute ? 'and' : 'or';
- if (is_array($columns)) {
- $query->has($attribute, '>=', 1, $operator, static function (Builder $query) use ($columns, $value): Builder {
- foreach ($columns as $column) {
- $operator = $columns[0] === $column ? 'and' : 'or';
+ if (is_array($fields)) {
+ $query->has($attribute, '>=', 1, $operator, static function (Builder $query) use ($fields, $value): Builder {
+ foreach ($fields as $field) {
+ $operator = $fields[0] === $field ? 'and' : 'or';
- $query->where($query->qualifyColumn($column), 'like', "%{$value}%", $operator);
+ $query->where($query->qualifyColumn($field), 'like', "%{$value}%", $operator);
}
return $query;
diff --git a/src/Filters/Sort.php b/src/Filters/Sort.php
index 810d09446..f895fd685 100644
--- a/src/Filters/Sort.php
+++ b/src/Filters/Sort.php
@@ -2,9 +2,9 @@
namespace Cone\Root\Filters;
-use Cone\Root\Columns\Column;
-use Cone\Root\Columns\Columns;
-use Cone\Root\Columns\Relation;
+use Cone\Root\Fields\Field;
+use Cone\Root\Fields\Fields;
+use Cone\Root\Fields\Relation;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\Relation as EloquentRelation;
@@ -13,16 +13,16 @@
class Sort extends Filter
{
/**
- * The sortable columns.
+ * The sortable fields.
*/
- protected Columns $columns;
+ protected Fields $fields;
/**
* Create a new filter instance.
*/
- public function __construct(Columns $columns)
+ public function __construct(Fields $fields)
{
- $this->columns = $columns;
+ $this->fields = $fields;
}
/**
@@ -32,9 +32,9 @@ public function apply(Request $request, Builder $query, mixed $value): Builder
{
$value = array_replace(['by' => 'id', 'order' => 'desc'], (array) $value);
- $attributes = $this->columns->mapWithKeys(static function (Column $column): array {
+ $attributes = $this->fields->mapWithKeys(static function (Field $field): array {
return [
- $column->getModelAttribute() => $column instanceof Relation ? $column->getSortableRelationAttribute() : null,
+ $field->getModelAttribute() => $field instanceof Relation ? $field->getSortableColumn() : null,
];
})->all();
@@ -57,7 +57,7 @@ public function apply(Request $request, Builder $query, mixed $value): Builder
? $relation->getQualifiedOwnerKeyName()
: $relation->getQualifiedParentKeyName();
- return $relation->whereColumn($relation->getQualifiedForeignKeyName(), '=', $key);
+ return $relation->whereField($relation->getQualifiedForeignKeyName(), '=', $key);
});
return $query->orderBy(
diff --git a/src/Resources/Resource.php b/src/Resources/Resource.php
index 98cb8ad22..d6eccbde0 100644
--- a/src/Resources/Resource.php
+++ b/src/Resources/Resource.php
@@ -12,7 +12,6 @@
use Cone\Root\Traits\Authorizable;
use Cone\Root\Traits\RegistersRoutes;
use Cone\Root\Traits\ResolvesActions;
-use Cone\Root\Traits\ResolvesColumns;
use Cone\Root\Traits\ResolvesFilters;
use Cone\Root\Traits\ResolvesWidgets;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
@@ -31,13 +30,12 @@ abstract class Resource implements Arrayable, Form, Table
{
use AsForm;
use Authorizable;
- use RegistersRoutes {
- RegistersRoutes::registerRoutes as __registerRoutes;
- }
use ResolvesActions;
- use ResolvesColumns;
use ResolvesFilters;
use ResolvesWidgets;
+ use RegistersRoutes {
+ RegistersRoutes::registerRoutes as __registerRoutes;
+ }
/**
* The model class.
@@ -274,7 +272,9 @@ public function paginate(Request $request): LengthAwarePaginator
->through(function (Model $model) use ($request): array {
return [
'id' => $model->getKey(),
- 'cells' => $this->resolveColumns($request)->mapToCells($request, $model),
+ 'url' => $this->modelUrl($model),
+ 'model' => $model,
+ 'fields' => $this->resolveFields($request)->mapToDisplay($request, $model),
];
});
}
@@ -315,15 +315,14 @@ public function toIndex(Request $request): array
{
return array_merge($this->toArray(), [
'title' => $this->getName(),
- 'columns' => $this->resolveColumns($request)->mapToHeads($request),
- 'actions' => $this->resolveActions($request)->mapToTableComponents($request),
+ 'actions' => $this->resolveActions($request)->mapToForms($request),
'data' => $this->paginate($request),
'widgets' => $this->resolveWidgets($request)->all(),
'perPageOptions' => $this->getPerPageOptions(),
'filters' => $this->resolveFilters($request)
->renderable()
->map(function (RenderableFilter $filter) use ($request): array {
- return $filter->toField()->toFormComponent($request, $this->getModelInstance());
+ return $filter->toField()->toInput($request, $this->getModelInstance());
})
->all(),
'activeFilters' => $this->resolveFilters($request)->active($request)->count(),
@@ -340,7 +339,7 @@ public function toCreate(Request $request): array
'model' => $model = $this->getModelInstance(),
'action' => $this->getUri(),
'method' => 'POST',
- 'fields' => $this->resolveFields($request)->mapToFormComponents($request, $model),
+ 'fields' => $this->resolveFields($request)->mapToInputs($request, $model),
]);
}
@@ -354,7 +353,7 @@ public function toEdit(Request $request, Model $model): array
'model' => $model,
'action' => $this->modelUrl($model),
'method' => 'PATCH',
- 'fields' => $this->resolveFields($request)->mapToFormComponents($request, $model),
+ 'fields' => $this->resolveFields($request)->mapToInputs($request, $model),
]);
}
}
diff --git a/src/Traits/ResolvesColumns.php b/src/Traits/ResolvesColumns.php
deleted file mode 100644
index 4f86ef6c5..000000000
--- a/src/Traits/ResolvesColumns.php
+++ /dev/null
@@ -1,66 +0,0 @@
-columns)) {
- $this->columns = new Columns($this->columns($request));
-
- if ($this->resolveActions($request)->isNotEmpty()) {
- $this->columns->prepend(
- RowSelect::make(__('Select'), 'id')->value(static function (Request $request, Model $model): string {
- return $model->getKey();
- })
- );
- }
-
- $this->columns->push(
- RowActions::make(__('Actions'), 'id')->value(function (Request $request, Model $model): string {
- return $this->modelUrl($model);
- })
- );
-
- $this->columns->each(function (Column $column) use ($request): void {
- $this->resolveColumn($request, $column);
- });
- }
-
- return $this->columns;
- }
-
- /**
- * Handle the callback for the column resolution.
- */
- protected function resolveColumn(Request $request, Column $column): void
- {
- //
- }
-}
diff --git a/src/Traits/ResolvesFilters.php b/src/Traits/ResolvesFilters.php
index 14b2311ab..c247b39fd 100644
--- a/src/Traits/ResolvesFilters.php
+++ b/src/Traits/ResolvesFilters.php
@@ -2,7 +2,7 @@
namespace Cone\Root\Traits;
-use Cone\Root\Columns\Columns;
+use Cone\Root\Fields\Fields;
use Cone\Root\Filters\Filter;
use Cone\Root\Filters\Filters;
use Cone\Root\Filters\Search;
@@ -36,16 +36,16 @@ public function resolveFilters(Request $request): Filters
if (is_null($this->filters)) {
$this->filters = new Filters($this->filters($request));
- $this->resolveColumns($request)
+ $this->resolveFields($request)
->searchable()
- ->whenNotEmpty(function (Columns $columns): void {
- $this->filters->prepend(new Search($columns));
+ ->whenNotEmpty(function (Fields $fields): void {
+ $this->filters->prepend(new Search($fields));
});
- $this->resolveColumns($request)
+ $this->resolveFields($request)
->sortable()
- ->whenNotEmpty(function (Columns $columns): void {
- $this->filters->register(new Sort($columns));
+ ->whenNotEmpty(function (Fields $fields): void {
+ $this->filters->register(new Sort($fields));
});
if (in_array(SoftDeletes::class, class_uses_recursive($this->getModel()))) {
diff --git a/src/Traits/ResolvesModelValue.php b/src/Traits/ResolvesModelValue.php
deleted file mode 100644
index 1b6f1d64f..000000000
--- a/src/Traits/ResolvesModelValue.php
+++ /dev/null
@@ -1,81 +0,0 @@
-valueResolver = $callback;
-
- return $this;
- }
-
- /**
- * Resolve the value.
- */
- public function resolveValue(Request $request, Model $model): mixed
- {
- $value = $this->getValue($model);
-
- if (is_null($this->valueResolver)) {
- return $value;
- }
-
- return call_user_func_array($this->valueResolver, [$request, $model, $value]);
- }
-
- /**
- * Get the default value from the model.
- */
- public function getValue(Model $model): mixed
- {
- return $model->getAttribute($this->getModelAttribute());
- }
-
- /**
- * Set the format resolver.
- */
- public function format(Closure $callback): static
- {
- $this->formatResolver = $callback;
-
- return $this;
- }
-
- /**
- * Format the value.
- */
- public function resolveFormat(Request $request, Model $model): mixed
- {
- $value = $this->resolveValue($request, $model);
-
- if (is_null($this->formatResolver)) {
- return $value;
- }
-
- return call_user_func_array($this->formatResolver, [$request, $model, $value]);
- }
-}
diff --git a/src/Traits/ResolvesVisibility.php b/src/Traits/ResolvesVisibility.php
new file mode 100644
index 000000000..0126c6936
--- /dev/null
+++ b/src/Traits/ResolvesVisibility.php
@@ -0,0 +1,58 @@
+visibilityResolvers as $callback) {
+ if (! call_user_func_array($callback, [$context])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Set a custom visibility resolver.
+ */
+ public function visibleOn(string|array|Closure $context): static
+ {
+ $this->visibilityResolvers[] = $context instanceof Closure
+ ? $context
+ : static function (string|array $currentContext) use ($context) {
+ return ! empty(array_intersect(Arr::wrap($currentContext), Arr::wrap($context)));
+ };
+
+ return $this;
+ }
+
+ /**
+ * Set a custom hidden resolver.
+ */
+ public function hiddenOn(string|array|Closure $context): static
+ {
+ $context = $context instanceof Closure
+ ? static function (array|string $currentContext) use ($context): bool {
+ return ! call_user_func_array($context, [$currentContext]);
+ }
+ : static function (string|array $currentContext) use ($context) {
+ return empty(array_intersect(Arr::wrap($currentContext), Arr::wrap($context)));
+ };
+
+ return $this->visibleOn($context);
+ }
+}
diff --git a/stubs/Resource.stub b/stubs/Resource.stub
index db1122b25..0d0deb47f 100644
--- a/stubs/Resource.stub
+++ b/stubs/Resource.stub
@@ -5,7 +5,6 @@ namespace {{ namespace }};
use Cone\Root\Fields\Fields;
use Cone\Root\Interfaces\Form;
use Cone\Root\Resources\Resource;
-use Cone\Root\Columns\Columns;
use Cone\Root\Table\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
@@ -18,16 +17,6 @@ class {{ class }} extends Resource
*/
protected string $model = {{ model }};
- /**
- * Define the columns.
- */
- public function columns(Request $request): array
- {
- return array_merge(parent::columns($request), [
- //
- ]);
- }
-
/**
* Define the fields.
*/
diff --git a/stubs/UserResource.stub b/stubs/UserResource.stub
index 29c7a1437..d5e15ed0c 100644
--- a/stubs/UserResource.stub
+++ b/stubs/UserResource.stub
@@ -2,9 +2,8 @@
namespace App\Root\Resources;
-use Cone\Root\Columns\Column;
-use Cone\Root\Columns\ID;
use Cone\Root\Fields\Email;
+use Cone\Root\Fields\ID;
use Cone\Root\Fields\Text;
use Cone\Root\Resources\Resource;
use Illuminate\Database\Eloquent\Model;
@@ -18,30 +17,14 @@ class UserResource extends Resource
*/
protected string $icon = 'users';
- /**
- * Define the columns.
- */
- public function columns(Request $request): array
- {
- return array_merge(parent::columns($request), [
- ID::make(),
-
- Column::make(__('Name'), 'name')
- ->searchable()
- ->sortable(),
-
- Column::make(__('Email'), 'email')
- ->searchable()
- ->sortable(),
- ]);
- }
-
/**
* Define the fields.
*/
public function fields(Request $request): array
{
return array_merge(parent::fields($request), [
+ ID::make(),
+
Text::make(__('Name'), 'name')
->rules(['required', 'string', 'max:256']),