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.
Files changed (39) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +120 -0
  3. package/package.json +59 -0
  4. package/src/source/RotorFramework.bs +654 -0
  5. package/src/source/RotorFrameworkTask.bs +278 -0
  6. package/src/source/base/BaseModel.bs +52 -0
  7. package/src/source/base/BasePlugin.bs +48 -0
  8. package/src/source/base/BaseReducer.bs +184 -0
  9. package/src/source/base/BaseStack.bs +92 -0
  10. package/src/source/base/BaseViewModel.bs +124 -0
  11. package/src/source/base/BaseWidget.bs +104 -0
  12. package/src/source/base/DispatcherCreator.bs +193 -0
  13. package/src/source/base/DispatcherExternal.bs +260 -0
  14. package/src/source/base/ListenerForDispatchers.bs +246 -0
  15. package/src/source/engine/Constants.bs +74 -0
  16. package/src/source/engine/animator/Animator.bs +334 -0
  17. package/src/source/engine/builder/Builder.bs +213 -0
  18. package/src/source/engine/builder/NodePool.bs +236 -0
  19. package/src/source/engine/builder/PluginAdapter.bs +139 -0
  20. package/src/source/engine/builder/PostProcessor.bs +331 -0
  21. package/src/source/engine/builder/Processor.bs +156 -0
  22. package/src/source/engine/builder/Tree.bs +278 -0
  23. package/src/source/engine/builder/TreeBase.bs +313 -0
  24. package/src/source/engine/builder/WidgetCreate.bs +322 -0
  25. package/src/source/engine/builder/WidgetRemove.bs +72 -0
  26. package/src/source/engine/builder/WidgetUpdate.bs +113 -0
  27. package/src/source/engine/providers/Dispatcher.bs +72 -0
  28. package/src/source/engine/providers/DispatcherProvider.bs +95 -0
  29. package/src/source/engine/services/I18n.bs +169 -0
  30. package/src/source/libs/animate/Animate.bs +753 -0
  31. package/src/source/libs/animate/LICENSE.txt +21 -0
  32. package/src/source/plugins/DispatcherProviderPlugin.bs +127 -0
  33. package/src/source/plugins/FieldsPlugin.bs +180 -0
  34. package/src/source/plugins/FocusPlugin.bs +1522 -0
  35. package/src/source/plugins/FontStylePlugin.bs +159 -0
  36. package/src/source/plugins/ObserverPlugin.bs +548 -0
  37. package/src/source/utils/ArrayUtils.bs +495 -0
  38. package/src/source/utils/GeneralUtils.bs +181 -0
  39. 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