Browse Source

Initial work on allowing in band registrations. XEP 077

JC Brand 10 years ago
parent
commit
e9638e70c7

+ 110 - 75
converse.js

@@ -179,13 +179,11 @@
             'dnd':          2,
             'online':       1
         };
-
         var INACTIVE = 'inactive';
         var ACTIVE = 'active';
         var COMPOSING = 'composing';
         var PAUSED = 'paused';
         var GONE = 'gone';
-
         var HAS_CSPRNG = ((typeof crypto !== 'undefined') &&
             ((typeof crypto.randomBytes === 'function') ||
                 (typeof crypto.getRandomValues === 'function')
@@ -195,7 +193,6 @@
             (typeof OTR !== "undefined") &&
             (typeof DSA !== "undefined")
         );
-
         var OPENED = 'opened';
         var CLOSED = 'closed';
 
@@ -440,7 +437,6 @@
         };
 
         this.onConnect = function (status, condition, reconnect) {
-            var $button, $form;
             if ((status === Strophe.Status.CONNECTED) ||
                 (status === Strophe.Status.ATTACHED)) {
                 if ((typeof reconnect !== 'undefined') && (reconnect)) {
@@ -1991,10 +1987,12 @@
                 var cfg = {'$parent': this.$el.find('.controlbox-panes'), 'model': this};
                 if (!this.loginpanel) {
                     this.loginpanel = new converse.LoginPanel(cfg);
+                    this.registerpanel = new converse.RegisterPanel(cfg);
                 } else {
                     this.loginpanel.delegateEvents().initialize(cfg);
                 }
                 this.loginpanel.render();
+                this.registerpanel.render().$el.hide();
                 this.initDragResize();
             },
 
@@ -2420,72 +2418,15 @@
                     $stanza = $(stanza),
                     $fields = $stanza.find('field'),
                     title = $stanza.find('title').text(),
-                    instructions = $stanza.find('instructions').text(),
-                    i, j, options=[], $field, $options,
-					values=[], $values, value;
-                var input_types = {
-                    'text-private': 'password',
-                    'text-single': 'textline',
-                    'fixed': 'label',
-                    'boolean': 'checkbox',
-                    'hidden': 'hidden',
-                    'jid-multi': 'textarea',
-                    'list-single': 'dropdown',
-                    'list-multi': 'dropdown'
-                };
+                    instructions = $stanza.find('instructions').text();
                 $form.find('span.spinner').remove();
                 $form.append($('<legend>').text(title));
                 if (instructions != title) {
                     $form.append($('<p>').text(instructions));
                 }
-                for (i=0; i<$fields.length; i++) {
-                    $field = $($fields[i]);
-                    if ($field.attr('type') == 'list-single' || $field.attr('type') == 'list-multi') {
-						values = [];
-                        $values = $field.children('value');
-                        for (j=0; j<$values.length; j++) {
-							values.push($($values[j]).text());
-						}
-                        options = [];
-                        $options = $field.children('option');
-                        for (j=0; j<$options.length; j++) {
-                            value = $($options[j]).find('value').text();
-                            options.push(converse.templates.select_option({
-                                value: value,
-                                label: $($options[j]).attr('label'),
-								selected: (values.indexOf(value) >= 0)
-                            }));
-                        }
-                        $form.append(converse.templates.form_select({
-                            name: $field.attr('var'),
-                            label: $field.attr('label'),
-                            options: options.join(''),
-                            multiple: ($field.attr('type') == 'list-multi')
-                        }));
-                    } else if ($field.attr('type') == 'fixed') {
-                        $form.append($('<p>').text($field.find('value').text()));
-                    } else if ($field.attr('type') == 'jid-multi') {
-                        $form.append(converse.templates.form_textarea({
-                            name: $field.attr('var'),
-                            label: $field.attr('label') || '',
-                            value: $field.find('value').text()
-                        }));
-                    } else if ($field.attr('type') == 'boolean') {
-                        $form.append(converse.templates.form_checkbox({
-                            name: $field.attr('var'),
-                            type: input_types[$field.attr('type')],
-                            label: $field.attr('label') || '',
-                            checked: $field.find('value').text() === "1" && 'checked="1"' || ''
-                        }));
-                    } else {
-                        $form.append(converse.templates.form_input({
-                            name: $field.attr('var'),
-                            type: input_types[$field.attr('type')],
-                            label: $field.attr('label') || '',
-                            value: $field.find('value').text()
-                        }));
-                    }
-                }
+                _.each($fields, function (field) {
+                    $form.append(utils.xForm2webForm(field));
+                });
                 $form.append('<input type="submit" value="'+__('Save')+'"/>');
                 $form.append('<input type="button" value="'+__('Cancel')+'"/>');
                 $form.on('submit', $.proxy(this.saveConfiguration, this));
@@ -3087,7 +3028,9 @@
                 this.model.each($.proxy(function (model) {
                     var id = model.get('id');
                     if (include_controlbox || id !== 'controlbox') {
-                        this.get(id).close();
+                        if (this.get(id)) { // Should always resolve, but shit happens
+                            this.get(id).close();
+                        }
                     }
                 }, this));
                 return this;
@@ -4550,28 +4493,108 @@
             }
         });
 
