1167 lines
No EOL
37 KiB
JavaScript
Executable file
1167 lines
No EOL
37 KiB
JavaScript
Executable file
// Form submission and validation
|
|
var updateModes = {create: 1, update: 2, delete: 3};
|
|
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 = [];
|
|
var create = true;
|
|
var pollCount = 0;
|
|
var pollIndex = 0;
|
|
var pollEditActive = false;
|
|
var numOfOpts = 2;
|
|
|
|
function finalisePolls() {
|
|
// Update the value of the poll count input
|
|
$('#poll-count-input').val(pollCount);
|
|
|
|
// Remove the empty and hidden poll row from the poll table
|
|
var formset = $(".formset[data-formset-prefix='polls']");
|
|
var emptyForm = formset.children('.formset-form-empty');
|
|
emptyForm.remove();
|
|
}
|
|
|
|
$("#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();
|
|
finalisePolls();
|
|
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 eventTimingsValid = isEventTimingsValid();
|
|
var pollCountValid = isPollCountValid();
|
|
var organisersEmailsValid = areOrganisersEmailsValid();
|
|
var trusteesEmailsValid = areTrusteesEmailsValid();
|
|
var votersListValid = isVotersListValid();
|
|
|
|
return nameValid && slugValid && voteStartValid && voteEndValid && pollCountValid
|
|
&& eventTimingsValid && 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().trim();
|
|
|
|
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 helpBlockId = "vote-end-input-error-block";
|
|
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: helpBlockId
|
|
})
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
$('#vote-end-input').change(function(e) {
|
|
validateFormField(isVoteEndValid, "vote-end-input-error-block");
|
|
});
|
|
|
|
$( "#vote-end-input" ).click(function() {
|
|
$( "#vote-end-input" ).change();
|
|
});
|
|
|
|
// This is different to the start and end validation functions in that it will check whether or not
|
|
// the start and end times overlap in an invalid way i.e. end time is before start etc.
|
|
function isEventTimingsValid() {
|
|
var valid = true;
|
|
var helpBlockId = "event-timings-error-block";
|
|
|
|
// Extract the string val from the vote start and end input controls
|
|
var start_date_time = $('#vote-start-input').val();
|
|
var end_date_time = $('#vote-end-input').val();
|
|
|
|
// Convert the string vals to Date objects
|
|
var start_dateObj = new Date(start_date_time);
|
|
var end_dateObj = new Date(end_date_time);
|
|
|
|
// Ensure that the start date is before the end date and that the end date is after the start date
|
|
if(!(start_dateObj < end_dateObj && end_dateObj > start_dateObj)) {
|
|
checkAndAddError({
|
|
error: "The start date must be before the end date and the end after the start date.",
|
|
helpBlockId: "event-timings-error-block"
|
|
});
|
|
|
|
valid = false;
|
|
} else {
|
|
clearError(helpBlockId);
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
function isDateValid(date_time) {
|
|
return dateRegex.test(date_time);
|
|
}
|
|
|
|
function isPollCountValid() {
|
|
var valid = true;
|
|
|
|
if(pollCount < 1) {
|
|
checkAndAddError({
|
|
error: "You need to define at least 1 poll.",
|
|
helpBlockId: "polls-input-error-block"
|
|
});
|
|
|
|
valid = false;
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
function isPollQValid() {
|
|
var valid = true;
|
|
|
|
// Check question is valid
|
|
var question = $('#question-name-input-' + pollIndex).val();
|
|
|
|
if(question === '') {
|
|
checkAndAddError({
|
|
error: "Question / Statement for the poll is blank.",
|
|
helpBlockId: "question-input-error-block-" + pollIndex
|
|
});
|
|
|
|
valid = false;
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
function isPollOptionsValid() {
|
|
var valid = true;
|
|
var optsInputs = $('.option-formset #option-name-input-' + pollIndex);
|
|
var helpBlockId = "options-input-error-block-" + pollIndex;
|
|
|
|
if(numOfOpts < 1) {
|
|
checkAndAddError({
|
|
error: "There needs to be at least 1 option",
|
|
helpBlockId: helpBlockId
|
|
});
|
|
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
function isMinMaxSelectionValid() {
|
|
var valid = true;
|
|
var minInput = $('#minimum-input-' + pollIndex);
|
|
var minInputMinAttr = parseInt(minInput[0].min);
|
|
var minInputVal = minInput.val();
|
|
var helpBlockId = "selections-input-error-block-" + pollIndex;
|
|
var errorStr = "";
|
|
|
|
if(minInputVal === "" || minInputVal < minInputMinAttr) {
|
|
errorStr = "The minimum option selection value cannot be less than " + minInputMinAttr + " or blank";
|
|
valid = false;
|
|
} else if (minInputVal > numOfOpts) {
|
|
errorStr = "The minimum option selection value cannot be more than the number of options (" + numOfOpts + ")";
|
|
valid = false;
|
|
}
|
|
|
|
var maxInput = $('#maximum-input-' + pollIndex);
|
|
var maxInputMinAttr = parseInt(maxInput[0].min);
|
|
var maxInputVal = maxInput.val();
|
|
|
|
if(maxInputVal === "" || maxInputVal < maxInputMinAttr) {
|
|
if(errorStr !== '') {
|
|
errorStr = errorStr + " and the maximum cannot be less than " + maxInputMinAttr + " or blank";
|
|
} else {
|
|
errorStr = "The maximum option selection value cannot be less than " + maxInputMinAttr + " or blank";
|
|
}
|
|
|
|
valid = false;
|
|
} else if (maxInputVal > numOfOpts) {
|
|
if (errorStr !== '') {
|
|
errorStr = errorStr + " and the maximum cannot be more than the number of options (" + numOfOpts + ")";
|
|
} else {
|
|
errorStr = "The maximum option selection value (" + maxInputVal + ") cannot be more than the number of options (" + numOfOpts + ")";
|
|
}
|
|
|
|
valid = false;
|
|
}
|
|
|
|
if(valid === false) {
|
|
errorStr = errorStr + ".";
|
|
|
|
checkAndAddError({
|
|
error: errorStr,
|
|
helpBlockId: helpBlockId
|
|
});
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
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 helpBlockId = "voters-input-error-block";
|
|
var votersInputVal = $('#voters-list-input').val();
|
|
|
|
// Check if the text area is blank
|
|
if(votersInputVal === '') {
|
|
checkAndAddError({
|
|
error: "The voters list is blank.",
|
|
helpBlockId: helpBlockId
|
|
});
|
|
|
|
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: helpBlockId
|
|
});
|
|
|
|
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: helpBlockId
|
|
});
|
|
}
|
|
|
|
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(', '));
|
|
|
|
// Finally validate the form field
|
|
validateFormField(isVotersListValid, "voters-input-error-block");
|
|
} else {
|
|
// There were errors, so inform the user
|
|
$('#result')
|
|
.removeClass("hidden")
|
|
.html("Error reading uploaded file! Check the format and try again")
|
|
.addClass("errorText");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
var filesHandle = document.getElementById('files');
|
|
|
|
if(filesHandle) {
|
|
filesHandle.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);
|
|
}
|
|
|
|
$("#polls-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, updateModes.update);
|
|
});
|
|
removedForms.each(function(index) {
|
|
updateForm($(this), forms.length + index, updateModes.delete);
|
|
});
|
|
}
|
|
|
|
function updateForm(form, formIndex, mode) { // Ported from DEMOS 1.
|
|
// Specific update for option forms
|
|
var mayBeTextInput = form.find('input:text')[0];
|
|
if(mayBeTextInput !== undefined && 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');
|
|
|
|
if (formsetPrefix === 'polls' && mode === updateModes.update) {
|
|
// Get a reference to the fields that need updating from the form including the table
|
|
var formFields = form.find('.formset-form-fields:first >');
|
|
var table = form.find('.table:first');
|
|
|
|
// Perform the ID updates on the fields based on the poll index
|
|
performFormInputUpdates(formFields, table, formIndex);
|
|
}
|
|
|
|
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()));
|
|
}
|
|
|
|
function updatePollFormInputs(form) {
|
|
// Obtain the cloned input fields for the dialog in order to update them
|
|
var clonedFields = form.find('.formset-form-fields:first >');
|
|
|
|
// Obtain a reference to the options table
|
|
var table = form.find('.table:first');
|
|
|
|
// Perform the ID updates on the fields based on the poll index
|
|
performFormInputUpdates(clonedFields, table, pollIndex);
|
|
}
|
|
|
|
function performFormInputUpdates(fields, table, index) {
|
|
// Update the table ID
|
|
table.attr("id", "options-table-" + index);
|
|
|
|
// Update the poll question / statement ID
|
|
fields.find(".dialogQ:first")
|
|
.attr("id", "question-name-input-" + index)
|
|
.attr("name", "question-name-input-" + index);
|
|
|
|
// Update one of the help block IDs for various sections of the dialog
|
|
var pollQuestionErrorHelpBlock = fields.find("#question-input-error-block");
|
|
pollQuestionErrorHelpBlock.attr("id", "question-input-error-block-" + index);
|
|
|
|
var pollOptionsErrorHelpBlock = fields.find("#options-input-error-block");
|
|
pollOptionsErrorHelpBlock.attr("id", "options-input-error-block-" + index);
|
|
|
|
var pollSelectionsErrorHelpBlock = fields.find("#selections-input-error-block");
|
|
pollSelectionsErrorHelpBlock.attr("id", "selections-input-error-block-" + index);
|
|
|
|
// Update the poll option input IDs
|
|
var optsInputs = fields.find(".dialogO");
|
|
|
|
for(var i = 0; i < optsInputs.length; i++) {
|
|
var input = optsInputs[i];
|
|
input.id = "option-name-input-" + index;
|
|
input.name = "option-name-input-" + index;
|
|
}
|
|
|
|
// Update the data-formset-prefix for correct referencing
|
|
var dataFormsetPrefix = "options-" + index;
|
|
var optionFormSet = fields.find(".option-formset");
|
|
optionFormSet.attr("data-formset-prefix", dataFormsetPrefix);
|
|
|
|
var addPollOptBtn = fields.find('.formset-add');
|
|
addPollOptBtn.attr("data-formset-prefix", dataFormsetPrefix);
|
|
|
|
// Update the poll min and max selection
|
|
fields.find(".min-input:first")
|
|
.attr("id", "minimum-input-" + index)
|
|
.attr("name", "minimum-input-" + index);
|
|
|
|
fields.find(".max-input:first")
|
|
.attr("id", "maximum-input-" + index)
|
|
.attr("name", "maximum-input-" + index);
|
|
}
|
|
|
|
function isDialogFormValid() {
|
|
var pollQValid = true;
|
|
var optsValid = true;
|
|
var minMaxSelValid = true;
|
|
|
|
// Check question is valid
|
|
var pollQErrorHelpBlockId = "question-input-error-block-" + pollIndex;
|
|
pollQValid = isPollQValid();
|
|
|
|
if(pollQValid === true) {
|
|
clearError(pollQErrorHelpBlockId);
|
|
} else {
|
|
highlightError(pollQErrorHelpBlockId);
|
|
}
|
|
|
|
// Check opts are valid
|
|
var pollOptsErrorHelpBlockId = "options-input-error-block-" + pollIndex;
|
|
optsValid = isPollOptionsValid();
|
|
|
|
if(optsValid === true) {
|
|
clearError(pollOptsErrorHelpBlockId);
|
|
} else {
|
|
highlightError(pollOptsErrorHelpBlockId);
|
|
}
|
|
|
|
// Check min and max selections are valid
|
|
var pollSelErrorHelpBlockId = "selections-input-error-block-" + pollIndex;
|
|
minMaxSelValid = isMinMaxSelectionValid();
|
|
|
|
if(minMaxSelValid === true) {
|
|
clearError(pollSelErrorHelpBlockId);
|
|
} else {
|
|
highlightError(pollSelErrorHelpBlockId);
|
|
}
|
|
|
|
return pollQValid && optsValid && minMaxSelValid;
|
|
}
|
|
|
|
function updateSelectionsMaxAtrr() {
|
|
var minInput = $('#minimum-input-' + pollIndex);
|
|
var maxInput = $('#maximum-input-' + pollIndex);
|
|
|
|
// Get the vals from the selection inputs and update them if they exceed the new max
|
|
var minInputVal = minInput.val();
|
|
|
|
if(minInputVal !== '' && (minInputVal > numOfOpts)) {
|
|
minInput.val(numOfOpts);
|
|
}
|
|
|
|
var maxInputVal = maxInput.val();
|
|
|
|
if(maxInputVal !== '' && (maxInputVal > numOfOpts)) {
|
|
maxInput.val(numOfOpts);
|
|
}
|
|
|
|
// Finally update the max attr to include the new total num of opts
|
|
minInput.attr("max", numOfOpts);
|
|
maxInput.attr("max", numOfOpts);
|
|
}
|
|
|
|
$('.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');
|
|
|
|
switch (formsetPrefix) {
|
|
case "polls":
|
|
// Set the index
|
|
pollIndex = pollCount;
|
|
|
|
// Update the IDs and names of all of the cloned input form fields based on the number of polls
|
|
updatePollFormInputs(form);
|
|
|
|
// 2 is the default number of opts shown upon the launch of the dialog
|
|
numOfOpts = 2;
|
|
|
|
// New poll is being created so edit mode hasn't been activated
|
|
pollEditActive = false;
|
|
break;
|
|
case "options-" + pollIndex:
|
|
numOfOpts++;
|
|
updateSelectionsMaxAtrr();
|
|
clearError("options-input-error-block-" + pollIndex);
|
|
break;
|
|
}
|
|
|
|
var formIndex = formset.children('.formset-form:not(.formset-form-empty)').length;
|
|
|
|
formset.append(form);
|
|
updateForm(form, formIndex, updateModes.create);
|
|
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);
|
|
}
|
|
|
|
// We need to reduce the poll count if we've removed a poll
|
|
if(formPrefix === "poll") {
|
|
pollCount--;
|
|
}
|
|
|
|
// Update the formset and inform that a form has been removed
|
|
updateFormset(formset);
|
|
formset.trigger('formsetFormRemoved');
|
|
|
|
// Perform validation and other operations now that a row has been removed based on the affected table
|
|
switch (formPrefix) {
|
|
case 'option':
|
|
// Decrement the number of total options and validate the options list
|
|
numOfOpts--;
|
|
updateSelectionsMaxAtrr();
|
|
validateFormField(isPollOptionsValid, "options-input-error-block-" + pollIndex);
|
|
break;
|
|
case 'organiser':
|
|
validateFormField(areOrganisersEmailsValid, "organisers-input-error-block");
|
|
break;
|
|
case 'trustee':
|
|
validateFormField(areTrusteesEmailsValid, "trustees-input-error-block");
|
|
break;
|
|
}
|
|
});
|
|
|
|
$('.formset-form-save').click(function (e) {
|
|
var dialogValid = isDialogFormValid();
|
|
|
|
if(dialogValid === true) {
|
|
var modal = $(this).closest('.modal');
|
|
var form = modal.data('form');
|
|
var name = $('#question-name-input-' + pollIndex).val();
|
|
form.find('.formset-form-name:first').text(name);
|
|
modal.data('formSave', true);
|
|
modal.modal('hide');
|
|
|
|
if(!pollEditActive) {
|
|
// Increment the poll count and clear any validation errors
|
|
pollCount++;
|
|
} else {
|
|
pollEditActive = false;
|
|
}
|
|
|
|
clearError("polls-input-error-block");
|
|
}
|
|
});
|
|
|
|
function extractPollIndexFromId(id) {
|
|
var idSplitArray = id.split('-');
|
|
pollIndex = parseInt(idSplitArray[3]);
|
|
}
|
|
|
|
$('.formset-form-edit').click(function (e) {
|
|
var form = $(this).closest('.formset-form');
|
|
var questionNameInput = form.find('.formset-form-fields:first > .dialogFormField > .dialogQ');
|
|
extractPollIndexFromId(questionNameInput.attr('id'));
|
|
$('#formset-modal').data('form', form).modal('show');
|
|
pollEditActive = true;
|
|
});
|
|
|
|
$('#formset-modal').on('show.bs.modal', function (e) { // Ported from DEMOS1
|
|
var modal = $(this);
|
|
var modalBody = modal.find('.modal-body > .row > [class^="col-"]');
|
|
var modalTitle = modal.find('.modal-title');
|
|
var form = modal.data('form');
|
|
var formset = form.parent('.formset');
|
|
var formFields = form.find('.formset-form-fields:first >').detach();
|
|
modal.data('formFields', formFields);
|
|
|
|
var clonedFields = formFields.clone(true);
|
|
modalBody.append(clonedFields);
|
|
|
|
modalTitle.text(formset.attr('data-formset-modal-title'));
|
|
formset.trigger('formsetModalShow', [modalBody]);
|
|
|
|
// Attach an event handler for poll option row sorting
|
|
$("#options-table-" + pollIndex).sortable({
|
|
items: "tr",
|
|
update: update
|
|
});
|
|
});
|
|
|
|
$('#formset-modal').on('hide.bs.modal', function (e) {
|
|
var modal = $(this);
|
|
var modalBody = modal.find('.modal-body > .row > [class^="col-"]');
|
|
var form = modal.data('form');
|
|
var formset = form.parent('.formset');
|
|
if (modal.data('formSave')) {
|
|
var formset = form.parent('.formset');
|
|
if (modal.data('formAdd')) {
|
|
manageTotalForms(formset, +1);
|
|
form.removeClass('hidden');
|
|
}
|
|
} else {
|
|
if (modal.data('formAdd')) {
|
|
form.remove();
|
|
}
|
|
}
|
|
formset.trigger('formsetModalHide', [modalBody]);
|
|
});
|
|
|
|
$('#formset-modal').on('hidden.bs.modal', function (e) {
|
|
var modal = $(this);
|
|
var modalBody = modal.find('.modal-body > .row > [class^="col-"]');
|
|
var form = modal.data('form');
|
|
var formset = form.parent('.formset');
|
|
var formFields = form.find('.formset-form-fields:first');
|
|
if (modal.data('formSave')) {
|
|
formFields.append(modalBody.children().detach());
|
|
if (modal.data('formAdd')) {
|
|
formset.trigger('formsetFormAdded', [form]);
|
|
} else {
|
|
formset.trigger('formsetFormEdited', [form]);
|
|
}
|
|
} else {
|
|
modalBody.empty();
|
|
if (!modal.data('formAdd')) {
|
|
formFields.append(modal.data('formFields'));
|
|
}
|
|
}
|
|
modal.find('.modal-title').text('');
|
|
modal.removeData();
|
|
}); |