Merge pull request #3 from vincentmdealmeida/ValidationV2
Added full input validation to the Create Event including unique even…
This commit is contained in:
commit
6fc6628995
4 changed files with 613 additions and 106 deletions
|
@ -1,17 +1,12 @@
|
|||
import os
|
||||
from io import StringIO
|
||||
from django.shortcuts import render
|
||||
from django.forms import inlineformset_factory, formset_factory
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect, HttpResponse, Http404
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import get_object_or_404, render, render_to_response
|
||||
from django.template import RequestContext
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils import timezone
|
||||
from django.views import generic
|
||||
from django.conf import settings
|
||||
from django.core import serializers
|
||||
|
||||
from .forms import EventForm, PollForm, OptionFormset, QuestionFormset, OrganiserFormSet, TrusteeFormSet, VoteForm, EventSetupForm, EventEditForm, DecryptionFormset, DecryptionFormSetHelper
|
||||
from .models import Event, Poll, PollOption, EmailUser, Ballot, TrusteeKey, Decryption
|
||||
|
@ -304,21 +299,12 @@ def create_event(request):
|
|||
|
||||
return HttpResponseRedirect("/event/")
|
||||
elif request.method == "GET":
|
||||
#form = EventForm()
|
||||
#organiser_formset = OrganiserFormSet(prefix="formset_organiser", initial=[{'email': request.user.email }])
|
||||
#trustee_formset = TrusteeFormSet(prefix="formset_trustee", initial=[{'email': request.user.email }])
|
||||
# Create the formset, specifying the form and formset we want to use.
|
||||
'''return render(request,
|
||||
"polls/create_event.html",
|
||||
{
|
||||
"event": event,
|
||||
"form": form,
|
||||
"organiser_formset": organiser_formset,
|
||||
"trustee_formset": trustee_formset,
|
||||
"G_R_SITE_KEY": settings.RECAPTCHA_PUBLIC_KEY
|
||||
})'''
|
||||
# Obtain context data for the rendering of the html template
|
||||
events = Event.objects.all()
|
||||
demo_users = DemoUser.objects.all()
|
||||
|
||||
return render(request, "polls/create_event.html", {"G_R_SITE_KEY": settings.RECAPTCHA_PUBLIC_KEY, "user_email": request.user.email})
|
||||
# Render the template
|
||||
return render(request, "polls/create_event.html", {"G_R_SITE_KEY": settings.RECAPTCHA_PUBLIC_KEY, "user_email": request.user.email, "events": events, "demo_users": demo_users})
|
||||
else:
|
||||
return HttpResponseNotAllowed()
|
||||
|
||||
|
|
|
@ -5,6 +5,29 @@
|
|||
|
||||
{% block content %}
|
||||
|
||||
<script type="text/javascript">
|
||||
// This events list is generated for later input validation
|
||||
var events_list = [
|
||||
{% for event in events %}
|
||||
{% if not forloop.first %},{% endif %}
|
||||
{
|
||||
title: "{{ event.title }}",
|
||||
slug: "{{ event.EID }}"
|
||||
}
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
// This list of demo user emails will be used for input validation
|
||||
var user_emails = [
|
||||
{% for user in demo_users %}
|
||||
{% if not forloop.first %},{% endif %}
|
||||
{
|
||||
email: "{{ user.email }}"
|
||||
}
|
||||
{% endfor %}
|
||||
];
|
||||
</script>
|
||||
|
||||
<!-- The following UI was ported from the Election Authority UI in DEMOS1 by Vincent de Almeida -->
|
||||
<!-- The DEMOS1 repository can be found at: https://github.com/mlevogiannis/demos-voting -->
|
||||
<!-- TODO: look into i18n translations as this was a feature implemented in DEMOS1 -->
|
||||
|
@ -19,31 +42,33 @@
|
|||
{% csrf_token %}
|
||||
<!-- TODO: Have not imported the if form not valid template code as this needs further investigating -->
|
||||
<!-- Name -->
|
||||
<div class="form-group"> <!-- Excluded class(missing %s): { if election_form.name.errors }has-error{ endif } -->
|
||||
<div class="form-group">
|
||||
<label for="name-input" class="col-sm-3 col-md-2 control-label">Name:</label> <!-- This text can be a template variable -->
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<input type="text" class="form-control input-control" id="name-input" placeholder="Example: My poll" name="name-input" maxlength="255">
|
||||
<span id="name-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
<span id="name-input-hint-block" class="help-block">
|
||||
A short and clear name.
|
||||
<!-- TODO: Alignment is potentially slightly too much to the left -->
|
||||
</span>
|
||||
<span id="name-input-error-block" class="help-block errorText">
|
||||
<!-- Errors flagged here -->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Slug / Identifier -->
|
||||
<div class="form-group"> <!-- Excluded class(missing %s): { if election_form.slug.errors }has-error{ endif } -->
|
||||
<div class="form-group">
|
||||
<label for="identifier-input" class="col-sm-3 col-md-2 control-label">Identifier:</label> <!-- This text can be a template variable -->
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<input type="text" class="form-control input-control" id="identifier-input" placeholder="Example: My-poll" name="identifier-input" maxlength="255">
|
||||
<span id="identifier-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
Used in the election URL, it must only consist of letters, numbers, underscores or hyphens; no whitespace is permitted.
|
||||
<!-- TODO: Alignment is potentially slightly too much to the left -->
|
||||
</span>
|
||||
<span id="identifier-input-error-block" class="help-block errorText">
|
||||
<!-- Errors flagged here -->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Voting start time -->
|
||||
<div class="form-group "> <!-- Excluded class(missing %s): { if election_form.voting_starts_at.errors }has-error{ endif } -->
|
||||
<div class="form-group ">
|
||||
<label for="vote-start-input" class="col-sm-3 col-md-2 control-label">Voting starts at:</label>
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<div class="input-group date">
|
||||
|
@ -55,13 +80,15 @@
|
|||
</span>
|
||||
</div>
|
||||
<span id="vote-start-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
Date and time when registered voters can commence voting. This includes the UTC offset starting with '+'.
|
||||
</span>
|
||||
<span id="vote-start-input-error-block" class="help-block errorText">
|
||||
<!-- Errors flagged here -->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Voting end time -->
|
||||
<div class="form-group "> <!-- Excluded class(missing %s): { if election_form.voting_ends_at.errors }has-error{ endif } -->
|
||||
<div class="form-group ">
|
||||
<label for="vote-end-input" class="col-sm-3 col-md-2 control-label">Voting ends at:</label>
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<div class="input-group date">
|
||||
|
@ -73,9 +100,11 @@
|
|||
</span>
|
||||
</div>
|
||||
<span id="vote-end-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
Date and time when registered voters can no longer vote. This includes the UTC offset starting with '+'.
|
||||
</span>
|
||||
<span id="vote-end-input-error-block" class="help-block errorText">
|
||||
<!-- Errors flagged here -->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Question / Statement -->
|
||||
|
@ -84,9 +113,10 @@
|
|||
<div class="col-sm-9 col-md-10">
|
||||
<input type="text" class="form-control input-control" id="question-input" placeholder="Example: Elections for the European Parliament" name="question-input" maxlength="200">
|
||||
<span id="question-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
Question / Statement that will be put forward to voters along with the below options.
|
||||
<!-- TODO: Alignment is potentially slightly too much to the left -->
|
||||
</span>
|
||||
<span id="question-input-error-block" class="help-block errorText">
|
||||
<!-- Errors flagged here -->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -94,14 +124,13 @@
|
|||
<div class="form-group">
|
||||
<label for="options-input" class="col-sm-3 col-md-2 control-label">Options:</label> <!-- This text can be a template variable -->
|
||||
<div class="col-sm-9 col-md-10">
|
||||
<div class="form-group"> <!-- Excluded class(missing %s): { if option_formset.non_form_errors }has-error{ endif }-->
|
||||
<div class="form-group">
|
||||
<table id="options-input-table" class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">#</th>
|
||||
<th>Option</th>
|
||||
<th class="text-center">Actions</th>
|
||||
<!--Not sure what this does so disabling it: <th class="hidden">{ option_formset.management_form }</th> -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sort" class="formset option-formset" data-formset-prefix="questions" data-formset-type="inline">
|
||||
|
@ -113,10 +142,9 @@
|
|||
</th>
|
||||
<!-- Option Label -->
|
||||
<td>
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<div>
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: Candidate 1" id="option-name-input" name="option-name-input" maxlength="200">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
<!-- Delete Action -->
|
||||
|
@ -134,10 +162,9 @@
|
|||
</th>
|
||||
<!-- Option Label -->
|
||||
<td>
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<div>
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: Candidate 2" id="option-name-input" name="option-name-input" maxlength="200">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
<!-- Delete Action -->
|
||||
|
@ -155,10 +182,9 @@
|
|||
</th>
|
||||
<!-- Option Label -->
|
||||
<td>
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<div>
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: Candidate X" id="option-name-input" name="option-name-input" maxlength="200">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
<!-- Delete Action -->
|
||||
|
@ -177,9 +203,11 @@
|
|||
</button>
|
||||
</div>
|
||||
<span id="question-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
Drag and drop to re-order options.
|
||||
</span>
|
||||
<span id="options-input-error-block" class="help-block errorText">
|
||||
<!-- Errors flagged here -->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -191,18 +219,17 @@
|
|||
<div class="col-xs-6">
|
||||
<label class="sr-only" for="minimum-input">Minimum</label>
|
||||
<input type="number" class="form-control input-control" id="minimum-input" placeholder="Minimum" value="" name="minimum-input" min="0"> <!-- TODO: Max should be set to the number of options -->
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<label class="sr-only" for="maximum-input">Maximum</label>
|
||||
<input type="number" class="form-control input-control" id="maximum-input" placeholder="Maximum" value="" name="maximum-input" min="1"> <!-- TODO: Max should be set to the number of options -->
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</div>
|
||||
<span id="question-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
Minimum and maximum number of option selections that a voter can make for the specified question / statement.
|
||||
<!-- TODO: Alignment is potentially slightly too much to the left -->
|
||||
</span>
|
||||
<span id="selections-input-error-block" class="help-block errorText">
|
||||
<!-- Errors flagged here -->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -217,7 +244,6 @@
|
|||
<th class="text-center">#</th>
|
||||
<th>Email</th>
|
||||
<th class="text-center">Actions</th>
|
||||
<!--Not sure what this does so disabling it: <th class="hidden">{ option_formset.management_form }</th> -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="formset organiser-formset" data-formset-prefix="organisers" data-formset-type="inline">
|
||||
|
@ -229,10 +255,9 @@
|
|||
</th>
|
||||
<td>
|
||||
<!-- Email -->
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<div>
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: organiser@example.com" id="organiser-email-input" name="organiser-email-input" value="{{ user_email }}" maxlength="255">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
<td class="formset-form-actions text-center">
|
||||
|
@ -250,10 +275,9 @@
|
|||
</th>
|
||||
<td>
|
||||
<!-- Email -->
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<div>
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: organiser@example.com" id="organiser-email-input" name="organiser-email-input" maxlength="255">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
<td class="formset-form-actions text-center">
|
||||
|
@ -270,10 +294,9 @@
|
|||
</th>
|
||||
<td>
|
||||
<!-- Email -->
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<div>
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: organiser@example.com" id="organiser-email-input" name="organiser-email-input" maxlength="255">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: organiserX@example.com" id="organiser-email-input" name="organiser-email-input" maxlength="255">
|
||||
</div>
|
||||
</td>
|
||||
<td class="formset-form-actions text-center">
|
||||
|
@ -291,9 +314,11 @@
|
|||
</button>
|
||||
</div>
|
||||
<span id="question-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
Drag and drop to re-order emails.
|
||||
</span>
|
||||
<span id="organisers-input-error-block" class="help-block errorText">
|
||||
<!-- Errors flagged here -->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -308,7 +333,6 @@
|
|||
<th class="text-center">#</th>
|
||||
<th>Email</th>
|
||||
<th class="text-center">Actions</th>
|
||||
<!--Not sure what this does so disabling it: <th class="hidden">{ option_formset.management_form }</th> -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="formset trustee-formset" data-formset-prefix="trustees" data-formset-type="inline">
|
||||
|
@ -323,7 +347,6 @@
|
|||
<div> <!-- Has error conditional class removed -->
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: trustee@example.com" id="trustee-email-input" name="trustee-email-input" value="{{ user_email }}" maxlength="255">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
<td class="formset-form-actions text-center">
|
||||
|
@ -344,7 +367,6 @@
|
|||
<div> <!-- Has error conditional class removed -->
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: trustee@example.com" id="trustee-email-input" name="trustee-email-input" maxlength="255">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
</div>
|
||||
</td>
|
||||
<td class="formset-form-actions text-center">
|
||||
|
@ -363,8 +385,7 @@
|
|||
<!-- Email -->
|
||||
<div> <!-- Has error conditional class removed -->
|
||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: trustee@example.com" id="trustee-email-input" name="trustee-email-input" maxlength="255">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: trusteeX@example.com" id="trustee-email-input" name="trustee-email-input" maxlength="255">
|
||||
</div>
|
||||
</td>
|
||||
<td class="formset-form-actions text-center">
|
||||
|
@ -381,10 +402,12 @@
|
|||
Add Trustee Email
|
||||
</button>
|
||||
</div>
|
||||
<span id="question-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
<span id="trustees-input-help-block" class="help-block">
|
||||
Drag and drop to re-order emails.
|
||||
</span>
|
||||
<span id="trustees-input-error-block" class="help-block errorText">
|
||||
<!-- Errors flagged here -->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -394,15 +417,16 @@
|
|||
<div class="col-sm-9 col-md-10">
|
||||
<textarea class="form-control input-control" id="voters-list-input" placeholder="alice@example.com, bob@example.com..." name="voters-list-input" rows="4"></textarea>
|
||||
<span id="voters-list-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
Manually enter email addresses separated with commas. Alternatively, you can also upload a CSV file:
|
||||
<!-- TODO: Alignment is potentially slightly too much to the left -->
|
||||
</span>
|
||||
<label for="files" class="btn btn-primary">
|
||||
Upload CSV
|
||||
</label>
|
||||
<input type="file" id="files" name="file" class="btn-info">
|
||||
<h4 id="result" class="hidden"></h4>
|
||||
<span id="voters-input-error-block" class="help-block errorText">
|
||||
<!-- Errors flagged here -->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- reCAPTCHA -->
|
||||
|
@ -414,15 +438,16 @@
|
|||
data-expired-callback="reCExpiredCallback"
|
||||
data-sitekey="{{ G_R_SITE_KEY }}"></div> <!-- Need to finish server implementation and import key from settings -->
|
||||
<span id="recaptcha-input-help-block" class="help-block">
|
||||
<!-- Error handling / input validation has been removed temporarily and would be placed here -->
|
||||
Tick the box to prove that you're not a robot.
|
||||
<!-- TODO: Alignment is potentially slightly too much to the left -->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<input class="btn btn-success" type="submit" value="Create Event" id="submit-event-create" disabled/>
|
||||
<input class="btn btn-danger" type="button" value="Cancel" id="cancel-event-create" onclick="location.href='{% url 'polls:index' %}'" />
|
||||
<span id="all-errors-help-block" class="help-block errorText">
|
||||
<!-- Place a summary of all errors here -->
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -135,6 +135,8 @@ div.formset_object {
|
|||
|
||||
/* Error */
|
||||
.errorText {
|
||||
margin-top: 0.5em;
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
// Form submission and validation
|
||||
var submitBtn = $("#submit-event-create");
|
||||
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]{2}:[0-9]{2}\s\+[0-9]{2}:[0-9]{2}$/;
|
||||
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
|
||||
|
@ -11,7 +17,7 @@ $("#election-form").submit(function(e) {
|
|||
|
||||
// Get a reference to the submit button
|
||||
submitBtn.prop('disabled', true);
|
||||
submitBtn.val('Please wait...');
|
||||
submitBtn.val(submitBtnWaitLabel);
|
||||
|
||||
// Disable the cancel button during validation
|
||||
var cancelBtn = $("#cancel-event-create");
|
||||
|
@ -21,91 +27,575 @@ $("#election-form").submit(function(e) {
|
|||
var formDataValid = isFormValid();
|
||||
|
||||
if( formDataValid === true ) {
|
||||
clearErrors();
|
||||
form.submit();
|
||||
} else {
|
||||
submitBtn.val('Errors Found');
|
||||
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 = arePollsAndOptsValid();
|
||||
var minSelectionValid = isMinSelectionValid();
|
||||
var maxSelectionValid = isMaxSelectionValid();
|
||||
var pollOptsValid = isPollAndOptsValid();
|
||||
var organisersEmailsValid = areOrganisersEmailsValid();
|
||||
var trusteesEmailsValid = areTrusteesEmailsValid();
|
||||
var votersListValid = isVotersListValid();
|
||||
var reCaptchaValid = isReCaptchaStillValid();
|
||||
|
||||
return nameValid && slugValid && voteStartValid && voteEndValid
|
||||
&& pollOptsValid && minSelectionValid && maxSelectionValid
|
||||
&& organisersEmailsValid && trusteesEmailsValid && votersListValid
|
||||
&& reCaptchaValid;
|
||||
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
|
||||
// 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() {
|
||||
return true;
|
||||
// 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;
|
||||
}
|
||||
|
||||
function isVoteStartValid() {
|
||||
var start_date_time = $('#vote-start-input').val();
|
||||
return isDateValid(start_date_time);
|
||||
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();
|
||||
return isDateValid(end_date_time);
|
||||
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 arePollsAndOptsValid() {
|
||||
// Future validation could be added here
|
||||
return true;
|
||||
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 isMinSelectionValid() {
|
||||
return true;
|
||||
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;
|
||||
}
|
||||
|
||||
function isMaxSelectionValid() {
|
||||
return true;
|
||||
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() {
|
||||
return true;
|
||||
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() {
|
||||
return true;
|
||||
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() {
|
||||
return true;
|
||||
}
|
||||
var valid = true;
|
||||
var votersInputVal = $('#voters-list-input').val();
|
||||
|
||||
function isReCaptchaStillValid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
$('.input-control').on('input', function(e) {
|
||||
if(reCaptchaValid === true) {
|
||||
submitBtn.val('Create Event');
|
||||
submitBtn.removeAttr('disabled');
|
||||
}
|
||||
// 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) {
|
||||
|
@ -145,7 +635,7 @@ function processFileChange(event) {
|
|||
$('#result').removeClass("hidden").html(
|
||||
totalNumEmails + " email(s) have been successfully uploaded.");
|
||||
|
||||
$('#voters-list-input').html(emails.join(', '));
|
||||
$('#voters-list-input').val(emails.join(', '));
|
||||
} else {
|
||||
// There were errors, so inform the user
|
||||
$('#result')
|
||||
|
@ -163,9 +653,8 @@ document.getElementById('files').addEventListener('change', processFileChange, f
|
|||
// reCAPTCHA
|
||||
|
||||
function reCVerificationCallback() {
|
||||
// TODO: call isFormValid before doing this and highlighting errors if any found
|
||||
reCaptchaValid = true;
|
||||
submitBtn.removeAttr('disabled');
|
||||
validateForm();
|
||||
}
|
||||
|
||||
function reCExpiredCallback() {
|
||||
|
@ -270,9 +759,14 @@ function updateFormset(formset) { // Ported from DEMOS 1. Updates the row number
|
|||
function updateForm(form, formIndex) { // Ported from DEMOS 1.
|
||||
// Specific update for option forms
|
||||
var mayBeTextInput = form.find('input:text')[0];
|
||||
if(mayBeTextInput.placeholder !== undefined
|
||||
&& mayBeTextInput.placeholder.indexOf("Candidate") > -1) {
|
||||
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');
|
||||
|
|
Reference in a new issue