Difference between revisions of "Dev/Safety Form"

Line 1: Line 1:
{{Main2017}}
+
/*
{{HTML/JSDisabled}}
+
*This is the code for the Forms
<html>
+
*The code is based of Kelly Drinkwater's code, which can be found here: https://igem.org/Template:JS/KForms_2pt4?action=raw&ctype=text/javascript
 +
*/
  
<link rel="stylesheet" type="text/css" href="https://igem.org/wiki/index.php?title=HQ:Forms.css&action=raw&ctype=text/css" />
 
  
  
 +
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);
  
<script type="text/javascript">
+
function FormError(message) {
var form_info = {};
+
this.name = "FormError";
form_info.name="2017_final_safety_form";
+
this.message = message;
form_info.display_title='2017 Final Safety Form';
+
this.stack = (new Error()).stack;
form_info.year="2017";
+
}
form_info.default_mode = "edit";
+
FormError.prototype = Object.create(Error.prototype);
form_info.owner_type = "team";
+
form_info.permissions = {view: ["public"],
+
edit: ["group_members"],
+
submit: ["Instructor", "Primary PI", "Secondary PI"],
+
admin: ["super_users"]};
+
form_info.validate_unspecified_fields = "required";
+
form_info.ajax_URL = "https://2017.igem.org/cgi/forms/form.cgi";
+
</script>
+
  
  
<script src="https://igem.org/wiki/index.php?title=HQ:Safety_Forms.js&action=raw&ctype=text/javascript"></script>
+
// =====================================================
 +
// 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
 +
};
 +
}
  
<div class="column full_size">
 
<h1>Final Safety Form</h1>
 
  
<p>This form is for you to tell us all about your project, the organisms/parts you are using, the potential risks of your project, and what you are doing to reduce those risks.</p>
 
  
<ul>
 
<li><strong>We encourage STUDENTS, instead of instructors, to complete this form. However, you will need an Instructor or PI to sign and submit this form.</strong></li>
 
<li>While you type, this form will remember your answers. When you are finished, press the "Submit" button at the bottom to send your form to the iGEM Safety Committee.</li>
 
<li>Submit this form by September 1.</li>
 
<li>If you will not be able to complete this form before the deadline, please email us (safety AT igem DOT org) and tell us about your situation.</li>
 
</ul>
 
</div>
 
  
<div class="clear extra_space"></div>
+
if (typeof String.prototype.startsWith != "function") {
 +
String.prototype.startsWith = function(str) {
 +
return this.slice(0, str.length) == str;
 +
}
 +
}
  
  
 +
//VARIABLES
  
 +
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"
 +
};
  
  
<div class="admins_only" id="admin_link">
 
<p>Admin view (orange)  is only visible to super users. (<a href="#" id="hide_admin">Hide admin view</a>)</p>
 
<p><b><a class="change_mode" data-mode="admin">Go to Admin Mode</a></b> / <a class="change_team" data-team="1874">Go to Team Example</a> / Go to Username: <input type="text" id="author_select" autocomplete="off"></p>
 
</div>
 
  
 +
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"];
  
 +
//this is where the valid user/viewer categories are defined
 +
var valid_permission_groups = ["author", "group_members", "super_users", "any_users", "public"];
 +
var valid_roles = ["Primary Contact", "Primary PI", "Secondary PI", "Student Leader", "Student", "Instructor", "Advisor", "Other", "None"];
 +
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
  
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
  
THIS IS WHERE THE FORM BEGINS
+
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");
 +
}
  
  
<div class="column full_size" style="text-align:center; margin:auto;"><h1> Safety Form </h1> </div>
+
// =====================================================
<div class="clear"></div>
+
// BLOCK
<div class="line_divider"></div>
+
// VALIDATING INDIVIDUAL FORMS CONFIG
 +
// =====================================================
  
<div class="column full_size" id="formbody">
+
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();
  
TEAM INFORMATION
+
//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: "https://igem.org/aj/team_list.cgi",
 +
type: "GET",
 +
timeout: 30000,
 +
dataType: "json",
 +
data: {command: "get_team_list", year: form_info.year},
 +
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);
 +
}
  
<fieldset>
+
$(document).ready(document_ready_handler);
  
<h2>1.- Team Selection </h2>
+
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);
 +
}
 +
}
  
<div id="formtitlemodewrapper" class="column half_size">
 
<p class="question">
 
<!-- <span id="form_title_display"></span> --> <span id="owner_name_display"></span> <span id="prompt_to_choose_team"> Please choose a team</span>
 
</p>
 
  
<h4 id="modes"></h4>
 
  
<select id="team_list_dropdown">
+
function DFF_change_handler_with_validation(event) {
<option id="no_team_selected" value="">Choose team...</option>
+
var validation_result = validate_one_field(this);
</select>
+
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);
 +
}
  
<div id='aj_form_errors'> </div>
 
  
</div>
 
  
 +
function admin_lockout_click_handler(event) {
 +
//console.log("admin_lockout_click_handler");
 +
event.preventDefault();
 +
alert(messages.user_cannot_edit_admin_fields);
 +
}
  
  
  
<div id="submitted_warning"  class="column half_size">
+
function click_handler_for_submit(event) {
<p>This form has been submitted.</p>
+
event.preventDefault();
<p>You can <a href="#" id="popwin_unsubmit">unsubmit the form</a> if you wish to make further edits.</p>
+
<p><a href="#" id="popwin_dismiss">Click here to dismiss this message</a></p>
+
//if there are any validation error messages, show them and don't submit
</div>
+
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();
 +
}
 +
}
  
</fieldset>
+
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
 +
// =====================================================
  
CONTACT INFORMATION
+
//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);
 +
}
 +
});
 +
}
  
<fieldset>
 
  
<div class="column half_size">
 
<h2>2.-  Contact Information </h2>
 
<p>Name <input type="text" name="Team member Name" data-form-field="corr_team_member_name" placeholder="Name"></p>
 
<p>Email <input type="text" name="Team member Email" data-validate-as="email" class="validate-on-change" data-form-field="corr_team_member_email" placeholder="Email"></span></p>
 
</div>
 
  
<div class="column half_size message_box">
+
//**These get_foo_info functions are kind of repetitive. Maybe rewrite later.
<h4> Note </h4>
+
<p>It is your responsibility to demonstrate that your work is safe. The easiest way to do this is through a well-researched and comprehensive safety form.</p>
+
</div>
+
  
</fieldset>
+
//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);
 +
}
 +
});
 +
}
  
ABOUT OUR LAB
+
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);
 +
}
 +
});
 +
}
  
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------->
 
  
<fieldset>
 
