converse-controlbox.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  1. // Converse.js (A browser based XMPP chat client)
  2. // http://conversejs.org
  3. //
  4. // Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com>
  5. // Licensed under the Mozilla Public License (MPLv2)
  6. //
  7. /*global define */
  8. (function (root, factory) {
  9. define(["converse-core",
  10. "tpl!add_contact_dropdown",
  11. "tpl!add_contact_form",
  12. "tpl!change_status_message",
  13. "tpl!chat_status",
  14. "tpl!choose_status",
  15. "tpl!contacts_panel",
  16. "tpl!contacts_tab",
  17. "tpl!controlbox",
  18. "tpl!controlbox_toggle",
  19. "tpl!login_panel",
  20. "tpl!login_tab",
  21. "tpl!search_contact",
  22. "tpl!status_option",
  23. "converse-chatview",
  24. "converse-rosterview"
  25. ], factory);
  26. }(this, function (
  27. converse,
  28. tpl_add_contact_dropdown,
  29. tpl_add_contact_form,
  30. tpl_change_status_message,
  31. tpl_chat_status,
  32. tpl_choose_status,
  33. tpl_contacts_panel,
  34. tpl_contacts_tab,
  35. tpl_controlbox,
  36. tpl_controlbox_toggle,
  37. tpl_login_panel,
  38. tpl_login_tab,
  39. tpl_search_contact,
  40. tpl_status_option
  41. ) {
  42. "use strict";
  43. var USERS_PANEL_ID = 'users';
  44. // Strophe methods for building stanzas
  45. var Strophe = converse.env.Strophe,
  46. Backbone = converse.env.Backbone,
  47. utils = converse.env.utils;
  48. // Other necessary globals
  49. var $ = converse.env.jQuery,
  50. _ = converse.env._,
  51. moment = converse.env.moment;
  52. converse.plugins.add('converse-controlbox', {
  53. overrides: {
  54. // Overrides mentioned here will be picked up by converse.js's
  55. // plugin architecture they will replace existing methods on the
  56. // relevant objects or classes.
  57. //
  58. // New functions which don't exist yet can also be added.
  59. initSession: function () {
  60. this.controlboxtoggle = new this.ControlBoxToggle();
  61. this.__super__.initSession.apply(this, arguments);
  62. },
  63. initConnection: function () {
  64. this.__super__.initConnection.apply(this, arguments);
  65. if (this.connection) {
  66. this.addControlBox();
  67. }
  68. },
  69. _tearDown: function () {
  70. this.__super__._tearDown.apply(this, arguments);
  71. if (this.rosterview) {
  72. this.rosterview.unregisterHandlers();
  73. // Removes roster groups
  74. this.rosterview.model.off().reset();
  75. this.rosterview.each(function (groupview) {
  76. groupview.removeAll();
  77. groupview.remove();
  78. });
  79. this.rosterview.removeAll().remove();
  80. }
  81. },
  82. clearSession: function () {
  83. this.__super__.clearSession.apply(this, arguments);
  84. if (_.isUndefined(this.connection) && this.connection.connected) {
  85. this.chatboxes.get('controlbox').save({'connected': false});
  86. }
  87. },
  88. ChatBoxes: {
  89. chatBoxMayBeShown: function (chatbox) {
  90. return this.__super__.chatBoxMayBeShown.apply(this, arguments) &&
  91. chatbox.get('id') !== 'controlbox';
  92. },
  93. onChatBoxesFetched: function (collection, resp) {
  94. var _converse = this.__super__._converse;
  95. this.__super__.onChatBoxesFetched.apply(this, arguments);
  96. if (!_.includes(_.map(collection, 'id'), 'controlbox')) {
  97. _converse.addControlBox();
  98. }
  99. this.get('controlbox').save({connected:true});
  100. },
  101. },
  102. ChatBoxViews: {
  103. onChatBoxAdded: function (item) {
  104. var _converse = this.__super__._converse;
  105. if (item.get('box_id') === 'controlbox') {
  106. var view = this.get(item.get('id'));
  107. if (view) {
  108. view.model = item;
  109. view.initialize();
  110. return view;
  111. } else {
  112. view = new _converse.ControlBoxView({model: item});
  113. return this.add(item.get('id'), view);
  114. }
  115. } else {
  116. return this.__super__.onChatBoxAdded.apply(this, arguments);
  117. }
  118. },
  119. closeAllChatBoxes: function () {
  120. var _converse = this.__super__._converse;
  121. this.each(function (view) {
  122. if (view.model.get('id') === 'controlbox' &&
  123. (_converse.disconnection_cause !== _converse.LOGOUT || _converse.show_controlbox_by_default)) {
  124. return;
  125. }
  126. view.close();
  127. });
  128. return this;
  129. },
  130. getChatBoxWidth: function (view) {
  131. var _converse = this.__super__._converse;
  132. var controlbox = this.get('controlbox');
  133. if (view.model.get('id') === 'controlbox') {
  134. /* We return the width of the controlbox or its toggle,
  135. * depending on which is visible.
  136. */
  137. if (!controlbox || !controlbox.$el.is(':visible')) {
  138. return _converse.controlboxtoggle.$el.outerWidth(true);
  139. } else {
  140. return controlbox.$el.outerWidth(true);
  141. }
  142. } else {
  143. return this.__super__.getChatBoxWidth.apply(this, arguments);
  144. }
  145. }
  146. },
  147. ChatBox: {
  148. initialize: function () {
  149. if (this.get('id') === 'controlbox') {
  150. this.set({'time_opened': moment(0).valueOf()});
  151. } else {
  152. this.__super__.initialize.apply(this, arguments);
  153. }
  154. },
  155. },
  156. ChatBoxView: {
  157. insertIntoDOM: function () {
  158. var _converse = this.__super__._converse;
  159. this.$el.insertAfter(_converse.chatboxviews.get("controlbox").$el);
  160. return this;
  161. }
  162. }
  163. },
  164. initialize: function () {
  165. /* The initialize function gets called as soon as the plugin is
  166. * loaded by converse.js's plugin machinery.
  167. */
  168. var _converse = this._converse,
  169. __ = _converse.__;
  170. this.updateSettings({
  171. allow_logout: true,
  172. default_domain: undefined,
  173. show_controlbox_by_default: false,
  174. sticky_controlbox: false,
  175. xhr_user_search: false,
  176. xhr_user_search_url: ''
  177. });
  178. var LABEL_CONTACTS = __('Contacts');
  179. _converse.addControlBox = function () {
  180. return _converse.chatboxes.add({
  181. id: 'controlbox',
  182. box_id: 'controlbox',
  183. closed: !_converse.show_controlbox_by_default
  184. });
  185. };
  186. _converse.ControlBoxView = _converse.ChatBoxView.extend({
  187. tagName: 'div',
  188. className: 'chatbox',
  189. id: 'controlbox',
  190. events: {
  191. 'click a.close-chatbox-button': 'close',
  192. 'click ul#controlbox-tabs li a': 'switchTab',
  193. },
  194. initialize: function () {
  195. this.$el.insertAfter(_converse.controlboxtoggle.$el);
  196. this.model.on('change:connected', this.onConnected, this);
  197. this.model.on('destroy', this.hide, this);
  198. this.model.on('hide', this.hide, this);
  199. this.model.on('show', this.show, this);
  200. this.model.on('change:closed', this.ensureClosedState, this);
  201. this.render();
  202. if (this.model.get('connected')) {
  203. this.insertRoster();
  204. }
  205. },
  206. render: function () {
  207. if (this.model.get('connected')) {
  208. if (_.isUndefined(this.model.get('closed'))) {
  209. this.model.set('closed', !_converse.show_controlbox_by_default);
  210. }
  211. }
  212. if (!this.model.get('closed')) {
  213. this.show();
  214. } else {
  215. this.hide();
  216. }
  217. this.el.innerHTML = tpl_controlbox(
  218. _.extend(this.model.toJSON(), {
  219. 'sticky_controlbox': _converse.sticky_controlbox
  220. }));
  221. if (!_converse.connection.connected ||
  222. !_converse.connection.authenticated ||
  223. _converse.connection.disconnecting) {
  224. this.renderLoginPanel();
  225. } else if (this.model.get('connected') &&
  226. (!this.contactspanel || !this.contactspanel.$el.is(':visible'))) {
  227. this.renderContactsPanel();
  228. }
  229. return this;
  230. },
  231. onConnected: function () {
  232. if (this.model.get('connected')) {
  233. this.render().insertRoster();
  234. this.model.save();
  235. }
  236. },
  237. insertRoster: function () {
  238. /* Place the rosterview inside the "Contacts" panel.
  239. */
  240. this.contactspanel.$el.append(_converse.rosterview.$el);
  241. return this;
  242. },
  243. renderLoginPanel: function () {
  244. this.loginpanel = new _converse.LoginPanel({
  245. '$parent': this.$el.find('.controlbox-panes'),
  246. 'model': this
  247. });
  248. this.loginpanel.render();
  249. return this;
  250. },
  251. renderContactsPanel: function () {
  252. if (_.isUndefined(this.model.get('active-panel'))) {
  253. this.model.save({'active-panel': USERS_PANEL_ID});
  254. }
  255. this.contactspanel = new _converse.ContactsPanel({
  256. '$parent': this.$el.find('.controlbox-panes')
  257. });
  258. this.contactspanel.insertIntoDOM();
  259. _converse.xmppstatusview = new _converse.XMPPStatusView({
  260. 'model': _converse.xmppstatus
  261. });
  262. _converse.xmppstatusview.render();
  263. },
  264. close: function (ev) {
  265. if (ev && ev.preventDefault) { ev.preventDefault(); }
  266. if (_converse.connection.connected && !_converse.connection.disconnecting) {
  267. this.model.save({'closed': true});
  268. } else {
  269. this.model.trigger('hide');
  270. }
  271. _converse.emit('controlBoxClosed', this);
  272. return this;
  273. },
  274. ensureClosedState: function () {
  275. if (this.model.get('closed')) {
  276. this.hide();
  277. } else {
  278. this.show();
  279. }
  280. },
  281. hide: function (callback) {
  282. this.$el.addClass('hidden');
  283. utils.refreshWebkit();
  284. _converse.emit('chatBoxClosed', this);
  285. if (!_converse.connection.connected) {
  286. _converse.controlboxtoggle.render();
  287. }
  288. _converse.controlboxtoggle.show(callback);
  289. return this;
  290. },
  291. onControlBoxToggleHidden: function () {
  292. var that = this;
  293. utils.fadeIn(this.el, function () {
  294. _converse.controlboxtoggle.updateOnlineCount();
  295. utils.refreshWebkit();
  296. that.model.set('closed', false);
  297. _converse.emit('controlBoxOpened', that);
  298. });
  299. },
  300. show: function () {
  301. _converse.controlboxtoggle.hide(
  302. this.onControlBoxToggleHidden.bind(this)
  303. );
  304. return this;
  305. },
  306. switchTab: function (ev) {
  307. // TODO: automatically focus the relevant input
  308. if (ev && ev.preventDefault) { ev.preventDefault(); }
  309. var $tab = $(ev.target),
  310. $sibling = $tab.parent().siblings('li').children('a'),
  311. $tab_panel = $($tab.attr('href'));
  312. $($sibling.attr('href')).addClass('hidden');
  313. $sibling.removeClass('current');
  314. $tab.addClass('current');
  315. $tab_panel.removeClass('hidden');
  316. if (!_.isUndefined(_converse.chatboxes.browserStorage)) {
  317. this.model.save({'active-panel': $tab.data('id')});
  318. }
  319. return this;
  320. },
  321. showHelpMessages: function () {
  322. /* Override showHelpMessages in ChatBoxView, for now do nothing.
  323. *
  324. * Parameters:
  325. * (Array) msgs: Array of messages
  326. */
  327. return;
  328. }
  329. });
  330. _converse.LoginPanel = Backbone.View.extend({
  331. tagName: 'div',
  332. id: "login-dialog",
  333. className: 'controlbox-pane',
  334. events: {
  335. 'submit form#converse-login': 'authenticate'
  336. },
  337. initialize: function (cfg) {
  338. cfg.$parent.html(this.$el.html(
  339. tpl_login_panel({
  340. 'ANONYMOUS': _converse.ANONYMOUS,
  341. 'EXTERNAL': _converse.EXTERNAL,
  342. 'LOGIN': _converse.LOGIN,
  343. 'PREBIND': _converse.PREBIND,
  344. 'auto_login': _converse.auto_login,
  345. 'authentication': _converse.authentication,
  346. 'label_username': __('XMPP Username:'),
  347. 'label_password': __('Password:'),
  348. 'label_anon_login': __('Click here to log in anonymously'),
  349. 'label_login': __('Log In'),
  350. 'placeholder_username': (_converse.locked_domain || _converse.default_domain) && __('Username') || __('user@server'),
  351. 'placeholder_password': __('password')
  352. })
  353. ));
  354. this.$tabs = cfg.$parent.parent().find('#controlbox-tabs');
  355. },
  356. render: function () {
  357. this.$tabs.append(tpl_login_tab({label_sign_in: __('Sign in')}));
  358. this.$el.find('input#jid').focus();
  359. if (!this.$el.is(':visible')) {
  360. this.$el.show();
  361. }
  362. return this;
  363. },
  364. authenticate: function (ev) {
  365. if (ev && ev.preventDefault) { ev.preventDefault(); }
  366. var $form = $(ev.target);
  367. if (_converse.authentication === _converse.ANONYMOUS) {
  368. this.connect($form, _converse.jid, null);
  369. return;
  370. }
  371. var $jid_input = $form.find('input[name=jid]'),
  372. jid = $jid_input.val(),
  373. $pw_input = $form.find('input[name=password]'),
  374. password = $pw_input.val(),
  375. errors = false;
  376. if (!jid) {
  377. errors = true;
  378. $jid_input.addClass('error');
  379. }
  380. if (!password && _converse.authentication !== _converse.EXTERNAL) {
  381. errors = true;
  382. $pw_input.addClass('error');
  383. }
  384. if (errors) { return; }
  385. if (_converse.locked_domain) {
  386. jid = Strophe.escapeNode(jid) + '@' + _converse.locked_domain;
  387. } else if (_converse.default_domain && !_.includes(jid, '@')) {
  388. jid = jid + '@' + _converse.default_domain;
  389. }
  390. this.connect($form, jid, password);
  391. return false;
  392. },
  393. connect: function ($form, jid, password) {
  394. var resource;
  395. if ($form) {
  396. $form.find('input[type=submit]').hide().after('<span class="spinner login-submit"/>');
  397. }
  398. if (jid) {
  399. resource = Strophe.getResourceFromJid(jid);
  400. if (!resource) {
  401. jid = jid.toLowerCase() + _converse.generateResource();
  402. } else {
  403. jid = Strophe.getBareJidFromJid(jid).toLowerCase()+'/'+resource;
  404. }
  405. }
  406. _converse.connection.reset();
  407. _converse.connection.connect(jid, password, _converse.onConnectStatusChanged);
  408. },
  409. remove: function () {
  410. this.$tabs.empty();
  411. this.$el.parent().empty();
  412. }
  413. });
  414. _converse.XMPPStatusView = Backbone.View.extend({
  415. el: "form#set-xmpp-status",
  416. events: {
  417. "click a.choose-xmpp-status": "toggleOptions",
  418. "click #fancy-xmpp-status-select a.change-xmpp-status-message": "renderStatusChangeForm",
  419. "submit": "setStatusMessage",
  420. "click .dropdown dd ul li a": "setStatus"
  421. },
  422. initialize: function () {
  423. this.model.on("change:status", this.updateStatusUI, this);
  424. this.model.on("change:status_message", this.updateStatusUI, this);
  425. this.model.on("update-status-ui", this.updateStatusUI, this);
  426. },
  427. render: function () {
  428. // Replace the default dropdown with something nicer
  429. var $select = this.$el.find('select#select-xmpp-status'),
  430. chat_status = this.model.get('status') || 'offline',
  431. options = $('option', $select),
  432. $options_target,
  433. options_list = [];
  434. this.$el.html(tpl_choose_status());
  435. this.$el.find('#fancy-xmpp-status-select')
  436. .html(tpl_chat_status({
  437. 'status_message': this.model.get('status_message') || __("I am %1$s", this.getPrettyStatus(chat_status)),
  438. 'chat_status': chat_status,
  439. 'desc_custom_status': __('Click here to write a custom status message'),
  440. 'desc_change_status': __('Click to change your chat status')
  441. }));
  442. // iterate through all the <option> elements and add option values
  443. options.each(function () {
  444. options_list.push(tpl_status_option({
  445. 'value': $(this).val(),
  446. 'text': this.text
  447. }));
  448. });
  449. $options_target = this.$el.find("#target dd ul").hide();
  450. $options_target.append(options_list.join(''));
  451. $select.remove();
  452. return this;
  453. },
  454. toggleOptions: function (ev) {
  455. ev.preventDefault();
  456. $(ev.target).parent().parent().siblings('dd').find('ul').toggle('fast');
  457. },
  458. renderStatusChangeForm: function (ev) {
  459. ev.preventDefault();
  460. var status_message = _converse.xmppstatus.get('status_message') || '';
  461. var input = tpl_change_status_message({
  462. 'status_message': status_message,
  463. 'label_custom_status': __('Custom status'),
  464. 'label_save': __('Save')
  465. });
  466. var $xmppstatus = this.$el.find('.xmpp-status');
  467. $xmppstatus.parent().addClass('no-border');
  468. $xmppstatus.replaceWith(input);
  469. this.$el.find('.custom-xmpp-status').focus().focus();
  470. },
  471. setStatusMessage: function (ev) {
  472. ev.preventDefault();
  473. this.model.setStatusMessage($(ev.target).find('input').val());
  474. },
  475. setStatus: function (ev) {
  476. ev.preventDefault();
  477. var $el = $(ev.currentTarget),
  478. value = $el.attr('data-value');
  479. if (value === 'logout') {
  480. this.$el.find(".dropdown dd ul").hide();
  481. _converse.logOut();
  482. } else {
  483. this.model.setStatus(value);
  484. this.$el.find(".dropdown dd ul").hide();
  485. }
  486. },
  487. getPrettyStatus: function (stat) {
  488. if (stat === 'chat') {
  489. return __('online');
  490. } else if (stat === 'dnd') {
  491. return __('busy');
  492. } else if (stat === 'xa') {
  493. return __('away for long');
  494. } else if (stat === 'away') {
  495. return __('away');
  496. } else if (stat === 'offline') {
  497. return __('offline');
  498. } else {
  499. return __(stat) || __('online');
  500. }
  501. },
  502. updateStatusUI: function (model) {
  503. var stat = model.get('status');
  504. // For translators: the %1$s part gets replaced with the status
  505. // Example, I am online
  506. var status_message = model.get('status_message') || __("I am %1$s", this.getPrettyStatus(stat));
  507. this.$el.find('#fancy-xmpp-status-select').removeClass('no-border').html(
  508. tpl_chat_status({
  509. 'chat_status': stat,
  510. 'status_message': status_message,
  511. 'desc_custom_status': __('Click here to write a custom status message'),
  512. 'desc_change_status': __('Click to change your chat status')
  513. }));
  514. }
  515. });
  516. _converse.ContactsPanel = Backbone.View.extend({
  517. tagName: 'div',
  518. className: 'controlbox-pane',
  519. id: 'users',
  520. events: {
  521. 'click a.toggle-xmpp-contact-form': 'toggleContactForm',
  522. 'submit form.add-xmpp-contact': 'addContactFromForm',
  523. 'submit form.search-xmpp-contact': 'searchContacts',
  524. 'click a.subscribe-to-user': 'addContactFromList'
  525. },
  526. initialize: function (cfg) {
  527. this.parent_el = cfg.$parent[0];
  528. this.tab_el = document.createElement('li');
  529. _converse.chatboxes.on('change:num_unread', this.render, this);
  530. },
  531. render: function () {
  532. var controlbox = _converse.chatboxes.get('controlbox');
  533. var is_current = controlbox.get('active-panel') === USERS_PANEL_ID;
  534. this.tab_el.innerHTML = tpl_contacts_tab({
  535. 'label_contacts': LABEL_CONTACTS,
  536. 'is_current': is_current,
  537. 'num_unread': _.sum(_converse.chatboxes.pluck('num_unread'))
  538. });
  539. var widgets = tpl_contacts_panel({
  540. label_online: __('Online'),
  541. label_busy: __('Busy'),
  542. label_away: __('Away'),
  543. label_offline: __('Offline'),
  544. label_logout: __('Log out'),
  545. include_offline_state: _converse.include_offline_state,
  546. allow_logout: _converse.allow_logout
  547. });
  548. if (_converse.allow_contact_requests) {
  549. widgets += tpl_add_contact_dropdown({
  550. label_click_to_chat: __('Click to add new chat contacts'),
  551. label_add_contact: __('Add a contact')
  552. });
  553. }
  554. this.el.innerHTML = widgets;
  555. if (!is_current) {
  556. this.$el.addClass('hidden');
  557. }
  558. return this;
  559. },
  560. insertIntoDOM: function () {
  561. this.parent_el.appendChild(this.render().el);
  562. this.tabs = this.parent_el.parentNode.querySelector('#controlbox-tabs');
  563. this.tabs.appendChild(this.tab_el);
  564. this.$el.find('.search-xmpp ul').append(
  565. this.generateAddContactHTML()
  566. );
  567. return this;
  568. },
  569. generateAddContactHTML: function () {
  570. if (_converse.xhr_user_search) {
  571. return tpl_search_contact({
  572. label_contact_name: __('Contact name'),
  573. label_search: __('Search')
  574. });
  575. } else {
  576. return tpl_add_contact_form({
  577. label_contact_username: __('e.g. user@example.org'),
  578. label_add: __('Add')
  579. });
  580. }
  581. },
  582. toggleContactForm: function (ev) {
  583. ev.preventDefault();
  584. this.$el.find('.search-xmpp').toggle('fast', function () {
  585. if ($(this).is(':visible')) {
  586. $(this).find('input.username').focus();
  587. }
  588. });
  589. },
  590. searchContacts: function (ev) {
  591. ev.preventDefault();
  592. $.getJSON(_converse.xhr_user_search_url+ "?q=" + $(ev.target).find('input.username').val(), function (data) {
  593. var $ul= $('.search-xmpp ul');
  594. $ul.find('li.found-user').remove();
  595. $ul.find('li.chat-info').remove();
  596. if (!data.length) {
  597. $ul.append('<li class="chat-info">'+__('No users found')+'</li>');
  598. }
  599. $(data).each(function (idx, obj) {
  600. $ul.append(
  601. $('<li class="found-user"></li>')
  602. .append(
  603. $('<a class="subscribe-to-user" href="#" title="'+__('Click to add as a chat contact')+'"></a>')
  604. .attr('data-recipient', Strophe.getNodeFromJid(obj.id)+"@"+Strophe.getDomainFromJid(obj.id))
  605. .text(obj.fullname)
  606. )
  607. );
  608. });
  609. });
  610. },
  611. addContactFromForm: function (ev) {
  612. ev.preventDefault();
  613. var $input = $(ev.target).find('input');
  614. var jid = $input.val();
  615. if (! jid) {
  616. // this is not a valid JID
  617. $input.addClass('error');
  618. return;
  619. }
  620. _converse.roster.addAndSubscribe(jid);
  621. $('.search-xmpp').hide();
  622. },
  623. addContactFromList: function (ev) {
  624. ev.preventDefault();
  625. var $target = $(ev.target),
  626. jid = $target.attr('data-recipient'),
  627. name = $target.text();
  628. _converse.roster.addAndSubscribe(jid, name);
  629. $target.parent().remove();
  630. $('.search-xmpp').hide();
  631. }
  632. });
  633. _converse.ControlBoxToggle = Backbone.View.extend({
  634. tagName: 'a',
  635. className: 'toggle-controlbox hidden',
  636. id: 'toggle-controlbox',
  637. events: {
  638. 'click': 'onClick'
  639. },
  640. attributes: {
  641. 'href': "#"
  642. },
  643. initialize: function () {
  644. _converse.chatboxviews.$el.prepend(this.render());
  645. this.updateOnlineCount();
  646. var that = this;
  647. _converse.on('initialized', function () {
  648. _converse.roster.on("add", that.updateOnlineCount, that);
  649. _converse.roster.on('change', that.updateOnlineCount, that);
  650. _converse.roster.on("destroy", that.updateOnlineCount, that);
  651. _converse.roster.on("remove", that.updateOnlineCount, that);
  652. });
  653. },
  654. render: function () {
  655. // We let the render method of ControlBoxView decide whether
  656. // the ControlBox or the Toggle must be shown. This prevents
  657. // artifacts (i.e. on page load the toggle is shown only to then
  658. // seconds later be hidden in favor of the control box).
  659. return this.$el.html(
  660. tpl_controlbox_toggle({
  661. 'label_toggle': __('Toggle chat')
  662. })
  663. );
  664. },
  665. updateOnlineCount: _.debounce(function () {
  666. if (_.isUndefined(_converse.roster)) {
  667. return;
  668. }
  669. var $count = this.$('#online-count');
  670. $count.text('('+_converse.roster.getNumOnlineContacts()+')');
  671. if (!$count.is(':visible')) {
  672. $count.show();
  673. }
  674. }, _converse.animate ? 100 : 0),
  675. hide: function (callback) {
  676. this.el.classList.add('hidden');
  677. callback();
  678. },
  679. show: function (callback) {
  680. utils.fadeIn(this.el, callback);
  681. },
  682. showControlBox: function () {
  683. var controlbox = _converse.chatboxes.get('controlbox');
  684. if (!controlbox) {
  685. controlbox = _converse.addControlBox();
  686. }
  687. if (_converse.connection.connected) {
  688. controlbox.save({closed: false});
  689. } else {
  690. controlbox.trigger('show');
  691. }
  692. },
  693. onClick: function (e) {
  694. e.preventDefault();
  695. if ($("div#controlbox").is(':visible')) {
  696. var controlbox = _converse.chatboxes.get('controlbox');
  697. if (_converse.connection.connected) {
  698. controlbox.save({closed: true});
  699. } else {
  700. controlbox.trigger('hide');
  701. }
  702. } else {
  703. this.showControlBox();
  704. }
  705. }
  706. });
  707. var disconnect = function () {
  708. /* Upon disconnection, set connected to `false`, so that if
  709. * we reconnect,
  710. * "onConnected" will be called, to fetch the roster again and
  711. * to send out a presence stanza.
  712. */
  713. var view = _converse.chatboxviews.get('controlbox');
  714. view.model.set({connected:false});
  715. view.$('#controlbox-tabs').empty();
  716. view.renderLoginPanel();
  717. };
  718. _converse.on('disconnected', disconnect);
  719. var afterReconnected = function () {
  720. /* After reconnection makes sure the controlbox's is aware.
  721. */
  722. var view = _converse.chatboxviews.get('controlbox');
  723. if (view.model.get('connected')) {
  724. _converse.chatboxviews.get("controlbox").onConnected();
  725. } else {
  726. view.model.set({connected:true});
  727. }
  728. };
  729. _converse.on('reconnected', afterReconnected);
  730. }
  731. });
  732. }));