Search

Domino Upgrade

VersionSupport end
5.0
6.0
6.5
7.0
8.0
8.5
Upgrade to 9.x now!
(see the full Lotus lifcyle) To make your upgrade a success use the Upgrade Cheat Sheet.
Contemplating to replace Notes? You have to read this! (also available on Slideshare)

Languages

Other languages on request.

Twitter

Useful Tools

Get Firefox
Use OpenDNS
The support for Windows XP has come to an end . Time to consider an alternative to move on.

About Me

I am the "IBM Collaboration & Productivity Advisor" for IBM Asia Pacific. I'm based in Singapore.
Reach out to me via:
Follow notessensei on Twitter
(posts)
Skype
Sametime
IBM
Facebook
LinkedIn
XING
Amazon Store
Amazon Kindle
NotesSensei's Spreadshirt shop
profile for stwissel on Stack Exchange, a network of free, community-driven Q&A sites

« Ownership flow of customer community created cases | Main| Static APEX code analysis with PMD »

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

Disclaimer

This site is in no way affiliated, endorsed, sanctioned, supported, nor enlightened by Lotus Software nor IBM Corporation. I may be an employee, but the opinions, theories, facts, etc. presented here are my own and are in now way given in any official capacity. In short, these are my words and this is my site, not IBM's - and don't even begin to think otherwise. (Disclaimer shamelessly plugged from Rocky Oliver)
© 2003 - 2017 Stephan H. Wissel - some rights reserved as listed here: Creative Commons License
Unless otherwise labeled by its originating author, the content found on this site is made available under the terms of an Attribution/NonCommercial/ShareAlike Creative Commons License, with the exception that no rights are granted -- since they are not mine to grant -- in any logo, graphic design, trademarks or trade names of any type. Code samples and code downloads on this site are, unless otherwise labeled, made available under an Apache 2.0 license. Other license models are available on written request and written confirmation.