Craftable

Relations

Craftable supports all Eloquent Relationships defined in Laravel documentation.

Code from example section is placed in Craftable demo repository.


Example

Let's imagine you have articles with authors and you want to implement this behavior:

  • show authors information in listing,
  • authors filter to search articles of selected authors in listing,
  • choose author from multiselect when creating/editing article,

Migration

Our example migration contains author_id column which reference to authors table.

Schema::create('articles_with_relationships', function (Blueprint $table) {
    $table->increments('id');
    $table->string('title');
    $table->text('perex')->nullable();
    $table->date('published_at')->nullable();
    $table->boolean('enabled')->default(false);
    $table->integer('author_id')->nullable();
    $table->foreign('author_id')->references('id')->on('authors')->onDelete('cascade');
    $table->timestamps();
});

Model

In Author model add hasMany relation to ArticlesWithRelationship::class

public function articlesWithRelationships()
{
    return $this->hasMany(ArticlesWithRelationship::class);
}

In ArticlesWithRelationship model add belongsTo relation to Author::class.

public function author() {
    return $this->belongsTo(Author::class);
}

Controller

Controller methods should look like:

Index method

{info} Note, that we have to load the relation using $query->with(['author']);.

public function index(IndexArticlesWithRelationship $request)
{
    // create and AdminListing instance for a specific model and
    $data = AdminListing::create(ArticlesWithRelationship::class)->processRequestAndGet(
        // pass the request with params
        $request,

        // set columns to query
        ['id', 'title', 'published_at', 'enabled', 'author_id'],

        // set columns to searchIn
        ['id', 'title', 'perex'],

        function ($query) use ($request) {
            $query->with(['author']);
            if($request->has('authors')){
                $query->whereIn('author_id', $request->get('authors'));
            }
        }
    );

    if ($request->ajax()) {
        return ['data' => $data];
    }

    return view('admin.articles-with-relationship.index', [
        'data' => $data,
        'authors' => Author::all()
    ]);
}

Create method

public function create()
{
    $this->authorize('admin.articles-with-relationship.create');

    return view('admin.articles-with-relationship.create', [
        'authors' => Author::all(),
    ]);
}

Store method

public function store(StoreArticlesWithRelationship $request)
{
    // Sanitize input
    $sanitized = $request->validated();
    $sanitized['author_id'] = $request->getAuthorId();
    // Store the ArticlesWithRelationship
    $articlesWithRelationship = ArticlesWithRelationship::create($sanitized);

    if ($request->ajax()) {
        return [
            'redirect' => url('admin/articles-with-relationships'),
            'message' => trans('brackets/admin-ui::admin.operation.succeeded')
        ];
    }

    return redirect('admin/articles-with-relationships');
}

Edit method

public function edit(ArticlesWithRelationship $articlesWithRelationship)
{
    $this->authorize('admin.articles-with-relationship.edit', $articlesWithRelationship);

    return view('admin.articles-with-relationship.edit', [
        'articlesWithRelationship' => $articlesWithRelationship,
        'authors' => Author::all(),
    ]);
}

Update method

public function update(UpdateArticlesWithRelationship $request, ArticlesWithRelationship $articlesWithRelationship)
{
    // Sanitize input
    $sanitized = $request->validated();
    $sanitized['author_id'] = $request->getAuthorId();
    // Update changed values ArticlesWithRelationship
    $articlesWithRelationship->update($sanitized);

    if ($request->ajax()) {
        return ['redirect' => url('admin/articles-with-relationships'), 'message' => trans('brackets/admin-ui::admin.operation.succeeded')];
    }

    return redirect('admin/articles-with-relationships');
}

Requests

Store request

Store request requires methods:

public function rules()
{
    return [
        'title' => ['required', 'string'],
        'perex' => ['nullable', 'string'],
        'published_at' => ['nullable', 'date'],
        'enabled' => ['required', 'boolean'],
        'author' => ['required'],

    ];
}
public function getAuthorId(){
    if ($this->has('author')){
        return $this->get('author')['id'];
    }
    return null;
}

Update request

Update request requires methods:

public function rules()
{
    return [
        'title' => ['sometimes', 'string'],
        'perex' => ['nullable', 'string'],
        'published_at' => ['nullable', 'date'],
        'enabled' => ['sometimes', 'boolean'],
        'author' => ['required'],

    ];
}
public function getAuthorId(){
    if ($this->has('author')){
        return $this->get('author')['id'];
    }
    return null;
}

Form.js

Add authors to props.

props: [
    'authors'
]

Add author property in data form object.

author:  '' ,

Listing.js

Add following code to data() and watch

data() {
    return {
        showAuthorsFilter: false,
        authorsMultiselect: {},

        filters: {
            authors: [],
        },
    }
},

watch: {
    showAuthorsFilter: function (newVal, oldVal) {
        this.authorsMultiselect = [];
    },
    authorsMultiselect: function(newVal, oldVal) {
        this.filters.authors = newVal.map(function(object) { return object['key']; });
        this.filter('authors', this.filters.authors);
    }
}

create.blade.php

Add authors prop to form component.

:authors="{{$authors->toJson()}}"

edit.blade.php

Add authors prop to form component.

:authors="{{$authors->toJson()}}"

index.blade.php

In place where you want to have authors filter add following code.

Also you can prepare :options value on backend with own properties.

<div class="row" v-if="showAuthorsFilter">
    <div class="col-sm-auto form-group">
        <p>{{ __('Select author/s') }}</p>
    </div>
    <div class="col col-lg-12 col-xl-12 form-group">
        <multiselect v-model="authorsMultiselect"
             :options="{{ $authors->map(function($author) { return ['key' => $author->id, 'label' =>  $author->title]; })->toJson() }}"
             label="label"
             track-by="key"
             placeholder="{{ __('Type to search a author/s') }}"
             :limit="2"
             :multiple="true">
        </multiselect>
    </div>
</div>

You can also use User detail tooltip in <tbody>

<user-detail-tooltip :user="item.author" v-if="item.author">
</user-detail-tooltip>

form-elements.blade.php

In form-elements.blade.php add following code in place where you want to have option to choose author of article.

<div class="form-group row align-items-center"
     :class="{'has-danger': errors.has('author_id'), 'has-success': this.fields.author_id && this.fields.author_id.valid }">
    <label for="author_id"
           class="col-form-label text-center col-md-4 col-lg-3">{{ trans('admin.post.columns.author_id') }}</label>
    <div class="col-md-8 col-lg-9">

        <multiselect
            v-model="form.author"
            :options="authors"
            :multiple="false"
            track-by="id"
            label="full_name"
            tag-placeholder="{{ __('Select Author') }}"
            placeholder="{{ __('Author') }}">
        </multiselect>

        <div v-if="errors.has('author_id')" class="form-control-feedback form-text" v-cloak>@{{
            errors.first('author_id') }}
        </div>
    </div>
</div>

Summary

  • We created additional behavior as we defined in start of Example section to generated files.
  • Now you are able to define own behavior with relations in Craftable.