{"tag":"jquery","articles":{"blog\/technology\/moving-to-vanilla-js":{"key":"blog\/technology\/moving-to-vanilla-js","type":"article","published":true,"meta":{"createdAt":"2018-01-19T19:02:20+01:00","publishedAt":"2018-01-31T19:01:00+01:00","group":"vanillin","category":"blog","subcategory":"technology","slug":"moving-to-vanilla-js"},"content":{"en":{"slug":"moving-to-vanilla-js","title":"Moving to vanilla JS","intro":"\u003Cfigure\u003E\n                \u003Cnoscript\u003E\n                    \u003Cimg src=\u0022https:\/\/avris.it\/image\/avris-forms-multiple_small.png\u0022 alt=\u0022\u0022 class=\u0022border-bottom\u0022 width=\u0022480\u0022 height=\u0022201.10091743119\u0022\u003E                \n                \u003C\/noscript\u003E\n                \u003Cspan class=\u0022hide-noscript\u0022\u003E\u003Cimg src=\u0022data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAPCAYAAACMa21tAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAB5klEQVRIib2VTW4TQRCFv+rpiRMyDnISLyEcACkhXAEuAVsuxQk4AogFSNkgWLIFkVVAiW1kouD58XQXi\/H4D409YxHeZko93VWvq15VS5ymygrI5KsKIov2yoMbwgrCoD8gsIY8dwTWgiqdTmcSXFHVKZESegtsjDFYADFCnueIGARwqihFdkSEb1+\/cH1zw\/5Bl6P799Y67l1d8uPyijwbc\/r4tBEpiZPVJQOmGWqK8lK1yYhgq4\/M3JXaMcasdeq9R4Gg5l738RPu7Axz8gj79AlWKq8w+2HE0B\/0iKI92tFuZYDcOdJ0TBiGDIdDRAzdw\/3K\/Wk2Ri++Q6+PPz9HBCQpu6xMSFkdma1536RkUnRgnTKLIKMROvyFtCOk3UbiNFs4KUw6aEpON9ZQU4gY7HL\/6l8GGCMkSUJrq4W1ttKh844sG2PDkDRJCIKAne3tyv25c+Sv3+DevcecHBM+f7asobk6LVGM4wRrLVaqCQUmILCTm6jHe1nfZqrgHKiWGsr+Tz1qwtbRh\/P+1giMfMrP8W92gxaHW22q8z+HOjNlU7y6+MDbwWcObMTLhy+wayfp0oO6YP+DYj\/Y6XLX3uF476h4quKk0NAsmCIUYtSJ0Ip1QUQX7IKYTMdCM3t+Xs3sP+dY5dEk0L+pAAAAAElFTkSuQmCC\u0022 data-src=\u0022https:\/\/avris.it\/image\/avris-forms-multiple_small.png\u0022 alt=\u0022\u0022 class=\u0022border-bottom\u0022 width=\u0022480\u0022 height=\u0022201.10091743119\u0022\u003E\u003C\/span\u003E\n                \n            \u003C\/figure\u003E\u003C\/p\u003E\n\u003Cp\u003EWhile working on \u003Ca href=\u0022https:\/\/gitlab.com\/Avris\/Forms\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E\u003Csvg class=\u0022icon\u0022\u003E\u003Cuse xlink:href=\u0022#light-link\u0022\u003E\u003C\/use\u003E\u003C\/svg\u003E Avris Forms v4.0\u003C\/a\u003E, I\u2019ve decided to migrate some code from \u003Ca href=\u0022http:\/\/coffeescript.org\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E\u003Csvg class=\u0022icon\u0022\u003E\u003Cuse xlink:href=\u0022#light-link\u0022\u003E\u003C\/use\u003E\u003C\/svg\u003E CoffeScript\u003C\/a\u003E with \u003Ca href=\u0022https:\/\/jquery.com\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E\u003Csvg class=\u0022icon\u0022\u003E\u003Cuse xlink:href=\u0022#light-link\u0022\u003E\u003C\/use\u003E\u003C\/svg\u003E jQuery\u003C\/a\u003E to \u003Ca href=\u0022http:\/\/vanilla-js.com\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E\u003Csvg class=\u0022icon\u0022\u003E\u003Cuse xlink:href=\u0022#light-link\u0022\u003E\u003C\/use\u003E\u003C\/svg\u003E Vanilla JS\u003C\/a\u003E. And I guess it might be a good idea to share this transition \ud83d\ude09\u003C\/p\u003E\u003Csvg xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022 style=\u0022display: none;\u0022\u003E\u003C\/svg\u003E","content":"\u003Cfigure\u003E\n                \u003Cnoscript\u003E\n                    \u003Cimg src=\u0022https:\/\/avris.it\/image\/avris-forms-multiple_big.png\u0022 alt=\u0022\u0022 class=\u0022border\u0022 width=\u0022654\u0022 height=\u0022274\u0022\u003E                \n                \u003C\/noscript\u003E\n                \u003Cspan class=\u0022hide-noscript\u0022\u003E\u003Cimg src=\u0022data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAPCAYAAACMa21tAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAB5klEQVRIib2VTW4TQRCFv+rpiRMyDnISLyEcACkhXAEuAVsuxQk4AogFSNkgWLIFkVVAiW1kouD58XQXi\/H4D409YxHeZko93VWvq15VS5ymygrI5KsKIov2yoMbwgrCoD8gsIY8dwTWgiqdTmcSXFHVKZESegtsjDFYADFCnueIGARwqihFdkSEb1+\/cH1zw\/5Bl6P799Y67l1d8uPyijwbc\/r4tBEpiZPVJQOmGWqK8lK1yYhgq4\/M3JXaMcasdeq9R4Gg5l738RPu7Axz8gj79AlWKq8w+2HE0B\/0iKI92tFuZYDcOdJ0TBiGDIdDRAzdw\/3K\/Wk2Ri++Q6+PPz9HBCQpu6xMSFkdma1536RkUnRgnTKLIKMROvyFtCOk3UbiNFs4KUw6aEpON9ZQU4gY7HL\/6l8GGCMkSUJrq4W1ttKh844sG2PDkDRJCIKAne3tyv25c+Sv3+DevcecHBM+f7asobk6LVGM4wRrLVaqCQUmILCTm6jHe1nfZqrgHKiWGsr+Tz1qwtbRh\/P+1giMfMrP8W92gxaHW22q8z+HOjNlU7y6+MDbwWcObMTLhy+wayfp0oO6YP+DYj\/Y6XLX3uF476h4quKk0NAsmCIUYtSJ0Ip1QUQX7IKYTMdCM3t+Xs3sP+dY5dEk0L+pAAAAAElFTkSuQmCC\u0022 data-src=\u0022https:\/\/avris.it\/image\/avris-forms-multiple_big.png\u0022 alt=\u0022\u0022 class=\u0022border\u0022 width=\u0022654\u0022 height=\u0022274\u0022\u003E\u003C\/span\u003E\n                \n            \u003C\/figure\u003E\u003C\/p\u003E\n\u003Cp\u003EWhile working on \u003Ca href=\u0022https:\/\/gitlab.com\/Avris\/Forms\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E\u003Csvg class=\u0022icon\u0022\u003E\u003Cuse xlink:href=\u0022#light-link\u0022\u003E\u003C\/use\u003E\u003C\/svg\u003E Avris Forms v4.0\u003C\/a\u003E, I\u2019ve decided to migrate some code from \u003Ca href=\u0022http:\/\/coffeescript.org\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E\u003Csvg class=\u0022icon\u0022\u003E\u003Cuse xlink:href=\u0022#light-link\u0022\u003E\u003C\/use\u003E\u003C\/svg\u003E CoffeScript\u003C\/a\u003E with \u003Ca href=\u0022https:\/\/jquery.com\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E\u003Csvg class=\u0022icon\u0022\u003E\u003Cuse xlink:href=\u0022#light-link\u0022\u003E\u003C\/use\u003E\u003C\/svg\u003E jQuery\u003C\/a\u003E to \u003Ca href=\u0022http:\/\/vanilla-js.com\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E\u003Csvg class=\u0022icon\u0022\u003E\u003Cuse xlink:href=\u0022#light-link\u0022\u003E\u003C\/use\u003E\u003C\/svg\u003E Vanilla JS\u003C\/a\u003E. And I guess it might be a good idea to share this transition \ud83d\ude09\u003C\/p\u003E\n\u003Cp\u003EThe code handles the form widget visible in the screenshot above. More specifically: those buttons in the right column. Clicking on the green one adds a new element\/row based on a template hidden inside a \u003Ccode\u003E\u0026lt;script\u0026gt;\u003C\/code\u003E tag. Clicking on a red button results in removal of the current row (after asking for a confirmation).\u003C\/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022hljs javascript border\u0022\u003E$(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027body\u0027\u003C\/span\u003E).on \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027click\u0027\u003C\/span\u003E, \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027.form-multiple-add\u0027\u003C\/span\u003E, -\u0026gt;\n  $form = $(\u003Cspan class=\u0022hljs-keyword\u0022\u003Ethis\u003C\/span\u003E).parents(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027.form-multiple\u0027\u003C\/span\u003E)\n  newIndices = $form.find(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027[data-index^=new]\u0027\u003C\/span\u003E).map((i, el) -\u0026gt; el.dataset[\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027index\u0027\u003C\/span\u003E].substr(\u003Cspan class=\u0022hljs-number\u0022\u003E3\u003C\/span\u003E)).get()\n  newIndex = \u003Cspan class=\u0022hljs-keyword\u0022\u003Eif\u003C\/span\u003E newIndices.length then \u003Cspan class=\u0022hljs-built_in\u0022\u003EMath\u003C\/span\u003E.max.apply(\u003Cspan class=\u0022hljs-literal\u0022\u003Enull\u003C\/span\u003E, newIndices) + \u003Cspan class=\u0022hljs-number\u0022\u003E1\u003C\/span\u003E \u003Cspan class=\u0022hljs-keyword\u0022\u003Eelse\u003C\/span\u003E \u003Cspan class=\u0022hljs-number\u0022\u003E0\u003C\/span\u003E\n  $template = $($form.find(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027.form-multiple-add-template\u0027\u003C\/span\u003E).html().replace(\u003Cspan class=\u0022hljs-regexp\u0022\u003E\/%i%\/g\u003C\/span\u003E, \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027new\u0027\u003C\/span\u003E + newIndex))\n  $(\u003Cspan class=\u0022hljs-keyword\u0022\u003Ethis\u003C\/span\u003E).parents(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027tr\u0027\u003C\/span\u003E).before $template\n  $template.find(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027:input:enabled:visible:first\u0027\u003C\/span\u003E).focus()\n\n$(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027body\u0027\u003C\/span\u003E).on \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027click\u0027\u003C\/span\u003E, \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027.form-multiple-remove\u0027\u003C\/span\u003E, -\u0026gt;\n  \u003Cspan class=\u0022hljs-keyword\u0022\u003Ereturn\u003C\/span\u003E \u003Cspan class=\u0022hljs-literal\u0022\u003Efalse\u003C\/span\u003E \u003Cspan class=\u0022hljs-keyword\u0022\u003Eif\u003C\/span\u003E !confirm(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027Are you sure?\u0027\u003C\/span\u003E))\n  $(\u003Cspan class=\u0022hljs-keyword\u0022\u003Ethis\u003C\/span\u003E).parents(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027tr\u0027\u003C\/span\u003E).remove()\n\u003C\/code\u003E\u003C\/pre\u003E\n\u003Cp\u003EIt was not a part of the library (as the library was supposed to only provide PHP), instead each project included this part on its own. Which led to code duplication (and the worst kind: between projects) and also kinda prevented me from abandoning CoffeeScript in newer projects, even if I wanted to. \u003C\/p\u003E\n\u003Cp\u003EAnd I did want to, because JavaScript has matured as a language over the last years. An additional level of complexity in my projects simply wasn\u2019t necessary anymore. CoffeeScript did a great job of making fontend scripting bearable, but now it\u2019s slowly time for it to get a deserved retirement.\u003C\/p\u003E\n\u003Cp\u003ESo I\u2019ve decided to integrate the frontend part into the library. Except I shouldn\u2019t be forcing users to use CoffeeScript or jQuery, it should just work out of the box in any project.\u003C\/p\u003E\n\u003Cp\u003ESince CoffeeScript compiles into JS (although usually with from some unnecessary returns and similar features), getting rid of it is really simple. After using \u003Ca href=\u0022http:\/\/js2.coffee\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E\u003Csvg class=\u0022icon\u0022\u003E\u003Cuse xlink:href=\u0022#light-link\u0022\u003E\u003C\/use\u003E\u003C\/svg\u003E js2coffee\u003C\/a\u003E or \u003Ca href=\u0022http:\/\/decaffeinate-project.org\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E\u003Csvg class=\u0022icon\u0022\u003E\u003Cuse xlink:href=\u0022#light-link\u0022\u003E\u003C\/use\u003E\u003C\/svg\u003E decaffeinate\u003C\/a\u003E we would end up with something like this:\u003C\/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022hljs javascript border\u0022\u003E$(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027body\u0027\u003C\/span\u003E).on(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027click\u0027\u003C\/span\u003E, \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027.form-multiple-add\u0027\u003C\/span\u003E, \u003Cspan class=\u0022hljs-function\u0022\u003E\u003Cspan class=\u0022hljs-keyword\u0022\u003Efunction\u003C\/span\u003E(\u003Cspan class=\u0022hljs-params\u0022\u003E\u003C\/span\u003E) \u003C\/span\u003E{\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Evar\u003C\/span\u003E $form, $template, newIndex, newIndices;\n    $form = $(\u003Cspan class=\u0022hljs-keyword\u0022\u003Ethis\u003C\/span\u003E).parents(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027.form-multiple\u0027\u003C\/span\u003E);\n    newIndices = $form.find(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027[data-index^=new]\u0027\u003C\/span\u003E).map(\u003Cspan class=\u0022hljs-function\u0022\u003E\u003Cspan class=\u0022hljs-keyword\u0022\u003Efunction\u003C\/span\u003E(\u003Cspan class=\u0022hljs-params\u0022\u003Ei, el\u003C\/span\u003E) \u003C\/span\u003E{\n        \u003Cspan class=\u0022hljs-keyword\u0022\u003Ereturn\u003C\/span\u003E el.dataset[\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027index\u0027\u003C\/span\u003E].substr(\u003Cspan class=\u0022hljs-number\u0022\u003E3\u003C\/span\u003E);\n    }).get();\n    newIndex = newIndices.length ? \u003Cspan class=\u0022hljs-built_in\u0022\u003EMath\u003C\/span\u003E.max.apply(\u003Cspan class=\u0022hljs-literal\u0022\u003Enull\u003C\/span\u003E, newIndices) + \u003Cspan class=\u0022hljs-number\u0022\u003E1\u003C\/span\u003E : \u003Cspan class=\u0022hljs-number\u0022\u003E0\u003C\/span\u003E;\n    $template = $($form.find(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027.form-multiple-add-template\u0027\u003C\/span\u003E).html().replace(\u003Cspan class=\u0022hljs-regexp\u0022\u003E\/%i%\/g\u003C\/span\u003E, \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027new\u0027\u003C\/span\u003E + newIndex));\n    $(\u003Cspan class=\u0022hljs-keyword\u0022\u003Ethis\u003C\/span\u003E).parents(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027tr\u0027\u003C\/span\u003E).before($template);\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Ereturn\u003C\/span\u003E $template.find(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027:input:enabled:visible:first\u0027\u003C\/span\u003E).focus();\n});\n\n$(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027body\u0027\u003C\/span\u003E).on(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027click\u0027\u003C\/span\u003E, \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027.form-multiple-remove\u0027\u003C\/span\u003E, \u003Cspan class=\u0022hljs-function\u0022\u003E\u003Cspan class=\u0022hljs-keyword\u0022\u003Efunction\u003C\/span\u003E(\u003Cspan class=\u0022hljs-params\u0022\u003E\u003C\/span\u003E) \u003C\/span\u003E{\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Eif\u003C\/span\u003E (!confirm(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027Are you sure?\u0027\u003C\/span\u003E)) {\n        \u003Cspan class=\u0022hljs-keyword\u0022\u003Ereturn\u003C\/span\u003E \u003Cspan class=\u0022hljs-literal\u0022\u003Efalse\u003C\/span\u003E;\n    }\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Ereturn\u003C\/span\u003E $(\u003Cspan class=\u0022hljs-keyword\u0022\u003Ethis\u003C\/span\u003E).parents(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027tr\u0027\u003C\/span\u003E).remove();\n});\n\u003C\/code\u003E\u003C\/pre\u003E\n\u003Cp\u003ERemoving the dependency on jQuery is more tricky.\u003C\/p\u003E\n\u003Cp\u003EThere are some \u003Ca href=\u0022http:\/\/youmightnotneedjquery.com\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E\u003Csvg class=\u0022icon\u0022\u003E\u003Cuse xlink:href=\u0022#light-link\u0022\u003E\u003C\/use\u003E\u003C\/svg\u003E nice lists\u003C\/a\u003E that show you how to replace some jQuery usage with plain JS. Those Javascript versions used to be longer, more complicated and require some browser-specific checks and adjustments. Those times are almost over though (as you can see from the list), so the need for jQuery gets smaller and smaller.\u003C\/p\u003E\n\u003Cp\u003EReplacing those calls from the list with Vanilla JS\u2019s counterparts was not enough in my case. I needed two functionalities more: finding an element\u2019s parent by a selector (\u003Ccode\u003E$el.parents(\u0027.foo\u0027)\u003C\/code\u003E) and binding an event listener to elements matching a selector, even if those elements don\u2019t exist yet at the moment of binding (\u003Ccode\u003E$(\u0027body\u0027).on(\u0027click\u0027, \u0027.foo\u0027, =\u0026gt; ... )\u003C\/code\u003E).\u003C\/p\u003E\n\u003Cp\u003EThey were surprisingly easy to implement:\u003C\/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022hljs javascript border\u0022\u003E\u003Cspan class=\u0022hljs-keyword\u0022\u003Evar\u003C\/span\u003E findParent = \u003Cspan class=\u0022hljs-function\u0022\u003E\u003Cspan class=\u0022hljs-keyword\u0022\u003Efunction\u003C\/span\u003E(\u003Cspan class=\u0022hljs-params\u0022\u003Eel, selector\u003C\/span\u003E) \u003C\/span\u003E{\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Edo\u003C\/span\u003E {\n        \u003Cspan class=\u0022hljs-keyword\u0022\u003Eif\u003C\/span\u003E (el.matches(selector)) {\n            \u003Cspan class=\u0022hljs-keyword\u0022\u003Ereturn\u003C\/span\u003E el;\n        }\n        el = el.parentNode;\n    } \u003Cspan class=\u0022hljs-keyword\u0022\u003Ewhile\u003C\/span\u003E (el \u0026amp;\u0026amp; el.matches);\n};\n\n\u003Cspan class=\u0022hljs-keyword\u0022\u003Evar\u003C\/span\u003E on = \u003Cspan class=\u0022hljs-function\u0022\u003E\u003Cspan class=\u0022hljs-keyword\u0022\u003Efunction\u003C\/span\u003E (\u003Cspan class=\u0022hljs-params\u0022\u003Eselector, event, handler\u003C\/span\u003E) \u003C\/span\u003E{\n    \u003Cspan class=\u0022hljs-built_in\u0022\u003Edocument\u003C\/span\u003E.addEventListener(event, \u003Cspan class=\u0022hljs-function\u0022\u003E\u003Cspan class=\u0022hljs-keyword\u0022\u003Efunction\u003C\/span\u003E (\u003Cspan class=\u0022hljs-params\u0022\u003Ee\u003C\/span\u003E) \u003C\/span\u003E{\n        \u003Cspan class=\u0022hljs-keyword\u0022\u003Evar\u003C\/span\u003E match = findParent(e.target, selector);\n        \u003Cspan class=\u0022hljs-keyword\u0022\u003Eif\u003C\/span\u003E (match) {\n            \u003Cspan class=\u0022hljs-keyword\u0022\u003Evar\u003C\/span\u003E result = handler.apply(match, [e]);\n            \u003Cspan class=\u0022hljs-keyword\u0022\u003Eif\u003C\/span\u003E (result === \u003Cspan class=\u0022hljs-literal\u0022\u003Efalse\u003C\/span\u003E) {\n                e.preventDefault();\n                e.stopPropagation();\n            }\n        }\n    });\n};\n\u003C\/code\u003E\u003C\/pre\u003E\n\u003Cp\u003EWith those two helpers I could transform my code to this vanilla JavaScript:\u003C\/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022hljs javascript border\u0022\u003Eon(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027.form-multiple-add\u0027\u003C\/span\u003E, \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027click\u0027\u003C\/span\u003E, \u003Cspan class=\u0022hljs-function\u0022\u003E\u003Cspan class=\u0022hljs-keyword\u0022\u003Efunction\u003C\/span\u003E (\u003Cspan class=\u0022hljs-params\u0022\u003E\u003C\/span\u003E) \u003C\/span\u003E{\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Evar\u003C\/span\u003E form = findParent(\u003Cspan class=\u0022hljs-keyword\u0022\u003Ethis\u003C\/span\u003E, \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027.form-multiple\u0027\u003C\/span\u003E);\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Eif\u003C\/span\u003E (!form) {\n        \u003Cspan class=\u0022hljs-keyword\u0022\u003Ereturn\u003C\/span\u003E;\n    }\n\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Evar\u003C\/span\u003E newIndices = \u003Cspan class=\u0022hljs-built_in\u0022\u003EArray\u003C\/span\u003E.from(form.querySelectorAll(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027[data-index^=new]\u0027\u003C\/span\u003E)).map(\u003Cspan class=\u0022hljs-function\u0022\u003E\u003Cspan class=\u0022hljs-keyword\u0022\u003Efunction\u003C\/span\u003E (\u003Cspan class=\u0022hljs-params\u0022\u003Eel\u003C\/span\u003E) \u003C\/span\u003E{\n        \u003Cspan class=\u0022hljs-keyword\u0022\u003Ereturn\u003C\/span\u003E el.dataset[\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027index\u0027\u003C\/span\u003E].substr(\u003Cspan class=\u0022hljs-number\u0022\u003E3\u003C\/span\u003E);\n    });\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Evar\u003C\/span\u003E newIndex = newIndices.length ? \u003Cspan class=\u0022hljs-built_in\u0022\u003EMath\u003C\/span\u003E.max.apply(\u003Cspan class=\u0022hljs-literal\u0022\u003Enull\u003C\/span\u003E, newIndices) + \u003Cspan class=\u0022hljs-number\u0022\u003E1\u003C\/span\u003E : \u003Cspan class=\u0022hljs-number\u0022\u003E0\u003C\/span\u003E;\n\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Evar\u003C\/span\u003E template = form.querySelector(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027.form-multiple-add-template\u0027\u003C\/span\u003E).innerHTML.replace(\u003Cspan class=\u0022hljs-regexp\u0022\u003E\/%i%\/g\u003C\/span\u003E, \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027new\u0027\u003C\/span\u003E + newIndex);\n\n    findParent(\u003Cspan class=\u0022hljs-keyword\u0022\u003Ethis\u003C\/span\u003E, \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027tr\u0027\u003C\/span\u003E).insertAdjacentHTML(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027beforebegin\u0027\u003C\/span\u003E, template);\n\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Evar\u003C\/span\u003E firstInput = form.querySelector(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027[data-index=new\u0027\u003C\/span\u003E+newIndex+\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027] :enabled\u0027\u003C\/span\u003E);\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Eif\u003C\/span\u003E (firstInput) {\n        firstInput.focus();\n    }\n});\n\non(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027.form-multiple-remove\u0027\u003C\/span\u003E, \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027click\u0027\u003C\/span\u003E, \u003Cspan class=\u0022hljs-function\u0022\u003E\u003Cspan class=\u0022hljs-keyword\u0022\u003Efunction\u003C\/span\u003E (\u003Cspan class=\u0022hljs-params\u0022\u003E\u003C\/span\u003E) \u003C\/span\u003E{\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Eif\u003C\/span\u003E (!confirm(\u003Cspan class=\u0022hljs-string\u0022\u003E\u0027Are you sure?\u0027\u003C\/span\u003E)) {\n        \u003Cspan class=\u0022hljs-keyword\u0022\u003Ereturn\u003C\/span\u003E \u003Cspan class=\u0022hljs-literal\u0022\u003Efalse\u003C\/span\u003E;\n    }\n    \u003Cspan class=\u0022hljs-keyword\u0022\u003Evar\u003C\/span\u003E row = findParent(\u003Cspan class=\u0022hljs-keyword\u0022\u003Ethis\u003C\/span\u003E, \u003Cspan class=\u0022hljs-string\u0022\u003E\u0027tr\u0027\u003C\/span\u003E);\n    row.parentNode.removeChild(row);\n});\n\u003C\/code\u003E\u003C\/pre\u003E\n\u003Cp\u003ENot bad, is it? \ud83d\ude09\u003C\/p\u003E\u003Csvg xmlns=\u0022http:\/\/www.w3.org\/2000\/svg\u0022 style=\u0022display: none;\u0022\u003E\u003C\/svg\u003E","tags":["coffeescript","javascript","jquery","js","programming","vanilla"],"hasMore":true,"image":"https:\/\/avris.it\/image\/avris-forms-multiple_small.png","introLite":"\u003Cfigure\u003E\u003Ca href=\u0022https:\/\/avris.it\/image\/avris-forms-multiple_big.png\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E\u003Cimg src=\u0022https:\/\/avris.it\/image\/avris-forms-multiple_mini.png\u0022 alt=\u0022\u0022 width=\u0022240\u0022 height=\u0022100.5504587156\u0022 loading=\u0022lazy\u0022\u003E\u003C\/a\u003E\u003C\/figure\u003E\u003C\/p\u003E\n\u003Cp\u003EWhile working on \u003Ca href=\u0022https:\/\/gitlab.com\/Avris\/Forms\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E Avris Forms v4.0\u003C\/a\u003E, I\u2019ve decided to migrate some code from \u003Ca href=\u0022http:\/\/coffeescript.org\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E CoffeScript\u003C\/a\u003E with \u003Ca href=\u0022https:\/\/jquery.com\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E jQuery\u003C\/a\u003E to \u003Ca href=\u0022http:\/\/vanilla-js.com\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E Vanilla JS\u003C\/a\u003E. And I guess it might be a good idea to share this transition \ud83d\ude09\u003C\/p\u003E","contentLite":"\u003Cfigure\u003E\u003Ca href=\u0022https:\/\/avris.it\/image\/avris-forms-multiple_big.png\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E\u003Cimg src=\u0022https:\/\/avris.it\/image\/avris-forms-multiple_mini.png\u0022 alt=\u0022\u0022 width=\u0022240\u0022 height=\u0022100.5504587156\u0022 loading=\u0022lazy\u0022\u003E\u003C\/a\u003E\u003C\/figure\u003E\u003C\/p\u003E\n\u003Cp\u003EWhile working on \u003Ca href=\u0022https:\/\/gitlab.com\/Avris\/Forms\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E Avris Forms v4.0\u003C\/a\u003E, I\u2019ve decided to migrate some code from \u003Ca href=\u0022http:\/\/coffeescript.org\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E CoffeScript\u003C\/a\u003E with \u003Ca href=\u0022https:\/\/jquery.com\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E jQuery\u003C\/a\u003E to \u003Ca href=\u0022http:\/\/vanilla-js.com\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E Vanilla JS\u003C\/a\u003E. And I guess it might be a good idea to share this transition \ud83d\ude09\u003C\/p\u003E\n\u003Cp\u003EThe code handles the form widget visible in the screenshot above. More specifically: those buttons in the right column. Clicking on the green one adds a new element\/row based on a template hidden inside a \u003Ccode\u003E\u0026lt;script\u0026gt;\u003C\/code\u003E tag. Clicking on a red button results in removal of the current row (after asking for a confirmation).\u003C\/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-js\u0022\u003E$(\u0027body\u0027).on \u0027click\u0027, \u0027.form-multiple-add\u0027, -\u0026gt;\n  $form = $(this).parents(\u0027.form-multiple\u0027)\n  newIndices = $form.find(\u0027[data-index^=new]\u0027).map((i, el) -\u0026gt; el.dataset[\u0027index\u0027].substr(3)).get()\n  newIndex = if newIndices.length then Math.max.apply(null, newIndices) + 1 else 0\n  $template = $($form.find(\u0027.form-multiple-add-template\u0027).html().replace(\/%i%\/g, \u0027new\u0027 + newIndex))\n  $(this).parents(\u0027tr\u0027).before $template\n  $template.find(\u0027:input:enabled:visible:first\u0027).focus()\n\n$(\u0027body\u0027).on \u0027click\u0027, \u0027.form-multiple-remove\u0027, -\u0026gt;\n  return false if !confirm(\u0027Are you sure?\u0027))\n  $(this).parents(\u0027tr\u0027).remove()\u003C\/code\u003E\u003C\/pre\u003E\n\u003Cp\u003EIt was not a part of the library (as the library was supposed to only provide PHP), instead each project included this part on its own. Which led to code duplication (and the worst kind: between projects) and also kinda prevented me from abandoning CoffeeScript in newer projects, even if I wanted to. \u003C\/p\u003E\n\u003Cp\u003EAnd I did want to, because JavaScript has matured as a language over the last years. An additional level of complexity in my projects simply wasn\u2019t necessary anymore. CoffeeScript did a great job of making fontend scripting bearable, but now it\u2019s slowly time for it to get a deserved retirement.\u003C\/p\u003E\n\u003Cp\u003ESo I\u2019ve decided to integrate the frontend part into the library. Except I shouldn\u2019t be forcing users to use CoffeeScript or jQuery, it should just work out of the box in any project.\u003C\/p\u003E\n\u003Cp\u003ESince CoffeeScript compiles into JS (although usually with from some unnecessary returns and similar features), getting rid of it is really simple. After using \u003Ca href=\u0022http:\/\/js2.coffee\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E js2coffee\u003C\/a\u003E or \u003Ca href=\u0022http:\/\/decaffeinate-project.org\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E decaffeinate\u003C\/a\u003E we would end up with something like this:\u003C\/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-js\u0022\u003E$(\u0027body\u0027).on(\u0027click\u0027, \u0027.form-multiple-add\u0027, function() {\n    var $form, $template, newIndex, newIndices;\n    $form = $(this).parents(\u0027.form-multiple\u0027);\n    newIndices = $form.find(\u0027[data-index^=new]\u0027).map(function(i, el) {\n        return el.dataset[\u0027index\u0027].substr(3);\n    }).get();\n    newIndex = newIndices.length ? Math.max.apply(null, newIndices) + 1 : 0;\n    $template = $($form.find(\u0027.form-multiple-add-template\u0027).html().replace(\/%i%\/g, \u0027new\u0027 + newIndex));\n    $(this).parents(\u0027tr\u0027).before($template);\n    return $template.find(\u0027:input:enabled:visible:first\u0027).focus();\n});\n\n$(\u0027body\u0027).on(\u0027click\u0027, \u0027.form-multiple-remove\u0027, function() {\n    if (!confirm(\u0027Are you sure?\u0027)) {\n        return false;\n    }\n    return $(this).parents(\u0027tr\u0027).remove();\n});\u003C\/code\u003E\u003C\/pre\u003E\n\u003Cp\u003ERemoving the dependency on jQuery is more tricky.\u003C\/p\u003E\n\u003Cp\u003EThere are some \u003Ca href=\u0022http:\/\/youmightnotneedjquery.com\/\u0022 target=\u0022_blank\u0022 rel=\u0022noopener\u0022\u003E nice lists\u003C\/a\u003E that show you how to replace some jQuery usage with plain JS. Those Javascript versions used to be longer, more complicated and require some browser-specific checks and adjustments. Those times are almost over though (as you can see from the list), so the need for jQuery gets smaller and smaller.\u003C\/p\u003E\n\u003Cp\u003EReplacing those calls from the list with Vanilla JS\u2019s counterparts was not enough in my case. I needed two functionalities more: finding an element\u2019s parent by a selector (\u003Ccode\u003E$el.parents(\u0027.foo\u0027)\u003C\/code\u003E) and binding an event listener to elements matching a selector, even if those elements don\u2019t exist yet at the moment of binding (\u003Ccode\u003E$(\u0027body\u0027).on(\u0027click\u0027, \u0027.foo\u0027, =\u0026gt; ... )\u003C\/code\u003E).\u003C\/p\u003E\n\u003Cp\u003EThey were surprisingly easy to implement:\u003C\/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-js\u0022\u003Evar findParent = function(el, selector) {\n    do {\n        if (el.matches(selector)) {\n            return el;\n        }\n        el = el.parentNode;\n    } while (el \u0026amp;\u0026amp; el.matches);\n};\n\nvar on = function (selector, event, handler) {\n    document.addEventListener(event, function (e) {\n        var match = findParent(e.target, selector);\n        if (match) {\n            var result = handler.apply(match, [e]);\n            if (result === false) {\n                e.preventDefault();\n                e.stopPropagation();\n            }\n        }\n    });\n};\u003C\/code\u003E\u003C\/pre\u003E\n\u003Cp\u003EWith those two helpers I could transform my code to this vanilla JavaScript:\u003C\/p\u003E\n\u003Cpre\u003E\u003Ccode class=\u0022language-js\u0022\u003Eon(\u0027.form-multiple-add\u0027, \u0027click\u0027, function () {\n    var form = findParent(this, \u0027.form-multiple\u0027);\n    if (!form) {\n        return;\n    }\n\n    var newIndices = Array.from(form.querySelectorAll(\u0027[data-index^=new]\u0027)).map(function (el) {\n        return el.dataset[\u0027index\u0027].substr(3);\n    });\n    var newIndex = newIndices.length ? Math.max.apply(null, newIndices) + 1 : 0;\n\n    var template = form.querySelector(\u0027.form-multiple-add-template\u0027).innerHTML.replace(\/%i%\/g, \u0027new\u0027 + newIndex);\n\n    findParent(this, \u0027tr\u0027).insertAdjacentHTML(\u0027beforebegin\u0027, template);\n\n    var firstInput = form.querySelector(\u0027[data-index=new\u0027+newIndex+\u0027] :enabled\u0027);\n    if (firstInput) {\n        firstInput.focus();\n    }\n});\n\non(\u0027.form-multiple-remove\u0027, \u0027click\u0027, function () {\n    if (!confirm(\u0027Are you sure?\u0027)) {\n        return false;\n    }\n    var row = findParent(this, \u0027tr\u0027);\n    row.parentNode.removeChild(row);\n});\u003C\/code\u003E\u003C\/pre\u003E\n\u003Cp\u003ENot bad, is it? \ud83d\ude09\u003C\/p\u003E","words":731,"readTime":3,"lang":"en"}}}}}