3

I have implemented the logic of adding likes to articles:

html

<a href="/article/{{ $article->id }}?type=heart" class="comments-sub-header__item like-button {{ $article->hasLikedToday('heart') ? 'active' : '' }}">
    <div class="comments-sub-header__item-icon-count"> {{ $article->getLikeHeartTotal() }} </div>
</a>

<a href="/article/{{ $article->id }}?type=finger" class="comments-sub-header__item like-button {{ $article->hasLikedToday('finger') ? 'active' : '' }}">
    <div class="comments-sub-header__item-icon-count"> {{ $article->getLikeFingerTotal() }} </div>
</a>

php

model

public function hasLikedToday(string $type)
{
    $articleLikesJson = Cookie::get('article_likes', '{}');

    $articleLikes = json_decode($articleLikesJson, true);

    if (!array_key_exists($this->id, $articleLikes)) {
        return false;
    }

    if (!array_key_exists($type, $articleLikes[$this->id])) {
        return false;
    }

    $likeDatetime = Carbon::createFromFormat('Y-m-d H:i:s', $articleLikes[$this->id][$type]);

    return !$likeDatetime->addDay()->lt(now());
}

public function setLikeCookie(string $type)
{
    $articleLikesJson = Cookie::get('article_likes', '[]');

    $articleLikes = json_decode($articleLikesJson, true);

    $articleLikes[$this->id][$type] = now()->format('Y-m-d H:i:s');

    $articleLikesJson = json_encode($articleLikes);

    return cookie()->forever('article_likes', $articleLikesJson);
}

controller

public function postLike($id, Request $request)
{
    $article = Article::find($id);

    if (!$article) {
        return abort(404);
    }

    $type = $request->input('type');

    if ($article->hasLikedToday($type)) {
        return response()
            ->json(
                [
                    'message' => 'You have already liked the Article ' . $article->id . ' with ' . $type . '.',
                ]
            );
    }

    $cookie = $article->setLikeCookie($type);

    $article->increment("like_{$type}");

    return response()
        ->json(
            [
                'message' => 'Liked the Article ' . $article->id . ' with ' . $type . '.',
                'cookie_json' => $cookie->getValue(),
            ]
        )
        ->withCookie($cookie);
}

js

$(function() {
  $.ajaxSetup({
    headers: {
      'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content'),
    },
  });

  $('.like-button').on('click', function(event) {
    event.preventDefault();
    var targetElement=$(this);

    let href = $(this).attr('href');

    $.ajax({
      url: href,
      type: 'POST',
      success: function() {
        if (targetElement.hasClass('active')) { 
          return false;
        }

        else {
          var counter = targetElement.find('.comments-sub-header__item-icon-count');
          var current = parseInt(counter.html());

          counter .html(current + 1)
      
            targetElement.addClass('active');
        }
     },
    });
  });
});

Now it turns out that both types of likes have a separate html page like /article/11?type=heart and /article/11?type=finger

Because of these links to the page, Google software swears, and gives such an error

the server returned a 405 method not allowed

Can I somehow keep all this functionality in a simple way, but make it so that the like request is not a POST and does not have a link so as not to receive such an error?

Abdulla Nilam
  • 36,589
  • 17
  • 64
  • 85
Hector
  • 41
  • 9
  • 2
    "500 (Internal Server Error)" - that's a server error. What have you tried to resolve it? – Nico Haase Mar 27 '23 at 15:47
  • A 500 error is a generic error message and covers pretty much every single thing that can go wrong with a PHP script. Check your server error logs to find out the exact error message. For Laravel, also check the logs in `/storage/logs` – aynber Mar 27 '23 at 16:04
  • Hi your form has input with name `blog-search` and in laravel code you are using `$request->get('search')` to get value of it .Isn't that should be `$request->get('blog-search')` ? – Swati Mar 27 '23 at 16:04
  • You don't have a submit button, so I'm guessing you don't want the actual form to submit. So I would suggest changing the action to probably `#` – aynber Mar 27 '23 at 16:27
  • get should be a function ... ; shouldn't it be $articleSearch = Article::where('title', 'LIKE', '%'.$textSearch.'%')->get(); – Anand Mar 27 '23 at 16:55
  • Inspect the network tab on your browser to check what does the request return. – Sachin Bahukhandi Mar 27 '23 at 17:15
  • In error you have *GET* http verb/method, and in route definition you also have *get*. In your js code, you have *post* method. Switch it to *get* and remove trailing `/` in uri in `$ajax` function. Or redo your route definition to `post`. – Sergey Ligus Mar 27 '23 at 17:36
  • And what version of Laravel is it ? Usually your views should be placed under `resources` folder `resources/views/blog/search.blade.php` – Sergey Ligus Mar 27 '23 at 17:46
  • https://stackoverflow.com/questions/32738763/laravel-csrf-token-mismatch-for-ajax-post-request#answer-41867849 Csrf token for ajax is also set ? ok, if you are getting the response, then you need to look what comes in the payload from the server. Probably you don't have `html` property in your response. Try to console log `result` for example. if you want to sent html block as response you need to render it first i think https://laravel.com/docs/10.x/blade#rendering-inline-blade-templates – Sergey Ligus Mar 27 '23 at 17:58
  • `php artisan cache:clear`, `php artisan config:clear`, webserver restart ? ;) Then you are sending full page as a response. And instead of `searchResult.html(result.html);` -> `searchResult.html(result);` – Sergey Ligus Mar 27 '23 at 18:11
  • Then instead of rendering entire page, send `json` response https://laravel.com/docs/10.x/responses#json-responses. And instead of `return view('blog.search', compact('articleSearch'));` -> `return response()->json([ 'html' => $articleSearch ]);` The result i encourge you to figure out the same way as you did debug ;) – Sergey Ligus Mar 27 '23 at 18:27
  • https://laravel.com/docs/10.x/views#creating-and-rendering-views. `use Illuminate\Support\Facades\View;` `return View::make('blog.search', compact('articleSearch'));` – Sergey Ligus Mar 27 '23 at 19:02
  • So, then you should specify before, that the file in which you have this block of code is called differently then you are showing. Then change it to `blog.search-list` in `search` method. But I still don't see any issues here with rendering block of html with php code even with `view()`. Under the hood it uses `ob_start()` and `ob_get_contents()` and `ob_get_clean()` both in Yii, Symfony and any other php framework which are using Twig, Blade whatsoever. Except the cases when you are using JS frameworks as a template engine. You need to check for issues that probably you missed in process. – Sergey Ligus Mar 27 '23 at 19:29
  • Hi, you are saying "like request is not a POST.." but in your ajax call you have use `type: 'POST',` not exactly sure what you trying to implement here ? – Swati May 06 '23 at 15:37
  • @Swati I have a POST now, and as far as I understand, I need to do a GET – Hector May 06 '23 at 16:35

