Add Stipple,jl reactive elements to existed Genie.jl app with controllers

So I made Genie.jl app as it described in movie example. With controllers, route.jl, ets…

Is it possible to use Stipple.jl functionality in existed project?

I have no app.jl in Genie.jl project… Where I should use all of these elements?

using GenieFramework # < inside controller?
using Statistics
@genietools # < inside controller?

@app begin # < inside controller?
    @in N = 0
    @out m = 0.0
    @out name = "John"
    @onchange N begin
        m = mean(rand(N))
    end
end

# Define the UI for each page
index() = [h1("Welcome {{name}}!"), a(href="/form")]
function formui()  # < call this function from routes.jl 
    cell([
        textfield("How many numbers?", :N),
        p("The average of {{N}} random numbers is {{m}}"),
    ])
end

@route("/", index) # < remove this ?
@page("/form", formui) # < remove this ?

Can i just add?:

using MyGenieApp.OnesController
route("/trials", OnesController.formui)

After some tries…

and

import Pkg; Pkg.add("StipplePivotTable")

got…

Failed to precompile StipplePivotTable [adab0226-ee3c-44de-8f2f-393b1bf1e936] to C:\Users\...\.julia\compiled\v1.8\StipplePivotTable\jl_A02.tmp.
ERROR: LoadError: UndefVarError: @kwdef not defined

Are you using pre-1.9 Julia? What does @kwdef do? - #4 by j-fu says that @kwdef was exported from Base in version 1.9.

So, yes. This was a problem with @kwdef, julia version 1.9 solve it.

But as i correctly understand I cant use something like @route("/", index) in route.jl for simple Genie.jl mvc project.
Seems I should manually make page with java scripts for asynchronous work.

I’ve never used Genie or Stipple, but the docs here say to define routes with the @page macro: Introduction - Genie

Some other posts here suggest contacting the developers at Genie Community

I hope that helps

Hi! Seems @page work only for GenieFramework.jl and not for Genie.jl , my task to add reactive model to existed Genie project, but I have no answer on discord yet…
Is Genie project alive?

I have a working solution, but the layout doesn’t yet match the rest of the app. This is how it currently looks (with slight modifications). Note that I use some packages not (yet?) public, which you should ignore for the moment. (Also internationalization hasn’t been implemented yet for the Stipple example in the app—it would typically involve something like @t(“panels!heading”), and so on.)

The app’s code (a little different) comes from the default setup created by my private AuthPlugin with AuthPlugin.install. Some important parts you will find below.

app/resources/panels/PanelsController.jl

module PanelsController
using Genie, Stipple, Stipple.ReactiveTools, StippleUI, AuthPlugin, I18nPlugin

@app PanelModel begin
    @in N = 0
    @out msg = ""
    @onchange N begin
        msg = "N = $N"
    end
end

function panels()
    @authenticated!
    locale = params(:locale, "en")
    model = @init PanelModel
    view = include(path"app/resources/panels/views/panels.jl")
    layout = filepath("app/layouts/authapp_stipple.jl.html")
     html!(view;
    layout  = layout,
    context = @__MODULE__,
    model   = model,
    locale  = locale
  )
end
end

app/resources/panels/views/panels.jl

  [
        cell([
                p("Enter a number")
                textfield("N", :N )
            ])
        cell([
                bignumber("The value of N is", :N)
            ])
    ]


app/resources/layouts/authapp.jl.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="/css/authpluginstyle.css">
    <title>Online Shop</title>
</head>

