{
  "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 | Artikel mit dem Tag \"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/tagged/programmierung/rss.json",
      "rel": "self",
      "type": "application/rss+json"
    },
    "lastBuildDate": "Tue, 24 Mar 2026 20:00:16 +0100",
    "atom_updated": "2026-03-24T20:00:16+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": "Sicherheit!",
        "description": "<blockquote><p>„SICHERHEIT“ wird bei uns groß geschrieben!<br />…und ist unser Root-Kennwort.</p></blockquote>",
        "content_encoded": "<blockquote><p>„SICHERHEIT“ wird bei uns groß geschrieben!<br />…und ist unser Root-Kennwort.</p></blockquote><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Sicherheit!&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2025-11-22-sicherheit%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2025-11-22-sicherheit/",
        "pubDate": "Sat, 22 Nov 2025 18:05:00 +0100",
        "atom_published": "2025-11-22T18:05:00+01:00",
        "atom_updated": "2025-11-22T20:24:05+01:00",
        "guid": "user/posts/2025-11-22-sicherheit/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Lustiges",
          "Programmierung"
        ]
      },
      {
        "title": "ChatGPT does flight plans",
        "description": "<p>A brilliant conversation with ChatGPT for proposing a flight plan:</p>",
        "content_encoded": "<p>A brilliant conversation with ChatGPT for proposing a flight plan:</p>\n<!-- more -->\n<p id=\"more\">Please propose a short flight from Martha&#39;s Vineyard airport to a nearby airport with a Cessna 172</p>\n<blockquote><p>A great short flight from Martha&#39;s Vineyard Airport (MVY) in a Cessna 172 would be to Nantucket Memorial Airport (ACK). The flight covers about 30 nautical miles, which takes roughly 20–30 minutes depending on conditions. This route offers stunning views of the Atlantic Ocean and the islands, making it a scenic and enjoyable flight.</p><p>Given the relatively short distance and straightforward navigation over open water, it&#39;s a perfect trip for a short hop in a Cessna 172.</p></blockquote>\n<p>Could you give me a flight plan recommendation?</p>\n<blockquote><p>Certainly! Below is a suggested VFR (Visual Flight Rules) flight plan for a Cessna 172 from Martha&#39;s Vineyard Airport (KMVY) to Nantucket Memorial Airport (KACK).</p><h2 id=\"flight-plan-overview\">Flight Plan Overview:</h2><ul><li><strong>Departure:</strong> Martha&#39;s Vineyard Airport (KMVY)</li><li><strong>Destination:</strong> Nantucket Memorial Airport (KACK)</li><li><strong>Distance:</strong> ~30 NM</li><li><strong>Estimated Flight Time:</strong> 20–30 minutes</li><li><strong>Cruising Altitude:</strong> 3,000 feet (VFR recommended altitude)</li><li><strong>Flight Type:</strong> VFR</li></ul><h2 id=\"flight-plan-details\">Flight Plan Details:</h2><ul><li><p><strong>Pre-Flight:</strong></p><ul><li><strong>Weather:</strong> Check METAR/TAF for both MVY and ACK, as well as en route weather.</li><li><strong>Weight &amp; Balance:</strong> Ensure your Cessna 172 is within weight and balance limits.</li><li><strong>Fuel:</strong> Verify adequate fuel for the flight, including reserves.</li><li><strong>Navigation:</strong> Set up your navigation radios and GPS if available.</li></ul></li><li><p><strong>Departure from KMVY:</strong></p><ul><li><strong>Runway:</strong> Depending on the wind, likely Runway 24 or 06.</li><li><strong>Climb Out:</strong> After takeoff, climb to a cruising altitude of 3,000 feet.</li><li><strong>Heading:</strong> After departure, fly a south-eastern heading towards Nantucket. The initial heading will be approximately 120°.</li></ul></li><li><p><strong>En Route:</strong></p><ul><li><strong>Direct Route:</strong> Fly direct from MVY to ACK.</li><li><strong>Landmarks:</strong> You’ll be over water most of the way, with limited landmarks. However, keep an eye out for the following:<ul><li><strong>Edgartown</strong> on Martha&#39;s Vineyard, off to your left shortly after departure.</li><li><strong>Tuckernuck Island</strong> before reaching Nantucket.</li></ul></li></ul></li><li><p><strong>Arrival at KACK:</strong></p><ul><li><strong>Communication:</strong> Contact Nantucket Tower as you approach the Class D airspace.</li><li><strong>Descent:</strong> Begin your descent to pattern altitude (1,000 feet AGL) as you near the airport.</li><li><strong>Pattern Entry:</strong> Depending on the active runway, enter the appropriate traffic pattern.</li><li><strong>Runway:</strong> Likely Runway 24 or 06, depending on wind conditions.</li></ul></li><li><p><strong>Landing:</strong></p><ul><li>Perform a standard landing, taxi to the ramp, and shut down.</li></ul></li></ul><h2 id=\"communication-frequencies\">Communication Frequencies:</h2><ul><li><strong>MVY ATIS:</strong> 118.12 MHz</li><li><strong>MVY Ground:</strong> 121.90 MHz</li><li><strong>MVY Tower:</strong> 119.70 MHz</li><li><strong>ACK ATIS:</strong> 133.45 MHz</li><li><strong>ACK Tower:</strong> 118.30 MHz</li></ul><h2 id=\"considerations\">Considerations:</h2><ul><li><strong>Wind:</strong> Check the wind direction and adjust your runway choice and headings accordingly.</li><li><strong>Traffic:</strong> Nantucket can be busy, especially in the summer, so stay alert for other traffic.</li><li><strong>Alternate:</strong> If weather deteriorates or there&#39;s an issue, you could divert back to Martha&#39;s Vineyard or head to Cape Cod (Hyannis, KHYA).</li></ul><p>This is a straightforward flight, but it offers stunning views and a good practice opportunity for navigation over water and handling short cross-country flights. Safe flying!</p></blockquote>\n<p>Can you export that flight plan as Garmin fpl file?</p>\n<blockquote><p>Sure! To create a Garmin .fpl file, I&#39;ll generate the flight plan with the necessary waypoints and export it as an .fpl file. I&#39;ll base the file on the flight plan details we discussed.</p><p>I&#39;ll get that ready for you now.</p><p>I&#39;ve created the Garmin .fpl flight plan file for your route from Martha&#39;s Vineyard (KMVY) to Nantucket (KACK). The file is packaged in a .zip archive, which is often required by Garmin devices.</p><p>You can download the file using the link below:</p><p>Download MVY_to_ACK.zip</p><p>Feel free to upload it to your Garmin device, and have a great flight! If you need anything else, just let me know.</p></blockquote>\n<p>This is brilliant. Thank you</p>\n<blockquote><p>You&#39;re very welcome! I&#39;m glad you found the flight plan helpful. Enjoy the flight, and if you have any more questions or need further assistance, feel free to reach out. Safe skies! ✈️</p></blockquote><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=ChatGPT%20does%20flight%20plans&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2024-08-28-chatgpt-does-flight-plans%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2024-08-28-chatgpt-does-flight-plans/",
        "pubDate": "Wed, 28 Aug 2024 18:55:44 +0200",
        "atom_published": "2024-08-28T18:55:44+02:00",
        "atom_updated": "2024-08-28T18:58:54+02:00",
        "guid": "user/posts/2024-08-28-chatgpt-does-flight-plans/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Fliegerei",
          "Programmierung",
          "Simulation",
          "Technologie",
          "The Cool"
        ]
      },
      {
        "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": "Sommermodus mit FRITZ!DECT und Home Assistant",
        "description": "<p><img src=\"https://journal.3960.org/posts/2023-05-26-sommermodus-mit-fritz-dect-home-assistant/ha-301-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Der Sommermodus für die FRITZ!DECT-Thermostate wird über die FRITZ!Box gesteuert – mit einem festen Zeitplan. Mehr Flexibilität gibt es mit dem <a href=\"https://www.home-assistant.io/\">Home Assistant</a>, um zum Beispiel auf Temperaturdaten oder Wetterprognosen reagieren zu können.</p>",
        "content_encoded": "<p><img src=\"https://journal.3960.org/posts/2023-05-26-sommermodus-mit-fritz-dect-home-assistant/ha-301-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Der Sommermodus für die FRITZ!DECT-Thermostate wird über die FRITZ!Box gesteuert – mit einem festen Zeitplan. Mehr Flexibilität gibt es mit dem <a href=\"https://www.home-assistant.io/\">Home Assistant</a>, um zum Beispiel auf Temperaturdaten oder Wetterprognosen reagieren zu können.</p>\n<!-- more -->\n<p id=\"more\">Dummerweise lässt sich der Sommermodus an den Thermostaten vom Home Assistant aus nicht einfach auslösen. Und selbst die im Home Assistant vorhandene Möglichkeit, den Thermostat einfach auszuschalten, funktioniert nicht wirklich gut – der nächste Schaltbefehl an den Thermostat hebt die Abschaltung wieder auf. Außerdem kann der Sommermodus deutlich mehr als nur die Heizung abzuschalten – unter anderem kümmert er sich um das gelegentliche Bewegen der Ventile, um sie vor dem Steckenbleiben zu bewahren.</p>\n<h2 id=\"die-lösung\">Die Lösung</h2>\n<p>…ist zweiteilig: In der FRITZ!Box legen wir mittels Vorlagen das ein- und ausschalten des Sommermodus&#39; an – und im Home Assistant lösen wir dann die Vorlagen aus.</p>\n<p><span class=\"figure\"><img src=\"https://journal.3960.org/posts/2023-05-26-sommermodus-mit-fritz-dect-home-assistant/fritz-heizen.png\" alt=\"Vorlagen in der FRITZ!Box - nützlicher, als gedacht\" /><span class=\"figcaption\" aria-hidden=\"true\">Vorlagen in der FRITZ!Box - nützlicher, als gedacht<br /></span></span></p>\n<ol>\n<li>In der FRITZ!Box legt ihr unter „Smart Home &gt; Automatisierungen“ eine neue Vorlage an, die ihr zum Beispiel „Sommermodus an“ nennt.<ol>\n<li>Wählt alle Heizungen aus, die ihr zeitgleich in den Sommermodus schicken wollt.</li>\n<li>Hinterlegt nun mit „Automatische Einstellungen &gt; Heizen beendet“ einen Zeitplan für den Sommermodus, der einfach das ganze Jahr umfasst. (Vorsichtige Naturen können dies auch vom 01. Mai bis 01. Oktober wählen.)</li>\n</ol>\n</li>\n<li>In der FRITZ!Box baut ihr nun eine zweite Vorlage, die ihr zum Beispiel „Sommermodus aus“ nennt.<ol>\n<li>Wählt wieder die selben Heizungen wie bei „Sommermodus an“ aus.</li>\n<li>Wählt nun die Option „Automatische Einstellungen &gt; Heizen beendet“ und hinterlegt <em>keinen</em> Zeitplan. Durch Auslösen dieses Vorlage wird nun der Sommermodus in den Heizungen gelöscht.</li>\n</ol>\n</li>\n<li>Nun wechselt in eurem Home Assistant und macht unter „Einstellungen &gt; Geräte und Dienste &gt; AVM FRITZ!SmartHome“ mittels der Option „Neu laden“ die neuen Vorlagen auch im Home Assistant bekannt. Diese hier „Smart Home Templates“ genannten Entitäten erzeugen automatisch ein Gerät, nämlich einen virtuellen Button.</li>\n<li>Diese Buttons könnt ihr nun eurem Dashboard hinzufügen, und so manuell den Sommermodus an- und ausschalten.</li>\n</ol>\n<h2 id=\"reaktion-auf-knöpfe\">Reaktion auf Knöpfe</h2>\n<p>Den Druck auf einen dieser Knöpfe könnt ihr im Hone Assistant abfangen und zusätzliche Aktionen auslösen.</p>\n<p>Dazu müsst ihr eine Automatisierung bauen, die beim Druck auf die Knöpfe neben den Vorlagen in der FRITZ!Box noch zusätzliche Aktionen auslösen:</p>\n<pre><code class=\"language-yaml\"><i>alias</i>: <kbd>&quot;Heizungen: Sommermodus an&quot;</kbd>\n<i>description</i>: <kbd>&quot;&quot;</kbd>\n<i>trigger</i>:\n  - <i>platform</i>: device\n    <i>device_id</i>: 5f6c4925f76d4ef27407160a968bb7fd\n    <i>domain</i>: button\n    <i>entity_id</i>: button.heizungen_sommermodus_an\n    <i>type</i>: pressed\n<i>condition</i>: []\n<i>action</i>:\n  - <i>service</i>: notify.notify\n    <i>data</i>:\n      <i>title</i>: <kbd>&quot;Sommermodus an&quot;</kbd>\n      <i>message</i>: &gt;-\n        Alle Heizungen sind nun abgeschaltet, willkommen im Sommermodus.\n    <i>enabled</i>: <samp>true</samp>\n<i>mode</i>: single\n</code></pre>\n<h2 id=\"automatisierung-des-sommermodus\">Automatisierung des Sommermodus&#39;</h2>\n<p>Wir haben aber ja die ganze Arbeit nicht auf uns genommen, um im Home Assistant selber auf Knöpfe drücken zu müssen. Tatsächlich können wir <strong>Automatisierungen</strong> des Home Assistants für uns auf den Knopf drücken lassen.</p>\n<p>Dabei kann in jeder Automatisierung als Aktion der Druck auf die Knöpfe ausgelöst werden:</p>\n<pre><code class=\"language-yaml\"><i>action</i>:\n  - <i>service</i>: button.press\n    <i>data</i>: {}\n    <i>target</i>:\n    <i>entity_id</i>: button.sommermodus_an\n    <i>enabled</i>: <samp>true</samp>\n</code></pre>\n<p>Somit könnt ihr euch nun beliebige Auslöser im Home Assistant konfigurieren, die eure Heizung in den Sommermodus schicken.</p>\n<h3 id=\"mehr-automatisierungen-mit-der-fritzbox-und-dem-home-assistant\">Mehr Automatisierungen mit der FRITZ!Box und dem Home Assistant</h3>\n<p>Die selbe Methode lässt sich neben dem Sommermodus auch für den Urlaubsmodus und eigentlich jede andere Vorlage in der FRITZ!Box anwenden. Dafür müsst ihr nur jeweils in der FRITZ!Box das gewünschte Verhalten als Vorlage definieren, um danach den zugehörigen Button im Home Assistant drücken zu können – beziehungsweise durch eine Automatisierungen auslösen zu können.</p>\n<p>Theoretisch könnt ihr darüber auch abweichende Zeitpläne oder geänderte Spar-/Komforttemperaturen oder zeitlich begrenzte EInstellungen wie Boost oder Fenster-Modus aktivieren.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Sommermodus%20mit%20FRITZ!DECT%20und%20Home%20Assistant&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2023-05-26-sommermodus-mit-fritz-dect-home-assistant%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2023-05-26-sommermodus-mit-fritz-dect-home-assistant/",
        "pubDate": "Fri, 26 May 2023 18:45:26 +0200",
        "atom_published": "2023-05-26T18:45:26+02:00",
        "atom_updated": "2023-05-29T09:48:19+02:00",
        "guid": "user/posts/2023-05-26-sommermodus-mit-fritz-dect-home-assistant/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Home Assistant",
          "Programmierung",
          "Technologie",
          "The Cool",
          "AVM",
          "Homeoffice"
        ]
      },
      {
        "title": "Verabschiedung",
        "description": "<figure class=\"blockquote\"><blockquote><p>Alle Branches sind gepusht,<br />ich meld&#39; mich ab.</p></blockquote>\n<figcaption><cite>Ein Programmierer verabschiedet sich von seinem bisherigen Arbeitgeber</cite></figcaption></figure>",
        "content_encoded": "<figure class=\"blockquote\"><blockquote><p>Alle Branches sind gepusht,<br />ich meld&#39; mich ab.</p></blockquote>\n<figcaption><cite>Ein Programmierer verabschiedet sich von seinem bisherigen Arbeitgeber</cite></figcaption></figure><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Verabschiedung&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2023-04-10-verabschiedung%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2023-04-10-verabschiedung/",
        "pubDate": "Mon, 10 Apr 2023 18:54:53 +0200",
        "atom_published": "2023-04-10T18:54:53+02:00",
        "atom_updated": "2023-04-10T18:54:53+02:00",
        "guid": "user/posts/2023-04-10-verabschiedung/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Programmierung",
          "Philosophie",
          "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": "Pragmatische Heizungssteuerung im Home-Office",
        "description": "<p><img src=\"https://journal.3960.org/posts/2023-02-14-pragmatische-heizungssteuerung-home-office/ha-301-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Mein neuestes Abenteuer mit dem <a href=\"https://www.home-assistant.io/\">Home Assistant</a> in Verbindung mit <a href=\"https://avm.de/produkte/fritzdect/fritzdect-301/\">FRITZ!DECT 301</a> beschäftigt sich mit einer smarten Heizungssteuerung für das Home-Office.</p>",
        "content_encoded": "<p><img src=\"https://journal.3960.org/posts/2023-02-14-pragmatische-heizungssteuerung-home-office/ha-301-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Mein neuestes Abenteuer mit dem <a href=\"https://www.home-assistant.io/\">Home Assistant</a> in Verbindung mit <a href=\"https://avm.de/produkte/fritzdect/fritzdect-301/\">FRITZ!DECT 301</a> beschäftigt sich mit einer smarten Heizungssteuerung für das Home-Office.</p>\n<!-- more -->\n<h2 id=\"more\">Wie es lief</h2>\n<p>Ach, was habe ich alles gebaut und gebastelt. Los ging es mit einem einfachen Zeitplan in der FRITZ!Box, der <strong>jeden Montag bis Freitag</strong> in der Arbeitszeit die Heizung ein- und danach wieder ausschaltete. Ich war&#39;s zufrieden.</p>\n<p>Aber an <strong>Feiertagen</strong>, die zwischen Montag und Freitag lagen, lief die Heizung immer noch. Höchste Zeit, den <a href=\"https://www.home-assistant.io/integrations/workday/\">Werktags-Sensor</a> über den Home Assistant zu verbauen und in die Heizungssteuerung mit einzubeziehen. Dafür musste ich dann den Zeitplan aus der FRITZ!Box entfernen und ebenfalls in den Home Assistant übertragen.</p>\n<p>Und im Urlaub? Urlaube sind ja keine Feiertage. Also noch einen Kalender in den Home Assistant integriert, der an <strong>Urlaubstagen</strong> die Heizung ebenfalls ausgeschaltet hält.</p>\n<p>Oha, <strong>Gleitzeit und Überstunden</strong>: Mal starte ich früher, mal höre ich später auf. Also diesen Teil von Hand steuern? Und wie ist es am Wochenende, wenn man im Arbeitszimmer am privaten PC etwas bastelt?</p>\n<p>Wie man es auch dreht und wendet – der Plan hatte inzwischen viel zu viele Variablen bekommen. Eigentlich wollte ich doch nur, dass die Heizung läuft, wenn ich im Raum bin! Aber wie kann ich das dem Home Assistant beibringen?</p>\n<h2 id=\"wie-es-läuft\">Wie es läuft</h2>\n<p>Ganz einfach! Ob im Büro jemand arbeitet kann man daran erkennen, dass einer der PCs im Büro eingeschaltet ist. Ein super-simples Rezept kann also prüfen, ob mindestens einer der Rechner dort an und im Netzwerk sichtbar ist, und schaltet entsprechend die Heizung an. Wenn <em>keiner</em> der Rechner im Netzwerk ist, wird die Heizung entsprechend ausgeschaltet.</p>\n<h3 id=\"teil-1-status-der-pcs-überwachen\">Teil 1: Status der PCs überwachen</h3>\n<p><img src=\"https://journal.3960.org/posts/2023-02-14-pragmatische-heizungssteuerung-home-office/network-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Dazu bauen wir uns einen Zustand, der eingeschaltet ist, wenn irgendeiner der PCs angeschaltet und im Netzwerk ist – und erst ausgeschaltet ist, wenn <em>alle</em> PCs ausgeschaltet sind. Das funktioniert wie folgt:</p>\n<ol>\n<li>Wenn ihr im Home Assistant und „Einstellungen &gt; Geräte &amp; Dienste &gt; FRITZ!Box“ die Liste der Geräte aufmacht, seht ihr die Liste aller von eurer FRITZ!Box erkannten Netzwerkteilnehmer. In meinem Beispiel nennen wir die zwei PCs <code>arbeitspferd</code> und <code>daddelkiste</code>.</li>\n<li>Merkt euch die Namen der Netzwerkteilnehmer, die beim Einschalten eure Heizung einschalten sollen. Unter „Entwicklerwerkzeuge &gt; Zustände“ findet ihr dazu den passenden Zustand, der mit <code>device_tracker</code> beginnt. In meinem Beispiel gehen wir von zwei PC-Trackern aus, <code>device_tracker.arbeitspferd</code> und <code>device_tracker.daddelkiste</code>.</li>\n<li>Mit dem <a href=\"https://community.home-assistant.io/t/home-assistant-community-add-on-visual-studio-code/107863\">Home Assistant Community Add-on: Visual Studio Code</a> kann die <code>configuration.yaml</code> nun so erweitert werden, dass die beiden Tracker (oder beliebig viele Tracker) zu einem Sammel-Tracker zusammengefasst werden.</li>\n</ol>\n<pre><code class=\"language-yaml\">- <i>name</i>: <kbd>&quot;Home-Office PCs&quot;</kbd>\n  <i>unique_id</i>: device_tracker_homeoffice\n  <i>state</i>: <kbd>&quot;{{ is_state('device_tracker.arbeitspferd', 'home') or is_state('device_tracker.daddelkiste', 'home') }}&quot;</kbd>\n  <i>icon</i>: <kbd>&quot;mdi:lan-connect&quot;</kbd>\n  <i>device_class</i>: presence\n</code></pre>\n<p>Nach einmaligem Neustart sollte unter „Entwicklerwerkzeuge &gt; Zustände“ der neue Tracker sichtbar sein. Er heißt dann wahrscheinlich <code>binary_sensor.device_tracker_homeoffice</code>.</p>\n<p>Unser neuer kleiner Device-Tracker „Home-Office PCs“ kann nun zum Beispiel auf dem Dashboard verbaut werden. Sobald eines der Geräte eingeschaltet wird, wird auch der Tracker eingeschaltet – beim Ausschalten gibt es knapp zwei Minuten Verzögerung, bis der Home Assistant das Gerät wirklich als offline annimmt.</p>\n<h3 id=\"teil-2-heizung-steuern\">Teil 2: Heizung steuern</h3>\n<p>Der Home Assistant kann nun die Programmierung der FRITZ!Box verbessern. Dafür wird in der FRITZ!Box die Heizung zu 100% in den Spar-Modus geschickt, weil ab sofort der Home Assistant die Heizung zwischen Spar- und Komfort-Modus hin- und herschaltet. Die Verbindung ist dabei einfach:</p>\n<ol>\n<li>Wenn auch nur einer der PCs an ist, soll die Heizung im Komfort-Modus sein.</li>\n<li>Wenn keiner der PCs an ist, soll die Heizung im Spar-Modus sein.</li>\n</ol>\n<p>Unter „Einstellungen &gt; Automatisierungen &amp; Szenen“ fügen wir dazu ein einfaches Rezept hinzu:</p>\n<pre><code class=\"language-yaml\"><u># Replace all occurences of `office` with your thermostat's identifier</u>\n<u># `device_id` has to be assigned via GUI</u>\n<i>alias</i>: <kbd>&quot;Office: Someone is there&quot;</kbd>\n<i>description</i>: <kbd>&quot;Be nice, and turn on the heating if someone is using a PC in the office&quot;</kbd>\n<i>trigger</i>:\n  - <i>platform</i>: state\n    <i>entity_id</i>:\n      - binary_sensor.device_tracker_homeoffice\n    <i>from</i>: <kbd>&quot;on&quot;</kbd>\n  - <i>platform</i>: state\n    <i>entity_id</i>:\n      - binary_sensor.device_tracker_homeoffice\n    <i>to</i>: <kbd>&quot;on&quot;</kbd>\n<i>action</i>:\n  - <i>if</i>:\n      - <i>condition</i>: state\n        <i>entity_id</i>: binary_sensor.device_tracker_homeoffice\n        <i>state</i>: <kbd>&quot;on&quot;</kbd>\n    <i>then</i>:\n      - <i>device_id</i>: b46c4851235fb8c90f4a659b6e9a953d\n        <i>domain</i>: climate\n        <i>entity_id</i>: climate.office\n        <i>type</i>: set_preset_mode\n        <i>preset_mode</i>: comfort\n    <i>else</i>:\n      - <i>device_id</i>: b46c4851235fb8c90f4a659b6e9a953d\n        <i>domain</i>: climate\n        <i>entity_id</i>: climate.office\n        <i>type</i>: set_preset_mode\n        <i>preset_mode</i>: eco\n<i>mode</i>: single\n</code></pre>\n<p>Erst mit der Zusammenfassung der beiden Zustände zu einem Tracker ist es sauber möglich, zwischen Spar- und Komfort-Modus hin- und herzuschalten. Wenn wir die Tracker trennen würden könnte es ansonsten passieren, dass das Ausschalten eines Rechners die Heizung deaktiviert, obwohl der andere Rechner eigentlich noch läuft.</p>\n<h2 id=\"wie-es-laufen-könnte\">Wie es laufen könnte</h2>\n<p>Tatsächlich kann man diese Idee auch auf andere Geräte ausdehnen, wie zum Beispiel Fernseher und Spielekonsolen, die ebenfalls im Internet hängen. Eine kleine Automatisierung könnte also abends die Heizung noch etwas länger an lassen, solange der Fernseher läuft.</p>\n<p>Oder wie wäre es mit einem Sammel-Tracker, der die Smartphones aller Haushaltsteilnehmer als Schalter verwendet?</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Pragmatische%20Heizungssteuerung%20im%20Home-Office&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2023-02-14-pragmatische-heizungssteuerung-home-office%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2023-02-14-pragmatische-heizungssteuerung-home-office/",
        "pubDate": "Tue, 14 Feb 2023 19:04:06 +0100",
        "atom_published": "2023-02-14T19:04:06+01:00",
        "atom_updated": "2023-02-21T10:37:45+01:00",
        "guid": "user/posts/2023-02-14-pragmatische-heizungssteuerung-home-office/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Home Assistant",
          "Programmierung",
          "Technologie",
          "The Cool",
          "AVM",
          "Homeoffice"
        ]
      },
      {
        "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": "Remote-Zugriff auf den Home Assistant – mit der AVM FRITZ!Box",
        "description": "<p><img src=\"https://journal.3960.org/posts/2023-01-01-fritz-box-home-assistant-remote/ha-301-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Der <a href=\"https://www.home-assistant.io/\">Home Assistant</a> hat gegenüber vielen anderen Smart-Home-Lösungen einen Vorteil: Er benötigt keinen Internet-Dienst, um zu funktionieren. Damit ist er von vorne herein sehr sicher, da initial keine Daten aus eurem Smart-Home ins Internet entweichen.</p>\n<p>Einen kleinen Nachteil hat diese Konstruktion aber: Euer Home Assistant lässt sich nicht außerhalb eures Heimnetzwerks steuern. Oder?</p>",
        "content_encoded": "<p><img src=\"https://journal.3960.org/posts/2023-01-01-fritz-box-home-assistant-remote/ha-301-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Der <a href=\"https://www.home-assistant.io/\">Home Assistant</a> hat gegenüber vielen anderen Smart-Home-Lösungen einen Vorteil: Er benötigt keinen Internet-Dienst, um zu funktionieren. Damit ist er von vorne herein sehr sicher, da initial keine Daten aus eurem Smart-Home ins Internet entweichen.</p>\n<p>Einen kleinen Nachteil hat diese Konstruktion aber: Euer Home Assistant lässt sich nicht außerhalb eures Heimnetzwerks steuern. Oder?</p>\n<!-- more -->\n<p id=\"more\">Natürlich stimmt das nicht: Direkt im Home Assistant wird Werbung für die <a href=\"https://www.home-assistant.io/cloud/\">Home Assistant Cloud</a> gemacht. Mit diesem kleinen Dienst kann eure vormals nicht im Internet erreichbare Lösung auch außerhalb eures Heimnnetzwerks angesprochen werden. Neben der Tatsache, dass ihr damit ein Sicherheitsfeature eures Home Assistants über Bord geworfen habt, kostet dieser Dienst aber auch Geld.</p>\n<h2 id=\"fritzbox-und-vpn\">FRITZ!Box und VPN</h2>\n<p>Tatsächlich ist der entscheidende Satz, dass euer Home Assistant nur aus eurem Heimnetzwerk zugreifbar ist… beziehungsweise, wenn ihr Zugriff auf euer Heimnetzwerk habt. Wenn ihr also mittels eine Virtual Private Networks auch außerhalb eurer WLAN-Reichweite euch in euer Heimnnetz einwählen könnt, könnt ihr natürlich auch auf euren Home Assistant zugreifen.</p>\n<p>Das schöne an dieser Lösung: Der Zugang zu eurem System ist somit nur durch eine VPN-Verbindung möglich; die Daten eures Systems wie auch der Zugang zum Home Assistant sind nicht direkt im Internet verfügbar.</p>\n<p>Die AVM FRITZ!Box bietet netterweise einen eingebauten VPN-Dienst, der euch mit beliebigen Betriebssystemen für PCs, Laptops und Smartphones eine VPN-Verbindung erlaubt. Die Einrichtung ist nicht schwierig, aber voller Fallstricke. </p>\n<p>Richtet also auf eurer <a href=\"https://avm.de/service/vpn/\">AVM FRITZ!Box einen VPN-Zugang</a> ein, aber mit einer <em>wichtigen</em> Änderung: Wenn euer Home Assistant bereits mit der FRITZ!Box eingerichtet wurde, solltet ihr keinesfalls (wie von AVM vorgeschlagen) eure FRITZ!Box (und damit alle anderen Netzwerkteilnehmer) mit neuen IPs versehen. Denn der Home Assistant merkt sich für so ziemlich jedes Gerät explizit die IP, und eine Änderung ist ein größerer Schmerz. Tatsächlich könnt ihr die IP eurer FRITZ!Box so belassen – und verliert nur die Möglichkeit, über einen Router mit der selben IP das VPN zu benutzen. Tatsächlich erhaltet ihr aber bei einer Mobilverbindung immer eine IP außerhalb des Heimbereichs, könnt also problemlos das VPN nutzen.</p>\n<p>An eurem Home Assistant muss keine weitere Änderung vorgenommen werden. Sobald ihr die VPN-Verbindung aufgebaut habt, könnt ihr sowohl im Browser als auch über die App den Home Assistant benutzen, als ob ihr im Heimnetzwerk wärt.</p>\n<p>Interessanterweise kann eure Home Assistant App (zumindest unter Android) auch außerhalb des Heimnetzwerks Benachrichtigungen eures Home Assistants empfangen. Das liegt daran, dass der Home Assistant mit dem Übertragen der Benachrichtigungen einen Android-Dienst außerhalb eures Netzwerks beauftragt, der euch die Nachrichten über das Internet zustellt.</p>\n<h2 id=\"also-kurz-gefasst\">Also kurz gefasst</h2>\n<ol>\n<li>Richtet ein VPN ein…</li>\n<li>…aber ändert um Gottes willen nicht die IP eures Heimnetzwerks, wenn ihr den Home Assistant bereits eingerichtet habt.</li>\n<li>Richtet den VPN-Zugang auf dem Gerät ein, von dem ihr von außerhalb des Heimnetzwerks auf den Home Assistant zugreifen wollt.</li>\n<li>Wenn ihr unterwegs seid, müsst ihr nur noch das VPN aktivieren, und könnt dann den Home Assistant benutzen, als ob ihr im Heimnetzwerk wärt.</li>\n</ol><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=Remote-Zugriff%20auf%20den%20Home%20Assistant%20%E2%80%93%20mit%20der%20AVM%20FRITZ!Box&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2023-01-01-fritz-box-home-assistant-remote%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2023-01-01-fritz-box-home-assistant-remote/",
        "pubDate": "Sun, 01 Jan 2023 18:17:24 +0100",
        "atom_published": "2023-01-01T18:17:24+01:00",
        "atom_updated": "2023-01-01T18:17:24+01:00",
        "guid": "user/posts/2023-01-01-fritz-box-home-assistant-remote/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Programmierung",
          "Technologie",
          "The Cool",
          "AVM",
          "Home Assistant"
        ]
      },
      {
        "title": "FRITZ!DECT 301 im Home Assistant",
        "description": "<p><img src=\"https://journal.3960.org/posts/2022-11-08-fritz-dect-301-home-assistant/ha-301-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Heizungssteuerung ist in aller Munde. Als Besitzer einer FRITZ!Box habe ich mir also ein paar <a href=\"https://avm.de/produkte/fritzdect/fritzdect-301/\">FRITZ!DECT 301 Thermostate</a> der Firma AVM zugelegt, um meine Heizung sparsamer zu machen.</p>\n<p>Und dann bin ich dummerweise auf <a href=\"https://www.home-assistant.io/\">Home Assistant</a> aufmerksam gemacht worden, mit dem man die etwas spartanischen Möglichkeiten der Thermostate gewaltig aufbohren kann.</p>\n<p>Begleitet mich auf meiner Reise in den Kaninchenbau.</p>",
        "content_encoded": "<p><img src=\"https://journal.3960.org/posts/2022-11-08-fritz-dect-301-home-assistant/ha-301-240x240.png\" class=\"quad\" width=\"240\" height=\"240\" style=\"--aspect-ratio: 1/1;\" alt=\"\" /> Heizungssteuerung ist in aller Munde. Als Besitzer einer FRITZ!Box habe ich mir also ein paar <a href=\"https://avm.de/produkte/fritzdect/fritzdect-301/\">FRITZ!DECT 301 Thermostate</a> der Firma AVM zugelegt, um meine Heizung sparsamer zu machen.</p>\n<p>Und dann bin ich dummerweise auf <a href=\"https://www.home-assistant.io/\">Home Assistant</a> aufmerksam gemacht worden, mit dem man die etwas spartanischen Möglichkeiten der Thermostate gewaltig aufbohren kann.</p>\n<p>Begleitet mich auf meiner Reise in den Kaninchenbau.</p>\n<!-- more -->\n<h2 id=\"more\">FRITZ!DECT verstehen</h2>\n<p>Bei der Steuerung der Thermostate ist wichtig zu wissen, dass der Home Assistant (wie auch die FRITZ!Box selber) nur in festen Intervallen Aufträge an die Thermostate übertragen kann. Im schlimmsten Fall dauert es bis zu 15 Minuten, bis euer Auftrag dort angekommen ist. Wenn ihr also Automatisierungen oder eine Steuerung bastelt, müsst ihr etwas Vorlauf in Kauf nehmen.</p>\n<p>Meine Philosophie bei der Integration des Home Assistant war wie folgt:</p>\n<ol>\n<li>Die Zeitpläne der Thermostate werden primär über die FRITZ!Box gesteuert. Diese Zeitpläne werden punktgenau vom Thermostat ausgeführt.</li>\n<li>Der Home Assistant verbessert diese Zeitpläne um Ausnahmen oder zusätzliche Szenarien. In jedem Szenario müsst ihr aber (neben dem oben erwähnten Vorlauf) bedenken, dass der nächste in der FRITZ!Box hinterlegte Schaltpunkt wieder den Thermostat zurücksetzen wird. Das kann aber auch sehr praktisch sein, zum Beispiel als verlässlicher Reset. Beispiele dafür sind Szenarien wie „Heute ist länger Betrieb im Wohnzimmer“ oder „An Arbeitstagen kann die Heizung in der Küche früher starten“.</li>\n<li>Bei anderen Einsatzideen kann die Koordination zwischen FRITZ!Box und Home Assistant zu kompliziert werden. Hier löscht man einfach die Zeitpläne in der FRITZ!Box und steuert nur noch über den Home Assistant. Beispiel dafür ist die Heizung im Büro, die nur an Arbeitstagen geschaltet werden soll – oder wenn ich außerhalb der Arbeitszeiten meinen Rechner eingeschaltet habe. Hier können aber zumindest die Vorlagen in der FRITZ!Box helfen, die man aus dem Home Assistant dann triggern kann.</li>\n</ol>\n<h2 id=\"regler-verbessern\">Regler verbessern</h2>\n<p>Nach der Installation wird man von den etwas überzüchteten Heizkörperreglern in der Oberfläche des Home Assistant begrüßt. Meine Regler sollen nur aus drei Teilen bestehen:</p>\n<ol>\n<li>Dem eigentlich Temperatur-Regler (verbunden mit der Anzeige des aktuellen Modus und der aktuell gemessenen Temperatur),</li>\n<li>einem Knopf für den Spar-Modus,</li>\n<li>und einem Knopf für den Komfort-Modus.</li>\n</ol>\n<p>Das ist tatsächlich schön konfigurativ zu lösen…</p>\n<p><img src=\"https://journal.3960.org/posts/2022-11-08-fritz-dect-301-home-assistant/ha-301-config-400x225.png\" class=\"default\" width=\"400\" height=\"225\" style=\"--aspect-ratio: 16/9;\" alt=\"\" /></p>\n<p>…oder mit dem folgendem YAML als schnelles Rezept für das <strong>Dashboard</strong>:</p>\n<pre><code class=\"language-yaml\"><u># Replace all occurences of `wohnzimmer` with your thermostat's identifier</u>\n<i>type</i>: thermostat\n<i>features</i>:\n  - <i>style</i>: icons\n    <i>preset_modes</i>:\n      - eco\n      - comfort\n    <i>type</i>: climate-preset-modes\n<i>entity</i>: climate.wohnzimmer\n</code></pre>\n<p>(<strong>Update 2024–02</strong>: Die neuen Konfigurationsmöglichkeiten des Home Assistant haben den Einbau weiterer Knöpfe deutlich vereinfacht.)</p>\n<h2 id=\"temperatur-am-thermostat-anzeigen\">Temperatur am Thermostat anzeigen</h2>\n<p>Durch den <a href=\"https://www.home-assistant.io/\">Home Assistant</a> werden die <a href=\"https://avm.de/produkte/fritzdect/fritzdect-301/\">FRITZ!DECT 301 Thermostate</a> folgerichtig als Thermostate erkannt. Tatsächlich beinhalten sie aber auch einen Temperatur-Sensor, den man mit etwas Bastelei auch im Dashboard anzeigen kann.</p>\n<p>Mit dem <a href=\"https://community.home-assistant.io/t/home-assistant-community-add-on-visual-studio-code/107863\">Home Assistant Community Add-on: Visual Studio Code</a> kann die <code>configuration.yaml</code> um die folgenden Zeilen erweitern werden:</p>\n<pre><code class=\"language-yaml\"><u># Converting thermostats into thermometers</u>\n<u># Replace all occurences of `wohnzimmer` with your thermostat's identifier</u>\n<i>template</i>:\n  - <i>sensor</i>:\n      - <i>name</i>: <kbd>&quot;Wohnzimmer Heizung Temperatur&quot;</kbd>\n        <i>unique_id</i>: wohnzimmer_heizung_temperatur\n        <i>state</i>: <kbd>&quot;{{ state_attr('climate.wohnzimmer', 'current_temperature') }}&quot;</kbd>\n        <i>unit_of_measurement</i>: <kbd>&quot;°C&quot;</kbd>\n        <i>device_class</i>: temperature\n</code></pre>\n<p>Danach können diese Sensoren im <strong>Dashboard</strong> angezeigt werden:</p>\n<pre><code class=\"language-yaml\"><u># Replace all occurences of `wohnzimmer` with your thermostat's identifier</u>\n<i>type</i>: gauge\n<i>entity</i>: sensor.wohnzimmer_heizung_temperatur\n<i>min</i>: <em>10</em>\n<i>max</i>: <em>32</em>\n<i>severity</i>:\n  <i>green</i>: <em>21</em>\n  <i>yellow</i>: <em>23</em>\n  <i>red</i>: <em>25</em>\n</code></pre>\n<h2 id=\"urlaubsmodus-sichtbar-machen\">Urlaubsmodus sichtbar machen</h2>\n<p>Die <a href=\"https://avm.de/produkte/fritzdect/fritzdect-301/\">FRITZ!DECT 301 Thermostate</a> können in einen Urlaubsmodus geschickt werden – ein Zustand, der im Home Assistant ebenfalls ausgelesen werden kann. Besonders luxuriös geschieht das mit einem eigenen Binär-Sensor.</p>\n<p>Mit dem <a href=\"https://community.home-assistant.io/t/home-assistant-community-add-on-visual-studio-code/107863\">Home Assistant Community Add-on: Visual Studio Code</a> kann die <code>configuration.yaml</code> um die folgenden Zeilen erweitern werden:</p>\n<pre><code class=\"language-yaml\"><u># Replace all occurences of `wohnzimmer` with your thermostat's identifier</u>\n<i>template</i>:\n  - <i>binary_sensor</i>:\n      - <i>name</i>: <kbd>&quot;Wohnzimmer Heizung Urlaubsmodus&quot;</kbd>\n        <i>unique_id</i>: wohnzimmer_heizung_urlaubsmodus\n        <i>state</i>: <kbd>&quot;{{ state_attr('climate.wohnzimmer', 'holiday_mode') }}&quot;</kbd>\n        <i>icon</i>: <kbd>&quot;mdi:bag-checked&quot;</kbd>\n</code></pre>\n<p>Analog lässt sich auch der Sommermodus der Heizung in der <code>configuration.yaml</code> als eigener Sensor einrichten:</p>\n<pre><code class=\"language-yaml\"><u># Replace all occurences of `wohnzimmer` with your thermostat's identifier</u>\n<i>template</i>:\n  - <i>binary_sensor</i>:\n      - <i>name</i>: <kbd>&quot;Wohnzimmer Heizung Sommermodus&quot;</kbd>\n        <i>unique_id</i>: wohnzimmer_heizung_sommermodus\n        <i>state</i>: <kbd>&quot;{{ state_attr('climate.wohnzimmer', 'summer_mode') }}&quot;</kbd>\n        <i>icon</i>: <kbd>&quot;mdi:hvac-off&quot;</kbd>\n</code></pre>\n<h2 id=\"regelung-für-das-home-office\">Regelung für das Home Office</h2>\n<p>In der FRITZ!Box hatte ich die Steuerung meiner Büro-Heizung bisher stumpf an die Wochentage Montags bis Freitag gekoppelt. So lief die Heizung auch, wenn ein Feiertag das eigentlich unnötig machte. Höchste Zeit also für den <a href=\"https://www.home-assistant.io/integrations/workday/\">Arbeitstag-Sensor</a>.</p>\n<p>Mit dem <a href=\"https://community.home-assistant.io/t/home-assistant-community-add-on-visual-studio-code/107863\">Home Assistant Community Add-on: Visual Studio Code</a> kann die <code>configuration.yaml</code> um die folgenden Zeilen erweitern werden:</p>\n<pre><code class=\"language-yaml\"><u># Workday sensor</u>\n<i>binary_sensor</i>:\n  - <i>platform</i>: workday\n    <i>country</i>: DE\n    <i>province</i>: NI <u># Niedersachsen, Lower Saxony</u>\n</code></pre>\n<p>Der Home Assistant kann nun die Programmierung der FRITZ!Box verbessern. Dafür wird in der FRITZ!Box die Heizung zu 100% in den Spar-Modus geschickt, weil ab sofort der Home Assistant die Heizung auf den Komfort-Modus schaltet, wenn ein Arbeitstag vorliegt.</p>\n<p>Vorher legen wir uns einen Zeitplan an, den wir in folgende <strong>Automatisierung</strong> einbinden:</p>\n<pre><code class=\"language-yaml\"><u># Replace all occurences of `office` with your thermostat's identifier</u>\n<u># `device_id` has to be assigned via GUI</u>\n<i>alias</i>: <kbd>&quot;Home Office&quot;</kbd>\n<i>description</i>: Only trigger comfort mode on work days\n<i>trigger</i>:\n  - <i>platform</i>: state\n    <i>entity_id</i>:\n      - binary_sensor.office_heizung_home_office\n<i>condition</i>:\n  - <i>condition</i>: and\n    <i>conditions</i>:\n      - <i>condition</i>: state\n        <i>entity_id</i>: binary_sensor.workday_sensor\n        <i>state</i>: <kbd>&quot;on&quot;</kbd>\n      - <i>condition</i>: state\n        <i>entity_id</i>: binary_sensor.office_heizung_urlaubsmodus\n        <i>state</i>: <kbd>&quot;off&quot;</kbd>\n<i>action</i>:\n  - <i>if</i>:\n      - <i>condition</i>: state\n        <i>state</i>: <kbd>&quot;on&quot;</kbd>\n        <i>entity_id</i>: binary_sensor.office_heizung_home_office\n    <i>then</i>:\n      - <i>device_id</i>: b46c4851235fb8c90f4a659b6e9a953a\n        <i>domain</i>: climate\n        <i>entity_id</i>: climate.office\n        <i>type</i>: set_preset_mode\n        <i>preset_mode</i>: comfort\n    <i>else</i>:\n      - <i>device_id</i>: b46c4851235fb8c90f4a659b6e9a953a\n        <i>domain</i>: climate\n        <i>entity_id</i>: climate.office\n        <i>type</i>: set_preset_mode\n        <i>preset_mode</i>: eco\n    <i>enabled</i>: <samp>true</samp>\n<i>mode</i>: single\n</code></pre>\n<p>Hier können aber auch die Vorlagen in der FRITZ!Box hilfreich sein, um aus dem Home Assistant in der FRITZ!Box komplexere Szenarien auslösen zu können.</p><img src=\"https://stats.3960.org/p.php?idsite=2amp;action_name=FRITZ!DECT%20301%20im%20Home%20Assistant&amp;url=https%3A%2F%2Fjournal.3960.org%2Fposts%2F2022-11-08-fritz-dect-301-home-assistant%2F%3Futm_source%3Dnewsfeed_view\" style=\"border:0;\" alt=\"\" />",
        "link": "https://journal.3960.org/posts/2022-11-08-fritz-dect-301-home-assistant/",
        "pubDate": "Tue, 08 Nov 2022 18:17:23 +0100",
        "atom_published": "2022-11-08T18:17:23+01:00",
        "atom_updated": "2024-01-05T12:46:57+01:00",
        "guid": "user/posts/2022-11-08-fritz-dect-301-home-assistant/index.md",
        "author": "info@3960.org (Frank Boës)",
        "categories": [
          "Home Assistant",
          "Programmierung",
          "Technologie",
          "The Cool",
          "AVM",
          "Homeoffice"
        ]
      },
      {
        "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": "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"
        ]
      }
    ]
  }
}