PageSourceSearch

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

js ivaprime.com collected 2026-10-03 21:38:03 UTC 11,240 bytes, 337 lines download raw bytes

1const Search = new class {
2    constructor() {
3        /**
4         * Search bar
5         */
6        this.searchResults = [];
7        this.searchFocusIndex = null;
8        document.addEventListener('DOMContentLoaded', function() {
9            const searchBar = document.getElementById("search-bar")
10            searchBar.addEventListener("input", function() {
11                Search.search();
12            });
13
14            const searchAACheckbox = document.getElementById("search-aa").checked;
15            Utilities.addInputValidator(
16                searchBar,
17                (searchAACheckbox) ? "aa": "dna",
18            );
19
20            searchBar.addEventListener("keydown", function(event) {
21                if (event.key === "ArrowUp") {
22                    event.preventDefault();
23                    Search.navigateResults(-1);
24                } else if (event.key === "ArrowDown") {
25                    event.preventDefault();
26                    Search.navigateResults(1);
27                };
28            });
29        });
30    };
31
32    /**
33     * Clears the search bar
34     */
35    clear(clearInput=true) {
36        if (clearInput) {
37            document.getElementById("search-bar").value = "";
38        };
39        PlasmidViewer.unhighlightBases("base-search");
40        PlasmidViewer.unhighlightBases("base-search-focus");
41        this.searchResults = [];
42        this.searchFocusIndex = null;
43        this.updateBarInfo();
44    };
45
46
47    /**
48     * Search for DNA or AA sequence in plasmid
49     */
50    search() {
51        const searchBar = document.getElementById("search-bar");
52        const searchAASeq = document.getElementById("search-aa")?.checked;
53        const query = searchBar?.value?.toUpperCase().replace(/\s+/g, "");
54
55        this.clear(false);
56
57        if (!query) return;
58
59        if (searchAASeq) {
60            if (/^[a-zA-Z][0-9]+$/.test(query)) {
61                this.searchSpecificAA(query);
62                return;
63            };
64
65            this.searchAA(query);
66            return;
67        };
68
69
70        this.searchDNA(query);
71        return;
72    };
73
74
75    /**
76     * Search for DNA sequence in plasmid
77     * 
78     * @param {String} query - DNA Sequence
79     */
80    searchDNA(query) {
81        if (!Nucleotides.isNucleotideSequence(query)) return;
82
83        const activePlasmid = Session.activePlasmid();
84        if (!activePlasmid) return;
85
86        const sequences = [activePlasmid.sequence, activePlasmid.complementarySequence];
87        const queries = [query, query.split("").reverse().join("")];
88
89        for (let i = 0; i < 2; i++) {
90            const query = queries[i]
91            const sequence = sequences[i];
92            const strand = ["fwd", "rev"][i]
93            let indices = [];
94            let index = sequence.indexOf(query);
95            while (index !== -1) {
96                indices.push(index);
97                index = sequence.indexOf(query, index + 1);
98            };
99        
100            for (let j = 0; j < indices.length; j++) {
101                const span = [indices[j]+1, indices[j]+query.length];
102                PlasmidViewer.highlightBases(
103                    span,
104                    "base-search",
105                    strand,
106                );
107
108                this.searchResults.push({strand: strand, span: span});
109            };
110        };
111
112        this.findNearestResult();
113        this.focusResult();
114        this.updateBarInfo();
115    };
116
117
118    /**
119     * Search for AA sequence in plasmid
120     * 
121     * @param {String} query - AA sequence
122     */
123    searchAA(query) {
124        const activePlasmid = Session.activePlasmid();
125        if (!activePlasmid) return; // No plasmid loaded
126
127        // Iterate over both strands
128        const sequences = [activePlasmid.sequence, Nucleotides.reverseComplementary(activePlasmid.sequence)];
129        const strands = ["fwd", "rev"];
130        for (let i = 0; i < 2; i++) {
131            const sequence = sequences[i];
132            const strand = strands[i];
133
134            // Get the 3 possible reading frames
135            const dnaFrames = [
136                sequence,
137                sequence.slice(-1) + sequence.slice(0, -1),
138                sequence.slice(-2) + sequence.slice(0, -2)
139            ];
140
141            // Iterate over reading frames
142            for (let j = 0; j < 3; j++) {
143                const dnaFrame = dnaFrames[j];
144                
145                // Translate reading frame
146                let aaFrame = "";
147                for (let k = 0; k+3 <= dnaFrame.length; k += 3) {
148                    aaFrame += Nucleotides.codonTable[dnaFrame.slice(k, k+3)] ?? "X";
149                };
150                console.log(j, aaFrame)
151
152
153                // Search for query and save its index
154                let indices = [];
155                let index = aaFrame.indexOf(query);
156                while (index !== -1) {
157                    if (strand === "fwd") {
158                        const aaIndex = index*3 + 1 - j;
159                        indices.push(aaIndex);
160                    } else {
161                        const aaIndex = index*3 + 1 - j;
162                        indices.push(dnaFrame.length - aaIndex)
163                    }
164                    index = aaFrame.indexOf(query, index + 1);
165                };
166            
167                // Highlight search results
168                for (let k = 0; k < indices.length; k++) {
169                    const span = (strand === "fwd")
170                    ? [indices[k], indices[k] + query.length*3 - 1]
171                    : [indices[k] - query.length*3 + 2, indices[k] + 1]
172                    
173                    console.log("Search.searchAA ->", query, span);
174
175                    PlasmidViewer.highlightBases(
176                        span,
177                        "base-search",
178                        strand,
179                    );
180
181                    this.searchResults.push({strand: strand, span: span});
182                };
183            };
184        };
185
186        this.findNearestResult();
187        this.focusResult();
188        this.updateBarInfo();
189    };
190
191
192    searchSpecificAA(query) {
193        const activePlasmid = Session.activePlasmid();
194        if (!activePlasmid) return; // No plasmid loaded
195
196        const aaCode = query[0].toUpperCase();
197        const aaIndex = parseInt(query.slice(1));
198
199        const matchingAABlocks = document.querySelectorAll(`g[aa-index="${aaIndex - 1}"][aa="${aaCode}"]`);
200        
201        for (let i = 0; i < matchingAABlocks.length; i++) {
202            const currAABlock = matchingAABlocks[i];
203            let aaSpan = currAABlock.getAttribute("aa-span").split(",").map(s => parseInt(s));
204
205
206            let strand = (aaSpan[0] < aaSpan[1]) ? "fwd": "rev";
207            aaSpan.sort()
208
209            PlasmidViewer.highlightBases(
210                aaSpan,
211                "base-search",
212                strand,
213            );
214    
215            this.searchResults.push({strand: strand, span: aaSpan});
216        };
217
218        this.findNearestResult();
219        this.focusResult();
220        this.updateBarInfo();
221    };
222
223
224    findNearestResult() {
225        const gridViewContainer = document.getElementById("grid-view-container");
226
227        const bases = Array.from(gridViewContainer.getElementsByClassName("base-search"));
228
229        const containerRect = gridViewContainer.getBoundingClientRect();
230        let firstBaseInView;
231        for (let i = 0; i < bases.length; i++) {
232            const base = bases[i]
233            const rect = base.getBoundingClientRect();
234            if (
235                rect.top >= containerRect.top &&
236                rect.bottom <= containerRect.bottom
237            ) {
238                firstBaseInView = base;
239            };
240        };
241
242        const targetBase = (firstBaseInView) ? firstBaseInView: bases[0];
243        if (!targetBase) return;
244
245        const targetBaseIndex = parseInt(targetBase.getAttribute("base-index"));
246
247        this.searchResults.sort((a, b) => a.span[0] - b.span[0]);
248        for (let i = 0; i < this.searchResults.length; i++) {
249            const resultSpan = this.searchResults[i].span;
250            if (resultSpan[0] <= targetBaseIndex <= resultSpan[1]) {
251                this.searchFocusIndex = i;
252                break;
253            };
254        };
255    };
256
257
258    navigateResults(increment) {
259        PlasmidViewer.unhighlightBases("base-search-focus");
260        if (this.searchFocusIndex + increment < 0) {
261            this.searchFocusIndex = this.searchResults.length - 1;
262        } else if (this.searchFocusIndex + increment >= this.searchResults.length) {
263            this.searchFocusIndex = 0;
264        } else {
265            this.searchFocusIndex += increment;
266        };
267        this.focusResult();
268        this.updateBarInfo();
269    };
270
271
272    focusResult() {
273        const searchResult = this.searchResults[this.searchFocusIndex];
274        if (!searchResult) return;
275
276        PlasmidViewer.highlightBases(searchResult.span, "base-search-focus", searchResult.strand);
277
278        const container = document.getElementById("grid-view");
279        const containerRect = container.getBoundingClientRect();
280        const basesInSearchResult = Array.from(container.querySelectorAll(".base-search-focus")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
281
282        
283        const basesHeightDifference = basesInSearchResult[basesInSearchResult.length - 1].getBoundingClientRect().bottom
284                                    - basesInSearchResult[0].getBoundingClientRect().top
285        
286        const targetBase = searchResult.strand === "fwd" 
287            ? basesInSearchResult[0] 
288            : basesInSearchResult.at(-1);
289
290        const targetSVG = targetBase.closest("svg");
291        const targetSVGRect = targetSVG.getBoundingClientRect();
292        const targetTop = targetSVGRect.top - containerRect.top + container.scrollTop;
293        const targetBottom = targetSVGRect.bottom - containerRect.top + container.scrollTop;
294        const halfContainer = containerRect.height / 2;
295        const halfBases = basesHeightDifference / 2;
296        
297        let targetHeight;
298        
299        if (basesHeightDifference > containerRect.height) {
300            targetHeight = searchResult.strand === "fwd" 
301                ? targetTop 
302                : targetBottom - containerRect.height;
303        } else {
304            targetHeight = searchResult.strand === "fwd" 
305                ? targetTop - halfContainer + halfBases 
306                : targetBottom - halfContainer - halfBases;
307        };
308
309        document.getElementById("grid-view").scrollTo({ top: targetHeight, behavior: "smooth" });
310    };
311
312
313    updateBarInfo() {
314        const infoSpan = document.getElementById("search-bar-info");
315
316        if (this.searchResults.length === 0) {
317            infoSpan.innerText = "";
318        } else {
319            infoSpan.innerText = (this.searchFocusIndex+1) + " / " + this.searchResults.length;
320        };
321    };
322
323
324    searchAACheckboxCallback(event) {
325        const isChecked = event.target.checked;
326
327        Utilities.addInputValidator(
328            document.getElementById("search-bar"),
329            (isChecked) ? "aaOrSpecific": "dna",
330        );
331
332        let inputEvent = new Event('input', { bubbles: true, });
333        document.getElementById("search-bar").dispatchEvent(inputEvent);
334
335        this.search();
336    };
337};

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.