Skip to content

Commit b7de9c5

Browse files
author
Sabine Lim
authored
Refactor reactive framework (#85)
* Fix addObject * Refactors Fix * Preserve lowercase first char behaviour * Default empty props * Allow for reactive props from imperative code
1 parent a0a7173 commit b7de9c5

File tree

3 files changed

+110
-151
lines changed

3 files changed

+110
-151
lines changed

Basalt/libraries/xmlParser.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ local XMLParser = {
7070
if #stack > 1 then
7171
error("XMLParser: unclosed " .. stack[#stack].tag)
7272
end
73-
return top
73+
return top.children
7474
end
7575
}
7676

Basalt/objects/Container.lua

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,15 @@ return function(name, basalt)
7373
for event, _ in pairs(element:getRegisteredEvents()) do
7474
self:addEvent(event, element)
7575
end
76-
77-
if(element.init~=nil)then element:init() end
78-
if(element.load~=nil)then element:load() end
79-
if(element.draw~=nil)then element:draw() end
80-
76+
if (element.init~=nil) then
77+
element:init()
78+
end
79+
if (element.load~=nil) then
80+
element:load()
81+
end
82+
if (element.draw~=nil) then
83+
element:draw()
84+
end
8185
return element
8286
end
8387

Basalt/plugins/reactive.lua

Lines changed: 100 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,36 @@
11
local XMLParser = require("xmlParser")
2-
local utils = require("utils")
3-
local uuid = utils.uuid
42

5-
local function maybeExecuteScript(nodeTree, renderContext)
6-
for _, node in ipairs(nodeTree.children) do
7-
if (node.tag == "script") then
8-
return load(node.value, nil, "t", renderContext.env)()
3+
local Layout = {
4+
fromXML = function(text)
5+
local nodes = XMLParser.parseText(text)
6+
local script = nil
7+
for index, node in ipairs(nodes) do
8+
if (node.tag == "script") then
9+
script = node.value
10+
table.remove(nodes, index)
11+
break
12+
end
913
end
14+
return {
15+
nodes = nodes,
16+
script = script
17+
}
1018
end
19+
}
20+
21+
local executeScript = function(script, env)
22+
return load(script, nil, "t", env)()
1123
end
1224

13-
local function registerFunctionEvent(self, event, script, renderContext)
14-
local eventEnv = renderContext.env
15-
event(self, function(...)
16-
eventEnv.event = {...}
17-
local success, msg = pcall(load(script, nil, "t", eventEnv))
25+
local registerFunctionEvent = function(object, event, script, env)
26+
event(object, function(...)
27+
local success, msg = pcall(load(script, nil, "t", env))
1828
if not success then
1929
error("XML Error: "..msg)
2030
end
2131
end)
2232
end
2333

24-
local function registerFunctionEvents(self, node, events, renderContext)
25-
for _, event in pairs(events) do
26-
local expression = node.attributes[event]
27-
if (expression ~= nil) then
28-
registerFunctionEvent(self, self[event], expression .. "()", renderContext)
29-
end
30-
end
31-
end
32-
3334
local currentEffect = nil
3435

3536
local clearEffectDependencies = function(effect)
@@ -45,13 +46,39 @@ end
4546

4647
return {
4748
basalt = function(basalt)
48-
local object = {
49-
layout = function(path)
50-
return {
51-
path = path,
52-
}
53-
end,
49+
local createObjectsFromXMLNode = function(node, env)
50+
local layout = env[node.tag]
51+
if (layout ~= nil) then
52+
local props = {}
53+
for prop, expression in pairs(node.attributes) do
54+
props[prop] = load("return " .. expression, nil, "t", env)
55+
end
56+
return basalt.createObjectsFromLayout(layout, props)
57+
end
58+
59+
local objectName = node.tag:gsub("^%l", string.upper)
60+
local object = basalt:createObject(objectName, node.attributes["id"])
61+
for attribute, expression in pairs(node.attributes) do
62+
if (attribute:sub(1, 2) == "on") then
63+
registerFunctionEvent(object, object[attribute], expression .. "()", env)
64+
else
65+
local update = function()
66+
local value = load("return " .. expression, nil, "t", env)()
67+
object:setProperty(attribute, value)
68+
end
69+
basalt.effect(update)
70+
end
71+
end
72+
for _, child in ipairs(node.children) do
73+
local childObjects = basalt.createObjectsFromXMLNode(child, env)
74+
for _, childObject in ipairs(childObjects) do
75+
object:addChild(childObject)
76+
end
77+
end
78+
return {object}
79+
end
5480

81+
local object = {
5582
reactive = function(initialValue)
5683
local value = initialValue
5784
local observerEffects = {}
@@ -102,139 +129,67 @@ return {
102129
setValue(computeFn())
103130
end)
104131
return getValue;
105-
end
106-
}
107-
return object
108-
end,
109-
110-
VisualObject = function(base, basalt)
132+
end,
111133

112-
local object = {
113-
setValuesByXMLData = function(self, node, renderContext)
114-
renderContext.env[self:getName()] = self
115-
for attribute, expression in pairs(node.attributes) do
116-
local update = function()
117-
local value = load("return " .. expression, nil, "t", renderContext.env)()
118-
self:setProperty(attribute, value)
119-
end
120-
basalt.effect(update)
134+
layout = function(path)
135+
if (not fs.exists(path)) then
136+
error("Can't open file " .. path)
121137
end
122-
registerFunctionEvents(self, node, {
123-
"onClick",
124-
"onClickUp",
125-
"onHover",
126-
"onScroll",
127-
"onDrag",
128-
"onKey",
129-
"onKeyUp",
130-
"onRelease",
131-
"onChar",
132-
"onGetFocus",
133-
"onLoseFocus",
134-
"onResize",
135-
"onReposition",
136-
"onEvent",
137-
"onLeave"
138-
}, renderContext)
139-
return self
138+
local f = fs.open(path, "r")
139+
local text = f.readAll()
140+
f.close()
141+
return Layout.fromXML(text)
140142
end,
141-
}
142-
return object
143-
end,
144143

145-
ChangeableObject = function(base, basalt)
146-
local object = {
147-
setValuesByXMLData = function(self, node, renderContext)
148-
base.setValuesByXMLData(self, node, renderContext)
149-
registerFunctionEvent(self, node, {
150-
"onChange"
151-
}, renderContext)
152-
return self
153-
end,
144+
createObjectsFromLayout = function(layout, props)
145+
local env = _ENV
146+
env.props = {}
147+
local updateFns = {}
148+
for prop, getFn in pairs(props) do
149+
updateFns[prop] = basalt.derived(function()
150+
return getFn()
151+
end)
152+
end
153+
setmetatable(env.props, {
154+
__index = function(_, k)
155+
return updateFns[k]()
156+
end
157+
})
158+
if (layout.script ~= nil) then
159+
executeScript(layout.script, env)
160+
end
161+
local objects = {}
162+
for _, node in ipairs(layout.nodes) do
163+
local _objects = createObjectsFromXMLNode(node, env)
164+
for _, object in ipairs(_objects) do
165+
table.insert(objects, object)
166+
end
167+
end
168+
return objects
169+
end
154170
}
155171
return object
156172
end,
157173

158174
Container = function(base, basalt)
159-
local lastXMLReferences = {}
160-
161-
local function xmlDefaultValues(node, obj, renderContext)
162-
if (obj~=nil) then
163-
obj:setValuesByXMLData(node, renderContext)
164-
end
165-
end
166-
167-
local function addXMLObjectType(node, addFn, self, renderContext)
168-
if (node ~= nil) then
169-
if (node.attributes ~= nil) then
170-
node = {node}
171-
end
172-
for _, v in pairs(node) do
173-
local obj = addFn(self, v["@id"] or uuid())
174-
lastXMLReferences[obj:getName()] = obj
175-
xmlDefaultValues(v, obj, renderContext)
176-
end
177-
end
178-
end
179-
180-
local function insertChildLayout(self, layout, node, renderContext)
181-
local updateFns = {}
182-
for prop, expression in pairs(node.attributes) do
183-
updateFns[prop] = basalt.derived(function()
184-
return load("return " .. expression, nil, "t", renderContext.env)()
185-
end)
186-
end
187-
local props = {}
188-
setmetatable(props, {
189-
__index = function(_, k)
190-
return updateFns[k]()
191-
end
192-
})
193-
self:loadLayout(layout.path, props)
194-
end
195-
196175
local object = {
197-
setValuesByXMLData = function(self, node, renderContext)
198-
lastXMLReferences = {}
199-
base.setValuesByXMLData(self, node, renderContext)
200-
201-
local _OBJECTS = basalt.getObjects()
202-
203-
for _, child in pairs(node.children) do
204-
local tagName = child.tag
205-
if (tagName == "animation") then
206-
addXMLObjectType(child, self.addAnimation, self, renderContext)
207-
else
208-
local layout = renderContext.env[tagName]
209-
local objectKey = tagName:gsub("^%l", string.upper)
210-
if (layout ~= nil) then
211-
insertChildLayout(self, layout, child, renderContext)
212-
elseif (_OBJECTS[objectKey] ~= nil) then
213-
local addFn = self["add" .. objectKey]
214-
addXMLObjectType(child, addFn, self, renderContext)
215-
end
176+
loadLayout = function(self, path, props)
177+
local wrappedProps = {}
178+
if (props == nil) then
179+
props = {}
180+
end
181+
for prop, value in pairs(props) do
182+
wrappedProps[prop] = function()
183+
return value
216184
end
217185
end
218-
end,
219-
220-
loadLayout = function(self, path, props)
221-
if(fs.exists(path))then
222-
local renderContext = {}
223-
renderContext.env = _ENV
224-
renderContext.env.props = props
225-
local f = fs.open(path, "r")
226-
local nodeTree = XMLParser.parseText(f.readAll())
227-
f.close()
228-
lastXMLReferences = {}
229-
maybeExecuteScript(nodeTree, renderContext)
230-
self:setValuesByXMLData(nodeTree, renderContext)
186+
local layout = basalt.layout(path)
187+
local objects = basalt.createObjectsFromLayout(layout, wrappedProps)
188+
for _, object in ipairs(objects) do
189+
self:addChild(object)
231190
end
232191
return self
233-
end,
234-
235-
getXMLElements = function(self)
236-
return lastXMLReferences
237-
end,
192+
end
238193
}
239194
return object
240195
end

0 commit comments

Comments
 (0)