Skip to content

Allow sub-entities #605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions demo/app/Sharp/Entities/PostBlockEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,11 @@

use App\Sharp\Posts\Blocks\PostBlockList;
use App\Sharp\Posts\Blocks\PostBlockPolicy;
use App\Sharp\Posts\Blocks\PostBlockTextForm;
use App\Sharp\Posts\Blocks\PostBlockVideoForm;
use App\Sharp\Posts\Blocks\PostBlockVisualsForm;
use Code16\Sharp\Utils\Entities\SharpEntity;

class PostBlockEntity extends SharpEntity
{
protected ?string $list = PostBlockList::class;
protected ?string $policy = PostBlockPolicy::class;
protected string $label = 'Block';

public function getMultiforms(): array
{
return [
'text' => [PostBlockTextForm::class, 'Text block'],
'visuals' => [PostBlockVisualsForm::class, 'Visuals block'],
'video' => [PostBlockVideoForm::class, 'Video block'],
];
}
}
11 changes: 11 additions & 0 deletions demo/app/Sharp/Entities/PostBlockTextEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace App\Sharp\Entities;

use App\Sharp\Posts\Blocks\PostBlockTextForm;

class PostBlockTextEntity extends PostBlockEntity
{
protected ?string $form = PostBlockTextForm::class;
protected string $label = 'Text block';
}
11 changes: 11 additions & 0 deletions demo/app/Sharp/Entities/PostBlockVideoEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace App\Sharp\Entities;

use App\Sharp\Posts\Blocks\PostBlockVideoForm;

class PostBlockVideoEntity extends PostBlockEntity
{
protected ?string $form = PostBlockVideoForm::class;
protected string $label = 'Video block';
}
13 changes: 13 additions & 0 deletions demo/app/Sharp/Entities/PostBlockVisualsEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace App\Sharp\Entities;

use App\Sharp\Posts\Blocks\PostBlockVisualsForm;
use App\Sharp\Posts\Blocks\PostBlockVisualsShow;

class PostBlockVisualsEntity extends PostBlockEntity
{
protected ?string $form = PostBlockVisualsForm::class;
protected ?string $show = PostBlockVisualsShow::class;
protected string $label = 'Visual block';
}
13 changes: 12 additions & 1 deletion demo/app/Sharp/Posts/Blocks/PostBlockList.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

use App\Models\Media;
use App\Models\PostBlock;
use App\Sharp\Entities\PostBlockTextEntity;
use App\Sharp\Entities\PostBlockVideoEntity;
use App\Sharp\Entities\PostBlockVisualsEntity;
use Code16\Sharp\EntityList\Eloquent\SimpleEloquentReorderHandler;
use Code16\Sharp\EntityList\EntityListEntities;
use Code16\Sharp\EntityList\Fields\EntityListField;
use Code16\Sharp\EntityList\Fields\EntityListFieldsContainer;
use Code16\Sharp\EntityList\Filters\HiddenFilter;
Expand All @@ -29,7 +33,14 @@ protected function buildList(EntityListFieldsContainer $fields): void

public function buildListConfig(): void
{
$this->configureMultiformAttribute('type')
$this
->configureEntityMap(
attribute: 'type',
entities: EntityListEntities::make()
->addEntity('text', PostBlockTextEntity::class)
->addEntity('video', PostBlockVideoEntity::class)
->addEntity('visuals', PostBlockVisualsEntity::class),
)
->configureReorderable(new SimpleEloquentReorderHandler(PostBlock::class))
->configureQuickCreationForm();
}
Expand Down
62 changes: 62 additions & 0 deletions demo/app/Sharp/Posts/Blocks/PostBlockVisualsShow.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace App\Sharp\Posts\Blocks;

use App\Models\PostBlock;
use Code16\Sharp\Form\Eloquent\Uploads\Transformers\SharpUploadModelFormAttributeTransformer;
use Code16\Sharp\Show\Fields\SharpShowFileField;
use Code16\Sharp\Show\Fields\SharpShowListField;
use Code16\Sharp\Show\Fields\SharpShowTextField;
use Code16\Sharp\Show\Layout\ShowLayout;
use Code16\Sharp\Show\Layout\ShowLayoutColumn;
use Code16\Sharp\Show\Layout\ShowLayoutSection;
use Code16\Sharp\Show\SharpShow;
use Code16\Sharp\Utils\Fields\FieldsContainer;

class PostBlockVisualsShow extends SharpShow
{
protected function buildShowFields(FieldsContainer $showFields): void
{
$showFields
->addField(
SharpShowListField::make('files')
->setLabel('Visuals')
->addItemField(
SharpShowFileField::make('file')
)
->addItemField(
SharpShowTextField::make('legend')
->setLabel('Legend')
)
);
}

protected function buildShowLayout(ShowLayout $showLayout): void
{
$showLayout
->addSection(function (ShowLayoutSection $section) {
$section
->addColumn(12, function (ShowLayoutColumn $column) {
$column
->withListField('files', function (ShowLayoutColumn $item) {
$item->withFields('file')
->withField('legend');
});
});
});
}

public function find(mixed $id): array
{
$postBlock = PostBlock::findOrFail($id);

return $this
->setCustomTransformer('files', new SharpUploadModelFormAttributeTransformer())
->transform($postBlock);
}

public function delete(mixed $id): void
{
PostBlock::findOrFail($id)->delete();
}
}
4 changes: 2 additions & 2 deletions docs/.vitepress/sidebar.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {DefaultTheme} from "vitepress";
import type { DefaultTheme } from "vitepress";

