-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathreact-0.5.1.tm
456 lines (409 loc) · 15.3 KB
/
react-0.5.1.tm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
package require oo::metaclass
package require ensembled
namespace eval ::react ensembled
variable ::react::i 0
# Currently we need to redefine the metaclass unknown definition to handle our
# component resolution. This way we can internally handle creation and deletion.
# TODO: Implement a workaround here.
proc ::oo::metaclass::unknown {self what args} {
if { [string equal [string index $what 0] *] } {
# Our Modifications to metaclasses unknown
set component [uplevel 1 [list namespace which [string range $what 1 end]]]
if { $component eq {} } { throw error "$what is not a known component" }
uplevel 1 [list \
[uplevel 1 {namespace current}]::my \
@@RenderChild \
$component \
{*}$args
]
} else {
# The original metaclass unknown
if { $what in [info object methods $self -all] } {
tailcall $self $what {*}$args
} elseif { [list ::oo::define::$what] in [info commands ::oo::define::*] } {
tailcall ::oo::define $self $what {*}$args
} else { tailcall ::unknown $what {*}$args }
}
}
proc ::react::render args {
set args [ lassign $args component ]
if { [info commands $component] eq {} } {
set component [uplevel 1 [list namespace which $component]]
if { [info commands $component] eq {} } {
throw error "[lindex $args 0] is not a known component"
}
}
if { [info commands ::react::root] ne {} } {
set root_name ::react::root[incr ::react::i]
} else { set root_name ::react::root }
set root [ $component create $root_name [dict create order 0 root 1] {*}$args ]
}
::oo::class create ::react::mixin {
variable @@COMPONENT PROPS STATE
constructor {context args} {
set STATE [my static default_state]
set PROPS [dict merge [my static default_props] [lindex $args 0]]
set @@COMPONENT [dict create]
dict set @@COMPONENT status disable_state_rerender 1
if { [self next] ne {} } { next {*}$args }
dict unset @@COMPONENT status disable_state_rerender
my componentWillMount
my @@Render
my componentDidMount
}
destructor {
my @@UnmountChildren @@all
if { [self next] ne {} } { next }
namespace delete [namespace current]
if { [info commands [namespace qualifiers [self]]::*] eq {} } {
namespace delete [namespace qualifiers [self]]
}
}
method static {cmd args} {
tailcall [info object class [self]] $cmd {*}$args
}
method @namespace {} { return [namespace current] }
method @@RenderChild { C args } {
set component [set @@COMPONENT]
if { ! [dict exists $component status rendering] } {
my @@Log "You may only render children during a render"
return 0
}
set child [dict get $component render_child]
if { ! [dict exists $args key] } {
#my @@Log "Each child must have a unique key. The key must be unique to all children of the component"
#return 0
set key auto_$child
} else {
set key [dict get $args key]
dict unset args key
}
if { [dict exists $component c $key] } {
dict lappend @@COMPONENT render_queue [list [my @@Child $key]::my setProps [dict create {*}$args]]
} else {
if { $C eq {} || [info commands $C] eq {} } {
throw error "$C is not a known Component"
} else {
set self [self]
dict lappend @@COMPONENT render_queue [join [list \
[subst -nocommands { set _component [$C create ${self}::c::$key {o {$child}} {$args}] }] \
[format { dict set @@COMPONENT c {%s} ${_component} } $key ] \
] \;]
}
}
dict lappend @@COMPONENT rendered $key
dict incr @@COMPONENT render_child
}
method @@CompleteRender {} {
set component [set @@COMPONENT]
if { [dict exists $component render_queue_id] } {
after cancel [dict get $component render_queue_id]
dict unset @@COMPONENT render_queue_id
}
if { [dict exists $component render_queue] } {
set render_queue [dict get $component render_queue]
foreach queued $render_queue {
try $queued
}
dict unset @@COMPONENT render_queue
}
}
# Internal method that we call when we want to start a render process.
method @@Render {} {
# We do this as a workaround of the bug
# http://core.tcl.tk/tcl/tktview/900cb0284bcf1bf27038a7ae02c9f1440b150c86
if { [my render 1] eq {} } {
return
}
dict set @@COMPONENT status rendering 1
dict set @@COMPONENT render_child 0
try { my render } on error {result options} {
my @@Log "An Error Occurred During Render: $result"
# ~! "Render Error" "An Error Occurred During Rendering: $result" \
# -type error \
# -context $options
}
set component [set @@COMPONENT]
if { [dict exists $component rendered] && [dict exists $component c] } {
foreach rendered [dict get $component rendered] {
dict unset component c $rendered
}
my @@UnmountChildren [dict get $component c]
} elseif { [dict exists $component c] } {
my @@UnmountChildren [dict get $component c]
}
my @@CompleteRender
dict unset @@COMPONENT render_child
dict unset @@COMPONENT status rendering
dict unset @@COMPONENT rendered
}
method @@Child { key } {
return [ [self]::c::${key} @namespace ]
}
method @@UnmountChildren { children } {
if { $children eq "@@all" } {
set children {}
set component [set @@COMPONENT]
if { [dict exists $component c] } {
set children [dict get $component c]
}
}
dict for { key child_num } $children {
[my @@Child $key]::my componentWillUnmount
dict unset @@COMPONENT c $key
}
}
method @@Log msg {
set msg "[my static display_name] | [self] | $msg"
# ~! "UI Log" "$msg" \
# -type "info"
return $msg
}
# my setState $state
# Performs a shallow merge of nextState into current state. This is the primary
# method you use to trigger UI updates from event handlers and server request
# callbacks.
#
# setState does not immediately mutate this.state but creates a pending state
# transition. Accessing this.state after calling this method can potentially
# return the existing value.
method setState { state } {
set component [set @@COMPONENT]
if { [dict exists $component status disable_state_rerender] } {
set rerender 0
} else {
if { [dict exists $component status disable_set_state] } {
throw error "[my static display_name] | You may not set state from within a render or update lifecycle, this will cause an endless loop"
} else {
set rerender 1
}
}
if { [dict exists $component next_state] } {
dict set @@COMPONENT next_state [dict merge \
[dict get $component next_state] \
$state
]
} else {
dict set @@COMPONENT next_state $state
}
if { $rerender && ! [dict exists $component queued_update] } {
dict set @@COMPONENT queued_update [ after 0 \
[namespace code [list my shouldComponentUpdate]]
]
}
}
method setProps { props } {
set component [set @@COMPONENT]
if { [dict exists $component next_props] } {
set next_props [dict merge \
[dict get $component next_props] \
$props
]
} else { set next_props $props }
if { $next_props ne $PROPS } {
dict set @@COMPONENT next_props $next_props
if { ! [dict exists $component queued_props_update] } {
dict set @@COMPONENT queued_props_update [ after 0 \
[namespace code [list my componentWillReceiveProps]]
]
}
} else {
if { [dict exists $component queued_props_update] } {
after cancel [dict get $component queued_props_update]
dict unset @@COMPONENT queued_props_update
}
if { [dict exists $component next_props] } {
dict unset @@COMPONENT next_props
}
}
}
# When we want to check if we should update the component, this will be called.
# If our child has the method defined then we will use their response to determine
# if we should update. Otherwise we will simply update.
#
# -- When defined in the component:
# Use shouldComponentUpdate() to let React know if a component's output is not affected
# by the current change in state or props. The default behavior is to re-render on
# every state change, and in the vast majority of cases you should rely on the default
# behavior.
method shouldComponentUpdate { {force 0} } {
dict set @@COMPONENT disable_set_state 1
set component [set @@COMPONENT]
set prev_props $PROPS ; set prev_state $STATE
if { [dict exists $component queued_update] } {
after cancel [dict get $component queued_update]
dict unset @@COMPONENT queued_updated
}
if { [dict exists $component queued_props_update] } {
after cancel [dict get $component queued_props_update]
dict unset @@COMPONENT queued_props_update
}
if { [dict exists $component next_state] } {
set next_state [dict merge $STATE [dict get $component next_state]]
dict unset @@COMPONENT next_state
} else { set next_state $STATE }
if { [dict exists $component next_props] } {
set next_props [dict merge $PROPS [dict get $component next_props]]
dict unset @@COMPONENT next_props
} else { set next_props $PROPS }
# Have the props or state changed during this update? If the net result
# does not have any changed values then we won't even call the child.
if { $force || $next_props ne $PROPS || $next_state ne $STATE } {
if { ! $force && [self next] ne {} } {
if { [ string is true -strict [next $next_props $next_state] ] } {
set update 1
}
} else { set update 1 }
if { [info exists update] } {
# If we will update / call render then we will call componentWillUpdate
# if it is defined with the new props and state.
my componentWillUpdate $next_props $next_state
}
# Regardless we change the state and props if the values were changed
set STATE $next_state
set PROPS $next_props
} else { return }
# Should we call the render method?
if { [info exists update] } {
my @@Render
my componentDidUpdate $prev_props $prev_state
}
# Ok, we can now allow updating of state again!
dict unset @@COMPONENT status disable_set_state
}
# componentWillUpdate() is invoked immediately before rendering when new props or
# state are being received. Use this as an opportunity to perform preparation before
# an update occurs. This method is not called for the initial render.
method componentWillUpdate { next_props next_state } {
if { [self next] ne {} } { catch { next $next_props $next_state } }
}
# componentDidUpdate() is invoked immediately after updating occurs. This method
# is not called for the initial render.
method componentDidUpdate { prev_props prev_state } {
if { [self next] ne {} } {
next $prev_props $prev_state
}
}
# componentWillUnmount() is invoked immediately before a component is unmounted and
# destroyed. Perform any necessary cleanup in this method, such as invalidating timers,
# canceling network requests, or cleaning up any DOM elements that were created in
# componentDidMount
method componentWillUnmount {} {
set component [set @@COMPONENT]
if { [dict exists $component c] } {
my @@UnmountChildren [dict get $component c]
}
if { [self next] ne {} } { catch { next } }
[self] destroy
}
# componentWillMount() is invoked immediately before mounting occurs. It is called
# before render(), therefore setting state in this method will not trigger a
# re-rendering. Avoid introducing any side-effects or subscriptions in this method.
method componentWillMount {} {
if { [self next] ne {} } { next }
}
method componentDidMount {} {
if { [self next] ne {} } { next }
}
# componentWillReceiveProps() is invoked before a mounted component receives new props.
# If you need to update the state in response to prop changes (for example, to reset it),
# you may compare this.props and nextProps and perform state transitions using this.setState()
# in this method.
method componentWillReceiveProps {} {
set component [set @@COMPONENT]
if { [dict exists $component next_props] } {
if { [self next] ne {} } {
dict set @@COMPONENT status disable_state_rerender 1
catch { next [dict get $component next_props] }
dict unset @@COMPONENT status disable_state_rerender
}
my shouldComponentUpdate
}
}
method render { {check 0} } {
if { $check } { return [self next] }
if { [self next] ne {} } { next }
}
# By default, when your component's state or props change, your component will
# re-render. If your render() method depends on some other data, you can tell
# React that the component needs re-rendering by calling forceUpdate().
method forceUpdate {} {
# set component [set @@COMPONENT]
# if { [dict exists $component queued_update] } {
# after cancel [dict get $component queued_update]
# }
# dict set @@COMPONENT queued_update [after 0 \
# [namespace code [list my shouldComponentUpdate 1]]
# ]
my shouldComponentUpdate 1
}
# method render {} {
# if { [self next] ne {} } { next }
# }
# Do not allow outside calls of our lifecycle methods
unexport render shouldComponentUpdate componentDidUpdate \
componentDidMount componentWillMount setState \
componentWillUnmount componentWillUpdate \
componentWillReceiveProps forceUpdate setProps \
destroy
# Used to get the objects namespace. This is how we override
# and call the lifecycle methods as necessary from the parent.
export @namespace
}
# Our Component metaclass handles the internal syntax and static values
# capabilities.
::oo::metaclass create Component {
# We want all children to use ::Components as their base namespace. Their
# command resolution still occurs where created and they have access to resolve
# commands defined within the namespace it is created within.
#
# If this is not defined, then each component would be rendered within the ns
# that creates it which makes it harder to conduct introspection and
# cleanup.
scope ::react::components
constructor data {
next [join [list $data {
mixin -append ::react::mixin
} \;]]
}
method default_props { {props {}} } {
my variable default_props
if { [info exists default_props] } {
return $default_props
} elseif { $props ne {} } {
set default_props [dict create {*}$props]
}
}
method default_state { {state {}} } {
my variable default_state
if { [info exists default_state] } {
return $default_state
} elseif { $state ne {} } {
set default_state [dict create {*}$state]
}
}
method display_name { {name {}} } {
my variable display_name
if { [info exists display_name] || $name eq {} } {
return [expr { [info exists display_name]
? $display_name
: [uplevel 1 {self}]
}]
} elseif { $name ne {} } {
set display_name $name
}
}
method static { prop {to {}} } {
my variable $prop
if { $to ne {} } {
set $prop $to
} elseif { [info exists $prop] } { return [set $prop] }
}
method ref { key args } {
if { $args ne {} } {
tailcall [uplevel 1 {self}]::c::$key {*}$args
} else {
return [uplevel 1 {self}]::c::$key
}
}
}