add documentation site with markdown rendering (M12)
This commit is contained in:
506
portal/internal/web/docs/guides/lua-scripting.md
Normal file
506
portal/internal/web/docs/guides/lua-scripting.md
Normal file
@@ -0,0 +1,506 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user