DEV Community

Cover image for On the ninth day of Enhancing: Externalizing Scripts
Simon MacDonald for Begin

Posted on • Originally published at blog.begin.com

1

On the ninth day of Enhancing: Externalizing Scripts

Yesterday, we progressively enhanced our submit button to enable a better user experience when JavaScript is enabled. Unfortunately, the more complicated your single-file component becomes, the worse the developer experience becomes, as it’s hard to write JavaScript in a tagged template literal, even with modern developer tools. Today we’ll break this single-file component into separate files for a better developer experience.

Externalizing the script tag

First, create a new file public/submit-button-pe.mjs. Then cut the contents of the script tag from app/elements/submit-button-pe.mjs and paste it into public/submit-button-pe.mjs. You will also need to add export default to the class definition. Here’s what the source would look like:

export default class SubmitButton extends HTMLElement {
 constructor () {
   super()
   this.submitForm = this.submitForm.bind(this)
   this.addEventListener('click', this.submitForm)
 }
 submitForm (e) {
   if ("fetch" in window) {
     e.preventDefault()
     let form = this.closest('form')
     let body = JSON.stringify(Object.fromEntries(new FormData(form)))
     fetch(form.action, {
       method: form.method,
       body,
       headers: {
         "Content-Type": "application/json",
         "Accept": "application/json",
       },
     })
       .then(response => response.json())
       .then(data => {
         const main = document.querySelector('main')
         const details = document.querySelector('details')
         let article = document.createElement('article')
         article.innerHTML = this.createArticle(data)
         main.insertBefore(article, details)
       })
       .catch(error => {
         console.log(error)
       })
   }
 }
 createArticle({comment}) {
   return `<div class="mb0">
   <p class="pb-2"><strong class="capitalize">name: </strong>${comment.name}</p>
   <p class="pb-2"><strong class="capitalize">email: </strong>s${comment.email}</p>
   <p class="pb-2"><strong class="capitalize">subject: </strong>${comment.subject}</p>
   <p class="pb-2"><strong class="capitalize">message: </strong>${comment.message}</p>
   <p class="pb-2"><strong class="capitalize">key: </strong>${comment.key}</p>
 </div>
 <p class="mb-1">
   <link-element href="/comments/${comment.key}">

 <a href="/comments/${comment.key}">
   Edit this comment

 </a></link-element>
 </p>
 <form action="/comments/${comment.key}/delete" method="POST" class="mb-1">
   <submit-button>

 <button class="whitespace-no-wrap pb-3 pt-3 pl0 pr0 font-medium text0 cursor-pointer radius0"><span slot="label">Delete this comment</span></button>
     </submit-button>
 </form>`
 }
}
customElements.define('submit-button-pe', SubmitButton)
Enter fullscreen mode Exit fullscreen mode

Then update the script tag in app/elements/submit-button-pe.mjs to point to our externalized JavaScript file:

export default function Element({ html }) {
 return html`
<style>
:host button {
 color: var(--light);
 background-color: var(--primary-500)
}
:host button:focus, :host button:hover {
 outline: none;
 background-color: var(--primary-400)
}
</style>
<button class="whitespace-no-wrap pb-3 pt-3 pl0 pr0 font-medium text0 cursor-pointer radius0">
 <slot name="label"></slot>
</button>
<script type="module" src="/_public/submit-button-pe.mjs"></script>
`
}
Enter fullscreen mode Exit fullscreen mode

Play around with http://localhost:3333/comments you won’t notice any changes in the browser, but your developer experience will be way better now that you are not editing JavaScript in a tagged template literal.

What about that ugly createArticle method?

Oh right, I was hoping you wouldn’t notice that, but you are too smart for me. Let’s clean that up by extracting it into it’s own web component.

Create a new file public/comment-article.mjs and populate it with the following code;

export default class CommentArticle extends HTMLElement {
 connectedCallback() {
   const name = this.getAttribute('name') || ''
   const email = this.getAttribute('email') || ''
   const subject = this.getAttribute('subject') || ''
   const message = this.getAttribute('message') || ''
   const key = this.getAttribute('key') || ''
   this.innerHTML = `<div class="mb0">
     <p class="pb-2"><strong class="capitalize">name: </strong>${name}</p>
     <p class="pb-2"><strong class="capitalize">email: </strong>${email}</p>
     <p class="pb-2"><strong class="capitalize">subject: </strong>${subject}</p>
     <p class="pb-2"><strong class="capitalize">message: </strong>${message}</p>
     <p class="pb-2"><strong class="capitalize">key: </strong>${key}</p>
   </div>
   <p class="mb-1">
     <link-element href="/comments/${key}">

   <a href="/comments/${key}">
     Edit this comment

   </a></link-element>
   </p>
   <form action="/comments/${key}/delete" method="POST" class="mb-1">
     <submit-button>

   <button class="whitespace-no-wrap pb-3 pt-3 pl0 pr0 font-medium text0 cursor-pointer radius0"><span slot="label">Delete this comment</span></button>
       </submit-button>
   </form>`
 }
}
customElements.define('comment-article', CommentArticle)
Enter fullscreen mode Exit fullscreen mode

Creating the UI is still ugly but now it’s divorced from our submit-button-pe. Over in app/elements/submit-button-pe.mjs we’ll add another script tag so we can use this component. Right above the existing script tag add the line:

<script type="module" src="/_public/comment-article.mjs"></script>
Enter fullscreen mode Exit fullscreen mode

Finally we’ll update public/submit-button-pe.mjs to use the create-article component instead of the createArticle method. We’ll delete the createArticle method and then instead of setting the innerHTML of the article we’ll create a create-article component and set it’s attributes programmatically.

export default class SubmitButton extends HTMLElement {
 constructor () {
   super()
   this.submitForm = this.submitForm.bind(this)
   this.addEventListener('click', this.submitForm)
 }
 submitForm (e) {
   if ("fetch" in window) {
     e.preventDefault()
     let form = this.closest('form')
     let body = JSON.stringify(Object.fromEntries(new FormData(form)))
     fetch(form.action, {
       method: form.method,
       body,
       headers: {
         "Content-Type": "application/json",
         "Accept": "application/json",
       },
     })
       .then(response => response.json())
       .then(({comment}) => {
         const main = document.querySelector('main')
         const details = document.querySelector('details')
         let article = document.createElement('comment-article')
         article.setAttribute('name', comment.name)
         article.setAttribute('email', comment.email)
         article.setAttribute('subject', comment.subject)
         article.setAttribute('message', comment.message)
         article.setAttribute('key', comment.key)
         main.insertBefore(article, details)
       })
       .catch(error => {
         console.log(error)
       })
   }
 }
}
customElements.define('submit-button-pe', SubmitButton)
Enter fullscreen mode Exit fullscreen mode

Once again, no changes for the user but it cleans up our code quite a bit. Is that exactly how I’d leave that comment-article component? Well no, but it’s good enough for right now.

Next Steps

Tomorrow we’ll do the first deployment of our app.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

Cloudinary image

Optimize, customize, deliver, manage and analyze your images.

Remove background in all your web images at the same time, use outpainting to expand images with matching content, remove objects via open-set object detection and fill, recolor, crop, resize... Discover these and hundreds more ways to manage your web images and videos on a scale.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay