DEV Community

Cover image for Building a Radio Player with Ruby & WebForms Core
Elanat Framework
Elanat Framework

Posted on

Building a Radio Player with Ruby & WebForms Core

WebForms Core (WFC) is a web development technology owned and developed by Elanat. It provides a server-driven approach for building interactive web interfaces in which the server generates commands that are executed in the browser by WebFormsJS. WFC is designed to work independently of a specific web framework, so the Ruby implementation can be integrated with Sinatra, Rails, or other Ruby web frameworks that can handle HTTP requests and responses. The same architecture is also available for other backend languages, allowing WebForms Core to provide a consistent server-driven UI approach across different technology stacks.

WebForms Class and WebFormsJS

The Ruby implementation of WebForms Core consists of two main parts:

  • WebForms Class: The server-side class used by the backend application to generate commands.
  • WebFormsJS: The client-side JavaScript runtime that receives and executes those commands in the browser.

The WebForms class acts as the Commander. Your Ruby application creates a WebForms object and calls methods such as set_text, set_attribute, add_text, and set_comment_event.

WebFormsJS acts as the Executor. It runs in the browser and applies the generated operations to the existing DOM.

This means that the server does not need to send a complete new HTML page after every interaction. Instead, it can send a compact set of instructions describing what should change.

For example:

form.set_text("radio-name", Fetch.get_text("$"))
Enter fullscreen mode Exit fullscreen mode

The server generates a command that tells WebFormsJS to update the element identified by radio-name. The browser then performs that operation on the existing page.

This approach also allows WebForms Core to work without a persistent server-side UI circuit or a virtual DOM. The browser maintains the actual HTML DOM, while the server generates commands when an interaction requires a change.

Building the Radio Player

WebForms Core Technology in Ruby

The following example creates a simple radio player using Ruby, Sinatra, and WebForms Core.

The application retrieves radio station data, creates a radio card for each station, and assigns a click event to every generated card.

require 'sinatra'
require 'wfc'

include WebFormsCore

get '/' do
  form = WebForms.new

  form.not_exist(Fetch.cache("radio-data"))
  form.add_cache_value("radio-data", Fetch.load_url("/api/radio.json"))

  form.for_each("[0]", Fetch.cache("radio-data"), "foreach-data")
  form.start_bracket
    form.add_text("{radio-container}", Fetch.load_html("/api/template.html", "Radio"))
    form.bind_json_to_template("{radio-card}-1", Fetch.format_store("foreach-data"), "[0]", "{{value}}")
    form.set_comment_event("{radio-card}-1|<>", HtmlEvent.on_click, "play-radio")
  form.end_bracket

  form.start_index("play-radio")
  form.set_attribute("audio", HtmlEvent.on_load_start, "this.play()")
  form.set_attribute("audio", "src", Fetch.get_attribute("$", "data-stream"))
  form.set_text("radio-name", Fetch.get_text("$"))

  erb :view, locals: { form: form }
end
Enter fullscreen mode Exit fullscreen mode

Loading the radio data

The first part of the code creates the WebForms object:

form = WebForms.new
Enter fullscreen mode Exit fullscreen mode

The application then checks whether the radio data is already available in the client-side cache:

form.not_exist(Fetch.cache("radio-data"))
form.add_cache_value("radio-data", Fetch.load_url("/api/radio.json"))
Enter fullscreen mode Exit fullscreen mode

If the cache does not contain radio-data, the data is loaded from /api/radio.json and stored in the cache.

radio.json

[
  {
    "title": "Radio Paradise",
    "stream": "https://stream.radioparadise.com/aac-128"
  },
  {
    "title": "SomaFM - Groove Salad",
    "stream": "https://ice1.somafm.com/groovesalad-128-mp3"
  },
  {
    "title": "SomaFM - Drone Zone",
    "stream": "https://ice1.somafm.com/dronezone-128-mp3"
  },
  {
    "title": "SomaFM - DEF CON Radio",
    "stream": "https://ice1.somafm.com/defcon-128-mp3"
  }
]
Enter fullscreen mode Exit fullscreen mode

Creating the radio cards

The for_each operation iterates through the returned data:

form.for_each("[0]", Fetch.cache("radio-data"), "foreach-data")
Enter fullscreen mode Exit fullscreen mode

The result of each iteration is then used inside the bracketed block:

form.start_bracket
  form.add_text("{radio-container}", Fetch.load_html("/api/template.html", "Radio"))
  form.bind_json_to_template("{radio-card}-1", Fetch.format_store("foreach-data"), "[0]", "{{value}}")
  form.set_comment_event("{radio-card}-1|<>", HtmlEvent.on_click, "play-radio")
form.end_bracket
Enter fullscreen mode Exit fullscreen mode

Here, load_html retrieves the HTML template for a radio station.

template.html

<!DOCTYPE html>
<template id="Radio">
    <div class="radio-card">
        <b data-stream="{{stream}}">
            {{title}}
        </b>
    </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Note: Any call that targets a <template> element returns only the structure inside it, not the <template> element itself.

bind_json_to_template fills the template with the current radio station's data.

Finally:

form.set_comment_event("{radio-card}-1|<>", HtmlEvent.on_click, "play-radio")
Enter fullscreen mode Exit fullscreen mode

associates the click event with the play-radio action.

The important point is that these operations describe what should happen in the browser. Even the for_each method only generates a string that describes the operation. WebFormsJS executes the resulting commands on the client.

Handling the Play Action

The next section defines what happens when a radio card is clicked:

