Simple Login implementation for Dojo MVC
dojo, javascript, model-view-controller
Solution
I would like to add my solution: http://jsfiddle.net/phusick/tG8Sg/
It is a bit twisted due to constrains of jsFiddle, but the main principles are the same as I employ in my coding.
First of all, the login process (or any other form processing) is encapsulated inside the dialog and it communicate with the rest of the application thought emitting evens via `dojo/Evented`. This is how it works:
var loginDialog = new LoginDialog({ controller: loginController});
loginDialog.startup();
loginDialog.show();
loginDialog.on("cancel", function() {
console.log("Login cancelled.");
});
loginDialog.on("error", function() {
console.log("Login error.");
});
loginDialog.on("success", function() {
console.log("Login success.");
console.log(JSON.stringify(this.form.get("value")));
});
As you can see in the jsFiddle, there are two templates dialog-template and login-form-template which I assemble inside the `LoginDialog` constructor. The reason is I would normally have also a class wrapping `dijit/form/Form` to do some magic beyond standard `dijit/form/Form` validation and data serialization, but since login is simple and it'd be a mess in the single file of jsFiddle, I got rid of it. The advantage of separating a form from a dialog is you can use the same form (i.e. View) together with all the form ad hoc code somewhere else, e.g. in a `ContentPane`. The form is there just to display and gather data to/from user, it should not directly communicate with Model, i.e. web service, there is a Controller for that purpose:
var LoginController = declare(null, {
constructor: function(kwArgs) {
lang.mixin(this, kwArgs);
},
login: function(data) {
// simulate calling web service for authentication
var def = new Deferred();
setTimeout(lang.hitch(this, function() {
def.resolve(data.username == this.username && data.password == this.password);
}), 1000);
return def;
}
});
Create an instance of `LoginController`:
// provide username & password in the constructor
// since we do not have web service here to authenticate against
var loginController = new LoginController({username: "user", password: "user"});
and pass it to `LoginDialog` constructor as yet seen above:
var loginDialog = new LoginDialog({ controller: loginController});
Finally `LoginDialog` class:
var LoginDialog = declare([Dialog, Evented], {
READY: 0,
BUSY: 1,
title: "Login Dialog",
message: "",
busyLabel: "Working...",
// Binding property values to DOM nodes in templates
// see: http://www.enterprisedojo.com/2010/10/02/lessons-in-widgetry-binding-property-values-to-dom-nodes-in-templates/
attributeMap: lang.delegate(dijit._Widget.prototype.attributeMap, {
message: {
node: "messageNode",
type: "innerHTML"
}
}),
constructor: function(/*Object*/ kwArgs) {
lang.mixin(this, kwArgs);
var dialogTemplate = dom.byId("dialog-template").textContent;
var formTemplate = dom.byId("login-form-template").textContent;
var template = lang.replace(dialogTemplate, {
form: formTemplate
});
var contentWidget = new (declare(
[_Widget, _TemplatedMixin, _WidgetsInTemplateMixin],
{
templateString: template
}
));
contentWidget.startup();
var content = this.content = contentWidget;
this.form = content.form;
// shortcuts
this.submitButton = content.submitButton;
this.cancelButton = content.cancelButton;
this.messageNode = content.messageNode;
},
postCreate: function() {
this.inherited(arguments);
this.readyState= this.READY;
this.okLabel = this.submitButton.get("label");
this.connect(this.submitButton, "onClick", "onSubmit");
this.connect(this.cancelButton, "onClick", "onCancel");
this.watch("readyState", lang.hitch(this, "_onReadyStateChange"));
this.form.watch("state", lang.hitch(this, "_onValidStateChange"));
this._onValidStateChange();
},
onSubmit: function() {
this.set("readyState", this.BUSY);
this.set("message", "");
var data = this.form.get("value");
// ask the controller to login
var auth = this.controller.login(data);
Deferred.when(auth, lang.hitch(this, function(loginSuccess) {
if (loginSuccess === true) {
this.onLoginSuccess();
return;
}
this.onLoginError();
}));
},
onLoginSuccess: function() {
this.set("readyState", this.READY);
this.emit("success");
},
onLoginError: function() {
this.set("readyState", this.READY);
this.set("message", "Please try again.");
this.emit("error");
},
onCancel: function() {
this.emit("cancel");
},
_onValidStateChange: function() {
this.submitButton.set("disabled", !!this.form.get("state").length);
},
_onReadyStateChange: function() {
var isBusy = this.get("readyState") == this.BUSY;
this.submitButton.set("label", isBusy ? this.busyLabel : this.okLabel);
this.submitButton.set("disabled", isBusy);
}
});
Please see templates at the aforementioned jsFiddle. They should be in separate files `required` via `dojo/text!` normally. I put them into `<script type="text/template">` to fit into jsFiddle.
Problem
Is there any example on how to implement a simple login page/dialog? I have been trying to do it with dojo boilerplate (check my previous questions : Layout implementation for Dojo MVC). So far, I have been able to display my dialog box. But I want to be able to get my data and on click event want to have an alert box for example (with his content). My view: ``` <form action="Login" method="post" validate="true" id="loginForm"> <table width="258"> <tr> <td><label>Login</label></td> <td><input class="cell" type="text" trim="true" dojoType="dijit.form.TextBox" value="" name="login" id="userId"/></td> </tr> <tr> <td><label>Password</label></td> <td><input class="cell" type="password" trim="true" dojoType="dijit.form.TextBox" value="" name="password" id="password"/></td> </tr> <tr><td colspan="2"> </td></tr> <tr> <td colspan="2" align="center"> <table border="0" cellspacing="0" cellpadding="0"> <tr> <td align="center" valign="top"><button dojoType="dijit.form.Button" type="submit" id="LoginButton" onClick="connect">Ok</button></td> <td align="left" valign="top"><button dojoType="dijit.form.Button" type="submit" onclick="document.Login.reset()" id="Cancel">Cancel</button></td> <td><button dojoType="dijit.form.Button" type="submit" onclick="showDialog();" id="resetPassword"> Show Dialog </button></td> </tr> </table> </td> </tr> </table> </form> ``` My Widget module/class ``` define([ "dojo/_base/declare", "dijit/_Widget", "dijit/_TemplatedMixin", "dijit/_WidgetsInTemplateMixin", "dojo/text!app/views/Login/Login.html", "dijit/Dialog", "dijit/form/Button", "dijit/form/TextBox" ], function( declare, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, template, Dialog ){ return declare([_Widget, _TemplatedMixin, _WidgetsInTemplateMixin, Dialog], { templateString: template }); }); ``` Now, if you check the HTML the element id LoginButton should call in this case a "connection" function. Which should show (on an alert box) the username and password current input. Where do I put my function? kind of... ``` connect : function(){ alert("username :" + dom.byId("userId").value() + " Password: " + dom.byId("password").value()); } ``` EDIT: New code ``` define([ "dojo/_base/declare", "dijit/_Widget", "dojo/dom", "dijit/_TemplatedMixin", "dijit/_WidgetsInTemplateMixin", "dojo/text!app/views/Login/Login.html", "dijit/Dialog", "dijit/form/Button", "dijit/form/TextBox" ], function( declare, dom, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, template, Dialog ){ return declare("login", [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin, Dialog], { templateString: template, postCreate: function() { this.inherited(arguments); // make reference to widget from the node attachment this.submitButton = dijit.getEnclosingWidget(this.submitButtonNode); // override its onSubmit this.submitButton.onClick = function(){ alert("username :" + dom.byId("userId").value() + " Password: " + dom.byId("password").value()); }; }, // and a sample on how to implement widget-in-template stateful get/setter pattern // e.g. if submitbutton label should change on some event, call as such: // dijit.byId('loginForm').set("submitLabel", "Sendin login, please wait"); _setSubmitLabelAttr : function(value) { return this.submitButton.set("label", value); }, _getSubmitLabelAttr : function() { return this.submitButton.get("label"); }, }); }); ``` My main.js: ``` define([ 'dojo/has', 'require', 'dojo/_base/sniff'], function (has, require) { var app = {}; if (has('host-browser')) { require([ './views/Login', 'dojo/domReady!' ], function (Login) { app.login = new Login().placeAt(document.body); app.login.startup(); // And now… app.login.show(); }); } else { console.log('Hello from the server!'); } }); ```