CommentWidget is an easy-to-use, platform-independent, cross-browser-compatible, open-source, ajax, threaded comment widget.
- Platform Independent - It doesn't matter what back-end you use - CommentWidget just need a few URLs that return information in JSON.
- Cross-Browser Compatible - Tested in IE6/7, FF2/3, and Safari 3.
- Ajax - Let people comment without reloading the page.
- Threaded - Comments can be discussions.
- Open Source - Free, open, and extensible, forever.
The caveat is you need to set up your particular server to store and use comments. Below, you can see the code for a Django/Python project configured to use CommentWidget. However, CommentWidget really doesn't care what kind of database, web framework, or other back-end macinery you use. It just needs a place to store comments and three URLs where it can communicate with your server and get JSON responses.
In our example, we have a model to store comments, and a model to store webpages, which have threads of comments. We just have comments about webpages in this example, but you can see an example with comments about multiple things.class Comment(models.Model): comment = models.TextField(blank=True) author = models.ForeignKey("User", null=True) time_created = models.DateTimeField(default=datetime.now) parent = models.ForeignKey('self', related_name="parent", null=True, blank=True) #generic relation fields object_id = models.PositiveIntegerField(db_index=True) content_type = models.ForeignKey(ContentType) content_object = generic.GenericForeignKey()class Webpage(models.Model): url = models.URLField(null=True) comments = generic.GenericRelation(Comment, null=True, blank=True)
Along with the models, we specify URLs for 1) posting a comment about a webpage, 2) getting comments about a webpage, and 3) posting a reply to a comment.
from django.conf.urls.defaults import * from thetrailbehind.apps.comments import views urlpatterns = patterns('', (r'^get_comments/webpage/(?P[^/]+)$', views.get_webpage_comments), (r'^post_comment/webpage/(?P[^/]+)$', views.post_webpage_comment), (r'^post_reply/(?P[^/]+)$', views.post_reply), )
In each of the URLs, the server always returns a JSON list of comments that looks like:
{"comments": [{"comment": string, "id":int,"parent":int, "author": string, "time_created": string}, ...]}
Here are the views that underly the URLs.
from django.util import simplejson from django.http import HttpResponse from thetrailbehind.apps.site.models import * def post_webpage_comment(request, id): '''post a comment about a webpage''' webpage = Webpage.objects.get(id=id) myComment = request.POST.get("comment") return save_comment_to_db(request, myComment, None, webpage) def post_reply(request, comment_id): '''This saves a Comment when the user enters a reply to a comment, similar to PostComment()''' reply_div = "replyDiv" + str(comment_id) myComment = request.POST.get(reply_div) reply_comment = Comment.objects.get(id=comment_id) object = reply_comment.content_object return save_comment_to_db(request, myComment, reply_comment, object) def save_comment_to_db(request, myComment, parent, comment_object): '''saves a comment to the database and returns a list of comments about comment_object''' newComment = Comment() if parent: newComment.parent = parent newComment.content_object = comment_object newComment.comment = myComment if str(request.user) != 'AnonymousUser': newComment.author = UserProfile.objects.get(user=request.user) newComment.save() return get_comments(request, comment_object) def get_webpage_comments(request, id): '''returns a JSON list of comments about a webpage''' webpage = Webpage.objects.get(id=id) return get_comments(request, webpage) def get_comments(request, object): '''returns a JSON dictionary of comments about the object''' comments = object.comments.all().order_by('-id') comments = make_json_comments(comments) response_dict = {'success':True, 'comments':comments} return HttpResponse(simplejson.dumps(response_dict), mimetype='application/javascript') def make_json_comments(comments): '''turn a queryset of comments into a JSON list''' json_comments = [] for comment in comments: new_comment = {} new_comment['id'] = comment.id new_comment['comment'] = comment.comment try: new_comment['parent'] = comment.parent.id except: new_comment['parent'] = None new_comment['author'] = str(comment.author) json_comments.append(new_comment); return json_commentsYou probably want to include comments.js and comments.css at the end of your HTML template, so they will load after the rest of your page. Just do the includes before you close the HTML tag. Or you can load the files from our servers, and get updates automatically when we release them:
<link rel="stylesheet" type="text/css" href="path/to/comments.css"> <script type="text/javascript" src="/path/to/comments.js"></script>
<link rel="stylesheet" type="text/css" href="http://www.trailbehind.com/site_media/css/comments.css"> <script type="text/javascript" src="http://www.trailbehind.com/site_media/javascript/comments.js"></script>Creating a CommentWidget only requires one parameter - the id of the div on the page where the comment widget should be rendered. Optionally, you can also pass parameters to specify different base URLs than the defaults.
var commentWidget = new CommentWidget('comments')
Then, call commentWidget.select to specify target for the widget. The select function sends an ajax request to /comments/get_comments/webpage/1, and gets back a list of comments in JSON format.
commentWidget.select('webpage', 1); //sends an ajax request to /comments/get_comments/webpage/1
CommentWidget also communicates with the server when there is a comment or reply posted. Assuming that a 'webpage' with id '1' was selected as above, when a comment is posted, the widget sends a request to:
/comments/post_comment/webpage/1
The server-side of this URL should first save the comment and author from the request(see the above Django views), and then it should return a JSON list of comments.
Similarly, when a reply is posted, the widget sends a request to:
/comments/post_reply/{some_id}
The server-side of this URL first saves the reply (see the Django views above). Then it return a JSON list of comments as usual.
{"comments": [{"comment": string, "id":int,"parent":int, "author": string, "time_created": string}, ...]}Many sites have multiple things to be commented on. For example, you might want there to be comments attached to blogposts and webpages. That's why the select function of CommentWidget takes a string.
commentWidget.select('webpage', 1); //sends an ajax request to /comments/get_comments/webpage/1
Here's how you might set up the back-end of a Django site for comments on webpages AND blogposts:
class Comment(models.Model): comment = models.TextField(blank=True) author = models.ForeignKey("User", null=True) time_created = models.DateTimeField(default=datetime.now) parent = models.ForeignKey('self', related_name="parent", null=True, blank=True) #generic relation fields object_id = models.PositiveIntegerField(db_index=True) content_type = models.ForeignKey(ContentType) content_object = generic.GenericForeignKey()class Webpage(models.Model): url = models.URLField(null=True) comments = generic.GenericRelation(Comment, null=True, blank=True)
class Blogpost(models.Model): text = models.TextField(blank=True) comments = generic.GenericRelation(Comment, null=True, blank=True)
from django.conf.urls.defaults import * from thetrailbehind.apps.comments import views urlpatterns = patterns('', (r'^get_comments/webpage/(?P[^/]+)$', views.get_webpage_comments), (r'^get_comments/blogpost/(?P[^/]+)$', views.get_blogpost_comments), (r'^post_comment/webpage/(?P[^/]+)$', views.post_webpage_comment), (r'^post_comment/blogpost/(?P[^/]+)$', views.post_blogpost_comment), (r'^post_reply/(?P[^/]+)$', views.post_reply), )
from django.util import simplejson from django.http import HttpResponse from thetrailbehind.apps.site.models import * def post_webpage_comment(request, id): '''post a comment about a webpage''' webpage = Webpage.objects.get(id=id) myComment = request.POST.get("comment") return save_comment_to_db(request, myComment, None, webpage) def post_blogpost_comment(request, id): '''post a comment about a blogpost''' blogpost = Blogpost.objects.get(id=id) myComment = request.POST.get("comment") return save_comment_to_db(request, myComment, None, blogpost) def post_reply(request, comment_id): '''This saves a Comment when the user enters a reply to a comment, similar to PostComment()''' reply_div = "replyDiv" + str(comment_id) myComment = request.POST.get(reply_div) reply_comment = Comment.objects.get(id=comment_id) object = reply_comment.content_object return save_comment_to_db(request, myComment, reply_comment, object) def save_comment_to_db(request, myComment, parent, comment_object): '''saves a comment to the database and returns a list of comments about comment_object''' newComment = Comment() if parent: newComment.parent = parent newComment.content_object = comment_object newComment.comment = myComment if str(request.user) != 'AnonymousUser': newComment.author = UserProfile.objects.get(user=request.user) newComment.save() return get_comments(request, comment_object) def get_webpage_comments(request, id): '''returns a JSON list of comments about a webpage''' webpage = Webpage.objects.get(id=id) return get_comments(request, webpage) def get_blogpost_comments(request, id): '''returns a JSON list of comments about a webpage''' blogpost = Blogpost.objects.get(id=id) return get_comments(request, blogpost) def get_comments(request, object): '''returns a JSON dictionary of comments about the object''' comments = object.comments.all().order_by('-id') comments = make_json_comments(comments) response_dict = {'success':True, 'comments':comments} return HttpResponse(simplejson.dumps(response_dict), mimetype='application/javascript') def make_json_comments(comments): '''turn a queryset of comments into a JSON list''' json_comments = [] for comment in comments: new_comment = {} new_comment['id'] = comment.id new_comment['comment'] = comment.comment try: new_comment['parent'] = comment.parent.id except: new_comment['parent'] = None new_comment['author'] = str(comment.author) json_comments.append(new_comment); return json_commentsBy default, CommentWidget sends requests to the server at the following URLs:
/comments/get_comments/{some_string}/{some_int} /comments/post_comment/{some_string}/{some_int} /comments/post_reply
You can change the base URLs of these requests when you instantiate the CommentWidget:
var commentWidget = CommentWidget('foo', '/my/get/path', 'my/post/path', 'my/reply/path');
Then, CommentWidget will send requests as follows:
/my/get/path/{some_string}/{some_int} /my/post/path/{some_string}/{some_int} /my/reply/path/post_replyBy default, CommentWidget use the following text for the default text in the text form:
Your comment goes here.
You can change this when you instantiate the CommentWidget:
var commentWidget = CommentWidget('foo', false, false, false, 'Your custom text.');By default, comments are posted anonymously and show up as 'by AnonymousUser.' However, if a user is logged in, you'll probably want to store and display that user as the author of the comment. Here's how we do it in the example code:
def save_comment_to_db(request, myComment, parent, comment_object): '''saves a comment to the database and returns a list of comments about comment_object''' newComment = Comment() if parent: newComment.parent = parent newComment.content_object = comment_object newComment.comment = myComment if str(request.user) != 'AnonymousUser': newComment.author = UserProfile.objects.get(user=request.user) newComment.save() return get_comments(request, comment_object)
TO DO: Add an option to CommentWidget to use a Name form field and a captcha, instead of requiring website designers to integrate with their login system.
The CommentWidget comes with a very basic CSS. You can skin the widget however you want by overriding the CSS in your HTML template:.clickedTextBox { height: 60px; } .commentText { width: 96%; margin-top: 10px; margin-bottom: 10px; } .replyText { margin-bottom: 5px; width: 96%; } a.jLink { color: #27408B; cursor: pointer; text-decoration: underline; }
- Make a sample Django project to demo CommentWidget.
- Add voting/moderation.
- Add integrated login widget.
- Make comments sortable by date, votes, etc.
- More detailed API documentation.
- Make default reply text a parameter.
- Option to use name form and captcha instead of requiring login integration.
- Add a README file to the zip.
- Do better minimization of comments-min.js.
- Release a YUI-integrated version.