<h2>3.- About our Lab </h2>
 
<p> This part of the form is for you to tell us about your laboratory space and your safety equipment. If you need help, please consult your faculty advisor or laboratory manager. You can also read the <a href="https://2017.igem.org/Safety">Safety page</a> and the <a href="https://2017.igem.org/Safety/Risk_Groups">Risk Groups page</a> for basic information about different types of biology labs. </p>
 
  
<!--------------------------------------------------------------------------------------------- Question 1 --------------------------------------------------------------------------------------------------------->
 
  
  
<p class="question">1. Please upload a photo or two of your lab, preferably showing the relevant safety features and paste the link here:</p>
+
function respond_to_error_in_owner_info() {
<textarea rows="1" name="Lab Photograph" data-form-field="lab_photo"></textarea>
+
aj_display_error("Invalid " + form_info.owner_type + " selected");
 +
}
  
  
<!--------------------------------------------------------------------------------------------- Question 2 --------------------------------------------------------------------------------------------------------->
 
  
  
<h3> Biosafety Level </h3>
+
//**Moe some of this down in to subform utility functions
<p class="question">2. What is the Safety Level of your lab? <a target="_blank" href="https://2016.igem.org/Safety/Risk_Groups">[Help about Risk Groups and Safety Levels]</a></p>
+
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();
 +
}
 +
}
  
<div class="column half_size">
 
<ul>
 
<li><label><input type="radio" name="1. Lab Safety Level" data-form-field="lab_bsl" value="bsl_1"> Level 1 (low risk)</label></li>
 
<li><label><input type="radio" name="1. Lab Safety Level" data-form-field="lab_bsl" value="bsl_2"> Level 2 (moderate risk)</label></li>
 
<li style="color: #a0a0a0;"><label><span class="wrapper"><input type="radio" disabled id="bsl_3"></span> Level 3 (high risk)</label></li>
 
<li style="color: #a0a0a0;"><label><span class="wrapper"><input type="radio" disabled id="bsl_4"></span> Level 4 (extreme risk)</label></li>
 
<li><label><input type="radio" name="1. Lab Safety Level" data-form-field="lab_bsl" value="bsl_none"> Our team is not doing any wet-lab work</label></li>
 
</ul>
 
</div>
 
  
<div class="column half_size message_box">
 
<h4> Note </h4>
 
<p>
 
iGEM teams should not use Risk Group 3 or 4 organisms, and they should not work in Safety Level 3 or 4 labs.
 
If you are planning to work at Safety Level 3 or 4, contact safety (AT) igem (DOT) org right away!!
 
</p>
 
</div>
 
<div class="clear"></div>
 
<ul>
 
<li><label><input type="radio" name="1. Lab Safety Level" data-form-field="lab_bsl" value="bsl_other"> Other safety level:</label> <textarea rows="2" name="Other safety level (describe)" data-form-field="bsl_other_describe" data-validation="[value=bsl_other]" placeholder="Please describe"></textarea></li>
 
  
<li><label><input type="radio" name="1. Lab Safety Level" data-form-field="lab_bsl" value="bsl_multiple"> We have several different lab spaces with different Safety Levels:</label> <textarea rows="2" name="Different safety levels (describe)" data-form-field="bsl_multiple_describe" data-validation="[value=bsl_multiple]" placeholder=" Please describe what experiments you do in each space"></textarea></li>
 
  
  
<li><label><input type="radio" name="1. Lab Safety Level" data-form-field="lab_bsl" value="bsl_unknown"> Unknown (please comment):</label> <textarea rows="2" name="Safety level unknown (comment)" data-form-field="bsl_unknown_describe" data-validation="[value=bsl_unknown]" placeholder="Please describe"></textarea></li>
 
</ul>
 
  
 +
//**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();
 +
}
  
<!--------------------------------------------------------------------------------------------- Question 3 --------------------------------------------------------------------------------------------------------->
+
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");
 +
}
 +
});
 +
}
  
<h3> Work areas </h3>
+
// =====================================================
<p class="question"> 3 Which work areas do you use to handle biological materials? Please check all that apply.</p>
+
// BLOCK
 +
// OWNER/MODE SHOWING/CHANGING
 +
// =====================================================
  
<ul>
+
//modes!
<li><label><input type="radio" name="3. Work Areas" data-form-field="openbench"> Open bench </label></li>
+
//All this does is reconcile the view/edit/admin mode indicator at the top of the page
<li><label><input type="radio" name="3. Work Areas" data-form-field="bsc"> Biosafety cabinet </label></li>
+
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");
 +
}
 +
}
  
<li><label><input type="radio" name="3. Work Areas" data-form-field="workarea_other"> Other work area: </label> <textarea rows="2" name="Other work area (describe)" data-form-field="workarea_other_describe" data-validation="[data-form-field=workarea_other]" placeholder="Please describe"></textarea></li>
+
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 = "https://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();
 +
}
  
<li><label><input type="radio" name="3. Work Areas" data-form-field="workarea_unknown"> Unknown: </label> <textarea rows="2" name="Work area unknown (comment)" data-form-field="workarea_unknown_describe" data-validation="[data-form-field=workarea_unknown]" placeholder="Please comment"></textarea></li>
+
function prompt_to_choose_team() {
</ul>
+
//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();
 +
}
  
<!--------------------------------------------------------------------------------------------- Question 4 --------------------------------------------------------------------------------------------------------->
+
function change_team(new_team_id) {
 +
//console.log("change_team");
 +
refresh_form();
 +
team_id = new_team_id;
 +
get_owner_info();
 +
}
  
<h3> Biosafety Training </h3>
+
//Change to a different user's form
<p class="question">4. Have your team members received any safety training yet?</p>
+
function change_author(new_author) {
 +
refresh_form();
 +
author = new_author;
 +
get_owner_info();
 +
}
  
<div class="column half_size">
+
//Checks the mode and sets to default_mode if it's invalid or unspecified
<ul>
+
function check_mode() {
<li><input type="radio" name="3. a) Safety Training" data-form-field="training" value="training_already"> Yes, we have already received safety training.</label></li>
+
if (valid_modes.indexOf(mode) == -1) {
<li><input type="radio" name="3. a) Safety Training" data-form-field="training" value="training_notyet"> We plan to receive safety training in the future: </label> <textarea rows="2" name="Plan to receive safety training in future (when?)" data-form-field="training_notyet_describe" data-validation="[value=training_notyet]" placeholder="Please specify approximately when"></textarea></li>
+
mode = form_info.default_mode;
 +
}
 +
}
  
<li><input type="radio" name="3. a) Safety Training" data-form-field="training" value="training_none"> We will not have safety training:</label> <textarea rows="2" name="No safety training (comment)" data-form-field="training_none_describe" data-validation="[value=training_none]" placeholder="Please comment"></textarea></li>
+
//This function goes into the team_select_container and replaces all the
</ul>
+
//links with a link to the current page / query string with the selected team_id
</div>
+
//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);
 +
});
 +
}
  
<div class="column half_size message_box">
+
// =====================================================
<h4> Note </h4>
+
// BLOCK
<p>
+
// SENDING AND RECEIVING FORM ANSWERS
For the purposes of iGEM, biosafety and biosecurity training covers the procedures and practices used to manage risks to your team, colleagues and institution, communities and the environment - from accidents or deliberate misuse of your projects. All team members are expected to be aware of these risks and to work to manage them
+
// =====================================================
</p>
+
</div>
+
  
 +
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");
  
<!--------------------------------------------------------------------------------------------- Question 5  --------------------------------------------------------------------------------------------------------->
+
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();
 +
}
  
<div class="clear"></div>
+
// 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");
 +
}
  
<p class="question">5. Please briefly describe the topics that you learned about (or will learn about) in your safety training.</p>
+
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();
 +
});
 +
}
  
<ul>
+
// Step 1: Format up one entry
<li><input type="radio" data-form-field="learn" value="learn_lab_access_and_rules"> Lab access and rules (including appropriate clothing, eating and drinking, etc.) </label></li>
+
function get_one_entry(input_element) {
<li><input type="radio"  data-form-field="learn" value="learn_responsible"> Responsible individuals (such as lab or departmental specialist or institutional biosafety officer) </label></li>
+
//console.log("get_one_entry");
<li><input type="radio"  data-form-field="learn" value="learn_biosafety_levels">Differences between biosafety levels </label></li>
+
var name = input_element.attr("data-form-field");
<li><input type="radio"  data-form-field="learn" value="learn_biosafety_equipment">Biosafety equipment (such as biosafety cabinets) </label></li>
+
var answer = input_element.val();
<li><input type="radio" data-form-field="learn" value="learn_biosafety_good_microbal_technique"> Good microbial technique (such as lab practices)</label></li>
+
<li><input type="radio"  data-form-field="learn" value="learn_disinfection"> Disinfection and sterilization </label></li>
+
//if the answer is an array, as it is from a select multiple,
<li><input type="radio"  data-form-field="learn" value="learn_emergency"> Emergency procedures </label></li>
+
//then preemptively JSON.stringify it
<li><input type="radio"  data-form-field="learn" value="learn_transport"> Transport rules </label></li>
+
if (answer instanceof Array) {
<li><input type="radio"  data-form-field="learn" value="learn_chemicals_fire_electrical">Chemicals, fire and electrical safety </label></li>
+
// console.log("Found an Array answer, converting to string");
<li><input type="radio"  data-form-field="learn" value="learn_other">Other </label> <textarea rows="2" name="learn other(comment)" data-form-field="learn_other" data-validation="[value=learn_other]" placeholder="Please comment"></textarea></li>
+
answer = answer.toString();
</ul>
+
// 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+")");
  
<!--------------------------------------------------------------------------------------------- Question 6  --------------------------------------------------------------------------------------------------------->
+
// 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);
 +
}
 +
}
  
  
<p class="question"> 6.  Who provided your safety training? Did you get training from multiple people? </p>
 
  
 +
//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
 +
}
  
<ul>
 
<li><input type="radio"  data-form-field="training_who" value="training_who_university"> University biosafety office </label></li>
 
<li><input type="radio"  data-form-field="training_who" value="training_who_department"> Departmental specialist </label></li>
 
<li><input type="radio"  data-form-field="training_who" value="training_who_PI"> PIs/instructors </label></li>
 
<li><input type="radio"  data-form-field="training_who" value="training_who_other"> Other </label> <textarea rows="1" name="training who other(comment)" data-form-field="training_who" data-validation="[value=training_who_other]" placeholder="Please comment"></textarea></li>
 
</ul>
 
  
  
<!--------------------------------------------------------------------------------------------- Question 7  --------------------------------------------------------------------------------------------------------->
+
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);
 +
}
  
<h3> Oversight </h3>
 
  
<p class="question">7. Who is responsible for the safety of biology labs at your institution? What are the guidelines for laboratory biosafety? Please give a link to these guidelines, or briefly describe them if you cannot give a link.</p>
+
//
 +
// ===========================================================================================================================================
 +
// DETERMINE PERMISSIONS
 +
 +
//ana
  
<textarea rows="3" name="7. Your institution's biosafety" data-form-field="institution_biosafety"></textarea>
+
//Check the view/edit/submit/admin permissions of the current user on the current form.
 +
//Puts them in global variable permissions.
 +
function determine_permissions() {
 +
 +
var perm_group;
 +
$.each(form_info.permissions, function(perm_name, groups_having_this_perm) {
 +
console.log("Permission to " + perm_name);
 +
perm_group = groups_having_this_perm[index];
 +
 +
for (index in groups_having_this_perm) {
 +
switch(perm_group){
 +
 +
case "public": {
 +
return true;
 +
break;
 +
}
 +
 +
case "any_users": {
 +
permissions.view = true;
 +
return user_info.user_name != "";
 +
break;
 +
}
 +
 +
case "super_users": {
 +
permissions.admin = true;
 +
permissions.submit = true;
 +
permissions.edit = true;
 +
permissions.view = true;
 +
 +
return user_info.is_super == "true";
 +
break;
 +
}
 +
 +
case "group_members": {
  
 +
permissions.edit = true;
 +
permissions.view = true;
 +
return (typeof owner_info.role == "string" && owner_info.role != "None");
 +
break;
 +
}
 +
 +
case "author": {
 +
 +
permissions.submit = true;
 +
permissions.edit = true;
 +
permissions.view = true;
 +
return user_info.user_name.toLowerCase() == author.toLowerCase();
 +
break;
 +
}
 +
}}
 +
 +
});
 +
 +
return permissions;
 +
}
  
  
<!--------------------------------------------------------------------------------------------- Question 8  --------------------------------------------------------------------------------------------------------->
 
  
  
<p class="question">8. In your country / region, what are the laws and regulations that govern biosafety in research laboratories? Please give a link to these regulations, or briefly describe them if you cannot give a link.</p>
 
  
<textarea rows="3" name="8. Your country's biosafety laws" data-form-field="country_biosafety"></textarea>
 
  
  
 +