3 Answers3

2

Basically, you need to make sure that you decide whether you want your request to be GET and POST as well as making sure that the routes are set correctly. If you want to have a GET route, then you will need something like

Route::get('/article/{id}', function (string $id) {
    $type = $request->type; //getting the type
    //your code here
    return 'success';
});

or, if you want a post, you can have something like

Route::post('/article/{id}', function (string $id) {
    $type = $request->type; //getting the type
    //your code here
    return 'success';
});

Of course, you will need to make sure that your AJAX request has type: 'POST', if it's a POST request and type: 'GET',, if it's a GET request, respectively.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
  • **Comments have been [moved to chat](https://chat.stackoverflow.com/rooms/253513/discussion-on-answer-by-lajos-arpad-how-to-implement-logic-for-adding-likes-with); please do not continue the discussion here.** Before posting a comment below this one, please review the [purposes of comments](/help/privileges/comment). Comments that do not request clarification or suggest improvements usually belong as an [answer](/help/how-to-answer), on [meta], or in [chat]. Comments continuing discussion may be removed. – Machavity May 06 '23 at 19:22
2

If you don't need <a> tag then you change that to span or button and then use data-attr for setting value of $article->id & type. Then, use this data-attr to fetch value in ajax and pass it your server .

Changes you can do in your code :

HTML :

<!--added span tag and use data-attr-->
<span data-id="{{ $article->id }}" data-type="heart" class="comments-sub-header__item like-button {{ $article->hasLikedToday('heart') ? 'active' : '' }}">
    <div class="comments-sub-header__item-icon-count"> {{ $article->getLikeHeartTotal() }} </div>
</span>

<span data-id="{{ $article->id }}"  data-type="finger" class="comments-sub-header__item like-button {{ $article->hasLikedToday('finger') ? 'active' : '' }}">
    <div class="comments-sub-header__item-icon-count"> {{ $article->getLikeFingerTotal() }} </div>
</span>

AJAX:

  $('.like-button').on('click', function(event) {
    var targetElement=$(this);

    let article_id = targetElement.data('id');
    let type = targetElement.data('type');
    let href = `/article/${article_id}?type=${type}`
    //your ajax call change type:"POST" to type:"GET"

  });

Code Demo :

$('.like-button').on('click', function(event) {
  var targetElement = $(this);

  let article_id = targetElement.data('id');
  let type = targetElement.data('type');
  let href = `/article/${article_id}?type=${type}`
  console.log(href)
  //..your ajax call change type:"POST" to type:"GET"
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span data-id="1" data-type="heart" class="comments-sub-header__item like-button active">
        <div class="comments-sub-header__item-icon-count"> 1</div>
</span>

<span data-id="2" data-type="finger" class="comments-sub-header__item like-button active">
        <div class="comments-sub-header__item-icon-count"> 2 </div>
</span>
Swati
  • 28,069
  • 4
  • 21
  • 41
2

Do like this: The easiest and most proper way to do this is with one function. In future, you can extend for more reactions as well, just changing this data-type="reaction".


Modify your <a>, with data-* attribute

<a data-article-id="{{ $article->id }}" data-type="heart" class="comments-sub-header__item like-button {{ $article->hasLikedToday('heart') ? 'active' : '' }}">
    <div class="comments-sub-header__item-icon-count"> {{ $article->getLikeHeartTotal() }} </div>
</a>

<a data-article-id="{{ $article->id }}" data-type="finger" class="comments-sub-header__item like-button {{ $article->hasLikedToday('finger') ? 'active' : '' }}">
    <div class="comments-sub-header__item-icon-count"> {{ $article->getLikeFingerTotal() }} </div>
</a>

You've to change to data-article-id and data-type.


In AJAX

$('.like-button').on('click', function(event) {
  event.preventDefault();
  var targetElement = $(this);

  let articleId = targetElement.data('article-id'); # Article ID
  let likeType = targetElement.data('type'); # this has your like type

  $.ajax({
    url: '/article/' + articleId + '/like',
    type: 'POST',
    data: {
      type: likeType,
    },
    success: function(response) {

      var counter = targetElement.find('.comments-sub-header__item-icon-count');
      var current = parseInt(counter.html());

      counter.html(current + 1);
      targetElement.addClass('active');
    },
  });
});

In Route

Route::post('/article/{id}/like',  [ArticleController::class, 'postLike']);

In Controller

public function postLike($id, Request $request)
{
    $type = $request->input('type');
    
    // rest of the code
}
Abdulla Nilam
  • 36,589
  • 17
  • 64
  • 85