sanger/sequencescape

View on GitHub
app/frontend/entrypoints/submissions.js

Summary

Maintainability
B
5 hrs
Test Coverage
// Submission workflow jQuery Plugin...
(function (window, $) {
  "use strict";

  var methods = {
    init: function (_options) {
      return this;
    },

    // Returns true if the input fields in a pane have a value
    allFieldsComplete: function (_pane) {
      // This is not very pretty but it is IE safe...
      var validationResult = true;
      this.find("input.required").each(function (_element) {
        if (!$(this).val().match(/^\d+$/)) {
          validationResult = false;
        }
      });

      return validationResult;
    },

    hasAssets: function () {
      if (
        this.find(".submission_asset_group_id").val() ||
        this.find(".submission_sample_names_text").val() ||
        this.find(".submission_barcodes_wells_text").val()
      ) {
        return true;
      } else {
        return false;
      }
    },

    currentPane: function () {
      return this.closest(".pane");
    },

    loadStudyAssets: function (submission) {
      var element = this;

      $.get("/submissions/study_assets", { submission: submission }, function (data) {
        element.find(".study-assets").fadeOut(function () {
          $(this).html(data).fadeIn();
        });
      });

      return this;
    },

    markPaneIncomplete: function () {
      this.addClass("section-in-progress").removeClass("section-complete section-error");

      // Move this to an initialised callback
      $("#add-order").attr("disabled", true);

      return this;
    },

    markPaneInvalid: function () {
      return this.addClass("section-error").removeClass("section-in-progress section-complete");
    },

    markPaneComplete: function () {
      this.addClass("section-complete").removeClass("section-in-progress section-error").find("input, select");

      // Move this to an initialised callback
      this.find(".save-order, .cancel-order").hide();

      // Move this to an initialised callback
      $("#add-order").removeAttr("disabled");

      return this;
    },
  };

  $.fn.submission = function (method) {
    if (methods[method]) {
      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    } else if (typeof method === "object" || !method) {
      return methods.init.apply(this, arguments);
    } else {
      return $.error("Method " + method + " does not exist on jQuery.submission");
    }
  };
})(window, window.jQuery);