//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") {
 +
 +
permissions.view = true;
 +
return user_info.user_name != "";
 +
}
 +
 +
else if (perm_group == "super_users") {
 +
 +
permissions.admin = true;
 +
permissions.submit = true;
 +
permissions.edit = true;
 +
permissions.view = true;
 +
 +
return user_info.is_super == "true";
  
<!--------------------------------------------------------------------------------------------- Question 9  --------------------------------------------------------------------------------------------------------->
+
}
 +
 +
 +
else if (perm_group == "group_members") {
  
 +
permissions.edit = true;
 +
permissions.view = true;
 +
return (typeof owner_info.role == "string" && owner_info.role != "None");
 +
}
 +
 +
else if (perm_group == "author") {
  
<h3> Uncertainties </h3>
+
permissions.submit = true;
 +
permissions.edit = true;
 +
permissions.view = true;
 +
return user_info.user_name.toLowerCase() == author.toLowerCase();
 +
}
 +
 +
 +
else if (valid_roles.indexOf(perm_group) > -1) {
 +
return owner_info.role == perm_group;
 +
}
 +
}
  
<p class="question">9. Are there any areas where you have encountered uncertainties concerning institutional, regional, national or international rules and regulations and whether they are relevant to your lab and/or work?</p>
 
  
<textarea rows="3" name="9. institutional uncertainties" data-form-field="institutional_uncertainties"></textarea>
+
//when the user doesn't have permission to view/ edit the form, display the appropiate message
 +
function deny_permission() {
 +
 +
// alert message to give to user
 +
var alert_string = "Sorry, you don't have permission to " + mode + " that form.";
 +
 +
//because I got rid of the no kick checkbox
 +
nokick = false;
 +
 +
//if the user is not a super user, hide admin view
 +
if (!user_info.is_super) {
 +
$(".admins_only").hide();
 +
}
 +
 +
  
 +
//**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");
 +
}
 +
}
  
  
 +
//
 +
// ===========================================================================================================================================
 +
//
  
<!-------------------------------------------------------------------------------------------- Question 10  --------------------------------------------------------------------------------------------------------->
 
  
<p class="question">10. Do you think the design of current regulations is sufficient to ensure safe and ethical practices? </p>
 
  
<textarea rows="3" name="10. are current regulations sufficient " data-form-field="sufficient_regulations"></textarea>
 
  
<p>
 
(If not, how else could you approach the design? We’re interested in your ideas for strategies that could be used to promote safe and ethical practices as it becomes easier to engineer biology (i.e. monitoring people or information, building safety into the design of equipment, etc). Can you think of any useful examples from other fields?)
 
</p>
 
  
  
</fieldset>
 
  
  
Line 317: Line 1,275:
  
  
<!-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
  
PROJECT OVERVIEW
 
  
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------->
+
// =====================================================
 +
// BLOCK
 +
// AJAX HELPERS
 +
// =====================================================
  
<fieldset>
+
//General helper for ajax errors.
<h2>4.- About our Project </h2>
+
//In every case, it will display an alert and an aj_display_error.
<p> This part of the form is for you to tell us about your primary project idea, what organisms and parts you will use and what your project will do. We know that iGEM teams often change project topics during the summer. When you change your primary project idea, please update this form to tell us about your new idea!
+
//Not unifying these because form writers shouldn't change them.
If you are still working on multiple project ideas by June 30, you may choose one to describe on this form, or you may tell us about other ideas in the "Further Comments" section.
+
function general_ajax_error(jqxhr, textStatus, errorThrown, custom_msg) {
</p>
+
//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;
 +
}
 +
}
  
  
<!-------------------------------------------------------------------------------------------- Question 11  --------------------------------------------------------------------------------------------------------->
+
// =====================================================
 +
// 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");
 +
}
 +
}
  
<h3> Project Overview </h3>
 
  
<p class="question">11. How will your project work?</p>
+
//Function and wrappers for moving subforms around.
<p>Describe the goal of your project: what is your engineered organism supposed to do? Please include specific technical details and names of important parts. (Even though your project might change, please describe the main project idea you are working on right now. See the example answers for help.)</p>
+
// - 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");
 +
}
  
<textarea rows="6" name="How your project works" data-form-field="how_it_works"></textarea>
+
function empty_sub_form_list() {
 +
$("[data-form-field=sub_form]").children("optgroup").empty();
 +
}
  
  
<!-------------------------------------------------------------------------------------------- Question 12  --------------------------------------------------------------------------------------------------------->
+
// =====================================================
 +
// 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);
 +
}
 +
}
  
<h3> Future applications  </h3>
+
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");
 +
}
  
<p class="question">12. How would your project be used in the real world?</p>
+
//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();
 +
}
  
<p>Imagine that your project was fully developed into a real product that real people could use. How would people use it? Check all appropriate boxes and expand in the comments section.
 
</p>
 
  
<ul>
+
//function simply unchecks all confirmation boxes (radio or checkbox) with
<li><label><input type="checkbox" name="Real world application" data-form-field="rwa_foundational"> Our project is foundational / we do not have a specific real-world application in mind </label><br> (Examples: library of standardized promoters, system for communication between cells)
+
//class="confirmation"
</li>
+
function uncheck_all_confirmations() {
 +
$(".confirmation").prop("checked", false);
 +
}
  
<li><label><input type="checkbox" name="Real world application" data-form-field="rwa_lab"> Only in the lab</label><br />
+
function submission_not_confirmed() {
(Examples: reporter strain for measuring the strength of promoters)</li>
+
if (typeof custom_submission_not_confirmed == "function") {
 +
custom_submission_not_confirmed();
 +
}
 +
else {
 +
alert(messages.please_confirm_submission);
 +
}
 +
}
  
<li><label><input type="checkbox" name="Real world application" data-form-field="rwa_factory"> In a factory</label><br />
+
//gets all sets of radio buttons that have a data-form-field attribute
(Examples: cells that make a flavor chemical for food, cells that make biofuel)
+
//returns them as an array of "name"s
</li>
+
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;
 +
}
  
<li><label><input type="checkbox" name="Real world application" data-form-field="rwa_consumer"> In a consumer product that ordinary people buy</label><br />
 
(Examples: cells that clean your clothes, bread made with engineered yeast)
 
</li>
 
  
<li><label><input type="checkbox" name="Real world application" data-form-field="rwa_agriculture"> In agriculture / on a farm</label><br />
 
