jquery-comments

jquery-comments is a jQuery plugin for implementing an out-of-the-box commenting solution to any web application with an existing backend. It provides all the UI functionalities and ties them to callbacks that let you easily define what you want to do with the data. The library is highly customizable and very easy to integrate thanks to a wide variety of settings.

Screenshot of jquery-comments

Features

Demo

http://viima.github.io/jquery-comments/demo/

Quick start

1) Add the following to your HTML file

<link rel="stylesheet" type="text/css" href="css/jquery-comments.css">
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script type="text/javascript" src="js/jquery-comments.js"></script>

2) Initialize the library

$('#comments-container').comments({
  profilePictureURL: 'https://viima-app.s3.amazonaws.com/media/public/defaults/user-icon.png',
  getComments: function(success, error) {
    var commentsArray = [{
      id: 1,
      created: '2015-10-01',
      content: 'Lorem ipsum dolort sit amet',
      fullname: 'Simon Powell',
      upvote_count: 2,
      user_has_upvoted: false
    }];
    success(commentsArray);
  }
});

If you are not using Font Awesome for icons, you should replace the icons with custom images by overriding following options when initializing the library:

spinnerIconURL: '',
noCommentsIconURL: '',
closeIconURL: '',
upvoteIconURL: '',      // Only if upvoting is enabled
replyIconURL: '',       // Only if replying is enabled
uploadIconURL: '',      // Only if attachments are enabled
attachmentIconURL: '',  // Only if attachments are enabled

Example data

The library expects the comment (and attachment) data to be provided with the structure demonstrated below. Please note that you can use fieldMappings to map the field names to match with your API.

{
   "id": 4,
   "parent": 3,
   "created": "2015-01-04",
   "modified": "2015-01-04",
   "content": "Check out this video",
   "attachments": [
      {
         "id": 1,
         "file": "http://www.w3schools.com/html/mov_bbb.mp4",
         "mime_type": "video/mp4",
      }, 
   ],
   "creator": 4,
   "fullname": "Todd Brown",
   "profile_picture_url": "https://viima-app.s3.amazonaws.com/media/public/defaults/user-icon.png",
   "created_by_admin": false,
   "created_by_current_user": false,
   "upvote_count": 0,
   "user_has_upvoted": false,
   "is_new": true
}

Configuration options

Current user
profilePictureURL

An url for profile picture of the current user. Not required if Font Awesome is used.

""
$('#comments-container').comments({
  profilePictureURL: '/user_profiles/user-icon.png'
});
currentUserIsAdmin

A boolean value determing whether the current user is administarator

false
$('#comments-container').comments({
  currentUserIsAdmin: true
});
Images
spinnerIconURL

An url for spinner icon that is shown before comments have been fetched. Not required if Font Awesome is used.

""
$('#comments-container').comments({
  spinnerIconURL: '/img/spinner.gif'
});
upvoteIconURL

An url for upvote icon that is used as a button for upvoting. Not required if Font Awesome is used.

""
$('#comments-container').comments({
  upvoteIconURL: '/img/upvote-icon.png'
});
replyIconURL

An url for reply icon that is shown after the author of the comment if the comment is a reply. Not required if Font Awesome is used.

""
$('#comments-container').comments({
  replyIconURL: '/img/reply-icon.png'
});
uploadIconURL

An url for upload icon that is used as a button for uploading attachments. Not required if Font Awesome is used.

""
$('#comments-container').comments({
  uploadIconURL: '/img/upload-icon.png'
});
attachmentIconURL

An url for attachment icon that is used as a symbol for attachments. Not required if Font Awesome is used.

""
$('#comments-container').comments({
  attachmentIconURL: '/img/attachment-icon.png'
});
closeIconURL

An url for close icon that is used as a button for closing commenting field. Not required if Font Awesome is used.

""
$('#comments-container').comments({
  closeIconURL: '/img/close-icon.png'
});
noCommentsIconURL