-        this.LoginPanel = Backbone.View.extend({
+        this.RegisterPanel = Backbone.View.extend({
             tagName: 'div',
-            id: "login-dialog",
+            id: "register",
+            className: 'controlbox-pane',
             events: {
-                'submit form#converse-login': 'authenticate'
+                'submit form#converse-register': 'query'
             },
 
-            connect: function ($form, jid, password) {
+            initialize: function (cfg) {
+                this.$parent = cfg.$parent;
+                this.$tabs = cfg.$parent.parent().find('#controlbox-tabs');
+            },
+
+            render: function () {
+                this.$parent.append(this.$el.html(
+                    converse.templates.register_panel({
+                        'label_domain': __('XMPP Provider'), // TODO: make this a dropdown of servers...
+                        'label_register': __('Request Registration Form')
+                    })
+                ));
+                this.$tabs.append(converse.templates.register_tab({label_register: __('Register')}));
+                this.$el.find('input#jid').focus();
+                return this;
+            },
+
+            query: function (ev) {
+                if (ev && ev.preventDefault) { ev.preventDefault(); }
+                var $form = $(ev.target),
+                    $domain_input = $form.find('input[name=domain]'),
+                    domain = $domain_input.val(),
+                    errors = false;
+                if (!domain) { errors = true; $domain_input.addClass('error'); }
+                if (errors) { return; } // TODO provide error messages
+                this.connect($form, domain);
+                return false;
+            },
+
+            connect: function ($form, domain) {
                 if ($form) {
                     $form.find('input[type=submit]').hide().after('<span class="spinner login-submit"/>');
                 }
-                var resource = Strophe.getResourceFromJid(jid);
-                if (!resource) {
-                    jid += '/converse.js-' + Math.floor(Math.random()*139749825).toString();
+                converse.connection.register.connect(domain, $.proxy(this.onRegistering, this));
+            },
+
+            giveFeedback: function (message, klass) {
+                $('.conn-feedback').attr('class', 'conn-feedback').text(message);
+                if (klass) {
+                    $('.conn-feedback').addClass(klass);
                 }
-                converse.connection.connect(jid, password, converse.onConnect);
+            },
+
+            onRegistering: function (status) {
+                console.log('onRegistering');
+                if (status === Strophe.Status.CONNECTING) {
+                    converse.giveFeedback(__('Connecting'));
+                } else if (status === Strophe.Status.CONNFAIL) {
+                    converse.renderLoginPanel();
+                    this.giveFeedback(__('Connection Failed'), 'error');
+                } else if (status === Strophe.Status.DISCONNECTING) {
+                    if (!converse.connection.connected) {
+                        converse.renderLoginPanel();
+                    } else {
+                        this.giveFeedback(__('Disconnecting'), 'error');
+                    }
+                } else if (status == Strophe.Status.REGISTER) {
+                    this.renderRegistrationForm();
+                }
+            },
+
+            renderRegistrationForm: function () {
+                var register = converse.connection.register,
+                    $form= this.$el.find('form'),
+                    $stanza = $(register.query),
+                    $fields = $stanza.find('field');
+
+                $form.empty().append($('<p>').text(register.instructions));
+                _.each($fields, function (field) {
+                    $form.append(utils.xForm2webForm(field));
+                });
+                $form.append('<input type="submit" value="'+__('Register')+'"/>');
+                $form.on('submit', $.proxy(this.register, this));
+            },
+
+            remove: function () {
+                // XXX ?
+                this.$tabs.empty();
+                this.$el.parent().empty();
+            }
+        });
+
+        this.LoginPanel = Backbone.View.extend({
+            tagName: 'div',
+            id: "login-dialog",
+            className: 'controlbox-pane',
+            events: {
+                'submit form#converse-login': 'authenticate'
             },
 
             initialize: function (cfg) {
                 cfg.$parent.html(this.$el.html(
                     converse.templates.login_panel({
-                        'label_username': __('XMPP/Jabber Username:'),
+                        'label_username': __('XMPP Username:'),
                         'label_password': __('Password:'),
                         'label_login': __('Log In')
                     })
@@ -4616,6 +4639,18 @@
                 return false;
             },
 
+            connect: function ($form, jid, password) {
+                if ($form) {
+                    $form.find('input[type=submit]').hide().after('<span class="spinner login-submit"/>');
+                }
+                var resource = Strophe.getResourceFromJid(jid);
+                if (!resource) {
+                    jid += '/converse.js-' + Math.floor(Math.random()*139749825).toString();
+                }
+                converse.connection.connect(jid, password, converse.onConnect);
+            },
+
+
             remove: function () {
                 this.$tabs.empty();
                 this.$el.parent().empty();

+ 21 - 22
css/converse.css

@@ -926,9 +926,6 @@ dl.add-converse-contact {
   display: block;
   clear: both;
 }
-#conversejs .chatroom-form label.label-ta {
-    height: auto;
-}
 #conversejs .chatroom-form label input,
 #conversejs .chatroom-form label select {
   float: right;
@@ -1110,9 +1107,17 @@ dl.add-converse-contact {
   width: 300px;
 }
 #conversejs .controlbox-pane {
-  padding: 0;
-  border-bottom-right-radius: 4px;
+  background-color: white;
   border-bottom-left-radius: 4px;
+  border-bottom-right-radius: 4px;
+  border: 0;
+  font-size: 14px;
+  height: 289px;
+  height: calc(100% - 35px);
+  overflow-y: hidden;
+  padding: 0;
+  position: absolute;
+  width: 100%;
 }
 #conversejs .controlbox-pane dd {
   margin-left: 0;
@@ -1122,18 +1127,27 @@ dl.add-converse-contact {
 #conversejs .controlbox-pane dd.odd {
   background-color: #DCEAC5;
 }
+#conversejs form#converse-register,
 #conversejs form#converse-login {
   background: white;
   padding: 2em 0.5em;
 }
+#conversejs form#converse-register input,
 #conversejs form#converse-login input {
-  width: 100%;
+  width: 98%;
+  height: 30px;
 }
+#conversejs form#converse-register label,
 #conversejs form#converse-login label {
   margin-top: 0.5em;
 }
-#conversejs form#converse-login .login-submit {
+#conversejs form#converse-register .login-submit,
+#conversejs form#converse-login .login-submit,
+#conversejs form#converse-register .submit,
+#conversejs form#converse-login .submit {
+  width: 100%;
   margin-top: 1em;
+  height: 30px;
 }
 #conversejs form.set-xmpp-status {
   background: none;
@@ -1201,21 +1215,6 @@ select#select-xmpp-status {
   cursor: default;
   color: #666666;
 }
-#conversejs div#users,
-#conversejs div#chatrooms,
-#conversejs div#login-dialog,
-#conversejs div#settings {
-  border: 0;
-  font-size: 14px;
-  background-color: white;
-  border-bottom-right-radius: 4px;
-  border-bottom-left-radius: 4px;
-  width: 100%;
-  height: 289px;
-  height: calc(100% - 35px);
-  overflow-y: hidden;
-  position: absolute;
-}
 #conversejs div#chatrooms {
   overflow-y: auto;
 }

