Jump to content
MilenKo

FieldType Comments Up/Down votes and Reply button question

Recommended Posts

Good day fellas. I was working on a theme that required me to setup a comment system with star rating and votes and it is all completed markup wise, however I noticed something strange during the test - if the field option is set to use Up/Down votes a user can do both simultaneously. My question is, would it be possible to allow ONLY up or down-vote to be used at a single time as presently I could click that I like a page and at the same time dislike it. Logically, if I would click to like the page and then click to dislike it, it would diminish my previous vote and only allow to either like or not a page.

Also, when in the field settings I set a reply depth, I would like to have the Reply button still showing instead of disappearing. It is much more functional to have the reply button showing even after the depth limit is reached which would allow the user to reply quickly if a lot of comments are shown on the page. Instead of scrolling down to the comment adding form, the reply button would still show it under the chosen comment, however the indent would stop changing.

Any ideas how to allow only one voting at the time and keep the reply button after the depth limit is reached would be really appreciated. I am attaching an image to better demonstrate the need...

 

Comments-Reply-Votting.png

Share this post


Link to post
Share on other sites

Quoting from your recent questions in the other thread:

1. The votes count is working, but is not automatically refreshing so the page has to be reloaded in order to see the numbers

2. The votes allow a user to vote UP and Down at the same time which in my scenario does not make much sense as you either like a comment or don't.

3. How to implement the comment depth.

#1. Refresh: If you need anything to be automatically refreshed without loading a page, you need to use Ajax. I know of no other technology. Simple steps:

  • On click votes count, capture that event using Ajax (JavaScript)
  • Send the Ajax post (or get) to the specified URL, most likely the current page, so "./"
  • Server-side, the template file that the current page uses checks if an Ajax call has been made, i.e. if($config->ajax) {capture and sanitize form inputs, send response back, usually as JSON)
  • Client-side, the code you used to send your Ajax post is waiting for an answer from the server in a particular format. If that response comes through...
  • Find the inputs on the page you need to refresh and refresh them.

Example $config->ajax is here:

#2: Up/Down Votes: How do you want to control this? Do you want to stop it client-side or both client-side and server-side? If server-side as well, how are you distinguishing users? Are you requiring users to log in before voting? If client side, you can use JavaScript to enable the HTML elements act like a toggle switch. If voting is mission critical to your system, you cannot rely on client-side prevention alone, hence the server-side checks. 

Server-side, if you are relying on IP only, that doesn't work always as some users could have dynamic or share IPs. Are you using email and registered users? 

3#. Comment Depth: I already answered this question. I pointed you to the code in Dashboard Notes. If you want nested replies/comments, then you need to use a recursive function. If you want to limit output (in your case depth), you will need to use some sort of counter, in this case a count-down counter works well. Both of these are in the code I referred you to in Dashboard Notes. Did you have a look? How did it work for you? What went wrong? Etc...?

  • Like 1

Share this post


Link to post
Share on other sites

I was working this morning on the depth and I believe I achieved exactly what is needed. As per my markup and needs, ideally I would have the reply button to appear on every comment (even when the depth is reached) which would make a visitor/user reply easier unless they want to post a new comment from the form above/bellow.

In regards to the rating, I used a function from the module itself to render the result:

$stars = $comment->renderStars()

I tried to use something similar on the votes using:

$votes = $comment->renderVotes();

however that lead to an error: Method Comment::renderVotes does not exist or is not callable in this context. Would someone know a way to render the votes?

To achieve the needed functionality of the depth and markup, I used some code from @fbg13 in this topic .

Here is my initial markup:

Spoiler

<div class="comments-area">
	<div class="sec-title"><h2>2 Comments</h2></div>
	<!--Comment Box-->
	<div class="comment-box">
		<div class="comment">
			<div class="author-thumb"><img src="images/resource/author-2.jpg" alt=""></div>
			<div class="comment-inner">
				<div class="comment-info">Sandra Mavic</div>
				<div class="post-date">March 03, 2016</div>
				<div class="text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit doli. Aenean commodo ligula eget dolor. Aenean massa. Cumtipsu sociis natoque penatibus et magnis dis parturient montesti, nascetur ridiculus mus. Donec qam penatibus et magnis .</div>
				<a href="#" class="reply-btn">Reply</a>
			</div>
		</div>
	</div>
	<!--Comment Box-->
	<div class="comment-box reply-comment">
		<div class="comment">
			<div class="author-thumb"><img src="images/resource/author-2.jpg" alt=""></div>
			<div class="comment-inner">
				<div class="comment-info">Sandra Mavic</div>
				<div class="post-date">March 03, 2016</div>
				<div class="text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit doli. Aenean commodo ligula eget dolor. Aenean massa. Cumtipsu sociis natoque penatibus et magnis dis parturient montesti, nascetur ridiculus mus. Donec qam penatibus et magnis .</div>
				<a href="#" class="reply-btn">Reply</a>
			</div>
		</div>
	</div>	
</div>

 

and here is the function code I added to my _func.php:

Spoiler

// Function to pull up and serve comments
// To call the function in the markup, use:
// echo getComments($page->comments, $page);
// Shared by: fbg13
// URL: https://processwire.com/talk/topic/15077-threaded-comments/?do=findComment&comment=135107
  
