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,278 @@
|
|
|
1
|
+
import "TreeBase.bs"
|
|
2
|
+
|
|
3
|
+
namespace Rotor.ViewBuilder
|
|
4
|
+
|
|
5
|
+
' =====================================================================
|
|
6
|
+
' TreeRoot - Root node of the widget tree
|
|
7
|
+
'
|
|
8
|
+
' Provides minimal API for rendering and erasing at the root level.
|
|
9
|
+
' =====================================================================
|
|
10
|
+
class TreeRoot
|
|
11
|
+
|
|
12
|
+
node as object
|
|
13
|
+
|
|
14
|
+
children = {}
|
|
15
|
+
|
|
16
|
+
viewModelState = {}
|
|
17
|
+
|
|
18
|
+
id = ""
|
|
19
|
+
HID = "0"
|
|
20
|
+
vmHID = "0"
|
|
21
|
+
parentHID = invalid
|
|
22
|
+
isViewModel = true
|
|
23
|
+
childrenHIDhash = {}
|
|
24
|
+
|
|
25
|
+
function getFrameworkInstance() as object
|
|
26
|
+
return GetGlobalAA().rotor_framework_helper.frameworkInstance
|
|
27
|
+
end function
|
|
28
|
+
|
|
29
|
+
sub render(payloads as dynamic, params = {} as object)
|
|
30
|
+
for each payload in Rotor.Utils.ensureArray(payloads)
|
|
31
|
+
if payload.DoesExist("id") = false and payload.DoesExist("HID") = false
|
|
32
|
+
payload.id = m.id
|
|
33
|
+
payload.HID = m.HID
|
|
34
|
+
end if
|
|
35
|
+
end for
|
|
36
|
+
if Rotor.Utils.isValid(params.callback) then params.callbackScope = m
|
|
37
|
+
m.getFrameworkInstance().builder.render(payloads, params)
|
|
38
|
+
end sub
|
|
39
|
+
|
|
40
|
+
sub erase(payloads as dynamic, shouldSkipNodePool)
|
|
41
|
+
m.getFrameworkInstance().builder.erase(payloads, m.parentHID, shouldSkipNodePool)
|
|
42
|
+
end sub
|
|
43
|
+
|
|
44
|
+
sub destroy()
|
|
45
|
+
m.node = invalid
|
|
46
|
+
end sub
|
|
47
|
+
|
|
48
|
+
end class
|
|
49
|
+
|
|
50
|
+
' =====================================================================
|
|
51
|
+
' WidgetTree - Main widget tree implementation extending WidgetTreeBase
|
|
52
|
+
'
|
|
53
|
+
' Manages widget hierarchy, parent-child relationships,
|
|
54
|
+
' and ViewModel scope propagation through the tree.
|
|
55
|
+
' =====================================================================
|
|
56
|
+
class WidgetTree extends Rotor.ViewBuilder.WidgetTreeBase
|
|
57
|
+
|
|
58
|
+
' sub new()
|
|
59
|
+
' end sub
|
|
60
|
+
|
|
61
|
+
' ---------------------------------------------------------------------
|
|
62
|
+
' init - Initializes the widget tree
|
|
63
|
+
'
|
|
64
|
+
sub init()
|
|
65
|
+
end sub
|
|
66
|
+
|
|
67
|
+
' ---------------------------------------------------------------------
|
|
68
|
+
' destroy - Destroys the widget tree and cleans up resources
|
|
69
|
+
'
|
|
70
|
+
sub destroy()
|
|
71
|
+
m.tree.destroy()
|
|
72
|
+
m.tree_HIDHash.stack.Clear()
|
|
73
|
+
end sub
|
|
74
|
+
|
|
75
|
+
' ---------------------------------------------------------------------
|
|
76
|
+
' add - Adds a widget to the tree and returns the widget instance
|
|
77
|
+
'
|
|
78
|
+
' @param {object} config - Widget configuration with id and parentHID
|
|
79
|
+
' @param {object} ViewModelClass - Optional ViewModel class constructor (default: invalid)
|
|
80
|
+
' @returns {object} Created widget instance with generated HID
|
|
81
|
+
'
|
|
82
|
+
function add(config as object, ViewModelClass = invalid) as object
|
|
83
|
+
|
|
84
|
+
if Rotor.Utils.isFunction(ViewModelClass as function) ' Custom View extends Widget
|
|
85
|
+
' bs:disable-next-line
|
|
86
|
+
widget = new ViewModelClass()
|
|
87
|
+
else
|
|
88
|
+
widget = new Rotor.Widget() ' Standard Widget
|
|
89
|
+
end if
|
|
90
|
+
|
|
91
|
+
id = config.id
|
|
92
|
+
|
|
93
|
+
' Parse config properties
|
|
94
|
+
widget.id = id
|
|
95
|
+
widget.childrenHIDhash = {} ' prepare local HID cache
|
|
96
|
+
widget.isRootChild = config.parentHID = "0" ' detect root
|
|
97
|
+
widget.children = {} ' default
|
|
98
|
+
|
|
99
|
+
' Check if has parent widget
|
|
100
|
+
if widget.isRootChild
|
|
101
|
+
parent = m.tree
|
|
102
|
+
else
|
|
103
|
+
parent = m.get(config.parentHID)
|
|
104
|
+
end if
|
|
105
|
+
widget.parentHID = config.parentHID
|
|
106
|
+
HID = m.generateHID(parent)
|
|
107
|
+
|
|
108
|
+
' register as child by readable id
|
|
109
|
+
parent.children[LCase(id)] = widget
|
|
110
|
+
|
|
111
|
+
' register as child by readable HID (Hierarchical Identifier)
|
|
112
|
+
parent.childrenHIDhash[HID] = true ' local cache for HID
|
|
113
|
+
|
|
114
|
+
widget.HID = HID
|
|
115
|
+
widget.parent = parent ' link parent widget
|
|
116
|
+
|
|
117
|
+
if widget?.isViewModel = true
|
|
118
|
+
widget.vmHID = widget.HID ' start new VM reference in hierarchy
|
|
119
|
+
else
|
|
120
|
+
widget.vmHID = parent.vmHID ' populate down the current VM reference in hierarchy
|
|
121
|
+
VM = m.getByHID(parent.vmHID)
|
|
122
|
+
widget.props = VM.props ' reference to ancestor's viewModel's props (sharing props across descendant widgets)
|
|
123
|
+
widget.viewModelState = VM.viewModelState ' reference to ancestor ViewModel's viewModelState
|
|
124
|
+
end if
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
' Build virtual widget tree
|
|
128
|
+
m.tree_HIDHash.set(HID, widget)
|
|
129
|
+
|
|
130
|
+
return widget
|
|
131
|
+
end function
|
|
132
|
+
|
|
133
|
+
' ---------------------------------------------------------------------
|
|
134
|
+
' getChildrenWidgets - Gets child widgets of a parent in SceneGraph node order
|
|
135
|
+
'
|
|
136
|
+
' @param {object} parentWidget - Parent widget to get children from
|
|
137
|
+
' @param {string} matchingPattern - Optional glob pattern to filter children (default: "")
|
|
138
|
+
' @returns {object} Array of child widgets in node order
|
|
139
|
+
'
|
|
140
|
+
function getChildrenWidgets(parentWidget as object, matchingPattern = "" as string) as object
|
|
141
|
+
|
|
142
|
+
if parentWidget = invalid then return invalid
|
|
143
|
+
|
|
144
|
+
' Get all children in node order
|
|
145
|
+
childrenWidgets = parentWidget.children.items()
|
|
146
|
+
|
|
147
|
+
childrenWidgetsCount = childrenWidgets.Count()
|
|
148
|
+
childrenNodes = parentWidget.node.getChildren(-1, 0)
|
|
149
|
+
|
|
150
|
+
orderedWidgets = []
|
|
151
|
+
for each node in childrenNodes
|
|
152
|
+
isFound = false
|
|
153
|
+
itemIndex = 0
|
|
154
|
+
while isFound = false and itemIndex < childrenWidgetsCount
|
|
155
|
+
widget = childrenWidgets[itemIndex].value
|
|
156
|
+
if widget.node.isSameNode(node)
|
|
157
|
+
' Apply ID filtering if pattern is provided
|
|
158
|
+
if matchingPattern = "" or m.matchesPattern(LCase(widget.id), LCase(matchingPattern))
|
|
159
|
+
orderedWidgets.push(widget)
|
|
160
|
+
end if
|
|
161
|
+
isFound = true
|
|
162
|
+
else
|
|
163
|
+
itemIndex++
|
|
164
|
+
end if
|
|
165
|
+
end while
|
|
166
|
+
end for
|
|
167
|
+
|
|
168
|
+
return orderedWidgets
|
|
169
|
+
end function
|
|
170
|
+
|
|
171
|
+
' ---------------------------------------------------------------------
|
|
172
|
+
' remove - Removes a widget from the tree by HID
|
|
173
|
+
'
|
|
174
|
+
' @param {string} HID - Hierarchical ID of widget to remove
|
|
175
|
+
'
|
|
176
|
+
sub remove (HID as string)
|
|
177
|
+
widget = m.getByHID(HID)
|
|
178
|
+
' if widget = invalid then return
|
|
179
|
+
|
|
180
|
+
' remove from parent
|
|
181
|
+
parent = widget.parent
|
|
182
|
+
parent.children.Delete(widget.id)
|
|
183
|
+
|
|
184
|
+
parent.childrenHIDhash.Delete(widget.HID)
|
|
185
|
+
|
|
186
|
+
m.tree_HIDHash.remove(HID)
|
|
187
|
+
|
|
188
|
+
' remove animators
|
|
189
|
+
if widget.animators <> invalid and widget.animators.Count() > 0
|
|
190
|
+
for each animatorId in widget.animators
|
|
191
|
+
widget.animator(animatorId).destroy()
|
|
192
|
+
end for
|
|
193
|
+
end if
|
|
194
|
+
|
|
195
|
+
if widget.isViewModel = true
|
|
196
|
+
widget.props.Clear()
|
|
197
|
+
widget.viewModelState.Clear()
|
|
198
|
+
end if
|
|
199
|
+
|
|
200
|
+
widget.Clear()
|
|
201
|
+
|
|
202
|
+
end sub
|
|
203
|
+
|
|
204
|
+
' ===== HELPER METHODS =====
|
|
205
|
+
|
|
206
|
+
' ---------------------------------------------------------------------
|
|
207
|
+
' isBranchOfRemove - Checks if widget is a top-level branch marked for removal
|
|
208
|
+
'
|
|
209
|
+
' @param {object} widget - Widget to check
|
|
210
|
+
' @returns {boolean} True if widget is removal branch root
|
|
211
|
+
'
|
|
212
|
+
function isBranchOfRemove(widget as object) as boolean
|
|
213
|
+
if false = widget.DoesExist("markedToRemove") then return false
|
|
214
|
+
if widget.isRootChild = true
|
|
215
|
+
return true
|
|
216
|
+
end if
|
|
217
|
+
|
|
218
|
+
return widget.parent.DoesExist("markedToRemove") ? false : true
|
|
219
|
+
end function
|
|
220
|
+
|
|
221
|
+
' ---------------------------------------------------------------------
|
|
222
|
+
' isBranchOfAppend - Checks if widget is a top-level branch marked for append
|
|
223
|
+
'
|
|
224
|
+
' @param {object} widget - Widget to check
|
|
225
|
+
' @returns {boolean} True if widget is append branch root
|
|
226
|
+
'
|
|
227
|
+
function isBranchOfAppend(widget as object) as boolean
|
|
228
|
+
if false = widget.DoesExist("markedToAppend") then return false
|
|
229
|
+
if widget.isRootChild = true
|
|
230
|
+
return true
|
|
231
|
+
end if
|
|
232
|
+
return widget.parent.DoesExist("markedToAppend") ? false : true
|
|
233
|
+
end function
|
|
234
|
+
|
|
235
|
+
' ---------------------------------------------------------------------
|
|
236
|
+
' getTreeItem - Gets tree item by HID or returns root if HID is empty
|
|
237
|
+
'
|
|
238
|
+
' @param {string} HID - Hierarchical ID to lookup
|
|
239
|
+
' @returns {object} Widget at HID or tree root
|
|
240
|
+
'
|
|
241
|
+
function getTreeItem(HID)
|
|
242
|
+
return HID = "" ? m.tree : m.getByHID(HID)
|
|
243
|
+
end function
|
|
244
|
+
|
|
245
|
+
' Deprecated
|
|
246
|
+
' function checkRegexIncluded(part as string) as object
|
|
247
|
+
' if Left(part, 6) = "regex:"
|
|
248
|
+
' return {
|
|
249
|
+
' isRegex: true,
|
|
250
|
+
' regex: CreateObject("roRegex", Right(part, Len(part) - 6), "i")
|
|
251
|
+
' }
|
|
252
|
+
' else
|
|
253
|
+
' return {
|
|
254
|
+
' isRegex: false
|
|
255
|
+
' }
|
|
256
|
+
' end if
|
|
257
|
+
' end function
|
|
258
|
+
|
|
259
|
+
' ---------------------------------------------------------------------
|
|
260
|
+
' setRootNode - Sets the SceneGraph node for the tree root
|
|
261
|
+
'
|
|
262
|
+
' @param {object} node - SceneGraph node to set as root
|
|
263
|
+
'
|
|
264
|
+
sub setRootNode (node as object)
|
|
265
|
+
m.tree.node = node
|
|
266
|
+
end sub
|
|
267
|
+
|
|
268
|
+
' ---------------------------------------------------------------------
|
|
269
|
+
' getRootNode - Gets the root SceneGraph node
|
|
270
|
+
'
|
|
271
|
+
' @returns {object} Root SceneGraph node
|
|
272
|
+
'
|
|
273
|
+
function getRootNode() as object
|
|
274
|
+
return m.tree.node
|
|
275
|
+
end function
|
|
276
|
+
end class
|
|
277
|
+
|
|
278
|
+
end namespace
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
namespace Rotor.ViewBuilder
|
|
2
|
+
|
|
3
|
+
' =====================================================================
|
|
4
|
+
' WidgetTreeBase - Base class for widget tree management with HID generation and search capabilities
|
|
5
|
+
'
|
|
6
|
+
' Provides core functionality for hierarchical widget organization,
|
|
7
|
+
' lookup operations, and glob pattern matching.
|
|
8
|
+
' =====================================================================
|
|
9
|
+
class WidgetTreeBase
|
|
10
|
+
|
|
11
|
+
tree = new TreeRoot()
|
|
12
|
+
tree_HIDHash = new Rotor.BaseStack()
|
|
13
|
+
|
|
14
|
+
' sub new()
|
|
15
|
+
' super()
|
|
16
|
+
' end sub
|
|
17
|
+
|
|
18
|
+
' ---------------------------------------------------------------------
|
|
19
|
+
' generateHID - Generates a unique Hierarchical ID for a widget under a parent
|
|
20
|
+
'
|
|
21
|
+
' @param {object} parent - Parent widget object
|
|
22
|
+
' @returns {string} Unique HID string
|
|
23
|
+
'
|
|
24
|
+
function generateHID(parent) as string
|
|
25
|
+
tryCounter = 32
|
|
26
|
+
newHID = ""
|
|
27
|
+
' Performance way
|
|
28
|
+
while tryCounter > 0 and newHID = ""
|
|
29
|
+
newHID = parent.HID + Rotor.Utils.getUUIDHex(3)
|
|
30
|
+
if parent.childrenHIDhash.DoesExist(newHID) = true
|
|
31
|
+
newHID = ""
|
|
32
|
+
end if
|
|
33
|
+
tryCounter--
|
|
34
|
+
end while
|
|
35
|
+
' Ok, then try another way (very rare scenario)
|
|
36
|
+
if newHID = ""
|
|
37
|
+
for decValue = 0 to 4095
|
|
38
|
+
hexValue = stri(decValue, 16)
|
|
39
|
+
newHID = parent.HID + hexValue
|
|
40
|
+
if parent.childrenHIDhash.DoesExist(newHID) = true
|
|
41
|
+
newHID = ""
|
|
42
|
+
exit for
|
|
43
|
+
end if
|
|
44
|
+
end for
|
|
45
|
+
end if
|
|
46
|
+
return newHID
|
|
47
|
+
end function
|
|
48
|
+
|
|
49
|
+
' ---------------------------------------------------------------------
|
|
50
|
+
' get - Gets a single widget by search pattern
|
|
51
|
+
'
|
|
52
|
+
' @param {string} searchPattern - HID or glob pattern to search for
|
|
53
|
+
' @param {string} HID - Starting context HID (default: "0")
|
|
54
|
+
' @returns {object} First matching widget or invalid if not found
|
|
55
|
+
'
|
|
56
|
+
function get(searchPattern as string, HID = "0" as string) as object
|
|
57
|
+
untilFirstItem = true
|
|
58
|
+
results = m.find(searchPattern, HID, untilFirstItem)
|
|
59
|
+
if results = invalid
|
|
60
|
+
return invalid
|
|
61
|
+
else
|
|
62
|
+
return results.shift()
|
|
63
|
+
end if
|
|
64
|
+
end function
|
|
65
|
+
|
|
66
|
+
' ---------------------------------------------------------------------
|
|
67
|
+
' getByHID - Gets a widget directly by its HID
|
|
68
|
+
'
|
|
69
|
+
' @param {string} HID - Hierarchical ID to look up
|
|
70
|
+
' @returns {object} Widget at the specified HID or invalid if not found
|
|
71
|
+
'
|
|
72
|
+
function getByHID(HID as string) as object
|
|
73
|
+
return HID = "0" ? m.tree : m.tree_HIDHash.get(HID)
|
|
74
|
+
end function
|
|
75
|
+
|
|
76
|
+
' ---------------------------------------------------------------------
|
|
77
|
+
' hasByHID - Checks if a widget exists at the specified HID
|
|
78
|
+
'
|
|
79
|
+
' @param {string} HID - Hierarchical ID to check
|
|
80
|
+
' @returns {boolean} True if widget exists, false otherwise
|
|
81
|
+
'
|
|
82
|
+
function hasByHID(HID as string) as boolean
|
|
83
|
+
return m.tree_HIDHash.has(HID)
|
|
84
|
+
end function
|
|
85
|
+
|
|
86
|
+
' ---------------------------------------------------------------------
|
|
87
|
+
' find - Searches the widget tree using a path-like glob pattern or direct HID
|
|
88
|
+
'
|
|
89
|
+
' Supports:
|
|
90
|
+
' - Direct lookup by HID
|
|
91
|
+
' - Relative path resolution (./ and .. operators)
|
|
92
|
+
' - Glob patterns: *, ** and normalization (e.g. multiple slashes, stars)
|
|
93
|
+
'
|
|
94
|
+
' @param {string} searchPattern - The lookup expression
|
|
95
|
+
' @param {string} HID - Optional starting node (default: root)
|
|
96
|
+
' @param {boolean} untilFirstItem - If true, stops after first match (default: false)
|
|
97
|
+
' @returns {object} Array of matching widgets, or invalid if none found
|
|
98
|
+
'
|
|
99
|
+
function find(searchPattern as string, HID = "0" as string, untilFirstItem = false as boolean) as object
|
|
100
|
+
' TODO: In the future, this could use a well-designed ID-based cache to improve performance
|
|
101
|
+
|
|
102
|
+
' Direct lookup: if the search pattern is an HID
|
|
103
|
+
if m.hasByHID(searchPattern) = true
|
|
104
|
+
return [m.getByHID(searchPattern)]
|
|
105
|
+
end if
|
|
106
|
+
|
|
107
|
+
' Check if the pattern begins with "./" (relative to root)
|
|
108
|
+
prefix = Left(searchPattern, 2)
|
|
109
|
+
if prefix = "./"
|
|
110
|
+
searchPattern = Right(searchPattern, Len(searchPattern) - 2)
|
|
111
|
+
HID = "0" ' Reset to root if explicitly referencing from top
|
|
112
|
+
end if
|
|
113
|
+
|
|
114
|
+
' Normalize search pattern
|
|
115
|
+
searchPattern = /^\/*/.ReplaceAll(searchPattern, "") ' Remove leading slashes
|
|
116
|
+
searchPattern = /\/{2,}/.ReplaceAll(searchPattern, "/") ' Replace multiple slashes with one
|
|
117
|
+
searchPattern = /\*{3,}/.ReplaceAll(searchPattern, "**") ' Replace 3+ stars with **
|
|
118
|
+
searchPattern = /\*\*$/.ReplaceAll(searchPattern, "*") ' Convert trailing ** to *
|
|
119
|
+
searchPattern = /(\*\*\/){2,}/.ReplaceAll(searchPattern, "**/")' Collapse multiple **/** into one
|
|
120
|
+
|
|
121
|
+
searchPattern = LCase(searchPattern)
|
|
122
|
+
parts = /\//.Split(searchPattern)
|
|
123
|
+
|
|
124
|
+
startNode = HID = "0" ? m.tree : m.getByHID(HID)
|
|
125
|
+
|
|
126
|
+
' Resolve ".." (parent) operators
|
|
127
|
+
while parts[0] = ".." and startNode.HID <> "0"
|
|
128
|
+
parts.shift()
|
|
129
|
+
startNode = m.getByHID(startNode.parentHID)
|
|
130
|
+
end while
|
|
131
|
+
|
|
132
|
+
results = []
|
|
133
|
+
|
|
134
|
+
' If parts is empty after resolving "..", return the startNode itself
|
|
135
|
+
if parts.Count() = 0
|
|
136
|
+
return [startNode]
|
|
137
|
+
end if
|
|
138
|
+
|
|
139
|
+
' Add leading "**" if pattern doesn't start with ** or *
|
|
140
|
+
' This enables deep search when user provides just an ID like "menuItem2"
|
|
141
|
+
if parts[0] <> "**" and parts[0] <> "*"
|
|
142
|
+
parts.unshift("**")
|
|
143
|
+
end if
|
|
144
|
+
|
|
145
|
+
lastPart = parts[parts.Count() - 1]
|
|
146
|
+
if startNode.HID <> "0" and LCase(startNode.id) = lastPart
|
|
147
|
+
results.unshift(startNode)
|
|
148
|
+
end if
|
|
149
|
+
|
|
150
|
+
' Begin recursive search
|
|
151
|
+
m.recursionFind(results, parts, untilFirstItem, startNode.children)
|
|
152
|
+
|
|
153
|
+
if results.Count() = 0 then return invalid
|
|
154
|
+
|
|
155
|
+
return results
|
|
156
|
+
end function
|
|
157
|
+
|
|
158
|
+
' ---------------------------------------------------------------------
|
|
159
|
+
' matchesPattern - Check if a string matches a wildcard pattern
|
|
160
|
+
'
|
|
161
|
+
' Supports:
|
|
162
|
+
' - "*" matches everything
|
|
163
|
+
' - "prefix*" matches strings starting with prefix
|
|
164
|
+
' - "*suffix" matches strings ending with suffix
|
|
165
|
+
' - "prefix*suffix" matches strings starting with prefix and ending with suffix
|
|
166
|
+
'
|
|
167
|
+
' @param {string} str - The string to check
|
|
168
|
+
' @param {string} pattern - The pattern with optional wildcards
|
|
169
|
+
' @returns {boolean} True if matches, false otherwise
|
|
170
|
+
'
|
|
171
|
+
function matchesPattern(str as string, pattern as string) as boolean
|
|
172
|
+
if pattern = "*" then return true
|
|
173
|
+
if pattern.Instr("*") < 0 then return str = pattern ' No wildcard, exact match
|
|
174
|
+
|
|
175
|
+
' Split pattern by *
|
|
176
|
+
parts = pattern.Split("*")
|
|
177
|
+
partsCount = parts.Count()
|
|
178
|
+
|
|
179
|
+
' Check prefix (before first *)
|
|
180
|
+
if parts[0] <> "" and str.Left(parts[0].Len()) <> parts[0]
|
|
181
|
+
return false
|
|
182
|
+
end if
|
|
183
|
+
|
|
184
|
+
' Check suffix (after last *)
|
|
185
|
+
if parts[partsCount - 1] <> "" and str.Right(parts[partsCount - 1].Len()) <> parts[partsCount - 1]
|
|
186
|
+
return false
|
|
187
|
+
end if
|
|
188
|
+
|
|
189
|
+
' For patterns like "prefix*suffix", check both prefix and suffix
|
|
190
|
+
if partsCount = 2 and parts[0] <> "" and parts[1] <> ""
|
|
191
|
+
' Check if string is long enough
|
|
192
|
+
if str.Len() < parts[0].Len() + parts[1].Len()
|
|
193
|
+
return false
|
|
194
|
+
end if
|
|
195
|
+
end if
|
|
196
|
+
|
|
197
|
+
return true
|
|
198
|
+
end function
|
|
199
|
+
|
|
200
|
+
' ---------------------------------------------------------------------
|
|
201
|
+
' recursionFind - Recursively finds widgets in a tree structure based on glob-like patterns
|
|
202
|
+
'
|
|
203
|
+
' Supports:
|
|
204
|
+
' - "*" to match any key at one level
|
|
205
|
+
' - "**" to match any number of nested levels
|
|
206
|
+
'
|
|
207
|
+
' @param {object} results - Array to collect matching widgets
|
|
208
|
+
' @param {object} parts - Pattern parts split by "/"
|
|
209
|
+
' @param {boolean} untilFirstItem - If true, stops after the first match
|
|
210
|
+
' @param {object} children - The widget tree branch to search (default: {})
|
|
211
|
+
' @param {integer} index - Current position in the pattern (default: 0)
|
|
212
|
+
' @returns {object} Array of matching widgets
|
|
213
|
+
'
|
|
214
|
+
function recursionFind(results as object, parts as object, untilFirstItem as boolean, children = {} as object, index = 0 as integer) as object
|
|
215
|
+
currentPart = parts[index]
|
|
216
|
+
partsCount = parts.Count()
|
|
217
|
+
isLastPart = (partsCount - 1 = index)
|
|
218
|
+
nextPart = isLastPart ? "" : parts[index + 1]
|
|
219
|
+
|
|
220
|
+
if children.Count() > 0
|
|
221
|
+
for each key in children
|
|
222
|
+
widget = children[key]
|
|
223
|
+
' Note: key is already lowercase because widgets are stored with LCase(id) as key in tree.bs:85
|
|
224
|
+
|
|
225
|
+
if isLastPart = true
|
|
226
|
+
' Check if current part matches the key (supports wildcards like "menuItem*")
|
|
227
|
+
if m.matchesPattern(key, currentPart)
|
|
228
|
+
results.push(widget)
|
|
229
|
+
if untilFirstItem = true then return results
|
|
230
|
+
end if
|
|
231
|
+
else
|
|
232
|
+
nextChildren = {}
|
|
233
|
+
matchedParent = false
|
|
234
|
+
|
|
235
|
+
if currentPart = "**"
|
|
236
|
+
' Deep search: check if next part matches current key
|
|
237
|
+
if m.matchesPattern(key, nextPart)
|
|
238
|
+
matchedParent = true
|
|
239
|
+
nextChildren = children ' Stay at same level, exit ** mode
|
|
240
|
+
else
|
|
241
|
+
nextChildren = widget.children
|
|
242
|
+
end if
|
|
243
|
+
else if m.matchesPattern(key, currentPart)
|
|
244
|
+
' Matched current level, move to next level
|
|
245
|
+
matchedParent = true
|
|
246
|
+
nextChildren = widget.children
|
|
247
|
+
end if
|
|
248
|
+
|
|
249
|
+
newIndex = matchedParent = true ? index + 1 : index
|
|
250
|
+
|
|
251
|
+
if nextChildren.Count() > 0 and newIndex < partsCount
|
|
252
|
+
m.recursionFind(results, parts, untilFirstItem, nextChildren, newIndex)
|
|
253
|
+
end if
|
|
254
|
+
end if
|
|
255
|
+
end for
|
|
256
|
+
end if
|
|
257
|
+
|
|
258
|
+
return results
|
|
259
|
+
end function
|
|
260
|
+
|
|
261
|
+
' ---------------------------------------------------------------------
|
|
262
|
+
' getSubtreeClone - Creates a cloned subtree from a widget by search pattern
|
|
263
|
+
'
|
|
264
|
+
' @param {string} searchPattern - Pattern to find the root widget
|
|
265
|
+
' @param {object} keyPathList - List of key paths to include in clone (default: [])
|
|
266
|
+
' @param {string} parentHID - Parent HID context (default: "0")
|
|
267
|
+
' @returns {object} Cloned subtree structure or invalid if not found
|
|
268
|
+
'
|
|
269
|
+
function getSubtreeClone(searchPattern as string, keyPathList = [] as object, parentHID = "0" as string) as object
|
|
270
|
+
subTree = m.get(searchPattern, parentHID)
|
|
271
|
+
if subTree = invalid then return invalid
|
|
272
|
+
|
|
273
|
+
' keyPathList.push("id")
|
|
274
|
+
keyPathList.push("HID")
|
|
275
|
+
' keyPathList.push("parentHID")
|
|
276
|
+
|
|
277
|
+
subTreeClone = {}
|
|
278
|
+
m.recursion_getSubtreeClone(subTree, subTreeClone, keyPathList)
|
|
279
|
+
|
|
280
|
+
return subTreeClone
|
|
281
|
+
end function
|
|
282
|
+
|
|
283
|
+
' ---------------------------------------------------------------------
|
|
284
|
+
' recursion_getSubtreeClone - Recursively clones a widget subtree by specified key paths
|
|
285
|
+
'
|
|
286
|
+
' Note: Tree uses AA for children but clone uses Array for updates.
|
|
287
|
+
'
|
|
288
|
+
' @param {object} subTree - Source subtree to clone
|
|
289
|
+
' @param {object} subTreeClone - Target clone object to populate
|
|
290
|
+
' @param {object} keyPathList - List of key paths to include in clone
|
|
291
|
+
'
|
|
292
|
+
sub recursion_getSubtreeClone(subTree as object, subTreeClone as object, keyPathList as object)
|
|
293
|
+
' Note that
|
|
294
|
+
for each keyPath in keyPathList
|
|
295
|
+
clonedValue = Rotor.Utils.getCloneByKeyPath(subTree, keyPath)
|
|
296
|
+
if clonedValue <> invalid
|
|
297
|
+
Rotor.Utils.deepExtendAA(subTreeClone, clonedValue)
|
|
298
|
+
end if
|
|
299
|
+
end for
|
|
300
|
+
if subTree.children <> invalid and subTree.children.Count() > 0
|
|
301
|
+
subTreeClone.children = []
|
|
302
|
+
childIndex = 0
|
|
303
|
+
for each id in subTree.children
|
|
304
|
+
subTreeClone.children.push({ id: id })
|
|
305
|
+
m.recursion_getSubtreeClone(subTree.children[id], subTreeClone.children[childIndex], keyPathList)
|
|
306
|
+
childIndex++
|
|
307
|
+
end for
|
|
308
|
+
end if
|
|
309
|
+
end sub
|
|
310
|
+
|
|
311
|
+
end class
|
|
312
|
+
|
|
313
|
+
end namespace
|