Преглед изворни кода

Use lit-html to render form fields

JC Brand пре 4 година
родитељ
комит
da131715ba

+ 3 - 6
spec/muc.js

@@ -1614,12 +1614,9 @@ describe("Groupchats", function () {
                         .c('value').t('cauldronburn');
                         .c('value').t('cauldronburn');
             _converse.connection._dataRecv(mock.createRequest(config_stanza));
             _converse.connection._dataRecv(mock.createRequest(config_stanza));
 
 
-            const form = await u.waitUntil(() => view.el.querySelector('.muc-config-form'));
-            expect(form.querySelectorAll('fieldset').length).toBe(2);
-            const membersonly = view.el.querySelectorAll('input[name="muc#roomconfig_membersonly"]');
-            expect(membersonly.length).toBe(1);
-            expect(membersonly[0].getAttribute('type')).toBe('checkbox');
-            membersonly[0].checked = true;
+            const membersonly = await u.waitUntil(() => view.el.querySelector('input[name="muc#roomconfig_membersonly"]'));
+            expect(membersonly.getAttribute('type')).toBe('checkbox');
+            membersonly.checked = true;
 
 
             const moderated = view.el.querySelectorAll('input[name="muc#roomconfig_moderatedroom"]');
             const moderated = view.el.querySelectorAll('input[name="muc#roomconfig_moderatedroom"]');
             expect(moderated.length).toBe(1);
             expect(moderated.length).toBe(1);

+ 1 - 1
spec/register.js

@@ -353,7 +353,7 @@ describe("The Registration Panel", function () {
             </iq>`);
             </iq>`);
         _converse.connection._dataRecv(mock.createRequest(stanza));
         _converse.connection._dataRecv(mock.createRequest(stanza));
         expect(registerview.form_type).toBe('xform');
         expect(registerview.form_type).toBe('xform');
-        expect(registerview.el.querySelectorAll('#converse-register input[required="required"]').length).toBe(3);
+        expect(registerview.el.querySelectorAll('#converse-register input[required]').length).toBe(3);
         // Hide the controlbox so that we can see whether the test
         // Hide the controlbox so that we can see whether the test
         // passed or failed
         // passed or failed
         u.addClass('hidden', _converse.chatboxviews.get('controlbox').el);
         u.addClass('hidden', _converse.chatboxviews.get('controlbox').el);

+ 1 - 3
src/components/adhoc-commands.js

@@ -4,7 +4,6 @@ import { CustomElement } from './element.js';
 import { __ } from '../i18n';
 import { __ } from '../i18n';
 import { api, converse } from "@converse/headless/core";
 import { api, converse } from "@converse/headless/core";
 import { html } from "lit-html";
 import { html } from "lit-html";
-import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
 
 
 const { Strophe, $iq, sizzle } = converse.env;
 const { Strophe, $iq, sizzle } = converse.env;
 const u = converse.env.utils;
 const u = converse.env.utils;
@@ -21,8 +20,7 @@ const tpl_command_form = (o, command) => {
                 <input type="hidden" name="command_jid" value="${command.jid}"/>
                 <input type="hidden" name="command_jid" value="${command.jid}"/>
 
 
                 <p class="form-help">${command.instructions}</p>
                 <p class="form-help">${command.instructions}</p>
-                <!-- Fields are generated internally, with xForm2webForm -->
-                ${ command.fields.map(field =>  unsafeHTML(field)) }
+                ${ command.fields }
             </fieldset>
             </fieldset>
             <fieldset>
             <fieldset>
                 <input type="submit" class="btn btn-primary" value="${i18n_run}">
                 <input type="submit" class="btn btn-primary" value="${i18n_run}">

+ 1 - 3
src/modals/muc-commands.js

@@ -57,7 +57,7 @@ export default BootstrapModal.extend({
         command.fields;
         command.fields;
         try {
         try {
             const iq = await api.sendIQ(stanza);
             const iq = await api.sendIQ(stanza);
-            command.fields = sizzle('field', iq).map(f => u.xForm2webForm(f, iq))
+            command.fields = sizzle('field', iq).map(f => u.xForm2TemplateResult(f, iq))
         } catch (e) {
         } catch (e) {
             if (e === null) {
             if (e === null) {
                 log.error(`Error: timeout while trying to execute command for ${jid}`);
                 log.error(`Error: timeout while trying to execute command for ${jid}`);
@@ -83,7 +83,5 @@ export default BootstrapModal.extend({
             </command>
             </command>
         </iq>
         </iq>
         */
         */
-
-
     }
     }
 });
 });

+ 1 - 1
src/plugins/muc-views/config-form.js

@@ -32,7 +32,7 @@ const MUCConfigForm = View.extend({
         };
         };
         return tpl_muc_config_form({
         return tpl_muc_config_form({
             'closeConfigForm': ev => this.closeConfigForm(ev),
             'closeConfigForm': ev => this.closeConfigForm(ev),
-            'fields': fields.map(f => u.xForm2webForm(f, stanza, options)),
+            'fields': fields.map(f => u.xForm2TemplateResult(f, stanza, options)),
             'instructions': stanza.querySelector('instructions')?.textContent,
             'instructions': stanza.querySelector('instructions')?.textContent,
             'submitConfigForm': ev => this.submitConfigForm(ev),
             'submitConfigForm': ev => this.submitConfigForm(ev),
             'title': stanza.querySelector('title')?.textContent
             'title': stanza.querySelector('title')?.textContent

+ 39 - 51
src/plugins/register.js

@@ -8,15 +8,16 @@
  */
  */
 import "./controlbox/index.js";
 import "./controlbox/index.js";
 import log from "@converse/headless/log";
 import log from "@converse/headless/log";
-import tpl_form_input from "../templates/form_input.html";
-import tpl_form_username from "../templates/form_username.html";
+import tpl_form_input from "../templates/form_input.js";
+import tpl_form_url from "../templates/form_url.js";
+import tpl_form_username from "../templates/form_username.js";
 import tpl_register_panel from "../templates/register_panel.html";
 import tpl_register_panel from "../templates/register_panel.html";
-import tpl_registration_form from "../templates/registration_form.html";
+import tpl_registration_form from "../templates/registration_form.js";
 import tpl_registration_request from "../templates/registration_request.html";
 import tpl_registration_request from "../templates/registration_request.html";
 import tpl_spinner from "../templates/spinner.js";
 import tpl_spinner from "../templates/spinner.js";
 import utils from "@converse/headless/utils/form";
 import utils from "@converse/headless/utils/form";
 import { View } from "@converse/skeletor/src/view";
 import { View } from "@converse/skeletor/src/view";
-import { __ } from '../i18n';
+import { __ } from 'i18n';
 import { _converse, api, converse } from "@converse/headless/core";
 import { _converse, api, converse } from "@converse/headless/core";
 import { pick } from "lodash-es";
 import { pick } from "lodash-es";
 import { render } from 'lit-html';
 import { render } from 'lit-html';
@@ -403,39 +404,40 @@ converse.plugins.add('converse-register', {
                 }
                 }
             },
             },
 
 
-            renderLegacyRegistrationForm (form) {
-                Object.keys(this.fields).forEach(key => {
+            getLegacyFormFields () {
+                const input_fields = Object.keys(this.fields).map(key => {
                     if (key === "username") {
                     if (key === "username") {
-                        form.insertAdjacentHTML(
-                            'beforeend',
-                            tpl_form_username({
-                                'domain': ` @${this.domain}`,
-                                'name': key,
-                                'type': "text",
-                                'label': key,
-                                'value': '',
-                                'required': true
-                            })
-                        );
+                        return tpl_form_username({
+                            'domain': ` @${this.domain}`,
+                            'name': key,
+                            'type': "text",
+                            'label': key,
+                            'value': '',
+                            'required': true
+                        });
                     } else {
                     } else {
-                        form.insertAdjacentHTML(
-                            'beforeend',
-                            tpl_form_input({
-                                'label': key,
-                                'name': key,
-                                'placeholder': key,
-                                'required': true,
-                                'type': (key === 'password' || key === 'email') ? key : "text",
-                                'value': ''
-                            })
-                        );
+                        return tpl_form_input({
+                            'label': key,
+                            'name': key,
+                            'placeholder': key,
+                            'required': true,
+                            'type': (key === 'password' || key === 'email') ? key : "text",
+                            'value': ''
+                        })
                     }
                     }
                 });
                 });
-                // Show urls
-                this.urls.forEach(u => form.insertAdjacentHTML(
-                    'afterend',
-                    '<a target="blank" rel="noopener" href="'+u+'">'+u+'</a>'
-                ));
+                const urls = this.urls.map(u => tpl_form_url({'label': '', 'value': u}));
+                return [...input_fields, ...urls];
+            },
+
+            getFormFields (stanza) {
+                if (this.form_type === 'xform') {
+                    return Array.from(stanza.querySelectorAll('field')).map(field =>
+                        utils.xForm2TemplateResult(field, stanza, {'domain': this.domain})
+                    );
+                } else {
+                    return this.getLegacyFormFields();
+                }
             },
             },
 
 
             /**
             /**
@@ -447,28 +449,14 @@ converse.plugins.add('converse-register', {
              */
              */
             renderRegistrationForm (stanza) {
             renderRegistrationForm (stanza) {
                 const form = this.el.querySelector('form');
                 const form = this.el.querySelector('form');
-                form.innerHTML = tpl_registration_form({
-                    '__': __,
+                const tpl = tpl_registration_form({
                     'domain': this.domain,
                     'domain': this.domain,
                     'title': this.title,
                     'title': this.title,
                     'instructions': this.instructions,
                     'instructions': this.instructions,
-                    'registration_domain': api.settings.get('registration_domain')
+                    'fields': this.fields,
+                    'form_fields': this.getFormFields(stanza)
                 });
                 });
-
-                const buttons = form.querySelector('fieldset.buttons');
-                if (this.form_type === 'xform') {
-                    stanza.querySelectorAll('field').forEach(field => {
-                        buttons.insertAdjacentHTML(
-                            'beforebegin',
-                            utils.xForm2webForm(field, stanza, {'domain': this.domain})
-                        );
-                    });
-                } else {
-                    this.renderLegacyRegistrationForm(form);
-                }
-                if (!this.fields) {
-                    form.querySelector('.button-primary').classList.add('hidden');
-                }
+                render(tpl, form);
                 form.classList.remove('hidden');
                 form.classList.remove('hidden');
                 this.model.set('registration_form_rendered', true);
                 this.model.set('registration_form_rendered', true);
             },
             },

+ 0 - 9
src/templates/form_captcha.html

@@ -1,9 +0,0 @@
-{[ if (o.label) { ]}
-<label>
-    {{{o.label}}}
-</label>
-{[ } ]}
-<img src="data:{{{o.type}}};base64,{{{o.data}}}">
-<input name="{{{o.name}}}" type="text" {[ if (o.required) { ]} required="required" {[ } ]} />
-
-

+ 9 - 0
src/templates/form_captcha.js

@@ -0,0 +1,9 @@
+import { html } from "lit-html";
+
+export default  (o) => html`
+    <fieldset class="form-group">
+        ${o.label ? html`<label>${o.label}</label>` : '' }
+        <img src="data:${o.type};base64,${o.data}">
+        <input name="${o.name}" type="text" ?required="${o.required}" />
+    </fieldset>
+`;

+ 0 - 4
src/templates/form_checkbox.html

@@ -1,4 +0,0 @@
-<div class="form-group">
-    <input id="{{{o.id}}}" name="{{{o.name}}}" type="checkbox" {{{o.checked}}} {[ if (o.required) { ]} required {[ } ]} />
-    <label class="form-check-label" for="{{{o.id}}}">{{{o.label}}}</label>
-</div>

+ 7 - 0
src/templates/form_checkbox.js

@@ -0,0 +1,7 @@
+import { html } from "lit-html";
+
+export default  (o) => html`
+    <fieldset class="form-group">
+        <input id="${o.id}" name="${o.name}" type="checkbox" ?checked=${o.checked} ?required=${o.required} />
+        <label class="form-check-label" for="${o.id}">${o.label}</label>
+    </fieldset>`;

+ 3 - 0
src/templates/form_help.js

@@ -0,0 +1,3 @@
+import { html } from "lit-html";
+
+export default  (o) => html`<p class="form-help">${o.text}</p>`;

+ 0 - 16
src/templates/form_input.html

@@ -1,16 +0,0 @@
-<div class="form-group">
-    {[ if (o.type !== 'hidden') { ]}
-        <label for="{{{o.id}}}">{{{o.label}}}</label>
-    {[ } ]}
-    {[ 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" {[ } ]} />
-</div>

+ 22 - 0
src/templates/form_input.js

@@ -0,0 +1,22 @@
+import { html } from "lit-html";
+
+export default  (o) => html`
+    <div class="form-group">
+        ${ o.type !== 'hidden' ? html`<label for="${o.id}">${o.label}</label>` : '' }
+
+        <!-- This is a hack to prevent Chrome from auto-filling the username in
+             any of the other input fields in the MUC configuration form. -->
+        ${ (o.type === 'password' && o.fixed_username) ? html`
+            <input class="hidden-username" type="text" autocomplete="username" value="${o.fixed_username}"></input>
+        ` : '' }
+
+        <input
+            autocomplete="${o.autocomplete || ''}"
+            class="form-control"
+            id="${o.id}"
+            name="${o.name}"
+            placeholder="${o.placeholder || ''}"
+            type="${o.type}"
+            value="${o.value || ''}"
+            ?required=${o.required} />
+    </div>`;

+ 0 - 4
src/templates/form_select.html

@@ -1,4 +0,0 @@
-<div class="form-group">
-    <label for="{{{o.id}}}">{{{o.label}}}</label>
-    <select class="form-control" id="{{{o.id}}}" name="{{{o.name}}}" {[ if (o.multiple) { ]} multiple="multiple" {[ } ]}>{{o.options}}</select>
-</div>

+ 11 - 0
src/templates/form_select.js

@@ -0,0 +1,11 @@
+import { html } from "lit-html";
+
+const tpl_option = (o) => html`<option value="${o.value}" ?selected="${o.selected}">${o.label}</option>`;
+
+export default  (o) => html`
+    <div class="form-group">
+        <label for="${o.id}">${o.label}</label>
+        <select class="form-control" id="${o.id}" name="${o.name}" ?multiple="${o.multiple}">
+            ${o.options?.map(o => tpl_option(o))}
+        </select>
+    </div>`;

+ 0 - 2
src/templates/form_textarea.html

@@ -1,2 +0,0 @@
-<label class="label-ta">{{{o.label}}}</label>
-<textarea name="{{{o.name}}}">{{{o.value}}}</textarea>

+ 6 - 0
src/templates/form_textarea.js

@@ -0,0 +1,6 @@
+import { html } from "lit-html";
+
+export default  (o) => html`
+    <label class="label-ta">${o.label}</label>
+    <textarea name="${o.name}">${o.value}</textarea>
+`;

+ 0 - 4
src/templates/form_url.html

@@ -1,4 +0,0 @@
-<label>
-    {{{o.label}}}
-    <a class="form-url" target="_blank" rel="noopener" href="{{{o.value}}}">{{{o.value}}}</a>
-</label>

+ 6 - 0
src/templates/form_url.js

@@ -0,0 +1,6 @@
+import { html } from "lit-html";
+
+export default  (o) => html`
+    <label>${o.label}
+        <a class="form-url" target="_blank" rel="noopener" href="${o.value}">${o.value}</a>
+    </label>`;

+ 0 - 15
src/templates/form_username.html

@@ -1,15 +0,0 @@
-<div class="form-group">
-    {[ if (o.label) { ]}
-    <label>
-        {{{o.label}}}
-    </label>
-    {[ } ]}
-    <div class="input-group">
-        <div class="input-group-prepend">
-            <input name="{{{o.name}}}" type="{{{o.type}}}"
-                {[ if (o.value) { ]} value="{{{o.value}}}" {[ } ]}
-                {[ if (o.required) { ]} required="required" {[ } ]} />
-            <div class="input-group-text col" title="{{{o.domain}}}">{{{o.domain}}}</div>
-        </div>
-    </div>
-</div>

+ 15 - 0
src/templates/form_username.js

@@ -0,0 +1,15 @@
+import { html } from "lit-html";
+
+export default  (o) => html`
+    <div class="form-group">
+        ${ o.label ? html`<label>${o.label}</label>` :  '' }
+        <div class="input-group">
+            <div class="input-group-prepend">
+                <input name="${o.name}"
+                       type="${o.type}"
+                       value="${o.value || ''}"
+                       ?required="${o.required}" />
+                <div class="input-group-text col" title="${o.domain}">${o.domain}</div>
+            </div>
+        </div>
+    </div>`;

+ 2 - 4
src/templates/muc_config_form.js

@@ -1,6 +1,5 @@
 import { html } from "lit-html";
 import { html } from "lit-html";
-import { __ } from '../i18n';
-import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
+import { __ } from 'i18n';
 
 
 export default (o) => {
 export default (o) => {
     const i18n_save = __('Save');
     const i18n_save = __('Save');
@@ -10,8 +9,7 @@ export default (o) => {
             <fieldset class="form-group">
             <fieldset class="form-group">
                 <legend>${o.title}</legend>
                 <legend>${o.title}</legend>
                 ${ (o.title !== o.instructions) ? html`<p class="form-help">${o.instructions}</p>` : '' }
                 ${ (o.title !== o.instructions) ? html`<p class="form-help">${o.instructions}</p>` : '' }
-                <!-- Fields are generated internally, with xForm2webForm -->
-                ${ o.fields.map(field =>  unsafeHTML(field)) }
+                ${ o.fields }
             </fieldset>
             </fieldset>
             <fieldset>
             <fieldset>
                 <input type="submit" class="btn btn-primary" value="${i18n_save}">
                 <input type="submit" class="btn btn-primary" value="${i18n_save}">

+ 0 - 15
src/templates/registration_form.html

@@ -1,15 +0,0 @@
-<legend class="col-form-label">{{{o.__("Account Registration:")}}} {{{o.domain}}}</legend>
-<p class="title">{{{o.title}}}</p>
-<p class="form-help instructions">{{{o.instructions}}}</p>
-<div class="form-errors hidden"></div>
-
-<fieldset class="buttons">
-    <input type="submit" class="btn btn-primary" value="{{{o.__('Register')}}}"/>
-    {[ if (!o.registration_domain) { ]}
-        <input type="button" class="btn btn-secondary button-cancel" value="{{{o.__('Choose a different provider')}}}"/>
-    {[ } ]}
-    <div class="switch-form">
-        <p>{{{ o.__("Already have a chat account?") }}}</p>
-        <p><a class="login-here toggle-register-login" href="#converse/login">{{{o.__("Log in here")}}}</a></p>
-    </div>
-</fieldset>

+ 28 - 0
src/templates/registration_form.js

@@ -0,0 +1,28 @@
+import { __ } from 'i18n';
+import { api } from "@converse/headless/core";
+import { html } from "lit-html";
+
+export default (o) => {
+    const i18n_choose_provider = __('Choose a different provider');
+    const i18n_has_account = __("Already have a chat account?");
+    const i18n_legend = __("Account Registration:");
+    const i18n_login = __("Log in here");
+    const i18n_register = __('Register');
+    const registration_domain = api.settings.get('registration_domain')
+
+    return html`
+        <legend class="col-form-label">${i18n_legend} ${o.domain}</legend>
+        <p class="title">${o.title}</p>
+        <p class="form-help instructions">${o.instructions}</p>
+        <div class="form-errors hidden"></div>
+        ${ o.form_fields }
+
+        <fieldset class="buttons form-group">
+            ${ o.fields ? html`<input type="submit" class="btn btn-primary" value="${i18n_register}"/>` : '' }
+            ${ registration_domain ? '' : html`<input type="button" class="btn btn-secondary button-cancel" value="${i18n_choose_provider}"/>` }
+            <div class="switch-form">
+                <p>${i18n_has_account}</p>
+                <p><a class="login-here toggle-register-login" href="#converse/login">${i18n_login}</a></p>
+            </div>
+        </fieldset>`;
+}

+ 0 - 1
src/templates/select_option.html

@@ -1 +0,0 @@
-<option value="{{{o.value}}}" {[ if (o.selected) { ]} selected="selected" {[ } ]} >{{{o.label}}}</option>

+ 21 - 20
src/utils/html.js

@@ -7,15 +7,15 @@ import URI from "urijs";
 import log from '@converse/headless/log';
 import log from '@converse/headless/log';
 import tpl_audio from  "../templates/audio.js";
 import tpl_audio from  "../templates/audio.js";
 import tpl_file from "../templates/file.js";
 import tpl_file from "../templates/file.js";
-import tpl_form_captcha from "../templates/form_captcha.html";
-import tpl_form_checkbox from "../templates/form_checkbox.html";
-import tpl_form_input from "../templates/form_input.html";
-import tpl_form_select from "../templates/form_select.html";
-import tpl_form_textarea from "../templates/form_textarea.html";
-import tpl_form_url from "../templates/form_url.html";
-import tpl_form_username from "../templates/form_username.html";
+import tpl_form_captcha from "../templates/form_captcha.js";
+import tpl_form_checkbox from "../templates/form_checkbox.js";
+import tpl_form_help from "../templates/form_help.js";
+import tpl_form_input from "../templates/form_input.js";
+import tpl_form_select from "../templates/form_select.js";
+import tpl_form_textarea from "../templates/form_textarea.js";
+import tpl_form_url from "../templates/form_url.js";
+import tpl_form_username from "../templates/form_username.js";
 import tpl_image from "../templates/image.js";
 import tpl_image from "../templates/image.js";
-import tpl_select_option from "../templates/select_option.html";
 import tpl_video from "../templates/video.js";
 import tpl_video from "../templates/video.js";
 import u from "../headless/utils/core";
 import u from "../headless/utils/core";
 import { api, converse } from  "@converse/headless/core";
 import { api, converse } from  "@converse/headless/core";
@@ -583,38 +583,38 @@ u.fadeIn = function (el, callback) {
 
 
 
 
 /**
 /**
- * Takes a field in XMPP XForm (XEP-004: Data Forms) format
- * and turns it into an HTML field.
- * Returns either text or a DOM element (which is not ideal, but fine for now).
- * @private
- * @method u#xForm2webForm
+ * Takes an XML field in XMPP XForm (XEP-004: Data Forms) format returns a
+ * [TemplateResult](https://lit-html.polymer-project.org/api/classes/_lit_html_.templateresult.html).
+ * @method u#xForm2TemplateResult
  * @param { XMLElement } field - the field to convert
  * @param { XMLElement } field - the field to convert
+ * @param { XMLElement } stanza - the containing stanza
+ * @param { Object } options
+ * @returns { TemplateResult }
  */
  */
-u.xForm2webForm = function (field, stanza, options) {
+u.xForm2TemplateResult = function (field, stanza, options) {
     if (field.getAttribute('type') === 'list-single' ||
     if (field.getAttribute('type') === 'list-single' ||
         field.getAttribute('type') === 'list-multi') {
         field.getAttribute('type') === 'list-multi') {
-
         const values = u.queryChildren(field, 'value').map(el => el?.textContent);
         const values = u.queryChildren(field, 'value').map(el => el?.textContent);
         const options = u.queryChildren(field, 'option').map(option => {
         const options = u.queryChildren(field, 'option').map(option => {
             const value = option.querySelector('value')?.textContent;
             const value = option.querySelector('value')?.textContent;
-            return tpl_select_option({
+            return {
                 'value': value,
                 'value': value,
                 'label': option.getAttribute('label'),
                 'label': option.getAttribute('label'),
                 'selected': values.includes(value),
                 'selected': values.includes(value),
                 'required': !!field.querySelector('required')
                 'required': !!field.querySelector('required')
-            });
+            };
         });
         });
         return tpl_form_select({
         return tpl_form_select({
+            options,
             'id': u.getUniqueId(),
             'id': u.getUniqueId(),
-            'name': field.getAttribute('var'),
             'label': field.getAttribute('label'),
             'label': field.getAttribute('label'),
-            'options': options.join(''),
             'multiple': (field.getAttribute('type') === 'list-multi'),
             'multiple': (field.getAttribute('type') === 'list-multi'),
+            'name': field.getAttribute('var'),
             'required': !!field.querySelector('required')
             'required': !!field.querySelector('required')
         });
         });
     } else if (field.getAttribute('type') === 'fixed') {
     } else if (field.getAttribute('type') === 'fixed') {
         const text = field.querySelector('value')?.textContent;
         const text = field.querySelector('value')?.textContent;
-        return '<p class="form-help">'+text+'</p>';
+        return tpl_form_help({text});
     } else if (field.getAttribute('type') === 'jid-multi') {
     } else if (field.getAttribute('type') === 'jid-multi') {
         return tpl_form_textarea({
         return tpl_form_textarea({
             'name': field.getAttribute('var'),
             'name': field.getAttribute('var'),
@@ -670,4 +670,5 @@ u.xForm2webForm = function (field, stanza, options) {
         });
         });
     }
     }
 }
 }
+
 export default u;
 export default u;