Skip to content

Instantly share code, notes, and snippets.

@pamelafox
Last active December 18, 2015 02:49
Show Gist options
  • Save pamelafox/5714109 to your computer and use it in GitHub Desktop.
Save pamelafox/5714109 to your computer and use it in GitHub Desktop.
Forum Utility functions
function setupFormSave($form, xhrOptions, autoSave) {
xhrOptions = xhrOptions || {};
var lastFormData = $form.serialize();
xhrOptions.url = xhrOptions.url || $form.attr('action');
xhrOptions.type = xhrOptions.type || $form.attr('type');
var $saveButton = $form.find('button[type="submit"]');
var $saveStatus = $($saveButton.attr('data-update'));
function changeButtonText(message) {
var messageText = $saveButton.attr('data-' + message + '-message');
$saveButton.html(messageText);
}
function resetButton() {
var $requiredCheckboxes = $form.find('input[type="checkbox"]').filter('[required]');
if ($requiredCheckboxes.length && !$requiredCheckboxes.is(':checked')) {
$saveButton.attr('disabled', 'disabled');
return;
}
$saveButton.removeAttr('disabled');
changeButtonText('default');
}
// Re-enable button when form changes
$form.find('input, select')
.on('change', resetButton);
$form.find('input, textarea')
.on('keyup', resetButton);
// Do not submit on enter in an input besides the very last one
var $allInputs = $form.find('input, select');
$allInputs.each(function(ind, elem) {
if (ind != (($allInputs.length) - 1)) {
$(elem)
.on('keypress', function(event) {
return event.keyCode != 13;
});
}
});
function sendXHR() {
lastFormData = $form.serialize();
var type = (xhrOptions.type || 'POST')
.toLowerCase();
if (Coursera.api[type]) {
Coursera.api[type](xhrOptions.url, {
data: xhrOptions.data || $form.serialize() || null,
headers: xhrOptions.headers || {},
dataType: xhrOptions.dataType || 'json'
})
.done(function(responseJSON, textStatus, xhr) {
if (xhrOptions.success) {
xhrOptions.success(responseJSON, textStatus, xhr);
}
changeButtonText('success');
$saveStatus.html('(Saved!)');
renderAllErrors($form, null);
})
.fail(function(xhr, textStatus, errorThrown) {
var data = $.parseJSON(xhr.responseText);
if (xhrOptions.error) {
xhrOptions.error(xhr, textStatus, errorThrown);
}
resetButton();
// Clear as-we-type validators as server errors take precedence
// For example: a user registering with previously registered email
$form.find('input')
.off('.validate');
// Render errors from the server
renderAllErrors($form, data);
})
}
}
var shouldValidateForm = $form.attr('data-validate') == 'true';
if (shouldValidateForm) {
$form.find('input')
.each(function() {
var $field = $(this);
// It doesn't make sense to validate checkboxes on user interaction,
// only on form submit
if ($field.attr('type') == 'checkbox') {
return;
}
// While the user is typing, we *do* want to clear any errors if the field now validates
// but we don't want to show new errors yet.
$field.on('keyup.validate', function() {
if (!validateField($field)) {
renderFieldErrors($field, null);
}
});
// When the user moves away from the field (blur) or pastes,
// we do want to check for success and errors
$field.on('blur.validate paste.validate', function() {
renderFieldErrors($field, validateField($field));
});
});
}
$saveButton.on('click', function(event) {
event.preventDefault();
if (!shouldValidateForm || (shouldValidateForm && !validateForm($form))) {
changeButtonText('inflight');
$saveButton.attr('disabled', 'disabled');
sendXHR(xhrOptions);
}
});
function triggerSave() {
if ($form.serialize() != lastFormData) {
$saveButton.trigger('click');
$saveStatus.html('(Saving...)');
}
}
if (autoSave) {
var triggerSaveD = _.debounce(triggerSave, 1000);
$form.find('input, select')
.on('change.autosave', triggerSaveD);
$form.find('input, textarea')
.on('keyup.autosave', triggerSaveD);
$form.find('input, select, blur')
.on('blur.autosave paste.autosave', triggerSave);
}
}
function validateForm($form) {
var foundError = null;
$form.find('input')
.each(function() {
var $field = $(this);
var fieldError = validateField($field);
renderFieldErrors($field, fieldError);
foundError = foundError || fieldError;
if (fieldError && !foundError && $('input:focus')
.length === 0) {
$field.focus();
}
});
if (foundError) {
var invalidMessage = $form.attr('data-invalid-message') || 'Uh-oh, please check the form!';
renderFormError($form, invalidMessage);
// Shake the button
$form.find('button[type="submit"]')
.animate({
opacity: 0.4
}, 100)
.animate({
"margin-left": "-=10"
}, 100)
.animate({
"margin-left": "+=20"
}, 100)
.animate({
"margin-left": "-=20"
}, 100)
.animate({
"margin-left": "+=10"
}, 100)
.animate({
opacity: 1
}, 100);
}
return foundError;
}
function validateField($field) {
var fieldType = $field.attr('type');
var fieldValue = $field.val();
var fieldPattern = $field.attr('pattern');
var error = null;
// Check pattern
var regEx = null;
if (fieldType == 'email') {
regEx = new RegExp(/[a-zA-Z0-9!#$%&'*+\/=?\^_`{|}~\-]+(?:\.[a-zA-Z0-9!#$%&'*+\/=?\^_`{|}~\-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?/);
} else if (fieldPattern) {
regEx = new RegExp(fieldPattern);
}
if (regEx) {
if (!regEx.test(fieldValue)) {
error = $field.attr('data-invalid-message') || 'Invalid field';
}
}
// Check required
if ($field.attr('required')) {
if (fieldType == 'text' && fieldValue.length === 0) {
error = 'This field is required.';
}
if (fieldType == 'checkbox' && !$field.is(':checked')) {
error = '⬆ Please check this!';
}
}
return error;
}
function renderAllErrors($form, data) {
var $errorsDom;
$form.find('.form-errors').remove();
$form.find('.error .help-inline').remove();
$form.find('.error').removeClass('error');
if (!data) return;
if (data.error_message) {
renderFormError($form, data.error_message);
}
if (data.field_errors) {
for (var fieldName in data.field_errors) {
var $fieldDom = $form.find('*[name="' + fieldName + '"]');
renderFieldErrors($fieldDom, data.field_errors[fieldName]);
$fieldDom.focus();
}
}
}
function renderFormError($form, error) {
$form.find('.form-errors').remove();
$errorsDom = $('<span></span>')
.addClass('form-errors')
.attr('role', 'alert')
.attr('aria-live', 'assertive')
.html(error);
if ($form.find('.actions').length) {
$form.find('.actions').children(':last').after($errorsDom);
} else if ($form.find('.modal-footer').length) {
$form.find('.modal-footer').children(':last').after($errorsDom);
} else if ($form.find('button[type="submit"]')) {
$form.find('button[type="submit"]').after($errorsDom);
}
}
function renderFieldErrors($fieldDom, fieldErrors) {
var $parentGroup = ($fieldDom.parents('.control-mini-group').length && $fieldDom.parents('.control-mini-group')
|| $fieldDom.parents('.control-group'));
$parentGroup.removeClass('error');
$parentGroup.find('.help-inline.error').remove();
if (fieldErrors) {
var errorId = ($fieldDom.attr('id') || '') + '-form-field-error-' + (new Date().getTime());
var describedBy = ($fieldDom.attr('aria-describedby') || '').split(',');
describedBy.push(errorId);
$fieldDom.attr('aria-describedby', describedBy.join(','));
$errorsDom = $('<span></span>')
.addClass('help-inline error')
.attr('role', 'alert')
.attr('aria-live', 'assertive')
.attr('id', errorId);
if (fieldErrors instanceof Array) {
for (var i = 0; i < fieldErrors.length; i++) {
$errorsDom.append('<span>' + fieldErrors[i] + '</span>');
}
} else {
$errorsDom.append(fieldErrors);
}
$parentGroup.addClass('error');
if ($fieldDom.attr('type') == 'checkbox') {
$parentGroup.find('.controls').append($errorsDom);
} else {
$fieldDom.after($errorsDom);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment