{
  "version": "2.0",
  "xmlns": {
    "atom": "http://www.w3.org/2005/Atom",
    "content": "http://purl.org/rss/1.0/modules/content/",
    "georss": "http://www.georss.org/georss",
    "gml": "http://www.opengis.net/gml"
  },
  "channel": {
    "title": "fboës - Der Blog | Programmierung",
    "link": "https://journal.3960.org/",
    "description": "Programmierung, Luft- & Raumfahrt, Kurioses: Der Blog von und mit Frank Boës.",
    "language": "de-DE",
    "copyright": "© 2008-2023 Creative Commons BY",
    "atom_link": {
      "href": "https://journal.3960.org/category/programmierung/rss.json",
      "rel": "self",
      "type": "application/rss+json"
    },
    "lastBuildDate": "Tue, 24 Mar 2026 20:00:22 +0100",
    "atom_updated": "2026-03-24T20:00:22+01:00",
    "generator": "blogophon",
    "image": {
      "url": "https://cdn.3960.org/images/tile-128x128.png",
      "title": "fboës - Der Blog",
      "link": "https://journal.3960.org/"
    },
    "items": [
      {
        "title": "I did not know Node.js could do that",
        "description": "<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>",
        "content_encoded": "<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=\"\" />",
        "link": "https://journal.3960.org/posts/2026-03-20-did-not-know-node-js-could-do-that/",
        "pubDate": "Fri, 20 Mar 2026 18:35:59 +0100",
        "atom_published": "2026-03-20T18:35:59+01:00",
        "atom_updated": "2026-03-21T14:22:30+01:00",
        "guid": "user/posts/2026-03-20-did-not-know-node-js-could-do-that/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Javascript",
          "Programmierung",
          "Technologie",
          "Webdevelop"
        ]
      },
      {
        "title": "Der kleinste JavaScript-Unit-Tester der Welt",
        "description": "<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>",
        "content_encoded": "<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=\"\" />",
        "link": "https://journal.3960.org/posts/2024-02-03-kleinste-javascript-unit-tester-welt/",
        "pubDate": "Sat, 03 Feb 2024 18:14:32 +0100",
        "atom_published": "2024-02-03T18:14:32+01:00",
        "atom_updated": "2026-03-09T11:10:37+01:00",
        "guid": "user/posts/2024-02-03-kleinste-javascript-unit-tester-welt/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Javascript",
          "Programmierung",
          "Webdevelop"
        ]
      },
      {
        "title": "Wie funktioniert der Tolino-Webbrowser?",
        "description": "<p>In den eBook-Readern von Tolino steckt auch ein Webbrowser. Welche Fähigkeiten hat der eigentlich? Und lohnt es sich, eigens für eBook-Reader optimierte Webseiten zu bauen?</p>",
        "content_encoded": "<p>In den eBook-Readern von Tolino steckt auch ein Webbrowser. Welche Fähigkeiten hat der eigentlich? Und lohnt es sich, eigens für eBook-Reader optimierte Webseiten zu bauen?</p>\n<!-- more -->\n<h2 id=\"more\">User Agent String für Tolino Webbrowser</h2>\n<p>Jeder Browser strahlt seine eigene Browser-Kennung ab: Den <i>User Agent String</i>. Er verrät nicht nur, welche Browser-Software in welcher Version im Einsatz ist, sondern auch meist das Betriebssystem.</p>\n<p>Für einen Tolino Shine sieht das wie folgt aus:</p>\n<pre><code>Mozilla/5.0 (Linux; Android 8.1.0; de-; tolino shine 4/16.1.0) AppleWebKit/537.36 (KHTML, like Gecko) Verions/4.0 Chrome/61.0.0.0 Mobile Mobile Safari/537.36\n</code></pre>\n<p>Damit lüften sich mehrere Geheimnisse: Nicht nur ist auf dem Tolino ein Android 8 als Betriebssystem im Einsatz, sondern der Browser ist auch ein veralteter Google Chrome 61. (Zum Zeitpunkt der Untersuchung war gerade der Google Chrome 121 aktuell.) Damit beherrscht er leider nicht alle modernen Tricks von CSS und ECMAScript, ist aber für Brot-und-Butter-Websites immer noch eine sehr gute Wahl.</p>\n<h2 id=\"besondere-header\">Besondere Header</h2>\n<p>Mein Tolino Webbrowser hat eine interessante Eigenheit: Er strahlt an jeden besuchten Web-Server einen eigenen Header ab:</p>\n<pre><code>X-REQUESTED-WITH: de.telekom.epub\n</code></pre>\n<p>Tatsächlich gibt es möglicherweise historische Gründe für diesen Header, uns hilft dies aber sonst nicht weiter. Viel spannender ist für uns…</p>\n<h2 id=\"screen-properties-für-den-tolino-webbrowser\">Screen Properties für den Tolino Webbrowser</h2>\n<p>Die physische Bildschirmgröße des Tolino Shine 4 ist 1072×1448 Pixel. Im Web wird dabei ein Device Pixel Ratio von 1.875 angenommen.</p>\n<h2 id=\"media-queries-für-den-tolino-webbrowser\">Media Queries für den Tolino Webbrowser</h2>\n<p>Nun haben wir auf einem eBook-Reader zwei Herausforderungen, die bei der Gestaltung von Websites berücksichtigt werden müssen:</p>\n<ul>\n<li>eBook-Reader haben nur wenige Graustufen zur Verfügung (beim Tolino Shine 16 Graustufen).</li>\n<li><a href=\"https://de.wikipedia.org/wiki/Elektronisches_Papier\">ePaper-Displays</a> eignen sich wenig für Animationen.</li>\n<li>eBook-Reader werden höchstens per Touch bedient.</li>\n</ul>\n<p>Sinnvoll wäre also eine Reaktion des Webbrowsers auf folgende Media Queries:</p>\n<ul>\n<li><a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/@media/monochrome\"><code>monochrome</code></a></li>\n<li><a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/@media/color-gamut\"><code>color-gamut</code></a></li>\n<li><a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion\"><code>prefers-reduced-motion</code></a></li>\n</ul>\n<p>Und hier kommt der etwas traurige Part: In Bezug auf <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/@media\">Media Queries</a> gibt es leider keine Anpassungen des Google Chrome auf dem Tolino gegenüber einem normalen Google Chrome auf einem Android-Smartphone oder -Tablet:</p>\n<div class=\"table-wrapper\"><table>\n<thead>\n<tr>\n<th>Media Query</th>\n<th>Value</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>Media type</td>\n<td class=\"tag-code\"><code>screen</code></td>\n</tr>\n<tr>\n<td>Monochrome</td>\n<td class=\"tag-code\"><code>false</code></td>\n</tr>\n<tr>\n<td>Color</td>\n<td class=\"tag-code\"><code>true</code></td>\n</tr>\n<tr>\n<td>Width</td>\n<td class=\"tag-code\"><code>572px</code></td>\n</tr>\n<tr>\n<td>Height</td>\n<td class=\"tag-code\"><code>773px</code></td>\n</tr>\n<tr>\n<td>Resolution (dpi)</td>\n<td class=\"tag-code\"><code>180</code></td>\n</tr>\n<tr>\n<td>Colors</td>\n<td class=\"tag-code\"><code>8</code></td>\n</tr>\n<tr>\n<td>Orientation</td>\n<td class=\"tag-code\"><code>portrait</code></td>\n</tr>\n<tr>\n<td>Script</td>\n<td class=\"tag-code\"><code>false</code></td>\n</tr>\n<tr>\n<td>Hover</td>\n<td class=\"tag-code\"><code>false</code></td>\n</tr>\n<tr>\n<td>Pointer</td>\n<td class=\"tag-code\"><code>coarse</code></td>\n</tr>\n<tr>\n<td>Prefers reduced motion</td>\n<td class=\"tag-code\"><code>false</code></td>\n</tr>\n</tbody></table></div>\n<p>Kurz gesprochen gibt es also keine Media Query, mit der das CSS einer Website für den Webbrowser des Tolino zugeschnitten werden kann.</p>\n<h2 id=\"und-trotzdem-css-für-ebook-reader\">Und trotzdem: CSS für eBook-Reader</h2>\n<p>Meine Lösung für dieses Dilemma ist wenig schön, aber besser als nichts:</p>\n<pre><code class=\"language-js\">(<b>function</b> () {\n  <i>if</i> (window.navigator.userAgent.match(/(Tolino|Kindle)/i)) {\n    document.body.classList.add(<kbd>&quot;is-ebook-reader&quot;</kbd>);\n  }\n})();\n</code></pre>\n<p>Auf jeder neuen Seite wird mit einer kleinen Zeile JavaScript kurz überprüft, ob es im <i>User Agent String</i> einen Hinweis darauf gibt, dass es sich bei dem besuchenden Browser um einen eBook-Reader handelt. Wenn ja, wird der Seite eine CSS-Klasse <code>is-ebook-reader</code> hinzugefügt.</p>\n<p>Zum Spaß habe ich das hier in diesen Blog eingebaut, und einfach mittels <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/--*\">CSS Custom Properties</a> eine kleine Fallunterscheidung in mein CSS gebaut. Eine ganz einfache Lösung könnte aber wie folgt aussehen:</p>\n<pre><code class=\"language-css\"><i>:root</i> {\n  <var>--color-background</var>: <em>#eee</em>;\n  <var>--color-text</var>: <em>#112</em>;\n  <var>--color-link</var>: orange;\n  <var>--text-decoration-link</var>: none\n}\n\n<i>.is-ebook-reader</i> {\n  <var>--color-background</var>: white;\n  <var>--color-text</var>: black;\n  <var>--color-link</var>: black;\n  <var>--text-decoration-link</var>: underline dotted;\n}\n\n<i>a</i> {\n  <b>color</b>: <b>var</b>(<var>--color-link</var>);\n  <b>text-decoration</b>: <b>var</b>(<var>--text-decoration-link</var>);\n}\n</code></pre><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Wie%20funktioniert%20der%20Tolino-Webbrowser%3F&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2023-12-29-wie-funktioniert-tolino-webbrowser%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2023-12-29-wie-funktioniert-tolino-webbrowser/",
        "pubDate": "Fri, 29 Dec 2023 18:14:24 +0100",
        "atom_published": "2023-12-29T18:14:24+01:00",
        "atom_updated": "2023-12-30T19:47:58+01:00",
        "guid": "user/posts/2023-12-29-wie-funktioniert-tolino-webbrowser/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "CSS",
          "Programmierung",
          "Webdevelop"
        ]
      },
      {
        "title": "Die 24h-Uhr – als Web Component",
        "description": "<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>",
        "content_encoded": "<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=\"\" />",
        "link": "https://journal.3960.org/posts/2023-03-05-24h-uhr-als-web-component/",
        "pubDate": "Sun, 05 Mar 2023 18:56:12 +0100",
        "atom_published": "2023-03-05T18:56:12+01:00",
        "atom_updated": "2023-03-05T18:56:12+01:00",
        "guid": "user/posts/2023-03-05-24h-uhr-als-web-component/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Webdevelop",
          "Web-Components",
          "SVG",
          "Javascript",
          "CSS",
          "Geografie",
          "Outdoor",
          "Programmierung",
          "Technologie"
        ]
      },
      {
        "title": "JavaScript: Pattern zum Event-Handling",
        "description": "<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>",
        "content_encoded": "<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=\"\" />",
        "link": "https://journal.3960.org/posts/2023-01-27-javascript-pattern-zum-event-handling/",
        "pubDate": "Fri, 27 Jan 2023 19:34:49 +0100",
        "atom_published": "2023-01-27T19:34:49+01:00",
        "atom_updated": "2023-10-25T15:45:55+02:00",
        "guid": "user/posts/2023-01-27-javascript-pattern-zum-event-handling/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Javascript",
          "Programmierung",
          "Webdevelop"
        ]
      },
      {
        "title": "Controller testen mit dem JavaScript Gamepad API Test",
        "description": "<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>",
        "content_encoded": "<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=\"\" />",
        "link": "https://3960.org/sandbox/gamepad-test.html",
        "pubDate": "Mon, 27 Jun 2022 19:39:11 +0200",
        "atom_published": "2022-06-27T19:39:11+02:00",
        "atom_updated": "2022-06-27T19:39:11+02:00",
        "guid": "user/posts/2022-06-27-javascript-gamepad-api-test/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Javascript",
          "Joystick",
          "Programmierung",
          "Simulation",
          "Webdevelop"
        ]
      },
      {
        "title": "JavaScript: Die Gamepad-API verwenden",
        "description": "<p>Moderne Browser haben inzwischen eine Vielzahl interessanter JavaScript-Schnittstellen. Höchste Zeit, die <code>Gamepad</code>-API genau zu begutachten.</p>",
        "content_encoded": "<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=\"\" />",
        "link": "https://journal.3960.org/posts/2022-06-26-javascript-gamepad-api-verwenden/",
        "pubDate": "Sun, 26 Jun 2022 18:22:50 +0200",
        "atom_published": "2022-06-26T18:22:50+02:00",
        "atom_updated": "2024-03-22T11:02:23+01:00",
        "guid": "user/posts/2022-06-26-javascript-gamepad-api-verwenden/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Javascript",
          "Joystick",
          "Idee",
          "Programmierung",
          "Simulation",
          "Webdevelop"
        ]
      },
      {
        "title": "SVG kugelsicher machen",
        "description": "<p>SVGs können nicht nur mit Grafikprogrammen wie Adobe Illustrator oder <a href=\"https://inkscape.org/\">Inkscape</a> erstellt werden, sondern auch mit einem einfachen Texteditor – oder programmatisch mit jeder Skriptsprache, die man so zur Hand hat. Damit die SVG-Grafik aber in jedem Umfeld funktioniert, sind ein paar Besonderheiten zu beachten:</p>",
        "content_encoded": "<p>SVGs können nicht nur mit Grafikprogrammen wie Adobe Illustrator oder <a href=\"https://inkscape.org/\">Inkscape</a> erstellt werden, sondern auch mit einem einfachen Texteditor – oder programmatisch mit jeder Skriptsprache, die man so zur Hand hat. Damit die SVG-Grafik aber in jedem Umfeld funktioniert, sind ein paar Besonderheiten zu beachten:</p>\n<!-- more -->\n<h2 id=\"more\">Struktur</h2>\n<pre><code class=\"language-xml\"><b>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;</b>\n&lt;<i>svg</i> <var>version</var>=&quot;<kbd>1.1</kbd>&quot; <var>viewBox</var>=&quot;<kbd>0 0 100 100</kbd>&quot; <var>width</var>=&quot;<kbd>210mm</kbd>&quot; <var>height</var>=&quot;<kbd>210mm</kbd>&quot; <var>xmlns</var>=&quot;<kbd>http://www.w3.org/2000/svg</kbd>&quot;&gt;\n  &lt;<i>title</i>&gt;<u>&lt;!-- Document Title goes here --&gt;</u>&lt;/<i>title</i>&gt;\n  &lt;<i>style</i>&gt;\n    <u>&lt;!-- CSS goes here --&gt;</u>\n  &lt;/<i>style</i>&gt;\n  &lt;<i>defs</i>&gt;\n    <u>&lt;!-- Optional: Pastable objects --&gt;</u>\n  &lt;/<i>defs</i>&gt;\n  <u>&lt;!-- Here comes the actual SVG --&gt;</u>\n&lt;/<i>svg</i>&gt;\n</code></pre>\n<p>Die Grundstruktur eines SVG-Dokuments kann man sich ähnlich wie die eines HTML-Dokuments vorstellen:</p>\n<p>Zuerst kommen Meta-Angaben. Dazu gehört der Dokumenten-Titel, CSS und gegebenenfalls noch <a href=\"https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs\"><code>&lt;defs&gt;</code></a>, falls ihr Grafikkomponenten mehrfach verwenden wollt.</p>\n<p>Erst danach sollte die eigentliche Ausgabe von Grafikelementen beginnen.</p>\n<h2 id=\"title\">Title</h2>\n<p>In SVG hat das <a href=\"https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title\"><code>&lt;title&gt;</code>-Tag</a> mehrere Bedeutungen:</p>\n<ol>\n<li>Wenn es auf der obersten Ebene steht wird es wie das <code>&lt;title&gt;</code>-Tag HTML verwendet, um den Namen eines Dokuments festzulegen.</li>\n<li>Wenn es innerhalb des Dokuments vorkommt übernimmt es die Funktion des aus HTML bekannten <code>title</code>-Attributs. Beim Überfahren mit der Maus wird zum Beispiel eine kleiner Tooltip angezeigt.</li>\n</ol>\n<h2 id=\"css\">CSS</h2>\n<p>…funktioniert wie in HTML. Dementsprechend sollte man versuchen, nicht jedem Element einzeln mittels <code>stroke</code>- und <code>fill</code>-Attributen Styles zuzuordnen, sondern mit CSS das Layout globaler zu definieren.</p>\n<p>Tatsächlich gibt es aber ein paar Besonderheiten bei der <a href=\"https://css-tricks.com/svg-properties-and-css/\">Verwendung von CSS in SVGs</a>:</p>\n<h3 id=\"rgba-farben-funktionieren-nicht-zuverlässig\">RGBA-Farben funktionieren nicht zuverlässig</h3>\n<p>RGBA-Farben haben die Fähigkeit, darunterliegende Teile einer Grafik durchscheinen zu lassen. Der Alpha-Kanal bestimmt dabei die Durchlässigkeit bzw. Opazität. Leider werden RGBA-Farben nicht in jedem Fall korrekt ausgegeben. Die meisten Browser können es, dafür kaum ein Grafikprogramm oder Bildbetrachter.</p>\n<p>Stattdessen gibt es verschiedene Formen von Opazität bzw. Transparenz.</p>\n<ul>\n<li><code>opacity</code> macht ein gesamtes Element,</li>\n<li><code>fill-opacity</code> nur die Füllung,</li>\n<li><code>stroke-opacity</code> nur die Umrandung opak bzw. transparent.</li>\n</ul>\n<p>In CSS sieht das wie folgt aus:</p>\n<pre><code class=\"language-css\"><i>.black-a-fifty</i> {\n  <u>/* fill: rgba(0,0,0,0.5); --- will not always work */</u>\n  <b>fill</b>: <em>#000000</em>;\n  <b>fill-opacity</b>: 0.5;\n}\n</code></pre>\n<h3 id=\"css-custom-properties-funktionieren-nicht-zuverlässig\">CSS Custom Properties funktionieren nicht zuverlässig</h3>\n<p>Fans von <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties\">CSS Custom Properties</a> haben bei SVG ebenfalls kein Glück: Die meisten Grafikprogramme können diese Variablen nicht verarbeiten. Stattdessen gibt es aber einen kleinen Ausweg mit dem CSS-Wert <code>currentColor</code>:</p>\n<p>Der Wert für <code>currentColor</code> wird mit der in SVG sonst irrelevanten CSS-Eigenschaft <a href=\"https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color\"><code>color</code></a> gesetzt (die im Gegensatz zu der gleichnamigen Eigenschaft in HTML nicht aktiv ELemente einfärbt – dafür ist in SVG ja <code>fill</code> und <code>stroke</code> da). Der Aufruf von <code>currentColor</code> wird dann mit dem zuletzt via <code>color</code> gesetztem Wert gefüllt – ganz wie bei einer CSS Custom Property.</p>\n<pre><code class=\"language-css\"><i>.black</i> {\n  <b>color</b>: black; <u>/* sets currentColor to `black` */</u>\n}\n\n<i>.red</i> {\n  <b>color</b>: red; <u>/* sets currentColor `red` */</u>\n}\n\n<u>/* These elements will be colored black or red, depending on class */</u>\n\n<i>rect</i> {\n  <b>stroke</b>: currentColor;\n  <b>fill</b>: currentColor;\n}\n\n<i>text</i> {\n  <b>fill</b>: currentColor;\n}\n</code></pre>\n<h2 id=\"mehrzeiliger-text\">Mehrzeiliger Text</h2>\n<p>In SVG kann in Theorie nur einzeiliger Text gesetzt werden, beziehungsweise muss jede neue Zeile neu positioniert werden. Mit einem kleinen Trick kann man aber mehrzeiligen Text erstellen, indem jede Zeile in ein <a href=\"https://developer.mozilla.org/en-US/docs/Web/SVG/Element/tspan\"><code>&lt;tspan&gt;</code>-Element</a> gehüllt und diese relativ unter der vorhergehenden Zeile positioniert wird:</p>\n<pre><code class=\"language-xml\">&lt;<i>text</i>&gt;\n  &lt;<i>tspan</i> <var>x</var>=&quot;<kbd>0</kbd>&quot;&gt;Line 1&lt;/<i>tspan</i>&gt;\n  &lt;<i>tspan</i> <var>x</var>=&quot;<kbd>0</kbd>&quot; <var>dy</var>=&quot;<kbd>1.1em</kbd>&quot;&gt;Line 2&lt;/<i>tspan</i>&gt;\n  &lt;<i>tspan</i> <var>x</var>=&quot;<kbd>0</kbd>&quot; <var>dy</var>=&quot;<kbd>1.1em</kbd>&quot;&gt;Line 3&lt;/<i>tspan</i>&gt;\n  &lt;<i>tspan</i> <var>x</var>=&quot;<kbd>0</kbd>&quot; <var>dy</var>=&quot;<kbd>1.1em</kbd>&quot;&gt;Line 4&lt;/<i>tspan</i>&gt;\n&lt;/<i>text</i>&gt;\n</code></pre>\n<p>Wichtig ist hier die Definition des Zeilenabstands – diese muss in jedem <code>dy</code>-Attribut wiederholt werden.</p>\n<h2 id=\"links\">Links</h2>\n<p>Wenn ihr Links in eurem SVG verwenden wollt, gibt es zwei Wege, wobei je nach Ausgabemedium nur einer von beiden funktioniert… oder auch gar keiner.</p>\n<p>Die veraltete, aber noch weit verbreitete Methode sind <a href=\"https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href\"><code>xlink:href</code>-Attribute</a>, die die Ziel-URL an einem Element festlegen. Damit diese funktionieren, muss zuerst an dem <code>&lt;svg&gt;</code>-Knoten ein neuer <a href=\"https://developer.mozilla.org/en-US/docs/Related/IMSC/Namespaces\">Namespace</a> deklariert werden:</p>\n<pre><code class=\"language-xml\">&lt;<i>svg</i> … <var>xmlns:xlink</var>=&quot;<kbd>http://www.w3.org/1999/xlink</kbd>&quot;&gt;\n</code></pre>\n<p>Sobald dieser Namespace bekannt ist, kann das Attribut verwendet werden.</p>\n<p>In SVG2 wird stattdessen das aus HTML bekannte <a href=\"https://developer.mozilla.org/en-US/docs/Web/SVG/Element/a\"><code>&lt;a&gt;</code>-Tag mit <code>href</code>-Attribut</a> verwendet.</p>\n<p>Für eine maximale Kompatibilität kann man beide Techniken miteinander kombinieren:</p>\n<pre><code class=\"language-xml\">&lt;<i>a</i> <var>xlink:href</var>=&quot;<kbd>https://www.example.com</kbd>&quot; <var>href</var>=&quot;<kbd>https://www.example.com</kbd>&quot;&gt;…&lt;/<i>a</i>&gt;\n</code></pre>\n<p>Übrigens: Die Links für <a href=\"https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use\"><code>&lt;defs&gt;</code> und <code>&lt;use&gt;</code></a> werden mit dem selben <code>xlink:href</code>- beziehungsweise <code>href</code>-Attribut gebaut.</p>\n<h2 id=\"linien-abrunden\">Linien abrunden</h2>\n<p>Im Standard sind SVG-Linien eine scharfkantige Sache. Zum Glück gibt es CSS-Eigenschaften, die sowohl die Enden mit <a href=\"https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linecap\"><code>stroke-linecap</code></a> als auch die Ecken mit <a href=\"https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linejoin\"><code>stroke-linejoin</code></a> abrunden können.</p>\n<pre><code class=\"language-css\"><i>.rounded-line</i> {\n  <b>stroke-linecap</b>: round;\n  <b>stroke-linejoin</b>: round;\n}\n</code></pre>\n<h2 id=\"inkscape\">Inkscape</h2>\n<p>Wenn euer SVG-Dokument auch in <a href=\"https://inkscape.org/\">Inkscape</a> bearbeitbar sein soll, könnt ihr mit einem zusätzlichen XML-Namensraum kleine Helferlein im Dokument hinterlassen:</p>\n<pre><code class=\"language-xml\">&lt;<i>svg</i> … <var>xmlns:inkscape</var>=&quot;<kbd>http://www.inkscape.org/namespaces/inkscape</kbd>&quot;&gt;\n</code></pre>\n<p>Jetzt steht euch die Möglichkeit zur Verfügung, die aus Inkscape bekannten Layer in eurem SVG zu verwenden. Dazu müsst ihr eine beliebige Gruppe nur mit zusätzlichen Attributen ausstatten:</p>\n<pre><code class=\"language-xml\">&lt;<i>g</i> <var>inkscape:groupmode</var>=&quot;<kbd>layer</kbd>&quot; <var>inkscape:label</var>=&quot;<kbd>Name your layer</kbd>&quot;&gt;…&lt;/<i>g</i>&gt;\n</code></pre>\n<h2 id=\"fazit\">Fazit</h2>\n<p>Wenn eure SVGs nicht nur in modernsten Browsern, sondern auch in älteren Browsern und  Grafikprogrammen funktionieren soll, müssen ein paar Spielregeln eingehalten werden. Im Endeffekt ist nach kurzer Umgewöhnung das Erzeugen wunderschöner SVG-Dokumente nicht viel komplizierter als das Erstellen von HTML-Dokumenten.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=SVG%20kugelsicher%20machen&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2022-05-10-svg-kugelsicher-machen%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2022-05-10-svg-kugelsicher-machen/",
        "pubDate": "Tue, 10 May 2022 19:06:51 +0200",
        "atom_published": "2022-05-10T19:06:51+02:00",
        "atom_updated": "2022-05-11T08:52:28+02:00",
        "guid": "user/posts/2022-05-10-svg-kugelsicher-machen/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "SVG",
          "CSS",
          "Programmierung",
          "Webdevelop"
        ]
      },
      {
        "title": "Gnome-Erweiterungen für Ubuntu",
        "description": "<p>Die <a href=\"https://extensions.gnome.org/\">GNOME Shell-Erweiterungen</a> sind für Ubuntu (und andere Linux-Betriebssysteme) ein schöner Weg, um das eine oder andere Verhalten des Betriebssystems aufzubohren.</p>",
        "content_encoded": "<p>Die <a href=\"https://extensions.gnome.org/\">GNOME Shell-Erweiterungen</a> sind für Ubuntu (und andere Linux-Betriebssysteme) ein schöner Weg, um das eine oder andere Verhalten des Betriebssystems aufzubohren.</p>\n<!-- more -->\n<p id=\"more\">Dazu muss „GNOME Shell Extensions“ und „GNOME Tweaks“ zuerst installiert werden:</p>\n<pre><code class=\"language-bash\"><b>sudo</b> add-apt-repository universe\n<b>sudo</b> apt install gnome-tweak-tool\n<b>sudo</b> apt install gnome-shell-extensions\n</code></pre>\n<p>Meine Lieblings-GNOME-Erweiterungen sind diese:</p>\n<dl>\n<dt><a href=\"https://extensions.gnome.org/extension/1160/dash-to-panel/\">Dash to Panel</a></dt><dd> Als alter Windows- / OSX-Nutzer habe ich meine Applikations- und System-Tray-Symbole gerne unten am Monitor angedockt.</dd>\n<dt><a href=\"https://extensions.gnome.org/extension/779/clipboard-indicator/\">Clipboard Indicator</a></dt><dd> Dieses kleine Helferlein erlaubt es, mehrere Einträge in der Zwischenablage zu haben.</dd>\n<dt><a href=\"https://extensions.gnome.org/extension/1462/panel-date-format/\">Panel Date Format</a></dt><dd> Das Datumsformat im System-Tray kann mit dieser Erweiterungen sehr genau angepasst werden.</dd>\n<dt><a href=\"https://extensions.gnome.org/extension/906/sound-output-device-chooser/\">Sound Input &amp; Output Device Chooser</a></dt><dd> Im Home-Office wechsele ich öfter zwischen Boxen und Kopfhörer. Mit dieser kleinen Erweiterungen kann ich schnell zwischen den Kanälen hin- und herschalten, ohne irgendwelche Kabel aus dem Rechner ziehen zu müssen.</dd>\n</dl><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Gnome-Erweiterungen%20f%C3%BCr%20Ubuntu&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2022-03-03-gnome-erweiterungen-fuer-ubuntu%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2022-03-03-gnome-erweiterungen-fuer-ubuntu/",
        "pubDate": "Thu, 03 Mar 2022 18:49:58 +0100",
        "atom_published": "2022-03-03T18:49:58+01:00",
        "atom_updated": "2022-03-03T18:49:58+01:00",
        "guid": "user/posts/2022-03-03-gnome-erweiterungen-fuer-ubuntu/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Technologie",
          "Webdevelop",
          "Linux"
        ]
      },
      {
        "title": "World's smallest PHP template engine",
        "description": "<p>A fundamental principle of building (web) applications is to have a strict separation between logic and output – at least if your goal as a programmer is to stay sane. For outputting stuff most often there is a simple (template) language to prevent mixing too much logic into your templates.</p>\n<p>For PHP template engines like <a href=\"https://twig.symfony.com/\">Twig</a> or <a href=\"https://www.smarty.net/\">Smarty</a> solve the outputting part. But do we really need a template engine? Is it possible that PHP (without any additional libraries) is quite sufficient to do templating?</p>",
        "content_encoded": "<p>A fundamental principle of building (web) applications is to have a strict separation between logic and output – at least if your goal as a programmer is to stay sane. For outputting stuff most often there is a simple (template) language to prevent mixing too much logic into your templates.</p>\n<p>For PHP template engines like <a href=\"https://twig.symfony.com/\">Twig</a> or <a href=\"https://www.smarty.net/\">Smarty</a> solve the outputting part. But do we really need a template engine? Is it possible that PHP (without any additional libraries) is quite sufficient to do templating?</p>\n<!-- more -->\n<p id=\"more\">The main idea of template engines is:</p>\n<ul>\n<li>Template engines remove logic on purpose – to concentrate complexity in your programming in a different spot, and to remove additional points of error.</li>\n<li>The remaining control structures like <code>if</code>, <code>while</code> or <code>for</code> are more simple to use in a template engine.</li>\n<li>Template engines take care of proper encoding / quoting whenever you are outputting a variable.</li>\n<li>Template engines support re-use of template parts (called snippets or partials) in different context.</li>\n</ul>\n<p>A perfect example for this philosophy is the template engine <a href=\"http://mustache.github.io/\">Mustache</a>: It only contains a minimum of required methods in order to reduce complexity.</p>\n<p>But do we really need the overhead of a proper template engine? Or is it possible to use plain PHP for our templating needs, without adding dependencies like template engines to our project?</p>\n<p>Actually PHP has good capabilities to do templating – its <a href=\"https://www.php.net/manual/en/intro-whatis.php\">founding idea revolves around templating</a>, as a matter of fact.</p>\n<h2 id=\"file-extensions\">File Extensions</h2>\n<p>It is common practice to put all your templates into a separate folder, and append a common file extension to all template files. For example <a href=\"https://symfony.com/doc/current/templates.html#creating-templates\">Symfony with Twig</a> puts all templates into a directory called <code>/templates</code> and use the file extension <code>.twig</code>.</p>\n<p>Actually there is nothing stopping us from using the same <code>/templates</code> directory for our templates. As file extension we are using (like Twig and to keep our mental health):</p>\n<div class=\"table-wrapper\"><table>\n<thead>\n<tr>\n<th>Template output</th>\n<th>File extension</th>\n<th>(MIME-type)</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>HTML file</td>\n<td class=\"tag-code\"><code>*.html.php</code></td>\n<td class=\"tag-code\"><code>text/html</code></td>\n</tr>\n<tr>\n<td>XML file</td>\n<td class=\"tag-code\"><code>*.xml.php</code></td>\n<td><code>text/xml</code> or <code>application/xml</code></td>\n</tr>\n<tr>\n<td>JSON file</td>\n<td class=\"tag-code\"><code>*.json.php</code></td>\n<td class=\"tag-code\"><code>application/json</code></td>\n</tr>\n<tr>\n<td>Text file</td>\n<td class=\"tag-code\"><code>*.txt.php</code></td>\n<td class=\"tag-code\"><code>text/plain</code></td>\n</tr>\n<tr>\n<td>CSV file</td>\n<td class=\"tag-code\"><code>*.csv.php</code></td>\n<td class=\"tag-code\"><code>text/csv</code></td>\n</tr>\n</tbody></table></div>\n<p>This list is easily extended be even more MIME-types and corresponding file extensions. Important for us is that all files end with <code>.php</code>, so our code editor knows how to handle these files. This also secures the files if by mistake these files get called directly by a web server; files ending with <code>.php</code> will be executed as PHP, and their PHP secrets will be compiled and not shown to a world-wide audience.</p>\n<p>It is good custom to name your template after the controller calling it. Accordingly a HTML template for an index-controller will be <code>index.html.php</code>, for an user-controller <code>user.html.php</code>. The XML template for the index-controller becomes <code>index.xml.php</code>.</p>\n<p>Templates not attached to a controller (called snippets or partials) will be prefixed with a <code>_</code>, e.g. <code>_meta.html.php</code>. The purpose of these files will be explained later on.</p>\n<h2 id=\"variable-output\">Variable Output</h2>\n<p>We will take advantage of the fact that PHP returns every line of code not enclosed in <a href=\"https://www.php.net/manual/en/language.basic-syntax.phptags.php\">PHP tags</a> (<code>&lt;?php ?&gt;</code>) unaltered back to the browser. That allows us to concentrate on building our beautiful template output:</p>\n<pre><code class=\"language-html\">&lt;!DOCTYPE html&gt;\n&lt;<i>html</i> <var>xmlns</var>=&quot;<kbd>http://www.w3.org/1999/xhtml</kbd>&quot; <var>lang</var>=&quot;<kbd>en</kbd>&quot;&gt;\n&lt;<i>head</i>&gt;\n&lt;/<i>head</i>&gt;\n&lt;<i>body</i>&gt;\n  <u>&lt;!-- I am valid PHP --&gt;</u>\n&lt;/<i>body</i>&gt;\n</code></pre>\n<p>The most important task of templates is to output variables prepared by the controller. Like in every template language we open up a instruction block and execute a function for outputting a variable.</p>\n<pre><code class=\"language-html\"><u>&lt;!-- Bad idea --&gt;</u>\n&lt;<i>div</i>&gt;<b>&lt;?php echo($title) ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<p>PHP kindly allows for shortening the output instruction:</p>\n<pre><code class=\"language-html\"><u>&lt;!-- Still a bad idea --&gt;</u>\n&lt;<i>div</i>&gt;<b>&lt;?= $title ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<p>But actually the above examples are very bad ideas, because both ignore quoting / escaping – a core function of any good template engine, because only this secures variable output against malicious intentions. Look at the above examples and imagine some HTML tags (or characters like a single <code>&lt;</code>) inside <code>$title</code> – as you can see these will be embedded into the HTML output. In most cases this is undesirable, possibly breaking your site or (even worse) allowing attackers to make your site send harmful HTML to your visitor&#39;s browsers. Want to see an example?</p>\n<pre><code class=\"language-html\"><u>&lt;!-- See, a bad idea --&gt;</u>\n&lt;<i>div</i>&gt;<b>&lt;?= $_GET[&#39;search&#39;] ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<p>But PHP has you covered: <a href=\"https://www.php.net/htmlspecialchars\"><code>htmlspecialchars</code></a> converts any string to safe HTML (or XML, for that matter). Characters like <code>&lt;</code>, <code>&gt;</code> or <code>&quot;</code> will be safely encoded:</p>\n<pre><code class=\"language-html\"><u>&lt;!-- Better idea --&gt;</u>\n&lt;<i>div</i>&gt;<b>&lt;?= htmlspecialchars($title) ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<p>Because this main method of outputting variables will become tedious to write, we are introducing an <em>alias</em> for <code>htmlspecialchars</code> – the beginning of the world&#39;s smallest PHP template engine:</p>\n<pre><code class=\"language-php\"><u>// Template.php</u>\n\n<u>/**\n * Alias for `htmlspecialchars`\n */</u>\n<b>function</b> html(<var>$s</var>): <b>string</b>\n{\n  <i>return</i> htmlspecialchars(<var>$s</var>);\n}\n</code></pre>\n<p>This function shortens our work to convert variables and will be our new main function to send variable content back to the browser:</p>\n<pre><code class=\"language-html\"><u>&lt;!-- Best idea --&gt;</u>\n&lt;<i>div</i>&gt;<b>&lt;?= html($title) ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<h3 id=\"more-quoting\">More Quoting</h3>\n<p>PHP offers even more function for quoting / escaping. There is a function for encoding query parameters used in URLs, quoting characters like <code>#</code> or <code>&amp;</code>: <a href=\"https://www.php.net/urlencode\"><code>urlencode</code></a>. These can be easily combined with our <code>html</code> function:</p>\n<pre><code class=\"language-html\">&lt;<i>a</i> href=&quot;https://www.example.com?id=<b>&lt;?= html(urlencode($id)) ?&gt;</b>&quot;&gt;Test&lt;/<i>a</i>&gt;\n</code></pre>\n<p>Sometimes we also have to convert our PHP variables to JavaScript variables. Not explicitly build for that purpose but highly recommendable is a function called <a href=\"https://www.php.net/json_encode\"><code>json_encode</code></a>. It actually converts the given variable to JSON – which is also valid JavaScript. Even better the function also converts PHP arrays and objects to JavaScript arrays and objects:</p>\n<pre><code class=\"language-html\">&lt;<i>script</i>&gt;\nvar data = <b>&lt;?= json_encode($data) ?&gt;</b>;\n&lt;/<i>script</i>&gt;\n</code></pre>\n<h2 id=\"conditions\">Conditions</h2>\n<p>The most important control structure in templates are conditions, with your best friend <code>if</code> to be mentioned first. But „pure“ PHP with its curly brackets used in conjunction with <code>if</code> tends to make matters confusing in your template, making it hard to see where your conditional block ends:</p>\n<pre><code class=\"language-html\"><b>&lt;?php if (!empty($title)) { ?&gt;</b>\n  &lt;<i>div</i>&gt;<b>&lt;?= html($title) ?&gt;</b>&lt;/<i>div</i>&gt;\n<b>&lt;?php } ?&gt;</b>\n</code></pre>\n<p>Do not despair, <a href=\"https://www.php.net/manual/en/control-structures.alternative-syntax.php\">PHP has an alternative syntax for controls structures</a> tailored for templating tasks. Instead of starting a block via <code>{</code> a simple  <code>:</code> is used. And instead of ending a block via <code>}</code> something like <code>end...;</code> will be used:</p>\n<pre><code class=\"language-html\"><b>&lt;?php if (!empty($title)): ?&gt;</b>\n  &lt;<i>div</i>&gt;<b>&lt;?= html($title) ?&gt;</b>&lt;/<i>div</i>&gt;\n<b>&lt;?php endif ?&gt;</b>\n</code></pre>\n<p>Your <code>if</code>-conditions can use any operators and functions known to PHP. One of your most important conditions in templating will be a check if a given variable <em>has</em> content to output. This test is simply executed by using <code>!empty</code> or <code>isset</code>.</p>\n<p>Of course there is also <code>else</code> and <code>elseif</code> available:</p>\n<pre><code class=\"language-html\"><b>&lt;?php if (!empty($results)): ?&gt;</b>\n  &lt;<i>h4</i>&gt;We have found <b>&lt;?= html(count($results)) ?&gt;</b> results.&lt;/<i>h4</i>&gt;\n<b>&lt;?php else: ?&gt;</b>\n  &lt;<i>h4</i>&gt;Sorry, we have found no results.&lt;/<i>h4</i>&gt;\n<b>&lt;?php endif ?&gt;</b>\n</code></pre>\n<h2 id=\"loops-and-arrays\">Loops and Arrays</h2>\n<p>Then there are lists and tables in your templates. With PHP loops your are able to iterate over an PHP array to output lists and tables. Lucky for you <a href=\"https://www.php.net/manual/en/control-structures.alternative-syntax.php\">there is also an alternate syntax for looping control structures</a>:</p>\n<pre><code class=\"language-html\"><b>&lt;?php if (!empty($list)): ?&gt;</b>\n  &lt;<i>ul</i>&gt;\n    <b>&lt;?php foreach($list as $index =&gt; $item): ?&gt;</b>\n      &lt;<i>li</i>&gt;<b>&lt;?= html($item) ?&gt;</b>&lt;/<i>li</i>&gt;\n    <b>&lt;?php endforeach ?&gt;</b>\n  &lt;/<i>ul</i>&gt;\n<b>&lt;?php endif ?&gt;</b>\n</code></pre>\n<p>This syntax does a proper job for <code>for</code>, <code>foreach</code> or <code>while</code>:</p>\n<pre><code class=\"language-html\">&lt;<i>h4</i>&gt;Lottery numbers&lt;/<i>h4</i>&gt;\n&lt;<i>ol</i>&gt;\n  <b>&lt;?php for ($i = 1; $i &lt;= 49; $i++): ?&gt;</b>\n    &lt;<i>li</i>&gt;\n      &lt;<i>input</i> <var>type</var>=&quot;<kbd>checkbox</kbd>&quot; name=&quot;lottery_<b>&lt;?= html($i) ?&gt;</b>&quot; id=&quot;lottery_<b>&lt;?= html($i) ?&gt;</b>&quot; value=&quot;1&quot; /&gt;\n      &lt;<i>label</i> for=&quot;lottery_<b>&lt;?= html($i) ?&gt;</b>&quot;&gt;<b>&lt;?= html($i) ?&gt;</b>&lt;/<i>label</i>&gt;\n    &lt;/<i>li</i>&gt;\n  <b>&lt;?php endfor ?&gt;</b>\n&lt;/<i>ol</i>&gt;\n</code></pre>\n<h2 id=\"snippets--partials\">Snippets / Partials</h2>\n<p>Partial templates present in multiple controllers (like a header, footer or navigation) are called… partials (doh!) or snippets. These partials are prefixed with a single <code>_</code>, for example <code>_header.xml.php</code>.</p>\n<p>These partials or snippets can be embedded into other templates by PHP&#39;s very own <code>include</code> instruction:</p>\n<pre><code class=\"language-html\">&lt;!DOCTYPE html&gt;\n&lt;<i>html</i> <var>xmlns</var>=&quot;<kbd>http://www.w3.org/1999/xhtml</kbd>&quot; <var>lang</var>=&quot;<kbd>en</kbd>&quot;&gt;\n&lt;<i>head</i>&gt;\n  <b>&lt;?php include(&#39;_meta.html.php&#39;) ?&gt;</b>\n&lt;/<i>head</i>&gt;\n&lt;<i>body</i>&gt;\n  <b>&lt;?php include(&#39;_header.html.php&#39;) ?&gt;</b>\n  ...\n  <b>&lt;?php include(&#39;_footer_.html.php&#39;) ?&gt;</b>\n&lt;/<i>body</i>&gt;\n</code></pre>\n<h2 id=\"translations\">Translations</h2>\n<p>You never would have guessed, but PHP also handles translations! The <a href=\"https://www.php.net/manual/en/book.gettext.php\">Gettext library</a> is included in PHP and allows to translate strings, which Gettext will look up in a dictionary you are able to build. If a translation is found, Gettext will return it to PHP.</p>\n<p>Most important for translations is to tell PHP the language / locale you are using. This is done via <a href=\"https://www.php.net/setlocale\"><code>setlocale</code></a>:</p>\n<pre><code class=\"language-php\">setlocale(LC_MESSAGES, <kbd>'de_DE'</kbd>); <u>// German in Germany</u>\nsetlocale(LC_MESSAGES, <kbd>'en_GB'</kbd>); <u>// English in Great Britain</u>\n</code></pre>\n<p>This also switches to the correct format for numbers and dates in the given language, as well as using the correct date terms for months and weekdays.</p>\n<p>The strings you want translated are passed to the <a href=\"https://www.php.net/_\"><code>gettext</code> / <code>_</code> function</a>:</p>\n<pre><code class=\"language-html\"><u>&lt;!-- translate to &#39;Guten Morgen!&#39; --&gt;</u>\n&lt;<i>h4</i>&gt;<b>&lt;?= html(_(&#39;Good morning!&#39;)) ?&gt;</b>&lt;/<i>h4</i>&gt;\n</code></pre>\n<p>The required dictionaries to translate your strings can be created by yourself. The required <a href=\"http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files\">PO- and MO-files</a> can be created using programmes like <a href=\"https://poedit.net/\">Poedit</a>. <i>Poedit</i> also offers to search your whole project for translatable strings to automatically add them to your dictionaries.</p>\n<p>If your happen to add variables to your translatable strings things can get a little hairy. This somewhat complex construction allows for a translatable string to also work with variables:</p>\n<pre><code class=\"language-html\">&lt;<i>div</i>&gt;<b>&lt;?= html(vsprintf(_(&#39;There are %d results&#39;), [$count])) ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<p>So here is our next alias to be added to our simple template engine: A shortcut for outputting translatable strings mixed with variables.</p>\n<pre><code class=\"language-php\"><u>// Template.php</u>\n\n<u>/**\n * Alias for `vsprintf`, but with HTML escaping and translation\n */</u>\n<b>function</b> _html(<b>string</b> <var>$format</var>, <b>array</b> <var>$args</var> = []): <b>string</b>\n{\n  <i>return</i> htmlspecialchars(<var>$args</var>\n    ? vsprintf(_(<var>$format</var>), <var>$args</var>)\n    : _(<var>$format</var>)\n  );\n}\n</code></pre>\n<p>…which makes writing translatable strings mixed with variables somewhat less tedious:</p>\n<pre><code class=\"language-html\">&lt;<i>h4</i>&gt;<b>&lt;?= _html(&#39;Good morning!&#39;) ?&gt;</b>&lt;/<i>h4</i>&gt;\n&lt;<i>div</i>&gt;<b>&lt;?= _html(&#39;There are %d results&#39;, [$count]) ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<h2 id=\"variable-dumping\">Variable Dumping</h2>\n<p>It is very helpful for developers as well as template builders alike to have a look into complex variable constructs for debugging purposes – think of big objects or arrays to traverse. PHP offers <a href=\"https://www.php.net/print_r\"><code>print_r</code></a> and <a href=\"https://www.php.net/var_dump\"><code>var_dump</code></a> / <a href=\"https://www.php.net/var_export\"><code>var_export</code></a> for this purpose. These functions are helpful, but not in HTML as they do not produce any line breaks, dumping all your structured data into a single line. Again our little template engine comes to the rescue with a small shortcut:</p>\n<pre><code class=\"language-php\"><u>// Template.php</u>\n\n<u>/**\n * HTML dumper für PHP variables\n */</u>\n<b>function</b> debug(<var>$mixed</var>, <b>bool</b> <var>$extended</var> = <samp>false</samp>): <b>void</b>\n{\n  <i>echo</i>(<kbd>'&lt;pre class=&quot;debug&quot; style=&quot;margin: 1em 0; border: 1px solid red; background: #fee; color: #000; padding: 1em;&quot;&gt;'</kbd>);\n  <i>echo</i>(htmlspecialchars(<var>$extended</var>\n    ? var_export(<var>$mixed</var>, <samp>true</samp>)\n    : print_r(<var>$mixed</var>, <samp>true</samp>)\n  ));\n  <i>echo</i>(<kbd>'&lt;/pre&gt;'</kbd>);\n}\n</code></pre>\n<p>This allows for complex variables to be dumped in a readable manner:</p>\n<pre><code class=\"language-html\"><b>&lt;?php debug($data) ?&gt;</b>\n</code></pre>\n<p>While your at it, you might consider <a href=\"https://journal.3960.org/posts/einfachste-php-objekt-debugger-fuer-browser/\">using <code>console.log</code> for debugging output</a> in the console of your browser.</p>\n<h2 id=\"outputting-different-content-types\">Outputting different Content-Types</h2>\n<p>PHP was build to output HTML. This works by PHP telling the browser that the MIME-type of the document it is sending is <code>text/html</code>. But actually PHP is perfectly capable to send any other MIME-type header. The function <a href=\"https://www.php.net/header\"><code>header</code></a> allows PHP to send custom headers to your visitor&#39;s browsers, replacing default headers while your at it. </p>\n<p>The header used to set the MIME-type is <code>Content-Type</code>. So it is perfectly possible for PHP to send <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types\">any conceivable MIME-type</a>.</p>\n<h3 id=\"xml\">XML</h3>\n<p>For XML we choose the file extension <code>.xml.php</code>, for example in <code>sitemap.xml.php</code>. This template does not differ very much from a standard HTML template, and uses the same quoting / encoding routines. We just have to send a different <code>Content-Type</code> header and we are serving proper XML:</p>\n<pre><code class=\"language-xml\"><b>&lt;?php header(&#39;Content-Type: text/xml&#39;); ?&gt;</b>\n&lt;<i>urlset</i>&gt;\n  <b>&lt;?php foreach($urls as $url): ?&gt;</b>\n    &lt;<i>url</i>&gt;\n      &lt;<i>loc</i>&gt;<b>&lt;?= html($url) ?&gt;</b>&lt;/<i>loc</i>&gt;\n    &lt;/<i>url</i>&gt;\n  <b>&lt;?php endforeach ?&gt;</b>\n&lt;/<i>urlset</i>&gt;\n</code></pre>\n<h3 id=\"json\">JSON</h3>\n<p>Outputting JSON is also quite simple with PHP. For this MIME-type we are using the file extension <code>.json.php</code> (like in <code>feed.json.php</code>) and need to set the correct <code>Content-Type</code>. To properly use quoting / escaping the inbuilt PHP function <a href=\"https://www.php.net/json_encode\"><code>json_encode</code></a> is very helpful, as it converts even complex PHP variables into structured JSON output:</p>\n<pre><code class=\"language-php\">&lt;?php\n  header(<kbd>'Content-Type: application/json'</kbd>);\n  <i>echo</i>(json_encode(<var>$data</var>));\n</code></pre>\n<p>If you like your JSON&#39;s source to look nice, there is also a parameter called <code>JSON_PRETTY_PRINT</code> for <code>json_encode</code>:</p>\n<pre><code class=\"language-php\">&lt;?php\n  header(<kbd>'Content-Type: application/json'</kbd>);\n  <i>echo</i>(json_encode(<var>$data</var>, JSON_PRETTY_PRINT));\n</code></pre>\n<p>Actually PHP&#39;s <code>json_encode</code> precisely converts PHP variable types to the matching JSON types. A PHP integer will be a JSON integer, a PHP string will be a JSON string. So note the difference between <code>1</code> and <code>„1“</code>.</p>\n<h3 id=\"get-the-right-template\">Get the right template</h3>\n<p>The examples above for fetching templates and setting the correct MIME-type can be solved with a general <code>switch</code> block:</p>\n<pre><code class=\"language-php\"><u>// e.g. $template = 'index';</u>\n<u>// e.g. $contentType = 'html';</u>\n\n<i>switch</i> (<var>$contentType</var>) {\n  <i>case</i> <kbd>'xml'</kbd>:\n    header(<kbd>'Content-Type: text/xml'</kbd>);\n    <i>break</i>;\n  <i>case</i> <kbd>'json'</kbd>:\n    header(<kbd>'Content-Type: application/json'</kbd>);\n    <i>break</i>;\n  <i>case</i> <kbd>'txt'</kbd>:\n    header(<kbd>'Content-Type: text/plain'</kbd>);\n    <i>break</i>;\n  <i>case</i> <kbd>'csv'</kbd>:\n    header(<kbd>'Content-Type: text/csv'</kbd>);\n    <i>break</i>;\n  default:\n    <var>$contentType</var> = <kbd>'html'</kbd>;\n    <i>break</i>;\n}\n\n<var>$templateFilename</var> = __DIR__ . <kbd>'/templates/'</kbd> . <var>$template</var> . <kbd>'.'</kbd> . <var>$contentType</var> . <kbd>'.php'</kbd>;\n<i>require</i>(<var>$templateFilename</var>);\n</code></pre>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>EVery PHP project needs to have a clean separation between logic and output – but not always you will need a template engine like <a href=\"https://twig.symfony.com/\">Twig</a> or <a href=\"https://www.smarty.net/\">Smarty</a> for this separation. Common off-the-shelf PHP (with few small helpers) can be a simple, fuss-free alternative for templating.</p>\n<p>So here it is, our small manual of <a href=\"https://www.php.net/manual/en/ref.strings.php\">the most important PHP functions for templating</a>:</p>\n<div class=\"table-wrapper\"><table>\n<thead>\n<tr>\n<th>Function</th>\n<th>Alias</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><a href=\"https://www.php.net/htmlspecialchars\"><code>htmlspecialchars</code></a></td>\n<td class=\"tag-code\"><code>html</code></td>\n<td>HTML escaping</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/urlencode\"><code>urlencode</code></a></td>\n<td>-</td>\n<td>URL escaping</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/json_encode\"><code>json_encode</code></a></td>\n<td>-</td>\n<td>JSON/JavaScript escaping</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/setlocale\"><code>setlocale</code></a></td>\n<td>-</td>\n<td>Set language / locale for translation</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/_\"><code>gettext</code> / <code>_</code></a></td>\n<td class=\"tag-code\"><code>_html</code></td>\n<td>Outputting translation</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/strftime\"><code>strftime</code></a></td>\n<td>-</td>\n<td>Output localized date format</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/localeconv\"><code>localeconv</code></a></td>\n<td>-</td>\n<td>Get number format for current locale</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/printf\"><code>printf</code></a></td>\n<td class=\"tag-code\"><code>_html</code></td>\n<td>Output formatted variables in string</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/nl2br\"><code>nl2br</code></a></td>\n<td>-</td>\n<td>Convert line breaks to <code>&lt;br /&gt;</code></td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/implode\"><code>implode</code></a></td>\n<td>-</td>\n<td>Joins array members to a single string</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/var_dump\"><code>var_dump</code></a></td>\n<td class=\"tag-code\"><code>debug</code></td>\n<td>Dump PHP variables</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/header\"><code>header</code></a></td>\n<td>-</td>\n<td>Change <code>Content-Type</code> in browser</td>\n</tr>\n</tbody></table></div>\n<p>The only thing missing from PHP is template inheritance, like in <a href=\"https://twig.symfony.com/doc/3.x/templates.html#template-inheritance\">Twig&#39;s template inheritance</a> or <a href=\"https://www.smarty.net/docs/en/advanced.features.template.inheritance.tpl\">Smarty&#39;s template inheritance</a>. Also have a look at <a href=\"https://css-tricks.com/comparing-html-preprocessor-features/\"><i>CSS-Trick&#39;s</i> comparison of template engines</a>.</p>\n<hr />\n<p>Es gibt auch eine <a href=\"https://journal.3960.org/posts/2020-08-20-kleinste-php-templating-engine-welt/\" rel=\"alternate\">deutsche Version von diesem Artikel, „Die kleinste PHP-Templating-Engine der Welt“</a>.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=World's%20smallest%20PHP%20template%20engine&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2021-12-17-world-smallest-php-templating-engine%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2021-12-17-world-smallest-php-templating-engine/",
        "pubDate": "Fri, 17 Dec 2021 18:48:53 +0100",
        "atom_published": "2021-12-17T18:48:53+01:00",
        "atom_updated": "2024-02-02T17:26:09+01:00",
        "guid": "user/posts/2021-12-17-world-smallest-php-templating-engine/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "PHP",
          "Programmierung",
          "Webdevelop"
        ]
      },
      {
        "title": "Print-Stylesheets… für SVG-Grafiken",
        "description": "<p>Vektor-Grafiken haben für Logos, Diagramme und Symbole die fantastische Eigenschaft, bei beliebig hoher Auflösung relativ kleine Dateigrößen zu verursachen. Und hier folgen ein paar Kniffe, um SVG-Grafiken mit der Hilfe von CSS sowohl am Bildschirm als auch im Druck gut aussehen zu lassen.</p>",
        "content_encoded": "<p>Vektor-Grafiken haben für Logos, Diagramme und Symbole die fantastische Eigenschaft, bei beliebig hoher Auflösung relativ kleine Dateigrößen zu verursachen. Und hier folgen ein paar Kniffe, um SVG-Grafiken mit der Hilfe von CSS sowohl am Bildschirm als auch im Druck gut aussehen zu lassen.</p>\n<!-- more -->\n<p id=\"more\">Seitdem <a href=\"https://caniuse.com/svg\" rel=\"nomention\">die Unterstützung von SVG-Vektor-Grafiken in jedem modernen Browser kein Problem mehr darstellt</a>, ist der Siegeszug von SVG-Grafiken unaufhaltsam. Ob via <code>&lt;img&gt;</code>, als CSS-Hintergrundbild, oder direkt in ein HTML-Dokument eingebettet: kleine Symbole wie komplexe Grafiken können so responsiv auf den Bildschirm gezaubert werden. Eine <a href=\"https://css-tricks.com/using-svg/\">Einleitung in das Thema „SVG“ bei CSS-Tricks</a> erklärt die grundsätzlichen Handgriffe.</p>\n<p>Fast immer wird eine SVG-Grafik mit einem Vektor-Grafikprogramm (wie zum Beispiel dem kostenlosen <a href=\"https://inkscape.org/\">Inkscape</a>) erzeugt. Dabei wird oft vergessen, dass SVG-Dateien eigentlich nur aus XML bestehen, das mit <a href=\"https://developer.mozilla.org/en-US/docs/Web/SVG\" title=\"Dokumentation zum manuellen Erstellen von SVG-Grafiken\">Hilfe eines Text-Editors bearbeitet werden kann</a>.</p>\n<p>Interessanterweise kann <a href=\"https://developer.mozilla.org/en-US/docs/Web/SVG/Applying_SVG_effects_to_HTML_content\">innerhalb eines SVG-Dokuments auch CSS verwendet werden</a>. Und mit CSS können wir auch <strong>Media-Queries</strong> einsetzen.</p>\n<p>Höchste Zeit, den Text-Editor auszupacken und unserem SVG zusätzliches CSS überzuhelfen!</p>\n<h2 id=\"ein-einfaches-print-stylesheet\">Ein einfaches Print-Stylesheet</h2>\n<p>Ein einfaches Stylesheet, um beim Druck von HTML- und SVG-Dokumenten im Browser störendes Beiwerk wie die automatisch vom Browser erzeugte Kopf- und Fußzeile loszuwerden, sieht wie folgt aus:</p>\n<pre><code class=\"language-css\"><i>@page</i> {\n  <b>margin</b>: 0;\n  <b>padding</b>: 0;\n}\n<i>@page</i> :footer {\n  display: none\n}\n<i>@page</i> :header {\n  display: none\n}\n</code></pre>\n<p>Genau dieses CSS lässt sich mit einem <code>&lt;style&gt;</code>-Tag direkt in die SVG-Grafik einbauen. Dazu kann man an jeder beliebigen Stelle im Dokument ein solches Tag einfügen, und dann CSS einfügen:</p>\n<pre><code class=\"language-xml\"><b>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;</b>\n&lt;<i>svg</i> <var>version</var>=&quot;<kbd>1.1</kbd>&quot; <var>xmlns</var>=&quot;<kbd>http://www.w3.org/2000/svg</kbd>&quot;&gt;\n  &lt;<i>title</i>&gt;…&lt;/<i>title</i>&gt;\n  &lt;<i>style</i>&gt;\n  @page {\n    margin: 0;\n    padding: 0;\n  }\n  @page :footer {\n    display: none\n  }\n  @page :header {\n    display: none\n  }\n  &lt;/<i>style</i>&gt;\n  <u>&lt;!-- your svg here --&gt;</u>\n&lt;/<i>svg</i>&gt;\n</code></pre>\n<p>Natürlich könnt ihr auch jede andere Media-Query in das Dokument einfügen. Zu beachten: Beim Re-Import in ein Grafik-Programm kann dies möglicherweise nicht mehr korrekt interpretiert werden, und eure Grafik wirkt etwas verspielt. Uns interessiert aber sowieso nur das Ergebnis im Browser.</p>\n<h2 id=\"druckvorschau\">Druckvorschau</h2>\n<p>Um die Idee weiterzutreiben, kann man für eine SVG-Grafik im DIN A4 eine praktische Druckvorschau mitliefern. Dazu bauen wir uns ein Dokument mit den entsprechenden Abmessungen von 210×297 Millimetern und erzeugen ein Rechteck, dass exakt dem Seitenrand folgt:</p>\n<pre><code class=\"language-xml\"><b>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;</b>\n&lt;<i>svg</i> <var>width</var>=&quot;<kbd>210mm</kbd>&quot; <var>height</var>=&quot;<kbd>297mm</kbd>&quot; <var>viewBox</var>=&quot;<kbd>0 0 210 297</kbd>&quot; <var>version</var>=&quot;<kbd>1.1</kbd>&quot; <var>xmlns</var>=&quot;<kbd>http://www.w3.org/2000/svg</kbd>&quot;&gt;\n  &lt;<i>rect</i> <var>x</var>=&quot;<kbd>0</kbd>&quot; <var>y</var>=&quot;<kbd>0</kbd>&quot; <var>width</var>=&quot;<kbd>210</kbd>&quot; <var>height</var>=&quot;<kbd>297</kbd>&quot; /&gt;\n  <u>&lt;!-- your svg here --&gt;</u>\n&lt;/<i>svg</i>&gt;\n</code></pre>\n<p>Dieses Rechteck ist nun sowohl auf dem Bildschirm als auch im Druck zu sehen. Interessanterweise können wir so ziemlich jedem SVG-Element mittels <code>class</code> und <code>id</code> Attribute mitgeben, die wir (wie aus HTML gewohnt) CSS zuweisen können.</p>\n<p>In diesem Fall wollen wir die eigentliche Seite weiß und den nicht druckbaren Bereich grau darstellen. Nicht zuletzt zeigt eine gepunktete Linie die Blattgrenze an:</p>\n<pre><code class=\"language-xml\"><b>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;</b>\n&lt;<i>svg</i> <var>width</var>=&quot;<kbd>210mm</kbd>&quot; <var>height</var>=&quot;<kbd>297mm</kbd>&quot; <var>viewBox</var>=&quot;<kbd>0 0 210 297</kbd>&quot; <var>version</var>=&quot;<kbd>1.1</kbd>&quot; <var>xmlns</var>=&quot;<kbd>http://www.w3.org/2000/svg</kbd>&quot;&gt;\n  &lt;<i>title</i>&gt;…&lt;/<i>title</i>&gt;\n  &lt;<i>style</i>&gt;\n  svg {\n    margin: 1em auto;\n    background-color: #ddd;\n  }\n  .page {\n    stroke: #999;\n    stroke-dasharray: 0.5, 0.5;\n    fill: #fff;\n    display: inline;\n  }\n  &lt;/<i>style</i>&gt;\n  &lt;<i>rect</i> <var>class</var>=&quot;<kbd>page</kbd>&quot; <var>x</var>=&quot;<kbd>0</kbd>&quot; <var>y</var>=&quot;<kbd>0</kbd>&quot; <var>width</var>=&quot;<kbd>210</kbd>&quot; <var>height</var>=&quot;<kbd>297</kbd>&quot; /&gt;\n  <u>&lt;!-- your svg here --&gt;</u>\n&lt;/<i>svg</i>&gt;\n</code></pre>\n<p>Damit wird unsere Kosmetik nun nicht im Druck zu Gesicht bekommen, unterscheiden wir noch zwischen Screen- und Print-Styles:</p>\n<pre><code class=\"language-xml\"><b>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;</b>\n&lt;<i>svg</i> <var>width</var>=&quot;<kbd>210mm</kbd>&quot; <var>height</var>=&quot;<kbd>297mm</kbd>&quot; <var>viewBox</var>=&quot;<kbd>0 0 210 297</kbd>&quot; <var>version</var>=&quot;<kbd>1.1</kbd>&quot; <var>xmlns</var>=&quot;<kbd>http://www.w3.org/2000/svg</kbd>&quot;&gt;\n  &lt;<i>title</i>&gt;…&lt;/<i>title</i>&gt;\n  &lt;<i>style</i>&gt;\n  @page {\n    margin: 0;\n    padding: 0;\n  }\n  @page :footer {\n    display: none\n  }\n  @page :header {\n    display: none\n  }\n  .page {\n    display: none;\n  }\n  @media screen {\n    svg {\n      margin: 1em auto;\n      background-color: #ddd;\n    }\n    .page {\n      stroke: #999;\n      stroke-dasharray: 0.5, 0.5;\n      fill: #fff;\n      display: inline;\n    }\n  }\n  &lt;/<i>style</i>&gt;\n  &lt;<i>rect</i> <var>class</var>=&quot;<kbd>page</kbd>&quot; <var>x</var>=&quot;<kbd>0</kbd>&quot; <var>y</var>=&quot;<kbd>0</kbd>&quot; <var>width</var>=&quot;<kbd>210</kbd>&quot; <var>height</var>=&quot;<kbd>297</kbd>&quot; /&gt;\n  <u>&lt;!-- your svg here --&gt;</u>\n&lt;/<i>svg</i>&gt;\n</code></pre>\n<p>Fertig ist die <a href=\"https://journal.3960.org/posts/2021-12-04-print-stylesheets-fuer-svg-grafiken/orbit.svg\">SVG-Grafik mit Media Queries</a> (in diesem Falle ein Spielbrett für Raumschiffe) – sowohl für die Ausgabe am Bildschirm als auch den Druck vorbereitet.</p>\n<h2 id=\"weitere-ideen\">Weitere Ideen</h2>\n<p>Die Kombination aus SVG und CSS kann man natürlich noch weiter treiben. Da so ziemlich jede Media-Query im Browser für SVGs funktioniert, können auch Anpassungen an den <a href=\"https://journal.3960.org/posts/2020-06-15-css-variables-dark-mode/\">Dark Mode</a> oder für verschiedene Monitor-Größen direkt im SVG eingebettet werden.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Print-Stylesheets%E2%80%A6%20f%C3%BCr%20SVG-Grafiken&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2021-12-04-print-stylesheets-fuer-svg-grafiken%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2021-12-04-print-stylesheets-fuer-svg-grafiken/",
        "pubDate": "Sat, 04 Dec 2021 18:23:43 +0100",
        "atom_published": "2021-12-04T18:23:43+01:00",
        "atom_updated": "2021-12-04T18:23:43+01:00",
        "guid": "user/posts/2021-12-04-print-stylesheets-fuer-svg-grafiken/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "SVG",
          "CSS",
          "Idee",
          "Programmierung",
          "Webdevelop"
        ]
      },
      {
        "title": "Merge in einen Git-Branch – abgekürzt",
        "description": "<p>Ob man nun <a href=\"https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow\">Gitflow</a>, <a href=\"https://guides.github.com/introduction/flow/\">GitHub Flow</a> oder ein selbstausgedachte Abwandlung dieser Modelle verwendet: Einige dieser Modelle benötigen einen Weg, um einen Branch in einen anderen Branch zu mergen. Dieser Handgriff ist in Git mehrteilig – und teilweise etwas nervig.</p>",
        "content_encoded": "<p>Ob man nun <a href=\"https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow\">Gitflow</a>, <a href=\"https://guides.github.com/introduction/flow/\">GitHub Flow</a> oder ein selbstausgedachte Abwandlung dieser Modelle verwendet: Einige dieser Modelle benötigen einen Weg, um einen Branch in einen anderen Branch zu mergen. Dieser Handgriff ist in Git mehrteilig – und teilweise etwas nervig.</p>\n<!-- more -->\n<p id=\"more\">Git sieht dafür den Weg vor, in den Ziel-Branch zu wechseln (z.B. <code>main</code>), und vom Quell-Branch (z.B. <code>feature/XYZ</code>) mittels <code>git merge</code> zu ziehen. Das ist in der Regel eine sehr gute Methode, um Fehlbedienungen zu vermeiden.</p>\n<p>Bei vielen Git-Workflows wird nun aber davon ausgegangen, dass im Ziel-Branch nicht direkt gearbeitet werden soll. Es ist also sehr wichtig, nach Abschluss des Merges wieder in den Quell-Branch zu wechseln, um dort versehentliche Commits zu vermeiden. Die verschiedenen <strong>Branch-Wechsel</strong> bedürfen also einiger Konzentration.</p>\n<p>Zudem ist die <strong>Schreibarbeit</strong> für einen solchen Merge auf der Kommandozeile etwas umfangreicher, will man nichts übersehen und vor allen Dingen alle Zwischenschritte auch auf dem Repo-Server bzw. <code>origin</code> bekannt machen:</p>\n<pre><code class=\"language-bash\"><em class=\"bang\">$ </em><span class=\"cmd\">git push</span>\n<em class=\"bang\">$ </em><span class=\"cmd\">git checkout main</span>\n<em class=\"bang\">$ </em><span class=\"cmd\">git pull</span>\n<em class=\"bang\">$ </em><span class=\"cmd\">git merge feature/XYZ</span>\n<em class=\"bang\">$ </em><span class=\"cmd\">git push</span>\n<em class=\"bang\">$ </em><span class=\"cmd\">git checkout feature/XYZ</span>\n</code></pre>\n<p>Tatsächlich gibt es für Gitflow die <a href=\"https://danielkummer.github.io/git-flow-cheatsheet/\">Gitflow-CLI-Tools</a>, die genau dieses Problem lösen:</p>\n<pre><code class=\"language-bash\"><em class=\"bang\">$ </em><span class=\"cmd\">git flow feature finish feature/XYZ</span>\n</code></pre>\n<p>Dummerweise haben diese Tools den Nachteil, dass das Ziel der Operation fest verdrahtet ist – und außerdem die Installation der Tools voraussetzt. In der Regel scheitert es bei mir daran, dass in dem Projekt gar nicht Gitflow verwendet wird. <span class=\"emoji emoji--1f609\" title=\";)\">&#x1F609;</span></p>\n<p>Der Wunsch ist also: Ein leichtgewichtiges, flexibles Tool, dass sicher einen Quell-Branch in einen Ziel-Branch merged.</p>\n<h2 id=\"bash-aliases\">Bash Aliases</h2>\n<p>Nicht zuletzt durch die beeindruckende Sammlung von <a href=\"https://linuxize.com/post/how-to-create-bash-aliases/\">Bash Aliases</a> meines Kollegen Marty kam ich auf die Idee, für meine Wünsche ebenfalls einen Bash-Alias zu schreiben. Analog zu <a href=\"https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases\">Git Aliases</a> (zu denen ich meine <a href=\"https://journal.3960.org/posts/2018-07-19-git-werkzeugkiste/\">persönliche Sammlung von Git Aliases</a> angelegt habe) kann man mittels Bash-Aliases neue Befehle für die Bash erzeugen.</p>\n<p>Im einfachsten Fall sind Bash-Aliases Aufrufe, die komplizierte Einzeiler ersetzen. Meine Idee war etwas komplexer, so dass ich stattdessen eine <a href=\"https://linuxize.com/post/bash-functions/\">Bash Function</a> verwendet habe, um meine Idee umzusetzen. Ich hatte die folgende Vorstellung:</p>\n<ol>\n<li>Ich starte in dem Quell-Branch, den ich mergen möchte,</li>\n<li>und gebe den Ziel-Branch ein.</li>\n<li>Es gibt eine kurze Rückfrage, ob ich mich nicht geirrt habe – und eine Überprüfung ob die Operation wirklich Sinn macht.</li>\n<li>Danach wird der Ziel-Branch ausgecheckt, aktualisiert, und der Quell-Branch (kommentarlos) via Merge in den Ziel-Branch gezogen.</li>\n<li>Schlussendlich wird zurück auf den Quell-Branch gewechselt.</li>\n</ol>\n<p>Sollten Merge Conflicts auftreten, würde das Programm abbrechen und Gelegenheit zur Korrektur des Konflikts geben.</p>\n<p>Das finale Script (die aktuellste Version findet sich auch in <a href=\"https://gist.github.com/fboes/4e404de6b0a2056922707eb708aedfe8\">meinen öffentlichen <code>.bash_aliases</code></a>) sieht nun wie folgt aus:</p>\n<pre><code class=\"language-bash\"><u># Put me in `.bash_aliases`, works like an alias.</u>\ngit-merge-to() {\n  TARGET_BRANCH=develop\n  <i>if</i> [[ <kbd>&quot;${1}&quot;</kbd> ]]; <i>then</i>\n    TARGET_BRANCH=<var>${1}</var>\n  <i>fi</i>\n\n  FEATURE_BRANCH=$(git branch | sed <em>-n</em> <em>-e</em> <kbd>'s/^\\* \\(.*\\)/\\1/p'</kbd>)\n\n  <i>if</i> [[ ! <kbd>&quot;${FEATURE_BRANCH}&quot;</kbd> ]]; <i>then</i>\n    <b>echo</b> <em>-e</em> <kbd>&quot;\\e[91mERROR\\e[0m: No git branch found&quot;</kbd>\n    return 4\n  <i>fi</i>\n  <i>if</i> [[ <kbd>&quot;${FEATURE_BRANCH}&quot;</kbd> =~ ^(main|master|develop|preview)$ ]]; <i>then</i>\n    <b>echo</b> <em>-e</em> <kbd>&quot;\\e[91mERROR\\e[0m: Branch ${FEATURE_BRANCH} is not a mergable branch&quot;</kbd>\n    git branch\n    return 2\n  <i>fi</i>\n  <i>if</i> [[ <kbd>&quot;${FEATURE_BRANCH}&quot;</kbd> == <kbd>&quot;${TARGET_BRANCH}&quot;</kbd> ]]; <i>then</i>\n    <b>echo</b> <em>-e</em> <kbd>&quot;\\e[91mERROR\\e[0m: Branches are identical&quot;</kbd>\n    return 3\n  <i>fi</i>\n\n  <b>echo</b> <em>-en</em> <kbd>&quot;Merge branch \\e[94m${FEATURE_BRANCH}\\e[0m into \\e[94m${TARGET_BRANCH}\\e[0m? [yn] &quot;</kbd>\n  read CONFIRM\n  <i>if</i> [[ ! <kbd>&quot;${CONFIRM}&quot;</kbd> =~ ^(y|Y|yes|Yes)$ ]]; <i>then</i>\n    <b>echo</b> <kbd>&quot;Cancelled&quot;</kbd>\n    return 1\n  <i>fi</i>\n\n  git push\n  git checkout <var>${TARGET_BRANCH}</var>\n  git pull\n  git merge <var>${FEATURE_BRANCH}</var> <em>--no-edit</em>\n  git push\n  git checkout <var>${FEATURE_BRANCH}</var>\n}\n</code></pre>\n<p>Sobald dieses Script in der <code>.bash_aliases</code> eingetragen ist, kann man es auf dem lokalen Rechner von jedem Ort aus starten:</p>\n<pre><code class=\"language-shell\"><em class=\"bang\">$ </em><span class=\"cmd\">git-merge-to develop</span>\nMerge branch feature/web-components into develop? [yn] y\n  Everything up-to-date\n  Switched to branch <kbd>'develop'</kbd>\n  Your branch is up-to-date with <kbd>'origin/develop'</kbd>.\n  Already up-to-date.\n  Merge made by the <kbd>'recursive'</kbd> strategy.\n  Writing objects: 100% (6/6), 578 bytes | 578.00 KiB/s, <i>done</i>.\n  Switched to branch <kbd>'feature/web-components'</kbd>\n  Your branch is up-to-date with <kbd>'origin/feature/web-components'</kbd>.\n$\n</code></pre>\n<p>Netterweise funktioniert auch die Auto-Vervollständigung des Befehles mittels der Tabulatoren-Taste, so dass ein <code>git-</code> und <kbd>TAB</kbd> den Befehl anzeigt.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Merge%20in%20einen%20Git-Branch%20%E2%80%93%20abgek%C3%BCrzt&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2021-08-27-merge-einen-git-branch-abgekuerzt%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2021-08-27-merge-einen-git-branch-abgekuerzt/",
        "pubDate": "Fri, 27 Aug 2021 18:07:49 +0200",
        "atom_published": "2021-08-27T18:07:49+02:00",
        "atom_updated": "2021-08-27T19:29:27+02:00",
        "guid": "user/posts/2021-08-27-merge-einen-git-branch-abgekuerzt/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Git",
          "Programmierung",
          "Webdevelop"
        ]
      },
      {
        "title": "Pragmatisches CSS für AMP-Layouts",
        "description": "<p><img src=\"https://journal.3960.org/posts/2020-08-30-pragmatisches-css-fuer-amp-layouts/amp-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Mit <a href=\"https://amp.dev/\">Accelerated Mobile Pages</a> hat eine inzwischen nur noch lose an Google angekoppelte Stiftung einen Standard verabschiedet, der das Internet deutlich schneller machen soll – vor allen Dingen im mobilen Bereich. Die Einschränkungen im Bereich Layout (u.a. in Bezug auf CSS) sind für Entwickler aber eine gewisse Herausforderung.</p>\n<p>Zum Glück gibt es <a href=\"https://sass-lang.com/\">SASS</a> – damit können wir ein bestehendes Layouts für eine „konventionelle“ Site pragmatisch für AMP übernehmen und anpassen, ohne ein komplett eigenständiges AMP-Layout bauen zu müssen.</p>",
        "content_encoded": "<p><img src=\"https://journal.3960.org/posts/2020-08-30-pragmatisches-css-fuer-amp-layouts/amp-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Mit <a href=\"https://amp.dev/\">Accelerated Mobile Pages</a> hat eine inzwischen nur noch lose an Google angekoppelte Stiftung einen Standard verabschiedet, der das Internet deutlich schneller machen soll – vor allen Dingen im mobilen Bereich. Die Einschränkungen im Bereich Layout (u.a. in Bezug auf CSS) sind für Entwickler aber eine gewisse Herausforderung.</p>\n<p>Zum Glück gibt es <a href=\"https://sass-lang.com/\">SASS</a> – damit können wir ein bestehendes Layouts für eine „konventionelle“ Site pragmatisch für AMP übernehmen und anpassen, ohne ein komplett eigenständiges AMP-Layout bauen zu müssen.</p>\n<!-- more -->\n<p id=\"more\">Dazu muss man wissen, wie <abbr title=\"Accelerated Mobile Pages\">AMP</abbr> dafür sorgt, dass die in AMP geschriebenen Seiten schnell werden:</p>\n<ul>\n<li>AMP verbietet bestimmte HTML-Tags, die ansonsten Entwickler und Site-Betreiber dazu verleiten, die Seite mit nicht-performanten Code zuzupflastern (u.a. <code>&lt;script&gt;</code>, <code>&lt;iframe&gt;</code>).</li>\n<li>AMP liefert <em>neue</em> HTML-Tags (in Form von <a href=\"https://developer.mozilla.org/de/docs/Web/Web_Components\">Web Compoments</a>), die immer wiederkehrende HTML-Konstrukte performant und responsiv umsetzen.</li>\n<li>Nicht zuletzt setzt AMP Größenbeschränkungen durch – unter anderem die <a href=\"https://amp.dev/documentation/guides-and-tutorials/develop/style_and_layout/\">Beschränkung von CSS auf 75kB</a>.</li>\n</ul>\n<p>Für die Verwendung von AMP gibt es zwei Strategien:</p>\n<ol>\n<li>Die eigene Website wird nur noch als AMP-Seiten ausgegeben, oder…</li>\n<li>…die <a href=\"https://amp.dev/documentation/guides-and-tutorials/start/create/prepare_for_discovery/\">reguläre Website erhält ein AMP-Duplikat</a>, d.h. die Inhalte werden gedoppelt, aber einmal in regulärem HTML und einmal in AMP-HTML erzeugt. Beide Varianten verweisen auf einander, so dass Suchmaschinen oder ggf. der Browser entscheiden kann, welche von beiden Varianten angezeigt wird.</li>\n</ol>\n<p>Wenn wir uns für die Strategie „AMP-Duplikat“ entscheiden, haben wir auf einmal zwei Template-Sets (reguläres HTML und AMP), und deswegen auch zwei verschiedene Layouts. Natürlich können wir versuchen, auf beiden Seiten auch das selbe Layout zu verwenden – aber wie schaffen wir es, die Größenbeschränkung von AMP einzuhalten, und für die eigenen AMP-Tags eigene Styles zu schreiben, ohne <em>zwei</em> verschiedene CSS-Dateien schreiben zu müssen?</p>\n<h2 id=\"sass-wird-uns-alle-retten\">SASS wird uns alle retten</h2>\n<p>Hier kommt <a href=\"https://sass-lang.com/\">SASS</a> ins Spiel: Mit diesem CSS-Preprocessor können wir nicht nur eine CSS-Datei erzeugen – wir können auch <em>zwei</em> CSS-Dateien erzeugen. Dabei machen wir uns zu nutze, dass SASS jede <code>.scss</code>-Datei ohne <code>_</code> am Anfang des Dateinamens in eine CSS-Datei kompiliert.</p>\n<p>Unser Setup ist also:</p>\n<ul>\n<li><code>styles.scss</code> enthält das Layout für die regulären HTML-Seiten, erzeugt <code>styles.css</code></li>\n<li><code>amp.scss</code> enthält das Layout für die AMP-Seiten, erzeugt <code>amp.css</code></li>\n</ul>\n<p>SASS bietet darüber hinaus an, mittels <code>@import</code> den Inhalt anderer SASS-Dateien zu inkludieren. Das machen wir uns im Falle der <code>amp.scss</code> zu nutze:</p>\n<pre><code class=\"language-scss\"><u>// amp.scss</u>\n\n<i>@import</i> <kbd>&quot;styles&quot;</kbd>;\n</code></pre>\n<p>Jetzt wird SASS die <code>amp.scss</code> zu einer <code>amp.css</code> kompilieren, die das selbe Ergebnis hat wie <code>styles.scss</code> bzw. <code>styles.css</code>.</p>\n<h2 id=\"media-queries-für-amp\">Media Queries für AMP?</h2>\n<p>Ein Konzept, mit dem jeder CSS-Entwickler vertraut ist, sind <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries\">Media Queries</a>: Als CSS-Entwickler sperren wir CSS-Regeln in diese Queries ein, um je nach Ausgabegerät bzw. -kanal ein unterschiedliches Layout zu erzwingen.</p>\n<p>Genau so denken wir nun AMP-CSS: Wir klammern in unserer SASS-Datei <code>styles.scss</code> nun Blöcke ein, die nicht (oder <em>nur</em>) in der <code>amp.css</code> auftauchen sollen. Dazu schreiben wir uns zwei kleine Mixins:</p>\n<pre><code class=\"language-scss\"><u>// styles.scss</u>\n\n<var>$amp</var>: false !default;\n\n<u>// Only output this block in AMP pages.</u>\n<i>@mixin</i> amp() {\n  <i>@if</i>(<var>$amp</var>) {\n    <i>@content</i>;\n  }\n}\n\n<u>// Only output this block in non-AMP pages.</u>\n<i>@mixin</i> no-amp() {\n  <i>@if</i>(not(<var>$amp</var>)) {\n    <i>@content</i>;\n  }\n}\n</code></pre>\n<p>Diese beiden Mixins reagieren auf eine Variable namens <code>$amp</code>. Im oberen Mixin wird der an das Mixin übergebene Inhalt nur ausgegeben, wenn <code>$amp: true</code> ist – im unteren Beispiel wird er nur ausgeben, wenn <code>$amp: false</code> ist.</p>\n<p>Davor definieren wir die Variable <code>$amp</code>. Wichtig ist hier die Verwendung von <code>!default</code>. Damit verraten wir SASS, dass der Wert dieser Variable nur <code>false</code> sein soll, wenn <code>$amp</code> nicht bereits vorher gesetzt wurde.</p>\n<p>Übrigens können wir die Mixins auch in eine eigene Datei auslagern (zum Beispiel <code>_mixins.scss</code>), das Prinzip bleibt aber das gleiche.</p>\n<p>Unsere <code>styles.scss</code> soll nun alle AMP-Regeln <em>nicht</em> ausgeben, und alle Nicht-AMP-Regeln ausgeben. Dementsprechend setzen wir <code>$amp</code> zu <code>false</code>.</p>\n<p>Derweil in der <code>amp.scss</code>:</p>\n<pre><code class=\"language-scss\"><u>// amp.scss</u>\n\n<var>$amp</var>: true;\n<i>@import</i> <kbd>&quot;styles&quot;</kbd>;\n</code></pre>\n<p>Hier setzen wir also <code>$amp</code> auf <code>true</code> und holen uns danach die Inhalte von <code>styles.scss</code> – hier wird das dort definierte <code>$amp</code> überschrieben, und danach die <code>styles.scss</code> von SASS ganz normal weiter kompiliert. Damit haben wir die selben Inhalte, aber die Mixins werden sich nun genau umgekehrt verhalten.</p>\n<h2 id=\"media-queries-für-amp-1\">Media Queries für AMP!</h2>\n<p>Damit können wir nun alle CSS-Regeln für unsere regulären HTML-Seiten <em>und</em> AMP-Seiten in der <code>style.scss</code> schreiben. An den Stellen, wo eine Unterscheidung zwischen regulären und AMP-CSS notwendig wird, verwenden wir die Mixins:</p>\n<pre><code class=\"language-scss\"><u>// styles.scss</u>\n\n<u>// AMP &amp; regular CSS</u>\n<i>nav </i> {\n  <i>a</i> {\n    <b>color</b>: red;\n  }\n}\n\n<u>// Hide rule for AMP CSS</u>\n<i>@include</i> no-amp() {\n  <i>header</i> {\n    <b>background-color</b>: black;\n    <b>color</b>: white;\n  }\n}\n\n<i>footer</i> {\n  <b>background-color</b>: black;\n  <b>color</b>: white;\n  <u>// Hide partial rule for AMP CSS</u>\n  <i>@include</i> no-amp() {\n    <b>font-size</b>: 0.6em;\n  }\n}\n\n<u>// Hide rule for non-AMP CSS</u>\n<i>@include</i> amp() {\n  <i>amp-img</i> {\n    <b>border</b>: 1px solid red;\n  }\n}\n</code></pre>\n<p>Damit müssen viele CSS-Regeln nur einmal definiert werden, und werden in beiden Layouts identisch ausgegeben. Dieser Artikel zum Beispiel verwendet <a href=\"https://journal.3960.org/posts/2020-08-30-pragmatisches-css-fuer-amp-layouts/index.html\">im regulären HTML</a> und <a href=\"https://journal.3960.org/posts/2020-08-30-pragmatisches-css-fuer-amp-layouts/amp.html\">AMP-HTML</a> weitestgehend identisches CSS, und kann trotzdem auf die Besonderheiten und Kompressionswünsche von AMP eingehen.</p>\n<h2 id=\"letzter-schritt-ampcss-montieren\">Letzter Schritt: <code>amp.css</code> montieren</h2>\n<p>Das so produzierte <code>amp.css</code> dürft ihr <a href=\"https://amp.dev/documentation/guides-and-tutorials/develop/style_and_layout/\">gemäß den Vorschriften von AMP für die Integration von CSS</a> nicht als separates Stylesheet laden. Stattdessen erreicht AMP seine hohe Geschwindigkeit durch die Tatsache, dass das gesamte AMP-CSS in die HTML-Seite eingebunden werden muss. Hier müssen wir also nur eine kleine Template-Anpassung in den AMP-Templates durchführen, die den Inhalt der <code>amp.css</code> im AMP-Tag <code>&lt;style amp-custom&gt;</code> am Beginn einer jeden AMP-Seite montiert.</p>\n<pre><code class=\"language-html\">&lt;!doctype html&gt;\n&lt;<i>head</i>&gt;\n  …\n  &lt;<i>style</i> amp-custom&gt;\n    /* Content of amp.css goes here. */\n  &lt;/<i>style</i>&gt;\n  …\n&lt;/<i>head</i>&gt;\n</code></pre>\n<p>Übrigens empfiehlt es sich spätestens hier, alle überflüssigen Leerzeichen aus der <code>amp.css</code> zu entfernen, um weiter Platz zu sparen. Eine Ersetzung mit einem regulären Ausdruck (wie z.B. <code>/\\n\\s+/</code>) lässt hier weiter die Luft raus, oder ihr setzt für den SASS-Compiler den Schalter <code>compressed</code>.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Pragmatisches%20CSS%20f%C3%BCr%20AMP-Layouts&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2020-08-30-pragmatisches-css-fuer-amp-layouts%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2020-08-30-pragmatisches-css-fuer-amp-layouts/",
        "pubDate": "Sun, 30 Aug 2020 18:37:17 +0200",
        "atom_published": "2020-08-30T18:37:17+02:00",
        "atom_updated": "2020-08-31T07:38:32+02:00",
        "guid": "user/posts/2020-08-30-pragmatisches-css-fuer-amp-layouts/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Webdevelop",
          "Web-Components",
          "Blog",
          "CSS",
          "Idee",
          "Programmierung"
        ]
      },
      {
        "title": "Die kleinste PHP-Templating-Engine der Welt",
        "description": "<p>Die Trennung von Logik und Ausgabe in einer (Web-)Anwendung ist eines der fundamentalen Prinzipien, um als Programmierer seine geistige Gesundheit zu behalten. Für die Ausgabe wird dabei eine zumeist einfache (Template-)Sprache verwendet, die verhindern soll, dass zu viel Logik in die Ausgabe wandert.</p>\n<p>In PHP wird dies in der Regel von Template-Engines wie <a href=\"https://twig.symfony.com/\">Twig</a> oder <a href=\"https://www.smarty.net/\">Smarty</a> gelöst. Aber warum benötigen wir eigentlich eine Template-Engine? Könnte es sein, dass PHP ohne weitere Zusätze auch eine ganz passable Lösung für Templating parat hält?</p>",
        "content_encoded": "<p>Die Trennung von Logik und Ausgabe in einer (Web-)Anwendung ist eines der fundamentalen Prinzipien, um als Programmierer seine geistige Gesundheit zu behalten. Für die Ausgabe wird dabei eine zumeist einfache (Template-)Sprache verwendet, die verhindern soll, dass zu viel Logik in die Ausgabe wandert.</p>\n<p>In PHP wird dies in der Regel von Template-Engines wie <a href=\"https://twig.symfony.com/\">Twig</a> oder <a href=\"https://www.smarty.net/\">Smarty</a> gelöst. Aber warum benötigen wir eigentlich eine Template-Engine? Könnte es sein, dass PHP ohne weitere Zusätze auch eine ganz passable Lösung für Templating parat hält?</p>\n<!-- more -->\n<p id=\"more\">Die Hauptidee hinter den meisten Template-Engines ist:</p>\n<ul>\n<li>Template-Engines bieten mit Vorsatz wenig Logik, um die Komplexität einer Programmierung an einer anderen Stelle zu bündeln, und Fehlerquellen zu vermeiden.</li>\n<li>Dafür bieten Template-Engines einfach zu bedienende Kontrollstrukturen wie <code>if</code>, <code>while</code> und <code>for</code>.</li>\n<li>Template-Engines kümmern sich um die korrekte Ausgabe von Variablen (Quoting / Escaping).</li>\n<li>Template-Engines erlauben die mehrfache Verwendung von Template-Teilen (Snippets / Partials) in verschiedenen Kontexten.</li>\n</ul>\n<p>Ein ausgezeichnetes Beispiel für die Philosophie von Template-Engines ist <a href=\"http://mustache.github.io/\">Mustache</a>, das ein absolutes Minimum von Methoden anbietet, um Komplexität zu verhindern.</p>\n<p>Aber brauchen wir in PHP den Overhead einer Template-Engine wirklich? Oder können wir nicht mit ein bisschen Disziplin direkt mit PHP alle unsere Template-Bedürfnisse stillen – ohne uns in Abhängigkeit zu einer weiteren Software-Bibliothek zu begeben?</p>\n<p>Tatsächlich bietet PHP (auch aufgrund seiner <a href=\"https://www.php.net/manual/de/intro-whatis.php\">Genese</a>) sehr gute Möglichkeiten, Templating damit zu betreiben.</p>\n<h2 id=\"dateiendungen\">Dateiendungen</h2>\n<p>Template-Engines legen ihre Templates in der Regel in einen eigenen Ordner mit einer eigenen Dateiendung ab. In der Kombination <a href=\"https://symfony.com/doc/current/templates.html#creating-templates\">Symfony mit Twig</a> liegen Templates im Projekt in einem Ordner <code>/templates</code> und haben die Dateiendung <code>.twig</code>.</p>\n<p>Tatsächlich hindert uns nichts daran, unsere PHP-Templates ebenfalls in einem <code>/templates</code>-Verzeichnis abzulegen. Als Datei-Endung verwenden wir dabei (analog zu Twig und zur Erhaltung der geistigen Gesundheit):</p>\n<div class=\"table-wrapper\"><table>\n<thead>\n<tr>\n<th>Template-Ausgabe</th>\n<th>Datei-Endung</th>\n<th>(MIME-Type)</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>HTML-Dateien</td>\n<td class=\"tag-code\"><code>*.html.php</code></td>\n<td class=\"tag-code\"><code>text/html</code></td>\n</tr>\n<tr>\n<td>XML-Dateien</td>\n<td class=\"tag-code\"><code>*.xml.php</code></td>\n<td><code>text/xml</code> oder <code>application/xml</code></td>\n</tr>\n<tr>\n<td>JSON-Dateien</td>\n<td class=\"tag-code\"><code>*.json.php</code></td>\n<td class=\"tag-code\"><code>application/json</code></td>\n</tr>\n<tr>\n<td>Text-Dateien</td>\n<td class=\"tag-code\"><code>*.txt.php</code></td>\n<td class=\"tag-code\"><code>text/plain</code></td>\n</tr>\n<tr>\n<td>CSV-Dateien</td>\n<td class=\"tag-code\"><code>*.csv.php</code></td>\n<td class=\"tag-code\"><code>text/csv</code></td>\n</tr>\n</tbody></table></div>\n<p>Diese Liste lässt sich natürlich beliebig um neue Dateitypen erweitern. Wichtig ist nur, dass die Dateien auf PHP enden, damit sie einerseits von einem Code-Editor als PHP-Dateien erkannt werden, andererseits beim irrtümlichen Aufruf über einen Webserver auch als PHP ausgeführt werden, und nicht irgendwelche PHP-Geheimnisse verraten.</p>\n<p>Nach alter Sitte benennen wir das Template nach dem Controller. So wird das HTML-Template für den Index-Controller <code>index.html.php</code>, für den User-Controller <code>user.html.php</code> benannt. Das XML-Template für den Index-Controller ist dann entsprechend <code>index.xml.php</code>.</p>\n<p>Templates, die keinen Controller zugeordnet werden können (sogenannte Snippets oder Partials) beginnen mit einem <code>_</code>, also zum Beispiel <code>_meta.html.php</code>. Was es mit diesen Dateien auf sich hat klären wir weiter unten.</p>\n<h2 id=\"variablen-ausgabe\">Variablen-Ausgabe</h2>\n<p>Wir nutzen die Fähigkeit von PHP, dass jede Anweisung, die nicht in <a href=\"https://www.php.net/manual/de/language.basic-syntax.phptags.php\">PHP-Tags</a> (<code>&lt;?php ?&gt;</code>) steht, nicht als PHP verstanden wird, sondern direkt an den Browser zurückgegeben wird. Damit können wir innerhalb unserer Template-Dateien uns tatsächlich auf die Ausgabe konzentrieren:</p>\n<pre><code class=\"language-html\">&lt;!DOCTYPE html&gt;\n&lt;<i>html</i> <var>xmlns</var>=&quot;<kbd>http://www.w3.org/1999/xhtml</kbd>&quot; <var>lang</var>=&quot;<kbd>en</kbd>&quot;&gt;\n&lt;<i>head</i>&gt;\n&lt;/<i>head</i>&gt;\n&lt;<i>body</i>&gt;\n  <u>&lt;!-- I am valid PHP --&gt;</u>\n&lt;/<i>body</i>&gt;\n</code></pre>\n<p>Die wichtigste Funktion eines Templates ist die Ausgabe von Variablen, die im Controller vorbereitet wurden. Dazu öffnen wir (wie in jeder Template-Sprache) einen Kommandoblock, und führen darin das Kommando zum Ausgeben von Variablen aus.</p>\n<pre><code class=\"language-html\"><u>&lt;!-- Bad idea --&gt;</u>\n&lt;<i>div</i>&gt;<b>&lt;?php echo($title) ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<p>Netterweise gibt es eine PHP-Tag, das diese Ausgabe nochmals verkürzt:</p>\n<pre><code class=\"language-html\"><u>&lt;!-- Still a bad idea --&gt;</u>\n&lt;<i>div</i>&gt;<b>&lt;?= $title ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<p>Tatsächlich sind aber beide Wege eine ganz schlechte Idee, weil wir hier sträflich das Quoting / Escaping vernachlässigt haben – eine Kernfunktionalität einer jeden Template-Engine und ein Garant für die Sicherheit unserer Web-Applikation. Im obigen Beispiel wäre es möglich, dass in <code>$title</code> HTML steckt, was direkt wieder auf der Seite ausgegeben werden kann. In den wenigsten Fällen ist dies erwünscht und setzt voraus, dass man dem Variablen-Inhalt jederzeit vertrauen kann. Beispiel gefällig?</p>\n<pre><code class=\"language-html\"><u>&lt;!-- See, a bad idea --&gt;</u>\n&lt;<i>div</i>&gt;<b>&lt;?= $_GET[&#39;search&#39;] ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<p>Praktischerweise liefert PHP die Funktion <a href=\"https://www.php.net/htmlspecialchars\"><code>htmlspecialchars</code></a> mit, die einen String für die Ausgabe in sicherem HTML umwandelt. So werden Zeichen wie <code>&lt;</code>, <code>&gt;</code> und <code>&quot;</code> sicher umgewandelt:</p>\n<pre><code class=\"language-html\"><u>&lt;!-- Better idea --&gt;</u>\n&lt;<i>div</i>&gt;<b>&lt;?= htmlspecialchars($title) ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<p>Da diese Art der Ausgabe unsere Hauptmethode sein soll, die Schreibweise aber etwas sperrig ist, bauen wir uns einen <em>Alias</em> für <code>htmlspecialchars</code>. Der Beginn der kleinsten PHP-Template-Engine der Welt:</p>\n<pre><code class=\"language-php\"><u>// Template.php</u>\n\n<u>/**\n * Alias for `htmlspecialchars`\n */</u>\n<b>function</b> html(<var>$s</var>): <b>string</b>\n{\n  <i>return</i> htmlspecialchars(<var>$s</var>);\n}\n</code></pre>\n<p>Diese Funktion kürzt uns die Schreibarbeit ab, um eine Variable sicher auszugeben, und ist ab sofort unsere Hauptfunktion zur Ausgabe von Variablen:</p>\n<pre><code class=\"language-html\"><u>&lt;!-- Best idea --&gt;</u>\n&lt;<i>div</i>&gt;<b>&lt;?= html($title) ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<h3 id=\"weiteres-quoting\">Weiteres Quoting</h3>\n<p>PHP bietet aber noch mehr Funktion für Quoting / Escaping. Unter anderem müssen wir bei der Ausgabe von Query-Parametern in URLs Zeichen wie <code>#</code> und <code>&amp;</code> umwandeln. Dafür hat PHP die Funktion <a href=\"https://www.php.net/urlencode\"><code>urlencode</code></a> parat  – in Kombination mit unserer <code>html</code>-Funktion unschlagbar:</p>\n<pre><code class=\"language-html\">&lt;<i>a</i> href=&quot;https://www.example.com?id=<b>&lt;?= html(urlencode($id)) ?&gt;</b>&quot;&gt;Test&lt;/<i>a</i>&gt;\n</code></pre>\n<p>Dann und wann müssen wir auch PHP-Variablen in JavaScript-Variablen umwandeln. Hier bietet sich die (etwas artfremde) Funktion <a href=\"https://www.php.net/json_encode\"><code>json_encode</code></a> an, die PHP-Variablen als JSON ausgibt. Damit wird die Variable nicht nur in korrekte Anführungszeichen gesetzt (die wir wiederum nicht HTML-enkodieren dürfen), sondern erlaubt sogar die Ausgabe von komplexen Variablen wie Arrays:</p>\n<pre><code class=\"language-html\">&lt;<i>script</i>&gt;\nvar data = <b>&lt;?= json_encode($data) ?&gt;</b>;\n&lt;/<i>script</i>&gt;\n</code></pre>\n<h2 id=\"bedingungen\">Bedingungen</h2>\n<p>Die wichtigsten Kontrollstrukturen für das Templating sind Bedingungen. Hier ist das <code>if</code> der wichtigste Ansprechpartner. „Echtes“ PHP mit seinen geschweiften Klammern ist dabei aber eher unübersichtlich, da zum Beispiel bei Schachtelungen unklar sein kann, welche schließende Klammer für welchen Block zuständig ist:</p>\n<pre><code class=\"language-html\"><b>&lt;?php if (!empty($title)) { ?&gt;</b>\n  &lt;<i>div</i>&gt;<b>&lt;?= html($title) ?&gt;</b>&lt;/<i>div</i>&gt;\n<b>&lt;?php } ?&gt;</b>\n</code></pre>\n<p>Praktischerweise <a href=\"https://www.php.net/manual/en/control-structures.alternative-syntax.php\">kennt PHP auch eine alternative Syntax für Kontrollstrukturen</a>, bei dem jeder Block hinter einem Kommando nicht mit einem <code>{</code>, sondern mit einem <code>:</code> beginnt – und statt mit einem <code>}</code> mit einem <code>end...;</code> beendet wird.</p>\n<pre><code class=\"language-html\"><b>&lt;?php if (!empty($title)): ?&gt;</b>\n  &lt;<i>div</i>&gt;<b>&lt;?= html($title) ?&gt;</b>&lt;/<i>div</i>&gt;\n<b>&lt;?php endif ?&gt;</b>\n</code></pre>\n<p>In den <code>if</code>-Bedingungen kann man dabei alle Operatoren und Funktionen verwenden, die PHP so kennt. Wichtig ist (wie immer), dass vor dem Verwenden einer Variable getestet wird, ob die Variable überhaupt definiert wird, was mit <code>!empty</code> oder <code>isset</code> geschehen kann.</p>\n<p>Darüber hinaus stehen natürlich auch <code>else</code> und <code>elseif</code> zur Verfügung:</p>\n<pre><code class=\"language-html\"><b>&lt;?php if (!empty($results)): ?&gt;</b>\n  &lt;<i>h4</i>&gt;Wir haben <b>&lt;?= html(count($results)) ?&gt;</b> Ergebnisse gefunden.&lt;/<i>h4</i>&gt;\n<b>&lt;?php else: ?&gt;</b>\n  &lt;<i>h4</i>&gt;Wir haben leider keine Ergebnisse gefunden.&lt;/<i>h4</i>&gt;\n<b>&lt;?php endif ?&gt;</b>\n</code></pre>\n<h2 id=\"schleifen-und-arrays\">Schleifen und Arrays</h2>\n<p>Die Ausgabe von Listen und Tabellen ist ebenfalls eine wichtige Funktion von Templates. Mittels Schleifen kann man zum Beispiel über vorher definierte Arrays iterieren – ebenfalls mit der <a href=\"https://www.php.net/manual/en/control-structures.alternative-syntax.php\">alternative Syntax für Kontrollstrukturen</a>:</p>\n<pre><code class=\"language-html\"><b>&lt;?php if (!empty($list)): ?&gt;</b>\n  &lt;<i>ul</i>&gt;\n    <b>&lt;?php foreach($list as $index =&gt; $item): ?&gt;</b>\n      &lt;<i>li</i>&gt;<b>&lt;?= html($item) ?&gt;</b>&lt;/<i>li</i>&gt;\n    <b>&lt;?php endforeach ?&gt;</b>\n  &lt;/<i>ul</i>&gt;\n<b>&lt;?php endif ?&gt;</b>\n</code></pre>\n<p>Dabei funktionieren <code>for</code>, <code>foreach</code> und <code>while</code> ganz wunderbar:</p>\n<pre><code class=\"language-html\">&lt;<i>h4</i>&gt;Lottozahlen&lt;/<i>h4</i>&gt;\n&lt;<i>ol</i>&gt;\n  <b>&lt;?php for ($i = 1; $i &lt;= 49; $i++): ?&gt;</b>\n    &lt;<i>li</i>&gt;\n      &lt;<i>input</i> <var>type</var>=&quot;<kbd>checkbox</kbd>&quot; name=&quot;lotto_<b>&lt;?= html($i) ?&gt;</b>&quot; id=&quot;lotto_<b>&lt;?= html($i) ?&gt;</b>&quot; value=&quot;1&quot; /&gt;\n      &lt;<i>label</i> for=&quot;lotto_<b>&lt;?= html($i) ?&gt;</b>&quot;&gt;<b>&lt;?= html($i) ?&gt;</b>&lt;/<i>label</i>&gt;\n    &lt;/<i>li</i>&gt;\n  <b>&lt;?php endfor ?&gt;</b>\n&lt;/<i>ol</i>&gt;\n</code></pre>\n<h2 id=\"snippets--partials\">Snippets / Partials</h2>\n<p>Um Template-Teile wie zum Beispiel einen Header, Footer oder Navigation in ein Template einzufügen, fügen wir dem Dateinamen des Template-Teils einfach einen führenden <code>_</code> hinzu, wie zum Beispiel <code>_header.xml.php</code>.</p>\n<p>Ein Template-Teil (auch als Snippet oder Partial bekannt) kann mit dem PHP eigenen <code>include</code> ins ein anderes Template eingebunden werden:</p>\n<pre><code class=\"language-html\">&lt;!DOCTYPE html&gt;\n&lt;<i>html</i> <var>xmlns</var>=&quot;<kbd>http://www.w3.org/1999/xhtml</kbd>&quot; <var>lang</var>=&quot;<kbd>en</kbd>&quot;&gt;\n&lt;<i>head</i>&gt;\n  <b>&lt;?php include(&#39;_meta.html.php&#39;) ?&gt;</b>\n&lt;/<i>head</i>&gt;\n&lt;<i>body</i>&gt;\n  <b>&lt;?php include(&#39;_header.html.php&#39;) ?&gt;</b>\n  ...\n  <b>&lt;?php include(&#39;_footer_.html.php&#39;) ?&gt;</b>\n&lt;/<i>body</i>&gt;\n</code></pre>\n<h2 id=\"übersetzung\">Übersetzung</h2>\n<p>Sogar Übersetzungen von Templates beherrscht PHP! Die in PHP vorhandene <a href=\"https://www.php.net/manual/en/book.gettext.php\">Gettext-Bibliothek</a> erlaubt es, einen String zu übergeben, für den Gettext in einem beigefügten Wörterbuch nach der korrekten Übersetzung sucht, und diesen zurückgibt.</p>\n<p>Wichtig dafür ist das Setzen der Locale in PHP mittels <a href=\"https://www.php.net/setlocale\"><code>setlocale</code></a>:</p>\n<pre><code class=\"language-php\">setlocale(LC_MESSAGES, <kbd>'de_DE'</kbd>);\n</code></pre>\n<p>Die korrekte Bedienung dieser Funktion sorgt übrigens auch für die Übersetzung von Zahlen- und Datumsformaten, wie auch Datumsbezeichnern.</p>\n<p>Die eigentlich zu übersetzenden Bezeichner können an die <a href=\"https://www.php.net/_\">Funktion <code>gettext</code> bzw. <code>_</code></a> übergeben werden.</p>\n<pre><code class=\"language-html\"><u>&lt;!-- translate to &#39;Guten Morgen!&#39; --&gt;</u>\n&lt;<i>h4</i>&gt;<b>&lt;?= html(_(&#39;Good morning!&#39;)) ?&gt;</b>&lt;/<i>h4</i>&gt;\n</code></pre>\n<p>Die dafür notwendigen Wörterbücher kann man spielend leicht selber erstellen. Die dafür notwendigen <a href=\"http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files\">PO- und MO-Dateien</a> kann man zum Beispiel mit <a href=\"https://poedit.net/\">Poedit</a> erstellen. <i>Poedit</i> ist sogar in der Lage, die gesamte PHP-Programmierung nach noch nicht katalogisierten, übersetzbaren Strings zu durchsuchen.</p>\n<p>Wenn wir mit Variablen-Ersetzung arbeiten, wird die Konstruktion etwas schwieriger:</p>\n<pre><code class=\"language-html\">&lt;<i>div</i>&gt;<b>&lt;?= html(vsprintf(_(&#39;There are %d results&#39;), [$count])) ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<p>Da wir so ziemlich jeden hardcodierten Text übersetzbar machen wollen, können wir unserer simplen Template-Engine eine weitere Funktion als Abkürzung hinzufügen:</p>\n<pre><code class=\"language-php\"><u>// Template.php</u>\n\n<u>/**\n * Alias for `vsprintf`, but with HTML escaping and translation\n */</u>\n<b>function</b> _html(<b>string</b> <var>$format</var>, <b>array</b> <var>$args</var> = []): <b>string</b>\n{\n  <i>return</i> htmlspecialchars(<var>$args</var>\n    ? vsprintf(_(<var>$format</var>), <var>$args</var>)\n    : _(<var>$format</var>)\n  );\n}\n</code></pre>\n<p>…was den Aufruf deutlich einfacher macht:</p>\n<pre><code class=\"language-html\">&lt;<i>h4</i>&gt;<b>&lt;?= _html(&#39;Good morning!&#39;) ?&gt;</b>&lt;/<i>h4</i>&gt;\n&lt;<i>div</i>&gt;<b>&lt;?= _html(&#39;There are %d results&#39;, [$count]) ?&gt;</b>&lt;/<i>div</i>&gt;\n</code></pre>\n<h2 id=\"variablen-dumping\">Variablen-Dumping</h2>\n<p>Für Entwickler wie auch Template-Designer ist es hilfreich, sich komplexe Variablen im Frontend zu Debugging-Zwecken ausgeben zu lassen. Dafür bietet PHP die Funktionen <a href=\"https://www.php.net/print_r\"><code>print_r</code></a> und <a href=\"https://www.php.net/var_dump\"><code>var_dump</code></a> / <a href=\"https://www.php.net/var_export\"><code>var_export</code></a>. Dummerweise ist in HTML ein solcher Dump schwer lesbar, da die Zeilenumbrüche in HTML ignoriert werden. Eine kleine Funktion für unsere Template-Engine kann auch diesen Missstand beheben:</p>\n<pre><code class=\"language-php\"><u>// Template.php</u>\n\n<u>/**\n * HTML dumper für PHP variables\n */</u>\n<b>function</b> debug(<var>$mixed</var>, <b>bool</b> <var>$extended</var> = <samp>false</samp>): <b>void</b>\n{\n  <i>echo</i>(<kbd>'&lt;pre class=&quot;debug&quot; style=&quot;margin: 1em 0; border: 1px solid red; background: #fee; color: #000; padding: 1em;&quot;&gt;'</kbd>);\n  <i>echo</i>(htmlspecialchars(<var>$extended</var>\n    ? var_export(<var>$mixed</var>, <samp>true</samp>)\n    : print_r(<var>$mixed</var>, <samp>true</samp>)\n  ));\n  <i>echo</i>(<kbd>'&lt;/pre&gt;'</kbd>);\n}\n</code></pre>\n<p>Damit können auch komplexe Variablen bis hin zu Objekten lesbar ausgegeben werden:</p>\n<pre><code class=\"language-html\"><b>&lt;?php debug($data) ?&gt;</b>\n</code></pre>\n<p>Statt einer weithin sichtbaren Debug-Ausgabe könnte man natürlich gleich auf die <a href=\"https://journal.3960.org/posts/einfachste-php-objekt-debugger-fuer-browser/\">Ausgabe von Debugging-Daten in der Browser-Console</a> ausweichen.</p>\n<h2 id=\"ausgabe-von-anderen-content-typen\">Ausgabe von anderen Content-Typen</h2>\n<p>PHP erzeugt von Haus aus eine HTML-Ausgabe. Im Browser funktioniert dies so, dass jedes PHP-Skript den Webserver anweist, als MIME-Type <code>text/html</code> zurückzugeben. Tatsächlich kann PHP aber auch ganz andere MIME-Types ausgeben. Dazu gibt es die Funktion <a href=\"https://www.php.net/header\"><code>header</code></a>, mit der PHP den Webserver anweisen kann, bestehende Header zu ändern bzw. neue zu setzen. Der für uns interessante Header ist <code>Content-Type</code>.</p>\n<p>So können wir PHP anweisen, <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types\">jeden erdenklichen MIME-Type</a> zurückzugeben.</p>\n<h3 id=\"xml\">XML</h3>\n<p>Für XML verwenden wir die Template-Endung <code>.xml.php</code>, also zum Beispiel <code>sitemap.xml.php</code>. Diese Templates unterscheiden sich tatsächlich nicht großartig von HTML-Templates, und verwenden sogar die selben Quoting/Escaping-Funktionen. Nur der zusätzliche <code>Content-Type</code>-Header muss mitgeschickt werden:</p>\n<pre><code class=\"language-xml\"><b>&lt;?php header(&#39;Content-Type: text/xml&#39;); ?&gt;</b>\n&lt;<i>urlset</i>&gt;\n  <b>&lt;?php foreach($urls as $url): ?&gt;</b>\n    &lt;<i>url</i>&gt;\n      &lt;<i>loc</i>&gt;<b>&lt;?= html($url) ?&gt;</b>&lt;/<i>loc</i>&gt;\n    &lt;/<i>url</i>&gt;\n  <b>&lt;?php endforeach ?&gt;</b>\n&lt;/<i>urlset</i>&gt;\n</code></pre>\n<h3 id=\"json\">JSON</h3>\n<p>Auch JSON kann PHP für uns ausgeben. Hierfür verwenden wir die Template-Endung <code>.json.php</code>, wie zum Beispiel <code>feed.json.php</code>, und senden wieder den korrekten <code>Content-Type</code>. Um das Quoting/Escaping in den Griff zu bekommen, bauen wir unser JSON nicht von Hand, sondern verwenden die Funktion <a href=\"https://www.php.net/json_encode\"><code>json_encode</code></a>, die auch strukturierte PHP-Variablen strukturiert und proper formatiert ausspuckt:</p>\n<pre><code class=\"language-php\">&lt;?php\n  header(<kbd>'Content-Type: application/json'</kbd>);\n  <i>echo</i>(json_encode(<var>$data</var>));\n</code></pre>\n<p>Für besonders schönes JSON kann man übrigens den Parameter <code>JSON_PRETTY_PRINT</code> mitgeben:</p>\n<pre><code class=\"language-php\">&lt;?php\n  header(<kbd>'Content-Type: application/json'</kbd>);\n  <i>echo</i>(json_encode(<var>$data</var>, JSON_PRETTY_PRINT));\n</code></pre>\n<p>Übrigens respektiert die Funktion <code>json_encode</code> tatsächlich PHP-Typen. Damit werden PHP-Integer tatsächlich als JSON-Integer ausgegeben, und PHP-Strings als JSON-Strings. Hier muss also in PHP den Unterschied zwischen <code>1</code> und <code>„1“</code> berücksichtigt werden.</p>\n<h3 id=\"hol-das-richtige-template\">Hol das richtige Template</h3>\n<p>Die obigen Beispiele zum Setzen des korrekten MIME-Types und Holen der Templates kann man natürlich auch allgemein lösen:</p>\n<pre><code class=\"language-php\"><u>// e.g. $template = 'index';</u>\n<u>// e.g. $contentType = 'html';</u>\n\n<i>switch</i> (<var>$contentType</var>) {\n  <i>case</i> <kbd>'xml'</kbd>:\n    header(<kbd>'Content-Type: text/xml'</kbd>);\n    <i>break</i>;\n  <i>case</i> <kbd>'json'</kbd>:\n    header(<kbd>'Content-Type: application/json'</kbd>);\n    <i>break</i>;\n  <i>case</i> <kbd>'txt'</kbd>:\n    header(<kbd>'Content-Type: text/plain'</kbd>);\n    <i>break</i>;\n  <i>case</i> <kbd>'csv'</kbd>:\n    header(<kbd>'Content-Type: text/csv'</kbd>);\n    <i>break</i>;\n  default:\n    <var>$contentType</var> = <kbd>'html'</kbd>;\n    <i>break</i>;\n}\n\n<var>$templateFilename</var> = __DIR__ . <kbd>'/templates/'</kbd> . <var>$template</var> . <kbd>'.'</kbd> . <var>$contentType</var> . <kbd>'.php'</kbd>;\n<i>require</i>(<var>$templateFilename</var>);\n</code></pre>\n<h2 id=\"fazit\">Fazit</h2>\n<p>Jedes PHP-Projekt braucht eine saubere Trennung zwischen Logik und Ausgabe – aber für diese Trennung braucht es nicht immer eine Template-Engine wie <a href=\"https://twig.symfony.com/\">Twig</a> oder <a href=\"https://www.smarty.net/\">Smarty</a>. Auch handelsübliches PHP kann (mit wenigen, kleinen Helferlein) eine performante, wartungsarme Alternative fürs Templating sein.</p>\n<p>Hier nochmals eine Übersicht über <a href=\"https://www.php.net/manual/de/ref.strings.php\">die wichtigsten PHP-Funktionen für Templating</a>:</p>\n<div class=\"table-wrapper\"><table>\n<thead>\n<tr>\n<th>Funktion</th>\n<th>Alias</th>\n<th>Beschreibung</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><a href=\"https://www.php.net/htmlspecialchars\"><code>htmlspecialchars</code></a></td>\n<td class=\"tag-code\"><code>html</code></td>\n<td>HTML-Escaping</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/urlencode\"><code>urlencode</code></a></td>\n<td>-</td>\n<td>URL-Escaping</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/json_encode\"><code>json_encode</code></a></td>\n<td>-</td>\n<td>JSON/JavaScript-Escaping</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/setlocale\"><code>setlocale</code></a></td>\n<td>-</td>\n<td>Setzen der Sprache für Übersetzungen</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/_\"><code>gettext</code> / <code>_</code></a></td>\n<td class=\"tag-code\"><code>_html</code></td>\n<td>Ausgabe von Übersetzungen</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/strftime\"><code>strftime</code></a></td>\n<td>-</td>\n<td>Gibt ein lokales Datumsformat aus</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/localeconv\"><code>localeconv</code></a></td>\n<td>-</td>\n<td>Ermittelt die lokale Ausgabeeinstellungen für Zahlen</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/printf\"><code>printf</code></a></td>\n<td class=\"tag-code\"><code>_html</code></td>\n<td>Ausgabe von Variablen in Strings</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/nl2br\"><code>nl2br</code></a></td>\n<td>-</td>\n<td>Konvertiert Zeilenumbrüche in HTML-<code>&lt;br /&gt;</code></td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/implode\"><code>implode</code></a></td>\n<td>-</td>\n<td>Verbindet ein Array zu einem String</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/var_dump\"><code>var_dump</code></a></td>\n<td class=\"tag-code\"><code>debug</code></td>\n<td>Dump von PHP-Variablen</td>\n</tr>\n<tr>\n<td><a href=\"https://www.php.net/header\"><code>header</code></a></td>\n<td>-</td>\n<td>Ausgabe von anderen <code>Content-Type</code></td>\n</tr>\n</tbody></table></div>\n<p>Einzig beim Thema Vererbung von Templates sind echte Template-Engines deutlich komfortabler, wie <a href=\"https://twig.symfony.com/doc/3.x/templates.html#template-inheritance\">Twigs Template-Vererbung</a> und <a href=\"https://www.smarty.net/docs/en/advanced.features.template.inheritance.tpl\">Smartys Template-Vererbung</a> zeigen. Siehe dazu auch den <a href=\"https://css-tricks.com/comparing-html-preprocessor-features/\">Vergleich der Templating-Engines für PHP auf <i>CSS-Tricks</i></a>.</p>\n<hr />\n<p>There also is an <a href=\"https://journal.3960.org/posts/2021-12-17-world-smallest-php-templating-engine/\" rel=\"alternate\">English version of this article, „World&#39;s smallest template engine“</a>.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Die%20kleinste%20PHP-Templating-Engine%20der%20Welt&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2020-08-20-kleinste-php-templating-engine-welt%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2020-08-20-kleinste-php-templating-engine-welt/",
        "pubDate": "Thu, 20 Aug 2020 18:02:28 +0200",
        "atom_published": "2020-08-20T18:02:28+02:00",
        "atom_updated": "2022-02-21T18:22:25+01:00",
        "guid": "user/posts/2020-08-20-kleinste-php-templating-engine-welt/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "PHP",
          "Programmierung",
          "Webdevelop"
        ]
      },
      {
        "title": "Sichere Passwörter für Leute, die sichere Passwörter hassen",
        "description": "<p>Wirklich sichere Passwörter haben in der Regel den Nachteil, dass sie schwer zu merken oder schwer zu schreiben sind. Das muss aber nicht sein: mit diesem einfachen Rezept kann man die Sicherheit seiner Passwörter erhöhen, und muss sich nur <em>ein</em> Passwort merken.</p>",
        "content_encoded": "<p>Wirklich sichere Passwörter haben in der Regel den Nachteil, dass sie schwer zu merken oder schwer zu schreiben sind. Das muss aber nicht sein: mit diesem einfachen Rezept kann man die Sicherheit seiner Passwörter erhöhen, und muss sich nur <em>ein</em> Passwort merken.</p>\n<!-- more -->\n<p id=\"more\">Und damit sind nicht <a href=\"https://www.bsi-fuer-buerger.de/BSIFB/DE/Empfehlungen/Passwoerter/Passwort_Manager/Passwort_Manager_node.html\">Passwort-Manager</a> gemeint. Stattdessen versteht sich dieses einfache Rezept als Anleitung für diejenigen, die auch im Jahr 2020 nur <em>ein</em> Passwort für alle ihre Accounts verwenden. Denkt zum Beispiel an die Passwörter für eure Oma. <span class=\"emoji emoji--1f609\" title=\";)\">&#x1F609;</span></p>\n<p>Wohlgemerkt: Das folgende Rezept sollte nur verwendet werden, wenn man keine Lust hat, sich ein <a href=\"https://www.bsi-fuer-buerger.de/BSIFB/DE/Empfehlungen/Passwoerter/passwoerter_node.html\">wirklich sicheres Passwort zuzulegen</a>. Deswegen nenne ich das folgende, mit meiner Frau zusammen entwickelte Rezept…</p>\n<h2 id=\"omas-passwort-rezept-für-sicherere-passwörter\">Omas Passwort-Rezept für sicherere Passwörter</h2>\n<p>Wir beginnen einfach mal mit der Annahme, dass das Standardpasswort bisher folgendes war:</p>\n<pre><code>passwort\n</code></pre>\n<p>Das ist nachgewiesenermaßen ein schlechtes Passwort: Es ist ganze 8 Zeichen lang, steht direkt so in einem Wörterbuch, und ist (laut <a href=\"https://password.kaspersky.com/de/\">Kaspersky-Tool zum Testen der Passwortsicherheit</a>) in 20 Sekunden<sup>1</sup> geknackt. Aber mit wenigen Tricks und gleichem Merkaufwand kann dieses schlechte Passwort in ein deutlich sichereres Passwort umgewandelt werden.</p>\n<p>Die Minimalanforderungen an Passwörter sind in der Regel:</p>\n<ol>\n<li>Eine Mischung aus Groß- und Kleinbuchstaben,</li>\n<li>dazu ein paar Ziffern,</li>\n<li>und ein Sonderzeichen.</li>\n</ol>\n<p>Die <strong>Groß- und Kleinschreibung</strong> erledigen wird über einen großen Anfangsbuchstaben, was die Zeit zum Knacken schon auf 39 Sekunden<sup>1</sup> anhebt:</p>\n<pre><code>Passwort\n</code></pre>\n<p>Da die deutsche Sprache gerne <a href=\"https://de.wikipedia.org/wiki/Komposition_%28Grammatik%29\">Komposita</a> verwendet, kann man zusätzlich jedes im Passwort vorkommende Nomen einzeln mit einem Großbuchstaben versehen:</p>\n<pre><code>PassWort\n</code></pre>\n<p>Das hebt die Zeit zum Knacken schon auf 12 Minuten<sup>1</sup> an – diese Regel ignorieren wir aber der Einfachheit halber, und machen mit anderen Rezeptbestandteilen weiter.</p>\n<p>Ein paar <strong>Ziffern</strong> hat jeder von uns im Kopf. In der Regel Jahreszahlen mit vier Stellen – aber warum nicht eine deutsche Postleitzahl mit fünf Stellen, oder auch die auswendig gelernte Telefonnummer eures ersten Schwarms mit acht Stellen?</p>\n<pre><code>Passwort09648\n</code></pre>\n<p>Jetzt benötigen wir zum Knacken bereits 16 Tage<sup>1</sup>.</p>\n<p>Und als <strong>Sonderzeichen</strong> fügen wir zwischen dem Buchstaben- und Ziffernteil sowie am Ende ein einfaches Sonderzeichen ein.</p>\n<pre><code>Passwort-09648!\n</code></pre>\n<p>Damit ist unser neues Standardpasswort zwar nicht kompliziert… aber mit 15 Zeichen <a href=\"https://www.bsi-fuer-buerger.de/BSIFB/DE/Empfehlungen/Passwoerter/passwoerter_node.html\">ist es nun deutlich länger</a> und widersteht Knackversuchen für circa 4 Jahrhunderte<sup>1</sup>!</p>\n<h2 id=\"die-extra-zutat-unterschiedliche-passwörter-für-jeden-dienst\">Die Extra-Zutat: Unterschiedliche Passwörter für jeden Dienst</h2>\n<p>Das eigentlich wichtige an Passwörtern ist aber, dass für jeden Account ein eigenes Passwort verwendet wird. Andernfalls riskieren wir, dass beim Hack einer Passwort-Datenbank der Hacker danach Zugriff auf <em>alle</em> unsere Accounts hat.</p>\n<p>Darüber hinaus besteht die Gefahr, dass der Hacker das Passwort einfach mal bei eurem E-Mail-Account ausprobiert – und dann für <em>alle</em> eure Accounts das Passwort über die „Passwort vergessen“-Funktion zurückgesetzt werden kann. Also benötigen wir <strong>unterschiedliche Passwörter für alle Accounts</strong>.</p>\n<p>Auch hier greift <i>Omas Passwort-Rezept</i> – als Extra-Zutat erweitert man das Passwort um den Namen des Dienstes:</p>\n<pre><code>Passwort-09648!google\nPasswort-09648!facebook\nPasswort-09648!web.de\n</code></pre>\n<p>Damit wird aus unserem 15-Zeichen-Passwort ein Passwort mit über 20 Zeichen Länge, dass mehr als 10.000 Jahrhunderte<sup>1</sup> standhält, und durch seine Länge verblüffenderweise sicherer ist als z.B. „<code>&amp;uG4ftL!</code>“ mit 12 Tagen Knackdauer<sup>1</sup>. Selbst ein so einfacher Vertreter wie „<code>Klaus-123!google</code>“ hält immerhin 24 Jahrhunderte<sup>1</sup>.</p>\n<p>Um die Tipparbeit zu reduzieren kann die Extra-Zutat auch nur durch drei Buchstaben ersetzt werden (mit Dank an Mathias für die Inspiration). Das können zum Beispiel die ersten drei Buchstaben des Dienstes sein, oder jeder zweite Buchstabe – was verblüffenderweise bei der bereits erreichten Länge die Sicherheit gar nicht so großartig reduziert<sup>1</sup>:</p>\n<pre><code>Passwort-09648!goo\nPasswort-09648!fac\nPasswort-09648!web\n</code></pre>\n<p>Die Extra-Zutat kann für Schreibfaule noch weiter verkürzt werden, indem man nur den Anfangsbuchstaben des Dienstes verwendet, was zumindest 8 Jahrhunderte hält<sup>1</sup>:</p>\n<pre><code>Passwort-09648!g\nPasswort-09648!f\nPasswort-09648!w\n</code></pre>\n<h3 id=\"ist-das-nicht-gefährlich\">Ist das nicht gefährlich?</h3>\n<p>„Moment“, denkt ihr jetzt, „wenn mein Passwort in einem Account geklaut wird, kann man ja einfach erraten, wie es in einem anderen Account aussehen würde.“ Gut mitgedacht, aber gar nicht so wahrscheinlich:</p>\n<p>Tatsächlich speichert kein ernsthafter Dienst euer Passwort, sondern eine Art „<a href=\"https://de.wikipedia.org/wiki/Hashfunktion\">Quersumme</a>“ eures Passworts, ein sogenanntes Hash. Euer Passwort passt genau zu diesem Hash, aber von dem Hash kann man nicht unbedingt auf das Passwort schließen. Alleine aus diesem Grund kann euch niemand euer Passwort nachträglich zuschicken. Je länger das Passwort, desto zufälliger wird das Hash, und desto schwerer ist es, auf das ursprüngliche Passwort zurückzuschließen<sup>2</sup>.</p>\n<div class=\"table-wrapper\"><table>\n<caption id=\"beispiele-für-hashes\">Beispiele für Hashes</caption>\n<thead>\n<tr>\n<th>Passwort</th>\n<th>MD5 Hash ohne Salt</th>\n</tr>\n</thead>\n<tbody><tr>\n<td class=\"tag-code\"><code>Passwort-09648!</code></td>\n<td class=\"tag-code\"><code>b443a258958c87a19b7fe521de7a6958</code></td>\n</tr>\n<tr>\n<td class=\"tag-code\"><code>Passwort-09648!g</code></td>\n<td class=\"tag-code\"><code>da01b07dd811a00e6576fa4d83b4eee6</code></td>\n</tr>\n<tr>\n<td class=\"tag-code\"><code>Passwort-09648!goo</code></td>\n<td class=\"tag-code\"><code>fd39389348f433257dbde5361d33d757</code></td>\n</tr>\n<tr>\n<td class=\"tag-code\"><code>Passwort-09648!google</code></td>\n<td class=\"tag-code\"><code>0da72493f73589b4e1c96dbff8f7d2e1</code></td>\n</tr>\n<tr>\n<td class=\"tag-code\"><code>Passwort-09648!facebook</code></td>\n<td class=\"tag-code\"><code>c20ed3dc0acd60e05a6d668ad25758fb</code></td>\n</tr>\n<tr>\n<td class=\"tag-code\"><code>Passwort-09648!web.de</code></td>\n<td class=\"tag-code\"><code>7a3d0f5304dd9bdad469c025269ca274</code></td>\n</tr>\n</tbody></table></div>\n<p>Ergo: Falls jemand das <em>Hash</em> eures Passworts klaut, wird er viel Zeit brauchen, dieses Hash in euer Passwort zurückzuverwandeln, um sich danach in eurem Namen in dem Dienst anzumelden. In der Regel macht sich aber niemand die Mühe, dieses Passwort anzuschauen um dann auszuknobeln, wie das Passwort bei anderen Diensten aussehen könnte.</p>\n<h2 id=\"nochmals-im-überblick\">Nochmals im Überblick</h2>\n<p><i>Omas Passwort-Rezept</i> besteht also aus den folgenden Schritten:</p>\n<ol>\n<li>Ein Wort wählen.</li>\n<li>Das erste Zeichen groß schreiben.</li>\n<li>Ein Sonderzeichen anhängen, z.B. ein „<code>-</code>“.</li>\n<li>Eine Zahl anhängen.</li>\n<li>Ein Sonderzeichen anhängen, z.B. ein „<code>!</code>“.</li>\n<li>Und daran pro Account den Namen des benutzten Dienstes anhängen.</li>\n</ol>\n<p>Die ganze schöne Anleitung ändert aber nichts an der Tatsache, dass man die eigenen Passwörter geheim halten muss – und jedes Gerät sicher sein muss, auf dem man ein Passwort eintippt.</p>\n<hr />\n<ul>\n<li><sup>1)</sup> – Alle Zeitschätzungen für das Knacken von Passwörtern stammen aus dem <a href=\"https://password.kaspersky.com/de/\">Kaspersky-Tool zum Testen der Passwortsicherheit</a>.</li>\n<li><sup>2)</sup> – Vereinfachte Darstellung. <span class=\"emoji emoji--1f609\" title=\";)\">&#x1F609;</span></li>\n</ul><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Sichere%20Passw%C3%B6rter%20f%C3%BCr%20Leute%2C%20die%20sichere%20Passw%C3%B6rter%20hassen&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2020-07-30-sichere-passwoerter-fuer-leute-sichere-passwoerter-hassen%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2020-07-30-sichere-passwoerter-fuer-leute-sichere-passwoerter-hassen/",
        "pubDate": "Thu, 30 Jul 2020 19:06:51 +0200",
        "atom_published": "2020-07-30T19:06:51+02:00",
        "atom_updated": "2020-07-31T09:25:06+02:00",
        "guid": "user/posts/2020-07-30-sichere-passwoerter-fuer-leute-sichere-passwoerter-hassen/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Sicherheit",
          "Programmierung",
          "Idee",
          "Webdevelop",
          "Geckobar"
        ]
      },
      {
        "title": "CSS-Variablen – bombensicher mit SASS",
        "description": "<p>CSS-Variablen erlauben komfortabel, Farben und andere CSS-Eigenschaften in CSS-Stylesheets zu definieren – wenn da nur nicht ältere Browser wären, die CSS-Variablen nicht unterstützen, und damit den schönen CSS-Plan zerstören.</p>\n<p>Aber keine Bange: <a href=\"https://sass-lang.com/\"><strong>SASS</strong></a> und CSS-Variablen sind die perfekte Kombination, um eine bombensichere Unterstützung für CSS-Variablen zu erreichen.</p>",
        "content_encoded": "<p>CSS-Variablen erlauben komfortabel, Farben und andere CSS-Eigenschaften in CSS-Stylesheets zu definieren – wenn da nur nicht ältere Browser wären, die CSS-Variablen nicht unterstützen, und damit den schönen CSS-Plan zerstören.</p>\n<p>Aber keine Bange: <a href=\"https://sass-lang.com/\"><strong>SASS</strong></a> und CSS-Variablen sind die perfekte Kombination, um eine bombensichere Unterstützung für CSS-Variablen zu erreichen.</p>\n<!-- more -->\n<h2 id=\"more\">Wofür brauche ich eigentlich CSS-Variablen?</h2>\n<p><a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties\">CSS-Variablen bzw. <i>CSS Custom Properties</i></a> erlauben eine übersichtliche, wiederverwendbare Definition von CSS-Eigenschaften. Wie schon von SASS, LESS und anderen CSS-Präprozessoren bekannt, kann eine solche Variable mehrfach verwendet werden – eine Änderung an der zentralen Definition ändert zeitgleich und zuverlässig die CSS-Ausgabe:</p>\n<pre><code class=\"language-css\"><i>:root</i> {\n  <var>--color-background</var>: <em>#ffffee</em>;\n  <var>--color-text</var>: <em>#111100</em>;\n}\n\n<i>body</i> {\n  <b>color</b>: <b>var</b>(<var>--color-text</var>);\n  <b>background-color</b>: <b>var</b>(<var>--color-background</var>);\n}\n\n<i>article</i> {\n  <b>border</b>: 1px dotted <b>var</b>(<var>--color-text</var>);\n  <b>padding</b>: 1em;\n}\n</code></pre>\n<p>Das ist besonders praktisch, wenn man einem Styleguide folgt, später übersichtlich Werte im CSS austauschen möchte, oder einen zentralen Ansatzpunkt für CSS-Änderung zum Beispiel durch Redakteure haben möchte.</p>\n<h2 id=\"dream-team-media-queries-und-css-variablen\">Dream Team: Media Queries und CSS-Variablen</h2>\n<p>Gleichzeitig kann die Definition von CSS-Variablen auch in Abhängigkeit von <i>Media Queries</i> (<code>@media</code>) definiert werden. So kann sich der Inhalt einer Variable für die Druckausgabe oder z.B. für die Ausgabe im <a href=\"https://journal.3960.org/posts/2020-06-15-css-variables-dark-mode/\"><i>Dark Mode</i></a> ändern.</p>\n<pre><code class=\"language-css\"><i>@media</i> screen {\n  <i>:root</i> {\n    <var>--color-background</var>: <em>#ffffee</em>;\n    <var>--color-text</var>: <em>#111100</em>;\n    <var>--color-link</var>: <em>#000899</em>;\n    <var>--color-link-hover</var>: <em>#1520c7</em>;\n  }\n}\n<i>@media</i> (prefers-color-scheme: dark) {\n  <i>:root</i> {\n    <var>--color-background</var>: <em>#333</em>;\n    <var>--color-text</var>: <em>#fff</em>;\n    <var>--color-link</var>: <em>#0eb9e7</em>;\n    <var>--color-link-hover</var>: <em>#39cbf6</em>;\n  }\n}\n<i>@media</i> print {\n  <i>:root</i> {\n    <var>--color-link</var>: blue;\n    <var>--color-link-hover</var>: blue;\n  }\n}\n</code></pre>\n<p>Damit entfällt die Notwendigkeit, Variablen durch SASS oder LESS definieren zu müssen – und damit in bestimmten Projekten die Notwendigkeit, überhaupt SASS oder LESS zu verwenden…</p>\n<h2 id=\"alte-browser-alte-leier\">Alte Browser, alte Leier</h2>\n<p>…wenn da nicht (gar nicht so) <a href=\"https://caniuse.com/#feat=css-variables\">alte Browser wären, die CSS-Variablen nicht interpretieren können</a>. So sind alle Versionen des Microsoft Internet Explorers, sowie ältere Versionen von Microsoft Edge und Apple Safari nicht in der Lage, CSS-Variablen zu verarbeiten.</p>\n<p>Netterweise gibt es aber einen Weg, auch diesen Browsern grundsätzliche Wünsche mitzuteilen:</p>\n<pre><code class=\"language-css\"><i>body</i> {\n  <b>color</b>: <em>#111100</em>;\n  <b>color</b>: <b>var</b>(<var>--color-text</var>);\n  <b>background-color</b>: <em>#ffffee</em>;\n  <b>background-color</b>: <b>var</b>(<var>--color-background</var>);\n}\n</code></pre>\n<p>CSS ist so aufgebaut, dass bei einer mehrfachen Zuweisung von CSS-Eigenschaften die letzte Eigenschaft verwendet wird, <em>die erfolgreich vom Browser interpretiert werden konnte</em>.</p>\n<p>In unserem obigen Beispiel werden ältere Browser nicht in der Lage sein, eine korrekte Ersetzung für Print und Dark Mode durchzuführen – aber wenigstens grundsätzlich sieht die Seite gut aus. Im Sinne von <a href=\"https://alistapart.com/article/understandingprogressiveenhancement/\"><i>progressive enhancement</i></a> (oder <i>graceful degradation</i>) hat man mit dieser Lösung schon einen passablen Job gemacht.</p>\n<p>Dieses Arbeiten mit Fallbacks hat aber zwei Nachteile:</p>\n<ul>\n<li>Bei jeder Verwendung einer CSS-Variable muss ein Fallback definiert werden.</li>\n<li>Der Wert des Fallbacks sollte grundsätzlich mit <em>einem</em> der Werte der CSS-Variable übereinstimmen (in der Regel dem Wert, den die Variable für Desktop-Browser auf dem Bildschirm annimmt).</li>\n</ul>\n<h2 id=\"also-doch-sass\">Also doch SASS!</h2>\n<p>Netterweise bieten sich SASS-Variablen an, um CSS-Variablen zu definieren. Unser erster Schritt: Wir definieren die Werte, die wir später als CSS-Variablen verwenden wollen. Dazu benutzen wir aber nicht reguläre SASS-Variablen, sondern stattdessen <a href=\"https://sass-lang.com/documentation/modules/map\">SASS-Maps</a>. Diese praktischen Konstrukte erlauben die Definition einer Liste von Schlüssel-Wert-Paaren:</p>\n<pre><code class=\"language-scss\"><var>$cssVariablesScreen</var>: (\n  <kbd>'color-background'</kbd>: #ffffee,\n  <kbd>'color-text'</kbd>: #111100,\n  <kbd>'color-link'</kbd>: #000899,\n  <kbd>'color-link-hover'</kbd>: #1520c7\n);\n</code></pre>\n<p>…die in SASS nur wenig komplizierter als eine SASS-Variable ausgegeben werden können:</p>\n<pre><code class=\"language-scss\"><i>body</i> {\n  <b>color</b>: <b>map-get</b>(<var>$cssVariables</var>, color-background);\n}\n</code></pre>\n<p>Wir aber interessieren uns für die Ausgabe der SASS-Variablen als CSS-Variablen. Hier hilft uns die SASS-Map, die in einer Schleife (<code>@each</code>) alle Schlüssel-Wert-Kombinationen ausgeben kann:</p>\n<pre><code class=\"language-scss\"><i>:root</i> {\n  <i>@each</i> <var>$name</var>, <var>$value</var> in <var>$cssVariablesScreen</var> {\n    <b>--#{<var>$name</var>}</b>: #{<var>$value</var>};\n  }\n}\n</code></pre>\n<p>Auf diese Weise können wir für jede Media Query eine SASS-Map erzeugen, und den dazu passenden Block an CSS-Variablen erzeugen lassen:</p>\n<pre><code class=\"language-scss\"><var>$cssVariablesDarkMode</var>: (\n  <kbd>'color-background'</kbd>: #333,\n  <kbd>'color-text'</kbd>: #fff,\n  <kbd>'color-link'</kbd>: #0eb9e7,\n  <kbd>'color-link-hover'</kbd>: #39cbf6\n);\n<var>$cssVariablesPrint</var>: (\n  <kbd>'color-link'</kbd>: blue,\n  <kbd>'color-link-hover'</kbd><i>: blue\n);\n\n:root</i> {\n  <i>@media</i> screen {\n    <i>@each</i> <var>$name</var>, <var>$value</var> in <var>$cssVariablesScreen</var> {\n      <b>--#{<var>$name</var>}</b>: #{<var>$value</var>};\n    }\n  }\n  <i>@media</i> (prefers-color-scheme: dark) {\n    <i>@each</i> <var>$name</var>, <var>$value</var> in <var>$cssVariablesDarkMode</var> {\n      <b>--#{<var>$name</var>}</b>: #{<var>$value</var>};\n    }\n  }\n  <i>@media</i> print {\n    <i>@each</i> <var>$name</var>, <var>$value</var> in <var>$cssVariablesPrint</var> {\n      <b>--#{<var>$name</var>}</b>: #{<var>$value</var>};\n    }\n  }\n}\n</code></pre>\n<h2 id=\"sass-mixins-für-die-hebearbeit\">SASS-Mixins für die Hebearbeit</h2>\n<p>Um uns unnötige Wiederholungen von SASS-Code zu vermeiden, verwenden wir <a href=\"https://sass-lang.com/documentation/at-rules/mixin\">SASS-Mixins</a> (<code>@mixin</code>). So kann das Durchlaufen von SASS-Maps zum Erzeugen von CSS-Variablen wunderbar in ein solches Mixin ausgelagert werden:</p>\n<pre><code class=\"language-scss\"><i>@mixin</i> make-css-variables(<var>$cssVariables</var>) {\n  <i>@each</i> <var>$name</var>, <var>$value</var> in <var>$cssVariables</var> {\n    <b>--#{<var>$name</var>}</b>: #{<var>$value</var>};\n  }\n}\n\n<i>:root</i> {\n  <i>@media</i> screen {\n    <i>@include</i> make-css-variables(<var>$cssVariablesScreen</var>)\n  }\n  <i>@media</i> (prefers-color-scheme: dark) {\n    <i>@include</i> make-css-variables(<var>$cssVariablesDarkMode</var>)\n  }\n  <i>@media</i> print {\n    <i>@include</i> make-css-variables(<var>$cssVariablesPrint</var>)\n  }\n}\n</code></pre>\n<p>Damit haben wir zumindest schonmal die Ausgabe der CSS-Variablen – haben aber leider noch nichts gewonnen, was die Fallbacks für ältere Browser angeht.</p>\n<p>Tatsächlich haben wir aber schon alle Teile herumliegen, die folgende Fallbacks möglich machen:</p>\n<pre><code class=\"language-css\"><i>body</i> {\n  <b>color</b>: <em>#111100</em>;\n  <b>color</b>: <b>var</b>(<var>--color-text</var>);\n  <b>background-color</b>: <em>#ffffee</em>;\n  <b>background-color</b>: <b>var</b>(<var>--color-background</var>);\n}\n</code></pre>\n<p>Theoretisch könnten wir uns der Aufgabe in SASS wie folgt nähern:</p>\n<pre><code class=\"language-css\"><i>body</i> {\n  <b>color</b>: <b>map-get</b>(<var>$cssVariables</var>, color-text);\n  <b>color</b>: <b>var</b>(<var>--color-text</var>);\n  <b>background-color</b>: <b>map-get</b>(<var>$cssVariables</var>, color-background);\n  <b>background-color</b>: <b>var</b>(<var>--color-background</var>);\n}\n</code></pre>\n<p>Bei einem genauen Blick erkennt man hier aber, das die beiden Zeilen direkt zusammenhängen, und ganz wunderbar durch das folgende Mixin abgebildet werden können:</p>\n<pre><code class=\"language-scss\"><i>@mixin</i> variable-fallback(<var>$property</var>, <var>$name</var>) {\n  <b>#{<var>$property</var>}</b>: <b>map-get</b>(<var>$cssVariablesScreen</var>, <var>$name</var>);\n  <b>#{<var>$property</var>}</b>: <b>var</b>(--#{<var>$name</var>});\n}\n\n<i>body</i> {\n  <i>@include</i> variable-fallback(color, color-text);\n  <i>@include</i> variable-fallback(background-color, color-background);\n}\n</code></pre>\n<p>Mit ein bisschen zusätzlicher Fehlerbehandlung und mehr Flexibilität, welche SASS-Map für den Fallback verwendet werden soll:</p>\n<pre><code class=\"language-scss\"><i>@mixin</i> variable-fallback(<var>$property</var>, <var>$name</var>, <var>$cssVariables</var>: <var>$cssVariablesScreen</var>) {\n  <i>@if</i> map-has-key(<var>$cssVariables</var>, <var>$name</var>) {\n    <b>#{<var>$property</var>}</b>: <b>map-get</b>(<var>$cssVariables</var>, <var>$name</var>);\n  } <i>@else</i> {\n    <i>@warn</i> <kbd>&quot;Missing CSS variable: #{$name}&quot;</kbd>\n  }\n  <b>#{<var>$property</var>}</b>: <b>var</b>(--#{<var>$name</var>});\n}\n</code></pre>\n<h2 id=\"fazit\">Fazit</h2>\n<p>CSS-Variablen mit Kompatibilität für Browser, die CSS-Variablen nicht unterstützen, kann man mit wenig Schreibarbeit via SASS lösen. Die Schritt sind:</p>\n<ol>\n<li>CSS-Variablen als SASS-Map definieren.</li>\n<li>Die SASS-Map zum Erzeugen der CSS-Variablen verwenden – gegebenenfalls via Mixin.</li>\n<li>Bei jeder Verwendung einer CSS-Variable ein Mixin verwenden, das gleichzeitig die CSS-Variable <em>und</em> den Fallback-Wert im CSS ausgibt.</li>\n</ol>\n<pre><code class=\"language-scss\"><u>// Defining SASS variables to build CSS variables</u>\n<u>// --------------------------------------------------------------</u>\n\n<var>$cssVariablesScreen</var>: (\n  <kbd>'color-background'</kbd>: #ffffee,\n  <kbd>'color-text'</kbd>: #111100,\n  <kbd>'color-link'</kbd>: #000899,\n  <kbd>'color-link-hover'</kbd>: #1520c7\n);\n<var>$cssVariablesDarkMode</var>: (\n  <kbd>'color-background'</kbd>: #333,\n  <kbd>'color-text'</kbd>: #fff,\n  <kbd>'color-link'</kbd>: #0eb9e7,\n  <kbd>'color-link-hover'</kbd>: #39cbf6\n);\n<var>$cssVariablesPrint</var>: (\n  <kbd>'color-link'</kbd>: blue,\n  <kbd>'color-link-hover'</kbd>: blue\n);\n\n<u>// Defining mixins for converting SASS variables to CSS variables</u>\n<u>// --------------------------------------------------------------</u>\n\n<i>@mixin</i> make-css-variables(<var>$cssVariables</var>) {\n  <i>@each</i> <var>$name</var>, <var>$value</var> in <var>$cssVariables</var> {\n    <b>--#{<var>$name</var>}</b>: #{<var>$value</var>};\n  }\n}\n\n<i>@mixin</i> variable-fallback(<var>$property</var>, <var>$name</var>, <var>$cssVariables</var>: <var>$cssVariablesScreen</var>) {\n  <i>@if</i> map-has-key(<var>$cssVariables</var>, <var>$name</var>) {\n    <b>#{<var>$property</var>}</b>: <b>map-get</b>(<var>$cssVariables</var>, <var>$name</var>);\n  } <i>@else</i> {\n    <i>@warn</i> <kbd>&quot;Missing CSS variable: #{$name}&quot;</kbd>\n  }\n  <b>#{<var>$property</var>}</b>: <b>var</b>(--#{<var>$name</var>});\n}\n\n<u>// Convert SASS variables to CSS variables</u>\n<u>// --------------------------------------------------------------</u>\n\n<i>:root</i> {\n  <i>@media</i> screen {\n    <i>@include</i> make-css-variables(<var>$cssVariablesScreen</var>)\n  }\n  <i>@media</i> (prefers-color-scheme: dark) {\n    <i>@include</i> make-css-variables(<var>$cssVariablesDarkMode</var>)\n  }\n  <i>@media</i> print {\n    <i>@include</i> make-css-variables(<var>$cssVariablesPrint</var>)\n  }\n}\n\n<u>// Use CSS variables</u>\n<u>// --------------------------------------------------------------</u>\n\n<i>body</i> {\n  <i>@include</i> variable-fallback(color, color-text);\n  <i>@include</i> variable-fallback(background-color, color-background);\n}\n</code></pre><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=CSS-Variablen%20%E2%80%93%20bombensicher%20mit%20SASS&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2020-06-17-css-variablen-bombensicher-sass%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2020-06-17-css-variablen-bombensicher-sass/",
        "pubDate": "Wed, 17 Jun 2020 18:41:26 +0200",
        "atom_published": "2020-06-17T18:41:26+02:00",
        "atom_updated": "2020-06-18T11:41:37+02:00",
        "guid": "user/posts/2020-06-17-css-variablen-bombensicher-sass/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Webdevelop",
          "CSS",
          "Programmierung"
        ]
      },
      {
        "title": "CSS-Variablen und Dark Mode",
        "description": "<p><img src=\"https://journal.3960.org/posts/2020-06-15-css-variables-dark-mode/blackwhite-240x240.jpg\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Mit dem <i>Dark Mode</i> kann man in so ziemlich jedem Betriebssystem den Wunsch äußern, dass alle Darstellungen möglichst dunkel gehalten werden sollen – in der Regel mit heller Text auf dunklem Hintergrund. Interessanterweise betrifft dieser Wunsch auch Webseiten.</p>",
        "content_encoded": "<p><img src=\"https://journal.3960.org/posts/2020-06-15-css-variables-dark-mode/blackwhite-240x240.jpg\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Mit dem <i>Dark Mode</i> kann man in so ziemlich jedem Betriebssystem den Wunsch äußern, dass alle Darstellungen möglichst dunkel gehalten werden sollen – in der Regel mit heller Text auf dunklem Hintergrund. Interessanterweise betrifft dieser Wunsch auch Webseiten.</p>\n<!-- more -->\n<p id=\"more\">Der <i>Dark Mode</i> hat inzwischen in so ziemlich jedem Betriebssystem Einzug gehalten. So sind z.B. in <a href=\"https://www.howtogeek.com/222614/how-to-enable-windows-10s-hidden-dark-theme/\">Windows 10</a>, <a href=\"https://support.apple.com/de-de/HT208976\">Mac OSX</a>, <a href=\"https://www.omgubuntu.co.uk/2020/04/enable-full-dark-mode-in-ubuntu-20-04\">Linux</a>, <a href=\"https://www.heise.de/tipps-tricks/Android-Dark-Mode-aktivieren-4638226.html\">Android</a> und auch <a href=\"https://support.apple.com/en-us/HT210332\">iOS</a> Einstellungen vorhanden, mit denen viele Applikationen sich diesem Farbwunsch anpassen.</p>\n<p>Interessanterweise wird dieser Wunsch vom Betriebssystem auch auch an Browser weitergereicht. Der Browser entscheidet aber nicht selber, wie eine Seite im <i>Dark Mode</i> umgefärbt werden muss, sondern überlässt die Definition des Verhaltens dem jeweiligen Betreiber der Website, die gerade besucht wird.</p>\n<h2 id=\"media-prefers-color-scheme-dark\"><code>@media (prefers-color-scheme: dark)</code></h2>\n<p>Technisch wird das über eine <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme\"><code>@media</code>-Query in CSS</a> gelöst:</p>\n<pre><code class=\"language-css\"><i>@media</i> (prefers-color-scheme: dark) {\n  <i>body</i> {\n    <b>color</b>: white;\n    <b>background</b>: black;\n  }\n\n  <i>a</i> {\n    <b>color</b>: orange;\n  }\n}\n</code></pre>\n<p>Dabei müsst ihr als Entwickler nicht zwangsläufig euer Betriebssystem auf <i>Dark Mode</i> umschalten, um eure Styles zu testen. In Chrome kann man den <i>Dark Mode</i> kurzzeitig aktivieren, indem man im Web Inspektor (z.B. via <kbd>F12</kbd> öffnen) mittels <kbd>Strg+Shift+P</kbd> die Kommandoleiste aktiviert, und dort „color-scheme“ (oder „dark“) eintippt. Eine Auswahl namens „Emulate CSS prefers-color-scheme: dark“ für die Aktivierung des <i>Dark Mode</i> im Browser erscheint.</p>\n<h2 id=\"dark-mode-mittels-css-variablen\">Dark Mode mittels CSS-Variablen</h2>\n<p>Mit <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties\">CSS-Variablen bzw. <i>CSS Custom Properties</i></a> kann man unter anderem auch wunderschöne Farbdefinitionen hinterlegen, die je nach <code>@media</code>-Query abgeändert werden können. Neben der Verwendung für Print-Styles ist die Verwendung für den <i>Dark Mode</i> eine gute Idee.</p>\n<p>Anstatt in eurem CSS an jedem Element Farbcodes zu hinterlegen, die dann für jede Kombination aus Media-Query und Element neu definiert werden müssen, hinterlegt ihr an den einzufärbenden Elementen nur CSS-Variablen. Diese definiert ihr zentral, und könnt diese auch zentral umschalten:</p>\n<pre><code class=\"language-css\"><i>@media</i> screen {\n  <i>:root</i> {\n    <var>--color-background</var>: <em>#ffffee</em>;\n    <var>--color-text</var>: <em>#111100</em>;\n    <var>--color-link</var>: <em>#000899</em>;\n    <var>--color-link-hover</var>: <em>#1520c7</em>;\n  }\n}\n<i>@media</i> (prefers-color-scheme: dark) {\n  <i>:root</i> {\n    <var>--color-background</var>: <em>#333</em>;\n    <var>--color-text</var>: <em>#fff</em>;\n    <var>--color-link</var>: <em>#0eb9e7</em>;\n    <var>--color-link-hover</var>: <em>#39cbf6</em>;\n  }\n}\n\n<i>body</i> {\n  <b>color</b>: <b>var</b>(<var>--color-text</var>);\n  <b>background-color</b>: <b>var</b>(<var>--color-background</var>);\n}\n\n<i>a</i> {\n  <b>color</b>: <b>var</b>(<var>--color-link</var>);\n}\n\n<i>a:hover</i> {\n  <b>color</b>: <b>var</b>(<var>--color-link-hover</var>);\n}\n</code></pre>\n<p>Übrigens kann man auch SVG-Grafiken in der Seite mit diesem Trick umfärben lassen, da <a href=\"https://journal.3960.org/posts/2020-04-05-svg-web-components/\">SVG-Grafiken ebenfalls über CSS ihre Farben verändern können</a>:</p>\n<pre><code class=\"language-css\"><i>svg</i> {\n  <b>fill</b>: <b>var</b>(<var>--color-text</var>);\n  <b>stroke</b>: <b>var</b>(<var>--color-text</var>);\n}\n</code></pre>\n<h2 id=\"dark-mode-und-grafiken\"><i>Dark Mode</i> und Grafiken</h2>\n<p>Mit der obigen Methode können natürlich auch Hintergrundgrafiken ausgetauscht werden. In vielen Fällen ist das aber nicht nötig: Üblicherweise werden beim Bau von Grafiken von Websites viele Grafiken auf weiße Hintergrundfarben freigestellt. Hier sollte man prüfen, ob diese nicht besser auf einen transparenten Hintergrund (als PNG-Grafik) freigestellt werden.</p>\n<h2 id=\"update-2024\">Update 2024</h2>\n<p><a href=\"https://css-tricks.com/come-to-the-light-dark-side/\">Come To The Light-dark() Side – CSS-Tricks</a> erklärt neuere Möglichkeiten, um noch einfacher Dark Mode CSS zu erzeugen.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=CSS-Variablen%20und%20Dark%20Mode&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2020-06-15-css-variables-dark-mode%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2020-06-15-css-variables-dark-mode/",
        "pubDate": "Mon, 15 Jun 2020 18:41:26 +0200",
        "atom_published": "2020-06-15T18:41:26+02:00",
        "atom_updated": "2024-10-28T09:07:26+01:00",
        "guid": "user/posts/2020-06-15-css-variables-dark-mode/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Webdevelop",
          "CSS",
          "Programmierung"
        ]
      },
      {
        "title": "Web Components mit Markdown verwenden",
        "description": "<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>",
        "content_encoded": "<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=\"\" />",
        "link": "https://journal.3960.org/posts/2020-04-10-web-components-mit-markdown-verwenden/",
        "pubDate": "Fri, 10 Apr 2020 19:32:33 +0200",
        "atom_published": "2020-04-10T19:32:33+02:00",
        "atom_updated": "2020-04-10T20:34:10+02:00",
        "guid": "user/posts/2020-04-10-web-components-mit-markdown-verwenden/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Webdevelop",
          "Web-Components",
          "Blog",
          "Javascript",
          "Programmierung",
          "Technologie"
        ]
      },
      {
        "title": "Ideen für Web Components",
        "description": "<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>",
        "content_encoded": "<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=\"\" />",
        "link": "https://journal.3960.org/posts/2020-04-07-ideen-fuer-web-components/",
        "pubDate": "Tue, 07 Apr 2020 19:18:32 +0200",
        "atom_published": "2020-04-07T19:18:32+02:00",
        "atom_updated": "2021-08-30T10:16:09+02:00",
        "guid": "user/posts/2020-04-07-ideen-fuer-web-components/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Webdevelop",
          "Web-Components",
          "Idee",
          "Javascript",
          "Programmierung",
          "Technologie",
          "Für Tumblr"
        ]
      },
      {
        "title": "SVG und Web Components",
        "description": "<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>",
        "content_encoded": "<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=\"\" />",
        "link": "https://journal.3960.org/posts/2020-04-05-svg-web-components/",
        "pubDate": "Sun, 05 Apr 2020 18:41:26 +0200",
        "atom_published": "2020-04-05T18:41:26+02:00",
        "atom_updated": "2023-03-05T10:14:07+01:00",
        "guid": "user/posts/2020-04-05-svg-web-components/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Webdevelop",
          "Web-Components",
          "SVG",
          "Javascript",
          "CSS",
          "Programmierung",
          "Technologie",
          "The Cool",
          "Fliegerei"
        ]
      }
    ]
  }
}