A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 43KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="fr">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>How to create a search page for a static website with vanilla JS (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Documented, feel free to shoot an email. -->
  28. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  29. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  30. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  31. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  32. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  36. <script>
  37. function toggleTheme(themeName) {
  38. document.documentElement.classList.toggle(
  39. 'forced-dark',
  40. themeName === 'dark'
  41. )
  42. document.documentElement.classList.toggle(
  43. 'forced-light',
  44. themeName === 'light'
  45. )
  46. }
  47. const selectedTheme = localStorage.getItem('theme')
  48. if (selectedTheme !== 'undefined') {
  49. toggleTheme(selectedTheme)
  50. }
  51. </script>
  52. <meta name="robots" content="noindex, nofollow">
  53. <meta content="origin-when-cross-origin" name="referrer">
  54. <!-- Canonical URL for SEO purposes -->
  55. <link rel="canonical" href="https://gomakethings.com/how-to-create-a-search-page-for-a-static-website-with-vanilla-js/">
  56. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  57. <article>
  58. <header>
  59. <h1>How to create a search page for a static website with vanilla JS</h1>
  60. </header>
  61. <nav>
  62. <p class="center">
  63. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  64. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  65. </svg> Accueil</a> •
  66. <a href="https://gomakethings.com/how-to-create-a-search-page-for-a-static-website-with-vanilla-js/" title="Lien vers le contenu original">Source originale</a>
  67. </p>
  68. </nav>
  69. <hr>
  70. <p>One of the biggest missing features from most static site generators (like <a href="https://gohugo.io/">Hugo</a>, <a href="https://www.11ty.io/">11ty</a>, and <a href="https://jekyllrb.com/">Jekyll</a>, ) is that they lack built-in search.</p>
  71. <p>Database-driven platforms like WordPress make a server call and search the database to find matching content. Static websites have no database to query.</p>
  72. <p>Today, I’m going to share how I built <a href="https://gomakethings.com/search/">the search functionality for my site</a> with vanilla JS. Let’s dig in!</p>
  73. <h2 id="quick-aside-done-for-you-alternative">Quick aside: done-for-you alternative</h2>
  74. <p>If you don’t want to roll-your-own search functionality, <a href="https://www.algolia.com/">Algolia</a> and <a href="https://www.elastic.co/">ElasticSearch</a> are two done-for-you search vendors.</p>
  75. <p>They both offer free tiers, as well as paid versions with more advanced features.</p>
  76. <p>But, because I like to <del>do things the hard way</del> have more control over the user experience, I wrote my own search functionality instead of using one of them.</p>
  77. <h2 id="the-search-form">The Search Form</h2>
  78. <p>My search functionality starts as a progressively enhanced search form.</p>
  79. <div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">form</span> <span class="na">action</span><span class="o">=</span><span class="s">"https://duckduckgo.com/"</span> <span class="na">method</span><span class="o">=</span><span class="s">"get"</span> <span class="na">id</span><span class="o">=</span><span class="s">"form-search"</span><span class="p">&gt;</span>
  80. <span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">"input-search"</span><span class="p">&gt;</span>Enter your search criteria:<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
  81. <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"text"</span> <span class="na">name</span><span class="o">=</span><span class="s">"q"</span> <span class="na">id</span><span class="o">=</span><span class="s">"input-search"</span><span class="p">&gt;</span>
  82. <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">"hidden"</span> <span class="na">name</span><span class="o">=</span><span class="s">"sites"</span> <span class="na">value</span><span class="o">=</span><span class="s">"YourAwesomeWebsite.com"</span><span class="p">&gt;</span>
  83. <span class="p">&lt;</span><span class="nt">button</span><span class="p">&gt;</span>Search<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
  84. <span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span></code></pre></div>
  85. <p>If the JavaScript fails (or the user tries to search before it loads), this will open up <a href="https://duckduckgo.com/">Duck Duck Go</a> and search for articles only on my site.</p>
  86. <p>Be sure to replace <code>YourAwesomeWebsite.com</code> with the actual URL to your site.</p>
  87. <p>We’ll also add two additional elements to the page. The <code>#search-results</code> element is where we’ll inject the actual search results. The <code>#search-status</code> element is where we’ll display the number of items found.</p>
  88. <p>We want this to announce to screen readers, so we’ll also add the <code>[role="status"]</code> attribute to it.</p>
  89. <div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">"search-status"</span> <span class="na">role</span><span class="o">=</span><span class="s">"status"</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
  90. <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">"search-results"</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></code></pre></div>
  91. <h2 id="creating-a-search-index">Creating a search index</h2>
  92. <p>In order to search your site, we need to create an index of content.</p>
  93. <p>The process for this varies from one static site generator to another, but the end result is the same. You want to generate an array of all of the searchable content on your site.</p>
  94. <p>Some people create an external JSON file for this, but I prefer to embed it as a JavaScript variable directly on the search page. it looks like this:</p>
  95. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">let</span> <span class="nx">searchIndex</span> <span class="o">=</span> <span class="p">[</span>
  96. <span class="p">{</span>
  97. <span class="nx">title</span><span class="o">:</span> <span class="s2">"My awesome article"</span><span class="p">,</span>
  98. <span class="nx">date</span><span class="o">:</span> <span class="s2">"December 18, 2018"</span><span class="p">,</span>
  99. <span class="nx">url</span><span class="o">:</span> <span class="s2">"https://gomakethings.com/my-awesome-article"</span><span class="p">,</span>
  100. <span class="nx">content</span><span class="o">:</span> <span class="s2">"The full text of the content..."</span><span class="p">,</span>
  101. <span class="nx">summary</span><span class="o">:</span> <span class="s2">"A short summary or preview of the content (can also be a clipped version of the first few sentences)..."</span>
  102. <span class="p">},</span>
  103. <span class="c1">// More content...
  104. </span><span class="c1"></span><span class="p">];</span>
  105. </code></pre></div>
  106. <p>We can use this to both search for articles and generate results on the page.</p>
  107. <h2 id="creating-a-search-function">Creating a search function</h2>
  108. <p>Next, let’s create a function to actually <em>do</em> the searching. This can be <a href="https://gomakethings.com/the-many-ways-to-write-an-immediately-invoked-function-expression-iife-in-javascript/">an IIFE</a> or a named function. We just want a way to <a href="https://gomakethings.com/how-scope-works-in-javascript/">scope our code</a>.</p>
  109. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
  110. <span class="c1">// Code will go here...
  111. </span><span class="c1"></span><span class="p">})();</span>
  112. </code></pre></div>
  113. <p>Next, we need to get the needed elements from the DOM. We can do that with the <code>document.querySelector()</code> method.</p>
  114. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
  115. <span class="c1">// Get the DOM elements
  116. </span><span class="c1"></span> <span class="kd">let</span> <span class="nx">form</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">'#form-search'</span><span class="p">);</span>
  117. <span class="kd">let</span> <span class="nx">input</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">'#input-search'</span><span class="p">);</span>
  118. <span class="kd">let</span> <span class="nx">resultList</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">'#search-results'</span><span class="p">);</span>
  119. <span class="kd">let</span> <span class="nx">searchStatus</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">'#search-status'</span><span class="p">);</span>
  120. <span class="p">})();</span>
  121. </code></pre></div>
  122. <p>If we can’t find any of them, or if the <code>searchIndex</code> doesn’t exist, we’ll <code>return</code> to stop the function from doing anything else.</p>
  123. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
  124. <span class="c1">// Get the DOM elements
  125. </span><span class="c1"></span> <span class="kd">let</span> <span class="nx">form</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">'#form-search'</span><span class="p">);</span>
  126. <span class="kd">let</span> <span class="nx">input</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">'#input-search'</span><span class="p">);</span>
  127. <span class="kd">let</span> <span class="nx">resultList</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">'#search-results'</span><span class="p">);</span>
  128. <span class="kd">let</span> <span class="nx">searchStatus</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">'#search-status'</span><span class="p">);</span>
  129. <span class="c1">// Make sure required content exists
  130. </span><span class="c1"></span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">form</span> <span class="o">||</span> <span class="o">!</span><span class="nx">input</span> <span class="o">||</span> <span class="o">!</span><span class="nx">resultList</span> <span class="o">||</span> <span class="o">!</span><span class="nx">searchStatus</span> <span class="o">||</span> <span class="o">!</span><span class="nx">searchIndex</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
  131. <span class="p">})();</span>
  132. </code></pre></div>
  133. <h2 id="running-a-search">Running a search</h2>
  134. <p>Next, we need to detect when the user searches for something. To do that, we’ll listen for <code>submit</code> events on the <code>form</code> element.</p>
  135. <p>(<em>The rest of the code all happens inside the IIFE, but I’m sharing just the relevant stuff to make it easier to read.</em>)</p>
  136. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="c1">// Create a submit handler
  137. </span><span class="c1"></span><span class="nx">form</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">'submit'</span><span class="p">,</span> <span class="nx">submitHandler</span><span class="p">);</span>
  138. </code></pre></div>
  139. <p>In the <code>submitHandler()</code> function, we’ll use the <code>event.preventDefault()</code> method to stop the form from submitting to Duck Duck Go. Then, we’ll pass the <code>input.value</code> into a <code>search()</code> function that will actually look for results.</p>
  140. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="cm">/**
  141. </span><span class="cm"> * Handle submit events
  142. </span><span class="cm"> */</span>
  143. <span class="kd">function</span> <span class="nx">submitHandler</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
  144. <span class="nx">event</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
  145. <span class="nx">search</span><span class="p">(</span><span class="nx">input</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
  146. <span class="p">}</span>
  147. </code></pre></div>
  148. <h2 id="searching-for-results">Searching for results</h2>
  149. <p>Here’s where stuff gets a bit messy.</p>
  150. <p>Rather than search for complete phrases, we want to look at each word from the search query, and look for it in the titles and content of our articles. We want to ignore case, and we probably also want to ignore common words like <code>a</code>, <code>an</code>, and <code>the</code>.</p>
  151. <p>I use <a href="https://gomakethings.com/converting-strings-to-uppercase-and-lowercase-with-vanilla-javascript/">the <code>String.toLowerCase()</code> method</a> to convert the <code>query</code> to lowercase. Then, I use <a href="https://gomakethings.com/getting-an-array-from-a-string-with-vanilla-js/">the <code>String.split()</code> method</a> to convert it to an array, with each word as its own item.</p>
  152. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="cm">/**
  153. </span><span class="cm"> * Search for matches
  154. </span><span class="cm"> * @param {String} query The term to search for
  155. </span><span class="cm"> */</span>
  156. <span class="kd">function</span> <span class="nx">search</span> <span class="p">(</span><span class="nx">query</span><span class="p">)</span> <span class="p">{</span>
  157. <span class="c1">// Create a regex for each query
  158. </span><span class="c1"></span> <span class="kd">let</span> <span class="nx">regMap</span> <span class="o">=</span> <span class="nx">query</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">().</span><span class="nx">split</span><span class="p">(</span><span class="s1">' '</span><span class="p">);</span>
  159. <span class="p">}</span>
  160. </code></pre></div>
  161. <p>Next, I created an array of <code>stopWords</code>: words that should be ignored. I found a list on the web, and modified it based on the type of content I have on my site.</p>
  162. <p>For example, I added <code>vanilla</code>, <code>javascript</code>, and <code>js</code> to my list, since almost every article I write includes those words heavily, making them meaningless.</p>
  163. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="kd">let</span> <span class="nx">stopWords</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'a'</span><span class="p">,</span> <span class="s1">'an'</span><span class="p">,</span> <span class="s1">'and'</span><span class="p">,</span> <span class="s1">'are'</span><span class="p">,</span> <span class="s1">'aren\'t'</span><span class="p">,</span> <span class="s1">'as'</span><span class="p">,</span> <span class="s1">'by'</span><span class="p">,</span> <span class="s1">'can'</span><span class="p">,</span> <span class="s1">'cannot'</span><span class="p">,</span> <span class="s1">'can\'t'</span><span class="p">,</span> <span class="s1">'could'</span><span class="p">,</span> <span class="s1">'couldn\'t'</span><span class="p">,</span> <span class="s1">'how'</span><span class="p">,</span> <span class="s1">'is'</span><span class="p">,</span> <span class="s1">'isn\'t'</span><span class="p">,</span> <span class="s1">'it'</span><span class="p">,</span> <span class="s1">'its'</span><span class="p">,</span> <span class="s1">'it\'s'</span><span class="p">,</span> <span class="s1">'that'</span><span class="p">,</span> <span class="s1">'the'</span><span class="p">,</span> <span class="s1">'their'</span><span class="p">,</span> <span class="s1">'there'</span><span class="p">,</span> <span class="s1">'they'</span><span class="p">,</span> <span class="s1">'they\'re'</span><span class="p">,</span> <span class="s1">'them'</span><span class="p">,</span> <span class="s1">'to'</span><span class="p">,</span> <span class="s1">'too'</span><span class="p">,</span> <span class="s1">'us'</span><span class="p">,</span> <span class="s1">'very'</span><span class="p">,</span> <span class="s1">'was'</span><span class="p">,</span> <span class="s1">'we'</span><span class="p">,</span> <span class="s1">'well'</span><span class="p">,</span> <span class="s1">'were'</span><span class="p">,</span> <span class="s1">'what'</span><span class="p">,</span> <span class="s1">'whatever'</span><span class="p">,</span> <span class="s1">'when'</span><span class="p">,</span> <span class="s1">'whenever'</span><span class="p">,</span> <span class="s1">'where'</span><span class="p">,</span> <span class="s1">'with'</span><span class="p">,</span> <span class="s1">'would'</span><span class="p">,</span> <span class="s1">'yet'</span><span class="p">,</span> <span class="s1">'you'</span><span class="p">,</span> <span class="s1">'your'</span><span class="p">,</span> <span class="s1">'yours'</span><span class="p">,</span> <span class="s1">'yourself'</span><span class="p">,</span> <span class="s1">'yourselves'</span><span class="p">,</span> <span class="s1">'the'</span><span class="p">,</span> <span class="s1">'vanilla'</span><span class="p">,</span> <span class="s1">'javascript'</span><span class="p">,</span> <span class="s1">'js'</span><span class="p">];</span>
  164. </code></pre></div>
  165. <p>Back in my <code>search()</code> function, I use <a href="https://gomakethings.com/what-array.filter-does-in-vanilla-js/">the <code>Array.filter()</code> method</a> to remove any <code>word</code> that’s an empty string or part of the <code>stopWords</code> array.</p>
  166. <p>I use <a href="https://gomakethings.com/how-to-check-for-an-item-in-an-array-with-vanilla-js/">the <code>Array.includes()</code> method</a> to check if the <code>word</code> is in <code>stopWords</code>.</p>
  167. <p>Finally, I use <a href="https://gomakethings.com/what-array.map-does-in-vanilla-js/">the <code>Array.map()</code> method</a> an <code>new RegExp()</code> constructor to create an array of regex searches from my <code>query</code>.</p>
  168. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="cm">/**
  169. </span><span class="cm"> * Search for matches
  170. </span><span class="cm"> * @param {String} query The term to search for
  171. </span><span class="cm"> */</span>
  172. <span class="kd">function</span> <span class="nx">search</span> <span class="p">(</span><span class="nx">query</span><span class="p">)</span> <span class="p">{</span>
  173. <span class="c1">// Create a regex for each query
  174. </span><span class="c1"></span> <span class="kd">let</span> <span class="nx">regMap</span> <span class="o">=</span> <span class="nx">query</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">().</span><span class="nx">split</span><span class="p">(</span><span class="s1">' '</span><span class="p">).</span><span class="nx">filter</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">word</span><span class="p">)</span> <span class="p">{</span>
  175. <span class="k">return</span> <span class="nx">word</span><span class="p">.</span><span class="nx">length</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nx">stopWords</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">word</span><span class="p">);</span>
  176. <span class="p">}).</span><span class="nx">map</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">word</span><span class="p">)</span> <span class="p">{</span>
  177. <span class="k">return</span> <span class="k">new</span> <span class="nb">RegExp</span><span class="p">(</span><span class="nx">word</span><span class="p">,</span> <span class="s1">'i'</span><span class="p">);</span>
  178. <span class="p">});</span>
  179. <span class="p">}</span>
  180. </code></pre></div>
  181. <h2 id="doing-the-actual-search">Doing the actual search</h2>
  182. <p>Now that I have my regex patterns all setup, I can actually <em>do</em> the search.</p>
  183. <p>For this, I use <a href="https://gomakethings.com/using-array.reduce-in-vanilla-js/">the <code>Array.reduce()</code> method</a> on my <code>searchIndex</code>. I want to create a new array containing just matching items. I also want to include a <code>priority</code> rating, so that more closing matching items are shown higher in the results.</p>
  184. <p>I pass in an empty array (<code>[]</code>) as my <em>accumulator</em>, which I assign to the <code>results</code> parameter.</p>
  185. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="cm">/**
  186. </span><span class="cm"> * Search for matches
  187. </span><span class="cm"> * @param {String} query The term to search for
  188. </span><span class="cm"> */</span>
  189. <span class="kd">function</span> <span class="nx">search</span> <span class="p">(</span><span class="nx">query</span><span class="p">)</span> <span class="p">{</span>
  190. <span class="c1">// Create a regex for each query
  191. </span><span class="c1"></span> <span class="c1">// ...
  192. </span><span class="c1"></span>
  193. <span class="c1">// Get and sort the results
  194. </span><span class="c1"></span> <span class="kd">let</span> <span class="nx">results</span> <span class="o">=</span> <span class="nx">searchIndex</span><span class="p">.</span><span class="nx">reduce</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">results</span><span class="p">,</span> <span class="nx">article</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">{</span>
  195. <span class="c1">// Do stuff...
  196. </span><span class="c1"></span> <span class="p">},</span> <span class="p">[]);</span>
  197. <span class="p">}</span>
  198. </code></pre></div>
  199. <p>Inside the callback function, I create a <code>priority</code> variable with a value of <code>0</code>.</p>
  200. <p>Then, I loop through each item in my <code>regMap</code> using <a href="https://gomakethings.com/the-for...of-loop-in-vanilla-js/">a <code>for...of</code> loop</a>. I use the <code>RegExp.test()</code> method to look for matches in the <code>article.title</code>, and <code>RegExp.match()</code> method to look for matches in the <code>article.content</code>.</p>
  201. <p>I give more weight to the <code>title</code> than content. If there’s a match, I increase the <code>priority</code> by <code>100</code>. For every match in <code>content</code>, I increase the <code>priority</code> by <code>1</code>.</p>
  202. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="cm">/**
  203. </span><span class="cm"> * Search for matches
  204. </span><span class="cm"> * @param {String} query The term to search for
  205. </span><span class="cm"> */</span>
  206. <span class="kd">function</span> <span class="nx">search</span> <span class="p">(</span><span class="nx">query</span><span class="p">)</span> <span class="p">{</span>
  207. <span class="c1">// Create a regex for each query
  208. </span><span class="c1"></span> <span class="c1">// ...
  209. </span><span class="c1"></span>
  210. <span class="c1">// Get and sort the results
  211. </span><span class="c1"></span> <span class="kd">let</span> <span class="nx">results</span> <span class="o">=</span> <span class="nx">searchIndex</span><span class="p">.</span><span class="nx">reduce</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">results</span><span class="p">,</span> <span class="nx">article</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">{</span>
  212. <span class="c1">// Setup priority count
  213. </span><span class="c1"></span> <span class="kd">let</span> <span class="nx">priority</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  214. <span class="c1">// Assign priority
  215. </span><span class="c1"></span> <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">reg</span> <span class="k">of</span> <span class="nx">regMap</span><span class="p">)</span> <span class="p">{</span>
  216. <span class="k">if</span> <span class="p">(</span><span class="nx">reg</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">article</span><span class="p">.</span><span class="nx">title</span><span class="p">))</span> <span class="p">{</span> <span class="nx">priority</span> <span class="o">+=</span> <span class="mi">100</span><span class="p">;</span> <span class="p">}</span>
  217. <span class="kd">let</span> <span class="nx">occurences</span> <span class="o">=</span> <span class="nx">article</span><span class="p">.</span><span class="nx">content</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="nx">reg</span><span class="p">);</span>
  218. <span class="k">if</span> <span class="p">(</span><span class="nx">occurences</span><span class="p">)</span> <span class="p">{</span> <span class="nx">priority</span> <span class="o">+=</span> <span class="nx">occurences</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="p">}</span>
  219. <span class="p">}</span>
  220. <span class="p">},</span> <span class="p">[]);</span>
  221. <span class="p">}</span>
  222. </code></pre></div>
  223. <p>If <code>priority</code> is greater than <code>0</code>, I use the <code>Array.push()</code> method to add a new object (<code>{}</code>) to the <code>results</code> array.</p>
  224. <p>I include the <code>priority</code> and <code>article</code> as properties. Then, I <code>return</code> the <code>results</code>.</p>
  225. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="cm">/**
  226. </span><span class="cm"> * Search for matches
  227. </span><span class="cm"> * @param {String} query The term to search for
  228. </span><span class="cm"> */</span>
  229. <span class="kd">function</span> <span class="nx">search</span> <span class="p">(</span><span class="nx">query</span><span class="p">)</span> <span class="p">{</span>
  230. <span class="c1">// Create a regex for each query
  231. </span><span class="c1"></span> <span class="c1">// ...
  232. </span><span class="c1"></span>
  233. <span class="c1">// Get and sort the results
  234. </span><span class="c1"></span> <span class="kd">let</span> <span class="nx">results</span> <span class="o">=</span> <span class="nx">searchIndex</span><span class="p">.</span><span class="nx">reduce</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">results</span><span class="p">,</span> <span class="nx">article</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">{</span>
  235. <span class="c1">// Setup priority count
  236. </span><span class="c1"></span> <span class="kd">let</span> <span class="nx">priority</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  237. <span class="c1">// Assign priority
  238. </span><span class="c1"></span> <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">reg</span> <span class="k">of</span> <span class="nx">regMap</span><span class="p">)</span> <span class="p">{</span>
  239. <span class="k">if</span> <span class="p">(</span><span class="nx">reg</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">article</span><span class="p">.</span><span class="nx">title</span><span class="p">))</span> <span class="p">{</span> <span class="nx">priority</span> <span class="o">+=</span> <span class="mi">100</span><span class="p">;</span> <span class="p">}</span>
  240. <span class="kd">let</span> <span class="nx">occurences</span> <span class="o">=</span> <span class="nx">article</span><span class="p">.</span><span class="nx">content</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="nx">reg</span><span class="p">);</span>
  241. <span class="k">if</span> <span class="p">(</span><span class="nx">occurences</span><span class="p">)</span> <span class="p">{</span> <span class="nx">priority</span> <span class="o">+=</span> <span class="nx">occurences</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="p">}</span>
  242. <span class="p">}</span>
  243. <span class="c1">// If any matches, push to results
  244. </span><span class="c1"></span> <span class="k">if</span> <span class="p">(</span><span class="nx">priority</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
  245. <span class="nx">results</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
  246. <span class="nx">priority</span><span class="o">:</span> <span class="nx">priority</span><span class="p">,</span>
  247. <span class="nx">article</span><span class="o">:</span> <span class="nx">article</span>
  248. <span class="p">});</span>
  249. <span class="p">}</span>
  250. <span class="k">return</span> <span class="nx">results</span><span class="p">;</span>
  251. <span class="p">},</span> <span class="p">[]);</span>
  252. <span class="p">}</span>
  253. </code></pre></div>
  254. <p>Finally, I use <a href="https://gomakethings.com/array-sorting-basics-with-vanilla-javascript/">the <code>Array.sort()</code> method</a> to order the <code>results</code> by article priority. Items with the highest <code>priority</code> show up first.</p>
  255. <p>Then, I pass the <code>results</code> into a <code>showResults()</code> method that renders them into the UI.</p>
  256. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="cm">/**
  257. </span><span class="cm"> * Search for matches
  258. </span><span class="cm"> * @param {String} query The term to search for
  259. </span><span class="cm"> */</span>
  260. <span class="kd">function</span> <span class="nx">search</span> <span class="p">(</span><span class="nx">query</span><span class="p">)</span> <span class="p">{</span>
  261. <span class="c1">// Create a regex for each query
  262. </span><span class="c1"></span> <span class="c1">// ...
  263. </span><span class="c1"></span>
  264. <span class="c1">// Get and sort the results
  265. </span><span class="c1"></span> <span class="kd">let</span> <span class="nx">results</span> <span class="o">=</span> <span class="nx">searchIndex</span><span class="p">.</span><span class="nx">reduce</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">results</span><span class="p">,</span> <span class="nx">article</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">{</span>
  266. <span class="c1">// ...
  267. </span><span class="c1"></span> <span class="p">},</span> <span class="p">[]).</span><span class="nx">sort</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">article1</span><span class="p">,</span> <span class="nx">article2</span><span class="p">)</span> <span class="p">{</span>
  268. <span class="k">return</span> <span class="nx">article2</span><span class="p">.</span><span class="nx">priority</span> <span class="o">-</span> <span class="nx">article1</span><span class="p">.</span><span class="nx">priority</span><span class="p">;</span>
  269. <span class="p">});</span>
  270. <span class="c1">// Display the results
  271. </span><span class="c1"></span> <span class="nx">showResults</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
  272. <span class="p">}</span>
  273. </code></pre></div>
  274. <h2 id="rendering-search-results">Rendering search results</h2>
  275. <p>Inside the <code>showResults()</code> method, I do a quick check to see if their are any results to show.</p>
  276. <p>If there are, I inject a message into the <code>searchStatus</code> element that shares how many matches were found. This also gets read aloud by screen readers.</p>
  277. <p>Then, I use the <code>results</code> to create an HTML string with the <code>title</code> and a link to the article. The appearance of this varies from one site to another, but you can style it however you want.</p>
  278. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="cm">/**
  279. </span><span class="cm"> * Show the search results in the UI
  280. </span><span class="cm"> * @param {Array} results The results to display
  281. </span><span class="cm"> */</span>
  282. <span class="kd">function</span> <span class="nx">showResults</span> <span class="p">(</span><span class="nx">results</span><span class="p">)</span> <span class="p">{</span>
  283. <span class="k">if</span> <span class="p">(</span><span class="nx">results</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
  284. <span class="nx">searchStatus</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="sb">`&lt;p&gt;Found </span><span class="si">${</span><span class="nx">results</span><span class="p">.</span><span class="nx">length</span><span class="si">}</span><span class="sb"> matching articles&lt;/p&gt;`</span><span class="p">;</span>
  285. <span class="nx">resultList</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">myTemplate</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
  286. <span class="p">}</span>
  287. <span class="p">}</span>
  288. </code></pre></div>
  289. <p>If there are no <code>results</code>, I clear the <code>resultList</code> element and show a message saying there were no matches.</p>
  290. <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="cm">/**
  291. </span><span class="cm"> * Show the search results in the UI
  292. </span><span class="cm"> * @param {Array} results The results to display
  293. </span><span class="cm"> */</span>
  294. <span class="kd">function</span> <span class="nx">showResults</span> <span class="p">(</span><span class="nx">results</span><span class="p">)</span> <span class="p">{</span>
  295. <span class="k">if</span> <span class="p">(</span><span class="nx">results</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
  296. <span class="nx">searchStatus</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="sb">`&lt;p&gt;Found </span><span class="si">${</span><span class="nx">results</span><span class="p">.</span><span class="nx">length</span><span class="si">}</span><span class="sb"> matching articles&lt;/p&gt;`</span><span class="p">;</span>
  297. <span class="nx">resultList</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">myTemplate</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
  298. <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
  299. <span class="nx">searchStatus</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s1">'&lt;p&gt;Sorry, no matches were found.&lt;/p&gt;'</span><span class="p">;</span>
  300. <span class="nx">resultList</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span>
  301. <span class="p">}</span>
  302. <span class="p">}</span>
  303. </code></pre></div>
  304. <h2 id="what-else">What else?</h2>
  305. <p>Tomorrow, I’ll show you how I update the URL with the search query, and run a search automatically on page load if there’s a query in the URL.</p>
  306. <p>This let’s people bookmark searches.</p>
  307. </article>
  308. <hr>
  309. <footer>
  310. <p>
  311. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  312. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  313. </svg> Accueil</a> •
  314. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  315. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  316. </svg> Suivre</a> •
  317. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  318. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  319. </svg> Pro</a> •
  320. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  321. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  322. </svg> Email</a> •
  323. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  324. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  325. </svg> Légal</abbr>
  326. </p>
  327. <template id="theme-selector">
  328. <form>
  329. <fieldset>
  330. <legend><svg class="icon icon-brightness-contrast">
  331. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  332. </svg> Thème</legend>
  333. <label>
  334. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  335. </label>
  336. <label>
  337. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  338. </label>
  339. <label>
  340. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  341. </label>
  342. </fieldset>
  343. </form>
  344. </template>
  345. </footer>
  346. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  347. <script>
  348. function loadThemeForm(templateName) {
  349. const themeSelectorTemplate = document.querySelector(templateName)
  350. const form = themeSelectorTemplate.content.firstElementChild
  351. themeSelectorTemplate.replaceWith(form)
  352. form.addEventListener('change', (e) => {
  353. const chosenColorScheme = e.target.value
  354. localStorage.setItem('theme', chosenColorScheme)
  355. toggleTheme(chosenColorScheme)
  356. })
  357. const selectedTheme = localStorage.getItem('theme')
  358. if (selectedTheme && selectedTheme !== 'undefined') {
  359. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  360. }
  361. }
  362. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  363. window.addEventListener('load', () => {
  364. let hasDarkRules = false
  365. for (const styleSheet of Array.from(document.styleSheets)) {
  366. let mediaRules = []
  367. for (const cssRule of styleSheet.cssRules) {
  368. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  369. continue
  370. }
  371. // WARNING: Safari does not have/supports `conditionText`.
  372. if (cssRule.conditionText) {
  373. if (cssRule.conditionText !== prefersColorSchemeDark) {
  374. continue
  375. }
  376. } else {
  377. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  378. continue
  379. }
  380. }
  381. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  382. }
  383. // WARNING: do not try to insert a Rule to a styleSheet you are
  384. // currently iterating on, otherwise the browser will be stuck
  385. // in a infinite loop…
  386. for (const mediaRule of mediaRules) {
  387. styleSheet.insertRule(mediaRule.cssText)
  388. hasDarkRules = true
  389. }
  390. }
  391. if (hasDarkRules) {
  392. loadThemeForm('#theme-selector')
  393. }
  394. })
  395. </script>
  396. </body>
  397. </html>