This repository has been archived on 2022-08-01. You can view files and clone it, but cannot push or open issues or pull requests.
DEMOS2/static/js/create-event-poll.js

848 lines
26 KiB
JavaScript
Raw Normal View History

// Form submission and validation
var submitBtn = $("#submit-event-create");
var submitBtnLabel = "Create Event";
var submitBtnWaitLabel = "Please wait...";
var submitBtnErrLabel = "Errors Found";
var dateRegex = /^[0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)-(0[1-9]|[1-2][0-9]|30)))\s[0-9]?[0-9]:[0-9]{2}\s\+[0-9]{2}:[0-9]{2}$/;
var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
var reCaptchaValid = false;
var generalErrorBlock = document.getElementById('all-errors-help-block');
var errors = [];
$("#election-form").submit(function(e) {
// Intercept submission of form and temporarily suspend it
e.preventDefault();
var form = this;
// Get a reference to the submit button
submitBtn.prop('disabled', true);
submitBtn.val(submitBtnWaitLabel);
// Disable the cancel button during validation
var cancelBtn = $("#cancel-event-create");
cancelBtn.prop('disabled', true);
// Perform input validation
var formDataValid = isFormValid();
if( formDataValid === true ) {
clearErrors();
form.submit();
} else {
submitBtn.val(submitBtnErrLabel);
cancelBtn.removeAttr('disabled');
highlightErrors();
}
});
function validateForm() {
var formDataValid = isFormValid();
if( formDataValid === true ) {
clearErrors();
submitBtn.removeAttr('disabled');
submitBtn.val(submitBtnLabel);
} else {
submitBtn.val(submitBtnErrLabel);
highlightErrors();
}
}
function isFormValid() {
var nameValid = isNameValid();
var slugValid = isSlugValid();
var voteStartValid = isVoteStartValid();
var voteEndValid = isVoteEndValid();
var pollOptsValid = isPollAndOptsValid();
var organisersEmailsValid = areOrganisersEmailsValid();
var trusteesEmailsValid = areTrusteesEmailsValid();
var votersListValid = isVotersListValid();
return nameValid && slugValid && voteStartValid && voteEndValid && pollOptsValid
&& organisersEmailsValid && trusteesEmailsValid && votersListValid;
}
function validateFormField(validationFn, helpBlockId) {
var valid = validationFn();
if(valid === false) {
highlightError(helpBlockId);
} else {
clearError(helpBlockId);
if(reCaptchaValid === true) {
if(submitBtn.val() === submitBtnErrLabel) {
clearErrors();
}
submitBtn.removeAttr('disabled');
submitBtn.val(submitBtnLabel);
}
}
}
function isNameValid() {
// Based on a list of names supplied from the create_event html template
if(events_list !== undefined) {
var valid = true;
var event_name = $('#name-input').val();
if(event_name === '') {
checkAndAddError({
error: "The event name field is blank.",
helpBlockId: "name-input-error-block"
});
return false;
}
for(var i = 0; i < events_list.length; i++) {
var name = events_list[i].title;
if(name === event_name) {
valid = false;
// We need to flag this error to the user by generating an error that's
// later rendered
checkAndAddError({
error: "The event name '" + event_name + "' is already in use.",
helpBlockId: "name-input-error-block"
});
}
}
return valid;
} else {
// Can't perform validation
return true;
}
}
$('#name-input').on('input', function (e) { // Validation performed with every keystroke
validateFormField(isNameValid, "name-input-error-block");
});
function isSlugValid() {
// Based on a list of identifiers supplied from the create_event html template
if(events_list !== undefined) {
var valid = true;
var event_slug = $('#identifier-input').val();
if(event_slug === '') {
checkAndAddError({
error: "The event slug field is blank.",
helpBlockId: "identifier-input-error-block"
});
return false;
}
for(var i = 0; i < events_list.length; i++) {
var slug = events_list[i].slug;
if(slug === event_slug) {
valid = false;
// We need to flag this error to the user by generating an error that's
// later rendered
checkAndAddError({
error: "The event slug '" + event_slug + "' is already in use.",
helpBlockId: "identifier-input-error-block"
});
}
}
return valid;
} else {
// Can't perform validation
return true;
}
}
$('#identifier-input').on('input', function(e) {
validateFormField(isSlugValid, "identifier-input-error-block");
});
function isVoteStartValid() {
var helpBlockId = "vote-start-input-error-block";
var start_date_time = $('#vote-start-input').val();
var valid = isDateValid(start_date_time);
if(valid === false) {
checkAndAddError({
error: "The voting start date and time format is invalid.",
helpBlockId: helpBlockId
})
} else {
clearError(helpBlockId);
}
return valid;
}
$('#vote-start-input').change(function(e) {
validateFormField(isVoteStartValid, "vote-start-input-error-block");
});
$( "#vote-start-input" ).click(function() {
$( "#vote-start-input" ).change();
});
function isVoteEndValid() {
var end_date_time = $('#vote-end-input').val();
var valid = isDateValid(end_date_time);
if(valid === false) {
checkAndAddError({
error: "The voting end date and time format is invalid.",
helpBlockId: "vote-end-input-error-block"
})
}
return valid;
}
$('#vote-end-input').change(function(e) {
validateFormField(isVoteEndValid, "vote-end-input-error-block");
});
$( "#vote-end-input" ).click(function() {
$( "#vote-end-input" ).change();
});
function isDateValid(date_time) {
return dateRegex.test(date_time);
}
function isPollAndOptsValid() {
var pollValid = true;
var optsValid = true;
var minMaxSelValid = true;
// Check question is valid
pollValid = isPollValid();
// Check opts are valid
optsValid = isPollOptionsValid();
// Check min and max selections are valid
minMaxSelValid = isMinMaxSelectionValid();
return pollValid && optsValid && minMaxSelValid;
}
function isPollValid() {
var valid = true;
// Check question is valid
var question = $('#question-input').val();
if(question === '') {
checkAndAddError({
error: "Question / Statement for the poll is blank.",
helpBlockId: "question-input-error-block"
});
valid = false;
}
return valid;
}
function isPollOptionsValid() {
var valid = true;
var optsInputs = $('.option-formset #option-name-input');
var helpBlockId = "options-input-error-block";
var index = 0;
var errorStr = "Option ";
for(var i = 0; i < optsInputs.length; i++) {
var input = optsInputs[i];
if(input.placeholder.indexOf("X") === -1) {
if(input.value === ''){
errorStr = errorStr + (index+1) + " ";
valid = false;
}
index++;
}
}
if(valid === false) {
errorStr = errorStr + " is blank.";
checkAndAddError({
error: errorStr,
helpBlockId: helpBlockId
});
}
return valid;
}
$('#question-input').on('input', function (e) {
validateFormField(isPollValid, "question-input-error-block");
});
$('.option-formset #option-name-input').on('input', function(e) {
validateFormField(isPollOptionsValid, "options-input-error-block");
});
function isMinMaxSelectionValid() {
var valid = true;
var minInput = $('#minimum-input');
var minInputMinAttr = parseInt(minInput[0].min);
var minInputVal = minInput.val();
var helpBlockId = "selections-input-error-block";
var errorStr = "";
if(minInputVal < minInputMinAttr) {
errorStr = "The minimum option selection cannot be less than " + minInputMinAttr;
valid = false;
}
var maxInput = $('#maximum-input');
var maxInputMinAttr = parseInt(maxInput[0].min);
var maxInputVal = maxInput.val();
if(maxInputVal < maxInputMinAttr) {
if(errorStr !== '') {
errorStr = errorStr + " and the maximum cannot be less than " + maxInputMinAttr;
} else {
errorStr = "The maximum option selection cannot be less than " + maxInputMinAttr;
}
valid = false;
}
if(valid === false) {
errorStr = errorStr + ".";
checkAndAddError({
error: errorStr,
helpBlockId: helpBlockId
});
}
return valid;
}
$('#minimum-input, #maximum-input').on('input', function(e) {
validateFormField(isMinMaxSelectionValid, "selections-input-error-block");
});
function areOrganisersEmailsValid() {
var valid = true;
var organiserInputs = $('.organiser-formset #organiser-email-input');
var helpBlockId = "organisers-input-error-block";
var index = 0;
var errorBlankStr = "Organiser ";
var errorInvalidStr = "Organiser ";
var errorNotUserStr = "";
for(var i = 0; i < organiserInputs.length; i++) {
var input = organiserInputs[i];
if(input.placeholder.indexOf("X") === -1) {
// Check if the input field is blank
if(input.value === ''){
errorBlankStr = errorBlankStr + (index+1) + " ";
valid = false;
} else {
// Ensure that any email supplied is of a valid format
if (emailRegex.test(input.value) === false) {
errorInvalidStr = errorInvalidStr + (index + 1) + " ";
valid = false;
} else {
// If the email format is valid, ensure that an email of a registered DemoUser is being
// supplied and not a random email address
var foundMatch = user_emails.some(function (obj) {
return obj.email === input.value;
});
if(!foundMatch) {
errorNotUserStr = input.value + " is not a registered user and cannot be an organiser.";
valid = false;
}
}
}
index++;
}
}
if(valid === false) {
var errorStr = "";
// Will be greater than 10 if either a blank input or invalid input has been detected (10 char is the base
// length of the original err strings)
if( errorBlankStr.length > 10 ) {
errorStr = errorBlankStr + " email is blank. ";
}
if( errorInvalidStr.length > 10 ) {
errorStr = errorStr + errorInvalidStr + " email is invalid. ";
}
// This means an invalid user has been detected
if(errorNotUserStr.length > 0) {
errorStr = errorStr + errorNotUserStr;
}
checkAndAddError({
error: errorStr,
helpBlockId: helpBlockId
});
}
return valid;
}
$('.organiser-formset #organiser-email-input').on('input', function(e) {
validateFormField(areOrganisersEmailsValid, "organisers-input-error-block");
});
function areTrusteesEmailsValid() {
var valid = true;
var trusteeInputs = $('.trustee-formset #trustee-email-input');
var helpBlockId = "trustees-input-error-block";
var index = 0;
var errorBlankStr = "Trustee ";
var errorInvalidStr = "Trustee ";
for(var i = 0; i < trusteeInputs.length; i++) {
var input = trusteeInputs[i];
if(input.placeholder.indexOf("X") === -1) {
// Check if the input field is blank
if(input.value === ''){
errorBlankStr = errorBlankStr + (index+1) + " ";
valid = false;
} else if (emailRegex.test(input.value) === false) {
errorInvalidStr = errorInvalidStr + (index+1) + " ";
valid = false;
}
index++;
}
}
if(valid === false) {
var errorStr = "";
// Will be greater than 8 if either a blank input or invalid input has been detected (8 char is the base
// length of the original err strings)
if( errorBlankStr.length > 8 ) {
errorStr = errorBlankStr + " email is blank. ";
}
if( errorInvalidStr.length > 8 ) {
errorStr = errorStr + errorInvalidStr + " email is invalid.";
}
checkAndAddError({
error: errorStr,
helpBlockId: helpBlockId
});
}
return valid;
}
$('.trustee-formset #trustee-email-input').on('input', function(e) {
validateFormField(areTrusteesEmailsValid, "trustees-input-error-block");
});
function isVotersListValid() {
var valid = true;
var votersInputVal = $('#voters-list-input').val();
// Check if the text area is blank
if(votersInputVal === '') {
checkAndAddError({
error: "The voters list is blank.",
helpBlockId: "voters-input-error-block"
});
return false;
}
var errorStr = "";
var invalidCount = 0;
// Check whether one or multiple emails have been supplied
if(votersInputVal.indexOf(',') === -1) {
// Check the validity of the single email address
if(emailRegex.test(votersInputVal) === false) {
errorStr = errorStr + votersInputVal + " ";
valid = false;
invalidCount++;
}
} else {
// Proceed to check if the data within the text area is valid csv
var csvParseOutput = Papa.parse(votersInputVal);
if (csvParseOutput.errors.length > 0) {
checkAndAddError({
error: "The voters list contains invalid data. It should be a csv list containing voter email addresses.",
helpBlockId: "voters-input-error-block"
});
return false;
}
// Check that the emails supplied are valid email addresses (using a basic regex)
var votersEmails = csvParseOutput.data[0];
for(var i = 0; i < votersEmails.length; i++) {
var voter_email = votersEmails[i].replace(' ', '');
if (emailRegex.test(voter_email) === false) {
errorStr = errorStr + voter_email + " ";
valid = false;
invalidCount++;
}
}
}
if(valid === false) {
if(invalidCount > 1) {
errorStr = errorStr + "are invalid email addresses.";
} else {
errorStr = errorStr + "is an invalid email address.";
}
checkAndAddError({
error: errorStr,
helpBlockId: "voters-input-error-block"
});
}
return valid;
}
$('#voters-list-input').change(function(e) {
validateFormField(isVotersListValid, "voters-input-error-block");
});
function checkAndAddError(newError) { // Ensures that an error hasn't already been pushed
var found = errors.some(function(error) {
return error.error === newError.error && error.helpBlockId === newError.helpBlockId
});
if(!found) {
errors.push(newError);
}
}
function highlightErrors() {
// Generate the general list of errors
var baseGeneralString = "Errors were found in the form as follows:\n";
generalErrorBlock.appendChild(document.createTextNode(baseGeneralString));
generalErrorBlock.appendChild(makeErrorUL());
}
function highlightError(helpBlockId) {
for(var i = 0; i < errors.length; i++) {
var error = errors[i];
if(helpBlockId === error.helpBlockId) {
$('#' + helpBlockId).html(error.error);
}
}
}
function makeErrorUL() {
// Create the list element:
var list = document.createElement('ul');
for(var i = 0; i < errors.length; i++) {
// Perform list item generation
// Create the list item:
var item = document.createElement('li');
// Set its contents:
var errorText = errors[i].error;
item.appendChild(document.createTextNode(errorText));
// Add it to the list:
list.appendChild(item);
// Populate the error's associated error block with the data
$('#' + errors[i].helpBlockId).html(errorText);
}
return list;
}
function clearErrors() {
// Clear the errors array
errors.splice(0,errors.length);
// Clear the general list of errors
$('#all-errors-help-block').html('');
}
function clearError(helpBlockId) {
$('#' + helpBlockId).html('');
errors = errors.filter(e => e.helpBlockId !== helpBlockId);
}
// File handling
function processFileChange(event) {
var files = event.target.files;
// By parsing the file we ensure that it's valid CSV at the client side. Papa parse
// also allows us to aggregate emails from multiple rows in a file.
if(files !== undefined
&& files[0] !== undefined) {
Papa.parse(files[0], {
complete: function(results) {
var errors = results.errors;
if(errors.length === 0) {
var data = results.data;
var numRows = data.length;
var totalNumEmails = 0;
var emails = [];
if(numRows > 1) {
for(var i = 0; i < numRows; i++) {
var numEmails = data[i].length;
totalNumEmails += numEmails;
for(var j = 0; j < numEmails; j++) {
emails.push(data[i][j]);
}
}
} else if(numRows === 1) {
totalNumEmails = data[0].length;
for(var i = 0; i < totalNumEmails; i++) {
emails.push(data[0][i]);
}
}
$('#result').removeClass("hidden").html(
totalNumEmails + " email(s) have been successfully uploaded.");
$('#voters-list-input').val(emails.join(', '));
} else {
// There were errors, so inform the user
$('#result')
.removeClass("hidden")
.html("Error reading uploaded file! Check the format and try again")
.addClass("errorText");
}
}
});
}
}
document.getElementById('files').addEventListener('change', processFileChange, false);
// reCAPTCHA
function reCVerificationCallback() {
reCaptchaValid = true;
validateForm();
}
function reCExpiredCallback() {
reCaptchaValid = false;
submitBtn.prop('disabled', true);
}
// Slug field.
function slugify(value) {
return value.toString()
.replace(/[^\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\w\s-]+/g, '')
.replace(/^[-\s]+/, '')
.replace(/[-\s]+$/, '')
.replace(/[-\s]+/g, '-')
.toLowerCase();
}
$('#name-input').on('input', function (e) {
var slugField = $('#identifier-input');
if (!slugField.data('changed')) {
var name = $(this).val();
var maxLength = parseInt(slugField.attr('maxlength'));
var slug = slugify(name).substring(0, maxLength);
slugField.val(slug);
slugField.trigger('input');
}
});
$('#identifier-input').change(function (e) {
$(this).data('changed', $(this).val().length > 0);
});
// Poll start and end
var datetime_now = window.moment().seconds(0);
var datetime_format = "YYYY-MM-DD H:mm";
$("#vote-start-input, #vote-end-input").each(function(index, element) {
// Set datetimepickers' current, default and minimum date/time
var datetime_picker = $(element);
var datetime_iso8601 = datetime_picker.siblings(".datetime-iso8601-input").val();
var datetime_local = moment(datetime_iso8601);
datetime_picker.datetimepicker({
sideBySide: false,
minDate: datetime_now.clone().startOf("day"),
format: datetime_format,
widgetParent: $(datetime_picker)
});
var minutes = (Math.ceil(datetime_now.minute() / 5) * 5) + 5 * index;
var datetime_default = datetime_now.clone().minutes(minutes);
datetime_picker.data("DateTimePicker").defaultDate(datetime_default);
datetime_local = datetime_local.isValid() ? datetime_local.format(datetime_format) : "";
datetime_picker.children("input").val(datetime_local);
});
$('#vote-start-input, #vote-end-input').parent('.date').datetimepicker({
allowInputToggle: true,
icons: {
time: 'fa fa-clock-o',
date: 'fa fa-calendar',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right'
},
minDate: moment().startOf('day'),
useCurrent: false,
locale: moment.utc()
});
// Form management and Sortable rows
function update(event, ui) {
var formsetPrefix = $(event.target.lastElementChild).attr('data-formset-prefix');
var formset = $('.formset[data-formset-prefix="' + formsetPrefix + '"]');
updateFormset(formset);
}
$("#options-input-table, #organisers-input-table, #trustees-input-table").sortable({
items: "tr",
update: update
});
function updateFormset(formset) { // Ported from DEMOS 1. Updates the row number for the # and performs any removals.
var forms = formset.children('.formset-form:not(.formset-form-empty, .formset-form-removed)');
var removedForms = formset.children('.formset-form.formset-form-removed');
forms.each(function(index) {
updateForm($(this), index);
});
removedForms.each(function(index) {
updateForm($(this), forms.length + index);
});
}
function updateForm(form, formIndex) { // Ported from DEMOS 1.
// Specific update for option forms
var mayBeTextInput = form.find('input:text')[0];
if(mayBeTextInput.placeholder !== undefined) {
if( mayBeTextInput.placeholder.indexOf("Candidate") > -1) {
mayBeTextInput.placeholder = "Example: Candidate " + (formIndex + 1);
} else if (mayBeTextInput.placeholder.indexOf("trusteeX") > -1) {
mayBeTextInput.placeholder = "Example: trustee@example.com";
} else if (mayBeTextInput.placeholder.indexOf("organiserX") > -1) {
mayBeTextInput.placeholder = "Example: organiser@example.com";
}
}
var formset = form.parent('.formset');
var formsetPrefix = formset.attr('data-formset-prefix');
var formPrefix = formsetPrefix + '-' + formIndex;
var formPrefixRegex = new RegExp(formsetPrefix + '-(?:__prefix__|\\d+)');
form.find('*').addBack().each(function(index, element) {
$.each(this.attributes, function(index, attr) {
$(element).attr(attr.nodeName, function(index, attrValue) {
return attrValue.replace(formPrefixRegex, formPrefix);
});
});
});
form.find('input[name="' + formPrefix + '-ORDER"]').val(formIndex);
form.find('.formset-form-index:first').text(formIndex + 1);
}
function manageTotalForms(formset, value) { // Ported from DEMOS1.
var formsetPrefix = formset.attr('data-formset-prefix');
var totalForms = $('#id_' + formsetPrefix + '-TOTAL_FORMS');
var maxNumForms = $('#id_' + formsetPrefix + '-MAX_NUM_FORMS');
totalForms.val(parseInt(totalForms.val()) + value);
var addButton = $('.formset-add[data-formset-prefix="' + formsetPrefix + '"]');
var removedForms = formset.children('.formset-form.formset-form-removed');
addButton.prop('disabled', parseInt(totalForms.val()) - removedForms.length >= parseInt(maxNumForms.val()));
}
$('.formset-add').click(function (e) { // Ported from DEMOS1
var formsetPrefix = $(this).attr('data-formset-prefix');
var formset = $('.formset[data-formset-prefix="' + formsetPrefix + '"]');
var emptyForm = formset.children('.formset-form-empty');
var emptyFormCheckedInputs = emptyForm.find('input:checkbox:checked, input:radio:checked');
var form = emptyForm.clone(true).removeClass('formset-form-empty');
var formIndex = formset.children('.formset-form:not(.formset-form-empty)').length;
formset.append(form);
updateForm(form, formIndex);
emptyFormCheckedInputs.each(function (index) {
$(this).prop('checked', true);
});
switch (formset.attr('data-formset-type')) {
case 'modal':
$('#formset-modal').data('form', form).data('formAdd', true).modal('show');
break;
case 'inline':
manageTotalForms(formset, +1);
form.removeClass('hidden');
formset.trigger('formsetFormAdded', [form]);
break;
}
});
$('.formset-form-remove').click(function (e) { // Ported from DEMOS1
var form = $(this).closest('.formset-form');
var formPrefix = form.attr('data-formset-form-prefix');
var formset = form.parent('.formset');
if ($('#id_' + formPrefix + '-id').val()) {
$('#id_' + formPrefix + '-DELETE').prop('checked', true);
form.addClass('formset-form-removed hidden');
} else {
form.remove();
manageTotalForms(formset, -1);
}
updateFormset(formset);
formset.trigger('formsetFormRemoved');
// Perform validation now that a row has been removed
switch (formPrefix) {
case 'option':
validateFormField(isPollOptionsValid, "options-input-error-block");
break;
case 'organiser':
validateFormField(areOrganisersEmailsValid, "organisers-input-error-block");
break;
case 'trustee':
validateFormField(areTrusteesEmailsValid, "trustees-input-error-block");
break;
}
});