A place to cache linked articles (think custom and personal wayback machine)
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

index.html 31KB

il y a 4 ans
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Progressive enhancement with handlers and enhancers (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="https://hiddedevries.nl/en/blog/2015-04-03-progressive-enhancement-with-handlers-and-enhancers">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. Progressive enhancement with handlers and enhancers (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="https://hiddedevries.nl/en/blog/2015-04-03-progressive-enhancement-with-handlers-and-enhancers">Source originale du contenu</a></h3>
  445. <p class="intro">Recently I adopted a different way to manage bits of JavaScript in websites I was building. It exercises progressive enhancement (PE) by declaring handler and enhancer functions on <span class="caps">HTML</span> elements.</p>
  446. <p><strong>TL;DR</strong>: When JavaScript is used to handle user interactions like clicks, or enhance the page by manipulating the <span class="caps">DOM</span>, traditionally you&#8217;d have JavaScript find that <span class="caps">HTML</span> element in your page, and hook some code into it. But what if you&#8217;d switch that around, and have the <span class="caps">HTML</span> element “tell” JavaScript what function to execute?</p>
  447. <p>How? I declare JavaScript functions on <span class="caps">HTML</span> elements. Separating them to functions that happen on click and those that happen on page load, I use two attributes (<code>data-handler</code> and <code>data-enhancer</code>) that both get space-separated function names as their values. Then, with a little bit of JavaScript, I make sure the functions execute when they need to.</p>
  448. <p>Note that this works best for websites for which the mark-up is rendered on the server, to which JavaScript is added as an extra layer. Websites that rely on JavaScript for rendering content will probably have options to do the same within the framework they are built with.</p>
  449. <h2>Separating to handlers and enhancers</h2>
  450. <p>On many small to medium sized websites, the required JavaScript can be drilled down to two types of usage: </p>
  451. <ol>
  452. <li>things that need to happen on a click</li>
  453. <li>enhancements after the inital page load</li>
  454. </ol>
  455. <p>Surely, there are often other events we want to use for triggering JavaScript (scroll, anyone?), but let&#8217;s focus on these two types first.</p>
  456. <p>Many simple websites will have both types of script in a single <code>scripts.js</code> file, which is also used to query the nodes interactions need to happen on, and to add click handlers to those nodes.</p>
  457. <p>This year, <a href="http://krijnhoetmer.nl">Krijn</a> and <a href="http://brinkhu.is">Matijs</a> introduced me to a new way to go about this. It has proven to be a very powerful pattern in some of my recent projects, which is why I’d like to share it here. Credits for the pattern, and for the initialisation functions I will discuss later, go to them.</p>
  458. <h2>Including JavaScript functions to the declarative model</h2>
  459. <p>The pattern starts from the idea that web pages have three layers to them: structure (<span class="caps">HTML</span>), lay-out (<span class="caps">CSS</span>) and enhancement (JavaScript). <span class="caps">HTML</span> is a declarative language: as an author, you declare what you’d like something to be, and that is what it will be. </p>
  460. <blockquote class="in-content">
  461. <p class="in-content">Let this be a header, let this be a list item, let this be a block quote.</p>
  462. </blockquote>
  463. <p>This is great, browsers can now know that ‘this’ is a list item, and treat it as such. Screenreaders may announce it as a list, your <span class="caps">RSS</span> reader or mobile Safari “reader view” can view it as a list, et cetera.</p>
  464. <p>With <span class="caps">CSS</span> we declare what things look like:</p>
  465. <blockquote class="in-content">
  466. <p class="in-content">Let headers be dark blue, let list items have blue bullets and let block quotes render in slightly smaller type.</p>
  467. </blockquote>
  468. <p>This works rather well for us, because now we don’t need to think about what will happen if we add another list item. It being a list item, it will have the <span class="caps">CSS</span> applied to it, so it will have blue bullets. </p>
  469. <p>The idea I’d like to share here, is that of making JavaScript functions part of this declarative model. If we can declare what something looks like in <span class="caps">HTML</span>, why not declare what its behaviour is, too?</p>
  470. <h2 id="handlers">Handlers: things that happen on a click</h2>
  471. <p>The idea is simple: we introduce a <code>data-handler</code> attribute to all elements that need to trigger function execution. As their value, we add one or more functions name(s) that need(s) to execute on click. </p>
  472. <p>For example, here’s a link:</p>
  473. <pre class="language-markup"><code>&lt;a href=&quot;#punk-ipa&quot;&gt;More about Punk IPA&lt;/a&gt;</code></pre>
  474. <p>This is an in-page link to a section that explains more about Punk <span class="caps">IPA</span>. It will work regardless of JavaScript, but can be improved with it.</p>
  475. <p>Cooler than linking to a section about Punk <span class="caps">IPA</span>, is to have some sort of overlay open, with or without a fancy animation. We add a <code>data-handler</code> attribute:</p>
  476. <pre class="language-markup"><code>&lt;a href=&quot;#punk-ipa&quot; data-handler=&quot;overlay&quot;&gt;More about Punk IPA&lt;/a&gt; </code></pre>
  477. <p>In the data-handler, the value holds a function name, in this case ‘overlay’. The idea is that the overlay function executes when the link is clicked. Naturally, you would be able to add more than one function name, and separate function names by spaces. This works just like <code>class=&quot;foo bar&quot;</code>.</p>
  478. <p>Within the function declaration, we will know which element was clicked, so we can access attributes. We can access the <code>href</code> or any data attribute. With that, it can grab the content that’s being linked to, append it into some sort of overlay, and smoothly transition the overlay into the page.</p>
  479. <p>Note that this is similar to doing <code>&lt;a onclick=&quot;overlayfunction(); anotherfunction();&quot;&gt;</code>, but with the added benefit that a <code>data-handler</code> only gets meaning once JavaScript is active and running, and that it contains strings like <span class="caps">CSS</span> classes, instead of actual JavaScript code. This way, the scripting is separated in the same way as the style rules are.</p>
  480. <p>Also note that is best practice to only add handlers to <span class="caps">HTML</span> elements that are made for click behaviour, like <code>&lt;button&gt;</code>s and <code>&lt;a&gt;</code>.</p>
  481. <h3>Adding function definitions</h3>
  482. <p>In our JavaScript (e.g. an included <code>scripts.js</code> file), we add all functions for click behaviour to one object: </p>
  483. <pre class="language-javascript"><code>var handlers = {
  484. &#039;function-name&#039; : function(e) {
  485. // This function is executed on click of any element with
  486. // &#039;function-name&#039; in its data-handler attribute.
  487. // The click event is in &#039;e&#039;, $(this).attr(&#039;data-foo&#039;) holds the
  488. // value of data-foo of the element the user clicked on
  489. },
  490. &#039;another-function-name&#039; : function(e) {}
  491. };</code></pre>
  492. <p>Those working in teams could consider making the handler object global. That way functions can have their own files, making collaboration through version control easier.</p>
  493. <h3>Adding click handling: one handler to rule them all</h3>
  494. <p>If we set all our click-requiring functionality up within <code>data-handler</code> attributes, we only need to attach one click handler to our document. <a href="http://davidwalsh.name/event-delegate">Event delegation</a> can then be used to do stuff to the actual element that is clicked on, even when that actual element did not exist on page load (i.e. was loaded in with <span class="caps">AJAX</span>).</p>
  495. <p>This function (jQuery) can be used to handle clicks, then search through the handler functions and apply the ones specified in the data-handler function:</p>
  496. <pre class="language-javascript"><code>$(function() {
  497. &#039;use strict&#039;;
  498. // generic click handler
  499. $(document).on(&#039;click&#039;, &#039;[data-handler]&#039;, function(event) {
  500. var handler = this.getAttribute(&#039;data-handler&#039;);
  501. // honour default behaviour when using modifier keys when clicking
  502. // for example:
  503. // cmd + click / ctrl + click opens a link in a new tab
  504. // shift + click opens a link in a new window
  505. if (this.tagName === &#039;A&#039; &amp;&amp; (event.metaKey || event.ctrlKey || event.shiftKey)) {
  506. return;
  507. }
  508. if (handlers &amp;&amp; typeof handlers[handler] === &#039;function&#039;) {
  509. handlers[handler].call(this, event);
  510. }
  511. else {
  512. if (window.console &amp;&amp; typeof console.log === &#039;function&#039;) {
  513. console.log(&#039;Non-existing handler: &quot;%s&quot; on %o&#039;, handler, this);
  514. }
  515. }
  516. });
  517. });</code></pre>
  518. <p>(<a href="https://gist.github.com/matijs/5b2d6675265ec440bcba">Source</a>; see also <a href="https://gist.github.com/matijs/9bb314a9a184b53952e2">its vanilla JavaScript version</a>)</p>
  519. <h2 id="enhancers">Enhancers: things that happen after page load</h2>
  520. <p>We can run functions to enhance elements in a similar way, by adding their function names to a <code>data-enhancer</code> attribute. The corresponding functions go into a <code>enhancers</code> object, just like the <code>handlers</code> object above.</p>
  521. <p>For example, a page element that needs to display tweets. Again, here&#8217;s a link:</p>
  522. <pre class="language-markup"><code>&lt;a href=&quot;https://twitter.com/bbc&quot;&gt;Tweets of the BBC&lt;/a&gt;</code></pre>
  523. <p>To enhance this link to the <span class="caps">BBC</span>&#8217;s tweets, we may want to load a widget that displays actual tweets. The function to do that may add some container <code>&lt;div&gt;</code>s, and run some calls to the Twitter <span class="caps">API</span> to grab tweets. To trigger this function:</p>
  524. <pre class="language-markup"><code>&lt;a href=&quot;https://twitter.com/bbc&quot; data-enhancer=&quot;twitter-widget&quot;&gt;
  525. Tweets of the BBC&lt;/a&gt;</code></pre>
  526. <p>To find out whose Twitter widget to display, our function could analyse the <span class="caps">URL</span> in the <code>href</code> attribute, or we can add an extra attribute:</p>
  527. <pre class="language-markup"><code>&lt;a href=&quot;https://twitter.com/bbc&quot; data-enhancer=&quot;twitter-widget&quot;
  528. data-twitter-user=&quot;bbc&quot;&gt;Tweets of the BBC&lt;/a&gt;</code></pre>
  529. <p>Another example: of a large amount of text, we want to hide all but the first paragraph, then add a “Show all” button to show the remainder. The <span class="caps">HTML</span> will contain all of the content, and we will hide the remainder with JavaScript.</p>
  530. <pre class="language-markup"><code>&lt;section&gt;
  531. &lt;p&gt;Some text&lt;/p&gt;
  532. &lt;p&gt;Some more text&lt;/p&gt;
  533. &lt;p&gt;Some more text&lt;/p&gt;
  534. &lt;/section&gt;</code></pre>
  535. <p>To the block of text we add a data-enhancer function that makes sure everything but the first paragraph is hidden, and a “Show all” button is added.</p>
  536. <pre class="language-markup"><code>&lt;section data-enhancer=&quot;only-show-first-paragraph&quot;&gt;
  537. &lt;p&gt;Some text&lt;/p&gt;
  538. &lt;p&gt;Some more text&lt;/p&gt;
  539. &lt;p&gt;Some more text&lt;/p&gt;
  540. &lt;/section&gt;</code></pre>
  541. <p>A function named ‘only-show-first-paragraph’ could then take care of removing the content, and adding a button that reveals it (this button could have a <code>data-handler</code> for that behaviour).</p>
  542. <h3>Running all enhancements</h3>
  543. <p>Assuming all our <code>enhancer</code> functions are in one <code>enhancer</code> object, we can run all <code>enhancers</code> on a page with one function. The function looks for all elements with a <code>data-enhancer</code> attribute, and calls the appropriate functions.</p>
  544. <pre class="language-javascript"><code>$(function() {
  545. &#039;use strict&#039;;
  546. // kick off js enhancements
  547. $(&#039;[data-enhancer]&#039;).each(function() {
  548. var enhancer = this.getAttribute(&#039;data-enhancer&#039;);
  549. if (enhancers &amp;&amp; typeof enhancers[enhancer] === &#039;function&#039;) {
  550. enhancers[enhancer].call(this);
  551. }
  552. else {
  553. if (window.console &amp;&amp; typeof console.log === &#039;function&#039;) {
  554. console.log(&#039;Non-existing enhancer: &quot;%s&quot; on %o&#039;, enhancer, this);
  555. }
  556. }
  557. });
  558. });</code></pre>
  559. <h2>Wrap-up</h2>
  560. <p>So the basic idea is: functions for click behaviour are handlers, and those that happen on page load are enhancers. They are stored in a <code>handlers</code> and <code>enhancers</code> object respectively, and triggered from <span class="caps">HTML</span> elements that have <code>data-handler</code> and <code>data-enhancer</code> attributes on them, to declare which functions they need.</p>
  561. <p>In summary: </p>
  562. <ol>
  563. <li>All functions that need to execute on click (or touch or pointer events), are declared on the <span class="caps">HTML</span> element that should trigger the click, in a <code>data-handler</code> attribute</li>
  564. <li>All functions that need to change/enhance stuff on the <span class="caps">DOM</span>, are declared on the <span class="caps">HTML</span> element they need to change, in a <code>data-enhancer</code> attribute</li>
  565. <li>Two JavaScript functions run through <code>data-handler</code> and <code>data-enhancer</code> attributes respectively, and execute all functions when they are required</li>
  566. </ol>
  567. <h2>Thoughts?</h2>
  568. <p>This pattern is not new, similar things have been done by others. Rik Schennink wrote about <a href="http://rikschennink.nl/thoughts/controlling-behaviour/">controlling behaviour</a> before, and his <a href="http://conditionerjs.com">conditioner.js</a> deserves special mention. It is a library that not only manages modules of JavaScript, it also activates them based on responsive breakpoints, something the pattern described here does not do out of the box (it could).</p>
  569. <p>For me and teams I worked in, the above has proven to be a useful way to declare actions and enhancements within pages. It adds maintainability, because it helps keeping functions organised. It also promotes reusability: although new functions can be added for each use, multiple elements can make use of the same function. </p>
  570. <p>The method is not perfect for every project, and it can definitely be improved upon. For example, we could add the function that triggers all <code>data-handler</code> functions to an enhancer (<code>&lt;html data-enhancer=&quot;add-handlers&quot;&gt;</code>), as it is an enhancement (of the page) by itself. In big projects with many people working on the same codebase, it may be useful to have all functions in separate files and have a globally available handlers object.</p>
  571. <p><em>Update 07/04/2015</em>: I have opened comments, would love to hear any feedback</p>
  572. </article>
  573. </section>
  574. <nav id="jumpto">
  575. <p>
  576. <a href="/david/blog/">Accueil du blog</a> |
  577. <a href="https://hiddedevries.nl/en/blog/2015-04-03-progressive-enhancement-with-handlers-and-enhancers">Source originale</a> |
  578. <a href="/david/stream/2019/">Accueil du flux</a>
  579. </p>
  580. </nav>
  581. <footer>
  582. <div>
  583. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  584. <p>
  585. Bonjour/Hi!
  586. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  587. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  588. </p>
  589. <p>
  590. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  591. </p>
  592. <p>
  593. Voici quelques articles choisis :
  594. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  595. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  596. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  597. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  598. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  599. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  600. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  601. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  602. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  603. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  604. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  605. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  606. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  607. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  608. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  609. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  610. </p>
  611. <p>
  612. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  613. </p>
  614. <p>
  615. Je ne traque pas ta navigation mais mon
  616. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  617. conserve des logs d’accès.
  618. </p>
  619. </div>
  620. </footer>
  621. <script type="text/javascript">
  622. ;(_ => {
  623. const jumper = document.getElementById('jumper')
  624. jumper.addEventListener('click', e => {
  625. e.preventDefault()
  626. const anchor = e.target.getAttribute('href')
  627. const targetEl = document.getElementById(anchor.substring(1))
  628. targetEl.scrollIntoView({behavior: 'smooth'})
  629. })
  630. })()
  631. </script>