(Examples: cells that guard against pests, engineered rice plants, cells that promote growth of crop plants)</li>
 
  
<li><label><input type="checkbox" name="Real world application" data-form-field="rwa_device"> In a small enclosed device</label><br />
+
// =====================================================
(Examples: a bio-sensing strip with cells that detect arsenic)
+
// BLOCK
</li>
+
// READONLY/DISABLE FUNCTIONS
 +
// =====================================================
  
<li><label><input type="checkbox" name="Real world application" data-form-field="rwa_environment"> In the natural environment</label><br />
+
//Simply disables everything with a data-form-field
(Examples: cells that remove pollution from lakes, engineered forest trees that can resist drought) </li>
+
function disable_all_DFFs() {
 +
$('[data-form-field]').prop('disabled', true);
 +
}
  
<li><label><input type="checkbox" name="Real world application" data-form-field="rwa_humanbody"> To be used in the human body, or in food</label><br />
+
//Function makes all DFF elements either readonly (for text boxes) or disabled (everything else).
(Examples: anti-cancer bacteria, bread made with engineered yeast, engineered rice plants) </li>
+
//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);
 +
}
 +
});
 +
}
  
<li><label><input type="checkbox" name="Real world application" data-form-field="rwa_other"> Other</label><br />
+
//undisable all DFFs except admin ones and static ones
(Examples: bacteria that live on Mars) <textarea rows="1" name="Real world application other" data-form-field="real_world_application_other" data-validation="[value=rwa_other]" placeholder="Please describe"></textarea></li>
+
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);
 +
}
  
</ul>
+
//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);
 +
}
 +
}
  
  
<!-------------------------------------------------------------------------------------------- Question 13  --------------------------------------------------------------------------------------------------------->
+
// =====================================================
 +
// 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();
 +
}
  
  
<p class="question">13. What safety or ethical risks would be involved with such a use? </p>
 
  
<textarea rows="3" name="13. what are the safety ethical risks" data-form-field="safety_ethical_risks"></textarea>
+
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>");
 +
}
  
  
<!-------------------------------------------------------------------------------------------- Question 14  --------------------------------------------------------------------------------------------------------->
 
  
  
<h3> Specific risks </h3>
+
// 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 + ' &nbsp;  jQuery UI v ' + ui_v + ' ]' );
 +
}
  
<p class="question">14.  Does your project include the use of gene drives? </p>
 
  
<div class="column half_size">
 
<ul>
 
<li><label><input type="radio"  data-form-field="gene drives yes" value="gene_drive_yes"> Yes </label> <br>
 
STOP: Gene Drives are not allowed in iGEM projects without a special exception from the Safety Committee. For more information see the Safety Policy page and the White List. Please contact the Safety Committee by emailing safety AT igem DOT org
 
</li>
 
  
<li><label><input type="radio"  data-form-field="gene drives no" value="gene_drive_no"> No </label></li>
 
  
</ul>
+
function aj_display_error(error_text){
</div>
+
if (!error_text) {
 +
return;
 +
}
 +
var div = $('#aj_form_errors');
 +
div.html("<div id='aj_form_error_inner_div'><img src='https://static.igem.org/mediawiki/2017/d/d6/Info_demo_icon-13.svg'> <br> <h5>Software error:</h5>" + error_text + "</div>");
 +
div.show();
 +
}
  
  
<div class="column half_size message_box">
 
<h4> Note </h4>
 
<p>For the purposes of iGEM, a gene drive includes Cas9 (and other endonucleases, such as dCas9 and Cpf1) integrated into the genome (including through the use of gRNA) of a sexually reproducing eukaryotic organisms (including organisms that reproduce both sexually and asexually, such as yeast) and/or the use of a drive to impact the progeny.
 
  
</p>
 
</div>
 
  
<div class="clear"></div>
+
function aj_clear_error(){
 +
//aj_display_error_text("");
 +
$("#aj_form_errors").text("").hide();
 +
}
  
<!-------------------------------------------------------------------------------------------- Question 15  --------------------------------------------------------------------------------------------------------->
 
  
<p class="question">15. Does your project include the use of animals? </p>
+
// =====================================================
 +
// 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);
 +
}
 +
}
  
<ul>
 
<li><label><input type="radio"  data-form-field="animal yes" value="animal_yes"> Yes </label> <br>
 
STOP: The use of animals is not allowed in iGEM projects without a special exception from the Safety Committee. For more information see the Safety Policy page and the White List. Please contact the Safety Committee by emailing safety AT igem DOT org
 
</li>
 
  
<li><label><input type="radio"  data-form-field="animal no" value="animal_no"> No </label></li>
 
  
</ul>
+
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");
 +
}
  
<!-------------------------------------------------------------------------------------------- Question 16  --------------------------------------------------------------------------------------------------------->
 
  
  
 +
// =====================================================
 +
// BLOCK
 +
// VALIDATION
 +
// =====================================================
  
<p class="question">16. Does your project include the use of anti-microbial resistance factors? For more information see the Safety Policy page and the White List. </p>
+
function validate_form_entries() {
<div class="column half_size">
+
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;
 +
}
  
<ul>
 
<li> <label>
 
<input type="radio"  data-form-field="antimicrobial resistance common" value="antimicrobial_resistance_yes_common"> Yes – the use of the resistance factor is common in research </label>
 
<textarea rows="1" name="Real world application other" data-form-field="antimicrobial_resistance_yes_common_a" data-validation="[value=antimicrobial_resistance_yes_common_a]" placeholder="Please provide a reference"></textarea>
 
If you cannot provide such a reference you will need to complete a check-in form for this part.
 
</li>
 
  
  
  
<li> <label>
 
<input type="radio"  data-form-field="antimicrobial resistance connected" value="antimicrobial_resistance_yes_connected"> Yes – the resistance factor is connected to an antimicrobial in clinical use </label>
 
<textarea rows="1" name="Real world application other" data-form-field="antimicrobial_resistance_yes_connected_a" data-validation="[value=antimicrobial_resistance_yes_connected_a]" placeholder="Please provide a reference"></textarea>
 
You will need to complete a check-in form for this part.
 
</li>
 
  
 +
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
 +
}
  
<li> <label>
 
<input type="radio"  data-form-field="antimicrobial resistance another" value="antimicrobial_resistance_yes_another"> Yes – it is another resistance factor </label>
 
<textarea rows="1" name="Real world application other" data-form-field="antimicrobial_resistance_yes_another_a" data-validation="[value=antimicrobial_resistance_yes_another_a]" placeholder="Please provide details"></textarea>
 
</li>
 
  
  
<li> <label>
 
<input type="radio"  data-form-field="antimicrobial resistance no" value="antimicrobial_resistance_no">No </label></li>
 
</ul>
 
  
 +
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;
 +
}
  
  
</div>
 
  
<div class="column half_size message_box">
+
// =====================================================
<h4> Note </h4>
+
// YELLOW-AND-GREEN FEEDBACK
<p> For the purposes of iGEM, anti-microbial resistance factors are genetic sequences, or any other factor, that can increase an organism's resistance to drugs or therapeutics used to control them. This includes genes known to confer resistance to antibiotics or antiviral drugs.
+
// =====================================================
</p>
+
</div>
+
  
 +
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";
  
<div class="clear"></div>
+
function feedback(element, command) {
 +
element = (element instanceof jQuery) ? element : $(element);
 +
  
<!-------------------------------------------------------------------------------------------- Question 17  --------------------------------------------------------------------------------------------------------->
+
var wrapper = element.parent(".wrapper");
 +
 +
if(command == "none") {
 +
wrapper.css("background-color", wrapper_none_color);
 +
}
 +
else if(command == "sending") {
 +
wrapper.children(".dff-warning").remove();
 +
wrapper.css("background-color", wrapper_sending_color);
 +
}
 +
else if(command == "sent") {
 +
wrapper.children(".dff-warning").remove();
 +
fade_sent(wrapper);
 +
}
 +
else if(command == "invalid") {
 +
wrapper.css("background-color", wrapper_invalid_color);
 +
}
 +
else if(command == "readonly") {
 +
wrapper.css("background-color", wrapper_readonly_color);
 +
}
 +
else {
 +
throw new FormError("Invalid command " + command + " for feedback on element #" + element.attr("id"));
 +
}
 +
}
  
<h3>Parts and organisms not on the Whitelist </h3>
+
//Fades a wrapper from fresh green thru pale green back to white.
 +
//**We should really be using the jQuery Color() plugin, or jQuery UI, for this.
 +
//(because native jQuery cannot animate a non-numeric CSS property like background-color)
 +
//ORRRRR just fade the OPACITY
 +
function fade_sent(wrapper) {
 +
var interval = 100;
 +
wrapper.css("background-color", wrapper_sent4);
 +
setTimeout(function() {
 +
wrapper.css("background-color", wrapper_sent3);
 +
setTimeout(function() {
 +
wrapper.css("background-color", wrapper_sent2);
 +
setTimeout(function() {
 +
wrapper.css("background-color", wrapper_sent1);
 +
setTimeout(function() {
 +
wrapper.css("background-color", wrapper_none_color);
 +
}, interval);
 +
}, interval);
 +
}, interval);
 +
}, 3 * interval);
 +
}
  
