Meerkat | A fully-featured comment platform for your Statamic site.

Allowing Visitors to Reply to Each Other

Most sites provide the ability for visitors to reply to one-another's comments. Meerkat supports this out of the box, but might be one of the most involved aspects of integrating Meerkat in your site's theme.

When Meerkat generates the opening tags of the comment form, it adds the [data-meerkat-form="comment-form"] attribute to the form. This tells Meerkat "Hey, this is the form that is used to post comments!"; this is the form that will be used inline when a visitor clicks the "Reply To" link below a comment on your site.

Adding the Necessary JavaScript (JabbaScripts)

While you could technically roll your own JavaScript to allow visitors to reply to other comments on your site, it is definitely easier to harness Meerkat's "reply to" JavaScript file. To add this file to your themes, simply use the following tag to your default layout (presumably after all of the other JavaScript files):

{{ meerkat:replies-to }}

This will create a <script> tag referencing Meerkat's replies-to.js JavaScript file.

Note: If you have installed Meerkat below the web root, you will need to add your own JavaScript file for this since the {{ meerkat:replies-to }} tag was designed for site's deployed using the default Statamic configuration.

In order for visitor's to reply to other visitor's comments, you need to include a "Reply To" link on your site. The exact wording, styling, etc. does not matter. The only thing that matters is that you include the following markup on the "Reply To" link (Note: only the meerkat-* data attributes are required, apart from that, go wild!):

<a href="#" data-meerkat-form="reply" data-meerkat-reply-to="{{ id }}">Reply</a>

That should be all you have to do to get Meerkat to enable an awesome "reply to" form for your site's visitors. This method will simply create a clone of your site's main comment form, and display it below the comment you would like to reply to. While this is the fastest way to get you up and running, you most likely want a little more freedom in how Meerkat displays the "reply to" form; don't worry - Meerkat has got you covered.

First, however, let's discuss how Meerkat decides which reply to form to display.

How Meerkat Decides What Form to Display

Meerkat, by default, will display whatever form has been generated by the {{ meerkat:create }} tag (pro tip: the form generated by this tag has the data attribute data-meerkat-form="comment-form"). This form has the data attribute data-meerkat-form="comment-form" attached to it, and this is how Meerkat knows to use that form. However, you can create a custom "reply to" form by creating a form with the data attribute data-meerkat-form="comment-reply-form" data attribute.

If Meerkat detects that an element exists on the page with the data-meerkat-form="comment-reply-form data attribute, it will use that as the "reply to" form that is displayed to your site's visitors whenever they click a "Reply To" link.

As an example, the following example demonstrates how you might create a custom "reply to" form:

<div style="display:none;">
    {{ meerkat:create attr="data-meerkat-form:comment-reply-form" }}
        <p>Implement the rest of your form here.</p>
    {{ /meerkat:create }}
</div>

In the example above, the "reply to" form was wrapped in <div /> that with the style display:none;. This was used to prevent the form from displaying on the page until a visitor clicks on the "reply to" link below a comment. Feel free to implement this however you'd like, just make sure the data-meerkat-form="comment-reply-form" data attribute is applied to the <form> element.

It is important that the data-meerkat-form="comment-reply-form" attribute is added to the <form> element because Meerkat will dynamically add hidden input elements that tell the underlying systems which comment the visitor is replying to.

Using a Different Form for Replies

You can use a form that is different from your main comment form by simply creating a second form with the data attribute data-meerkat-form="comment-reply-form". When Meerkat detects that a form exists with this data attribute, it will be used instead of the main commdent form.

We can create this form with the following example markup:

{{ meerkat:create attr="data-meerkat-form:comment-reply-form" }}

{{ /meerkat:create }}

Notice that we are defining the new data attribute as an argument to the attr parameter.

Extending via JavaScript

You can hook into Meerkat's reply-to functionality by modifying the MeerkatReply object. This object is simply a wrapper object that contains a number of functions that get called at various times during the comment reply process. It is important that any customized JavaScript code be loaded after Meerkat's {{ meekrat:replies-to }} tag.

Properties

To make things as simple as possible, Meerkat provides you with some properties that you can use in your own form implementation:

Endpoints

The Endpoints static object contains the API endpoints that you can submit to from your site. The endpoints are:

Endpoint Description
SubmitComment The API endpoint to POST comment replies to.

You might use these like so (the following example utilizes jQuery):

MeerkatReply.submit = function (evt) {
    // Collect the data.
    var data = {};

    $.post(MeerkatReply.Endpoints.SubmitComment, data, function () {
        // Implement the handler.
    });

    evt.preventDefault();
};

Settings

The MeerkatReply object contains a few settings that can be used to alter the behavior of the comment reply process.

closeOnCancel

By default, Meerkat will automatically remove the comment reply form when a user clicks on the "Cancel Reply" button:

