Skip to content

Instantly share code, notes, and snippets.

@cowwoc
Created January 28, 2015 22:23
Show Gist options
  • Save cowwoc/fabfd4bff45c677362ed to your computer and use it in GitHub Desktop.
Save cowwoc/fabfd4bff45c677362ed to your computer and use it in GitHub Desktop.
/* 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