<p class="question">17. Does your project include parts or organisms not on the Whitelist?</p>
+
function warn(element, warning_text) {
 +
if (!warning_text || typeof warning_text != "string") {
 +
unwarn(element);
 +
}
 +
element = (element instanceof jQuery) ? element : $(element);
 +
var wrapper = element.parent(".wrapper");
 +
if (wrapper.length == 0) {
 +
return;
 +
}
 +
 +
//First, remove any warnings that exist already
 +
wrapper.children(".dff-warning").remove();
  
 
+
if (wrapper.is("span") && element.attr("type") != "text") { //everything except textarea, select, and input type="text"
 
+
wrapper.prepend($("<span class='dff-warning'>" + warning_text + "</span>"));
 
+
}
<!-------------------------------------------------------------------------------------------- Question 18  --------------------------------------------------------------------------------------------------------->
+
else if (element.attr("type") == "text") {
 
+
wrapper.append($("<span class='dff-warning'>" + warning_text + "</span>"));
<p class="question">18. What is your chassis organism?</p>
+
}
<p>Check all species you are genetically modifying in your project.</p>
+
else { //textarea, select
 
+
wrapper.prepend($("<p class='dff-warning'>" + warning_text + "</p>"));
<ul>
+
}
<li><label><input type="checkbox" name="Chassis organism" data-form-field="chassis_ecoli"> E. coli (give names of all strains you are using):</label> <textarea rows="2" name="E. coli strain" data-form-field="chassis_ecoli_describe" data-validation="[data-form-field=chassis_ecoli]"></textarea></li>
+
<li><label><input type="checkbox" name="Chassis organism" data-form-field="chassis_yeast"> Yeast (Saccharomyces)</label></li>
+
feedback(element, "invalid");
<li><label><input type="checkbox" name="Chassis organism" data-form-field="chassis_lactobacillus"> Lactobacillus</label></li>
+
<li><label><input type="checkbox" name="Chassis organism" data-form-field="chassis_bsubtilis"> B. subtilis</label></li>
+
<li><label><input type="checkbox" name="Chassis organism" data-form-field="chassis_other"> Others (give species names): </label> <textarea rows="2" name="Other chassis (give species names)" data-form-field="chassis_other_describe" data-validation="[data-form-field=chassis_other]"></textarea></li>
+
<li><label><input type="checkbox" name="Chassis organism" data-form-field="chassis_none"> No chassis organism (please comment):</label> <textarea rows="2" name="No chassis (comment)" data-form-field="chassis_none_describe" data-validation="[data-form-field=chassis_none]"></textarea></li>
+
</ul>
+
 
+
<p class="question">Comments:</p>
+
<textarea rows="2" name="Chassis organism -- comments" data-form-field="chassis_comments"></textarea>
+
 
+
 
+
 
+
<!-------------------------------------------------------------------------------------------- Question 19  --------------------------------------------------------------------------------------------------------->
+
 
+
 
+
<p class="question">19. Besides your chassis, do you plan to use any other organisms (or parts from them)?
+
</p>
+
 
+
<textarea rows="4" name="Other organisms" data-form-field="other_organisms"></textarea>
+
 
+
 
+
 
+
<!-------------------------------------------------------------------------------------------- Question 20  --------------------------------------------------------------------------------------------------------->
+
 
+
 
+
 
+
<p class="question">20. Are there parts of your project which you think may have ethical, safety or security concerns that are not fully covered by current rules and standards?</p>
+
 
+
<textarea rows="4" name="any concernss" data-form-field="any_concerns" placeholder="Please describe"></textarea>
+
 
+
 
+
<!-------------------------------------------------------------------------------------------- Question 20  --------------------------------------------------------------------------------------------------------->
+
 
+
 
+
 
+
<p class="question">21. Who have you worked with to resolve any uncertainties or gaps in how you ensure the safety of your project and how difficult have they been to contact?</p>
+
 
+
<textarea rows="4" name="any concernss who did you work with" data-form-field="any_uncertainties_who" placeholder="Please comment"></textarea>
+
 
+
</fieldset>
+
 
+
 
+
 
+
 
+
 
+
 
+
<!-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
 
+
NEW PARTS
+
 
+
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------->
+
 
+
<fieldset>
+
<h2> 5.- New Parts </h2>
+
<p> This part of the of the form is for you to tell us about the parts you have developed during your project. It summarises information that might already have been submitted through check-in forms.
+
Please visit this page to download a blank copy of the spreadsheet for this question. (If you need a CSV version instead of XLSX, visit this page.)
+
Complete the spreadsheet. Include all new or highly modified protein coding parts that you are using. If you submitted a Check-In for an organism or part, you should still include it in this spreadsheet.
+
You may omit non-protein-coding parts (except if they are known virulence factors – you should undertake a literature search to determine if they are), and you may omit parts that were already in the Registry if you are using them without significant modifications. For more information on virulence factors see the Safety Policy page and the White List. Please contact the Safety Committee by emailing safety AT igem DOT org
+
</p>
+
 
+
 
+
 
+
<DIV class="highlightBoxB">
+
    <a href="#" class="click_expand">Click here to show/hide instructions for completing the spreadsheet</a>
+
    <div>
+
        <p><strong>Remember to change the filename of your spreadsheet! Put your team's name in place of "TeamName".</strong></p>
+
        <ol style="list-style-type:upper-alpha">
+
            <li><strong>Species name (including strain):</strong> For an organism, give the scientific name of the species. Include a strain name or number (such as "K-12" for E. coli K-12) if there is one. For a part, give the name and strain of the organism that the part originally came from.</li>
+
            <li><strong>Risk Group:</strong> Give the Risk Group of the organism in column A. You may use a categorization according to your home country, according to the USA, or according to the WHO. If the organism falls into an 'in-between' or special category such as 2+ or 2-Agricultural, explain this category in the Notes column. If you cannot find any Risk Group categorization for this organism, write "N/A" and explain in the Notes column. (Multicellular organisms generally do not have a Risk Group.)</li>
+
            <li><strong>Risk Group Source:</strong> Cite the source from which you obtained the Risk Group information. See <a target="_blank" href="https://2017.igem.org/Safety/Risk_Groups#HowToFindRiskGroup">Risk Group Guide</a> for recommended sources. If you got the information from the Canadian PSDS, from the NIH Guidelines, or from the DSMZ catalogue, you may simply write "PSDS", "NIH", or "DSMZ". Otherwise, please give a web link or a full citation for your source.</li>
+
            <li><strong>Disease risk to humans?:</strong> Does this organism cause any disease in humans? If yes, what disease does it cause?</li>
+
            <li><strong>Part number/name:</strong> For a part: If it has a Registry part number (like BBa_XXXXX), write that number. If it has no Registry part number, give a short name for the part. (For example: "Actin", "Alcohol Dehydrogenase".) For a whole organism, leave this column blank.</li>
+
            <li><strong>Natural function of part:</strong> For a part: Briefly describe what the part does in its parent organism. (If it is an enzyme, what reaction does it catalyze? If it is a receptor, what molecules does it bind to? Etc.) For a whole organism, leave this column blank.</li>
+
            <li><strong>How did you acquire it?:</strong> Describe how you acquired the organism/part. If you have not acquired it yet, describe how you plan to acquire it. (For example: did you receive the part DNA from another lab? Did you order the part DNA from a synthesis company? Did you use PCR to isolate the part from genomic DNA of its parent organism? Did you order the cell line from a company?)</li>
+
            <li><strong>How will you use it?:</strong> Describe how you are using the organism/part in the lab. (For example: "This organism is our chassis." "This part senses when the cells are exposed to glucose." "This organism is the source for a part that we are isolating by PCR." "This part produces the toxin which our bio-sensor is designed to detect.")</li>
+
            <li><strong>Notes:</strong> Use this column to give any additional information that is necessary.</li>
+
        </ol>
+
    </div>
+
</DIV>
+
<br />
+
 
+
<p><strong><a href="" target="_blank" id="spreadsheet_upload"><button type="button">Upload Spreadsheet</button></a> -- Please do not change the "Destination Filename"!</strong> [File:<em>TeamName</em> Safety2017 Spreadsheet.xls]</p>
+
<p>You may upload multiple versions of your spreadsheet, using the same Destination Filename. The wiki software will keep track of different versions, and list them in chronological order.</p>
+
<p><strong><a href="" target="_blank" id="spreadsheet_view">Click here to VIEW your spreadsheet</a></strong></p>
+
<br />
+
</fieldset>
+
 
+
 
+
 
+
 
+
 
+
 
+
 
+
 
+
 
+
<!-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
 
+
SUBMIT
+
 
+
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------->
+
 
+
 
+
 
+
<fieldset>
+
<h2>6.- Sign Off</h2>
+
<p>Only a team Instructor or PI may submit the Safety Form.</p>
+
<p>Instructors/PIs, please read the form you are submitting, and confirm that all its information is correct. By checking the "I Agree" box and clicking the "Submit" button, you are agreeing that the Final Safety Form accurately describes the activities of your team. We are using the "I Agree" box in lieu of a signature with paper and pen.</p>
+
<input class="confirmation" autocomplete="off" type="checkbox" name="I Agree" id="i_agree">
+
<label for="i_agree" class="question">I Agree</label>
+
 
+
<div class="clear extra_space"></div>
+
 
+
<div class="column half_size">
+
<p class="question">Ready to submit Form?</p>
+
<input type="submit" data-form-field="submit" data-confirmation="i_agree" value="Submit Form">
+
</div>
+
 
+
<div class="column half_size">
+
 
+
<p class="question">Need to make changes? </p>
+
<input type="submit" data-form-field="return_form" value="Unsubmit Form">
+
</div>
+
 
+
</fieldset>
+
 
+
</html>{{HTML/Temp/FormsAdmin}}<html>
+
 
+
</div>
+
 
+
</div> <!-- end of formbody -->
+
 
+
 
+
 
+
 
+
 
+
<script type="text/javascript">
+
function custom_get_owner_info_response(data, textStatus, jqxhr) {
+
console.log("custom get owner info response called");
+
console.log(jqxhr.responseJSON.track);
+
spreadsheet_access();
+
}
+
var lastreq;
+
function custom_one_input_changed_response(input_element, command, effective_date, data, textStatus, jqxhr) {
+
console.log("OIC custom callback");
+
console.log(jqxhr.responseJSON.return_error);
+
lastreq = jqxhr;
+
 
}
 
}
  
