tighten / nova-package-development

A forum for talking about the process of Nova Package Development
52 stars 2 forks source link

Nova EAV Attributes #69

Open cord opened 5 years ago

cord commented 5 years ago

Package that allows to administrate EAV attributes and edit attribute values through Nova.

This allows to easily have custom attributes added to any model and edit the values within the resources.

Based on https://github.com/rinvex/laravel-attributes

I have a running implementation which supports sorting and translatable attributes.

If you are interested to finalise this into a package, please let me know how to share the code

icehouze commented 5 years ago

I'd be very interested in a package like this. I'm currently planning to implement rivenex/laravel-attributes and was concerned about getting it all to play nice in Nova.

While I can't be of any help in putting together a package (simply not experienced enough), I would love to see code samples if you're willing to share.

cord commented 5 years ago

let me outline the solution, which will give you sortable, translatable attributes you can add to any Model/Resource in Nova:

install and configure https://github.com/rinvex/laravel-attributes

Migration to add column id to attribute_entity Table

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddIdToAttributeEntityTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('attribute_entity', function (Blueprint $table) {
            $table->increments('id')->first();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('attribute_entity', function (Blueprint $table) {
            $table->dropColumn('id');
        });
    }
}

make a wrapper models for nova compatibility:

Attribute.php

use Illuminate\Database\Eloquent\Relations\HasMany;

class Attribute extends \Rinvex\Attributes\Models\Attribute
{
    public $timestamps = true;
    /**
     * Get the entities attached to this attribute.
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function attributeEntities(): HasMany
    {
        return parent::entities();
    }
}

AttributeEntity.php

use Illuminate\Database\Eloquent\Relations\BelongsTo;

class AttributeEntity extends  \Rinvex\Attributes\Models\AttributeEntity
{
    public $timestamps = true;
    /**
     * Get the attribute attached to this entity.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function attribute(): BelongsTo
    {
        return $this->belongsTo(config('rinvex.attributes.models.attribute'));
    }
}

Resources:

Nova/Attribute.php


namespace App\Nova;

use Illuminate\Http\Request;
use Laravel\Nova\Fields\HasMany;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Boolean;

use Naxon\NovaFieldSortable\Sortable;
use Naxon\NovaFieldSortable\Concerns\SortsIndexEntries;

use Laravel\Nova\Fields\Select;

class Attribute extends Resource
{
    use SortsIndexEntries;

    /**
     * The model the resource corresponds to.
     *
     * @var  string
     */
    public static $model = Attribute::class;

    /**
     * The single value that should be used to represent the resource when being displayed.
     *
     * @var  string
     */
    public static $title = 'name';

    /**
     * The columns that should be searched.
     *
     * @var  array
     */
    public static $search = [
        'slug', 'name'
    ];

    /**
     * Get the displayable label of the resource.
     *
     * @return  string
     */
    public static function label()
    {
        return __('Attributes');
    }

    /**
     * Get the displayable singular label of the resource.
     *
     * @return  string
     */
    public static function singularLabel()
    {
        return __('Attribute');
    }

    /**
     * Get the fields displayed by the resource.
     *
     * @param    \Illuminate\Http\Request $request
     * @return  array
     */
    public function fields(Request $request)
    {
        return
                [
                    Sortable::make(__('Sort Order'), 'id')->onlyOnIndex(),
                    Select::make(__('Type'), 'type')
                        ->options(
                            array_combine(
                                array_keys(\Rinvex\Attributes\Models\Attribute::typeMap()),
                                array_keys(array_change_key_case(\Rinvex\Attributes\Models\Attribute::typeMap(), CASE_UPPER))
                            )
                        )
                        ->rules('required')
                    ,
                    Text::make(__('Group'), 'group')
                    ,
                    Boolean::make(__('Is Required'), 'is_required')
                        ->rules('required')
                    ,
                    Boolean::make(__('Is Collection'), 'is_collection')
                        ->rules('required')
                    ,
                    Text::make(__('Default'), 'default')
                    ,

                    HasMany::make(__('Attribute Entities'), 'AttributeEntities')
                        ->rules('required'),
                ];
    }

}

