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 2017-07-20 10:58 | Comments (0) | categories: Blog

Comments

  1. No comments yet, be the first to comment