Upgraded the Create Event page and back-end view to handle the creation of multiple polls in one go. The validation has been appropriately updated to cope with the UI changes but requires further testing. Event list has been updated to include a delete action which requires wiring up to a back-end function which hasn't been created yet
This commit is contained in:
parent
6b70b01e06
commit
1314e3be1a
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