Open pqr opened 3 years ago
Hi, I was thinking about creating something like a lookup table.
Where the syntax will not be bound to the field, instead, we will create small components. Something similar to, https://github.com/orchidsoftware/platform/issues/1489
Description::make('name')->component(ShortString::class);
Description::make('price')->component(Numeric::class);
Description::make('body')->component(Html::class);
// img/html/etc
This way, it will be easy for us to add this to the screen. At the same time, maintaining the independence of the fields. What do you think about it?
Why is it so? Because, for example, the Textarea field can have different values for display. First 100 characters, 300 characters. Or for example, @mention support as soon as I did in this post @pqr
Also, with others. Whether a reference to an object should be specified in the Relation / Select field or just display text. And many, many other things. Such simple Laravel components, according to the idea, should help us.
On the one hand we could have many, many other things to be be customized on "view" screen - agree.
On the the other hand in most cases there is a "default" behavior that satisfy:
\nl2br(\e($value))
$value->format($this->format)
\e($value)
and so on... So for every type of Field we could define reasonable and sensible way to display it on "view"/"list" screen - this would cover most default cases.
When customization is needed, then Field class could provide a few options:
TextArea::make('description')->viewComponent(MyTextView::class)
TextArea::make('description')->viewRender(/* pass callback here which returns view or string */)
TextArea::make('description')->viewTemplate('path/to/blade/template')
By the way sometimes it also helpful to customize "edit" representation of field without creating a new extends Field
class.
TextArea::make('description')->editComponent(MyTextEdit::class)
TextArea::make('description')->editRender(/* pass callback here which returns view or string */)
TextArea::make('description')->editTemplate('path/to/blade/template')
@pqr Did you mean exactly CRUD (https://github.com/orchidsoftware/crud) or just use in general?
I just don't really understand the idea.
We'll have to specify fields everywhere, but depending on the context (the result will be different for the layers)?
class ListLayout extends Table
{
protected function columns() : array
{
return [
TextArea::make('description'),
]
}
}
Result: simple text
class Edit extends Row
{
protected function fields() : array
{
return [
TextArea::make('description'),
]
}
}
Result: <textarea>
tag.
Is this how it should work?
Is this how it should work?
Exactly!
The main idea: define field once, use everywhere (in table layout, in view layout, in edit layout...)
More robust example:
(note: Money
is not a real class from Orchid, imagine it just for example)
class FinanceFields
{
public static function invoiceAmount()
{
return Money::make('invoice_amount', 'Invoice Amount')
->align(TD::ALIGN_RIGHT)
->sort()
->currency('USD')
->placeholder('Enter Invoice Amount')
->required()
}
}
class ListLayout extends Table
{
protected function columns() : array
{
return [
FinanceFields:: invoiceAmount(),
]
}
}
class Edit extends Row
{
protected function fields() : array
{
return [
FinanceFields:: invoiceAmount(),
]
}
}
Here we mix methods and properties from Field and from TD:
align()
, sort()
- have effect only when rendered in Table layoutplaceholder()
, required()
- needed only for Edit layoutcurrency()
- rendered in all layoutsPhilosophically it sounds bad: we mix different responsibilities in one class, we all know low cohesion is anti-pattern.
On the other side me personally like it: I like to define all field properties for all possible environments (table/view/edit) in one place.
Did you mean exactly CRUD (https://github.com/orchidsoftware/crud) or just use in general?
First I thought about CRUD, but then I decided to place this issue into platform repository to propose such unification in general in the core of Orchid.
One more thing: this concept of "unified field" can be implemented as class which holds only meta information about field properties and settings, but no actual methods to render.
abstract class UniField {} // keeps only basic properties like $sort, $align, $required
class UniMoney extends UniField {} // add money specific fields like $currency
Then we could have factories to produce good old orchids Field or TD:
class UniMoney extends UniField
{
protected string $currency = '';
// ... other pros related to money functionality
public function asField(): Fieldable
{
return Input::make(...); // converts meta information about this field into good old Orchids Input field
}
public function asTD(): TD
{
return TD::make(...); // converts meta information about this field into good old Orchids TD
}
}
I think I can try to build it as separate package and battle test in my current projects.
In theory, we don't need to link them at a low level. Just make a facade over them. Something like:
namespace Orchid\Support;
use Orchid\Screen\Fields\Input;
use Orchid\Screen\TD;
/**
* Class ExampleCombinator
*
* @mixin Input|TD
*/
class ExampleCombinator
{
/**
* @var Input
*/
protected $field;
/**
* @var TD
*/
protected $td;
/**
* Combinator constructor.
*
* @param string|null $name
*/
public function __construct(string $name = null)
{
$this->field = Input::make($name);
$this->td = TD::make($name);
}
/**
* Create a new Field element.
*
* @param string|null $name
*
* @return static
*/
public static function make(string $name = null): self
{
return new static($name);
}
/**
* @return Input
*/
public function getField()
{
return $this->field;
}
/**
* @return TD
*/
public function getTd(): TD
{
return $this->td;
}
/**
* @param string $name
* @param array $arguments
*
* @return Combinator
*/
public function __call(string $name, array $arguments)
{
$this->fill($name, $arguments);
return $this;
}
/**
* @param string $name
* @param array|null $arguments
*/
private function fill(string $name, ?array $arguments = [])
{
rescue(function () use ($name, $arguments) {
$this->getField()->$name(...$arguments);
$this->getTd()->$name(...$arguments);
}, null, false);
}
}
Then the result of using will be something like what you want:
$combinator = ExampleCombinator::make('name')->required();
dd($combinator);
It would also work right now, without the major version. For example, we defined fields somewhere (for example, in a model)
public static function fields(): Collection
{
return collect([
ExampleCombinator::make('name')->required()
]);
}
Then in rows and tables, we only needed to call:
class ListLayout extends Table
{
protected function columns() : array
{
return YourModel::fields()->map->getTd()->toArray();
}
}
class Edit extends Row
{
protected function fields() : array
{
return YourModel::fields()->map->getField()->toArray();
}
}
I think we have many options, from a facade to a simple DTO with a set of getter and setter.
VDF::make()
->setField(Input::make()->required())
->setTd(Td::make()->align(TD::ALIGN_RIGHT))
The only thing that confuses me is the use of all sorts of groupings and dynamic things.
Is your feature request related to a problem? Please describe. For typical CRUD interface I'am usually create 3 screens: List screen, View scree, Edit screen.
Currently I have to describe each model field 3 times:
TD
with customrender
function for List screenViewField
orLabel
for View screenField
for Edit screenIt would be helpful if each type of Field (e.g. Input, TextArea, DateTimer, ...) can be easily rendered as readonly markup for List screen (as TD) and for View screen.
Describe the solution you'd like Good example is Laravel Nova - fields are described only once and then Nova renders them in different forms (in table cell, on view screen, on edit screen)