Line 1: | Line 1: | ||
− | + | /* | |
− | + | *This is the code for the Forms | |
− | + | *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 | |
+ | */ | ||
− | |||
+ | 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 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); | ||
+ | } | ||
− | + | $(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"); | ||
+ | } | ||
+ | } | ||
+ | // | ||
+ | // =========================================================================================================================================== | ||
+ | // | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
Line 317: | Line 1,275: | ||
− | |||
− | |||
− | + | // ===================================================== | |
+ | // BLOCK | ||
+ | // AJAX HELPERS | ||
+ | // ===================================================== | ||
− | + | //General helper for ajax errors. | |
− | + | //In every case, it will display an alert and an aj_display_error. | |
− | + | //Not unifying these because form writers shouldn't change them. | |
− | + | function general_ajax_error(jqxhr, textStatus, errorThrown, custom_msg) { | |
− | + | //case: timeout error | |
+ | if (textStatus == "timeout") { | ||
+ | alert("Timeout error"); | ||
+ | aj_display_error("Timeout error: there could be a problem with your internet connection, or the server could be under a heavy load. Please try again later."); | ||
+ | } | ||
+ | //case: internet probably unplugged or something | ||
+ | else if (jqxhr.status == 0) { | ||
+ | alert("Unable to connect to server. Please check your Internet connection and try again."); | ||
+ | aj_display_error("Unable to connect to server. Please check your Internet connection and try again."); | ||
+ | } | ||
+ | //case: 4xx/5xx error | ||
+ | else if (400 <= jqxhr.status <= 599) { | ||
+ | alert("Server error [" + jqxhr.status + " " + jqxhr.statusText + "]"); | ||
+ | aj_display_error("Server error [" + jqxhr.status + " " + jqxhr.statusText + "]. Please report this error to iGEM HQ. In your message, please include the URL of this page, and the date/time of the error."); | ||
+ | } | ||
+ | |||
+ | //other error | ||
+ | else { | ||
+ | alert("Error in ajax\n[" + textStatus + "]"); | ||
+ | custom_msg = (typeof custom_msg == "undefined") ? "general AJAX error" : custom_msg; | ||
+ | aj_display_error(custom_msg); | ||
+ | } | ||
+ | } | ||
+ | //All our ajax requests will receive both a responseJSON and a responseText. | ||
+ | //Here, we attempt parsing the responseText. If there is an error in between | ||
+ | //"ajax request fail" and "you gave wrong parameters to form.cgi", the responseText | ||
+ | //will not be parseable as JSON. | ||
+ | function try_parse_received_info(responseText) { | ||
+ | try { | ||
+ | JSON.parse(responseText); | ||
+ | return true; | ||
+ | } | ||
+ | catch (err) { | ||
+ | aj_log("JSON.parse error: " + err); | ||
+ | aj_display_error(unparseable_ajax_response); | ||
+ | return false; | ||
+ | } | ||
+ | } | ||
− | + | // ===================================================== | |
+ | // BLOCK | ||
+ | // SUBFORM STUFF | ||
+ | // ===================================================== | ||
+ | //Accepts a string and makes a new choice in the subform dropdown. | ||
+ | //Boolean gotoit (default false) causes that choice to then be selected immediately. | ||
+ | function make_new_sub_form(name, submitted, gotoit) { | ||
+ | gotoit = (typeof gotoit == "undefined") ? false : gotoit; | ||
+ | |||
+ | var option = $("<option>").text(name).attr("value", name); | ||
+ | option.appendTo( submitted ? $("#sub_forms_submitted") : $("#sub_forms_unsubmitted") ); | ||
+ | if (gotoit) { | ||
+ | option.prop("selected", true);//.trigger("change"); | ||
+ | } | ||
+ | } | ||
− | |||
− | + | //Function and wrappers for moving subforms around. | |
− | + | // - option should be a jQuery object containing an option, or a string equaling the value of an option | |
+ | // - move_to should be "submitted" or "unsubmitted" | ||
+ | function move_sub_form(option, move_to) { | ||
+ | if (typeof option == "string") { | ||
+ | option = $("[data-form-field=sub_form]").find("[value='" + option + "']"); | ||
+ | } | ||
+ | option.detach().appendTo($("#sub_forms_" + move_to)); | ||
+ | } | ||
+ | function mark_current_sub_form_as_submitted() { | ||
+ | move_sub_form($("[data-form-field=sub_form]").find(":selected"), "submitted"); | ||
+ | } | ||
+ | function mark_current_sub_form_as_unsubmitted() { | ||
+ | move_sub_form($("[data-form-field=sub_form]").find(":selected"), "unsubmitted"); | ||
+ | } | ||
− | + | function empty_sub_form_list() { | |
+ | $("[data-form-field=sub_form]").children("optgroup").empty(); | ||
+ | } | ||
− | + | // ===================================================== | |
+ | // BLOCK | ||
+ | // MISC UTILITY FUNCTIONS | ||
+ | // ===================================================== | ||
+ | //Fake "assert" function for assertion testing. | ||
+ | //It is best practice to strip out assertions before code goes to real users. | ||
+ | function assert(condition, message) { | ||
+ | if (!condition) { | ||
+ | message = message || "Assertion failed"; | ||
+ | if (typeof FormError !== "undefined") { | ||
+ | throw new FormError(message); | ||
+ | } | ||
+ | throw new Error(message); | ||
+ | } | ||
+ | } | ||
− | + | function clear_form_but_keep_sub_form() { | |
+ | $('[data-form-field][type!=checkbox][type!=radio][type!=submit][data-form-field!=sub_form]').val(''); | ||
+ | $('[data-form-field][type=checkbox]').prop('checked', false); | ||
+ | $("[data-form-field][type=radio]").prop('checked', false); | ||
+ | feedback($("[data-form-field]"), "none"); | ||
+ | } | ||
+ | function clear_form() { | ||
+ | $("[data-form-field][type!=checkbox][type!=radio][type!=submit][data-form-field!='sub_form']").val(''); | ||
+ | $('[data-form-field][type=checkbox]').prop('checked', false); | ||
+ | $("[data-form-field][type=radio]").prop('checked', false); | ||
+ | $("[data-form-field='sub_form']").val(""); | ||
+ | feedback($("[data-form-field]"), "none"); | ||
+ | } | ||
− | + | //Upon first page load, or upon changing owner, we must clear the form to keep the browser from | |
+ | //autofilling fields, and to keep the last owner's answers from leaving droppings in the new form | ||
+ | //also we readonly all DFFs -- they will be made un-readonly upon successful entry into | ||
+ | //Edit or Admin mode | ||
+ | function refresh_form(also_clear_answers) { | ||
+ | also_clear_answers = (typeof also_clear_answers == "undefined") ? true : false; | ||
+ | if (also_clear_answers) { | ||
+ | empty_sub_form_list(); | ||
+ | clear_form(); | ||
+ | form_displayed = false; | ||
+ | } | ||
+ | readonly_all_DFFs(); | ||
+ | aj_clear_error(); | ||
+ | } | ||
− | |||
− | |||
− | + | //function simply unchecks all confirmation boxes (radio or checkbox) with | |
− | + | //class="confirmation" | |
− | + | function uncheck_all_confirmations() { | |
+ | $(".confirmation").prop("checked", false); | ||
+ | } | ||
− | + | function submission_not_confirmed() { | |
− | ( | + | if (typeof custom_submission_not_confirmed == "function") { |
+ | custom_submission_not_confirmed(); | ||
+ | } | ||
+ | else { | ||
+ | alert(messages.please_confirm_submission); | ||
+ | } | ||
+ | } | ||
− | + | //gets all sets of radio buttons that have a data-form-field attribute | |
− | + | //returns them as an array of "name"s | |
− | + | function get_all_sets_of_radios() { | |
+ | var names_list = []; | ||
+ | |||
+ | $("[data-form-field][type='radio']").each(function(index) { | ||
+ | //get the name | ||
+ | var this_radio_name = $(this).attr("name"); | ||
+ | |||
+ | //check if the name is already in the list. if not, add it | ||
+ | if(names_list.indexOf(this_radio_name) == -1) { | ||
+ | names_list.push(this_radio_name); | ||
+ | } | ||
+ | //else skip it | ||
+ | }); | ||
+ | return names_list; | ||
+ | } | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | // ===================================================== | |
− | + | // BLOCK | |
− | + | // READONLY/DISABLE FUNCTIONS | |
+ | // ===================================================== | ||
− | + | //Simply disables everything with a data-form-field | |
− | ( | + | function disable_all_DFFs() { |
+ | $('[data-form-field]').prop('disabled', true); | ||
+ | } | ||
− | + | //Function makes all DFF elements either readonly (for text boxes) or disabled (everything else). | |
− | ( | + | //It does not change any of their event bindings. |
+ | function readonly_all_DFFs() { | ||
+ | $('[data-form-field]').each(function() { | ||
+ | var field = $(this); | ||
+ | if (field.is("textarea") || field.is("input[type='text']")) { | ||
+ | field.prop('readonly', true); | ||
+ | } | ||
+ | else { | ||
+ | field.prop('disabled', true); | ||
+ | } | ||
+ | }); | ||
+ | } | ||
− | + | //undisable all DFFs except admin ones and static ones | |
− | ( | + | function undisable_all_DFFs() { |
+ | $(non_admin_DFFs).not(".static").prop('disabled', false).prop('readonly', false); | ||
+ | } | ||
+ | //undisable admin DFFs, except static ones | ||
+ | function undisable_admin_DFFs() { | ||
+ | $('[data-form-field^="admin"]').not(".static").prop("disabled", false).prop("readonly", false); | ||
+ | } | ||
− | + | //switch one field to "off" or "on" (tested, works) | |
+ | function dffswitch(field, offon) { | ||
+ | field = (field instanceof jQuery) ? field : $(field); | ||
+ | if (offon == "off") { | ||
+ | if (field.is("textarea") || field.is("input[type='text']")) { | ||
+ | field.prop('readonly', true); | ||
+ | } | ||
+ | else { | ||
+ | field.prop('disabled', true); | ||
+ | } | ||
+ | } | ||
+ | else if (offon == "on") { | ||
+ | field.prop("disabled", false).attr("readonly", false); | ||
+ | } | ||
+ | else { | ||
+ | throw new FormError("I can't switch field " + field.attr("data-form-field") + " to state " + offon); | ||
+ | } | ||
+ | } | ||
− | + | // ===================================================== | |
+ | // BLOCK | ||
+ | // LOG ENTRIES AND ERROR MESSAGES | ||
+ | // ===================================================== | ||
+ | // If you wish to Display a Log of Transactions add a div with id= 'aj_form_log' | ||
+ | function aj_display_log_div() { | ||
+ | var div = $('#aj_form_log'); | ||
+ | div.addClass("admins_only"); | ||
+ | div.html("<DIV id='aj_form_log_inner_div' " | ||
+ | + "style='display: none; width: 500px;min-height:50px;padding: 3px 10px;margin:10px; " | ||
+ | + "border: 1px solid gray;background-color:white'>" | ||
+ | + "<\/DIV>" ); | ||
+ | jq_versions(); | ||
+ | aj_display_log_buttons(); | ||
+ | } | ||
− | |||
− | + | function aj_display_log_buttons() { | |
+ | var div = $('#aj_form_log_inner_div'); | ||
+ | div.before("<button type='button' id='clear_log'>Clear Log<\/button>"); | ||
+ | div.before("<button type='button' id='toggle_log'>Toggle Log<\/button>"); | ||
+ | $("#clear_log").on("click", aj_display_log_div); | ||
+ | $("#toggle_log").on("click", function(event) { | ||
+ | $("#aj_form_log_inner_div").toggle(); | ||
+ | }); | ||
+ | } | ||
+ | function aj_log(text) { | ||
+ | var div = $('#aj_form_log_inner_div'); | ||
+ | if (div.length == 0) { | ||
+ | return; | ||
+ | } | ||
+ | var old_text = div.html(); | ||
+ | div.html(old_text + "<P>" + text + "<\/P>"); | ||
+ | } | ||
− | |||
− | + | // Put the jQuery versions in the log box | |
+ | function jq_versions() { | ||
+ | var jq_v='None'; | ||
+ | try { | ||
+ | jq_v = jQuery.fn.jquery || ''; | ||
+ | } | ||
+ | catch (err) {} | ||
+ | var ui_v = 'None'; | ||
+ | try { | ||
+ | ui_v= jQuery.ui ? jQuery.ui.version || 'pre 1.6' : ''; | ||
+ | } | ||
+ | catch (err) {} | ||
+ | aj_log( 'AJ Form Log: [ jQuery v '+ jq_v + ' jQuery UI v ' + ui_v + ' ]' ); | ||
+ | } | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | </ | + | function aj_display_error(error_text){ |
− | </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(); | ||
+ | } | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | 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($("<span class='dff-warning'>" + warning_text + "</span>")); | |
− | + | } | |
− | + | else if (element.attr("type") == "text") { | |
− | + | wrapper.append($("<span class='dff-warning'>" + warning_text + "</span>")); | |
− | + | } | |
− | + | else { //textarea, select | |
− | + | wrapper.prepend($("<p class='dff-warning'>" + warning_text + "</p>")); | |
− | + | } | |
− | + | ||
− | + | 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"); | ||
} | } | ||
− |
Revision as of 03:46, 30 March 2017
/*
- This is the code for the Forms
- 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
- /
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("$("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("
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");
}