// Keep the reply form open.
MeerkatReply.closeOnCancel = false;

Helper Methods

The MeerkatReply helper methods exist to provide a little help for common tasks when building out the dynamic comment reply features on your site.

getOpenReplyForm

This method will get the currently open "reply to" form on your page:

MeerkatReply.submit = function (evt) {
    var currentForm = MeerkatReply.getOpenReplyForm();

    evt.preventDefault();
};

Event Methods

submit

The sumbit() method is called when a visitor clicks on the "Submit Reply" button. This method is wired up to the form's submit event, so you can do anything you'd like here:

MeerkatReply.submit = function (evt) {
    // Handle the form.
    console.log('The visitor clicked sumbit!');

    // Prevent the default submit behavior.
    evt.preventDefault();
};

canceled

The canceled() method is called after a visitor has clicked the "Cancel Reply" link on your site. Meerkat will supply the ID of the comment that the visitor was replying to as well as an instance of the comment form:

MeerkatReply.canceled = function (commentId, form) {
    console.log('The visitor canceled their reply to: ' + commentId);

    // If you set `closeOnCancel` to `false`, we can do our own thing here.
    $(form).fadeOut();
};

replyOpen

The replyOpen method is called when the reply form is being presented to the user. If you previosuly hid the form for some reason, or would like to animate the appearance, you can do that here. You will receive an instance of the reply form as the only argument:

MeerkatReply.replyOpen = function(form) {
    // Show the form and fade in.
    $(form).show().fadeIn();
};

Submitting Comments via AJAX

When customizing your comment form with the JavaScript API and helper methods, you might want to submit the comments to the Meerkat API via AJAX. In doing so, you can create incredibly interactive experiences for your site's visitors.

Before diving into the rest of the docs on this page, it is important to understand that the implementation details below are very opinionated; they are not the definitive way to implement Meerkat, or any AJAX based system for that matter. These docs will get you up and running, but don't feel like this is the "one true source"; feel free to innovate and experiment. If you come up with something amazing, feel free to share it!

Markup Used

The following examples will use the template markup for this site. The choice was also made to use a different form for the comment replies, as we will have greater control over where the validation errors are displayed later.

The template code for the "reply" form is as follows:

<div style="display:none;">
    {{ meerkat:create attr="data-meerkat-form:comment-reply-form|class:meerkat__reply--form" }}
    <div class="row">
        <div class="col-sm-12">
            <div class="form-group">
                <div class="col-xs-6">
                    <label for="name">Name</label>
                    <input class="form-control input-lg" type="text" id="name"
                            name="name" placeholder="Please enter your name. This will be public."
                            value="{{ old:name }}">
                    <p class="text-danger" data-validation-error></p>
                </div>
                <div class="col-xs-6">
                    <label for="email">Email</label>
                    <input class="form-control input-lg" type="email" id="email"
                            name="email" placeholder="Please enter your email. This remains private."
                            value="{{ old:email }}">
                    <p class="text-danger" data-validation-error></p>                    
                </div>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-sm-12">
            <div class="form-group">
                <div class="col-xs-12">
                    <label for="comment">Comment</label>
                    <textarea class="form-control input-lg" id="comment" name="comment" rows="5"
                                placeholder="Enter your comment or feedback here (minimum of five characters)">{{ old:comment }}</textarea>
                    <p class="text-danger" data-validation-error></p>                    
                </div>
            </div>
        </div>
    </div>
    <div class="form-group">
        <div class="col-xs-12">
            <button class="btn btn-primary" type="submit"><i class="fa fa-comment-o"></i> Submit Reply</button>
            <a href="#" data-meerkat-form="cancel-reply">Cancel Reply</a>
        </div>
    </div>
    {{ /meerkat:create }}
</div>

The reply form was wrapped in a hidden <div /> so it wouldn't be visibile to visitors unless they clicked the reply button on a comment. Also, the decision was made to add the following <p /> after each input element:

<p class="text-danger" data-validation-error></p>

We will use these later when displaying validation error messages to the visitor.

Where to Make AJAX Calls

When integrating AJAX in your Meerkat comment's section, you typically will make AJAX calls from the MeerkatReply.submit method. The following examples will use jQuery, but you can use whatever methods you like.

The general steps are as following:

  1. We need to retrieve the comment data to submit to the server;
  2. Make the AJAX calls to the server;
  3. Retrieve the results from the server;
  4. Update the UI to indicate success or failure;
  5. Add the new comment to the page, if desired.
