tag:leastbad.com,2014:/feedleastbad2020-12-18T08:52:48-08:00least badhttp://leastbad.comhello@leastbad.comSvbtle.comtag:leastbad.com,2014:Post/developer-happiness2020-12-18T08:52:48-08:002020-12-18T08:52:48-08:00Developer Happiness<p><a href="https://svbtleusercontent.com/8wqZ3j5FzgEPrzmbuNxTBa0xspap.jpg"><img src="https://svbtleusercontent.com/8wqZ3j5FzgEPrzmbuNxTBa0xspap_small.jpg" alt="happiness.jpg"></a></p>
<p>It’s red letter day: after three months of development and <em>nine</em> pre-release candidates, StimulusReflex v3.4 has finally <a href="https://rubygems.org/gems/stimulus_reflex">dropped</a>.</p>
<p>The introduction of <a href="https://docs.stimulusreflex.com/morph-modes#introducing-morphs">Morphs</a> in September elevated StimulusReflex from a cool proof-of-concept to a promising tool for building reactive UIs.</p>
<p><a href="https://svbtleusercontent.com/oZaURzYg1pBnK5AvswEzct0xspap.jpg"><img src="https://svbtleusercontent.com/oZaURzYg1pBnK5AvswEzct0xspap_small.jpg" alt="Reflex is faster than a reaction."></a></p>
<p>Open source projects need to think creatively to stand out. <strong>Hiring an actor to record a fake testimonial by a beloved fictional douchebag didn’t compute for some.</strong> The <a href="https://twitter.com/theleastbad/status/1308401495185010700">initial tweet</a> was watched 9,100 times with 111 re-tweets, resulting in 27,000 impressions and 4,200 engagements. The <a href="https://www.youtube.com/watch?v=utxCm3uLhIE">YouTube version</a> has been viewed 5,800 times to date. This prompted a flurry of high profile articles, most notably Rails legend Obie Fernandez’s epic “<a href="https://medium.com/@obie/react-is-dead-long-live-reactive-rails-long-live-stimulusreflex-and-viewcomponent-cd061e2b0fe2">React is dead. Long live Reactive Rails. Long live StimulusReflex and ViewComponent</a>”. StimulusReflex was the featured story in <a href="https://rubyweekly.com/issues/520">Ruby Weekly</a>. Jason Charnes announced that <a href="https://courses.jasoncharnes.com/stimulus-reflex">a course</a> is in the works. Ruby Hero Ryan Bates asked Digital Ocean to <a href="https://twitter.com/rbates/status/1331379415008247808">donate $5,000</a> to the project. Membership on our <a href="https://discord.gg/XveN625">Discord</a> has doubled, and our weekly downloads went from under 5,000 to <a href="https://www.npmjs.com/package/stimulus_reflex">over 12,000</a>.</p>
<p>Perhaps most importantly, <strong>we’ve crossed the rubicon from being mocked to being seen as a threat</strong>. Given the thousands of hours passionately invested in building and supporting StimulusReflex and CableReady, $600 to 5x our name recognition and establish this stack as a preferable alternative to JS-based SPAs - <em>pretty much over night</em> - seems like a solid return on investment.</p>
<p>Knowing that we’re influencing the direction of Rails itself is possibly the most gratifying upside of all. As evidenced by DHH gradually walking-back <a href="https://twitter.com/patmisch/status/1252772796633137152">earlier statements</a> saying the long-promised <em>NEW MAGIC</em> is “<a href="https://twitter.com/dhh/status/1225612112556281858">unrelated</a>” to StimulusReflex, the narrative has progressively evolved from “<a href="https://twitter.com/dhh/status/1275907797431902213">similar in intent</a>” to “<a href="https://twitter.com/dhh/status/1336959367879516162">an alternative to</a>” StimulusReflex. We want Rails to kick ass and we’re glad to be keeping one of our heroes on his toes. Granted, it’s getting harder and harder for him to imply that he “discovered” these ideas in a fevered burst of primordial creative insight.</p>
<hr>
<p>When I think about what “developer happiness” means to me, it comes down to two things:</p>
<ul>
<li>a magical sense of “things just work”, made possible by ideas like the Principle of Least Surprise, intelligent defaults, methods that you hope will be there <em>and they are</em>, and the defenestration of ceremony and boilerplate code</li>
<li>the anticipation and mitigation of painful things that could otherwise reduce my happiness</li>
</ul>
<h1 id="things-just-work_1">Things Just Work <a class="head_anchor" href="#things-just-work_1">#</a>
</h1>
<p>The recent method chaining capability of CableReady v4.4 combined with the magic <code class="prettyprint">cable_ready</code> method available inside of Reflex actions is a great example of things working very well. In StimulusReflex v3.3, using CableReady to replace an element for the current user looked like this:</p>
<p><a href="https://svbtleusercontent.com/xhSiN8RxQdKrBX3BPyAnYc0xspap.png"><img src="https://svbtleusercontent.com/xhSiN8RxQdKrBX3BPyAnYc0xspap_small.png" alt="chrome_56nIIZv2s0.png"></a></p>
<p>Here’s the same thing in StimulusReflex v3.4. How’s that for “conceptual compression”?</p>
<p><a href="https://svbtleusercontent.com/9HLD6ALUMxH2HHd82Fpexc0xspap.png"><img src="https://svbtleusercontent.com/9HLD6ALUMxH2HHd82Fpexc0xspap_small.png" alt="chrome_VQqKSbP9BP.png"></a></p>
<p>As of StimulusReflex v3.4 and CableReady v4.3, every time a DOM event is created, a jQuery event with the same name and details is <em>also</em> created - but only if the jQuery library is present and detected in the current application. We welcome the millions of jQuery users with open arms.</p>
<p>There are two new features that are subtle but exciting:</p>
<ul>
<li>we’ve introduced a new <a href="https://docs.stimulusreflex.com/lifecycle#client-side-reflex-callbacks">finalize</a> life-cycle stage that runs <em>after</em> all CableReady operations triggered by Morphs have finished. It’s perfect for initiating animations and Turbolinks navigations</li>
<li>there’s now an optional “<a href="https://docs.stimulusreflex.com/reflexes#tab-isolation">tab isolation</a>” mode, which makes sure that Morphs only impact the DOM of the browser tab that initiated the Reflex</li>
</ul>
<p>You can also magically <a href="https://docs.stimulusreflex.com/reflexes#signed-and-unsigned-global-id-accessors">unpack signed Global ID</a>s right into ActiveRecord models. As the Great Dane often says, “look at all of the code I didn’t have to write!”</p>
<h1 id="mitigation-of-pain_1">Mitigation of Pain <a class="head_anchor" href="#mitigation-of-pain_1">#</a>
</h1>
<p>It has to be the ultimate cliche in software development that every new release is described as faster, with fewer bugs and far more robust.</p>
<p>v3.4 isn’t just faster, with fewer bugs and far more robust… it’s also substantially smaller, with the client footprint shrinking to just <a href="https://bundlephobia.com/result?p=stimulus_reflex@3.4.0-pre9">11kb</a> including CableReady, morphdom and ActionCable.</p>
<p>Stimulus 2 Just Works. You can use v1.1, too. Whatever you like.</p>
<p>Significant client-side refactoring made our vastly improved <a href="https://docs.stimulusreflex.com/troubleshooting#client-side-logging">logging module</a>, tab isolation and the <code class="prettyprint">finalize</code> stage possible while ensuring that multiple concurrent Reflexes can be in-flight without corruption or side-effects. We’re also far more forgiving in scenarios where the element which initiates a Reflex is replaced - a major source of confusion for newcomers in the past.</p>
<p>It’s not just the client that has been given white glove treatment, either: v3.4 introduces a brand new <a href="https://docs.stimulusreflex.com/troubleshooting#stimulusreflex-logging">server-side logging</a> module that is <em>disturbingly</em> customizable without being “too much”:</p>
<p><a href="https://svbtleusercontent.com/eUaty7cud2fxtXNkCoB1Vj0xspap.png"><img src="https://svbtleusercontent.com/eUaty7cud2fxtXNkCoB1Vj0xspap_small.png" alt="WindowsTerminal_dQPv6fcNZM.png"></a></p>
<p>We’ve added an initializer, but the defaults are so intelligent, it’s very likely that you’ll never change them. That said, it’s nice to know that if you need your Page Morphs to support <a href="https://docs.stimulusreflex.com/setup#rack-middleware-support">Rack middleware</a>, it’s super easy.</p>
<p>Speaking of pain: weird bugs due to mismatched gem + npm package versions are now a thing of the past. StimulusReflex will now yell loudly <em>and abort</em> if versions don’t match… unless you turn warnings off in the initializer.</p>
<p><em>One more thing…</em> while it’s not technically part of the SR/CR stack, I released a Stimulus controller called <a href="https://www.npmjs.com/package/radiolabel">radiolabel</a> that gives you visual feedback on your CableReady operations as they happen in development. It’s an easy way to make debugging your app faster and more explicit.</p>
<hr>
<p>The coolest and most profound <a href="https://docs.stimulusreflex.com/#new-release-v-3-4-developer-happiness-edition">changes</a> in v3.4 came from community contributors, sometimes making their first PRs. In particular, I’d like to call out the code and support efforts of folks like <a href="https://github.com/rolandstuder">Roland Studer</a>, <a href="https://github.com/paramagicdev">Konnor Rogers</a>, <a href="https://github.com/piotrwodz">Piotr Wodz</a>, <a href="https://github.com/excid3">Chris Oliver</a> and <a href="https://github.com/existentialmutt">Rafe Rosen</a>.</p>
<p>Meanwhile, <a href="https://github.com/joshleblanc">Josh LeBlanc</a> and his <a href="http://view-component-reflex-expo.grep.sh/">View Component Reflex</a> might rightfully be considered our secret weapon(s).</p>
<p>A personal thanks to all of the contributors, including my co-conspirators <a href="https://github.com/hopsoft">Nate Hopkins</a>, <a href="https://github.com/julianrubisch">Julian Rubisch</a> and <a href="https://github.com/marcoroth">Marco Roth</a>.</p>
<p><a href="https://svbtleusercontent.com/pfMPiGsvVmN5FxqXd3vtEU0xspap.jpg"><img src="https://svbtleusercontent.com/pfMPiGsvVmN5FxqXd3vtEU0xspap_small.jpg" alt="resistance.jpg"></a></p>
<p>NEW MAGIC is a wing of the #resistance, and the #resistance is kicking ass.</p>
<p>Remember: a reflex is faster than a reaction. ❤️</p>
tag:leastbad.com,2014:Post/introducing-stimulus-shortcut2020-07-22T18:34:32-07:002020-07-22T18:34:32-07:00Introducing stimulus-shortcut<p><a href="https://svbtleusercontent.com/aG7rkG9SfqbgKvaxCrgY6U0xspap.jpg"><img src="https://svbtleusercontent.com/aG7rkG9SfqbgKvaxCrgY6U0xspap_small.jpg" alt="No shortcuts."></a></p>
<p>Hot on the heels of <a href="https://dev.to/leastbad/introducing-stimulus-hotkeys-4f98">stimulus-hotkeys</a>, I am thrilled to release <a href="https://www.npmjs.com/package/stimulus-shortcut">stimulus-shortcut</a>, “a Stimulus controller for mapping keystrokes to element events”.</p>
<p>Based on the <a href="https://github.com/stimulusjs/dev-builds/archive/b8cc8c4/stimulus.tar.gz">preview version</a> of Stimulus 2.0, <code class="prettyprint">stimulus-hotkeys</code> wraps the amazing <a href="https://wangchujiang.com/hotkeys/">HotKeys.js</a> library and takes advantage of the new Stimulus <a href="https://github.com/stimulusjs/stimulus/pull/202">Values API</a> to bind the keystrokes people type to the default events of elements on your page.</p>
<p>Let’s look at a simple example, in which the user hits the “p” key and it makes the link click itself.</p>
<pre><code class="prettyprint lang-html"><a data-controller="shortcut" data-shortcut-key-value="p">Type 'p' to activate me!</a>
</code></pre>
<p>It’s a sort of sister-controller to <code class="prettyprint">stimulus-hotkeys</code> in that they share dependencies and together, cover both common shortcut key patterns. They can be used happily together in the same project.</p>
<p>That’s about it! You can learn more on the <a href="https://github.com/leastbad/stimulus-shortcut">Github project page</a>.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/p-mGXLgGqkY"></iframe>
tag:leastbad.com,2014:Post/introducing-stimulus-hotkeys2020-07-14T05:46:49-07:002020-07-14T05:46:49-07:00Introducing stimulus-hotkeys<p><a href="https://svbtleusercontent.com/b8KNwwK34rTAtyadu97VP0xspap.jpg"><img src="https://svbtleusercontent.com/b8KNwwK34rTAtyadu97VP0xspap_small.jpg" alt="Keytar!"></a></p>
<p>Continuing with my mission of crafting a viable ecosystem of elegant, composable Stimulus controllers, I am pleases to unveil <a href="https://www.npmjs.com/package/stimulus-hotkeys">stimulus-hotkeys</a>, “a Stimulus controller for mapping keystrokes to behaviors”.</p>
<p>Based on the <a href="https://github.com/stimulusjs/dev-builds/archive/b8cc8c4/stimulus.tar.gz">preview version</a> of Stimulus 2.0, <code class="prettyprint">stimulus-hotkeys</code> wraps the amazing <a href="https://wangchujiang.com/hotkeys/">HotKeys.js</a> library and takes advantage of the new Stimulus <a href="https://github.com/stimulusjs/stimulus/pull/202">Values API</a> to bind the keystrokes people type to functions in your controllers.</p>
<p>Configuration is done by providing a JSON-compliant object to the <code class="prettyprint">data-hotkeys-bindings-value</code> attribute. The object keys are literally the key(s) your users will press, while the values are a mapping that resembles a Stimulus action:</p>
<p><code class="prettyprint">selector->identifier#method</code></p>
<p>Let’s look at a simple example, in which the user hits the “p” key and will see “PONG” on the console.</p>
<pre><code class="prettyprint lang-html"><div data-controller="hotkeys"
data-hotkeys-bindings-value='{"p": "#foo->example#ping"}'></div>
<div id="foo" data-controller="example"></div>
</code></pre>
<pre><code class="prettyprint lang-js">// example_controller.js
import { Controller } from 'stimulus'
export default class extends Controller {
ping () { console.log('PONG') }
}
</code></pre>
<p>If you’re new to Stimulus, there’s some interesting meta topics to consider.</p>
<p>I mentioned at the beginning of this post that this is a contribution towards an ecosystem of composable Stimulus controllers. This means that many common objectives can be completed just by adding a controller identifier to an element in your page. This might seem familiar to people who have used Bootstrap components that magically activate just by putting attributes on the right markup.</p>
<p>The Stimulus approach is similar but improved because it’s both standardized and <a href="https://leastbad.com/mutation-first-development">idempotent</a>; that is, you can dynamically add elements to your page after the initial load (via Ajax calls or a <a href="https://docs.stimulusreflex.com">StimulusReflex</a> update) and it will just pick up any controllers without you needing to initialize them.</p>
<p>Since you can attach multiple Stimulus controllers to a single element, combining packaged controllers like <code class="prettyprint">stimulus-hotkeys</code> with “home-cooked” controllers that are specific to your project is easy and powerful. You can <em>compose</em> off-the-shelf functionality with sprinkles of custom logic. For those of you who love Alpine, <em>this was the precise moment when you lost the war</em>. I’m sorry, but it’s true. Please take this package to your queen.</p>
<p>Alright, we’ve reached the end! If you’re still here, I have a bonus reward for you: since this is a Stimulus component and updates are <strong>live</strong>, you can give your users the ability to dynamically create their own keyboard shortcuts by setting the <code class="prettyprint">data-hotkeys-bindings-value</code> attribute to a new JSON object.</p>
<p>And when you’re done, the JSON representing that user’s custom shortcut mappings can be saved to a JSON column on your User model. 🤯</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/wFKswPaxDtA?controls=0"></iframe>
tag:leastbad.com,2014:Post/jquery-events-to-dom-events2020-05-12T08:34:59-07:002020-05-12T08:34:59-07:00Introducing jquery-events-to-dom-events (and jboo)<p><a href="https://svbtleusercontent.com/r8Th6ghuXjj6MSyumDC5Cj0xspap.jpg"><img src="https://svbtleusercontent.com/r8Th6ghuXjj6MSyumDC5Cj0xspap_small.jpg" alt="quantum_entanglement_wormholes.jpg"></a></p>
<p>Did you know that <strong>jQuery events aren’t events</strong>?</p>
<p>It’s true - and it’ll really mess up your night if you need to capture events from legacy jQuery components. Looking at you, <code class="prettyprint">hidden.bs.modal</code>.</p>
<p>I needed a way to make <code class="prettyprint">$(document).trigger('fart')</code> emit a standard <code class="prettyprint">$fart</code> DOM event, so I wrote it:</p>
<p><a href="https://www.npmjs.com/package/jquery-events-to-dom-events">https://www.npmjs.com/package/jquery-events-to-dom-events</a></p>
<p>This library is short and sweet, with zero dependencies - including jQuery. It’s just two functions: <code class="prettyprint">delegate</code> and <code class="prettyprint">abnegate</code>. It is <a href="https://leastbad.com/mutation-first-development">Mutation-First</a>; designed to work great in Stimulus and supports Turbolinks out of the box.</p>
<p>It even has the secret ability to listen for DOM events with jQuery event listeners, but don’t tell anyone.</p>
<p>You can <a href="https://codepen.io/leastbad/pen/VwvQxxJ?editors=1011"><strong>try it now</strong> on CodePen</a> or even better, <a href="https://github.com/leastbad/jboo">clone a sample Rails project</a> to experiment in a mutation-first context with Stimulus.</p>
<p>The Rails project is called <strong>jboo</strong>. Don’t read into the name.</p>
<h2 id="usage_2">Usage <a class="head_anchor" href="#usage_2">#</a>
</h2>
<p><strong>Note</strong>: it is assumed that <a href="https://github.com/leastbad/jquery-events-to-dom-events#setup">jQuery is available in the global window scope</a> as <code class="prettyprint">$</code>.</p>
<p>In the most basic configuration, you:</p>
<ol>
<li><code class="prettyprint">import { delegate } from 'jquery-events-to-dom-events'</code></li>
<li>Call <code class="prettyprint">delegate(eventName)</code> for every jQuery event you want to capture.</li>
<li>Set up DOM event listeners for those events, <strong>prepending a $ to the event name</strong>.</li>
</ol>
<p>Let’s say that you want to respond to the user closing a Bootstrap modal window:</p>
<pre><code class="prettyprint lang-js">import { delegate } from 'jquery-events-to-dom-events'
delegate('hidden.bs.modal')
document.addEventListener('$hidden.bs.modal', () => console.log('Modal closed!'))
</code></pre>
<p>That might be it. Go make a sandwich - you’ve earned it.</p>
<hr>
<p>You can learn more about working with jquery-events-to-dom-events on <a href="https://github.com/leastbad/jquery-events-to-dom-events">the Github repo</a>.</p>
<p>As always, the right music is important for establishing proper context.</p>
<p>You don’t have to listen to music, but your transpiler configuration will almost certainly fail lint checks if you are not listening to “<em>In Harmony New Found Freedom</em>” by <a href="https://en.wikipedia.org/wiki/Swirlies">The Swirlies</a>, from their 1996 album “<a href="https://www.youtube.com/watch?v=S1rTKIsDS8o">They Spent Their Wild Youthful Days In The Glittering World Of The Salons</a>” while you integrate this library.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/idCfuK4t2vo"></iframe>
tag:leastbad.com,2014:Post/stimulus-image-grid2020-05-06T05:00:22-07:002020-05-06T05:00:22-07:00My first npm package: stimulus-image-grid<p><a href="https://svbtleusercontent.com/oCfEKyCfwps3Ebogfwpm5x0xspap.jpg"><img src="https://svbtleusercontent.com/oCfEKyCfwps3Ebogfwpm5x0xspap_small.jpg" alt="grid-2.jpg"></a></p>
<h1 id="a-stimulus-controller-for-beautiful-image-gri_1">A Stimulus controller for beautiful image grids <a class="head_anchor" href="#a-stimulus-controller-for-beautiful-image-gri_1">#</a>
</h1>
<p>I published my first npm package today!</p>
<p><a href="https://www.npmjs.com/package/stimulus-image-grid">https://www.npmjs.com/package/stimulus-image-grid</a></p>
<p>With only three optional parameters, this is a simple, drop-in, backend-agnostic, code-free solution that is completely free of CSS opinions. It’s responsive and scales to whatever bounding container you give it. </p>
<p>It’s also performant AF: stimulus-image-grid uses the ResizeObserver so there’s zero screen flicker. It’s compatible with Turbolinks by design and free for personal and commercial use.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/e08sFfBYoiE"></iframe>
<h2 id="built-for-stimulusjs_2">Built for StimulusJS <a class="head_anchor" href="#built-for-stimulusjs_2">#</a>
</h2>
<p>This <a href="https://stimulusjs.org/">Stimulus</a> controller allows you to make any configurations for the image grid directly with data attributes in your HTML. Once registered in your Stimulus application, you can use it anywhere you like.</p>
<p>Here is a simple example:</p>
<pre><code class="prettyprint lang-html"><div data-controller="image-grid">
<img src="https://placehold.it/350x300/EEE04A/ffffff">
<img src="https://placehold.it/420x320/5cb85c/ffffff">
<img src="https://placehold.it/320x380/5bc0de/ffffff">
<img src="https://placehold.it/472x500/f0ad4e/ffffff">
<img src="https://placehold.it/540x360/FF3167/ffffff">
</div>
</code></pre>
<p>Yes, that’s really it.</p>
<h2 id="setup_2">Setup <a class="head_anchor" href="#setup_2">#</a>
</h2>
<p>Add an import to your main JS entry point, and then register it:</p>
<pre><code class="prettyprint lang-js">import { Application } from 'stimulus'
import ImageGrid from 'stimulus-image-grid'
import { definitionsFromContext } from 'stimulus/webpack-helpers'
const application = Application.start()
const context = require.context('../controllers', true, /\.js$/)
application.load(definitionsFromContext(context))
// Manually register ImageGrid as a Stimulus controller
application.register('image-grid', ImageGrid)
</code></pre>
<p>And you’re done! Note, this package relies on the alpha preview of Stimulus v2, which is stable and available <a href="https://github.com/stimulusjs/stimulus/pull/202">here</a>.</p>
tag:leastbad.com,2014:Post/optimism2020-02-28T14:00:15-08:002020-02-28T14:00:15-08:00Introducing Optimism: realtime remote form validation for Rails<p><a href="https://svbtleusercontent.com/2zEyCHRhZaNr9sn2AGagx70xspap.jpg"><img src="https://svbtleusercontent.com/2zEyCHRhZaNr9sn2AGagx70xspap_small.jpg" alt="female-mechanics.jpg"></a></p>
<p>I’m proud to announce the public release of <a href="https://github.com/leastbad/optimism">Optimism</a>, a new Ruby on Rails gem that makes it easy to add realtime validation error messages to your applications.</p>
<p>When a model validation error prevents an update from succeeding, Optimism builds a list of issues that must be resolved. This list is broadcast to the browser over a websocket connection, and the live document is changed to show the necessary validation hints. No page refreshes are required and the entire process happens faster than you can blink.</p>
<p>Optimism injects text content, CSS class updates and even DOM events into your page. It has no client-side scripting requirements beyond a dependency to the awesome <a href="https://cableready.stimulusreflex.com/">CableReady</a> library. It automatically handles multi-level nested forms and plays well with other technologies, such as Turbolinks.</p>
<p>You can <a href="https://optimism-demo.herokuapp.com">try a live demo</a> right now.</p>
<p>Full documentation is available <a href="https://optimism.leastbad.com">here</a>. The project lives on Github <a href="https://github.com/leastbad/optimism">here</a> and you can get help via Discord <a href="https://discord.gg/wKzsAYJ">here</a>.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/dAP3unTl1f4"></iframe>
tag:leastbad.com,2014:Post/stimulus-power-move2020-02-14T05:30:03-08:002020-02-14T05:30:03-08:00The best one-line Stimulus power move<p><a href="https://svbtleusercontent.com/otYVbHVRMo8Z2jX19nuDp50xspap.jpg"><img src="https://svbtleusercontent.com/otYVbHVRMo8Z2jX19nuDp50xspap_small.jpg" alt="simone-biles.jpg"></a></p>
<p><a href="https://stimulusjs.org">Stimulus</a> is a tiny and absurdly productive JavaScript framework for developers who are looking for just the right amount of structure (lifecycle events and standard HTML) without attempting to re-invent how the web works (no template rendering or routing). It is criminally underappreciated in the JavaScript community.</p>
<p>When using Stimulus, you write controllers in JavaScript and attach instances of those controllers to DOM elements by setting <code class="prettyprint">data-controller="controller-name"</code>.</p>
<p>Unfortunately, there’s no easy way to access methods in a controller from another controller, external scripts, jQuery plugins or the console… <em>or is there?</em></p>
<hr>
<p>Before I do the big reveal, there is <em>technically</em> a way to access another controller instance from inside of a controller. It’s an undocumented method so there’s no guarantee that it won’t disapper someday, but the real clue that this isn’t intended to be used is the laughably long name: <code class="prettyprint">this.application.getControllerForElementAndIdentifier(element, controller)</code>.</p>
<p>Controllers have access to the global Stimulus application scope, which has <code class="prettyprint">getControllerForElementAndIdentifier</code> as a member function. If you have a reference to the element with the controller attached and the name of the controller, you can get a reference to any controller on your page. Still, this doesn’t offer any solutions to developers working outside of a Stimulus controller.</p>
<hr>
<p><strong>Here’s what we should all do instead.</strong></p>
<p>In your controller’s <code class="prettyprint">connect()</code> method, add this line:</p>
<pre><code class="prettyprint lang-JavaScript">this.element[this.identifier] = this
</code></pre>
<p>Boom! This hangs a reference to the Stimulus controller instance off the DOM element that has the same name as the controller itself. Now, if you can get a reference to the element, you can access <code class="prettyprint">element.controllerName</code> anywhere you need it.</p>
<p>What’s cool about this trick is that since Stimulus calls <code class="prettyprint">connect()</code> every time an instance is created, you can be confident that your elements will always have a direct reference to their parent, even if they are attached to elements that are dynamically inserted by something like <a href="https://github.com/patrick-steele-idem/morphdom">morphdom</a>.</p>
<p><code class="prettyprint">this.identifier</code> can be replaced with any camelCase string as you desire.</p>
<hr>
<p>I’ll provide a basic example.</p>
<pre><code class="prettyprint lang-JavaScript">// test_controller.js
import { Controller } from 'stimulus'
export default class extends Controller {
connect () {
this.element[this.identifier] = this
}
name () {
this.element.innerHTML = `I am ${this.element.dataset.name}.`
}
}
// index.html
<div id="person" data-controller="test" data-name="Steve"></div>
// run this in your console
document.querySelector('#person').test.name()
</code></pre>
<p>If everything goes according to plan, the <code class="prettyprint">div</code> should now say: <em>I am Steve.</em></p>
<hr>
<p>If you want to automatically camelCase the name of your Stimulus controller, “one-line” becomes dubious, but it can still be done:</p>
<pre><code class="prettyprint lang-JavaScript"> this.element[
(str => {
return str
.split('--')
.slice(-1)[0]
.split(/[-_]/)
.map(w => w.replace(/./, m => m.toUpperCase()))
.join('')
.replace(/^\w/, c => c.toLowerCase())
})(this.identifier)
] = this
</code></pre>
<p>I broke the statement up into multiple lines to help illustrate the acrobatics required to pull this off. It can still be expressed on a single line if you choose. However, the devil hides in clever code.</p>
<hr>
<p>The only caveat I can think of is that you should exercise common sense and not expose any controller instances that you wouldn’t want people to access. Even though there’s no visible proof that an element has a variable on it in the inspector, you shouldn’t assume that it’s locked down.</p>
<p>If you’re working in FinTech, you might need to skip this technique. Everyone else should be doing this by default.</p>
tag:leastbad.com,2014:Post/mutation-first-development2020-02-12T07:00:44-08:002020-02-12T07:00:44-08:00Mutation-first development: a call to action<p><a href="https://svbtleusercontent.com/o8LJuUcmS7DYt9tD6etQPd0xspap.gif"><img src="https://svbtleusercontent.com/o8LJuUcmS7DYt9tD6etQPd0xspap_small.gif" alt="goldblum.gif"></a></p>
<p>Not that long ago, someone designing a JavaScript component could rely on a simple life-cycle premise: your content would load before the jQuery embedded in the bottom of the page would come to life and initialize everything that needed initializing. The user would then click a link or hit the back button, causing the cycle to repeat itself. There was a 1:1 relationship between pages requested and load events firing.</p>
<p>In this era of reactive asyncronous content, that assumption is now screwing us.</p>
<p>Web page life-cycles continue to get more complex and the page load event is no longer a reliable singular entry point to our UI setup code. <strong>This post attempts to describe the problem and offer a strategy to fix how we create libraries and components.</strong></p>
<p>We need to stop acting as though multi-stage life-cycles are edge cases. Instead, we can build <a href="https://stackoverflow.com/questions/1077412/what-is-an-idempotent-operation">idempotent</a> libraries which support use in applications that don’t <em>have</em> page loads. This will make programming for the web more fun, more productive, less error-prone and reduce the support burden on open source maintainers.</p>
<hr>
<p>In early 2011, GitHub founder Chris “defunkt” Wanstrath announced a jQuery plugin he named <a href="https://github.com/defunkt/jquery-pjax">pjax</a>. pjax introduced a simple idea with dramatic implications: when a user clicks a link, we can replace the contents of the body tag already loaded in the browser with something new, loaded via an Ajax request. Loading pages the old-fashioned way is slow, especially on smartphones. People like experiences that are snappy and responsive, and this trick made web sites interactive in a way that isn’t possible when every click results in a pause and complete page re-draw. pjax took over responsibility for keeping the navigation history synced up as part of the deal, ensuring that the back button works as expected. What could go wrong?</p>
<p>The team behind Rails took the pjax concept and ran with it, announcing a new library called <a href="https://youtu.be/SWEts0rlezA?t=202">Turbolinks</a> that would become a flagship feature of Rails. The fact that it was optional, easy to disable and delivered on its promise didn’t stop a loud segment of developers from screaming like they were being murdered.</p>
<p>Remember when Apple removed the floppy drive? I’ve now carbon-dated myself. Okay… remember when Apple removed the CD/DVD drive? People lost their minds. It’s not a computer if it doesn’t have removable media, right? Wrong! <strong>Apple anticipated the near-future and tore the bandage off.</strong> It’s hard to remember what seemed like such a painful amputation at the time.</p>
<p>Turbolinks challenged the status quo and received an undeserved reputation for making all your scripts “break”. Removing it became the first thing many developers did when starting a new project. In hindsight, this pain was a preview of what was to come, whether we liked it or not: <strong>Turbolinks didn’t make anyone’s scripts break; the scripts themselves were already broken.</strong> The community blamed the messenger instead of confronting the ramifications of having coded themselves into a corner.</p>
<hr>
<p>Today, there are many approaches to developing reactive content and managing the state of a user’s UI without page loads. Libraries such as <a href="https://github.com/hopsoft/stimulus_reflex">StimulusReflex</a> use websockets and <a href="https://github.com/patrick-steele-idem/morphdom">morphdom</a> to replace what’s displayed in your browser with something new. These updates can happen in response to user actions <em>or</em> things happening on the server.</p>
<p>Yet, server-rendered interface updates faster than React state changes come at the cost of forcing the developer to think about <strong>code re-entrancy</strong>. When you’re building something amazing, you need to stop and consider the different contexts in which future developers will use it. The reason that all those jQuery plugins stopped working when you installed TurboLinks is that most plugins did not account for people swapping out their DOMs without a complete page load cycle. This led to code that:</p>
<ul>
<li>is loaded into the global namespace with the expectation that other code can access it from anywhere</li>
<li>raises errors if you attempt to execute it more than once </li>
<li>attaches event handlers to elements that <em>will</em> get replaced</li>
<li>never removes those event handlers, leading to memory leaks</li>
<li>is not aware of <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver">DOM mutations</a> and will not operate on new, dynamic content</li>
<li>adds, moves or removes elements, both upon initialization and during use</li>
</ul>
<p>And the worst problem of all: what happens when a component re-arranges your DOM during initialization, but then doesn’t recognize its own mess if you try to initialize it again?</p>
<p>You know exactly what happens: <strong>it’s a shit show</strong>. The back button appears to load a UI where calendar pickers and fancy file uploaders don’t open when you click on them.</p>
<p>This is the specific reason that every SPA framework seems to have wrappers for every popular JS library. These wrappers all serve the same basic function: you have to smooth out the library’s rough edges and make them usable in a contempory project. Making a library’s API look like a native framework component is cosmetic; it’s the hacks that suppress errors caused by side effects and rearrange brittle DOM hierarchies that makes these wrappers valuable. I’ve written more than a few of them for <a href="https://stimulusjs.org/">Stimulus</a>, which happens to be better than your favorite tool.</p>
<hr>
<p>A big part of the reason Stimulus is such a feat of software engineering genius is that it offers three life-cycle events - <a href="https://stimulusjs.org/reference/lifecycle-callbacks">initialize, connect and disconnect</a> - which receive their marching orders from the hyper-performant <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver">MutationObserver API</a>. It’s fine if you haven’t heard of it; it’s a powerful tool that is usually abstracted away in higher-level libraries like Stimulus. MutationObserver fires a callback when something in the document changes, allowing us to invent new life-cycle events.</p>
<p>When you dynamically insert new markup into a page, if that markup contains an element with Stimulus controllers declared, those controllers’ life-cycle events will fire as though they had been there since the page was first loaded.</p>
<p>This thoughful design intent makes Stimulus an obvious choice for wrapping older libraries and components.</p>
<hr>
<p>We should all be thankful that people write wrappers, but if these libraries were re-engineered with idempotency as a primary goal, most wrappers could be retired.</p>
<p>The next stage of growth and maturity for the JavaScript community is a necessary shift from hiding the ugly tumors out of sight to cutting the cancer out and blasting it with radiation. It will hurt and not every library will survive, but those that do will be stronger afterwards.</p>
<p><strong>Consider this a call to action. Mutation-first means:</strong></p>
<ol>
<li>Developers should create or update libraries <em>and their documentation</em> to <strong>assume re-entrancy by default</strong>.</li>
<li>A library is not considered high-quality unless it is idempotent. Developers should be able to initialize and destroy an instance many times during a single browser page context, including releasing event handlers and cleaning up/preparing the DOM state for caching during an <em>unload</em> event.</li>
<li>The most celebrated libraries will be atomic, making as few assumptions about DOM structure or CSS framework as possible while allowing several simultaneous instances of the library on a page.</li>
</ol>
<hr>
<p>If you think replacing shims with native browser functionality is exciting, then you’re going to <em>love</em> making library wrappers a thing of the past.</p>
<p>We have the tools. We have the talent. Do we have the will and integrity to stop blaming jQuery, TurboLinks and “JS ecosystem complexity” for short-sighted design decisions made a decade ago?</p>
<p>Unlike so many problems facing the world today, this is actually something that we can come together to fix on a reasonable timeline for our benefit, as well as the benefit of everyone who follows us. Let’s do this.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/5ohTi8lbeok"></iframe>