An url for no-comments icon that is shown if there are no comments to show. Not required if Font Awesome is used.

""
$('#comments-container').comments({
  noCommentsIconURL: '/img/no-comments-icon.png'
});
Strings
textareaPlaceholderText

A string that is displayed as a placeholder in commenting fields

"Add a comment"
$('#comments-container').comments({
  textareaPlaceholderText: 'Leave a comment'
});
newestText

A string that is displayed on sorting button for newest comments

"Newest"
$('#comments-container').comments({
  newestText: 'New'
});
oldestText

A string that is displayed on sorting button for oldest comments

"Oldest"
$('#comments-container').comments({
  oldestText: 'Old'
});
popularText

A string that is displayed on sorting button for most popular comments

"Popular"
$('#comments-container').comments({
  popularText: 'Most popular'
});
attachmentsText

A string that is displayed on sorting button for attachments

"Attachments"
$('#comments-container').comments({
  attachmentsText: 'Show attachments'
});
sendText

A string that is displayed on send button

"Send"
$('#comments-container').comments({
  sendText: 'Comment'
});
replyText

A string that is displayed on reply button

"Reply"
$('#comments-container').comments({
  replyText: 'Answer'
});
editText

A string that is displayed on edit button

"Edit"
$('#comments-container').comments({
  editText: 'Modify'
});
editedText

A string that is displayed on edited timestamp

"Edited"
$('#comments-container').comments({
  editedText: 'Modified'
});
youText

A string that is displayed as a name of the user for new comments by default

"You"
$('#comments-container').comments({
  youText: 'Me'
});
saveText

A string that is displayed on save button

"Save"
$('#comments-container').comments({
  saveText: 'Update'
});
deleteText

A string that is displayed on delete button

"Delete"
$('#comments-container').comments({
  deleteText: 'Remove'
});
viewAllRepliesText

A string that is displayed to show all replies when there are more replies than defined in maxRepliesVisible option. Variable __replyCount__ is used as a placeholder for the reply count.

"View all __replyCount__ replies"
$('#comments-container').comments({
  viewAllRepliesText: 'Show all replies (__replyCount__)'
});
hideRepliesText

A string that is displayed to hide replies that weren't visible initially

"Hide replies"
$('#comments-container').comments({
  hideRepliesText: 'Hide'
});
noCommentsText

A string that is displayed if there are no comments to show

"No comments"
$('#comments-container').comments({
  noCommentsText: 'There are no comments'
});
noAttachmentsText

A string that is displayed if there are no attachments to show

"No attachments"
$('#comments-container').comments({
  noAttachmentsText: 'There are no attachments'
});
attachmentDropText

A string that is used to inform the user where the attachments can be dropped

"Drop files here"
$('#comments-container').comments({
  attachmentDropText: 'Drop here'
});
Colors
highlightColor

A css value for the highlight color that is used for example to highlight active sorting button and comments by admin

#337AB7
$('#comments-container').comments({
  highlightColor: '#23A6F0'
});
deleteButtonColor

A css value for the color of the delete button

#C9302C
$('#comments-container').comments({
  deleteButtonColor: 'red'
});
Functionalities
enableReplying

A boolean value determing whether replying is enabled

true
$('#comments-container').comments({
  enableReplying: false
});
enableEditing

A boolean value determing whether editing is enabled

true
$('#comments-container').comments({
  enableEditing: false
});
enableUpvoting

A boolean value determing whether upvoting is enabled

true
$('#comments-container').comments({
  enableUpvoting: false
});
enableDeleting

A boolean value determing whether deleting is enabled

true
$('#comments-container').comments({
  enableDeleting: false
});
enableDeletingCommentWithReplies

A boolean value determing whether user is allowed to delete own comment that has replies (replies will be deleted as well)

true
$('#comments-container').comments({
  enableDeletingCommentWithReplies: false
});
enableAttachments

