Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

phayes0289's avatar

Make Emoji's Clickable and Display the Click Count

I have a comment system built that works for multiple models. Within my comment component, I have laid out five emojis for future use that I want to have the following functionality

  1. Display the count of people who have clicked on each emoji in each comment when the page loads.
  2. When a user clicks on the emoji, I want a record created in the CommentReaction model for that emoji and the count adjusted to account for the click.
  3. If a user clicks a second time, it removes the record created in the CommentReaction model for that emoji and lowers the count by one.

This is the multi-model component:

@props(['comments', 'model'])

<div>

    <h1>Comments</h1>
    {{--        Begin Laravel-Comments--}}
    <div class="card border mb-4 mt-4 no-print">
        <div class="card-header py-2">
            <div class="card-title">
                Post A Comment
            </div>
        </div>

        <div class="m-4">
            <form method="POST" action=" {{ route('comments.create') }}">
                @csrf
                <input type="hidden" name="commentable_type" value="{{ get_class($model) }}">
                <input type="hidden" name="commentable_id" value="{{ $model->id }}">
                <textarea name="text" id="comment" placeholder="Your Comment Here." class="form-control"></textarea>
                <button type="submit" class="btn btn-xs btn-primary waves-effect waves-themed">Add Comment</button>
            </form>
        </div>

        <div class=" card-footer text-muted py-2">
            Warning... Any inappropriate content will be removed!
        </div>
    </div>
    {{--        End Comment Component--}}

    @foreach($comments as $comment)
        <div class="card mb-g border shadow-0">
            <div class="card-header p-0">
                <div class="p-3 d-flex flex-row">
                    <div class="d-block flex-shrink-0">
                        <img src="{{ $comment->user->media()->count() > 0 ? $comment->user->getFirstMediaUrl('profile') : asset('media/images/silhouettes/firefighter-silhouette_white_bg.jpg') }}"
                             class="img-fluid img-thumbnail" alt="" style="width:70px;">
                    </div>
                    <div class="d-block ml-2">
                            <span class="h6 font-weight-bold text-uppercase d-block m-0"><a
                                        href="javascript:void(0);">{{ $comment->user->currentAssignment->rank->name}} {{ $comment->user->firstname }} {{ $comment->user->lastname }}</a>

                            </span>
                        <a href="javascript:void(0);"
                           class="fs-sm text-info h6 fw-500 mb-0 d-block"> {{ $comment->user->currentAssignment->shift->name}}
                            , {{ $comment->user->currentAssignment->station->name}}</a>
                        <div class="d-flex mt-1 text-warning align-items-center">
                            <i class="fas fa-star mr-1"></i>
                            <i class="fas fa-star mr-1"></i>
                            <i class="fas fa-star mr-1"></i>
                            <i class="fas fa-star mr-1"></i>
                            <i class="fal fa-star mr-1"></i>
                            <span class="text-muted fs-xs font-italic">
                                    (140 votes)
                                </span>
                        </div>
                    </div>
                    <a href="javascript:void(0);"
                       class="d-inline-flex align-items-center text-dark ml-auto align-self-start">
                        <span>55</span><i class="fas fa-heart ml-1 text-danger"></i>
                    </a>
                </div>
            </div>
            <div class="card-body ">
                {!! $comment->text !!}

            </div>

            <div class="row m-2 text-left-sm">
                <div class="col-sm-6 text-sm-left text-center mb-2"> <!-- Added text-center class -->
                    <div class="emoji-reactions">
                        <span class="emoji badge badge-pill badge-primary p-1" title="Like"
                              data-reaction="šŸ‘">šŸ‘ 121</span>
                        <span class="emoji badge badge-pill badge-primary p-1" title="Dislike"
                              data-reaction="šŸ‘Ž">šŸ‘Ž 121</span>
                        <span class="emoji badge badge-pill badge-primary p-1" title="Love"
                              data-reaction="ā¤ļø">ā¤ļø 10</span>
                        <span class="emoji badge badge-pill badge-primary p-1" title="Laughing"
                              data-reaction="šŸ˜‚">šŸ˜‚ 40</span>
                        <span class="emoji badge badge-pill badge-primary p-1" title="Following"
                              data-reaction="šŸ‘€">šŸ‘€ 40</span>
                    </div>
                </div>
                <div class="col-sm-6 text-sm-right text-center mb-2">
                    <!-- Added text-center and text-sm-right classes -->
                    <a href="{{ route('directory.show', $comment->user->slug) }}">Public Profile</a> • Delete • Report
                </div>
            </div>

            <div class="card-footer">
                <div class="d-flex align-items-center">
                    <span class="text-sm text-muted font-italic"><i class="fal fa-clock mr-1"></i> Posted {{ $comment->created_at->diffForHumans() }}</span>
                    <a href="javascript:void(0);" class="flex-shrink-0 ml-auto">Reply <i
                                class="fal fa-reply ml-2"></i> </a>
                </div>
            </div>
        </div>
    @endforeach
</div>

I have marked the place where I want the count to be shown with ā€œ999ā€.

My CommentReaction model has the following fields to track the clicks.

commentable_type: The full modell name (i.e. App\Models\Post).
commentable_id:  The id with in the above model.
user_id:  The user id of the person clicking on the emoji.
reaction:  The actual emoji.
created_at:  Create daytimestamp.
updated_at: Update daytimestamp.

