1
1
require 'react/ext/string'
2
2
3
3
module React
4
+ #
5
+ # Wraps the React Native element class
6
+ #
7
+ # adds the #on method to add event handlers to the element
8
+ #
9
+ # adds the #render method to place elements in the DOM and
10
+ # #delete (alias/deprecated #as_node) method to remove elements from the DOM
11
+ #
12
+ # handles the haml style class notation so that
13
+ # div.bar.blat becomes div(class: "bar blat")
14
+ # by using method missing
15
+ #
4
16
class Element
5
17
include Native
6
18
@@ -20,58 +32,124 @@ def initialize(native_element, type, properties, block)
20
32
@native = native_element
21
33
end
22
34
23
- def on ( event_name )
24
- name = event_name . to_s . event_camelize
25
- props = if React ::Event ::BUILT_IN_EVENTS . include? ( "on#{ name } " )
26
- { "on#{ name } " => %x{
27
- function(event){
28
- #{ yield React ::Event . new ( `event` ) }
29
- }
30
- } }
31
- else
32
- { "_on#{ name } " => %x{
33
- function(){
34
- #{ yield *Array ( `arguments` ) }
35
- }
36
- } }
37
- end
38
- @native = `React.cloneElement(#{ self . to_n } , #{ props . to_n } )`
39
- @properties . merge! props
35
+ # Attach event handlers.
36
+
37
+ def on ( *event_names , &block )
38
+ event_names . each { |event_name | merge_event_prop! ( event_name , &block ) }
39
+ @native = `React.cloneElement(#{ to_n } , #{ properties . to_n } )`
40
40
self
41
41
end
42
42
43
- def render ( props = { } ) # for rendering children
43
+ # Render element into DOM in the current rendering context.
44
+ # Used for elements that are not yet in DOM, i.e. they are provided as children
45
+ # or they have been explicitly removed from the rendering context using the delete method.
46
+
47
+ def render ( props = { } )
44
48
if props . empty?
45
49
React ::RenderingContext . render ( self )
46
50
else
47
51
React ::RenderingContext . render (
48
52
Element . new (
49
- `React.cloneElement(#{ self . to_n } , #{ API . convert_props ( props ) } )` ,
50
- type ,
51
- properties . merge ( props ) ,
52
- block
53
+ `React.cloneElement(#{ to_n } , #{ API . convert_props ( props ) } )` ,
54
+ type , properties . merge ( props ) , block
53
55
)
54
56
)
55
57
end
56
58
end
57
59
60
+ # Delete (remove) element from rendering context, the element may later be added back in
61
+ # using the render method.
62
+
63
+ def delete
64
+ React ::RenderingContext . delete ( self )
65
+ end
66
+
67
+ # Deprecated version of delete method
68
+
69
+ def as_node
70
+ React ::RenderingContext . as_node ( self )
71
+ end
72
+
73
+ # Any other method applied to an element will be treated as class name (haml style) thus
74
+ # div.foo.bar(id: :fred) is the same as saying div(class: "foo bar", id: :fred)
75
+ #
76
+ # single underscores become dashes, and double underscores become a single underscore
77
+ #
78
+ # params may be provide to each class (but typically only to the last for easy reading.)
79
+
58
80
def method_missing ( class_name , args = { } , &new_block )
59
- class_name = class_name . split ( "__" ) . collect { | s | s . gsub ( "_" , "-" ) } . join ( "_" )
81
+ class_name = class_name . gsub ( /__|_/ , '__' => '_' , '_' => '-' )
60
82
new_props = properties . dup
61
- new_props [ "class" ] = "#{ new_props [ 'class' ] } #{ class_name } #{ args . delete ( "class" ) } #{ args . delete ( 'className' ) } " . split ( " " ) . uniq . join ( " " )
83
+ new_props [ :class ] = "\
84
+ #{ class_name } #{ new_props [ :class ] } #{ args . delete ( :class ) } #{ args . delete ( :className ) } \
85
+ ". split ( ' ' ) . uniq . join ( ' ' )
62
86
new_props . merge! args
63
87
React ::RenderingContext . replace (
64
88
self ,
65
- React :: RenderingContext . build { React :: RenderingContext . render ( type , new_props , &new_block ) }
89
+ RenderingContext . build { RenderingContext . render ( type , new_props , &new_block ) }
66
90
)
67
91
end
68
92
69
- def as_node
70
- React ::RenderingContext . as_node ( self )
93
+ private
94
+
95
+ # built in events, events going to native components, and events going to reactrb
96
+
97
+ # built in events will have their event param translated to the Event wrapper
98
+ # and the name will camelcased and have on prefixed, so :click becomes onClick.
99
+ #
100
+ # events emitting from native components are assumed to have the same camel case and
101
+ # on prefixed.
102
+ #
103
+ # events emitting from reactrb components will just have on_ prefixed. So
104
+ # :play_button_pushed attaches to the :on_play_button_pushed param
105
+ #
106
+ # in all cases the default name convention can be overriden by wrapping in <...> brackets.
107
+ # So on("<MyEvent>") will attach to the "MyEvent" param.
108
+
109
+ def merge_event_prop! ( event_name , &block )
110
+ if event_name =~ /^<(.+)>$/
111
+ merge_component_event_prop! event_name . gsub ( /^<(.+)>$/ , '\1' ) , &block
112
+ elsif React ::Event ::BUILT_IN_EVENTS . include? ( name = "on#{ event_name . event_camelize } " )
113
+ merge_built_in_event_prop! name , &block
114
+ elsif @type . instance_variable_get ( '@native_import' )
115
+ merge_component_event_prop! name , &block
116
+ else
117
+ merge_deprecated_component_event_prop! event_name , &block
118
+ merge_component_event_prop! "on_#{ event_name } " , &block
119
+ end
71
120
end
72
121
73
- def delete
74
- React ::RenderingContext . delete ( self )
122
+ def merge_built_in_event_prop! ( prop_name )
123
+ @properties . merge! (
124
+ prop_name => %x{
125
+ function(event){
126
+ return #{ yield ( React ::Event . new ( `event` ) ) }
127
+ }
128
+ }
129
+ )
130
+ end
131
+
132
+ def merge_component_event_prop! ( prop_name )
133
+ @properties . merge! (
134
+ prop_name => %x{
135
+ function(){
136
+ return #{ yield ( *Array ( `arguments` ) ) }
137
+ }
138
+ }
139
+ )
140
+ end
141
+
142
+ def merge_deprecated_component_event_prop! ( event_name )
143
+ prop_name = "_on#{ event_name . event_camelize } "
144
+ fn = %x{function(){#{
145
+ React ::Component . deprecation_warning (
146
+ "In future releases React::Element#on('#{ event_name } ') will no longer respond " \
147
+ "to the '#{ prop_name } ' emitter.\n " \
148
+ "Rename your emitter param to 'on_#{ event_name } ' or use .on('<#{ prop_name } >')"
149
+ ) }
150
+ return #{ yield ( *Array ( `arguments` ) ) }
151
+ }}
152
+ @properties . merge! ( prop_name => fn )
75
153
end
76
154
end
77
155
end
0 commit comments