MeerkatReply.submit = function(evt) {
    // Prevent the default submit.
    evt.preventDefault();

    // Get a reference to the current form. We could
    // use the `MeerkatReply.getOpenReplyForm();`
    // helper method, but since we are using
    // jQuery for this example, $(this)
    // will also work to get the form
    var replyForm = $(this);

    // Get the form data. We will iterate the serialized
    // form data; then just create a new data object.
    var data = {};
    var commentData = $(this).serializeArray().map(function (x) {
        data[x.name] = x.value;
    });

    // Perform the AJAX call.
    $.post(MeerkatReply.Endpoints.SubmitComment, data, function (e) {
        // Check if the comment post was successfull.
        if (e.success) {
            // Great, everything worked. Let's hide the form.
            // We will also use the fade out animation so
            // the change isn't jarring to the visitor.
            $(replyForm).fadeOut(150, function () {
                replyForm.remove();
            });

            // The `submission` property contains the new comment.
            var comment = e.submission;

            // Do anything you'd like here.
        } else {
            // Something went wrong here; not related to data validation.
        }
    }).fail(function (xhr, status, error) {
        // We will handle errors later.
    });
};

Great! We now have a nice place to make AJAX calls to the Meerkat server API. However, we still need to handle any errors that might come across.

Handling Errors

Meerkat will enforce the validation rules set on your comment form's formset, even when making AJAX calls to the server. When there are validation errors, we can catch these via the fail() jQuery method. The validation errors are returned by the server in a field named errors.

The errors data will consist of a multi-dimensional array, where the key is the name of the input field and the value is another array consisting of all the validation error messages.

The following example demonstrates how we might retrieve these error messages:

MeerkatReply.submit = function(evt) {
    // Prevent the default submit.
    evt.preventDefault();

    // Get a reference to the current form. We could
    // use the `MeerkatReply.getOpenReplyForm();`
    // helper method, but since we are using
    // jQuery for this example, $(this)
    // will also work to get the form
    var replyForm = $(this);

    // Get the form data. We will iterate the serialized
    // form data; then just create a new data object.
    var data = {};
    var commentData = $(this).serializeArray().map(function (x) {
        data[x.name] = x.value;
    });

    // Perform the AJAX call.
    $.post(MeerkatReply.Endpoints.SubmitComment, data, function (e) {
        // Check if the comment post was successfull.
        if (e.success) {
            // Great, everything worked. Let's hide the form.
            // We will also use the fade out animation so
            // the change isn't jarring to the visitor.
            $(replyForm).fadeOut(150, function () {
                replyForm.remove();
            });

            // The `submission` property contains the new comment.
            var comment = e.submission;

            // Do anything you'd like here.
        } else {
            // Something went wrong here; not related to data validation.
        }
    }).fail(function (xhr, status, error) {
        // Get the submission errors.
        var errors = xhr.responseJSON.errors;
    });
};

The validation errors will now be available via the errors variable. Pretty neat! But, how should we display these errors to the visitor? The method to do this will change depending on your theme's markup, the CSS framework used and personal preference.

Having said that, the following would work using the Bootstrap framework (and the example markup seen earlier):

MeerkatReply.submit = function (evt) {
    evt.preventDefault();
    var data = {};
    var commentData = $(this).serializeArray().map(function (x) {
        data[x.name] = x.value;
    });

        // Let's get a reference to our reply form. We need to get
        // the last form returned by this selector since there
        // is a hidden comment reply form hanging around :)
    var replyForm = $(this);

    $.post(MeerkatReply.Endpoints.SubmitComment, data, function (e) {
        // Check if everything was successfull.
        if (e.success) {
            // Great, everything worked. Let's close the form.
            $(replyForm).fadeOut(150, function () {
                replyForm.remove();
            });

            // Get the new comment.
            var template = $.tmpl($('#comment-template').html(), e.submission);

            // Add our rendered comment to the DOM.
            $('[data-meerkat-comment="' + e.submission.parent_comment_id + '"].media-body').append(template).fadeIn();
            $('[data-meerkat-form="comment-form"]').show();                    
        } else {
            // Something went wrong.
        }
    }).fail(function (xhr, status, error) {
        // Get the submission errors.
        var errors = xhr.responseJSON.errors;

        // Let's iterate the errors, and update the UI.
        $.each(errors, function (key, value) {
            // 1. Target the reply form and the input element.
            // 2. Modify the DOM as required.

            var inputElement = $(replyForm).find(':input[name="' + key + '"]');

            if (typeof inputElement !== 'undefined' && inputElement !== null && inputElement.length > 0) {
                inputElement = inputElement[0];

                // Find the closest form group and add the `has-error` class to it.
                $(inputElement).closest('.form-group').addClass('has-error');

                // Update the <p /> element below any input element that has validation errors.
                $(inputElement).closest('.form-group').find('[data-validation-error]').text(value[0]);
            }

        });
    });
};

While it might look complicated, we are simply iterating each of the error messages returned by the server and looking for any inputs that match in our reply form. If we find a message, we add the has-error class to it's parent element that has the form-group class; after that we update the error message that is displayed to the visitor.

Subscribe to our newsletter