Files
MosisService/portal/internal/web/docs/guides/lua-scripting.md

8.9 KiB

Lua Scripting Guide

Mosis apps use Lua for scripting and interactivity. Each app runs in an isolated sandbox with access to Mosis-specific APIs.

Getting Started

Embed Lua directly in your RML files:

<body>
    <button onclick="sayHello()">Click Me</button>

    <script>
        function sayHello()
            print("Hello from Lua!")
        end
    </script>
</body>

Or use external files:

<head>
    <script src="scripts/app.lua"/>
</head>

Lua Basics

If you're new to Lua, here's a quick primer:

Variables

-- Local variables (preferred)
local name = "Mosis"
local count = 42
local enabled = true
local items = {"apple", "banana", "cherry"}

-- Global variables (avoid when possible)
globalVar = "accessible everywhere"

Functions

-- Basic function
function greet(name)
    return "Hello, " .. name .. "!"
end

-- Function with multiple returns
function getPosition()
    return 100, 200
end

local x, y = getPosition()

-- Anonymous functions
local double = function(n) return n * 2 end

Control Flow

-- If statements
if score > 100 then
    print("High score!")
elseif score > 50 then
    print("Good job!")
else
    print("Keep trying!")
end

-- Loops
for i = 1, 10 do
    print(i)
end

for index, value in ipairs(items) do
    print(index, value)
end

while condition do
    -- loop body
end

Tables

-- Array-like table
local colors = {"red", "green", "blue"}
print(colors[1])  -- "red" (Lua is 1-indexed)

-- Dictionary-like table
local user = {
    name = "Alice",
    age = 25,
    premium = true
}
print(user.name)
print(user["age"])

-- Mixed table
local app = {
    name = "MyApp",
    version = "1.0",
    features = {"dark mode", "notifications"}
}

DOM Manipulation

Access and modify UI elements using the document object:

Getting Elements

-- By ID
local button = document:GetElementById("my-button")

-- By tag name
local paragraphs = document:GetElementsByTagName("p")

-- By class name
local cards = document:GetElementsByClassName("card")

Modifying Content

local element = document:GetElementById("message")

-- Set inner content (HTML-like)
element.inner_rml = "<strong>Hello!</strong>"

-- Get inner content
local content = element.inner_rml

-- Set text only (safer, no HTML parsing)
element:SetInnerRML("Plain text here")

Modifying Attributes

local input = document:GetElementById("username")

-- Get attribute
local value = input:GetAttribute("value")

-- Set attribute
input:SetAttribute("placeholder", "Enter username")

-- Remove attribute
input:RemoveAttribute("disabled")

Modifying Styles

local box = document:GetElementById("box")

-- Set individual properties
box.style.width = "200dp"
box.style.backgroundColor = "#00d4ff"
box.style.display = "none"  -- hide element

-- Read properties
local width = box.style.width

Classes

local element = document:GetElementById("panel")

-- Add class
element:SetClass("active", true)

-- Remove class
element:SetClass("active", false)

-- Check class
if element:IsClassSet("active") then
    print("Panel is active")
end

Event Handling

Inline Events

<button onclick="handleClick()">Click</button>
<input onchange="handleChange(event)"/>
<div onmouseover="handleHover()"/>

Event Listeners

local button = document:GetElementById("my-button")

-- Add listener
button:AddEventListener("click", function(event)
    print("Button clicked!")
end)

-- Remove listener (need reference)
local handler = function(event)
    print("Clicked")
end
button:AddEventListener("click", handler)
button:RemoveEventListener("click", handler)

Event Object

function handleEvent(event)
    -- Event type
    print(event.type)  -- "click", "change", etc.

    -- Target element
    local target = event:GetCurrentElement()

    -- Mouse position (for mouse events)
    local x = event.parameters.mouse_x
    local y = event.parameters.mouse_y

    -- Stop propagation
    event:StopPropagation()
end

Common Events

Event Description
click Element clicked
dblclick Element double-clicked
mousedown Mouse button pressed
mouseup Mouse button released
mouseover Mouse enters element
mouseout Mouse leaves element
focus Element gains focus
blur Element loses focus
change Input value changed
submit Form submitted
keydown Key pressed
keyup Key released

Timers

setTimeout