+ 1 - 0
css/theme.css

@@ -16,6 +16,7 @@ h4,
 h5,
 h6 {
   margin: 0 0 35px;
+  text-transform: uppercase;
   font-family: "Montserrat", "Helvetica Neue", Helvetica, Arial, sans-serif;
   font-weight: 700;
   letter-spacing: 1px;

+ 2 - 8
index.html

@@ -12,14 +12,8 @@
     <link type="text/css" rel="stylesheet" media="screen" href="components/bootstrap/dist/css/bootstrap.min.css" />
     <link type="text/css" rel="stylesheet" media="screen" href="components/fontawesome/css/font-awesome.min.css" />
     <link type="text/css" rel="stylesheet" media="screen" href="css/theme.css" />
-    <link type="text/css" rel="stylesheet" media="screen" href="css/converse.min.css" />
-    <!-- Only for development: <script data-main="main" src="components/requirejs/require.js"></script> -->
-    <![if gte IE 9]>
-        <script src="builds/converse.website.min.js"></script>
-    <![endif]>
-    <!--[if lt IE 9]>
-        <script src="builds/converse.website-no-otr.min.js"></script>
-    <![endif]-->
+    <link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
+     <script data-main="main" src="components/requirejs/require.js"></script> 
 </head>
 
 <body id="page-top" data-spy="scroll" data-target=".navbar-custom">

+ 21 - 20
less/converse.less

@@ -1247,9 +1247,17 @@ dl.add-converse-contact {
 }
 
 #conversejs .controlbox-pane {
-    padding: 0;
-    border-bottom-right-radius: 4px;
+    background-color: white;
     border-bottom-left-radius: 4px;
+    border-bottom-right-radius: 4px;
+    border: 0;
+    font-size: 14px;
+    height: 289px;
+    height: ~"calc(100% - 35px)";
+    overflow-y: hidden;
+    padding: 0;
+    position: absolute;
+    width: 100%;
 }
 
 #conversejs .controlbox-pane dd {
