(function(context,$) { "use strict"; context.JK = context.JK || {}; context.JK.TextMessageDialog = function(app) { var logger = context.JK.logger; var rest = context.JK.Rest(); var $dialog = null; var $previousMessages = null; var $previousMessagesScroller = null; var $sendTextMessage = null; var $form = null; var $textBox = null; var userLookup = null; var otherId = null; var offset = 0; var sendingMessage = false; var LIMIT = 20; var showing = false; var fullyInitialized = false; var user = null; var renderQueue = []; // to handle race condition between dialog showing and populated using REST, and messages coming in var remainingCap = 400; function reset() { fullyInitialized = false; renderQueue = []; sendingMessage = false; offset = 0; userLookup = {}; $previousMessages.empty(); $textBox.val(''); $sendTextMessage.unbind('click'); } function buildParams() { return { type: 'TEXT_MESSAGE', receiver: otherId, offset: offset, limit: LIMIT}; } function buildMessage() { var message = {}; message['message'] = $textBox.val(); message['receiver'] = otherId; return message; } function sendMessage() { var msg = $textBox.val(); if(!msg || msg == '') { // don't bother the server with empty messages return; } if(!sendingMessage) { sendingMessage = true; $sendTextMessage.text('SENDING...') rest.createTextMessage(buildMessage()) .done(function() { $textBox.val(''); renderMessage(msg, user.id, user.name, new Date().toISOString(), true); }) .fail(function(jqXHR) { app.notifyServerError(jqXHR, 'Unable to Send Message'); }) .always(function() { sendingMessage = false; $sendTextMessage.text('SEND'); }) } return false; } function scrollToBottom(instant) { $previousMessagesScroller.animate({scrollTop: $previousMessagesScroller[0].scrollHeight}, instant ? 0 : 'slow'); } function renderMessage(msg, senderId, senderName, sent, append) { var options = { msg: msg, sender: senderId == user.id ? 'me' : senderName, sent: sent }; var txt = $(context._.template($('#template-previous-message').html(), options, { variable: 'data' })); txt.find('.timeago').timeago(); if(append) { $previousMessages.append(txt); scrollToBottom(); } else { $previousMessages.prepend(txt); } } function drainQueue() { context._.each(renderQueue, function(msg) { renderMessage(msg.msg, msg.senderId, msg.senderName, msg.sent, true); }); renderQueue = []; } function formatTextMessage(msg, sender_id, sender_name, clipped_msg) { var markedUpMsg = $(' says: '); markedUpMsg.find('.sender-name').text(sender_name) markedUpMsg.find('.text-message').text(clipped_msg ? msg + "... " : msg).attr('data-is-clipped', clipped_msg) var moreTextLink = markedUpMsg.find('.more-text-available').attr('data-sender-id', sender_id); if(clipped_msg) { moreTextLink.text('more').show(); moreTextLink.click(function(e) { app.layout.showDialog('text-message', {d1: $(this).attr('data-sender-id')}); return false; }); } else { moreTextLink.hide(); } return markedUpMsg; } // we handled the notification, meaning the dialog showed this message as a chat message function handledNotification(payload) { return showing && payload.description == "TEXT_MESSAGE" && payload.sender_id == otherId; } function isNoisyNotification(payload) { return handledNotification(payload); } function afterShow(args) { $textBox.focus(); } function beforeShow(args) { app.layout.closeDialog('text-message') // ensure no others are showing. this is a singleton dialog app.user() .done(function(userDetail) { user = userDetail; var other = args.d1; if(!other) throw "other must be specified in TextMessageDialog" otherId = other; showing = true; userLookup[user.id] = user; rest.getUserDetail({id: otherId}) .done(function(otherUser) { userLookup[otherUser.id] = otherUser; $dialog.find('.receiver-name').text(otherUser.name); $dialog.find('textarea').attr('placeholder', 'enter a message to ' + otherUser.name + '...'); $dialog.find('.offline-tip').text('An email will be sent if ' + otherUser.name + ' is offline'); $sendTextMessage.click(sendMessage); rest.getNotifications(buildParams()) .done(function(response) { context._.each(response, function(textMessage) { renderMessage(textMessage.message, textMessage.source_user_id, userLookup[textMessage.source_user_id].name, textMessage.created_at); }) scrollToBottom(true); fullyInitialized = true; drainQueue(); }) .fail(function(jqXHR) { app.notifyServerError(jqXHR, 'Unable to Load Conversation') }) }) .fail(function(jqXHR) { app.notifyServerError(jqXHR, 'Unable to Load Other User') }) }) } function afterHide() { showing = false; reset(); } function pasteIntoInput(el, text) { el.focus(); if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") { var val = el.value; var selStart = el.selectionStart; el.value = val.slice(0, selStart) + text + val.slice(el.selectionEnd); el.selectionEnd = el.selectionStart = selStart + text.length; } else if (typeof document.selection != "undefined") { var textRange = document.selection.createRange(); textRange.text = text; textRange.collapse(false); textRange.select(); } } function handleEnter(evt) { if (evt.keyCode == 13 && evt.shiftKey) { pasteIntoInput(this, "\n"); evt.preventDefault(); } else if(evt.keyCode == 13 && !evt.shiftKey){ sendMessage(); return false; } } function events() { $form.submit(sendMessage) // http://stackoverflow.com/questions/6014702/how-do-i-detect-shiftenter-and-generate-a-new-line-in-textarea $textBox.keydown(handleEnter); } function respondTextInvitation(args) { app.layout.showDialog('text-message', {d1: args.sender_id}) ; } // called from sidebar when messages come in function messageReceived(payload) { if(showing && otherId == payload.sender_id) { if(fullyInitialized) { renderMessage(payload.msg, payload.sender_id, payload.sender_name, payload.created_at, true); } else { // the dialog caught a message as it was initializing... queue it for later once dialog is showing renderQueue.push({msg: payload.msg, senderId: payload.sender_id, senderName: payload.sender_name, sent: msg.created_at}); } } else { payload.msg = formatTextMessage(payload.msg, payload.sender_id, payload.sender_name, payload.clipped_msg); app.notify({ "title": "Message from " + payload.sender_name, "text": payload.msg, "icon_url": context.JK.resolveAvatarUrl(payload.photo_url) }, { "ok_text": "REPLY", "ok_callback": respondTextInvitation, "ok_callback_args": { "sender_id": payload.sender_id, "notification_id": payload.notification_id } }); } } /** function showDialog(_other) { app.layout.closeDialog('text-message') // this dialog is implemented as a singleton, so must enforce this reset(); if(!_other) throw "other must be specified in TextMessageDialog" otherId = _other; app.layout.showDialog('text-message') }*/ function initialize() { var dialogBindings = { 'beforeShow' : beforeShow, 'afterShow' : afterShow, 'afterHide': afterHide }; app.bindDialog('text-message', dialogBindings); $dialog = $('#text-message-dialog'); $previousMessagesScroller = $dialog.find('.previous-messages-scroller'); $previousMessages = $dialog.find('.previous-messages'); $sendTextMessage = $dialog.find('.btn-send-text-message'); $form = $dialog.find('form'); $textBox = $form.find('textarea'); events(); } this.initialize = initialize; this.messageReceived = messageReceived; this.formatTextMessage = formatTextMessage; this.handledNotification = handledNotification; } return this; })(window,jQuery);