Nova/AttributeEntity.php


namespace App\Nova;

use Illuminate\Http\Request;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\Select;

class AttributeEntity extends Resource
{
    /**
     * The model the resource corresponds to.
     *
     * @var  string
     */
    public static $model = \AttributeEntity::class;

    /**
     * The single value that should be used to represent the resource when being displayed.
     *
     * @var  string
     */
    public static $title = 'entity_type';

    /**
     * The columns that should be searched.
     *
     * @var  array
     */
    public static $search = [
        'id','entity_type'
    ];

    /**
     * Get the displayable label of the resource.
     *
     * @return  string
     */
    public static function label()
    {
        return __('Attribute Entities');
    }

    /**
     * Get the displayable singular label of the resource.
     *
     * @return  string
     */
    public static function singularLabel()
    {
        return __('Attribute Entity');
    }

    /**
     * Get the fields displayed by the resource.
     *
     * @param    \Illuminate\Http\Request $request
     * @return  array
     */
    public function fields(Request $request)
    {
        return [
            BelongsTo::make(__('Attribute'), 'Attribute')
                ->rules('required')
            ,
            Select::make(__('Entity Type'), 'entity_type')
                ->options(
                    array_combine(
                        app('rinvex.attributes.entities')->toArray(),
                        app('rinvex.attributes.entities')->toArray()
                    )
                )
                ->rules('required')
            ,
//            Number::make(__('Entity Id'), 'entity_id'), // not used???
        ];
    }
}

Now you should be able to administrate the attributes in Nova.

In your models you want to add support for attributes using traits as following:

use Rinvex\Attributes\Traits\Attributable as Attributable;
use Rinvex\Support\Traits\HasTranslations as HasTranslations;

//... you code

    // Trait with compatibility hack to support translatable attributes
    use Attributable, HasTranslations {
        Attributable::setAttribute insteadof HasTranslations;
    }

In your Nova/Resource.php add


    public function attributeFields()
    {
        $attributes = app('rinvex.attributes.attribute')::whereHas('entities', function ($query) {
            $query->where('entity_type', '=', static::$model);
        })
            ->orderBy(static::$defaultSortField, 'asc')
            ->get();
        if (!$attributes) {
            return [];
        }
        $fields = [];

        $fields[] = Heading::make('Attributes');
        foreach ($attributes as $attribute) {
            $namespace = 'Laravel\Nova\Fields\\';

            switch ($attribute->type) {
                case 'varchar':
                    $type = 'Text';
                    break;
                case 'text':
                    $type = 'Textarea';
                    break;
                case 'integer':
                    $type = 'Number';
                    break;
                default:
                    $type = 'Text';
                    break;
            }

            $type = $namespace . $type;

            $fields[] = $type::make(__($attribute->name), $attribute->slug);
        }
        return $fields;
    }

and use this in the ´fields´ method of Resources:

                return array_merge([
                      // some fields
                ],
                $this->attributeFields());

Now you should see the configured attributes in your Nova Resource.

Hope that helps forward and someone more experienced is able to make a package from these fragments.

Qoraiche commented 5 years ago

@cord sounds great!

SunnyDayGuide commented 4 years ago

I am just getting back to this in my project. Thank you so much!

flakerimi commented 4 years ago

This package would help people a lot if it was made. @cord did you abandon it?

cord commented 4 years ago

The given code still works here, hope someone can pick it up and make a package from it?

flakerimi commented 4 years ago

Yeap, just used it, its awesome. I misread, I thought you are going to make it :) maybe I will now.

cord commented 4 years ago

@flakerimi would be great - am happy to contribute!

flakerimi commented 4 years ago

@cord I found this one : https://github.com/sunel/eav More completed than rinvex and very active, I will try it to make it work with this one first then try to convert it as package.