Embroidery

Fork it or leave issues at GitHub
Download from NPM

Introduction

What is Embroidery?
If you've ever written code that produces HTML you probably at some point have to do some kind of DOM manipulation or event listening. You want to click a button to update a feed, do an AJAX form submission or maybe toggle CSS classes. While you definitely can just use regular Javascript or even jQuery it can feel a bit cumbersome or repetitive.

You might reach for React or Vue. These frameworks/libraries are great but also introduce a lot of new concepts, learning curve and adds a lot to the bundle size of your app. 

Embroidery tries to make it easier to add Javascript behaviors to your HTML while keeping your code very close to the metal.

Embroidery aims to be 2kB (minified and gzipped) or less. Currently it's 1.9kB.

Getting started

There are a few ways to get going with Embroidery.

With a bundler (like webpack, rollup, parcel, etc)
First install Embroidery
yarn add embroidery
// or
npm install embroidery
And then in your main js file
import { Embroidery } from 'embroidery'
const app = Embroidery.start()
app.register({ /* your controllers */})

Using a CDN (without a bundler)
If you don't have to support IE11:
import { Embroidery } from 'https://unpkg.com/embroidery@0.1.3/dist/embroidery.m.js'
const app = Embroidery.start()
app.register({ /* your controllers */})

Usage

Let's dive in! Start by adding some HTML.
<!-- HTML somewhere in your web app -->
<div data-controller="myController">
  <input data-target="name" type="text" />

  <button data-action="click->greet">Greet</button>

  <span data-target="output"></span>
</div>
Then create a controller. This can be a separate file or part of a combined js file.
// my-controller.js
export const myController = {
  greet({ name, output }) {
    output.textContent = name.value
  },
}
Then you register your controller in your main js file.
import { Embroidery } from 'embroidery'
import { myController } from 'my-controller'
let app = Embroidery.start()
app.register({ myController })
Then make sure this file is included in the HTML's head.

There are a few things to look at here: controllers, targets and actions.

Controllers are the wrapper of your Embroidery code. The name in data-controller (HTML) maps to the name of the exported const in the js file - they always need to be the same. Controllers can not be kebab-cased since that is not a valid variable name in js. I prefer using camelCase.

Targets can be accessed as properties by destructuring the first parameter in the action's function. In the above case we get the input value (name) and set the textContent on output.

Actions are the event and name of the function that should run when the event is triggered. Events are regular DOM events. As you can see they are separated by an arrow: ->, where the first part is the event and the last part is the name of the function.

Try to change "click" to "hover"!

Actions come with a few default events. For example buttons and links have the default event "click".

Element   Event
a         'click'
button    'click'
form      'submit'
input     'input'
select    'change'
textarea  'input' 
You can also chain actions!
<div data-controller="manyActions">
  <div data-action="mouseover->doThis mouseout->doThat">Do this or that</div>
</div>
Multiple targets
If you have multiple elements on the same level you can access them as an array. You'll have to add [] to mark them as being part of an array.
Note! The name to access the target in the controller will change to <targetname>Targets. So the target name snowboard[] becomes snowboardTargets.
<div>
  <div data-target="hello[]">Hello to you</div>
  <div data-target="hello[]">Hello to me</div>
  <div data-target="hello[]">Hello to everyone</div>
</div>

// hello-controller.js
export const helloController = {
  init({ helloTargets }) {
    helloTargets.forEach((target) => {
      //...
    })
  },
}
Managing state
State in Embroidery is similar to class variables and, in contrast to React, mutable.
Here's an example of how to store the state of an input value.
export const controller = {
  name: '',
  myName({ name }) { // name is an <input /> in this case
    this.name = name.value
  },
  submit() {
    fetch('/update', {
      method: 'POST',
      body: JSON.stringify({
        name: this.name
      })
    })
  }
}

Experimental: Partials
Most REST API's return JSON or XML and leaves it up to you on how to organize and style the data. But there might be times when you can take a shortcut and return HTML asynchronously instead. Basecamp calls this html-over-the-wire and (re)popularized this ancient technique in HEY. It can be a way to reduce the data payload and defer content that does not need to be rendered immediately on the server to arrive later on the client.

This is now supported in Embroidery as well, and it's super simple.
<div data-partial="/endpoint-that-returns-html"></div>
What's cool about this is that you can have controllers embedded in the partial HTML and Embroidery will pick them up at runtime!



How is Embroidery different from Alpine.js and Stimulus?

First of all, Embroidery would not exist if it weren't for the following libs. I am very thankful for what they've done and that it allowed me to base my work on theirs.
They are very similar. My intention is that Embroidery should be easier to pick up but I believe both Alpine and Stimulus have a few more bells and whistles.  

Alpine.js's niche is to be the "Tailwind of Javascript", so you write your code in Alpine specific directive strings. This can certainly work for many use cases but might introduce other annoyances, like code formatting. Reusability can also be tricky, and I found myself most often copy/pasting code between templates.

Stimulus and Embroidery have a similar syntax. The big difference is that Stimulus uses classes (you inherit from the Stimulus controller), relies on strings for naming targets and requires more repetition and boilerplate.

Choose which suits you the best!


Last updated by Anton, Nov 2020