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

507 lines
8.9 KiB
Markdown

# 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:
```xml
<body>
<button onclick="sayHello()">Click Me</button>
<script>
function sayHello()
print("Hello from Lua!")
end
</script>
</body>
```
Or use external files:
```xml
<head>
<script src="scripts/app.lua"/>
</head>
```
## Lua Basics
If you're new to Lua, here's a quick primer:
### Variables
```lua
-- 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
```lua
-- 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
```lua
-- 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
```lua
-- 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
```lua
-- 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
```lua
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
```lua
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
```lua
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
```lua
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
```xml
<button onclick="handleClick()">Click</button>
<input onchange="handleChange(event)"/>
<div onmouseover="handleHover()"/>
```
### Event Listeners
```lua
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
```lua
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
```lua
-- Execute once after delay
local timerId = setTimeout(function()
print("Executed after 1 second")
end, 1000) -- milliseconds
-- Cancel timer
clearTimeout(timerId)
```
### setInterval
```lua
-- Execute repeatedly
local intervalId = setInterval(function()
print("Tick")
end, 1000)
-- Cancel interval
clearInterval(intervalId)
```
## Storage
Persist data between app sessions:
```lua
-- 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:
```lua
-- 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
```lua
-- 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):
```lua
-- 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
```lua
-- 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
```lua
-- 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
```lua
-- 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
```lua
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
```lua
-- 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
```lua
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
```lua
-- 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
```
```lua
-- main.lua
local Utils = import("utils")
print(Utils.formatCurrency(19.99))
```
## Next Steps
- [Permissions Guide](permissions.md) - Request device capabilities
- [API Reference](../api/lua-api.md) - Complete API documentation
- [Debugging Guide](debugging.md) - Debug your Lua code