<body>

    <nav>
        <div class="container">
            <div class="logo">
                     <a href="$(linkto(:main,locale=locale))">$(@t("layout!site_name"))</a>
            </div>
            <label for="menu-toggle" class="menu-icon">&#9776;</label>
            <input type="checkbox" id="menu-toggle">
            <ul class="menu">
                <li><a href="$(linkto(:main,locale=locale))">$(@t("layout!home"))</a></li>
                <% if Main.UserApp.authenticated() %>
                    <li><a href="$(linkto(:panel,locale=locale))">$(@t("layout!panel"))</a></li>
                    <% end %>
                        <% if Main.UserApp.authenticated() %>
                            <li><a href="$(linkto(:account_index,locale=locale))">$(@t("layout!my_account"))</a></li>
                            <% else %>
                                <li><a href="$(linkto(:show_login,locale=locale))">$(@t("layout!login"))</a></li>
                                <% end %>
                                    <% if Main.UserApp.has_role(Main.UserApp.current_user(), "admin" ) %>
                                        <li><a href="$(linkto(:admin_index,locale=locale))">$(@t("layout!admin"))</a>
                                        </li>
                                        <% end %>

                                            <li class="lang-picker">
                                                <select id="langSelectMenu" onchange="window.location=this.value">
                                                    <% for_each(available_locales(I18N)) do lang %>
                                                        $(
                                                        option(
                                                        uppercase(lang),
                                                        value = switch_locale_link(lang),
                                                        selected = (lang == locale)
                                                        )
                                                        )
                                                        <% end %>
                                                </select>
                                            </li>
            </ul>
        </div>
    </nav>
    <% @yield %>
</body>

<footer>
    <div class="container">
        <p>$(@t("layout!footer_text"))</p>
        <p>$(@t("layout!footer_copyright"))</p>
        <p class="legal-links">
            <a href="$(linkto(:get_imprint,locale=locale))">$(@t("layout!imprint"))</a>&nbsp;|&nbsp;
            <a href="$(linkto(:get_privacy,locale=locale))">$(@t("layout!privacy"))</a>
        </p>
    </div>
</footer>

</html>

app/resources/layouts/authapp_stipple.jl.html

<!DOCTYPE html>
<html lang="en">
  <head>
     <% Stipple.sesstoken() %>
    <meta charset="utf-8" />
       <link rel="stylesheet" href="/css/authpluginstyle.css">
    <title>$(@t("layout!site_name"))</title>
  </head>
  <body>

  <nav>
        <div class="container">
            <div class="logo">
                <a href="$(linkto(:main,locale=locale))">$(@t("layout!site_name"))</a>
            </div>
            <label for="menu-toggle" class="menu-icon">&#9776;</label>
             <input type="checkbox" id="menu-toggle">
             <ul class="menu">
                <li><a href="$(linkto(:main,locale=locale))">$(@t("layout!home"))</a></li>
   <% if Main.UserApp.authenticated() %>
   <li><a href="$(linkto(:panel,locale=locale))">$(@t("layout!panel"))</a></li>
   <% end %>
   <% if Main.UserApp.authenticated() %>
    <li><a href="$(linkto(:account_index,locale=locale))">$(@t("layout!my_account"))</a></li>
   <% else %>
    <li><a href="$(linkto(:show_login,locale=locale))">$(@t("layout!login"))</a></li>
   <% end %>
   <% if Main.UserApp.has_role(Main.UserApp.current_user(), "admin") %>
      <li><a href="$(linkto(:admin_index,locale=locale))">$(@t("layout!admin"))</a></li>
    <% end %>

<li class="lang-picker">
  <select id="langSelectMenu" onchange="window.location=this.value">
      <% for_each(available_locales(I18N)) do lang %>
        $(
          option(
            uppercase(lang),
            value    =  switch_locale_link(lang),
            selected = (lang == locale)
          )
        )
      <% end %>
    </select>
  </li>
            </ul>
        </div>
    </nav>
 <% Stipple.page(model, partial = true, v__cloak = true, [Stipple.Genie.Renderer.Html.@yield], Stipple.@if(:isready)) %>
  </body>

 <footer>
  <div class="container">
    <p>$(@t("layout!footer_text"))</p>
    <p>$(@t("layout!footer_copyright"))</p>
    <p class="legal-links">
      <a href="$(linkto(:get_imprint,locale=locale))">$(@t("layout!imprint"))</a>&nbsp;|&nbsp;
      <a href="$(linkto(:get_privacy,locale=locale))">$(@t("layout!privacy"))</a>
    </p>
  </div>
