Crud setup for topics

This tutorial is part of this mini project on topic oriented user community. To see all the sections of this project Click Here

Now, it's time to give focus on following methods in controller TopicController.php

<?php
namespace App\Http\Controllers;

use App\Http\Requests\StoreTopicRequest;
use App\Http\Requests\UpdateTopicRequest;
use App\Models\Comment;
use App\Models\Topic;

class TopicController extends Controller
{
    public function __construct()
    {
        //$this->middleware('auth')->except(['show']);
    }
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $topics = Topic::orderBy('id', 'desc')->paginate(10);

        return view('topics.index', compact('topics'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('topics.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(StoreTopicRequest $request)
    {
        $topic = Topic::create($request->validated());

        return redirect()->route('topics.index');
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit(Topic $topic)
    {
        return view('topics.edit', compact('topic'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(UpdateTopicRequest $request, Topic $topic)
    {

        $topic->update($request->validated()); //dd($request);
        // $topic->description = $request->input('description');
        // $topic->name = $request->input('name');
        // $topic->save();
        //$community->topics()->sync($request->topics);

        return redirect()->route('topics.index')->with('success', 'Successfully updated');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy(Topic $topic)
    {
        if( $topic->posts->count() > 0 ) {
            Comment::where('post_id',$topic->posts->pluck('id'))->delete();
            $topic->posts->each->delete();
        }

        $topic->delete();

        return redirect()->route('topics.index')->with('success', 'Successfully deleted');
    }

    public function show(Topic $topic)
    {
        //
        $query = $topic->posts();

        if ( !empty(request('search')) ) {
            $query->where(function($query) {
                $query->where('title', 'like', '%'.request('search').'%')
                    ->orWhere('post_text', 'like', '%'.request('search').'%');
            });
        } else {
            //$query->latest('id');
        }


        $posts = $query->orderBy('id')->paginate(10);

        return view('topics.show', compact( 'topic','posts'));
    }
}

In the code above, two request classes, StoreTopicRequest and UpdateTopicRequest, are utilized for validation. These can be recreated using request classes mentioned above with Community in this post. That's why request classes for TopicController are not mentioned again.

Now, Let's turn our attention to the blade files for TopicController.php now, specifically index.blade.php, create.blade.php, and show.blade.php

For master layout we will be using previously created app.blade.php ( in this post ) for topic related blade files as well.

1. index.blade.php

@extends('layouts.app')
@section('content')
    <div class="container mt-5">
        <div class="row align-center justify-content-center">
            <div class="col-xl-12">
                <h1 class="float-start me-2">Topics</h1>
                <div class="float-end mt-2"><a href="/topics/create" class="btn btn-sm btn-primary">Create</a></div>
                <table class="table table-striped mt-3">
                    <tr>
                        <th>
                            ID
                        </th>
                        <th>
                            Name
                        </th>
                        <th>
                            Actions
                        </th>
                    </tr>
                    @foreach ($topics as $topic)
                        <tr>
                            <td>
                                {{ $topic->id }}
                            </td>
                            <td>
                                <a href="{{ route('topics.show', [$topic]) }}">{{ $topic->name }} ({{ $topic->posts->count() }})</a>
                            </td>
                            <td>
                                <a href="{{ route('topics.edit', $topic) }}" class="btn btn-sm btn-primary">Edit</a>
                                <a href="#" class="resource-delete-row btn btn-sm btn-danger" data-resource-name="{{ $topic->name  }}" data-dellink="{{ route('topics.destroy', $topic)  }}" data-toggle="tooltip" title="Delete">
                                    Delete
                                </a>
                            </td>
                        </tr>
                    @endforeach
                </table>
                {{ $topics->links() }}
            </div>
        </div>
    </div>

    <div id="resource-delete-confirm" class="modal fade">
        <div class="modal-dialog modal-md modal-dialog-centered" >
            <div class="modal-content" >
                <div class="modal-header">
                    <h4 class="modal-title">Confirm Delete!</h4>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <div id="resource-confirm-form">
                        <div class="text-center">
                            <p>Would you like to delete this row "<strong><span id="resource-delete-title"></span></strong>"? <br>Once deleted it cannot be reverted.</p>
                            <a id="resource-delete-link" class="btn btn-sm btn-danger">Yes</a> &nbsp;&nbsp;
                            <form id="delete-submit-form" action="" method="POST" class="d-none">
                                @csrf
                                @method('DELETE')
                            </form>
                            <a href="#" class="btn btn-sm btn-info white modal-close">No</a>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Since, BS modal has been used here to show popup and delete confirmation, we also need to add some JS to achieve this inside resources/js/app.js

import bootstrap from 'bootstrap/dist/js/bootstrap.bundle.js';
import $ from 'jquery';
window.$ = $;

import select2 from 'select2';
select2($);

let deleteModal;

function delFormSubmit(e) {
    e.preventDefault();
    // setting form action with js
    $('#delete-submit-form').attr('action', $('#resource-delete-link').attr('href'));
    // submitting form
    $('#delete-submit-form').submit();
}

function modalHide() {
    deleteModal.hide();
}

// Custom jQuery code
$(document).ready(function() {
    $('.resource-delete-row').click(function(event) {
        event.preventDefault(); // Prevent default behavior

        $('#resource-delete-title').html($(this).data('resourceName'));
        $('#resource-delete-link').attr( 'href', $(this).data('dellink') );

        deleteModal = new bootstrap.Modal($('#resource-delete-confirm')[0]);
        deleteModal.show();
    });

    $(".modal-close").click(modalHide); // Bind event listener to "modal-close")
    $("#resource-delete-link").click(delFormSubmit);

});

$(document).ready(function() {
    $('.basic-select2').select2();
});

$(document).ready(function() {

    const formElement = document.getElementById('community-topic-form');
    if(formElement){
        const communityId = formElement.dataset.communityId;
        const topicId = formElement.dataset.topicId;

        if (communityId) {
            loadTopics(communityId, topicId);
        }

        $('#community_id').change(function() {
            loadTopics($(this).val());
        });
    }
});

function loadTopics(communityId , selectedTopicId) {

    if (communityId) {
        $.ajax({
            url: '/community/' + communityId + '/topics',
            type: 'GET',
            success: function(data) {
                $('#topic_id').empty().append('<option value="">Choose topic</option>');

                $.each(data, function(index, topic) {
                    const option = $('<option></option>')
                        .val(topic.id)
                        .text(topic.name);

                    if (selectedTopicId && (selectedTopicId == topic.id) ) {
                        option.attr('selected', 'selected');
                    }

                    $('#topic_id').append(option);
                });
            }
        });
    } else {
        $('#topic_id').empty().append('<option value="">Choose topic</option>');
    }
}

2. create.blade.php

@extends('layouts.app')
@section('content')
    <div class="container mt-5">
        <div class="row align-center justify-content-center">
            <div class="col-lg-7">
                <h2>Create Topic</h2>
                <form action="{{ route('topics.store') }}" method="POST">
                    @csrf
                    <div class="mb-3">
                        <label for="exampleInputName" class="form-label">Name</label>
                        <input type="text" class="form-control" id="name" name="name" value="{{ old('name') }}" >
                        @if ($errors->has('name'))
                            <span class="text-danger">{{ $errors->first('name') }}</span>
                        @endif
                    </div>
                    <div class="mb-3">
                        <label for="exampleInputDes" class="form-label">Description</label>
                        <input type="text" class="form-control" id="name" name="description" value="{{ old('description') }}" >
                        @if ($errors->has('description'))
                            <span class="text-danger">{{ $errors->first('description') }}</span>
                        @endif
                    </div>
                    <button type="submit" class="btn btn-primary">Submit</button>
                    &nbsp;<a href="{{ route('topics.index') }}" class="btn btn-danger">Back</a>
                </form>
            </div>
        </div>
    </div>
@endsection

3. edit.blade.php

@extends('layouts.app')
@section('content')
    <div class="container mt-5">
        <div class="row align-center justify-content-center">
            <div class="col-lg-7">
                <h2>Edit Topic</h2>
                <form action="{{ route('topics.update', $topic) }}" method="POST">
                    @csrf
                    @method('PUT')
                    <div class="mb-3">
                        <label for="exampleInputName" class="form-label">Name</label>
                        <input type="text" class="form-control" id="name" name="name" value="{{ $topic->name }}">
                        @if ($errors->has('name'))
                            <span class="text-danger">{{ $errors->first('name') }}</span>
                        @endif
                    </div>
                    <div class="mb-3">
                        <label for="exampleInputDes" class="form-label">Description</label>
                        <input type="text" class="form-control" id="name" name="description" value="{{ $topic->description }}">
                        @if ($errors->has('description'))
                            <span class="text-danger">{{ $errors->first('description') }}</span>
                        @endif
                    </div>
                    <button type="submit" class="btn btn-primary">Submit</button>
                    &nbsp;<a href="{{ route('topics.index') }}" class="btn btn-danger">Back</a>
                </form>
            </div>
        </div>
    </div>
@endsection

4. show.blade.php

@extends('layouts.app')
@section('content')
    <div class="container mt-5">
        <div class="row">
            <div class="col-md-10 col-lg-8 col-xl-12">
                <h2 class="float-start me-2">Topic - {{ $topic->name }}</h2>
                <div class="clearfix"></div>
                <hr>
                <div class="mb-3">{!! $topic->description !!}</div>
                <!-- Another variation with a button -->
                <form method="GET" action="{{ route('topics.show', $topic) }}">
                    <div class="input-group">
                        <input type="text" class="form-control" name="search" value="{{ request('search','') }}" placeholder="search laravel snippets in {{ $topic->name }}"> &nbsp;
                        <div class="input-group-append">
                            <button class="btn btn-sm btn-secondary py-2" type="submit">
                                Search
                            </button>
                            <a class="btn btn-sm btn-danger py-2" href="{{ route('topics.show', $topic) }}">
                                Reset
                            </a>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    </div>
    <div class="container">
        @forelse ($posts as $post)
            @php
                //dd($post);
            @endphp
            <div class="row my-5 border bg-light shadow">
                <div class="col-md-12 align-self-center p-4 ">
                    <a class="text-dark" href="{{ route('communities.topics.posts.show', [ $post->community, $post->topic, $post]) }}">
                        <h5 >{{ $post->title }}</h5>
                    </a>
                    <h6>{{ $post->created_at->diffForHumans() }}</h6>
                    <p>{{ \Illuminate\Support\Str::words(strip_tags($post->post_text), 20) }}</p>
                    <a href="{{ route('communities.topics.posts.show', [ $post->community, $post->topic, $post]) }}" class="btn btn-outline-danger btn-sm">Learn More</a>
                </div>
            </div>
        @empty
            <div class="text-center">
                No posts found.
            </div>
        @endforelse
        <div class="text-center">
            {!! $posts->links() !!}
        </div>
    </div>
@endsection

Lastly, setting up routes for TopicController.php

Route::resource('topics', \App\Http\Controllers\TopicController::class);

So, TopicController.php and blade files for the controller have been setup. Now, we can move on to the next post.


Related Posts


Crud setup for communities

Crud setup for posts

Ajax setup for post comment