@@ -1262,21 +1270,30 @@ dl.add-converse-contact {
     background-color: #DCEAC5;
 }
 
+#conversejs form#converse-register,
 #conversejs form#converse-login {
     background: white;
     padding: 2em 0.5em;
 }
 
+#conversejs form#converse-register input,
 #conversejs form#converse-login input {
-    width: 100%;
+    width: 98%;
+    height: 30px;
 }
 
+#conversejs form#converse-register label,
 #conversejs form#converse-login label {
     margin-top: 0.5em;
 }
 
-#conversejs form#converse-login .login-submit {
+#conversejs form#converse-register .login-submit,
+#conversejs form#converse-login .login-submit,
+#conversejs form#converse-register .submit,
+#conversejs form#converse-login .submit {
+    width: 100%;
     margin-top: 1em;
+    height: 30px;
 }
 
 #conversejs form.set-xmpp-status {
@@ -1357,22 +1374,6 @@ select#select-xmpp-status {
     color: rgb(102,102,102);
 }
 
-#conversejs div#users,
-#conversejs div#chatrooms,
-#conversejs div#login-dialog,
-#conversejs div#settings {
-    border: 0;
-    font-size: 14px;
-    background-color: white;
-    border-bottom-right-radius: 4px;
-    border-bottom-left-radius: 4px;
-    width: 100%;
-    height: 289px;
-    height: ~"calc(100% - 35px)";
-    overflow-y: hidden;
-    position: absolute;
-}
-
 #conversejs div#chatrooms {
     overflow-y: auto;
 }

+ 4 - 0
main.js