</footer>
</html>

routes.jl / or plugins/authplugin.jl

...
using ..Main.UserApp.PanelsController
route("/$(patternize(I18N))/panels",PanelsController.panels, named = :panel)

This is a the structure of the app (a little modified).

├── app
│   ├── helpers
│   │   └── ValidationHelper.jl
│   ├── layouts
│   │   ├── app.jl.html
│   │   ├── authapp.jl.html
│   │   └── authapp_stipple.jl.html
│   └── resources
│       ├── accounts
│       │   ├── AccountsController.jl
│       │   └── views
│       │       └── account_index.jl.html
│       ├── admins
│       │   ├── AdminsController.jl
│       │   └── views
│       │       └── index.jl.html
│       ├── legals
│       │   ├── LegalsController.jl
│       │   └── views
│       │       ├── imprint.jl.html
│       │       └── privacy.jl.html
│       ├── mains
│       │   ├── MainsController.jl
│       │   └── views
│       │       └── index.jl.html
│       └── panels
│           ├── PanelsController.jl
│           └── views
│               └── panels.jl
├── bin
│   ├── repl
│   ├── repl.bat
│   ├── runtask
│   ├── runtask.bat
│   ├── server
│   └── server.bat
├── THISAPP.code-workspace
├── bootstrap.jl
├── build
│   └── GenieViews
├── config
│   ├── env
│   │   ├── dev.jl
│   │   ├── global.jl
│   │   ├── prod.jl
│   │   └── test.jl
│   ├── initializers
│   │   ├── authplugin_init.jl
│   │   ├── autoload.jl
│   │   ├── channel_authorizer.jl
│   │   ├── converters.jl
│   │   ├── i18nplugin.jl
│   │   ├── inflector.jl
│   │   ├── logging.jl
│   │   └── searchlight.jl
│   └── secrets.jl
├── db
│   ├── connection.yml
│   ├── dev.sqlite3
│   ├── migrations
│   └── seeds
├── locales
│   └── en
│       ├── accounts.toml
│       ├── admins.toml
│       ├── layout.toml
│       ├── legals.toml
│       └── mains.toml
├── log
│   ├── prod-2025-07-23.log
│   ├── prod-2025-07-24.log
│   ├── prod-2025-07-25.log
│   ├── prod-2025-07-26.log
│   ├── prod-2025-07-27.log
│   └── test-2025-07-18.log
├── Manifest.toml
├── plugins
│   └── authplugin.jl
├── Project.toml
├── public
│   ├── css
│   │   ├── authpluginstyle.css
│   │   ├── genie
│   │   │   ├── prism.css
│   │   │   └── style.css
│   │   └── old?authpluginstyle.css
│   ├── error-404.html
│   ├── error-500.html
│   ├── error-xxx.html
│   ├── favicon.ico
│   ├── fonts
│   │   ├── glyphicons-halflings-regular.eot
│   │   ├── glyphicons-halflings-regular.svg
│   │   ├── glyphicons-halflings-regular.ttf
│   │   ├── glyphicons-halflings-regular.woff
│   │   └── glyphicons-halflings-regular.woff2
│   ├── img
│   │   └── genie
│   │       ├── community.png
│   │       ├── contribute-2.png
│   │       ├── docs.png
│   │       ├── genie.png
│   │       └── genie-sad.png
│   ├── js
│   │   └── genie
│   │       ├── ansi_up.js
│   │       ├── ansi_up.js.map
│   │       ├── jquery.min.js
│   │       ├── jquery.min.map
│   │       ├── prism.js
│   │       └── static.js
│   ├── robots.txt
│   └── welcome.html
├── routes.jl
├── src
│   └── THISAPP.jl
├── test
│   ├── Manifest.toml
│   ├── Project.toml
│   └── runtests.jl