diff --git a/samples/chat/README.md b/samples/chat/README.md new file mode 100644 index 000000000..0ea8cc32c --- /dev/null +++ b/samples/chat/README.md @@ -0,0 +1,26 @@ +# Overview +QuickBlox JS Sample Chat + +This is a code sample for [QuickBlox](http://quickblox.com/) platform. It is a great way for developers using QuickBlox platform to learn how to integrate private and group chat, add text and image attachments sending into your application. + +# Credentials + +Welcome to QuickBlox [Credentials](https://quickblox.com/developers/5_Minute_Guide), where you can get your credentials in just 5 minutes! All you need is to: + +1. Register a free QuickBlox account and add your App there. +2. Update credentials in your application code.[Chat](https://quickblox.com/developers/5_Minute_Guide#Update_authentication_credentials_3) + +# Chat Sample + +This Sample demonstrates how to work with [Chat](https://quickblox.com/developers/Web_XMPP_Chat_Sample#Guide:_Getting_Started_with_Chat_API) QuickBlox module. + +It allows: + +1. Authenticate with Quickblox Chat and REST. +2. Receive and display list of dialogs. +3. Modify dialog by adding occupants. +4. Real-time chat messaging and attachment's handling. + +# Documentation + +Original sample description & setup guide - [JS Chat Sample](https://quickblox.com/developers/Web_XMPP_Chat_Sample#Guide:_Getting_Started_with_Chat_API) \ No newline at end of file diff --git a/samples/chat/css/dashboard.css b/samples/chat/css/dashboard.css index 70f646b36..38a25e4c4 100644 --- a/samples/chat/css/dashboard.css +++ b/samples/chat/css/dashboard.css @@ -593,6 +593,20 @@ padding: 7px 24px; } +.group_chat__filter { + border-bottom: 1px solid #DADFE1; + display: flex; + padding: 13px 8px; +} +.group_chat__filter > input { + flex: 1 1 100%; + font-size: 16px; + line-height: 19px; + border: none; + margin: 0 10px 0 0; + padding: 0 10px; +} + .user__item { padding: 8px 24px; display: flex; diff --git a/samples/chat/css/dialogs.css b/samples/chat/css/dialogs.css index 486c2bd0f..ada33ad88 100644 --- a/samples/chat/css/dialogs.css +++ b/samples/chat/css/dialogs.css @@ -250,3 +250,8 @@ .dialog_unread_counter.hidden { display: none; } + +.center { + width: 100%; + text-align: center; +} diff --git a/samples/chat/css/login.css b/samples/chat/css/login.css index 32e13b16a..b969b36e3 100644 --- a/samples/chat/css/login.css +++ b/samples/chat/css/login.css @@ -87,6 +87,14 @@ color: #17D04B; } +.login_form__row.error input { + border-bottom: 2px solid red !important; +} + +.login_form__row.error label { + color: red !important; +} + .m-login__button { width: 100%; } diff --git a/samples/chat/index.html b/samples/chat/index.html index f6643ac9f..75bd0af57 100755 --- a/samples/chat/index.html +++ b/samples/chat/index.html @@ -11,7 +11,7 @@ - + @@ -28,20 +28,26 @@ QuickBlox]

Quickblox Chat Sample

-

Please enter your username and user group. - Users within the same group will be able - to communicate, create chats with each other -

+

Please enter your Login and User name

- - + +
- - + +
+
+ +

Select participants:

diff --git a/samples/chat/js/QBconfig.js b/samples/chat/js/QBconfig.js index dc8c92940..efbe3473e 100644 --- a/samples/chat/js/QBconfig.js +++ b/samples/chat/js/QBconfig.js @@ -1,8 +1,8 @@ var QBconfig = { credentials: { - appId: 72448, - authKey: 'f4HYBYdeqTZ7KNb', - authSecret: 'ZC7dK39bOjVc-Z8' + appId: '', + authKey: '', + authSecret: '' }, appConfig: { chatProtocol: { @@ -39,6 +39,7 @@ var CONSTANTS = { }, NOTIFICATION_TYPES: { NEW_DIALOG: '1', - UPDATE_DIALOG: '2' + UPDATE_DIALOG: '2', + LEAVE_DIALOG: '3' } }; diff --git a/samples/chat/js/app.js b/samples/chat/js/app.js index add1d8f9b..6c0be5b55 100644 --- a/samples/chat/js/app.js +++ b/samples/chat/js/app.js @@ -57,22 +57,17 @@ App.prototype.renderDashboard = function (activeTabName) { listeners.setListeners(); logoutBtn.addEventListener('click', function () { - QB.users.delete(app.user.id, function(err, user){ - if (err) { - console.error('Can\'t delete user by id: '+app.user.id+' ', err); - } - loginModule.isLogin = false; - app.isDashboardLoaded = false; + loginModule.isLogin = false; + app.isDashboardLoaded = false; - localStorage.removeItem('user'); - helpers.clearCache(); + localStorage.removeItem('user'); + helpers.clearCache(); - QB.chat.disconnect(); - QB.destroySession(); - - router.navigate('#!/login'); - }); + QB.chat.disconnect(); + QB.destroySession(() => null); + + router.navigate('#!/login'); }); this.tabSelectInit(); @@ -90,7 +85,7 @@ App.prototype.loadWelcomeTpl = function () { App.prototype.tabSelectInit = function () { var self = this, tabs = document.querySelectorAll('.j-sidebar__tab_link'); - + _.each(tabs, function (item) { item.addEventListener('click', function (e) { e.preventDefault(); @@ -99,7 +94,16 @@ App.prototype.tabSelectInit = function () { } var tab = e.currentTarget; - self.loadChatList(tab); + self.loadChatList(tab).then(function(){ + var activeTab = document.querySelector('.j-sidebar__tab_link.active'), + tabType = activeTab.dataset.type, + dialogType = dialogModule._cache[dialogModule.dialogId].type === 1 ? 'public' : 'chat', + isActiveTab = tabType === dialogType; + + if(isActiveTab && dialogModule._cache[dialogModule.dialogId].type === CONSTANTS.DIALOG_TYPES.CHAT ) { + dialogModule.renderMessages(dialogModule.dialogId); + } + }); }); }); }; @@ -153,17 +157,10 @@ App.prototype.buildCreateDialogTpl = function () { if (document.forms.create_dialog.create_dialog_submit.disabled) return false; document.forms.create_dialog.create_dialog_submit.disabled = true; - - var users = self.userListConteiner.querySelectorAll('.selected'), - type = users.length > 2 ? 2 : 3, - name = document.forms.create_dialog.dialog_name.value, - occupants_ids = []; - - _.each(users, function (user) { - if (+user.id !== self.user.id) { - occupants_ids.push(user.id); - } - }); + + var occupants_ids = userModule.selectedUserIds, + type = occupants_ids.length > 1 ? 2 : 3, + name = document.forms.create_dialog.dialog_name.value; if (!name && type === 2) { var userNames = []; @@ -197,6 +194,7 @@ App.prototype.buildCreateDialogTpl = function () { document.forms.create_dialog.dialog_name.value = titleText.slice(0, 40); } }); + userModule.initGettingUsers(); }; diff --git a/samples/chat/js/dialog.js b/samples/chat/js/dialog.js index cce3a9c7e..2d784a851 100644 --- a/samples/chat/js/dialog.js +++ b/samples/chat/js/dialog.js @@ -69,10 +69,7 @@ Dialog.prototype.loadDialogs = function (type) { var dialogs = resDialogs.items; _.each(dialogs, function (dialog) { - if (!self._cache[dialog._id]) { - self._cache[dialog._id] = helpers.compileDialogParams(dialog); - } - + self._cache[dialog._id] = helpers.compileDialogParams(dialog); self.renderDialog(self._cache[dialog._id]); }); @@ -188,7 +185,7 @@ Dialog.prototype.replaceDialogLink = function (elem) { Dialog.prototype.joinToDialog = function (id) { var self = this, - jidOrUserId = self._cache[id].jidOrUserId; + jidOrUserId = ((typeof self._cache[id].jidOrUserId == "number")?String(self._cache[id].jidOrUserId):self._cache[id].jidOrUserId); return new Promise(function (resolve, reject){ QB.chat.muc.join(jidOrUserId, function (resultStanza) { @@ -210,7 +207,7 @@ Dialog.prototype.joinToDialog = function (id) { Dialog.prototype.renderMessages = function (dialogId) { var self = this, dialog = self._cache[dialogId]; - + dialog.tplDateMessage = {}; document.querySelector('.j-sidebar__create_dialog').classList.remove('active'); if (!document.forms.send_message) { @@ -297,8 +294,8 @@ Dialog.prototype.changeLastMessagePreview = function (dialogId, msg) { var self = this, dialog = document.getElementById(dialogId), message = msg.message; - - if (message.indexOf('\n') !== -1) { + + if (message && message.indexOf('\n') !== -1) { message = message.slice(0, message.indexOf('\n')); } @@ -317,6 +314,7 @@ Dialog.prototype.changeLastMessagePreview = function (dialogId, msg) { } dialog.querySelector('.j-dialog__last_message_date').innerText = msg.date_sent; + dialogModule.sortedByLastMessage(dialogId); } }; @@ -324,9 +322,7 @@ Dialog.prototype.createDialog = function (params) { if (!app.checkInternetConnection()) { return false; } - var self = this; - QB.chat.dialog.create(params, function (err, createdDialog) { if (err) { console.error(err); @@ -358,7 +354,7 @@ Dialog.prototype.createDialog = function (params) { save_to_history: 1, dialog_id: createdDialog._id, notification_type: 1, - occupants_ids: occupants.toString() + date_sent: Date.now() } }; @@ -366,36 +362,37 @@ Dialog.prototype.createDialog = function (params) { return item != app.user.id }); - for (var i = 0; i < newOccupantsIds.length; i++) { - QB.chat.sendSystemMessage(newOccupantsIds[i], systemMessage); - } - /* Check dialog in cache */ if (!self._cache[id]) { self._cache[id] = helpers.compileDialogParams(createdDialog); } - self.joinToDialog(id).then(function(){ - if(createdDialog.type === CONSTANTS.DIALOG_TYPES.GROUPCHAT){ - messageModule.sendMessage(id, notificationMessage); + (new Promise(function (resolve){ + self.joinToDialog(id).then(function(){ + if(createdDialog.type === CONSTANTS.DIALOG_TYPES.GROUPCHAT){ + messageModule.sendMessage(id, notificationMessage); + } + resolve(); + }); + })).then(function(){ + for (var i = 0; i < newOccupantsIds.length; i++) { + QB.chat.sendSystemMessage(newOccupantsIds[i], systemMessage); } - }); - - /* Check active tab [chat / public] */ - var type = params.type === CONSTANTS.DIALOG_TYPES.PUBLICCHAT ? 'public' : 'chat', - activeTab = document.querySelector('.j-sidebar__tab_link.active'); - - if (activeTab && type !== activeTab.dataset.type) { - var tab = document.querySelector('.j-sidebar__tab_link[data-type="chat"]'); - app.loadChatList(tab).then(function () { + /* Check active tab [chat / public] */ + var type = params.type === CONSTANTS.DIALOG_TYPES.PUBLICCHAT ? 'public' : 'chat', + activeTab = document.querySelector('.j-sidebar__tab_link.active'); + if (activeTab && type !== activeTab.dataset.type) { + var tab = document.querySelector('.j-sidebar__tab_link[data-type="chat"]'); + app.loadChatList(tab).then(function () { + self.renderDialog(self._cache[id], true); + }).catch(function(error){ + console.error(error); + }); + } else { self.renderDialog(self._cache[id], true); - }).catch(function(error){ - console.error(error); - }); - } else { - self.renderDialog(self._cache[id], true); - router.navigate('/dialog/' + id); - } + router.navigate('/dialog/' + id); + } + }); } }); }; @@ -459,7 +456,8 @@ Dialog.prototype.updateDialog = function (updates) { dialog_id: dialog._id, notification_type: 2, dialog_updated_at: Date.now() / 1000 - } + }, + markable: 1 }; if(dialog.type !== CONSTANTS.DIALOG_TYPES.GROUPCHAT) return false; @@ -487,7 +485,7 @@ Dialog.prototype.updateDialog = function (updates) { self._cache[dialogId].users = self._cache[dialogId].users.concat(newUsers); updatedMsg.body = app.user.name + ' adds ' + usernames.join(', ') + ' to the conversation.'; - updatedMsg.extension.occupants_ids_added = newUsers.join(','); + updatedMsg.extension.new_occupants_ids = newUsers.join(','); } else { router.navigate('/dialog/' + dialogId); return false; @@ -499,6 +497,19 @@ Dialog.prototype.updateDialog = function (updates) { _notifyNewUsers(newUsers); } + var msg = { + extension: { + notification_type: 2, + dialog_id: dialog._id, + new_occupants_ids: newUsers.toString() + } + }; + + _.each(dialog.occupants_ids, function(user){ + QB.chat.sendSystemMessage(+user, msg); + }); + + messageModule.sendMessage(dialogId, updatedMsg); if(updates.title){ @@ -532,7 +543,8 @@ Dialog.prototype.updateDialog = function (updates) { var msg = { extension: { notification_type: 2, - dialog_id: dialog._id + dialog_id: dialog._id, + new_occupants_ids: users.toString() } }; @@ -564,24 +576,29 @@ Dialog.prototype.quitFromTheDialog = function(dialogId){ alert('you can\'t remove this dialog'); break; case CONSTANTS.DIALOG_TYPES.CHAT: - QB.chat.dialog.delete([dialogId], function(err) { - if (err) { - console.error(err); - } else { - _removedilogFromCacheAndUi(); - router.navigate('/dashboard'); - } - }); - break; case CONSTANTS.DIALOG_TYPES.GROUPCHAT: - // remove user from current group dialog; - _notuyfyUsers(); - QB.chat.dialog.update(dialogId, { - pull_all: { - occupants_ids: [app.user.id] + if(CONSTANTS.DIALOG_TYPES.GROUPCHAT===dialog.type){ + // remove user from current group dialog; + _notuyfyUsers(); + + var systemMessage = { + extension: { + notification_type: 3, + dialog_id: dialog._id + } + }; + + for (var i = 0; i < dialog.users.length; i++) { + if (dialog.users[i] === app.user.id) { + continue; + } + QB.chat.sendSystemMessage(dialog.users[i], systemMessage); } - }, function(err, res) { + + } + + QB.chat.dialog.delete([dialogId], function(err) { if (err) { console.error(err); } else { @@ -606,10 +623,10 @@ Dialog.prototype.quitFromTheDialog = function(dialogId){ extension: { save_to_history: 1, dialog_id: dialog._id, - notification_type: 2, - dialog_updated_at: Date.now() / 1000, - occupants_ids_removed: app.user.id - } + notification_type: 3, + dialog_updated_at: Date.now() / 1000 + }, + markable: 1 }; messageModule.sendMessage(dialogId, msg); @@ -617,4 +634,13 @@ Dialog.prototype.quitFromTheDialog = function(dialogId){ }; +Dialog.prototype.sortedByLastMessage = function(dialogId){ + var self = this, + elem = document.getElementById(dialogId); + if(elem) { + self.dialogsListContainer.insertBefore(elem, self.dialogsListContainer.firstElementChild); + } +}; + + var dialogModule = new Dialog(); diff --git a/samples/chat/js/helpers.js b/samples/chat/js/helpers.js index c31913f44..87b420921 100644 --- a/samples/chat/js/helpers.js +++ b/samples/chat/js/helpers.js @@ -156,8 +156,8 @@ Helpers.prototype.getMessageStatus = function(message){ var self = this, deleveredToOcuupants = self.checkIsMessageDeliveredToOccupants(message), readedByOcuupants = self.checkIsMessageReadedByOccupants(message), - status = !deleveredToOcuupants ? 'not delivered yet' : - readedByOcuupants ? 'seen' : 'delivered'; + status = !deleveredToOcuupants ? 'sent' : + readedByOcuupants ? 'read' : 'delivered'; return status; @@ -224,8 +224,8 @@ Helpers.prototype.fillNewMessageParams = function (userId, msg) { message.notification_type = msg.extension.notification_type; } - if(msg.extension.occupants_ids_added){ - message.occupants_ids_added = msg.extension.occupants_ids_added; + if(msg.extension.new_occupants_ids){ + message.new_occupants_ids = msg.extension.new_occupants_ids; } message.status = (userId !== app.user.id) ? self.getMessageStatus(message) : undefined; diff --git a/samples/chat/js/listeners.js b/samples/chat/js/listeners.js index 52bd531f6..cfdf5d28f 100644 --- a/samples/chat/js/listeners.js +++ b/samples/chat/js/listeners.js @@ -21,12 +21,19 @@ function Listeners() {}; Listeners.prototype.onMessageListener = function (userId, message) { - if(userId === app.user.id) return false; var self = this, msg = helpers.fillNewMessageParams(userId, message), dialog = dialogModule._cache[message.dialog_id]; + if (dialog) { + dialogModule.sortedByLastMessage(message.dialog_id); + } + + if(userId === app.user.id){ + return false; + } + if(message.markable){ messageModule.sendDeliveredStatus(msg._id, userId, msg.chat_dialog_id); } @@ -41,11 +48,14 @@ Listeners.prototype.onMessageListener = function (userId, message) { var activeTab = document.querySelector('.j-sidebar__tab_link.active'), tabType = activeTab.dataset.type, - dialogType = dialog.type === 1 ? 'public' : 'chat'; + dialogType = dialog.type === 1 ? 'public' : 'chat', + isActiveTab = tabType === dialogType; - if(tabType === dialogType){ + if(isActiveTab) { dialogModule.renderDialog(dialog, true); + } + if(isActiveTab || message.dialog_id === dialogModule.dialogId ){ if (dialogModule.dialogId === msg.chat_dialog_id) { messageModule.renderMessage(msg, true); } else { @@ -79,14 +89,14 @@ Listeners.prototype.onNotificationMessage = function(userId, message){ dialog = dialogModule._cache[message.dialog_id], extension = message.extension, dialogId = message.dialog_id, - occupantsIdsAdded = extension.occupants_ids_added && extension.occupants_ids_added.split(','); + occupantsIdsAdded = extension.new_occupants_ids && extension.new_occupants_ids.split(','); - if(extension.notification_type === CONSTANTS.NOTIFICATION_TYPES.UPDATE_DIALOG){ - if (extension.occupants_ids_removed) { + if(message.extension && ['2','3'].indexOf(message.extension.notification_type) !== -1){ + if (message.extension.notification_type === '3') { dialogModule._cache[dialogId].users = dialogModule._cache[dialogId].users.filter(function(user){ return user !== userId; }); - } else if(extension.occupants_ids_added) { + } else if(extension.new_occupants_ids) { _.each(occupantsIdsAdded, function(userId) { if (dialog.users.indexOf(+userId) === -1) { dialog.users.push(+userId); @@ -133,7 +143,6 @@ Listeners.prototype.onMessageTypingListener = function (isTyping, userId, dialog Listeners.prototype.onSystemMessageListener = function (message) { var dialog = dialogModule._cache[message.dialog_id || message.extension.dialog_id]; - if (message.extension && message.extension.notification_type === CONSTANTS.NOTIFICATION_TYPES.NEW_DIALOG) { if (message.extension.dialog_id) { dialogModule.getDialogById(message.extension.dialog_id).then(function (dialog) { @@ -149,13 +158,12 @@ Listeners.prototype.onSystemMessageListener = function (message) { }); } return false; - } else if(message.extension && message.extension.notification_type === CONSTANTS.NOTIFICATION_TYPES.UPDATE_DIALOG) { + } else if(message.extension && ['2','3'].indexOf(message.extension.notification_type) !== -1 ) { if(!dialog){ dialogModule.getDialogById(message.extension.dialog_id).then(function (dialog) { dialogModule._cache[dialog._id] = helpers.compileDialogParams(dialog); var type = dialog.type === 1 ? 'public' : 'chat', activeTab = document.querySelector('.j-sidebar__tab_link.active'); - if (activeTab && type === activeTab.dataset.type) { dialogModule.renderDialog(dialogModule._cache[dialog._id], true); } @@ -182,15 +190,13 @@ Listeners.prototype.onSentMessageCallback = function (messageLost, messageSent) var message = messageSent || messageLost, data = { _id: message.id, - dialogId: message.extension.dialog_id + dialogId: message.extension.dialog_id, + status: 'sent' }; if (messageLost) { // message was not sent to the chat. - data.status = 'not sent'; - } else { - // message was sent to the chat but not delivered to che opponent. - data.status = 'not delivered yet'; + data.status = 'not ' + data.status; } messageModule.setMessageStatus(data); @@ -202,7 +208,7 @@ Listeners.prototype.onReadStatusListener = function (messageId, dialogId, userId _id: messageId, dialogId: dialogId, userId: userId, - status: 'seen' + status: 'read' }; messageModule.setMessageStatus(data); diff --git a/samples/chat/js/login.js b/samples/chat/js/login.js index d8f8c20c5..e6ea92da2 100644 --- a/samples/chat/js/login.js +++ b/samples/chat/js/login.js @@ -97,7 +97,10 @@ Login.prototype.login = function (user) { function loginError(error){ self.renderLoginPage(); console.error(error); - alert(error + "\n" + error.detail); + var message = Object.keys(error.detail).map(function (key) { + return key + ' ' + error.detail[key].join('') + }) + alert(message); reject(error); } }); @@ -122,26 +125,25 @@ Login.prototype.renderLoadingPage = function(){ Login.prototype.setListeners = function(){ var self = this, loginForm = document.forms.loginForm, - formInputs = [loginForm.userName, loginForm.userGroup], + formInputs = [loginForm.userName, loginForm.userLogin], loginBtn = loginForm.login_submit; loginForm.addEventListener('submit', function(e){ e.preventDefault(); - if(loginForm.hasAttribute('disabled')){ + if(loginForm.hasAttribute('disabled') || !loginForm.userName.isValid || !loginForm.userLogin.isValid){ return false; } else { loginForm.setAttribute('disabled', true); } var userName = loginForm.userName.value.trim(), - userGroup = loginForm.userGroup.value.trim(); + userLogin = loginForm.userLogin.value.trim(); var user = { - login: helpers.getUui(), + login: userLogin, password: 'webAppPass', - full_name: userName, - tag_list: userGroup + full_name: userName }; localStorage.setItem('user', JSON.stringify(user)); @@ -176,14 +178,24 @@ Login.prototype.setListeners = function(){ } }); - i.addEventListener('input', function(){ + i.addEventListener('input', function(e){ var userName = loginForm.userName.value.trim(), - userGroup = loginForm.userGroup.value.trim(); - if(userName.length >=3 && userGroup.length >= 3){ - loginBtn.removeAttribute('disabled'); - } else { - loginBtn.setAttribute('disabled', true); - } + userLogin = loginForm.userLogin.value.trim(); + + loginForm.userName.isValid = 15 >= userName.length && userName.length >=3; + loginForm.userLogin.isValid = 15 >= userLogin.length && userLogin.length >= 3 && (userLogin.match(/^[a-zA-Z]{1}[a-zA-Z0-9]+$/)!=null); + + formInputs.forEach(function(e) { + var container = e.parentElement; + if(e.isValid){ + container.classList.remove('error'); + }else{ + container.classList.add('error'); + } + }); + + loginBtn.removeAttribute('disabled'); + }) }); }; diff --git a/samples/chat/js/message.js b/samples/chat/js/message.js index 1095c9ea9..d07bff349 100644 --- a/samples/chat/js/message.js +++ b/samples/chat/js/message.js @@ -196,6 +196,7 @@ Message.prototype.getMessages = function (dialogId) { if (dialogModule._cache[dialogId].type === 1) { self.checkUsersInPublicDialogMessages(messages.items, params.skip); } else { + for (var i = 0; i < messages.items.length; i++) { var message = helpers.fillMessagePrams(messages.items[i]); @@ -260,7 +261,10 @@ Message.prototype.sendReadStatus = function(messageId, userId, dialogId){ dialogId: dialogId }; - QB.chat.sendReadStatus(params); + if (document.visibilityState === 'visible') { + QB.chat.sendReadStatus(params); + } + }; Message.prototype.renderMessage = function (message, setAsFirst) { @@ -281,7 +285,8 @@ Message.prototype.renderMessage = function (message, setAsFirst) { messagesHtml = helpers.fillTemplate('tpl_notificationMessage', { id: message._id, - text: messageText + text: messageText, + date_sent: message.date_sent }); } else { messageText = message.message ? helpers.fillMessageBody(message.message || '') : helpers.fillMessageBody(message.body || ''); @@ -364,6 +369,27 @@ Message.prototype.renderMessage = function (message, setAsFirst) { self.container.scrollTop += containerHeightAfterAppend - containerHeightBeforeAppend; } } + + var currentDialog = dialogModule._cache[dialogId], + date = new Date(message.created_at), + month = date.toLocaleString('en-us', { month: 'long' }), + template = helpers.fillTemplate('tpl_date_message', {'month':month, 'date':date}), + elemDate = helpers.toHtml(template)[0], + tmpElem = document.querySelector('#'+month+'-'+date.getDate()); + + currentDialog.tplDateMessage = currentDialog.tplDateMessage?currentDialog.tplDateMessage:{}; + + if(!currentDialog.tplDateMessage[month+'/'+ date.getDate()] || + parseInt(currentDialog.tplDateMessage[month+'/'+ date.getDate()].replace(/:/, '')) > + parseInt(message.date_sent.replace(/:/, '')) + ){ + if(tmpElem){ + tmpElem.remove(); + } + currentDialog.tplDateMessage[month+'/'+ date.getDate()] = message.date_sent; + self.container.insertBefore(elemDate,elem); + } + }; Message.prototype.prepareToUpload = function (e) { diff --git a/samples/chat/js/route.js b/samples/chat/js/route.js index e78cafed6..cb932f56d 100644 --- a/samples/chat/js/route.js +++ b/samples/chat/js/route.js @@ -46,33 +46,40 @@ router.on({ dialogModule.loadDialogs('chat'); } }, - '/dialog/create': function(){ - if (!loginModule.isLogin){ - loginModule.init().then(function(isLogedIn){ - if(!isLogedIn){ + '/dialog/create': { + uses: function() { + if (!loginModule.isLogin){ + loginModule.init().then(function(isLogedIn){ + if(!isLogedIn){ + router.navigate('/login'); + return; + } + if(!app.isDashboardLoaded) { + app.renderDashboard('chat'); + dialogModule.loadDialogs('chat'); + } + _renderNewDialogTmp(); + }).catch(function(error){ + console.error(error); router.navigate('/login'); - return; - } - if(!app.isDashboardLoaded) { - app.renderDashboard('chat'); - dialogModule.loadDialogs('chat'); - } + }); + } else { _renderNewDialogTmp(); - }).catch(function(error){ - console.error(error); - router.navigate('/login'); - }); - } else { - _renderNewDialogTmp(); - } + } - function _renderNewDialogTmp(){ - var createDialogTab = document.querySelector('.j-sidebar__create_dialog'); + function _renderNewDialogTmp(){ + var createDialogTab = document.querySelector('.j-sidebar__create_dialog'); - createDialogTab.classList.add('active'); - app.sidebar.classList.remove('active'); + createDialogTab.classList.add('active'); + app.sidebar.classList.remove('active'); - app.buildCreateDialogTpl(); + app.buildCreateDialogTpl(); + } + }, + hooks: { + leave: function () { + userModule.reset() + } } }, '/dialog/:dialogId': function(params){ @@ -101,196 +108,195 @@ router.on({ function _renderSelectedDialog(){ var currentDialog = dialogModule._cache[dialogId]; - if(!currentDialog){ - dialogModule.getDialogById(dialogId).then(function(dialog){ - var tabDataType = dialog.type === CONSTANTS.DIALOG_TYPES.PUBLICCHAT ? 'public' : 'chat', - tab = document.querySelector('.j-sidebar__tab_link[data-type="' + tabDataType + '"]'); - - app.loadChatList(tab).then(function(){ + if(currentDialog) { + dialogModule.selectCurrentDialog(dialogId); + } + dialogModule.getDialogById(dialogId).then(function(dialog){ + var tabDataType = dialog.type === CONSTANTS.DIALOG_TYPES.PUBLICCHAT ? 'public' : 'chat', + tab = document.querySelector('.j-sidebar__tab_link[data-type="' + tabDataType + '"]'); + if(!currentDialog) { + app.loadChatList(tab).then(function () { dialogModule.renderMessages(dialogId); app.sidebar.classList.remove('active'); - }).catch(function(error){ + }).catch(function (error) { console.error(error); }); - - }).catch(function(error){ - console.error(error); - var tab = document.querySelector('.j-sidebar__tab_link[data-type="chat"]'); - app.loadChatList(tab) - router.navigate('/dashboard'); - }); - } else { - dialogModule.renderMessages(dialogId); - dialogModule.selectCurrentDialog(dialogId); - } - } - }, - '/dialog/:dialogId/edit': function(params){ - var dialogId = params.dialogId; - var currentDialog = null; - - if (!loginModule.isLogin){ - loginModule.init().then(function(isLogedIn){ - if(!isLogedIn){ - router.navigate('/login'); - return; + }else if(tabDataType === 'chat') { + userModule.getUsersByIds(currentDialog.users).then(function () { + document.getElementById(dialogId).querySelector('.dialog__name').innerHTML = dialog.name; + dialogModule.renderMessages(dialogId); + }).catch(function (error) { + console.error(error); + }); + }else{ + dialogModule.renderMessages(dialogId); } - _renderEditDialogPage(); }).catch(function(error){ console.error(error); - router.navigate('/login'); + var tab = document.querySelector('.j-sidebar__tab_link[data-type="chat"]'); + app.loadChatList(tab); + router.navigate('/dashboard'); }); - } else { - _renderEditDialogPage(); - } - function _renderEditDialogPage(){ - if(!app.isDashboardLoaded) { - app.renderDashboard(); - } - currentDialog = dialogModule._cache[dialogId]; - - if(!currentDialog){ - dialogModule.dialogId = dialogId; - dialogModule.getDialogById(dialogId).then(function(dialog){ - var tabDataType = dialog.type === CONSTANTS.DIALOG_TYPES.PUBLICCHAT ? 'public' : 'chat', - tab = document.querySelector('.j-sidebar__tab_link[data-type="' + tabDataType + '"]'); - // add to dialog template - app.content.innerHTML = helpers.fillTemplate('tpl_UpdateDialogContainer', {title: dialog.name, _id: dialog._id}); - _setEditDiaogListeners(); - _renderUsers(dialog.occupants_ids); - app.loadChatList(tab).then(function(){}) - .catch(function(error){ - console.error(error); - }); - }).catch(function(error){ - router.navigate('#!/dashboard'); - }); - } else { - app.content.innerHTML = helpers.fillTemplate('tpl_UpdateDialogContainer', {title: currentDialog.name, _id: currentDialog._id}); - _setEditDiaogListeners(); - _renderUsers(currentDialog.users); - } } - function _renderUsers(dialogOccupants){ - var userList = document.querySelector('.j-update_chat__user_list'), - counterElem = document.querySelector('.j-update__chat_counter'), - addUsersBtn = document.querySelector('.j-update_dialog_btn'), + document.addEventListener('visibilitychange', function() { + var currentDialog = dialogModule._cache[dialogId], + dialogType = currentDialog.type === 1 ? 'public' : 'chat'; - newUsersCount = +counterElem.innerText.trim(); + if (document.visibilityState !== 'visible') { + dialogType = currentDialog.type === 1 ? 'chat' : 'public'; + } - userModule.getUsers().then(function(usersArray){ - var users = usersArray.map(function(user){ - var userItem = JSON.parse(JSON.stringify(user)); + var tab = document.querySelector('.j-sidebar__tab_link[data-type="'+dialogType+'"]'); + app.loadChatList(tab).then(function () { + if (document.visibilityState === 'visible' + && window.location.href.match(/\/dialog\/[a-zA-Z0-9]+$/) + && !window.location.href.match(/\/dialog\/create$/)) { + dialogModule.renderMessages(dialogModule.dialogId); + } + }); + }); - userItem.selected = dialogOccupants.indexOf(userItem.id) !== -1; - - return userItem; + }, + '/dialog/:dialogId/edit': { + uses: function(params) { + var dialogId = params.dialogId; + var currentDialog = null; + + if (!loginModule.isLogin){ + loginModule.init().then(function(isLogedIn){ + if(!isLogedIn){ + router.navigate('/login'); + return; + } + _renderEditDialogPage(); + }).catch(function(error){ + console.error(error); + router.navigate('/login'); }); + } else { + _renderEditDialogPage(); + } - _.each(users, function(user){ - var userTpl = helpers.fillTemplate('tpl_editChatUser', user), - userElem = helpers.toHtml(userTpl)[0]; - - userElem.addEventListener('click', function(e){ - var elem = e.currentTarget; - if(elem.classList.contains('disabled')) return; - if(elem.classList.contains('selected')){ - elem.classList.remove('selected'); - newUsersCount--; - } else { - elem.classList.add('selected'); - newUsersCount++; - } - - counterElem.innerText = newUsersCount; - - addUsersBtn.disabled = !newUsersCount; + function _renderEditDialogPage(){ + if(!app.isDashboardLoaded) { + app.renderDashboard(); + } + currentDialog = dialogModule._cache[dialogId]; + + if(!currentDialog) { + dialogModule.dialogId = dialogId; + dialogModule.getDialogById(dialogId).then(function(dialog) { + var tabDataType = dialog.type === CONSTANTS.DIALOG_TYPES.PUBLICCHAT ? 'public' : 'chat', + tab = document.querySelector('.j-sidebar__tab_link[data-type="' + tabDataType + '"]'); + // add to dialog template + app.content.innerHTML = helpers.fillTemplate('tpl_UpdateDialogContainer', {title: dialog.name, _id: dialog._id}); + _renderUsers(dialog.occupants_ids).then(_setEditDialogListeners); + app.loadChatList(tab).then(function(){}) + .catch(function(error){ + console.error(error); + }); + }).catch(function(error){ + router.navigate('#!/dashboard'); }); - - userList.appendChild(userElem); - }); - - }).catch(function(error){ - console.error(error); - }); - } - - function _setEditDiaogListeners(){ - var editTitleBtn = document.querySelector('.j-update_chat__title_button'), - editTitleForm = document.forms.update_chat_name, - editTitleInput = editTitleForm.update_chat__title, - editUsersCountForm = document.forms.update_dialog, - canselBtn = editUsersCountForm.update_dialog_cancel; - - // change Title listener - editTitleBtn.addEventListener('click', function(e){ - e.preventDefault(); - e.stopPropagation(); - - - editTitleForm.classList.toggle('active'); - - if(editTitleForm.classList.contains('active')){ - editTitleInput.removeAttribute('disabled'); - editTitleInput.focus(); } else { - editTitleInput.setAttribute('disabled', true); - _updateDialogTitleRequest(); + app.content.innerHTML = helpers.fillTemplate('tpl_UpdateDialogContainer', {title: currentDialog.name, _id: currentDialog._id}); + _renderUsers(currentDialog.users).then(_setEditDialogListeners); } - }); + } - editTitleInput.addEventListener('input', function(e){ - var titleText = editTitleInput.value, - sylmbolsCount = titleText.length; - if(sylmbolsCount > 40) { - editTitleInput.value = titleText.slice(0, 40); - } - }); + function _renderUsers(dialogOccupants){ + userModule.selectedUserIds = dialogOccupants.slice(); + userModule.disabledUserIds = dialogOccupants.slice(); + return userModule.initGettingUsers('.j-update_chat__user_list'); + } - editTitleForm.addEventListener('submit', function (e) { - e.preventDefault(); + function _setEditDialogListeners(){ + var editTitleBtn = document.querySelector('.j-update_chat__title_button'), + editTitleForm = document.forms.update_chat_name, + addUsersBtn = document.querySelector('.j-update_dialog_btn'), + counterElem = document.querySelector('.j-update__chat_counter'), + editTitleInput = editTitleForm.update_chat__title, + userList = document.querySelector('.j-update_chat__user_list'), + editUsersCountForm = document.forms.update_dialog, + canselBtn = editUsersCountForm.update_dialog_cancel; + + // change Title listener + editTitleBtn.addEventListener('click', function(e){ + e.preventDefault(); + e.stopPropagation(); + + + editTitleForm.classList.toggle('active'); + + if(editTitleForm.classList.contains('active')){ + editTitleInput.removeAttribute('disabled'); + editTitleInput.focus(); + } else { + editTitleInput.setAttribute('disabled', true); + _updateDialogTitleRequest(); + } + }); - _updateDialogTitleRequest(); - }); + editTitleInput.addEventListener('input', function(e){ + var titleText = editTitleInput.value, + sylmbolsCount = titleText.length; + if(sylmbolsCount > 40) { + editTitleInput.value = titleText.slice(0, 40); + } + }); - editUsersCountForm.addEventListener('submit', function(e){ - e.preventDefault(); + userList.addEventListener('click', function (e) { + if (e.target.classList.contains('disabled')) return; + var addUsersCount = userModule.selectedUserIds.filter(function (userId) { + return userModule.disabledUserIds.indexOf(userId) === -1; + }).length; + counterElem.innerText = addUsersCount; + addUsersBtn.disabled = addUsersCount === 0; + }); - var userItemsList = document.querySelectorAll('.user__item.selected:not(.disabled)'), - userList = []; + editTitleForm.addEventListener('submit', function (e) { + e.preventDefault(); - _.each(userItemsList, function(userItem){ - userList.push(+userItem.id); + _updateDialogTitleRequest(); }); - var params = { - id: dialogId, - userList: userList - }; + editUsersCountForm.addEventListener('submit', function(e){ + e.preventDefault(); - dialogModule.updateDialog(params); - }); + var params = { + id: dialogId, + userList: userModule.selectedUserIds + }; - canselBtn.addEventListener('click', function(e){ - e.preventDefault(); - e.stopPropagation(); - router.navigate('/dialog/' + dialogId); - }); + dialogModule.updateDialog(params); + }); - function _updateDialogTitleRequest(){ - var params = { - id: dialogId, - title: editTitleInput.value.trim() - }; + canselBtn.addEventListener('click', function(e){ + e.preventDefault(); + e.stopPropagation(); + router.navigate('/dialog/' + dialogId); + }); - if(dialogModule._cache[dialogId].name !== params.title) { - dialogModule.updateDialog(params); - editTitleForm.classList.remove('active'); - editTitleInput.setAttribute('disabled', true); + function _updateDialogTitleRequest(){ + var params = { + id: dialogId, + title: editTitleInput.value.trim() + }; + + if(dialogModule._cache[dialogId].name !== params.title) { + dialogModule.updateDialog(params); + editTitleForm.classList.remove('active'); + editTitleInput.setAttribute('disabled', true); + } } } + }, + hooks: { + leave: function () { + userModule.reset() + } } } }).resolve(); diff --git a/samples/chat/js/user.js b/samples/chat/js/user.js index 0156e6b57..0959444fa 100644 --- a/samples/chat/js/user.js +++ b/samples/chat/js/user.js @@ -2,26 +2,176 @@ function User() { this._cache = {}; + this.selectedUserIds = []; this.userListConteiner = null; this.content = null; -} + this.userListFilter = null; + this.disabledUserIds = []; + this._isFetching = false; -User.prototype.initGettingUsers = function () { var self = this; - self.content = document.querySelector('.j-content'); - self.userListConteiner = document.querySelector('.j-group_chat__user_list'); + var fullName = ''; + var currentPage = 1; + var totalPages = 1; + var timeout; - self.userListConteiner.classList.add('loading'); + function getUsersFilteredByName () { + helpers.clearView(self.userListConteiner); + self.getUsers({ page: 1 }); + }; - self.getUsers().then(function(userList){ - _.each(userList, function(user){ - self.buildUserItem(self._cache[user.id]); + /** + * @typedef GetUsersParam + * @type {object} + * @property {string} full_name name of user to search by + * @property {string} order see [API docs]{@link https://quickblox.com/developers/Users#Sort} for more info on order format + * @property {number} page number of page to show + * @property {number} per_page limit of items to show per page + */ + /** + * Gets users from API + * @param {GetUsersParam} args + * @returns {Promise} promise resolved with list of users or rejected with error + */ + var _getUsers = function (args) { + if (typeof args !== 'object') { + args = {} + } + var params = { + filter: { + field: 'full_name', + param: 'in', + value: [args.full_name || fullName] + }, + order: args.order || { + field: 'updated_at', + sort: 'desc' + }, + page: args.page || currentPage, + per_page: args.per_page || 100 + }; + + return new Promise(function (resolve, reject) { + self._isFetching = true; + QB.users.listUsers(params, function (err, responce) { + if (err) { + self._isFetching = false; + return reject(err); + } + currentPage = responce.current_page; + totalPages = Math.ceil(responce.total_entries / responce.per_page); + var userList = responce.items.map(function(data){ + return self.addToCache(data.user); + }); + + self._isFetching = false; + resolve(userList); + }); }); - self.userListConteiner.classList.remove('loading'); - }).catch(function(error){ - self.userListConteiner.classList.remove('loading'); - }); + }; + + /** + * Toggles loading state for users list element, load users and append results to the list of users + * @param {GetUsersParam} args search / filter criteria + * @param {boolean} [renderUsers=true] wheter to render fetched users (`true` by default) + */ + this.getUsers = function (args, renderUsers) { + var usersListEl = self.userListConteiner; + usersListEl && usersListEl.classList.add('loading'); + return new Promise(function (resolve, reject) { + _getUsers.call(self, args) + .then(function (users) { + if (renderUsers !== false) { + users.forEach(function (user) { + self.buildUserItem(self._cache[user.id]); + }); + } + usersListEl && usersListEl.classList.remove('loading'); + resolve(users); + }) + .catch(function (err) { + usersListEl && usersListEl.classList.remove('loading'); + reject(err); + }); + }) + }; + + this.filter = function eventHandler (e) { + fullName = e.target.value; + if (timeout) { + clearTimeout(timeout); + timeout = undefined; + } + timeout = setTimeout(getUsersFilteredByName, 600); + }; + + this.scrollHandler = function (e) { + var item = e.target.children.length + ? e.target.children[0] + : undefined; + var itemHeight = item ? item.getBoundingClientRect().height : 0; + var isFetching = self._isFetching; + var scrolledToEnd = ( + e.target.clientHeight + e.target.scrollTop >= e.target.scrollHeight - itemHeight + ); + var shouldLoadNextPage = scrolledToEnd && !isFetching; + var notLastPage = currentPage < totalPages; + if (shouldLoadNextPage && notLastPage) { + self.getUsers({ page: currentPage + 1 }); + } + }; + + this.reset = function () { + currentPage = 1; + fullName = ''; + self.selectedUserIds = []; + self.disabledUserIds = []; + self._isFetching = false; + self.userListContainer = null; + self.userListFilter = null; + } +} + +/** + * @param {string | HTMLElement} userListContainer HTMLElement or selector string (optional) + * @param {string | HTMLElement} userListFilter HTMLElement or selector string (optional) + */ +User.prototype.initGettingUsers = function (userListContainer, userListFilter) { + var userListSelector = '.j-group_chat__user_list'; + var userListFilterSelector = '.group_chat__filter > input'; + if (userListContainer) { + if (userListContainer instanceof HTMLElement) { + this.userListConteiner = userListContainer; + } else { + if (typeof userListContainer === 'string') { + userListSelector = userListContainer; + } + this.userListConteiner = document.querySelector(userListSelector); + } + } else { + this.userListConteiner = document.querySelector(userListSelector); + } + if (userListFilter) { + if (userListFilter instanceof HTMLElement) { + this.userListFilter = userListFilter; + } else { + if (typeof userListFilter === 'string') { + userListFilterSelector = userListFilter; + } + this.userListFilter = document.querySelector(userListFilterSelector) + } + } else { + this.userListFilter = document.querySelector(userListFilterSelector); + } + + this.userListConteiner && + this.userListConteiner.addEventListener('scroll', this.scrollHandler); + + this.userListFilter && + this.userListFilter.addEventListener('input', this.filter); + + return this.getUsers(); }; User.prototype.addToCache = function(user) { @@ -34,10 +184,11 @@ User.prototype.addToCache = function(user) { color: _.random(1, 10), last_request_at: user.last_request_at }; - } else { - self._cache[id].last_request_at = user.last_request_at; + }else if(self._cache[id].name !== user.full_name ){ + self._cache[id].name = user.full_name; } + self._cache[id].last_request_at = user.last_request_at; return self._cache[id]; }; @@ -53,8 +204,10 @@ User.prototype.getUsersByIds = function (userList) { }; return new Promise(function(resolve, reject) { + self._isFetching = true; QB.users.listUsers(params, function (err, responce) { if (err) { + self._isFetching = false; reject(err); } else { var users = responce.items; @@ -68,60 +221,55 @@ User.prototype.getUsersByIds = function (userList) { self.addToCache(user.user); } }); + self._isFetching = false; resolve(); } }); }); }; -User.prototype.getUsers = function () { - var self = this, - params = { - tags: app.user.user_tags, - per_page: 100 - }; - - return new Promise(function(resolve, reject){ - QB.users.get(params, function (err, responce) { - if (err) { - reject(err); - } - - var userList = responce.items.map(function(data){ - return self.addToCache(data.user); - }); - - resolve(userList); - }); - }); -}; - User.prototype.buildUserItem = function (user) { var self = this, userItem = JSON.parse(JSON.stringify(user)); - if(userItem.id === app.user.id){ + if (userItem.id === app.user.id) { + userItem.selected = true; + } + + if (this.disabledUserIds.indexOf(userItem.id) > -1) { userItem.selected = true; } var userTpl = helpers.fillTemplate('tpl_newGroupChatUser', {user: userItem}), elem = helpers.toHtml(userTpl)[0]; + + if (this.selectedUserIds.indexOf(userItem.id) > -1) { + elem.classList.add('selected'); + } elem.addEventListener('click', function () { if (elem.classList.contains('disabled')) return; - + var userId = +elem.getAttribute('id'); + var index = self.selectedUserIds.indexOf(userId); elem.classList.toggle('selected'); - - if (self.userListConteiner.querySelectorAll('.selected').length > 1) { - document.forms.create_dialog.create_dialog_submit.disabled = false; + if (index > -1) { + self.selectedUserIds.splice(index, 1); } else { - document.forms.create_dialog.create_dialog_submit.disabled = true; + self.selectedUserIds.push(userId); } + + if (document.forms.create_dialog) { + if (self.selectedUserIds.length) { + document.forms.create_dialog.create_dialog_submit.disabled = false; + } else { + document.forms.create_dialog.create_dialog_submit.disabled = true; + } - if (self.userListConteiner.querySelectorAll('.selected').length >= 3) { - document.forms.create_dialog.dialog_name.disabled = false; - } else { - document.forms.create_dialog.dialog_name.disabled = true; + if (self.selectedUserIds.length >= 2) { + document.forms.create_dialog.dialog_name.disabled = false; + } else { + document.forms.create_dialog.dialog_name.disabled = true; + } } });