A place to cache linked articles (think custom and personal wayback machine)
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

index.html 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="en">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  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,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Generating Config driven Dynamic Forms using Web Components (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Is that even respected? Retrospectively? What a shAItshow…
  28. https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
  29. <meta name="robots" content="noai, noimageai">
  30. <!-- Documented, feel free to shoot an email. -->
  31. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  32. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  36. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  37. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  38. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  39. <script>
  40. function toggleTheme(themeName) {
  41. document.documentElement.classList.toggle(
  42. 'forced-dark',
  43. themeName === 'dark'
  44. )
  45. document.documentElement.classList.toggle(
  46. 'forced-light',
  47. themeName === 'light'
  48. )
  49. }
  50. const selectedTheme = localStorage.getItem('theme')
  51. if (selectedTheme !== 'undefined') {
  52. toggleTheme(selectedTheme)
  53. }
  54. </script>
  55. <meta name="robots" content="noindex, nofollow">
  56. <meta content="origin-when-cross-origin" name="referrer">
  57. <!-- Canonical URL for SEO purposes -->
  58. <link rel="canonical" href="https://codeburst.io/generating-config-driven-dynamic-forms-using-web-components-7c8d400f7f2e">
  59. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  60. <article>
  61. <header>
  62. <h1>Generating Config driven Dynamic Forms using Web Components</h1>
  63. </header>
  64. <nav>
  65. <p class="center">
  66. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  67. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  68. </svg> Accueil</a> •
  69. <a href="https://codeburst.io/generating-config-driven-dynamic-forms-using-web-components-7c8d400f7f2e" title="Lien vers le contenu original">Source originale</a>
  70. <br>
  71. Mis en cache le 2024-02-23
  72. </p>
  73. </nav>
  74. <hr>
  75. <div class="ch bg fy fz ga gb"><p id="b3f8" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Over the years, we as UI Developers have become so habitual of using the already available UI Frameworks and Libraries like React, Angular, Vue etc due to the increasing demand of shipping things quickly. And the main selling point of these frameworks is that they not only solve this problem efficiently but also provide us with a sophisticated interface and apis while hiding the lower level details. The details are so hidden that over the years, we’ve sort of become complacent accepting the “magic” that happens behind the scenes that renders our Angular or React component we just created in typescript.</p>
  76. <h1 id="bece" class="ol om gt be on oo op oq or os ot ou ov ow ox oy oz pa pb pc pd pe pf pg ph pi bj">Agenda</h1><p id="8882" class="pw-post-body-paragraph nn no gt np b nq pj ns nt nu pk nw nx ny pl oa ob oc pm oe of og pn oi oj ok gm bj">The purpose of this post is not into exploring how the internals mechanisms of these frameworks work. Rather, I’d be discussing about a scenario wherein let’s assume that these frameworks don’t exist in the market and if you were to write components today using plain VanillaJS and Web Standards, how you’d do that along with some complexity of introducing dynamic config driven and reactive components. Throughout the article, we’ll observe the cost we pay for not using a sophisticated framework and how you have to think of workarounds to get the job done by yourself. Here’s the repo link <a class="af po" href="https://github.com/paramsinghvc/dynamic-form-web-components" rel="noopener ugc nofollow" target="_blank">https://github.com/paramsinghvc/dynamic-form-web-components</a>. Feel free to fork and extend it’s functionality.</p><h1 id="7a2e" class="ol om gt be on oo op oq or os ot ou ov ow ox oy oz pa pb pc pd pe pf pg ph pi bj">Web Components</h1><figure class="pq pr ps pt pu na nj nk paragraph-image"><figcaption class="nh fe ni nj nk nl nm be b bf z dt">Web Components analogy to Lego Blocks</figcaption></figure><blockquote class="pz qa qb"><p id="4b5f" class="nn no qc np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.</p></blockquote><p id="9e26" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">In other words, the web components standard implemented by the browsers help us create custom html elements by extending the existing elements or base elements while keeping them well encapsulated or independent and hence suitable for reusability throughout our applications.</p><p id="e383" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">The web components specification is based on the following three pillars:</p><ol class=""><li id="3778" class="nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok qd qe qf bj"><strong class="np gu">Custom Elements</strong>: We can now create our own html tags like <code class="cw qg qh qi qj b">&lt;my-element&gt;</code> with the set of api provided for the same. Just like we create a React or Angular component to isolate a piece of functionality and to perform some desired behaviour on some event, we can use the custom elements api to do the same without using any third party framework.</li><li id="30bc" class="nn no gt np b nq qk ns nt nu ql nw nx ny qm oa ob oc qn oe of og qo oi oj ok qd qe qf bj"><strong class="np gu">Shadow DOM</strong>: Shadow DOM is a great way to encapsulate the DOM and CSSOM of our web component so that it doesn’t mess around or get messed around by it’s surrounding or ancestor elements. In other words, it doesn’t allow leaking of styles from the parent to children sub-dom trees and vice versa. The use case fits best when you want to create a pluggable component like a chat support window inside the application such that it’s theming and styles are not influenced by the host application (Remember using iframes for the same thing?)</li></ol><figure class="pq pr ps pt pu na nj nk paragraph-image"><figcaption class="nh fe ni nj nk nl nm be b bf z dt">Example of shadow DOM</figcaption></figure><p id="a941" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">And, did you know that the already existing html elements also have shadow dom built inside them, you can enable them to be seen inside the devtools by going to settings → elements→ show user agent shadow dom</p><figure class="pq pr ps pt pu na nj nk paragraph-image"><figcaption class="nh fe ni nj nk nl nm be b bf z dt">In Built elements also have shadow dom under the hood</figcaption></figure><figure class="pq pr ps pt pu na nj nk paragraph-image"><figcaption class="nh fe ni nj nk nl nm be b bf z dt">Enabling flag to peek the shadow dom of browser elements</figcaption></figure><p id="cb27" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">3. <strong class="np gu">Template Tag</strong>: The <code class="cw qg qh qi qj b">&lt;template&gt;</code> and <code class="cw qg qh qi qj b">&lt;slot&gt;</code> tags can be used to create markup that can be loaded into the existing DOM on demand, pretty much like we create handlebars but sadly <code class="cw qg qh qi qj b">&lt;template&gt;</code> doesn’t support interpolation ( <code class="cw qg qh qi qj b">{{title}}</code> ) of dynamic variables.</p><figure class="pq pr ps pt pu na nj nk paragraph-image"><figcaption class="nh fe ni nj nk nl nm be b bf z dt">Handlebars Template example</figcaption></figure><p id="7038" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Anything inside the template tag isn’t loaded or honoured unless its activated dynamically by using JavaScript. Hence, all the scripts, images and markup is dormant till we add it to a host element explicitly.</p><figure class="pq pr ps pt pu na nj nk paragraph-image"><figcaption class="nh fe ni nj nk nl nm be b bf z dt">Template tag example</figcaption></figure><p id="0695" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">The content property of a template tag returns the <a class="af po" href="https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment" rel="noopener ugc nofollow" target="_blank">document fragment</a> of what’s present inside the tag. Document Fragment is a collection of dom nodes to which if any change is made like appending, removing, it won’t cause a paint or layout reflow unless it’s added to the dom. Hence, we can define the markup of our custom element using it so that it’ll be re used across all our component instances.</p><h1 id="2801" class="ol om gt be on oo op oq or os ot ou ov ow ox oy oz pa pb pc pd pe pf pg ph pi bj">Config Driven Development (CDD)</h1><p id="52d6" class="pw-post-body-paragraph nn no gt np b nq pj ns nt nu pk nw nx ny pl oa ob oc pm oe of og pn oi oj ok gm bj">A config driven development involves maintaining a config in say JSON format, which can be used to do all the mundane and repetitive tasks of rendering. Maintaining a config has a lot of benefits</p><ul class=""><li id="d284" class="nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok qu qe qf bj">First, it provides a generic interface to develop things which help your project scale well.</li><li id="6802" class="nn no gt np b nq qk ns nt nu ql nw nx ny qm oa ob oc qn oe of og qo oi oj ok qu qe qf bj">It saves a lot of development time and effort. Instead of developing new pages or modules in a imperative way by writing the code for each module yourself, you can just configure the module or page.</li><li id="28e6" class="nn no gt np b nq qk ns nt nu ql nw nx ny qm oa ob oc qn oe of og qo oi oj ok qu qe qf bj">The config can be decoupled from the frontend code base and hence any modification required in the future won’t require a deployment on UI.</li><li id="497e" class="nn no gt np b nq qk ns nt nu ql nw nx ny qm oa ob oc qn oe of og qo oi oj ok qu qe qf bj">Since the config is maintained in the backend as JSON, experimentation by product teams can be done easily by just changing the configuration but yeah incorporation of a new element or widget would require writing code for it on the UI of course.</li><li id="7eb3" class="nn no gt np b nq qk ns nt nu ql nw nx ny qm oa ob oc qn oe of og qo oi oj ok qu qe qf bj">Centralised code for taking and rendering the config would be more robust since any errors would be centralised to that code only. Contrary to a scenario where we had different codebases for different modules.</li></ul><figure class="pq pr ps pt pu na nj nk paragraph-image"><figcaption class="nh fe ni nj nk nl nm be b bf z dt">End result of the UI rendered from config</figcaption></figure><p id="3023" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Let me explain with an example, suppose you have an application which has a lot of forms to be rendered. So instead of writing form code multiple times which involves, writing html markup, styling, initialising, binding event handlers, validations, what we can do is that we can maintain a configuration file wherein we need to write about the form elements and their behaviours in response to events like keypress, click, change and how to handle validations etc. And this configuration framework can now be used to create any sort of form. So, it’s like a one time investment which gives a lot of benefits in the long run.</p><p id="e13c" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Here’s how the config looks like</p><figure class="pq pr ps pt pu na"></figure>
  77. <h2 id="19b7" class="qz om gt be on ra rb dx or rc rd dz ov ny re rf rg oc rh ri rj og rk rl rm rn bj">Config Parser</h2><p id="044d" class="pw-post-body-paragraph nn no gt np b nq pj ns nt nu pk nw nx ny pl oa ob oc pm oe of og pn oi oj ok gm bj">The above config needs to be read and translated into Web Components to be rendered as</p><figure class="pq pr ps pt pu na nj nk paragraph-image"></figure><p id="67f5" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">The config passed is looped over and for each entry in the config, we determine it’s component to be rendered based on the type passed. There’s a <code class="cw qg qh qi qj b">COMPONENTS_MAPPING</code> that we need to maintain to map the component type to the actual web component tag name. Once, we get that we create that element in JS using <code class="cw qg qh qi qj b">document.createElement</code> etc which is hidden behind the helper method <code class="cw qg qh qi qj b">createNode(nodeName, options)</code> in the above snippet.</p><p id="8363" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Besides this, there’s a <code class="cw qg qh qi qj b">formValidations</code> map that is to be kept which is nothing but a map of elementId vs it’s current form validation state. For eg: An age form element at any point could hold validations of required, out of range, not a number etc. All of it could be maintained inside this map.</p><p id="a238" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Along with this, we need to maintain a <code class="cw qg qh qi qj b">formElementsMap</code> which would be used for triggering changes imperatively on a component in response to an event. We’ll see that later.</p><h1 id="a63b" class="ol om gt be on oo op oq or os ot ou ov ow ox oy oz pa pb pc pd pe pf pg ph pi bj">Reactive Components</h1><p id="cfe7" class="pw-post-body-paragraph nn no gt np b nq pj ns nt nu pk nw nx ny pl oa ob oc pm oe of og pn oi oj ok gm bj">Now, the way we want our components to be dynamic and reactive enough to respond to any changes in the so called “props” that we pass to them.</p><p id="9b7d" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Before jumping on to how we’d implement them with web components, lets reflect upon how it works with the UI frameworks we have today. In React, we pass props to a component, when any of the prop changes, a reconciliation process is triggered internally by react which traverses the whole component hierarchy to mark any changes required in the given component at a time.</p><figure class="pq pr ps pt pu na nj nk paragraph-image"><figcaption class="nh fe ni nj nk nl nm be b bf z dt">Uni-directional data flow in React apps</figcaption></figure><p id="e812" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Same goes for Angular, where we specify <code class="cw qg qh qi qj b">@Input()</code> decorator for all the input props of given components. By doing this, all the frameworks maintain a separate space in the memory for internal housekeeping like maintaining a registry of the props of a given component and checking the previous props and next props and then responding to any changes if found by re-rendering the part to be rendered. Hence, they always make sure that the component view is always in the sync with the component state or model. That’s the main selling point of these sophisticated frameworks out there. Because, natively it has not been possible in vanilla JS to determine the changes in response to the component props.</p><p id="6ea0" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">But today, there are two ways you can listen to any prop changes happening on a web component</p><ul class=""><li id="3381" class="nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok qu qe qf bj">Using <a class="af po" href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver" rel="noopener ugc nofollow" target="_blank">Mutation Observer</a> API</li><li id="2687" class="nn no gt np b nq qk ns nt nu ql nw nx ny qm oa ob oc qn oe of og qo oi oj ok qu qe qf bj">Web Components Api <code class="cw qg qh qi qj b">attributeChangedCallback(attrName, oldValue, newValue)</code></li></ul>
  78. <h1 id="2a3b" class="ol om gt be on oo op oq or os ot ou ov ow ox oy oz pa pb pc pd pe pf pg ph pi bj">Building Custom Components</h1><p id="b6a7" class="pw-post-body-paragraph nn no gt np b nq pj ns nt nu pk nw nx ny pl oa ob oc pm oe of og pn oi oj ok gm bj">Let’s extend HTML with our own form elements.</p><figure class="pq pr ps pt pu na nj nk paragraph-image"></figure><p id="f1c9" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Here’s how it would look like to implement your own custom web component without using any third party library or framework. But it comes with a cost which I’ll be discussing soon. We can register the given component class onto the current window by doing</p><figure class="pq pr ps pt pu na nj nk paragraph-image"></figure><p id="1cf2" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Since, we’ve been using libs like React and Angular for over years now, we expect our web components as well to behave the same way as the ones created with React. But here we won’t be investing time in building an elegant layer of our own compilation, data binding, reconciliation (change detection). Rather, my purpose here is to use the existing web apis out of the box.</p><p id="da36" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">So, for detecting changes based on the props, <code class="cw qg qh qi qj b">attributeChangedCallback</code> seems a good (and only :P) fit. Hence, we produce all the dynamic properties on the components as attributes and we need to inform out web component about which properties it should observe by defining a static getter property on it as</p><figure class="pq pr ps pt pu na nj nk paragraph-image"></figure><p id="86c2" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">It means that if any of these attributes change on a given component, we will be able to tap into the process by writing logic in the <code class="cw qg qh qi qj b">attributeChangedCallback</code></p><p id="5ccb" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">So far, we discussed about inputs to our component, what about outputs? If any internal state of our component changes, we need to tell the outer world or the parent component about it by dispatch a custom event.</p><figure class="pq pr ps pt pu na nj nk paragraph-image"></figure><p id="aea7" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">If you observe the component we defined carefully here, we’re basically treating it as a black block, it could have it’s internal state, animations, data at any given point of time. All we’re interested in is giving it an input and expecting an output to keep the input too in sync. Just like how unidirectional data flow concept of React works.</p><figure class="pq pr ps pt pu na nj nk paragraph-image"></figure><p id="90d7" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Here, view is our component, state is the input attribute accessed via getter <code class="cw qg qh qi qj b">this.value</code> and action is the change event we’re triggering when the value changes through user input in the text box. Hence, in this example, we’re just relaying input and output to and from our input box inside it so far.</p>
  79. <h1 id="daf2" class="ol om gt be on oo op oq or os ot ou ov ow ox oy oz pa pb pc pd pe pf pg ph pi bj">Validations</h1><p id="abf3" class="pw-post-body-paragraph nn no gt np b nq pj ns nt nu pk nw nx ny pl oa ob oc pm oe of og pn oi oj ok gm bj">But now, let’s say we want to incorporate support for validations for our form components. Since an element could have multiple validation error, a map would be a good data structure to hold it against every component id.</p><figure class="pq pr ps pt pu na nj nk paragraph-image"><figcaption class="nh fe ni nj nk nl nm be b bf z dt">Validation Example</figcaption></figure><p id="e398" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">It looks like this in the form of map.</p><figure class="pq pr ps pt pu na nj nk paragraph-image"><figcaption class="nh fe ni nj nk nl nm be b bf z dt">Data structure for storing validations</figcaption></figure><p id="fb1b" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Please note that, it’ll be better to use an ES6 Map here to avoid unnecessary load of object proto on it. But I haven’t used because of JSON stringification.</p><p id="1f1e" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">We’ll store the element validity status inside a sort of global map as <code class="cw qg qh qi qj b">formValidations</code> . The idea is to update this map by listening to the valueChange event and checking that value based on the validation configuration passed for that element and appending an error key in the map if the validation criteria fails. Here’s a small example for Range validation.</p><figure class="pq pr ps pt pu na nj nk paragraph-image"></figure><p id="8490" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Now that we’ve updated the formValidations map, we need to pass it as an attribute to our custom component but wait a second till you see this</p><figure class="pq pr ps pt pu na nj nk paragraph-image"><figcaption class="nh fe ni nj nk nl nm be b bf z dt">attributes values are converted to strings</figcaption></figure><p id="792d" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Whoops! HTML attributes get stringified, hence we can’t pass objects or array into it. Hence, an extra step of wrapping and unwrapping to and from JSON needs to be done here. This is one thing that I found a little disappointing working with web components because we’re so used to passing non-primitive data structures in and out our components that we found it to be instinctive here as well.</p><figure class="pq pr ps pt pu na nj nk paragraph-image"></figure><p id="35b0" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Big deal! We can do it by identifying whether the attribute value being passed is of non primitive type. (Please mind that typeof array is also an object). Although this is not a very fool proof way for identifying since creating a string using <code class="cw qg qh qi qj b">new String('')</code> would give typeof as true.</p><p id="b6e2" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">Now the next step is to tap into the <code class="cw qg qh qi qj b">attributeChangedCallback</code> hook provided in the web components lifecycle api and listen for the changes happening for a given attribute and react accordingly by re-rendering the desired piece of dom. Here, in case of validations we’d like to render the <code class="cw qg qh qi qj b">&lt;ul&gt;</code> that we have created as a sibling to our input component. We need to parse the stringified json attribute values and shallow compare them as then trigger changes or else there would be a lot of re-renders.</p><figure class="pq pr ps pt pu na nj nk paragraph-image"></figure><p id="a3b1" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">We’re reacting to the validations attribute by checking whether it’s an empty object or not. If it’s empty, it means there are no validation errors and we need to remove the previously rendered errors ui or else re-render the errors div and apply styling on the input element accordingly like a red border.</p><figure class="pq pr ps pt pu na nj nk paragraph-image"></figure><p id="c6b0" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">The renderErrors function look like this. I’m using documentFragment to append all the dom nodes first and then add it to the original dom at once in the end.</p><p id="fd11" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">So, overall this is the basic approach I’ve found to make the custom components analogous to the way we create them using any framework.</p>
  80. <h1 id="b06a" class="ol om gt be on oo op oq or os ot ou ov ow ox oy oz pa pb pc pd pe pf pg ph pi bj">Lessons Learnt</h1><p id="8a58" class="pw-post-body-paragraph nn no gt np b nq pj ns nt nu pk nw nx ny pl oa ob oc pm oe of og pn oi oj ok gm bj">Following are some lessons learnt in my wishful pursuit of using vanilla JS completely for creating dynamic and reactive applications that we create today:</p><ul class=""><li id="9163" class="nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok qu qe qf bj">It’s hard to pass the data into the components due to an extra json stringification and de-stringification layer.</li><li id="8a62" class="nn no gt np b nq qk ns nt nu ql nw nx ny qm oa ob oc qn oe of og qo oi oj ok qu qe qf bj">You have to rely on setAttribute and getAttribute for “props” to a component since attributeChangedCallback is the only way to detect changes.</li><li id="1d00" class="nn no gt np b nq qk ns nt nu ql nw nx ny qm oa ob oc qn oe of og qo oi oj ok qu qe qf bj">If I reminisce about angular or vue data bindings and how they work actually. These frameworks have compilers or dom parsers which look for these bindings and update the UI in response to any change in the bound properties(model). But here, we have to explicitly, write updation logic for handling every prop/attribute change, just how we did for <code class="cw qg qh qi qj b">validations</code> key by calling renderErrors every time which is far beyond an optimised approach compared to React or Vue keys or angular’s <code class="cw qg qh qi qj b">trackBy</code>.</li></ul><p id="9e36" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">It’s due to these limitations and poor browser support and incompatibilities that these frameworks exists because they didn’t wanted to wait for browsers to implement all this. Hence, these frameworks have come up with their own individual ways of dom creation and manipulation with a splendid work for years now.</p>
  81. <h1 id="3186" class="ol om gt be on oo op oq or os ot ou ov ow ox oy oz pa pb pc pd pe pf pg ph pi bj">Conclusion</h1><p id="03c0" class="pw-post-body-paragraph nn no gt np b nq pj ns nt nu pk nw nx ny pl oa ob oc pm oe of og pn oi oj ok gm bj">We explored a twisted way of creating web components with a vow of not using any third party thing and we ran through some obstacles and discussed how we could solve them while keeping the problem statement non-trivial. Relying on Web components to create production ready app would a strong statement to say because I still find it to be in an immature state. I’ve been just wondering about the future days wherein the web apis would be mature enough to let us create what we do using third party frameworks today. A utopia for web that I can envision would have rich component creation and manipulation capabilities in built as a part of spec and browsers implementing them. Hence, we won’t have to include any of the heavy third party libraries which will give faster load times to the users by saving the download costs by a huge amounts.</p><p id="4f7f" class="pw-post-body-paragraph nn no gt np b nq nr ns nt nu nv nw nx ny nz oa ob oc od oe of og oh oi oj ok gm bj">That said, here’s a reason to cheer. The web components are platform neutral and if you’re thinking of creating framework agnostic components with all the lux and ease of apis at par with the frameworks we have today, we can use <a class="af po" href="https://www.polymer-project.org" rel="noopener ugc nofollow" target="_blank">Polymer</a> or <a class="af po" href="https://stenciljs.com" rel="noopener ugc nofollow" target="_blank">Stencil</a> for the same.</p>
  82. <h1 id="322d" class="ol om gt be on oo op oq or os ot ou ov ow ox oy oz pa pb pc pd pe pf pg ph pi bj">References</h1></div>
  83. </article>
  84. <hr>
  85. <footer>
  86. <p>
  87. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  88. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  89. </svg> Accueil</a> •
  90. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  91. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  92. </svg> Suivre</a> •
  93. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  94. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  95. </svg> Pro</a> •
  96. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  97. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  98. </svg> Email</a> •
  99. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  100. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  101. </svg> Légal</abbr>
  102. </p>
  103. <template id="theme-selector">
  104. <form>
  105. <fieldset>
  106. <legend><svg class="icon icon-brightness-contrast">
  107. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  108. </svg> Thème</legend>
  109. <label>
  110. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  111. </label>
  112. <label>
  113. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  114. </label>
  115. <label>
  116. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  117. </label>
  118. </fieldset>
  119. </form>
  120. </template>
  121. </footer>
  122. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  123. <script>
  124. function loadThemeForm(templateName) {
  125. const themeSelectorTemplate = document.querySelector(templateName)
  126. const form = themeSelectorTemplate.content.firstElementChild
  127. themeSelectorTemplate.replaceWith(form)
  128. form.addEventListener('change', (e) => {
  129. const chosenColorScheme = e.target.value
  130. localStorage.setItem('theme', chosenColorScheme)
  131. toggleTheme(chosenColorScheme)
  132. })
  133. const selectedTheme = localStorage.getItem('theme')
  134. if (selectedTheme && selectedTheme !== 'undefined') {
  135. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  136. }
  137. }
  138. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  139. window.addEventListener('load', () => {
  140. let hasDarkRules = false
  141. for (const styleSheet of Array.from(document.styleSheets)) {
  142. let mediaRules = []
  143. for (const cssRule of styleSheet.cssRules) {
  144. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  145. continue
  146. }
  147. // WARNING: Safari does not have/supports `conditionText`.
  148. if (cssRule.conditionText) {
  149. if (cssRule.conditionText !== prefersColorSchemeDark) {
  150. continue
  151. }
  152. } else {
  153. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  154. continue
  155. }
  156. }
  157. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  158. }
  159. // WARNING: do not try to insert a Rule to a styleSheet you are
  160. // currently iterating on, otherwise the browser will be stuck
  161. // in a infinite loop…
  162. for (const mediaRule of mediaRules) {
  163. styleSheet.insertRule(mediaRule.cssText)
  164. hasDarkRules = true
  165. }
  166. }
  167. if (hasDarkRules) {
  168. loadThemeForm('#theme-selector')
  169. }
  170. })
  171. </script>
  172. </body>
  173. </html>