Tweaked wrappers: now selects should be wrapped in a span, not a div
New page
//KForms 2.3.1
//Moving to a new page for 2016, even though I haven't yet made enough changes to merit a new version number
// todo:
// fix the duplicate subforms in the dropdown bug
//Changelog
//later in 2.3.1: new team selector
//in 2.3.1: form warns you when silent logout bug has happened. (and old warnings are removed correctly if you logout/in in another tab & don't reload)
//in 2.3: subform-mandatory forms e.g. Check In
//in 2.2.1: confirm before unsubmit a signed form, clean up messaging and allow custom error msgs
//in 2.2: submit and submitted-warning, permissions by role
//in 2.1: smarter validation, ability to add a warning on any element,
//ability to validate a DFF on change as well as on submit
//in 2.0: view/edit/admin modes, changing team/mode without reloading
// later:
//- consider rewriting owner_info / team_id / author global var logic like this:
// owner = {type: "user", key: "kdrinkwa", info: {stuff that came from ajax}}
// -default Ajax options using extend()
// =====================================================
// BLOCK
// CUSTOM ERROR CLASSES
// =====================================================
function AjaxError(message) {
this.name = "AjaxError";
this.message = message;
this.stack = (new Error()).stack;
}
AjaxError.prototype = Object.create(Error.prototype);
function PerlError(message) {
this.name = "PerlError";
this.message = message;
this.stack = (new Error()).stack;
}
PerlError.prototype = Object.create(Error.prototype);
function FormError(message) {
this.name = "FormError";
this.message = message;
this.stack = (new Error()).stack;
}
FormError.prototype = Object.create(Error.prototype);
// =====================================================
// BLOCK
// URL parser and String.startsWith
// =====================================================
//This function parses a URL (or the current page's URL, if no argument is passed in)
//and returns an object full of useful properties.
//Reference:
// http://www.abeautifulsite.net/parsing-urls-in-javascript/
// https://gist.github.com/jlong/2428561
function parseURL(url) {
if (typeof url == "undefined") {
parser = window.location;
}
else {
var parser = document.createElement('a');
// Let the browser do the work
parser.href = url;
}
var searchObject = {},
queries, split, i;
// Convert query string to object
queries = parser.search.replace(/^\?/, '').split('\u0026');
for( i = 0; i < queries.length; i++ ) {
split = queries[i].split('=');
searchObject[split[0]] = split[1];
}
return {
protocol: parser.protocol,
host: parser.host,
hostname: parser.hostname,
port: parser.port,
pathname: parser.pathname,
search: parser.search,
searchObject: searchObject,
hash: parser.hash
};
}
if (typeof String.prototype.startsWith != "function") {
String.prototype.startsWith = function(str) {
return this.slice(0, str.length) == str;
}
}
// =====================================================
// BLOCK
// CONSTANTS AND QUERY STRING PARAMETERS
// =====================================================
var default_messages = {
kicking_to_view_mode: "Returning to View mode.",
not_kicking_to_view_mode: "Please return to View mode.",
user_cannot_edit: "You are not a member of the selected team.\nIf this is an error, make sure you are logged in and your account is included on your team's roster.",
user_cannot_submit: "You do not have permission to submit this form.",
user_cannot_use_admin_mode: "You are not an administrator.",
user_cannot_edit_admin_fields: "That field is only for use by iGEM Headquarters.",
//used by get_user_info for the case of a user-owned form only (right now, those can't be made public)
not_logged_in: "You are not logged in. Please log in (or <a href='http:\/\/igem.org/Account_Apply.cgi'>sign up for an account</a>) to complete this form.",
//to be used by OIC if the user is mid-editing and the silent logout bug happens.
silent_logout_bug: "Oops, the silent logout bug happened!",
form_validation_fail_beginning: "Some required answers are missing:",
unparseable_ajax_response: "There was an error in FORM.CGI. Please inform us of this error by emailing hq (at) igem (dot) org.",
please_confirm_submission: "Please check the box to confirm your submission.",
confirm_deletion: "Are you sure you want to delete this form?\nYou cannot undo this action.",
field_validation_fail_beginning: "Answer missing:",
invalid_email_address: "Invalid email address"
};
var messages = jQuery.extend({}, default_messages, form_info.messages);
var non_admin_DFFs = "[data-form-field]:not([data-form-field^='admin'])"; //convenient selector for non-admin DFFs
var valid_modes = ["view", "edit", "admin"];
var valid_owner_types = ["user", "judge", "team", "lab", "course"];
var valid_permission_groups = ["author", "group_members", "super_users", "any_users", "public"];
var valid_roles = ["Primary Contact", "Student", "Instructor", "Advisor"];//lol not implemented
var valid_required = ["required", "optional"];
//Explicitly initialize global variables
var mode;
var team_id;
var author;
var owner_info;
var user_info;
var form_displayed = false;
var form_submitted = false; //sigh, it looks like I can't get away without this one
var permissions = {view: false, edit: false, submit: false, admin: false};
var parsed_query_string = parseURL().searchObject;
mode = parsed_query_string["mode"];
if (form_info.owner_type == "team") {
if (typeof team_id == "undefined") {
team_id = parseInt(parsed_query_string["team_id"]);
}
}
else if (form_info.owner_type == "user") {
author = parsed_query_string["author"];
}
else {
throw new FormError("Judge, Lab, and Course owned forms are not implemented yet");
}
// =====================================================
// BLOCK
// VALIDATING INDIVIDUAL FORMS CONFIG
// =====================================================
function validate_form_info() {
assert(typeof form_info == "object", "form_info is not an object");
assert(typeof form_info.name == "string", "form_info.name is not a string");
assert(typeof form_info.display_title == "string", "form_info.display_title is not a string");
assert(valid_modes.indexOf(form_info.default_mode) > -1, "form_info.default_mode is not valid");
assert(valid_owner_types.indexOf(form_info.owner_type) > -1, "form_info.owner_type is not valid");
assert(form_info.permissions instanceof Object, "form_info.permissions is not an object");
["view", "edit", "submit", "admin"].forEach(function(action, index, array) {
assert(form_info.permissions[action] instanceof Array, "form_info.permissions." + action + " is not an array");
form_info.permissions[action].forEach(function(perm_group, index, array) {
assert(valid_permission_groups.indexOf(perm_group) > -1 || valid_roles.indexOf(perm_group) > -1 , "invalid permission group in form_info.permissions." + action);
});
});
assert(typeof form_info.ajax_URL == "string", "form_info.ajax_URL is not a string");
assert(valid_required.indexOf(form_info.validate_unspecified_fields) > -1, "form_info.validate_unspecified_fields is not valid");
}
validate_form_info();
// =====================================================
// BLOCK
// SETUP ON DOCUMENT.READY (yes it is the kitchen sink)
// =====================================================
function document_ready_handler() {
//Validate mode and default to something if invalid
check_mode();
//NOW A WHOLE BUNCH OF RANDOM ELEMENT SETUP
//Display form title
$("#form_title_display").text(form_info.display_title).show();
//read the nokick checkbox and bind it to change nokick
nokick = $("#nokick").prop("checked");
$("#nokick").change(function(event) {
nokick = $(this).prop("checked");
});
//bind author-select input on enter key
$("#author_select").keyup(function(event) {
if (event.which == 13) {
change_author($(this).val());
}
});
//apply wrapper divs for feedback function
$("[data-form-field]:not(textarea)").wrap("<span class='wrapper'><\/span>");
$("textarea[data-form-field]").wrap("<DIV class='wrapper'><\/DIV>");
$("p").find(".wrapper").has("[type=radio], [type=checkbox]").css("margin-left", "2em");
//force create AJ log and error divs
$("#formbody").before("<DIV id='aj_form_errors'></DIV>").after("<DIV id='aj_form_log'></DIV>");
//display log div
//(it will be hidden from non-admin users by default, but if you're debugging for such a user
//then you can go into the console and show it -- you do want it to EXIST for all users.
//So don't move this into an if-user-is-admin clause!)
aj_display_log_div();
//bind click events for .change_mode(team,author) links
//needs to delegate from #bodyContent because not all .change_mode links exist
//at document.ready
$("#bodyContent").on("click", "a.change_mode", function(e) {
var new_action = $(this).attr("data-mode");
change_mode(new_action);
});
$("#bodyContent").on("click", "a.change_team", function(e) {
var new_team = $(this).attr("data-team");
change_team(new_team);
});
$("#bodyContent").on("click", "a.change_author", function(e) {
var new_author = $(this).attr("data-author");
change_author(new_author);
});
//bind hide_admin
$("#hide_admin").click(function(event) {
event.preventDefault();
$(".admins_only").hide();
});
//The event handlers for data-form-fields used to be bound here.
//But because some of them depend on user_info, I've moved them into their own function
//which is called by get_user_info when it's done.
//**This change was dumb. Revert it. But it's tangled, so that will take some doing.
refresh_form();
//Start the chain by getting user info
get_user_info();
$("#team_list_dropdown").on("change", function(e) {
var team_id_chosen = $(this).val();
change_team(parseInt(team_id_chosen));
});
if (form_info.owner_type == "team") {
//put teams in the team list dropdown
jQuery.ajax({
url: "http://igem.org/aj/team_list.cgi",
type: "GET",
timeout: 30000,
dataType: "json",
data: {command: "get_team_list", year: "2015"}, //**Make this auto-update itself
error: function(jqxhr, textStatus, errorThrown) {
general_ajax_error( jqxhr, textStatus, errorThrown,
"Failed to get list of teams");
aj_log("Failed to get list of teams");
},
success: function(data, textStatus, jqxhr) {
put_teams_in_selector(data);
}
});
}
else {
$("#team_list_dropdown").hide();
}
//if it's a user-owned form and no author is specified, it defaults to self.
//but that happens later, in get_user_info success
//(because it can't default to self if you're not logged in)
}
function put_teams_in_selector(team_list) {
//Each element of team_list is an object like
//{ region: "Europe", name: "Aachen", id: "1585" }
var optgroups = { "Africa": "<optgroup label='Africa'>",
"Asia": "<optgroup label='Asia'>",
"Europe": "<optgroup label='Europe'>",
"Latin America": "<optgroup label='Latin America'>",
"North America": "<optgroup label='North America'>"
};
team_list.forEach(function(team) {
optgroups[team.region] += "<option value='" + team.id + "'>" + team.name + "</option>";
});
//I'm not using a for..in loop for this because order is important
$("#team_list_dropdown").append(optgroups["Africa"] + "</optgroup>");
$("#team_list_dropdown").append(optgroups["Asia"] + "</optgroup>");
$("#team_list_dropdown").append(optgroups["Europe"] + "</optgroup>");
$("#team_list_dropdown").append(optgroups["Latin America"] + "</optgroup>");
$("#team_list_dropdown").append(optgroups["North America"] + "</optgroup>");
}
function bind_DFF_event_handlers() {
//bind change handler to all data-form-field elements
//except admin fields and submit-family buttons
//and validate-on-change fields
$(non_admin_DFFs).not("[type='submit']").not(".validate-on-change").change(change_handler_for_most_DFFs);
$(non_admin_DFFs).filter(".validate-on-change").change(DFF_change_handler_with_validation);
//bind handler to admin data-form-fields according to whether user is admin
if(user_info.is_super) {
$("[data-form-field^='admin']").change(admin_DFF_change_handler);
}
else {
$("[data-form-field^='admin']").click(admin_lockout_click_handler);
}
//bind click handlers for submit/delete/return buttons
//These are bound regardless of permissions, BUT
//one_input_changed will reject these changes if the user doesn't have submit permissions
$("[data-form-field=submit][type='submit']").click(click_handler_for_submit);
$("[data-form-field=delete_form][type='submit']").click(click_handler_for_delete);
$("[data-form-field=return_form][type='submit']").click(click_handler_for_return);
$("#popwin_unsubmit").on("click", click_handler_for_return);
$("#popwin_dismiss").on("click", function(event) {
event.preventDefault();
$("#submitted_warning").hide();
});
//bind click handler for submit confirmation box
$("#" + $("[data-form-field=submit]").attr("data-confirmation")).on("click", click_handler_for_confirm_submit);
//bind handler for custom new subform event
//this event is right now triggered by code that's inline in the form page,
//which also handles validation of new sub form fields
//(eventually should be moved into here, though)
$("[data-form-field=sub_form]").on("kforms:newsubform", newsf_handler);
}
$(document).ready(document_ready_handler);
// =====================================================
// BLOCK
// OTHER NAMED EVENT HANDLERS
// =====================================================
function change_handler_for_most_DFFs(event) {
//console.log("change_handler_for_most_DFFs");
if ($(this).attr("data-form-field") == "sub_form") {
console.log("change handler on sub_form");
var offon = ($(this).val() == "") ? "on" : "off";
$("[data-form-field^='sub_form_']").each(function(index, field) {
dffswitch(field, offon);
});
one_input_changed(this, 'store', '', false, false);
}
else if ($(this).attr("data-form-field").startsWith("sub_form_")) {
if ($("[data-form-field=sub_form]").val() == "") { return; }
one_input_changed(this, 'store', '', false, true);
}
else {
one_input_changed(this, 'store', '', false, true);
}
}
function DFF_change_handler_with_validation(event) {
var validation_result = validate_one_field(this);
if (validation_result == "") {
one_input_changed(this, 'store', '', false, true);
}
}
function admin_DFF_change_handler(event) {
//console.log("admin_DFF_change_handler");
one_input_changed(this, 'store', '', false, true);
}
function admin_lockout_click_handler(event) {
//console.log("admin_lockout_click_handler");
event.preventDefault();
alert(messages.user_cannot_edit_admin_fields);
}
function click_handler_for_submit(event) {
event.preventDefault();
//if there are any validation error messages, show them and don't submit
var errors = validate_form_entries();
if(errors.length > 0) {
alert(messages.form_validation_fail_beginning + "\n\n" + errors.join("\n"));
return;
}
//moved confirmation-box-polling to OIC so it happens AFTER permission checking
//finally, actually submit the form
one_input_changed(this, 'submit', '', false, true);
}
function click_handler_for_confirm_submit(event) {
if (!permissions.submit) {
alert(messages.user_cannot_submit);
event.preventDefault();
}
}
function click_handler_for_delete(event) {
//console.log("starting delete click handler");
event.preventDefault();
var confirmed = window.confirm(messages.confirm_deletion);
if (confirmed) {
one_input_changed(this, 'delete_form', '', false, true);
}
}
function click_handler_for_return(event) {
event.preventDefault();
one_input_changed(this, 'return_form', '', false, true);
}
function newsf_handler(event, newname) {
console.log("newsf_handler, value " + newname);
make_new_sub_form(newname, false, true);
one_input_changed(this, 'store', '', true, true);
}
// =====================================================
// BLOCK
// USER AND OWNER INFO
// =====================================================
//Get information about the logged-in user
//(nb: it looks at the login cookie, not at wgUserName)
function get_user_info() {
aj_log("Getting user info");
var request = jQuery.ajax({ url: form_info.ajax_URL,
type: "POST",
timeout: 30000,
data: {command: 'get_user_info'},
dataType: "json",
error: function(jqxhr, textStatus, errorThrown) {
general_ajax_error( jqxhr, textStatus, errorThrown,
"Failed to get information about the user");
},
success: function(data, textStatus, jqxhr) {
aj_log("Received user info: " + jqxhr.responseText);
if(try_parse_received_info(jqxhr.responseText)) {
user_info = jqxhr.responseJSON;
bind_DFF_event_handlers();
//Show hide admin stuff
if (user_info.is_super == "true") { $(".admins_only").show(); }
else { $(".admins_only").hide(); }
//now, if it's a user-owned form, we can handle some special cases at this point
if (form_info.owner_type == "user") {
//If they're not logged in,
//Yell at them and stop the setup chain.
//**eventually I may want to change this, if I want to allow the public to view
//any user-owned forms
if (user_info.user_name == "") {
aj_display_error(messages.not_logged_in);
return;
}
//Now we know they must be logged in, and we can default to self-author if no author specified.
else if (typeof author == "undefined") {
author = user_info.user_name;
}
}
//allow individual forms to have a custom extra response at this point
if (typeof custom_get_user_info_response == "function") {
custom_get_user_info_response(data, textStatus, jqxhr);
}
get_owner_info();
}
else {
aj_display_error(messages.unparseable_ajax_response);
}
}
});
}
//Get information about the form owner
//This calls one of several specialized ajax getters, and then triggers the whole
//rest of form setup upon success of the request.
function get_owner_info(query_param) {
aj_log("Getting owner info");
owner_info = undefined;
//refresh_form();
var ajax_getter;
if (form_info.owner_type == "team") {ajax_getter = get_team_info;}
else if (form_info.owner_type == "user") {ajax_getter = get_author_info;}
else if (form_info.owner_type == "judge") {ajax_getter = get_judge_info;}
else if (form_info.owner_type == "lab") {ajax_getter = get_lab_info;}
else if (form_info.owner_type == "course") {ajax_getter = get_course_info;}
var request = ajax_getter(query_param);
request.done( function (data, textStatus, jqxhr) {
if(try_parse_received_info(jqxhr.responseText)) {
if (data.error) {
//we did the query wrong, oh no
//this case will catch invalid team/lab/course IDs
respond_to_error_in_owner_info();
}
else {
//special case: invalid usernames
//will return empty strings for user_name and full_name
if (form_info.owner_type == "user" && data.user_name == "") {
respond_to_error_in_owner_info();
}
else {
owner_info = data; //put owner info into global object
new_form_setup(); //this used to be set_up_form_and_mode
}
}
if (typeof custom_get_owner_info_response == "function") {
custom_get_owner_info_response(data, textStatus, jqxhr);
}
}
else {
aj_display_error(messages.unparseable_ajax_response);
}
});
}
//**These get_foo_info functions are kind of repetitive. Maybe rewrite later.
//Get information about a user of a specified username.
//If none is specified, the function will look at global var author.
function get_author_info(query_author) {
query_author = (typeof query_author == "undefined") ? author : query_author
return jQuery.ajax({ url: form_info.ajax_URL,
type: "POST",
timeout: 30000,
dataType: "json",
data: {command: 'get_user_info', username: query_author},
error: function(jqxhr, textStatus, errorThrown) {
general_ajax_error( jqxhr, textStatus, errorThrown,
"Failed to get information about author " + query_author);
aj_log("Failed to get author info");
},
success: function(data, textStatus, jqxhr) {
aj_log("Received author info: " + jqxhr.responseText);
}
});
}
function get_judge_info(query_judge_username) {
query_judge_username = (typeof query_judge_username == "undefined") ? judge_username : query_judge_username;
return jQuery.ajax({ url: form_info.ajax_URL,
type: 'POST',
timeout: 30000,
dataType: "json",
data: {command: "get_judge_info", judge_username: query_judge_username},
error: function(jqxhr, textStatus, errorThrown) {
general_ajax_error(jqxhr, textStatus, errorThrown, "Failed to get judge information");
aj_log("Failed to get judge info");
},
success: function(data, textStatus, jqxhr) {
aj_log("Received judge info: " + jqxhr.responseText);
}
});
}
// Get information about a team (and about the user w/r/t that team)
function get_team_info(query_team_id) {
query_team_id = (typeof query_team_id == "undefined") ? team_id : query_team_id;
return jQuery.ajax({ url: form_info.ajax_URL,
type: 'POST',
timeout: 30000,
dataType: "json",
data: {command: 'get_info', form_name: form_info.name, team_id: parseInt(query_team_id)},
error: function(jqxhr, textStatus, errorThrown) {
general_ajax_error(jqxhr, textStatus, errorThrown, "Failed to get team information");
aj_log("Failed to get team info");
},
success: function(data, textStatus, jqxhr) {
aj_log("Received team info: " + jqxhr.responseText);
}
});
}
function get_lab_info(query_lab_id) {
query_lab_id = (typeof query_lab_id == "undefined") ? lab_id : query_lab_id;
return jQuery.ajax({ url: form_info.ajax_URL,
type: 'POST',
timeout: 30000,
dataType: "json",
data: {command: 'get_info', form_name: form_info.name, lab_id: parseInt(query_lab_id)},
error: function(jqxhr, textStatus, errorThrown) {
general_ajax_error(jqxhr, textStatus, errorThrown, "Failed to get lab information");
aj_log("Failed to get lab info");
},
success: function(data, textStatus, jqxhr) {
aj_log("Received lab info: " + jqxhr.responseText);
}
});
}
function get_course_info(query_course_id) {
query_course_id = (typeof query_course_id == "undefined") ? course_id : query_course_id;
return jQuery.ajax({ url: form_info.ajax_URL,
type: 'POST',
timeout: 30000,
dataType: "json",
data: {command: 'get_info', form_name: form_info.name, course_id: parseInt(query_course_id)},
error: function(jqxhr, textStatus, errorThrown) {
general_ajax_error(jqxhr, textStatus, errorThrown, "Failed to get course information");
aj_log("Failed to get course info");
},
success: function(data, textStatus, jqxhr) {
aj_log("Received course info: " + jqxhr.responseText);
}
});
}
function get_sub_form_list() {
aj_log("Getting sub form list");
return jQuery.ajax({ url: form_info.ajax_URL,
type: 'POST',
timeout: 30000,
dataType: "json",
data: {command: 'get_sub_form_list', form_name: form_info.name, team_id: team_id},
//**THIS DATA DOES NOT WORK FOR ANY OWNER TYPE EXCEPT TEAMS.
error: function(jqxhr, textStatus, errorThrown) {
general_ajax_error(jqxhr, textStatus, errorThrown, "Failed to get subforms");
aj_log("Failed to get sub form list");
},
success: function(data, textStatus, jqxhr) {
aj_log("Received sub_form list: " + jqxhr.responseText);
}
});
}
// =====================================================
// BLOCK
// USER INFO RESPONSE
// =====================================================
//This function has been obsoleted by new_form_setup, which is currently in testing.
//function set_up_form_and_mode() {
//Some of this was redundant with show_hide_submitted_status.
// determine_permissions();
// show_owner();
// show_mode();
// if (form_info.owner_type == "team") {
// $("#team_select_inner").slideUp();
// }
// //if the user has permission, go ahead and set up for each mode
// if (permissions[mode] == true) {
// if (mode == "view") {
// // console.log("setup view");
// $(".admins_only").hide();
// }
// else if (mode == "edit") {
// // console.log("setup edit");
// if (!form_submitted) {
// undisable_all_DFFs();
// }
// else {
// $("[data-form-field='return_form']").prop("disabled", false);
// }
// $(".admins_only").hide();
// }
// else if (mode == "admin") {
// // console.log("setup admin");
// undisable_all_DFFs();
// undisable_admin_DFFs();
// $(".admins_only").show();
// }
// //finish displaying the form
// if (!form_displayed) {
// display_form();
// }
// }
// else {
// //NOPE, USER, STAHP THAT
// deny_permission();
// }
// //Show admins the <p> that contains basic access to admin functions
// if(user_info.is_super == "true") {
// $("#admin_link").show();
// }
//}
function respond_to_error_in_owner_info() {
aj_display_error("Invalid " + form_info.owner_type + " selected");
}
//**Moe some of this down in to subform utility functions
function display_form() {
if ($("[data-form-field='sub_form']").length == 1) {
var sfreq = get_sub_form_list();
sfreq.done(function(data, textStatus, jqxhr){
data.sub_forms.forEach(function(sf) {
if (sf.sub_form == "") {
return;
}
make_new_sub_form(sf.sub_form, sf.submitted == "1", false);
});
});
initially_get_old_answers();
}
else {
initially_get_old_answers();
}
}
function deny_permission() {
//because I got rid of the no kick checkbox
nokick = false;
if (!user_info.is_super) { $(".admins_only").hide(); }
var alert_string = "Sorry, you don't have permission to " + mode + " that form.";
//**figure out how to unify this string because it depends on current mode
if (permissions.view) {
if (nokick) {
alert_string += "\n" + messages.not_kicking_to_view_mode;
}
else {
alert_string += "\n" + messages.kicking_to_view_mode;
}
}
if (nokick) {
aj_display_error(alert_string);
}
else {
alert(alert_string);
change_mode("view");
}
}
//This function shows or hides an optional div id="submitted_warning"
//and also does some new stuff and is part of the control flow
//but is partly redundant with set_up_form_and_mode
//so should be cleaned up.
//UPDATE: this function has been obsoleted by new_form_setup and is not called anywhere anymore.
//function show_hide_submitted_status(submitted) {
// if (submitted > 0) {
// $("#submitted_warning").show();
// readonly_all_DFFs();
// if ((permissions["edit"] || permissions["admin"]) && mode != "view") {
// $("[data-form-field='return_form']").prop("disabled", false);
// }
// }
// else {
// $("#submitted_warning").hide();
// if (mode == "edit" && permissions["edit"]) {
// undisable_all_DFFs();
// }
// else if (mode == "admin" && permissions["admin"]) {
// undisable_all_DFFs();
// undisable_admin_DFFs();
// }
// if ((permissions["edit"] || permissions["admin"]) && mode != "view") {
// $("[data-form-field='return_form']").prop("disabled", true);
// }
// }
//}
//**Okay so this needs MAJOR testing.
//The problem is that the interaction of form_submitted, form_displayed, display_form, and OIC
//may create an infinite loop. If we're gonna call this on initial setup AND every time someone hits
//the submit button... can we not call it twice? Problem is OIC calls this when someone hits the su
function new_form_setup() {
determine_permissions();
//special-case because it's common to poke around a bunch of teams, then go back to your team
//and be confused that you can't edit
if (mode == "view" && form_info.default_mode == "edit" && permissions["edit"]) {
mode = "edit";
}
show_owner();
show_mode();
if (!permissions[mode]) {
deny_permission();
}
//Here's where we handle the visibility of all the accessory crap that ISN'T a dff.
if (form_info.owner_type == "team") {
$("#team_select_inner").slideUp(); //this won't get called unless a team has been picked
}
if (form_submitted) {
$("#submitted_warning").show();
}
else {
$("#submitted_warning").hide();
}
if (!form_displayed) {display_form();}
set_up_dffs_for_mode();
}
function set_up_dffs_for_mode(mymode) {
mymode = (typeof mymode == "undefined") ? mode : mymode;
if (!permissions[mode]) {
mymode = "view";
}
//**Carefully review all these conditions against what they should be. Oh god.
$("[data-form-field]").each(function(index, field) {
field = $(field);
var dff = field.attr("data-form-field");
if (dff == "sub_form") { //the subform select
dffswitch(field, "on");
}
else if (dff.startsWith("sub_form")) { //subform accessory fields
dffswitch(field, ($("[data-form-field='sub_form']").val() == "" && mode != "view") ? "on" : "off");
}
else if (dff.startsWith("admin")) { //admin dffs
dffswitch(field, (mode == "admin" && user_info.is_super == "true") ? "on" : "off");
}
else if (dff == "submit") { //submit button
dffswitch(field, (mode != "view" && permissions.submit && !form_submitted) ? "on" : "off");
}
else if (dff == "return_form") { //unsubmit button
dffswitch(field, (mode != "view" && permissions.edit && form_submitted) ? "on" : "off");
}
else if (dff == "delete_form") { //delete button
dffswitch(field, (mode != "view" && permissions.edit) ? "on" : "off");
}
else { //Regular DFF
//This will ALSO check for an empty sub_form (value "").
//Note that a NONEXISTENT sub_form would have val() undefined.
dffswitch(field, (mode != "view" && !form_submitted && $("[data-form-field=sub_form]").val() != "") ? "on" : "off");
}
});
}
// =====================================================
// BLOCK
// OWNER/MODE SHOWING/CHANGING
// =====================================================
//modes!
//All this does is reconcile the view/edit/admin mode indicator at the top of the page
function show_mode() {
//console.log("show_mode");
var m = $("#modes");
if (mode == "view") {
m.html(jQuery.parseHTML("Mode: View <a class='change_mode' data-mode='edit'>(click for edit mode)</a>"));
}
else if (mode == "edit") {
m.html(jQuery.parseHTML("Mode: Edit <a class='change_mode' data-mode='view'>(click for view mode)</a>"));
}
else if (mode == "admin") {
m.html(jQuery.parseHTML("Mode: Admin <a class='change_mode' data-mode='view'>(view)</a> <a class='change_mode' data-mode='edit'>(edit)</a>"));
}
//if the user can't edit, color edit links gray
if (!permissions.edit) {
$("[data-mode='edit']").css("color", "gray");
}
}
function show_owner() {
var owner_name;
var display_text = "[none]";
//case team
if (form_info.owner_type == "team") {
$("#prompt_to_choose_team").hide();
owner_name = owner_info.team_name;
var this_teams_profile_href = "http://igem.org/Team.cgi?id=" + team_id;
display_html = "for Team <a target='_blank' href='" + this_teams_profile_href + "'>" + owner_name + "</a>";
}
//case author
else if (form_info.owner_type == "user") {
owner_name = owner_info.full_name;
display_html = "for user " + owner_name;
}
//case lab, course, judge
else {
//owner_name = display_html = "oops, this form is owned by a lab, course, or judge";
throw new FormError("Can't show_owner for lab/course/judge: not implemented yet");
}
$("#owner_name_display").html(display_html).show();
}
function prompt_to_choose_team() {
//console.log("prompt_to_choose_team");
$("#owner_name_display").hide();
$("#prompt_to_choose_team").show();
$("#team_select_inner").show();
}
function change_mode(new_mode) {
if (form_info.owner_type == "team" && typeof owner_info == "undefined") {
alert("Please choose a team first!"); //**unify? or make team-choosing better
return;
}
//console.log("change_mode");
refresh_form(false);
mode = new_mode;
check_mode();
//set_up_form_and_mode();
new_form_setup();
}
function change_team(new_team_id) {
//console.log("change_team");
refresh_form();
team_id = new_team_id;
get_owner_info();
}
//Change to a different user's form
function change_author(new_author) {
refresh_form();
author = new_author;
get_owner_info();
}
//Checks the mode and sets to default_mode if it's invalid or unspecified
function check_mode() {
if (valid_modes.indexOf(mode) == -1) {
mode = form_info.default_mode;
}
}
//This function goes into the team_select_container and replaces all the
//links with a link to the current page / query string with the selected team_id
//and the current mode.
function replace_links_in_team_selector() {
$("#team_select_inner").find("a").each(function() {
var the_link = $(this);
var existing_href = the_link.attr("href");
var team_id_from_this_link = existing_href.split("?id=")[1];
the_link.removeAttr("href").addClass("change_team").attr("data-team", team_id_from_this_link);
});
}
// =====================================================
// BLOCK
// SENDING AND RECEIVING FORM ANSWERS
// =====================================================
function one_input_changed( input_element, command, effective_date, send_sub_form_accessories, check_for_silent_logout ) {
send_sub_form_accessories = (typeof send_sub_form_accessories == "undefined") ? false : send_sub_form_accessories;
check_for_silent_logout = (typeof check_for_silent_logout == "undefined") ? true : check_for_silent_logout;
var input_element = $( input_element );
//permissions
if (command == "submit" || command == "delete_form") {
if (!permissions["submit"] && !permissions["admin"]) {
alert(messages.user_cannot_submit);
if (typeof custom_refuse_submission == "function") {
custom_refuse_submission(input_element, command, effective_date);
}
return;
}
}
if (command == "submit") {
//check if it has a confirmation box and if it's checked
//if not, refuse submission
//(currently unable to handle multiple confirmation boxes)
var confirmation_box_id = input_element.attr("data-confirmation");
if(typeof confirmation_box_id != "undefined") {
var confirmed = $("#" + confirmation_box_id).prop("checked");
if(!confirmed) {
submission_not_confirmed();
return;
}
}
}
//warning on unsubmitting a signed form
if (command == "return_form") {
if (permissions.edit && !permissions.submit) {
//**can't unify this msg yet
var confirm_string = "Are you sure you want to unsubmit this form?\n\n";
confirm_string += "You will need a " + form_info.permissions.submit.join(" / ") + " to submit the form again."
if (!confirm(confirm_string)) {
return;
}
}
}
feedback(input_element, "sending");
if (owner_info == undefined) {
aj_log( "Error: no form owner specified");
return;
}
//Uncheck all confirmation boxes,
//except on submit, return, and the initial "store" request when input_element is ''
if (command != "submit" && command != "return_form" && input_element.length != 0) {
uncheck_all_confirmations();
}
// Step 1: Format information about the element that changed and send it to the server
// If a sub_form element exists, get its value
var sub_form = $("[data-form-field=sub_form]").val() || '';
var eff_date = ( typeof effective_date !== 'undefined' ) ? effective_date : '2004-01-01';
if (send_sub_form_accessories) {
//in this condition we want to package all the values of the subform accessory fields into a single entry_list
var entry_list = $("[data-form-field^=sub_form_]").get().reduce(function(previous, current, index, array) {
var tmp = get_one_entry($(current));
if (typeof tmp == "undefined") { return previous; }
else { return previous.concat(tmp); }
}, []);
}
else {
var entry_list = get_one_entry( input_element );
}
var json_entries = JSON.stringify( entry_list );
aj_log( "OIC: " + command + ' '+ json_entries );
// When the sub_form changes, we clear all the inputs and they are reloaded by ajax
if ( input_element.attr('data-form-field') == 'sub_form' ) {
clear_form_but_keep_sub_form();
}
// When the sub-form is deleted, we clear all the inputs and they are reloaded by ajax
if ( input_element.attr('data-form-field') == 'delete_form') {
//and delete the corresponding subform, unless it's the empty one
$("[data-form-field=sub_form]").find(":selected").not("[value='']").remove();
clear_form();
}
var data = {command: command, form_name: form_info.name, sub_form: sub_form, eff_date: eff_date, entry_list: json_entries};
if (form_info.owner_type == "team") {
data.team_id = team_id;
}
else if (form_info.owner_type == "user") {
data.author_username = author;
}
else {
throw new FormError("OIC can't deal with labs, courses, or judges");
}
var stor_req = jQuery.ajax({url: form_info.ajax_URL,
type: 'POST',
timeout: 30000,
data: data,
dataType: "json",
error: function(jqxhr, textStatus, errorThrown) {
feedback(input_element, "invalid");
general_ajax_error(jqxhr, textStatus, errorThrown, "Failed to save entry " + input_element.attr("name"));
aj_log("Failed one_input_changed on entry " + input_element.attr("name"));
},
success: function(data, textStatus, jqxhr) {
aj_log( "Received: " +jqxhr.responseText);
if (check_for_silent_logout && data.return_error == "Not logged in") {
respond_to_silent_logout(input_element);
return;
}
form_submitted = parseInt(data.submitted) > 0;
//console.log("Done with OIC! Command was " + command);
if (command == "submit" || command == "return_form") {
//console.log(typeof data.submitted);
//show_hide_submitted_status( data.submitted );
new_form_setup();
if (command == "submit") { mark_current_sub_form_as_submitted(); }
else { mark_current_sub_form_as_unsubmitted(); }
}
if (input_element.get(0) == $("[data-form-field=sub_form]").get(0)) {
//set_up_dffs_for_mode();
new_form_setup();
}
process_received_data( jqxhr.responseText );
// Removes any warnings from other elements of the same name (radio buttons)
unwarn($("[name='" + input_element.attr("name") + "']").not(input_element));
feedback(input_element, "sent");
aj_clear_error();
if(typeof custom_one_input_changed_response == "function") {
//Allow individual forms to have a custom response
custom_one_input_changed_response(input_element, command, effective_date, data, textStatus, jqxhr);
}
}
});
stor_req.done(function(data, textStatus, jqxhr) {
jQuery.noop();
});
}
// Step 1: Format up one entry
function get_one_entry(input_element) {
//console.log("get_one_entry");
var name = input_element.attr("data-form-field");
var answer = input_element.val();
//if the answer is an array, as it is from a select multiple,
//then preemptively JSON.stringify it
if (answer instanceof Array) {
// console.log("Found an Array answer, converting to string");
answer = answer.toString();
// console.log(answer);
}
if (input_element.attr('type') == 'checkbox') {
answer = input_element.prop('checked');
}
else if (input_element.attr('type') == 'radio') {
if (!input_element.prop('checked')) {
return;
}
answer = input_element.prop('value');
}
else if (input_element.attr('type') == 'submit') {
answer = '';
}
return [ {'field_name': name, 'answer':answer } ];
}
// Step: 2 Process the received data for all the inputs
// The received data looks like { ... , : [ { input_name: 'name', answers: 'answer'}, .... ]
function process_received_data( data ) {
//console.log("process_received_data");
try {
data = JSON.parse( data ); //**Rewrite this to use try_parse_received_data
}
catch (err) {
aj_log( 'JSON.parse error: ' + err );
return;
}
aj_display_error(data.error);
var entry_list = data.entry_list || [];
for (var i = 0; i < entry_list.length; i++) {
process_one_received_entry( entry_list[i] );
}
// if (!form_displayed) {
// new_form_setup();
// }
form_displayed = true;
return;
}
//**Make this not fill in admin fields if you're not an admin! Or only some of them. Or something.
function process_one_received_entry(entry) {
var field_name = entry["field_name"] || '';
var answer = entry["answer"] || '';
var input_element = $("[data-form-field='"+ field_name +"']");
var input_type = input_element.attr('type');
if (input_type == 'submit') {
return;
}
//Special case for select-multiples
if (input_element.is('select') && input_element.prop("multiple")) {
// console.log("Hey, this element is a select-multiple, I'm gonna split the answer");
// console.log("The old answer is " + answer);
answer = answer.split(",");
// console.log("The new answer is " + answer);
}
aj_log("Processing " + field_name + " = '" + answer + "' (Found: " +input_element.length+ " element Type: "+input_type+")");
// Radio buttons are identified by form-field and value
if (input_type == 'radio') {
input_element = input_element.filter("[value='"+answer+"']");
input_element.prop('checked', true);
}
else if (input_type == "checkbox") {
if (answer == 'false' || answer == '0') {
answer = false;
}
input_element.prop("checked", answer );
}
else {
input_element.val(answer);
}
}
//Helper function for initially loading in the answers from a new form
function initially_get_old_answers() {
one_input_changed('', 'store', '', false, false); //Store nothing, process all returned answers
feedback($("[data-form-field]"), "none"); //Clear green/yellow feedback divs
//form_displayed = true; //moved to OIC.done
}
function respond_to_silent_logout(input_element) {
input_element = (input_element instanceof jQuery) ? input_element : $(input_element);
var msg = "Your login session has expired.\n Answer to question \"" + input_element.attr("name") + "\" was not saved.\n Please log out and then log in to continue.";
warn(input_element, "Answer not saved. Please log out and log back in.");
alert(msg);
aj_display_error(msg);
}
// =====================================================
// BLOCK
// DETERMINING PERMISSIONS
// =====================================================
//Check the view/edit/submit/admin permissions of the current user on the current form.
//Puts them in global variable permissions.
function determine_permissions() {
//start by setting all permissions to false,
//otherwise you'll keep permissions wrongly when moving from a more-privileged owner
//to a less-privileged
permissions.view = false;
permissions.edit = false;
permissions.submit = false;
permissions.admin = false;
jQuery.each(form_info.permissions, function(perm_name, groups_having_this_perm) {
//console.log("Permission to " + perm_name);
for (index in groups_having_this_perm) {
var perm_group = groups_having_this_perm[index];
//console.log("Is user in group " + perm_group);
if (user_is_in_permission_group(perm_group)) {
//console.log("yes");
permissions[perm_name] = true;
break;
}
}
});
return permissions;
}
//Determine whether the current user falls into a given permission group on the current form.
//groups are ["individualOwner", "group_members", "super_users", "any_users", "public"]
function user_is_in_permission_group(perm_group) {
if (perm_group == "public") {
return true;
}
else if (perm_group == "any_users") {
return user_info.user_name != "";
}
else if (perm_group == "super_users") {
return user_info.is_super == "true";
}
else if (perm_group == "group_members") {
return (typeof owner_info.role == "string" && owner_info.role != "None");
}
else if (perm_group == "author") {
return user_info.user_name.toLowerCase() == author.toLowerCase();
}
else if (valid_roles.indexOf(perm_group) > -1) {
return owner_info.role == perm_group;
}
}
// =====================================================
// BLOCK
// AJAX HELPERS
// =====================================================
//General helper for ajax errors.
//In every case, it will display an alert and an aj_display_error.
//Not unifying these because form writers shouldn't change them.
function general_ajax_error(jqxhr, textStatus, errorThrown, custom_msg) {
//case: timeout error
if (textStatus == "timeout") {
alert("Timeout error");
aj_display_error("Timeout error: there could be a problem with your internet connection, or the server could be under a heavy load. Please try again later.");
}
//case: internet probably unplugged or something
else if (jqxhr.status == 0) {
alert("Unable to connect to server. Please check your Internet connection and try again.");
aj_display_error("Unable to connect to server. Please check your Internet connection and try again.");
}
//case: 4xx/5xx error
else if (400 <= jqxhr.status <= 599) {
alert("Server error [" + jqxhr.status + " " + jqxhr.statusText + "]");
aj_display_error("Server error [" + jqxhr.status + " " + jqxhr.statusText + "]. Please report this error to iGEM HQ. In your message, please include the URL of this page, and the date/time of the error.");
}
//other error
else {
alert("Error in ajax\n[" + textStatus + "]");
custom_msg = (typeof custom_msg == "undefined") ? "general AJAX error" : custom_msg;
aj_display_error(custom_msg);
}
}
//All our ajax requests will receive both a responseJSON and a responseText.
//Here, we attempt parsing the responseText. If there is an error in between
//"ajax request fail" and "you gave wrong parameters to form.cgi", the responseText
//will not be parseable as JSON.
function try_parse_received_info(responseText) {
try {
JSON.parse(responseText);
return true;
}
catch (err) {
aj_log("JSON.parse error: " + err);
aj_display_error(unparseable_ajax_response);
return false;
}
}
// =====================================================
// BLOCK
// SUBFORM STUFF
// =====================================================
//Accepts a string and makes a new choice in the subform dropdown.
//Boolean gotoit (default false) causes that choice to then be selected immediately.
function make_new_sub_form(name, submitted, gotoit) {
gotoit = (typeof gotoit == "undefined") ? false : gotoit;
var option = $("<option>").text(name).attr("value", name);
option.appendTo( submitted ? $("#sub_forms_submitted") : $("#sub_forms_unsubmitted") );
if (gotoit) {
option.prop("selected", true);//.trigger("change");
}
}
//Function and wrappers for moving subforms around.
// - option should be a jQuery object containing an option, or a string equaling the value of an option
// - move_to should be "submitted" or "unsubmitted"
function move_sub_form(option, move_to) {
if (typeof option == "string") {
option = $("[data-form-field=sub_form]").find("[value='" + option + "']");
}
option.detach().appendTo($("#sub_forms_" + move_to));
}
function mark_current_sub_form_as_submitted() {
move_sub_form($("[data-form-field=sub_form]").find(":selected"), "submitted");
}
function mark_current_sub_form_as_unsubmitted() {
move_sub_form($("[data-form-field=sub_form]").find(":selected"), "unsubmitted");
}
function empty_sub_form_list() {
$("[data-form-field=sub_form]").children("optgroup").empty();
}
// =====================================================
// BLOCK
// MISC UTILITY FUNCTIONS
// =====================================================
//Fake "assert" function for assertion testing.
//It is best practice to strip out assertions before code goes to real users.
function assert(condition, message) {
if (!condition) {
message = message || "Assertion failed";
if (typeof FormError !== "undefined") {
throw new FormError(message);
}
throw new Error(message);
}
}
function clear_form_but_keep_sub_form() {
$('[data-form-field][type!=checkbox][type!=radio][type!=submit][data-form-field!=sub_form]').val('');
$('[data-form-field][type=checkbox]').prop('checked', false);
$("[data-form-field][type=radio]").prop('checked', false);
feedback($("[data-form-field]"), "none");
}
function clear_form() {
$("[data-form-field][type!=checkbox][type!=radio][type!=submit][data-form-field!='sub_form']").val('');
$('[data-form-field][type=checkbox]').prop('checked', false);
$("[data-form-field][type=radio]").prop('checked', false);
$("[data-form-field='sub_form']").val("");
feedback($("[data-form-field]"), "none");
}
//Upon first page load, or upon changing owner, we must clear the form to keep the browser from
//autofilling fields, and to keep the last owner's answers from leaving droppings in the new form
//also we readonly all DFFs -- they will be made un-readonly upon successful entry into
//Edit or Admin mode
function refresh_form(also_clear_answers) {
also_clear_answers = (typeof also_clear_answers == "undefined") ? true : false;
if (also_clear_answers) {
empty_sub_form_list();
clear_form();
form_displayed = false;
}
readonly_all_DFFs();
aj_clear_error();
}
//function simply unchecks all confirmation boxes (radio or checkbox) with
//class="confirmation"
function uncheck_all_confirmations() {
$(".confirmation").prop("checked", false);
}
function submission_not_confirmed() {
if (typeof custom_submission_not_confirmed == "function") {
custom_submission_not_confirmed();
}
else {
alert(messages.please_confirm_submission);
}
}
//gets all sets of radio buttons that have a data-form-field attribute
//returns them as an array of "name"s
function get_all_sets_of_radios() {
var names_list = [];
$("[data-form-field][type='radio']").each(function(index) {
//get the name
var this_radio_name = $(this).attr("name");
//check if the name is already in the list. if not, add it
if(names_list.indexOf(this_radio_name) == -1) {
names_list.push(this_radio_name);
}
//else skip it
});
return names_list;
}
// =====================================================
// BLOCK
// READONLY/DISABLE FUNCTIONS
// =====================================================
//Simply disables everything with a data-form-field
function disable_all_DFFs() {
$('[data-form-field]').prop('disabled', true);
}
//Function makes all DFF elements either readonly (for text boxes) or disabled (everything else).
//It does not change any of their event bindings.
function readonly_all_DFFs() {
$('[data-form-field]').each(function() {
var field = $(this);
if (field.is("textarea") || field.is("input[type='text']")) {
field.prop('readonly', true);
}
else {
field.prop('disabled', true);
}
});
}
//undisable all DFFs except admin ones and static ones
function undisable_all_DFFs() {
$(non_admin_DFFs).not(".static").prop('disabled', false).prop('readonly', false);
}
//undisable admin DFFs, except static ones
function undisable_admin_DFFs() {
$('[data-form-field^="admin"]').not(".static").prop("disabled", false).prop("readonly", false);
}
//switch one field to "off" or "on" (tested, works)
function dffswitch(field, offon) {
field = (field instanceof jQuery) ? field : $(field);
if (offon == "off") {
if (field.is("textarea") || field.is("input[type='text']")) {
field.prop('readonly', true);
}
else {
field.prop('disabled', true);
}
}
else if (offon == "on") {
field.prop("disabled", false).attr("readonly", false);
}
else {
throw new FormError("I can't switch field " + field.attr("data-form-field") + " to state " + offon);
}
}
// =====================================================
// BLOCK
// LOG ENTRIES AND ERROR MESSAGES
// =====================================================
// If you wish to Display a Log of Transactions add a div with id= 'aj_form_log'
function aj_display_log_div() {
var div = $('#aj_form_log');
div.addClass("admins_only");
div.html("<DIV id='aj_form_log_inner_div' "
+ "style='display: none; width: 500px;min-height:50px;padding: 3px 10px;margin:10px; "
+ "border: 1px solid gray;background-color:white'>"
+ "<\/DIV>" );
jq_versions();
aj_display_log_buttons();
}
function aj_display_log_buttons() {
var div = $('#aj_form_log_inner_div');
div.before("<button type='button' id='clear_log'>Clear Log<\/button>");
div.before("<button type='button' id='toggle_log'>Toggle Log<\/button>");
$("#clear_log").on("click", aj_display_log_div);
$("#toggle_log").on("click", function(event) {
$("#aj_form_log_inner_div").toggle();
});
}
function aj_log(text) {
var div = $('#aj_form_log_inner_div');
if (div.length == 0) {
return;
}
var old_text = div.html();
div.html(old_text + "<P>" + text + "<\/P>");
}
// Put the jQuery versions in the log box
function jq_versions() {
var jq_v='None';
try {
jq_v = jQuery.fn.jquery || '';
}
catch (err) {}
var ui_v = 'None';
try {
ui_v= jQuery.ui ? jQuery.ui.version || 'pre 1.6' : '';
}
catch (err) {}
aj_log( 'AJ Form Log: [ jQuery v '+ jq_v + ' jQuery UI v ' + ui_v + ' ]' );
}
function aj_display_error(error_text){
if (!error_text) {
return;
}
var div = $('#aj_form_errors');
div.html("<div id='aj_form_error_inner_div'><h5>Software error:</h5>" + error_text + "</div>");
div.show();
}
function aj_clear_error(){
//aj_display_error_text("");
$("#aj_form_errors").text("").hide();
}
// =====================================================
// BLOCK Hey I know you don't like this but don't get rid of it
// SHIELDING It could be useful for sign/lock/submit!
// =====================================================
//All these shield-manipulating functions expect a jQuery object as an argument.
//But if none is given, they default to the single shield on a form, which is assumed
//to have id="shield".
//Shield divs should have class "shield-active" or "shield-inactive" as defined in
//Template:CSS/SafetyForms
function toggle_shield(s) {
s = (typeof s == "undefined") ? $("#shield") : s;
if(s.hasClass("shield-inactive")) {
add_shield(s);
}
else {
remove_shield(s);
}
}
function add_shield(s) {
s = (typeof s == "undefined") ? $("#shield") : s;
s.removeClass("shield-inactive");
s.addClass("shield-active");
}
function remove_shield(s) {
s = (typeof s == "undefined") ? $("#shield") : s;
s.removeClass("shield-active");
s.addClass("shield-inactive");
}
// =====================================================
// BLOCK
// VALIDATION
// =====================================================
function validate_form_entries() {
var fields_to_validate;
var results = [];
$(non_admin_DFFs).each(function(index, field){
field = $(field);
var this_result = "";
var what_the_field_says = field.attr("data-validation");
if (what_the_field_says == "required") {
this_result = validate_one_field(field);
}
else if (what_the_field_says == "optional") {
return;
}
else if (typeof what_the_field_says == "undefined") {
if (form_info.validate_unspecified_fields == "required") {
this_result = validate_one_field(field);
}
}
//If we reach this case, data-validation is set to something custom
//It should be a selector (NOT JUST AN ID) for a radio or checkbox input elsewhere on the page.
//We will require field to be nonempty if the indicated radio/checkbox is checked.
else {
//console.log("Custom validating field " + field.attr("data-form-field") + " with respect to " + what_the_field_says);
var other_field = $(what_the_field_says);
if (other_field.length == 0) {
throw new FormError("Can't validate field " + field.attr("data-form-field") + " with respect to selector '" + what_the_field_says + "'; that field does not exist");
}
else if (other_field.attr('type') != "radio" && other_field.attr('type') != "checkbox") {
throw new FormError("Can't validate field " + field.attr("data-form-field") + " with respect to selector '" + what_the_field_says + "'; that field is not a radio or checkbox");
}
else {
if (other_field.prop("checked")) {
this_result = validate_one_field(field);
}
}
}
//Only add nonempty, nonduplicate strings
if (this_result != "" && results.indexOf(this_result) == -1) {
results.push(this_result);
}
});
return results;
}
function validate_one_field(fld) {
fld = (fld instanceof jQuery) ? fld : $(fld);
var type = fld.attr("type");
var name = fld.attr("name");
var result = "";
var isValid;
if (fld.attr("data-validate-as") == "email") {
return validate_email(fld);
}
if (type == "radio" || type == "checkbox") {
isValid = $("[name='"+name+"']:checked").length > 0;
}
else {
isValid = (fld.val() != "" && fld.val() != null && typeof fld.val() != "undefined");
}
if (!isValid) {
result = messages.field_validation_fail_beginning + " " + name;
feedback(fld, "invalid");
}
return result
}
var email_regex = /^.+@.+\..+/;
function validate_email(fld) {
var result;
if (email_regex.test(fld.val())) {
result = "";
unwarn(fld);
}
else {
result = messages.invalid_email_address;
feedback(fld, "invalid");
warn(fld, messages.invalid_email_address);
}
return result;
}
// =====================================================
// BLOCK
// YELLOW-AND-GREEN FEEDBACK
// =====================================================
var wrapper_none_color = "";
var wrapper_sending_color = "#FFFF99";
var wrapper_sent4 = "#B3FF66";
var wrapper_sent3 = "#C6FF8C";
var wrapper_sent2 = "#D9FFB3";
var wrapper_sent1 = "#ECFFD9";
var wrapper_invalid_color = "pink";
var wrapper_readonly_color = "#EEEEEE";
function feedback(element, command) {
element = (element instanceof jQuery) ? element : $(element);
//select the wrappers to color. If it's a set of radios or checkboxes, we color them all together,
//all that have the same "name" attribute.
// Otherwise, we color every input separately.
// var type = element.attr("ty