function getComments($comments, $page, $parentID = 0, $reply = true, $child = null)
{
	$out = "";
	
	// Cycle through all page comments
	foreach($comments as $comment)
	{
		if($comment->parent_id == $parentID)
		{
			// Check if the $reply variable is true and if yes, define the markup for the reply link/button
			$reply = ($reply == true) ? "<a class='reply-btn CommentActionReply' data-comment-id='{$comment->id}' href='#Comment{$comment->id}'>Reply</a>" : "";
			
			// Check the comment status
			// Comments status is as follows:
			// 1 = approved
			// 0 = pending
			// -2 = spam
			if($comment->status < 1) continue;
			
			// The bellow lines can be used to pull up
			// user display name
			// for my needs I used $cite which posts the author but not user display name
			// Kept the lines to extend the needs of the markup for custom cases
			// $userID = $comment->created_users_id;
			// $u = wire("pages")->get($userID);
			// $name = ($u->displayName) ? $u->displayName : $u->name;
			
			// Assign the comment author to $cite
			$cite = htmlentities($comment->cite);
			
			// Assign the comment text to $text
			$text = htmlentities($comment->text);
			
			// Assign the user gravatar to $gravatar
			$gravatar = $comment->gravatar();

			// Define the date format of the markup
			// F - A full textual representation of a month, such as January or March
			// j - Day of the month without leading zeros
			// Y - A full numeric representation of a year, 4 digits
			// G - 24-hour format of an hour without leading zeros	
			// i - Minutes with leading zeros	
			// Reference: http://it2.php.net/manual/en/function.date.php
			$date = date('F j, Y G:i', $comment->created);
			
			// Render the comment rating and assign it to $stars variable
			$stars = $comment->renderStars();

			// Define the comment list markup
			$out .= "

				<div class='comment-box{$child}' id='comment-{$comment->id}'>
					<div class='comment'>
						<div class='author-thumb'>
							<img src='{$gravatar}' alt='{$cite} gravatar'>
						</div>
						<div class='comment-inner'>
							<div class='comment-info'>{$cite} &nbsp; {$stars}</div>
							<div class='post-date'>{$date}</div>
							<div class='text'>{$text}</div>
							{$reply}
						</div>
					</div>
				</div>";
			
			// Render the comments and apply the child style markup 
			// in my case the class is reply-comment
			$out .= getComments($comments, $page, $comment->id, true, " reply-comment");


		}//endif
	
	}//endforeach
	
	// Return the function output
	return $out;
	
}//EOF function

 

So only the voting is left before I move to style the comment form.

P.S. Added a screenshot for the comments appearance and here is how to render the comments in the template:

echo getComments($page->_your_comment_field_here_, $page); 
Spoiler

comments-appearance.jpg

 

Share this post


Link to post
Share on other sites

Was there a question in there? 🙂. Or maybe you are just documenting progress? If not, what is pending?

Share this post


Link to post
Share on other sites

Well sorry, it appeared more like a documentation versus the question.

So yes, there is still a question - how to render in the function the votes 

50 minutes ago, MilenKo said:

I tried to use something similar on the votes using:


$votes = $comment->renderVotes();

however that lead to an error: Method Comment::renderVotes does not exist or is not callable in this context. Would someone know a way to render the votes?

If I manage to render the votes, I won't have to deal with Ajax calls as the function is working fine during the rendering, but how to render those - that is the question of the day 😉

Share this post


Link to post
Share on other sites
1 hour ago, MilenKo said:

however that lead to an error: Method Comment::renderVotes does not exist or is not callable in this context. Would someone know a way to render the votes?

The error is happening because a Comment object does not have a such a method. If you look at the file CommentList.php, you will see it has a method renderVotes() as well as a renderStars(). Both are passed one argument/parameter, a Comment object. The two methods just implement some string cleanup and html output. The most important thing for you is the number of votes, down and up. You get this using:

  •  $comment->downvotes; for down votes
  • $comment->upvotes; for up votes

You can wrap those around whatever element you want, e.g.

"<span class='down_votes'>{$comment->downvotes}</span>";
"<span class='up_votes'>{$comment->upvotes}</span>";

Then style those as you wish using CSS.

Share this post


Link to post
Share on other sites

Hey kongondo, that is clear that I can use $comment->downvotes  or upvotes,) however that returns the values of up/down votes. If I go that way, I will be forced to use use ajax to refresh the values etc. Isnt there a simpler way to call {votes} in the custom output? Or that is why it is called custom, as I have to figure out the up/down votes using some custom code? I've already figured out how to implement the up/down vote showing as well as the increment, however I had the refresh issue for which you suggested to use Ajax. That is why I was hoping that there will be a simpler way to call:

$this->renderVotes($comment), $out);

which would show both up/down values and an arrow up/down to change the values.

Also, I've tested the ProcessWire voting system and it allows both - up/down votes for a single session. Would you know how I can only allow one to be selected?

Share this post


Link to post
Share on other sites
24 minutes ago, MilenKo said:

however that returns the values of up/down votes. If I go that way, I will be forced to use use ajax to refresh the values etc. Isnt there a simpler way to call {votes} in the custom output? Or that is why it is called custom, as I have to figure out the up/down votes using some custom code? I've already figured out how to implement the up/down vote showing as well as the increment, however I had the refresh issue for which you suggested to use Ajax. That is why I was hoping that there will be a simpler way to call:


$this->renderVotes($comment), $out);

which would show both up/down values and an arrow up/down to change the values.

OK. Let me try and understand what you are after.

25 minutes ago, MilenKo said:

Isnt there a simpler way to call {votes} in the custom output?

I'm not sure I follow. what are {votes} in the curly braces? What do you mean by custom output? Using your own form?

26 minutes ago, MilenKo said:

however I had the refresh issue for which you suggested to use Ajax

How will this site operate? From my understanding:

  • Abdi visits your site. He likes the comment by Susan and up-votes it.
  • Mary-Lou visits your site. She doesn't like the comment by Singh. She down-votes it.
  1. Do you want Abdi to see the latest (including his) count of up votes to Susan's comment? The same for Mary-Lou. Do you want her to see the latest total of down votes to Singh's comment? 
  2. When Abdi and Mary-Lou vote, do you wish their votes to be saved to the server immediately?

If the answer to either question is yes, then the voting actions will need to tell the server that a vote has been submitted. If you don't want to refresh the page manually (F5) to see the new votes, then it has to be done via Ajax (the most common method). Before that, the votes have to be submitted to the server. The most common way to do it without a physical refresh is to use Ajax.

Maybe I'm still not getting what you are after. On the other hand, if I am on the right track, we can then move to how to submit a vote using Ajax.

Share this post


Link to post
Share on other sites

Well, in your example, if Abdi visits the site and decides to vote for Susan, he can ONLY like or dislike her comment. Same applies for any other user. Technically it does not make much sense if I like your comment and don't like it at the same time (which in default functionality is allowed before I get the message that I've already voted).

{votes} is used in the commentHeader to show the votes in CommentList.php line 165:

if(strpos($out, '{votes}') !== false) {
	$out = str_replace('{votes}', $this->renderVotes($comment), $out);
}

That is what I was trying to understand how to achieve...

