Packages
mob_dev
0.3.21
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.37
0.3.35
0.3.34
0.3.33
0.3.28
0.3.26
0.3.23
0.3.21
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Development tooling for the Mob mobile framework
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/mob.gen.live_screen.ex
defmodule Mix.Tasks.Mob.Gen.LiveScreen do
use Mix.Task
@shortdoc "Generate a LiveView + Mob.Screen pair"
@moduledoc """
Generates a paired `Mob.Screen` and Phoenix `LiveView` for LiveView mode apps.
## Usage
mix mob.gen.live_screen NAME [PATH]
`NAME` is the LiveView module name (PascalCase). `PATH` is the URL path
(defaults to `/name` derived from `NAME`).
## Examples
mix mob.gen.live_screen Dashboard
# → lib/<app>_web/live/dashboard_live.ex (LiveView)
# → lib/<app>/screens/dashboard_screen.ex (Mob.Screen)
mix mob.gen.live_screen Settings /preferences
# → path override: /preferences
## What gets generated
### LiveView (`lib/<app>_web/live/<name>_live.ex`)
defmodule MyAppWeb.DashboardLive do
use MyAppWeb, :live_view
use Mob.LiveView
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(assigns) do
~H\"""
<div>
<h1>Dashboard</h1>
</div>
\"""
end
# Receive messages from the native layer:
# window.mob.send({ type: "back" })
def handle_event("mob_message", _data, socket) do
{:noreply, socket}
end
# Push messages to the native layer JS:
# push_event(socket, "mob_push", %{type: "haptic"})
end
### Mob.Screen (`lib/<app>/screens/<name>_screen.ex`)
defmodule MyApp.DashboardScreen do
use Mob.Screen
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(_assigns) do
Mob.UI.webview(
url: Mob.LiveView.local_url("/dashboard"),
show_url: false
)
end
end
## Router note
Add the LiveView to your Phoenix router:
live "/dashboard", DashboardLive
Then navigate to the screen from Elixir:
Mob.Socket.navigate(socket, {:push, MyApp.DashboardScreen})
"""
@impl Mix.Task
def run(argv) do
case argv do
[] ->
Mix.raise("Usage: mix mob.gen.live_screen NAME [PATH]")
[name | rest] ->
path = List.first(rest)
project_dir = File.cwd!()
unless File.exists?(Path.join(project_dir, "mix.exs")) do
Mix.raise("No mix.exs found. Run from your project root.")
end
app_name = read_app_name(project_dir)
generate(project_dir, app_name, name, path)
end
end
# ── Generation ────────────────────────────────────────────────────────────
defp generate(project_dir, app_name, name, path_override) do
module_name = Macro.camelize(app_name)
snake_name = Macro.underscore(name)
url_path = path_override || "/#{snake_name}"
live_module = "#{module_name}Web.#{name}Live"
screen_module = "#{module_name}.#{name}Screen"
web_module = "#{module_name}Web"
live_path =
Path.join([project_dir, "lib", "#{app_name}_web", "live", "#{snake_name}_live.ex"])
screen_path = Path.join([project_dir, "lib", app_name, "screens", "#{snake_name}_screen.ex"])
write_file(live_path, live_view_template(live_module, web_module, name, snake_name))
write_file(screen_path, screen_template(screen_module, url_path))
Mix.shell().info("""
Generated:
#{live_path}
#{screen_path}
Next steps:
1. Add the route to your Phoenix router (lib/#{app_name}_web/router.ex):
live "#{url_path}", #{name}Live
2. Navigate to the screen from Elixir:
Mob.Socket.navigate(socket, {:push, #{screen_module}})
3. Send events from JS to Elixir:
window.mob.send({ type: "action", payload: "hello" })
Handle them in #{live_module}:
def handle_event("mob_message", %{"type" => "action"} = data, socket) do
{:noreply, socket}
end
""")
end
defp write_file(path, content) do
if File.exists?(path) do
Mix.shell().info(" * skip #{path} (already exists)")
else
File.mkdir_p!(Path.dirname(path))
File.write!(path, content)
Mix.shell().info([:green, " * create ", :reset, path])
end
end
# ── Templates ─────────────────────────────────────────────────────────────
defp live_view_template(live_module, web_module, name, snake_name) do
display = name |> String.replace(Regex.compile!("([A-Z])"), " \\1") |> String.trim()
"""
defmodule #{live_module} do
use #{web_module}, :live_view
use Mob.LiveView
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(assigns) do
~H\"\"\"
<div class="mob-screen" id="#{snake_name}">
<h1>#{display}</h1>
</div>
\"\"\"
end
# Receive messages from the native layer via window.mob.send(data).
def handle_event("mob_message", _data, socket) do
{:noreply, socket}
end
# Push to native layer: push_event(socket, "mob_push", %{...})
end
"""
end
defp screen_template(screen_module, url_path) do
"""
defmodule #{screen_module} do
use Mob.Screen
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(_assigns) do
Mob.UI.webview(
url: Mob.LiveView.local_url("#{url_path}"),
show_url: false
)
end
end
"""
end
# ── Helpers ───────────────────────────────────────────────────────────────
defp read_app_name(project_dir) do
MobDev.Enable.read_app_name_from(Path.join(project_dir, "mix.exs"))
rescue
e -> Mix.raise(Exception.message(e))
end
end