Commit d311e140 authored by JC Brand's avatar JC Brand

Render MUC forms with Backbone.VDOMView

That way we don't lose the user's input values, we avoid flashing and we
avoid unnecessary rendering.

In the process, fixed an annoying issue where Chrome auto-completes
what it thinks is the username into the "Language Tag" field of the MUC
config form.

Instead we tell Chrome that the MUC JID is the username, thereby also
letting it save the password to to that JID.
parent 66adf035
......@@ -10033,6 +10033,11 @@ body.converse-fullscreen {
font-size: 80%;
font-weight: normal; }
#conversejs form .hidden-username {
opacity: 0 !important;
height: 0 !important;
padding: 0 !important; }
#conversejs form .error-feedback {
margin-bottom: 0.5em; }
......@@ -10124,7 +10129,11 @@ body.converse-fullscreen {
padding-bottom: 0; }
#conversejs form.converse-centered-form {
min-height: 66%;
text-align: center; }
#conversejs form.converse-centered-form input {
max-width: 30em;
margin: auto; }
#conversejs .chatbox-navback {
display: none; }
......@@ -11676,6 +11685,12 @@ body.converse-fullscreen {
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .chatroom-form-container .button-primary,
#conversejs .chatroom .box-flyout .chatroom-body .chatroom-form-container .button-primary {
background-color: var(--chatroom-head-button-color); }
#conversejs.converse-embedded .chatroom .box-flyout .chatroom-body .chatroom-form,
#conversejs .chatroom .box-flyout .chatroom-body .chatroom-form {
display: flex;
flex-direction: column;
justify-content: center;
padding: 2em; }
#conversejs.converse-embedded .chatroom .muc-bottom-panel,
#conversejs .chatroom .muc-bottom-panel {
border-top: var(--message-input-border-top);
......
This diff is collapsed.
......@@ -303,6 +303,13 @@
background-color: var(--chatroom-head-button-color);
}
}
.chatroom-form {
display: flex;
flex-direction: column;
justify-content: center;
padding: 2em;
}
}
}
......
......@@ -5,6 +5,11 @@
}
form {
.hidden-username {
opacity: 0 !important;
height: 0 !important;
padding: 0 !important;
}
.error-feedback {
margin-bottom: 0.5em;
}
......@@ -113,7 +118,12 @@
}
&.converse-centered-form {
min-height: 66%;
text-align: center;
input {
max-width: 30em;
margin: auto;
}
}
}
}
......@@ -299,7 +299,6 @@
const view = _converse.chatboxviews.get('lounge@localhost');
spyOn(view, 'join').and.callThrough();
spyOn(view, 'submitNickname').and.callThrough();
/* <iq to="myroom@conference.chat.example.org"
* from="jordie.langen@chat.example.org/converse.js-11659299"
......@@ -336,7 +335,6 @@
const input = await test_utils.waitUntil(() => view.el.querySelector('input[name="nick"]'));
input.value = 'nicky';
view.el.querySelector('input[type=submit]').click();
expect(view.submitNickname).toHaveBeenCalled();
expect(view.join).toHaveBeenCalled();
// The user has just entered the room (because join was called)
......@@ -3455,7 +3453,7 @@
const view = _converse.chatboxviews.get(groupchat_jid);
spyOn(view, 'renderPasswordForm').and.callThrough();
var presence = $pres().attrs({
const presence = $pres().attrs({
'from': `${groupchat_jid}/dummy`,
'id': u.getUniqueId(),
'to': 'dummy@localhost/pda',
......@@ -3469,7 +3467,7 @@
const chat_body = view.el.querySelector('.chatroom-body');
expect(view.renderPasswordForm).toHaveBeenCalled();
expect(chat_body.querySelectorAll('form.chatroom-form').length).toBe(1);
expect(chat_body.querySelector('legend').textContent)
expect(chat_body.querySelector('label').textContent)
.toBe('This groupchat requires a password');
// Let's submit the form
......
......@@ -137,42 +137,17 @@ converse.plugins.add('converse-bookmarks', {
},
renderBookmarkForm () {
const { _converse } = this.__super__,
{ __ } = _converse,
body = this.el.querySelector('.chatroom-body');
_.each(body.children, child => child.classList.add('hidden'));
_.each(body.querySelectorAll('.chatroom-form-container'), u.removeElement);
body.insertAdjacentHTML(
'beforeend',
tpl_chatroom_bookmark_form({
'default_nick': this.model.get('nick'),
'heading': __('Bookmark this groupchat'),
'label_autojoin': __('Would you like this groupchat to be automatically joined upon startup?'),
'label_cancel': __('Cancel'),
'label_name': __('The name for this bookmark:'),
'label_nick': __('What should your nickname for this groupchat be?'),
'label_submit': __('Save'),
'name': this.model.get('name')
})
);
const form = body.querySelector('form.chatroom-form');
form.addEventListener('submit', ev => this.onBookmarkFormSubmitted(ev));
form.querySelector('.button-cancel').addEventListener('click', () => this.closeForm());
},
onBookmarkFormSubmitted (ev) {
ev.preventDefault();
const { _converse } = this.__super__;
_converse.bookmarks.createBookmark({
'jid': this.model.get('jid'),
'autojoin': _.get(ev.target.querySelector('input[name="autojoin"]'), 'checked') || false,
'name': _.get(ev.target.querySelector('input[name=name]'), 'value'),
'nick': _.get(ev.target.querySelector('input[name=nick]'), 'value')
});
u.removeElement(this.el.querySelector('div.chatroom-form-container'));
this.renderAfterTransition();
this.hideChatRoomContents();
if (!this.bookmark_form) {
const { _converse } = this.__super__;
this.bookmark_form = new _converse.MUCBookmarkForm({
'model': this.model,
'chatroomview': this
});
const container_el = this.el.querySelector('.chatroom-body');
container_el.insertAdjacentElement('beforeend', this.bookmark_form.el);
}
u.showElement(this.bookmark_form.el);
},
toggleBookmark (ev) {
......@@ -395,6 +370,49 @@ converse.plugins.add('converse-bookmarks', {
}
});
_converse.MUCBookmarkForm = Backbone.VDOMView.extend({
className: 'muc-bookmark-form',
events: {
'submit form': 'onBookmarkFormSubmitted',
'click .button-cancel': 'closeBookmarkForm'
},
initialize (attrs) {
this.chatroomview = attrs.chatroomview;
this.render();
},
toHTML () {
return tpl_chatroom_bookmark_form({
'default_nick': this.model.get('nick'),
'heading': __('Bookmark this groupchat'),
'label_autojoin': __('Would you like this groupchat to be automatically joined upon startup?'),
'label_cancel': __('Cancel'),
'label_name': __('The name for this bookmark:'),
'label_nick': __('What should your nickname for this groupchat be?'),
'label_submit': __('Save'),
'name': this.model.get('name')
});
},
onBookmarkFormSubmitted (ev) {
ev.preventDefault();
_converse.bookmarks.createBookmark({
'jid': this.model.get('jid'),
'autojoin': _.get(ev.target.querySelector('input[name="autojoin"]'), 'checked') || false,
'name': _.get(ev.target.querySelector('input[name=name]'), 'value'),
'nick': _.get(ev.target.querySelector('input[name=nick]'), 'value')
});
this.closeBookmarkForm(ev);
},
closeBookmarkForm (ev) {
ev.preventDefault();
this.chatroomview.closeForm();
}
});
_converse.BookmarksList = Backbone.Model.extend({
defaults: {
"toggle-state": _converse.OPENED
......
This diff is collapsed.
......@@ -499,7 +499,7 @@ converse.plugins.add('converse-register', {
_.each(stanza.querySelectorAll('field'), (field) => {
buttons.insertAdjacentHTML(
'beforebegin',
utils.xForm2webForm(field, stanza, this.domain)
utils.xForm2webForm(field, stanza, {'domain': this.domain})
);
});
} else {
......
<div class="chatroom-form-container">
<div class="chatroom-form-container muc-bookmark-form">
<form class="converse-form chatroom-form">
<legend>{{{o.heading}}}</legend>
<fieldset class="form-group">
......
<div class="chatroom-form-container">
<form class="converse-form chatroom-form">
<div class="chatroom-form-container muc-config-form">
<form class="converse-form chatroom-form" autocomplete="off">
<fieldset class="form-group">
<span class="spinner fa fa-spinner centered"/>
<legend>{{{o.title}}}</legend>
{[ if (o.title !== o.instructions) { ]}
<p class="form-help">{{{o.instructions}}}</p>
{[ } ]}
<!-- Fields are generated internally, with xForm2webForm -->
{[ o.fields.forEach(function (field) { ]} {{ field }} {[ }) ]}
</fieldset>
<fieldset>
<input type="submit" class="btn btn-primary" value="{{{o.__('Save')}}}"/>
<input type="button" class="btn btn-secondary .button-cancel" value="{{{o.__('Cancel')}}}"/>
</fieldset>
</form>
</div>
<div class="chatroom-form-container">
<div class="chatroom-form-container muc-nickname-form">
<form class="converse-form chatroom-form converse-centered-form">
<fieldset class="form-group">
<label>{{{o.heading}}}</label>
<p class="validation-message">{{{o.validation_message}}}</p>
<input type="text" required="required" name="nick" class="form-control" placeholder="{{{o.label_nickname}}}"/>
<input type="text" required="required" name="nick"
class="form-control {{o.error_class}}" placeholder="{{{o.label_nickname}}}"/>
</fieldset>
<fieldset class="form-group">
<input type="submit" class="btn btn-primary" name="join" value="{{{o.label_join}}}"/>
</fieldset>
<input type="submit" class="btn btn-primary" name="join" value="{{{o.label_join}}}"/>
</form>
</div>
<div class="chatroom-form-container">
<form class="pure-form converse-form chatroom-form">
<div class="chatroom-form-container muc-password-form">
<form class="converse-form chatroom-form converse-centered-form">
<fieldset class="form-group">
<legend>{{{o.heading}}}</legend>
<label>{{{o.label_password}}}</label>
<input type="password" name="password"/>
<label>{{{o.heading}}}</label>
<p class="validation-message">{{{o.validation_message}}}</p>
<input class="hidden-username" type="text" autocomplete="username" value="{{{o.jid}}}"></input>
<input type="password" name="password" required="required"
class="form-control {{o.error_class}}" placeholder="{{{o.label_password}}}"/>
</fieldset>
<fieldset class="form-group">
<input class="btn btn-primary" type="submit" value="{{{o.label_submit}}}"/>
</fieldset>
<input class="btn btn-primary" type="submit" value="{{{o.label_submit}}}"/>
</form>
</div>
......@@ -2,7 +2,14 @@
{[ if (o.type !== 'hidden') { ]}
<label for="{{{o.id}}}">{{{o.label}}}</label>
{[ } ]}
<input class="form-control" name="{{{o.name}}}" type="{{{o.type}}}" id="{{{o.id}}}"
{[ if (o.type === 'password' && o.fixed_username) { ]}
<!-- This is a hack to prevent Chrome from auto-filling the username in
any of the other input fields in the MUC configuration form. -->
<input class="hidden-username" type="text" autocomplete="username" value="{{{o.fixed_username}}}"></input>
{[ } ]}
<input
class="form-control" name="{{{o.name}}}" type="{{{o.type}}}" id="{{{o.id}}}"
{[ if (o.autocomplete) { ]} autocomplete="{{{o.autocomplete}}}" {[ } ]}
{[ if (o.placeholder) { ]} placeholder="{{{o.placeholder}}}" {[ } ]}
{[ if (o.value) { ]} value="{{{o.value}}}" {[ } ]}
{[ if (o.required) { ]} required="required" {[ } ]} />
......
......@@ -26,6 +26,13 @@ import u from "../headless/utils/core";
const URL_REGEX = /\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<>]{2,200}\b\/?/g;
function getAutoCompleteProperty (name, options) {
return {
'muc#roomconfig_lang': 'language',
'muc#roomconfig_roomsecret': options.new_password ? 'new-password' : 'current-password'
}[name];
}
const logger = _.assign({
'debug': _.get(console, 'log') ? console.log.bind(console) : _.noop,
'error': _.get(console, 'log') ? console.log.bind(console) : _.noop,
......@@ -538,7 +545,7 @@ u.fadeIn = function (el, callback) {
* @method u#xForm2webForm
* @param { XMLElement } field - the field to convert
*/
u.xForm2webForm = function (field, stanza, domain) {
u.xForm2webForm = function (field, stanza, options) {
if (field.getAttribute('type') === 'list-single' ||
field.getAttribute('type') === 'list-multi') {
......@@ -591,7 +598,7 @@ u.xForm2webForm = function (field, stanza, domain) {
});
} else if (field.getAttribute('var') === 'username') {
return tpl_form_username({
'domain': ' @'+domain,
'domain': ' @'+options.domain,
'name': field.getAttribute('var'),
'type': XFORM_TYPE_MAP[field.getAttribute('type')],
'label': field.getAttribute('label') || '',
......@@ -609,10 +616,13 @@ u.xForm2webForm = function (field, stanza, domain) {
'required': !_.isNil(field.querySelector('required'))
});
} else {
const name = field.getAttribute('var');
return tpl_form_input({
'id': u.getUniqueId(),
'label': field.getAttribute('label') || '',
'name': field.getAttribute('var'),
'name': name,
'fixed_username': options.fixed_username,
'autocomplete': getAutoCompleteProperty(name, options),
'placeholder': null,
'required': !_.isNil(field.querySelector('required')),
'type': XFORM_TYPE_MAP[field.getAttribute('type')],
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment