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:
vincentmdealmeida 2018-06-18 17:12:37 +01:00 committed by GitHub
commit 11abbb8a64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 410 additions and 171 deletions

View file

@ -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[:] = []

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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;
} }

View file

@ -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();
}); });