A boolean value determing whether attachments are enabled

false
$('#comments-container').comments({
  enableAttachments: true
});
enableHashtags

A boolean value determing whether hashtags are enabled. Enabling this functionality highlights the hashtags and the hashtagClicked callback function will be executed once the user has clicked a hashtag.

false
$('#comments-container').comments({
  enableHashtags: true
});
enablePinging

A boolean value determing whether pinging users is enabled. Enabling this functionality highlights the pings and the pingClicked callback function will be executed once the user has clicked a ping.


  • The feature is depended on a javascript library called jquery-textcomplete
  • You need to implement the searchUsers callback function which returns a list of all users matching the ping
  • jquery-comments assumes that the pings are sent to the server in format @<user_id>, for example @145
  • jquery-comments assumes that the pings are returned from the server in format @<user_fullname>, for example @Bryan Connery together with a dictionary of pinged users and respective full names
    {
      content: "What do you think @Bryan Connery?",
      pings: {254: "Bryan Connery"},
      ...
    }
    
Switching the ping format is required in order to keep the link between the user model and the ping so that the ping won't get broken in case the user changes her name for example. On the other hand, we expect the server to return the ping in user friendly format so that the API will always return comments in a format that can be displayed without processing.

false
$('#comments-container').comments({
  enablePinging: true
});
enableNavigation

A boolean value determing whether navigation is enabled

true
$('#comments-container').comments({
  enableNavigation: false
});
postCommentOnEnter

A boolean value determing whether comments will be posted by pressing enter

false
$('#comments-container').comments({
  postCommentOnEnter: true
});
forceResponsive

A boolean value determing whether the main navigation elements are presented in a dropdown

false
$('#comments-container').comments({
  forceResponsive: true
});
readOnly

A boolean value determing whether any actions are enabled

false
$('#comments-container').comments({
  readOnly: true
});
Field mappings
fieldMappings

A dictionary that is used to map the fields between jquery-comments and the server. The keys of the dictionary represent field names used within the jquery-comments whereas the values represent the field names from your API. In callback functions the data is remapped to match with your API so you can use the comment data as such.

id                    // Required
parent                // Required if replying is enabled
created               // Required
modified              // Required if editing is enabled
content               // Optional
attachments           // Required if attachments are enabled
pings                 // Required if pinging is enabled
creator               // Required if pinging is enabled
fullname              // Required
profilePictureURL     // Optional
isNew                 // Optional
createdByAdmin        // Optional
createdByCurrentUser  // Required if editing is enabled
upvoteCount           // Required if upvoting is enabled
userHasUpvoted        // Required if upvoting is enabled
fieldMappings: {
  id: 'id',
  parent: 'parent',
  created: 'created',
  modified: 'modified',
  content: 'content',
  attachments: 'attachments',
  pings: 'pings',
  creator: 'creator',
  fullname: 'fullname',
  profilePictureURL: 'profile_picture_url',
  isNew: 'is_new',
  createdByAdmin: 'created_by_admin',
  createdByCurrentUser: 'created_by_current_user',
  upvoteCount: 'upvote_count',
  userHasUpvoted: 'user_has_upvoted'
}
$('#comments-container').comments({
  fieldMappings: {
     parent: 'comment_id',
     modified: 'edited',
     fullname: 'name',
     profilePictureURL: 'user_image',
     upvoteCount: 'upvotes',
  }
});
Callbacks
refresh

A callback function that is called after the comments have been rendered

function() {}
$('#comments-container').comments({
  refresh: function() {
    $('#comments-container').addClass('rendered');
  }
});
getComments

A callback function that is used to fetch the comments array from the server. The callback provides both success and error callbacks which should be called based on the result from the server. The success callback takes the comment array as a parameter.