form.start_index("play-radio")
form.set_attribute("audio", HtmlEvent.on_load_start, "this.play()")
form.set_attribute("audio", "src", Fetch.get_attribute("$", "data-stream"))
form.set_text("radio-name", Fetch.get_text("$"))
Enter fullscreen mode Exit fullscreen mode

start_index("play-radio") starts the command group associated with the event.

The selected radio card becomes the current element represented by $.

The stream URL is read from its data-stream attribute:

Fetch.get_attribute("$", "data-stream")
Enter fullscreen mode Exit fullscreen mode

and assigned to the audio element:

form.set_attribute("audio", "src", Fetch.get_attribute("$", "data-stream"))
Enter fullscreen mode Exit fullscreen mode

The radio name is then extracted from the selected element and displayed:

form.set_text("radio-name", Fetch.get_text("$"))
Enter fullscreen mode Exit fullscreen mode

Finally, the loadstart event is configured to start playback:

form.set_attribute("audio", HtmlEvent.on_load_start, "this.play()")
Enter fullscreen mode Exit fullscreen mode

The entire interaction is therefore defined from the server side without writing a separate JavaScript event handler for the radio cards.

HTML View

The Sinatra ERB view contains the normal HTML structure of the application:

<!DOCTYPE html>
<html>
<head>
    <title>WebForms Core Radio</title>
    <script type="module" src="/script/web-forms.js"></script>

    <style>
        ...
    </style>
</head>
<body>

    <main>
        <h1>Radio Stations in Ruby</h1>
        <h2>Building a Radio Player with WebForms Core</h2>

        <div class="radio-container"></div>

        <audio id="audio" controls></audio>

        <div id="now-playing">
            <span class="playing-indicator"></span>
            <span class="speaker-icon">🔊</span>
            <span id="radio-name">No radio selected</span>
        </div>
    </main>

    <%= form.export_to_html_comment %>

</body>
</html>
Enter fullscreen mode Exit fullscreen mode

The important part is:

<script type="module" src="/script/web-forms.js"></script>
Enter fullscreen mode Exit fullscreen mode

This loads WebFormsJS, the client-side executor.

The other important part is:

<%= form.export_to_html_comment %>
Enter fullscreen mode Exit fullscreen mode

The commands generated by the Ruby WebForms object are exported into the HTML response. WebFormsJS reads those commands and executes them in the browser.

The application therefore keeps its UI as standard HTML while WebForms Core provides the server-driven behavior.

Output of the export_to_html_comment method

Is the code above understandable to humans? Please share your thoughts in the comments section.

Screenshot of Radio Player
WebForms Core Project by Ruby

CSS

The CSS is completely independent of WebForms Core. You can use your own styles, CSS framework, or no framework at all.

For a compact version of the styles used in this example:

*{box-sizing:border-box}body{margin:0;min-height:100vh;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#101114;color:#fff}main{width:min(720px,calc(100% - 32px));margin:60px auto}h1{margin-bottom:24px;font-size:32px}.radio-container{display:grid;gap:12px}.radio-card{border:1px solid #2d3036;border-radius:14px;background:#181a1f;transition:.2s}.radio-card:hover{background:#22252b;border-color:#555a63;transform:translateY(-2px)}.radio-card b{display:block;font-size:18px;margin:0 24px;line-height:64px;cursor:pointer}audio{width:100%;margin-top:28px}#now-playing{margin:14px auto 0;padding:10px 16px;display:flex;align-items:center;justify-content:center;gap:9px;border-radius:10px;background:#181818;color:#f2f2f2;font-size:15px;font-weight:600;letter-spacing:.2px}.speaker-icon{font-size:18px}.playing-indicator{font-size:11px;animation:blink 1s infinite}@keyframes blink{0%,100%{opacity:1}50%{opacity:.2}}#radio-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
Enter fullscreen mode Exit fullscreen mode

Installing WebForms Core for Ruby

WebForms Core is available as the wfc RubyGem.

Install it directly using the RubyGems CLI:

gem install wfc
Enter fullscreen mode Exit fullscreen mode

Then require it in your Ruby application:

require "wfc"

include WebFormsCore
Enter fullscreen mode Exit fullscreen mode

The wfc Gem provides the WebFormsCore namespace and the WebForms class together with the WebForms Core APIs for Ruby.

WebFormsJS is installed separately because it is the client-side runtime:

CLI:

npm install webformsjs
Enter fullscreen mode Exit fullscreen mode

WebFormsJS in npm: webformsjs on npm

If you cannot install WebFormsJS using npm, it is also available at the following link:

https://github.com/elanatframework/Web_forms

After installing the webformsjs package, configure your web server to serve its web-forms.js file at /script/web-forms.js, as used in the HTML view above.

Then include it in your HTML:

<script type="module" src="/script/web-forms.js"></script>
Enter fullscreen mode Exit fullscreen mode

WebFormsJS is separate from the RubyGem and runs in the browser. This separation is intentional: the RubyGem provides the server-side Commander, while WebFormsJS provides the c*lient-side Executor*.

Conclusion

This radio player demonstrates how Ruby and WebForms Core can be used to build an interactive browser interface while keeping the application logic on the server.

WebForms Core Radio Project

The Ruby code describes the required UI operations through the WebForms class. WebFormsJS receives the resulting commands and applies them directly to the browser's DOM.

There is no need to build a separate client-side component system for this example. The HTML remains ordinary HTML, the server controls the interaction through WebForms Core, and the browser executes the resulting commands through WebFormsJS.

Source Code

WebForms Core:

https://github.com/webforms-core

WebForms Core classes:

https://github.com/webforms-core/Web_forms_classes

WebFormsJS:

https://github.com/elanatframework/Web_forms/blob/elanat_framework/web-forms.js

Top comments (0)