@@ -17,6 +17,7 @@ require.config({
         "strophe":                  "components/strophe/strophe",
         "strophe.disco":            "components/strophejs-plugins/disco/strophe.disco",
         "strophe.muc":              "components/strophe.muc/index",
+        "strophe.register":         "components/strophejs-plugins/register/strophe.register",
         "strophe.roster":           "src/strophe.roster",
         "strophe.vcard":            "components/strophejs-plugins/vcard/strophe.vcard",
         "text":                     'components/requirejs-text/text',
@@ -93,6 +94,8 @@ require.config({
         "pending_contacts":         "src/templates/pending_contacts",
         "requesting_contact":       "src/templates/requesting_contact",
         "requesting_contacts":      "src/templates/requesting_contacts",
+        "register_panel":           "src/templates/register_panel",
+        "register_tab":             "src/templates/register_tab",
         "room_description":         "src/templates/room_description",
         "room_item":                "src/templates/room_item",
         "room_panel":               "src/templates/room_panel",
@@ -143,6 +146,7 @@ require.config({
         'strophe':              { exports: 'Strophe' },
         'strophe.disco':        { deps: ['strophe'] },
         'strophe.muc':          { deps: ['strophe'] },
+        'strophe.register':     { deps: ['strophe'] },
         'strophe.roster':       { deps: ['strophe'] },
         'strophe.vcard':        { deps: ['strophe'] }
     }

+ 55 - 36
mockup/controlbox.html

@@ -31,16 +31,26 @@
             <div class="chat-head controlbox-head">
                 <ul id="controlbox-tabs">
                     <li><a class="current" href="#login">Sign in</a></li>
+                    <li><a href="#register">Register</a></li>
                 </ul>
                 <a class="close-chatbox-button icon-close"></a>
             </div>
-            <div id="login-dialog">
+            <div id="login-dialog" class="controlbox-pane">
                 <form id="converse-login">
-                    <label>XMPP/Jabber Username:</label><input type="text" id="jid">
+                    <label>XMPP Username:</label><input type="text" id="jid" placeholder="name@server">
                     <label>Password:</label><input type="password" id="password">
-                    <input class="login-submit" type="submit" value="Log In">
+                    <input class="submit" type="submit" value="Log In">
+                    <span class="conn-feedback"></span>
+                </form>
+            </div>
+            <div id="register" class="controlbox-pane" style="display: none">
+                <form id="converse-register">
+                    <label>XMPP Username:</label><input type="text" id="jid" placeholder="name@server">
+                    <label>Password:</label><input type="password" name="password">
+                    <label>Confirm Password:</label><input type="password" name="password_confirm">
+                    <input class="submit" type="submit" value="Register">
+                    <span class="conn-feedback"></span>
                 </form>
-                <span class="conn-feedback"></span>
             </div>
         </div>
     </div>
@@ -55,6 +65,7 @@
                 </ul>
                 <a class="close-chatbox-button icon-close"></a>
             </div>
+
             <div id="users" class="controlbox-pane" style="display: block;">
                 <form class="set-xmpp-status" action="" method="post">
                     <span id="xmpp-status-holder">
@@ -227,39 +238,41 @@
                         </dd>
                     </dl>
                 </div>
-                <div id="chatrooms" style="display: none;">
-                    <form class="add-chatroom" action="" method="post">
-                        <input type="text" name="chatroom" class="new-chatroom-name" placeholder="Room name">
-                        <input type="text" name="nick" class="new-chatroom-nick" placeholder="Nickname">
-                        <input type="text" name="server" class="new-chatroom-server" placeholder="Server">
-                        <input type="submit" name="join" value="Join">
-                        <input type="button" name="show" id="show-rooms" value="Show rooms" style="display: inline-block;">
-                    </form>
-                    <dl id="available-chatrooms">
-                        <dt>Rooms on conference.opkode.im</dt>
-                        <dd class="available-chatroom">
-                            <a class="open-room" 
-                                data-room-jid="converse.js@conference.opkode.im"
-                                title="Click to open this room" href="#">Special chatroom with a long name (2)</a>
-                            <a class="room-info icon-room-info" 
-                                data-room-jid="converse.js@conference.opkode.im" 
-                                title="Show more information on this room" href="#">&nbsp;</a>
-                            <div class="room-info">
-                                <p class="room-info"><strong>Description:</strong></p>
-                                <p class="room-info"><strong>Occupants:</strong> 2</p>
-                                <p class="room-info"><strong>Features:</strong> </p>
-                                <ul>
-                                    <li class="room-info">Moderated</li><li class="room-info">Open room</li>
-                                    <li class="room-info">Permanent room</li><li class="room-info">Public</li>
-                                    <li class="room-info">Semi-anonymous</li>
-                                    <li class="room-info">Requires authentication <span class="icon-lock"></span></li>
-                                    <p></p>
-                                </ul>
-                            </div>
-                        </dd>
-                    </dl>
-                </div>
             </div>
+
+            <div id="chatrooms" class="controlbox-pane" style="display: none;">
+                <form class="add-chatroom" action="" method="post">
+                    <input type="text" name="chatroom" class="new-chatroom-name" placeholder="Room name">
+                    <input type="text" name="nick" class="new-chatroom-nick" placeholder="Nickname">
+                    <input type="text" name="server" class="new-chatroom-server" placeholder="Server">
+                    <input type="submit" name="join" value="Join">
+                    <input type="button" name="show" id="show-rooms" value="Show rooms" style="display: inline-block;">
+                </form>
+                <dl id="available-chatrooms">
+                    <dt>Rooms on conference.opkode.im</dt>
+                    <dd class="available-chatroom">
+                        <a class="open-room" 
+                            data-room-jid="converse.js@conference.opkode.im"
+                            title="Click to open this room" href="#">Special chatroom with a long name (2)</a>
+                        <a class="room-info icon-room-info" 
+                            data-room-jid="converse.js@conference.opkode.im" 
+                            title="Show more information on this room" href="#">&nbsp;</a>
+                        <div class="room-info">
+                            <p class="room-info"><strong>Description:</strong></p>
+                            <p class="room-info"><strong>Occupants:</strong> 2</p>
+                            <p class="room-info"><strong>Features:</strong> </p>
+                            <ul>
+                                <li class="room-info">Moderated</li><li class="room-info">Open room</li>
+                                <li class="room-info">Permanent room</li><li class="room-info">Public</li>
+                                <li class="room-info">Semi-anonymous</li>
+                                <li class="room-info">Requires authentication <span class="icon-lock"></span></li>
+                                <p></p>
+                            </ul>
+                        </div>
+                    </dd>
+                </dl>
+            </div>
+
         </div>
     </div>
 </div>
@@ -272,6 +285,12 @@ $(document).ready(function () {
     $('a[href=#users]').click(function (ev) {
         switchTab(ev); 
     });
+    $('a[href=#register]').click(function (ev) { 
+        switchTab(ev);
+    });
+    $('a[href=#login]').click(function (ev) { 
+        switchTab(ev);
+    });
 
     $("a.choose-xmpp-status").click(function (ev) {
         ev.preventDefault();

+ 1 - 0
src/deps-full.js

@@ -10,6 +10,7 @@ define("converse-dependencies", [
     "typeahead",
     "strophe",
     "strophe.muc",
+    "strophe.register",
     "strophe.roster",
     "strophe.vcard",
     "strophe.disco"

+ 1 - 0
src/deps-website.js

@@ -12,6 +12,7 @@ define("converse-dependencies", [
     "typeahead",
     "strophe",
     "strophe.muc",
+    "strophe.register",
     "strophe.roster",
     "strophe.vcard",
     "strophe.disco"

+ 18 - 14
src/templates.js

@@ -29,6 +29,8 @@ define("converse-templates", [
     "tpl!occupant",
     "tpl!pending_contact",
     "tpl!pending_contacts",
+    "tpl!register_panel",
+    "tpl!register_tab",
     "tpl!requesting_contact",
     "tpl!requesting_contacts",
     "tpl!room_description",
@@ -75,19 +77,21 @@ define("converse-templates", [
         occupant:               arguments[27],
         pending_contact:        arguments[28],
         pending_contacts:       arguments[29],
-        requesting_contact:     arguments[30],
-        requesting_contacts:    arguments[31],
-        room_description:       arguments[32],
-        room_item:              arguments[33],
-        room_panel:             arguments[34],
-        roster:                 arguments[35],
-        roster_item:            arguments[36],
-        select_option:          arguments[37],
-        search_contact:         arguments[38],
-        status_option:          arguments[39],
-        toggle_chats:           arguments[40],
-        toolbar:                arguments[41],
-        trimmed_chat:           arguments[42],
-        form_textarea:          arguments[43]
+        register_panel:         arguments[30],
+        register_tab:           arguments[31],
+        requesting_contact:     arguments[32],
+        requesting_contacts:    arguments[33],
+        room_description:       arguments[34],
+        room_item:              arguments[35],
+        room_panel:             arguments[36],
+        roster:                 arguments[37],
+        roster_item:            arguments[38],
+        select_option:          arguments[39],
+        search_contact:         arguments[40],
+        status_option:          arguments[41],
+        toggle_chats:           arguments[42],
+        toolbar:                arguments[43],
+        trimmed_chat:           arguments[44],
+        form_textarea:          arguments[45]
     };
 });

+ 3 - 3
src/templates/login_panel.html

@@ -1,8 +1,8 @@
 <form id="converse-login" method="post">
     <label>{{label_username}}</label>
-    <input type="username" name="jid" placeholder="Username">
+    <input type="username" name="jid" placeholder="user@server">
     <label>{{label_password}}</label>
-    <input type="password" name="password" placeholder="Password">
-    <input class="login-submit" type="submit" value="{{label_login}}">
+    <input type="password" name="password" placeholder="password">
+    <input class="submit" type="submit" value="{{label_login}}">
     <span class="conn-feedback"></span>
 </form>

+ 6 - 0
src/templates/register_panel.html

@@ -0,0 +1,6 @@
+<form id="converse-register">
+    <label>{{label_domain}}</label>
+    <input type="text" name="domain" placeholder="e.g. conversejs.org">
+    <input class="submit" type="submit" value="{{label_register}}">
+    <span class="conn-feedback"></span>
+</form>

+ 1 - 0
src/templates/register_tab.html

@@ -0,0 +1 @@
+<li><a class="s" href="#register">{{label_register}}</a></li>

+ 69 - 1
src/utils.js

@@ -1,4 +1,17 @@
-define(["jquery"], function ($) {
+define(["jquery", "converse-templates"], function ($, templates) {
+    "use strict";
+
+    var XFORM_TYPE_MAP = {
+        'text-private': 'password',
+        'text-single': 'textline',
+        'fixed': 'label',
+        'boolean': 'checkbox',
+        'hidden': 'hidden',
+        'jid-multi': 'textarea',
+        'list-single': 'dropdown',
+        'list-multi': 'dropdown'
+    };
+
     $.fn.hasScrollBar = function() {
         if (!$.contains(document, this.get(0))) {
             return false;
@@ -52,6 +65,61 @@ define(["jquery"], function ($) {
                 * See actionInfoMessages
                 */
             return str;
+        },
+
+        xForm2webForm: function (field) {
+            /* Takes a field in XMPP XForm (XEP-004: Data Forms) format
+             * and turns it into a HTML DOM field.
+             *
+             *  Parameters:
+             *      (XMLElement) field - the field to convert
+             */
+            var $field = $(field), options = [],
+                j, $options, $values, value, values;
+            if ($field.attr('type') == 'list-single' || $field.attr('type') == 'list-multi') {
+                values = [];
+                $values = $field.children('value');
+                for (j=0; j<$values.length; j++) {
+                    values.push($($values[j]).text());
+                }
+                $options = $field.children('option');
+                for (j=0; j<$options.length; j++) {
+                    value = $($options[j]).find('value').text();
+                    options.push(templates.select_option({
+                        value: value,
+                        label: $($options[j]).attr('label'),
+                        selected: (values.indexOf(value) >= 0)
+                    }));
+                }
+                return templates.form_select({
+                    name: $field.attr('var'),
+                    label: $field.attr('label'),
+                    options: options.join(''),
+                    multiple: ($field.attr('type') == 'list-multi')
+                });
+            } else if ($field.attr('type') == 'fixed') {
+                return $('<p>').text($field.find('value').text());
+            } else if ($field.attr('type') == 'jid-multi') {
+                return templates.form_textarea({
+                    name: $field.attr('var'),
+                    label: $field.attr('label') || '',
+                    value: $field.find('value').text()
+                });
+            } else if ($field.attr('type') == 'boolean') {
+                return templates.form_checkbox({
+                    name: $field.attr('var'),
+                    type: XFORM_TYPE_MAP[$field.attr('type')],
+                    label: $field.attr('label') || '',
+                    checked: $field.find('value').text() === "1" && 'checked="1"' || ''
+                });
+            } else {
+                return templates.form_input({
+                    name: $field.attr('var'),
+                    type: XFORM_TYPE_MAP[$field.attr('type')],
+                    label: $field.attr('label') || '',
+                    value: $field.find('value').text()
+                });
+            }
         }
     };
     return utils;