From Blogsphere to a Static Site (Part 5) - Comment front-end
In Part 4 I described the comment backend. This installment sheds a light on the comment front-end.
Comments can be tricky. One lesson I learned early: When your comment form is standard HTML form, it attracts spam like a light bulb attracts moths. So the requirement were:
- The original blog entry should not contain any HTML form. It should be loaded on a button click using JavaScript. Nota bene: this isn't hide/show, but actual manipulation of the DOM
- The dynamic form shall not contain a POST URL, but submission should be in JavaScript - keeps a lot of the scumbags out already
- Submission should be secured with a Captcha
- Some formatting should be allowed. I opted for a Markdown editor with preview capabilities
The first component is the placeholder for the existing comments and the button showing the comment form:
<a name="comments"></a>
{{^commentsclosed}}
<div class="well well-raised" style="text-align : center">
<button class="btn btn-lg btn-info" data-toggle="collapse" data-target="#commentform_{{UNID}}" type="button">
Add your comment... <span class="glyphicon glyphicon-comment"></span>
</button>
</div>
<div id="commentform_{{UNID}}" class="collapse"></div>
{{/commentsclosed}}
<div class="well well-raised">
<h4>Comments</h4>
<ol id="commentList">
{{#comments}}
<li>
{{#gravatarURL}}<img src="{{.}}" class="gravatarimg" /> {{/gravatarURL}} posted by <b>{{author}}</b> on <i>{{createdString}}</i>:
<br /> {{& comment}}
<hr style="clear : both" />
</li> {{/comments}} {{^comments}}
<li id="nocomments">
<h5>No comments yet, be the first to comment</h5>
</li>
{{/comments}}
</ol>
</div>
The second component is the comment form, implemented as mustache template - one of the reasons I picked Mustache: runs on the server and the client in tons of languages
<form title="Comment form for blog discussion" onSubmit="return addComment(this,'{{recaptchaid}}','{{parentId}}')" class="form-vertical well well-raised">
<fieldset>
<legend>Add your comment</legend>
<p>Please note: <b>Comments without a valid and working eMail address will be removed.</b>
<br /> This is my site, so I decide what stays here and what goes.</p>
<div class="control-group" id="commentcontrol">
<label class="control-label" for="Commentor">Name (required, published)</label>
<div class="controls">
<input class="input-xlarge focused" id="Commentor" size="30" accesskey="n" name="nameAuthor" />
</div>
<label class="control-label" for="Email">eMail (required, not published)</label>
<div class="controls">
<input type="eMail" class="input-xlarge focused" id="Email" size="30" accesskey="n" name="txtEmail" placeholder="A working eMail please!" />
</div>
<label class="control-label" for="webSite">URL (optional)</label>
<div class="controls">
<input type="url" class="input-xlarge" id="webSite" size="30" accesskey="n" name="txtWebSite" />
</div>
<div class="controls">
<div id="wmd-panel" class="wmd-panel">
<table style="width : 100%" border="0">
<tr>
<td style="width : 50%; vertical-align : top">
<label class="control-label" for="wmd-input">Your Comment (Use markdown like <a href="//stackoverflow.com/editing-help" target="_blank">Stackoverflow</a>)</label>
<div id="wmd-button-bar"></div>
<textarea class="wmd-input" id="wmd-input" name="Body"></textarea>
</td>
<td style="width : 50%; vertical-align : top">
<label class="control-label">Preview</label>
<div id="wmd-preview" class="wmd-panel wmd-preview"></div>
</td>
</tr>
</table>
</div>
</div>
<div class="controls" id="captchadiv">Captcha here</div>
<div class="form-actions">
<button id="commentsubmit" type="submit" class="btn btn-primary btn-large">Post your comment</button>
</div>
</div>
<div class="alert alert-block" id="alertContainer" style="display : none">One moment please, submitting comment...</div>
</fieldset>
</form>
The whole mechanism gets to work with just a few jQuery JavaScript functions (Vanilla JS would work too, but I had JQuery already for the social buttons, so I reused that) and the respective JS files:
- mustache.js
- blogcomments.js
- Markdown.Converter.js
- Markdown.Sanitizer.js
- Markdown.Editor.j
- //www.google.com/recaptcha/api/js/recaptcha_ajax.js
blogcomments.js
var destinationURL = "/blog/comments";
function getCommentForm(recaptchaid, parentId) {
var params = {};
params.recaptchaid = recaptchaid;
params.parentId = parentId;
var result = {};
$.ajax({
url: '/blog/js2/comment.mustache',
dataType: 'text',
success: function(template) {
result = Mustache.render(template, params);
},
async: false
});
return result;
}
function addComment(form, recaptchaid, parentId) {
$('#commentsubmit').hide();
$('#commentcontrol').hide();
$('#captchadiv').hide();
$("#alertContainer").html("One moment please, submitting comment...")
.show();
$.postJSON({
url: destinationURL,
data: {
Commentor: this.Commentor.value,
eMail: this.Email.value,
webSite: this.webSite.value,
Body: this["wmd-input"].value,
rChallenge: this.recaptcha_challenge_field.value,
rResponse: this.recaptcha_response_field.value,
parentId: parentId
},
success: function(result) {
$("#alertContainer").html(result).addClass("alert-error").delay(
2000).hide(200, function() {
resetComment(recaptchaid, parentId);
});
},
error: function(err) {
$("#alertContainer").html(err.responseText).addClass("alert-error")
.delay(1000).hide(200, function() {
resetComment(recaptchaid, parentId);
});
}
});
return false;
}
function resetComment(recaptchaid, parentId) {
var hasSuccess = $("div.commentsuccess");
if (hasSuccess.length == 0) {
// It didn't work!
$("#alertContainer").show();
$('#commentsubmit').show();
$('#commentcontrol').show();
$('#captchadiv').show();
} else {
renderComment(recaptchaid, parentId);
}
}
function renderComment(recaptchaid, parentId) {
var fid = "#commentform\_" + parentId;
var form = getCommentForm(recaptchaid, parentId);
$(fid).empty().append(form);
if (Recaptcha) {
var theDiv = document.getElementById("captchadiv");
Recaptcha.create(recaptchaid, theDiv, {
tabindex: 1,
theme: "clean"
/\*
_, callback : Recaptcha.focus_response_field
_/
});
}
// Markdown
var converter1 = Markdown.getSanitizingConverter();
var editor1 = new Markdown.Editor(converter1);
editor1.run();
// Comments that are not yet static
$.ajax({
url: destinationURL + "?parentid=" + parentId,
type: 'GET',
async: true,
success: function(data) {
if (data && data.length > 0) {
$("li.dynamicComments").remove();
$("#nocomments").remove();
$("#commentList").append(data);
}
},
error: function(data) {
// Crude error handling
$("#alertContainer").html(data).show();
}
});
}
jQuery.extend({
postJSON: function(params) {
return jQuery.ajax(jQuery.extend(params, {
type: "POST",
data: JSON.stringify(params.data),
dataType: "json",
contentType: "application/json",
processData: false
}));
}
});
As usual: YMMV
Posted by Stephan H Wissel on 20 July 2017 | Comments (1) | categories: Blog