Created
January 28, 2015 22:23
-
-
Save cowwoc/fabfd4bff45c677362ed to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* jshint browserify: true */ | |
var Preconditions = require("preconditions/Preconditions"); | |
var User = require("net/User"); | |
var Authentication = require("net/Authentication"); | |
var Authentications = require("page/util/Authentications"); | |
var Html = require("page/util/Html"); | |
var Form = require("page/util/Form"); | |
var LoginPanel = require("page/util/LoginPanel"); | |
var ConflictingResourceException = require("util/ConflictingResourceException"); | |
var RequestScope = require("util/RequestScope"); | |
var Utilities = require("util/Utilities"); | |
var log4javascript = require("webjars/log4javascript"); | |
var URI = require("webjars/uri"); | |
/** | |
* Creates a new Authentications page. | |
*/ | |
function AuthenticationsPage() | |
{ | |
"use strict"; | |
// Initialize the log | |
this.log = log4javascript.getLogger("AuthenticationsPage"); | |
//log.setLevel(log4javascript.Level.ERROR); | |
var appender = new log4javascript.BrowserConsoleAppender(); | |
appender.setLayout(new log4javascript.PatternLayout("[%d{HH:mm:ss.SSS}] %c - %m")); | |
this.log.addAppender(appender); | |
Object.defineProperty(this, "loginForm", | |
{ | |
value: this.createLoginForm(), | |
enumerable: true | |
}); | |
Object.defineProperty(this, "signupForm", | |
{ | |
value: this.createSignupForm(), | |
enumerable: true | |
}); | |
Object.defineProperty(this, "idToComponent", | |
{ | |
value: {}, | |
enumerable: true | |
}); | |
this.idToComponent[this.loginForm.getId()] = this.loginForm; | |
this.idToComponent[this.signupForm.getId()] = this.signupForm; | |
} | |
/** | |
* @return {Form} the form used to login | |
*/ | |
AuthenticationsPage.prototype.createLoginForm = function() | |
{ | |
"use strict"; | |
var validationRules = | |
{ | |
email: | |
{ | |
identifier: "email", | |
rules: | |
[ | |
{ | |
type: "email", | |
prompt: "Must be a valid email address" | |
} | |
] | |
}, | |
password: | |
{ | |
identifier: "password", | |
rules: | |
[ | |
{ | |
type: "length[8]", | |
prompt: "The minimum length is 8 characters" | |
}, | |
{ | |
type: "maxLength[100]", | |
prompt: "The maximum length is 100 characters" | |
} | |
] | |
} | |
}; | |
/** | |
* Redirect the browser back to the referrer page. | |
*/ | |
function redirectToReferrer() | |
{ | |
var referrer = new URI(window.location.href).query(true).referer; | |
if (referrer) | |
window.location.replace(Authentications.addToUri(new URI(referrer))); | |
else | |
window.location.reload(); | |
} | |
/** | |
* @function | |
* @this AuthenticationsPage | |
* @return {Q} a Promise that returns {@code undefined} on success and {@code Error} on | |
* failure | |
*/ | |
function onSubmit() | |
{ | |
// jshint validthis:true | |
Preconditions.requireThat(this, "this").isInstanceOf(AuthenticationsPage); | |
var $form = this.loginForm.$form; | |
var email = $form.find("input[name='email']").val(); | |
var password = $form.find("input[name='password']").val(); | |
return this.login(email, password). | |
then(Authentications.saveToken). | |
then(redirectToReferrer); | |
} | |
return new Form($("#loginForm"), validationRules, onSubmit.bind(this), | |
this.afterStateChanged.bind(this)); | |
}; | |
/** | |
* @return {Form} the form used to sign up | |
*/ | |
AuthenticationsPage.prototype.createSignupForm = function() | |
{ | |
"use strict"; | |
var validationRules = | |
{ | |
email: | |
{ | |
identifier: "email", | |
rules: | |
[ | |
{ | |
type: "email", | |
prompt: "Must be a valid email address" | |
} | |
] | |
}, | |
password: | |
{ | |
identifier: "password", | |
rules: | |
[ | |
{ | |
type: "length[8]", | |
prompt: "The minimum length is 8 characters" | |
}, | |
{ | |
type: "maxLength[100]", | |
prompt: "The maximum length is 100 characters" | |
} | |
] | |
}, | |
name: | |
{ | |
identifier: "name", | |
rules: | |
[ | |
{ | |
type: "empty", | |
prompt: "Please enter a name" | |
}, | |
{ | |
type: "maxLength[100]", | |
prompt: "The name must be shorter than 100 characters" | |
} | |
] | |
} | |
}; | |
/** | |
* @function | |
* @this AuthenticationsPage | |
* @return {Q} a Promise that returns {@code undefined} on success and {@code Error} on | |
* failure | |
*/ | |
function onSubmit() | |
{ | |
// jshint validthis:true | |
Preconditions.requireThat(this, "this").isInstanceOf(AuthenticationsPage); | |
var $form = this.signupForm.$form; | |
var email = $form.find("input[name='email']").val(); | |
var password = $form.find("input[name='password']").val(); | |
var name = $form.find("input[name='name']").val(); | |
return User.insert(Html.getBaseUri(), email, password, name).then(function() | |
{ | |
return this.login(email, password). | |
then(function() | |
{ | |
alert("You are signed up"); | |
}). | |
catch(function(e) | |
{ | |
alert(e); | |
}); | |
}.bind(this)); | |
} | |
var result = new Form($("#signupForm"), validationRules, onSubmit.bind(this), | |
this.afterStateChanged.bind(this)); | |
var oldSubmitError = result.afterSubmitError; | |
result.afterSubmitError = function(error) | |
{ | |
if (error instanceof ConflictingResourceException) | |
{ | |
this.showError("The email address is already in use by <a href=\"" + error.conflict + | |
"\">" + error.conflict + "</a>"); | |
return; | |
} | |
oldSubmitError.call(this, error); | |
}.bind(result); | |
return result; | |
}; | |
/** | |
* Invoked after the page's state changes. | |
*/ | |
AuthenticationsPage.prototype.afterStateChanged = function() | |
{ | |
"use strict"; | |
history.replaceState(this.getState(), window.title, window.location.href); | |
}; | |
/** | |
* @typedef {Object} AuthenticationsState | |
* | |
* @property {String} id the DOM id of the component that generated the state | |
* @property {Object} state the page's state | |
*/ | |
/** | |
* @return {AuthenticationsState} the page's state | |
*/ | |
AuthenticationsPage.prototype.getState = function() | |
{ | |
"use strict"; | |
return { | |
id: "main", | |
state: | |
{ | |
loginForm: this.loginForm.getState(), | |
signupForm: this.signupForm.getState() | |
} | |
}; | |
}; | |
/** | |
* Sets the page's state. | |
* | |
* @param {AuthenticationsState} data the page's state | |
*/ | |
AuthenticationsPage.prototype.setState = function(data) | |
{ | |
"use strict"; | |
Preconditions.requireThat(data, "data").isSet(); | |
Preconditions.requireThat(data.id, "data.id").isSet(); | |
Preconditions.requireThat(data.state, "data.state").isSet(); | |
Preconditions.requireThat(data.state.loginForm, "data.state.loginForm").isSet(); | |
Preconditions.requireThat(data.state.signupForm, "data.state.signupForm").isSet(); | |
this.loginForm.setState(data.state.loginForm); | |
this.signupForm.setState(data.state.signupForm); | |
}; | |
/** | |
* Logs into the server. | |
* | |
* @param {String} email the user's email address | |
* @param {String} password the user's password | |
* @return {Q} a Promise that returns {@code undefined} on success and {@code Error} on | |
* failure | |
*/ | |
AuthenticationsPage.prototype.login = function(email, password) | |
{ | |
"use strict"; | |
return Authentication.insert(Html.getBaseUri(), email, password); | |
}; | |
/** | |
* Invoked on page load. | |
*/ | |
AuthenticationsPage.prototype.onLoad = function() | |
{ | |
"use strict"; | |
LoginPanel.configure(); | |
$(".menu .item").tab( | |
{ | |
history: false | |
} | |
); | |
$(".ui.loader").parent().dimmer("hide"); | |
}; | |
$(function() | |
{ | |
"use strict"; | |
Authentications.reload().then(function() | |
{ | |
RequestScope.csrfToken = $("input[name='" + Utilities.csrfToken + "']").val(); | |
var page = new AuthenticationsPage(); | |
page.onLoad(); | |
window.addEventListener("popstate", function() | |
{ | |
// The browser fires "popstate" after transitioning to a new page using the forward or back button. | |
// | |
// We are expected to register an event listener as soon as possible to ensure that we do | |
// not miss any events ("popstate" may be invoked on page load). Unfortunately, we cannot act on the event | |
// without the help of RequireJS modules and by the time they load the event has already fired. To work around | |
// this problem, we invoke {@code afterStateChanged()} when the page first loads. This method | |
// accesses {@code history.state} instead of relying on {@code event.state}. | |
// | |
// We use "popstate" to load from {@code history.state} into components after transitioning to a new page, and | |
// {@code afterStateChanged()} to save into {@code history.state} after updating components. | |
if (!history.state) | |
return; | |
// See http://stackoverflow.com/a/24903057/14731 | |
Utilities.assert(history.state.id === "main", history.state.id); | |
this.setState(history.state); | |
}); | |
// Set initial state | |
page.afterStateChanged(); | |
}).done(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment