I am working on a blogging application in Laravel 8.
There is the option to add tags to articles. There is a many-to-many relationship between articles and tags. I have an article_tag
pivot table.
In the Tag
model I have:
class Tag extends Model
{
use HasFactory;
protected $fillable = ['name'];
public function articles()
{
return $this->belongsToMany(Article::class);
}
}
To the Article
model I have added the tags()
method
public function tags()
{
return $this->belongsToMany(Tag::class)->as('tags');
}
I the ArticleController controller, I have two methods for editing and updating an article:
public function edit($id)
{
$article = Article::find($id);
$attached_tags = $article->tags()->get()->pluck('id')->toArray();
return view(
'dashboard/edit-article',
[
'categories' => $this->categories(),
'tags' => $this->tags(),
'attached_tags' => $attached_tags,
'article' => $article
]
);
}
public function update(Request $request, $id)
{
$validator = Validator::make($request->all(), $this->rules, $this->messages);
if ($validator->fails()) {
return redirect()->back()->withErrors($validator->errors())->withInput();
}
$fields = $validator->validated();
$article = Article::find($id);
// If a new image is uploaded, set it as the article image
// Otherwise, set the old image...
if (isset($request->image)) {
$imageName = md5(time()) . Auth::user()->id . '.' . $request->image->extension();
$request->image->move(public_path('images/articles'), $imageName);
} else {
$imageName = $article->image;
}
$article->title = $request->get('title');
$article->short_description = $request->get('short_description');
$article->category_id = $request->get('category_id');
$article->tags[] = $request->get('tags[]');
$article->featured = $request->has('featured');
$article->image = $request->get('image') == 'default.jpg' ? 'default.jpg' : $imageName;
$article->content = $request->get('content');
// Save changes to the article
$article->save();
//Attach tags to article
if (isset($request->tags)) {
$article->tags()->sync($request->tags);
} else {
$article->tags()->sync([]);
}
return redirect()->route('dashboard.articles')->with('success', 'The article titled "' . $article->title . '" was updated');
}
In edit-article.blade.php
, I use a multiselect element to assign tags to articles:
<div class="row mb-2">
<label for="tags" class="col-md-12">{{ __('Tags') }}</label>
<select name="tags[]" id="tags" class="form-control" multiple="multiple">
@foreach ($tags as $tag)
<option value="{{ $tag->id }}"
{{ in_array($tag->id, $attached_tags) ? 'selected' : '' }}>{{ $tag->name }}</option>
@endforeach
</select>
</div>
The problem I am faced with is that when I edit the tags list, the <select>
element above does not keep the selected tags if the form is invalid because of validation errors on other form fields.
Where is my mistake?
There are few errors in this (optimizations too)
Remove this line
$article->tags[] = $request->get('tags[]'); // $article->tags()->sync will do the job
Change this
if ($request->has('tags')) { # changed
$article->tags()->sync($request->tags);
} else {
$article->tags()->sync([]);
}
In view
@php
// Use old input if present, otherwise use the attached tags from the article.
$selectedTags = old('tags', $attached_tags);
@endphp
<div class="row mb-2">
<label for="tags" class="col-md-12">{{ __('Tags') }}</label>
<select name="tags[]" id="tags" class="form-control" multiple="multiple">
@foreach ($tags as $tag)
<option value="{{ $tag->id }}" {{ in_array($tag->id, $selectedTags) ? 'selected' : '' }}>
{{ $tag->name }}
</option>
@endforeach
</select>
</div>