1const Modals = new class { 2 /** 3 * Create modal window. 4 * 5 * @param {String} id - Modal id 6 * @param {HTMLElement} body - Body HTML 7 */ 8 _create(id, body) { 9 // Create window element 10 const modalWindow = document.createElement("div"); 11 modalWindow.id = id; 12 modalWindow.classList.add("modal-window"); 13 14 // Add body 15 body.classList.add("modal-body"); 16 modalWindow.appendChild(body); 17 18 // Add modal window to modal container 19 const modal = document.querySelector("div.modal"); 20 modal.style.display = "block"; 21 modal.appendChild(modalWindow); 22 23 // Add validators to text inputs 24 const inputElements = modalWindow.querySelectorAll("input[validator], textarea[validator]"); 25 inputElements.forEach((input) => { 26 // Add validator 27 const validatorType = input.getAttribute("validator"); 28 Utilities.addInputValidator(input, validatorType); 29 30 // Add event listener to re-check validator after every input 31 input.addEventListener("input", function () { 32 // Select action button of modal window 33 const actionButton = document.getElementById(`${id}-action-button`); 34 35 // Check to see if there is any input that has failed its validator 36 const inputFailedValidator = modalWindow.querySelector("[incorrect]") !== null; 37 if (inputFailedValidator) { 38 // If any input fails its validator, disable action buttom 39 actionButton.setAttribute("disabled", ""); 40 } else { 41 // Otherwise enable it 42 actionButton.removeAttribute("disabled"); 43 }; 44 }); 45 }); 46 }; 47 48 49 /** 50 * Remove modal window and hide modal overlay 51 * 52 * @param {String} modalWindowId - ID of modal window to remove 53 */ 54 remove(modalWindowId) { 55 const modalWindow = document.getElementById(modalWindowId); 56 const modal = modalWindow.parentNode; 57 modal.style.display = "none" 58 modal.removeChild(modalWindow); 59 }; 60 61 62 /** 63 * Check if the modal overlay is active 64 * 65 * @returns {Boolean} 66 */ 67 isActive() { 68 return document.getElementById("modal").style.display === "block" 69 }; 70 71 72 /** 73 * Create modal title element 74 * 75 * @param {String} title - Title text 76 * @returns 77 */ 78 _createModalTitle(title) { 79 const modalTitle = document.createElement("DIV"); 80 modalTitle.classList.add("modal-title"); 81 modalTitle.innerText = title; 82 83 return modalTitle; 84 }; 85 86 87 /** 88 * Create action and cancel buttons 89 * 90 * @param {String} actionLabel - Text on the action button 91 * @param {String} id - Id of modal window 92 * @param {Function} action - Action to be perforemd when clicking action button 93 * @returns {HTMLElement} 94 */ 95 _createButtons(actionLabel, id, action) { 96 // Container 97 const buttonsContainer = document.createElement("div"); 98 buttonsContainer.classList.add("modal-hgroup"); 99 100 101 // Action button 102 const actionButton = document.createElement("span"); 103 actionButton.classList.add("button-round", "button-green"); 104 actionButton.id = `${id}-action-button`; 105 actionButton.innerText = actionLabel; 106 buttonsContainer.appendChild(actionButton); 107 108 actionButton.addEventListener("click", function (event) { 109 event.preventDefault(); 110 action(); 111 Modals.remove(id); 112 }); 113 114 115 // Cancel button 116 const cancelButton = document.createElement("span"); 117 cancelButton.classList.add("button-round", "button-red"); 118 cancelButton.innerText = "Cancel"; 119 cancelButton.onclick = `Modals.remove('${id}')` 120 buttonsContainer.appendChild(cancelButton); 121 122 cancelButton.addEventListener("click", function (event) { 123 Modals.remove(id); 124 }); 125 126 127 /** 128 * Press action button on enter key 129 * 130 * @param {KeyboardEvent} event 131 */ 132 function modalOnEnterKey(event) { 133 if (event.key === "Enter") { 134 event.preventDefault(); 135 document.getElementById(`${id}-action-button`).click(); 136 document.removeEventListener("keydown", modalOnEnterKey); 137 }; 138 }; 139 document.addEventListener("keydown", modalOnEnterKey); 140 141 142 /** 143 * Close modal window on escape key 144 * 145 * @param {KeyboardEvent} event 146 */ 147 function modalOnEscapeKey(event) { 148 if (event.key === "Escape") { 149 event.preventDefault(); 150 Modals.remove(buttonsContainer.closest(".modal-window").id); 151 document.removeEventListener("keydown", modalOnEscapeKey); 152 }; 153 }; 154 document.addEventListener("keydown", modalOnEscapeKey); 155 156 return buttonsContainer; 157 }; 158 159 160 /** 161 * Create a text input 162 * 163 * @param {String} label - Label of text input 164 * @param {String} id - Id of text input 165 * @param {String} defaultText - Default text in the text input 166 * @param {String} validator - Validator type 167 * @param {String} suffix - Text suffix 168 * @returns 169 */ 170 _createInput(label, id, defaultText="", validator=null, suffix=null) { 171 // Create container 172 const inputContainer = document.createElement("DIV"); 173 inputContainer.classList.add("modal-vgroup"); 174 175 // If label given, create label element 176 if (label) { 177 const inputLabel = document.createElement("label"); 178 inputLabel.innerText = label; 179 inputContainer.appendChild(inputLabel); 180 }; 181 182 183 if (!suffix) {
184 // Input with no suffix 185 186 const input = document.createElement("input"); 187 input.setAttribute("type", "text"); 188 input.setAttribute("id", id); 189 input.classList.add("modal-input"); 190 input.setAttribute("value", defaultText); 191 if (validator) input.setAttribute("validator", validator); 192 inputContainer.appendChild(input); 193 194 return inputContainer; 195 196 } else { 197 // Input with suffix 198 199 const inputWrapper = document.createElement("DIV"); 200 inputWrapper.classList.add("modal-input-wrapper"); 201 inputContainer.appendChild(inputWrapper); 202 203 const input = document.createElement("input"); 204 input.setAttribute("type", "text"); 205 input.setAttribute("id", id); 206 input.classList.add("modal-input", "modal-input-with-suffix"); 207 input.setAttribute("value", defaultText); 208 if (validator) input.setAttribute("validator", validator); 209 inputWrapper.appendChild(input); 210 211 const suffix = document.createElement("div"); 212 suffix.classList.add("modal-input-suffix"); 213 suffix.innerText = suffix; 214 inputWrapper.appendChild(suffix); 215 216 return inputContainer; 217 }; 218 }; 219 220 221 /** 222 * Create the dropdown element for common insertions 223 * 224 * @param {HTMLElement} targetInput - Text input to populate with text when clicking on option 225 * @returns {HTMLElement} 226 */ 227 _createCommonInsertionsDropdown(targetInput) { 228 // Main container 229 const container = document.createElement("div"); 230 container.classList.add("modal-hgroup"); 231 232 233 // Label 234 const label = document.createElement("label"); 235 label.innerText = "Commonly inserted sequences:"; 236 container.appendChild(label); 237 238 239 // Select element 240 const select = document.createElement("select"); 241 select.classList.add("common-insertions-dropdown"); 242 container.appendChild(select); 243 244 // Default option 245 const defaultOption = document.createElement("option"); 246 defaultOption.textContent = "<No sequence selected>"; 247 defaultOption.disabled = true; 248 defaultOption.selected = true; 249 defaultOption.classList.add("common-insertions-group"); 250 select.appendChild(defaultOption); 251 252 // Nr of spaces per indent level in options tree 253 const spacePerLevel = 4; 254 255 /** 256 * Recursively populate the dropdown with options 257 * 258 * @param {Object[]} entries - List of entries to populate with 259 * @param {HTMLElement} optgroup - Parent optgroup 260 * @param {Number} depth - Depth level tracker for indentation 261 */ 262 const addEntriesToOptgroup = (entries, optgroup, depth = 1) => { 263 // Iterate over entries and append them to the parent optgroup 264 entries.forEach(entry => { 265 // Current indent 266 const indent = "\u00A0".repeat(depth * spacePerLevel); 267 268 if (entry.type === "group") { 269 // Render subgroup as a disabled option with indentation 270 const subgroupOption = document.createElement("option"); 271 subgroupOption.textContent = indent + entry.name; 272 subgroupOption.disabled = true; 273 subgroupOption.classList.add("common-insertions-group"); 274 optgroup.appendChild(subgroupOption); 275 276 // Add entries inside this subgroup 277 addEntriesToOptgroup(entry.entries, optgroup, depth + 1); 278 } else if (entry.type === "item") { 279 // Actual item 280 const itemOption = document.createElement("option"); 281 if (entry.aa.length >= 60) { 282 itemOption.classList.add("common-insertions-item-long"); 283 }; 284 itemOption.value = entry.aa; 285 itemOption.setAttribute("feature-label", entry.label); 286 itemOption.textContent = indent + entry.name; 287 optgroup.appendChild(itemOption); 288 } 289 }); 290 }; 291 292 // Iterate over common insertions tree and populate dropdown with top-level optgroups 293 Nucleotides.commonInsertions.forEach(topGroup => { 294 if (topGroup.type === "group") { 295 const optgroup = document.createElement("optgroup"); 296 optgroup.label = topGroup.name; 297 optgroup.classList.add("common-insertions-group"); 298 299 addEntriesToOptgroup(topGroup.entries, optgroup, 1); 300 select.appendChild(optgroup); 301 }; 302 }); 303 304 305 // When selection an option, set the text of the targetInput to the option's value 306 select.addEventListener("change", (event) => { 307 const insertionSeq = event.target.value; 308 targetInput.value = insertionSeq; 309 310 if (insertionSeq.length >= 60) { 311 Alerts.warning( 312 "Long insertion sequence", 313 "Insertions of sequences longer than 60 bp will result in long, expensive primers and cannot be guaranteed to succeed. For longer insertions, subcloning or inserting from a linear fragment may be more appropriate.", 314 15, 315 "orange" 316 ); 317 }; 318 }); 319 320 return container; 321 }; 322 323 324 /** 325 * Create text input for DNA sequences 326 * 327 * @param {String} inputID - ID of input 328 * @param {String} type - Type of input (text or textarea) 329 * @param {String[]}
329 cssClasses - List of CSS classes to add 330 * @param {Boolean} disableSpellcheck - Flag to explicitly disable spellcheck 331 * @returns {HTMLElement} 332 */ 333 _createDNAInput(inputID, type="text", cssClasses=["modal-input"], disableSpellcheck=false) { 334 // Main container 335 const dnaGroup = document.createElement("div"); 336 dnaGroup.classList.add("modal-vgroup"); 337 338 // Label 339 const dnaLabel = document.createElement("label"); 340 dnaLabel.innerText = "DNA sequence:"; 341 dnaGroup.appendChild(dnaLabel); 342 343 // Input 344 const dnaInput = document.createElement("input"); 345 dnaInput.type = type; 346 dnaInput.id = inputID; 347 dnaInput.classList.add(...cssClasses); 348 if (disableSpellcheck) dnaInput.setAttribute("spellcheck", false); 349 dnaInput.setAttribute("validator", "dna"); 350 dnaGroup.appendChild(dnaInput); 351 352 return dnaGroup; 353 }; 354 355 356 /** 357 * Create text input for AA sequences 358 * 359 * @param {String} inputID - ID of input 360 * @param {String} type - Type of input (text or textarea) 361 * @param {String[]} cssClasses - List of CSS classes to add 362 * @param {Boolean} disableSpellcheck - Flag to explicitly disable spellcheck 363 * @returns {HTMLElement} 364 */ 365 _createAAInput(inputID, type="text", cssClasses=["modal-input"], disableSpellcheck=false) { 366 // Main container 367 const aaGroup = document.createElement("div"); 368 aaGroup.classList.add("modal-vgroup"); 369 370 371 // Label 372 const aaLabel = document.createElement("label"); 373 aaLabel.innerText = "Amino acid sequence:"; 374 aaGroup.appendChild(aaLabel); 375 376 377 // Input 378 const aaInput = document.createElement("input"); 379 aaInput.type = type; 380 aaInput.id = inputID; 381 aaInput.classList.add(...cssClasses); 382 if (disableSpellcheck) aaInput.setAttribute("spellcheck", false); 383 aaInput.setAttribute("validator", "aa"); 384 aaGroup.appendChild(aaInput); 385 386 387 // Hint 388 const aaHint = document.createElement("div"); 389 aaHint.classList.add("modal-hint"); 390 aaHint.innerText = 'Accepted STOP letter codes: "*", "-", "X".'; 391 aaGroup.appendChild(aaHint); 392 393 return aaGroup; 394 }; 395 396 397 /** 398 * Create the organism dropdown selector for codon optimization 399 * 400 * @returns {HTMLElement} 401 */ 402 _createCodonOptimizationDropdown() { 403 // Main container 404 const codonGroup = document.createElement("div"); 405 codonGroup.classList.add("modal-vgroup"); 406 407 // Hgroup for label and select 408 const codonHGroup = document.createElement("div"); 409 codonHGroup.classList.add("modal-hgroup"); 410 codonGroup.appendChild(codonHGroup); 411 412 413 // Label 414 const codonLabel = document.createElement("label"); 415 codonLabel.innerText = "Optimize codons for:"; 416 codonHGroup.appendChild(codonLabel); 417 418 419 // Select 420 const select = document.createElement("select"); 421 select.id = "insertion-select-organism"; 422 423 424 // Get current user preference 425 const preferredOrganism = UserPreferences.get("preferredOrganism"); 426 // Iterate over organisms and add them as options 427 for (const key in Nucleotides.codonWeights) { 428 const option = document.createElement("option"); 429 option.value = key; 430 option.textContent = key; 431 if (key === preferredOrganism) option.selected = true; 432 select.appendChild(option); 433 }; 434 codonHGroup.appendChild(select); 435 436 437 // Codon hint 438 const codonHint = document.createElement("div"); 439 codonHint.classList.add("modal-hint"); 440 codonHint.innerHTML = ` 441 Codon frequency tables from 442 <a href="https://hive.biochemistry.gwu.edu/review/codon2" target="_blank">CoCoPUTs</a> 443 (<a href="https://doi.org/10.1016/j.jmb.2019.04.021" target="_blank">Alexaki et al. 2019</a>). 444 `; 445 codonGroup.appendChild(codonHint); 446 447 448 return codonGroup; 449 }; 450 451 452 /**
453 * Create the "translate new feature" checkbox 454 * 455 * @param {String} checkboxID - ID of checkbox 456 * @returns {HTMLElement} 457 */ 458 _createTranslateFeatureCheckbox(checkboxID) { 459 // Container 460 const translateGroup = document.createElement("div"); 461 translateGroup.classList.add("modal-hgroup"); 462 463 464 // Label 465 const translateLabel = document.createElement("label"); 466 translateLabel.innerText = "Translate new feature:"; 467 translateGroup.appendChild(translateLabel); 468 469 470 // Checkbox 471 const translateCheckbox = document.createElement("input"); 472 translateCheckbox.type = "checkbox"; 473 translateCheckbox.id = checkboxID; 474 translateCheckbox.name = checkboxID; 475 translateCheckbox.checked = false; 476 translateGroup.appendChild(translateCheckbox); 477 478 479 return translateGroup; 480 }; 481 482 483 //#region New file 484 /** 485 * New file from sequence modal window 486 */ 487 createNewFileModal() { 488 const id = "modal-window-new-file"; 489 490 const body = document.createElement("div"); 491 body.classList.add("modal-new-file"); 492 493 // Title 494 body.appendChild( 495 this._createModalTitle("Create new file") 496 ); 497 498 // New plasmid name input group 499 const nameGroup = document.createElement("div"); 500 nameGroup.classList.add("modal-vgroup"); 501 body.appendChild(nameGroup); 502 503 const nameLabel = document.createElement("label"); 504 nameLabel.textContent = "New plasmid name:"; 505 nameGroup.appendChild(nameLabel); 506 507 const nameInputWrapper = document.createElement("div"); 508 nameInputWrapper.classList.add("modal-input-wrapper"); 509 nameGroup.appendChild(nameInputWrapper); 510 511 const nameInput = document.createElement("input"); 512 nameInput.type = "text"; 513 nameInput.id = "new-file-name-input"; 514 nameInput.classList.add("modal-input", "modal-input-with-suffix"); 515 nameInput.value = "untitled"; 516 nameInputWrapper.appendChild(nameInput); 517 518 const nameSuffix = document.createElement("div"); 519 nameSuffix.classList.add("modal-input-suffix"); 520 nameSuffix.textContent = ".gb"; 521 nameInputWrapper.appendChild(nameSuffix); 522 523 524 // DNA sequence input group 525 const sequenceGroup = document.createElement("div"); 526 sequenceGroup.classList.add("modal-vgroup", "modal-new-file-sequence-input"); 527 body.appendChild(sequenceGroup); 528 529 const sequenceLabel = document.createElement("label"); 530 sequenceLabel.textContent = "DNA Sequence:"; 531 sequenceGroup.appendChild(sequenceLabel); 532 533 const sequenceTextarea = document.createElement("textarea"); 534 sequenceTextarea.id = "new-file-sequence-input"; 535 sequenceTextarea.classList.add("modal-input", "modal-textarea"); 536 sequenceTextarea.spellcheck = false; 537 sequenceTextarea.setAttribute("validator", "iupacBases"); 538 sequenceGroup.appendChild(sequenceTextarea); 539 540 541 // Annotate features checkbox 542 const annotateGroup = document.createElement("div"); 543 annotateGroup.classList.add("modal-hgroup"); 544 body.appendChild(annotateGroup); 545 546 const annotateLabel = document.createElement("label"); 547 annotateLabel.textContent = "Annotate common features:"; 548 annotateGroup.appendChild(annotateLabel); 549 550 const annotateCheckbox = document.createElement("input"); 551 annotateCheckbox.type = "checkbox"; 552 annotateCheckbox.id = "new-file-annotate-features-checkbox"; 553 annotateCheckbox.name = "new-file-annotate-features-checkbox"; 554 annotateCheckbox.checked = true; 555 annotateGroup.appendChild(annotateCheckbox); 556 557 558 // Sequence topology dropdown 559 const topologyGroup = document.createElement("div"); 560 topologyGroup.classList.add("modal-hgroup"); 561 body.appendChild(topologyGroup); 562 563 const topologyLabel = document.createElement("label"); 564 topologyLabel.textContent = "Sequence topology:"; 565 topologyGroup.appendChild(topologyLabel); 566 567 const topologySelect = document.createElement("select"); 568 topologySelect.id = "new-file-topology-select"; 569 topologyGroup.appendChild(topologySelect); 570 571 const linearOption = document.createElement("option"); 572 linearOption.value = "linear"; 573 linearOption.textContent = "Linear"; 574 linearOption.selected = true; 575 topologySelect.appendChild(linearOption); 576 577 const circularOption = document.createElement("option"); 578 circularOption.value = "circular"; 579 circularOption.textContent = "Circular"; 580 topologySelect.appendChild(circularOption); 581 582 583 // Action button 584 const action = FileIO.newFileFromSequence.bind(FileIO); 585 body.appendChild( 586 this._createButtons("Create file", id, action) 587 ); 588 589 // Create the modal 590 this._create(id, body, action); 591 }; 592 593 594 //#region Rename plasmid 595 /** 596 * Create the rename plasmid modal window 597 * 598 * @param {Plasmid} targetPlasmid - Target plasmid object 599 */ 600 createRenamePlasmidModal(targetPlasmid) {
601 const id = "modal-window-rename-plasmid"; 602 603 const body = document.createElement("div"); 604 605 // Title 606 body.appendChild( 607 this._createModalTitle("Rename plasmid") 608 ); 609 610 const vgroup = document.createElement("div"); 611 vgroup.classList.add("modal-vgroup"); 612 body.appendChild(vgroup); 613 614 const nameLabel = document.createElement("label"); 615 nameLabel.textContent = "New plasmid name:"; 616 vgroup.appendChild(nameLabel); 617 618 const inputWrapper = document.createElement("div"); 619 inputWrapper.classList.add("modal-input-wrapper"); 620 vgroup.appendChild(inputWrapper); 621 622 const nameInput = document.createElement("input"); 623 nameInput.type = "text"; 624 nameInput.id = `${id}-input`; 625 nameInput.classList.add("modal-input", "modal-input-with-suffix"); 626 nameInput.value = targetPlasmid.name; 627 inputWrapper.appendChild(nameInput); 628 629 630 if (targetPlasmid.extension) { 631 const suffixDiv = document.createElement("div"); 632 suffixDiv.classList.add("modal-input-suffix"); 633 suffixDiv.textContent = targetPlasmid.extension; 634 inputWrapper.appendChild(suffixDiv); 635 }; 636 637 638 const action = () => { 639 const newName = document.getElementById(`${id}-input`).value; 640 targetPlasmid.rename(newName); 641 }; 642 643 body.appendChild( 644 this._createButtons("Rename", id, action) 645 ); 646 647 this._create(id, body, action); 648 }; 649 650 651 //#region Rename primers 652 /** 653 * Creat the reanme primers modal window 654 * 655 * @param {Number} primerSetIndex 656 */ 657 createRenamePrimersModal(primerSetIndex) { 658 const id = "rename-primers"; 659 660 // Modal window body 661 const modalBody = document.createElement("div"); 662 modalBody.classList.add("modal-wide"); 663 664 // Title 665 modalBody.appendChild( 666 this._createModalTitle("Rename primers") 667 ); 668 669 // Select primer set to rename 670 const primerSet = Session.activePlasmid().primers[primerSetIndex]; 671 672 // Rename primer set input 673 const renamePrimerSet = this._createInput("Set name", `${id}-input`, primerSet.title, null, null); 674 modalBody.appendChild(renamePrimerSet); 675 676 // Container for primer name inputs 677 const individualPrimersContainer = document.createElement("div"); 678 //individualPrimersContainer.style.marginLeft = "20px"; 679 modalBody.appendChild(individualPrimersContainer); 680 681 682 /** 683 * Update synced primer name inputs based on primer set name input 684 */ 685 const syncStates = {}; 686 const updateSyncedPrimers = () => { 687 // Get name of primer set 688 const baseName = document.getElementById(`${id}-input`).value; 689 690 // Iterate over primer name inputs 691 const nrOfInputs = document.getElementById(id).querySelectorAll("input").length; 692 for (let i = 0; i < nrOfInputs - 1; i++) { 693 // Select primer name input 694 const primerNameInput = document.getElementById(`${id}-input${i}`); 695 696 // Select the sync button 697 const primerNameInputSync = document.getElementById(`${id}-input${i}-sync`); 698 699 // If the sync button is active, sync the primer name 700 if (primerNameInputSync.hasAttribute("active")) { 701 const suffix = primerNameInput.getAttribute("suffix"); 702 primerNameInput.value = baseName + suffix; 703 }; 704 }; 705 }; 706 // Update synced primer names whenever the user changes the primer set name 707 renamePrimerSet.addEventListener("input", updateSyncedPrimers); 708 709 // Iterate over primers in the primer set and create their text inputs 710 for (let i = 0; i < primerSet.primers.length; i++) { 711 // Select primer 712 const primer = primerSet.primers[i]; 713 714 // Container 715 const vGroup = document.createElement("div"); 716 vGroup.classList.add("modal-vgroup"); 717 individualPrimersContainer.appendChild(vGroup); 718 719 // Label 720 const label = document.createElement("label"); 721 label.innerText = `${primer.name}:`; 722 vGroup.appendChild(label); 723 724 // Hgroup for sync button and text input 725 const hGroup = document.createElement("div"); 726 hGroup.classList.add("modal-hgroup"); 727 hGroup.style.height = "40px"; 728 individualPrimersContainer.appendChild(hGroup); 729 730 731 /** 732 * Sync button 733 */ 734 syncStates[i] = true; 735 const syncToggle = document.createElement("div"); 736 syncToggle.id = `${id}-input${i}-sync`; 737 syncToggle.classList.add( 738 "modal-input-sync", 739 "toolbar-button", 740 "footer-button", 741 ); 742 syncToggle.setAttribute("active", ""); 743 hGroup.appendChild(syncToggle); 744 745 const syncIcon = document.createElement("span"); 746 syncIcon.classList.add("modal-input-sync-icon"); 747 syncToggle.appendChild(syncIcon); 748 749 // Sync toggle click handler 750 syncToggle.addEventListener("click", () => { 751 syncStates[i] = !syncStates[i]; 752 753 if (syncStates[i]) { 754 syncToggle.setAttribute("active", "") 755 updateSyncedPrimers(); 756 } else { 757 syncToggle.removeAttribute("active"); 758 }; 759 }); 760 761 /** 762 * Input 763 */ 764 const input = document.createElement("input"); 765 input.type = "text"; 766 input.id = `${id}-input${i}`; 767 input.classList.add("modal-input"); 768 input.value = primer.label;
769 input.style.flexGrow = 1; 770 input.setAttribute( 771 "suffix", 772 { 773 "Forward primer": "_fwd", 774 "Reverse primer": "_rev", 775 "Vector forward primer": "_vec_fwd", 776 "Vector reverse primer": "_vec_rev", 777 }[primer.name] 778 ); 779 hGroup.appendChild(input); 780 }; 781 782 783 /** 784 * On action, get the new names from the text inputs and pass them to the plasmid 785 */ 786 const action = () => { 787 // Prime set name 788 const newPrimerSetName = document.getElementById(`${id}-input`).value; 789 790 // Iterate over the primer text inputs and get their values 791 const nrOfInputs = document.getElementById(id).querySelectorAll("input").length; 792 const newPrimerNames = []; 793 for (let i = 0; i < nrOfInputs - 1; i++) { 794 newPrimerNames.push( 795 document.getElementById(`${id}-input${i}`).value 796 ); 797 }; 798 799 // Rename primer set 800 Session.activePlasmid().renamePrimerSet(primerSetIndex, newPrimerSetName, newPrimerNames); 801 }; 802 803 804 // Action and cancel buttons 805 modalBody.appendChild( 806 this._createButtons( 807 "Rename", 808 id, 809 action, 810 ) 811 ); 812 813 this._create(id, modalBody, action); 814 }; 815 816 817 //#region Insertion 818 /** 819 * Create the modal window for insertions and mutations 820 * 821 * @param {String} type - Type of insertion modal window ("insertion" or "mutation") 822 */ 823 createInsertionModal(type="insertion") { 824 const id = "modal-window-insertion"; 825 826 827 const body = document.createElement("div"); 828 body.classList.add("modal-insertion"); 829 830 831 // Title 832 body.appendChild( 833 this._createModalTitle((type === "insertion") ? "Insert here": "Mutate selection") 834 ); 835 836 837 // Create text inputs 838 const dnaSeqInput = this._createDNAInput("insertion-input-dna"); 839 const aaSeqInput = this._createAAInput("insertion-input-aa"); 840 const commonInsertionsDropdown = this._createCommonInsertionsDropdown(aaSeqInput.querySelector(".modal-input")); 841 body.appendChild(commonInsertionsDropdown); 842 body.appendChild(dnaSeqInput); 843 body.appendChild(aaSeqInput); 844 845 846 // Codon optimization dropdown 847 body.appendChild( 848 this._createCodonOptimizationDropdown() 849 ); 850 851 852 // Translate feature checkbox 853 body.appendChild( 854 this._createTranslateFeatureCheckbox("insertion-checkbox-translate") 855 ); 856 857 // Linear fragment hint 858 const linFragHint = document.createElement("div"); 859 linFragHint.classList.add("modal-hgroup", "lin-frag-hint"); 860 linFragHint.id = "lin-frag-hint"; 861 862 const linFragSpan = document.createElement("span"); 863 linFragSpan.classList.add("lin-frag-hint-span"); 864 linFragSpan.innerHTML = ` 865 The sequence you are trying to insert is very long. We recommend generating and ordering a linear fragment of dsDNA with overhangs to use as the insert. 866 In the context menu, select "Insert from linear fragment" instead. An explanation of how insertions from linear fragments work can be found in the 867 <a href="/about#lin-frag" target="_blank" class="underlined-link">About</a> page. 868 `; 869 linFragHint.appendChild(linFragSpan); 870 body.appendChild(linFragHint); 871 872 873 // Add event listeners to constantly check if the sequences are too long 874 // If they are too long, show the hint about using linear fragments 875 [dnaSeqInput, aaSeqInput].forEach((input) => { 876 input.addEventListener("input", function (e) { 877 const linFragHint = document.getElementById("lin-frag-hint"); 878 if (input.value && input.value.length > 400) { 879 linFragHint.setAttribute("visible", ""); 880 } else { 881 linFragHint.removeAttribute("visible") 882 }; 883 }); 884 }); 885 886 887 /** 888 * Action 889 */ 890 const action = () => { 891 // Get new feature name if the user selection a common insertion 892 const selectElement = commonInsertionsDropdown.querySelector(".common-insertions-dropdown"); 893 const selectedOption = selectElement.options[selectElement.selectedIndex]; 894 const newFeatureName = (selectedOption.hasAttribute("feature-label")) ? selectedOption.getAttribute("feature-label"): null; 895 896 // Perform IVA operation 897 Session.activePlasmid().IVAOperation( 898 (type === "insertion") ? "Insertion": "Mutation", 899 document.getElementById("insertion-input-dna").value, 900 document.getElementById("insertion-input-aa").value, 901 document.getElementById("insertion-select-organism").value, 902 document.getElementById("insertion-checkbox-translate").checked, 903 newFeatureName, 904 ); 905 }; 906 907 908 // Action and cancel buttons 909 body.appendChild( 910 this._createButtons("Create primers", id, action) 911 ); 912 913 this._create(id, body, action); 914 }; 915 916 917 //#region Subcloning 918 /** 919 * Creat the subcloning with insertions modal window 920 */ 921 createSubcloningModal() {
922 const id = "modal-window-subcloning"; 923 924 925 const body = document.createElement("div"); 926 body.classList.add("modal-subcloning"); 927 928 929 // Modal title 930 body.appendChild( 931 this._createModalTitle("Subclone with insertions") 932 ); 933 934 935 // 5' end 936 const modalSubtitle5Prime = document.createElement("div"); 937 modalSubtitle5Prime.classList.add("modal-subcloning-subtitle"); 938 modalSubtitle5Prime.innerText = "5' end insertion:"; 939 body.appendChild(modalSubtitle5Prime); 940 941 942 const dnaSeqInput5Prime = this._createDNAInput("insertion-input-5dna"); 943 const aaSeqInput5Prime = this._createAAInput("insertion-input-5aa"); 944 const commonInsertionsDropdown5Prime = this._createCommonInsertionsDropdown(aaSeqInput5Prime.querySelector(".modal-input")); 945 body.appendChild(commonInsertionsDropdown5Prime); 946 body.appendChild(dnaSeqInput5Prime); 947 body.appendChild(aaSeqInput5Prime); 948 949 950 // 3' end 951 const modalSubtitle3Prime = document.createElement("div"); 952 modalSubtitle3Prime.classList.add("modal-subcloning-subtitle"); 953 modalSubtitle3Prime.innerText = "3' end insertion:"; 954 body.appendChild(modalSubtitle3Prime); 955 956 const dnaSeqInput3Prime = this._createDNAInput("insertion-input-3dna"); 957 const aaSeqInput3Prime = this._createAAInput("insertion-input-3aa"); 958 const commonInsertionsDropdown3Prime = this._createCommonInsertionsDropdown(aaSeqInput3Prime.querySelector(".modal-input")); 959 body.appendChild(commonInsertionsDropdown3Prime); 960 body.appendChild(dnaSeqInput3Prime); 961 body.appendChild(aaSeqInput3Prime); 962 963 964 // Codon optimization dropdown 965 body.appendChild( 966 this._createCodonOptimizationDropdown() 967 ); 968 969 970 // Translate feature checkbox 971 body.appendChild( 972 this._createTranslateFeatureCheckbox("insertion-checkbox-translate") 973 ); 974 975 976 const action = () => { 977 Session.activePlasmid().IVAOperation( 978 "Subcloning", 979 [ 980 document.getElementById("insertion-input-5dna").value, 981 document.getElementById("insertion-input-3dna").value, 982 ], 983 [ 984 document.getElementById("insertion-input-5aa").value, 985 document.getElementById("insertion-input-3aa").value, 986 ], 987 document.getElementById("insertion-select-organism").value, 988 document.getElementById("insertion-checkbox-translate").checked, 989 ); 990 }; 991 992 993 body.appendChild( 994 this._createButtons("Create primers", id, action) 995 ); 996 997 998 this._create(id, body, action); 999 }; 1000 1001 1002 //#region Insert from linear fragment 1003 /** 1004 * Create insert from linear fragment modal window 1005 */ 1006 createInsertFromLinearFragmentModal() { 1007 const id = "modal-window-insertion"; 1008 1009 1010 const body = document.createElement("div"); 1011 body.classList.add("modal-insertion"); 1012 1013 1014 // Modal title 1015 body.appendChild( 1016 this._createModalTitle("Insert from linear fragment") 1017 ); 1018 1019 1020 // New plasmid name 1021 const newPlasmidNameVGroup = document.createElement("div"); 1022 newPlasmidNameVGroup.classList.add("modal-vgroup"); 1023 body.appendChild(newPlasmidNameVGroup); 1024 1025 const newPlasmidNameLabel = document.createElement("label"); 1026 newPlasmidNameLabel.innerText = "New plasmid name:"; 1027 newPlasmidNameVGroup.appendChild(newPlasmidNameLabel); 1028 1029 const newPlasmidNameInputWrapper = document.createElement("div"); 1030 newPlasmidNameInputWrapper.classList.add("modal-input-wrapper"); 1031 newPlasmidNameVGroup.appendChild(newPlasmidNameInputWrapper) 1032 1033 const newPlasmidNameInput = document.createElement("input"); 1034 newPlasmidNameInput.setAttribute("type", "text") 1035 newPlasmidNameInput.id = "lin-frag-input-name"; 1036 newPlasmidNameInput.classList.add("modal-input", "modal-input-with-suffix"); 1037 newPlasmidNameInput.setAttribute("value", "Linear fragment"); 1038 newPlasmidNameInputWrapper.appendChild(newPlasmidNameInput); 1039 1040 const newPlasmidNameInputSuffix = document.createElement("div"); 1041 newPlasmidNameInputSuffix.classList.add("modal-input-suffix"); 1042 newPlasmidNameInputSuffix.innerText = ".fasta"; 1043 newPlasmidNameInputWrapper.appendChild(newPlasmidNameInputSuffix); 1044 1045 1046 body.appendChild( 1047 this._createDNAInput("insertion-input-dna", "textarea", ["modal
1047-input", "modal-textarea"], true) 1048 ); 1049 1050 1051 body.appendChild( 1052 this._createAAInput("insertion-input-aa", "textarea", ["modal-input", "modal-textarea"], true) 1053 ); 1054 1055 1056 // Codon optimization dropdown 1057 body.appendChild( 1058 this._createCodonOptimizationDropdown() 1059 ); 1060 1061 1062 // Translate feature checkbox 1063 body.appendChild( 1064 this._createTranslateFeatureCheckbox("insertion-checkbox-translate") 1065 ); 1066 1067 1068 const action = () => { 1069 Session.activePlasmid().IVAOperation( 1070 "Linear fragment", 1071 document.getElementById("insertion-input-dna").value, 1072 document.getElementById("insertion-input-aa").value, 1073 document.getElementById("insertion-select-organism").value, 1074 document.getElementById("insertion-checkbox-translate").checked, 1075 null, 1076 document.getElementById("lin-frag-input-name").value, 1077 ); 1078 }; 1079 1080 1081 body.appendChild( 1082 this._createButtons("Create primers and linear fragment", id, action) 1083 ); 1084 1085 1086 this._create(id, body, action); 1087 }; 1088 1089 1090 //#region Set origin 1091 /** 1092 * Create the set plasmid origin modal window 1093 * 1094 * @param {Plasmid} targetPlasmid - Target plasmid object 1095 */ 1096 createSetOriginModal(targetPlasmid) { 1097 const id = "modal-window-set-origin"; 1098 1099 const body = document.createElement("div"); 1100 1101 body.appendChild( 1102 this._createModalTitle("Set new plasmid origin") 1103 ) 1104 1105 //Input 1106 const vGroup = document.createElement("div"); 1107 vGroup.classList.add("modal-vgroup"); 1108 body.appendChild(vGroup); 1109 1110 const label = document.createElement("label"); 1111 label.innerText = "New origin:"; 1112 vGroup.appendChild(label); 1113 1114 const input = document.createElement("input"); 1115 input.setAttribute("type", "number"); 1116 input.id = `${id}-input`; 1117 input.classList.add("modal-input"); 1118 input.setAttribute("min", "1"); 1119 input.setAttribute("max", targetPlasmid.sequence.length); 1120 input.setAttribute("step", "1"); 1121 input.setAttribute("value", "1"); 1122 vGroup.appendChild(input); 1123 1124 1125 const action = () => { 1126 targetPlasmid.setOrigin(document.getElementById(`${id}-input`).value); 1127 }; 1128 1129 1130 body.appendChild( 1131 this._createButtons("Set origin", id, action) 1132 ); 1133 1134 1135 this._create(id, body, action); 1136 }; 1137 1138 1139 //#region Set file topology 1140 /** 1141 * Create the set file topology modal window 1142 * 1143 * @param {Plasmid} targetPlasmid - Target plasmid 1144 */ 1145 createSetFileTopologyModal(targetPlasmid) { 1146 const body = document.createElement("div"); 1147 const id = "modal-window-set-file-topology"; 1148 1149 const currTopology = targetPlasmid.topology; 1150 1151 // Modal title 1152 body.appendChild( 1153 this._createModalTitle("Set file topology") 1154 ); 1155 1156 // HGroup container 1157 const hgroup = document.createElement("div"); 1158 hgroup.classList.add("toolbar-panel-section-hgroup-input"); 1159 hgroup.style.padding = "10px 0px"; 1160 hgroup.style.height = "25px"; 1161 body.appendChild(hgroup); 1162 1163 // "Linear" label 1164 const linearLabel = document.createElement("span"); 1165 linearLabel.textContent = "Linear"; 1166 hgroup.appendChild(linearLabel); 1167 1168 // Switch wrapper 1169 const switchWrapper = document.createElement("div"); 1170 switchWrapper.classList.add("boolean-switch-wrapper"); 1171 hgroup.appendChild(switchWrapper); 1172 1173 const switchLabel = document.createElement("label"); 1174 switchLabel.classList.add("boolean-switch"); 1175 switchWrapper.appendChild(switchLabel); 1176 1177 const switchInput = document.createElement("input"); 1178 switchInput.id = `${id}-input`; 1179 switchInput.type = "checkbox"; 1180 switchInput.classList.add("boolean-switch-input"); 1181 if (currTopology === "circular") switchInput.checked = true; 1182 switchLabel.appendChild(switchInput); 1183 1184 const switchSlider = document.createElement("span"); 1185 switchSlider.classList.add("boolean-switch-slider"); 1186 switchLabel.appendChild(switchSlider); 1187 1188 1189 // "Circular." label 1190 const circularLabel = document.createElement("span"); 1191 circularLabel.textContent = "Circular."; 1192 hgroup.appendChild(circularLabel); 1193 1194 1195 const action = () => { 1196 const newToplogy = (document.getElementById(`${id}-input`).checked) 1197 ? "circular" 1198 : "linear"; 1199 targetPlasmid.setTopology(newToplogy); 1200 }; 1201 1202 1203 body.appendChild( 1204 this._createButtons("Set topology", id, action) 1205 ); 1206 1207 1208 this._create(id, body, action); 1209 }; 1210};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.