Also, I am planing to allow everyone to share a comment no matter whether they are registered or not and also to see the votes of every comment. So I guess I will have to check the java script which is not allowing a person to vote more than once per session and to modify it to not allow the same user to give a positive and negative response at the same session. In other words a visitor would ONLY be able to appreciate a comment by liking it or not appreciate by dis-liking it (up/down vote).

If I use the standard rendering method:

Spoiler

echo $page->recipe_comments->render(array(
	
	// Comments headline markup with the comments count
	'headline' => "<div class='sec-title'><h2>{$comments}</h2></div>",
	
	// Comments header markup
	// Example: 'Posted by {cite} on {created} {stars}',
	'commentHeader' => '
	<div class="comment-info">{cite} {stars}</div>
	<div class="post-date">{created}</div>',

	// F - A full textual representation of a month, such as January or March
	// j - Day of the month without leading zeros
	// Y - A full numeric representation of a year, 4 digits
	// G - 24-hour format of an hour without leading zeros
	// i - Minutes with leading zeros
	// Reference: http://it2.php.net/manual/en/function.date.php
	'dateFormat' => 'F j, Y g:i',
	
	// Default comments character encoding
	'encoding' => 'UTF-8',
	
	// shows unapproved comments if true
	'admin' => false, 
	
	// Add a permalink to the comment for easier sharing
	'usePermalink' => true,

));

 

I can show the votes (↑1 ↓0) and they are rendered properly, but I had some difficulty applying the markup and also once the depth is reached, the reply button dissappears...

Share this post


Link to post
Share on other sites

I feel like we are going round in circles :-). Maybe it's just me. I was hoping we'd tackle one issue at a time. My previous last question (Abdi and co) was about "refreshing votes" You still haven't answered my questions 1 and 2. Instead, you've further elaborated on another issue you are having (which we could tackle later, namely, simultaneously being allowed to vote up and down).

1 hour ago, MilenKo said:

So I guess I will have to check the java script which is not allowing a person to vote more than once per session and to modify it to not allow the same user to give a positive and negative response at the same session.

This and ...

1 hour ago, MilenKo said:

Also, I am planing to allow everyone to share a comment no matter whether they are registered or not

this are incongruent. How will you prevent unregistered users from voting multiple times? Which of their credentials will you be assessing to prevent voting multiple times?

Secondly, JavaScript alone will not prevent anyone from voting more than once :-). For those really determined to vote multiple times, it is very easy to manipulate client-side validation. You really need to validate server-side.

If you can please answer #1 and #2 in my previous post, we can move this forward, maybe even with code examples.

Share this post


Link to post
Share on other sites

OK, so step by step and gradually will go and answer the questions.

1. Do you want Abdi to see the latest (including his) count of up votes to Susan's comment? The same for Mary-Lou. Do you want her to see the latest total of down votes to Singh's comment?  - YES (anyone see the up/down votes)

2. When Abdi and Mary-Lou vote, do you wish their votes to be saved to the server immediately? - The default votes shown during the comment rendering are totally fine for me. Abdi and Mary-Lou click on up/down vote and they see their vote added to the total count without a need to refresh the page.

In regards to the java script votes limitting - I am not sure if it is a java script or else, but I visit my demo page while I am not logged on as any user (guest) and I am able to rate any of the comments only once before I get the popup message saying: You have already voted for this comment. Presently, the popup is shown if I try to submit a vote for a second time, but it still allows me to press the up and down buttons once before I see the popup. It would make more sense, to click on any of the two buttons (Up/Down vote) and after that to see the popup.

Share this post


Link to post
Share on other sites
37 minutes ago, MilenKo said:

When Abdi and Mary-Lou vote, do you wish their votes to be saved to the server immediately? - The default votes shown during the comment rendering are totally fine for me. Abdi and Mary-Lou click on up/down vote and they see their vote added to the total count without a need to refresh the page.

OK, we are getting closer. One more clarification. You say Abdi and Mary-Lou will see their voted added to the total count. This is the crux of this issue. What is total count? If when Abdi was viewing the total was 9 votes and he adds one more, if we don't save it to the server but do it on the browser only (client-side) the count he will see is 10. On the other side of wherever, Mary-Lou also votes. When she was viewing, the count was 5. She votes, and if we don't save to server, she sees a total of 6. Is that what you desire really? I'd have thought you want the true count, i.e. true count as recorded on the server, meaning, Abdi's + Mary-Lou's votes + any other that were already saved on the server.

To confuse me further (:-)) you say without the need to refresh the page? Maybe you mean without a need to reload the page? I thought you wanted the page to be refreshed without the need to reload it?

44 minutes ago, MilenKo said:

In regards to the java script votes limitting - I am not sure if it is a java script or else, but I visit my demo page while I am not logged on as any user (guest) and I am able to rate any of the comments only once before I get the popup message saying: You have already voted for this comment. Presently, the popup is shown if I try to submit a vote for a second time, but it still allows me to press the up and down buttons once before I see the popup.

I'll have a look when I get the time. If it is only JavaScript, then it's probably using cookies. 

Share this post


Link to post
Share on other sites

Oh boy, I got you confused haven't I? All I am trying to achieve is to have the proper markup for comments rendered.

Maybe it would be easier to have a copy of FieldType Comments to my /site/modules and have the markup modifications. I know doing so would make it work properly for all the needs, but so far I got some issues with the depth. Trying to analyze the original markup generated after the comments rendering and mine in order to apply the styles, but as it appears, the default generated markup has a <ul> wrapper where mine does not and besides that every comment is a <li> where in my markup is again <div>.

I will post my code of CommentList.php and maybe that would solve most of the issues and have us going...

In regards to the reload of the page, you are right, I meant to RELOAD but not to refresh.  The total count of the comment votes I guess is saved prior to adding to the page because if I vote for a comment and try to login as someone else a minute later, I see all the total votes that has been added (up/down). I am not yet quite sure how does the module render exactly the comments but it feels like every time someone votes, the page is saved and the upvotes/downvotes counter is modified while only the numbers of the counter change dynamically without a page RELOAD.

Share this post


Link to post
Share on other sites

Hey all. With the kind assistance of @fbg13 the comment depth functionality was achieved by modifying the CommentList.php and the standard comment rendering. I've added some extra comments to the file so that it is easier to understand and modify.

I've noticed that having depth replies has some confusing part where even set to 1, any replies to the parent are still having indent applied to the right. So after having 20-30 replies, the markup would start looking weird:

Spoiler

comment-depth-one-appearance.thumb.jpg.49c766888284f378b5f233e6944d5461.jpg

Due to this fact, it could become a bit confusing for users which comment is a reply to what... I will try to set some limits of the indent applied as per @fbg13 suggested modifications and see if that would fit the purpose nicely.

At this stage, I would say that the earlier shared function code is looking better in regards to the comments alignment (sort of facebook style) however I should find a way to render the votes in the function.

Whichever comes first, I will use it and share the code, but so far we are moving forward... 😉 Also I need to find a way to allow the users ONLY to UPVOTE or Downvote but not both actions at the same time before the popup message appear stating that the vote has been already placed. My guess is that it should be located in the FieldtypeComments.module where the popup text is, but I have not yet managed to find the way.

CommentList.php

Share this post


Link to post
Share on other sites
Quote

any replies to the parent are still having indent applied to the right

Cause you're not closing your divs correctly.

You open 5 divs and only close them all when there are replies, if there are no replies you only close 4.

Share this post


Link to post
Share on other sites

Oh, I will have to check that. Thanks for pointing it out.

Share this post


Link to post
Share on other sites

Hey gents, sorry if it is not the right spot to report a little module issue that I've noticed but I am still getting my head around. While implementing comments on a new project, I have tested what would happen if I add a long word with no breaks (for example 60 letters of T or else). As I was expecting it did break my comment list, so I modified the CSS and added an extra line to make the comment appear normally and not break the style.

word-wrap: break-word;

While approving the comments from the admin, I've noticed that the long "no-break word" have appeared weird on the admin side as well. After modifying the class CommentText to:

    .CommentItem .CommentContent .CommentText {
      cursor: pointer;
      padding-right: 1em; 
	  word-wrap: break-word;}

It all started appearing properly and the long word got broken to several lines instead of going off the screen. I am attaching a few screenshots to demonstrate the issue. The path to the css is:

.../wire/modules/Process/ProcessCommentsManager/ProcessCommentsManager.css (line 103)

If it really not appear only with my local environment, until it is fixed, the class might need to be added to the css upon every update as far as the FieldType Comments is part of the core now.

Spoiler

 

comment-admin-long-text-issue-01.png

comment-admin-long-text-issue-02.png

comments-admin-after-modifications.jpg

 

 

Share this post


Link to post
Share on other sites

Hello all.