//**Call this function in one of the custom-when-team-info-gotten callbacks
+
function unwarn(element) {
function spreadsheet_access() {
+
element = (element instanceof jQuery) ? element : $(element);
if(typeof owner_info !== "undefined") {
+
var wrapper = element.parent(".wrapper");
//console.log("Setting spreadsheet links for team " + owner_info.team_name);
+
if (wrapper.length == 0) {
var url_start = "https://2017.igem.org/wiki/index.php?title=Special:Upload\u0026wpDestFile="
+
return;
var url_end = "_Safety2017_Spreadsheet.xls";
+
var whole_url = url_start + owner_info.team_name + url_end;
+
jQuery("#spreadsheet_upload").attr("href", whole_url);
+
+
jQuery("#spreadsheet_view").attr("href", "https://2017.igem.org/File:" + owner_info.team_name + "_Safety2017_Spreadsheet.xls");
+
}
+
else {
+
//console.log("Blanking spreadsheet links");
+
jQuery("#spreadsheet_upload").attr("href", "");
+
jQuery("#spreadsheet_view").attr("href", "");
+
 
}
 
}
 +
wrapper.children(".dff-warning").remove();
 +
feedback(element, "none");
 
}
 
}
</script>
 

Revision as of 03:46, 30 March 2017

/*


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


//VARIABLES

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"];

//this is where the valid user/viewer categories are defined var valid_permission_groups = ["author", "group_members", "super_users", "any_users", "public"]; var valid_roles = ["Primary Contact", "Primary PI", "Secondary PI", "Student Leader", "Student", "Instructor", "Advisor", "Other", "None"]; 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>");

$("textarea[data-form-field]").wrap("
<\/DIV>");

$("p").find(".wrapper").has("[type=radio], [type=checkbox]").css("margin-left", "2em");

//force create AJ log and error divs

//$("#formbody").before("
").after("
");

//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: "https://igem.org/aj/team_list.cgi", type: "GET", timeout: 30000, dataType: "json", data: {command: "get_team_list", year: form_info.year}, 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);

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



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




//**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 = "https://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); }


// // =========================================================================================================================================== // DETERMINE PERMISSIONS

//ana

//Check the view/edit/submit/admin permissions of the current user on the current form. //Puts them in global variable permissions. function determine_permissions() {

var perm_group; $.each(form_info.permissions, function(perm_name, groups_having_this_perm) { console.log("Permission to " + perm_name); perm_group = groups_having_this_perm[index];

for (index in groups_having_this_perm) { switch(perm_group){

case "public": { return true; break; }

case "any_users": { permissions.view = true; return user_info.user_name != ""; break; }

case "super_users": { permissions.admin = true; permissions.submit = true; permissions.edit = true; permissions.view = true;

return user_info.is_super == "true"; break; }

case "group_members": {

permissions.edit = true; permissions.view = true; return (typeof owner_info.role == "string" && owner_info.role != "None"); break; }

case "author": {

permissions.submit = true; permissions.edit = true; permissions.view = true; return user_info.user_name.toLowerCase() == author.toLowerCase(); 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") {

permissions.view = true; return user_info.user_name != ""; }

else if (perm_group == "super_users") {

permissions.admin = true; permissions.submit = true; permissions.edit = true; permissions.view = true;

return user_info.is_super == "true";

}


else if (perm_group == "group_members") {

permissions.edit = true; permissions.view = true; return (typeof owner_info.role == "string" && owner_info.role != "None"); }

else if (perm_group == "author") {

permissions.submit = true; permissions.edit = true; permissions.view = true; return user_info.user_name.toLowerCase() == author.toLowerCase(); }


else if (valid_roles.indexOf(perm_group) > -1) { return owner_info.role == perm_group; } }


//when the user doesn't have permission to view/ edit the form, display the appropiate message function deny_permission() {

// alert message to give to user var alert_string = "Sorry, you don't have permission to " + mode + " that form.";

//because I got rid of the no kick checkbox nokick = false;

//if the user is not a super user, hide admin view if (!user_info.is_super) { $(".admins_only").hide(); }


//**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"); } }


// // =========================================================================================================================================== //








// ===================================================== // 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>" ); 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 + "

" + 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("

<img src='https://static.igem.org/mediawiki/2017/d/d6/Info_demo_icon-13.svg'>
Software error:
" + error_text + "
");

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


// ===================================================== // 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);


var wrapper = element.parent(".wrapper");

if(command == "none") { wrapper.css("background-color", wrapper_none_color); } else if(command == "sending") { wrapper.children(".dff-warning").remove(); wrapper.css("background-color", wrapper_sending_color); } else if(command == "sent") { wrapper.children(".dff-warning").remove(); fade_sent(wrapper); } else if(command == "invalid") { wrapper.css("background-color", wrapper_invalid_color); } else if(command == "readonly") { wrapper.css("background-color", wrapper_readonly_color); } else { throw new FormError("Invalid command " + command + " for feedback on element #" + element.attr("id")); } }

//Fades a wrapper from fresh green thru pale green back to white. //**We should really be using the jQuery Color() plugin, or jQuery UI, for this. //(because native jQuery cannot animate a non-numeric CSS property like background-color) //ORRRRR just fade the OPACITY function fade_sent(wrapper) { var interval = 100; wrapper.css("background-color", wrapper_sent4); setTimeout(function() { wrapper.css("background-color", wrapper_sent3); setTimeout(function() { wrapper.css("background-color", wrapper_sent2); setTimeout(function() { wrapper.css("background-color", wrapper_sent1); setTimeout(function() { wrapper.css("background-color", wrapper_none_color); }, interval); }, interval); }, interval); }, 3 * interval); }

function warn(element, warning_text) { if (!warning_text || typeof warning_text != "string") { unwarn(element); } element = (element instanceof jQuery) ? element : $(element); var wrapper = element.parent(".wrapper"); if (wrapper.length == 0) { return; }

//First, remove any warnings that exist already wrapper.children(".dff-warning").remove();

if (wrapper.is("span") && element.attr("type") != "text") { //everything except textarea, select, and input type="text" wrapper.prepend($("" + warning_text + "")); } else if (element.attr("type") == "text") { wrapper.append($("" + warning_text + "")); } else { //textarea, select

wrapper.prepend($("<p class='dff-warning'>" + warning_text + "

"));

}

feedback(element, "invalid"); }

function unwarn(element) { element = (element instanceof jQuery) ? element : $(element); var wrapper = element.parent(".wrapper"); if (wrapper.length == 0) { return; } wrapper.children(".dff-warning").remove(); feedback(element, "none");

}