/*
Account.js
Code used for user login/signup process and similar pages
*/
$(function() {
/*
Show / Hide password input handling
*/
$('.btn-show-password').click(function () {
if ($(this).text() === 'Show') {
$('#showpassword').val($('#password').hide().val()).show();
$(this).text('Hide');
} else {
$('#password').val($('#showpassword').hide().val()).show();
$(this).text('Show');
}
});
$('#password').keyup(function () {
$('#showpassword').val($(this).val());
});
$('#showpassword').keyup(function () {
$('#password').val($(this).val());
});
});
// Rest Form Stuff (taken from base.js)
// Setup jQuery Extenders
(function($){
// jQuery Extending
$.fn.extend({
rest: function (options) {
this.each(function () {
restRequest.apply(this, [options]);
});
},
restForm: function (options) {
this.each(function () {
createRestForm.apply(this, [options])
});
}
});
var restRequest = function (options) {
/* TODO:
- Finish documentation
- Make restForm based off of this
- Make it work for $.rest direct calls (no element)
*/
var self = $(this);
var settings = {
url: self.attr('href') || self.data('url'),
method: self.data('method') || 'post',
timeout: self.data('timeout') || 30000,
data: {},
success: function () { },
error: function () { }
};
// Check to see if we were passed in a function
if (typeof(options) === 'function') {
settings.success = options;
} else {
// Overwrite settings with supplied options
$.extend(settings, options);
}
$.ajax({
url: settings.url,
type: settings.method,
timeout: settings.timeout,
contentType: 'application/json',
dataType: 'json',
data: settings.data,
success: function (resp) {
settings.success.apply(self, [resp]);
},
error: function (jqXHR, statusText) {
settings.error.apply(self, [jqXHR, statusText]);
}
});
};
var createRestForm = function (options) {
/*
* restForm
*
* This extension makes it easy to use REST endpoints with Bootstrap forms
*
* To initialize set data-form-handler="rest" as a form attribute or use:
*
* $('#form-id').restForm(options);
*
* Where options is an object containing overrides for the settings
* variable defined below.
*
* @url 'string' URL to which the form data will be submitted
* @type 'string' Method type - POST, PUT, DELETE
* @excludeFields [array] List of field names to exclude from data processing
* @data {object} Additional hardcoded data to include in the request
* @timeout (int) Milliseconds before request should timeout
* @disableSubmit (bool) Disables submit button until request returns
* @formErrorTarget (ele) Where to prepend any form-wide errors
*
*
* TODO: We could make some of the field handling methods more CPU efficient
* by using elements instead of field names.
*
*/
var self = $(this);
var settings = {
url: self.attr('action'),
method: self.attr('method'),
excludeFields: [],
data: {},
timeout: 30000,
disableSubmit: true,
formErrorTarget: null,
scrollToError: false,
/* Custom field handlers in the format:
{
password: {
clear: function () {
... Code to clear errors before re-submitting ...
},
error: function (errors) {
... Code to handle and render errors ...
},
data: function () {
... Code to return the data value of this field ...
}
}
}
*/
fieldHandlers: {},
// Prepares data to be submitted, takes data and returns processed data
prepareData: function (dataObj) {
return dataObj;
},
// Serializes form data - by default into JSON
serializeData: function (data) {
return JSON.stringify(data);
},
// Runs before the form is submitted via AJAX
beforeSubmit: function () { },
// Called immediately after AJAX is returned
afterSubmit: function () { },
// Clear overall form errors, Everything that's not a field
clearFormErrors: function () {
self.find('.form-error.alert-error').remove();
},
// Render overall form errors, Everything that's not a field
renderFormErrors: function (errorArray) {
if (typeof errorArray === 'string') {
errorArray = [errorArray];
}
var errorHTML = $("
").addClass('form-error alert alert-error').html(errorArray.join('
'));
if (settings.formErrorTarget) {
errorHTML.prependTo(settings.formErrorTarget);
} else {
errorHTML.prependTo(self);
}
},
// Clear errors on a field
clearFieldErrors: function (fieldName) {
var controlGroup = self.find('[name='+fieldName+']').parents('.control-group');
if (controlGroup.length) {
controlGroup.removeClass('error');
controlGroup.find('.alert-error').remove();
} else {
self.find('[name='+fieldName+']').siblings('.alert-error').remove();
}
},
// Renders errors for a field
renderFieldErrors: function (fieldName, errorArray) {
var field = self.find('[name='+fieldName+']');
var errorHTML = $("").addClass("help-inline ajax-error alert-error").text(errorArray.join('
'));
field.parents('.control-group').addClass('error');
errorHTML.insertAfter(field);
},
// Called on success
success: function (data) { },
// Called with errorObject right before errors are process
beforeError: function (errorObject) { },
// Fired after errors are processed
afterError: function (error, level, jqXHR) {
// Level is 0 by default, if it's higher you could log using sentry
},
// Fired on error if scrollToError setting is true
errorScroll: function (errorObject) {
var errorElement = $('.error,.form-error,.alert-error').first();
if (errorElement) {
$('html, body').animate({
scrollTop: Math.max(errorElement.offset().top - 150, 0)
}, 1000);
}
}
};
/********************** Initialization ************************/
// Use form error target if supplied with proper CSS attribute
var formErrorTarget = self.find('[data-form-target="error"]');
if (formErrorTarget.length) {
settings.formErrorTarget = formErrorTarget[0];
}
// Check to see if we were passed in a function
if (typeof(options) === 'function') {
settings.success = options;
} else {
// Overwrite settings with supplied options
$.extend(settings, options);
}
/******************** Built-in Handlers **********************/
var getFormData = function () {
/*
* Prepares form data object prior to serialization
*
* return @dataObj
*/
// Load array of form fields
var dataObj = {};
var formData = self.serializeArray();
// Loop through each form element
for (var i = 0; i < formData.length; i++) {
// Check to make sure this field isn't excluded
if (!settings.excludeFields.length && $.inArray(formData[i].name,settings.excludeFields) == -1) {
dataObj[formData[i].name] = formData[i].value;
}
}
// Loop through custom fieldHandlers to add in data for custom fields
$.each(settings.fieldHandlers, function (k, v) {
if (v.data) {
dataObj[k] = v.data.apply(self);
}
});
// Include extra data
$.extend(dataObj, settings.data);
// Run through custom prepareData function
dataObj = settings.prepareData.apply(self, [dataObj]);
return dataObj;
};
var beforeSubmitCallback = function () {
/*
* Handles preparing the form before the submit process
*
*/
// Disable submit buttons until data is returned
if (settings.disableSubmit) {
self.find('[type=submit]').attr('disabled', 'disabled');
}
// Clear form errors
settings.clearFormErrors.apply(self);
self.find('[name]').each( function () {
var name = $(this).attr('name');
if (settings.fieldHandlers[name] && settings.fieldHandlers[name].hasOwnProperty('clear')) {
settings.fieldHandlers[name].clear.apply(self)
} else {
settings.clearFieldErrors.apply(self, [name]);
}
});
settings.beforeSubmit.apply(self);
};
var afterSubmitCallback = function () {
/*
* Handles misc changes to the form after a response is returned
*
*/
// Re-enable the submit buttons
if (settings.disableSubmit) {
// Give some time to render any form changes
setTimeout(function () {
self.find('[type=submit]').removeAttr('disabled')
}, 200);
}
settings.afterSubmit.apply(self);
};
var errorsCallback = function (errorObject) {
/*
* Called on AJAX errors that pass back a JSON object of fields
*
* @errorObject JSON object containing {field: [errors]}
*/
settings.beforeError.apply(self, [errorObject]);
$.each(errorObject, function (k, v) {
// For form-level errors (not field) we use __all__ like Django Forms do
if (k === '__all__' || k == 'non_field_errors' || k == 'errors') {
settings.renderFormErrors.apply(self, [v]);
return;
}
// Check to see if custom error handler exists
if (settings.fieldHandlers[k] && settings.fieldHandlers[k].error) {
settings.fieldHandlers[k].error.apply(self, [v]);
} else {
// Use default error renderer
settings.renderFieldErrors.apply(self, [k, v]);
}
});
if(settings.scrollToError) {
settings.errorScroll.apply(self, [errorObject]);
}
};
/************************* Helper Functions *********************/
var getJSONObject = function (jsonString) {
/*
* Makes sure that only a JSON object is returned
*
* return {obj} or false
*/
try {
var obj = $.parseJSON(jsonString);
} catch (e) {
return false;
}
if (obj === null || typeof obj !== 'object') {
return false;
}
return obj;
};
/*************** Handler on the .submit() listener **************/
self.submit(function (e) {
// Stop event propogation just to be safe
e.stopPropagation();
e.preventDefault();
// Get form data
var data = settings.serializeData.apply(self, [getFormData()]);
// Call pre-submit handler
if (beforeSubmitCallback() === false) {
return false;
}
// Here it is, the holy grail. The actual request!!!
$.ajax({
url: settings.url,
type: settings.method,
timeout: settings.timeout,
contentType: 'application/json',
dataType: 'json',
data: data,
success: function (resp) {
// Call post-submit handler
afterSubmitCallback();
settings.success.apply(self, [resp]);
},
error: function (jqXHR, statusText) {
// Call post-submit handler
afterSubmitCallback();
// See if this is just a normal HTTP error (will have JSON)
var errors = getJSONObject(jqXHR.responseText);
if (errors !== false) {
errorsCallback(errors);
settings.afterError.apply(self, [statusText, 0, jqXHR]);
return;
}
// Handle common HTTP errors
if (statusText == 'error') {
var status = '';
switch (jqXHR.status) {
case 500:
case 502:
case 503:
status = 'Server Error. Please contact support!';
break;
case 404:
status = 'The resource you requested does not exist.';
break;
case 403:
status = 'You do not have permission to perform this action.'
break;
case 0:
status = 'Internet connection Lost.'
break;
default:
status = 'Uncaught error. Please contact support!';
break;
}
errorsCallback({'__all__': [status]});
settings.afterError.apply(self, [status, 2, jqXHR]);
// Check to see if it's an easy to define issue
} else {
switch (statusText) {
case 'parsererror': // Could not parse JSON form a normal response
case 'timeout': // Request timed out
case 'abort': // Ajax request aborted
default: // Uncaught error
errorsCallback({'__all__': [statusText]});
settings.afterError.apply(self, [statusText, 2, jqXHR]);
break;
}
}
// Log it because this is not an ordinary error
console.log('AJAX Exception: ' + statusText);
console.log(jqXHR);
}
});
// Need this in addition to stopping event propagation
return false;
});
};
})(jQuery);