After quite a bit of fiddling and wrapping my head around comments (of course with the kind assistance of @fbg13 and @kongondo I was able to implement the full functionality of comments with rating and votes. As far as it was a bit tough for me to find the way to properly close all the HTML tags as well as to figure out why this or that was not functioning, I've applied some changes to CommentList.php where I mostly added lots of easy to understand notes about the functioning of the code.

I am sharing the complete code of my CommentList.php as well as the initial HTML markup and the comment rendering call in my template so that anyone can easily analyze the implementation and apply their own styles when needed. Hope that helps.

Please note that I've added a few extra protected options that I used in my markup for the title tag of the Reply and Permalink title. Here is the content of the file:

Spoiler

<?php namespace ProcessWire;

/**
 * ProcessWire CommentListInterface and CommentList
 *
 * CommentListInterface defines an interface for CommentLists.
 * CommentList provides the default implementation of this interface. 
 *
 * Use of these is not required. These are just here to provide output for a FieldtypeComments field. 
 * Typically you would iterate through the field and generate your own output. But if you just need
 * something simple, or are testing, then this may fit your needs. 
 * 
 * ProcessWire 3.x, Copyright 2016 by Ryan Cramer
 * https://processwire.com
 *
 *
 */

/*
 * CommentListInterface defines an interface for CommentLists.
 *
 */
interface CommentListInterface {
	public function __construct(CommentArray $comments, $options = array()); 
	public function render();
	public function renderItem(Comment $comment);
}

/**
 * CommentList provides the default implementation of the CommentListInterface interface. 
 *
 */
class CommentList extends Wire implements CommentListInterface {
	
	/**
	 * Reference to CommentsArray provided in constructor
	 *
	 */
	protected $comments = null;
	
	protected $page;
	protected $field;

	/**
	 * Default options that may be overridden from constructor
	 *
	 */
	protected $options = array(
	
		// Any of the options bellow can be used
		// Headline of comments style
		// Eg. '<h3>Comments</h3>', 
		'headline' => '',
		
		// Comment header
		// Tags available to be rendered automatically
		// {cite} - comment author
		// {created} - comment posting date
		// {stars} - comment rating added during post
		// {url} - comments URL (can be used for Permalink)
		// Eg. 'Posted by {cite} on {created} {stars}',
		'commentHeader' => '',
		
		// Comment footer
		// Can be used with the same tags as commentHeader
		// Could be used to split the comment list markup
		'commentFooter' => '', 
		
		// F - A full textual representation of a month, such as January or March
		// j - Day of the month without leading zeros
		// Y - A full numeric representation of a year, 4 digits
		// G - 24-hour format of an hour without leading zeros
		// i - Minutes with leading zeros
		// Reference: http://it2.php.net/manual/en/function.date.php
		'dateFormat' => 'F j, Y g:i',

		// Comments character encoding
		'encoding' => 'UTF-8', 
		
		// Shows unapproved comments if true
		// Could be used to list spam/unapproved commentStars
		// or during development to avoid comments approval
		'admin' => false,
		
		// Enable gravatar avatars 
		// If enabled, specify maximum rating: [ g | pg | r | x ]
		// Leave it blank to disable gravatar
		'useGravatar' => '',
		
		// Set the default gravatar imageset
		// Choose one of the following types: 
		// [ 404 | mm | identicon | monsterid | wavatar ]
		'useGravatarImageset' => 'mm',	
		
		// Use permalink (direct link) to comment
		// If set to true, could be used for sharing
		// comments at social medias, messengers etc.
		'usePermalink' => false,
		
		// Allow the comments to be voted
		// Following settings exist:
		// Vptomg pff. Allow UPvoting, Allow UPvoting and DOWNvoting
		'useVotes' => 0,
		
		// Define the format of the UPvotes
		// {cnt} would be replaced by the UPvotes count
		'upvoteFormat' => '&uarr;{cnt}',

		// Define the format of the DOWNvotes
		// {cnt} would be replaced by the DOWNvotes count
		'downvoteFormat' => '&darr;{cnt}', 		
		
		// Allow posting of ratng
		// Possible options are:
		// Disabled, Yes (star rating optional), Yes (star rating required)
		// Stars could be replaced with any other icon (fontawesom, svg etc.)
		// However that might require some code changes to have it implemented
		'useStars' => 0,

		// Comments nesting/depth option of replies
		// Once the depth is set, the Reply link would not be showing
		// By default, the Reply & Permalink are dissappearing once the depth is reached
		// In this module version, I split them as I need the Permalink at all times shown
		'depth' => 0,
		
		// Define the text for the Reply button
		// Could be translated by a language file or manually from here
		'replyLabel' => 'Reply',
		
		// Define the title for the Reply button
		// in the markup it is set as title='...'
		'replyTitle' => '',
		
		// Define the Permalink label/text
		// Eg. '#'
		'permalinkLabel' => '', // 'Permalink'
		
		// Define the permalink title
		// in the markup it is set as title='...'
		'permalinkTitle' => '', // 'Direct comment link'
		
		); 

	/**
	 * Construct the CommentList
	 *
	 * @param CommentArray $comments 
	 * @param array $options Options that may override those provided with the class (see CommentList::$options)
	 *
	 */
	public function __construct(CommentArray $comments, $options = array()) {

		$h3 = $this->_('h3'); // Headline tag
		$this->options['headline'] = "<$h3>" . $this->_('Comments') . "</$h3>"; // Header text
		$this->options['replyLabel'] = $this->_('Reply');
		
		if(empty($options['commentHeader'])) {
			if(empty($options['dateFormat'])) {
				$this->options['dateFormat'] = 'relative';
			}
		} else {
			//$this->options['commentHeader'] = $this->('Posted by {cite} on {created}'); // Comment header // Include the tags {cite} and {created}, but leave them untranslated
			if(empty($options['dateFormat'])) {
				$this->options['dateFormat'] = $this->_('%b %e, %Y %l:%M %p'); // Date format in either PHP strftime() or PHP date() format // Example 1 (strftime): %b %e, %Y %l:%M %p = Feb 27, 2012 1:21 PM. Example 2 (date): m/d/y g:ia = 02/27/12 1:21pm.
			}
		}
		
		$this->comments = $comments; 
		$this->page = $comments->getPage();
		$this->field = $comments->getField();
		$this->options['useStars'] = $this->field->get('useStars');
		$this->options = array_merge($this->options, $options); 
	}

	/**
	 * Get replies to the given comment ID, or 0 for root level comments
	 * 
	 * @param int|Comment $commentID
	 * @return array
	 * 
	 */
	public function getReplies($commentID) {
		if(is_object($commentID)) $commentID = $commentID->id; 
		$commentID = (int) $commentID; 
		$admin = $this->options['admin'];
		$replies = array();
		foreach($this->comments as $c) {
			if($c->parent_id != $commentID) continue;
			if(!$admin && $c->status != Comment::statusApproved) continue;
			$replies[] = $c;
		}
		return $replies; 
	}

	/**
	 * Rendering of comments for API demonstration and testing purposes (or feel free to use for production if suitable)
	 *
	 * @see Comment::render()
	 * @return string or blank if no comments
	 *
	 */
	public function render() {
		$out = $this->renderList(0); 
		if($out) $out = "\n" . $this->options['headline'] . $out; 
		return $out;
	}
	
	protected function renderList($parent_id = 0, $depth = 0) {
		$out = $parent_id ? '' : $this->renderCheckActions();
		$comments = $this->options['depth'] > 0 ? $this->getReplies($parent_id) : $this->comments;
		if(!count($comments)) return $out;
		foreach($comments as $comment) $out .= $this->renderItem($comment, $depth);
		if(!$out) return '';
		
		// Define the defauult comment class
		$class = "CommentList";
		
		// Check if the comments depth is set in the admin (Details tab)
		// if yes, define the class for the comment list thread
		if($this->options['depth'] > 0) $class .= " CommentListThread";
		
			// Otherwise define the class for a normal comment list (non-threaded)
			else $class .= " CommentListNormal";
			
		// Check if the gravatar is enabled and if so, set the class for a comment with gravatar
		if($this->options['useGravatar']) $class .= " CommentListHasGravatar";
		
		// Check if the comment has a parent and if yes, set the class of a child
		if($parent_id) $class .= " CommentListReplies";
		
		// In this specific profile, instead of using the afore mentioned classes,
		// a .comment-class .comment-class is defined which permits the proper indent/depth of the list
		
		// The line bellow can be uncommented to wrap out the comments list
		//$out = "<ul class='$class'>$out\n</ul><!--/CommentList-->";
		
		return $out; 
	}

	/**
	 * Populate comment {variable} placeholders
	 * 
	 * @param Comment $comment
	 * @param string $out
	 * @param array $placeholders Additional placeholders to populate as name => value (exclude the brackets)
	 * @return string
	 * 
	 */
	protected function populatePlaceholders(Comment $comment, $out, $placeholders = array()) {
		
		if(empty($out) || strpos($out, '{') === false) return $out;
		
		foreach($placeholders as $key => $value) {
			$key = '{' . $key . '}';	
			if(strpos($out, $key) === false) continue;
			$out = str_replace($key, $value, $out);
		}
		
		if(strpos($out, '{votes}') !== false) {
			$out = str_replace('{votes}', $this->renderVotes($comment), $out);
		}
		if(strpos($out, '{stars}') !== false) {
			$out = str_replace('{stars}', $this->renderStars($comment), $out);
		}
		
		if(strpos($out, '{url}') !== false) {
			$out = str_replace('{url}', $comment->getPage()->url() . '#Comment' . $comment->id, $out);
		}
		
		if(strpos($out, '{page.') !== false) {
			$page = $comment->getPage();
			$out = str_replace('{page.', '{', $out);
			$out = $page->getMarkup($out);
		}
		
		return $out;
	}
	
	/**
	 * Render the comment
	 *
	 * This is the default rendering for development/testing/demonstration purposes
	 *
	 * It may be used for production, but only if it meets your needs already. Typically you'll want to render the comments
	 * using your own code in your templates. 
	 *
	 * @param Comment $comment
	 * @param int $depth Default=0
	 * @return string
	 * @see CommentArray::render()
	 *
	 */
	public function renderItem(Comment $comment, $depth = 0) {

		$text = $comment->getFormatted('text'); 
		$cite = $comment->getFormatted('cite'); 

		$gravatar = '';
		
		// Check if Gravatar is enabled
		if($this->options['useGravatar']) {
			
			$imgUrl = $comment->gravatar($this->options['useGravatar'], $this->options['useGravatarImageset']); 
			
			// Define the markup for the gravatar appearance
			// $imgUrl - used for the gravatar image2wbmp
			// $cite - used to set the comment author as the image alt tag
			if($imgUrl) $gravatar = "<div class='author-thumb'><img src='{$imgUrl}' alt='{$cite}' /></div>";
		}

		$website = '';
		
		// Check if the comment had a website added to it during post
		if($comment->website) $website = $comment->getFormatted('website');

		// if a website was added, link the author to the website URL
		if($website) $cite = "<a href='$website' rel='nofollow' target='_blank'>$cite</a>";
		$created = wireDate($this->options['dateFormat'], $comment->created); 
		$placeholders = array(
			'cite' => $cite, 
			'created' => $created, 
			'gravatar' => $gravatar
		);
		
		// Check if commentHeader is defined in the template rendering function
		if(empty($this->options['commentHeader'])) {
			
			//// if commentHeader was not defined in the function call, set the default markup
			$header = "<span class='CommentCite'>$cite</span> <small class='CommentCreated'>$created</small> ";
			
			// Check if the stars rating is allowed in the admin and if yes, add the rating to the header
			if($this->options['useStars']) $header .= $this->renderStars($comment);
			
			// Check if comment votes are allowed and if yes, add the votes to the footer
			// By default the stars and votes are set to header, however both could be used or any one of them
			if($this->options['useVotes']) $footer .= $this->renderVotes($comment); 
		
		} else {
			
			// If commentHeader has been populated during rendering, assign the markup to $header
			$header = $this->populatePlaceholders($comment, $this->options['commentHeader'], $placeholders);
		}

		// If the commentFooter has been populated during rendering, assign the markup to $footer
		$footer = $this->populatePlaceholders($comment, $this->options['commentFooter'], $placeholders); 
		$liClass = '';
		
		$replies = $this->options['depth'] > 0 ? $this->renderList($comment->id, $depth+1) : ''; 
		
		// CommentHasReplies - if the comment is a parent and has replies
		// Bellow few lines check if the comment has replies and if yes, assign the style for it
		if($replies) $liClass .= ' CommentHasReplies';
		
		// CommentStatusPending - if the comment has been in Pending status and 'admin' => is set to true allowing the listing
		if($comment->status == Comment::statusPending) {
			$liClass .= ' CommentStatusPending';
		
		// CommentStatusSpam - if the comment is set to spam but 'admin' => is set to true allowing the listing
		} else if($comment->status == Comment::statusSpam) {
			$liClass .= ' CommentStatusSpam';
		}
		
		$out  = "\n\t<div class='comment-root'>"; 					//.comment-root
		$out .= "\n\t\t<div class='comment-box'>";						//.comment-box
		$out .=	"\n\t\t\t<div class='comment'>"; 							//.comment
		$out .= "\n\t\t\t\t{$gravatar}";										//.author-thumb [ $gravatar ]
		$out .= "\n\t\t\t\t\t{$header}";										//.comment-inner (inside header) + [ {cite}, {created}, {stars}, {votes} ]					
		$out .= "\n\t\t\t\t\t<div class='text'>{$text}</div>";						// {$text}
		$out .= "\n\t\t\t\t\t </div>";											//.comment-inner
		$out .= "\n\t\t\t</div>"; 											//.comment
		$out .= $footer;
					
				
		// Check if the use of Permalink is allowed in the field admin (Details tab)
		if($this->options['usePermalink']) {
			$permalink = $comment->getPage()->httpUrl;
			$urlSegmentStr = $this->wire('input')->urlSegmentStr;
			if($urlSegmentStr) $permalink .= rtrim($permalink, '/') . $urlSegmentStr . '/';
			
			// Grab the link to the comment and assign it to $permalink variable
			$permalink .= '#Comment' . $comment->id;
			
			// Define the markup for the permalink button/link
			$permalink = "\n\t\t\t\t<a class='permalink-btn' href='$permalink' title='". $this->options['permalinkTitle'] . "'>#" . $comment->id . "</a>";
			
			$out .= $permalink;
		
		} else {
			
			// If Permalink is not allowed, clear the value of $permalink
			$permalink = '';
		}

		// If the nested comments (comment depth) is allowed in the field admin (Details tab)
		// and the depth maximum is not reached yet, show the reply button
		// In the default module, the permalink is included ONLY before the depth limit is reached.
		// If this functionality is needed, grab it from the original module markup 
		// located at: \wire\modules\Fieldtype\FieldtypeComments
		if($this->options['depth'] > 0 && $depth < $this->options['depth']) {
			
			// Define the markup for the reply button
			// !!! WARNING !!! DO NOT REMOVE the CommentActionReply class after adding your markup
			// this class is used by the Java script to show the comment form bellow the post
			// if the class is removed, the form WOULD NOT DROP DOWN!
			// There is no styles defined in comments.css so it won't change the markup appearance!
			$out .= "\n\t\t\t\t<a class='reply-btn CommentActionReply' data-comment-id='$comment->id' href='#Comment{$comment->id}' title='{$this->options['replyTitle']}' >{$this->options['replyLabel']}</a> ";
	
			$out .= '</div>';	//.comment-box	
			
			if($replies) { 

				$out .= $replies; // comment-reply with depth applied
				
			}
			
		} else {
			$out .= "\n\t</div>"; //.comment-box
		}
	
		$out .= "\n\t</div>"; //.comment-root
	
		return $out; 	
	}
	
	public function renderVotes(Comment $comment) {
		
		if(!$this->options['useVotes']) return '';
		
		// Define he markup for the UPvote
		$upvoteFormat = str_replace('{cnt}', "<small class='CommentUpvoteCnt'>$comment->upvotes</small>", $this->options['upvoteFormat']);
		
		// Define the DOWNvote link for insertion in data-url
		$upvoteURL = "{$this->page->url}?comment_success=upvote&amp;comment_id=$comment->id&amp;field_id={$this->field->id}#Comment$comment->id";
		
		// Define the UPvote label for language translation
		$upvoteLabel = $this->_('Like this comment');

		// Define he markup for the DOWNvote
		$downvoteFormat = str_replace('{cnt}', "<small class='CommentDownvoteCnt'>$comment->downvotes</small>", $this->options['downvoteFormat']);
		
		// Define the DOWNvote link for insertion in data-url
		$downvoteURL = "{$this->page->url}?comment_success=downvote&amp;comment_id=$comment->id&amp;field_id={$this->field->id}#Comment$comment->id";
		
		// Define the DOWNvote label for language translation
		$downvoteLabel = $this->_('Dislike this comment');

		// !!! WARNING !!! If you remove the CommentVotes class from the markup,
		// !!! WARNING !!! the votes counter would not change
		// !!! WARNING !!! The class can be added to a wrapper <span> or else, 
		// !!! WARNING !!! but needs to be there in order for the count to refresh properly with no need of page reload!
		
		// !!! WARNING !!! If you remove the CommentActionUpvote or CommentActionDownvote (if downvote is enabled) 
		// !!! WARNING !!! the votes would not get posted and won't show up on the page even after a reload! 
		// note that data-url attribute stores the href (rather than href) so that we can keep crawlers out of auto-following these links
		$out = "<a class='votes-btn CommentVotes CommentActionUpvote' title='$upvoteLabel' data-url='$upvoteURL' href='#Comment$comment->id'>$upvoteFormat</a>";
		
		// Check if the field votes option allow DOWNvoting
		if($this->options['useVotes'] == FieldtypeComments::useVotesAll) {
			
			// Define the markup for the DOWNvote link
			$out .= "<a class='CommentActionDownvote' title='$downvoteLabel' data-url='$downvoteURL' href='#Comment$comment->id'>$downvoteFormat</a>";
		}
		
		return $out; 
	}

	public function renderStars(Comment $comment) {
		if(!$this->options['useStars']) return '';
		if(!$comment->stars) return '';
		$commentStars = new CommentStars();
		return $commentStars->render($comment->stars, false);
	}

	/**
	 * Check for URL-based comment approval actions
	 *
	 * Note that when it finds an actionable approval code, it performs a
	 * redirect back to the same page after completing the action, with
	 * ?comment_success=2 on successful action, or ?comment_success=3 on
	 * error.
	 *
	 * It also populates a session variable 'CommentApprovalMessage' with
	 * a text message of what occurred.
	 *
	 * @param array $options
	 * @return string
	 *
	 */
	public function renderCheckActions(array $options = array()) {
		
		$defaults = array(
			'messageIdAttr' => 'CommentApprovalMessage',
			'messageMarkup' => "<p id='{id}' class='{class}'><strong>{message}</strong></p>", 
			'linkMarkup' => "<a href='{href}'>{label}</a>",
			'successClass' => 'success',
			'errorClass' => 'error',
		);
		
		$options = array_merge($defaults, $options);
		$action = $this->wire('input')->get('comment_success');
		if(empty($action) || $action === "1") return '';

		if($action === '2' || $action === '3') {
			$message = $this->wire('session')->get('CommentApprovalMessage');
			if($message) {
				$this->wire('session')->remove('CommentApprovalMessage');
				$class = $action === '2' ? $options['successClass'] : $options['errorClass'];
				$commentID = (int) $this->wire('input')->get('comment_id');
				$message = $this->wire('sanitizer')->entities($message);
				if($commentID) {
					$link = str_replace(
						array('{href}', '{label}'), 
						array("#Comment$commentID", $commentID), 
						$options['linkMarkup']
					);
					$message = str_replace($commentID, $link, $message);
				}
				return str_replace(
					array('{id}', '{class}', '{message}'), 
					array($options['messageIdAttr'], $class, $message), 
					$options['messageMarkup']
				);
			}
		}

		if(!$this->field) return '';

		require_once(dirname(__FILE__) . '/CommentNotifications.php');
		$no = $this->wire(new CommentNotifications($this->page, $this->field));
		$info = $no->checkActions();
		if($info['valid']) { 
			$url = $this->page->url . '?'; 
			if($info['commentID']) $url .= "comment_id=$info[commentID]&";
			$url .= "comment_success=" . ($info['success'] ? '2' : '3');
			$this->wire('session')->set('CommentApprovalMessage', $info['message']);
			$this->wire('session')->redirect($url . '#' . $options['messageIdAttr']);
		}

		return '';
	}

}

 

Here is the official comment list markup:

Spoiler

<div class="comments-area">

	<div class="sec-title"><h2>2 Comments</h2></div>
	<!--Comment Box-->
	<div class="comment-box">
		<div class="comment">
			<div class="author-thumb"><img src="images/resource/author-2.jpg" alt=""></div>
			<div class="comment-inner">
              <div class="comment-info"><a href="http://pcservices.ca">Sandra Mavic</a></div>
				<div class="post-date">March 03, 2016</div>
				<div class="text">Text for comment 1</div>
				<a href="#" class="reply-btn">Reply</a>
			</div>
		</div>
	</div>
	<!--Comment Box-->
	<div class="comment-box reply-comment">
		<div class="comment">
			<div class="author-thumb"><img src="images/resource/author-2.jpg" alt=""></div>
			<div class="comment-inner">
              <div class="comment-info"><a href="http://pcservices.ca">Sandra Mavic</a></div>
				<div class="post-date">March 03, 2016</div>
				<div class="text">Text for comment 2</div>
				<a href="#" class="reply-btn">Reply</a>
			</div>
		</div>
	</div>

</div>

 

And here is the way I called the comment rendering function in my template:

Spoiler

echo $page->recipe_comments->render(array(
	
	// Comments headline markup with the comments count
	'headline' => "<div class='sec-title'><h2>{$comments}</h2></div>",
	
	// Comments header markup
	// Example: 'Posted by {cite} on {created} {stars}',
	'commentHeader' =>									
		"<div class='comment-inner'>									
			<div class='comment-info'>{cite} {stars}</div>
			<div class='post-date'>{created}</div>",
			
	'commentFooter' => '{votes}',		

	// F - A full textual representation of a month, such as January or March
	// j - Day of the month without leading zeros
	// Y - A full numeric representation of a year, 4 digits
	// G - 24-hour format of an hour without leading zeros
	// i - Minutes with leading zeros
	// Reference: http://it2.php.net/manual/en/function.date.php
	'dateFormat' => 'F j, Y g:i',
	
	// Default comments character encoding
	'encoding' => 'UTF-8',
	
	// shows unapproved comments if true
	'admin' => false, 
	
	// Add a permalink to the comment for easier sharing
	'usePermalink' => true,
	
	'replyLabel' => 'Reply',
	
	'replyTitle' => 'Reply to this comment',

	'permalinkTitle' => 'Grab the link to this comment',
	
	'upvoteFormat' => '<i class="fa fa-thumbs-up"></i> | {cnt}',

));

 

If you have any questions, ask, I will be glad to help if I can depending on the markup. 

Share this post


Link to post
Share on other sites

Hey all,

After being done with the CommentList.php I started checking up the markup of CommentForm.php which would be responsible for the reply form in two spots  - under the parent comment (after clicking on Reply button with a depth > 0 setting in Admin of the comment field) and under the list of comments, I was able to setup the second form which appeared fully matching my original markup. However, I was having issues with the form showing after a Reply button where it looked blured and with a different style than the original one:

Comments-form-issue.jpg.4b87906468a949f148613af852ff6153.jpg

Having noticed that some of the classes in the FieldTypeComment javas are related to the proper functionality of the comments list and forms, is there some classes that I might have omitted that would cause the form to show right under the comment author or it is just a CSS issue that I could deal with on that level?

P.S. For sure I could disable the replies and be over with that issue, however I've spent quite some time to understand better how the module works and how could I simplify my next comment markup so it won't make sense to give up now when I am on the final line, isn't it? 😉

 

Share this post


Link to post
Share on other sites

I've styled the reply form to match the original one under the comment list and it looks great, however the only thing left to achieve is to show the reply form under the comment text but not right under the comment author.

I am just trying to figure out is the reply form called right under the Reply button or there is another mechanisme that is pushing down the form by a used class in the form or else. I've noticed some clases that were mandatory for the votes to be counting properly and working so I guess it would be a similar case with the reply, however am not able to find the class yet or where to look for it. Other than that, I think the result is quite satisfying even as it is:

comments-reply-form.png.f4b4c2d85a2d451bf53b5b1129742420.png

 

Share this post


Link to post
Share on other sites

Alright fellas, I've completed the styling of both comment forms and moved on with the next feature I decided would be great to have - comment sharing. It all started with enabling the Permalink feature and then following the logic that a comment can be shared by users in social medias. Officially the module would show the permalink together with the reply button which would mean that if a comment is a reply, then the Permalink and Reply button would disappear. To break that logic, I moved the Permalink markup out of the Reply check so now it is permanently showing and only the Reply button disappear once the depth set in the admin is achieved.

Once that was solved, I started searching for an easy and nice way to share comments. I thought initially to use tooltip however it causes some inconvenience as it is toggled by default by onMouseOver. Then I decided to try the bootstrap popover and I had great success, however I am unable to force the social sharing window to open in a small popup and it opens as a new page. You can see the sample code I've shared earlier here.

Last night I've decided to try to use a modal window which would be triggered by a click on the comment sharing button. It all came out very nice and looked perfectly, however this morning while I was wrapping and cleaning up the things and commenting my code, I've discovered that $permalink inserted in the modal is not listing the actual comment URL of the one that I pressed but shows the ID of the first comment in the page (always). I've tried to clear the cache of the profile and modules as well as any other things I am aware of, however for some bizarre reason the issue still appears. I had no issues when I tested with the popover, however that did not allow the Facebook and Twitter pages to open in a modal as it should.

Here is the code I used so far:

Spoiler

		// Check if the use of Permalink is allowed in the field admin (Details tab)
		if($this->options['usePermalink']) {
			$permalink = $comment->getPage()->httpUrl;
			$urlSegmentStr = $this->wire('input')->urlSegmentStr;
			if($urlSegmentStr) $permalink .= rtrim($permalink, '/') . $urlSegmentStr . '/';
			
			// Grab the link to the comment and assign it to $permalink variable
			$permalink .= '%23Comment' . $comment->id;
			
			// Define the markup for the permalink button/link
			// data-placement value can be: top, right, bottom or left to change the position of the social buttons popover

			$permalink = 		
				"<div class='modal fade' id='myModal{$comment->id}'>
					<div class='modal-dialog modal-sm modal-dialog-centered'>
						<div class='modal-content'>
							<div class='modal-header'>
								<h4 class='modal-title'>Share the comment</h4>
							</div>
							<div class='modal-body'>
							
								<div id='commentURL' class='well well-sm' id='myInput'>{$permalink}</div>
							
								<a class='btn customer share' href='https://www.facebook.com/sharer.php?u={$permalink}' title='Share in facebook' target='_blank'><span class='fa fa-facebook'></span></a>
							
								<a class='btn customer share' href='https://twitter.com/share?url={$permalink}&amp;text=Shared comment from: {$comment->getPage()->httpUrl}&amp;hashtags=codepen' title='Share in Twitter' target='_blank'><span class='fa fa-twitter'></span></a>
								
								<a class='btn customer share' href='https://twitter.com/share?url=https://codepen.io/patrickkahl&amp;text=Share popup on &amp;hashtags=codepen' title='Send via email' target='_blank'><span class='glyphicon glyphicon-envelope'></span></a>
								
								<a class='btn' onclick='myFunction()' title='Copy the URL' data-clipboard-target='#commentURL'><span class='fa fa-link'></span></a>
																						
							</div>
							<div class='modal-footer'>
								<a data-dismiss='modal'>Close</a>
							</div>
						</div>
					</div>
				</div>

				<a href='' title='Share this comment' data-toggle='modal' data-target='#myModal{$comment->id}'>#{$comment->id}</a>";

 

P.S. The fix of this issue seemed to be quite easy once I gave it a second thought and found out why the modal was ALWAYS showing the same comment ID. As far as every modal from the loop would come with the same ID, the button calling that ID would show the first one in the row. To fix this, I just added the comment ID to the modal ID and the button calling it, so now it all works perfectly fine (the code above is updated with the necessary changes). Now it is only a matter of time to style the modal and use some media queries to show it properly on mobile devices, but that is beyond this topic 😉

 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...