rotor-framework 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +120 -0
- package/package.json +59 -0
- package/src/source/RotorFramework.bs +654 -0
- package/src/source/RotorFrameworkTask.bs +278 -0
- package/src/source/base/BaseModel.bs +52 -0
- package/src/source/base/BasePlugin.bs +48 -0
- package/src/source/base/BaseReducer.bs +184 -0
- package/src/source/base/BaseStack.bs +92 -0
- package/src/source/base/BaseViewModel.bs +124 -0
- package/src/source/base/BaseWidget.bs +104 -0
- package/src/source/base/DispatcherCreator.bs +193 -0
- package/src/source/base/DispatcherExternal.bs +260 -0
- package/src/source/base/ListenerForDispatchers.bs +246 -0
- package/src/source/engine/Constants.bs +74 -0
- package/src/source/engine/animator/Animator.bs +334 -0
- package/src/source/engine/builder/Builder.bs +213 -0
- package/src/source/engine/builder/NodePool.bs +236 -0
- package/src/source/engine/builder/PluginAdapter.bs +139 -0
- package/src/source/engine/builder/PostProcessor.bs +331 -0
- package/src/source/engine/builder/Processor.bs +156 -0
- package/src/source/engine/builder/Tree.bs +278 -0
- package/src/source/engine/builder/TreeBase.bs +313 -0
- package/src/source/engine/builder/WidgetCreate.bs +322 -0
- package/src/source/engine/builder/WidgetRemove.bs +72 -0
- package/src/source/engine/builder/WidgetUpdate.bs +113 -0
- package/src/source/engine/providers/Dispatcher.bs +72 -0
- package/src/source/engine/providers/DispatcherProvider.bs +95 -0
- package/src/source/engine/services/I18n.bs +169 -0
- package/src/source/libs/animate/Animate.bs +753 -0
- package/src/source/libs/animate/LICENSE.txt +21 -0
- package/src/source/plugins/DispatcherProviderPlugin.bs +127 -0
- package/src/source/plugins/FieldsPlugin.bs +180 -0
- package/src/source/plugins/FocusPlugin.bs +1522 -0
- package/src/source/plugins/FontStylePlugin.bs +159 -0
- package/src/source/plugins/ObserverPlugin.bs +548 -0
- package/src/source/utils/ArrayUtils.bs +495 -0
- package/src/source/utils/GeneralUtils.bs +181 -0
- package/src/source/utils/NodeUtils.bs +180 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
namespace Rotor
|
|
2
|
+
|
|
3
|
+
' =====================================================================
|
|
4
|
+
' ObserverPlugin - Field change observation for roSGNodes
|
|
5
|
+
'
|
|
6
|
+
' Rotor Framework plugin for observing field changes on roSGNodes.
|
|
7
|
+
' Manages observer registration, callback routing, and cleanup through
|
|
8
|
+
' widget lifecycle hooks.
|
|
9
|
+
'
|
|
10
|
+
' Key Responsibilities:
|
|
11
|
+
' - Attaches/detaches observers to widget nodes during lifecycle
|
|
12
|
+
' - Routes native SceneGraph field change callbacks to registered observers
|
|
13
|
+
' - Manages observer lifecycle (once, until conditions)
|
|
14
|
+
' - Provides unique attachment IDs for tracking observers per node
|
|
15
|
+
' =====================================================================
|
|
16
|
+
class ObserverPlugin extends Rotor.BasePlugin
|
|
17
|
+
|
|
18
|
+
' =============================================================
|
|
19
|
+
' MEMBER VARIABLES
|
|
20
|
+
' =============================================================
|
|
21
|
+
|
|
22
|
+
observerStack as object ' ObserverStack instance managing all active observers
|
|
23
|
+
helperInterfaceId as object ' Unique field ID used to store plugin metadata on nodes
|
|
24
|
+
|
|
25
|
+
' =============================================================
|
|
26
|
+
' CONSTRUCTOR
|
|
27
|
+
' =============================================================
|
|
28
|
+
|
|
29
|
+
' ---------------------------------------------------------------------
|
|
30
|
+
' new - Initializes the ObserverPlugin instance
|
|
31
|
+
'
|
|
32
|
+
' @param {string} key - Plugin identifier (default: "observer")
|
|
33
|
+
'
|
|
34
|
+
sub new(key = "observer")
|
|
35
|
+
super(key)
|
|
36
|
+
end sub
|
|
37
|
+
|
|
38
|
+
' =============================================================
|
|
39
|
+
' LIFECYCLE HOOKS
|
|
40
|
+
' =============================================================
|
|
41
|
+
|
|
42
|
+
hooks = {
|
|
43
|
+
' ---------------------------------------------------------------------
|
|
44
|
+
' beforeMount - Attaches observers when widget is mounted
|
|
45
|
+
'
|
|
46
|
+
' Called during widget creation to register all observers defined in the widget config.
|
|
47
|
+
'
|
|
48
|
+
' @param {object} scope - Plugin instance (m)
|
|
49
|
+
' @param {object} widget - Widget being mounted
|
|
50
|
+
'
|
|
51
|
+
beforeMount: sub(scope as object, widget as object)
|
|
52
|
+
config = widget[scope.key]
|
|
53
|
+
scope.attach(widget.node, config, widget)
|
|
54
|
+
end sub,
|
|
55
|
+
|
|
56
|
+
' ---------------------------------------------------------------------
|
|
57
|
+
' beforeUpdate - Updates observers when widget config changes
|
|
58
|
+
'
|
|
59
|
+
' Detaches old observers and attaches new ones based on updated configuration.
|
|
60
|
+
'
|
|
61
|
+
' @param {object} scope - Plugin instance (m)
|
|
62
|
+
' @param {object} widget - Widget being updated
|
|
63
|
+
' @param {object} newValue - New observer configuration
|
|
64
|
+
' @param {object} oldValue - Previous observer configuration
|
|
65
|
+
'
|
|
66
|
+
beforeUpdate: sub(scope as object, widget as object, newValue, oldValue = {})
|
|
67
|
+
if oldValue <> invalid
|
|
68
|
+
scope.detach(widget.node)
|
|
69
|
+
end if
|
|
70
|
+
widget[scope.key] = newValue
|
|
71
|
+
scope.attach(widget.node, newValue, widget)
|
|
72
|
+
end sub,
|
|
73
|
+
|
|
74
|
+
' ---------------------------------------------------------------------
|
|
75
|
+
' beforeDestroy - Detaches all observers before widget destruction
|
|
76
|
+
'
|
|
77
|
+
' Ensures cleanup of all observers when widget is removed from scene graph.
|
|
78
|
+
'
|
|
79
|
+
' @param {object} scope - Plugin instance (m)
|
|
80
|
+
' @param {object} widget - Widget being destroyed
|
|
81
|
+
'
|
|
82
|
+
beforeDestroy: sub(scope as object, widget as object)
|
|
83
|
+
scope.detach(widget.node)
|
|
84
|
+
end sub
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
' =============================================================
|
|
88
|
+
' INITIALIZATION
|
|
89
|
+
' =============================================================
|
|
90
|
+
|
|
91
|
+
' ---------------------------------------------------------------------
|
|
92
|
+
' init - Initializes plugin internal state
|
|
93
|
+
'
|
|
94
|
+
' Creates the observer stack and sets up the helper interface ID
|
|
95
|
+
' used to track observers on nodes.
|
|
96
|
+
'
|
|
97
|
+
sub init()
|
|
98
|
+
m.observerStack = new Rotor.ObserverPluginHelper.ObserverStack()
|
|
99
|
+
m.helperInterfaceId = Rotor.ObserverPluginHelper.OBSERVER_HELPER_INTERFACE + "-" + m.key
|
|
100
|
+
end sub
|
|
101
|
+
|
|
102
|
+
' =============================================================
|
|
103
|
+
' OBSERVER MANAGEMENT
|
|
104
|
+
' =============================================================
|
|
105
|
+
|
|
106
|
+
' ---------------------------------------------------------------------
|
|
107
|
+
' attach - Attaches observers to a node
|
|
108
|
+
'
|
|
109
|
+
' Sets up helper interface on the node (if not already present) and registers
|
|
110
|
+
' all observers defined in the configuration.
|
|
111
|
+
'
|
|
112
|
+
' @param {object} node - roSGNode to attach observers to
|
|
113
|
+
' @param {object} config - Observer configuration (single object or array)
|
|
114
|
+
' @param {object} listenerScope - Widget instance for callback execution
|
|
115
|
+
'
|
|
116
|
+
sub attach(node as object, config as object, listenerScope as object)
|
|
117
|
+
' Determine or create attachment ID for this node
|
|
118
|
+
attachmentId = invalid
|
|
119
|
+
if node.hasField(m.helperInterfaceId)
|
|
120
|
+
' Node already has helper interface - reuse existing attachmentId
|
|
121
|
+
pluginHelperValue = node.getField(m.helperInterfaceId)
|
|
122
|
+
attachmentId = pluginHelperValue.attachmentId
|
|
123
|
+
else
|
|
124
|
+
' First time attaching to this node - create new attachmentId
|
|
125
|
+
attachmentId = Rotor.Utils.getUUIDHex()
|
|
126
|
+
|
|
127
|
+
if node <> invalid
|
|
128
|
+
' Add helper interface field to node with plugin metadata
|
|
129
|
+
pluginHelperFields = Rotor.Utils.wrapObject(m.helperInterfaceId, {
|
|
130
|
+
pluginKey: m.key,
|
|
131
|
+
attachmentId: attachmentId
|
|
132
|
+
})
|
|
133
|
+
Rotor.Utils.setCustomFields(node, pluginHelperFields, true, false)
|
|
134
|
+
end if
|
|
135
|
+
end if
|
|
136
|
+
|
|
137
|
+
' Register each observer configuration
|
|
138
|
+
if config <> invalid and config.Count() > 0 and attachmentId <> invalid
|
|
139
|
+
observerConfigs = Rotor.Utils.ensureArray(config)
|
|
140
|
+
for each observerConfig in observerConfigs
|
|
141
|
+
m.registerObserver(observerConfig, node, attachmentId, m.helperInterfaceId, listenerScope)
|
|
142
|
+
end for
|
|
143
|
+
end if
|
|
144
|
+
end sub
|
|
145
|
+
|
|
146
|
+
' ---------------------------------------------------------------------
|
|
147
|
+
' registerObserver - Creates and registers a single observer instance
|
|
148
|
+
'
|
|
149
|
+
' Creates an Observer object, stores it in the stack, and sets up the native
|
|
150
|
+
' SceneGraph observeFieldScoped call.
|
|
151
|
+
'
|
|
152
|
+
' @param {object} observerConfig - Configuration for the specific observer
|
|
153
|
+
' @param {object} node - roSGNode being observed
|
|
154
|
+
' @param {string} attachmentId - Unique ID linking observers to this node
|
|
155
|
+
' @param {string} helperInterfaceId - Helper interface field ID
|
|
156
|
+
' @param {object} listenerScope - Widget scope for callback execution
|
|
157
|
+
'
|
|
158
|
+
sub registerObserver(observerConfig as object, node as object, attachmentId as string, helperInterfaceId as string, listenerScope as object)
|
|
159
|
+
' Create observer instance
|
|
160
|
+
newObserver = new Rotor.ObserverPluginHelper.Observer(observerConfig, node, attachmentId, listenerScope, m.key)
|
|
161
|
+
m.observerStack.set(newObserver.id, newObserver)
|
|
162
|
+
|
|
163
|
+
' Set up native SceneGraph observation
|
|
164
|
+
fieldId = observerConfig.fieldId
|
|
165
|
+
infoFields = newObserver.getInfoFields()
|
|
166
|
+
node.observeFieldScoped(fieldId, "Rotor_ObserverPluginHelper_observerNativeCallback", infoFields)
|
|
167
|
+
end sub
|
|
168
|
+
|
|
169
|
+
' ---------------------------------------------------------------------
|
|
170
|
+
' detach - Removes all observers associated with a node
|
|
171
|
+
'
|
|
172
|
+
' Unobserves all fields and removes observer instances from the stack.
|
|
173
|
+
'
|
|
174
|
+
' @param {dynamic} node - roSGNode to detach observers from
|
|
175
|
+
'
|
|
176
|
+
sub detach(node as dynamic)
|
|
177
|
+
' Get plugin helper metadata from node
|
|
178
|
+
pluginHelperValue = node.getField(m.helperInterfaceId)
|
|
179
|
+
if pluginHelperValue = invalid then return
|
|
180
|
+
|
|
181
|
+
attachmentId = pluginHelperValue.attachmentId
|
|
182
|
+
if attachmentId = invalid then return
|
|
183
|
+
|
|
184
|
+
' Find and remove all observers for this attachment
|
|
185
|
+
observers = m.observerStack.findObserverByAttachmentId(attachmentId)
|
|
186
|
+
for each observer in observers
|
|
187
|
+
if observer.node <> invalid
|
|
188
|
+
observer.node.unobserveFieldScoped(observer.fieldId)
|
|
189
|
+
end if
|
|
190
|
+
m.observerStack.remove(observer.id) ' Calls observer.destroy()
|
|
191
|
+
end for
|
|
192
|
+
end sub
|
|
193
|
+
|
|
194
|
+
' =============================================================
|
|
195
|
+
' CALLBACK ROUTING
|
|
196
|
+
' =============================================================
|
|
197
|
+
|
|
198
|
+
' ---------------------------------------------------------------------
|
|
199
|
+
' observerCallbackRouter - Routes native callback to appropriate observers
|
|
200
|
+
'
|
|
201
|
+
' Called by the global observerNativeCallback when a field changes.
|
|
202
|
+
' Finds matching observers and triggers their callbacks.
|
|
203
|
+
'
|
|
204
|
+
' Handles:
|
|
205
|
+
' - Payload parsing via custom parsePayload functions
|
|
206
|
+
' - 'once' observers (removed after first trigger)
|
|
207
|
+
' - 'until' condition observers (removed when condition met)
|
|
208
|
+
'
|
|
209
|
+
' @param {dynamic} value - New value of the observed field
|
|
210
|
+
' @param {object} extraInfo - Additional info from observeFieldScoped
|
|
211
|
+
' @param {string} fieldId - ID of the field that changed
|
|
212
|
+
' @param {string} attachmentId - Node attachment ID
|
|
213
|
+
' @param {string} pluginKey - Plugin instance key
|
|
214
|
+
'
|
|
215
|
+
sub observerCallbackRouter(value as dynamic, extraInfo as object, fieldId as string, attachmentId as string, pluginKey as string)
|
|
216
|
+
' Find all observers interested in this field change
|
|
217
|
+
interestedObservers = m.observerStack.findObserversByAttachmentAndField(attachmentId, fieldId)
|
|
218
|
+
|
|
219
|
+
for each observer in interestedObservers
|
|
220
|
+
' Build and parse payload
|
|
221
|
+
payload = Rotor.Utils.wrapObject(fieldId, value)
|
|
222
|
+
payload.append(extraInfo)
|
|
223
|
+
parsedPayload = observer.parsePayload(payload)
|
|
224
|
+
|
|
225
|
+
' Execute observer callback
|
|
226
|
+
observer.notify(parsedPayload)
|
|
227
|
+
|
|
228
|
+
' Handle observer removal conditions
|
|
229
|
+
if observer.once = true or (Rotor.Utils.isFunction(observer.until) and true = observer.until(parsedPayload))
|
|
230
|
+
' Unobserve before removing to prevent race conditions
|
|
231
|
+
if observer.node <> invalid
|
|
232
|
+
observer.node.unobserveFieldScoped(observer.fieldId)
|
|
233
|
+
end if
|
|
234
|
+
m.observerStack.remove(observer.id)
|
|
235
|
+
end if
|
|
236
|
+
end for
|
|
237
|
+
end sub
|
|
238
|
+
|
|
239
|
+
' =============================================================
|
|
240
|
+
' CLEANUP
|
|
241
|
+
' =============================================================
|
|
242
|
+
|
|
243
|
+
' ---------------------------------------------------------------------
|
|
244
|
+
' destroy - Cleans up all observers
|
|
245
|
+
'
|
|
246
|
+
' Called when the framework instance is destroyed.
|
|
247
|
+
' Unobserves all fields and removes all observer instances.
|
|
248
|
+
'
|
|
249
|
+
sub destroy()
|
|
250
|
+
if m.observerStack = invalid then return
|
|
251
|
+
|
|
252
|
+
' Collect observer IDs to avoid mutation during iteration
|
|
253
|
+
ids = []
|
|
254
|
+
all = m.observerStack.getAll()
|
|
255
|
+
for each id in all
|
|
256
|
+
observer = all[id]
|
|
257
|
+
if observer <> invalid and observer.node <> invalid
|
|
258
|
+
observer.node.unobserveFieldScoped(observer.fieldId)
|
|
259
|
+
end if
|
|
260
|
+
ids.push(id)
|
|
261
|
+
end for
|
|
262
|
+
|
|
263
|
+
' Remove all observers
|
|
264
|
+
for each id in ids
|
|
265
|
+
m.observerStack.remove(id) ' Calls observer.destroy()
|
|
266
|
+
end for
|
|
267
|
+
|
|
268
|
+
' Clear the stack
|
|
269
|
+
m.observerStack.clear()
|
|
270
|
+
end sub
|
|
271
|
+
|
|
272
|
+
end class
|
|
273
|
+
|
|
274
|
+
' =================================================================
|
|
275
|
+
' OBSERVER PLUGIN HELPER NAMESPACE
|
|
276
|
+
' =================================================================
|
|
277
|
+
|
|
278
|
+
namespace ObserverPluginHelper
|
|
279
|
+
|
|
280
|
+
' Prefix for helper field on nodes
|
|
281
|
+
const OBSERVER_HELPER_INTERFACE = "rotorObserverPluginKeysHelper"
|
|
282
|
+
|
|
283
|
+
' ---------------------------------------------------------------------
|
|
284
|
+
' observerNativeCallback - Global callback for SceneGraph field changes
|
|
285
|
+
'
|
|
286
|
+
' This function is registered with node.observeFieldScoped() and is called
|
|
287
|
+
' whenever an observed field changes. It extracts metadata, identifies
|
|
288
|
+
' the appropriate plugin instance, and routes to observerCallbackRouter.
|
|
289
|
+
'
|
|
290
|
+
' @param {object} msg - roSGNodeEvent from the observed node
|
|
291
|
+
'
|
|
292
|
+
sub observerNativeCallback(msg as object)
|
|
293
|
+
' Extract event data
|
|
294
|
+
extraInfo = msg.GetInfo()
|
|
295
|
+
fieldId = msg.getField()
|
|
296
|
+
value = msg.getData()
|
|
297
|
+
|
|
298
|
+
' Extract plugin metadata from helper field
|
|
299
|
+
pluginKey = ""
|
|
300
|
+
attachmentId = ""
|
|
301
|
+
for each key in extraInfo
|
|
302
|
+
if Left(key, Len(OBSERVER_HELPER_INTERFACE)) = OBSERVER_HELPER_INTERFACE
|
|
303
|
+
helperValue = extraInfo[key]
|
|
304
|
+
if helperValue <> invalid
|
|
305
|
+
pluginKey = helperValue.pluginKey
|
|
306
|
+
attachmentId = helperValue.attachmentId
|
|
307
|
+
end if
|
|
308
|
+
extraInfo.delete(key) ' Remove helper from payload
|
|
309
|
+
exit for
|
|
310
|
+
end if
|
|
311
|
+
end for
|
|
312
|
+
|
|
313
|
+
' Route to appropriate plugin instance
|
|
314
|
+
if attachmentId <> "" and pluginKey <> ""
|
|
315
|
+
globalScope = GetGlobalAA()
|
|
316
|
+
frameworkInstance = globalScope.rotor_framework_helper.frameworkInstance
|
|
317
|
+
plugin = invalid
|
|
318
|
+
|
|
319
|
+
' Handle special case for Rotor Animator observers
|
|
320
|
+
if extraInfo?.isRotorAnimatorNode = true
|
|
321
|
+
plugin = frameworkInstance.animatorProvider.animatorObservber
|
|
322
|
+
else
|
|
323
|
+
plugin = frameworkInstance.plugins[pluginKey]
|
|
324
|
+
end if
|
|
325
|
+
|
|
326
|
+
' Execute callback router
|
|
327
|
+
if plugin <> invalid
|
|
328
|
+
plugin.observerCallbackRouter(value, extraInfo, fieldId, attachmentId, pluginKey)
|
|
329
|
+
end if
|
|
330
|
+
end if
|
|
331
|
+
end sub
|
|
332
|
+
|
|
333
|
+
' =====================================================================
|
|
334
|
+
' ObserverStack - Collection manager for Observer instances
|
|
335
|
+
'
|
|
336
|
+
' Manages a collection of Observer instances with specialized lookup methods.
|
|
337
|
+
'
|
|
338
|
+
' Provides:
|
|
339
|
+
' - Storage and retrieval of observers by ID
|
|
340
|
+
' - Lookup by attachment ID and field ID
|
|
341
|
+
' - Automatic cleanup on removal
|
|
342
|
+
' =====================================================================
|
|
343
|
+
class ObserverStack extends Rotor.BaseStack
|
|
344
|
+
|
|
345
|
+
' ---------------------------------------------------------------------
|
|
346
|
+
' remove - Removes observer and triggers cleanup
|
|
347
|
+
'
|
|
348
|
+
' Overrides base class to call observer.destroy() before removal.
|
|
349
|
+
'
|
|
350
|
+
' @param {string} id - Observer ID to remove
|
|
351
|
+
'
|
|
352
|
+
override sub remove(id as string)
|
|
353
|
+
item = m.get(id)
|
|
354
|
+
if item <> invalid
|
|
355
|
+
item.destroy()
|
|
356
|
+
end if
|
|
357
|
+
super.remove(id)
|
|
358
|
+
end sub
|
|
359
|
+
|
|
360
|
+
' ---------------------------------------------------------------------
|
|
361
|
+
' findObserversByAttachmentAndField - Finds observers by attachment and field
|
|
362
|
+
'
|
|
363
|
+
' Returns all observers matching both the attachment ID and field ID.
|
|
364
|
+
'
|
|
365
|
+
' @param {string} attachmentId - Node attachment ID
|
|
366
|
+
' @param {string} fieldId - Field ID
|
|
367
|
+
' @returns {object} Array of matching Observer instances
|
|
368
|
+
'
|
|
369
|
+
function findObserversByAttachmentAndField(attachmentId as string, fieldId as string) as object
|
|
370
|
+
observers = []
|
|
371
|
+
for each id in m.stack
|
|
372
|
+
observer = m.stack[id]
|
|
373
|
+
if observer.fieldId = fieldId and observer.attachmentId = attachmentId
|
|
374
|
+
observers.push(observer)
|
|
375
|
+
end if
|
|
376
|
+
end for
|
|
377
|
+
return observers
|
|
378
|
+
end function
|
|
379
|
+
|
|
380
|
+
' ---------------------------------------------------------------------
|
|
381
|
+
' findObserverByAttachmentId - Finds all observers for an attachment
|
|
382
|
+
'
|
|
383
|
+
' Returns all observers associated with a specific node attachment.
|
|
384
|
+
'
|
|
385
|
+
' @param {string} attachmentId - Node attachment ID
|
|
386
|
+
' @returns {object} Array of matching Observer instances
|
|
387
|
+
'
|
|
388
|
+
function findObserverByAttachmentId(attachmentId as string) as object
|
|
389
|
+
observers = []
|
|
390
|
+
for each id in m.stack
|
|
391
|
+
observer = m.stack[id]
|
|
392
|
+
if observer.attachmentId = attachmentId
|
|
393
|
+
observers.push(observer)
|
|
394
|
+
end if
|
|
395
|
+
end for
|
|
396
|
+
return observers
|
|
397
|
+
end function
|
|
398
|
+
|
|
399
|
+
end class
|
|
400
|
+
|
|
401
|
+
' =====================================================================
|
|
402
|
+
' Observer - Single observer configuration for a node field
|
|
403
|
+
'
|
|
404
|
+
' Represents a single observer configuration for a node field.
|
|
405
|
+
'
|
|
406
|
+
' Responsibilities:
|
|
407
|
+
' - Stores observer configuration (callback, conditions, etc.)
|
|
408
|
+
' - Sets up initial field value if provided
|
|
409
|
+
' - Provides info fields for observeFieldScoped
|
|
410
|
+
' - Executes callbacks in correct scope
|
|
411
|
+
' - Manages cleanup of references
|
|
412
|
+
' =====================================================================
|
|
413
|
+
class Observer
|
|
414
|
+
|
|
415
|
+
' =============================================================
|
|
416
|
+
' MEMBER VARIABLES
|
|
417
|
+
' =============================================================
|
|
418
|
+
|
|
419
|
+
id as string ' Unique observer ID
|
|
420
|
+
node as object ' roSGNode being observed
|
|
421
|
+
pluginKey as string ' Key of managing ObserverPlugin
|
|
422
|
+
listenerScope as object ' Widget scope for callback execution
|
|
423
|
+
attachmentId as string ' Node attachment ID
|
|
424
|
+
fieldId as string ' Field name being observed
|
|
425
|
+
infoFields as object ' Additional fields to include in callback info
|
|
426
|
+
value as dynamic ' Initial field value (if any)
|
|
427
|
+
once as boolean ' Remove observer after first trigger
|
|
428
|
+
until as function ' Conditional removal function
|
|
429
|
+
callback as function ' Callback function to execute
|
|
430
|
+
parsePayload as function ' Payload transformation function
|
|
431
|
+
alwaysNotify as boolean ' Field alwaysNotify flag
|
|
432
|
+
|
|
433
|
+
' =============================================================
|
|
434
|
+
' CONSTRUCTOR
|
|
435
|
+
' =============================================================
|
|
436
|
+
|
|
437
|
+
' ---------------------------------------------------------------------
|
|
438
|
+
' new - Creates an Observer instance
|
|
439
|
+
'
|
|
440
|
+
' @param {object} config - Observer configuration
|
|
441
|
+
' @param {object} node - roSGNode to observe
|
|
442
|
+
' @param {string} attachmentId - Node attachment ID
|
|
443
|
+
' @param {object} listenerScope - Widget scope for callbacks
|
|
444
|
+
' @param {string} pluginKey - Managing plugin key
|
|
445
|
+
'
|
|
446
|
+
sub new(config as object, node as object, attachmentId as string, listenerScope as object, pluginKey as string)
|
|
447
|
+
' Generate unique ID
|
|
448
|
+
m.id = (config.id ?? "ID") + "-" + Rotor.Utils.getUUIDHex()
|
|
449
|
+
|
|
450
|
+
' Store references
|
|
451
|
+
m.node = node
|
|
452
|
+
m.pluginKey = pluginKey
|
|
453
|
+
m.listenerScope = listenerScope ?? {}
|
|
454
|
+
m.attachmentId = attachmentId
|
|
455
|
+
|
|
456
|
+
' Extract configuration
|
|
457
|
+
m.fieldId = config?.fieldId ?? ""
|
|
458
|
+
m.infoFields = config?.infoFields ?? []
|
|
459
|
+
m.value = config?.value
|
|
460
|
+
m.alwaysNotify = config?.alwaysNotify ?? true
|
|
461
|
+
m.once = config?.once ?? false
|
|
462
|
+
m.until = config?.until
|
|
463
|
+
|
|
464
|
+
' Set callback (required)
|
|
465
|
+
m.callback = config?.callback ?? sub() throw "Callback has not configured for observer"
|
|
466
|
+
end Sub
|
|
467
|
+
|
|
468
|
+
' Set payload parser (optional)
|
|
469
|
+
m.parsePayload = config?.parsePayload ?? function(payload)
|
|
470
|
+
return payload
|
|
471
|
+
end function
|
|
472
|
+
|
|
473
|
+
' Set up initial field value if provided
|
|
474
|
+
m.setupField(m.fieldId, m.value, m.alwaysNotify)
|
|
475
|
+
end sub
|
|
476
|
+
|
|
477
|
+
' =============================================================
|
|
478
|
+
' FIELD SETUP
|
|
479
|
+
' =============================================================
|
|
480
|
+
|
|
481
|
+
' ---------------------------------------------------------------------
|
|
482
|
+
' setupField - Sets initial value on observed field
|
|
483
|
+
'
|
|
484
|
+
' Creates or updates the field on the node if an initial value is provided.
|
|
485
|
+
'
|
|
486
|
+
' @param {string} fieldId - Field name
|
|
487
|
+
' @param {dynamic} value - Initial value (if not invalid)
|
|
488
|
+
' @param {boolean} alwaysNotify - alwaysNotify flag value
|
|
489
|
+
'
|
|
490
|
+
sub setupField(fieldId as string, value as dynamic, alwaysNotify as boolean)
|
|
491
|
+
fields = {}
|
|
492
|
+
fields[m.fieldId] = value
|
|
493
|
+
Rotor.Utils.setCustomFields(m.node, fields, m.value <> invalid, alwaysNotify)
|
|
494
|
+
end sub
|
|
495
|
+
|
|
496
|
+
' =============================================================
|
|
497
|
+
' INFO FIELDS
|
|
498
|
+
' =============================================================
|
|
499
|
+
|
|
500
|
+
' ---------------------------------------------------------------------
|
|
501
|
+
' getInfoFields - Builds field list for observeFieldScoped
|
|
502
|
+
'
|
|
503
|
+
' Combines user-defined infoFields with the helper interface ID.
|
|
504
|
+
'
|
|
505
|
+
' @returns {object} Array of field names for observeFieldScoped
|
|
506
|
+
'
|
|
507
|
+
function getInfoFields() as object
|
|
508
|
+
helperInterfaceId = Rotor.ObserverPluginHelper.OBSERVER_HELPER_INTERFACE + "-" + m.pluginKey
|
|
509
|
+
infoFields = []
|
|
510
|
+
infoFields.append(m.infoFields)
|
|
511
|
+
infoFields.push(helperInterfaceId)
|
|
512
|
+
return infoFields
|
|
513
|
+
end function
|
|
514
|
+
|
|
515
|
+
' =============================================================
|
|
516
|
+
' CALLBACK EXECUTION
|
|
517
|
+
' =============================================================
|
|
518
|
+
|
|
519
|
+
' ---------------------------------------------------------------------
|
|
520
|
+
' notify - Executes observer callback
|
|
521
|
+
'
|
|
522
|
+
' Calls the configured callback function in the correct scope with the payload.
|
|
523
|
+
'
|
|
524
|
+
' @param {dynamic} payload - Data to pass to callback
|
|
525
|
+
'
|
|
526
|
+
sub notify(payload as dynamic)
|
|
527
|
+
Rotor.Utils.callbackScoped(m.callback, m.listenerScope, payload)
|
|
528
|
+
end sub
|
|
529
|
+
|
|
530
|
+
' =============================================================
|
|
531
|
+
' CLEANUP
|
|
532
|
+
' =============================================================
|
|
533
|
+
|
|
534
|
+
' ---------------------------------------------------------------------
|
|
535
|
+
' destroy - Cleans up observer references
|
|
536
|
+
'
|
|
537
|
+
' Clears references to prevent memory leaks.
|
|
538
|
+
'
|
|
539
|
+
sub destroy()
|
|
540
|
+
m.node = invalid
|
|
541
|
+
m.listenerScope = invalid
|
|
542
|
+
end sub
|
|
543
|
+
|
|
544
|
+
end class
|
|
545
|
+
|
|
546
|
+
end namespace ' ObserverPluginHelper
|
|
547
|
+
|
|
548
|
+
end namespace ' Rotor
|