2 $timestamp => localtime()
5 <div class="modal falde" id="create_seedlots_trial_dialog" name="create_seedlots_trial_dialog" tabindex="-1" aria-labelledby="create_seedlots_trial_dialog_title">
6 <div class="modal-dialog modal-xl" role="document">
7 <div class="modal-content">
8 <div class="modal-header">
9 <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
10 <h4 class="modal-title" id="create_seedlots_trial_dialog_title">Create Seedlots from a Trial</h4>
12 <div class="modal-body">
13 <div class="container-fluid">
15 <!-- CREATE SEEDLOTS WORKFLOW -->
16 <&| /util/workflow.mas, id=> "create_seedlots_trial_workflow" &>
19 <!-- STEP 1: Intro -->
20 <&| /util/workflow.mas:step, title=> "Intro" &>
21 <& /page/page_title.mas, title=>"Introduction" &>
23 This workflow will guide you through the process of creating seedlots from the accessions
24 in a field trial. You can create one new seedlot for each plot or unique accession
25 in the trial. You can also set the initial contents (count or weight) of the seedlot
26 as the value of a recorded trait from the trial.
30 <button class="btn btn-primary" onclick="Workflow.complete(this);">Go to Next Step</button>
34 <!-- STEP 2: Select Trial -->
35 <&| /util/workflow.mas:step, title=> "Select Trial" &>
36 <& /page/page_title.mas, title=>"Select a Field Trial" &>
38 Enter the name of the field trial from which to create the seedlots:
41 <div class="form-group">
42 <label class="col-sm-3 control-label" style="text-align: right">Trial: </label>
43 <div class="col-sm-9" >
44 <input class="form-control" id="create_seedlots_trial_name" placeholder="Trial Name..." value="">
47 <br /><br /><br /><br />
48 <div class="alert alert-danger" id="trial_error_message_container" style="display: none">
49 <p id="trial_error_message"></p>
53 <button id="create_seedlots_trial_complete_select_trial" class="btn btn-primary">Go to Next Step</button>
57 <!-- STEP 3: Select Seedlots -->
58 <&| /util/workflow.mas:step, title=> "Select Seedlots" &>
59 <& /page/page_title.mas, title=>"Select Seedlots" &>
62 Seedlots can be generated from either each <strong>accession</strong> or <strong>plot</strong>
63 <span class='create_seedlots_trial_subplots_available'> or <strong>subplot</strong></span>
64 <span class='create_seedlots_trial_plants_available'> or <strong>plant</strong></span>
68 <li>If you select <strong>accessions</strong>: <span id="create_seedlots_trial_accession_count"></span> seedlots will be created</li>
69 <li>If you select <strong>plots</strong>: <span id="create_seedlots_trial_plot_count"></span> seedlots will be created</li>
70 <li class='create_seedlots_trial_subplots_available_list'>If you select <strong>subplots</strong>: <span id="create_seedlots_trial_subplot_count"></span> seedlots will be created</li>
71 <li class='create_seedlots_trial_plants_available_list'>If you select <strong>plants</strong>: <span id="create_seedlots_trial_plant_count"></span> seedlots will be created</li>
76 <div class="form-group">
77 <div class="col-sm-3"></div>
78 <div class="col-sm-6" >
79 <select id="create_seedlots_trial_stock_type" class="form-control">
80 <option value="accessions">Accessions</option>
81 <option value="plots">Plots</option>
82 <option class="create_seedlots_trial_subplots_available" value="subplots">Subplots</option>
83 <option class="create_seedlots_trial_plants_available" value="plants">Plants</option>
86 <div class="col-sm-3"></div>
89 <br/><br/><br /><br />
91 <button id="create_seedlots_trial_complete_select_stock_type" class="btn btn-primary">Go to Next Step</button>
95 <!-- STEP 4: Name Seedlots -->
96 <&| /util/workflow.mas:step, title=> "Name Seedlots" &>
97 <& /page/page_title.mas, title=>"Name Seedlots" &>
100 Enter a template for naming each new seedlot. The following variables (surrounded by curly braces) can be used in the template and the value of that variable will be replaced.
103 <li><code>trial_name</code></li>
104 <li><code>accession_name</code></li>
105 <li class="plot_level_variable"><code>plot_name</code></li>
106 <li class="plot_level_variable"><code>plot_number</code></li>
107 <li class="plot_level_variable"><code>rep</code></li>
108 <li class="plot_level_variable"><code>block</code></li>
109 <li class="subplot_level_variable"><code>subplot_name</code></li>
110 <li class="subplot_level_variable"><code>subplot_index</code></li>
111 <li class="plant_level_variable"><code>plant_name</code></li>
112 <li class="plant_level_variable"><code>plant_index</code></li>
113 <li><code>index</code> - an incrementing index number, starting with 1</li>
118 <div class="form-group">
119 <label class="col-sm-3 control-label" style="text-align: right">Name Template: </label>
120 <div class="col-sm-7" >
121 <input class="form-control" id="create_seedlots_trial_name_template" value="">
123 <div class="col-sm-2">
124 <button id="create_seedlots_trial_name_update" type="button" class="btn btn-primary btn-block" disabled>Update</button>
131 <button id="create_seedlots_trial_complete_name_seedlots" class="btn btn-primary">Go to Next Step</button>
136 <h4>Seedlot Names</h4>
137 <table id="create_seedlots_trial_name_table" class="table table-striped table-hover"></table>
140 <!-- STEP 5: Seedlot Contents -->
141 <&| /util/workflow.mas:step, title=> "Set Contents" &>
142 <& /page/page_title.mas, title=>"Set Seedlot Contents" &>
145 Each seedlot needs to have its initial contents (either a count or a weight) set. You
146 can either set the same initial value for each seedlot, use the value from one of the
147 traits recorded for this trial, or compute a new value from the recorded traits for this
148 trial. If you are creating a seedlot for each accession and select a trait or a computed
149 value as their initial value, then the sum of the trait values for each of the accession's
150 plots will be used. The contents of each seedlot can be modified in the table below
156 <div class="form-group">
157 <label class="col-sm-3 control-label" style="text-align: right">Contents Type: </label>
158 <div class="col-sm-9" >
159 <select class="form-control" id="create_seedlots_trial_contents_type">
160 <option value="amount">Amount / Count</option>
161 <option value="weight">Weight</option>
168 <div class="form-group">
169 <label class="col-sm-3 control-label" style="text-align: right">Contents Value: </label>
170 <div class="col-sm-9" >
171 <select class="form-control" id="create_seedlots_trial_contents_value">
172 <option value="constant">Constant Value</option>
173 <option value="trait">Trait Value</option>
174 <option value="computed">Computed Value from Traits</option>
181 <div class="form-group" id="create_seedlots_trial_contents_constant_form">
182 <label class="col-sm-3 control-label" style="text-align: right">Constant: </label>
183 <div class="col-sm-9" >
184 <input class="form-control" id="create_seedlots_trial_contents_constant" value="1">
188 <div class="form-group" id="create_seedlots_trial_contents_trait_form" style="display: none">
189 <label class="col-sm-3 control-label" style="text-align: right">Trait: </label>
190 <div class="col-sm-9" >
191 <select class="form-control" id="create_seedlots_trial_contents_trait"></select>
195 <div class="form-group" id="create_seedlots_trial_contents_computed_form" style="display: none">
196 <label class="col-sm-3 control-label" style="text-align: right">Trait Variables: </label>
197 <div class="col-sm-9">
199 Use the following variables as substitutions for the trait values to compute a new value
200 for the initial seedlot contents. If you are creating one seedlot <strong>per plot</strong>, then
201 the single plot trait value will be used in the computation. If you are creating one seedlot
202 <strong>per accession</strong>, then the sum of the trait values for each plot of that accession
203 will be used in the computation.
205 <table class="table table-striped">
212 <tbody id="create_seedlots_trial_contents_computed_table"></tbody>
216 <label class="col-sm-3 control-label" style="text-align: right">Contents Formula: </label>
217 <div class="col-sm-9">
218 <input class="form-control" id="create_seedlots_trial_contents_computed_formula" value="" placeholder="{trait_1}/10">
220 <strong>NOTE:</strong> The formula is evaluated using javascript, so any basic operators (+-*/) or
221 javascript Math functions (Math.abs(x)) can be used.
225 <div class="col-sm-12" style="text-align: center; margin: 25px 0">
226 <button id="create_seedlots_trial_complete_computed_compute" class="btn btn-primary" disabled>Compute</button>
233 <button id="create_seedlots_trial_complete_set_contents" class="btn btn-primary">Go to Next Step</button>
238 <h4>Seedlot Contents</h4>
239 <table id="create_seedlots_trial_contents_table" class="table table-striped table-hover"></table>
242 <!-- STEP 6: Seedlot Metadata -->
243 <&| /util/workflow.mas:step, title=> "Set Metadata" &>
244 <& /page/page_title.mas, title=>"Set Seedlot Metadata" &>
247 The following metadata is required for each seedlot. Fill out the form at the top
248 apply the metadata to all of the seedlots. Then, each seedlot can be manually
249 modified in the table below.
254 <div class="form-group">
255 <label class="col-sm-3 control-label" style="text-align: right">Breeding Program: </label>
256 <div class="col-sm-9" >
257 <div id="create_seedlots_trial_breeding_program_div"></div>
263 <div class="form-group">
264 <label class="col-sm-3 control-label" style="text-align: right">Location: </label>
265 <div class="col-sm-9" >
266 <input class="form-control create_seedlots_trial_metadata_input" id="create_seedlots_trial_location" value="" placeholder="Required">
272 <div class="form-group">
273 <label class="col-sm-3 control-label" style="text-align: right">Box Name: </label>
274 <div class="col-sm-9" >
275 <input class="form-control create_seedlots_trial_metadata_input" id="create_seedlots_trial_box_name" value="" placeholder="Required">
281 <div class="form-group">
282 <label class="col-sm-3 control-label" style="text-align: right">Quality Issues: </label>
283 <div class="col-sm-9" >
284 <input class="form-control create_seedlots_trial_metadata_input" id="create_seedlots_trial_quality_issues" value="" placeholder="Optional, list quality issues here, or 'ok' for good quality seed">
290 <div class="form-group">
291 <label class="col-sm-3 control-label" style="text-align: right">Organization: </label>
292 <div class="col-sm-9" >
293 <input class="form-control create_seedlots_trial_metadata_input" id="create_seedlots_trial_organization" value="" placeholder="Optional">
299 <div class="form-group">
300 <label class="col-sm-3 control-label" style="text-align: right">Timestamp: </label>
301 <div class="col-sm-9" >
302 <input class="form-control create_seedlots_trial_metadata_input" id="create_seedlots_trial_timestamp" value="<% $timestamp %>" placeholder="<% $timestamp %>">
308 <div class="form-group">
309 <label class="col-sm-3 control-label" style="text-align: right">Description: </label>
310 <div class="col-sm-9" >
311 <input class="form-control create_seedlots_trial_metadata_input" id="create_seedlots_trial_description" value="" placeholder="Optional">
318 <button id="create_seedlots_trial_update_metadata" class="btn btn-primary">Update</button>
324 <button id="create_seedlots_trial_complete_set_metadata" class="btn btn-primary" disabled>Go to Next Step</button>
329 <h4>Seedlot Metadata</h4>
330 <table id="create_seedlots_trial_metadata_table" class="table table-striped table-hover"></table>
333 <!-- STEP 7: Confirm -->
334 <&| /util/workflow.mas:step, title=> "Confirm" &>
335 <& /page/page_title.mas, title=>"Confirm Seedlots" &>
338 Confirm the attributes for the new seedlots in the table below. If something
339 is incorrect, navigate to the previous step and correct the information. Once
340 all the information is correct, continue to the next step to create the seedlots.
346 <button id="create_seedlots_trial_complete_confirm_seedlots" class="btn btn-primary">
347 <span class="glyphicon glyphicon-ok"></span> Create Seedlots
353 <h4>Seedlots to Create</h4>
354 <table id="create_seedlots_trial_confirm_table" class="table table-striped table-hover"></table>
357 <!-- STEP 8: Create -->
358 <&| /util/workflow.mas:step, title=> "Create" &>
359 <& /page/page_title.mas, title=>"Create Seedlots" &>
363 <div id="create_seedlots_trial_uploading">
364 <p id="create_seedlots_trial_status"><strong>Creating Seedlots...</strong></p>
365 <div class="progress">
366 <div id="create_seedlots_trial_progress" class="progress-bar" role="progressbar" aria-valuenow="70"
367 aria-valuemin="0" aria-valuemax="100" style="width:70%">
368 <span class="sr-only">70% Complete</span>
373 <div id="create_seedlots_trial_results"></div>
377 <h4>Seedlot Progress</h4>
378 <table id="create_seedlots_trial_results_table" class="table table-striped table-hover"></table>
395 <script type="text/javascript">
397 let BREEDING_PROGRAM_ID;
405 const UPLOAD_BATCH_SIZE = 5;
407 jQuery(document).ready(function() {
409 // Click / Change Listeners
410 jQuery('[name="create_seedlots_trial_button"]').click( function() {
411 jQuery('#create_seedlots_trial_dialog').modal('show');
413 jQuery("#create_seedlots_trial_complete_select_trial").click(completeSelectTrial);
414 jQuery("#create_seedlots_trial_complete_select_stock_type").click(completeSelectStockType);
415 jQuery("#create_seedlots_trial_name_template").keyup(function() {
416 jQuery("#create_seedlots_trial_name_update").attr("disabled", false);
417 jQuery("#create_seedlots_trial_complete_name_seedlots").attr("disabled", true);
419 jQuery("#create_seedlots_trial_name_update").click(updateSeedlotNames);
420 jQuery(document).on('keyup', '.create_seedlots_trial_name_seedlot', function() {
421 let el = jQuery(this);
422 let index = el.data("index");
424 SEEDLOTS[index].name = name;
426 jQuery("#create_seedlots_trial_complete_name_seedlots").click(completeNameSeedlots);
427 jQuery("#create_seedlots_trial_contents_value").change(function() {
428 let val = jQuery("#create_seedlots_trial_contents_value").val();
429 jQuery("#create_seedlots_trial_contents_constant_form").css("display", val === "constant" ? "block" : "none");
430 jQuery("#create_seedlots_trial_contents_trait_form").css("display", val === "trait" ? "block" : "none");
431 jQuery("#create_seedlots_trial_contents_computed_form").css("display", val === "computed" ? "block" : "none");
432 jQuery("#create_seedlots_trial_complete_set_contents").attr("disabled", val === "computed");
434 jQuery("#create_seedlots_trial_contents_value").change(updateSeedlotContents);
435 jQuery("#create_seedlots_trial_contents_constant").keyup(updateSeedlotContents);
436 jQuery("#create_seedlots_trial_contents_trait").change(function() {
437 let id = jQuery("#create_seedlots_trial_contents_trait").val();
438 let name = jQuery("#create_seedlots_trial_contents_trait option:selected").text();
440 updateSeedlotContents();
443 jQuery("#create_seedlots_trial_contents_computed_formula").keyup(function() {
444 jQuery("#create_seedlots_trial_complete_computed_compute").attr("disabled", false);
445 jQuery("#create_seedlots_trial_complete_set_contents").attr("disabled", true);
447 jQuery("#create_seedlots_trial_complete_computed_compute").click(updateSeedlotContents);
448 jQuery(document).on('keyup', '.create_seedlots_trial_contents_seedlot', function() {
449 let el = jQuery(this);
450 let index = el.data("index");
451 let contents = el.val();
452 SEEDLOTS[index].contents = contents;
454 jQuery("#create_seedlots_trial_complete_set_contents").click(completeSeedlotContents);
455 jQuery("#create_seedlots_trial_update_metadata").click(function() { updateSeedlotMetadata() });
456 jQuery(document).on('change', "#create_seedlots_trial_breeding_program_id", function() {
457 jQuery("#create_seedlots_trial_update_metadata").attr("disabled", false);
458 jQuery("#create_seedlots_trial_complete_set_metadata").attr("disabled", true);
460 jQuery(".create_seedlots_trial_metadata_input").keyup(function() {
461 jQuery("#create_seedlots_trial_update_metadata").attr("disabled", false);
462 jQuery("#create_seedlots_trial_complete_set_metadata").attr("disabled", true);
464 jQuery(document).on('change', '.create_seedlots_trial_metadata_input_seedlot_breeding_program', function() {
465 let el = jQuery(this);
466 let sel = el.find("option:selected");
467 let index = el.data("index");
469 let name = sel.text();
470 SEEDLOTS[index].metadata.breeding_program = { id: id, name: name };
472 jQuery(document).on('keyup', '.create_seedlots_trial_metadata_input_seedlot', function() {
473 let el = jQuery(this);
474 let index = el.data("index");
475 let prop = el.data("prop");
476 let value = el.val();
477 SEEDLOTS[index].metadata[prop] = value;
479 jQuery("#create_seedlots_trial_complete_set_metadata").click(completeSeedlotMetadata);
480 jQuery("#create_seedlots_trial_complete_confirm_seedlots").click(completeSeedlotConfirmation);
483 jQuery("#create_seedlots_trial_name").autocomplete({
484 source: '/ajax/trials/trial_autocomplete'
486 jQuery("#create_seedlots_trial_location").autocomplete({
487 source: '/ajax/stock/geolocation_autocomplete',
490 // Breeding Program Select Box
491 get_select_box('breeding_programs', 'create_seedlots_trial_breeding_program_div', { 'name' : 'create_seedlots_trial_breeding_program_id', 'id' : 'create_seedlots_trial_breeding_program_id' });
498 // WORKFLOW FUNCTIONS
502 * Complete the Select Trial step
503 * - Get details of selected trial
505 function completeSelectTrial() {
508 // Make sure trial name was entered
509 let trial_name = jQuery("#create_seedlots_trial_name").val();
510 if ( !trial_name || trial_name === '' ) {
511 alert("Enter a trial name to continue");
515 // Display working modal and disable button
516 jQuery('#working_modal').modal("show");
517 jQuery("#create_seedlots_trial_complete_select_trial").attr("disabled", true);
518 jQuery("#trial_error_message_container").hide();
521 getTrialID(trial_name, function(trial_id) {
525 // Get the trial details
526 getTrialDetails(trial_id, function(error, breeding_program_id, accessions, plots, subplots, plants, traits) {
531 else if ( breeding_program_id && accessions && plots ) {
532 BREEDING_PROGRAM_ID = breeding_program_id;
533 ACCESSIONS = accessions;
537 TRAITS = traits ? traits : [];
539 getTraitData(function() {
560 * Hide the working modal, renable the button, and continue (if complete)
562 function _finish(complete) {
563 jQuery('#working_modal').modal("hide");
565 jQuery("#trial_error_message").html(trial_error);
566 jQuery("#trial_error_message_container").show();
569 jQuery("#trial_error_message_container").hide();
571 jQuery("#create_seedlots_trial_complete_select_trial").attr("disabled", false);
572 jQuery("#create_seedlots_trial_accession_count").html(ACCESSIONS ? ACCESSIONS.length : '');
573 jQuery("#create_seedlots_trial_plot_count").html(PLOTS ? PLOTS.length : '');
574 jQuery("#create_seedlots_trial_subplot_count").html(SUBPLOTS ? SUBPLOTS.length : '');
575 jQuery("#create_seedlots_trial_plant_count").html(PLANTS ? PLANTS.length : '');
576 jQuery(".create_seedlots_trial_subplots_available").css("display", SUBPLOTS && SUBPLOTS.length > 0 ? 'inline' : 'none');
577 jQuery(".create_seedlots_trial_plants_available").css("display", PLANTS && PLANTS.length > 0 ? 'inline' : 'none');
578 jQuery(".create_seedlots_trial_subplots_available_list").css("display", SUBPLOTS && SUBPLOTS.length > 0 ? 'list-item' : 'none');
579 jQuery(".create_seedlots_trial_plants_available_list").css("display", PLANTS && PLANTS.length > 0 ? 'list-item' : 'none');
581 Workflow.complete('#create_seedlots_trial_complete_select_trial');
582 Workflow.focus("#create_seedlots_trial_workflow", 2);
588 * Complete the stock type selection step
589 * - Setup the initial Seedlots
591 function completeSelectStockType() {
592 let stock_type = jQuery("#create_seedlots_trial_stock_type").val();
594 // Hide the plot-level variables, if stock type is not plots
595 jQuery(".plot_level_variable").css("display", ["plots", "subplots", "plants"].includes(stock_type) ? "list-item" : "none");
596 jQuery(".subplot_level_variable").css("display", stock_type === 'subplots' ? "list-item" : "none");
597 jQuery(".plant_level_variable").css("display", stock_type === 'plants' ? "list-item" : "none");
598 jQuery("#create_seedlots_trial_name_template").val(
599 stock_type === "plots" ? "{trial_name}-{accession_name}-{rep}" :
600 stock_type === "subplots" ? "{trial_name}-{accession_name}-subplot{subplot_index}" :
601 stock_type === "plants" ? "{trial_name}-{accession_name}-plant{plant_index}" :
602 "{trial_name}-{accession_name}"
605 // Build the Seedlots
607 if ( stock_type === 'accessions' ) {
608 for ( let i = 0; i < ACCESSIONS.length; i++ ) {
610 accession: ACCESSIONS[i],
615 else if ( stock_type === 'plots' ) {
616 for ( let i = 0; i < PLOTS.length; i++ ) {
619 accession: PLOTS[i].accession,
624 else if ( stock_type === 'subplots' ) {
625 for ( let i = 0; i < SUBPLOTS.length; i++ ) {
627 plot: SUBPLOTS[i].plot,
628 subplot: SUBPLOTS[i].subplot,
629 accession: SUBPLOTS[i].accession,
634 else if ( stock_type === 'plants' ) {
635 for ( let i = 0; i < PLANTS.length; i++ ) {
637 plot: PLANTS[i].plot,
638 plant: PLANTS[i].plant,
639 accession: PLANTS[i].accession,
645 alert("ERROR: unknown seedlot stock type!");
648 Workflow.complete('#create_seedlots_trial_complete_select_stock_type');
649 Workflow.focus("#create_seedlots_trial_workflow", 3);
650 updateSeedlotNames();
654 * Complete the seedlot name step
655 * - Make sure names are unique
656 * - Setup trait information
658 function completeNameSeedlots() {
660 // Make sure Seedlot names are unique
663 for ( let i = 0; i < SEEDLOTS.length; i++ ) {
664 if ( names.includes(SEEDLOTS[i].name) ) {
665 alert("Seedlot names must be unique - '" + SEEDLOTS[i].name + "' is used more than once");
668 names.push(SEEDLOTS[i].name);
672 let html = "<option value=''>Choose trait...</option>";
673 for ( let i = 0; i < TRAITS.length; i++ ) {
674 html += "<option value='" + TRAITS[i].id + "'>" + TRAITS[i].name + "</option>";
676 jQuery("#create_seedlots_trial_contents_trait").html(html);
678 Workflow.complete('#create_seedlots_trial_complete_name_seedlots');
679 Workflow.focus("#create_seedlots_trial_workflow", 4);
680 updateSeedlotContents();
685 * Complete the seedlot contents step
687 function completeSeedlotContents() {
688 jQuery("#create_seedlots_trial_breeding_program_id").val(BREEDING_PROGRAM_ID);
689 Workflow.complete('#create_seedlots_trial_complete_set_contents');
690 Workflow.focus("#create_seedlots_trial_workflow", 5);
691 updateSeedlotMetadata(true);
695 * Complete the seedlot metadata step
697 function completeSeedlotMetadata() {
698 Workflow.complete('#create_seedlots_trial_complete_set_metadata');
699 Workflow.focus("#create_seedlots_trial_workflow", 6);
700 updateSeedlotDetails();
704 * Complete the seedlot confirmation step
705 * - Start uploading Seedlots to DB
707 function completeSeedlotConfirmation() {
708 Workflow.complete('#create_seedlots_trial_complete_confirm_seedlots');
709 Workflow.focus("#create_seedlots_trial_workflow", 7);
710 startSeedlotUpload(displayUploadResults);
721 * Set the name of each seedlot - using the template and replacing the values
722 * then update the table displaying the results
724 function updateSeedlotNames() {
725 let trial_name = jQuery("#create_seedlots_trial_name").val();
726 let stock_type = jQuery("#create_seedlots_trial_stock_type").val();
727 let name_template = jQuery("#create_seedlots_trial_name_template").val();
728 if ( name_template === '{plot_name}' ) {
729 return alert("The seedlot name cannot be the same as the plot name, please add a prefix or suffix to make the name unique");
732 let html = "<thead></tr>";
733 if ( stock_type === 'accessions' ) html += "<th>Trial Name</th>";
734 html += "<th>Accession Name</th>";
735 if ( ["plots", "subplots", "plants"].includes(stock_type) ) html += "<th>Plot Number</th>";
736 if ( ["plots", "subplots", "plants"].includes(stock_type) ) html += "<th>Rep</th>";
737 if ( ["plots", "subplots", "plants"].includes(stock_type) ) html += "<th>Block</th>";
738 if ( stock_type === 'subplots' ) html += "<th>Subplot Name</th>";
739 if ( stock_type === 'subplots' ) html += "<th>Subplot Index</th>";
740 if ( stock_type === 'plants' ) html += "<th>Plant Name</th>";
741 if ( stock_type === 'plants' ) html += "<th>Plant Index</th>";
742 html += "<th>Seedlot Name</th>";
743 html += "</tr></thead>";
745 for ( let i = 0; i < SEEDLOTS.length; i++ ) {
747 let name = name_template
748 .replace("{trial_name}", trial_name)
749 .replace("{accession_name}", SEEDLOTS[i].accession.name)
750 .replace("{index}", index);
751 if ( SEEDLOTS[i].plot ) {
753 .replace("{plot_name}", SEEDLOTS[i].plot.name)
754 .replace("{plot_number}", SEEDLOTS[i].plot.number)
755 .replace("{rep}", SEEDLOTS[i].plot.rep)
756 .replace("{block}", SEEDLOTS[i].plot.block);
758 if ( SEEDLOTS[i].subplot ) {
760 .replace("{subplot_name}", SEEDLOTS[i].subplot.name)
761 .replace("{subplot_index}", SEEDLOTS[i].subplot.index);
763 if ( SEEDLOTS[i].plant ) {
765 .replace("{plant_name}", SEEDLOTS[i].plant.name)
766 .replace("{plant_index}", SEEDLOTS[i].plant.index);
768 SEEDLOTS[i].name = name;
771 if ( stock_type === 'accessions' ) html += "<td>" + trial_name + "</td>";
772 html += "<td>" + SEEDLOTS[i].accession.name + "</td>";
773 if ( ["plots", "subplots", "plants"].includes(stock_type) ) html += "<td>" + SEEDLOTS[i].plot.number + "</td>";
774 if ( ["plots", "subplots", "plants"].includes(stock_type) ) html += "<td>" + SEEDLOTS[i].plot.rep + "</td>";
775 if ( ["plots", "subplots", "plants"].includes(stock_type) ) html += "<td>" + SEEDLOTS[i].plot.block + "</td>";
776 if ( stock_type === 'subplots' ) html += "<td>" + SEEDLOTS[i].subplot.name + "</td>";
777 if ( stock_type === 'subplots' ) html += "<td>" + SEEDLOTS[i].subplot.index + "</td>";
778 if ( stock_type === 'plants' ) html += "<td>" + SEEDLOTS[i].plant.name + "</td>";
779 if ( stock_type === 'plants' ) html += "<td>" + SEEDLOTS[i].plant.index + "</td>";
780 html += "<td><input class='form-control create_seedlots_trial_name_seedlot' data-index='" + i + "' value='" + name + "'></td>";
784 jQuery("#create_seedlots_trial_name_table").html(html);
785 jQuery("#create_seedlots_trial_complete_name_seedlots").attr("disabled", false);
786 jQuery("#create_seedlots_trial_name_update").attr("disabled", true);
790 * Calculate the seedlot contents and update the table displaying the results
792 function updateSeedlotContents() {
793 let stock_type = jQuery("#create_seedlots_trial_stock_type").val();
794 let contents_type = jQuery("#create_seedlots_trial_contents_type").val();
795 let value = jQuery("#create_seedlots_trial_contents_value").val();
796 let constant = jQuery("#create_seedlots_trial_contents_constant").val();
797 let trait_id = jQuery("#create_seedlots_trial_contents_trait").val();
798 let formula = jQuery("#create_seedlots_trial_contents_computed_formula").val();
800 let html = "<thead></tr>";
801 html += "<th>Seedlot Name</th>";
802 html += "<th>Contents</th>";
803 html += "</tr></thead>";
805 for ( let i = 0; i < SEEDLOTS.length; i++ ) {
808 // Set contents value
809 if ( value === 'constant' ) {
812 else if ( value === 'trait' && TRAIT_DATA ) {
813 contents = _getTraitData(i, trait_id);
815 else if ( value === 'computed' && TRAIT_DATA && formula && formula !== "" ) {
816 let seedlot_formula = formula;
817 for ( let j = 0; j < TRAITS.length; j++ ) {
818 let variable = TRAITS[j].variable;
819 let variable_trait_id = TRAITS[j].id;
820 if ( formula.includes('{' + variable + '}') ) {
821 let variable_value = _getTraitData(i, variable_trait_id);
822 seedlot_formula = seedlot_formula.replaceAll('{' + variable + '}', variable_value);
826 let eval_value = eval(seedlot_formula);
827 if ( eval_value ) contents = eval_value;
833 html += "<td>" + SEEDLOTS[i].name + "</td>";
834 html += "<td><input class='form-control create_seedlots_trial_contents_seedlot' data-index='" + i + "' value='" + contents + "'></td>";
837 SEEDLOTS[i].contents = contents;
840 jQuery("#create_seedlots_trial_contents_table").html(html);
841 jQuery("#create_seedlots_trial_complete_computed_compute").attr("disabled", true);
842 jQuery("#create_seedlots_trial_complete_set_contents").attr("disabled", false);
846 * Get the value of the specified trait for the specified seedlot
847 * If 'stock_type' == 'accessions', then the sum of the plots is returned
849 function _getTraitData(seedlot_index, trait_id) {
851 for ( let j = 0; j < TRAIT_DATA.length; j++ ) {
852 if ( TRAIT_DATA[j].trait_id + '' === trait_id + '' ) {
854 // Sum plots of the same accession
855 if ( stock_type === 'accessions' ) {
856 if ( SEEDLOTS[seedlot_index].accession.id === TRAIT_DATA[j].accession_id ) {
858 contents = contents + parseFloat(TRAIT_DATA[j].trait_value);
864 // Use the plot value
866 if ( SEEDLOTS[seedlot_index].plot.id === TRAIT_DATA[j].plot_id ) {
867 contents = TRAIT_DATA[j].trait_value;
878 * Set the seedlot metadata and update the table displaying the individual metadata
880 function updateSeedlotMetadata(quiet) {
881 let p_id = jQuery("#create_seedlots_trial_breeding_program_id").val();
882 let p_name = jQuery("#create_seedlots_trial_breeding_program_id option:selected").text();
883 let l = jQuery("#create_seedlots_trial_location").val();
884 let b = jQuery("#create_seedlots_trial_box_name").val();
885 let q = jQuery("#create_seedlots_trial_quality_issues").val();
886 let o = jQuery("#create_seedlots_trial_organization").val();
887 let t = jQuery("#create_seedlots_trial_timestamp").val();
888 let d = jQuery("#create_seedlots_trial_description").val();
890 // Check for required fields
891 if ( !p_id || p_id === '' ) {
892 if (!quiet) alert("Breeding Program is required");
895 if ( !l || l === '' ) {
896 if (!quiet) alert("Location is required");
899 if ( !b || b === '' ) {
900 if (!quiet) alert("Box Name is required");
905 let html = "<thead><tr>";
906 html += "<th>Seedlot Name</th>";
907 html += "<th>Metadata</th>";
908 html += "</tr></thead>";
910 for ( let i = 0; i < SEEDLOTS.length; i++ ) {
911 let p_select = jQuery("#create_seedlots_trial_breeding_program_id").html();
914 html += "<td>" + SEEDLOTS[i].name + "</td>";
916 html += "<strong>Breeding Program:</strong><select class='form-control create_seedlots_trial_metadata_input_seedlot_breeding_program' data-index='" + i + "'>" + p_select + "</select><br />";
917 html += "<strong>Location:</strong><input class='form-control create_seedlots_trial_metadata_input_seedlot' data-prop='location' data-index='" + i + "' value='" + l + "'><br />";
918 html += "<strong>Box Name:</strong><input class='form-control create_seedlots_trial_metadata_input_seedlot' data-prop='box_name' data-index='" + i + "' value='" + b + "'><br />";
919 html += "<strong>Quality Issues:</strong><input class='form-control create_seedlots_trial_metadata_input_seedlot' data-prop='quality_issues' data-index='" + i + "' value='" + q + "'><br />";
920 html += "<strong>Organization:</strong><input class='form-control create_seedlots_trial_metadata_input_seedlot' data-prop='organization' data-index='" + i + "' value='" + o + "'><br />";
921 html += "<strong>Timestamp:</strong><input class='form-control create_seedlots_trial_metadata_input_seedlot' data-prop='timestamp' data-index='" + i + "' value='" + t + "'><br />";
922 html += "<strong>Description:</strong><input class='form-control create_seedlots_trial_metadata_input_seedlot' data-prop='description' data-index='" + i + "' value='" + d + "'><br />";
926 SEEDLOTS[i].metadata = {
940 jQuery("#create_seedlots_trial_metadata_table").html(html);
941 jQuery(".create_seedlots_trial_metadata_input_seedlot_breeding_program").val(p_id);
942 jQuery("#create_seedlots_trial_update_metadata").attr("disabled", true);
943 jQuery("#create_seedlots_trial_complete_set_metadata").attr("disabled", false);
948 * Build the table of Seedlot details for the user to confirm
950 function updateSeedlotDetails() {
951 let stock_type = jQuery("#create_seedlots_trial_stock_type").val();
952 let contents_type = jQuery("#create_seedlots_trial_contents_type").val();
954 let html = "<thead><tr>";
955 html += "<th>Seedlot Name</th>";
956 html += "<th>Breeding Program</th>";
957 html += "<th>Source Accession</th>";
958 if ( stock_type === 'plots' ) html += "<th>Source Plot</th>";
959 if ( stock_type === 'subplots' ) html += "<th>Source Subplot</th>";
960 if ( stock_type === 'plants' ) html += "<th>Source Plant</th>";
961 if ( contents_type === 'amount' ) html += "<th>Contents (Amount)</th>";
962 if ( contents_type === 'weight' ) html += "<th>Contents (Weight)</th>";
963 html += "<th>Location</th>";
964 html += "<th>Box Name</th>";
965 html += "<th>Quality Issues</th>";
966 html += "<th>Organization</th>";
967 html += "<th>Timestamp</th>";
968 html += "<th>Description</th>";
969 html += "</tr></thead>";
971 for ( let i = 0; i < SEEDLOTS.length; i++ ) {
973 html += "<td>" + SEEDLOTS[i].name + "</td>";
974 html += "<td>" + SEEDLOTS[i].metadata.breeding_program.name + "</td>";
975 html += "<td>" + SEEDLOTS[i].accession.name + "</td>";
976 if ( stock_type === 'plots' ) html += "<td>" + SEEDLOTS[i].plot.name + "</td>";
977 if ( stock_type === 'subplots' ) html += "<td>" + SEEDLOTS[i].subplot.name + "</td>";
978 if ( stock_type === 'plants' ) html += "<td>" + SEEDLOTS[i].plant.name + "</td>";
979 html += "<td>" + SEEDLOTS[i].contents + "</td>";
980 html += "<td>" + SEEDLOTS[i].metadata.location + "</td>";
981 html += "<td>" + SEEDLOTS[i].metadata.box_name + "</td>";
982 html += "<td>" + SEEDLOTS[i].metadata.quality_issues + "</td>";
983 html += "<td>" + SEEDLOTS[i].metadata.organization + "</td>";
984 html += "<td>" + SEEDLOTS[i].metadata.timestamp + "</td>";
985 html += "<td>" + SEEDLOTS[i].metadata.description + "</td>";
989 jQuery("#create_seedlots_trial_confirm_table").html(html);
994 * Upload each of the Seedlots to the Database in batches of up to
995 * UPLOAD_BATCH_SIZE seedlots per batch
996 * @param {Function} callback Callback function(errors, success_count)
998 function startSeedlotUpload(callback) {
999 let stock_type = jQuery("#create_seedlots_trial_stock_type").val();
1000 let contents_type = jQuery("#create_seedlots_trial_contents_type").val();
1001 jQuery("#create_seedlots_trial_status").html("<strong>Creating Seedlots...</strong>");
1002 jQuery("#create_seedlots_trial_progress").css('width', '0%').attr('aria-valuenow', 0);
1003 jQuery("#create_seedlots_trial_results").empty();
1005 // Create Results table
1006 let html = "<thead><tr>";
1007 html += "<th>Seedlot</th>";
1008 html += "<th>Progress</th>";
1009 html += "</tr></thead>";
1010 html += "<tbody id='create_seedlots_trial_results_table_body'></tbody>";
1011 for ( let i = 0; i < SEEDLOTS.length; i++ ) {
1012 let status_id = "create_seedlots_trial_results_row_" + i;
1013 html += "<tr><td>" + SEEDLOTS[i].name + "</td><td id='" + status_id + "'>Pending...</td>";
1014 SEEDLOTS[i].status_id = status_id;
1016 jQuery("#create_seedlots_trial_results_table").html(html);
1018 // Information to return
1019 let upload_count = 0; // Count of successfully created/uploaded seedlots
1020 let errors = []; // Array of errors encountered during upload
1022 // Set counts of seedlots and batches
1023 let seedlot_count = 0; // Count of completed seedlots
1024 let seedlot_total = SEEDLOTS.length; // Total number of seedlots
1025 let seedlot_batch_count; // Count of completed seedlots, in current batch
1026 let seedlot_batch_total; // Total number of seedlots, in current batch
1027 let batch_count = 0 // Count of completed batches
1028 let batch_total = Math.ceil(seedlot_total/UPLOAD_BATCH_SIZE); // Total number of batches
1030 // Start the first batch
1034 * Upload a batch of Seedlots (max set with UPLOAD_BATCH_SIZE)
1035 * - when the seedlot is complete, call _finishSeedlot()
1037 function _uploadBatch() {
1038 let seedlot_batch_start = batch_count*UPLOAD_BATCH_SIZE;
1039 let seedlot_batch_end = seedlot_batch_start+UPLOAD_BATCH_SIZE;
1040 if ( seedlot_batch_end > seedlot_total ) seedlot_batch_end = seedlot_total;
1041 seedlot_batch_count = 0;
1042 seedlot_batch_total = seedlot_batch_end - seedlot_batch_start;
1043 for ( let seedlot_index = seedlot_batch_start; seedlot_index < seedlot_batch_end; seedlot_index++ ) {
1044 _uploadSeedlot(seedlot_index, function() {
1045 let p = ((seedlot_count+1)/seedlot_total)*100;
1046 jQuery("#create_seedlots_trial_progress").css('width', p+'%').attr('aria-valuenow', p);
1053 * Upload the specified seedlot
1055 function _uploadSeedlot(seedlot_index, callback) {
1056 let seedlot = SEEDLOTS[seedlot_index];
1057 _setStatusInfo(seedlot.status_id, "Creating...");
1059 // Set seedlot request data
1061 'seedlot_name': seedlot.name,
1062 'seedlot_location': seedlot.metadata.location,
1063 'seedlot_box_name': seedlot.metadata.box_name,
1064 'seedlot_accession_uniquename': seedlot.accession.name,
1065 'seedlot_organization': seedlot.metadata.organization,
1066 'seedlot_timestamp': seedlot.metadata.timestamp,
1067 'seedlot_description': seedlot.metadata.description,
1068 'seedlot_breeding_program_id': seedlot.metadata.breeding_program.id,
1069 'seedlot_quality': seedlot.metadata.quality_issues,
1072 if ( stock_type === 'plots' ) data['seedlot_source'] = seedlot.plot.name;
1073 if ( stock_type === 'subplots' ) data['seedlot_source'] = seedlot.subplot.name;
1074 if ( stock_type === 'plants' ) data['seedlot_source'] = seedlot.plant.name;
1075 if ( contents_type === 'amount' ) data['seedlot_amount'] = seedlot.contents;
1076 if ( contents_type === 'weight' ) data['seedlot_weight'] = seedlot.contents;
1079 url: '/ajax/breeders/seedlot-create',
1081 success: function(response) {
1082 jQuery('#working_modal').modal('hide');
1083 if (response.success === 1) {
1085 _setStatusComplete(seedlot.status_id);
1087 if (response.error) {
1089 index: seedlot_index,
1091 error: response.error
1093 _setStatusError(seedlot.status_id, response.error);
1096 error: function(response){
1097 let msg = "AJAX error";
1099 index: seedlot_index,
1103 _setStatusError(seedlot.status_id, msg);
1105 complete: function() {
1112 * Display an info message for the specified seedlot
1114 function _setStatusInfo(id, info) {
1115 jQuery("#" + id).html(info);
1119 * Display a complete message for the specified seedlot
1121 function _setStatusComplete(id) {
1122 jQuery("#" + id).html("<strong><span class='glyphicon glyphicon-ok-sign text-success'></span></strong> Seedlot Created!");
1126 * Display an error message for the specified seedlot
1128 function _setStatusError(id, error) {
1129 jQuery("#" + id).html("<strong><span class='glyphicon glyphicon-exclamation-sign text-danger'></span> ERROR:</strong> " + error);
1133 * Finished uploading a seedlot
1134 * - when all of the batch's seedlots have been uploaded, call _finishBatch()
1136 function _finishSeedlot() {
1138 seedlot_batch_count++;
1139 if ( seedlot_batch_count >= seedlot_batch_total ) {
1145 * Finished uploading a batch of seedlots
1146 * - when all of the batches are complete, refresh the matviews
1147 * - if there are remaining batches, start the next batch
1149 function _finishBatch() {
1151 if ( batch_count >= batch_total ) {
1153 url: '/ajax/breeder/refresh?matviews=stockprop'
1155 if ( callback ) return callback(errors, upload_count);
1164 * Display the status results message
1165 * @param {Object[]} errors List of upload errors
1166 * @param {int} upload_count Count of successfully uploaded seedlots
1168 function displayUploadResults(errors, upload_count) {
1169 jQuery("#create_seedlots_trial_status").html("<strong>Upload Complete</strong>");
1170 jQuery("#create_seedlots_trial_progress").css('width', '100%').attr('aria-valuenow', 100);
1172 let html = "<br /><br />";
1173 if ( !errors || errors.length === 0 ) {
1174 html += "<div class='alert-success' role='alert'>";
1175 html += "<strong><span class='glyphicon glyphicon-ok-sign'></span></strong> ";
1176 html += upload_count + " seedlots successfully created!";
1179 if ( errors && errors.length > 0 ) {
1180 html += "<div class='alert-danger' role='alert'>"
1181 html += "<strong><span class='glyphicon glyphicon-exclamation-sign'></span> " + errors.length + " errors encountered!<br />";
1182 html += "See below for which seedlots failed and more details<br />";
1183 if ( upload_count > 0 ) html += "<br /><strong><span class='glyphicon glyphicon-ok-sign'></span> " + upload_count + " seedlots successfully created";
1187 jQuery("#create_seedlots_trial_results").html(html);
1192 // DB QUERY FUNCTIONS
1196 * Query the database for the trial id (by name)
1197 * @param {string} trial_name Trial Name
1198 * @param {Function} callback Callback function(trial_id)
1200 function getTrialID(trial_name, callback) {
1204 url: '/ajax/breeders/trial_lookup',
1208 success: function(response) {
1209 if (response.error) {
1210 alert(response.error);
1212 else if ( response.trial_id ) {
1213 trial_id = response.trial_id;
1216 alert('Could not lookup trial');
1219 error: function(response){
1220 alert('Could not lookup trial');
1222 complete: function() {
1223 return callback(trial_id);
1229 * Get required metadata for the Trial:
1231 * - Breeding Program ID
1237 * @param {int} trial_id Trial ID
1238 * @param {function} callback Callback function(error, breeding_program_id, accessions, plots, subplots, plants, traits)
1240 function getTrialDetails(trial_id, callback) {
1244 let breeding_program_id, accessions, plots, subplots, plants, traits;
1247 _getBreedingProgram();
1252 function _getBreedingProgram() {
1255 url: '/breeders/programs_by_trial/' + trial_id,
1256 success: function(response) {
1257 if (response.error) {
1258 alert(response.error);
1260 else if ( response.projects ) {
1261 breeding_program_id = response.projects[0][0];
1264 alert('Could not lookup trial breeding program');
1267 error: function(response){
1268 alert('Could not lookup trial breeding program');
1270 complete: function() {
1276 function _getAccessions() {
1279 url: '/ajax/breeders/trial/' + trial_id + '/accessions',
1280 success: function(response) {
1281 if (response.error) {
1282 alert(response.error);
1284 else if ( response.accessions ) {
1286 for ( let i = 0; i < response.accessions[0].length; i++ ) {
1287 if ( response.accessions[0][i].stock_type !== 'accession' ) {
1288 error = `This tool only supports trials with plots that contain accessions. This trial has one or more plots that contain a ${response.accessions[0][i].stock_type}.`;
1291 id: response.accessions[0][i].stock_id,
1292 name: response.accessions[0][i].accession_name
1297 alert('Could not lookup trial accessions');
1300 error: function(response){
1301 alert('Could not lookup trial accessions');
1303 complete: function() {
1309 function _getPlots() {
1312 url: '/ajax/breeders/trial/' + trial_id + '/layout',
1313 success: function(response) {
1314 if (response.error) {
1315 alert(response.error);
1317 else if ( response.design ) {
1321 for (const [plot, info] of Object.entries(response.design)) {
1325 number: info.plot_number,
1326 name: info.plot_name,
1327 rep: info.rep_number,
1328 block: info.block_number
1331 name: info.accession_name,
1332 id: info.accession_id
1335 if ( info.subplot_names && info.subplot_ids && info.subplot_index_numbers ) {
1336 for ( let i = 0; i < info.subplot_names.length; i++ ) {
1339 name: info.subplot_names[i],
1340 id: info.subplot_ids[i],
1341 index: info.subplot_index_numbers[i]
1345 number: info.plot_number,
1346 name: info.plot_name,
1347 rep: info.rep_number,
1348 block: info.block_number
1351 name: info.accession_name,
1352 id: info.accession_id
1357 if ( info.plant_names && info.plant_ids && info.plant_index_numbers ) {
1358 for ( let i = 0; i < info.plant_names.length; i++ ) {
1361 name: info.plant_names[i],
1362 id: info.plant_ids[i],
1363 index: info.plant_index_numbers[i]
1367 number: info.plot_number,
1368 name: info.plot_name,
1369 rep: info.rep_number,
1370 block: info.block_number
1373 name: info.accession_name,
1374 id: info.accession_id
1382 alert('Could not lookup trial plots');
1385 error: function(response){
1386 alert('Could not lookup trial plots');
1388 complete: function() {
1394 function _getTraits() {
1397 url: '/ajax/breeders/trial/' + trial_id + '/traits_assayed?stock_type=plot',
1398 success: function(response) {
1399 if (response.error) {
1400 alert(response.error);
1402 else if ( response.traits_assayed ) {
1405 for ( let i = 0; i < response.traits_assayed[0].length; i++ ) {
1406 let id = response.traits_assayed[0][i][0];
1407 let name = response.traits_assayed[0][i][1];
1408 let variable = "trait_" + parseInt(i+1);
1414 html += "<tr><td><code>{" + variable + "}</code></td>";
1415 html += "<td>" + name + "</td></tr>";
1417 jQuery("#create_seedlots_trial_contents_computed_table").html(html);
1420 alert('Could not lookup trial traits');
1423 error: function(response){
1424 alert('Could not lookup trial traits');
1426 complete: function() {
1432 function _finish() {
1434 if ( count >= total ) {
1435 return callback(error, breeding_program_id, accessions, plots, subplots, plants, traits);
1441 * Set the plot-level trait data for all of the traits
1442 * @param {function} callback Callback function()
1444 function getTraitData(callback) {
1448 let total = TRAITS.length;
1450 for ( let i = 0; i < TRAITS.length; i++ ) {
1451 _getTraitData(TRAITS[i].id, TRAITS[i].name);
1458 function _getTraitData(trait_id, trait_name) {
1461 url: '/ajax/breeders/trial/' + TRIAL_ID + '/trait_phenotypes/?trait=' + trait_id + '&display=plot',
1462 success: function(response) {
1463 if (response.error) {
1464 alert(response.error);
1466 else if ( response.status && response.status === 'success' && response.data ) {
1467 _parseTraitData(response.data, trait_id, trait_name);
1470 alert('Could not get trait data');
1473 error: function(response){
1474 alert('Could not get trait data');
1476 complete: function() {
1482 function _parseTraitData(data, trait_id, trait_name) {
1483 let accession_id_col, plot_id_col, trait_col;
1484 let headers = data[0];
1485 for ( let i = 0; i < headers.length; i++ ) {
1486 if ( headers[i] === 'germplasmDbId' ) accession_id_col = i;
1487 if ( headers[i] === 'observationUnitDbId' ) plot_id_col = i;
1488 if ( headers[i] === trait_name ) trait_col = i;
1490 for ( let i = 1; i < data.length; i++ ) {
1492 accession_id: data[i][accession_id_col],
1493 plot_id: data[i][plot_id_col],
1495 trait_value: data[i][trait_col]
1500 function _finish() {
1502 if ( count >= total ) {
1510 .create_seedlots_trial_subplots_available, .create_seedlots_trial_plants_available {
1513 .alert-success, .alert-danger {
1514 padding: 15px !important;
1515 margin-bottom: 20px !important;
1516 border: 1px solid transparent !important;
1517 border-radius: 4px !important;