Craftable supports all Eloquent Relationships defined in Laravel documentation.
Code from example sections is placed in Craftable demo repository.
Let's imagine you have articles with authors and you want to implement this behavior:
Our example migration contain author_id
column which references 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();
});
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 methods should look like:
{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'],
// add 'authors.title' if you want to search by author title
['id', 'title', 'perex', 'authors.title'],
function ($query) use ($request) {
$query->with(['author']);
// add this line if you want to search by author attributes
$query->join('authors', 'authors.id', '=', 'articles_with_relationships.author_id');
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()
]);
}
public function create()
{
$this->authorize('admin.articles-with-relationship.create');
return view('admin.articles-with-relationship.create', [
'authors' => Author::all(),
]);
}
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');
}
{info} Note, that we have to load the relation using
$articlesWithRelationship->load('author');
.
public function edit(ArticlesWithRelationship $articlesWithRelationship)
{
$this->authorize('admin.articles-with-relationship.edit', $articlesWithRelationship);
$articlesWithRelationship->load('author');
return view('admin.articles-with-relationship.edit', [
'articlesWithRelationship' => $articlesWithRelationship,
'authors' => Author::all(),
]);
}
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');
}
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 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;
}
Add authors to props.
props: [
'authors'
]
Add author property in data form object.
author: '' ,
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);
}
}
Add authors prop to form component.
:authors="{{$authors->toJson()}}"
Add authors prop to form component.
:authors="{{$authors->toJson()}}"
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>
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>
Let's imagine you have articles with tags and you want to implement this behavior:
Our example migrations contain articles_with_relationships
and tags
tables.
Schema::create('articles_with_relationships', function (Blueprint $table) {
$table->increments('id');
...
$table->timestamps();
});
Schema::create('tags', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
Our example also contain pivot table articles_with_relationship_tag
which contain articles_with_relationship_id
column which references to articles_with_relationships
table and
tag_id
column which references to tags
table.
Schema::create('articles_with_relationship_tag', function (Blueprint $table) {
$table->unsignedInteger('articles_with_relationship_id');
$table->foreign('articles_with_relationship_id')
->references('id')
->on('articles_with_relationships')
->onDelete('cascade');
$table->unsignedInteger('tag_id');
$table->foreign('tag_id')
->references('id')
->on('tags')
->onDelete('cascade');
});
In ArticlesWithRelationship model add belongsToMany relation to Tag::class.
public function tags()
{
return $this->belongsToMany(Tag::class);
}
In Tag model add belongsToMany relation to ArticlesWithRelationship::class.
public function articlesWithRelationships()
{
$this->belongsToMany(ArticlesWithRelationship::class);
}
Controller methods should look like:
{info} Note, that we have to load the relation using
$query->with(['tags']);
.
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'],
// add 'tags.name' if you want to search by tag name
['id', 'title', 'perex', 'tags.name'],
function ($query) use ($request) {
$query->with(['tags']);
// add this line if you want to search by tags attributes
$query->join('articles_with_relationship_tag', 'articles_with_relationship_tag.articles_with_relationship_id', '=', 'articles_with_relationships.id')
->join('tags', 'tags.id', '=', 'articles_with_relationship_tag.tag_id')
->groupBy('articles_with_relationships.id');
}
);
if ($request->ajax()) {
return ['data' => $data];
}
return view('admin.articles-with-relationship.index', [
'data' => $data,
]);
}
public function create()
{
$this->authorize('admin.articles-with-relationship.create');
return view('admin.articles-with-relationship.create', [
'tags' => Tag::all(),
]);
}
public function store(StoreArticlesWithRelationship $request)
{
// Sanitize input
$sanitized = $request->validated();
$sanitized['tags'] = $request->getTags();
DB::transaction(function () use ($sanitized) {
// Store the ArticlesWithRelationship
$articlesWithRelationship = ArticlesWithRelationship::create($sanitized);
$articlesWithRelationship->tags()->sync($sanitized['tags']);
});
if ($request->ajax()) {
return [
'redirect' => url('admin/articles-with-relationships'),
'message' => trans('brackets/admin-ui::admin.operation.succeeded')
];
}
return redirect('admin/articles-with-relationships');
}
{info} Note, that we have to load the relation using
$articlesWithRelationship->load('tags');
.
public function edit(ArticlesWithRelationship $articlesWithRelationship)
{
$this->authorize('admin.articles-with-relationship.edit', $articlesWithRelationship);
$articlesWithRelationship->load('tags');
return view('admin.articles-with-relationship.edit', [
'articlesWithRelationship' => $articlesWithRelationship,
'tags' => Tag::all(),
]);
}
public function update(UpdateArticlesWithRelationship $request, ArticlesWithRelationship $articlesWithRelationship)
{
// Sanitize input
$sanitized = $request->validated();
$sanitized['tags'] = $request->getTags();
DB::transaction(function () use ($articlesWithRelationship, $sanitized) {
// Update changed values ArticlesWithRelationship
$articlesWithRelationship->update($sanitized);
$articlesWithRelationship->tags()->sync($sanitized['tags']);
});
if ($request->ajax()) {
return [
'redirect' => url('admin/articles-with-relationships'),
'message' => trans('brackets/admin-ui::admin.operation.succeeded')
];
}
return redirect('admin/articles-with-relationships');
}
Store request requires methods:
public function rules()
{
return [
'title' => ['required', 'string'],
'perex' => ['nullable', 'string'],
'published_at' => ['nullable', 'date'],
'enabled' => ['required', 'boolean'],
'tags' => ['required'],
];
}
public function getTags(): array
{
if ($this->has('tags')) {
$tags = $this->get('tags');
return array_column($tags, 'id');
}
return [];
}
Update request requires methods:
public function rules()
{
return [
'title' => ['sometimes', 'string'],
'perex' => ['nullable', 'string'],
'published_at' => ['nullable', 'date'],
'enabled' => ['sometimes', 'boolean'],
'tags' => ['required'],
];
}
public function getTags(): array
{
if ($this->has('tags')) {
$tags = $this->get('tags');
return array_column($tags, 'id');
}
return [];
}
Add availableTags to props.
props: [
'availableTags'
]
Add tags property in data form object.
tags: '',
Add available-tags prop to form component.
:available-tags="{{ $tags->toJson() }}"
Add available-tags prop to form component.
:available-tags="{{ $tags->toJson() }}"
In place where you want to show tags add following code in <tbody>
.
<div v-for="tag in item.tags">
<span class="badge badge-success text-white">@{{ tag.name }}</span>
</div>
In form-elements.blade.php add following code in place where you want to have option to choose tags of article.
<div class="form-group row align-items-center"
:class="{'has-danger': errors.has('tags'), 'has-success': this.fields.tags && this.fields.tags.valid }">
<label for="author_id"
class="col-form-label text-center col-md-4 col-lg-3">{{ trans('admin.articles-with-relationship.columns.tags') }}</label>
<div class="col-md-8 col-lg-9">
<multiselect
v-model="form.tags"
:options="availableTags"
:multiple="true"
track-by="id"
label="name"
tag-placeholder="{{ __('Select Tags') }}"
placeholder="{{ __('Tags') }}">
</multiselect>
<div v-if="errors.has('tags')" class="form-control-feedback form-text" v-cloak>@{{
errors.first('tags') }}
</div>
</div>
</div>