This is my CommentController that handles the creation of the comments:

 public function create(Request $request)
    {
        // Validate the form fields
        $formFields = $request->validate([
            'text' => ['required', 'min:2'],
            'commentable_type' => ['required', 'string'],
            'commentable_id' => ['required', 'integer'],
        ]);

        // Ensure the commentable_type is a valid model
        $commentableType = $formFields['commentable_type'];
        if (!in_array($commentableType, ['App\Models\Alert', 'App\Models\Post', 'App\Models\Event', 'App\Models\Preplan', 'App\Models\Hydrant'])) {
            abort(404);
        }

        // Create the comment
        $formFields['user_id'] = auth()->user()->id;
        $comment = Comment::create($formFields);

        // Redirect back to the appropriate page
        $routeName = strtolower(class_basename($commentableType)) . '.show';
        return redirect()->route($routeName, $formFields['commentable_id'])->with('message', 'Your comment has been created successfully!');
    }
}

How to I accomplish this without reloading the page with each click. Do I need to add some more relationships? Is Javascript the best way to go or would Livewire be better? I am a little better with Javacript at this point.

0 likes
2 replies
LaryAI's avatar
Level 58

To accomplish the emoji click and count functionality without reloading the page, you can use JavaScript along with AJAX to send requests to the server. Since you mentioned you're more comfortable with JavaScript, I'll provide a solution using vanilla JavaScript and Laravel's built-in AJAX support through Axios.

First, you'll need to add a route and a controller method to handle the emoji reactions. This method will create or delete the CommentReaction record and return the updated count.

Here's an example of what the controller method might look like:

// CommentReactionController.php

public function toggleReaction(Request $request)
{
    $request->validate([
        'comment_id' => 'required|integer',
        'reaction' => 'required|string',
    ]);

    $user = auth()->user();
    $commentId = $request->input('comment_id');
    $reaction = $request->input('reaction');

    $existingReaction = CommentReaction::where('comment_id', $commentId)
                                        ->where('user_id', $user->id)
                                        ->where('reaction', $reaction)
                                        ->first();

    if ($existingReaction) {
        $existingReaction->delete();
    } else {
        CommentReaction::create([
            'comment_id' => $commentId,
            'user_id' => $user->id,
            'reaction' => $reaction,
        ]);
    }

    $count = CommentReaction::where('comment_id', $commentId)
                            ->where('reaction', $reaction)
                            ->count();

    return response()->json(['count' => $count]);
}

Next, add a route for this controller method in your web.php:

// web.php

Route::post('/comments/reaction', 'CommentReactionController@toggleReaction')->name('comments.reaction');

Now, let's add the JavaScript to handle the click event and update the count without reloading the page. You can include this script in your Blade template:

<script>
document.addEventListener('DOMContentLoaded', function() {
    const emojis = document.querySelectorAll('.emoji');

    emojis.forEach(emoji => {
        emoji.addEventListener('click', function() {
            const commentId = this.closest('.card').dataset.commentId; // Assuming you have a data attribute for comment ID
            const reaction = this.dataset.reaction;

            axios.post('{{ route('comments.reaction') }}', {
                comment_id: commentId,
                reaction: reaction
            })
            .then(response => {
                this.textContent = `${reaction} ${response.data.count}`;
            })
            .catch(error => {
                console.error('Error:', error);
            });
        });
    });
});
</script>

Make sure to include the CSRF token in your Axios headers if it's not already globally set:

<script>
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
</script>

Lastly, ensure that each comment card has a data-comment-id attribute that stores the comment's ID, and each emoji span has the data-reaction attribute set to the corresponding emoji.

<div class="card mb-g border shadow-0" data-comment-id="{{ $comment->id }}">
    <!-- ... -->
    <div class="emoji-reactions">
        <span class="emoji badge badge-pill badge-primary p-1" title="Like" data-reaction="šŸ‘">šŸ‘ 999</span>
        <!-- ... -->
    </div>
</div>

This solution uses JavaScript to handle the click events and Axios to send AJAX requests to the server. The server-side logic creates or deletes the CommentReaction record and returns the updated count, which is then displayed on the page without a reload.

Tray2's avatar

Making something clickable in JS is pretty easy, you have two options,

  1. You use the onclick attribute on your element.
  2. You add an event listener to the element.

I prefer using the first approach on generated content, because I don't have to reattach the event listeners every time the content is regenerated.

So basically this.

<button onclick="increase('poop')">&poop;<span id="poop-count">0</span>

<script>
	async function increase(type) {
		let count = await postRequest(`/api/v1/increase/${type}`).count;
		document.querySelector(`#${type}-count`).innerText = count;
	} 

async function postRequest(url, parameters = {}, loading = true) {
  try {
      let responsePromise = await fetch(url, { 
          method: 'POST',
          headers: {
              'Content-Type': 'application/x-www-form-urlencoded'
          },
          body: new URLSearchParams(parameters)
      });
      if(! responsePromise.ok) {
          throw new Error(responsePromise.status );
      }
      return await responsePromise.json();
  } catch (error) {
      return error;
  }
}
</script>

Please or to participate in this conversation.