function(success, error) {
  success([]);
}
$('#comments-container').comments({
  getComments: function(success, error) {
    $.ajax({
      type: 'get',
      url: '/api/comments/',
      success: function(commentsArray) {
        success(commentsArray)
      },
      error: error
    });
  }
});
searchUsers

A callback function that is used for searching users when pinging. The callback provides both success and error callbacks which should be called based on the result from the server. The success callback takes the user array as a parameter.

function(term, success, error) {
  success([]);
}
$('#comments-container').comments({
  searchUsers: function(term, success, error) {
    $.ajax({
      type: 'get',
      url: '/api/users/?search=' + term,
      success: function(userArray) {
        success(userArray)
      },
      error: error
    });
  }
});
postComment

A callback function that is used to create a new comment to the server. The first parameter of the callback is commentJSON that contains the data of the new comment. The callback provides both success and error callbacks which should be called based on the result from the server. The success callback takes the created comment as a parameter.

function(commentJSON, success, error) {
  success(commentJSON);
}
$('#comments-container').comments({
  postComment: function(commentJSON, success, error) {
    $.ajax({
      type: 'post',
      url: '/api/comments/',
      data: commentJSON,
      success: function(comment) {
        success(comment)
      },
      error: error
    });
  }
});
putComment

A callback function that is used to update an existing comment to the server. The first parameter of the callback is commentJSON that contains the data of the updated comment. The callback provides both success and error callbacks which should be called based on the result from the server. The success callback takes the updated comment as a parameter.

function(commentJSON, success, error) {
  success(commentJSON);
}
$('#comments-container').comments({
  putComment: function(commentJSON, success, error) {
    $.ajax({
      type: 'put',
      url: '/api/comments/' + commentJSON.id,
      data: commentJSON,
      success: function(comment) {
        success(comment)
      },
      error: error
    });
  }
});
deleteComment

A callback function that is used to delete a comment from the server. The first parameter of the callback is commentJSON that contains the data of the comment to be deleted. The callback provides both success and error callbacks which should be called based on the result from the server.

function(commentJSON, success, error) {
  success();
}
$('#comments-container').comments({
  deleteComment: function(commentJSON, success, error) {
    $.ajax({
      type: 'delete',
      url: '/api/comments/' + commentJSON.id,
      success: success,
      error: error
    });
  }
});
upvoteComment

A callback function that is used to create or delete an upvote. The first parameter of the callback is commentJSON that contains the data of the upvoted comment. The callback provides both success and error callbacks which should be called based on the result from the server. The success callback takes the updated comment as a parameter.

function(commentJSON, success, error) {
  success(commentJSON);
}
$('#comments-container').comments({
  upvoteComment: function(commentJSON, success, error) {
    var commentURL = '/api/comments/' + commentJSON.id;
    var upvotesURL = commentURL + '/upvotes/';

    if(commentJSON.userHasUpvoted) {
      $.ajax({
        type: 'post',
        url: upvotesURL,
        data: {
          comment: commentJSON.id
        },
        success: function() {
          success(commentJSON)
        },
        error: error
      });
    } else {
      $.ajax({
        type: 'delete',
        url: upvotesURL + upvoteId,
        success: function() {
          success(commentJSON)
        },
        error: error
      });
    }
  }
});
validateAttachments

A callback function that is used to validate attachments prior uploading them to server. The first parameter of the callback is attachments array including all the attachments to be uploaded. The callback function takes an array of validated attachments as a parameter.

Please see Working with attachments for more information.

function(attachments, callback) {
  callback(attachments);
}
$('#comments-container').comments({
  validateAttachments: function(attachments, callback) {
    var validAttachments = [];

    $(attachments).each(function(index, attachment) {
      var maxFileSizeMb = 10;
      var fileSizeMb = attachment.file.size / 1000000;

      if(fileSizeMb <= maxFileSizeMb) {
        validAttachments.push(attachment);
      }
    });

    callback(validAttachments);
  }
});
hashtagClicked

