This article is a simple example of belongs to many() relationships in Laravel, with data management in form and CRUD table, using the Bootstrap framework and the Select2 library.

Here’s what we’re building: a list of articles with their tags.

Notice: This example was mainly generated with our QuickAdminPanel builder, but will be explained step by step in simple Laravel, so you can use it without our builder.

Step 1. Database/Model Structure

Here are the migrations.

Items:


Schema::create('articles', function (Blueprint $table) {
    $table->increments('id');
    $table->string('title')->nullable();
    $table->text('article_text')->nullable();    
    $table->timestamps();
});

Tags:


Schema::create('tags', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name')->nullable();    
    $table->timestamps();
});

Pivot Table:


Schema::create('article_tag', function (Blueprint $table) {
    $table->integer('article_id')->unsigned()->nullable();
    $table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade');
    $table->integer('tag_id')->unsigned()->nullable();
    $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
});

The templates are also very simple.

app/Tag.php:


class Tag extends Model
{
    protected $fillable = ['name'];
}

application/Article.php:


class Article extends Model
{
    protected $fillable = ['title', 'article_text'];
    
    public function tag()
    {
        return $this->belongsToMany(Tag::class, 'article_tag');
    }    
}

Routes, controller and create form

OUR routes/web.phpin addition to Route Group and Middleware, will have this main line:


Route::group(['middleware' => ['auth'], 'prefix' => 'admin', 'as' => 'admin.'], function () {
    // ... other routes
    Route::resource('articles', 'Admin\ArticlesController');
});

Now here is our (simplified) app/Http/Controllers/Admin/ArticlesController.php:


namespace App\Http\Controllers\Admin;

use App\Article;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\StoreArticlesRequest;
use App\Http\Requests\Admin\UpdateArticlesRequest;

class ArticlesController extends Controller
{

    public function index()
    {
        $articles = Article::all();
        return view('admin.articles.index', compact('articles'));
    }

    public function create()
    {
        $tags = \App\Tag::get()->pluck('name', 'id');
        return view('admin.articles.create', compact('tags'));
    }

    public function store(StoreArticlesRequest $request)
    {
        // ... to be discussed later
    }

    public function edit($id)
    {
        // ... to be discussed later
    }

    public function update(UpdateArticlesRequest $request, $id)
    {
        // ... to be discussed later
    }

    public function destroy($id)
    {
        // ... to be discussed later
    }
}

To add the articles with their tags, we need a creation form.
Here is our resources/views/admin/articles/create.blade.php:


@extends('layouts.app')

@section('content')
    <h3 class="page-title">Articles</h3>
    {!! Form::open(['method' => 'POST', 'route' => ['admin.articles.store']]) !!}

    <div class="panel panel-default">
        <div class="panel-heading">
            Create
        </div>
        
        <div class="panel-body">
            <div class="row">
                <div class="col-xs-12 form-group">
                    {!! Form::label('title', 'Title', ['class' => 'control-label']) !!}
                    {!! Form::text('title', old('title'), ['class' => 'form-control', 'placeholder' => '']) !!}
                    <p class="help-block"></p>
                    @if($errors->has('title'))
                        <p class="help-block">
                            {{ $errors->first('title') }}
                        </p>
                    @endif
                </div>
            </div>
            <div class="row">
                <div class="col-xs-12 form-group">
                    {!! Form::label('article_text', 'Article Text', ['class' => 'control-label']) !!}
                    {!! Form::textarea('article_text', old('article_text'), ['class' => 'form-control ', 'placeholder' => '']) !!}
                    <p class="help-block"></p>
                    @if($errors->has('article_text'))
                        <p class="help-block">
                            {{ $errors->first('article_text') }}
                        </p>
                    @endif
                </div>
            </div>
            <div class="row">
                <div class="col-xs-12 form-group">
                    {!! Form::label('tag', 'Tags', ['class' => 'control-label']) !!}
                    <button type="button" class="btn btn-primary btn-xs" id="selectbtn-tag">
                        Select all
                    </button>
                    <button type="button" class="btn btn-primary btn-xs" id="deselectbtn-tag">
                        Deselect all
                    </button>
                    {!! Form::select('tag[]', $tags, old('tag'), ['class' => 'form-control select2', 'multiple' => 'multiple', 'id' => 'selectall-tag' ]) !!}
                    <p class="help-block"></p>
                    @if($errors->has('tag'))
                        <p class="help-block">
                            {{ $errors->first('tag') }}
                        </p>
                    @endif
                </div>
            </div>
            
        </div>
    </div>

    {!! Form::submit('Save article', ['class' => 'btn btn-danger']) !!}
    {!! Form::close() !!}
@stop

@section('styles')
    @parent

    <link rel="stylesheet" href="
    <link href=" rel="stylesheet" />
@stop

@section('javascript')
    @parent

    <script src="
    <script src="
    <script src="
    <script src="
    <script>
        $("#selectbtn-tag").click(function(){
            $("#selectall-tag > option").prop("selected","selected");
            $("#selectall-tag").trigger("change");
        });
        $("#deselectbtn-tag").click(function(){
            $("#selectall-tag > option").prop("selected","");
            $("#selectall-tag").trigger("change");
        });

        $(document).ready(function () {
            $('.select2').select2();
        });
    </script>
@stop

Here is our visual result:

Now, there are quite a few places to explain here:

  1. In this article I will not discuss the main layout and code like @extends(‘layouts.app’) And @section(‘content’) because you can have different design and structure, that is not the subject of this article;
  2. We use the LaravelCollective/html package to create the forms with {!! Form::open()!!} and other methods;
  3. See how $tags are passed to the form, we get these values ​​from the controller mentioned above;
  4. Above the tag field, we have two buttons: Select all And Deselect all – their behavior in jQuery is implemented in @section(‘javascript’);
  5. We use the Select2 library to make tags searchable and visually appealing.

So here we have our creation form.


Data backup

This part is quite simple, here is a method of app/Http/Controllers/Admin/ArticlesController.php:


public function store(StoreArticlesRequest $request)
{
    $article = Article::create($request->all());
    $article->tag()->sync((array)$request->input('tag'));
    return redirect()->route('admin.articles.index');
}

To validate the data, we use app/Http/Requests/StoreArticlesRequest.php class:


namespace App\Http\Requests\Admin;

use Illuminate\Foundation\Http\FormRequest;

class StoreArticlesRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'tag.*' => 'exists:tags,id',
        ];
    }
}

And in the controller we just store the item then use synchronization() Many-to-many relationship method for storing tags.


Displaying tags in the table

In the screenshot at the top you can see how the tags are structured in the table. Here is our Blade file for this.
resources/views/admin/articles/index.blade.php:


<div class="panel-body table-responsive">
    <table class="table table-bordered table-striped datatable">
        <thead>
            <tr>
                <th>Title</th>
                <th>Tags</th>
                <th> </th>
            </tr>
        </thead>
        
        <tbody>
            @if (count($articles) > 0)
                @foreach ($articles as $article)
                    <tr data-entry-id="{{ $article->id }}">
                        <td field-key='title'>{{ $article->title }}</td>
                        <td field-key='tag'>
                            @foreach ($article->tag as $singleTag)
                                <span class="label label-info label-many">{{ $singleTag->name }}</span>
                            @endforeach
                        </td>
                        <td>
                            @can('article_view')
                            <a href=" route("admin.articles.show',[$article->id]) }}" class="btn btn-xs btn-primary">View</a>
                            @endcan
                            @can('article_edit')
                            <a href=" route("admin.articles.edit',[$article->id]) }}" class="btn btn-xs btn-info">Edit</a>
                            @endcan
                            @can('article_delete')
                            {!! Form::open(array(
                                'style' => 'display: inline-block;',
                                'method' => 'DELETE',
                                'onsubmit' => "return confirm('Are you sure?');",
                                'route' => ['admin.articles.destroy', $article->id])) !!}
                            {!! Form::submit('Delete', array('class' => 'btn btn-xs btn-danger')) !!}
                            {!! Form::close() !!}
                            @endcan
                        </td>
                        @endif
                    </tr>
                @endforeach
            @else
                <tr>
                    <td colspan="8">@lang('global.app_no_entries_in_table')</td>
                </tr>
            @endif
        </tbody>
    </table>
</div>

This is the most important line – we use Bootstrap label classes for styling:


<span class="label label-info label-many">{{ $singleTag->name }}</span>

Editing the article

Here is the controller code:


public function edit($id)
{
    $tags = \App\Tag::get()->pluck('name', 'id');
    $article = Article::findOrFail($id);
    return view('admin.articles.edit', compact('article', 'tags'));
}

And in resources/views/admin/articles/edit.blade.php we display the tags this way:


{!! Form::label('tag', trans('global.articles.fields.tag').'', ['class' => 'control-label']) !!}
<button type="button" class="btn btn-primary btn-xs" id="selectbtn-tag">
    {{ trans('global.app_select_all') }}
</button>
<button type="button" class="btn btn-primary btn-xs" id="deselectbtn-tag">
    {{ trans('global.app_deselect_all') }}
</button>
{!! Form::select('tag[]', $tags, old('tag') ? old('tag') : $article->tag->pluck('id')->toArray(), ['class' => 'form-control select2', 'multiple' => 'multiple', 'id' => 'selectall-tag' ]) !!}
<p class="help-block"></p>
@if($errors->has('tag'))
    <p class="help-block">
        {{ $errors->first('tag') }}
    </p>
@endif

As you can see, we get the current value in the form $article->tag->pluck(‘id’)->toArray().

Updating data is simple, here is the controller method, very similar to store():


public function update(CreateArticlesRequest $request, $id)
{
    $article = Article::findOrFail($id);
    $article->update($request->all());
    $article->tag()->sync((array)$request->input('tag'));
    return redirect()->route('admin.articles.index');
}

Deleting data

This is probably the simplest code – here’s the Controller method:


public function destroy($id)
{
    $article = Article::findOrFail($id);
    $article->delete();
    return redirect()->route('admin.articles.index');
}

But keep in mind that in our migrations files we specified that we must remove the tags when deleting the article, with this rule onDelete(‘cascade’):


Schema::create('article_tag', function (Blueprint $table) {
    $table->integer('article_id')->unsigned()->nullable();
    $table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade');
    $table->integer('tag_id')->unsigned()->nullable();
    $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
});

Basically, that’s it – a simple many-to-many demo with Select2 and Bootstrap. If you want it to be automatically generated, try our QuickAdminPanel!



Technology

Another Tech Information

Similar Posts