I am a novice to intermediate JavaScript programmer. This is some of by best code.
I'm not sure if this community is looking for stuff like this, but I'd be very interested to get some critiques on this JavaScript comment widget I wrote.
I have a couple of mentors, but I can never get enough code review. It seems to improve my code by light years whenever I get feedback from an expert.
So, if you have any thoughts on this code, criticism or commentary, I'd very much appreciate it.
I open soured the code with a demo here: http://www.trailbehind.com/comment_widget/
Here is the complete module pasted as well:
//stores and displays comments, and lets users post comments
function CommentWidget(id, getCommentsURL, commentURL, replyURL, commentEditURL, commentText) {
//the div that contains the comment form and comments
if (id) this.div = document.getElementById(id);
//a url to post comments to
this.commentURL = commentURL?commentURL:'/comments/post_comment/';
//a url to post replies to
this.replyURL = replyURL?replyURL:'/comments/post_reply/';
//a url that returns a JSON list of comments
this.getCommentsURL = getCommentsURL?getCommentsURL:'/comments/get_comments/';
//a url to post comment edits to
this.commentEditURL = commentEditURL?commentEditURL:'/comments/post_edit/';
//the text to display in the comment text box
this.commentText = commentText?commentText:'Your comment goes here.';
//the object being commented on: {'type':string, 'id':integer}
this.target = null;
//a list of comments
this.comments = [];
}
//displays the comment button and comments
CommentWidget.prototype.refresh = function() {
if (typeof this.div == 'undefined') return;
clearDiv(this.div)
this.div.appendChild(this.commentButtonHTML());
if (this.comments.length > 0) {
this.div.appendChild(this.commentsHTML());
}
}
//displays the comment form when the comment button is pressed
CommentWidget.prototype.displayCommentForm = function() {
if (typeof this.div == 'undefined') return;
clearDiv(this.div)
this.div.appendChild(this.commentFormHTML());
if (this.comments.length > 0) {
this.div.appendChild(this.commentsHTML());
}
}
//returns the HTML for the button that reveals the comment form
CommentWidget.prototype.commentButtonHTML = function() {
var input = document.createElement('input');
input.type = "button";
input.value = "Comment";
var self = this;
input.onclick = function () { self.displayCommentForm() };
var p = document.createElement('p');
p.appendChild(input);
return p;
}
//returns the HTML for the commenting form
CommentWidget.prototype.commentFormHTML = function() {
var form = document.createElement('form');
var h1 = document.createElement('h1');
h1.appendChild(dct('Comment on \"' + map.target.name + '\"'));
form.appendChild(h1);
var textarea = document.createElement('textarea');
textarea.onclick = expandCommentBox;
textarea.name = "comment";
textarea.value = this.commentText;
textarea.className = 'commentText'
form.appendChild(textarea);
var input = document.createElement('input');
input.type = 'button';
var self = this
input.onclick = function() { self.postComment(form) };
input.value = "Submit";
form.appendChild(input);
input = document.createElement('input');
input.type = 'button';
input.onclick = function() { self.refresh() };
input.value = "Cancel";
form.appendChild(input);
return form;
}
//returns the HTML for the tree of comments
CommentWidget.prototype.commentsHTML = function() {
//children[0] is a list of comments with no parent
//chidlren[i] is a list of comments whose parent comment id is i
var children = [];
children[0] = [];
for (var i = 0; i < this.comments.length; i++) {
comment = this.comments[i];
//if the comment has no parent, append it to the no parents list
if (comment.parent == null){
children[0].push(comment);
}
//if a list already exists for the parent, append the comment to that list
else if (children[comment.parent]){
children[comment.parent].push(comment);
}
//otherwise, create a list of comments for that parent and append the comment
else{
children[comment.parent] = new Array();
children[comment.parent].push(comment);
}
}
var commentHTML = document.createElement('div');
commentHTML.appendChild(this.createCommentHTML(children[0], children));
return commentHTML
}
//recursive function to create the HTML for the comments
CommentWidget.prototype.createCommentHTML = function(comments, children) {
var ul = document.createElement('ul');
for (var i = 0; i < comments.length; i++){
var comment = comments[i];
var li = document.createElement('li');
li.appendChild(document.createTextNode(comment.comment));
var div = document.createElement('div');
div.style.color = '#696969';
li.appendChild(div);
if (comment.comment != 'comment withdrawn' && comment['author']) {
div.innerHTML = "by ";
var a = document.createElement('a');
a.href = '/user/' + comment.username;
a.innerHTML = comment['author'];
div.appendChild(a);
} else {
div.innerHTML += "by Anonymous";
}
div.innerHTML += " " + comment.time_created + " ago "
var a = document.createElement('a');
a.replyID = comment.id;
a.className = 'jLink';
var self = this;
if (comment.username == user) {
a.onclick = function() { self.revealEditForm(this) };
a.innerHTML = "(edit)";
} else {
a.onclick = function() { self.revealReplyBox(this) };
a.innerHTML = "(reply)";
}
div.appendChild(a);
div.appendChild(document.createElement('br'));
div.appendChild(document.createElement('br'));
ul.appendChild(li);
//if the comment has children, then recurse
if (children[comment.id] != null){
var subul = this.createCommentHTML(children[comment.id], children);
ul.appendChild(subul);
}
}
return ul;
}
CommentWidget.prototype.revealEditForm = function(replyA) {
replyA.onclick = null;
var c = null;
for (var i = 0; i < this.comments.length; i++) {
console.log(this.comments[i].id)
console.log(replyA.replyID)
if (this.comments[i].id == replyA.replyID)
c = this.comments[i];
}
var p = document.createElement('p');
var form = document.createElement('form');
p.appendChild(form);
form.commentId = replyA.replyID;
var textarea = document.createElement('textarea');
textarea.className = 'commentText'
textarea.onclick = function () {
this.className += ' clickedTextBox';
}
textarea.name = 'commentDiv' + replyA.replyID;
textarea.value = c.comment;
form.appendChild(textarea);
var input = document.createElement('input');
input.type = 'button';
var self = this;
input.onclick = function () { self.postEdit(this.parentNode) } ;
input.value = 'Save';
form.appendChild(input);
input = document.createElement('input');
input.type = 'button';
input.onclick = function() { self.refresh() };
input.value = "Cancel";
form.appendChild(input);
var li = replyA.parentNode.parentNode;
li.style.listStyle = 'none';
clearDiv(li)
li.appendChild(p);
}
//reveals a reply form when a reply link is clicked
CommentWidget.prototype.revealReplyBox = function(replyA) {
replyA.onclick = null;
var p = document.createElement('p');
var form = document.createElement('form');
p.appendChild(form);
form.replyId = replyA.replyID;
var textarea = document.createElement('textarea');
textarea.className = 'replyText'
textarea.onclick = expandCommentBox;
textarea.name = 'replyDiv' + replyA.replyID;
textarea.value = "Your reply goes here.";
form.appendChild(textarea);
var input = document.createElement('input');
input.type = 'button';
var self = this;
input.onclick = function () { self.postReply(this.parentNode) } ;
input.value = 'Reply';
form.appendChild(input);
input = document.createElement('input');
input.type = 'button';
input.onclick = function() { self.refresh() };
input.value = "Cancel";
form.appendChild(input);
clearDiv(replyA)
replyA.appendChild(p);
}
//initiates an XHR request
CommentWidget.prototype.asyncRequest = function(method, uri, form) {
var o = createXhrObject()
if(!o) { return null; }
o.open(method, uri, true);
o.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
var self = this;
o.onreadystatechange = function () {self.callback(o)};
if (form) {
o.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
o.send(makePostData(form));
} else {
o.send('');
}
}
//after a comment is posted, this rewrites the comments on the page
CommentWidget.prototype.callback = function(o) {
if (o.readyState != 4) { return }
//turns the JSON string into a JavaScript object.
var response_obj = eval('(' + o.responseText + ')');
console.log(response_obj);
this.comments = response_obj.comments;
this.displayCommentForm();
this.updateCommentLink();
}
//creates a POST strng from a form
function makePostData (form) {
var data = []
for (var i = 0; i < form.elements.length; i++) {
oElement = form.elements[i];
oName = oElement.name;
oName = encodeURIComponent(oName) + '=';
oValue = encodeURIComponent(oElement.value);
data[i] = oName + oValue;
}
return data.join('&');
}
//a cross-browser-safe way to make an XHR request, cribbed from YUI
function createXhrObject() {
var msxml_progid = ['Microsoft.XMLHTTP',
'MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP']
var http;
try {
// Instantiates XMLHttpRequest in non-IE browsers and assigns to http.
http = new XMLHttpRequest();
} catch(e) {
for(var i=0; i<msxml_progid.length; ++i){
try {
// Instantiates XMLHttpRequest for IE and assign to http
http = new ActiveXObject(msxml_progid[i]);
break;
} catch(e2) {}
}
} finally {
return http;
}
}
//expands and clears the intro message from a comment or reply box when it's selected
function expandCommentBox() {
this.className += ' clickedTextBox';
this.value = "";
this.onclick = null;
}
//a function to abbreviate the common task of clearing a div on the page
function clearDiv(div) {
while(div.hasChildNodes()) {
div.removeChild(div.firstChild);
}
}
//update the widget with the new node target and report list
CommentWidget.prototype.updateFromFetch = function(responseObj) {
console.log(responseObj);
this.target = responseObj.target;
this.comments = responseObj.comments;
this.updateCommentLink();
}
//set the clicked node as the target of the widget and fetch comments
CommentWidget.prototype.updateFromNodeClick = function(node) {
this.target = node;
var commentURL = this.getCommentsURL + 'node/' + this.target.id;
this.asyncRequest('GET', commentURL, null);
}
//set the clicked trail as the target of the widget and fetch comments
CommentWidget.prototype.updateFromTrailClick = function(trail) {
this.target = trail;
var commentURL = this.getCommentsURL + 'trail/' + this.target.id;
this.asyncRequest('GET', commentURL, null);
}
CommentWidget.prototype.updateCommentLink = function() {
var a = gel('commentLinkId');
if (!a) return;
rac(a);
if (this.comments.length == 0) {
a.appendChild(dct('Comment'));
} else {
a.appendChild(dct('Comments (' + this.comments.length + ')'));
}
}
//posts the new comment, and then updates the page with the new comment
CommentWidget.prototype.postComment = function(form) {
if (this.target.gps) {
var commentURL = this.commentURL + 'trip/' + this.target.id;
} else if (this.target.attr == 'trail') {
var commentURL = this.commentURL + 'trail/' + this.target.id;
//} else if (this.target.attr == 'user') {
//var commentURL = this.commentURL + 'user/' + this.target.id;
} else {
var commentURL = this.commentURL + 'node/' + this.target.id;
}
var self = this;
var action = function () { self.asyncRequest('POST', commentURL, form) };
checkForLoginThenPost(action);
}
//posts the new reply comment, and then updates the page with the new comment list
CommentWidget.prototype.postReply = function(form) {
var replyURL = this.replyURL + form.replyId;
var self = this;
var action = function () { self.asyncRequest('POST', replyURL, form) };
checkForLoginThenPost(action);
}
//posts the new reply comment, and then updates the page with the new comment list
CommentWidget.prototype.postEdit = function(form) {
var editURL = this.commentEditURL + form.commentId;
this.asyncRequest('POST', editURL, form);
}