UNPKG

thywill

Version:

A Node.js clustered framework for single page web applications based on asynchronous messaging.

334 lines (301 loc) 10.4 kB
/*global document: false, Handlebars: false, Thywill: false */ /** * @fileOverview * Client Javascript for the Chat application. */ (function () { 'use strict'; // ------------------------------------------ // Define a Chat application class. // ------------------------------------------ /** * @class * An implementation of Thywill.ApplicationInterface for the Chat * application. * * @see Thywill.ApplicationInterface */ function ChatApplication (applicationId) { Thywill.ApplicationInterface.call(this, applicationId); // For storing Handlebars.js templates. this.templates = {}; // Set to true when currently assigned a chat partner. this.chatting = false; } Thywill.inherits(ChatApplication, Thywill.ApplicationInterface); var p = ChatApplication.prototype; // ------------------------------------------ // User Interface Methods // ------------------------------------------ /** * Create the application user interface and its event listeners. */ p.uiSetup = function () { var self = this; // Populate the DOM from the template. this.templates.uiTemplate = Handlebars.compile(jQuery('#{{{uiTemplateId}}}').html()); this.templates.messageTemplate = Handlebars.compile(jQuery('#{{{messageTemplateId}}}').html()); this.templates.disconnectMessageTemplate = Handlebars.compile(jQuery('#{{{disconnectMessageTemplateId}}}').html()); this.templates.reconnectMessageTemplate = Handlebars.compile(jQuery('#{{{reconnectMessageTemplateId}}}').html()); this.templates.channelTemplate = Handlebars.compile(jQuery('#{{{channelTemplateId}}}').html()); jQuery('body').append(this.templates.uiTemplate({ kickButtonText: 'Kick', kickText: 'Chat partner kicked.', kickedText: 'You have been kicked!', newPartnerButtonText: 'New Chat', sendButtonText: 'Send', title: 'Thywill: Chat Application', waitingText: 'Waiting for a chat partner...' })); // Attach the necessary functionality to the new chat buttons shown after // kicking or being kicked. The button aren't disabled at any point, just // hidden. jQuery('button.new-partner').click(function () { jQuery(this).parent().fadeOut('fast', function () { jQuery('#waiting').fadeIn('fast', function () { // Wait to send, otherwise we can get odd collisions between fade // out and fade in of #waiting if the server response is fast enough. self.send({ action: 'findPartner' }); }); }); }); }; /** * Disable the chat UI and set it to the waiting for chat partner * state. * * @param {string} closeCircumstance * 'kicked' if the close happens because this client was kicked by the * chat parter, or 'kick' if this client did the kicking. */ p.closeChatUi = function (closeCircumstance) { // Convenience function for showing the right banner message. function showMessage () { if (closeCircumstance === 'kick') { jQuery('#kick').fadeIn('fast'); } else if (closeCircumstance === 'kicked') { jQuery('#kicked').fadeIn('fast'); } else { jQuery('#waiting').fadeIn('fast'); } } this.chatting = false; // Turn off the buttons. jQuery('.send-button').off('click'); // Set the chat display back to the waiting state. jQuery('#chat-wrapper').removeClass('enabled'); jQuery('textarea').val('').prop('disabled', true); var messages = jQuery('.chat-message'); if (messages.length) { // The callback is only called if there actually are elements here, so // we can't just use it all the time. messages.fadeOut('fast', function () { messages.remove(); showMessage(); }); } else { showMessage(); } // Remove display of chat channel/partner. jQuery('#channel *').fadeOut('fast', function () { jQuery('#channel *').remove(); }); }; /** * Enable the chat UI for a new chat partner. * * @param {string} channelId * The channel for this chat. */ p.openChatUi = function (channelId) { // With multiple windows, can get this notice even if already chatting. So // do nothing in that case. if (this.chatting) { return; } var self = this; this.chatting = true; // Enable the send button. jQuery('.send-button').on('click', function () { var textarea = jQuery('textarea'); var val = textarea.val().trim(); if (val) { // Sending this user-entered data as a message to the server side of the // this application. self.send({ action: 'message', message: val }); textarea.val(''); } }); // Enable the kick button. jQuery('.kick-button').on('click', function () { // Shut off the UI, notify the server and the chat partner. self.closeChatUi('kick'); self.send({ action: 'kick' }); }); // Remove the notices, enable the chat window. jQuery('.notice').fadeOut('fast'); // Enabled versions for the UI. jQuery('#chat-wrapper').addClass('enabled'); // Other odds and ends. jQuery('textarea').prop('disabled', false); // Add the display of channel/chat partner. // Render the message HTML. var rendered = this.templates.channelTemplate({ channelId: channelId }); // Convert to DOM. The filter('*') gets rid of newline text nodes, which // cause jQuery issues. rendered = jQuery.parseHTML(rendered); jQuery(rendered).filter('*').hide().appendTo('#channel').fadeIn('fast'); }; /** * Display a newly arrived chat message. * * @param {string} messageText * The text of the message. */ p.displayChatMessage = function (messageText) { // Render the message HTML. var rendered = this.templates.messageTemplate({ data: messageText }); this.displayChatMessageHTML(rendered); }; /** * Display a message notice to show that the chat partner has disconnected. */ p.displayDisconnectedMessage = function () { var rendered = this.templates.disconnectMessageTemplate({}); this.displayChatMessageHTML(rendered); }; /** * Display a message notice to show that the chat partner has reconnected. */ p.displayReconnectedMessage = function () { var rendered = this.templates.reconnectMessageTemplate({}); this.displayChatMessageHTML(rendered); }; /** * Given its HTML, display a new chat message. * @param {string} html * The rendered HTML contents. */ p.displayChatMessageHTML = function (html) { // It's possible for a just-rightly timed message to arrive in the lag time // between a user getting kicked and the system noticing that. if (!this.chatting) { return; } // Set scroll top, or else the new message might not push down content // correctly. jQuery('#chat-output').scrollTop(0); // Convert to DOM. The filter('*') gets rid of newline text nodes, which // cause jQuery issues. html = jQuery.parseHTML(html); // Add the message content to the output div, and slide it in. jQuery(html).filter('*').hide().prependTo('#chat-output').slideDown(); }; /** * Change the status message. */ p.uiStatus = function (text, className) { var status = jQuery('#status'); var speed = 100; status.fadeOut(speed, function () { status.html(text) .removeClass('connecting connected disconnected') .addClass(className) .fadeIn(speed); }); }; // ------------------------------------------ // Other Methods // ------------------------------------------ /** * Rudimentary logging. * * @param {string} logThis * String to log. */ p.log = function (logThis) { console.log(logThis); }; /** * @see Thywill.ApplicationInterface#received */ p.received = function (message) { var data = message.getData(); // Start chatting. if (data.action === 'startChat') { this.openChatUi(data.channelId); } // The other side kicked this client out of the chat. else if (data.action === 'kicked') { this.closeChatUi('kicked'); } // The other side disconnected. else if (data.action === 'disconnected') { this.displayDisconnectedMessage(); } // The other side reconnected. else if (data.action === 'reconnected') { this.displayReconnectedMessage(); } // A message send to the chat channel. else if (data.action === 'message') { this.displayChatMessage(data.message); } }; /** * @see Thywill.ApplicationInterface#connecting */ p.connecting = function () { this.uiStatus('Connecting...', 'connecting'); this.log('Client attempting to connect.'); }; /** * @see Thywill.ApplicationInterface#connected */ p.connected = function () { this.uiStatus('Connected', 'connected'); this.log('Client connected.'); }; /** * @see Thywill.ApplicationInterface#connectionFailure */ p.connectionFailure = function () { this.uiStatus('Disconnected', 'disconnected'); this.closeChatUi(); this.log('Client failed to connect.'); }; /** * @see Thywill.ApplicationInterface#disconnected */ p.disconnected = function () { this.uiStatus('Disconnected', 'disconnected'); this.closeChatUi(); this.log('Client disconnected.'); }; // ---------------------------------------------------------- // Create an application instance and set it up. // ---------------------------------------------------------- // Create the application instance. The application ID will be populated // by the backend via the Handlebars template engine when this Javascript // file is prepared as a resource. var app = new ChatApplication('{{{applicationId}}}'); // Initial UI setup. jQuery(document).ready(function () { app.uiSetup(); }); })();