{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "fboës - Der Blog | Artikel mit dem Tag \"Javascript\"",
  "home_page_url": "https://journal.3960.org/",
  "feed_url": "https://journal.3960.org/tagged/javascript/feed.json",
  "description": "Programmierung, Luft- & Raumfahrt, Kurioses: Der Blog von und mit Frank Boës.",
  "icon": "https://cdn.3960.org/favicon-192x192.png",
  "favicon": "https://cdn.3960.org/images/tile-128x128.png",
  "author": {
    "name": "Frank Boës",
    "url": "mailto:info@3960.org"
  },
  "authors": [
    {
      "name": "Frank Boës",
      "url": "mailto:info@3960.org"
    }
  ],
  "language": "de-DE",
  "_rss": {
    "about": "http://cyber.harvard.edu/rss/rss.html",
    "copyright": "© 2008-2023 Creative Commons BY"
  },
  "items": [
    {
      "id": "user/posts/2026-03-20-did-not-know-node-js-could-do-that/index.md",
      "url": "https://journal.3960.org/posts/2026-03-20-did-not-know-node-js-could-do-that/",
      "title": "I did not know Node.js could do that",
      "content_html": "<p>Node.js has an astonishing integrated set of utilities which you might never have considered (and turned to NPM packages instead). So here are my favourites:</p>\n<!-- more -->\n<h2 id=\"more\">Utilities</h2>\n<h3 id=\"utilformat\"><a href=\"https://nodejs.org/api/util.html#utilformatformat-args\"><code>util.format</code></a></h3>\n<blockquote><p>The <code>util.format()</code> method returns a formatted string using the first argument as a <code>printf</code>-like format string which can contain zero or more format specifiers. Each specifier is replaced with the converted value from the corresponding argument. </p></blockquote>\n<h3 id=\"utilisdeepstrictequal\"><a href=\"https://nodejs.org/api/util.html#utilisdeepstrictequalval1-val2-options\"><code>util.isDeepStrictEqual</code></a></h3>\n<blockquote><p>Returns <code>true</code> if there is deep strict equality between <code>val1</code> and <code>val2</code>.</p></blockquote>\n<h3 id=\"utilparseenv\"><a href=\"https://nodejs.org/api/util.html#utilparseenvcontent\"><code>util.parseEnv</code></a></h3>\n<blockquote><p>The raw contents of a <code>.env</code> file.</p></blockquote>\n<p>Consider this example:</p>\n<pre><code class=\"language-javascript\"><i>import</i> { parseEnv } from <kbd>'node:util'</kbd>;\n\nparseEnv(<kbd>`\\\nDB_HOST=localhost\nDB_USER=user\n`</kbd>);\n<u>// Returns: { DB_HOST: &quot;localhost&quot;, DB_USER: &quot;user&quot; }</u>\n</code></pre>\n<h3 id=\"utiltypes\"><a href=\"https://nodejs.org/api/util.html#utiltypes\"><code>util.types</code></a></h3>\n<blockquote><p><code>util.types</code> provides type checks for different kinds of built-in objects. Unlike <code>instanceof</code> or <code>Object.prototype.toString.call(value)</code>, these checks do not inspect properties of the object that are accessible from JavaScript (like their prototype), and usually have the overhead of calling into C++.</p></blockquote>\n<h3 id=\"sqlite\"><a href=\"https://nodejs.org/api/sqlite.html\"><code>sqlite</code></a></h3>\n<blockquote><p>The <code>node:sqlite</code> module facilitates working with SQLite databases.</p></blockquote>\n<h3 id=\"utildeprecate\"><a href=\"https://nodejs.org/api/util.html#utildeprecatefn-msg-code-options\"><code>util.deprecate</code></a></h3>\n<blockquote><p>The <code>util.deprecate()</code> method wraps <code>fn</code> (which may be a function or class) in such a way that it is marked as deprecated.</p></blockquote>\n<h2 id=\"terminal\">Terminal</h2>\n<h3 id=\"utilparseargs\"><a href=\"https://nodejs.org/api/util.html#utilparseargsconfig\"><code>util.parseArgs</code></a></h3>\n<blockquote><p>Provides a higher level API for command-line argument parsing than interacting with <code>process.argv</code> directly. Takes a specification for the expected arguments and returns a structured object with the parsed options and positionals.</p></blockquote>\n<p>Consider this example:</p>\n<pre><code class=\"language-javascript\"><i>import</i> { parseArgs } from <kbd>&quot;node:util&quot;</kbd>;\n\n<b>const</b> { values,  positionals } = parseArgs({ \n  options: {\n    force: {\n      type: <kbd>&quot;boolean&quot;</kbd>,\n      <b>short</b>: <kbd>&quot;f&quot;</kbd>,\n    },\n    output: {\n      type: <kbd>&quot;string&quot;</kbd>,\n      <b>short</b>: <kbd>&quot;o&quot;</kbd>,\n      default: <kbd>&quot;.&quot;</kbd>\n    },\n  },\n  allowPositionals: <samp>true</samp>,\n});\n\nconsole.log(values, positionals);\n</code></pre>\n<p>Calling this on the CLI…</p>\n<pre><code class=\"language-bash\">node cli.js generate <em>--force</em> <em>-o</em>=./tmp\n</code></pre>\n<p>…will give you a structured object of all parameters as well as an array positional arguments.</p>\n<h3 id=\"utilstyletext\"><a href=\"https://nodejs.org/api/util.html#utilstyletextformat-text-options\"><code>util.styleText</code></a></h3>\n<blockquote><p>This function returns a formatted text considering the format passed for printing in a terminal. It is aware of the terminal&quot;s capabilities and acts according to the configuration set via <code>NO_COLOR</code>, <code>NODE_DISABLE_COLORS</code> and <code>FORCE_COLOR</code> environment variables.</p></blockquote>\n<p>See also the <a href=\"https://nodejs.org/api/util.html#modifiers\">full list of available styling modifiers</a>, which include foreground colors, bacgkround colors as well as text styles.</p>\n<h2 id=\"testing\">Testing</h2>\n<p>Consider this example:</p>\n<pre><code class=\"language-javascript\"><i>import</i> { describe, it } from <kbd>'node:test'</kbd>;\n\ndescribe(<kbd>&quot;Test suite A&quot;</kbd>, () <samp>=&gt;</samp> {\n  it(<kbd>&quot;should prove 1 is 1&quot;</kbd>, () <samp>=&gt;</samp> {\n    assert.strictEqual(<em>1</em>, <em>1</em>);\n  });\n\n  it(<kbd>&quot;should also prove that 2 is 2&quot;</kbd>, () <samp>=&gt;</samp> {\n    assert.strictEqual(<em>2</em>, <em>2</em>);\n  });\n});\n</code></pre>\n<p>Adhering to a file name convention for your tests, <code>node --test</code> can execute all of these tests with the test runner. See <a href=\"https://betterstack.com/community/guides/testing/nodejs-test-runner/\">Node.js Test Runner: A Beginner&#39;s Guide – Better Stack Community</a> for more information on how to set this up.</p>\n<h3 id=\"nodejs-test-runner\"><a href=\"https://nodejs.org/api/test.html\">Node.js Test runner</a></h3>\n<blockquote><p>The <code>node:test</code> module facilitates the creation of JavaScript tests. </p></blockquote>\n<h3 id=\"assert\"><a href=\"https://nodejs.org/api/assert.html\"><code>assert</code></a></h3>\n<blockquote><p>The <code>node:assert</code> module provides a set of assertion functions for verifying invariants.</p></blockquote><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=I%20did%20not%20know%20Node.js%20could%20do%20that&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2026-03-20-did-not-know-node-js-could-do-that%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Node.js has an astonishing integrated set of utilities which you might never have considered (and turned to NPM packages instead). So here are my favourites:",
      "date_published": "2026-03-20T18:35:59+01:00",
      "date_modified": "2026-03-21T14:22:30+01:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "en-GB",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "Javascript",
        "Programmierung",
        "Technologie",
        "Webdevelop"
      ]
    },
    {
      "id": "user/posts/2024-02-03-kleinste-javascript-unit-tester-welt/index.md",
      "url": "https://journal.3960.org/posts/2024-02-03-kleinste-javascript-unit-tester-welt/",
      "title": "Der kleinste JavaScript-Unit-Tester der Welt",
      "content_html": "<p>Schon das kleinste JavaScript-Projekt kann von Unit Testing profitieren. Aber gerade in kleinen Projekten können Lösungen wie <a href=\"https://mochajs.org/\">Mocha</a> oder <a href=\"https://jestjs.io/\">Jest</a> sich als viel zu schwergewichtig anfühlen.</p>\n<p>Aber warum eigentlich ein Framework verwenden? Wozu das Projekt mit Dependencies vollstopfen, wenn ein paar elegante Zeilen Code für unsere Zwecke reichen?</p>\n<!-- more -->\n<p id=\"more\">In seiner einfachsten Form ist Unit Testing nichts anderes als das Vergleichen von Annahmen mit der Ausgabe von tatsächlichen Code. In JavaScript gibt es dafür eine eingebaute Lösung: <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/console/assert_static\"><code>console.assert()</code></a>.</p>\n<pre><code class=\"language-javascript\"><b>const</b> starDestroyer = <i>new</i> StarDestroyer();\nconsole.assert(starDestroyer !== <samp>null</samp>, <kbd>'Star destroyer exists'</kbd>);\nconsole.assert(starDestroyer.speed &gt;= <em>0</em>, <kbd>'Star destroyer has speed property with positive Number'</kbd>);\nconsole.assert(starDestroyer.faction === <kbd>'imperial'</kbd>, <kbd>'Star destroyer is always imperial'</kbd>);\n</code></pre>\n<p>Im Browser wie auch in Node.js erzeugt diese Methode eine Konsolen-Ausgabe, wenn die Annahme nicht zutrifft.</p>\n<h2 id=\"scoping\">Scoping</h2>\n<p>Wenn in ein und derselben Datei mehrere Testfälle geprüft werden sollen, ist eine klares Scoping von Variablen notwendig. So wird sichergestellt, das Ergebnisse aus einem Test nicht irrtümlicherweise im nächsten Test verwendet werden. In JavaScript ist die Erzeugung eines neuen Scopes jederzeit mittels <code>{…}</code> möglich:</p>\n<pre><code class=\"language-javascript\">{\n  <b>const</b> starDestroyer = <i>new</i> StarDestroyer();\n  console.assert(starDestroyer !== <samp>null</samp>, <kbd>'Star destroyer exists'</kbd>);\n  console.assert(starDestroyer.speed &gt;= <em>0</em>, <kbd>'Star destroyer has speed property with positive Number'</kbd>);\n  console.assert(starDestroyer.faction === <kbd>'imperial'</kbd>, <kbd>'Star destroyer is always imperial'</kbd>);\n}\n\n<u>// No starDestroyer here</u>\n</code></pre>\n<p>Mehrere Tests können so in einer Datei abgehandelt werden.</p>\n<p>Für automatisiertes Testing ist die Verwendung von <code>console.assert</code> natürlich zu kurz gesprungen, denn hier kann nur durch Sichtkontrolle überprüft werden, ob alle Tests sauber durchgelaufen sind. Wichtig wäre es also, auf der Kommandozeile einen Exit-Code zu bekommen, der auf einen Fehler hinweist.</p>\n<h2 id=\"das-eingebaute-assert-in-nodejs\">Das eingebaute <code>Assert</code> in Node.js</h2>\n<p>In Node.js existiert dafür <a href=\"https://nodejs.org/api/assert.html\"><code>Assert</code></a>:</p>\n<pre><code class=\"language-javascript\"><b>const</b> assert = <i>require</i>(<kbd>'node:assert'</kbd>);\n\n{\n  <b>const</b> starDestroyer = <i>new</i> StarDestroyer();\n  assert.ok(starDestroyer !== <samp>null</samp>, <kbd>'Star destroyer exists'</kbd>);\n  assert.ok(starDestroyer.speed &gt;= <em>0</em>, <kbd>'Star destroyer has speed property with positive Number'</kbd>);\n  assert.strictEqual(starDestroyer.faction, <kbd>'imperial'</kbd>, <kbd>'Star destroyer is always imperial'</kbd>);\n}\n</code></pre>\n<p>Im Fehlerfall wird <code>assert.AssertionError</code> geworfen, der auf der CLI einen auch für Automatisierungen wahrnehmbaren Exit-Code erzeugt.</p>\n<p>Der Nachteil dieser Lösung ist gut erkennbar: Diese Art von Testing funktioniert nur in Node.js, aber nicht im Browser.</p>\n<h2 id=\"lösung-marke-eigenbau\">Lösung Marke Eigenbau</h2>\n<p>Gott sei Dank ist die Idee mit dem Werfen von Errors aber gar nicht so komplex. Und damit können wir uns eine kleine Funktion konstruieren, mit der wir ganz grundsätzliches Testing durchführen können:</p>\n<pre><code class=\"language-javascript\"><b>const</b> assertOk = (assertion, message = <kbd>'Assertion'</kbd>) <samp>=&gt;</samp> {\n  console.log((assertion ? <kbd>&quot;✅&quot;</kbd> : <kbd>&quot;💥&quot;</kbd>) + <kbd>&quot; &quot;</kbd> + message);\n  <i>if</i> (!assertion) {\n    <i>throw</i> <i>new</i> Error(<kbd>`&quot;${message}&quot; failed`</kbd>);\n  }\n};\n</code></pre>\n<p>Damit schlagen wir mehrere Fliegen mit einer Klappe:</p>\n<ul>\n<li>Jeder Testfall erzeugt eine Ausgabe, ob erfolgreich oder unerfolgreich.</li>\n<li>Die Tests können sowohl im Browser als auch mit Node.js ausgeführt werden.</li>\n<li>Ein unerfolgreicher Testfall erzeugt einen Fehler, der auch auf der CLI für einen Exit-Code sorgt.</li>\n</ul>\n<p>Etwas aussagekräftiger könnte die Funktion sogar so aussehen:</p>\n<pre><code class=\"language-javascript\"><b>const</b> assertStrictEqual = (a, b = <samp>true</samp>, message = <kbd>''</kbd>) <samp>=&gt;</samp> {\n    message === <kbd>''</kbd> || (message += <kbd>&quot; | &quot;</kbd>);\n    message += message = <kbd>`'${a}' equals '${b}'`</kbd>;\n\n    console.log((a === b ? <kbd>&quot;✅&quot;</kbd> : <kbd>&quot;💥&quot;</kbd>) + <kbd>&quot; &quot;</kbd> + message);\n    <i>if</i> (a !== b) {\n      <i>throw</i> <i>new</i> Error(<kbd>`&quot;${message}&quot; failed`</kbd>);\n    }\n};\n</code></pre>\n<p>Damit können nicht nur direkt Vergleiche durchgeführt werden, sondern auch gleich eine aussagekräftige Nachricht über den tatsächlichen Inhalt des Tests mit ausgegeben werden.</p>\n<p>Wenn ihr ein Drop-In-Replacement für <code>assert</code> von Node.js haben wollt, könnt ihr die beiden Funktionen in statische Methoden verwandeln:</p>\n<pre><code class=\"language-javascript\"><b>const</b> assert = {\n  <u>/**\n   * Check if `assertion` is `true`. Throw Error on error.\n   * @param {Boolean} assertion\n   * @param {String} message\n   */</u>\n  ok(assertion, message = <kbd>'Assertion'</kbd>) {\n    console.log((assertion ? <kbd>&quot;✅&quot;</kbd> : <kbd>&quot;💥&quot;</kbd>) + <kbd>&quot; &quot;</kbd> + message);\n    <i>if</i> (!assertion) {\n      <i>throw</i> <i>new</i> Error(<kbd>`&quot;${message}&quot; failed`</kbd>);\n    }\n  },\n\n  <u>/**\n   * Check if `actual` strictly equals `b`. Throw Error on error.\n   * @param {any} actual\n   * @param {any} expected\n   * @param {String} message\n   */</u>\n  strictEqual(actual, expected, message = <kbd>''</kbd>) {\n    message === <kbd>''</kbd> || (message += <kbd>&quot; | &quot;</kbd>);\n    message += message = <kbd>`'${actual}' equals '${expected}'`</kbd>;\n\n    assert.ok(actual == expected, message)\n  }\n};\n</code></pre>\n<p>Oh, und wenn ihr eure Tests noch gruppieren wollt:</p>\n<pre><code class=\"language-javascript\">console.group(<kbd>'Testing StarDestroyer'</kbd>);\n{\n  <b>const</b> starDestroyer = <i>new</i> StarDestroyer();\n  assert.ok(starDestroyer !== <samp>null</samp>, <kbd>'Star destroyer exists'</kbd>);\n  assert.ok(starDestroyer.speed &gt;= <em>0</em>, <kbd>'Star destroyer has speed property with positive Number'</kbd>);\n  assert.strictEqual(starDestroyer.faction, <kbd>'imperial'</kbd>, <kbd>'Star destroyer is always imperial'</kbd>);\n}\nconsole.groupEnd();\n</code></pre>\n<p>…erzeugt eine schön gruppierte Ausgabe eurer Tests.</p>\n<p>Voilà! Eine Beispiel-Ausgabe:</p>\n<pre><code>GeoJson.Feature\n  ✅ Type matches | &#39;Feature&#39; equals &#39;Feature&#39;\n  ✅ Feature.properties.title | &#39;Test&#39; equals &#39;Test&#39;\n  ✅ Feature.geometry.type | &#39;Point&#39; equals &#39;Point&#39;\n  ✅ No id\nGeoJson.FeatureCollection\n  ✅ &#39;FeatureCollection&#39; equals &#39;FeatureCollection&#39;\n  ✅ Bounding Box West | &#39;6.5664576&#39; equals &#39;6.5664576&#39;\n  ✅ Bounding Box South | &#39;51.04571&#39; equals &#39;51.04571&#39;\n  ✅ Bounding Box East | &#39;1.53946&#39; equals &#39;1.53946&#39;\n  ✅ Bounding Box North | &#39;58.109285&#39; equals &#39;58.109285&#39;\n  ✅ FeatureCollection | &#39;FeatureCollection&#39; equals &#39;FeatureCollection&#39;\n  ✅ Two Features exist | &#39;2&#39; equals &#39;2&#39;\n  ✅ Feature.type | &#39;Feature&#39; equals &#39;Feature&#39;\n  ✅ Feature.properties.title | &#39;Sailing boat&#39; equals &#39;Sailing boat&#39;\n</code></pre>\n<p>Und auch hier können wir uns ein Drop-In-Replacement analog zu den <a href=\"https://nodejs.org/api/test.html#describe-and-it-aliases\">im Node Testrunner vorhanden Funktionen <code>it</code> / <code>describe</code></a> bauen:</p>\n<pre><code class=\"language-javascript\"><u>/**\n * @param {String} name\n * @param {Function} fn \n */</u>\n<b>const</b> describe = (name, fn) <samp>=&gt;</samp> {\n  console.group(name);\n  fn();\n  console.groupEnd();\n}\n\n<b>const</b> it = describe;\n</code></pre>\n<p>…und damit unseren Test so bauen:</p>\n<pre><code class=\"language-javascript\">describe(<kbd>'Testing StarDestroyer'</kbd>, () <samp>=&gt;</samp> {\n  <b>const</b> starDestroyer = <i>new</i> StarDestroyer();\n  assert.ok(starDestroyer !== <samp>null</samp>, <kbd>'Star destroyer exists'</kbd>);\n  assert.ok(starDestroyer.speed &gt;= <em>0</em>, <kbd>'Star destroyer has speed property with positive Number'</kbd>);\n  assert.strictEqual(starDestroyer.faction, <kbd>'imperial'</kbd>, <kbd>'Star destroyer is always imperial'</kbd>);\n});\n</code></pre>\n<h2 id=\"fazit\">Fazit</h2>\n<p>Natürlich ersetzt diese sehr simple Herangehensweise nicht wirklich echte Unit-Test-Frameworks. So ist zum Beispiel der Vergleich von Objekt-Strukturen nicht möglich, das Testen von Exceptions oder auch die Verwendung von Mocking und ähnlichen Features. Aber zumindest senkt es die Hürde, <em>überhaupt</em> mit Testing zu beginnen. <span class=\"emoji emoji--1f609\" title=\";)\">&#x1F609;</span></p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Der%20kleinste%20JavaScript-Unit-Tester%20der%20Welt&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2024-02-03-kleinste-javascript-unit-tester-welt%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Schon das kleinste JavaScript-Projekt kann von Unit Testing profitieren. Aber gerade in kleinen Projekten können Lösungen wie Mocha oder Jest sich als viel zu…",
      "date_published": "2024-02-03T18:14:32+01:00",
      "date_modified": "2026-03-09T11:10:37+01:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "Javascript",
        "Programmierung",
        "Webdevelop"
      ]
    },
    {
      "id": "user/posts/2023-03-05-24h-uhr-als-web-component/index.md",
      "url": "https://journal.3960.org/posts/2023-03-05-24h-uhr-als-web-component/",
      "title": "Die 24h-Uhr – als Web Component",
      "content_html": "<p><img src=\"https://journal.3960.org/posts/2023-03-05-24h-uhr-als-web-component/24h-clock-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Web Components zu bauen ist gar nicht so kompliziert, selbst ohne Framework. Nach meinen vorherigen <a href=\"https://journal.3960.org/posts/2023-03-05-24h-uhr-als-web-component/../2020-04-05-svg-web-components/\">Überlegungen zur Verwendung von SVGs in Web Components</a> war es also höchste Zeit, eine 24-Stunden-Uhr zu bauen.</p>\n<!-- more -->\n<h2 id=\"more\">Die Idee</h2>\n<p>24h-Uhren sind analoge Uhren, die einen feinen Unterschied zu herkömmlichen Uhren aufweisen. An Stelle von 12 Stunden pro Umdrehung des Stundenzeigers zeigen sie 24 Stunden an. Damit braucht also der Stundenzeiger einen kompletten Tag für einen Vollkreis.</p>\n<p>Damit ist es möglich, die vierundzwanzig Stunden auf dem Ziffernblatt grafisch in Tag, Dämmerung und Nacht einzuteilen. Da diese sich in Bezug auf das Datum und die geografische Position verändern, brauchte die Uhr also auch diese Informationen. In meinem Fall habe ich eine Skala auf dem Stundenring angebracht, die sich für die Nacht dunkelblau, für die Dämmung mittelblau und für den Tag hellblau verfärbt.</p>\n<p><span class=\"figure\"><img src=\"https://journal.3960.org/posts/2023-03-05-24h-uhr-als-web-component/24h-clock-2.png\" alt=\"Im hohen Norden scheint die Sonne heute nur kurz\" /><span class=\"figcaption\" aria-hidden=\"true\">Im hohen Norden scheint die Sonne heute nur kurz<br /></span></span></p>\n<h2 id=\"die-umsetzung\">Die Umsetzung</h2>\n<p>Der Bau als <a href=\"https://journal.3960.org/tagged/web-components/\">Web Component</a> erlaubt es nun, die Uhr mit relativ wenig HTML in eine beliebige Seite einzubauen. Unter <a href=\"https://github.com/fboes/twentyfour-hours-clock\">der Github-Seite zur 24h-Uhr</a> findet sich eine knappe Anleitung, der Einbau einer Web Component muss aber nicht komplizierter sein als:</p>\n<pre><code class=\"language-html\">&lt;<i>twentyfour-hours-clock</i>&gt;&lt;/<i>twentyfour-hours-clock</i>&gt;\n</code></pre>\n<p>Weitere <strong>Attribute</strong> erlauben es, die Component vorher mit Werten zu bestücken – die sich auch im Nachhinein mit JavaScript ändern lassen:</p>\n<pre><code class=\"language-html\">&lt;<i>twentyfour-hours-clock</i> <var>width</var>=&quot;<kbd>128</kbd>&quot; <var>height</var>=&quot;<kbd>128</kbd>&quot; <var>datetime</var>=&quot;<kbd>2011-10-10T14:48:00</kbd>&quot; <var>longitude</var>=&quot;<kbd>auto</kbd>&quot; <var>latitude</var>=&quot;<kbd>auto</kbd>&quot;&gt;&lt;/<i>twentyfour-hours-clock</i>&gt;\n</code></pre>\n<p>Auch das Einfärben, Vergrößern und Verkleinern von Einzelteilen der Web Component sind mit <strong>CSS Custom Properties</strong> möglich:</p>\n<pre><code class=\"language-html\">&lt;<i>style</i>&gt;\ntwentyfour-hours-clock {\n  --color-watchhand: pink;\n  --color-night: #111111;\n}\n&lt;/<i>style</i>&gt;\n&lt;<i>twentyfour-hours-clock</i>&gt;&lt;/<i>twentyfour-hours-clock</i>&gt;\n</code></pre>\n<p>Die Web Component selber wurde mit TypeScript gebaut. Tatsächlich wäre auch der Bau direkt in <i>Vanilla JavaScript</i> möglich gewesen. Meine letzten Abenteuer in <a href=\"https://www.typescriptlang.org/\">TypeScript</a> sind aber so angenehme Erfahrungen gewesen, dass ich die Unterstützung bei der Typisierung in JavaScript nicht mehr missen möchte, und die Web Component in TypeScript gebaut habe. Inzwischen halte ich bei JavaScript die Verwendung von TypeScript für so hilfreich, dass ich auf ESLint zur Überprüfung meiner Programmierung bei Privatprojekten gerne verzichte.</p>\n<h2 id=\"her-mit-der-uhr\">Her mit der Uhr!</h2>\n<p>Ein funktionierendes Beispiel für die fertige Uhr findet sich auf der <a href=\"https://fboes.github.io/twentyfour-hours-clock/dist/\">Demonstrationsseite für die 24h-Uhr</a>.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Die%2024h-Uhr%20%E2%80%93%20als%20Web%20Component&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2023-03-05-24h-uhr-als-web-component%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Web Components zu bauen ist gar nicht so kompliziert, selbst ohne Framework. Nach meinen vorherigen Überlegungen zur Verwendung von SVGs in Web Components war…",
      "date_published": "2023-03-05T18:56:12+01:00",
      "date_modified": "2023-03-05T18:56:12+01:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://journal.3960.org/posts/2023-03-05-24h-uhr-als-web-component/24h-clock.png",
      "language": "de-DE",
      "image": "https://journal.3960.org/posts/2023-03-05-24h-uhr-als-web-component/24h-clock.png",
      "tags": [
        "Webdevelop",
        "Web-Components",
        "SVG",
        "Javascript",
        "CSS",
        "Geografie",
        "Outdoor",
        "Programmierung",
        "Technologie"
      ]
    },
    {
      "id": "user/posts/2023-01-27-javascript-pattern-zum-event-handling/index.md",
      "url": "https://journal.3960.org/posts/2023-01-27-javascript-pattern-zum-event-handling/",
      "title": "JavaScript: Pattern zum Event-Handling",
      "content_html": "<p>Manche JavaScript-Applikationen haben eine erhebliche Zahl an Event-Listenern. Diese ordentlich und übersichtlich zu erfassen und abzuarbeiten kann eine Herausforderung sein – bis wir uns die <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener\">Anleitung zu <code>addEventListener</code></a> genau durchlesen.</p>\n<!-- more -->\n<p id=\"more\">Wie schon in dem Artikel <a href=\"https://journal.3960.org/posts/2019-10-27-event-handling-mit-javascript-ohne-jquery/\">„Event-Handling mit JavaScript – und ohne jQuery“</a> angemerkt, ist ein gutes Verständnis von <code>addEventListener</code> sehr hilfreich, um sich in der eigenen JavaScript-Applikation keine Performance-Ärger einzubrocken – und gleichzeitig die Übersicht zu behalten, wenn die Anzahl der Listener schnell ansteigt.</p>\n<p>Gehen wir davon aus, dass ihr klassen- bzw. objektorientiert arbeitet. Schnell werdet ihr auf die Herausforderung treffen, dass <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#the_value_of_this_within_the_handler\"><code>this</code> innerhalb des Listeners eine andere Bedeutung hat als außerhalb</a>.</p>\n<p>Natürlich könntet ihr mit <code>.bind()</code> den Kontext neu setzen. Oder ihr könntet mit anonymen Arrow-Funktionen arbeiten, die aber den üblichen <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener\">Nachteil von anonymen Funktionen bei Listenern</a> mitbringen.</p>\n<p><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener\">MDN&#39;s Anleitung für <code>addEventListener</code></a> verrät uns, dass als Listener drei verschiedene Dinge eingetragen werden können:</p>\n<ol>\n<li><code>null</code></li>\n<li>Eine Funktion, ggf. auch eine anonyme Funktion</li>\n<li>Ein Objekt, wenn es über eine <code>handleEvent</code>-Methode verfügt.</li>\n</ol>\n<p>Die letzte Option erlaubt es uns, <code>this</code> mit wenig Aufwand vorhersehbar einzusetzen.</p>\n<h2 id=\"objekte-als-event-listener\">Objekte als Event-Listener</h2>\n<p>Tatsächlich ist die Übergabe des gesamten Objekts als Listener und die Definition einer <code>handleEvent</code>-Methode überraschend simpel:</p>\n<pre><code class=\"language-javascript\"><u>// 1st example: Multiple event listeners attached,</u>\n<u>//              one handler</u>\n<b>class</b> App {\n  constructor() {\n    <b>document</b>.<b>querySelectorAll</b>(<kbd>'a, button'</kbd>).<i>forEach</i>(<b>function</b>(element) {\n      element.<b>addEventListener</b>(<kbd>'click'</kbd>, <i>this</i>);\n    });\n  }\n\n  handleEvent(event) {\n    <u>// do something</u>\n  }\n}\n</code></pre>\n<p>Innerhalb von <code>handleEvent</code> kann nun <code>this</code> mit dem zu erwartenden Bezug auf das aktuelle Klassen-Objekt verwendet werden, während die für das Event notwendigen Eigenschaften (ebenfalls regulär) in dem Parameter <code>event</code> stehen.</p>\n<p>Ärgerlicherweise werden nun aber alle so registrierten Events mit ein und dem selben Listener bedient. Zum Glück verrät uns aber jedes Event, für welches Element es ausgelöst wurde – die sogenannte <a href=\"https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_delegation\">Event-Delegation</a>. Eine Fallunterscheidung z.B. nach ID kann uns also helfen, die einzelnen Aktionen auseinander zu halten:</p>\n<pre><code class=\"language-javascript\"><u>// 2nd example: Multiple event listeners attached,</u>\n<u>//              one handler with delegation per id</u>\n<b>class</b> App {\n  constructor() {\n    <b>document</b>.<b>querySelectorAll</b>(<kbd>'a, button'</kbd>).<i>forEach</i>(<b>function</b>(element) {\n      element.<b>addEventListener</b>(<kbd>'click'</kbd>, <i>this</i>);\n    });\n  }\n\n  handleEvent(event) {\n    <i>switch</i> (event.target.id) {\n      <i>case</i> <kbd>'example-1'</kbd>:\n        <u>// do something</u>\n        <i>break</i>;\n      <i>case</i> <kbd>'example-2'</kbd>:\n        <u>// do something other</u>\n        <i>break</i>;\n    }\n  }\n}\n</code></pre>\n<p>…und aus dem Nachteil ist ein Vorteil geworden, da jetzt die einzelnen Event-Listenern nichts anderes als Fälle in einer <code>switch</code>-Anweisung geworden sind.</p>\n<p>Jetzt können wir die Vielzahl der im DOM verteilten Event-Listener eigentlich auch gleich auf einen einzigen reduzieren, da der Listener sowieso nochmals überprüft, welches Element genau getroffen wurde. Dabei machen wir uns <a href=\"https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling\">Event-Bubbling</a> zu Nutze, und montieren an zentraler Stelle <em>einen</em> Event-Listener, der <em>alle</em> Interaktionen abfängt, und dann mittels <a href=\"https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_delegation\">Event-Delegation</a> den korrekten Ansprechpartner auswählt.</p>\n<p>Die zentralste Stelle ist natürlich… der <code>&lt;body&gt;</code>. Damit erschlagen wir übrigens gleich den Fall, dass nachträglich dynamisch DOM-Elemente auf der Seite hinzugefügt werden. Denn der auf dem <code>&lt;body&gt;</code> montierte Listener fängt natürlich auch erst nachträglich im DOM montierte Elemente ab.</p>\n<pre><code class=\"language-javascript\"><u>// 3rd example: Single event listeners attached,</u>\n<u>//              one handler with delegation per id</u>\n<b>class</b> App {\n  constructor() {\n    <b>document</b>.body.<b>addEventListener</b>(<kbd>&quot;click&quot;</kbd>, <i>this</i>);\n  }\n\n  handleEvent(event) {\n    <i>switch</i> (event.target.id) {\n      <i>case</i> <kbd>'example-1'</kbd>:\n        <u>// do something</u>\n        <i>break</i>;\n      <i>case</i> <kbd>'example-2'</kbd>:\n        <u>// do something</u>\n        <i>break</i>;\n    }\n  }\n}\n</code></pre>\n<p>Noch weiter abstrahieren lässt sich das Delegations-Beispiel, wenn wir vorher die <a href=\"https://developer.mozilla.org/en-US/docs/Web/Events#event_listing\">unterschiedlichen Event-Typen</a> auseinandersortieren:</p>\n<pre><code class=\"language-javascript\"><u>// 4th example: Single event listeners attached,</u>\n<u>//              one handler triggers subhandlers per event type</u>\n<b>class</b> App {\n  constructor() {\n    <b>document</b>.body.<b>addEventListener</b>(<kbd>&quot;input&quot;</kbd>, <i>this</i>, <samp>true</samp>);\n    <b>document</b>.body.<b>addEventListener</b>(<kbd>&quot;click&quot;</kbd>, <i>this</i>, <samp>true</samp>);\n    <b>document</b>.body.<b>addEventListener</b>(<kbd>&quot;change&quot;</kbd>, <i>this</i>, <samp>true</samp>);\n  }\n\n  handleEvent(event) {\n    <u>// @see https://developer.mozilla.org/en-US/docs/Web/API/Event</u>\n    <i>switch</i> (event.type) {\n      <i>case</i> <kbd>'click'</kbd> : <i>this</i>.handleEventClick(event);  <i>break</i>;\n      <i>case</i> <kbd>'input'</kbd> : <i>this</i>.handleEventInput(event);  <i>break</i>;\n      <i>case</i> <kbd>'change'</kbd>: <i>this</i>.handleEventChange(event); <i>break</i>;\n    }\n  }\n\n  handleEventClick(event) {\n    <i>switch</i> (event.target.id) {\n      <i>case</i> <kbd>'example-1'</kbd>:\n        <u>// do something</u>\n        <i>break</i>;\n      <i>case</i> <kbd>'example-2'</kbd>:\n        <u>// do something</u>\n        <i>break</i>;\n    }\n  }\n\n  handleEventInput(event) {\n    <i>switch</i> (event.target.id) {\n      <i>case</i> <kbd>'example-3'</kbd>:\n        <u>// do something</u>\n        <i>break</i>;\n      <i>case</i> <kbd>'example-4'</kbd>:\n        <u>// do something</u>\n        <i>break</i>;\n    }\n  }\n\n  handleEventChange(event) {\n    <i>switch</i> (event.target.id) {\n      <i>case</i> <kbd>'example-5'</kbd>:\n        <u>// do something</u>\n        <i>break</i>;\n      <i>case</i> <kbd>'example-6'</kbd>:\n        <u>// do something</u>\n        <i>break</i>;\n    }\n  }\n}\n</code></pre>\n<p>Dieses Pattern lässt sich natürlich auch ohne Objekte einsetzen, und mit regulären Funktionsaufrufen durchführen. Dabei verliert es aber natürlich die Möglichkeit, <code>this</code> ohne Gehirn- oder Code-Akrobatik zu verwenden.</p>\n<h2 id=\"deklaration-der-event-handler-im-html\">Deklaration der Event-Handler im HTML</h2>\n<p>Unser schönes Beispiel geht davon aus, dass wir die Auswahl der eigentlich durchzuführenden Aktion an der <code>id</code> des Elements festmachen, dass das Event ausgelöst hat. Tatsächlich können wir das aber an <em>jedes</em> Attribut koppeln. Damit sind auch <code>class</code>-Attribute mögliche Ziele.</p>\n<pre><code class=\"language-html\">&lt;<i>dialog</i>&gt;\n  &lt;<i>button</i> <var>class</var>=&quot;<kbd>modal-close</kbd>&quot;&gt;Close&lt;<i>button</i>&gt;\n  \n  &lt;<i>label</i> <var>for</var>=&quot;<kbd>date</kbd>&quot;&gt;Input&lt;/<i>label</i>&gt;\n  &lt;<i>input</i> <var>id</var>=&quot;<kbd>date</kbd>&quot; <var>type</var>=&quot;<kbd>date</kbd>&quot; <var>class</var>=&quot;<kbd>change-date</kbd>&quot; /&gt;\n\n  &lt;<i>button</i> <var>class</var>=&quot;<kbd>set-to-current</kbd>&quot;&gt;Set to current time&lt;/<i>button</i>&gt;\n&lt;/<i>dialog</i>&gt;\n</code></pre>\n<p>Das erlaubt uns zum Beispiel, die erste Klasse eines Elements darüber entscheiden zu lassen, was getan werden soll:</p>\n<pre><code class=\"language-javascript\"><u>// 5th example: Single event listeners attached,</u>\n<u>//              one handler triggers subhandlers per `class` attribute</u>\n<b>class</b> App {\n  constructor() {\n    <b>document</b>.body.<b>addEventListener</b>(<kbd>&quot;input&quot;</kbd>, <i>this</i>, <samp>true</samp>);\n    <b>document</b>.body.<b>addEventListener</b>(<kbd>&quot;click&quot;</kbd>, <i>this</i>, <samp>true</samp>);\n    <b>document</b>.body.<b>addEventListener</b>(<kbd>&quot;change&quot;</kbd>, <i>this</i>, <samp>true</samp>);\n  }\n\n  handleEvent(event) {\n    <u>// Get first `class` of element</u>\n    <b>const</b> handler = e.target.classList.item(<em>0</em>);\n    <i>switch</i> (handler) {\n      <i>case</i> <kbd>'modal-close'</kbd>   : <i>this</i>.handleModalClose(event);   <i>break</i>;\n      <i>case</i> <kbd>'change-data'</kbd>   : <i>this</i>.handleChangeDate(event);   <i>break</i>;\n      <i>case</i> <kbd>'set-to-current'</kbd>: <i>this</i>.handleSetToCurrent(event); <i>break</i>;\n    }\n  }\n\n  handleModalClose(event) {\n    <u>// do something</u>\n  }\n\n  handleChangeDate(event) {\n    <u>// do something</u>\n  }\n\n  handleSetToCurrent(event) {\n    <u>// do something</u>\n  }\n}\n</code></pre>\n<p>Aber wie wäre es mit einem eigenen <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*\"><code>data</code>-Attribut</a>, zum Beispiel in Form eines <code>data-handler</code>?</p>\n<pre><code class=\"language-html\">&lt;<i>dialog</i>&gt;\n  &lt;<i>button</i> <var>data-handler</var>=&quot;<kbd>modal-close</kbd>&quot;&gt;Close&lt;<i>button</i>&gt;\n  \n  &lt;<i>label</i> <var>for</var>=&quot;<kbd>date</kbd>&quot;&gt;Input&lt;/<i>label</i>&gt;\n  &lt;<i>input</i> <var>id</var>=&quot;<kbd>date</kbd>&quot; type==&quot;date&quot; <var>data-handler</var>=&quot;<kbd>change-date</kbd>&quot; /&gt;\n\n  &lt;<i>button</i> <var>data-handler</var>=&quot;<kbd>set-to-current</kbd>&quot;&gt;Set to current time&lt;/<i>button</i>&gt;\n&lt;/<i>dialog</i>&gt;\n</code></pre>\n<p>In diesem Fall hängen wir an alle HTML-Elemente ein <code>data-handle</code>, mit dem wir zum Beispiel auch direkt benennen können, mit welchem Handler gearbeitet werden soll:</p>\n<pre><code class=\"language-javascript\"><u>// 5th example: Single event listeners attached,</u>\n<u>//              one handler triggers subhandlers per `data-handler` attribute</u>\n<b>class</b> App {\n  constructor() {\n    <b>document</b>.body.<b>addEventListener</b>(<kbd>&quot;input&quot;</kbd>, <i>this</i>, <samp>true</samp>);\n    <b>document</b>.body.<b>addEventListener</b>(<kbd>&quot;click&quot;</kbd>, <i>this</i>, <samp>true</samp>);\n    <b>document</b>.body.<b>addEventListener</b>(<kbd>&quot;change&quot;</kbd>, <i>this</i>, <samp>true</samp>);\n  }\n\n  handleEvent(event) {\n    <u>// Bubble up to closest DOM element with `data-handler`</u>\n    <b>const</b> handler = e.target.closest(<kbd>'[data-handler]'</kbd>)?.dataset.handler;\n    <i>switch</i> (handler) {\n      <i>case</i> <kbd>'modal-close'</kbd>   : <i>this</i>.handleModalClose(event);   <i>break</i>;\n      <i>case</i> <kbd>'change-data'</kbd>   : <i>this</i>.handleChangeDate(event);   <i>break</i>;\n      <i>case</i> <kbd>'set-to-current'</kbd>: <i>this</i>.handleSetToCurrent(event); <i>break</i>;\n    }\n  }\n\n  handleModalClose(event) {\n    <u>// do something</u>\n  }\n\n  handleChangeDate(event) {\n    <u>// do something</u>\n  }\n\n  handleSetToCurrent(event) {\n    <u>// do something</u>\n  }\n}\n</code></pre>\n<p>Als angenehmer Nebeneffekt können wir uns auf die Elemente beschränken, die auch wirklich ein <code>data-handler</code>-Attribut haben – oder im DOM nach oben wandern, bis wir ein Element gefunden haben, dass über ein solches Attribut verfügt. \n<a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Element/closest\"><code>.closest()</code></a> ist hier eine wirkliche Hilfe.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=JavaScript%3A%20Pattern%20zum%20Event-Handling&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2023-01-27-javascript-pattern-zum-event-handling%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Manche JavaScript-Applikationen haben eine erhebliche Zahl an Event-Listenern. Diese ordentlich und übersichtlich zu erfassen und abzuarbeiten kann eine…",
      "date_published": "2023-01-27T19:34:49+01:00",
      "date_modified": "2023-10-25T15:45:55+02:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "Javascript",
        "Programmierung",
        "Webdevelop"
      ]
    },
    {
      "id": "user/posts/2022-06-27-javascript-gamepad-api-test/index.md",
      "url": "https://journal.3960.org/posts/2022-06-27-javascript-gamepad-api-test/",
      "title": "Controller testen mit dem JavaScript Gamepad API Test",
      "content_html": "<p><img src=\"https://journal.3960.org/posts/2022-06-27-javascript-gamepad-api-test/gampepad-xbox-360-controller-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Mit dem <a href=\"https://3960.org/sandbox/gamepad-test.html\">Gampead API Test</a> könnt ihr die tatsächlichen Rohwerte der <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API\"><code>Gamepad</code> API</a> auslesen – und nebenbei eure Gamepads, Joysticks, Schubkontrollen, Fußpedale und sonstige Controller testen.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Controller%20testen%20mit%20dem%20JavaScript%20Gamepad%20API%20Test&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2022-06-27-javascript-gamepad-api-test%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Mit dem Gampead API Test könnt ihr die tatsächlichen Rohwerte der Gamepad API auslesen – und nebenbei eure Gamepads, Joysticks, Schubkontrollen, Fußpedale und…",
      "date_published": "2022-06-27T19:39:11+02:00",
      "date_modified": "2022-06-27T19:39:11+02:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://journal.3960.org/posts/2022-06-27-javascript-gamepad-api-test/gampepad-xbox-360-controller.png",
      "language": "de-DE",
      "image": "https://journal.3960.org/posts/2022-06-27-javascript-gamepad-api-test/gampepad-xbox-360-controller.png",
      "external_url": "https://3960.org/sandbox/gamepad-test.html",
      "tags": [
        "Javascript",
        "Joystick",
        "Programmierung",
        "Simulation",
        "Webdevelop"
      ]
    },
    {
      "id": "user/posts/2022-06-26-javascript-gamepad-api-verwenden/index.md",
      "url": "https://journal.3960.org/posts/2022-06-26-javascript-gamepad-api-verwenden/",
      "title": "JavaScript: Die Gamepad-API verwenden",
      "content_html": "<p>Moderne Browser haben inzwischen eine Vielzahl interessanter JavaScript-Schnittstellen. Höchste Zeit, die <code>Gamepad</code>-API genau zu begutachten.</p>\n<!-- more -->\n<p id=\"more\">Die <code>Gamepad</code>-API ist eine der Zutaten, die moderne Browser für die Spiele-Entwicklung bereit stellen. Neben <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API\">Grafik</a> (inklusive <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API\">Virtual Reality</a>), <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement/Audio\">Sound</a> und <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/WebSocket\">Socket-Verbindungen</a> gibt es damit alles, was das Spiele-Entwickler-Herz begehrt.</p>\n<p>Anleitungen für die <code>Gamepad</code>-API gibt es viele: Allen voran die <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API\">Dokumentation zur Gamepad API auf MDN</a> lässt wenig Fragen offen. Ich habe mich aber in einen etwas speziellen Bereich vorgewagt. Inspiriert von einem <a href=\"https://github.com/ruben3d/retroflightsim\">Retro Flug-Simulator im Browser</a> \nhabe ich mir die Frage gestellt: Wie sieht es aus, wenn ich einen Flugsimulator für den Browser baue, und dort Unterstützung für Joysticks, Schubkontrollen oder Fußpedale integrieren möchte?</p>\n<p>Kurz gefasst: Gar nicht so übel.</p>\n<h2 id=\"grundsätzliches-zu-joysticks-im-browser\">Grundsätzliches zu Joysticks im Browser</h2>\n<p>Die <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API\">Dokumentation zur Gamepad API auf MDN</a> bespricht in den meisten Beispielen nur Gamepads. Der schöne Artikel <a href=\"https://www.smashingmagazine.com/2015/11/gamepad-api-in-web-games/\">„Using The Gamepad API In Web Games“ aus dem Smashing Magazine</a> erklärt deutlich mehr die Verwendung von Joysticks &amp; Co – aber auch hier bleiben ein paar Fragen offen.</p>\n<p>Tatsächlich funktioniert die Gamepad API mit so ziemlich jedem USB/Bluetooth-Eingabegerät, das wie ein Gamepad angesprochen werden kann. Darunter fallen Joysticks, Schubkontrollen, Ruder-Pedale und so ziemlich jeder andere <a href=\"https://www.reddit.com/r/hotas/\">HOTAS</a>-Controller, den Flugsimulator-Fans kennen.</p>\n<p>Der Vorgang ist in JavaScript verblüffend einfach: Mit der JavaScript-Methode <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getGamepads\"><code>Navigator.getGamepads()</code></a> erhaltet ihr ein Array aller an eurem PC/Smartphone/sonstigen Endgerät angeschlossenen Controller.</p>\n<p>Die Controller verraten folgendes über sich:</p>\n<ul>\n<li><code>gamepad.id</code>: Das ist die ID des Eingabegeräts, die die meisten Browser in einen verständlichen Namen übersetzen – wenn auch jeder Browser ein etwas anderes Ergebnis ausgibt. 😖</li>\n<li><code>gamepad.buttons</code>: Dieses Array verrät euch, wie viele Buttons das Eingabegerät hat.</li>\n<li><code>gamepad.axes</code>: Dieses Array verrät euch, wie viele Achsen das Eingabegerät hat.</li>\n<li><code>gamepad.mapping</code>: Hier verrät der Browser mit dem Schlüsselwort <code>„standard“</code>, ob der Controller auf ein <a href=\"https://w3c.github.io/gamepad/#remapping\">Standard-Gamepad</a> umbelegt werden konnte.</li>\n<li><code>gamepad.timestamp</code>: Der Zeitpunkt, an dem das letzte Mal eine Messung auf dem Gamepad durchgeführt wurde. Das wird später wichtig, um die Zeit zwischen den Messungen zu verfolgen, und damit z.B. die Dauer eines Tastendrucks zu erkennen.</li>\n</ul>\n<h3 id=\"exkurs-standard-mapping\">Exkurs: Standard-Mapping</h3>\n<p><span class=\"figure\"><img src=\"https://journal.3960.org/posts/2022-06-26-javascript-gamepad-api-verwenden/gampepad-xbox-360-controller.png\" alt=\"Gamepad-Belegung für den Xbox 360 Controller\" /><span class=\"figcaption\" aria-hidden=\"true\">Gamepad-Belegung für den Xbox 360 Controller<br /></span></span></p>\n<p>Das Standard-Mapping ist eine große Hilfe für die Implementation von Gamepads in den Browser: Dabei wird die Achsen- und Button-Belegung des Gamepads auf ein erwartbares <a href=\"https://w3c.github.io/gamepad/#remapping\">Layout eines Standard-Gamepads</a> umgebogen. Die Belegung beinhaltet:</p>\n<ul>\n<li>Achse 0/1 für den linken Stick,</li>\n<li>Achse 2/3 für den rechten Stick,</li>\n<li>Button 0..3 für den ersten Knöpfe-Cluster,</li>\n<li>Button 4..7 für die Schultertasten des Gamepads und</li>\n<li>Button 12..15 für das D-Pad.</li>\n</ul>\n<p>Der Haken ist, das ein und dasselbe Gamepad nicht von jedem Browser diesem Layout zugeordnet wird. So wird ein Xbox-Controller unter Firefox/Windows, Chrome/Windows und Chrome/Ubuntu als Standard-Controller erkannt, unter Firefox/Ubuntu dagegen nicht. 😖</p>\n<p>Das kann fatale Auswirkungen haben: Ein und dasselbe Gamepad kann zum Beispiel im Standard-Mapping die analogen Trigger als Buttons mit Analogwerten melden, während der nächste Browser ohne Standard-Mapping die Buttons streicht und stattdessen zwei zusätzliche Achsen sendet (die dann auch noch die Reihenfolge durcheinander bringen). Oder das D-Pad wird nicht als Sammlung von vier Knöpfen sondern als zwei Achsen gemeldet.</p>\n<h2 id=\"besondere-controller\">Besondere Controller</h2>\n<p>Höchste Zeit also, einen Blick auf den lokalen Fuhrpark an HOTAS-Controller zu werfen. Mit dem selbstgebauten <a href=\"https://3960.org/sandbox/gamepad-test.html\">Gamepad API Test</a> kann jedes beliebige Eingabegerät so dargestellt werden, wie der Browser dieses Gerät auswertet.</p>\n<p><span class=\"figure\"><img src=\"https://journal.3960.org/posts/2022-06-26-javascript-gamepad-api-verwenden/gamepad-ch-combatstick.png\" alt=\"Gamepad-Belegung für den CH Combatstick\" /><span class=\"figcaption\" aria-hidden=\"true\">Gamepad-Belegung für den CH Combatstick<br /></span></span></p>\n<p>Ziemlich schnell zeichnet sich dabei ein Muster ab. Jeder Joystick bietet verlässlich folgendes an:</p>\n<ul>\n<li>Achse 0/1 ist der eigentliche Joystick,</li>\n<li>Button 0/1 der primäre und sekundäre Feuerknopf.</li>\n</ul>\n<p>Bereits dahinter scheiden sich die Geister. In den meisten Fällen könnt ihr euch noch knapp auf die folgende Konvention verlassen:</p>\n<ul>\n<li>Achse 2 die Schubkontrolle und</li>\n<li>Achse 3 eine etwaig vorhandene Drehachse des Joysticks.</li>\n</ul>\n<p>Interessanterweise lassen es sich Joystick-Hersteller nicht nehmen, einige Achsen zu überspringen. Ein vermeintlicher 5-Achsen-Joysticks kann beim Auslesen der API trotzdem neun Achsen senden, wenn der Hersteller des Joysticks das für eine gute Idee hält.</p>\n<p><span class=\"figure\"><img src=\"https://journal.3960.org/posts/2022-06-26-javascript-gamepad-api-verwenden/gamepad-ch-pro-throttle.png\" alt=\"Gamepad-Belegung für die CH Pro Throttle\" /><span class=\"figcaption\" aria-hidden=\"true\">Gamepad-Belegung für die CH Pro Throttle<br /></span></span></p>\n<p>Weitere Eingabegeräte wie Schubkontrollen versuchen sich, auch an diese Konvention zu halten. Hier ist aber deutlich mehr Glück gefragt.</p>\n<p><span class=\"figure\"><img src=\"https://journal.3960.org/posts/2022-06-26-javascript-gamepad-api-verwenden/gamepad-saitek-pro-rudder-pedals.png\" alt=\"Gamepad-Belegung für die Saitek Pro Flight Rudder Pedals\" /><span class=\"figcaption\" aria-hidden=\"true\">Gamepad-Belegung für die Saitek Pro Flight Rudder Pedals<br /></span></span></p>\n<p>Ganz verrückt sieht es dann mit Fußpedalen aus, die über gar keine Buttons, aber dafür eine Achse für die Verschiebung der Füße und zwei Achsen für die jeweiligen Pedale haben.</p>\n<h2 id=\"auswertung-von-achsen-und-buttons\">Auswertung von Achsen und Buttons</h2>\n<p>Den Zustand des Gamepads und all seiner Achsen und Buttons zu kennen ist für euer Spiel oder eure Simulation nicht immer hilfreich, denn ihr bekommt die absoluten Roh-Daten des Controllers. Diese solltet ihr in der Regel in ein besser handhabbares Format umwandeln.</p>\n<blockquote><p>Die Programmier-Beispiele in diesem Artikel sind natürlich sehr simpel gehalten; in einer echten Umgebung kann deutlich mehr Abstraktion hilfreich sein.</p></blockquote>\n<h3 id=\"todzonen\">Todzonen</h3>\n<p><span class=\"figure\"><img src=\"https://journal.3960.org/posts/2022-06-26-javascript-gamepad-api-verwenden/gamepad-saitek-pro-rudder-pedals.png\" alt=\"Beispiel für einen ungenauen Messwert in der Mitte einer Achse\" /><span class=\"figcaption\" aria-hidden=\"true\">Beispiel für einen ungenauen Messwert in der Mitte einer Achse<br /></span></span></p>\n<p>Analoge Eingabeinstrumente haben in der Regel kleine Ungenauigkeiten. Manchmal zentrieren sie nicht richtig, manchmal flattern die Ausgabewerte etwas. Um diese kleinen Fehler zu korrigieren werden sogenannte <i>dead zones</i> eingerichtet.</p>\n<p><i>Dead zones</i> oder <i>dead bands</i> ignorieren bestimmte Werte einer Achse, und schneiden diese ab. Somit kann zum Beispiel eine leicht verkalibrierte Mitte eures Sticks trotzdem als <code>0</code> ausgelesen werden, oder ein Stick saubere <code>-1</code> und <code>+1</code> übermitteln, obwohl er aus Altersschwäche nur <code>+0.9995</code> erreicht.</p>\n<p>Ein fantastischer Artikel zu diesem Thema ist <a href=\"http://www.mimirgames.com/articles/games/joystick-input-and-using-deadbands/\">„Joystick input and using deadbands“ von Mimir Games</a>. Einfach gesprochen brauchen wir zwei Arten von Todzonen:</p>\n<ul>\n<li><code>innerThreshold</code>: Bei selbst zentrierenden Achsen sollte im einer kleinen Zone in der Mitte keine Eingabe genommen werden.</li>\n<li><code>outerThreshold</code>: Generell sollte das obere und untere Ende des Wertebereichs vorzeitig einen Vollausschlag erzeugen.</li>\n</ul>\n<p>Eine sehr einfache Funktion für diesen Zweck sieht wie folgt aus:</p>\n<pre><code class=\"language-javascript\"><b>const</b> axisDeadzone = <b>function</b>(axis, outerThreshold, innerThreshold = <em>0</em>) {\n  <i>if</i> (innerThreshold &gt; <em>0</em>) {\n    <b>const</b> multiplier = (axis &gt; <em>0</em> ? <em>1</em> : <em>-1</em>);\n    axis = Math.max(<em>0</em>, (Math.abs(axis) - innerThreshold) / (<em>1</em> - innerThreshold)) * multiplier;\n  }\n\n  <i>if</i> (outerThreshold &gt; <em>0</em>) {\n    axis = Math.max(<em>-1</em>, Math.min(<em>1</em>, axis / (<em>1</em> - outerThreshold)));\n  }\n\n  <i>return</i> axis;\n}\n</code></pre>\n<h3 id=\"achsen-im-zeitverlauf\">Achsen im Zeitverlauf</h3>\n<p>Die <code>Gamepad</code>-API liefert euch immer eine Momentaufnahme des Gamepads, die ihr so oft wie möglich abfragt. Der zeitliche Abstand zwischen diesen Abfragen ist nicht direkt vorhersehbar, sondern wird ebenfalls gemessen. Dafür übermittelt das Gamepad den <code>gamepad.timestamp</code>.</p>\n<p>Den Effekt einer Achs-Eingabe können wir einfach mit der Zeitspanne multiplizieren, der zwischen den letzten beiden Messungen vergangen ist; selbst wenn unser Gamepad etwas länger für die Messung braucht, ist der Effekt im Spiel trotzdem der selbe.</p>\n<p>Zuerst sollten wir uns eine Variable anlegen, in der wir uns den letzten Timestamp merken können. In der Abfrageschleife (dem Game-Loop) können wir nun die Zeit zwischen den letzten beiden Updates ermitteln:</p>\n<pre><code class=\"language-javascript\"><b>let</b> lastTimestamp = <em>0</em>;\n<b>let</b> posX = <em>0</em>;\n<b>let</b> posY = <em>0</em>;\n\n<b>function</b> gameLoop () {\n  <b>const</b> gamepad = navigator.getGamepads()[<em>0</em>]; <u>// this is just a stub ;)</u>\n\n  <u>// Find time elapsed since last gamepad update</u>\n  <b>const</b> elapsedTime = lastTimestamp === <em>0</em>\n    ? <em>0</em> \n    : timestamp - lastTimestamp;\n  <u>// Store current timestamp for next time</u>\n  lastTimestamp = timestamp;\n\n  <u>// Move Pac Man / Thunderblade / Turrican / whatever relative to elapsed time</u>\n  posX += gamepad.axes[<em>0</em>] * elapsedTime;\n  posY += gamepad.axes[<em>1</em>] * elapsedTime;\n\n  window.requestAnimationFrame(gameLoop); <u>// Restart loop</u>\n}\n</code></pre>\n<h3 id=\"buttons-im-zeitverlauf\">Buttons im Zeitverlauf</h3>\n<p>Buttons klingen trivial, sind es aber nicht. Die <code>Gamepad</code>-API übermittelt, ob ein Button gerade <em>jetzt</em> gedrückt wird. Ob er vorher schon gedrückt wurde und für wie lange, oder ob er in der aktuellen Messung das erste Mal gedrückt wurde – diese Information müssen wir uns selber erarbeiten.</p>\n<p>Folgende Zustände sind für uns hilfreich:</p>\n<ul>\n<li><code>pressed</code>: Der Button wird gerade gedrückt <em>gehalten</em></li>\n<li><code>triggered</code>: Der Button wird gerade <em>zum ersten Mal</em> gedrückt</li>\n<li><code>released</code>: Der Button wird gerade wieder <em>losgelassen</em></li>\n</ul>\n<p>In einem Skript könnte das wie folgt aussehen:</p>\n<pre><code class=\"language-javascript\"><b>let</b> lastButtonStates = [];\n\n<b>function</b> gameLoop () {\n  <b>const</b> gamepad = navigator.getGamepads()[<em>0</em>]; <u>// this is just a stub ;)</u>\n\n  <u>// Get button states</u>\n  <b>const</b> buttonStates = gamepad.buttons.<i>map</i>((button, index) <samp>=&gt;</samp> {\n    <i>return</i> {\n      pressed: button.pressed,\n      triggered: lastButtonStates[index]\n        ? (button.pressed &amp;&amp; lastButtonStates[index].pressed !== button.pressed)\n        : button.pressed,\n      released: lastButtonStates[index]\n        ? (!button.pressed &amp;&amp; lastButtonStates[index].pressed !== button.pressed)\n        : <samp>false</samp>\n    }\n  });\n\n  <u>// Have Pac Man / Thunderblade / Turrican / whatever do stuff</u>\n  buttonStates[<em>0</em>].triggered &amp;&amp; turrican.jump();\n  buttonStates[<em>1</em>].pressed   &amp;&amp; turrican.fire();\n  buttonStates[<em>2</em>].pressed   &amp;&amp; turrican.chargeSuperpower();\n  buttonStates[<em>2</em>].released  &amp;&amp; turrican.releaseSuperpower();\n\n  window.requestAnimationFrame(gameLoop); <u>// Restart loop</u>\n}\n</code></pre>\n<p>Mit den Informationen aus <code>triggered</code> und <code>released</code> könnt ihr noch die Zeitdauer des Gedrückthaltens ableiten, und zum Beispiel auch in einem bestimmten Intervall bei gedrückter Taste Aktionen ausführen.</p>\n<h2 id=\"spezielle-eingabe-achsen\">Spezielle Eingabe-Achsen</h2>\n<p>Einige unscheinbar Kontrollen an eurem Joystick sind in der Auswertung initial etwas kompliziert zu verstehen. Mit einer kleinen Trickkiste könnt ihr euch aber schnell Übersicht verschaffen.</p>\n<h3 id=\"schubkontrollen\">Schubkontrollen</h3>\n<p>Schubkontrollen sind eine ganz normale Achse wie zum Beispiel ein Joystick, mit dem Unterschied, dass sie sich nicht selber zentrieren. Sie haben den selben Wertebereich wie jede andere Achse auch. Der <code>value</code> kann zwischen <code>-1</code>..<code>+1</code> liegen.</p>\n<p>Dieser Wertebereich ist für eine Schubkontrolle in der Regel nicht so hilfreich, aber mittels einer einfachen Funktion auf den Bereich <code>0</code>..<code>1</code> transformierbar:</p>\n<pre><code class=\"language-javascript\"><b>const</b> axisToThrottle = <b>function</b>(axis) {\n  <i>return</i> (axis + <em>1</em>) / <em>2</em>;\n}\n</code></pre>\n<h3 id=\"analoge-trigger\">Analoge Trigger</h3>\n<p>Einige Buttons (auch gerne Trigger genannt) haben nicht nur einen festen Auslösepunkt, sondern auch einen analoge Achse verbaut. So sind bei den meisten Gamepads die unteren Schultertasten als analoge Trigger ausgeführt. Dies erlaubt euch als Spieleentwickler genau zu messen, wie weit der Button gedrückt wurde.</p>\n<p>Auf einem Standard-Gamepad wird dies am <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/GamepadButton\"><code>GampepadButton</code></a> als <code>value</code> gemeldet:</p>\n<pre><code class=\"language-javascript\">gamepad.button[<em>4</em>] = {\n  pressed: <samp>true</samp>,\n  value: <em>0.4321</em>\n};\n</code></pre>\n<p>Der <code>value</code> liegt dann zwischen <code>0</code>..<code>1</code>.</p>\n<p>Verflixt wird es, wenn das Eingabegerät nicht als Standard-Gamepad erkannt wird; denn tatsächlich ist der Trigger gar kein Button, sondern eine Achse:</p>\n<pre><code class=\"language-javascript\">gamepad.axes[<em>3</em>] = <em>-0.1358</em>;\n</code></pre>\n<p>…wobei der Wert zwischen <code>-1</code>..<code>1</code> liegt, womit er einen anderen Wertebereich umfasst als in seiner <code>GampepadButton</code>-Form.</p>\n<p>Eine einfache Transformation macht uns die Achse aber wieder zum Button:</p>\n<pre><code class=\"language-javascript\"><b>const</b> axisToButton = <b>function</b>(axis) {\n  <i>return</i> {\n    pressed: (axis &gt; <em>-1</em>),\n    value: (axis + <em>1</em>) / <em>2</em>,\n    touched: (axis &gt; <em>-1</em>)\n  };\n}\n</code></pre>\n<h3 id=\"coolie-hats-und-vier-wege-kreuze\"><i>Coolie hats</i> und Vier-Wege-Kreuze</h3>\n<p><span class=\"figure\"><img src=\"https://journal.3960.org/posts/2022-06-26-javascript-gamepad-api-verwenden/gamepad-ch-combatstick.png\" alt=\"Beispiel für 4-Wege-Kreuz und Coolie-Hat\" /><span class=\"figcaption\" aria-hidden=\"true\">Beispiel für 4-Wege-Kreuz und Coolie-Hat<br /></span></span></p>\n<p>Jedes Vier-Wege-Kreuz auf eurem Gamepad, Joystick oder Schubkontrolle kann eine kleine Überraschung für euch bereit halten. Tatsächlich gibt es mehrere Wege, wie sie in der <code>Gamepad</code>-API auftreten können:</p>\n<ul>\n<li>Eine Serie von 4 Buttons, als oben/unten/links/rechts belegt</li>\n<li>Eine Serie von 4 Buttons, als oben/rechts/unten/links belegt</li>\n<li>Eine Serie von zwei Achsen, links/rechts und oben/unten</li>\n<li><em>Eine</em> Achse!</li>\n</ul>\n<p>Wenn ein Vier- bzw. Acht-Wege-Kreuz als eine einzige Achse auftritt, hat der Hersteller sich etwas besonderes überlegt: Jeder Wert zwischen <code>-1..+1</code> steht für Ausrichtung des <i>Coolie hats</i> zwischen 0°..315°. In Ruheposition sendet er dagegen einen unmöglich hohen Wert, den ihr verwerfen solltet.</p>\n<p>Der <code>value</code> auf der Achse beginnen mit <code>-1</code> auf der 0°-Position. Pro 90°-Drehung erhöht sich der <code>value</code> um <code>0.5715</code>, bis er auf 315° bei <code>+1</code> endet.</p>\n<p>Um aus diesem analogen Wert einen von acht Zuständen zu machen hilft eine kleine Funktion:</p>\n<pre><code class=\"language-javascript\"><u>// 7 0 1</u>\n<u>// 6 ∗ 2</u>\n<u>// 5 4 3</u>\n<b>const</b> axisToEightWay = <b>function</b>(axis) {\n  <i>return</i> (axis &gt; <em>1</em>) \n    ? <samp>undefined</samp> <u>// centered state</u>\n    : Math.round((axis + <em>1</em>) * <em>7</em>/<em>2</em>); <u>// 0 means &quot;top&quot;, 4 means &quot;bottom&quot;</u>\n}\n</code></pre>\n<p>Diese acht Zustände können auch in zwei Achsen umgerechnet werden:</p>\n<pre><code class=\"language-javascript\"><b>const</b> eightWaytoAxes = <b>function</b>(value) {\n  <i>if</i> (value === <samp>undefined</samp>) {\n    <i>return</i> [<em>0</em>,<em>0</em>];\n  }\n\n  <b>let</b> x = value % <em>4</em> ? <em>1</em> : <em>0</em>;\n  x *= value &gt; <em>4</em> ? <em>-1</em> : <em>1</em>;\n  \n  <b>let</b> y = ((value +<em>2</em>) % <em>4</em>) ? <em>1</em> : <em>0</em>;\n  y *= value &lt; <em>2</em> || value &gt; <em>6</em> ? <em>-1</em> : <em>1</em>;\n  \n  <i>return</i> [x,y];\n}\n</code></pre>\n<p>Wenn euch vier Zustände reichen, braucht es eine etwas andere Formel:</p>\n<pre><code class=\"language-javascript\"><u>//   0</u>\n<u>// 3 + 1</u>\n<u>//   2 </u>\n<b>const</b> axisToFourWay = <b>function</b>(axis) {\n  <i>return</i> (axis &gt; <em>1</em>) \n    ? <samp>undefined</samp> <u>// centered state</u>\n    : Math.round((axis + <em>1</em>) * <em>7</em>/<em>4</em>) % <em>4</em>; <u>// 0 means &quot;top&quot;, 2 means &quot;bottom&quot;</u>\n}\n</code></pre>\n<h2 id=\"mapping\">Mapping</h2>\n<p>Das Auslesen eines Joysticks hat jede Menge Fallstricke. Aus gutem Grund spekulieren Spielekonsolen und viele Spiele auf das <a href=\"https://w3c.github.io/gamepad/#remapping\">Standard-Gamepad</a>, um sich Ärger beim Mapping zu ersparen.</p>\n<p>Andersherum stellt sich auch im Browser die Herausforderung, dass wir nicht bei jedem angeschlossenen Controller das genaue Mapping vorher kennen können. So oder so werdet ihr nicht darum herum kommen, dem Spieler einen Konfigurationsdialog anzubieten, mit dem die Zuordnung von Spielfunktion zu Controller-Button/Achse festgelegt werden kann. Bei der Speicherung des Mappings kann die <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Storage\">Web Storage API</a> weiterhelfen, damit der Aufwand nur einmalig anfällt.</p>\n<p>Ein Mapping könnte jedenfalls wie folgt aussehen:</p>\n<pre><code class=\"language-javascript\"><b>const</b> mapping = {\n  buttons: {\n    radio: <em>2</em>,\n    flapsUp: <em>4</em>,\n    flapsDown: <em>6</em>,\n    trimLeft: <em>7</em>,\n    trimRight: <em>5</em>\n  },\n  axes: {\n    roll: <em>0</em>,\n    pitch: <em>1</em>,\n    throttle: <em>3</em>,\n    yaw: <em>4</em>,\n    freeLook: <em>9</em>\n  }\n}\n</code></pre>\n<p>Ein Zugriff auf die konfigurierten Achsen ist damit auch deutlich übersichtlicher:</p>\n<pre><code class=\"language-javascript\"><b>let</b> roll = <em>0</em>;\n<b>let</b> pitch = <em>0</em>;\n\n<b>function</b> gameLoop () {\n  <b>const</b> gamepad = navigator.getGamepads()[<em>0</em>]; <u>// this is just a stub ;)</u>\n\n  <u>// ...</u>\n\n  roll += gamepad.axes[mapping.axes.roll] * elapsedTime;\n  pitch += gamepad.axes[mapping.axes.pitch] * elapsedTime;\n\n  window.requestAnimationFrame(gameLoop); <u>// Restart loop</u>\n}\n</code></pre>\n<p>Wichtig für die Konfiguration von Achsen ist es, die genaue Art der Achse zu kennen:</p>\n<ul>\n<li>Relative Achsen zentrieren sich selber, beziehungsweise kehren sie automatisch in einen Ruhezustand zurück.</li>\n<li>Absolute Achsen behalten ihren zuletzt eingestellten Wert bei. Ein Beispiel dafür ist die Schubkontrolle.</li>\n</ul>\n<p>In den meisten Fällen werdet ihr nur wenige absolute Achsen in einem Spiel benötigen. Beim Mapping ist es aber sinnvoll, für einen über eine absolute Achse einstellbaren Wert auch ein Mapping für relative Achsen anzubieten. Als Beispiel kann die Schubkontrolle dienen (die in einer Simulation ihren Wert 1:1 auf die simulierte Schubkontrolle überträgt), die auch durch einen kleinen Stick gesteuert werden könnte (was in der Simulation die simulierte Schubkontrolle nach oben oder unten schiebt).</p>\n<h3 id=\"modifier\">Modifier</h3>\n<p>Nicht zuletzt müsst ihr beim Mapping berücksichtigen, dass die Anzahl der vorhandenen Achsen und Knöpfe mit dem Eingabegerät des Nutzers nicht abdeckbar ist. Hier bieten sich sogenannte Modifier an: Ähnlich wie die <kbd>Shift</kbd>-, <kbd>Alt</kbd>- und <kbd>Strg</kbd>-Taste die Bedeutung einer Taste auf der Tastatur verändern kann, kann <a href=\"https://forum.dcs.world/topic/283264-list-of-console-gamepad-controller-layouts-for-dcs-world-modules/\">ein gedrückt gehaltener Button die Bedeutung der Achsen und Buttons eines Controllers verändern</a>.</p>\n<p>Das Mapping-Objekt für Modifier sowie der Konfigurationsdialog sind natürlich deutlich komplexer, können aber selbst <a href=\"https://www.8bitdo.com/\">Retro-Controller mit wenigen Achsen</a> mit einer erstaunlichen Vielzahl von Funktionen belegen.</p>\n<h2 id=\"fazit\">Fazit</h2>\n<p>Game Controller im Browser mittels der <code>Gamepad</code>-API anzusprechen ist nicht kompliziert – die Interpretation der Werte und Berücksichtigung der verschiedenen Controller hingegen schon. Ein robustes Handling im JavaScript erspart euch und euren Nutzern unschöne Erlebnisse.</p>\n<p>Die einzelnen Skripte aus diesem Artikel gibt es übrigens als <a href=\"https://3960.org/sandbox/GamepadHelper.js\" download>kompletten <code>GamepadHelper</code> zum Download</a>. Diese Bibliothek kann eine gute Grundlage für ein eigenes Mapping sein.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=JavaScript%3A%20Die%20Gamepad-API%20verwenden&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2022-06-26-javascript-gamepad-api-verwenden%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Moderne Browser haben inzwischen eine Vielzahl interessanter JavaScript-Schnittstellen. Höchste Zeit, die Gamepad-API genau zu begutachten.",
      "date_published": "2022-06-26T18:22:50+02:00",
      "date_modified": "2024-03-22T11:02:23+01:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://journal.3960.org/posts/2022-06-26-javascript-gamepad-api-verwenden/gampepad-xbox-360-controller.png",
      "language": "de-DE",
      "image": "https://journal.3960.org/posts/2022-06-26-javascript-gamepad-api-verwenden/gampepad-xbox-360-controller.png",
      "tags": [
        "Javascript",
        "Joystick",
        "Idee",
        "Programmierung",
        "Simulation",
        "Webdevelop"
      ]
    },
    {
      "id": "user/posts/2020-04-10-web-components-mit-markdown-verwenden/index.md",
      "url": "https://journal.3960.org/posts/2020-04-10-web-components-mit-markdown-verwenden/",
      "title": "Web Components mit Markdown verwenden",
      "content_html": "<p>Der Artikel <a href=\"https://web.dev/how-we-build-webdev-and-use-web-components/\">„How we build the site and use Web Components“</a> deutet im Nebensatz eine wunderbare Methode an, um in Content-Management-Systemen auf Markdown-Basis redaktionell <i>Web Components</i> zum Einsatz zu bringen.</p>\n<!-- more -->\n<p id=\"more\">Markdown hat eine oft gar nicht benötigte Eigenschaft: <a href=\"https://daringfireball.net/projects/markdown/syntax#html\">Inline-HTML</a>. In Markdown auftretendes HTML wird nach dem Umwandeln von Markdown in HTML vollkommen unverändert wieder ausgegeben. Also wird folgendes Markdown…</p>\n<pre><code class=\"language-markdown\">This <i>**Markdown example**</i> will <var>&lt;i&gt;</var>output HTML tags<var>&lt;/i&gt;</var> as well.\n</code></pre>\n<p>…zu folgendem HTML:</p>\n<pre><code class=\"language-html\">&lt;<i>p</i>&gt;This &lt;<i>strong</i>&gt;Markdown example&lt;/<i>strong</i>&gt; will &lt;<i>i</i>&gt;output HTML tags&lt;/<i>i</i>&gt; as well.&lt;/<i>p</i>&gt;\n</code></pre>\n<p>Wir erinnern uns: <a href=\"https://www.webcomponents.org/\" rel=\"nomention\">Web Components</a> sind durch JavaScript-Bibliotheken definierte HTML-Tags, die mit ihrem eigenen Verhalten und Layout versehen sind. So kann man Web Components wie HTML im Markdown verwenden, z.B. <a href=\"https://www.webcomponents.org/element/@lrnwebcomponents/word-count\" rel=\"nomention\"><code>word-count</code></a>:</p>\n<pre><code class=\"language-markdown\"><var>&lt;word-count&gt;</var>\n  Lorem ipsum <i>_dolor_</i> sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et <i>_dolore magna_</i> aliquyam erat, sed diam voluptua.\n<var>&lt;/word-count&gt;</var>\n</code></pre>\n<p>…wird damit zu…</p>\n<pre><code class=\"language-html\">&lt;<i>word-count</i>&gt;\n  &lt;<i>p</i>&gt;Lorem ipsum &lt;<i>em</i>&gt;dolor&lt;/<i>em</i>&gt; sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et &lt;<i>em</i>&gt;dolore magna&lt;/<i>em</i>&gt; aliquyam erat, sed diam voluptua.&lt;/<i>p</i>&gt;\n&lt;/<i>word-count</i>&gt;\n</code></pre>\n<p>…was wiederum im Browser dann den obigen Absatz mit einem kleinen Wort-Zähler dahinter anzeigt.</p>\n<p>Dieser Weg funktioniert unabhängig von der Art des eingesetzten Content Management Systems, solange das CMS redaktionelles Markdown verwendet. Für den Redakteur ist diese Lösung so einfach oder so kompliziert wie einen <a href=\"https://codex.wordpress.org/Shortcode\" rel=\"nomention\">Wordpress-Shortcode</a> einzusetzen.</p>\n<p>Dazu muss die zu verwendende Web-Component natürlich vorher geladen sein. Dies kann entweder im Template mit einem simplen <code>&lt;script&gt;</code>-Aufruf geschehen – oder (je nach Sicherheitseinstellung des CMS&#39;) auch <em>direkt</em> im Markdown des Artikels:</p>\n<pre><code class=\"language-html\">&lt;<i>script</i> <var>type</var>=&quot;<kbd>module</kbd>&quot; <var>src</var>=&quot;<kbd>https://unpkg.com/@lrnwebcomponents/word-count@2.6.5/word-count.js?module</kbd>&quot;&gt;&lt;/<i>script</i>&gt;\n</code></pre>\n<p>So oder so steht Redakteuren nun eine hinreichend bedienbare Methode zur Verfügung, in ihren Artikel Web Components zu verwenden. Alleine für diesen Zweck <a href=\"https://journal.3960.org/posts/2020-04-07-ideen-fuer-web-components/\">hatte ich schon ein paar Ideen, welche Web Components Redakteuren helfen könnten</a>:</p>\n<ul>\n<li><code>&lt;twitter-tweet&gt;</code> zum Einbetten von Tweets</li>\n<li><code>&lt;video-embed&gt;</code> zum Einbetten von Videos, zum Beispiel YouTube</li>\n<li><code>&lt;map-embed&gt;</code> zum Einbetten von Karten, wie z.B. Google Maps</li>\n<li><code>&lt;pull-quote&gt;</code> zum Anzeigen von aus dem Text herausgezogenen Zitaten</li>\n<li><code>&lt;linkbox-category&gt;</code> oder <code>&lt;linkbox-tag&gt;</code> zum Einbetten einer Linkbox, die verwandte Artikel anzeigt</li>\n<li><code>&lt;chess-board&gt;</code> oder <code>&lt;bar-chart&gt;</code> zum Darstellung von Spezialgrafiken wie einem Schachbrett oder Säulengrafiken (wir erinnern uns: <a href=\"https://journal.3960.org/posts/2020-04-05-svg-web-components/\">Web Components können auch SVG-Grafiken anzeigen</a>)</li>\n</ul>\n<p>Zu berücksichtigen wäre nur, dass einige ältere Browser Web Components nicht darstellen können. Das betrifft zum Beispiel auch einige RSS-Reader und E-Mail-Clients, so dass Artikel in diesem Umfeld nur eingeschränkt dargestellt werden. Das kann aber sogar erwünscht sein – denn dann wird das <em>innerhalb</em> des Web-Component-Tags verwendete Markdown bzw. HTML ausgegeben.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Web%20Components%20mit%20Markdown%20verwenden&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2020-04-10-web-components-mit-markdown-verwenden%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Der Artikel „How we build the site and use Web Components“ deutet im Nebensatz eine wunderbare Methode an, um in Content-Management-Systemen auf Markdown…",
      "date_published": "2020-04-10T19:32:33+02:00",
      "date_modified": "2020-04-10T20:34:10+02:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "Webdevelop",
        "Web-Components",
        "Blog",
        "Javascript",
        "Programmierung",
        "Technologie"
      ]
    },
    {
      "id": "user/posts/2020-04-07-ideen-fuer-web-components/index.md",
      "url": "https://journal.3960.org/posts/2020-04-07-ideen-fuer-web-components/",
      "title": "Ideen für Web Components",
      "content_html": "<p>Schlaue Beispiele gefällig, was man mit <a href=\"https://www.webcomponents.org/\" rel=\"nomention\">Web Components</a> alles bauen könnte, um HTML zu erweitern?</p>\n<!-- more -->\n<p id=\"more\">Web Components eignen sich ganz hervorragend, um von der Darstellung und/oder Funktionalität her komplexe Aufgaben gekapselt und wiederverwendbar zu lösen. Zudem können Web Components (ähnlich wie <code>&lt;iframe&gt;</code>, <code>&lt;object&gt;</code> und viele andere neue Tags) in ihrem Inneren einen Fallback enthalten, so dass Browser ohne die Fähigkeit zur Darstellung von Web Components trotzdem etwas anzuzeigen haben. </p>\n<p>Außerdem können Web Components dieses Fallback <a href=\"https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots\">gleichzeitig via <code>slot</code>-Mechanismus auswerten</a>, um die Fallback-Inhalte entweder wieder anzuzeigen, oder aber aus dem HTML noch weitere Informationen zu ziehen.</p>\n<figure class=\"blockquote\"><blockquote cite=\"https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots\"><p>An unnamed <code>&lt;slot&gt;</code> will be filled with all of the custom element&#39;s top-level child nodes that do not have the slot attribute.</p></blockquote>\n<figcaption><a href=\"https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots\"><cite>Using templates and slots</cite> – MDN Web Docs</a></figcaption></figure>\n<p>Im Sinne des <i>progressive enhancement</i> könnte man also mit Web Components viele alltägliche Probleme lösen:</p>\n<h2 id=\"tweets\">Tweets</h2>\n<pre><code class=\"language-html\">&lt;<i>twitter-status</i> <var>status</var>=&quot;<kbd>https://twitter.com/Interior/status/463440424141459456</kbd>&quot;&gt;\n  &lt;<i>blockquote</i>&gt;Sunsets don&#39;t get much better than this one over @GrandTetonNPS. #nature #sunset&lt;/<i>blockquote</i>&gt;\n  &lt;<i>cite</i>&gt;US Department of the Interior&lt;/<i>cite</i>&gt;\n&lt;/<i>twitter-status</i>&gt;\n</code></pre>\n<p>Diese Komponente zieht datenschutzzkonform Tweets von Twitter – und als Fallback braucht sie gar keine weiteren Requests, da der Content bereits auf der Seite zu sehen ist. <span class=\"emoji emoji--1f609\" title=\";)\">&#x1F609;</span></p>\n<h2 id=\"datenschutzkonformer-videoplayer\">Datenschutzkonformer Videoplayer</h2>\n<pre><code class=\"language-html\">&lt;<i>video-embed</i> <var>url</var>=&quot;<kbd>https://www.youtube.com/embed/G2dGWH90aew?autoplay=1</kbd>&quot;&gt;\n  &lt;<i>a</i> <var>href</var>=&quot;<kbd>https://www.youtube.com/embed/G2dGWH90aew?autoplay=1</kbd>&quot;&gt;Youtube-Video&lt;/<i>a</i>&gt;\n&lt;/<i>video-embed</i>&gt;\n</code></pre>\n<p>Diese Komponente verwandelt mittels einfacher Magie den Link zu einem Video in eine Video-Einbettung. Redakteure werden es lieben, nicht mehr mit Embed-Codes herumzuhantieren, sondern einfach den Link zum Video aus der URL-Leiste ihres Browsers kopieren zu können – und trotzdem im fertigen Artikel ein eingebettetes Video zu sehen. (Ein Trick, den das <a href=\"https://github.com/fboes/blogophon/\" rel=\"nomention\">Blogophon</a> auch beherrscht.)</p>\n<p>Mit <a href=\"https://github.com/paulirish/lite-youtube-embed\" rel=\"nomention\"><code>lite-youtube</code></a> schon ähnlich umgesetzt: Mit dieser Komponente bekommt man eine deutlich datenschutzkonforme Darstellung eines eingebetteten Videos von YouTube, Viemo oder ähnlichem – ähnlich wie in diesem Blog YouTube-Videos in die Seite eingebettet werden.</p>\n<h2 id=\"code-beispiele\">Code-Beispiele</h2>\n<pre><code class=\"language-html\">&lt;<i>code-highlighted</i> <var>lang</var>=&quot;<kbd>javascript</kbd>&quot;&gt;\n  &lt;<i>pre</i>&gt;\n    &lt;<i>code</i>&gt;\n      …\n    &lt;/<i>code</i>&gt;\n  &lt;/<i>pre</i>&gt;\n&lt;/<i>code</i>&gt;\n</code></pre>\n<p>Mit dieser Komponente kann ich Code-Beispiel mit Syntax-Highlighting versehen. In dem <code>lang</code>-Attribut wird dabei die verwendete Programmiersprache vermerkt.</p>\n<h2 id=\"karten\">Karten</h2>\n<pre><code class=\"language-html\">&lt;<i>map-embed</i> <var>provider</var>=&quot;<kbd>google-maps</kbd>&quot; <var>coordinates</var>=&quot;<kbd>53.246, 10.412</kbd>&quot;&gt;\n  &lt;<i>a</i> <var>href</var>=&quot;<kbd>https://www.google.de/maps/place/53°14&#39;45.6<samp>&amp;quot;</samp>N+10°24&#39;43.2<samp>&amp;quot;</samp>E</kbd>&quot;&gt;Die wunderschöne Stadt Lüneburg&lt;/<i>a</i>&gt;\n&lt;/<i>map-embed</i>&gt;\n</code></pre>\n<p>Karten in redaktionellen Kontext sind immer wieder hilfreich, um Orte und ihre Umgebung darzustellen. Als Fallback kann immer noch ein Link zu einer Karten-Plattform angeboten werden.</p>\n<h3 id=\"geografische-eingabefelder\">Geografische Eingabefelder</h3>\n<pre><code class=\"language-html\">&lt;<i>input-coordinates</i> <var>provider</var>=&quot;<kbd>google-maps</kbd>&quot;&gt;\n  &lt;<i>input</i> <var>name</var>=&quot;<kbd>coordinates</kbd>&quot; <var>type</var>=&quot;<kbd>coordinates</kbd>&quot; <var>step</var>=&quot;<kbd>0.001</kbd>&quot; <var>value</var>=&quot;<kbd>53.246, 10.412</kbd>&quot; /&gt;\n&lt;/<i>input-coordinates</i>&gt;\n</code></pre>\n<p>Getreu meiner Idee, ein <a href=\"https://journal.3960.org/posts/2018-02-08-html-input-fuer-koordinaten/\">Eingabefeld für Geo-Koordinaten</a> in HTML anzubieten, könnte diese Komponente eine Geo-Lokalisierung und Kartendarstellung beinhalten, um den Nutzer die Auswahl eines Standorts zu erlauben.</p>\n<h2 id=\"rich-text-editor\">Rich-Text-Editor</h2>\n<pre><code class=\"language-html\">&lt;<i>textarea-special</i> <var>type</var>=&quot;<kbd>text/html</kbd>&quot;&gt;\n  &lt;<i>textarea</i> <var>name</var>=&quot;<kbd>html</kbd>&quot;&gt;\n    …\n  &lt;/<i>textarea</i>&gt;\n&lt;/<i>textarea-special</i>&gt;\n</code></pre>\n<p>Wie lange haben wir schon auf einen einfach im Browser funktionierenden Rich-Text-Editor gewartet. Die Komponente ist sogar so schlau, das generierte HTML wieder in das über ihm liegende Formular zurückzugeben.</p>\n<p>Theoretisch könnte sie ja aber auch ganz andere Dinge zur Bearbeitung anbieten. <code>text/markdown</code>? <code>text/csv</code>? <code>text/yaml</code>? Warum nicht? Das könnte man alles über das <code>type</code>-Attribut lösen.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Ideen%20f%C3%BCr%20Web%20Components&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2020-04-07-ideen-fuer-web-components%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Schlaue Beispiele gefällig, was man mit Web Components alles bauen könnte, um HTML zu erweitern?",
      "date_published": "2020-04-07T19:18:32+02:00",
      "date_modified": "2021-08-30T10:16:09+02:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "Webdevelop",
        "Web-Components",
        "Idee",
        "Javascript",
        "Programmierung",
        "Technologie",
        "Für Tumblr"
      ]
    },
    {
      "id": "user/posts/2020-04-05-svg-web-components/index.md",
      "url": "https://journal.3960.org/posts/2020-04-05-svg-web-components/",
      "title": "SVG und Web Components",
      "content_html": "<p><img src=\"https://journal.3960.org/posts/2020-04-05-svg-web-components/hsi-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> <a href=\"https://www.webcomponents.org/\" rel=\"nomention\">Web Components</a> erlauben in modernen Browsern, eigene Tags mit beliebig komplexen Verhalten zu definieren. Und wie der Name „Komponente“ schon nahelegt, kann man diese kleinen Bibliotheken beliebig oft weiterverwenden, sobald diese einmalig geladen wurden.</p>\n<p>Mit einigen kleinen Kniffen kann die Entwicklung solcher Komponenten noch schneller von der Hand gehen.</p>\n<!-- more -->\n<h2 id=\"more\">Wie starte ich mit Web Components?</h2>\n<p>Für den Einstieg in das Thema Web Components empfiehlt sich die Lektüre der ausgezeichneten <a href=\"https://developers.google.com/web/fundamentals/web-components\" rel=\"nomention\">Einführung von Google</a> und <a href=\"https://css-tricks.com/an-introduction-to-web-components/\">CSS-Tricks</a>. Andere Anleitungen basieren teilweise auf älteren Ideen, die getrost ignoriert werden können.</p>\n<p><strong>Update 2020–09</strong>: Die Vielzahl der Wege, wie man eine Web Component bauen kann, <a href=\"https://webcomponents.dev/blog/all-the-ways-to-make-a-web-component/\" rel=\"nomention\">hat webcomponents.dev veranlasst, eine Auflistung aller möglichen Wege zum Erstellen einer Web Component zusammenzustellen</a>.</p>\n<p>Außerdem lohnt es sich immer, moderne Beispiele anzuschauen. Ich für meinen Teil habe entsprechend versucht, einen <a href=\"https://en.wikipedia.org/wiki/Horizontal_situation_indicator\" rel=\"nomention\">Horizontal Situation Indicator</a> als Vanilla-JavaScript Web Component mustergültig zu bauen. Der daraus resultierende <a href=\"https://github.com/fboes/webcomponent-hsi\">Quellcode der „Horizontal Situation Indicator“ Web Component</a> wird im Rahmen dieses Artikels immer wieder als Beispiel herangezogen.</p>\n<div class=\"embed embed--codepen\"><iframe allowfullscreen=\"allowfullscreen\" title=\"Die montierte &quot;Horizontal Situation Indicator&quot; Web Component\" src=\"https://journal.3960.org//codepen.io/fboes/embed/dyoBvzP/?height=265&amp;theme-id=0&amp;default-tab=result&amp;embed-version=2\" height=\"265\"></iframe></div>\n<h2 id=\"wie-denkt-man-eine-web-component\">Wie <em>denkt</em> man eine Web Component?</h2>\n<p>Die größte Hürde für den angehenden Komponenten-Bauer ist das Verständnis, wie all die schönen Teile zusammenpassen.</p>\n<p>Da eine Web Compoment gekapselt ist, kann nur über eine vorher definierte Schnittstelle von außen Zugriff auf ihr Verhalten genommen werden. Es ist also sehr sinnig, die Konzeption und den Bau einer Web Component wie den Bau einer Schnittstelle bzw. eines Interfaces zu verstehen.</p>\n<p>Als Beispiel nehmen wir einfach die <a href=\"https://3960.org/webcomponent-hsi/\">fertige Implementation der HSI-Web-Component</a>:</p>\n<pre><code class=\"language-html\">&lt;<i>horizontal-situation-indicator</i> <var>id</var>=&quot;<kbd>hsi</kbd>&quot; <var>heading</var>=&quot;<kbd>45.0</kbd>&quot; <var>heading-select</var>=&quot;<kbd>0.0</kbd>&quot;&gt;&lt;/<i>horizontal-situation-indicator</i>&gt;\n</code></pre>\n<h3 id=\"attribute-bzw-properties\">Attribute bzw. Properties</h3>\n<p>Beim Bau einer Web Component müssen neben dem <strong>Namen der Web Component</strong> auch die <strong>Attribute der Web Component und ihre möglichen Werte</strong> definiert werden.</p>\n<p>Diese Attribute verwandeln sich innerhalb der JavaScript-Repräsentation der Web Component in <strong>Properties</strong>, die mit den Attributen synchronisiert sind:</p>\n<pre><code class=\"language-javascript\"><b>const</b> el = <b>document</b>.<b>getElementById</b>(<kbd>'hsi'</kbd>);\nel.<b>getAttribute</b>(<kbd>'heading'</kbd>); <u>// &quot;45.0&quot;</u>\nel.heading; <u>// &quot;45.0&quot;</u>\n\nel.<b>setAttribute</b>(<kbd>'heading'</kbd>, <kbd>'60.0'</kbd>);\nel.<b>getAttribute</b>(<kbd>'heading'</kbd>); <u>// &quot;60.0&quot;</u>\nel.heading; <u>// &quot;60.0&quot;</u>\n\nel.heading = <kbd>'135.0'</kbd>;\nel.<b>getAttribute</b>(<kbd>'heading'</kbd>); <u>// &quot;135.0&quot;</u>\nel.heading; <u>// &quot;135.0&quot;</u>\n</code></pre>\n<p>Bei dem Zugriff auf eine Property mit einem <code>-</code> im Namen funktioniert der Zugriff leicht anders:</p>\n<pre><code class=\"language-javascript\">el.<b>getAttribute</b>(<kbd>'heading-select'</kbd>);\n<u>//el.heading-select existiert nicht</u>\nel[<kbd>'heading-select'</kbd>]; <u>// korrekte Schreibweise in `[]`</u>\n</code></pre>\n<p>Die Schreibweise mit <code>[]</code> erlaubt auch den dynamischen Zugriff auf Properties:</p>\n<pre><code class=\"language-javascript\"><b>let</b> attrName = <kbd>'heading-select'</kbd>;\n\nel.<b>getAttribute</b>(attrName);\nel[attrName];\n</code></pre>\n<h3 id=\"methoden\">Methoden</h3>\n<p>Darüber hinaus kann der Entwickler einer Web Component noch festlegen, dass die Komponente <strong>JavaScript-Methoden</strong> anbietet. Diese erlauben zum Beispiel von außen der Komponente zu befehlen, komplexe Prozesse innerhalb der Komponente zu erledigen.</p>\n<pre><code class=\"language-javascript\">el.synchronizeHeading();\n</code></pre>\n<p>Da JavaScript keine Sichtbarkeiten wie <code>public</code> und <code>private</code> für Methoden hat, hat sich als Konvention herausgebildet, private Methoden mit einem <code>_</code> zu beginnen.</p>\n<h3 id=\"events\">Events</h3>\n<p>Außerdem kann eine Web Component noch <strong>JavaScript-Events</strong> erzeugen, die außerhalb der Komponente registriert werden können. So emittiert z.B. das <code>&lt;video&gt;</code>-Tag Events, wenn ein Video beendet wurde, was ohne dieses Event außerhalb des Tags keiner wissen könnte.</p>\n<h2 id=\"was-kann-eine-web-component-alles-darstellen\">Was kann eine Web Component alles darstellen?</h2>\n<p>Web Components können fast alles darstellen, was auch regulär in einem Browser dargestellt werden kann. Besonderes Augenmerk muss aber darauf gelegt werden, dass alle benötigten Bestandteile in der einen JavaScript-Datei enthalten ist, die die Web Component definiert.</p>\n<p>Am Einfachsten einzubinden sind die folgenden Dinge:</p>\n<ul>\n<li>HTML</li>\n<li>CSS für die Web Component</li>\n<li>JavaScript-Interaktion für die Web Component</li>\n</ul>\n<p>Alle anderen Ressourcen (<strong>Bilder, Videos, Töne</strong>) können mit einem Trick eingeschmuggelt werden: Mittels <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs\" rel=\"nomention\">Data URLs</a> können Binär-Dateien in Base64-Zeichenketten umgewandelt werden, die dann z.B. im <code>src</code>-Attribut eines <code>&lt;img&gt;</code> eingebunden werden können.</p>\n<p>Besonders spannend: <strong>SVG-Bilder</strong> sind nicht nur schön kompakt in Bezug auf ihren Speicherplatz, sondern <a href=\"https://css-tricks.com/using-svg/#article-header-id-5\" title=\"Inline SVG in HTML\">können auch direkt in das HTML</a> eingebunden werden – benötigen also den Base64-Trick nicht.</p>\n<h2 id=\"gibt-es-build-tools-für-web-components\">Gibt es Build Tools für Web Components?</h2>\n<p>In der Tat gibt es eine ganze Menge Tools, um das Zusammensetzen der einzelnen Teile einer Web Component zu unterstützen. In der Regel reicht aber <a href=\"https://github.com/fboes/webcomponent-hsi/blob/master/dev/build.js\">ein kleiner flotter Node.js-Mehrzeiler als Web Component Build Tool</a>, der aus einzelnen Dateien die eigentliche Web Component zusammensetzt. Die Kurzfassung:</p>\n<pre><code class=\"language-javascript\"><b>const</b> fs = <i>require</i>(<kbd>'fs'</kbd>);\n\n<b>let</b> source      = fs.readFileSync(<kbd>`horizontal-situation-indicator.js`</kbd>).toString();\n<b>let</b> templateCss = fs.readFileSync(<kbd>`src/horizontal-situation-indicator.css`</kbd>).toString();\n<b>let</b> templateSvg = fs.readFileSync(<kbd>`src/horizontal-situation-indicator.svg`</kbd>).toString();\n\nsource = source.<b>replace</b>(/(&lt;style&gt;).*(&lt;<samp>\\/</samp>style&gt;)/ms, templateCss);\nsource = source.<b>replace</b>(/(&lt;<samp>\\/</samp>style&gt;).*(<kbd>`)/ms, templateSvg);\nfs.writeFileSync(`</kbd>horizontal-situation-indicator.js`, source);\n</code></pre>\n<p>Das tatsächliche Skript ist zwar etwas komplexer, das Grundprinzip ist aber ein denkbar einfaches: Die Entwicklung von SVG und CSS (oder jedem anderen Dateitypen) findet in separaten Dateien statt, die mit dem obigen Skript einfach in das JavaScript der Web Component hineinkopiert werden. Unter anderem könnte hier auch die Konvertierung von Binär-Daten in ihre Base64-Entsprechung durchgeführt werden.</p>\n<h2 id=\"kann-man-das-erzeugen-von-get-und-set-für-jede-property-abkürzen\">Kann man das Erzeugen von <code>get</code> und <code>set</code> für jede Property abkürzen?</h2>\n<p>Da jeder Web Component eine Liste der zu synchronisierenden Attribute / Properties mit der Methode <code>observedAttributes</code> bekannt gemacht werden muss, kann genau diese Liste im <code>constructor</code> auch <a href=\"https://css-tricks.com/creating-a-custom-element-from-scratch/\">zum programmatischen Erzeugern von Gettern / Settern</a> verwendet werden.</p>\n<pre><code class=\"language-javascript\"><i>this</i>.constructor.observedAttributes.<i>forEach</i>((attrName) <samp>=&gt;</samp> {\n  Object.defineProperty(<i>this</i>, attrName, {\n    get() {\n      <i>return</i> <i>this</i>.<b>getAttribute</b>(attrName);\n    },\n    set(attrValue) {\n      <i>this</i>.<b>setAttribute</b>(attrName, attrValue);\n    }\n  });\n});\n</code></pre>\n<p>Diese Methode hat in einigen Web-Components-Frameworks möglicherweise Nachteile – für die Vanilla-Nutzung ist sie aber weitestgehend ungefährlich.</p>\n<h2 id=\"wie-löse-ich-styling-in-der-web-component\">Wie löse ich Styling <em>in</em> der Web Component?</h2>\n<p>Da Web Components sowieso nur in aktuellen Browsern zuverlässig funktionieren, kann man sich gleichzeitig auch auf fortgeschrittene CSS-Möglichkeiten verlassen. Um CSS innerhalb der Komponente von außen zu beeinflussen, verwende ich <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/--*\">CSS-Variablen bzw. CSS-Custom-Properties</a>. Innerhalb des CSS&#39; des Komponente definiere ich sie direkt an der DOM-Wurzel der Komponente:</p>\n<pre><code class=\"language-css\"><i>:host</i> {\n  <var>--background-color</var>: black;\n  <var>--foreground-color</var>: white;\n  <var>--heading-select-color</var>: cyan;\n  <var>--stroke-width</var>: 0.5;\n}\n<u>/*…und verwende diese CSS-Custom-Properties dann später in Variablen - bei mir z.B. als SVG-CSS-Eigenschaften:*/</u>\n\n<i>#background</i> {\n  <b>fill</b>: <b>var</b>(<var>--background-color</var>);\n}\n<i>*</i> {\n  <b>fill</b>: <b>var</b>(<var>--foreground-color</var>);\n}\n<i>*[stroke]</i> {\n  <b>stroke-width</b>: <b>var</b>(<var>--stroke-width</var>);\n}\n\n<i>#heading-select</i> {\n  <b>fill</b>: <b>var</b>(<var>--heading-select-color</var>);\n}\n</code></pre>\n<p>Wer nun auch immer diese Komponente verwendet, kann diese CSS-Custom-Properties von außen beeinflussen:</p>\n<pre><code class=\"language-css\"><i>horizontal-situation-indicator</i> {\n  <var>--heading-select-color</var>: red;\n  <var>--stroke-width</var>: 1;\n}\n</code></pre>\n<p>Bei der <a href=\"https://3960.org/webcomponent-hsi/\">Beispiel-Implementation von <code>&lt;horizontal-situation-indicator&gt;</code></a> kann auch bewundert werden, wie durch JavaScript diese CSS-Custom-Properties am lebenden Objekt verändert werden, und in der Komponente sich alles fröhlich verfärbt.</p>\n<p>Ganz nebenbei haben wir für die Komponente eine weitere Schnittstelle geschaffen – in diesem Fall eine Styling-Schnittstelle.</p>\n<p><strong>Update:</strong> Andererseits können aber auch einzelne DOM-Knoten ohne CSS-Properties zum expliziten Styling freigegeben werden. Eine <a href=\"https://css-tricks.com/styling-in-the-shadow-dom-with-css-shadow-parts/\">Anleitung zum Freigeben von DOM-Knoten aus dem Shadow-DOM zum CSS-Styling bei CSS-Tricks</a> zeigt die notwendigen Anpassungen im HTML:</p>\n<pre><code class=\"language-html\">&lt;<i>div</i> <var>part</var>=&quot;<kbd>style-me</kbd>&quot;&gt;…&lt;/<i>div</i>&gt;\n</code></pre>\n<p>…und dem CSS im Eltern-Dokument:</p>\n<pre><code class=\"language-css\"><i>horizontal-situation-indicator::part(style-me)</i> {\n  <b>font-weight</b>: bold;\n}\n</code></pre>\n<p>Auch hier hat wieder der Autor der Komponente die Herrschaft über die Elemente, die er nach außen freigibt – wie bei einer Schnittstelle.</p>\n<h2 id=\"wie-animiere-ich-svgs-in-web-components\">Wie animiere ich SVGs in Web Components?</h2>\n<p>Der eigentliche Clou der <a href=\"https://github.com/fboes/webcomponent-hsi\">HSI Web Component</a> ist die generelle Fähigkeit von JavaScript, DOM-Elemente und ihre Eigenschaften zu verändern. Dies können sowohl CSS-Eigenschaften als auch generelle Attribute von DOM-Elementen sein.</p>\n<p>Bei SVG bieten sich die folgenden Operationen an:</p>\n<ul>\n<li>Änderung von <code>stroke</code> und <code>stroke-width</code> zur Beeinflussung von Linien</li>\n<li>Änderung von <code>fill</code> zur Veränderung der Füllfarbe</li>\n<li>Änderung von <code>opacity</code> zur Veränderung der Durchsichtigkeit eines Elements</li>\n<li>Veränderung von Position, Größe und Rotation mittels <code>transform</code></li>\n<li>Austausch des Text-Inhalts von <code>&lt;text&gt;</code>-Knoten mittels <code>.textContent</code></li>\n</ul>\n<p>Bei SVG gibt es dabei die Möglichkeit, nicht nur via CSS diese Eigenschaften zu beeinflussen, sondern auch durch das Setzen von Attributen innerhalb des SVGs an einzelnen SVG-DOM-Knoten.</p>\n<p>Auch das ist in der <a href=\"https://3960.org/webcomponent-hsi/\">Beispiel-Implementation von <code>&lt;horizontal-situation-indicator&gt;</code></a> zu bestaunen – hier sind die Attribute der Komponente mit Animationsmethoden verknüpft, so dass Änderungen an den Attributen bzw. Properties der Web Component zeitgleich die Darstellung des eingeschlossenen SVGs ändert.</p>\n<h2 id=\"wie-sollte-eine-dokumentation-für-eine-web-compontent-aussehen\">Wie sollte eine Dokumentation für eine Web Compontent aussehen?</h2>\n<p>Da eine Web Component im Endeffekt eine Schnittstelle ist, muss es dazu eine <strong>Schnittstellen-Dokumentation</strong> geben. Ohne diese Dokumentation können andere Entwickler, die die Komponente verwenden möchten, nicht zuverlässig wissen, wie die Komponente zu bedienen ist.</p>\n<p>Als Minimum muss eine Dokumentation enthalten:</p>\n<pre><code class=\"language-markdown\"><em>## Properties</em>\n\n| Name           | Type    | Default | Description  |\n| -------------- | ------- | ------- | ------------ |\n| <samp>`heading`</samp>      | <samp>`float`</samp> | <samp>`null`</samp>  | Lorem ipsum… |\n\n<em>## Methods</em>\n\n| Name           | Parameters | Description         |\n| -------------- | ---------- | ------------------- |\n| <samp>`revHeading`</samp>   | none       | Lorem ipsum…        |\n\n<em>## Events</em>\n\n| Name           | Description                      |\n| -------------- | -------------------------------- |\n| <samp>`synchronized`</samp> | Lorem ipsum…                     |\n\n<em>## Styling</em>\n\n<samp>```css\ncomponent-name {\n  --background: color; /* Lorem ipsum… */\n}\n\ncomponent-name::part(part-name) {} /* Lorem ipsum… */\n```</samp>\n</code></pre>\n<h2 id=\"fazit\">Fazit</h2>\n<p>Der fertige <a href=\"https://github.com/fboes/webcomponent-hsi\">Horizontal Situation Indicator als Web Component ist in einem GitHub-Repository</a> gelandet, und einen Blick auf die <a href=\"https://3960.org/webcomponent-hsi/\">fertige Implementation der HSI-Web-Component</a> erlaubt einen interaktiven Blick auf die Zusammenhänge in der Komponente.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=SVG%20und%20Web%20Components&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2020-04-05-svg-web-components%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Web Components erlauben in modernen Browsern, eigene Tags mit beliebig komplexen Verhalten zu definieren. Und wie der Name „Komponente“ schon nahelegt, kann…",
      "date_published": "2020-04-05T18:41:26+02:00",
      "date_modified": "2023-03-05T10:14:07+01:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://journal.3960.org/posts/2020-04-05-svg-web-components/hsi.png",
      "language": "de-DE",
      "image": "https://journal.3960.org/posts/2020-04-05-svg-web-components/hsi.png",
      "tags": [
        "Webdevelop",
        "Web-Components",
        "SVG",
        "Javascript",
        "CSS",
        "Programmierung",
        "Technologie",
        "The Cool",
        "Fliegerei"
      ]
    },
    {
      "id": "user/posts/2019-10-27-event-handling-mit-javascript-ohne-jquery/index.md",
      "url": "https://journal.3960.org/posts/2019-10-27-event-handling-mit-javascript-ohne-jquery/",
      "title": "Event-Handling mit JavaScript – und ohne jQuery",
      "content_html": "<p><img src=\"https://journal.3960.org/posts/2019-10-27-event-handling-mit-javascript-ohne-jquery/event-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Als Web-Entwickler fügen wir im Laufe eines Projektes einer Website eine zumeist nicht unerhebliche Anzahl an JavaScript-Event-Handlern hinzu – sei es mit jQuery oder regulärem JavaScript (<a href=\"http://youmightnotneedjquery.com/\">You Might Not Need jQuery</a>). Abhängig von der gewählten Methode lässt sich damit… die Performance einer Website gründlich ruinieren.</p>\n<p>Aber das muss nicht sein – wie dieser Überblick über die Montage von Event-Handlern in JavaScript / jQuery zeigt.</p>\n<!-- more -->\n<p id=\"more\">Ganz grundsätzlich muss jeder Prozessschritt beim Hinzufügen eines Event-Handlers richtig angewendet werden. Der ganze Vorgang besteht aus drei Schritten:</p>\n<ol>\n<li>Die DOM-Elemente <strong>selektieren</strong>,</li>\n<li>auf dem selektierten DOM-Elementen einen <strong>Event-Typ</strong> beobachten,</li>\n<li>und schließlich bei Auslösen des Events einen <strong>Event-Listener</strong> ausführen.</li>\n</ol>\n<p>In jedem dieser Schritte lässt sich zum Teil massiv optimieren.</p>\n<h2 id=\"kenne-deine-selektoren\">Kenne deine Selektoren</h2>\n<p>Um einen Event-Handler montieren zu können, muss dieser an ein Element angekoppelt werden – in der Regel ist dies ein DOM-Element. Dazu gibt es verschiedene Methoden, DOM-Elemente zu selektieren. Je nach gewählter Methode ist dies mehr oder weniger performant.</p>\n<blockquote><p>Faustformel: Je eindeutiger das Suchmerkmal und je kleiner die Menge der zu durchsuchenden Elemente, desto schneller ist die Suche.</p></blockquote>\n<p>Die <a href=\"https://jsperf.com/getelementbyid-vs-queryselector/304\">schnellste Methode ist dabei die Selektion über ein <code>id</code>-Attribut</a> – die langsamste dagegen die Suche nach einem beliebigen Attribut, im schlimmsten Fall mit der Prüfung, ob dieses Attribut einen bestimmten Wert beinhaltet.</p>\n<div class=\"table-wrapper\"><table>\n<caption id=\"dom-selektions-methoden-sortiert-nach-performance\">DOM-Selektions-Methoden, sortiert nach Performance</caption>\n<thead>\n<tr>\n<th>Methode</th>\n<th>jQuery</th>\n<th>JavaScript</th>\n<th>Neues JavaScript</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>ID</td>\n<td class=\"tag-code\"><code>$(&#39;#x&#39;)</code></td>\n<td><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById\"><code>document.getElementById(&#39;x&#39;)</code></a></td>\n<td><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector\"><code>document.querySelector(&#39;#x&#39;)</code></a></td>\n</tr>\n<tr>\n<td>Klasse</td>\n<td class=\"tag-code\"><code>$(&#39;.x&#39;)</code></td>\n<td><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName\"><code>document.getElementsByClassName(&#39;x&#39;)</code></a></td>\n<td><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll\"><code>document.querySelectorAll(&#39;.x&#39;)</code></a></td>\n</tr>\n<tr>\n<td>Tag</td>\n<td class=\"tag-code\"><code>$(&#39;x&#39;)</code></td>\n<td><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByTagName\"><code>document.getElementsByTagName(&#39;x&#39;)</code></a></td>\n<td><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll\"><code>document.querySelectorAll(&#39;x&#39;)</code></a></td>\n</tr>\n<tr>\n<td>Attribut</td>\n<td class=\"tag-code\"><code>$(&#39;[x]&#39;)</code></td>\n<td>n/a</td>\n<td><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll\"><code>document.querySelectorAll(&#39;[x]&#39;)</code></a></td>\n</tr>\n<tr>\n<td>CSS (s.u.)</td>\n<td class=\"tag-code\"><code>$(&#39;x y&#39;)</code></td>\n<td>n/a</td>\n<td><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll\"><code>document.querySelectorAll(&#39;x y&#39;)</code></a></td>\n</tr>\n</tbody></table></div>\n<p>Sowohl die jQuery-Methode <code>$(…)</code> als auch die JavaScript-Methoden <code>.querySelector()</code> / <code>.querySelectorAll()</code> unterstützen die Auswahl per CSS-Selektor. Damit können auch kompliziertere Suchen im DOM durchgeführt werden, wie z.B. mit <code>nav a</code> das Auffinden aller <code>&lt;a&gt;</code> in einem <code>&lt;nav&gt;</code>. Zum Glück ist die <a href=\"https://caniuse.com/#search=queryselector\">Browser-Unterstützung für <code>.querySelector()</code> / <code>.querySelectorAll()</code></a> inzwischen sehr gut. </p>\n<p>Zu beachten ist, dass die JavaScript-Methoden <code>.querySelector()</code> / <code>.querySelectorAll()</code> je nach Browser <a href=\"https://jsperf.com/getelementbyid-vs-queryselector/304\" title=\"Vergleich der verschiedenen JavaScript-Selektor-Methoden\">um ein Mehrfaches langsamer sind</a> als ihre „einfachen“ Geschwister <code>.getElementById</code>, <code>.getElementsByClassName</code> und <code>.getElementsByTagName</code>. </p>\n<h3 id=\"mengenlehre-selektieren-in-der-selektion\">Mengenlehre: Selektieren in der Selektion</h3>\n<p>Interessanterweise kann jede Methode nicht nur auf das gesamte Dokument angewendet werden, sondern auf eine bereits bestehende Selektion. Damit kann die Suche stark beschleunigt werden.</p>\n<p>In jQuery existiert dafür die <code>.find()</code>-Methode, die analog zu <code>.on()</code> funktioniert…</p>\n<pre><code class=\"language-javascript\"><b>var</b> navigation = $(<kbd>'nav'</kbd>);\n<b>var</b> navLinks = navigation.find(<kbd>'a'</kbd>);\n<b>var</b> navBolds = navigation.find(<kbd>'b'</kbd>);\n</code></pre>\n<p>…in regulärem JavaScript bleiben die Methoden identisch zu den für die Suche im Dokument verfügbaren Methoden:</p>\n<pre><code class=\"language-javascript\"><b>var</b> navigation = <b>document</b>.<b>querySelector</b>(<kbd>'nav'</kbd>);\n<b>var</b> navLinks = navigation.<b>querySelectorAll</b>(<kbd>'a'</kbd>);\n<b>var</b> navBolds = navigation.<b>querySelectorAll</b>(<kbd>'b'</kbd>);\n</code></pre>\n<p>Diese Methode kann sehr hilfreich sein, wenn später sowieso DOM-Manipulation an übergeordneten DOM-Elementen notwendig werden.</p>\n<p>Zu beachten ist bei regulärem JavaScript, dass die Methoden <code>.getElementById()</code> und <code>.querySelector()</code> ein einzelnes <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Element\"><code>Element</code></a> bzw. einen einzelnen <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Node\"><code>Node</code></a> (d.h. <em>ein</em> DOM-Element) zurückgeben, während alle anderen Selektions-Methoden eine <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection\"><code>HTMLCollection</code></a> bzw. <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/NodeList\"><code>NodeList</code></a> zurückgeben, die vereinfacht gesagt Arrays von <code>Node</code>s sind.</p>\n<h2 id=\"event-handler-hinzufügen\">Event-Handler hinzufügen</h2>\n<p>Für das Hinzufügen von Event-Listenern bietet <a href=\"https://api.jquery.com/on/\">jQuery die Methode <code>.on()</code></a>, und <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener\">JavaScript die Methode <code>.addEventListener()</code></a> an. (Wir ignorieren die Methoden zum Hinzufügen von Event-Handlern direkt via HTML-Attribut, da dadurch eine unglückliche Verkettung von Content (HTML) und Verhalten (JavaScript) entsteht.)</p>\n<p>In beiden Fällen verfügt die Selektion über eine Methode, der man nur den <strong>Event-Typ</strong> und den eigentlichen <strong>Event-Listener</strong> übergeben muss.</p>\n<pre><code class=\"language-javascript\">$(<kbd>'nav'</kbd>).on(<kbd>'click'</kbd>, <b>function</b>() {\n  $(<i>this</i>).addClass(<kbd>'active'</kbd>);\n})\n</code></pre>\n<p>Der selbe Aufruf ist in Vanilla-JavaScript etwas mehr Schreibarbeit, aber ansonsten identisch:</p>\n<pre><code class=\"language-javascript\"><b>document</b>.<b>querySelector</b>(<kbd>'nav'</kbd>).<b>addEventListener</b>(<kbd>'click'</kbd>, <b>function</b>(event) {\n  event.target.classList.add(<kbd>'active'</kbd>);\n});\n</code></pre>\n<p>Zu beachten in JavaScript: Event-Listener können nur einem <em>einzelnen</em> DOM-Element hinzugefügt werden – jQuery erlaubt es, am Stück <em>mehreren</em> DOM-Elementen ein und denselben Event-Listener hinzuzufügen.</p>\n<p>In beiden Fällen steht im Event-Listener mit <code>$(this)</code> bzw. <code>event.target</code> das DOM-Element direkt zur Verfügung, auf dem das Event ausgelöst wurde. Voraussetzung ist bei JavaScript, dass der Event-Listener als ersten Parameter eine Variable namens <code>event</code> gesetzt bekommen hat.</p>\n<h3 id=\"sieben-event-typen-auf-einen-streich\">Sieben Event-Typen auf einen Streich…</h3>\n<p>Ein nicht unwahrscheinlicher Anwendungsfall ist, verschiedene Event-Typen mit dem selben Event-Handler bedienen zu wollen. In jQuery kann man an die <code>.on()</code>-Methode eine Liste an verschiedenen Event-Typen übergeben:</p>\n<pre><code class=\"language-javascript\"><u>// Fires on `click` `keyup` `blur`</u>\n$(<kbd>'nav'</kbd>).on(<kbd>'click keyup blur'</kbd>, <b>function</b>() {\n  $(<i>this</i>).addClass(<kbd>'active'</kbd>);\n})\n</code></pre>\n<p>In JavaScript ist ein bisschen mehr Gehirnschmalz notwendig, denn hier müssen wir jeden Event-Listener mit einem einzelnen Aufruf hinzufügen. Das könnte man in einer Schleife tun…</p>\n<pre><code class=\"language-javascript\"><u>// Bad example: Fires on `click` `keyup` `blur`</u>\n[<kbd>'click'</kbd>, <kbd>'keyup'</kbd>, <kbd>'blur'</kbd>].<i>forEach</i>(<b>function</b>(eventType) {\n  <b>document</b>.<b>querySelector</b>(<kbd>'nav'</kbd>).<b>addEventListener</b>(eventType, <b>function</b>(event) {\n    event.target.classList.add(<kbd>'active'</kbd>);\n  });\n});\n</code></pre>\n<p>…und handelt sich auf diese Weise zwei Performance-Killer ein: Einerseits wird in jedem Schleifendurchlauf <code>document.querySelector</code> neu ausgewertet, andererseits wird jedes Mal Speicher für eine neue, anonyme Funktion reserviert. Glücklicherweise kann man beide Konstruktionen aus dem Schleifenkörper herausziehen:</p>\n<pre><code class=\"language-javascript\"><u>// Better example: Fires on `click` `keyup` `blur`</u>\n<b>var</b> eventTarget = <b>document</b>.<b>querySelector</b>(<kbd>'nav'</kbd>);\n<b>var</b> eventListener = <b>function</b>(event) {\n  event.target.classList.add(<kbd>'active'</kbd>);\n};\n\n[<kbd>'click'</kbd>, <kbd>'keyup'</kbd>, <kbd>'blur'</kbd>].<i>forEach</i>(<b>function</b>(eventType) {\n  eventTarget.<b>addEventListener</b>(eventType, eventListener);\n});\n</code></pre>\n<p>Das Array abgerollt sieht dann sogar noch übersichtlicher aus, und zeigt plastisch den Vorteil der vorherigen Deklaration von Event-Ziel und -Listener:</p>\n<pre><code class=\"language-javascript\"><u>// Best example for readability: Fires on `click` `keyup` `blur`</u>\n<b>var</b> eventTarget = <b>document</b>.<b>querySelector</b>(<kbd>'nav'</kbd>);\n<b>var</b> eventListener = <b>function</b>(event) {\n  event.target.classList.add(<kbd>'active'</kbd>);\n};\n\neventTarget.<b>addEventListener</b>(<kbd>'click'</kbd>, eventListener);\neventTarget.<b>addEventListener</b>(<kbd>'keyup'</kbd>, eventListener);\neventTarget.<b>addEventListener</b>(<kbd>'blur'</kbd>, eventListener);\n</code></pre>\n<h3 id=\"weniger-ist-weniger\">Weniger ist… weniger</h3>\n<p>Wenn sich auf einer Seite <em>mehrere</em> DOM-Objekte befinden, die wir mit einem identischen Event-Handler ausstatten wollen, so gibt es in jQuery die folgende Methode.</p>\n<pre><code class=\"language-javascript\"><u>// Add Event Handler to all `.btn`</u>\n$(<kbd>'.btn'</kbd>).on(<kbd>'click'</kbd>, <b>function</b>() {\n  $(<i>this</i>).addClass(<kbd>'active'</kbd>);\n})\n</code></pre>\n<p>Besonders spannend: Bei <code>.on()</code> und <code>.querySelectorAll()</code> können auch <em>mehrere</em> CSS-Selektoren, durch Kommata getrennt, gleichzeitig abgefragt werden. Mit <code>header a, footer a</code> kriegt man eine Liste aller <code>&lt;a&gt;</code> in <code>&lt;header&gt;</code> und <code>&lt;footer&gt;</code> zurück.</p>\n<p>In Vanilla-JavaScript wird das Hinzufügen zu Event-Listenern zu mehreren DOM-Elementen etwas umständlicher, weil ein Event-Handler immer nur <em>einem</em> DOM-Objekt hinzugefügt werden kann. Wenn wir also eine Liste von DOM-Objekten haben, müssen wir jedem DOM-Objekt beim Durchlaufen einer Schleife einen Handler verpassen:</p>\n<pre><code class=\"language-javascript\"><u>// Add Event Handler to all `.btn`</u>\n<b>document</b>.<b>querySelectorAll</b>(<kbd>'.btn'</kbd>).<i>forEach</i>(<b>function</b>(btn) {\n  btn.<b>addEventListener</b>(<kbd>'click'</kbd>, <b>function</b>(event) {\n    event.target.classList.add(<kbd>'active'</kbd>);\n  });\n});\n</code></pre>\n<p>Warum ist das in JavaScript eigentlich so deutlich weniger bequem? Der Grund ist ganz einfach: Diese Konstruktion hat massive Performance-Auswirkungen. Wir fügen damit eine größere Anzahl von Event-Handlern hinzu, die alle das Gleiche tun, aber unterschiedliche DOM-Objekte beobachten müssen. Damit muss der Browser mehr Dinge beobachten. Bei einer 10×10 Zellen umfassenden Tabelle kann <em>ein</em> Event-Listener für eine Tabellenzelle also auf insgesamt 100 Events verteilt werden.</p>\n<p>Für diesen Fall bietet jQuery eine performante Alternative: Statt jedes Element einzeln mit einem Event-Handler auszustatten, wird einfach ein DOM-Objekt ausgewählt, dass im DOM <em>oberhalb</em> der zu beobachtenden DOM-Objekte liegt. Dieses übergeordnete Objekt wird über alle Events informiert, die <em>unterhalb</em> von ihm stattfinden – das sogenannte „Event Bubbling“.</p>\n<p>In jQuery wird der Filter für das eigentliche Event-Ziel als zusätzlicher Parameter von <code>.on()</code> übergeben.</p>\n<pre><code class=\"language-javascript\"><u>// Add Event Handler to `nav`, fire if `.btn` was clicked</u>\n$(<kbd>'nav'</kbd>).on(<kbd>'click'</kbd>, <kbd>'.btn'</kbd>, <b>function</b>() {\n  $(<i>this</i>).addClass(<kbd>'active'</kbd>);\n})\n</code></pre>\n<p>In Vanilla-JavaScript wird das Event-Ziel innerhalb des Event-Listeners gefiltert:</p>\n<pre><code class=\"language-javascript\"><u>// Add Event Handler to `nav`, fire if `.btn` was clicked</u>\n<b>document</b>.<b>querySelector</b>(<kbd>'nav'</kbd>).<b>addEventListener</b>(<kbd>'click'</kbd>, <b>function</b>(event) {\n  <i>if</i> (event.target.<b>matches</b>(<kbd>'.btn'</kbd>)) {\n    event.target.classList.add(<kbd>'active'</kbd>);\n  }\n});\n</code></pre>\n<p>Kollege Malte wies mich noch darauf hin, dass ein Klick natürlich auch auf ein in dem eigentlich interessanten Element liegenden Element stattgefunden haben kann (zum Beispiel der Klick auf ein Bild, das in einem Link liegt, den wir überwachen wollen). Die Lösung dafür ist mit <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Element/closest\"><code>.closest()</code></a> einfach zu bewerkstelligen:</p>\n<pre><code class=\"language-javascript\"><u>// Add Event Handler to `nav`, fire if `.btn` or element inside `.btn` was clicked</u>\n<b>document</b>.<b>querySelector</b>(<kbd>'nav'</kbd>).<b>addEventListener</b>(<kbd>'click'</kbd>, <b>function</b>(event) {\n  <b>var</b> targetBtn = event.target.closest(<kbd>'.btn'</kbd>);\n  <i>if</i> (targetBtn) {\n    targetBtn.classList.add(<kbd>'active'</kbd>);\n  }\n});\n</code></pre>\n<p>Bezogen auf unser Beispiel mit den 100 Tabellenzellen haben wir gerade aus 100 Event-Handlern einen einzigen Event-Handler gemacht. Das spart nicht nur Speicher, sondern erlaubt es auch, auf DOM-Elemente zu reagieren, die beim Hinzufügen des Event-Handlers noch gar nicht im DOM existierten. Wenn zum Beispiel zu unserem <code>&lt;nav&gt;</code>-Element erst nach dem Hinzufügen des Event-Handlers neue <code>&lt;… class=„btn“&gt;</code> zum Beispiel via AJAX hinzugefügt werden, wird unser Event-Handler auf dem <code>&lt;nav&gt;</code> auch diese Elemente mit verarbeiten, da er ja auf <em>alle</em> Elemente unterhalb von ihm reagiert.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Event-Handling%20mit%20JavaScript%20%E2%80%93%20und%20ohne%20jQuery&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2019-10-27-event-handling-mit-javascript-ohne-jquery%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Als Web-Entwickler fügen wir im Laufe eines Projektes einer Website eine zumeist nicht unerhebliche Anzahl an JavaScript-Event-Handlern hinzu – sei es mit…",
      "date_published": "2019-10-27T17:01:02+01:00",
      "date_modified": "2023-01-26T14:39:02+01:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://journal.3960.org/posts/2019-10-27-event-handling-mit-javascript-ohne-jquery/event.png",
      "language": "de-DE",
      "image": "https://journal.3960.org/posts/2019-10-27-event-handling-mit-javascript-ohne-jquery/event.png",
      "tags": [
        "Javascript",
        "Programmierung",
        "Webdevelop"
      ]
    },
    {
      "id": "user/posts/2019-09-13-programmierung-zurueck-zur-werkbank/index.md",
      "url": "https://journal.3960.org/posts/2019-09-13-programmierung-zurueck-zur-werkbank/",
      "title": "Programmierung: Zurück zur Werkbank",
      "content_html": "<p>Wie schon in dem Artikel <a href=\"https://journal.3960.org/posts/2019-04-23-einfachheit-programmierung/\">„Simple &amp; Boring“</a> von Chris Coyier hat auch Bastian Allgeier eine Lanze für Einfachheit in der Programmierung gebrochen. Sein Artikel <a href=\"https://bastianallgeier.com/notes/simplicity-part-2\">„Simplicity (II)“</a> dürfte vielen altgedienten Programmierern aus der Seele sprechen.</p>\n<p>Tatsächlich bemerke ich sowohl in der privaten als auch beruflichen Programmierung den Trend, für mehr Geschwindigkeit ein neues Tool einzusetzen… das kleine Probleme verursacht, die durch ein weiteres Tool gelöst werden müssen… das kleine Probleme verursacht, die durch ein weiteres Tool gelöst werden müssen…</p>\n<figure class=\"blockquote\"><blockquote cite=\"https://bastianallgeier.com/notes/simplicity-part-2\"><p>When everything works, it feels like magic. When something breaks, it&#39;s hell.</p></blockquote>\n<figcaption><a href=\"https://bastianallgeier.com/notes/simplicity-part-2\">Bastian Allgeier, „<cite>Simplicity (II)</cite>“</a></figcaption></figure>\n<p>Der Artikel dreht sich zwar primär darum, was diese Abhängigkeiten gerade für ältere Projekte bedeuten (nämlich, dass Abhängigkeiten nach ein paar Jahren sich nicht wieder auslösen lassen, weil die dafür benötigten Versionen an Tools nicht mehr zur Verfügung stehen), inzwischen bemerke ich aber auch bei aktuellen Projekten die Probleme, die übermäßige Abhängigkeiten für die Entwicklungsgeschwindigkeit bedeuten können, wenn auch nur ein Teil ausfällt.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Programmierung%3A%20Zur%C3%BCck%20zur%20Werkbank&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2019-09-13-programmierung-zurueck-zur-werkbank%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Wie schon in dem Artikel „Simple & Boring“ von Chris Coyier hat auch Bastian Allgeier eine Lanze für Einfachheit in der Programmierung gebrochen. Sein Artikel…",
      "date_published": "2019-09-13T18:23:11+02:00",
      "date_modified": "2019-10-17T18:51:03+02:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "external_url": "https://bastianallgeier.com/notes/simplicity-part-2",
      "tags": [
        "CSS",
        "Javascript",
        "Meinung",
        "PHP",
        "Programmierung",
        "Webdevelop",
        "Geckobar"
      ]
    },
    {
      "id": "user/posts/2019-04-10-bilder-iframes-einfach-mit-lazy-loading-ausstatten/index.md",
      "url": "https://journal.3960.org/posts/2019-04-10-bilder-iframes-einfach-mit-lazy-loading-ausstatten/",
      "title": "Bilder und iFrames einfach mit Lazy-Loading ausstatten",
      "content_html": "<p>Lazy-Loading ist eine beliebte Technik, um die gefühlte Geschwindigkeit einer Internetseite zu erhöhen. Statt alle Bilder einer Webseite schon beim Laden der Seite mitzuladen, werden nur die Bilder geladen, die auch <em>tatsächlich</em> sichtbar sind. Damit verringert man gerade auf langen Seiten die initial geladene Menge an Bildern.</p>\n<p>Bisher hatte das mit etwas Aufwand zu tun, und auf jeden Fall mit JavaScript. Netterweise gibt es inzwischen eine deutlich einfachere Lösung.</p>\n<!-- more -->\n<p id=\"more\">Die bisherigen Lösungen gingen davon aus, dass irgendeine Form von <a href=\"https://appelsiini.net/projects/lazyload/\">LazyLoad-JavaScript</a> auf der Seite montiert wurde, und das HTML eines jeden Bildes abgeändert werden musste:</p>\n<pre><code class=\"language-html\"><u>&lt;!-- Load when visible --&gt;</u>\n&lt;<i>img</i> <var>src</var>=&quot;<kbd>example-lowres.jpg</kbd>&quot;\n  <var>data-src</var>=&quot;<kbd>example-highres.jpg</kbd>&quot; <var>class</var>=&quot;<kbd>lazyload</kbd>&quot;\n  <var>alt</var>=&quot;<kbd></kbd>&quot; <var>width</var>=&quot;<kbd>240</kbd>&quot; <var>height</var>=&quot;<kbd>240</kbd>&quot; /&gt;\n</code></pre>\n<p>Moderne Browser unterstützen aber auch ein Attribut namens <code>loading</code>, dass das Ladeverhalten von <code>&lt;img&gt;</code> und <code>&lt;iframe&gt;</code> steuert, <em>ohne</em> zusätzliches Javascript. Genaue Details kann man einem <a href=\"https://addyosmani.com/blog/lazy-loading/\">Blog-Post des Chrome-Entwicklers Addy Osmani über Lazy-Loading</a> entnehmen, im HTML sieht das aber schlicht und ergreifend wie folgt aus:</p>\n<pre><code class=\"language-html\"><u>&lt;!-- Load when visible --&gt;</u>\n&lt;<i>img</i> <var>src</var>=&quot;<kbd>example.jpg</kbd>&quot; <var>loading</var>=&quot;<kbd>lazy</kbd>&quot; <var>alt</var>=&quot;<kbd></kbd>&quot; <var>width</var>=&quot;<kbd>240</kbd>&quot; <var>height</var>=&quot;<kbd>240</kbd>&quot; /&gt;\n&lt;<i>iframe</i> <var>src</var>=&quot;<kbd>example.html</kbd>&quot; <var>loading</var>=&quot;<kbd>lazy</kbd>&quot;&gt;&lt;/<i>iframe</i>&gt;\n\n<u>&lt;!-- Load as soon as possible --&gt;</u>\n&lt;<i>img</i> <var>src</var>=&quot;<kbd>example.jpg</kbd>&quot; <var>loading</var>=&quot;<kbd>eager</kbd>&quot; <var>alt</var>=&quot;<kbd></kbd>&quot; <var>width</var>=&quot;<kbd>240</kbd>&quot; <var>height</var>=&quot;<kbd>240</kbd>&quot; /&gt;\n&lt;<i>iframe</i> <var>src</var>=&quot;<kbd>example.html</kbd>&quot; <var>loading</var>=&quot;<kbd>eager</kbd>&quot;&gt;&lt;/<i>iframe</i>&gt;\n</code></pre>\n<p>Danach sollte man beim Besuch einer Seite, die mit <a href=\"https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading#Images_and_iframes\" rel=\"nomention\"><code>loading</code>-Attributen</a> versehen ist, beim Öffnen des Inspektors bemerken, dass erst beim Scrollen auf der Seite weiter unten befindliche Bilder bzw. iFrames nachgeladen werden – oder halt, wenn der Browser sich gerade langweilt.</p>\n<p>Damit entfällt in <a href=\"https://caniuse.com/#feat=loading-lazy-attr\" rel=\"nomention\">Google Chrome, Mozilla Firefox, Opera und dem aktuellen Microsoft Edge</a> die Notwendigkeit, JavaScript für Lazy-Loading auf der eigenen Seite zu montieren (so wie in allen Browsern, die zukünftig dieses Feature unterstützen). Zudem können Browser ohne diese Möglichkeit bzw. ohne JavaScript immer noch die selben Inhalte sehen.</p>\n<p>Wichtig ist aber, nicht <em>jedes</em> Element mit Lazy-Loading auszustatten. Demnach scheint es für Browser <a href=\"https://web.dev/lcp-lazy-loading/\">Performance-Probleme für Bilder / iFrames im sichtbaren Bereich zu geben, wenn dort Lazy-Loading verwendet wird</a>. Eine halbwegs smarte Lösung würde also zumindest die erste Ressource auf der Seite nicht mit Lazy-Loading ausstatten.</p>\n<h2 id=\"mit-der-gießkanne-umsetzung-in-nodejs\">Mit der Gießkanne: Umsetzung in NodeJs</h2>\n<p>Um in einem gesamten Content-Block jedes <code>&lt;img&gt;</code> und <code>&lt;iframe&gt;</code> mit dem passenden <code>loading</code>-Attribut zu versehen, reicht folgende kleine Funktion:</p>\n<pre><code class=\"language-javascript\"><b>const</b> lazyloadAttributes = <b>function</b>(html, loading = <kbd>'lazy'</kbd>) {\n  <i>return</i> html.<b>replace</b>(/(&lt;(?:img|iframe) )/g, <kbd>'$1loading=&quot;'</kbd> + loading + <kbd>'&quot; '</kbd>);\n};\n</code></pre>\n<p>Um einen Automatismus für das Ignorieren der ersten X Bilder / iFrames für Lazy-Loading zu erreichen, führen wir den neuen Paramter <code>ignoreLoadingFor</code> ein, der standardmäßig für das erste passende Element <em>keine</em> Ersetzung vornimmt. Wenn wir <code>ignoreLoadingFor</code> auf 2 setzen, werden entsprechend die ersten zwei Elemente ignoriert. Mit etwas Geschick kann man so zum Beispiel auch in Schleifen nur das erste Element in dem ersten Schleifenelement nicht ersetzen.</p>\n<pre><code class=\"language-javascript\"><b>const</b> lazyloadAttributes = <b>function</b>(html, loading = <kbd>'lazy'</kbd>, ignoreLoadingFor = <em>1</em>) {\n  <i>return</i> html.<b>replace</b>(/(&lt;(?:img|iframe) )/g, <b>function</b>(all) {\n    <i>if</i> (ignoreLoadingFor &gt; <em>0</em>) {\n      ignoreLoadingFor --;\n      <i>return</i> all;\n    }\n    <i>return</i> all + <kbd>'loading=&quot;'</kbd> + loading + <kbd>'&quot; '</kbd>;\n  });\n};\n</code></pre>\n<p>Damit werden in einem Block die ersten X Elemente nicht mit Lazy-Loading versehen, alle nachfolgenden Elemente aber schon.</p>\n<h2 id=\"und-in-php\">…und in PHP</h2>\n<p>Gleichsam können in PHP z.B. redaktionelle Texte durch diese Funktion durchgeleitet werden, um überall Lazy-Loading hinzuzufügen:</p>\n<pre><code class=\"language-php\"><b>function</b> lazyloadAttributes(<var>$html</var>, <var>$loading</var> = <kbd>'lazy'</kbd>)\n{\n    <i>return</i> preg_replace(<kbd>'/(&lt;(?:img|iframe) )/is'</kbd>, <kbd>'$1loading=&quot;'</kbd> . <var>$loading</var> . <kbd>'&quot; '</kbd>, <var>$html</var>);\n}\n</code></pre>\n<p>…und mit dem Mechanismus zum Ignorieren der ersten Bilder / iFrames für Lazy-Loading:</p>\n<pre><code class=\"language-php\"><b>function</b> lazyloadAttributes(<b>string</b> <var>$html</var>, <b>string</b> <var>$loading</var> = <kbd>'lazy'</kbd>, <b>int</b> <var>$ignoreLoadingFor</var> = <em>1</em>): <b>string</b>\n{\n    <var>$html</var> = preg_replace(<kbd>'/(&lt;(?:img|iframe) )/is'</kbd>, <kbd>'$1loading=&quot;'</kbd> . <var>$loading</var> . <kbd>'&quot; '</kbd>, <var>$html</var>);\n    <i>if</i> (<var>$ignoreLoadingFor</var> &gt; <em>0</em>) {\n        <var>$html</var> = preg_replace(<kbd>'/(&lt;(?:img|iframe)) loading=&quot;.+?&quot;/is'</kbd>, <kbd>'$1'</kbd>, <var>$html</var>, <var>$ignoreLoadingFor</var>);\n    }\n    <i>return</i> <var>$html</var>;\n}\n</code></pre>\n<p>…wenn auch etwas weniger elegant als in JavaScript.</p>\n<h2 id=\"fazit\">Fazit</h2>\n<p>Eigentlich gibt es wenig Gründe, dass Attribut nicht einzusetzen. Gerade auf länglichen Übersichtsseiten kann das Erlebnis für den Besucher deutlich verbessert werden. Und für Mobilgeräten mit geringer Bandbreite kann der Geschwindigkeitszuwachs immens sein.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Bilder%20und%20iFrames%20einfach%20mit%20Lazy-Loading%20ausstatten&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2019-04-10-bilder-iframes-einfach-mit-lazy-loading-ausstatten%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Lazy-Loading ist eine beliebte Technik, um die gefühlte Geschwindigkeit einer Internetseite zu erhöhen. Statt alle Bilder einer Webseite schon beim Laden der…",
      "date_published": "2019-04-10T19:02:20+02:00",
      "date_modified": "2021-10-21T15:24:22+02:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "Webdevelop",
        "Für Facebook",
        "Javascript",
        "Programmierung"
      ]
    },
    {
      "id": "user/posts/2018-08-15-json-rss-neue-standardisierung/index.md",
      "url": "https://journal.3960.org/posts/2018-08-15-json-rss-neue-standardisierung/",
      "title": "JSON-RSS 2018 – Eine neue Standardisierung",
      "content_html": "<p>Schon vor sehr langer Zeit habe ich <a href=\"http://blog.3960.org/post/8478676503/rss-mit-json\">über JSON-RSS nachgedacht</a>. Dabei ging es mir darum, den <a href=\"http://cyber.harvard.edu/rss/rss.html\">Newsfeed-Standard RSS</a> statt mit XML in dem deutlich leichtgewichtigeren und vor allen Dingen mit JavaScript direkt lesbaren JSON zu erzeugen. Inzwischen habe ich ein paar neue Erkenntnisse zu dem aus 2011 stammenden JSON-RSS, und nenne diesen „Standard“ einfach JSON-RSS 2018.</p>\n<!-- more -->\n<p id=\"more\">Die Grundidee ist es, das XML mittels einer allgemein gültigen Übersetzungsregel in JSON zu übersetzen. Ein paar Besonderheiten gibt es aber, da XML deutlich anders strukturiert ist als JSON. Die (etwas unscharfen) Regeln sehen wie folgt aus:</p>\n<ol>\n<li>Tags</li>\n<li>Das Root-Element des XMLs entfällt, und ist das Root-JSON-Objekt.</li>\n<li>Tags im XML werden zu Objekt-Eigenschaften in JSON</li>\n<li>Tags, die mehrfach vorkommen können, werden zu einer Objekteigenschaft im JSON, deren Name der Mehrzahl des ursprünglichen Tagnamens entspricht, z.B. <code>&lt;item&gt;</code>als <code>items</code>. Die Eigenschaft muss dann ein Array von Objekten enthalten, wobei jedes Objekt die Eigenschaften des ursprünglichen Tags enthalten muss.</li>\n<li>Tag-Inhalte in XML werden zu Strings, Integers oder Numbers in JSON.</li>\n<li>Attribute</li>\n<li>Tags mit einem einzigen Attribut und ohne weiteren Inhalt müssen als Objekt geschrieben werden, bei dem der Tagname die Objekteigenschaft und der Attributwert der Objektwert wird.</li>\n<li>Tags mit einem einzigen, unwichtigen Attribut können ohne Ausgabe des Attributes umgesetzt werden, wie in 1.4 beschrieben.</li>\n<li>Tags mit mehreren Attributen und Kindknoten im XML werden zu gleichberechtigten Kind-Eigenschaften des Objekts in XML. Falls ein Attributname und Sub-Tag identisch sind, wird das Attribut nicht angegeben.</li>\n<li>Tags mit mehreren Attributen und einem Tag-Inhalt werden wie in 1.2 behandelt, wobei der Tag-Inhalt in einer Eigenschaft namens <code>_content</code> ausgegeben wird.</li>\n<li>Namensräume</li>\n<li>Die Definition von Namensräumen via <code>xmlns</code> wird in JSON durch ein <code>xmlns</code>-Objekt gelöst, in dem die einzelnen Namensräume zu Objekteigenschaften werden.</li>\n<li>Tags mit Namensräumen werden in JSON an Stelle des <code>:</code> mit <code>_</code> geschrieben, z.B. <code>&lt;geo:fields&gt;</code> als <code>geo_fields</code>.</li>\n</ol>\n<p>Umgemünzt auf JSON-RSS 2018 ergibt dies beispielhaft die folgende Ausgabe:</p>\n<pre><code class=\"language-json\">{\n  <b>&quot;version&quot;</b>: <kbd>&quot;2.0&quot;</kbd>,\n  <b>&quot;xmlns&quot;</b>: {\n    <b>&quot;atom&quot;</b>: <kbd>&quot;http://www.w3.org/2005/Atom&quot;</kbd>,\n    <b>&quot;content&quot;</b>: <kbd>&quot;http://purl.org/rss/1.0/modules/content/&quot;</kbd>,\n    <b>&quot;georss&quot;</b>: <kbd>&quot;http://www.georss.org/georss&quot;</kbd>,\n    <b>&quot;gml&quot;</b>: <kbd>&quot;http://www.opengis.net/gml&quot;</kbd>\n  },\n  <b>&quot;channel&quot;</b>: {\n    <b>&quot;title&quot;</b>: <kbd>&quot;fboës - Der Blog | Startseite&quot;</kbd>,\n    <b>&quot;link&quot;</b>: <kbd>&quot;https://journal.3960.org/&quot;</kbd>,\n    <b>&quot;description&quot;</b>: <kbd>&quot;Programmierung, Raumfahrt und Kurioses: Der Blog von und mit Frank Boës.&quot;</kbd>,\n    <b>&quot;language&quot;</b>: <kbd>&quot;de-DE&quot;</kbd>,\n    <b>&quot;copyright&quot;</b>: <kbd>&quot;© 2008-2018 Creative Commons BY&quot;</kbd>,\n    <b>&quot;atom_link&quot;</b>: {\n      <b>&quot;href&quot;</b>: <kbd>&quot;https://journal.3960.org/rss.json&quot;</kbd>,\n      <b>&quot;rel&quot;</b>: <kbd>&quot;self&quot;</kbd>,\n      <b>&quot;type&quot;</b>: <kbd>&quot;application/rss+json&quot;</kbd>\n    },\n    <b>&quot;lastBuildDate&quot;</b>: <kbd>&quot;Tue, 14 Aug 2018 21:57:32 +0200&quot;</kbd>,\n    <b>&quot;atom_updated&quot;</b>: <kbd>&quot;2018-08-14T21:57:32+02:00&quot;</kbd>,\n    <b>&quot;generator&quot;</b>: <kbd>&quot;blogophon&quot;</kbd>,\n    <b>&quot;image&quot;</b>: {\n      <b>&quot;url&quot;</b>: <kbd>&quot;https://cdn.3960.org/images/tile-128x128.png&quot;</kbd>,\n      <b>&quot;title&quot;</b>: <kbd>&quot;fboës - Der Blog&quot;</kbd>,\n      <b>&quot;link&quot;</b>: <kbd>&quot;https://journal.3960.org/&quot;</kbd>\n    },\n    <b>&quot;items&quot;</b>: [\n      {\n        <b>&quot;language&quot;</b>: <kbd>&quot;de-DE&quot;</kbd>,\n        <b>&quot;title&quot;</b>: <kbd>&quot;Meine Git-Werkzeugkiste&quot;</kbd>,\n        <b>&quot;description&quot;</b>: <kbd>&quot;Lorem ipsum...&quot;</kbd>,\n        <b>&quot;content_encoded&quot;</b>: <kbd>&quot;&lt;p&gt;More lorem ipsum...&lt;/p&gt;&quot;</kbd>,\n        <b>&quot;link&quot;</b>: <kbd>&quot;https://journal.3960.org/posts/2018-07-19-git-werkzeugkiste/&quot;</kbd>,\n        <b>&quot;pubDate&quot;</b>: <kbd>&quot;Thu, 19 Jul 2018 19:40:55 +0200&quot;</kbd>,\n        <b>&quot;atom_published&quot;</b>: <kbd>&quot;2018-07-19T19:40:55+02:00&quot;</kbd>,\n        <b>&quot;atom_updated&quot;</b>: <kbd>&quot;2018-08-14T08:10:35+02:00&quot;</kbd>,\n        <b>&quot;guid&quot;</b>: <kbd>&quot;user/posts/2018-07-19-git-werkzeugkiste.md&quot;</kbd>,\n        <b>&quot;author&quot;</b>: <kbd>&quot;info@3960.org (Frank Boës)&quot;</kbd>,\n        <b>&quot;categories&quot;</b>: [\n          <kbd>&quot;Programmierung&quot;</kbd>,\n          <kbd>&quot;Webdevelop&quot;</kbd>,\n          <kbd>&quot;Git&quot;</kbd>,\n          <kbd>&quot;Für Tumblr&quot;</kbd>\n        ]\n      }\n    ]\n  }\n}\n</code></pre>\n<p>Dieser Blog produziert ein <a href=\"https://journal.3960.org/rss.json\">lebendes JSON-RSS-Beispiel</a> in dem „JSON-RSS 2018“-Standard.</p>\n<h2 id=\"die-bessere-alternative\">Die bessere Alternative</h2>\n<p>Aber bei aller Begeisterung für das Gedankenexperiment „JSON-RSS“ empfehle ich doch, auf den seit 2017 existierenden und sich immer noch rapide verbreitenden Standard <a href=\"https://jsonfeed.org/\">JSON Feed</a> zu setzen. Neben einer zunehmenden Verbreitung auf Publikations-Seite (u.a. in Wordpress) und einer allgemein akzeptierten Standardisierung gibt es immer mehr Bibliotheken, die das Lesen von JSON-Feeds ermöglichen. </p>\n<p>Darüber hinaus hat JSON-Feed von vorne herein Erweiterungsmöglichkeiten, die ähnlich wie Namespacing in XML funktionieren.</p>\n<h2 id=\"und-rss\">Und RSS?</h2>\n<p>Übrigens ist RSS gerade wieder in aller Munde: Die Diskussion, ob RSS tot sei, hat in letzter Zeit zu neuen Artikeln geführt:</p>\n<ul>\n<li><a href=\"http://scripting.com/2018/08/12/010536.html\">„Silos vs decentralized networks“ von Dave Winer</a></li>\n<li><a href=\"https://www.heise.de/newsticker/meldung/RSS-Relevante-selbstverwaltete-Schlagzeilen-4136678.html\">„RSS: Relevante, selbst verwaltete Schlagzeilen“ von Heise.de</a></li>\n<li><a href=\"https://www.sueddeutsche.de/digital/rss-reader-nachrichten-1.4091261\">„Darum sollten Sie RSS nutzen“ von der Süddeutschen Zeitung</a></li>\n</ul>\n<p>Meiner Meinung nach kann <a href=\"https://journal.3960.org/posts/rss-ist-niemals-tot/\">RSS niemals sterben</a>, wenn man sich die fantastischen Möglichkeiten für seine Nutzung anschaut, die sich auf technischer Ebene eröffnen.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=JSON-RSS%202018%20%E2%80%93%20Eine%20neue%20Standardisierung&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2018-08-15-json-rss-neue-standardisierung%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Schon vor sehr langer Zeit habe ich über JSON-RSS nachgedacht. Dabei ging es mir darum, den Newsfeed-Standard RSS statt mit XML in dem deutlich…",
      "date_published": "2018-08-15T19:50:44+02:00",
      "date_modified": "2020-02-03T12:33:05+01:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "Webdevelop",
        "Blog",
        "Idee",
        "Javascript",
        "Programmierung",
        "Technologie"
      ]
    },
    {
      "id": "user/posts/2017-08-18-php-javascript-uebersetzen.md",
      "url": "https://journal.3960.org/posts/2017-08-18-php-javascript-uebersetzen/",
      "title": "PHP in Javascript übersetzen",
      "content_html": "<p>Schon vor längerer Zeit hatte ich mir darüber Gedanken gemacht, <a href=\"http://blog.3960.org/post/18447181636/php-in-javascript-%C3%BCbersetzen\">wie man PHP-Klassen in Javascript-Objekte übersetzt</a>. Jetzt habe ich einen kleinen Helfer bauen können, der einen Großteil der Arbeit automatisiert.</p>\n<!-- more -->\n<p id=\"more\">Wie man am Beispiel eines kleinen <a href=\"https://3960.org/sandbox/vigenere.php\">Vigenère-Listings</a> sieht, sind die Strukturen von PHP-Klassen und Javascript-Objekten eigentlich sehr gleichartig. Mit der folgenden Javascript kann man einen Großteil der Transformation durchführen, und muss danach nur noch einige Details anpassen.</p>\n<pre><code class=\"language-javascript\"><u>/**\n * Afterwards you will need to:\n * - add `var` in front of uninitialized variables\n * - move default values for function parameters into function body like `param = param || 'default';`\n * - add `,` after every object property (variables &amp; methods)\n * - replace PHP functions\n * @param  {String} php [description]\n * @return {String}     [description]\n */</u>\n<b>var</b> transformPhpToJs = <b>function</b>(php) {\n  <i>return</i> php\n    .<b>replace</b>(/&lt;<samp>\\?</samp>php<samp>\\s</samp>*/g, <kbd>''</kbd>)\n    .<b>replace</b>(/<samp>\\?</samp>&gt;<samp>\\s</samp>*/g, <kbd>''</kbd>)\n    .<b>replace</b>(/(<b>protected</b>|<b>public</b>|<b>private</b>|<b>const</b>) (<samp>\\S</samp>+)<samp>\\s</samp>?=/g, <kbd>'$2 :'</kbd>)\n    .<b>replace</b>(/(<b>protected</b>|<b>public</b>|<b>private</b>|<b>const</b>) /g, <kbd>''</kbd>)\n    .<b>replace</b>(/(<b>function</b>) (<samp>\\S</samp>+)<samp>\\s</samp>?/g, <kbd>'$2: $1'</kbd>)\n    .<b>replace</b>(/(<b>class</b>) (<samp>\\S</samp>+)<samp>\\s</samp>?/g, <kbd>'var $2 = '</kbd>)\n    .<b>replace</b>(/<samp>\\$</samp>/g, <kbd>''</kbd>)\n    .<b>replace</b>(/SELF::/g, <kbd>'this.'</kbd>)\n    .<b>replace</b>(/-&gt;/g, <kbd>'.'</kbd>)\n    .<b>replace</b>(/<b>array</b><samp>\\(</samp>([<samp>\\s</samp><samp>\\S</samp>]*?)<samp>\\)</samp>;/g, <kbd>'[$1];'</kbd>)\n    .<b>replace</b>(/<samp>=&gt;</samp>/g, <kbd>':'</kbd>)\n    .<b>replace</b>(/empty<samp>\\(</samp>(.+?)<samp>\\)</samp>/g, <kbd>'!$1'</kbd>)\n    .<b>replace</b>(/preg_replace<samp>\\(</samp>[<kbd>'&quot;](.+?)['</kbd>&quot;],(.+?),<samp>\\s</samp>?(.+?)<samp>\\s</samp>?<samp>\\)</samp>/g, <kbd>'$3.<b>replace</b>(/$1/g,$2)'</kbd>)\n    .<b>replace</b>(/preg_match<samp>\\(</samp>[<kbd>'&quot;](.+?)['</kbd>&quot;],<samp>\\s</samp>?(.+?)<samp>\\s</samp>?<samp>\\)</samp>/g, <kbd>'$2.<b>match</b>(/$1/)'</kbd>)\n  ;\n}\n</code></pre>\n<p>Diese Funktion könnt ihr z.B. in die <code>F12</code>-Konsole von eurem Browser eingeben, und danach euren PHP-Quelltext <em>im Browser</em> übersetzen lassen. Folgender Aufruf gibt euch das transformierte PHP zurück:</p>\n<pre><code class=\"language-javascript\">transformPhpToJs(<kbd>`INSERT YOUR PHP HERE`</kbd>);\n</code></pre>\n<p>Geht dafür wie folgt vor:</p>\n<ol>\n<li>Öffnet in Google Chrome oder Mozilla Firefox mit <code>F12</code> eure Entwicklerkonsole.</li>\n<li>Wechselt auf den Tab „Konsole“.</li>\n<li>Kopiert o.a. Funktion in die Konsole und drückt <code>Enter</code>.</li>\n<li>Jetzt könnt ihr mit dem o.a. Aufruf euren dort eingefügten PHP-Code in Javascript übersetzen lassen.</li>\n<li>Die Ausgabe könnt ihr nun kopieren und in eine Datei einfügen, um danach die letzten Anpassungen vorzunehmen.</li>\n</ol>\n<p>Natürlich muss das Ergebnis noch nachbearbeitet werden, aber für die meisten Fälle erledigt es zumindest die groben Arbeiten. Danach könnt ihr z.B. daran gehen <a href=\"https://journal.3960.org/posts/node-js-pattern-modul/\">die richtige NodeJS-Modul-Struktur einzubauen</a>.</p>\n<p><strong>Update:</strong> <a href=\"https://github.com/trendfischer\">Mathias</a> hat mich auf <a href=\"http://locutus.io/php/\">Locutus</a> aufmerksam gemacht:</p>\n<blockquote><p>Locutus is a project that seeks to assimilate other languages’ standard libraries to JavaScript.</p></blockquote>\n<p>Hier findet ihr viele PHP-Funktionen als Javascript-Module umgesetzt. Die ganze Bibliothek ist zwar mit Augenzwinkern zu verstehen, für schnelle Erfolgserlebnisse eignet sie sich aber alle Mal.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=PHP%20in%20Javascript%20%C3%BCbersetzen&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2017-08-18-php-javascript-uebersetzen%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Schon vor längerer Zeit hatte ich mir darüber Gedanken gemacht, wie man PHP-Klassen in Javascript-Objekte übersetzt. Jetzt habe ich einen kleinen Helfer bauen…",
      "date_published": "2017-08-18T19:02:00+02:00",
      "date_modified": "2020-01-31T16:55:40+01:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "PHP",
        "Javascript",
        "Programmierung",
        "NodeJS",
        "Für Tumblr"
      ]
    },
    {
      "id": "user/posts/backreferences-regulaeren-ausdruecken.md",
      "url": "https://journal.3960.org/posts/backreferences-regulaeren-ausdruecken/",
      "title": "Backreferences in regulären Ausdrücken",
      "content_html": "<p>Niemand auf diesem Planeten wird jemals ernsthaft behaupten, ein Meister der regulären Ausdrücke zu sein. Selbst nach jahrelangem Einsatz findet man immer wieder neue Techniken. Meine jüngste Erkenntnis: Backreferences in regulären Ausdrücken.</p>\n<!-- more -->\n<p id=\"more\">Im <a href=\"https://github.com/fboes/blogophon\">Blogophon</a> (der Blog-Software zu dieser Seite) gibt es eine größere Bibliothek, die HTML in noch viel schöneres HTML verwandeln soll. Dazu wiederum werden Unmengen an regulären Ausdrücken verwendet.</p>\n<p>Ein spezieller Ausdruck ist dafür zuständig, alle Vorkommen von <code>„…“</code> und <code>‚…‘</code> in ein <code>&lt;kbd&gt;</code>-Tag einzuwickeln. Der erste Versuch sah wie folgt aus:</p>\n<pre><code>.replace(/(&amp;quot;|&amp;#39;|&#39;)(.*?)(&amp;quot;|&amp;#39;|&#39;)/g, &#39;&lt;kbd&gt;$0&lt;/kbd&gt;&#39;)\n</code></pre>\n<p>Dummerweise ist der Ausdruck falsch, denn so findet er auch <code>„…‚</code> und <code>‘…“</code>. Die Lösung für dieses Dilemma sind dann separate Ersetzungen:</p>\n<pre><code>.replace(/(&amp;quot;)(.*?)(&amp;quot;)/g, &#39;&lt;kbd&gt;$0&lt;/kbd&gt;&#39;)\n.replace(/(&amp;#39;)(.*?)(&amp;#39;)/g, &#39;&lt;kbd&gt;$0&lt;/kbd&gt;&#39;)\n.replace(/(&#39;)(.*?)(&#39;)/g, &#39;&lt;kbd&gt;$0&lt;/kbd&gt;&#39;)\n</code></pre>\n<p>Wie man unschwer erkennt, ist das ein deutlicher Verstoß gegen das <a href=\"https://de.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself\">DRY-Prinzip</a>, aber augenscheinlich notwendig, um eine Ersetzung nur durchzuführen, wenn Anfangs- und Endzeichen identisch sind.</p>\n<h2 id=\"lesen-bildet-backreferences\">Lesen bildet: Backreferences</h2>\n<p>Beim Lesen des Buches <a href=\"https://www.manning.com/books/secrets-of-the-javascript-ninja-second-edition\">„Secrets of the Javascript Ninja“</a> von John Resig, Bear Bibeault und Josip Maras bin ich neben vielen anderen erhellenden Kapiteln auch auf das Kapitel über reguläre Ausdrücke gestoßen. Und hier werden im Nebensatz Backreferences erläutert.</p>\n<p>Mit einer Backreference kann man exakt den Inhalt einer einmal in einem regulären Ausdruck gefundenen Gruppe nochmals finden. Um z.B. den Inhalt von Gruppe 1 nochmal zu finden, verwendet man <code>\\1</code>, von Gruppe zwei dementsprechend <code>\\2</code>.</p>\n<p>Das obige Beispiel ist damit korrekt:</p>\n<pre><code>.replace(/(&amp;quot;|&amp;#39;|&#39;)(.*?)(\\1)/g, &#39;&lt;kbd&gt;$0&lt;/kbd&gt;&#39;)\n</code></pre><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Backreferences%20in%20regul%C3%A4ren%20Ausdr%C3%BCcken&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2Fbackreferences-regulaeren-ausdruecken%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Niemand auf diesem Planeten wird jemals ernsthaft behaupten, ein Meister der regulären Ausdrücke zu sein. Selbst nach jahrelangem Einsatz findet man immer…",
      "date_published": "2017-02-10T19:32:09+01:00",
      "date_modified": "2017-10-05T10:30:09+02:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "Programmierung",
        "Javascript",
        "Für Tumblr",
        "NodeJs",
        "Performance"
      ]
    },
    {
      "id": "user/posts/node-js-pattern-modul.md",
      "url": "https://journal.3960.org/posts/node-js-pattern-modul/",
      "title": "Node.js Pattern: Das Modul",
      "content_html": "<p>Nach mehrmonatigen Experimentieren habe ich für mich endlich das gelungene Pattern gefunden, mit dem ich in Node.js meine Module baue.</p>\n<!-- more -->\n<pre id=\"more\"><code class=\"language-javascript\"><u>// module-name.js</u>\n\n<kbd>'use strict'</kbd>;\n\n<b>const</b> someNodeModule = <i>require</i>(<kbd>'some-node-module'</kbd>);\n<b>const</b> someOwnModule  = <i>require</i>(<kbd>'./some-own-module'</kbd>);\n\n<u>/**\n * [modulName description]\n * @constructor\n */</u>\n<b>const</b> modulName = <b>function</b> (config) {\n  <b>const</b> <b>external</b> = {};\n  <b>const</b> <b>internal</b> = {};\n\n  <u>/**\n   * [someVariable description]\n   * @type {[type]}\n   */</u>\n  <b>external</b>.someVariable = <samp>null</samp>;\n\n  <u>/**\n   * [someOtherVariable description]\n   * @type {[type]}\n   */</u>\n  <b>internal</b>.someOtherVariable = <samp>null</samp>;\n\n  <u>/**\n   * [someFunction description]\n   * @params {[type]} [name] [description]\n   * @return {[type]} [description]\n   */</u>\n  <b>external</b>.someFunction = <b>function</b> () {\n  };\n\n  <u>/**\n   * [someOtherFunction description]\n   * @params {[type]} [name] [description]\n   * @return {[type]} [description]\n   */</u>\n  <b>internal</b>.someOtherFunction = <b>function</b> () {\n  };\n\n  <i>return</i> <b>external</b>;\n};\n\nmodule.exports = modulName;\n</code></pre>\n<p>Der Aufruf ist dann ganz einfach:</p>\n<pre><code class=\"language-javascript\">\n<b>const</b> moduleName  = <i>require</i>(<kbd>'./moduleName'</kbd>);\n\nmoduleName({});\n</code></pre>\n<p>Eine Erweiterung des Objekts um neue Eigenschaften geht ebenfalls flott von der Hand:</p>\n<pre><code class=\"language-javascript\"><u>// some-other-module-name.js</u>\n\n<kbd>'use strict'</kbd>;\n\n<b>const</b> moduleName  = <i>require</i>(<kbd>'./moduleName'</kbd>);\n\n<u>/**\n * [someOtherModuleName description]\n * @constructor\n */</u>\n<b>const</b> someOtherModuleName = <b>function</b> (config) {\n  <b>const</b> <b>external</b> = moduleName(config);\n  <b>const</b> <b>internal</b> = {};\n\n  <u>// Add extra variables and methods here</u>\n\n  <i>return</i> <b>external</b>;\n};\n\nmodule.exports = modulName;\n</code></pre>\n<p>Bei dieser Konstruktion sind folgende Punkte erfüllt:</p>\n<ul>\n<li>Das Modul hat <code>public</code> und <code>private</code> Variablen und Methoden. Diese sind hier als <code>external</code> und <code>internal</code> bezeichnet, da <code>public</code> und <code>private</code> reservierte Wörter sind.</li>\n<li>Aufruf des neuen Objektes ohne <code>new</code>.</li>\n<li>Erweiterbarkeit ohne große Verrenkungen.</li>\n</ul>\n<p>Natürlich gibt es noch unzählige andere Methoden. Aber bei dieser Methode finde ich die Mischung aus Übersichtlichkeit und Erweiterbarkeit für meine Belange am Besten.</p>\n<p><strong>Update</strong>: Für ECMAScript 6 wurden alle Variablendeklarationen mit <code>const</code> durchgeführt. Wenn die Deklarationen abwärtskompatibel sein sollen, können diese mit <code>var</code> gemacht werden.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Node.js%20Pattern%3A%20Das%20Modul&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2Fnode-js-pattern-modul%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Nach mehrmonatigen Experimentieren habe ich für mich endlich das gelungene Pattern gefunden, mit dem ich in Node.js meine Module baue.",
      "date_published": "2016-10-07T08:34:14+02:00",
      "date_modified": "2020-01-31T16:53:30+01:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "NodeJs",
        "Anleitung",
        "Javascript",
        "Programmierung"
      ]
    },
    {
      "id": "user/posts/dinge-man-tun-kann-wenn-man-nicht-mehr-fuer-den-internet-explorer-8-entwickeln-muss.md",
      "url": "https://journal.3960.org/posts/dinge-man-tun-kann-wenn-man-nicht-mehr-fuer-den-internet-explorer-8-entwickeln-muss/",
      "title": "Dinge, die man tun kann, wenn man nicht mehr für den Internet Explorer 8 entwickeln muss",
      "content_html": "<p>Da der <a href=\"http://gs.statcounter.com/#desktop-browser_version_partially_combined-ww-monthly-201606-201608-bar\">Microsoft Internet Explorer 8 global inzwischen eine Verbreitung unter 2% zu haben scheint</a>, kann man bei vielen Projekten inzwischen so programmieren, als ob es ihn nicht mehr gäbe. Das erlaubt den Zugriff auf viele neue, wichtige Browser-Features, und erspart Unmengen an Work-Arounds und Polyfills.</p>\n<!-- more -->\n<p id=\"more\">Meine Quelle für das <a href=\"http://caniuse.com/\">Vorhandensein von bestimmten Features in bestimmten Browsern ist „Can I Use“</a>. Dementsprechend kann man schön mit einem schnellen <a href=\"http://caniuse.com/#compare=ie+8,chrome+52\">Vergleich zwischen dem Internet Explorer 8 und z.B. dem aktuellen Chrome</a> feststellen, welche Features man nun vollkommen bedenkenlos benutzen darf.</p>\n<h3 id=\"css\">CSS</h3>\n<ul>\n<li><a href=\"http://caniuse.com/#feat=html5semantic\">CSS an neuen HTML5-Elementen</a></li>\n<li><a href=\"http://caniuse.com/#feat=css-mediaqueries\">Media-Queries</a>!</li>\n<li><a href=\"http://caniuse.com/#feat=css-opacity\">CSS3 Opacity</a>: Die uneingeschränkte Verwendung von PNGs.</li>\n<li><a href=\"http://caniuse.com/#feat=css3-colors\">CSS3 Farben</a>, wie z.B. RGBA.</li>\n<li><a href=\"http://caniuse.com/#feat=css-boxshadow\">CSS3 Box-Shadow</a></li>\n<li><a href=\"http://caniuse.com/#feat=viewport-units\">Verwendung von <code>vw</code> und <code>vh</code></a> als Breiten- und Höhenangaben relativ zum Browserfenster.</li>\n<li><a href=\"http://caniuse.com/#feat=border-radius\">Runde Ecken mit CSS</a></li>\n<li><a href=\"http://caniuse.com/#feat=transforms2d\">2d-Transformationen</a></li>\n<li><a href=\"http://caniuse.com/#feat=calc\">Das Berechnen von CSS-Werten mittels <code>calc</code></a></li>\n</ul>\n<h3 id=\"javascript\">Javascript</h3>\n<ul>\n<li><a href=\"http://caniuse.com/#feat=es5\">ECMAScript 5</a> mit vielen schlauen Methoden, z.B. zum Durchlaufen von Arrays.</li>\n<li><a href=\"http://caniuse.com/#feat=queryselector\">.querySelector()</a> &amp; <a href=\"http://caniuse.com/#feat=matchesselector\">.matches()</a>: Der Selektieren von DOM-Elemente in Javascript mit CSS-Selektoren, wie schon lange von jQuery bekannt.</li>\n<li><a href=\"http://caniuse.com/#feat=getelementsbyclassname\">.getElementsByClassName()</a></li>\n<li><a href=\"http://caniuse.com/#feat=addeventlistener\">EventTarget.addEventListener()</a></li>\n</ul>\n<h3 id=\"html\">HTML</h3>\n<ul>\n<li><a href=\"http://caniuse.com/#feat=video\">Video-Tag</a> inkl. <a href=\"http://caniuse.com/#feat=mpeg4\">MPEG-4-Unterstützung</a></li>\n<li><a href=\"http://caniuse.com/#feat=svg\">SVG</a></li>\n</ul>\n<h3 id=\"was-immer-noch-nicht-funktioniert-wegen-dem-ie9\">Was immer noch nicht funktioniert wegen dem IE9</h3>\n<p>Leider haben wir nun Ärgernisse wie den Internet Explorer 9 vor der Nase. Deswegen funktionieren folgende Features nach wie vor nicht:</p>\n<ul>\n<li><a href=\"http://caniuse.com/#feat=css-animation\">CSS Animationen</a></li>\n<li><a href=\"http://caniuse.com/#feat=css-textshadow\">CSS3 Text-Shadow</a></li>\n<li><a href=\"http://caniuse.com/#feat=css-gradients\">Gradienten bzw. Farbverläufe</a></li>\n<li><a href=\"http://caniuse.com/#feat=css-transitions\">CSS-Transitionen</a></li>\n<li><a href=\"http://caniuse.com/#feat=input-file-accept\"><code>accept</code>-Attribut an Input-Feldern</a></li>\n<li><a href=\"http://caniuse.com/#feat=input-email-tel-url\">Spezielle Input-Typen wie z.B. „E-Mail“</a></li>\n<li><a href=\"http://caniuse.com/#feat=input-placeholder\">Placeholder für leere Eingabefelder</a></li>\n<li><a href=\"http://caniuse.com/#feat=flexbox\">Flexboxen</a></li>\n</ul><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Dinge%2C%20die%20man%20tun%20kann%2C%20wenn%20man%20nicht%20mehr%20f%C3%BCr%20den%20Internet%20Explorer%208%20entwickeln%20muss&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2Fdinge-man-tun-kann-wenn-man-nicht-mehr-fuer-den-internet-explorer-8-entwickeln-muss%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Da der Microsoft Internet Explorer 8 global inzwischen eine Verbreitung unter 2% zu haben scheint, kann man bei vielen Projekten inzwischen so programmieren,…",
      "date_published": "2016-09-14T19:03:36+02:00",
      "date_modified": "2017-10-05T10:29:19+02:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "Anleitung",
        "CSS",
        "Webdevelop",
        "Javascript"
      ]
    },
    {
      "id": "user/posts/nodejs-pattern-promise.md",
      "url": "https://journal.3960.org/posts/nodejs-pattern-promise/",
      "title": "Node.js Pattern: Promise()",
      "content_html": "<p>Gerade für PHP-Programmierer ist die eventbasierte Programmierung in Javascript bzw. node.js eine Umstellung im Denken. Dabei sind Events die besondere Stärke, wenn man sie denn richtig anfasst.</p>\n<p>Dabei „horcht“ ein Stückchen Programmierung mittels eines Event-Listeners, ob ein bestimmtes Event ausgelöst wird. Wenn man das Javascript von Browsern noch kennt, sind Event-Listener eigentlich schon bekannt. Zumindest in node.js gibt es aber eine deutlich kompaktere Schreibweise für bestimmte Vorgänge.</p>\n<!-- more -->\n<p id=\"more\">Mein Anwendungsfall: Ich warte auf eine bestimmte Anzahl von Events, und löse mein eigenes Event aus, wenn alle meine Sub-Events erfolgreich abgeschlossen haben. Bisher sah das so aus (schon mit der Kraft von <a href=\"https://journal.3960.org/posts/nodejs-pattern-array-foreach/\"><code>Array.forEach</code></a>):</p>\n<pre><code class=\"language-javascript\">  <b>var</b> files = [<kbd>'a.txt'</kbd>,<kbd>'b.txt'</kbd>,<kbd>'c.txt'</kbd>];\n  <b>var</b> processed = <em>0</em>;\n\n  <b>var</b> checkProcessed  = <b>function</b>(err) {\n    <i>if</i> (err) {\n      console.log(<kbd>&quot;Error!&quot;</kbd>);\n    }\n    <i>if</i> (++processed === files.<i>length</i>) {\n      console.log(<kbd>&quot;Done!&quot;</kbd>);\n    }\n  };\n\n  files.<i>forEach</i>(<b>function</b>(file) {\n    fs.writeFile(file, <kbd>&quot;Test test test&quot;</kbd>, checkProcessed);\n  });\n</code></pre>\n<p>Seit geraumer Zeit bzw. Node 4.x gibt es nämlich <a href=\"https://www.promisejs.org/\"><code>Promise</code></a>. Der eigentlich Aufbau eines Promise wird an vielen Stellen hinlänglich erklärt. Zusammen mit <a href=\"https://journal.3960.org/posts/nodejs-pattern-array-map/\"><code>Array.map</code></a> erlaubt das folgende Konstruktion:</p>\n<pre><code class=\"language-javascript\">  <b>var</b> files = [<kbd>'a.txt'</kbd>,<kbd>'b.txt'</kbd>,<kbd>'c.txt'</kbd>];\n\n  <u>// Making promises</u>\n  <b>var</b> promises = files.<i>map</i>(<b>function</b>(file) {\n    <i>return</i> <i>new</i> Promise (\n      <b>function</b> (resolve, reject) {\n        fs.writeFile(file, <kbd>&quot;Test test test&quot;</kbd>, <b>function</b>(err) {\n          <i>if</i> (err) {\n            reject;\n          } <i>else</i> {\n            resolve;\n          }\n        });\n      }\n    );\n  });\n\n  <u>// Checking promises</u>\n  Promise\n    .all(promises)\n    .<i>then</i>(<b>function</b>() {\n      console.log(<kbd>&quot;Done!&quot;</kbd>);\n    })\n    .<i>catch</i>(<b>function</b>(err) {\n      console.log(<kbd>&quot;Error!&quot;</kbd>);     \n    })\n  ;\n};\n</code></pre>\n<p>Noch einfacher wird dieses Beispiel, wenn die Methode, auf die man wartet, nicht ein Event, sondern direkt ein Promise zurückgibt:</p>\n<pre><code class=\"language-javascript\">  <b>var</b> fsp   = <i>require</i>(<kbd>'fs-promise'</kbd>);\n  <b>var</b> files = [<kbd>'a.txt'</kbd>,<kbd>'b.txt'</kbd>,<kbd>'c.txt'</kbd>];\n\n  <u>// Making promises</u>\n  <b>var</b> promises = files.<i>map</i>(<b>function</b>(file) {\n    <i>return</i> fsp.writeFile(file, <kbd>&quot;Test test test&quot;</kbd>);\n  });\n\n  <u>// Checking promises</u>\n  Promise\n    .all(promises)\n    .<i>then</i>(<b>function</b>() {\n      console.log(<kbd>&quot;Done!&quot;</kbd>);\n    })\n    .<i>catch</i>(<b>function</b>(err) {\n      console.log(<kbd>&quot;Error!&quot;</kbd>);     \n    })\n  ;\n};\n</code></pre>\n<p>Viele node.js-Bibliotheken, die vormals mit Events funktionierten, gibt es inzwischen auch als Variante mit Promises. So existiert z.B. das Projekt „<a href=\"https://github.com/normalize/mz\">Modernize node.js</a>“, dass viele Events in Promises umwandelt.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Node.js%20Pattern%3A%20Promise()&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2Fnodejs-pattern-promise%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Gerade für PHP-Programmierer ist die eventbasierte Programmierung in Javascript bzw. node.js eine Umstellung im Denken. Dabei sind Events die besondere Stärke…",
      "date_published": "2016-08-28T19:05:24+02:00",
      "date_modified": "2020-01-31T16:53:25+01:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "NodeJs",
        "Anleitung",
        "Javascript",
        "Programmierung"
      ]
    },
    {
      "id": "user/posts/nodejs-pattern-array-map.md",
      "url": "https://journal.3960.org/posts/nodejs-pattern-array-map/",
      "title": "Node.js Pattern: Array.map()",
      "content_html": "<p>Wenn man jahrelang Javascript nur für Browser programmiert hat (bzw. für ältere Browser), beginnt man unweigerlich in node.js mit den selben Konstruktionen. Inzwischen gibt es aber eine Handvoll Methoden, die das Leben deutlich einfacher machen – und das nicht nur in node.js, sondern auch in neueren Browsern.</p>\n<p>Heute: Wie erzeugt man aus einem Array ein <em>neues</em> Array?</p>\n<!-- more -->\n<p id=\"more\">Vormals sah das Erzeugen eines neuen Arrays aus einem bereits existierenden Array wie folgt aus:</p>\n<pre><code class=\"language-javascript\">\n<b>var</b> a = [<kbd>'a'</kbd>,<kbd>'b'</kbd>,<kbd>'c'</kbd>];\n<b>var</b> b = [];\n\n<i>for</i> (<b>var</b> i = <em>0</em>; i &lt; a.<i>length</i>; i ++) {\n  <b>var</b> el = a[i];\n  <u>// do something with el</u>\n  b.push(el);\n}\n</code></pre>\n<p>Die Methode <a href=\"https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/map\"><code>Array.map()</code></a> erlaubt folgende Konstruktion:</p>\n<pre><code class=\"language-javascript\"><b>var</b> a = [<kbd>'a'</kbd>,<kbd>'b'</kbd>,<kbd>'c'</kbd>];\n\n<b>var</b> b = a.<i>map</i>(<b>function</b>(el, i) {\n  <u>// do something with el</u>\n  <i>return</i> el;\n});\n</code></pre>\n<p>Damit eigenet sich die Funktion z.B. zum Filtern oder Umformen von Arrays.</p>\n<p><code>map</code> ist Bestandteil von ECMAScript 5.1 bzw. Javascript 1.6. Damit ist <a href=\"http://caniuse.com/#search=foreach\"><code>Array.map</code> eigentlich in jedem Browser verfügbar (bis auf den Internet Explorer 8 und ältere)</a>.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Node.js%20Pattern%3A%20Array.map()&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2Fnodejs-pattern-array-map%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Wenn man jahrelang Javascript nur für Browser programmiert hat (bzw. für ältere Browser), beginnt man unweigerlich in node.js mit den selben Konstruktionen.…",
      "date_published": "2016-08-27T19:48:24+02:00",
      "date_modified": "2020-01-31T16:53:35+01:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "NodeJs",
        "Anleitung",
        "Javascript",
        "Programmierung"
      ]
    },
    {
      "id": "user/posts/nodejs-pattern-array-foreach.md",
      "url": "https://journal.3960.org/posts/nodejs-pattern-array-foreach/",
      "title": "Node.js Pattern: Array.forEach()",
      "content_html": "<p>Wenn man jahrelang Javascript nur für Browser programmiert hat (bzw. für ältere Browser), beginnt man unweigerlich in node.js mit den selben Konstruktionen. Inzwischen gibt es aber eine Handvoll Methoden, die das Leben deutlich einfacher machen – und das nicht nur in node.js, sondern auch in neueren Browsern.</p>\n<p>Heute: Wie durchläuft man am Einfachsten ein komplettes Array?</p>\n<!-- more -->\n<p id=\"more\">Vormals sah das Durchlaufen eines Array wie folgt aus:</p>\n<pre><code class=\"language-javascript\">\n<b>var</b> a = [<kbd>'a'</kbd>,<kbd>'b'</kbd>,<kbd>'c'</kbd>];\n\n<i>for</i> (<b>var</b> i = <em>0</em>; i &lt; a.<i>length</i>; i ++) {\n  <b>var</b> el = a[i];\n  <u>// do something with el</u>\n}\n</code></pre>\n<p>Die Methode <a href=\"https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach\"><code>Array.forEach()</code></a> erlaubt folgende Konstruktion:</p>\n<pre><code class=\"language-javascript\"><b>var</b> a = [<kbd>'a'</kbd>,<kbd>'b'</kbd>,<kbd>'c'</kbd>];\n\na.<i>forEach</i>(<b>function</b>(el, i) {\n  <u>// do something with el</u>\n});\n</code></pre>\n<p><code>forEach</code> ist Bestandteil von ECMAScript 5.1 bzw. Javascript 1.6. Damit ist <a href=\"http://caniuse.com/#search=foreach\"><code>Array.forEach</code> eigentlich in jedem Browser verfügbar (bis auf den Internet Explorer 8 und ältere)</a>.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Node.js%20Pattern%3A%20Array.forEach()&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2Fnodejs-pattern-array-foreach%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
      "summary": "Wenn man jahrelang Javascript nur für Browser programmiert hat (bzw. für ältere Browser), beginnt man unweigerlich in node.js mit den selben Konstruktionen.…",
      "date_published": "2016-08-26T18:48:24+02:00",
      "date_modified": "2020-01-31T16:53:44+01:00",
      "author": {
        "name": "Frank Boës",
        "url": "mailto:info@3960.org",
        "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
      },
      "authors": [
        {
          "name": "Frank Boës",
          "url": "mailto:info@3960.org",
          "avatar": "https://www.gravatar.com/avatar/71fcf51cf2ae9acdd54182d3e367ceca"
        }
      ],
      "banner_image": "https://cdn.3960.org/favicon-192x192.png",
      "language": "de-DE",
      "image": "https://cdn.3960.org/favicon-192x192.png",
      "tags": [
        "NodeJs",
        "Anleitung",
        "Javascript",
        "Programmierung"
      ]
    }
  ]
}