A callback function that is called after user has clicked a hashtag

function(hashtag) {}
$('#comments-container').comments({
  hashtagClicked: function(hashtag) {
    location.hash = 'tags/' + hashtag
  }
});
pingClicked

A callback function that is called after user has clicked a ping

function(ping) {}
$('#comments-container').comments({
  pingClicked: function(ping) {
    location.hash = 'users/' + ping
  }
});
Formatters
textFormatter

A callback function that is called for strings before inserting to DOM. Can be used for localization for instance.

function(text) {
  return text;
}
$('#comments-container').comments({
  textFormatter: function(text) {
    return i18n.translate(text);
  }
});
timeFormatter

A callback function that is called for timestamps before inserting to DOM. Can be used for relative times for instance.

function(time) {
  return new Date(time).toLocaleDateString();
}
$('#comments-container').comments({
  timeFormatter: function(time) {
    return moment(time).fromNow();
  }
});
Miscellaneous
defaultNavigationSortKey

A string value determing the default sorting. Possible values are newest, oldest, popularity and attachments

"newest"
$('#comments-container').comments({
  defaultNavigationSortKey: 'popularity'
});
roundProfilePictures

A boolean value determing whether profile pictures are rounded

false
$('#comments-container').comments({
  roundProfilePictures: true
});
textareaRows

An integer value determing how many rows there are in the commenting fields

2
$('#comments-container').comments({
  textareaRows: 1
});
textareaRowsOnFocus

An integer value determing how many rows there are in the commenting fields on focus

2
$('#comments-container').comments({
  textareaRowsOnFocus: 4
});
textareaMaxRows

An integer or a boolean value determing the maximum amount of rows in commenting fields as they increase when typing. If set to false, commenting fields will increase infinitely.

5
$('#comments-container').comments({
  textareaMaxRows: false
});
maxRepliesVisible

An integer value determing the maximum amount of replies that are visibile intially under a comment. The hidden replies can be shown by clicking the button with a text set in viewAllRepliesText option.

2
$('#comments-container').comments({
  maxRepliesVisible: 3
});

Working with attachments

Saving attachments is a bit different than saving other fields of the comment as attachments cannot be presented in plain text unlike other fields; the attachments are sent to the API in binary format and the API returns the URL of the saved file instead of the original binary data due to performance reasons.

The data should be provided to the API using FormData interface. Also please make sure that your API supports content type multipart/form-data to accept the binary files provided with the FormData. Unfortunately it's not possbile to provide hierarchical data structure with the FormData interface and thus the attachments must be sent to the API using a temporary field. The API must then get the files from that temporary field, save them to a file system and provide the URLs of those files in the attachments field.

In the example below, the attachments are sent to the API in a field named attachments_to_be_created. The API is expected to process the files from that field and provide the URL of the saved attachments in the attachments field of the response. Please see Example data for expected output.

$('#comments-container').comments({
  postComment: function(commentJSON, success, error) {

    // Create form data and append all other fields but attachments
    var formData = new FormData();
    $(Object.keys(commentJSON)).each(function(index, key) {
      if(key != 'attachments') {
        var value = commentJSON[key];
        if(value) formData.append(key, value);
      }
    });

    // Append attachments to be created to the form data
    var attachmentsToBeCreated = commentJSON.attachments.filter(function(attachment){
      return !attachment.id
    });
    $(attachmentsToBeCreated).each(function(index, attachment) {
      formData.append('attachments_to_be_created', attachment.file);
    });

    // Save the comment together with the attachments
    $.ajax({
      type: 'post',
      url: '/api/comments/',
      data: formData,
      contentType: 'multipart/form-data',
      success: function(comment) {
        success(comment);
      },
      error: error
    });
  }
});

Dependencies

Maintainers

Browser support

IE9+ and all modern browsers

Copyright and license

Code and documentation copyright 2020 Viima Solutions Oy. Code released under the MIT license.