Note that these code snippets are wrapped with {{#if}} statements, so that certain user interface elements are displayed only to sufficiently privileged users. A user that isn't logged in certainly shouldn't be able to post a message.
Now we have a lot of Socket.IO code to add:
{{#if notekey}}
{{#if user}}
<script>
$(document).ready(function () { ... });
{{/if}}
{{/if}}
There's another code section to handle the noteupdate and notedestroy messages. This new section has to do with messages that manage the comments.
We need to handle the form submission for posting a new comment, get the recent messages when first viewing a note, listen for events from the server about new messages or deleted messages, render the messages on the screen, and handle requests to delete a message:
$(document).ready(function () {
io('/view').emit('getnotemessages', '/view-{{notekey}}', function(msgs) {
$('#noteMessages').empty();
if (msgs.length > 0) {
msgs.forEach(function(newmsg) {
$('#noteMessages').append(formatMessage(newmsg));
});
$('#noteMessages').show();
connectMsgDelButton();
} else $('#noteMessages').hide();
});
var connectMsgDelButton = function() {
$('.message-del-button').on('click', function(event) {
$.post('/notes/del-message', {
id: $(this).data("id"),
namespace: $(this).data("namespace")
},
function(response) { });
event.preventDefault();
});
};
var formatMessage = function(newmsg) {
return '<div id="note-message-'+ newmsg.id +'" class="card">'
+'<div class="card-body">'
+'<h5 class="card-title">'+ newmsg.from +'</h5>'
+'<div class="card-text">'+ newmsg.message
+' <small style="display: block">'+ newmsg.timestamp
+'</small></div>'
+' <button type="button" class="btn btn-primary message-
del-button" data-id="'
+ newmsg.id +'" data-namespace="'+ newmsg.namespace +'">'
+'Delete</button>'
+'</div>'
+'</div>';
};
io('/view').on('newmessage', function(newmsg) {
if (newmsg.namespace === '/view-{{notekey}}') {
$('#noteMessages').prepend(formatMessage(newmsg));
connectMsgDelButton();
}
});
io('/view').on('destroymessage', function(data) {
if (data.namespace === '/view-{{notekey}}') {
$('#noteMessages #note-message-'+ data.id).remove();
}
});
$('form#submit-comment').submit(function(event) {
// Abort any pending request
if (request) { request.abort(); }
var $form = $('form#submit-comment');
var $target = $($form.attr('data-target'));
var request = $.ajax({
type: $form.attr('method'),
url: $form.attr('action'),
data: $form.serialize()
});
request.done(function (response, textStatus, jqXHR) { });
request.fail(function (jqXHR, textStatus, errorThrown) {
alert("ERROR "+ jqXHR.responseText);
});
request.always(function () {
// Reenable the inputs
$('#notes-comment-modal').modal('hide');
});
event.preventDefault();
});
});
The code within $('form#submit-comment').submit handles the form submission for the comment form. Because we already have jQuery available, we can use its AJAX support to POST a request to the server without causing a page reload.
Using event.preventDefault, we ensure that the default action does not occur. For the FORM submission, that means the browser page does not reload. What happens is an HTTP POST is sent to /notes/make-comment with a data payload consisting of the values of the form's input elements. Included in those values are three hidden inputs, from, namespace, and key, providing useful identification data.
If you refer to the /notes/make-comment route definition, this calls messagesModel.postMessage to store the message in the database. That function then posts an event, newmessage, which our server-side code forwards to any browser that's connected to the namespace. Shortly after that, a newmessage event will arrive in browsers.
The newmessage event adds a message block, using the formatMessage function. The HTML for the message is prepended to #noteMessages.
When the page is first loaded, we want to retrieve the current messages. This is kicked off with io('/view').emit('getnotemessages', ... This function, as the name implies, sends a getnotemessages message to the server. We showed the implementation of the server-side handler for this message earlier, in routes/notes.mjs.
If you remember, we said that Socket.IO supports the provision of a callback function that is called by the server in response to an event. You simply pass a function as the last parameter to a .emit call. That function is made available at the other end of the communication, to be called when appropriate. To make this clear, we have a callback function on the browser being invoked by server-side code.
In this case, the server-side calls our callback function with a list of messages. The message list arrives in the client-side callback function, which displays them in the #noteMessages area. It uses jQuery DOM manipulation to erase any existing messages, then renders each message into the messages area using the formatMessage function.
The message display template, in formatMessage, is straightforward. It uses a Bootstrap card to give a nice visual effect. And, there is a button for deleting messages.
In formatMessage we created a Delete button for each message. Those buttons need an event handler, and the event handler is set up using the connectMsgDelButton function. In this case, we send an HTTP POST request to /notes/del-message. We again use the jQuery AJAX support to post that HTTP request.
The /notes/del-message route in turn calls messagesModel.destroyMessage to do the deed. That function then emits an event, destroymessage, which gets sent back to the browser. As you see here, the destroymessage event handler causes the corresponding message to be removed using jQuery DOM manipulation. We were careful to add an id= attribute to every message to make removal easy.
Since the flip side of destruction is creation, we need to have the newmessage event handler sitting next to the destroymessage event handler. It also uses jQuery DOM manipulation to insert the new message into the #noteMessages area.