Skip to content

Commit

Permalink
Merge pull request #997 from lee-to/new-has-many-buttons
Browse files Browse the repository at this point in the history
fix: Hot
  • Loading branch information
lee-to authored May 29, 2024
2 parents 8819d83 + c488872 commit e887421
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 95 deletions.
5 changes: 3 additions & 2 deletions routes/moonshine.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@
GlobalSearchController::class
)->name('global-search');

Route::prefix('relation/{pageUri}')->controller(RelationModelFieldController::class)->group(
Route::prefix('relation/{pageUri}/{resourceUri?}/{resourceItem?}')->controller(RelationModelFieldController::class)->group(
function (): void {
Route::get('{resourceUri?}/{resourceItem?}', 'search')->name('relation.search');
Route::get('/has-many-form', 'hasManyForm')->name('relation.has-many-form');
Route::get('/', 'search')->name('relation.search');
}
);

Expand Down
108 changes: 16 additions & 92 deletions src/Buttons/HasManyButton.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,9 @@
namespace MoonShine\Buttons;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOneOrMany;
use MoonShine\ActionButtons\ActionButton;
use MoonShine\Collections\MoonShineRenderElements;
use MoonShine\Components\FormBuilder;
use MoonShine\Enums\JsEvent;
use MoonShine\Fields\Field;
use MoonShine\Fields\Fields;
use MoonShine\Fields\Hidden;
use MoonShine\Fields\Relationships\HasMany;
use MoonShine\Fields\Relationships\ModelRelationField;
use MoonShine\Resources\ModelResource;
use MoonShine\Support\AlpineJs;
use Throwable;

final class HasManyButton
Expand All @@ -27,58 +18,34 @@ final class HasManyButton
public static function for(
HasMany $field,
bool $update = false,
?ActionButton $button = null
?ActionButton $button = null,
): ActionButton {
/** @var ModelResource $resource */
$resource = $field->getResource();
$parent = $field->getRelatedModel();
$relation = $parent?->{$field->getRelationName()}();
$parentResource = moonshineRequest()->getResource();
$parentPage = moonshineRequest()->getPage();

if (! $resource->formPage()) {
return ActionButton::emptyHidden();
}

$action = $update
? static fn (Model $data) => $resource->route('crud.update', $data->getKey())
: static fn (?Model $data) => $resource->route('crud.store');
$action = static fn (?Model $data) => $parentResource->route(
'relation.has-many-form',
moonshineRequest()->getItemID(),
[
'pageUri' => $parentPage?->uriKey(),
'_relation' => $field->getRelationName(),
'_key' => $data?->getKey(),
]
);

$isAsync = $resource->isAsync() || $field->isAsync();

$getFields = function () use ($resource, $field, $isAsync, $parent, $update) {
$fields = $resource->getFormFields();

$fields->onlyFields()
->each(fn (Field $nestedFields): Field => $nestedFields->setParent($field))
// Uncomment if you need a parent resource
//->onlyRelationFields()
//->each(fn (ModelRelationField $nestedFields): Field => $nestedFields->setParentResource($resource))
;

return $fields->when(
$field->getRelation() instanceof MorphOneOrMany,
fn (Fields $f) => $f->push(
Hidden::make($field->getRelation()?->getMorphType())
->setValue($parent::class)
)
)->when(
$update,
fn (Fields $f) => $f->push(
Hidden::make('_method')->setValue('PUT'),
)
)
->push(
Hidden::make($field->getRelation()?->getForeignKeyName())
->setValue($parent->getKey())
)
->push(Hidden::make('_async_field')->setValue($isAsync))
->toArray();
};

$authorize = $update
? fn (?Model $item): bool => ! is_null($item) && in_array('update', $resource->getActiveActions())
&& $resource->setItem($item)->can('update')
&& $resource->setItem($item)->can('update')
: fn (?Model $item): bool => in_array('create', $resource->getActiveActions())
&& $resource->can('create');
&& $resource->can('create');

$actionButton = $button
? $button->setUrl($action)
Expand All @@ -88,52 +55,9 @@ public static function for(
->canSee($authorize)
->inModal(
title: fn (): array|string|null => __($update ? 'moonshine::ui.edit' : 'moonshine::ui.create'),
content: fn (?Model $data): string => (string) FormBuilder::make($action($data))
->reactiveUrl(
fn (): string => moonshineRouter()
->reactive(key: $data?->getKey(), page: $resource->formPage(), resource: $resource)
)
->name($resource->uriKey())
->switchFormMode(
$isAsync,
[
$resource->listEventName($field->getRelationName()),
AlpineJs::event(JsEvent::FORM_RESET, $resource->uriKey()),
]
)
->when(
$update,
fn (FormBuilder $form): FormBuilder => $form->fillCast(
$data,
$resource->getModelCast()
),
fn (FormBuilder $form): FormBuilder => $form->fillCast(
array_filter([
$field->getRelation()?->getForeignKeyName() => $parent?->getKey(),
...$field->getRelation() instanceof MorphOneOrMany
? [$field->getRelation()?->getMorphType() => $parent?->getMorphClass()]
: [],
], static fn ($value) => filled($value)),
$resource->getModelCast()
)
)
->submit(__('moonshine::ui.save'), ['class' => 'btn-primary btn-lg'])
->fields($getFields)
->onBeforeFieldsRender(fn (Fields $fields): MoonShineRenderElements => $fields->exceptElements(
fn (mixed $field): bool => $field instanceof ModelRelationField
&& $field->toOne()
&& $field->column() === $relation->getForeignKeyName()
))
->buttons($resource->getFormButtons())
->redirect(
$isAsync ?
null
: moonshineRequest()
->getResource()
?->formPageUrl($parent)
),
content: '',
async: $isAsync,
wide: true,
closeOutside: false,
)
->primary()
->icon($update ? 'heroicons.outline.pencil' : 'heroicons.outline.plus');
Expand Down
117 changes: 117 additions & 0 deletions src/Http/Controllers/RelationModelFieldController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,22 @@
namespace MoonShine\Http\Controllers;

use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOneOrMany;
use MoonShine\Collections\MoonShineRenderElements;
use MoonShine\Components\FormBuilder;
use MoonShine\Components\TableBuilder;
use MoonShine\Contracts\Fields\Relationships\HasAsyncSearch;
use MoonShine\Enums\JsEvent;
use MoonShine\Fields\Field;
use MoonShine\Fields\Fields;
use MoonShine\Fields\Hidden;
use MoonShine\Fields\Relationships\HasMany;
use MoonShine\Fields\Relationships\ModelRelationField;
use MoonShine\Fields\Relationships\MorphTo;
use MoonShine\Http\Requests\Relations\RelationModelFieldRequest;
use MoonShine\Resources\ModelResource;
use MoonShine\Support\AlpineJs;
use MoonShine\Support\DBOperators;
use MoonShine\Support\TableRowRenderer;
use Symfony\Component\HttpFoundation\Response;
Expand Down Expand Up @@ -113,4 +125,109 @@ public function searchRelations(RelationModelFieldRequest $request): mixed

return $value;
}

/**
* @throws Throwable
*/
public function hasManyForm(RelationModelFieldRequest $request): string
{
$parent = $request->getResource()?->getItemOrInstance();

/** @var HasMany $field */
$field = $request->getField();

/** @var ModelResource $resource */
$resource = $field->getResource();
$item = $field->getResource()
->setItemID($request->get('_key', ''))
->getItemOrInstance();
$update = $item->exists;
$relation = $parent?->{$field->getRelationName()}();

$field->resolveFill($parent->toArray(), $parent);

$action = $update
? static fn (Model $data) => $resource->route('crud.update', $data->getKey())
: static fn (?Model $data) => $resource->route('crud.store');

$isAsync = $resource->isAsync() || $field->isAsync();

$getFields = function () use ($resource, $field, $isAsync, $parent, $update) {
$fields = $resource->getFormFields();

$fields->onlyFields()
->each(fn (Field $nestedFields): Field => $nestedFields->setParent($field))
// Uncomment if you need a parent resource
//->onlyRelationFields()
//->each(fn (ModelRelationField $nestedFields): Field => $nestedFields->setParentResource($resource))
;

return $fields->when(
$field->getRelation() instanceof MorphOneOrMany,
fn (Fields $f) => $f->push(
Hidden::make($field->getRelation()?->getMorphType())
->setValue($parent::class)
)
)->when(
$update,
fn (Fields $f) => $f->push(
Hidden::make('_method')->setValue('PUT'),
)
)
->push(
Hidden::make($field->getRelation()?->getForeignKeyName())
->setValue($parent->getKey())
)
->push(Hidden::make('_async_field')->setValue($isAsync))
->toArray();
};

$formName = $resource->uriKey() . "-" . ($item?->getKey() ?? 'create');

return (string) FormBuilder::make($action($item))
->fields($getFields)
->reactiveUrl(
fn (): string => moonshineRouter()
->reactive(key: $item?->getKey(), page: $resource->formPage(), resource: $resource)
)
->name($formName)
->switchFormMode(
$isAsync,
array_filter([
$resource->listEventName($field->getRelationName()),
$update ? null : AlpineJs::event(JsEvent::FORM_RESET, $formName),
])
)
->when(
$update,
fn (FormBuilder $form): FormBuilder => $form->fillCast(
$item,
$resource->getModelCast()
),
fn (FormBuilder $form): FormBuilder => $form->fillCast(
array_filter([
$field->getRelation()?->getForeignKeyName() => $parent?->getKey(),
...$field->getRelation() instanceof MorphOneOrMany
? [$field->getRelation()?->getMorphType() => $parent?->getMorphClass()]
: [],
], static fn ($value) => filled($value)),
$resource->getModelCast()
)
)
->submit(__('moonshine::ui.save'), ['class' => 'btn-primary btn-lg'])
->onBeforeFieldsRender(fn (Fields $fields): MoonShineRenderElements => $fields->exceptElements(
fn (mixed $field): bool => $field instanceof ModelRelationField
&& $field->toOne()
&& $field->column() === $relation->getForeignKeyName()
))
->buttons($resource->getFormButtons())
->redirect(
$isAsync
?
null
: moonshineRequest()
->getResource()
?->formPageUrl($parent)
);
}
}
11 changes: 10 additions & 1 deletion src/Traits/Resource/ResourceModelQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,23 @@ trait ResourceModelQuery

protected ?Builder $customBuilder = null;

protected int|string|null $itemID = null;

protected array $parentRelations = [];

// TODO 3.0 rename to saveQueryState
protected bool $saveFilterState = false;

public function setItemID(int|string|null $itemID): static
{
$this->itemID = $itemID;

return $this;
}

public function getItemID(): int|string|null
{
return moonshineRequest()->getItemID();
return $this->itemID ?? moonshineRequest()->getItemID();
}

/**
Expand Down

0 comments on commit e887421

Please sign in to comment.