PageSourceSearch

https://ivaprime.com/static/js/modals.js

js ivaprime.com collected 2026-10-03 21:37:53 UTC 42,109 bytes, 1,210 lines download raw bytes

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.