This post describes the actual Like/Dislike mechanism in the program based on Mojolicious. Web pages style is created using the Bootstrap framework. The full version of the Mojolicious like-dislike
demo can be downloaded at: https://github.com/arslonga/like-dislike
So, in the start package Vote.pm we create routers for the post with a certain ID in the section named 'first-section'
(for example), and also for like
and dislike
:
sub startup {
my $self = shift;
my $r = $self->routes;
...
$r->any('/first-section/:id' => [ id => qr/[0-9]+/ ] )->to('post#article');
...
$r->any('/likeartcl')->to('voting#like');
$r->any('/dislikeartcl')->to('voting#dislike');
...
}
A snippet of code in the Post.pm package that describes the 'article'
method:
package Vote::Controller::Post;
use Mojo::Base 'Mojolicious::Controller';
use Session;
use SessCheck;
...
#---------------------------------
sub article {
#---------------------------------
my $self = shift;
my $id = $self->stash('id');
my($nickname, $status, $client_check, $user_id, $title_alias);
eval{
$nickname = $self->session('client')->[0];
$user_id = $self->session('client')->[2];
};
$client_check = SessCheck->client( $self, $nickname );
if( !$client_check ){
$status = ' disabled';
}
$title_alias = (split(/\//, $self->req->url))[1];
my $data = $self->db->select( 'posts', ['*'], {id => $id} )->hash;
$self->render(
dat => $data,
section_ident => $title_alias,
id => $id,
like_id => 'like_'.$title_alias.'_'.$id,
unlike_id => 'unlike_'.$title_alias.'_'.$id,
client_id => $user_id,
unlike_btn_name => 'unlike'.$title_alias.'_'.$id,
like_btn_name => 'like'.$title_alias.'_'.$id,
status => $status,
liked_cnt => $data->{liked} || 0,
unliked_cnt => $data->{unliked} || 0
);
}#---------------
...
1;
Template article.html.ep in /templates/post
% layout 'vote';
<hr>
<a class="btn btn-info" href="/first-section" role="button">
<span class="glyphicon glyphicon-arrow-left"></span>
</a>
<hr>
<div>
<h2><%= $dat->{title} %></h2>
<%= $dat->{body} %>
</div>
<hr>
<div class="ld-box text-right">
%# Rendering a template that describes a block of code
%# that contains two buttons:
%# 'like' and 'dislike'
<%= include 'voting/vote' %>
</div>
<script>
//---------- Like/Dislike block ---
async function LikeArtcl(titleAlias, articleId, vote_span, user_id) {
let likedislikeBox = document.querySelector('.ld-box');
let response = await fetch("/likeartcl?title_alias=" +
titleAlias +
'&article_id=' +
articleId +
'&vote_span=' +
vote_span +
'&user_id=' +
user_id);
if (response.ok) {
let respRendr = await response.text();
likedislikeBox.innerHTML = respRendr;
}else {
alert("Error HTTP: " + response.status);
}
}
async function UnlikeArtcl(titleAlias, articleId, vote_span, user_id) {
let likedislikeBox = document.querySelector('.ld-box');
let response = await fetch("/dislikeartcl?title_alias=" +
titleAlias +
'&article_id=' +
articleId +
'&vote_span=' +
vote_span +
'&user_id=' +
user_id);
if (response.ok) {
let respRendr = await response.text();
likedislikeBox.innerHTML = respRendr;
}else {
alert("Error HTTP: " + response.status);
}
}
//---------- Like/Dislike block END ---
</script>
Package Session.pm:
package Session;
use Mojo::Base -base;
#---------------------------------
sub user {
#---------------------------------
my($self, $c, $login, $password, $id) = @_;
$c->session( client => [$login, $password, $id], expiration => 120);
return 1;
}#---------------
# 'voting' method of Session called in 'Voting.pm' package (see code fragment below)
#---------------------------------
sub voting {
#---------------------------------
my($self, $c, $user_vote_id, $title_alias_and_id) = @_;
$c->signed_cookie( $user_vote_id => $title_alias_and_id, {expires => time + 120});
return 1;
}#---------------
#---------------------------------
sub client_expire {
#---------------------------------
my($self, $c) = @_;
delete $c->session->{'client'};
return 1;
}#---------------
1;
Package Vote/Voting.pm
Here we render template 'voting/vote.html.ep'
that describes code block with 'like'
and 'dislike'
buttons and 'like'
and 'dislike'
counts
package Vote::Voting;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::Util qw(trim encode decode);
use Mojo::Cookie;
use Session;
#---------------------------------
sub like {
#---------------------------------
my $self = shift;
my($nickname, $status);
eval{
$nickname = $self->session('client')->[0];
};
my $client_check = SessCheck->client( $self, $nickname );
my $title_alias = $self->param('title_alias');
my $article_id = $self->param('article_id');
my $user_id = $self->param('user_id');
$status = !$client_check ? ' disabled' : '';
my $like_count = $self->db->select( 'posts',
['liked'],
{id => $article_id} )
->hash->{liked};
my $unlike_count = $self->db->select( 'posts',
['unliked'],
{id => $article_id} )
->hash->{unliked};
my $like_cookie_name = 'like_user'.$user_id.'_'.$title_alias.'_'.$article_id;
my $unlike_cookie_name = 'unlike_user'.$user_id.'_'.$title_alias.'_'.$article_id;
if( !$self->signed_cookie( $like_cookie_name ) && $client_check ){
++$like_count;
$self->db->update( 'posts',
{'liked' => $like_count},
{'id' => $article_id} );
}
if( $self->signed_cookie( $unlike_cookie_name ) ){
--$unlike_count;
$self->db->update( 'posts',
{'unliked' => $unlike_count},
{'id' => $article_id} );
$self->signed_cookie( $unlike_cookie_name => '', {expires => 1});
}
# Store session for 'like' action where key is
#'like_user'.$user_id.'_'.$title_alias.'_'.$article_id
# and value is $title_alias.'_'.$article_id
Session->voting( $self,
'like_user'.$user_id.'_'.$title_alias.'_'.$article_id,
$title_alias.'_'.$article_id
);
$self->render(
template => 'voting/vote',
section_ident => $title_alias,
id => $article_id,
like_id => 'like_'.$title_alias.'_'.$article_id,
unlike_id => 'unlike_'.$title_alias.'_'.$article_id,
client_id => $user_id,
unlike_btn_name => 'unlike'.$title_alias.'_'.$article_id,
like_btn_name => 'like'.$title_alias.'_'.$article_id,
status => $status,
liked_cnt => $like_count,
unliked_cnt => $unlike_count
);
}#---------------
#---------------------------------
sub dislike {
#---------------------------------
my $self = shift;
my($nickname, $status);
eval{
$nickname = $self->session('client')->[0];
};
my $client_check = SessCheck->client( $self, $nickname );
my $title_alias = $self->param('title_alias');
my $article_id = $self->param('article_id');
my $user_id = $self->param('user_id');
$status = !$client_check ? ' disabled' : '';
my $unlike_count = $self->db->select( 'posts',
['unliked'],
{id => $article_id} )
->hash->{unliked};
my $like_count = $self->db->select( 'posts',
['liked'],
{id => $article_id} )
->hash->{liked};
my $like_cookie_name = 'like_user'.$user_id.'_'.$title_alias.'_'.$article_id;
my $unlike_cookie_name = 'unlike_user'.$user_id.'_'.$title_alias.'_'.$article_id;
if( !$self->signed_cookie( $unlike_cookie_name ) && $client_check ){
++$unlike_count;
$self->db->update( 'posts',
{'unliked' => $unlike_count},
{'id' => $article_id} );
}
if( $self->signed_cookie( $like_cookie_name ) ){
--$like_count;
$self->db->update( 'posts',
{'liked' => $like_count},
{'id' => $article_id} );
$self->signed_cookie( $like_cookie_name => '', {expires => 1});
}
# Store session for 'dislike' action where key is
#'unlike_user'.$user_id.'_'.$title_alias.'_'.$article_id
# and value is $title_alias.'_'.$article_id
Session->voting( $self,
'unlike_user'.$user_id.'_'.$title_alias.'_'.$article_id,
$title_alias.'_'.$article_id );
$self->render(
template => 'voting/vote',
section_ident => $title_alias,
id => $article_id,
like_id => 'like_'.$title_alias.'_'.$article_id,
unlike_id => 'unlike_'.$title_alias.'_'.$article_id,
client_id => $user_id,
unlike_btn_name => 'unlike'.$title_alias.'_'.$article_id,
like_btn_name => 'like'.$title_alias.'_'.$article_id,
status => $status,
unliked_cnt => $unlike_count,
liked_cnt => $like_count
);
}#---------------
1;
Template voting/vote.html.ep
Use it for both rendering 'like'
and 'dislike'
actions
%# voting/vote.html.ep
<button type="button" name="<%= $like_btn_name %>"
onclick="Voting('<%= $section_ident %>',
'<%= $id %>',
'<%= $like_id %>',
'<%= $client_id %>');
this.disabled='disabled';"
id="chevron_stl"<%= $status %>>
<span id="<%= $section_ident.'_'.$client_id.'-up' %>"
class="glyphicon glyphicon-chevron-up" aria-hidden="true"></span>
</button>
<span id="<%= $like_id %>" class="like_unlike"><%= $liked_cnt %></span>
<button type="button" name="<%= $unlike_btn_name %>"
onclick="Voting('<%= $section_ident %>',
'<%= $id %>',
'<%= $unlike_id %>',
'<%= $client_id %>');
this.disabled='disabled';"
id="chevron_stl"<%= $status %>>
<span id="<%= $section_ident.'_'.$client_id.'-down' %>"
class="glyphicon glyphicon-chevron-down" aria-hidden="true"></span>
</button>
<span id="<%= $unlike_id %>" class="like_unlike"><%= $unliked_cnt %></span>
Conclusion
Use the combination of article ID, user ID, and kind of action (like or dislike) to create a unique voting ID. This voting ID can be a key of signed cookie.
A simplified like/dislike system implemented in my project MornCat CMS at https://github.com/arslonga/my_blog
Top comments (0)