507 lines
8.9 KiB
Markdown
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
|