One such requirement was to have the ability to comment and reply on the comments on the news articles. Obviously, there was no out-of-the-box page layout in SharePoint online that gives this feature, so we had to do something custom.
<div class="container">
<div class="form-group col-md-6">
<hr> <label for="comment">Enter Comments:</label> <textarea class="form-control " rows="3" id="comment"></textarea> <a href="" ID="btn_CommentsSave" class="cbs-Line1Link ms-noWrap ms-displayBlock" data-bind="click: createListItem">
<span> <i class="ms-Icon ms-Icon--post"></i> Post Comment</span>
</a></div>
</div>
<div class="container">
<div class="row">
<div class="col-md-8">
<h2 class="page-header"><span data-bind="text: commentsCount"></span> Comments</h2>
<hr>
<section class="comment-list">
<!-- First Comment -->
<div class="row" data-bind="foreach: commentsfromUsers" style="padding: 5px !important">
<div class="col-md-2 col-sm-2 hidden-xs" style="padding: 5px !important">
<figure class="thumbnail"> <img class="img-responsive" data-bind="attr:{src: photo}" />
<figcaption class="text-center" data-bind="text: userName"></figcaption>
</figure>
</div>
<div class="col-md-10 col-sm-10">
<div class="panel panel-default arrow left">
<div class="panel-body" style="border:thin #C0C0C0 solid !important;">
<header class="text-left"> <a><i class="ms-Icon ms-Icon--person" aria-hidden="true"></i></a> <span data-bind="text: userName"> </span> <a><i class="ms-Icon ms-Icon--event" aria-hidden="true"></i></a> <time datetime="16-12-2014 01:05" data-bind="text: Created"></time> </header>
<div class="comment-post" data-bind="text: Comments"></div>
<div class="text-right" align="right"><span data-bind="text: repliesCount"></span> reply(s) <a href="#" data-bind="click: $root.ShowHideItems"><i class="ms-Icon ms-Icon--reply"></i> Reply</a></div>
</div>
</div>
</div>
<div class="row" data-bind="attr:{id: 'divReply'+Id}" style="display:none !important;padding: 5px !important;" align="right">
<div class="col-md-8 col-sm-8 col-md-offset-3 col-sm-offset-0 hidden-xs">
<div class="form-group col-md-8"> <textarea class="form-control " rows="3" data-bind="attr:{id: 'addReply'+Id}"></textarea> <a ID="btn_addReplySave" href="" data-bind="click: $root.createReplyListItem">
<i class="ms-Icon ms-Icon--post"></i> Post Reply
</a></div>
</div>
</div>
<div class="row"></div>
<!-- Second Comment Reply -->
<div data-bind="foreach: replies ">
<div class="row">
<div class="col-md-2 col-sm-2 col-md-offset-1 col-sm-offset-0 hidden-xs">
<figure class="thumbnail"> <img class="img-responsive" data-bind="attr:{src: photo}" />
<figcaption class="text-center" data-bind="text: userName"></figcaption>
</figure>
</div>
<div class="col-md-8 col-sm-8">
<div class="panel panel-default arrow left">
<div class="panel-heading right"> Reply</div>
<div class="panel-body" style="border:thin #C0C0C0 solid !important;">
<header class="text-left"> <a><i class="ms-Icon ms-Icon--person" aria-hidden="true"></i></a> <span data-bind="text: userName"></span> <a><i class="ms-Icon ms-Icon--event" aria-hidden="true"></i></a> <time datetime="16-12-2014 01:05" data-bind="text: Created"> </time> </header>
<div class="comment-post" data-bind="text: reply"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</div> `
The following JavaScript was added to the page layout.
var siteCollectionURL = _spPageContextInfo.webAbsoluteUrl;
var pageurl = window.location.href.replace("%20", "").replace("%27", "").replace(/[&$%‘’']/g, "");
function ShowHideItems() {
document.getElementById('replyToComments').style.display = 'block';
}
var Comment = function(comment, Id, Created, CreatedById, replies) {
this.Comments = comment;
this.Id = Id;
var num = parseInt(Created.replace(/[^0-9]/g, ""));
var date = new Date(num);
this.Created = date.toLocaleString();
this.CreatedById = CreatedById;
this.replies = ko.observableArray(replies);
this.photo = ko.observable();
this.userName = ko.observable();
this.repliesCount = ko.observable(replies.length);
var self = this;
$.ajax({
url: _spPageContextInfo.webServerRelativeUrl + '/_vti_bin/listdata.svc/UserInformationList?$filter=Id%20eq%20' + CreatedById,
method: "GET",
headers: {
"Accept": "application/json; odata=verbose"
},
success: function(data) {
self.photo("https://<TenantURL>/_layouts/15/userphoto.aspx?size=S&accountname=" + data.d.results[0].WorkEmail.toString());
self.userName(data.d.results[0].Name);
},
error: function(data) {
alert('Error' + data.error);
}
});
}
var Reply = function(reply, Id, ParentId, Created, CreatedById) {
this.reply = reply;
this.Id = Id;
this.ParentId = ParentId;
var num = parseInt(Created.replace(/[^0-9]/g, ""));
var date = new Date(num);
this.Created = date.toLocaleString();
this.CreatedById = CreatedById;
this.photo = ko.observable();
this.userName = ko.observable();
var self = this;
$.ajax({
url: _spPageContextInfo.webServerRelativeUrl + '/_vti_bin/listdata.svc/UserInformationList?$filter=Id%20eq%20' + CreatedById,
method: "GET",
headers: {
"Accept": "application/json; odata=verbose"
},
success: function(data) {
self.photo("https://<TenantURL>/_layouts/15/userphoto.aspx?size=S&accountname=" + data.d.results[0].WorkEmail.toString());
self.userName(data.d.results[0].Name);
},
error: function(data) {
alert('Error' + data.error);
}
});
}
function getReplies(ID) {
var commentsurl = siteCollectionURL + '/_vti_bin/listdata.svc/Replies?$filter=%20ParentID%20eq%20%27' + ID + '%27';
var replies = new Array();
$.ajax({
url: siteCollectionURL + '/_vti_bin/listdata.svc/Replies?$filter=%20ParentID%20eq%20%27' + ID + '%27',
method: "GET",
headers: {
"Accept": "application/json; odata=verbose"
},
success: function(data2) {
for (var i = 0; i < data2.d.results.length; i++) {
replies.push(new Reply(data2.d.results[i].Reply, data2.d.results[i].Id, ID, data2.d.results[i].Created, data2.d.results[i].CreatedById));
}
},
error: function(data2) {
replies = null;
},
async: false
});
return replies;
}
function ClearFields() {
document.getElementById('comment').value = "";
}
function getParameterByName(name, url) {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
function CommentReplyViewModel() {
var self = this;
self.userID = ko.observable();
self.commentsfromUsers = ko.observableArray();
self.RepliesfromUsers = ko.observableArray();
self.commentsCount = ko.observable();
self.ShowHideItems = function(data) {
$('#divReply' + data.Id).slideToggle();
}
self.getCommentsListItem = function() {
var commentsurl = siteCollectionURL + '/_vti_bin/listdata.svc/Comments?$filter=%20Title%20eq%20%27' + pageurl + '%27';
$.ajax({
url: siteCollectionURL + '/_vti_bin/listdata.svc/Comments?$filter=%20Title%20eq%20%27' + pageurl + '%27',
method: "GET",
headers: {
"Accept": "application/json; odata=verbose"
},
success: function(data2) {
self.commentsfromUsers.removeAll();
self.commentsCount(data2.d.results.length);
for (var i = 0; i < span data - mce - type = "bookmark"
id = "mce_SELREST_start"
data - mce - style = "overflow:hidden;line-height:0"
style = "overflow:hidden;line-height:0" > < /span><span data-mce-type="bookmark" id="mce_SELREST_start" data-mce-style="overflow:hidden;line-height:0" style="overflow:hidden;line-height:0" ></span > < data2.d.results.length; i++) {
self.commentsfromUsers.push(new Comment(data2.d.results[i].Comments, data2.d.results[i].Id, data2.d.results[i].Created, data2.d.results[i].CreatedById, getReplies(data2.d.results[i].Id)));
}
},
error: function(data2) {
alert('Error' + data.error);
}
});
}
self.createListItem = function() {
var commentsValue = document.getElementById('comment').value;
var url = siteCollectionURL + "/_vti_bin/ListData.svc/Comments";
var Comments = {
Comments: commentsValue,
SiteURL: pageurl,
Title: pageurl
};
var body = JSON.stringify(Comments);
$.ajax({
type: "POST",
url: url,
contentType: 'application/json',
processData: false,
data: body,
success: function() {
alert('Comments Saved Successfully.');
document.getElementById('comment').value = "";
self.getCommentsListItem();
}
});
}
self.createReplyListItem = function(data) {
var RepliesValue = document.getElementById('addReply' + data.Id).value;
var pageurl = window.location.href;
var allReplies;
var url = siteCollectionURL + "/_vti_bin/ListData.svc/Replies";
var Replies = {
Reply: RepliesValue,
ParentID: data.Id.toString(),
Title: pageurl
};
var body = JSON.stringify(Replies);
$.ajax({
type: "POST",
url: url,
contentType: 'application/json',
processData: false,
data: body,
success: function() {
alert('Reply Saved Successfully.');
document.getElementById('addReply' + data.Id).value = "";
$('#divReply' + data.Id).slideToggle();
allReplies = getReplies(data.Id);
data.replies(allReplies);
data.repliesCount(allReplies.length);
},
error: function(error) {
alert(error.error);
},
async: false
});
}
$.ajax({
url: url,
method: "GET",
headers: {
"Accept": "application/json; odata=verbose"
},
success: function(data) {
self.getCommentsListItem();
},
error: function(data) {
alert(data.error);
}
});
}
$(document).ready(function() {
ko.applyBindings(new CommentReplyViewModel());
});
Check-In and publish the page layout as a major version.