wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

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 on 20 July 2017 | Comments (0) | categories: Blog

Comments

  1. No comments yet, be the first to comment