Some more refactoring now that we use a vdom for the login panel

We can now simply call `render` whenever we want to show an error or
notification message.

The flip side is that the template is now larger and contains more login.
This commit is contained in:
JC Brand 2017-09-22 18:55:02 +02:00
parent be7f8ab4c4
commit 92af05d510
9 changed files with 163 additions and 135 deletions

View File

@ -1977,13 +1977,14 @@
font-size: 85%; } font-size: 85%; }
#conversejs #controlbox #converse-register .instructions:hover { #conversejs #controlbox #converse-register .instructions:hover {
color: #777; } color: #777; }
#conversejs #controlbox .conn-feedback p { #conversejs #controlbox .conn-feedback {
color: #578EA9; color: #578EA9; }
padding-bottom: 0.5em; } #conversejs #controlbox .conn-feedback.error {
#conversejs #controlbox .conn-feedback p.feedback-subject.error {
font-weight: bold; }
#conversejs #controlbox .conn-feedback p.error {
color: #A53214; } color: #A53214; }
#conversejs #controlbox .conn-feedback p {
padding-bottom: 0.5em; }
#conversejs #controlbox .conn-feedback p.feedback-subject.error {
font-weight: bold; }
#conversejs #controlbox .brand-heading-container .brand-heading { #conversejs #controlbox .brand-heading-container .brand-heading {
text-align: left; text-align: left;
font-size: 150%; } font-size: 150%; }

View File

@ -2065,13 +2065,14 @@ body {
font-size: 85%; } font-size: 85%; }
#conversejs #controlbox #converse-register .instructions:hover { #conversejs #controlbox #converse-register .instructions:hover {
color: #777; } color: #777; }
#conversejs #controlbox .conn-feedback p { #conversejs #controlbox .conn-feedback {
color: #578EA9; color: #578EA9; }
padding-bottom: 0.5em; } #conversejs #controlbox .conn-feedback.error {
#conversejs #controlbox .conn-feedback p.feedback-subject.error {
font-weight: bold; }
#conversejs #controlbox .conn-feedback p.error {
color: #A53214; } color: #A53214; }
#conversejs #controlbox .conn-feedback p {
padding-bottom: 0.5em; }
#conversejs #controlbox .conn-feedback p.feedback-subject.error {
font-weight: bold; }
#conversejs #controlbox .brand-heading-container .brand-heading { #conversejs #controlbox .brand-heading-container .brand-heading {
text-align: left; text-align: left;
font-size: 150%; } font-size: 150%; }
@ -2387,8 +2388,6 @@ body {
width: 200px; width: 200px;
float: left; float: left;
margin: 0; } margin: 0; }
#conversejs #controlbox .conn-feedback {
padding-top: 2em; }
#conversejs #controlbox .toggle-register-login { #conversejs #controlbox .toggle-register-login {
line-height: 30px; } line-height: 30px; }
#conversejs #controlbox .brand-heading-container { #conversejs #controlbox .brand-heading-container {

View File

@ -115,15 +115,15 @@
} }
.conn-feedback { .conn-feedback {
color: $controlbox-head-color;
&.error {
color: $error-color;
}
p { p {
color: $controlbox-head-color;
padding-bottom: 0.5em; padding-bottom: 0.5em;
&.feedback-subject.error { &.feedback-subject.error {
font-weight: bold; font-weight: bold;
} }
&.error {
color: $error-color;
}
} }
} }

View File

@ -5,10 +5,6 @@
float: left; float: left;
margin: 0; margin: 0;
.conn-feedback {
padding-top: 2em;
}
.toggle-register-login { .toggle-register-login {
line-height: $line-height-huge; line-height: $line-height-huge;
} }

View File