-- Execute once after delay
local timerId = setTimeout(function()
    print("Executed after 1 second")
end, 1000)  -- milliseconds

-- Cancel timer
clearTimeout(timerId)

setInterval

-- Execute repeatedly
local intervalId = setInterval(function()
    print("Tick")
end, 1000)

-- Cancel interval
clearInterval(intervalId)

Storage

Persist data between app sessions:

-- Save data
storage.set("username", "Alice")
storage.set("settings", {
    darkMode = true,
    notifications = false
})

-- Load data
local username = storage.get("username")
local settings = storage.get("settings")

-- Delete data
storage.remove("username")

-- Clear all data
storage.clear()

Navigation

Navigate between screens in your app:

-- Navigate to screen
navigateTo("settings")  -- loads assets/settings.rml

-- Go back
goBack()

-- Go to home screen
goHome()

-- Replace current screen (no back)
replaceTo("login")

Navigation Events

-- Listen for navigation
onNavigate(function(screenName)
    print("Navigated to: " .. screenName)
end)

-- Listen for back
onBack(function()
    print("Going back")
end)

HTTP Requests

Make network requests (requires network permission):

-- GET request
http.get("https://api.example.com/data", function(response)
    if response.ok then
        local data = json.decode(response.body)
        print(data.message)
    else
        print("Error: " .. response.status)
    end
end)

-- POST request
http.post("https://api.example.com/submit", {
    headers = {
        ["Content-Type"] = "application/json"
    },
    body = json.encode({
        name = "Alice",
        action = "subscribe"
    })
}, function(response)
    print("Status: " .. response.status)
end)

JSON

-- Parse JSON string
local data = json.decode('{"name": "Alice", "age": 25}')
print(data.name)

-- Convert to JSON string
local str = json.encode({
    items = {"a", "b", "c"},
    count = 3
})

Date and Time

-- Current timestamp
local now = os.time()

-- Format date
local formatted = os.date("%Y-%m-%d %H:%M:%S", now)

-- Parse date components
local t = os.date("*t", now)
print(t.year, t.month, t.day, t.hour, t.min, t.sec)

Utilities

String Functions

-- Concatenation
local greeting = "Hello, " .. name .. "!"

-- String functions
string.upper("hello")  -- "HELLO"
string.lower("HELLO")  -- "hello"
string.sub("hello", 1, 3)  -- "hel"
string.find("hello", "ll")  -- 3
string.gsub("hello", "l", "L")  -- "heLLo"
string.format("Score: %d", 100)  -- "Score: 100"

Math Functions

math.floor(3.7)  -- 3
math.ceil(3.2)   -- 4
math.round(3.5)  -- 4
math.abs(-5)     -- 5
math.min(1, 2, 3)  -- 1
math.max(1, 2, 3)  -- 3
math.random()      -- 0-1
math.random(1, 6)  -- 1-6

Table Functions

-- Insert
table.insert(items, "new item")
table.insert(items, 1, "at beginning")

-- Remove
table.remove(items)  -- remove last
table.remove(items, 1)  -- remove first

-- Sort
table.sort(items)
table.sort(items, function(a, b) return a > b end)  -- descending

-- Length
local count = #items

Sandbox Restrictions

For security, these are NOT available:

  • os.execute, io.popen - No shell commands
  • loadfile, dofile - No arbitrary file loading
  • require - No external modules (use import for app modules)
  • debug library - No debugging hooks
  • rawget, rawset - No metatable bypass

Best Practices

  1. Use local variables - Faster and prevents pollution
  2. Handle errors - Use pcall for operations that might fail
  3. Clean up timers - Clear intervals when navigating away
  4. Minimize DOM queries - Cache element references
  5. Batch updates - Group style changes together

Error Handling

local success, result = pcall(function()
    -- Code that might fail
    local data = json.decode(invalidJson)
    return data
end)

if success then
    print("Parsed:", result)
else
    print("Error:", result)
end

Module Pattern

-- utils.lua
local Utils = {}

function Utils.formatCurrency(amount)
    return string.format("$%.2f", amount)
end

function Utils.capitalize(str)
    return str:sub(1,1):upper() .. str:sub(2)
end

return Utils
-- main.lua
local Utils = import("utils")

print(Utils.formatCurrency(19.99))

Next Steps