Revolving Hearts Logo. Heartml Search ⌘K

Ride The Streetcar 🚋

All aboard the server 🔀 client train! #

Drive surgical state changes to your frontend DOM and JavaScript objects…from any backend (Ruby & JavaScript supported out of the gate). Fully compatible with any form fetch technique as well as htmx via a custom extension.

Elevator Pitch 🛗 #

You are but a humble web server. You have a received a request from a client which you know is a web browser. You would like to instruct the web browser what to do, for it lacks a complex preloaded JavaScript application and relies on you for helpful logic (aww 🥰). How do you do that? Isn’t it strange that there is no native web API for pushing DOM operations from server to client?

Well, that’s where Ride The Streetcar comes in! 🔔 If you’re a Ruby server, you might do this:

streetcar = RideTheStreetcar.dsl
response = streetcar.ride do
  query_selector("#progress-message").inner_html =
    "<em>Yay!</em> Your process is complete. <button>Done</button>"
end

Equivalent if you’re a JavaScript server:

const streetcar = RideTheStreetcar.dsl()
const response = streetcar.ride(sc => {
  sc.querySelector("#progress-message").innerHTML =
    "<em>Yay!</em> Your process is complete. <button>Done</button>"
})

And on the client, process with fetch in a two-liner:

const html = await fetch("/path/of/backend/fragment").then(r => r.text())
Streetcar.run(html)

What is this “HTML fragment”, you may ask? It’s a payload of serialized statements in the form of custom elements…a “streetcar line” if you will (line breaks/indentation added for visual clarity):

<sc-line>
  <sc-query-selector selectors="#progress-message">
    <sc-inner-html>
      <template>
        <em>Yay!</em> Your process is complete. <button>Done</button>
      </template>
    </sc-inner-html>
  </sc-query-selector>
</sc-line>

These custom elements aren’t “web components” in the traditional sense. They are non-visual and self-executing in order to translate the serialized logic into JavaScript statements (aka `document.querySelector(“…”).innerHTML = “…”) when processed by the client.

You may be familiar with “pull” based hypermedia tools such htmx (fragment-handling logic is defined client-side) or “push” based tools like Hotwired Turbo (fragment-handling logic is defined server-side). Streetcar is a push-based tool but happy to coordinate with existing pull-based tools—in fact, you can run Streetcar as an htmx extension!

Continue reading the documentation for installation & usage:

A Brief History 🕰️ #

I (Jared White) was the lead maintainer of the Xajax project (originally created by J. Max Wilson) in 2006. It provided a JavaScript client and a PHP server which let frontend DOM operations and functions be run via backend commands. Much later in 2021-2022, I was a contributor to CableReady and its new CableCar feature which let frontend DOM operations and functions be run via backend commands. After that, I switched to using Turbo Actions which let fronten—I think you see where this is going. Recently, I wrote about a pattern called Action Web Components which let fron—you know the drill!

At this point, I decided this pattern is simply too good to leave to happenstance. Ride The Streetcar (Streetcar for short) is my attempt to create the future-proof and framework-agnostic “Xajax” I always dreamed of. 🛤️

“Clang, clang, clang” went the trolley
“Ding, ding, ding” went the bell
“Zing, zing, zing” went my heart strings
For the moment I saw him I fell

The Trolley Song


Skip to content Close