Jekyll2023-09-21T17:39:42+00:00https://meefik.github.io/feed.xmlMeefik’s BlogMeefik's BlogAnton SkshidlevskyNEUX is a minimalist and reactive UX library2023-08-03T12:00:00+00:002023-08-03T12:00:00+00:00https://meefik.github.io/2023/08/03/neux<p>For a long time, I wanted to make a full-fledged implementation of the front-end UX library like Vue, React, or Solid. The previous attempt was implemented as a library <a href="/2021/12/02/libux/">LibUX</a>. This library was based on JS classes and implements component rendering functionality based on an EJS-like template syntax. Also, there is implemented support for working with states, routing, and localization.</p>
<p>This year I was able to take the time to design and develop a more modern approach. So the <a href="https://github.com/meefik/neux">NEUX</a> library appeared. The library includes a set of functions for creating such entities as states, views, localization, routing, synchronization with storage, remote procedure call. The library is available for use both with and without builders.</p>
<p><a href="https://github.com/meefik/neux">NEUX</a> is a nifty ecosystem for user experience development. Its short name is <code class="language-plaintext highlighter-rouge">{\}</code>. The JavaScript frontend library has features and tools are suitable for building single-page applications (SPA).</p>
<p><img src="/assets/images/neux.png" alt="neux" title="NEUX" /></p>
<p>Here are the main concepts behind NEUX:</p>
<ul>
<li>Minimum interaction with the library during development, more native JS code.</li>
<li>Instead of HTML templates and JSX, defining views as nested JS objects with a set of attributes that are completely equivalent to the attributes of native HTML elements.</li>
<li>Support for modern two-way reactivity.</li>
<li>Availability of standard components to implement the basic SPA functionality:
<ul>
<li>routing,</li>
<li>localization,</li>
<li>synchronization of states with persistent storage,</li>
<li>calling remote procedures on the backend.</li>
</ul>
</li>
<li>Small library size ~ 8kb (4kb gzipped).</li>
</ul>
<p>Additional information is available on <a href="/neux">the project page</a>.</p>
<!--more-->
<p>I prepared an example <a href="https://github.com/meefik/neux-todo-app">To-Do app</a> to demonstrate how the library works.</p>
<p><img src="/assets/images/neux-todo-app.gif" alt="neux-todo-app" title="NEUX To-Do App" /></p>
<p>This example uses the stack: <a href="https://github.com/meefik/neux">NEUX</a> + <a href="https://tailwindcss.com">Tailwind CSS</a> + <a href="https://vitejs.dev">Vite</a>.</p>Anton SkshidlevskyFor a long time, I wanted to make a full-fledged implementation of the front-end UX library like Vue, React, or Solid. The previous attempt was implemented as a library LibUX. This library was based on JS classes and implements component rendering functionality based on an EJS-like template syntax. Also, there is implemented support for working with states, routing, and localization. This year I was able to take the time to design and develop a more modern approach. So the NEUX library appeared. The library includes a set of functions for creating such entities as states, views, localization, routing, synchronization with storage, remote procedure call. The library is available for use both with and without builders. NEUX is a nifty ecosystem for user experience development. Its short name is {\}. The JavaScript frontend library has features and tools are suitable for building single-page applications (SPA). Here are the main concepts behind NEUX: Minimum interaction with the library during development, more native JS code. Instead of HTML templates and JSX, defining views as nested JS objects with a set of attributes that are completely equivalent to the attributes of native HTML elements. Support for modern two-way reactivity. Availability of standard components to implement the basic SPA functionality: routing, localization, synchronization of states with persistent storage, calling remote procedures on the backend. Small library size ~ 8kb (4kb gzipped). Additional information is available on the project page.Running Node.js server on PHP web hosting2023-07-27T12:00:00+00:002023-07-27T12:00:00+00:00https://meefik.github.io/2023/07/27/nodejs-on-php-web-hosting<p>What if you really want a Node.js web server, but hosting is only available in PHP? Run Node.js on PHP web hosting! Bonus - web hosting is cheaper than VPS.</p>
<p>A typical web hosting stack is Linux + Apache + MySQL + PHP (LAMP). It does not have root rights and there is no way to replace the web server (Apache) with something else (Node.js). Sometimes there is access to the server via SSH, but it may not be.</p>
<p>What we will need:</p>
<ul>
<li>Web hosting with Apache and PHP.</li>
<li>Ability to upload any files to the server.</li>
<li>Ability to change Apache rules via <code class="language-plaintext highlighter-rouge">mod_rewrite</code>.</li>
<li>Ability to run arbitrary scripts on the server via <code class="language-plaintext highlighter-rouge">cron</code>.</li>
</ul>
<p><img src="/assets/images/nodejs-via-php.png" alt="nodejs-via-php" title="Node.js via PHP" /></p>
<!--more-->
<h2 id="bootstrap-for-nodejs">Bootstrap for Node.js</h2>
<p>Create a new directory <code class="language-plaintext highlighter-rouge">~/app</code> in the user’s home directory. Load the Node.js project into it, along with the <code class="language-plaintext highlighter-rouge">package.json</code> file. In the same directory, create a <code class="language-plaintext highlighter-rouge">run.sh</code> script to run the Node application. Change permissions to 755 (rwxr-xr-x) for it. The script code is below:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="nb">set</span> <span class="nt">-ex</span>
<span class="nv">APP_DIR</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">HOME</span><span class="k">}</span><span class="s2">/app"</span>
<span class="nv">NODE_VER</span><span class="o">=</span><span class="s2">"node-v20.5.0-linux-x64"</span>
<span class="nv">NODE_URL</span><span class="o">=</span><span class="s2">"https://nodejs.org/dist/latest/</span><span class="k">${</span><span class="nv">NODE_VER</span><span class="k">}</span><span class="s2">.tar.gz"</span>
<span class="nb">cd</span> <span class="s2">"</span><span class="k">${</span><span class="nv">APP_DIR</span><span class="k">}</span><span class="s2">"</span>
<span class="k">if</span> <span class="o">[</span> <span class="o">!</span> <span class="nt">-e</span> <span class="s2">"</span><span class="k">${</span><span class="nv">APP_DIR</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">NODE_VER</span><span class="k">}</span><span class="s2">/bin/node"</span> <span class="o">]</span>
<span class="k">then
</span>wget <span class="nt">-O</span> - <span class="s2">"</span><span class="k">${</span><span class="nv">NODE_URL</span><span class="k">}</span><span class="s2">"</span> | <span class="nb">tar </span>xpz
<span class="k">fi
</span><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">PATH</span><span class="k">}</span><span class="s2">:</span><span class="k">${</span><span class="nv">APP_DIR</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">NODE_VER</span><span class="k">}</span><span class="s2">/bin"</span>
<span class="nb">export </span><span class="nv">NODE_ENV</span><span class="o">=</span><span class="s2">"production"</span>
<span class="k">if</span> <span class="o">[</span> <span class="o">!</span> <span class="nt">-e</span> <span class="s2">"</span><span class="k">${</span><span class="nv">APP_DIR</span><span class="k">}</span><span class="s2">/node_modules"</span> <span class="o">]</span>
<span class="k">then
</span>npm <span class="nb">install
</span><span class="k">fi
if</span> <span class="o">[</span> <span class="nt">-e</span> <span class="s2">"</span><span class="k">${</span><span class="nv">APP_DIR</span><span class="k">}</span><span class="s2">/run.pid"</span> <span class="o">]</span>
<span class="k">then
</span><span class="nv">pid</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat</span> <span class="s2">"</span><span class="k">${</span><span class="nv">APP_DIR</span><span class="k">}</span><span class="s2">/run.pid"</span><span class="si">)</span>
<span class="o">[</span> <span class="o">!</span> <span class="nt">-e</span> <span class="s2">"/proc/</span><span class="k">${</span><span class="nv">pid</span><span class="k">}</span><span class="s2">"</span> <span class="o">]</span> <span class="o">||</span> <span class="nb">exit </span>0
<span class="k">fi
</span><span class="nb">echo</span> <span class="nv">$$</span> <span class="o">></span> <span class="s2">"</span><span class="k">${</span><span class="nv">APP_DIR</span><span class="k">}</span><span class="s2">/run.pid"</span>
<span class="nb">exec </span>node server.js
</code></pre></div></div>
<p>And this is an example of the <code class="language-plaintext highlighter-rouge">server.js</code> file:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">http</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">node:http</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">PORT</span> <span class="o">=</span> <span class="mi">3000</span><span class="p">,</span> <span class="nx">HOST</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">127.0.0.1</span><span class="dl">'</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">enable</span><span class="p">(</span><span class="dl">'</span><span class="s1">trust proxy</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/api/hello</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="dl">'</span><span class="s1">Hello from Node.js</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">http</span><span class="p">.</span><span class="nx">Server</span><span class="p">(</span><span class="nx">app</span><span class="p">).</span><span class="nx">listen</span><span class="p">(</span><span class="nx">PORT</span><span class="p">,</span> <span class="nx">HOST</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">address</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">address</span><span class="p">();</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Listening on </span><span class="p">${</span><span class="nx">address</span><span class="p">.</span><span class="nx">address</span><span class="p">}</span><span class="s2">:</span><span class="p">${</span><span class="nx">address</span><span class="p">.</span><span class="nx">port</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>
<p>You also need to create a <code class="language-plaintext highlighter-rouge">package.json</code> file, where you need to add the modules used by Node to the “dependencies” section:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"express"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^4.18.2"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h2 id="reverse-proxy-in-php">Reverse proxy in PHP</h2>
<p>There is a good project <a href="https://github.com/michaelfranzl/no.php">no.php</a>. This is transparent reverse proxy written in PHP that allows you to not have to write PHP any more.</p>
<p>From the project repository, you need to take the <code class="language-plaintext highlighter-rouge">no.php</code> file and save it to the <code class="language-plaintext highlighter-rouge">~/app</code> directory under the name <code class="language-plaintext highlighter-rouge">proxy.php</code>. In this file, two variables need to be replaced:</p>
<ul>
<li>$backend_url = “http://127.0.0.1:3000”</li>
<li>$uri_rel = “proxy.php”</li>
</ul>
<p>Port <code class="language-plaintext highlighter-rouge">3000</code> is the port that the Node.js web server is listening on.</p>
<h2 id="rewrite-rule-for-apache">Rewrite rule for Apache</h2>
<p>Now we need to have all requests to the web server handled by the <code class="language-plaintext highlighter-rouge">proxy.php</code> script. To do this, place the <code class="language-plaintext highlighter-rouge">.htaccess</code> file with the following content in the root public directory of the web server:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . proxy.php [L]
</code></pre></div></div>
<h2 id="cron-for-run-it">Cron for run it</h2>
<p>You can run the <code class="language-plaintext highlighter-rouge">run.sh</code> file through a <code class="language-plaintext highlighter-rouge">cron</code> job. The <code class="language-plaintext highlighter-rouge">cron</code> setting is usually available on all web hostings. An example of setting up a cron job to run a task every minute:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>* * * * * /path/to/run.sh
</code></pre></div></div>
<p>It is important to correctly specify the full path to the <code class="language-plaintext highlighter-rouge">run.sh</code> file. If the script is already running, then it will not run again.</p>Anton SkshidlevskyWhat if you really want a Node.js web server, but hosting is only available in PHP? Run Node.js on PHP web hosting! Bonus - web hosting is cheaper than VPS. A typical web hosting stack is Linux + Apache + MySQL + PHP (LAMP). It does not have root rights and there is no way to replace the web server (Apache) with something else (Node.js). Sometimes there is access to the server via SSH, but it may not be. What we will need: Web hosting with Apache and PHP. Ability to upload any files to the server. Ability to change Apache rules via mod_rewrite. Ability to run arbitrary scripts on the server via cron.Human-readable MongoDB query syntax2023-06-24T12:00:00+00:002023-06-24T12:00:00+00:00https://meefik.github.io/2023/06/24/mongodb-queries<p>Sometimes you need to give your application user a more flexible way to search the database. The search should be universal for any data and easy to understand for a person without technical knowledge.</p>
<p>I was able to create a simple query syntax and a parser to convert them to MongoDB query syntax. Below is a description of the query syntax and the parser code for them.</p>
<!--more-->
<h2 id="query-syntax">Query syntax</h2>
<p>The query is a string that uses special characters. These symbols are similar in appearance and meaning to mathematical operations. This includes comparison operations, logical operations, grouping the order of operations.</p>
<p>Below is a table describing operators and use cases.</p>
<table>
<thead>
<tr>
<th>Operator</th>
<th>Description</th>
<th>Use case</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="language-plaintext highlighter-rouge">></code></td>
<td>Greater</td>
<td><code class="language-plaintext highlighter-rouge">field > 2021-12-01</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge"><</code></td>
<td>Less</td>
<td><code class="language-plaintext highlighter-rouge">field < 5</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">>=</code></td>
<td>Greater or equal</td>
<td><code class="language-plaintext highlighter-rouge">field >= 10</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge"><=</code></td>
<td>Lessor equal</td>
<td><code class="language-plaintext highlighter-rouge">field <= 50</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">=</code></td>
<td>Equal</td>
<td><code class="language-plaintext highlighter-rouge">field = "text"</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">!=</code></td>
<td>Not equal</td>
<td><code class="language-plaintext highlighter-rouge">field != "text"</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">~</code></td>
<td>Matches regular expression</td>
<td><code class="language-plaintext highlighter-rouge">field ~ ^text\d+$</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">~*</code></td>
<td>Matches regular expression case insensitive</td>
<td><code class="language-plaintext highlighter-rouge">field ~* ^TeXt</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">&</code></td>
<td>Logical AND</td>
<td><code class="language-plaintext highlighter-rouge">field > 0 & field < 10</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">|</code></td>
<td>Logical OR</td>
<td><code class="language-plaintext highlighter-rouge">field > 0 | field < 10</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">()</code></td>
<td>Expression grouping</td>
<td><code class="language-plaintext highlighter-rouge">(field > 0 & field < 10) | field > 100</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">[]</code></td>
<td>One of many</td>
<td><code class="language-plaintext highlighter-rouge">field = ['one', 'two', 'three']</code></td>
</tr>
</tbody>
</table>
<p>Note:</p>
<ul>
<li>The field names are passed as is.</li>
<li>If you need to refer to a nested field, then you should use a dot (e.g. <code class="language-plaintext highlighter-rouge">field.nested</code>).</li>
<li>Dates are written in <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a> format in UTC.</li>
<li>Text must be enclosed in double or single quotes.</li>
<li>You cannot use quotes unless the text contains spaces or special characters.</li>
<li>Empty quotes denote an empty string or <code class="language-plaintext highlighter-rouge">null</code>.</li>
<li>Numbers are automatically converted to <code class="language-plaintext highlighter-rouge">Number</code>.</li>
<li>Dates are automatically converted to <code class="language-plaintext highlighter-rouge">Date</code>.</li>
</ul>
<h2 id="operating-example">Operating example</h2>
<p>Take the following query as an example:</p>
<p><code class="language-plaintext highlighter-rouge">a.x1 > 2021-12-01 & ( b >= "5" | (c > 0 & d ~ 'a + b & c = 0' & f <= -1 | a ~* "hello" )) & e != 10 | g = [1,2,"z"] & n = ""</code></p>
<p>Such this parser converts into the MongoDB query of the following form:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="nl">"$or"</span><span class="p">:[</span><span class="w">
</span><span class="p">{</span><span class="nl">"$and"</span><span class="p">:[</span><span class="w">
</span><span class="p">{</span><span class="nl">"a.x1"</span><span class="p">:{</span><span class="nl">"$gt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2021-12-01T00:00:00.000Z"</span><span class="p">}},</span><span class="w">
</span><span class="p">{</span><span class="nl">"$or"</span><span class="p">:[</span><span class="w">
</span><span class="p">{</span><span class="nl">"b"</span><span class="p">:{</span><span class="nl">"$gte"</span><span class="p">:</span><span class="s2">"5"</span><span class="p">}},</span><span class="w">
</span><span class="p">{</span><span class="nl">"$or"</span><span class="p">:[</span><span class="w">
</span><span class="p">{</span><span class="nl">"$and"</span><span class="p">:[</span><span class="w">
</span><span class="p">{</span><span class="nl">"c"</span><span class="p">:{</span><span class="nl">"$gt"</span><span class="p">:</span><span class="mi">0</span><span class="p">}},</span><span class="w">
</span><span class="p">{</span><span class="nl">"d"</span><span class="p">:{</span><span class="nl">"$regex"</span><span class="p">:</span><span class="s2">"a + b & c = 0"</span><span class="p">}},</span><span class="w">
</span><span class="p">{</span><span class="nl">"f"</span><span class="p">:{</span><span class="nl">"$lte"</span><span class="p">:</span><span class="mi">-1</span><span class="p">}}</span><span class="w">
</span><span class="p">]},</span><span class="w">
</span><span class="p">{</span><span class="nl">"a"</span><span class="p">:{</span><span class="nl">"$regex"</span><span class="p">:</span><span class="s2">"hello"</span><span class="p">,</span><span class="nl">"$options"</span><span class="p">:</span><span class="s2">"i"</span><span class="p">}}</span><span class="w">
</span><span class="p">]}</span><span class="w">
</span><span class="p">]},</span><span class="w">
</span><span class="p">{</span><span class="nl">"e"</span><span class="p">:{</span><span class="nl">"$ne"</span><span class="p">:</span><span class="mi">10</span><span class="p">}}</span><span class="w">
</span><span class="p">]},</span><span class="w">
</span><span class="p">{</span><span class="nl">"$and"</span><span class="p">:[</span><span class="w">
</span><span class="p">{</span><span class="nl">"g"</span><span class="p">:{</span><span class="nl">"$in"</span><span class="p">:[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="s2">"z"</span><span class="p">]}},</span><span class="w">
</span><span class="p">{</span><span class="nl">"n"</span><span class="p">:{</span><span class="nl">"$in"</span><span class="p">:[</span><span class="s2">""</span><span class="p">,</span><span class="kc">null</span><span class="p">]}}</span><span class="w">
</span><span class="p">]}</span><span class="w">
</span><span class="p">]}</span><span class="w">
</span></code></pre></div></div>
<h2 id="parser-implementation">Parser implementation</h2>
<p>Below is the implementation of the query parser.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">patterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="sr">/</span><span class="se">(</span><span class="sr">'</span><span class="se">[^</span><span class="sr">'</span><span class="se">]</span><span class="sr">*'</span><span class="se">)</span><span class="sr">/</span><span class="p">,</span>
<span class="sr">/</span><span class="se">(</span><span class="sr">"</span><span class="se">[^</span><span class="sr">"</span><span class="se">]</span><span class="sr">*"</span><span class="se">)</span><span class="sr">/</span><span class="p">,</span>
<span class="sr">/</span><span class="se">\(([^</span><span class="sr">()</span><span class="se">]</span><span class="sr">+</span><span class="se">)\)</span><span class="sr">/</span><span class="p">,</span>
<span class="sr">/</span><span class="se">([^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+>=</span><span class="se">[^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+</span><span class="se">)</span><span class="sr">/</span><span class="p">,</span>
<span class="sr">/</span><span class="se">([^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+></span><span class="se">[^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+</span><span class="se">)</span><span class="sr">/</span><span class="p">,</span>
<span class="sr">/</span><span class="se">([^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+<=</span><span class="se">[^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+</span><span class="se">)</span><span class="sr">/</span><span class="p">,</span>
<span class="sr">/</span><span class="se">([^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+<</span><span class="se">[^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+</span><span class="se">)</span><span class="sr">/</span><span class="p">,</span>
<span class="sr">/</span><span class="se">([^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+!=</span><span class="se">[^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+</span><span class="se">)</span><span class="sr">/</span><span class="p">,</span>
<span class="sr">/</span><span class="se">([^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+=</span><span class="se">[^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+</span><span class="se">)</span><span class="sr">/</span><span class="p">,</span>
<span class="sr">/</span><span class="se">([^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+~</span><span class="se">\*[^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+</span><span class="se">)</span><span class="sr">/</span><span class="p">,</span>
<span class="sr">/</span><span class="se">([^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+~</span><span class="se">[^</span><span class="sr">&|</span><span class="se">]</span><span class="sr">+</span><span class="se">)</span><span class="sr">/</span><span class="p">,</span>
<span class="sr">/</span><span class="se">([^</span><span class="sr">|</span><span class="se">]</span><span class="sr">*</span><span class="se">[</span><span class="sr">&</span><span class="se">][^</span><span class="sr">|</span><span class="se">]</span><span class="sr">*</span><span class="se">)</span><span class="sr">/</span>
<span class="p">];</span>
<span class="kd">function</span> <span class="nx">parse</span> <span class="p">(</span><span class="nx">str</span><span class="p">,</span> <span class="nx">params</span> <span class="o">=</span> <span class="p">{},</span> <span class="nx">opts</span> <span class="o">=</span> <span class="p">{})</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">opts</span><span class="p">.</span><span class="nx">c</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">opts</span><span class="p">.</span><span class="nx">c</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">opts</span><span class="p">.</span><span class="nx">p</span><span class="p">)</span> <span class="p">{</span>
<span class="k">do</span> <span class="p">{</span>
<span class="nx">opts</span><span class="p">.</span><span class="nx">p</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">().</span><span class="nx">toString</span><span class="p">(</span><span class="mi">16</span><span class="p">).</span><span class="nx">slice</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="nx">str</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">opts</span><span class="p">.</span><span class="nx">p</span><span class="p">)</span> <span class="o">></span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">patterns</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">re</span> <span class="o">=</span> <span class="nx">patterns</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>
<span class="k">while</span> <span class="p">(</span><span class="kc">true</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">str</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="nx">re</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">r</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">k</span> <span class="o">=</span> <span class="s2">`x</span><span class="p">${</span><span class="o">++</span><span class="nx">opts</span><span class="p">.</span><span class="nx">c</span><span class="p">}</span><span class="s2">_</span><span class="p">${</span><span class="nx">opts</span><span class="p">.</span><span class="nx">p</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
<span class="nx">str</span> <span class="o">=</span> <span class="nx">str</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">k</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">param</span> <span class="o">=</span> <span class="nx">r</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">!==</span> <span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="p">?</span> <span class="nx">parse</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="nx">params</span><span class="p">,</span> <span class="nx">opts</span><span class="p">)</span> <span class="p">:</span> <span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="nx">params</span><span class="p">[</span><span class="nx">k</span><span class="p">]</span> <span class="o">=</span> <span class="nx">i</span> <span class="o"><</span> <span class="mi">2</span> <span class="p">?</span> <span class="nx">param</span> <span class="p">:</span> <span class="nx">param</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/</span><span class="se">\s</span><span class="sr">/g</span><span class="p">,</span> <span class="dl">''</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">str</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">compile</span> <span class="p">(</span><span class="nx">str</span><span class="p">,</span> <span class="nx">params</span> <span class="o">=</span> <span class="p">{})</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">val</span> <span class="o">=</span> <span class="nx">params</span><span class="p">[</span><span class="nx">str</span><span class="p">]</span> <span class="o">||</span> <span class="nx">str</span><span class="p">;</span>
<span class="k">switch</span> <span class="p">(</span><span class="kc">true</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="sr">/^'</span><span class="se">[^</span><span class="sr">'</span><span class="se">]</span><span class="sr">*'$/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">):</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="sr">/^'</span><span class="se">([^</span><span class="sr">'</span><span class="se">]</span><span class="sr">*</span><span class="se">)</span><span class="sr">'$/</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">||</span> <span class="dl">''</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">value</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">case</span> <span class="sr">/^"</span><span class="se">[^</span><span class="sr">"</span><span class="se">]</span><span class="sr">*"$/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">):</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="sr">/^"</span><span class="se">([^</span><span class="sr">"</span><span class="se">]</span><span class="sr">*</span><span class="se">)</span><span class="sr">"$/</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">||</span> <span class="dl">''</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">value</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">case</span> <span class="sr">/^</span><span class="se">\[[^</span><span class="sr">[</span><span class="se">\]]</span><span class="sr">*</span><span class="se">\]</span><span class="sr">$/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">):</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">arr</span> <span class="o">=</span> <span class="p">[];</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">).</span><span class="nx">split</span><span class="p">(</span><span class="sr">/</span><span class="se">\s</span><span class="sr">*,</span><span class="se">\s</span><span class="sr">*/</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">r</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="nx">i</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="nx">arr</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">arr</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">case</span> <span class="sr">/>=/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">):</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="sr">/</span><span class="se">\s</span><span class="sr">*>=</span><span class="se">\s</span><span class="sr">*/</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">field</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="k">return</span> <span class="p">{</span> <span class="p">[</span><span class="nx">field</span><span class="p">]:</span> <span class="p">{</span> <span class="na">$gte</span><span class="p">:</span> <span class="nx">value</span> <span class="p">}</span> <span class="p">};</span>
<span class="p">}</span>
<span class="k">case</span> <span class="sr">/>/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">):</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="sr">/</span><span class="se">\s</span><span class="sr">*></span><span class="se">\s</span><span class="sr">*/</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">field</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="k">return</span> <span class="p">{</span> <span class="p">[</span><span class="nx">field</span><span class="p">]:</span> <span class="p">{</span> <span class="na">$gt</span><span class="p">:</span> <span class="nx">value</span> <span class="p">}</span> <span class="p">};</span>
<span class="p">}</span>
<span class="k">case</span> <span class="sr">/<=/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">):</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="sr">/</span><span class="se">\s</span><span class="sr">*<=</span><span class="se">\s</span><span class="sr">*/</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">field</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="k">return</span> <span class="p">{</span> <span class="p">[</span><span class="nx">field</span><span class="p">]:</span> <span class="p">{</span> <span class="na">$lte</span><span class="p">:</span> <span class="nx">value</span> <span class="p">}</span> <span class="p">};</span>
<span class="p">}</span>
<span class="k">case</span> <span class="sr">/</</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">):</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="sr">/</span><span class="se">\s</span><span class="sr">*<</span><span class="se">\s</span><span class="sr">*/</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">field</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="k">return</span> <span class="p">{</span> <span class="p">[</span><span class="nx">field</span><span class="p">]:</span> <span class="p">{</span> <span class="na">$lt</span><span class="p">:</span> <span class="nx">value</span> <span class="p">}</span> <span class="p">};</span>
<span class="p">}</span>
<span class="k">case</span> <span class="sr">/!=/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">):</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="sr">/</span><span class="se">\s</span><span class="sr">*!=</span><span class="se">\s</span><span class="sr">*/</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">field</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">value</span><span class="p">)</span> <span class="nx">value</span> <span class="o">=</span> <span class="p">[</span><span class="dl">''</span><span class="p">,</span> <span class="kc">null</span><span class="p">];</span>
<span class="k">return</span> <span class="p">{</span> <span class="p">[</span><span class="nx">field</span><span class="p">]:</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">?</span> <span class="p">{</span> <span class="na">$nin</span><span class="p">:</span> <span class="nx">value</span> <span class="p">}</span> <span class="p">:</span> <span class="p">{</span> <span class="na">$ne</span><span class="p">:</span> <span class="nx">value</span> <span class="p">}</span> <span class="p">};</span>
<span class="p">}</span>
<span class="k">case</span> <span class="sr">/=/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">):</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="sr">/</span><span class="se">\s</span><span class="sr">*=</span><span class="se">\s</span><span class="sr">*/</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">field</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">value</span><span class="p">)</span> <span class="nx">value</span> <span class="o">=</span> <span class="p">[</span><span class="dl">''</span><span class="p">,</span> <span class="kc">null</span><span class="p">];</span>
<span class="k">return</span> <span class="p">{</span> <span class="p">[</span><span class="nx">field</span><span class="p">]:</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">?</span> <span class="p">{</span> <span class="na">$in</span><span class="p">:</span> <span class="nx">value</span> <span class="p">}</span> <span class="p">:</span> <span class="nx">value</span> <span class="p">};</span>
<span class="p">}</span>
<span class="k">case</span> <span class="sr">/~</span><span class="se">\*</span><span class="sr">/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">):</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="sr">/</span><span class="se">\s</span><span class="sr">*~</span><span class="se">\*\s</span><span class="sr">*/</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">field</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="k">return</span> <span class="p">{</span> <span class="p">[</span><span class="nx">field</span><span class="p">]:</span> <span class="p">{</span> <span class="na">$regex</span><span class="p">:</span> <span class="nx">value</span><span class="p">,</span> <span class="na">$options</span><span class="p">:</span> <span class="dl">'</span><span class="s1">i</span><span class="dl">'</span> <span class="p">}</span> <span class="p">};</span>
<span class="p">}</span>
<span class="k">case</span> <span class="sr">/~/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">):</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="sr">/</span><span class="se">\s</span><span class="sr">*~</span><span class="se">\s</span><span class="sr">*/</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">field</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="k">return</span> <span class="p">{</span> <span class="p">[</span><span class="nx">field</span><span class="p">]:</span> <span class="p">{</span> <span class="na">$regex</span><span class="p">:</span> <span class="nx">value</span> <span class="p">}</span> <span class="p">};</span>
<span class="p">}</span>
<span class="k">case</span> <span class="sr">/&/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">):</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">arr</span> <span class="o">=</span> <span class="p">[];</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="sr">/</span><span class="se">\s</span><span class="sr">*&</span><span class="se">\s</span><span class="sr">*/</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">r</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="nx">i</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="nx">arr</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="p">{</span> <span class="na">$and</span><span class="p">:</span> <span class="nx">arr</span> <span class="p">};</span>
<span class="p">}</span>
<span class="k">case</span> <span class="sr">/</span><span class="se">\|</span><span class="sr">/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">):</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">arr</span> <span class="o">=</span> <span class="p">[];</span>
<span class="kd">const</span> <span class="nx">r</span> <span class="o">=</span> <span class="nx">val</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="sr">/</span><span class="se">\s</span><span class="sr">*</span><span class="se">\|\s</span><span class="sr">*/</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">r</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">r</span><span class="p">[</span><span class="nx">i</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="nx">arr</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="p">{</span> <span class="na">$or</span><span class="p">:</span> <span class="nx">arr</span> <span class="p">};</span>
<span class="p">}</span>
<span class="nl">default</span><span class="p">:</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">val</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="nx">params</span><span class="p">[</span><span class="nx">value</span><span class="p">])</span> <span class="p">{</span>
<span class="nx">value</span> <span class="o">=</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">params</span><span class="p">[</span><span class="nx">value</span><span class="p">],</span> <span class="nx">params</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">value</span><span class="p">))</span> <span class="k">return</span> <span class="nb">Number</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">date</span> <span class="o">=</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">isNaN</span><span class="p">(</span><span class="nx">date</span><span class="p">))</span> <span class="k">return</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nx">date</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">value</span> <span class="o">||</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">convert</span> <span class="p">(</span><span class="nx">query</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">params</span> <span class="o">=</span> <span class="p">{};</span>
<span class="kd">const</span> <span class="nx">p</span> <span class="o">=</span> <span class="nx">parse</span><span class="p">(</span><span class="nx">query</span><span class="p">,</span> <span class="nx">params</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">compile</span><span class="p">(</span><span class="nx">p</span><span class="p">,</span> <span class="nx">params</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Usage example:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">query</span> <span class="o">=</span> <span class="nx">convert</span><span class="p">(</span><span class="dl">'</span><span class="s1">x > 10</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">query</span><span class="p">);</span>
<span class="c1">// {"x":{"$gt":10}}</span>
</code></pre></div></div>Anton SkshidlevskySometimes you need to give your application user a more flexible way to search the database. The search should be universal for any data and easy to understand for a person without technical knowledge. I was able to create a simple query syntax and a parser to convert them to MongoDB query syntax. Below is a description of the query syntax and the parser code for them.Reflections on JavaScript templates2023-05-17T12:00:00+00:002023-05-17T12:00:00+00:00https://meefik.github.io/2023/05/17/js-templates<p>I was thinking about template formats in JS frameworks and found the following options:</p>
<ul>
<li>Imperative creation of HTML elements in JS (native, but not convenient);</li>
<li>HTML markup as text (text cannot be validated in the IDE);</li>
<li>JSX markup (HTML tags inside JS code without quotes, syntactically incorrect in JS, but there is a layer for adaptation);</li>
<li>HyperText (HTML elements via a function, syntactically correct in JS);</li>
<li>Other less common options.</li>
</ul>
<!--more-->
<h2 id="html-markup">HTML markup</h2>
<p>Let’s say we want to get the following markup in HTML.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><section</span> <span class="na">class=</span><span class="s">"todoapp"</span><span class="nt">></span>
<span class="nt"><h1></span>To Do<span class="nt"></h1></span>
<span class="nt"><input</span> <span class="na">id=</span><span class="s">"add"</span> <span class="na">value=</span><span class="s">""</span> <span class="nt">/></span>
<span class="nt"><ul></span>
<span class="nt"><li></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"checkbox"</span> <span class="nt">/></span>
<span class="nt"><label></span>Item 1<span class="nt"></label></span>
<span class="nt"><button></span>x<span class="nt"></button></span>
<span class="nt"></li></span>
<span class="nt"></ul></span>
<span class="nt"></section></span>
</code></pre></div></div>
<h2 id="imperative-native-approach">Imperative native approach</h2>
<p>We can create the required markup using the browser’s native API.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createToDoView</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">section</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">section</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">section</span><span class="p">.</span><span class="nx">className</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">todoapp</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">header</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">h1</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">header</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">To Do</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">section</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">header</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">input</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">input</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">add</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">input</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
<span class="nx">section</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">input</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">ul</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">ul</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">section</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">ul</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">li</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">li</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">ul</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">li</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">checkbox</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">checkbox</span><span class="p">.</span><span class="nx">type</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">checkbox</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">li</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">checkbox</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">label</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">label</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">label</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Item 1</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">li</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">label</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">button</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">button</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">x</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">li</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">button</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">section</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This option is not particularly visual and redundantly verbose.</p>
<h2 id="dom-elements-from-text">DOM elements from text</h2>
<p>We can also use HTML markup as text. It looks more compact. But your IDE does not validate this.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createToDoView</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">el</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">div</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">el</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">`
<section class="todoapp">
<h1>To Do</h1>
<input id="add" value="" />
<ul>
<li>
<input type="checkbox" />
<label>Item 1</label>
<button>x</button>
</li>
</ul>
</section>
`</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">el</span><span class="p">.</span><span class="nx">firstChild</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="jsx">JSX</h2>
<p>JSX is an extension to the JavaScript language syntax. It is similar in appearance to HTML. But it is not native and adaptation is required to support the syntax.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createToDoView</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
<span class="o"><</span><span class="nx">section</span> <span class="kd">class</span><span class="o">=</span><span class="dl">"</span><span class="s2">todoapp</span><span class="dl">"</span><span class="o">></span>
<span class="o"><</span><span class="nx">h1</span><span class="o">></span><span class="nx">To</span> <span class="nx">Do</span><span class="o"><</span><span class="sr">/h1</span><span class="err">>
</span> <span class="o"><</span><span class="nx">input</span> <span class="nx">id</span><span class="o">=</span><span class="dl">"</span><span class="s2">add</span><span class="dl">"</span> <span class="nx">value</span><span class="o">=</span><span class="dl">""</span> <span class="o">/></span>
<span class="o"><</span><span class="nx">ul</span><span class="o">></span>
<span class="o"><</span><span class="nx">li</span><span class="o">></span>
<span class="o"><</span><span class="nx">input</span> <span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">checkbox</span><span class="dl">"</span> <span class="o">/></span>
<span class="o"><</span><span class="nx">label</span><span class="o">></span><span class="nx">Item</span> <span class="mi">1</span><span class="o"><</span><span class="sr">/label</span><span class="err">>
</span> <span class="o"><</span><span class="nx">button</span><span class="o">></span><span class="nx">x</span><span class="o"><</span><span class="sr">/button</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/li</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/ul</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/section</span><span class="err">>
</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="hyperscript">HyperScript</h2>
<p>Hierarchical structure represented as function calls. <a href="https://www.npmjs.com/package/hyper-dom-expressions">Hyper DOM Expressions</a> is implementation example.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createToDoView</span><span class="p">(</span><span class="nx">h</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">h</span><span class="p">(</span><span class="dl">'</span><span class="s1">section.todoapp</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span>
<span class="nx">h</span><span class="p">(</span><span class="dl">'</span><span class="s1">h1</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">To Do</span><span class="dl">'</span><span class="p">),</span>
<span class="nx">h</span><span class="p">(</span><span class="dl">'</span><span class="s1">input#add</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">value</span><span class="p">:</span> <span class="dl">''</span> <span class="p">}),</span>
<span class="nx">h</span><span class="p">(</span><span class="dl">'</span><span class="s1">ul</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span>
<span class="nx">h</span><span class="p">(</span><span class="dl">'</span><span class="s1">li</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span>
<span class="nx">h</span><span class="p">(</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">checkbox</span><span class="dl">'</span> <span class="p">}),</span>
<span class="nx">h</span><span class="p">(</span><span class="dl">'</span><span class="s1">label</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Item 1</span><span class="dl">'</span><span class="p">),</span>
<span class="nx">h</span><span class="p">(</span><span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">,</span> <span class="p">{},</span> <span class="dl">'</span><span class="s1">x</span><span class="dl">'</span><span class="p">)</span>
<span class="p">])</span>
<span class="p">])</span>
<span class="p">]);</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="declarative-markup-as-nested-arrays">Declarative markup as nested arrays</h2>
<p>Similar to HyperScript, but represented as nested arrays.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createToDoView</span><span class="p">(</span><span class="nx">parser</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">render</span><span class="p">(</span>
<span class="p">[</span><span class="dl">'</span><span class="s1">section</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span>
<span class="p">[</span><span class="dl">'</span><span class="s1">h1</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">To Do</span><span class="dl">'</span><span class="p">],</span>
<span class="p">[</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">value</span><span class="p">:</span> <span class="dl">''</span> <span class="p">}],</span>
<span class="p">[</span><span class="dl">'</span><span class="s1">ul</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span>
<span class="p">[</span><span class="dl">'</span><span class="s1">li</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span>
<span class="p">[</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">checkbox</span><span class="dl">'</span> <span class="p">}],</span>
<span class="p">[</span><span class="dl">'</span><span class="s1">label</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Item 1</span><span class="dl">'</span><span class="p">],</span>
<span class="p">[</span><span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">,</span> <span class="p">{},</span> <span class="dl">'</span><span class="s1">x</span><span class="dl">'</span><span class="p">]</span>
<span class="p">]]</span>
<span class="p">]]</span>
<span class="p">]]</span>
<span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>An example implementation of the render function:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">render</span><span class="p">(</span><span class="nx">view</span><span class="p">,</span> <span class="nx">target</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">view</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">view</span> <span class="o">=</span> <span class="nx">view</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">view</span><span class="p">))</span> <span class="p">{</span>
<span class="kd">let</span> <span class="p">[</span><span class="nx">tag</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">DIV</span><span class="dl">'</span><span class="p">,</span> <span class="nx">attrs</span><span class="p">,</span> <span class="nx">content</span><span class="p">]</span> <span class="o">=</span> <span class="nx">view</span><span class="p">;</span>
<span class="c1">// children</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">tag</span><span class="p">))</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">node</span> <span class="k">of</span> <span class="nx">view</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">render</span><span class="p">(</span><span class="nx">node</span><span class="p">,</span> <span class="nx">target</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// element</span>
<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">tag</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">attrs</span><span class="p">)</span> <span class="o">||</span> <span class="k">typeof</span> <span class="nx">attrs</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">object</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">content</span> <span class="o">=</span> <span class="nx">attrs</span><span class="p">;</span>
<span class="nx">attrs</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">el</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="nx">tag</span><span class="p">);</span>
<span class="nx">patch</span><span class="p">(</span><span class="nx">attrs</span><span class="p">,</span> <span class="nx">el</span><span class="p">);</span>
<span class="nx">render</span><span class="p">(</span><span class="nx">content</span><span class="p">,</span> <span class="nx">el</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">target</span><span class="p">)</span> <span class="nx">target</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">el</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// component</span>
<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">tag</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">attrs</span><span class="p">)</span> <span class="o">||</span> <span class="k">typeof</span> <span class="nx">attrs</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">object</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">content</span> <span class="o">=</span> <span class="nx">attrs</span><span class="p">;</span>
<span class="nx">attrs</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">obj</span> <span class="o">=</span> <span class="nx">tag</span><span class="p">(</span><span class="nx">attrs</span><span class="p">,</span> <span class="nx">content</span><span class="p">);</span>
<span class="nx">render</span><span class="p">(</span><span class="nx">obj</span><span class="p">,</span> <span class="nx">target</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">target</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">target</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="nx">view</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">patch</span> <span class="p">(</span><span class="nx">source</span> <span class="o">=</span> <span class="p">{},</span> <span class="nx">target</span> <span class="o">=</span> <span class="p">{})</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">key</span> <span class="k">in</span> <span class="nx">source</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">source</span><span class="p">[</span><span class="nx">key</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">value</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&&</span> <span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">object</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">patch</span><span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="nx">target</span><span class="p">[</span><span class="nx">key</span><span class="p">]);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">target</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="declarative-markup-as-nested-objects">Declarative markup as nested objects</h2>
<p>Declarative hierarchical structure represented as nested objects.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createToDoView</span><span class="p">(</span><span class="nx">parser</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">render</span><span class="p">(</span>
<span class="p">{</span> <span class="na">tagName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">section</span><span class="dl">'</span><span class="p">,</span> <span class="na">className</span><span class="p">:</span> <span class="dl">'</span><span class="s1">todoapp</span><span class="dl">'</span><span class="p">,</span> <span class="na">children</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="na">tagName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">h1</span><span class="dl">'</span><span class="p">,</span> <span class="na">textContent</span><span class="p">:</span> <span class="dl">'</span><span class="s1">To Do</span><span class="dl">'</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">tagName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">,</span> <span class="na">id</span><span class="p">:</span> <span class="dl">'</span><span class="s1">add</span><span class="dl">'</span><span class="p">,</span> <span class="na">value</span><span class="p">:</span> <span class="dl">''</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">tagName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ul</span><span class="dl">'</span><span class="p">,</span> <span class="na">children</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="na">tagName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">li</span><span class="dl">'</span><span class="p">,</span> <span class="na">children</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="na">tagName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">,</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">checkbox</span><span class="dl">'</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">tagName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">label</span><span class="dl">'</span><span class="p">,</span> <span class="na">textContent</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Item 1</span><span class="dl">'</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">tagName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">,</span> <span class="na">textContent</span><span class="p">:</span> <span class="dl">'</span><span class="s1">x</span><span class="dl">'</span> <span class="p">}</span>
<span class="p">]</span> <span class="p">}</span>
<span class="p">]</span> <span class="p">}</span>
<span class="p">]</span> <span class="p">}</span>
<span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>An example implementation of the render function:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">render</span> <span class="p">(</span><span class="nx">view</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span>
<span class="nx">attributes</span><span class="p">,</span>
<span class="nx">classList</span> <span class="o">=</span> <span class="p">[],</span>
<span class="nx">tagName</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">DIV</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">children</span>
<span class="p">}</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">view</span> <span class="p">};</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">attr</span> <span class="k">of</span> <span class="p">[</span><span class="dl">'</span><span class="s1">attributes</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">classList</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">tagName</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">children</span><span class="dl">'</span><span class="p">])</span> <span class="p">{</span>
<span class="k">delete</span> <span class="nx">view</span><span class="p">[</span><span class="nx">attr</span><span class="p">];</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">el</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="nx">tagName</span><span class="p">);</span>
<span class="nx">patch</span><span class="p">(</span><span class="nx">view</span><span class="p">,</span> <span class="nx">el</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">classList</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">cls</span> <span class="o">=</span> <span class="nx">classList</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>
<span class="nx">el</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">cls</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">attr</span> <span class="k">in</span> <span class="nx">attributes</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">val</span> <span class="o">=</span> <span class="nx">attributes</span><span class="p">[</span><span class="nx">attr</span><span class="p">];</span>
<span class="nx">el</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="nx">attr</span><span class="p">,</span> <span class="nx">val</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">children</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">object</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">nodes</span> <span class="o">=</span> <span class="p">[].</span><span class="nx">concat</span><span class="p">(</span><span class="nx">children</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">node</span> <span class="k">of</span> <span class="nx">nodes</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">child</span> <span class="o">=</span> <span class="nx">render</span><span class="p">(</span><span class="nx">node</span><span class="p">);</span>
<span class="nx">el</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">child</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">children</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">nodes</span> <span class="o">=</span> <span class="p">[].</span><span class="nx">concat</span><span class="p">(</span><span class="nx">children</span><span class="p">(</span><span class="nx">view</span><span class="p">));</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">node</span> <span class="k">of</span> <span class="nx">nodes</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">child</span> <span class="o">=</span> <span class="nx">render</span><span class="p">(</span><span class="nx">node</span><span class="p">);</span>
<span class="nx">el</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">child</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">el</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">patch</span> <span class="p">(</span><span class="nx">source</span> <span class="o">=</span> <span class="p">{},</span> <span class="nx">target</span> <span class="o">=</span> <span class="p">{})</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">key</span> <span class="k">in</span> <span class="nx">source</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">source</span><span class="p">[</span><span class="nx">key</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">value</span> <span class="o">=</span> <span class="nx">value</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="nx">key</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">value</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&&</span> <span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">object</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">patch</span><span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="nx">target</span><span class="p">[</span><span class="nx">key</span><span class="p">]);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">target</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>I like this option. It is native to JavaScript and uses standard DOM element attributes as a nested object structure.</p>Anton SkshidlevskyI was thinking about template formats in JS frameworks and found the following options: Imperative creation of HTML elements in JS (native, but not convenient); HTML markup as text (text cannot be validated in the IDE); JSX markup (HTML tags inside JS code without quotes, syntactically incorrect in JS, but there is a layer for adaptation); HyperText (HTML elements via a function, syntactically correct in JS); Other less common options.UX framework for JavaScript libraries2021-12-02T12:00:00+00:002021-12-02T12:00:00+00:00https://meefik.github.io/2021/12/02/libux<p>In one of my JS projects, I needed to implement UX interfaces in the Web SDK. Such a Web SDK was embedded on pages of third-party sites and for security reasons should not contain any external dependencies, including libraries like React. For these purposes, an own implementation of the UX framework was created, which can be embedded on the country of any site without problems and conflicts.</p>
<p>The code is implemented as a JS library and <a href="https://github.com/meefik/libux">posted on GitHub</a> under the MIT license. Build size - 12kb uncompressed (4kb gzipped).</p>
<p><img src="/assets/images/libux-todo.png" alt="libux-todo" title="To Do MVC example" /></p>
<!--more-->
<p>The library consists of the following modules (classes):</p>
<ul>
<li><strong>Component</strong> (extends State) - ensures the life cycle of the UX component and their states.</li>
<li><strong>Locale</strong> (extends State) - provides localization of interfaces.</li>
<li><strong>Service</strong> (extends State) - provides data exchange with the backend.</li>
<li><strong>HashRouter</strong> (extends Router) - implements roaming logic based on hashes in the address line.</li>
</ul>
<h2 id="components-and-states">Components and states</h2>
<p>Usually the application consists of separate interconnected components. The <code class="language-plaintext highlighter-rouge">Component</code> class is used to describe the logic of each UX component. This includes the following functionality:</p>
<ul>
<li>component status events:
<ul>
<li>added - a new element is added to one of the state fields (for arrays);</li>
<li>deleted - element of one of the state fields is deleted (for arrays);</li>
<li>updated - the value of one of the status fields has changed;</li>
</ul>
</li>
<li>component lifecycle events:
<ul>
<li>mounted - mounting the component in the DOM;</li>
<li>removed - removing the component from the DOM;</li>
<li>changed - the input parameters of the component have been changed;</li>
</ul>
</li>
<li>component’s overrideable properties:
<ul>
<li>data() - function for determining the initial value of the component state;</li>
<li>events() - function for defining event handlers for a component;</li>
<li>template() - function for determining the HTML template of a component (<a href="https://ejs.co/#docs">EJS</a> format).</li>
</ul>
</li>
</ul>
<p>In addition, there is a set of component methods that allow you to manage the state, subscribe to events, etc.</p>
<p>An example of a component description:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Component</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">libux</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nx">MyView</span> <span class="kd">extends</span> <span class="nx">Component</span> <span class="p">{</span>
<span class="kd">constructor</span> <span class="p">(...</span><span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">(...</span><span class="nx">args</span><span class="p">);</span>
<span class="c1">// this.params - includes this component arguments</span>
<span class="k">this</span><span class="p">.</span><span class="nx">mount</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">template</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="s2">`<div>Hello <%- this.state.text %>!</div>`</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">data</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">text</span><span class="p">:</span> <span class="dl">'</span><span class="s1">World</span><span class="dl">'</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="nx">events</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">[</span>
<span class="nx">mounted</span> <span class="p">()</span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="localization">Localization</h2>
<p>Localization of components allows you to display text information in different languages. This functionality is implemented in the <code class="language-plaintext highlighter-rouge">Locale</code> class.</p>
<p>Example of using localization:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Locale</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">libux</span><span class="dl">'</span><span class="p">;</span>
<span class="c1">// create a new instance</span>
<span class="kd">const</span> <span class="nx">locales</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">en</span><span class="p">:</span> <span class="p">{</span> <span class="na">hello</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Hello %{name}!</span><span class="dl">'</span> <span class="p">},</span>
<span class="na">ru</span><span class="p">:</span> <span class="p">{</span> <span class="na">hello</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Привет %{name}!</span><span class="dl">'</span> <span class="p">}</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">l10n</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Locale</span><span class="p">({</span> <span class="nx">locales</span><span class="p">,</span> <span class="na">lang</span><span class="p">:</span> <span class="dl">'</span><span class="s1">en</span><span class="dl">'</span> <span class="p">});</span>
<span class="c1">// get translation for specified path</span>
<span class="nx">l10n</span><span class="p">.</span><span class="nx">t</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">World</span><span class="dl">'</span> <span class="p">});</span> <span class="c1">// Hello World!</span>
<span class="c1">// list of supported languages</span>
<span class="nx">l10n</span><span class="p">.</span><span class="nx">languages</span><span class="p">;</span> <span class="c1">// ['en', 'ru']</span>
<span class="c1">// switch to localization 'ru'</span>
<span class="nx">l10n</span><span class="p">.</span><span class="nx">update</span><span class="p">({</span> <span class="na">lang</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ru</span><span class="dl">'</span> <span class="p">});</span>
</code></pre></div></div>
<h2 id="hash-routing">Hash Routing</h2>
<p>Routing at the application level allows you to organize the logic of switching components (pages of the <a href="https://en.wikipedia.org/wiki/Single-page_application">SPA</a> application). This functionality is implemented in the <code class="language-plaintext highlighter-rouge">HashRouter</code> class.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">HashRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">libux</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">TodoView</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">views/todo</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">router</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HashRouter</span><span class="p">({</span>
<span class="na">routes</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">#/</span><span class="dl">'</span><span class="p">:</span> <span class="nx">TodoView</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">router</span><span class="p">.</span><span class="nx">show</span><span class="p">(</span><span class="nx">location</span><span class="p">.</span><span class="nx">hash</span><span class="p">);</span>
</code></pre></div></div>
<h2 id="services">Services</h2>
<p>Services are the layer between the components and the backend API. They implement <a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a> functions for exchanging data with the backend. This functionality is implemented in the <code class="language-plaintext highlighter-rouge">Service</code> class.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">service</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Service</span><span class="p">({</span>
<span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http://localhost:3000/api/items</span><span class="dl">'</span>
<span class="p">});</span>
<span class="c1">// create new record</span>
<span class="nx">service</span><span class="p">.</span><span class="nx">post</span><span class="p">({</span> <span class="na">id</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1</span><span class="dl">'</span><span class="p">,</span> <span class="na">field1</span><span class="p">:</span> <span class="dl">'</span><span class="s1">a</span><span class="dl">'</span><span class="p">,</span> <span class="na">field2</span><span class="p">:</span> <span class="dl">'</span><span class="s1">b</span><span class="dl">'</span> <span class="p">});</span>
<span class="c1">// get all records</span>
<span class="nx">service</span><span class="p">.</span><span class="kd">get</span><span class="p">();</span>
<span class="c1">// get the record by id</span>
<span class="nx">serice</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">1</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// update the record by id</span>
<span class="nx">service</span><span class="p">.</span><span class="nx">put</span><span class="p">(</span><span class="dl">'</span><span class="s1">1</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">field1</span><span class="p">:</span> <span class="dl">'</span><span class="s1">c</span><span class="dl">'</span><span class="p">,</span> <span class="na">field2</span><span class="p">:</span> <span class="dl">'</span><span class="s1">d</span><span class="dl">'</span> <span class="p">});</span>
<span class="c1">// remove the record by id</span>
<span class="nx">service</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="dl">'</span><span class="s1">1</span><span class="dl">'</span><span class="p">);</span>
</code></pre></div></div>
<h2 id="css-modules">CSS modules</h2>
<p>Styles are recommended to be connected as <a href="https://webpack.js.org/loaders/css-loader/">CSS modules</a>. Each style will be assigned a unique identifier during assembly, which allows you to avoid conflicts with other styles on the page. In the project code, you will need to import a style file and call specific styles from the imported file by their name in it.</p>
<p>Example of using CSS modules:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">css</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">todomvc-app-css/index.css</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nx">TodoView</span> <span class="kd">extends</span> <span class="nx">Component</span> <span class="p">{</span>
<span class="nx">template</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="s2">`
<section class="</span><span class="p">${</span><span class="nx">css</span><span class="p">.</span><span class="nx">todoapp</span><span class="p">}</span><span class="s2">">
...
</section>
<footer class="</span><span class="p">${</span><span class="nx">css</span><span class="p">.</span><span class="nx">info</span><span class="p">}</span><span class="s2">">
...
</footer>
`</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The project has documentation in <a href="https://jsdoc.app">JSDoc</a> format and a tutorial with an example of use in the form of the <a href="http://todomvc.com">To-Do List</a> application.</p>Anton SkshidlevskyIn one of my JS projects, I needed to implement UX interfaces in the Web SDK. Such a Web SDK was embedded on pages of third-party sites and for security reasons should not contain any external dependencies, including libraries like React. For these purposes, an own implementation of the UX framework was created, which can be embedded on the country of any site without problems and conflicts. The code is implemented as a JS library and posted on GitHub under the MIT license. Build size - 12kb uncompressed (4kb gzipped).Encoding and decoding data in PNG with compression2020-02-22T12:00:00+00:002020-02-22T12:00:00+00:00https://meefik.github.io/2020/02/22/aspng<p>I want to talk about a couple of interesting ways to encode data - encoding in the form of a picture and embedding them into an existing picture. I experimented with the PNG format because it uses lossless compression and is supported in browsers in the <code class="language-plaintext highlighter-rouge">canvas</code> element. I am interested in JavaScript, so the implementation will be written in it. The code is implemented as a JS library and <a href="https://github.com/meefik/aspng">posted on GitHub</a> under the MIT license.</p>
<p>The first encoding option is to generate a new picture based on arbitrary data. To do this, each byte of data is recorded sequentially in the RGB channels of the PNG picture, while the alpha channel is not touched, since when the alpha channel changes, the RGB colors partially change when unloading from <code class="language-plaintext highlighter-rouge">canvas</code> to PNG. In this variant, <code class="language-plaintext highlighter-rouge">WIDTH * HEIGHT * 3</code> bytes of data can be packed in PNG. When encoding text, the image size is smaller than the source text, since Deflate compression is applied to the data.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// encode file to PNG</span>
<span class="nx">asPNG</span><span class="p">.</span><span class="nx">encode</span><span class="p">(</span><span class="nx">file</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">blob</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// encoded blob</span>
<span class="p">});</span>
<span class="c1">// decode file from PNG</span>
<span class="nx">asPNG</span><span class="p">.</span><span class="nx">decode</span><span class="p">(</span><span class="nx">file</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">blob</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// decoded blob</span>
<span class="p">});</span>
</code></pre></div></div>
<p><img src="/assets/images/aspng-encoded.png" alt="aspng_encoded" title="Data as an image" /></p>
<!--more-->
<p>The second encoding option is even more interesting and belongs to the area of <a href="https://en.wikipedia.org/wiki/Steganography">steganography</a>. The essence of the approach is that the encoded data is hidden among the original image data in such a way that the visually obtained image is almost indistinguishable from the original. The algorithm of my implementation is as follows:</p>
<ul>
<li>We align the data of each color channel in steps of 7, do not touch the alpha channel. This reduces the color gradation of the image by 7 times, instead of 256 shades per channel, we get approximately 36 shades per channel. This alignment degrades the image quality, but improves its compression and when adding a small amount of data, the size of the original PNG file decreases.</li>
<li>We add data in such a way that each byte of data can be encoded in one pixel of the image. To do this, add 1 byte of data in a sevenfold format to the aligned data.</li>
<li>We distribute the data evenly throughout the image if there is less data than can be placed in the image.</li>
</ul>
<p>In this variant, up to <code class="language-plaintext highlighter-rouge">WIDTH * HEIGHT</code> bytes of data can be packed in PNG.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// inject data to PNG image</span>
<span class="nx">asPNG</span><span class="p">.</span><span class="nx">inject</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="nx">img</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">blob</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// modified image</span>
<span class="p">});</span>
<span class="c1">// extract data from PNG image</span>
<span class="nx">asPNG</span><span class="p">.</span><span class="nx">extract</span><span class="p">(</span><span class="nx">img</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">blob</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// extracted data</span>
<span class="p">});</span>
</code></pre></div></div>
<p><img src="/assets/images/aspng-injected.png" alt="aspng_injected" title="Injected data into an image" /></p>Anton SkshidlevskyI want to talk about a couple of interesting ways to encode data - encoding in the form of a picture and embedding them into an existing picture. I experimented with the PNG format because it uses lossless compression and is supported in browsers in the canvas element. I am interested in JavaScript, so the implementation will be written in it. The code is implemented as a JS library and posted on GitHub under the MIT license. The first encoding option is to generate a new picture based on arbitrary data. To do this, each byte of data is recorded sequentially in the RGB channels of the PNG picture, while the alpha channel is not touched, since when the alpha channel changes, the RGB colors partially change when unloading from canvas to PNG. In this variant, WIDTH * HEIGHT * 3 bytes of data can be packed in PNG. When encoding text, the image size is smaller than the source text, since Deflate compression is applied to the data. // encode file to PNG asPNG.encode(file).then(blob => { // encoded blob }); // decode file from PNG asPNG.decode(file).then(blob => { // decoded blob });Homemade LED Keychain Beacon2019-09-29T12:00:00+00:002019-09-29T12:00:00+00:00https://meefik.github.io/2019/09/29/keychain<p>Some time ago I found a curious device on the Internet - a keychain powered by a radioisotope of hydrogen (tritium). The principle of operation there is as follows: there is an isotope in the sealed cavity, the inner part of the cavity is covered with a phosphor, which glows when exposed to electrons emitted as a result of beta decay of tritium. The half-life of tritium is 12 years, which ensures the continuous glow of the phosphor for many years.</p>
<p><img src="/assets/images/keychain.jpg" alt="keychain" title="LED Keychain Beacon" /></p>
<p>I was inspired by this idea and somewhere in 2013, the idea appeared to make a keychain beacon so that it glowed and was visible in the dark for a long time. You can use it for keys, you can use it for something else, I was interested in the idea. However, I decided to use not a radioactive element, but an electric circuit with an ordinary LED. It remained to make the LED glow for many years without replacing the batteries, while maintaining the size of the keychain.</p>
<!--more-->
<p>To implement the idea, I found the so-called <a href="https://en.wikipedia.org/wiki/Joule_thief">“Joule thief” circuit</a> to power the LED from 1.5 V pulsating current and selected the components so that the circuit consumed a minimum of energy. It turned out to achieve a consumption of 0.01 mA with a pulsation frequency of 1 Hz. The design life of such a scheme from the LR44 battery was 3 years, in practice the battery lasted about 5 years of continuous operation until the complete cessation of glow.</p>
<p>The JT circuit was as follows:</p>
<p><img src="/assets/images/jt4.png" alt="jt" title="JT diagram" /></p>
<p>This is how it looks assembled:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/7JCmp_e1h3U" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>I only got around to assembling the keychain case now. I used an old AA battery, sawed off the bottom of it and took out all the contents. I put the JT circuit inside, powered by an LR44 battery, and filled the circuit with hot melt adhesive for insulation. Then I took transparent epoxy resin and tinted it with green phosphor. Filled the insides with resin and left some resin on the LED side. After the resin cured, I sanded everything and put a loop on the back side to attach the keychain (also filled with resin). The result is a cylinder the size of an AA battery, one end of which glows and the other can be attached as a keychain. The epoxy resin makes the keychain durable and waterproof, and the phosphor adds a nice greenish glow between blinks of the LED.</p>
<p><img src="/assets/images/keychain.gif" alt="keychain" title="Keychain demo" /></p>
<p>That’s all, as they say, “Just for fun” :)</p>Anton SkshidlevskySome time ago I found a curious device on the Internet - a keychain powered by a radioisotope of hydrogen (tritium). The principle of operation there is as follows: there is an isotope in the sealed cavity, the inner part of the cavity is covered with a phosphor, which glows when exposed to electrons emitted as a result of beta decay of tritium. The half-life of tritium is 12 years, which ensures the continuous glow of the phosphor for many years. I was inspired by this idea and somewhere in 2013, the idea appeared to make a keychain beacon so that it glowed and was visible in the dark for a long time. You can use it for keys, you can use it for something else, I was interested in the idea. However, I decided to use not a radioactive element, but an electric circuit with an ordinary LED. It remained to make the LED glow for many years without replacing the batteries, while maintaining the size of the keychain.Training 5 points model for OpenCV Facemark API2018-09-20T12:00:00+00:002018-09-20T12:00:00+00:00https://meefik.github.io/2018/09/20/opencv-5points-facemark<p>More recently, OpenCV introduced an API for detecting anthropometric points of the face. There is a good article on using the Facemark API <a href="https://www.learnopencv.com/facemark-facial-landmark-detection-using-opencv/">at Learn OpenCV</a>. A good implementation of the search for such points is in <a href="http://dlib.net/face_landmark_detection_ex.cpp.html">the dlib library</a>, but sometimes you want to limit yourself to one library, especially when it comes to porting code to mobile devices or a browser (by the way, OpenCV <a href="https://docs.opencv.org/master/d5/d10/tutorial_js_root.html">supports compilation in WebAssembly</a>). Unfortunately, face point search algorithms use models of a sufficiently large size (68 points ~ 54 MB), and the size of the downloadable code per client may be limited. The dlib library has a pre-trained <a href="https://github.com/davisking/dlib-models">model for 5 points</a> (5.44 MB), but for OpenCV there is no such model, and there is not even support for such a model, at the moment models for 68 and 29 points are supported. The 5-point model can be used to normalize faces on the client. Below I will describe the process of learning your own model of a small size for 5 points.</p>
<!--more-->
<h3 id="patch-for-opencv">Patch for OpenCV</h3>
<p>As I said, OpenCV does not currently support 5-point models. Therefore, I had to get into the code of the FacemarkLBF module and correct this misunderstanding. We will use the implementation of the LBF method to search for points, but in OpenCV there is also an implementation of the AAM method. Below is a patch for the <a href="https://github.com/opencv/opencv_contrib/blob/master/modules/face/src/facemarkLBF.cpp">facemarkLBF.cpp</a> file that adds support for the 5-point model:</p>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/modules/face/src/facemarkLBF.cpp b/modules/face/src/facemarkLBF.cpp
index 50192286..dc354617 100644
</span><span class="gd">--- a/modules/face/src/facemarkLBF.cpp
</span><span class="gi">+++ b/modules/face/src/facemarkLBF.cpp
</span><span class="p">@@ -571,7 +571,13 @@</span> void FacemarkLBFImpl::data_augmentation(std::vector<Mat> &imgs, std::vector<Mat>
shape.at<double>(j-1, 1) = tmp; \
} while(0)
<span class="gd">- if (params.n_landmarks == 29) {
</span><span class="gi">+ if (params.n_landmarks == 5) {
+ for (int i = N; i < (int)gt_shapes.size(); i++) {
+ SWAP(gt_shapes[i], 1, 3);
+ SWAP(gt_shapes[i], 2, 4);
+ }
+ }
+ else if (params.n_landmarks == 29) {
</span> for (int i = N; i < (int)gt_shapes.size(); i++) {
SWAP(gt_shapes[i], 1, 2);
SWAP(gt_shapes[i], 3, 4);
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">face</code> module is in the <a href="https://github.com/opencv/opencv_contrib">opencv_contrib</a> repository, and the OpenCV itself is in the <a href="https://github.com/opencv/opencv">opencv</a> repository. To build, you need to download the main repository <code class="language-plaintext highlighter-rouge">opencv</code> and <code class="language-plaintext highlighter-rouge">opencv_contrib</code> and execute the following script from a separate empty subdirectory.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
cmake <span class="s2">"../opencv"</span> <span class="se">\</span>
<span class="nt">-DCMAKE_BUILD_TYPE</span><span class="o">=</span>Release <span class="se">\</span>
<span class="nt">-DBUILD_opencv_face</span><span class="o">=</span>ON <span class="se">\</span>
<span class="nt">-DOPENCV_EXTRA_MODULES_PATH</span><span class="o">=</span><span class="s2">"../opencv_contrib/modules"</span>
make <span class="nt">-j</span><span class="si">$(</span><span class="nb">grep</span> <span class="nt">-c</span> ^processor /proc/cpuinfo<span class="si">)</span>
</code></pre></div></div>
<h3 id="preparing-the-model-training-set">Preparing the Model Training Set</h3>
<p>In general, the training procedure is described in the <a href="https://docs.opencv.org/master/d7/dec/tutorial_facemark_usage.html">OpenCV manual</a>. But in our case, you need to prepare a training set of photos with markup on 5 points. Fortunately, there is a <a href="http://dlib.net/files/data/dlib_faces_5points.tar">corresponding training set</a> in dlib, all that remains is to convert it to OpenCV format. The archive contains 7364 photos with the markup as in the following picture:</p>
<p><img src="/assets/images/face_045889.jpg" alt="face_045889" title="5 points facemark" /></p>
<p>I wrote a script to convert labels to OpenCV format:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
parse_xml<span class="o">()</span>
<span class="o">{</span>
<span class="nv">xml_file</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
<span class="nv">out_dir</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>
xmllint <span class="nt">--xpath</span> <span class="s2">"//dataset/assets/images/image/@file"</span> <span class="s2">"</span><span class="nv">$xml_file</span><span class="s2">"</span> | xargs | <span class="nb">tr</span> <span class="s1">' '</span> <span class="s1">'\n'</span> | <span class="nb">cut</span> <span class="nt">-f2</span> <span class="nt">-d</span><span class="s1">'='</span> | <span class="k">while </span><span class="nb">read </span>f
<span class="k">do
</span><span class="nb">echo</span> <span class="nv">$f</span>
<span class="nv">out_file</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">f</span><span class="p">#*/</span><span class="k">}</span><span class="s2">"</span>
<span class="nv">out_file</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">out_file</span><span class="p">%.jpg</span><span class="k">}</span><span class="s2">.pts"</span>
<span class="nv">out_file</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">out_dir</span><span class="p">%/</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">out_file</span><span class="k">}</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"version: 1"</span> <span class="o">></span><span class="s2">"</span><span class="nv">$out_file</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"n_points: 5"</span> <span class="o">>></span><span class="s2">"</span><span class="nv">$out_file</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"{"</span> <span class="o">>></span><span class="s2">"</span><span class="nv">$out_file</span><span class="s2">"</span>
xmllint <span class="nt">--xpath</span> <span class="s2">"//dataset/assets/images/image[@file='</span><span class="nv">$f</span><span class="s2">']/box[1]/part"</span> <span class="s2">"</span><span class="nv">$xml_file</span><span class="s2">"</span> | <span class="nb">sed</span> <span class="s1">'s/>/\n/g'</span> | <span class="nb">sed</span> <span class="nt">-E</span> <span class="s1">'s/.*x=\"([0-9]+)\" y=\"([0-9]+)\".*/\1 \2/g'</span> <span class="o">>></span><span class="s2">"</span><span class="nv">$out_file</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"}"</span> <span class="o">>></span><span class="s2">"</span><span class="nv">$out_file</span><span class="s2">"</span>
<span class="k">done</span>
<span class="o">}</span>
<span class="nb">test</span> <span class="nt">-e</span> ./points <span class="o">||</span> <span class="nb">mkdir</span> ./points
parse_xml ./test_cleaned.xml ./points
parse_xml ./train_cleaned.xml ./points
<span class="o">(</span> <span class="nb">cd</span> ./images<span class="p">;</span> <span class="nb">ls</span> <span class="nv">$PWD</span>/<span class="k">*</span> <span class="o">)</span> <span class="o">></span>./images_train.txt
<span class="o">(</span> <span class="nb">cd</span> ./points<span class="p">;</span> <span class="nb">ls</span> <span class="nv">$PWD</span>/<span class="k">*</span> <span class="o">)</span> <span class="o">></span>./points_train.txt
</code></pre></div></div>
<p>The script must be placed in the <code class="language-plaintext highlighter-rouge">dlib_faces_5points</code> directory, the output will be the <code class="language-plaintext highlighter-rouge">points</code> directory with labels, as well as <code class="language-plaintext highlighter-rouge">images_train.txt</code> and <code class="language-plaintext highlighter-rouge">points_train.txt</code> files with a list of image and label files.</p>
<h3 id="training-and-use-of-the-model">Training and use of the model</h3>
<p>To train the model, a program has been prepared that must be precompiled using <code class="language-plaintext highlighter-rouge">cmake</code>. The model parameters are as follows:</p>
<table>
<thead>
<tr>
<th>Parameter</th>
<th>Value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>n_landmarks</td>
<td>5</td>
<td>Number of points</td>
</tr>
<tr>
<td>initShape_n</td>
<td>10</td>
<td>Training Sample Multiplier</td>
</tr>
<tr>
<td>stages_n</td>
<td>10</td>
<td>Number of training stages</td>
</tr>
<tr>
<td>tree_n</td>
<td>20</td>
<td>Number of trees</td>
</tr>
<tr>
<td>tree_depth</td>
<td>5</td>
<td>Depth of each tree</td>
</tr>
</tbody>
</table>
<p>You can see the result in an excerpt from the video <a href="https://www.youtube.com/watch?v=h-Gcl58WbGQ">15 Newly Discovered Facial Expressions</a>:</p>
<p><img src="/assets/images/facemark.gif" alt="facemark" title="5 points video" /></p>
<p>The code is published in <a href="https://github.com/meefik/opencv_facemark">the repository on Github</a>. Already trained model is <a href="https://raw.githubusercontent.com/meefik/opencv_facemark/master/lbfmodel.yaml">available here</a>, file size 3.6 MB. There is also a code for detecting points on video from a webcam, which is launched immediately after training. It should be noted that the model should be used only with the face detector, which was used in training, in this case Viola-Jones.</p>Anton SkshidlevskyMore recently, OpenCV introduced an API for detecting anthropometric points of the face. There is a good article on using the Facemark API at Learn OpenCV. A good implementation of the search for such points is in the dlib library, but sometimes you want to limit yourself to one library, especially when it comes to porting code to mobile devices or a browser (by the way, OpenCV supports compilation in WebAssembly). Unfortunately, face point search algorithms use models of a sufficiently large size (68 points ~ 54 MB), and the size of the downloadable code per client may be limited. The dlib library has a pre-trained model for 5 points (5.44 MB), but for OpenCV there is no such model, and there is not even support for such a model, at the moment models for 68 and 29 points are supported. The 5-point model can be used to normalize faces on the client. Below I will describe the process of learning your own model of a small size for 5 points.Face detection with rotation invariance2018-08-27T12:00:00+00:002018-08-27T12:00:00+00:00https://meefik.github.io/2018/08/27/picojs-face-detection<p>Not so long ago I came across the publication <a href="https://arxiv.org/abs/1305.4537">Object Detection with Pixel Intensity Comparisons Organized in Decision Trees</a>, the authors of which offer a modification of the <a href="https://en.wikipedia.org/wiki/Viola%E2%80%93Jones_object_detection_framework">Viola-Jones</a>. The main difference of the method is that instead of <a href="https://en.wikipedia.org/wiki/Haar-like_feature">Haar features</a>, simple pixel tests are used without the need to calculate <a href="https://en.wikipedia.org/wiki/Summed-area_table">an integral image</a>. This allows you to increase the speed of calculations and save memory.</p>
<p>The authors of the article give <a href="https://github.com/nenadmarkus/pico">an example of the implementation</a> of this algorithm in C with a pre-trained classifier for face detection. Recently, an implementation of the <a href="https://github.com/tehnokv/picojs">PICO algorithm in JS</a> has appeared, but it does not implement the invariance of turning the image (or tilting the head to the left/right). This is the shortcoming I decided to fix.</p>
<p>To implement the rotation invariance, it is necessary to run the algorithm several times for the image rotated at several angles. But since the algorithm works with pixels, and not an integral image, you can not perform a resource-intensive image rotation operation, but simply read the desired pixels using <a href="https://en.wikipedia.org/wiki/Rotation_matrix">a rotation matrix</a>.</p>
<p>The revision included:</p>
<ul>
<li>implementation of invariance to turn;</li>
<li>re-trained classifier of persons;</li>
<li>a more productive method for converting the RGBA image to grayscale;</li>
<li>parallel execution in the Web-worker;</li>
<li>code on ES6.</li>
</ul>
<!--more-->
<p>JS library code: <a href="https://github.com/meefik/picojs">https://github.com/meefik/picojs</a></p>
<h3 id="using-the-js-library">Using the JS library</h3>
<p>All parameters of the library are set in the constructor, here is their description:</p>
<table>
<thead>
<tr>
<th>Parameter</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>shiftfactor</td>
<td>0.1</td>
<td>Sliding window movement step as a percentage (10%) of the image size</td>
</tr>
<tr>
<td>scalefactor</td>
<td>1.1</td>
<td>Sliding window resizing step as a percentage (10%) of image size</td>
</tr>
<tr>
<td>initialsize</td>
<td>0.1</td>
<td>Initial size of the sliding window as a percentage (10%) of the image size</td>
</tr>
<tr>
<td>rotation</td>
<td>0</td>
<td>Array of rotation angles to be searched (0 to 360 in 1 degree increments)</td>
</tr>
<tr>
<td>threshold</td>
<td>0.2</td>
<td>Percentage (20%) of intersections of found candidates for grouping them into one area</td>
</tr>
<tr>
<td>memory</td>
<td>1</td>
<td>Number of images (frames) in memory to improve detection quality</td>
</tr>
</tbody>
</table>
<p>The output is an array of areas where the algorithm assumes there are faces. Here is a description of this area:</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>c</td>
<td>X-coordinate of the center of the found face area</td>
</tr>
<tr>
<td>r</td>
<td>Y-coordinate of the center of the found face area</td>
</tr>
<tr>
<td>s</td>
<td>Size of found area (width and height or diameter)</td>
</tr>
<tr>
<td>q</td>
<td>Detection quality (higher is better quality)</td>
</tr>
<tr>
<td>a</td>
<td>Rotation angle of the image (the most likely one listed in the rotation parameter)</td>
</tr>
</tbody>
</table>
<p>Code example:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// load cascade</span>
<span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">./cascade.dat</span><span class="dl">'</span><span class="p">)</span>
<span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">response</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">response</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">statusText</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">Request error</span><span class="dl">'</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">response</span><span class="p">.</span><span class="nx">arrayBuffer</span><span class="p">();</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">cascade</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// create PICO detector with options</span>
<span class="k">return</span> <span class="nx">PICO</span><span class="p">(</span><span class="nx">cascade</span><span class="p">,</span> <span class="p">{</span>
<span class="na">shiftfactor</span><span class="p">:</span> <span class="mf">0.1</span><span class="p">,</span> <span class="c1">// move the detection window by 10% of its size</span>
<span class="na">scalefactor</span><span class="p">:</span> <span class="mf">1.1</span><span class="p">,</span> <span class="c1">// resize the detection window by 10% when moving to the higher scale</span>
<span class="na">initialsize</span><span class="p">:</span> <span class="mf">0.1</span><span class="p">,</span> <span class="c1">// minimum size of a face (10% of image area)</span>
<span class="na">rotation</span><span class="p">:</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">60</span><span class="p">,</span> <span class="mi">90</span><span class="p">,</span> <span class="mi">270</span><span class="p">,</span> <span class="mi">300</span><span class="p">,</span> <span class="mi">330</span><span class="p">],</span> <span class="c1">// rotation angles in degrees</span>
<span class="na">threshold</span><span class="p">:</span> <span class="mf">0.2</span><span class="p">,</span> <span class="c1">// overlap threshold</span>
<span class="na">memory</span><span class="p">:</span> <span class="mi">3</span> <span class="c1">// number of images in the memory</span>
<span class="p">});</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">detect</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// image = ImageData</span>
<span class="k">return</span> <span class="nx">detect</span><span class="p">(</span><span class="nx">image</span><span class="p">);</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">dets</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// dets = [{ r: rows, c: cols, s: size, q: quality, a: angle }]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">dets</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>
<p>Running a demo:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install
</span>npm run dev
</code></pre></div></div>
<p>Once the server is up and running, the demo page will be available at <a href="http://localhost:8000">http://localhost:8000</a></p>
<p>And a small video:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/9WiGC08_ZFY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>Anton SkshidlevskyNot so long ago I came across the publication Object Detection with Pixel Intensity Comparisons Organized in Decision Trees, the authors of which offer a modification of the Viola-Jones. The main difference of the method is that instead of Haar features, simple pixel tests are used without the need to calculate an integral image. This allows you to increase the speed of calculations and save memory. The authors of the article give an example of the implementation of this algorithm in C with a pre-trained classifier for face detection. Recently, an implementation of the PICO algorithm in JS has appeared, but it does not implement the invariance of turning the image (or tilting the head to the left/right). This is the shortcoming I decided to fix. To implement the rotation invariance, it is necessary to run the algorithm several times for the image rotated at several angles. But since the algorithm works with pixels, and not an integral image, you can not perform a resource-intensive image rotation operation, but simply read the desired pixels using a rotation matrix. The revision included: implementation of invariance to turn; re-trained classifier of persons; a more productive method for converting the RGBA image to grayscale; parallel execution in the Web-worker; code on ES6.DTMF generation and recognition in JavaScript2018-04-26T12:00:00+00:002018-04-26T12:00:00+00:00https://meefik.github.io/2018/04/26/dtmf-javascript<p>When the task appears to transmit some code by audio, the classic solution is DTMF codes. DTMF is a two-tone multi-frequency signal used to dial a phone number. However, the actual application of this technology is much wider.</p>
<p>The signal format is the sum of two sinusoidal signals of certain frequencies. DTMF symbols are encoded by the following frequencies:</p>
<table>
<thead>
<tr>
<th> </th>
<th>1209 Hz</th>
<th>1336 Hz</th>
<th>1477 Hz</th>
<th>1633 Hz</th>
</tr>
</thead>
<tbody>
<tr>
<td>697 Hz</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>A</td>
</tr>
<tr>
<td>770 Hz</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>B</td>
</tr>
<tr>
<td>852 Hz</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>C</td>
</tr>
<tr>
<td>941 Hz</td>
<td>*</td>
<td>0</td>
<td>#</td>
<td>D</td>
</tr>
</tbody>
</table>
<p>There are many examples of DTMF implementation, one of the most well-known DTMF detection algorithms is <a href="https://en.wikipedia.org/wiki/Goertzel_algorithm">Goertzel algorithm</a>. There is even its <a href="https://github.com/Ravenstine/goertzeljs">JavaScript implementation</a>.</p>
<p>Code recognition occurs by frequency response, and by time response, noise filtering can be implemented.</p>
<!--more-->
<h3 id="js-library-for-working-with-dtmf">JS library for working with DTMF</h3>
<p>My version of the library <a href="https://github.com/meefik/dtmf.js">DTMF.js</a> is quite simple and uses the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a> for both signal generation and recognition in the browser. Audio is captured from the microphone by the getUserMedia function.</p>
<p>Example of receiving and recognizing DTMF codes:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">receiver</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DTMF</span><span class="p">.</span><span class="nx">Receiver</span><span class="p">();</span>
<span class="nb">navigator</span><span class="p">.</span><span class="nx">getUserMedia</span><span class="p">({</span> <span class="na">audio</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span> <span class="kd">function</span><span class="p">(</span><span class="nx">stream</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">receiver</span><span class="p">.</span><span class="nx">start</span><span class="p">(</span><span class="nx">stream</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">code</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">code</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">},</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">e</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>
<p>And here is an example of generating and reproducing DTMF codes:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">sender</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DTMF</span><span class="p">.</span><span class="nx">Sender</span><span class="p">();</span>
<span class="nx">sender</span><span class="p">.</span><span class="nx">play</span><span class="p">(</span><span class="dl">'</span><span class="s1">1234567890ABCD#*</span><span class="dl">'</span><span class="p">);</span>
</code></pre></div></div>
<p>A small demonstration:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/OS6yIiq_Cp8" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>Anton SkshidlevskyWhen the task appears to transmit some code by audio, the classic solution is DTMF codes. DTMF is a two-tone multi-frequency signal used to dial a phone number. However, the actual application of this technology is much wider. The signal format is the sum of two sinusoidal signals of certain frequencies. DTMF symbols are encoded by the following frequencies: 1209 Hz 1336 Hz 1477 Hz 1633 Hz 697 Hz 1 2 3 A 770 Hz 4 5 6 B 852 Hz 7 8 9 C 941 Hz * 0 # D There are many examples of DTMF implementation, one of the most well-known DTMF detection algorithms is Goertzel algorithm. There is even its JavaScript implementation. Code recognition occurs by frequency response, and by time response, noise filtering can be implemented.