// Submission page code...
(function (window, $) {
  "use strict";
  // Name spacing stuff...
  if (window.SCAPE === undefined) window.SCAPE = {};

  if (window.SCAPE.submission === undefined) {
    window.SCAPE.submission = {
      order_params: {},
    };
  }

  var templateChangeHandler = function (event) {
    var currentPane = $(event.currentTarget).submission("currentPane");

    delete window.SCAPE.submission.template_id;

    $("#order-parameters").slideUp(100, function () {
      $(this).html("");
      currentPane.submission("markPaneIncomplete");

      if ($(event.currentTarget).val()) {
        window.SCAPE.submission.template_id = $(event.currentTarget).val();

        // Load the parameters for the new order
        $.get("/submissions/order_fields", { submission: window.SCAPE.submission }, function (data) {
          $("#order-parameters").html(data);

          currentPane.submission("allFieldsComplete")
            ? currentPane.submission("markPaneComplete")
            : currentPane.submission("markPaneIncomplete");

          $("#order-parameters").show(100);
        });
        return true;
      }
    });
  };

  var studySelectHandler = function (event) {
    // This handler depends on the study template being set earlier in the wizard.
    var currentPane = $(event.target).submission("currentPane");

    currentPane.submission("markPaneIncomplete");

    window.SCAPE.submission.study_id = $(event.target).val();

    if ($(event.target).val().length > 0) {
      // Load asset groups for the selected study
      currentPane.submission("loadStudyAssets", window.SCAPE.submission);
    } else {
      // The study selector has been reset so fade out and reset the field.
      currentPane.find(".study-assets").fadeOut(function () {
        $(this).html("");
      });
    }
  };

  var saveOrderHandler = function (_event) {
    var currentPane = $(this).submission("currentPane");
    // refactor this little lot!
    window.SCAPE.submission.project_name = currentPane.find(".submission_project_name").val();
    window.SCAPE.submission.asset_group_id = currentPane.find(".submission_asset_group_id").val();
    window.SCAPE.submission.sample_names_text = currentPane.find(".submission_sample_names_text").val();
    window.SCAPE.submission.barcodes_wells_text = currentPane.find(".submission_barcodes_wells_text").val();
    window.SCAPE.submission.plate_purpose_id = currentPane.find(".submission_plate_purpose_id").val();
    window.SCAPE.submission.comments = currentPane.find(".submission_comments").val();
    window.SCAPE.submission.lanes_of_sequencing_required = currentPane.find(".lanes_of_sequencing").val();
    window.SCAPE.submission.order_params.gigabases_expected = currentPane.find(".gigabases_expected").val();
    window.SCAPE.submission.order_params.pre_capture_plex_level = currentPane.find(".pre_capture_plex_level").val();
    window.SCAPE.submission.pre_capture_plex_group = currentPane.find(".pre_capture_plex_group").val();

    window.SCAPE.submission.priority = $("#submission_priority").val();

    $.post("/submissions", { submission: window.SCAPE.submission }, function (data) {
      currentPane.fadeOut(function () {
        currentPane.detach().html(data).submission("markPaneComplete").removeClass("order-active invalid");

        $("#order-controls").before(currentPane);
        currentPane.fadeIn();

        $("#build-form").attr("action", "/submissions/" + window.SCAPE.submission.id);
        $("#start-submission").removeAttr("disabled");

        $(".pane").not("#blank-order").addClass("order-active");
      });
    }).fail(function (response) {
      currentPane.find(".project-details").html(response.responseText);
      currentPane.submission("markPaneInvalid");
    });

    // don't forget to stop the form submitting...
    return false;
  };

  var validateOrderParams = function (event) {
    var currentPane = $(event.target).submission("currentPane");

    return currentPane.submission("allFieldsComplete")
      ? currentPane.submission("markPaneComplete")
      : currentPane.submission("markPaneIncomplete");
  };

  // Validate that an order has a Project, Study and some Assets.
  var validateOrder = function (event) {
    var currentPane = $(event.target).submission("currentPane");

    var studyId = currentPane.find(".study_id").val() || currentPane.find(".cross_study").attr("checked");

    var projectName =
      currentPane.find(".submission_project_name").val() || currentPane.find(".cross_project").attr("checked");
    var hasAssets = currentPane.submission("hasAssets");

    if (studyId && projectName && hasAssets) {
      currentPane.find(".save-order").removeAttr("disabled");
    } else {
      currentPane.find(".save-order").attr("disabled", true);
    }
  };

  var getParamName = function (param) {
    return $(param).attr("id").replace("submission_order_params_", "");
  };

  var addOrderHandler = function (_event) {
    // Loads this order's parameters into thewindow.SCAPE.submission object...
    $("#order-parameters")
      .find("select, input")
      .each(function () {
        window.SCAPE.submission.order_params[getParamName(this)] = $(this).val();
      });

    // Mask out the order template parameters so that they can't be
    // changed once an order has been added.
    $("#order-template").find("select, input").attr("disabled", true);

    $(".order-active").removeClass("order-active");

    // Stop the submission from being built until new the order is either
    // saved or cancelled...
    $("#start-submission").attr("disabled", true);

    $("#add-order").attr("disabled", true);

    var newOrder = $("<li>").html($("#blank-order").html()).addClass("pane order-active order").hide();

    // Remove the disable from the form inputs
    // but leave the save button disabled
    newOrder
      .find("input, select, textarea")
      .not(".save-order")
      .css("opacity", 1)
      .removeAttr("disabled")
      .prop("disabled", false);

    // Enable the cross study/project buttons if appropriate
    if (window.SCAPE.submission.cross_compatible === false) {
      newOrder.find(".cross-compatible").remove();
    }

    newOrder.find(".cross_study").bind("change", function (e) {
      newOrder.find(".study_id").prop("disabled", this.checked);
      newOrder.find(".study_id option:eq(0)").prop("selected", true);
      validateOrder(e);
    });

    newOrder.find(".cross_project").bind("change", function (e) {
      newOrder.find(".submission_project_name").prop("disabled", this.checked);
      newOrder.find(".submission_project_name").prop("value", null);
      validateOrder(e);
    });
    // if this is not a sequencing order remove the lanes_of_sequencing_required stuff
    if (window.SCAPE.submission.is_a_sequencing_order === false) {
      newOrder.find(".lanes-of-sequencing").remove();
    }

    // we only need this box if we're pre-cap pooling
    if (window.SCAPE.submission.pre_capture_plex_level === null) {
      newOrder.find(".pre-capture-plex-level").remove();
      newOrder.find(".pre-capture-plex-group").remove();
    } else {
      newOrder.find(".pre_capture_plex_level").value = window.SCAPE.submission.pre_capture_plex_level;
      newOrder.find(".pre_capture_plex_level").value = window.SCAPE.submission.pre_capture_plex_group;
    }

    // And gigabase stuff is only for library creation
    if (window.SCAPE.submission.show_gigabses_expected === false) {
      newOrder.find(".gigabases-expected").remove();
    }

    // If we already have a study id set then load the asset_group for it.
    // e.g. someone coming to the page directly from a study page rather than
    // the submission inbox.
    if (window.SCAPE.submission.study_id) {
      newOrder.submission("loadStudyAssets", window.SCAPE.submission);
    }

    $("#blank-order").before(newOrder);

    newOrder.slideDown();

    newOrder.find("select").select2({ theme: "bootstrap4" });
  };

  var cancelOrderHandler = function (event) {
    var currentPane = $(event.target).submission("currentPane");

    currentPane.slideUp(function () {
      currentPane.remove();
      if ($(".order.completed").length === 0) {
        $("#order-template").addClass("order-active").find("select, input").removeAttr("disabled");
      }

      $("#add-order").removeAttr("disabled");

      if ($(".order.completed").length !== 0) {
        $("#start-submission").removeAttr("disabled");
      }
    });

    // don't forget to stop the form submitting...
    return false;
  };

  var deleteOrderHandler = function (event) {
    var currentPane = $(event.target).submission("currentPane");

    $.post(
      "/orders/" + currentPane.find(".order-id").val(),
      {
        _method: "delete",
        id: currentPane.find(".order-id").val(),
      },
      function (_response) {
        currentPane.slideUp(function () {
          currentPane.remove();
          $("#add-order").removeAttr("disabled");

          if ($(".order.completed").length === 0) {
            // If we're on an edit page and someone deletes the last order
            // then the submission has also been deleted so redirect them to
            // the submission inbox.
            if (window.location.pathname.match(/\/submissions\/\d+\/edit/)) {
              window.location.replace(window.SCAPE.submissions_inbox_url);
            }

            delete window.SCAPE.submission.id;

            $("#order-template").addClass("order-active").find("select, input").removeAttr("disabled");

            $("#start-submission").attr("disabled", true);
          }
        });
      },
    );

    // don't forget to stop the form submitting...
    return false;
  };

  // Document Ready stuff...
  $(function () {
    // Initialise the #start-submission button.
    $("#start-submission").attr("disabled", true);

    // Initialise the template selector and attach a change handler to
    // it.
    $("#submission_template_id").on("change", templateChangeHandler);

    // Validate the order-parameters
    $("#order-parameters").on("keypress", ".required", validateOrderParams);
    $("#order-parameters").on("blur", ".required", validateOrderParams);

    $("#add-order").click(addOrderHandler);

    // If there are any completed orders then enable the add-order button so we
    // can add more...
    if ($(".order.completed").length) $("#start-submission").removeAttr("disabled");
    // Uncomment for multiple orders
    // if ($('.order.completed').length) $('#add-order, #start-submission').removeAttr('disabled');

    // NB.  There seems to being some odd behaviour related to the
    // autocompleter select callback firing.  As a kludgey fix validation is
    // triggered on field key presses as a supplementary validation.
    $("ul#orders")
      .delegate("li.order select, li.order input, li.order textarea", "blur", validateOrder)
      .delegate(".sample_names_text, li.order input", "keypress", validateOrder)
      .delegate("li.order select", "change", validateOrder);

    // Most of the event handlers can be hung from the orders list...
    $("ul#orders")
      .on("change", ".study_id", studySelectHandler)
      .on("click", ".cancel-order", cancelOrderHandler)
      .on("click", ".save-order", saveOrderHandler)
      .on("click", ".delete-order", deleteOrderHandler);
  });
})(window, window.jQuery);