export function sidebar(): DefaultTheme.SidebarItem[] {
return [
Expand Down Expand Up @@ -26,7 +26,6 @@ export function sidebar(): DefaultTheme.SidebarItem[] {
collapsed: true,
items: [
{ text: 'Create a Form', link: '/guide/building-form.md' },
{ text: 'Multi-Forms', link: '/guide/multiforms.md' },
{ text: 'Using Single Form for unique resources', link: '/guide/single-form.md' },
{ text: 'Quick creation form', link: '/guide/quick-creation-form.md' },
{ text: 'Write an Embed for the Editor field', link: '/guide/form-editor-embeds.md' },
Expand All @@ -44,6 +43,7 @@ export function sidebar(): DefaultTheme.SidebarItem[] {
{ text: 'List', link: '/guide/form-fields/list.md' },
{ text: 'AutocompleteList', link: '/guide/form-fields/autocomplete-list.md' },
{ text: 'Geolocation', link: '/guide/form-fields/geolocation.md' },
{ text: 'Multi-Forms (deprecated)', link: '/guide/multiforms.md' },
// { text: 'Custom form field', link: '/guide/custom-form-fields.md' }
]
},
Expand Down
61 changes: 59 additions & 2 deletions docs/guide/building-entity-list.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,9 @@ Here is the full list of available methods:

- `configureDefaultSort(string $sortBy, string $sortDir = "asc")`: `EntityListQueryParams $queryParams` will be filled with this default value (see above)

- `configureMultiformAttribute(string $attribute)`: handle various types of entities; see [detailed doc](multiforms.md)
- `configureMultiformAttribute(string $attribute)`: :warning: This feature has been deprecated in version 9.6.0 and was replaced by the [Entity Map](entity-map.md) feature. You can still access to the [documentation](multiforms.md) for legacy usage.

- `configureEntityMap(string $attribute, EntityListEntities $entities)`: configure an Entity Map to display multiple entities in a single Entity List; [see detailed section](#entity-map) above.

- `configurePageAlert(string $template, string $alertLevel = null, string $fieldKey = null, bool $declareTemplateAsPath = false)`: display a dynamic message above the list; [see detailed doc](page-alerts.md)

Expand All @@ -239,10 +241,65 @@ Here is the full list of available methods:

- `configureCreateButtonLabel(string $label)` to set a custom "New..." button label.

## Configure the Entity List
## Declare the Entity List

The Entity List must be declared in the correct entity class, as documented here: [Write an entity](entity-class.md)).

After this we can access the Entity List at the following URL: **/sharp/s-list/products** (replace "products" by our entity key).

To go ahead and learn how to add a link in the Sharp side menu, [look here](building-menu.md).

## Entity Map

::: info
This feature replaces the deprecated Multiforms functionality, which remains available for legacy use in version 9.x but will be removed in 10.x.
:::

The Entity Map lets you display multiple entities within a single Entity List. This makes it possible to link different Show Pages or Forms based on a discriminating attribute.

To set it up, declare the Entity Map in the `buildListConfig()` method by using `configureEntityMap()`:

```php
class CarList extends SharpEntityList
{
// ...

public function buildListConfig(): void
{
$this
->configureEntityMap(
attribute: 'engine',
entities: EntityListEntities::make()
->addEntity('ice', InternalCombustionEngineCarEntity::class)
->addEntity('ev', ElectricEngineCarEntity::class, 'lucide-plug', 'EV')
);
}
}
```

The `attribute` parameter defines which attribute will be used to distinguish between entities. If needed, you can compute this value using a [custom transformer](./how-to-transform-data.md).

The `entities` parameter expects an instance of `EntityListEntities`, which maps each discriminant value to a specific Entity. You can also specify the icon and label that will be displayed in the "Create" dropdown.

Each mapped entity should be declared like any regular Entity, and can include a Show Page, a Form, a Policy, etc.

::: tip
It’s not mandatory, but a good practice is to make your “sub-entities” extend the main Entity class to share policies or common logic. For example:
:::

```php
class CarEntity extends SharpEntity
{
protected ?string $list = CarList::class;
protected ?string $policy = CarPolicy::class;
protected string $label = 'Car';
}
```

```php
class ElectricEngineCarEntity extends CarEntity
{
protected ?string $form = ElectricEngineCarForm::class;
protected string $label = 'EV';
}
```
6 changes: 4 additions & 2 deletions docs/guide/entity-class.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,13 @@ class MyEntity extends SharpEntity

### Single shows and forms

When you need to configure a "unique" resource that does not fit into a List / Show schema, like for instance an account or a configuration item, you can use a Single Show or Form. This is a dedicated topic, [documented here](single-show.md).
When you need to configure a "unique" resource that does not fit into a List / Show schema, like an account or a configuration item, you can use a Single Show or Form. This is a dedicated topic, [documented here](single-show.md).

### Handle Multiforms

Multiforms allows to declare different forms for the same entity, to hanle variants. This is a dedicated topic, [documented here](multiforms.md).
::: info
This feature has been deprecated and was replaced in version 9.6.0 by the [Entity Map](./building-entity-list.md#entity-map) feature.
:::

## Declare the Entity in Sharp configuration

Expand Down
8 changes: 6 additions & 2 deletions docs/guide/multiforms.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Multi-Forms
# Multi-Forms (deprecated)

::: warning
Multi-Forms is a feature that has been deprecated in version 9.6.0 and was replaced by the [Entity Map](./building-entity-list.md#entity-map) feature.
:::

Let's say you want to handle different variants for an entity in one Entity List.

For instance, maybe you want to display sold cars on an Entity List: easy enough, you create a `Car` entity, list and form. But you want the to handle different form fields for cars with an internal combustion engine and those with an electric engine; you can of course use a form and [conditional display](building-form.md#conditional-display) to achieve this, but in a case where there are many differences, the best option may be to split the Entity in two (or more) Forms. That's Multi-Form.
For instance, maybe you want to display sold cars on an Entity List: easy enough, you create a `Car` entity, list and form. But you want to handle different form fields for cars with an internal combustion engine and those with an electric engine; you can of course use a form and [conditional display](building-form.md#conditional-display) to achieve this, but in a case where there are many differences, the best option may be to split the Entity in two (or more) Forms. That's Multi-Form.

## Write the Form classes

Expand Down
6 changes: 3 additions & 3 deletions resources/js/Layouts/Layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@
<DropdownMenuSeparator class="first:hidden" />
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Sun class="w-4 h-4 mr-2 dark:hidden" />
<Moon class="hidden w-4 h-4 mr-2 dark:block" />
<Sun class="w-4 h-4 dark:hidden" />
<Moon class="hidden w-4 h-4 dark:block" />
{{ __('sharp::action_bar.color-mode-dropdown.label') }}
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
Expand All @@ -233,7 +233,7 @@
<form :action="route('code16.sharp.logout')" method="post">
<input name="_token" :value="getCsrfToken()" type="hidden">
<DropdownMenuItem type="submit" @click="$event.target.closest('form').submit()">
<LogOut class="w-4 h-4 mr-2" />
<LogOut class="w-4 h-4" />
{{ __('sharp::menu.logout_label') }}
</DropdownMenuItem>
</form>
Expand Down
2 changes: 1 addition & 1 deletion resources/js/Pages/Show/Show.vue
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@
:collapsable="section.collapsable"
:entity-key="entityKey"
:instance-id="instanceId"
:is-right-col="columnIndex === section.columns.length - 1"
:is-right-col="columnIndex > 0 && columnIndex === section.columns.length - 1"
:row="row"
@reordering="onEntityListReordering(fieldLayout.key, $event)"
/>
Expand Down
33 changes: 2 additions & 31 deletions resources/js/entity-list/EntityList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import {
EntityStateValueData,
FilterData,
} from "@/types";
import { getAppendableParentUri, route } from "@/utils/url";
import { EntityListInstance, InstanceId } from "./types";

export class EntityList implements EntityListData {
authorizations: EntityListData['authorizations'];
config: EntityListData['config'];
data: EntityListData['data'];
fields: EntityListData['fields'];
forms: EntityListData['forms'];
entities: EntityListData['entities'];
meta: EntityListData['meta'];
pageAlert: EntityListData['pageAlert'];
query: EntityListData['query'];
Expand Down Expand Up @@ -110,31 +109,6 @@ export class EntityList implements EntityListData {
return instance[this.config.instanceIdAttribute];
}

instanceUrl(instance: EntityListInstance): string | null {
const entityKey = this.entityKey;
const instanceId = this.instanceId(instance);

if(!this.authorizations.view.includes(instanceId)) {
return null;
}

const multiform = this.forms && Object.values(this.forms).find(form => form.instances.includes(instanceId));

if(this.config.hasShowPage) {
return route('code16.sharp.show.show', {
parentUri: getAppendableParentUri(),
entityKey: multiform ? `${entityKey}:${multiform.key}` : entityKey,
instanceId,
});
}

return route('code16.sharp.form.edit', {
parentUri: getAppendableParentUri(),
entityKey: multiform ? `${entityKey}:${multiform.key}` : entityKey,
instanceId,
});
}

instanceState(instance: EntityListInstance): string | number | null {
return this.config.state
? instance[this.config.state.attribute]
Expand All @@ -153,10 +127,7 @@ export class EntityList implements EntityListData {
}

instanceCanDelete(instance: EntityListInstance): boolean {
if(Array.isArray(this.authorizations.delete)) {
return this.authorizations.delete?.includes(this.instanceId(instance));
}
return !!this.authorizations.delete;
return instance._meta.authorizations.delete;
}

instanceCommands(instance: EntityListInstance): Array<Array<CommandData>> | undefined {
Expand Down
Loading
Loading