Merge pull request #5 from vincentmdealmeida/MultiplePolls
Upgraded the Create Event page and back-end view to handle the creati…
This commit is contained in:
commit
11abbb8a64
5 changed files with 410 additions and 171 deletions
|
@ -14,7 +14,7 @@ from allauthdemo.auth.models import DemoUser
|
||||||
|
|
||||||
Author: Vincent de Almeida
|
Author: Vincent de Almeida
|
||||||
|
|
||||||
Created: 11/07/2018
|
Created: 11/06/2018
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# TODO: Define a validation function that can do back-end verification on top of the front end validation
|
# TODO: Define a validation function that can do back-end verification on top of the front end validation
|
||||||
|
@ -31,8 +31,6 @@ class CreateNewEventModelAdaptor:
|
||||||
identifier = None
|
identifier = None
|
||||||
starts_at = None
|
starts_at = None
|
||||||
ends_at = None
|
ends_at = None
|
||||||
min_num_selections = 0
|
|
||||||
max_num_selections = 0
|
|
||||||
organisers = []
|
organisers = []
|
||||||
trustees = []
|
trustees = []
|
||||||
voters = []
|
voters = []
|
||||||
|
@ -47,6 +45,8 @@ class CreateNewEventModelAdaptor:
|
||||||
self.form_data = form_data.copy()
|
self.form_data = form_data.copy()
|
||||||
self.user = user
|
self.user = user
|
||||||
# TODO: Call validation func here (incl functionality for verifying CSRF + reCAPTCHA)
|
# TODO: Call validation func here (incl functionality for verifying CSRF + reCAPTCHA)
|
||||||
|
print("Form Data:")
|
||||||
|
print(self.form_data)
|
||||||
self.__extractData()
|
self.__extractData()
|
||||||
|
|
||||||
|
|
||||||
|
@ -108,11 +108,6 @@ class CreateNewEventModelAdaptor:
|
||||||
else:
|
else:
|
||||||
self.voters.append(EmailUser(email=voter_email))
|
self.voters.append(EmailUser(email=voter_email))
|
||||||
|
|
||||||
|
|
||||||
# Extract the min and max number of selections
|
|
||||||
self.min_num_selections = int(self.form_data.pop('minimum-input')[0])
|
|
||||||
self.max_num_selections = int(self.form_data.pop('maximum-input')[0])
|
|
||||||
|
|
||||||
# Create the Event model object - this does not persist it to the DB
|
# Create the Event model object - this does not persist it to the DB
|
||||||
self.event = Event(start_time=self.starts_at,
|
self.event = Event(start_time=self.starts_at,
|
||||||
end_time=self.ends_at,
|
end_time=self.ends_at,
|
||||||
|
@ -124,27 +119,35 @@ class CreateNewEventModelAdaptor:
|
||||||
|
|
||||||
|
|
||||||
def __gen_polls_options_map(self):
|
def __gen_polls_options_map(self):
|
||||||
# At the time of writing, you can only define one poll at event-creation time
|
# Get the poll count (the number of poll and options that have been defined)
|
||||||
|
poll_count = int(self.form_data.pop('poll-count-input')[0])
|
||||||
|
|
||||||
# Generate PollOption objects from the option data defined in form_data
|
for i in range(poll_count):
|
||||||
options = self.form_data.pop('option-name-input')
|
# String version of i
|
||||||
poll_options_list = []
|
i_str = str(i)
|
||||||
|
|
||||||
for option in options:
|
# Generate PollOption objects from the option data defined in form_data
|
||||||
if option != '':
|
options = self.form_data.pop('option-name-input-' + i_str)
|
||||||
poll_options_list.append(PollOption(choice_text=option, votes=0))
|
poll_options_list = []
|
||||||
|
votes = 0
|
||||||
|
|
||||||
# Extract required Poll object data and create a poll with its PollOption objects
|
for option in options:
|
||||||
text = self.form_data.pop('question-input')[0]
|
if option != '':
|
||||||
votes = 0
|
poll_options_list.append(PollOption(choice_text=option, votes=votes))
|
||||||
|
|
||||||
poll = Poll(question_text=text,
|
# Extract required Poll object data and create a poll with its PollOption objects
|
||||||
total_votes=votes,
|
text = self.form_data.pop('question-name-input-' + i_str)[0]
|
||||||
min_num_selections=self.min_num_selections,
|
min_num_selections = int(self.form_data.pop('minimum-input-' + i_str)[0])
|
||||||
max_num_selections=self.max_num_selections,
|
max_num_selections = int(self.form_data.pop('maximum-input-' + i_str)[0])
|
||||||
event=self.event)
|
|
||||||
|
poll = Poll(question_text=text,
|
||||||
|
total_votes=votes,
|
||||||
|
min_num_selections=min_num_selections,
|
||||||
|
max_num_selections=max_num_selections,
|
||||||
|
event=self.event)
|
||||||
|
|
||||||
|
self.polls_options_map.append([poll, poll_options_list])
|
||||||
|
|
||||||
self.polls_options_map.append([poll, poll_options_list])
|
|
||||||
|
|
||||||
# Instantiate all the polls and their associated poll options
|
# Instantiate all the polls and their associated poll options
|
||||||
def __get_instantiated_polls(self):
|
def __get_instantiated_polls(self):
|
||||||
|
@ -210,8 +213,6 @@ class CreateNewEventModelAdaptor:
|
||||||
self.identifier = None
|
self.identifier = None
|
||||||
self.starts_at = None
|
self.starts_at = None
|
||||||
self.ends_at = None
|
self.ends_at = None
|
||||||
self.min_num_selections = 0
|
|
||||||
self.max_num_selections = 0
|
|
||||||
self.organisers[:] = []
|
self.organisers[:] = []
|
||||||
self.trustees[:] = []
|
self.trustees[:] = []
|
||||||
self.voters[:] = []
|
self.voters[:] = []
|
||||||
|
|
|
@ -107,132 +107,180 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Question / Statement -->
|
<!-- Questions -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="question-input" class="col-sm-3 col-md-2 control-label">Question / Statement:</label> <!-- This text can be a template variable -->
|
<label for="questions-input" class="col-sm-3 col-md-2 control-label">Polls:</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="question-input" placeholder="Example: Elections for the European Parliament" name="question-input" maxlength="200">
|
|
||||||
<span id="question-input-help-block" class="help-block">
|
|
||||||
Question / Statement that will be put forward to voters along with the below options.
|
|
||||||
</span>
|
|
||||||
<span id="question-input-error-block" class="help-block errorText">
|
|
||||||
<!-- Errors flagged here -->
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Options -->
|
|
||||||
<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="col-sm-9 col-md-10">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<table id="options-input-table" class="table table-hover">
|
<table id="questions-input-table" class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center">#</th>
|
<th class="text-center">#</th>
|
||||||
<th>Option</th>
|
<th>Question / Statement</th>
|
||||||
<th class="text-center">Actions</th>
|
<th class="text-center">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="sort" class="formset option-formset" data-formset-prefix="options" data-formset-type="inline">
|
<tbody class="formset questions-formset" data-formset-prefix="questions" data-formset-type="modal" data-formset-modal-title="Add a New Poll">
|
||||||
<!-- Option -->
|
<!-- Question / Statement -->
|
||||||
<tr class="formset-form sorting-row" data-formset-form-prefix="option">
|
<tr class="formset-form formset-form-empty hidden" data-formset-form-prefix="question">
|
||||||
<!-- # -->
|
<!-- # -->
|
||||||
<th class="formset-form-index text-center" scope=row>
|
<td class="formset-form-index text-center" scope=row>
|
||||||
1
|
1
|
||||||
</th>
|
|
||||||
<!-- Option Label -->
|
|
||||||
<td>
|
|
||||||
<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">
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<!-- Delete Action -->
|
<!-- Question / Statement Label -->
|
||||||
|
<th class="formset-form-name">
|
||||||
|
<!-- Q Label Goes Here -->
|
||||||
|
</th>
|
||||||
<td class="formset-form-actions text-center">
|
<td class="formset-form-actions text-center">
|
||||||
|
<button type="button" class="btn btn-sm btn-default formset-form-edit" aria-label="Edit">
|
||||||
|
<i class="fa fa-pencil-square-o" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-default formset-form-remove" aria-label="Remove">
|
<button type="button" class="btn btn-sm btn-default formset-form-remove" aria-label="Remove">
|
||||||
<i class="fa fa-trash-o" aria-hidden="true"></i>
|
<i class="fa fa-trash-o" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
<td class="hidden">
|
||||||
<!-- Option -->
|
<div class="formset-form-fields">
|
||||||
<tr class="formset-form sorting-row" data-formset-form-prefix="option">
|
<!-- Name -->
|
||||||
<!-- # -->
|
<div class="form-group dialogFormField">
|
||||||
<th class="formset-form-index text-center" scope=row>
|
<label for="question-name-input">Question / Statement</label>
|
||||||
2
|
<input type="text" class="form-control dialogQ" id="question-name-input" name="question-name-input" placeholder="Example: Elections for the European Parliament" maxlength="200">
|
||||||
</th>
|
<span id="question-name-input-help-block" class="help-block">
|
||||||
<!-- Option Label -->
|
Question / Statement that will be put forward to voters along with the below options.
|
||||||
<td>
|
</span>
|
||||||
<div>
|
<span id="question-input-error-block" class="help-block errorText">
|
||||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
<!-- Errors flagged here -->
|
||||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: Candidate 2" id="option-name-input" name="option-name-input" maxlength="200">
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
<!-- Options -->
|
||||||
<!-- Delete Action -->
|
<div class="form-group dialogFormField">
|
||||||
<td class="formset-form-actions text-center">
|
<label for="options-table">Options</label>
|
||||||
<button type="button" class="btn btn-sm btn-default formset-form-remove" aria-label="Remove">
|
<table id="options-table" class="table table-hover">
|
||||||
<i class="fa fa-trash-o" aria-hidden="true"></i>
|
<thead>
|
||||||
</button>
|
<tr>
|
||||||
</td>
|
<th class="text-center">#</th>
|
||||||
</tr>
|
<th>Option</th>
|
||||||
<!-- Option -->
|
<th class="text-center">Actions</th>
|
||||||
<tr class="formset-form sorting-row formset-form-empty hidden" data-formset-form-prefix="option">
|
</tr>
|
||||||
<!-- # -->
|
</thead>
|
||||||
<th class="formset-form-index text-center" scope=row>
|
<tbody id="sort" class="formset option-formset" data-formset-prefix="options" data-formset-type="inline">
|
||||||
X
|
<!-- Option -->
|
||||||
</th>
|
<tr class="formset-form sorting-row" data-formset-form-prefix="option">
|
||||||
<!-- Option Label -->
|
<!-- # -->
|
||||||
<td>
|
<th class="formset-form-index text-center" scope=row>
|
||||||
<div>
|
1
|
||||||
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
</th>
|
||||||
<input type="text" class="form-control input-sm input-control" placeholder="Example: Candidate X" id="option-name-input" name="option-name-input" maxlength="200">
|
<!-- Option Label -->
|
||||||
</div>
|
<td>
|
||||||
</td>
|
<div>
|
||||||
<!-- Delete Action -->
|
<!-- TODO: Add an invisible screen reader label to associate with this and other inputs -->
|
||||||
<td class="formset-form-actions text-center">
|
<input type="text" class="form-control input-sm input-control dialogO" placeholder="Example: Candidate 1" id="option-name-input" name="option-name-input" maxlength="200">
|
||||||
<button type="button" class="btn btn-sm btn-default formset-form-remove" aria-label="Remove">
|
</div>
|
||||||
<i class="fa fa-trash-o" aria-hidden="true"></i>
|
</td>
|
||||||
</button>
|
<!-- Delete Action -->
|
||||||
|
<td class="formset-form-actions text-center">
|
||||||
|
<button type="button" class="btn btn-sm btn-default formset-form-remove" aria-label="Remove">
|
||||||
|
<i class="fa fa-trash-o" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Option -->
|
||||||
|
<tr class="formset-form sorting-row" data-formset-form-prefix="option">
|
||||||
|
<!-- # -->
|
||||||
|
<th class="formset-form-index text-center" scope=row>
|
||||||
|
2
|
||||||
|
</th>
|
||||||
|
<!-- Option Label -->
|
||||||
|
<td>
|
||||||
|
<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 dialogO" placeholder="Example: Candidate 2" id="option-name-input" name="option-name-input" maxlength="200">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<!-- Delete Action -->
|
||||||
|
<td class="formset-form-actions text-center">
|
||||||
|
<button type="button" class="btn btn-sm btn-default formset-form-remove" aria-label="Remove">
|
||||||
|
<i class="fa fa-trash-o" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Option -->
|
||||||
|
<tr class="formset-form sorting-row formset-form-empty hidden" data-formset-form-prefix="option">
|
||||||
|
<!-- # -->
|
||||||
|
<th class="formset-form-index text-center" scope=row>
|
||||||
|
X
|
||||||
|
</th>
|
||||||
|
<!-- Option Label -->
|
||||||
|
<td>
|
||||||
|
<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 dialogO" placeholder="Example: Candidate X" id="option-name-input" name="option-name-input" maxlength="200">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<!-- Delete Action -->
|
||||||
|
<td class="formset-form-actions text-center">
|
||||||
|
<button type="button" class="btn btn-sm btn-default formset-form-remove" aria-label="Remove">
|
||||||
|
<i class="fa fa-trash-o" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="clearfix">
|
||||||
|
<button type="button" class="btn btn-primary formset-add" data-formset-prefix="options">
|
||||||
|
<i class="fa fa-plus-circle" aria-hidden="true"></i>
|
||||||
|
Add Poll Option
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span id="question-input-help-block" class="help-block">
|
||||||
|
Drag and drop to re-order options.
|
||||||
|
</span>
|
||||||
|
<span id="options-input-error-block" class="help-block errorText">
|
||||||
|
<!-- Errors flagged here -->
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!-- Number of option selections -->
|
||||||
|
<div class="form-group dialogFormField">
|
||||||
|
<label for="selections-input" class="control-label">Number of Selections:</label> <!-- This text can be a template variable -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<label class="sr-only" for="minimum-input">Minimum</label>
|
||||||
|
<input type="number" class="form-control min-input" id="minimum-input" placeholder="Minimum" value="" name="minimum-input" min="0"> <!-- TODO: Max should be set to the number of options -->
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<label class="sr-only" for="maximum-input">Maximum</label>
|
||||||
|
<input type="number" class="form-control max-input" id="maximum-input" placeholder="Maximum" value="" name="maximum-input" min="1"> <!-- TODO: Max should be set to the number of options -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span id="question-input-help-block" class="help-block">
|
||||||
|
Minimum and maximum number of option selections that a voter can make for the specified question / statement.
|
||||||
|
</span>
|
||||||
|
<span id="selections-input-error-block" class="help-block errorText">
|
||||||
|
<!-- Errors flagged here -->
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
<button type="button" class="btn btn-primary formset-add" data-formset-prefix="options">
|
<button type="button" class="btn btn-primary formset-add" data-formset-prefix="questions">
|
||||||
<i class="fa fa-plus-circle" aria-hidden="true"></i>
|
<i class="fa fa-plus-circle" aria-hidden="true"></i>
|
||||||
Add Question Option
|
Add Poll
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span id="question-input-help-block" class="help-block">
|
<span id="question-input-help-block" class="help-block">
|
||||||
Drag and drop to re-order options.
|
Drag and drop to re-order polls.
|
||||||
</span>
|
</span>
|
||||||
<span id="options-input-error-block" class="help-block errorText">
|
<span id="question-input-error-block" class="help-block errorText">
|
||||||
<!-- Errors flagged here -->
|
<!-- Errors flagged here -->
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Number of option selections -->
|
<!-- Number of Polls -->
|
||||||
<div class="form-group">
|
<input type="number" id="poll-count-input" name="poll-count-input" class="hidden">
|
||||||
<label for="selections-input" class="col-sm-3 col-md-2 control-label">Number of Selections:</label> <!-- This text can be a template variable -->
|
|
||||||
<div class="col-sm-9 col-md-10">
|
|
||||||
<div class="row">
|
|
||||||
<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 -->
|
|
||||||
</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 -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span id="question-input-help-block" class="help-block">
|
|
||||||
Minimum and maximum number of option selections that a voter can make for the specified question / statement.
|
|
||||||
</span>
|
|
||||||
<span id="selections-input-error-block" class="help-block errorText">
|
|
||||||
<!-- Errors flagged here -->
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Organisers -->
|
<!-- Organisers -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="organisers-input" class="col-sm-3 col-md-2 control-label">Organisers:</label> <!-- This text can be a template variable -->
|
<label for="organisers-input" class="col-sm-3 col-md-2 control-label">Organisers:</label> <!-- This text can be a template variable -->
|
||||||
|
@ -453,5 +501,25 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- This dialog has been imported from DEMOS1 -->
|
||||||
|
<div class="modal fade" id="formset-modal" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false" aria-labelledby="formset-modal-label">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="formset-modal-label"></h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-success formset-form-save">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -26,7 +26,7 @@
|
||||||
<th class="text-center">Start Time</th>
|
<th class="text-center">Start Time</th>
|
||||||
<th class="text-center">End Time</th>
|
<th class="text-center">End Time</th>
|
||||||
<th class="text-center">No. Polls</th>
|
<th class="text-center">No. Polls</th>
|
||||||
<th class="text-center">Edit</th>
|
<th class="text-center">Actions</th>
|
||||||
<!-- Could also add a delete column to easily remove an event -->
|
<!-- Could also add a delete column to easily remove an event -->
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -40,7 +40,10 @@
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="{% url 'polls:edit-event' event.id %}">
|
<a href="{% url 'polls:edit-event' event.id %}">
|
||||||
<span class="btn btn-default glyphicon glyphicon-pencil"></span>
|
<span class="btn btn-default glyphicon glyphicon-pencil"></span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="#">
|
||||||
|
<span class="btn btn-default glyphicon glyphicon-trash"></span>
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -111,6 +111,10 @@ tbody.trustee-formset > tr {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialogFormField {
|
||||||
|
margin-top: 0.65em;
|
||||||
|
}
|
||||||
|
|
||||||
div.formset_object {
|
div.formset_object {
|
||||||
animation: 0.3s drop-intro;
|
animation: 0.3s drop-intro;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,19 @@ var dateRegex = /^[0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-
|
||||||
var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
var reCaptchaValid = false;
|
var reCaptchaValid = false;
|
||||||
var generalErrorBlock = document.getElementById('all-errors-help-block');
|
var generalErrorBlock = document.getElementById('all-errors-help-block');
|
||||||
|
|
||||||
var errors = [];
|
var errors = [];
|
||||||
|
var create = true;
|
||||||
|
var pollCount = 0;
|
||||||
|
|
||||||
|
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='questions']");
|
||||||
|
var emptyForm = formset.children('.formset-form-empty');
|
||||||
|
emptyForm.remove();
|
||||||
|
}
|
||||||
|
|
||||||
$("#election-form").submit(function(e) {
|
$("#election-form").submit(function(e) {
|
||||||
// Intercept submission of form and temporarily suspend it
|
// Intercept submission of form and temporarily suspend it
|
||||||
|
@ -28,6 +39,7 @@ $("#election-form").submit(function(e) {
|
||||||
|
|
||||||
if( formDataValid === true ) {
|
if( formDataValid === true ) {
|
||||||
clearErrors();
|
clearErrors();
|
||||||
|
finalisePolls();
|
||||||
form.submit();
|
form.submit();
|
||||||
} else {
|
} else {
|
||||||
submitBtn.val(submitBtnErrLabel);
|
submitBtn.val(submitBtnErrLabel);
|
||||||
|
@ -54,12 +66,11 @@ function isFormValid() {
|
||||||
var slugValid = isSlugValid();
|
var slugValid = isSlugValid();
|
||||||
var voteStartValid = isVoteStartValid();
|
var voteStartValid = isVoteStartValid();
|
||||||
var voteEndValid = isVoteEndValid();
|
var voteEndValid = isVoteEndValid();
|
||||||
var pollOptsValid = isPollAndOptsValid();
|
|
||||||
var organisersEmailsValid = areOrganisersEmailsValid();
|
var organisersEmailsValid = areOrganisersEmailsValid();
|
||||||
var trusteesEmailsValid = areTrusteesEmailsValid();
|
var trusteesEmailsValid = areTrusteesEmailsValid();
|
||||||
var votersListValid = isVotersListValid();
|
var votersListValid = isVotersListValid();
|
||||||
|
|
||||||
return nameValid && slugValid && voteStartValid && voteEndValid && pollOptsValid
|
return nameValid && slugValid && voteStartValid && voteEndValid
|
||||||
&& organisersEmailsValid && trusteesEmailsValid && votersListValid;
|
&& organisersEmailsValid && trusteesEmailsValid && votersListValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +97,7 @@ function isNameValid() {
|
||||||
// Based on a list of names supplied from the create_event html template
|
// Based on a list of names supplied from the create_event html template
|
||||||
if(events_list !== undefined) {
|
if(events_list !== undefined) {
|
||||||
var valid = true;
|
var valid = true;
|
||||||
var event_name = $('#name-input').val();
|
var event_name = $('#name-input').val().trim();
|
||||||
|
|
||||||
if(event_name === '') {
|
if(event_name === '') {
|
||||||
checkAndAddError({
|
checkAndAddError({
|
||||||
|
@ -215,33 +226,16 @@ function isDateValid(date_time) {
|
||||||
return dateRegex.test(date_time);
|
return dateRegex.test(date_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPollAndOptsValid() {
|
function isPollQValid() {
|
||||||
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;
|
var valid = true;
|
||||||
|
|
||||||
// Check question is valid
|
// Check question is valid
|
||||||
var question = $('#question-input').val();
|
var question = $('#question-name-input-' + pollCount).val();
|
||||||
|
|
||||||
if(question === '') {
|
if(question === '') {
|
||||||
checkAndAddError({
|
checkAndAddError({
|
||||||
error: "Question / Statement for the poll is blank.",
|
error: "Question / Statement for the poll is blank.",
|
||||||
helpBlockId: "question-input-error-block"
|
helpBlockId: "question-input-error-block-" + pollCount
|
||||||
});
|
});
|
||||||
|
|
||||||
valid = false;
|
valid = false;
|
||||||
|
@ -252,8 +246,8 @@ function isPollValid() {
|
||||||
|
|
||||||
function isPollOptionsValid() {
|
function isPollOptionsValid() {
|
||||||
var valid = true;
|
var valid = true;
|
||||||
var optsInputs = $('.option-formset #option-name-input');
|
var optsInputs = $('.option-formset #option-name-input-' + pollCount);
|
||||||
var helpBlockId = "options-input-error-block";
|
var helpBlockId = "options-input-error-block-" + pollCount;
|
||||||
|
|
||||||
var index = 0;
|
var index = 0;
|
||||||
var errorStr = "Option ";
|
var errorStr = "Option ";
|
||||||
|
@ -284,36 +278,28 @@ function isPollOptionsValid() {
|
||||||
return valid;
|
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() {
|
function isMinMaxSelectionValid() {
|
||||||
var valid = true;
|
var valid = true;
|
||||||
var minInput = $('#minimum-input');
|
var minInput = $('#minimum-input-' + pollCount);
|
||||||
var minInputMinAttr = parseInt(minInput[0].min);
|
var minInputMinAttr = parseInt(minInput[0].min);
|
||||||
var minInputVal = minInput.val();
|
var minInputVal = minInput.val();
|
||||||
var helpBlockId = "selections-input-error-block";
|
var helpBlockId = "selections-input-error-block-" + pollCount;
|
||||||
var errorStr = "";
|
var errorStr = "";
|
||||||
|
|
||||||
if(minInputVal < minInputMinAttr) {
|
if(minInputVal === "" || minInputVal < minInputMinAttr) {
|
||||||
errorStr = "The minimum option selection cannot be less than " + minInputMinAttr;
|
errorStr = "The minimum option selection cannot be less than " + minInputMinAttr + " or blank";
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxInput = $('#maximum-input');
|
var maxInput = $('#maximum-input-' + pollCount);
|
||||||
var maxInputMinAttr = parseInt(maxInput[0].min);
|
var maxInputMinAttr = parseInt(maxInput[0].min);
|
||||||
var maxInputVal = maxInput.val();
|
var maxInputVal = maxInput.val();
|
||||||
|
|
||||||
if(maxInputVal < maxInputMinAttr) {
|
if(maxInputVal === "" || maxInputVal < maxInputMinAttr) {
|
||||||
if(errorStr !== '') {
|
if(errorStr !== '') {
|
||||||
errorStr = errorStr + " and the maximum cannot be less than " + maxInputMinAttr;
|
errorStr = errorStr + " and the maximum cannot be less than " + maxInputMinAttr + " or blank";
|
||||||
} else {
|
} else {
|
||||||
errorStr = "The maximum option selection cannot be less than " + maxInputMinAttr;
|
errorStr = "The maximum option selection cannot be less than " + maxInputMinAttr + " or blank";
|
||||||
}
|
}
|
||||||
|
|
||||||
valid = false;
|
valid = false;
|
||||||
|
@ -331,10 +317,6 @@ function isMinMaxSelectionValid() {
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#minimum-input, #maximum-input').on('input', function(e) {
|
|
||||||
validateFormField(isMinMaxSelectionValid, "selections-input-error-block");
|
|
||||||
});
|
|
||||||
|
|
||||||
function areOrganisersEmailsValid() {
|
function areOrganisersEmailsValid() {
|
||||||
var valid = true;
|
var valid = true;
|
||||||
var organiserInputs = $('.organiser-formset #organiser-email-input');
|
var organiserInputs = $('.organiser-formset #organiser-email-input');
|
||||||
|
@ -739,7 +721,7 @@ function update(event, ui) {
|
||||||
updateFormset(formset);
|
updateFormset(formset);
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#options-input-table, #organisers-input-table, #trustees-input-table").sortable({
|
$("#questions-input-table, #organisers-input-table, #trustees-input-table").sortable({
|
||||||
items: "tr",
|
items: "tr",
|
||||||
update: update
|
update: update
|
||||||
});
|
});
|
||||||
|
@ -759,7 +741,7 @@ function updateFormset(formset) { // Ported from DEMOS 1. Updates the row number
|
||||||
function updateForm(form, formIndex) { // Ported from DEMOS 1.
|
function updateForm(form, formIndex) { // Ported from DEMOS 1.
|
||||||
// Specific update for option forms
|
// Specific update for option forms
|
||||||
var mayBeTextInput = form.find('input:text')[0];
|
var mayBeTextInput = form.find('input:text')[0];
|
||||||
if(mayBeTextInput.placeholder !== undefined) {
|
if(mayBeTextInput !== undefined && mayBeTextInput.placeholder !== undefined) {
|
||||||
if( mayBeTextInput.placeholder.indexOf("Candidate") > -1) {
|
if( mayBeTextInput.placeholder.indexOf("Candidate") > -1) {
|
||||||
mayBeTextInput.placeholder = "Example: Candidate " + (formIndex + 1);
|
mayBeTextInput.placeholder = "Example: Candidate " + (formIndex + 1);
|
||||||
} else if (mayBeTextInput.placeholder.indexOf("trusteeX") > -1) {
|
} else if (mayBeTextInput.placeholder.indexOf("trusteeX") > -1) {
|
||||||
|
@ -794,12 +776,107 @@ function manageTotalForms(formset, value) { // Ported from DEMOS1.
|
||||||
addButton.prop('disabled', parseInt(totalForms.val()) - removedForms.length >= parseInt(maxNumForms.val()));
|
addButton.prop('disabled', parseInt(totalForms.val()) - removedForms.length >= parseInt(maxNumForms.val()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateQuestionFormInputs(form) {
|
||||||
|
// Update the name and IDs of all the dialog input fields
|
||||||
|
var clonedFields = form.find('.formset-form-fields:first >');
|
||||||
|
|
||||||
|
// Update the table ID
|
||||||
|
var table = form.find('.table:first');
|
||||||
|
table.attr("id", "options-table-" + pollCount);
|
||||||
|
|
||||||
|
// Update the poll question / statement ID
|
||||||
|
clonedFields.find(".dialogQ:first")
|
||||||
|
.attr("id", "question-name-input-" + pollCount)
|
||||||
|
.attr("name", "question-name-input-" + pollCount);
|
||||||
|
|
||||||
|
// Update one of the help block IDs for various sections of the dialog
|
||||||
|
var pollQuestionErrorHelpBlock = clonedFields.find("#question-input-error-block");
|
||||||
|
pollQuestionErrorHelpBlock.attr("id", "question-input-error-block-" + pollCount);
|
||||||
|
|
||||||
|
var pollOptionsErrorHelpBlock = clonedFields.find("#options-input-error-block");
|
||||||
|
pollOptionsErrorHelpBlock.attr("id", "options-input-error-block-" + pollCount);
|
||||||
|
|
||||||
|
var pollSelectionsErrorHelpBlock = clonedFields.find("#selections-input-error-block");
|
||||||
|
pollSelectionsErrorHelpBlock.attr("id", "selections-input-error-block-" + pollCount);
|
||||||
|
|
||||||
|
// Update the poll option input IDs
|
||||||
|
var optsInputs = clonedFields.find(".dialogO");
|
||||||
|
|
||||||
|
for(var i = 0; i < optsInputs.length; i++) {
|
||||||
|
var input = optsInputs[i];
|
||||||
|
input.id = "option-name-input-" + pollCount;
|
||||||
|
input.name = "option-name-input-" + pollCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the data-formset-prefix for correct referencing
|
||||||
|
var dataFormsetPrefix = "options-" + pollCount;
|
||||||
|
var optionFormSet = clonedFields.find(".option-formset");
|
||||||
|
optionFormSet.attr("data-formset-prefix", dataFormsetPrefix);
|
||||||
|
|
||||||
|
var addPollOptBtn = clonedFields.find('.formset-add');
|
||||||
|
addPollOptBtn.attr("data-formset-prefix", dataFormsetPrefix);
|
||||||
|
|
||||||
|
// Update the poll min and max selection
|
||||||
|
clonedFields.find(".min-input:first")
|
||||||
|
.attr("id", "minimum-input-" + pollCount)
|
||||||
|
.attr("name", "minimum-input-" + pollCount);
|
||||||
|
|
||||||
|
clonedFields.find(".max-input:first")
|
||||||
|
.attr("id", "maximum-input-" + pollCount)
|
||||||
|
.attr("name", "maximum-input-" + pollCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDialogFormValid() {
|
||||||
|
var pollQValid = true;
|
||||||
|
var optsValid = true;
|
||||||
|
var minMaxSelValid = true;
|
||||||
|
|
||||||
|
// Check question is valid
|
||||||
|
var pollQErrorHelpBlockId = "question-input-error-block-" + pollCount;
|
||||||
|
pollQValid = isPollQValid();
|
||||||
|
|
||||||
|
if(pollQValid === true) {
|
||||||
|
clearError(pollQErrorHelpBlockId);
|
||||||
|
} else {
|
||||||
|
highlightError(pollQErrorHelpBlockId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check opts are valid
|
||||||
|
var pollOptsErrorHelpBlockId = "options-input-error-block-" + pollCount;
|
||||||
|
optsValid = isPollOptionsValid();
|
||||||
|
|
||||||
|
if(optsValid === true) {
|
||||||
|
clearError(pollOptsErrorHelpBlockId);
|
||||||
|
} else {
|
||||||
|
highlightError(pollOptsErrorHelpBlockId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check min and max selections are valid
|
||||||
|
var pollSelErrorHelpBlockId = "selections-input-error-block-" + pollCount;
|
||||||
|
minMaxSelValid = isMinMaxSelectionValid();
|
||||||
|
|
||||||
|
if(minMaxSelValid === true) {
|
||||||
|
clearError(pollSelErrorHelpBlockId);
|
||||||
|
} else {
|
||||||
|
highlightError(pollSelErrorHelpBlockId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pollQValid && optsValid && minMaxSelValid;
|
||||||
|
}
|
||||||
|
|
||||||
$('.formset-add').click(function (e) { // Ported from DEMOS1
|
$('.formset-add').click(function (e) { // Ported from DEMOS1
|
||||||
var formsetPrefix = $(this).attr('data-formset-prefix');
|
var formsetPrefix = $(this).attr('data-formset-prefix');
|
||||||
var formset = $('.formset[data-formset-prefix="' + formsetPrefix + '"]');
|
var formset = $('.formset[data-formset-prefix="' + formsetPrefix + '"]');
|
||||||
var emptyForm = formset.children('.formset-form-empty');
|
var emptyForm = formset.children('.formset-form-empty');
|
||||||
var emptyFormCheckedInputs = emptyForm.find('input:checkbox:checked, input:radio:checked');
|
var emptyFormCheckedInputs = emptyForm.find('input:checkbox:checked, input:radio:checked');
|
||||||
var form = emptyForm.clone(true).removeClass('formset-form-empty');
|
var form = emptyForm.clone(true).removeClass('formset-form-empty');
|
||||||
|
|
||||||
|
// Update the IDs of the inputs of the dialogs based on the number of questions
|
||||||
|
if(formsetPrefix === "questions") {
|
||||||
|
// Update the IDs and names of all of the cloned input form fields
|
||||||
|
updateQuestionFormInputs(form);
|
||||||
|
}
|
||||||
|
|
||||||
var formIndex = formset.children('.formset-form:not(.formset-form-empty)').length;
|
var formIndex = formset.children('.formset-form:not(.formset-form-empty)').length;
|
||||||
|
|
||||||
formset.append(form);
|
formset.append(form);
|
||||||
|
@ -836,7 +913,7 @@ $('.formset-form-remove').click(function (e) { // Ported from DEMOS1
|
||||||
// Perform validation now that a row has been removed
|
// Perform validation now that a row has been removed
|
||||||
switch (formPrefix) {
|
switch (formPrefix) {
|
||||||
case 'option':
|
case 'option':
|
||||||
validateFormField(isPollOptionsValid, "options-input-error-block");
|
validateFormField(isPollOptionsValid, "options-input-error-block-" + pollCount);
|
||||||
break;
|
break;
|
||||||
case 'organiser':
|
case 'organiser':
|
||||||
validateFormField(areOrganisersEmailsValid, "organisers-input-error-block");
|
validateFormField(areOrganisersEmailsValid, "organisers-input-error-block");
|
||||||
|
@ -845,4 +922,90 @@ $('.formset-form-remove').click(function (e) { // Ported from DEMOS1
|
||||||
validateFormField(areTrusteesEmailsValid, "trustees-input-error-block");
|
validateFormField(areTrusteesEmailsValid, "trustees-input-error-block");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.formset-form-save').click(function (e) {
|
||||||
|
var dialogValid = isDialogFormValid();
|
||||||
|
|
||||||
|
if(dialogValid === true) {
|
||||||
|
// TODO: Clear errors
|
||||||
|
var modal = $(this).closest('.modal');
|
||||||
|
var form = modal.data('form');
|
||||||
|
var name = $('#question-name-input-' + pollCount).val();
|
||||||
|
form.find('.formset-form-name:first').text(name);
|
||||||
|
modal.data('formSave', true);
|
||||||
|
modal.modal('hide');
|
||||||
|
pollCount++;
|
||||||
|
} else {
|
||||||
|
//highlightErrors();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.formset-form-edit').click(function (e) {
|
||||||
|
var form = $(this).closest('.formset-form');
|
||||||
|
$('#formset-modal').data('form', form).modal('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#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-" + pollCount).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();
|
||||||
});
|
});
|
Reference in a new issue