Open keizah7 opened 4 years ago
I was trying to use Nova Flexible Content in a multilingual context without using outl1ne/nova-translatable package so I needed to use resolveUsing
for this matter.
Until there will be a built-in support for this, I manage to solve my issue by extending the Flexible class like following:
<?php
namespace App\Nova\Flexible\Fields;
use Laravel\Nova\Http\Requests\NovaRequest;
use Whitecube\NovaFlexibleContent\Flexible;
use Whitecube\NovaFlexibleContent\Value\Resolver;
class MyFlexible extends Flexible
{
/**
* The currently defined layout groups
*
* @var Illuminate\Support\Collection
*/
public $groups;
/**
* Resolve the field's value.
*
* @param mixed $resource
* @param string|null $attribute
* @return void
*/
public function resolve($resource, $attribute = null)
{
if ($this->resolveCallback && is_callable($this->resolveCallback)) {
$this->value = call_user_func($this->resolveCallback, $this, $resource, $attribute);
return;
}
parent::resolve($resource, $attribute);
}
/**
* Hydrate the given attribute on the model based on the incoming request.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param string $requestAttribute
* @param object $model
* @param string $attribute
* @return null|Closure
*/
protected function fillAttribute(NovaRequest $request, $requestAttribute, $model, $attribute)
{
if (isset($this->fillCallback)) {
return call_user_func($this->fillCallback, $request, $model, $attribute, $requestAttribute);
}
return parent::fillAttribute($request, $requestAttribute, $model, $attribute);
}
/**
* Resolve all contained groups and their fields
*
* @param Illuminate\Support\Collection $groups
* @return Illuminate\Support\Collection
*/
public function resolveGroups($groups)
{
return $groups->map(function ($group) {
return $group->getResolved();
});
}
/**
* Define the field's actual layout groups (as "base models") based
* on the field's current model & attribute
*
* @param mixed $resource
* @param string $attribute
* @return Illuminate\Support\Collection
*/
public function buildGroups($resource, $attribute)
{
if (!$this->resolver) {
$this->resolver(Resolver::class);
}
return $this->groups = $this->resolver->get($resource, $attribute, $this->layouts);
}
/**
* Registers a reference to the origin model for nested & contained fields
*
* @param mixed $model
* @return void
*/
public function registerOriginModel($model)
{
if (is_a($model, \Laravel\Nova\Resource::class)) {
$model = $model->model();
} elseif (is_a($model, \Whitecube\NovaPage\Pages\Template::class)) {
$model = $model->getOriginal();
}
if (! is_a($model, \Illuminate\Database\Eloquent\Model::class)) {
return;
}
static::$model = $model;
}
}
And I used it in my Nova Resource like following:
<?php
namespace App\Nova;
use App\Nova\Flexible\Fields\MyFlexible;
use App\Nova\Flexible\Layouts\HTML;
use Eminiarts\Tabs\Tab;
use Eminiarts\Tabs\Tabs;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Resource;
class Page extends Resource
{
// [...]
/**
* Get the fields displayed by the resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return array
*/
public function fields(NovaRequest $request)
{
$locales = config('localized-routes.supported-locales');
$fields = [
ID::make()->sortable(),
];
$tabs = [];
foreach ($locales as $locale) {
$tabs[] = Tab::make(\Str::upper($locale), $this->translatableFields($locale));
}
$fields[] = Tabs::make('locale', $tabs);
return $fields;
}
/**
* Get translatable fields.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return array
*/
public function translatableFields($locale)
{
return [
MyFlexible::make('Sections', 'sections->' . $locale)
->resolveUsing(function ($flexible, $resource, $attribute) use ($locale) {
$currentLocale = app()->getLocale();
app()->setLocale($locale);
$attribute = $attribute ?? $this->attribute;
$flexible->registerOriginModel($resource);
$flexible->buildGroups($resource, (explode('->', $attribute ?? $flexible->attribute))[0]);
$flexible->value = $flexible->resolveGroups($flexible->groups);
$value = $flexible->value;
app()->setLocale($currentLocale);
return $value;
})
->hideFromDetail()
->addLayout(Hero::class)
->addLayout(HTML::class),
];
}
// [...]
}
Of-course this approach is not ideal and comes with maintenance cost but for now I can move forward.
I hope it helps.
Probably we can (or should) use a custom resolver class to achieve what we want.
I managed to solve my issue with using a custom resolver class like following:
<?php
// app\Nova\Flexible\Resolvers\MultilingualSectionsResolver.php
namespace App\Nova\Flexible\Resolvers;
use Whitecube\NovaFlexibleContent\Value\ResolverInterface;
use Whitecube\NovaFlexibleContent\Value\Resolver;
class MultilingualSectionsResolver extends Resolver implements ResolverInterface
{
public $locale = null;
public $attribute_separator = '->';
public function __construct($locale, $attribute_separator = null)
{
$this->locale = $locale;
if(!is_null($attribute_separator)) {
$this->attribute_separator = $attribute_separator;
}
}
/**
* get the field's value
*
* @param mixed $resource
* @param string $attribute
* @param \Whitecube\NovaFlexibleContent\Layouts\Collection $layouts
* @return \Illuminate\Support\Collection
*/
public function get($resource, $attribute, $layouts)
{
$currentLocale = app()->getLocale();
app()->setLocale($this->locale);
$value = parent::get($resource, (explode($this->attribute_separator, $attribute))[0], $layouts);
app()->setLocale($currentLocale);
return $value;
}
}
And I used it in my Nova Resource like following:
<?php
// app\Nova\Page.php
namespace App\Nova;
use App\Nova\Flexible\Layouts\HTML;
use App\Nova\Flexible\Layouts\Hero;
use App\Nova\Flexible\Resolvers\MultilingualSectionsResolver;
use Eminiarts\Tabs\Tab;
use Eminiarts\Tabs\Tabs;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Resource;
use Whitecube\NovaFlexibleContent\Flexible;
class Page extends Resource
{
// [...]
/**
* Get the fields displayed by the resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return array
*/
public function fields(NovaRequest $request)
{
$locales = config('localized-routes.supported-locales');
$fields = [
ID::make()->sortable(),
];
$tabs = [];
foreach ($locales as $locale) {
$tabs[] = Tab::make(\Str::upper($locale), $this->translatableFields($locale));
}
$fields[] = Tabs::make('locale', $tabs);
return $fields;
}
/**
* Get translatable fields.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return array
*/
public function translatableFields($locale)
{
return [
Flexible::make('Sections', 'sections->' . $locale)
->resolver( new MultilingualSectionsResolver($locale) )
->hideFromDetail()
->addLayout(Hero::class)
->addLayout(HTML::class),
];
}
// [...]
}
This is much cleaner than my previous work around.
Of course that would be cool if we can use the resolveUsing
function also but a custom resolver class will do just fine.
Kudos to @whitecube team and all contributors of this beautiful package, thank you for good work.
First of all, thank you for pointing me in the right direction. Indeed, the best thing would be to support the built in transformer methods (resolveUsing, etc.).
In my case, I am editing parts of a larger JSON, which I would not want to polute with the layouts syntax, nor can I use casters, as, again, it's about only parts of a larger JSON.
So here is my basic Resolver I whipped up just now. Its role is to take a JSON array and inject a specified layout. It does the job, at least in my usecaes.
You use it like ->resolver(new SimpleJsonResolver('my_layout_name'))
.
Hope this helps someone. Maybe it could even find its place in the base code, as it would be a good helper for basic use cases, where you only need to edit a piece of random JSON.
`<?php
namespace App\Nova\Flexible\Resolvers;
use Illuminate\Support\Arr; use Whitecube\NovaFlexibleContent\Value\Resolver;
class SimpleJsonResolver extends Resolver { private string $layout = '';
public function __construct(string $layout)
{
$this->layout = $layout;
}
/**
* get the field's value
*
* @param mixed $resource
* @param string $attribute
* @param \Whitecube\NovaFlexibleContent\Layouts\Collection $layouts
* @return \Illuminate\Support\Collection
*/
public function get($resource, $attribute, $layouts)
{
$rawValue = $this->extractValueFromResource($resource, $attribute);
$value = Arr::map($rawValue, fn ($v, $k) => (object) [
'key' => $k,
'attributes' => $v,
]);
return collect($value)->map(function ($item) use ($layouts) {
$layout = $layouts->find($this->layout);
if (! $layout) {
return;
}
return $layout->duplicateAndHydrate($item->key, (array) $item->attributes);
})->filter()->values();
}
/**
* Set the field's value
*
* @param mixed $model
* @param string $attribute
* @param \Illuminate\Support\Collection $groups
* @return string
*/
public function set($model, $attribute, $groups)
{
return $model->$attribute = $groups->map(function ($group) {
return $group->getAttributes();
});
}
}`
Flexible content don't work with resolveUsing.
Flexible::make('Lėšų savininkai', 'client[funds_owners]')
I need fill custom value in resource update page and I can't do it.Also method hideWhenUpdating don't work with this field