@ -58,6 +58,43 @@
const CHATBOX_TYPE = 'chatbox'; const CHATBOX_TYPE = 'chatbox';
const { Strophe, Backbone, Promise, utils, _, moment } = converse.env; const { Strophe, Backbone, Promise, utils, _, moment } = converse.env;
const CONNECTION_STATUS_CSS_CLASS = {
'Error': 'error',
'Connecting': 'info',
'Connection failure': 'error',
'Authenticating': 'info',
'Authentication failure': 'error',
'Connected': 'info',
'Disconnected': 'error',
'Disconnecting': 'warn',
'Attached': 'info',
'Redirect': 'info',
'Reconnecting': 'warn'
};
const PRETTY_CONNECTION_STATUS = {
0: 'Error',
1: 'Connecting',
2: 'Connection failure',
3: 'Authenticating',
4: 'Authentication failure',
5: 'Connected',
6: 'Disconnected',
7: 'Disconnecting',
8: 'Attached',
9: 'Redirect',
10: 'Reconnecting'
};
const REPORTABLE_STATUSES = [
0, // ERROR'
1, // CONNECTING
2, // CONNFAIL
3, // AUTHENTICATING
4, // AUTHFAIL
7, // DISCONNECTING
10 // RECONNECTING
];
converse.plugins.add('converse-controlbox', { converse.plugins.add('converse-controlbox', {
@ -299,7 +336,9 @@
renderLoginPanel () { renderLoginPanel () {
this.el.classList.add("logged-out"); this.el.classList.add("logged-out");
if (_.isNil(this.loginpanel)) { if (_.isNil(this.loginpanel)) {
this.loginpanel = new _converse.LoginPanel({'model': this}); this.loginpanel = new _converse.LoginPanel({
'model': new _converse.LoginPanelModel()
});
const panes = this.el.querySelector('.controlbox-panes'); const panes = this.el.querySelector('.controlbox-panes');
panes.innerHTML = ''; panes.innerHTML = '';
panes.appendChild(this.loginpanel.render().el); panes.appendChild(this.loginpanel.render().el);
@ -413,31 +452,51 @@
} }
}); });
_converse.LoginPanelModel = Backbone.Model.extend({
defaults: {
'errors': [],
}
});
_converse.LoginPanel = Backbone.View.extend({ _converse.LoginPanel = Backbone.View.extend({
tagName: 'div', tagName: 'div',
id: "converse-login-panel", id: "converse-login-panel",
className: 'controlbox-pane fade-in', className: 'controlbox-pane fade-in',
events: { events: {
'submit form#converse-login': 'authenticate' 'submit form#converse-login': 'authenticate',
'blur input': 'validate'
}, },
initialize (cfg) { initialize (cfg) {
_converse.connfeedback.on('change', this.renderConnectionFeedback, this); this.model.on('change', this.render, this);
this.listenTo(_converse.connfeedback, 'change', this.render);
}, },
render () { render () {
const html = tpl_login_panel({ const connection_status = _converse.connfeedback.get('connection_status');
'__': __, let feedback_class, pretty_status;
'ANONYMOUS': _converse.ANONYMOUS, if (_.includes(REPORTABLE_STATUSES, connection_status)) {
'EXTERNAL': _converse.EXTERNAL, pretty_status = PRETTY_CONNECTION_STATUS[connection_status];
'LOGIN': _converse.LOGIN, feedback_class = CONNECTION_STATUS_CSS_CLASS[pretty_status];
'PREBIND': _converse.PREBIND, }
'auto_login': _converse.auto_login, const html = tpl_login_panel(
'authentication': _converse.authentication, _.extend(this.model.toJSON(), {
'label_anon_login': __('Click here to log in anonymously'), '__': __,
'placeholder_username': (_converse.locked_domain || _converse.default_domain) && __('Username') || __('user@domain'), '_converse': _converse,
}); 'ANONYMOUS': _converse.ANONYMOUS,
'EXTERNAL': _converse.EXTERNAL,
'LOGIN': _converse.LOGIN,
'PREBIND': _converse.PREBIND,
'auto_login': _converse.auto_login,
'authentication': _converse.authentication,
'connection_status': connection_status,
'conn_feedback_class': feedback_class,
'conn_feedback_subject': pretty_status,
'conn_feedback_message': _converse.connfeedback.get('message'),
'placeholder_username': (_converse.locked_domain || _converse.default_domain) &&
__('Username') || __('user@domain'),
})
);
const form = this.el.querySelector('form'); const form = this.el.querySelector('form');
if (_.isNull(form)) { if (_.isNull(form)) {
this.el.innerHTML = html; this.el.innerHTML = html;
@ -445,75 +504,47 @@
const patches = vdom.diff(vdom_parser(form), vdom_parser(html)); const patches = vdom.diff(vdom_parser(form), vdom_parser(html));
vdom.patch(form, patches); vdom.patch(form, patches);
} }
this.renderConnectionFeedback();
return this; return this;
}, },
renderConnectionFeedback () { validate () {
const feedback_html = tpl_login_feedback({
'conn_feedback_class': _converse.connfeedback.get('klass'),
'conn_feedback_subject': _converse.connfeedback.get('subject'),
'conn_feedback_message': _converse.connfeedback.get('message'),
});
const feedback_el = this.el.querySelector('.conn-feedback');
if (_.isNull(feedback_el)) {
this.el.insertAdjacentHTML('afterbegin', feedback_html);
} else {
feedback_el.outerHTML = feedback_html;
}
},
showSpinner (only_submit_button=false) {
const form = this.el.querySelector('form'); const form = this.el.querySelector('form');
if (only_submit_button) { const jid = form.querySelector('input[name=jid]').value;
const button = form.querySelector('input[type=submit]'); const password = _.get(form.querySelector('input[name=password]'), 'value');
button.classList.add('hidden'); const errors = [];
button.insertAdjacentHTML('afterend', '<span class="spinner login-submit"/>'); if (!jid || (
} else { !_converse.locked_domain &&
form.innerHTML = tpl_spinner(); !_converse.default_domain &&
_.filter(jid.split('@')).length < 2)) {
errors.push(errors, 'invalid_jid');
} }
return this; if (!password && _converse.authentication !== _converse.EXTERNAL) {
errors.push(errors, 'password_required');
}
this.model.set('errors', errors);
return errors.length == 0;
}, },
authenticate (ev) { authenticate (ev) {
/* Authenticate the user based on a form submission event. /* Authenticate the user based on a form submission event.
*/ */
if (ev && ev.preventDefault) { ev.preventDefault(); } if (ev && ev.preventDefault) { ev.preventDefault(); }
const $form = $(ev.target);
if (_converse.authentication === _converse.ANONYMOUS) { if (_converse.authentication === _converse.ANONYMOUS) {
this.showSpinner().connect(_converse.jid, null); this.connect(_converse.jid, null);
return; return;
} }
const $jid_input = $form.find('input[name=jid]'); if (!this.validate()) {
const $jid_error_msg = $form.find('.invalid-jid-msg'); return;
const $pw_input = $form.find('input[name=password]');
const password = $pw_input.val();
let jid = $jid_input.val(),
errors = false;
if (!jid || (
!_converse.locked_domain &&
!_converse.default_domain &&
_.filter(jid.split('@')).length < 2)) {
errors = true;
$jid_input.addClass('error');
$jid_error_msg.removeClass('hidden');
} else {
$jid_error_msg.addClass('hidden');
} }
let jid = ev.target.querySelector('input[name=jid]').value;
const password = _.get(ev.target.querySelector('input[name=password]'), 'value');
if (!password && _converse.authentication !== _converse.EXTERNAL) {
errors = true;
$pw_input.addClass('error');
}
if (errors) { return; }
if (_converse.locked_domain) { if (_converse.locked_domain) {
jid = Strophe.escapeNode(jid) + '@' + _converse.locked_domain; jid = Strophe.escapeNode(jid) + '@' + _converse.locked_domain;
} else if (_converse.default_domain && !_.includes(jid, '@')) { } else if (_converse.default_domain && !_.includes(jid, '@')) {
jid = jid + '@' + _converse.default_domain; jid = jid + '@' + _converse.default_domain;
} }
this.showSpinner(true).connect(jid, password); this.connect(jid, password);
return false; return false;
}, },

View File

@ -100,7 +100,7 @@
_converse.OPENED = 'opened'; _converse.OPENED = 'opened';
_converse.PREBIND = "prebind"; _converse.PREBIND = "prebind";
_converse.PRETTY_CONNECTION_STATUS = { _converse.CONNECTION_STATUS = {
0: 'ERROR', 0: 'ERROR',
1: 'CONNECTING', 1: 'CONNECTING',
2: 'CONNFAIL', 2: 'CONNFAIL',
@ -110,7 +110,8 @@
6: 'DISCONNECTED', 6: 'DISCONNECTED',
7: 'DISCONNECTING', 7: 'DISCONNECTING',
8: 'ATTACHED', 8: 'ATTACHED',
9: 'REDIRECT' 9: 'REDIRECT',
10: 'RECONNECTING'
}; };
_converse.DEFAULT_IMAGE_TYPE = 'image/png'; _converse.DEFAULT_IMAGE_TYPE = 'image/png';
@ -407,10 +408,9 @@
_converse.everySecondTrigger = window.setInterval(_converse.onEverySecond, 1000); _converse.everySecondTrigger = window.setInterval(_converse.onEverySecond, 1000);
}; };
this.giveFeedback = function (subject, klass, message) { this.setConnectionStatus = function (connection_status, message) {
_converse.connfeedback.set({ _converse.connfeedback.set({
'subject': subject, 'connection_status': connection_status,
'klass': klass,
'message': message 'message': message
}); });
}; };
@ -431,9 +431,8 @@
this.reconnect = _.debounce(function () { this.reconnect = _.debounce(function () {
_converse.log('RECONNECTING'); _converse.log('RECONNECTING');
_converse.log('The connection has dropped, attempting to reconnect.'); _converse.log('The connection has dropped, attempting to reconnect.');
_converse.giveFeedback( _converse.setConnectionStatus(
__("Reconnecting"), Strophe.Status.RECONNECTING,
'warn',
__('The connection has dropped, attempting to reconnect.') __('The connection has dropped, attempting to reconnect.')
); );
_converse.connection.reconnecting = true; _converse.connection.reconnecting = true;
@ -495,9 +494,9 @@
* through various states while establishing or tearing down a * through various states while establishing or tearing down a
* connection. * connection.
*/ */
_converse.log(`Status changed to: ${_converse.PRETTY_CONNECTION_STATUS[status]}`); _converse.log(`Status changed to: ${_converse.CONNECTION_STATUS[status]}`);
if (status === Strophe.Status.CONNECTED || status === Strophe.Status.ATTACHED) { if (status === Strophe.Status.CONNECTED || status === Strophe.Status.ATTACHED) {
_converse.giveFeedback(); _converse.setConnectionStatus(status);
// By default we always want to send out an initial presence stanza. // By default we always want to send out an initial presence stanza.
_converse.send_initial_presence = true; _converse.send_initial_presence = true;
_converse.setDisconnectionCause(); _converse.setDisconnectionCause();
@ -517,20 +516,19 @@
_converse.setDisconnectionCause(status, message); _converse.setDisconnectionCause(status, message);
_converse.onDisconnected(); _converse.onDisconnected();
} else if (status === Strophe.Status.ERROR) { } else if (status === Strophe.Status.ERROR) {
_converse.giveFeedback( _converse.setConnectionStatus(
__('Connection error'), status,
'error',
__('An error occurred while connecting to the chat server.') __('An error occurred while connecting to the chat server.')
); );
} else if (status === Strophe.Status.CONNECTING) { } else if (status === Strophe.Status.CONNECTING) {
_converse.giveFeedback(__('Connecting…')); _converse.setConnectionStatus(status);
} else if (status === Strophe.Status.AUTHENTICATING) { } else if (status === Strophe.Status.AUTHENTICATING) {
_converse.giveFeedback(__('Authenticating…')); _converse.setConnectionStatus(status);
} else if (status === Strophe.Status.AUTHFAIL) { } else if (status === Strophe.Status.AUTHFAIL) {
if (!message) { if (!message) {
message = __('Your Jabber ID and/or password is incorrect. Please try again.'); message = __('Your Jabber ID and/or password is incorrect. Please try again.');
} }
_converse.giveFeedback(__('Authentication failed'), 'error', message); _converse.setConnectionStatus(status, message);
_converse.setDisconnectionCause(status, message, true); _converse.setDisconnectionCause(status, message, true);
_converse.onDisconnected(); _converse.onDisconnected();
} else if (status === Strophe.Status.CONNFAIL) { } else if (status === Strophe.Status.CONNFAIL) {
@ -541,11 +539,7 @@
} else if (!_.isUndefined(message) && message === _.get(Strophe, 'ErrorCondition.NO_AUTH_MECH')) { } else if (!_.isUndefined(message) && message === _.get(Strophe, 'ErrorCondition.NO_AUTH_MECH')) {
feedback = __("The XMPP server did not offer a supported authentication mechanism"); feedback = __("The XMPP server did not offer a supported authentication mechanism");
} }
_converse.giveFeedback( _converse.setConnectionStatus(status, feedback);
__('Connection failed'),
'error',
feedback
);
_converse.setDisconnectionCause(status, message); _converse.setDisconnectionCause(status, message);
} else if (status === Strophe.Status.DISCONNECTING) { } else if (status === Strophe.Status.DISCONNECTING) {
_converse.setDisconnectionCause(status, message); _converse.setDisconnectionCause(status, message);
@ -571,7 +565,7 @@
} }
}; };
this.initStatus = () => this.initStatus = () =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const promise = new utils.getWrappedPromise(); const promise = new utils.getWrappedPromise();
this.xmppstatus = new this.XMPPStatus(); this.xmppstatus = new this.XMPPStatus();
@ -1521,15 +1515,14 @@
this.ConnectionFeedback = Backbone.Model.extend({ this.ConnectionFeedback = Backbone.Model.extend({
initialize () { defaults: {
this.on('change', this.emitConnectionFeedbackChange); 'connection_status': undefined,
'message': ''
}, },
emitConnectionFeedbackChange () { initialize () {
_converse.emit('connfeedback', { this.on('change', () => {
'klass': _converse.connfeedback.get('klass'), _converse.emit('connfeedback', _converse.connfeedback);
'message': _converse.connfeedback.get('message'),
'subject': _converse.connfeedback.get('subject')
}); });
} }
}); });

View File

@ -148,10 +148,10 @@
const { _converse } = this, const { _converse } = this,
{ __ } = _converse; { __ } = _converse;
_converse.PRETTY_CONNECTION_STATUS[Strophe.Status.REGIFAIL] = 'REGIFAIL'; _converse.CONNECTION_STATUS[Strophe.Status.REGIFAIL] = 'REGIFAIL';
_converse.PRETTY_CONNECTION_STATUS[Strophe.Status.REGISTERED] = 'REGISTERED'; _converse.CONNECTION_STATUS[Strophe.Status.REGISTERED] = 'REGISTERED';
_converse.PRETTY_CONNECTION_STATUS[Strophe.Status.CONFLICT] = 'CONFLICT'; _converse.CONNECTION_STATUS[Strophe.Status.CONFLICT] = 'CONFLICT';
_converse.PRETTY_CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE'; _converse.CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE';
_converse.api.settings.update({ _converse.api.settings.update({
allow_registration: true, allow_registration: true,
@ -433,7 +433,7 @@
], status_code)) { ], status_code)) {
_converse.log( _converse.log(
`Problem during registration: Strophe.Status is ${_converse.PRETTY_CONNECTION_STATUS[status_code]}`, `Problem during registration: Strophe.Status is ${_converse.CONNECTION_STATUS[status_code]}`,
Strophe.LogLevel.ERROR Strophe.LogLevel.ERROR
); );
this.abortRegistration(); this.abortRegistration();

View File

@ -1,8 +0,0 @@
<div class="conn-feedback {[ if (!conn_feedback_subject && !conn_feedback_message) { ]} hidden {[ } ]}">
<p class="feedback-subject {[ if (!conn_feedback_subject) { ]} hidden {[ } ]} {{{ conn_feedback_class }}}">
{{{ conn_feedback_subject }}}
</p>
<p class="feedback-message {[ if (!conn_feedback_message) { ]} hidden {[ } ]} {{{ conn_feedback_class }}}">
{{{ conn_feedback_message }}}
</p>
</div>

View File

@ -1,22 +1,38 @@
<form class="pure-form pure-form-stacked converse-form" id="converse-login" method="post"> <form class="pure-form pure-form-stacked converse-form" id="converse-login" method="post">
{[ if (auto_login) { ]} <legend>{{{__("Login")}}}</legend>
<span class="spinner login-submit"/>
{[ } ]}
{[ if (!auto_login) { ]}
<legend>{{{__("Login")}}}</legend>
<div class="conn-feedback fade-in {[ if (!conn_feedback_subject) { ]} hidden {[ } ]} {{{conn_feedback_class}}}">
<p class="feedback-subject">{{{ conn_feedback_subject }}}</p>
<p class="feedback-message {[ if (!conn_feedback_message) { ]} hidden {[ } ]}">{{{conn_feedback_message}}}</p>
</div>
{[ if (auto_login || _converse.CONNECTION_STATUS[connection_status] === 'CONNECTING') { ]}
<span class="spinner centered"/>
{[ } else { ]}
{[ if (authentication == LOGIN || authentication == EXTERNAL) { ]} {[ if (authentication == LOGIN || authentication == EXTERNAL) { ]}
<label>{{{__("Jabber ID:")}}}</label> <label>{{{__("Jabber ID:")}}}</label>
<p class="form-help fade-in invalid-jid-msg error hidden">{{{_('Please enter a valid XMPP address')}}}</p> <p class="form-help fade-in error {[ if (!_.includes(errors, 'invalid_jid')) { ]} hidden {[ } ]}">
<input autofocus type="text" name="jid" placeholder="{{{placeholder_username}}}"> {{{_('Please enter a valid XMPP address')}}}
</p>
<input autofocus
type="text"
name="jid"
class="{[ if (_.includes(errors, 'invalid_jid')) { ]} error {[ } ]}"
placeholder="{{{placeholder_username}}}">
{[ if (authentication !== EXTERNAL) { ]} {[ if (authentication !== EXTERNAL) { ]}
<label>{{{__("Password:")}}}</label> <label>{{{__("Password:")}}}</label>
<input type="password" name="password" placeholder="{{{__('password')}}}"> <p class="form-help fade-in error {[ if (!_.includes(errors, 'password_required')) { ]} hidden {[ } ]}">
{{{_('Please enter your password')}}}
</p>
<input type="password"
name="password"
class="{[ if (_.includes(errors, 'password_required')) { ]} error {[ } ]}"
placeholder="{{{__('password')}}}">
{[ } ]} {[ } ]}
<input class="pure-button button-primary" type="submit" value="{{{__('Submit')}}}"> <input class="pure-button button-primary" type="submit" value="{{{__('Submit')}}}">
{[ } ]} {[ } ]}
{[ if (authentication == ANONYMOUS) { ]} {[ if (authentication == ANONYMOUS) { ]}
<input class="pure-button button-primary login-anon" type="submit" value="{{{label_anon_login}}}"/> <input class="pure-button button-primary login-anon" type="submit" value="{{{__('Click here to log in anonymously')}}}"/>
{[ } ]} {[ } ]}
{[ if (authentication == PREBIND) { ]} {[ if (authentication == PREBIND) { ]}
<p>Disconnected.</p> <p>Disconnected.</p>