netzke-core 0.6.4 → 0.6.5
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.
- data/CHANGELOG.rdoc +19 -0
- data/README.markdown +43 -0
- data/TODO +1 -5
- data/app/controllers/netzke_controller.rb +47 -15
- data/config/database.yml +2 -0
- data/features/component_loader.feature +6 -1
- data/features/composition.feature +2 -0
- data/features/js_include.feature +18 -0
- data/features/nested_views.feature +9 -0
- data/features/persistence.feature +6 -4
- data/features/support/paths.rb +3 -0
- data/javascripts/core.js +166 -519
- data/javascripts/ext.js +355 -0
- data/javascripts/touch.js +47 -0
- data/lib/netzke/actions.rb +31 -38
- data/lib/netzke/base.rb +48 -6
- data/lib/netzke/composition.rb +52 -63
- data/lib/netzke/configuration.rb +6 -2
- data/lib/netzke/core/version.rb +2 -2
- data/lib/netzke/core.rb +22 -15
- data/lib/netzke/javascript/scopes.rb +39 -0
- data/lib/netzke/javascript.rb +145 -114
- data/lib/netzke/railz/action_view_ext/ext.rb +59 -0
- data/lib/netzke/railz/action_view_ext/touch.rb +50 -0
- data/lib/netzke/railz/action_view_ext.rb +86 -0
- data/lib/netzke/railz/controller_extensions.rb +33 -0
- data/lib/netzke/{rails → railz}/routes.rb +0 -0
- data/lib/netzke/railz.rb +3 -0
- data/lib/netzke/session.rb +18 -3
- data/lib/netzke/state.rb +42 -15
- data/lib/netzke/stylesheets.rb +23 -8
- data/lib/netzke-core.rb +23 -16
- data/netzke-core.gemspec +52 -10
- data/spec/component/base_spec.rb +11 -0
- data/spec/component/javascript_spec.rb +3 -2
- data/spec/component/state_spec.rb +18 -0
- data/spec/spec_helper.rb +1 -1
- data/test/rails_app/Gemfile +3 -2
- data/test/rails_app/Gemfile.lock +73 -71
- data/test/rails_app/app/components/component_loader.rb +39 -4
- data/test/rails_app/app/components/{custom.css → component_with_custom_css/stylesheets/custom.css} +0 -0
- data/test/rails_app/app/components/component_with_custom_css.rb +2 -2
- data/test/rails_app/app/components/component_with_js_mixin/javascripts/extra_one.js +2 -0
- data/test/rails_app/app/components/component_with_js_mixin/javascripts/extra_two.js +2 -0
- data/test/rails_app/app/components/component_with_js_mixin/javascripts/method_set_one.js +6 -0
- data/test/rails_app/app/components/component_with_js_mixin/javascripts/method_set_two.js +5 -0
- data/test/rails_app/app/components/component_with_js_mixin.rb +8 -0
- data/test/rails_app/app/components/component_with_session_persistence.rb +10 -3
- data/test/rails_app/app/components/extended_component_with_js_mixin/javascripts/some_method_set.js +5 -0
- data/test/rails_app/app/components/extended_component_with_js_mixin.rb +7 -0
- data/test/rails_app/app/components/hello_world_component.rb +31 -0
- data/test/rails_app/app/components/server_caller.rb +1 -1
- data/test/rails_app/app/components/simple_panel.rb +2 -0
- data/test/rails_app/app/components/touch/hello_world_component.rb +25 -0
- data/test/rails_app/app/components/touch/server_caller.rb +28 -0
- data/test/rails_app/app/components/touch/simple_carousel.rb +17 -0
- data/test/rails_app/app/controllers/components_controller.rb +6 -1
- data/test/rails_app/app/controllers/touch_controller.rb +6 -0
- data/test/rails_app/app/helpers/touch_helper.rb +2 -0
- data/test/rails_app/app/views/components/panel_with_autoload.html.erb +2 -0
- data/test/rails_app/app/views/components/some_tab_panel.html.erb +11 -0
- data/test/rails_app/app/views/layouts/nested.html.erb +5 -0
- data/test/rails_app/app/views/layouts/touch.html.erb +13 -0
- data/test/rails_app/config/initializers/netzke.rb +1 -1
- data/test/rails_app/config/locales/en.yml +7 -1
- data/test/rails_app/config/routes.rb +10 -1
- data/test/rails_app/db/migrate/20110110132720_create_netzke_component_states.rb +20 -0
- data/test/rails_app/db/schema.rb +14 -1
- data/test/rails_app/spec/controllers/touch_controller_spec.rb +5 -0
- data/test/rails_app/spec/helpers/touch_helper_spec.rb +15 -0
- data/test/unit/netzke_core_test.rb +2 -6
- metadata +53 -11
- data/README.rdoc +0 -136
- data/lib/netzke/rails/action_view_ext.rb +0 -103
- data/lib/netzke/rails/controller_extensions.rb +0 -31
- data/test/rails_app/db/migrate/20100905214933_create_netzke_preferences.rb +0 -16
data/lib/netzke/base.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_support/core_ext'
|
2
|
+
require 'active_support/memoizable'
|
2
3
|
require 'netzke/core_ext'
|
3
4
|
require 'netzke/javascript'
|
4
5
|
require 'netzke/stylesheets'
|
@@ -47,20 +48,29 @@ module Netzke
|
|
47
48
|
attr_reader :global_id
|
48
49
|
|
49
50
|
class << self
|
51
|
+
extend ActiveSupport::Memoizable
|
52
|
+
|
50
53
|
# Component's short class name, e.g.:
|
51
54
|
# "Netzke::Module::SomeComponent" => "Module::SomeComponent"
|
52
55
|
def short_component_class_name
|
53
56
|
self.name.sub(/^Netzke::/, "")
|
54
57
|
end
|
55
58
|
|
56
|
-
# Component's class, given its name
|
59
|
+
# Component's class, given its name.
|
60
|
+
# Note: this method will be memoized if Rails.configuration.cache_classes is true.
|
57
61
|
def constantize_class_name(class_name)
|
58
|
-
"#{class_name}".constantize
|
62
|
+
"#{class_name}".constantize
|
63
|
+
rescue NameError
|
64
|
+
begin
|
65
|
+
"Netzke::#{class_name}".constantize
|
66
|
+
rescue NameError
|
67
|
+
nil
|
68
|
+
end
|
59
69
|
end
|
60
70
|
|
61
71
|
# Instance of component by config
|
62
72
|
def instance_by_config(config)
|
63
|
-
constantize_class_name(config[:class_name]).new(config)
|
73
|
+
(config[:klass] || constantize_class_name(config[:class_name])).new(config)
|
64
74
|
end
|
65
75
|
|
66
76
|
# All ancestor classes in the Netzke class hierarchy (i.e. up to Netzke::Base)
|
@@ -78,6 +88,27 @@ module Netzke
|
|
78
88
|
# We don't want here any values from the superclass (which is the consequence of using inheritable attributes).
|
79
89
|
res == self.superclass.read_inheritable_attribute(attr_name) ? {} : res
|
80
90
|
end
|
91
|
+
|
92
|
+
# Same as +read_inheritable_attribute+ returning a hash, but returns empty hash when it's equal to superclass's
|
93
|
+
def read_clean_inheritable_array(attr_name)
|
94
|
+
res = read_inheritable_attribute(attr_name) || []
|
95
|
+
# We don't want here any values from the superclass (which is the consequence of using inheritable attributes).
|
96
|
+
res == self.superclass.read_inheritable_attribute(attr_name) ? [] : res
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
def self.total_instances
|
102
|
+
@@instances || 0
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.reset_total_instances
|
106
|
+
@@instances = 0
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.increase_total_instances
|
110
|
+
@@instances ||= 0
|
111
|
+
@@instances += 1
|
81
112
|
end
|
82
113
|
|
83
114
|
# Instantiates a component instance. A parent can optionally be provided.
|
@@ -92,6 +123,8 @@ module Netzke
|
|
92
123
|
# initialize @components and @items
|
93
124
|
normalize_components_in_items
|
94
125
|
# auto_collect_actions_from_config_and_js_properties
|
126
|
+
|
127
|
+
self.class.increase_total_instances
|
95
128
|
end
|
96
129
|
|
97
130
|
# Proxy to the equally named class method
|
@@ -108,11 +141,20 @@ module Netzke
|
|
108
141
|
def before_load
|
109
142
|
end
|
110
143
|
|
144
|
+
def clean_up
|
145
|
+
component_session.clear
|
146
|
+
components.keys.each { |k| component_instance(k).clean_up }
|
147
|
+
end
|
148
|
+
|
149
|
+
def i18n_id
|
150
|
+
self.class.name.split("::").map{|c| c.underscore}.join(".")
|
151
|
+
end
|
152
|
+
|
111
153
|
private
|
112
154
|
|
113
155
|
def logger #:nodoc:
|
114
|
-
if defined?(Rails)
|
115
|
-
Rails.logger
|
156
|
+
if defined?(::Rails)
|
157
|
+
::Rails.logger
|
116
158
|
else
|
117
159
|
require 'logger'
|
118
160
|
Logger.new(STDOUT)
|
@@ -126,4 +168,4 @@ module Netzke
|
|
126
168
|
end
|
127
169
|
|
128
170
|
end
|
129
|
-
end
|
171
|
+
end
|
data/lib/netzke/composition.rb
CHANGED
@@ -1,6 +1,21 @@
|
|
1
1
|
# require 'active_support/core_ext/class/inheritable_attributes'
|
2
2
|
|
3
3
|
module Netzke
|
4
|
+
# You can define a nested components by calling the class method +component+:
|
5
|
+
#
|
6
|
+
# component :users, :data_class => "GridPanel", :model => "User"
|
7
|
+
#
|
8
|
+
# The method also accepts a block in case you want access to the component's instance:
|
9
|
+
#
|
10
|
+
# component :books do
|
11
|
+
# {:data_class => "Book", :title => build_title}
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# To override a component, define a method {component_name}_component, e.g.:
|
15
|
+
#
|
16
|
+
# def books_component
|
17
|
+
# super.merge(:title => "Modified Title")
|
18
|
+
# end
|
4
19
|
module Composition
|
5
20
|
extend ActiveSupport::Concern
|
6
21
|
|
@@ -14,8 +29,8 @@ module Netzke
|
|
14
29
|
# * <tt>:id</tt> - reference to the component
|
15
30
|
# * <tt>:container</tt> - Ext id of the container where in which the component will be rendered
|
16
31
|
endpoint :deliver_component do |params|
|
17
|
-
cache = params[:cache].
|
18
|
-
component_name = params
|
32
|
+
cache = params[:cache].split(",") # array of cached xtypes
|
33
|
+
component_name = params[:name].underscore.to_sym
|
19
34
|
component = components[component_name] && component_instance(component_name)
|
20
35
|
|
21
36
|
if component
|
@@ -38,15 +53,6 @@ module Netzke
|
|
38
53
|
module ClassMethods
|
39
54
|
|
40
55
|
# Defines a nested component.
|
41
|
-
# For example:
|
42
|
-
#
|
43
|
-
# component :users, :data_class => "GridPanel", :model => "User"
|
44
|
-
#
|
45
|
-
# It can also accept a block (receiving as parameter the eventual definition from super class):
|
46
|
-
#
|
47
|
-
# component :books do |orig|
|
48
|
-
# {:data_class => "Book", :title => orig[:title] + ", extended"}
|
49
|
-
# end
|
50
56
|
def component(name, config = {}, &block)
|
51
57
|
register_component(name)
|
52
58
|
config = config.dup
|
@@ -69,6 +75,7 @@ module Netzke
|
|
69
75
|
end
|
70
76
|
end
|
71
77
|
|
78
|
+
# DEPRECATED in favor of Symbol#component
|
72
79
|
# Component's js config used when embedding components as Container's items
|
73
80
|
# (see some_composite.rb for an example)
|
74
81
|
def js_component(name, config = {})
|
@@ -91,11 +98,13 @@ module Netzke
|
|
91
98
|
end
|
92
99
|
|
93
100
|
module InstanceMethods
|
101
|
+
extend ActiveSupport::Memoizable
|
94
102
|
|
95
|
-
def items
|
103
|
+
def items #:nodoc:
|
96
104
|
@items_with_normalized_components
|
97
105
|
end
|
98
106
|
|
107
|
+
# DEPRECATED in favor of Base.component
|
99
108
|
def initial_components
|
100
109
|
{}
|
101
110
|
end
|
@@ -105,14 +114,16 @@ module Netzke
|
|
105
114
|
@components ||= self.class.registered_components.inject({}){ |res, name| res.merge(name.to_sym => send(COMPONENT_METHOD_NAME % name)) }
|
106
115
|
end
|
107
116
|
|
108
|
-
def
|
117
|
+
def eager_loaded_components
|
109
118
|
components.reject{|k,v| v[:lazy_loading]}
|
110
119
|
end
|
111
120
|
|
121
|
+
# DEPRECATED
|
112
122
|
def add_component(aggr)
|
113
123
|
components.merge!(aggr)
|
114
124
|
end
|
115
125
|
|
126
|
+
# DEPRECATED
|
116
127
|
def remove_component(aggr)
|
117
128
|
if config[:persistent_config]
|
118
129
|
persistence_manager_class.delete_all_for_component("#{global_id}__#{aggr}")
|
@@ -120,51 +131,41 @@ module Netzke
|
|
120
131
|
components[aggr] = nil
|
121
132
|
end
|
122
133
|
|
123
|
-
#
|
124
|
-
def initial_late_components
|
125
|
-
{}
|
126
|
-
end
|
127
|
-
|
128
|
-
def add_late_component(aggr)
|
129
|
-
components.merge!(aggr.merge(:lazy_loading => true))
|
130
|
-
end
|
131
|
-
|
132
|
-
# called when the method_missing tries to processes a non-existing component
|
134
|
+
# Called when the method_missing tries to processes a non-existing component
|
133
135
|
def component_missing(aggr)
|
134
136
|
flash :error => "Unknown component #{aggr} for component #{name}"
|
135
137
|
{:feedback => @flash}.to_nifty_json
|
136
138
|
end
|
137
139
|
|
138
|
-
#
|
140
|
+
# Recursively instantiates a component based on its "path": e.g. if we have component :component1 which in its turn has component :component2, the path to the latter would be "component1__component2"
|
139
141
|
def component_instance(name, strong_config = {})
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
composite = component_class.new(conf, composite) # params: config, parent
|
157
|
-
# composite.weak_children_config = weak_children_config
|
158
|
-
# composite.strong_children_config = strong_children_config
|
159
|
-
end
|
160
|
-
composite
|
142
|
+
composite = self
|
143
|
+
name.to_s.split('__').each do |cmp|
|
144
|
+
cmp = cmp.to_sym
|
145
|
+
|
146
|
+
component_config = composite.components[cmp]
|
147
|
+
raise ArgumentError, "No child component '#{cmp}' defined for component '#{composite.global_id}'" if component_config.nil?
|
148
|
+
|
149
|
+
component_class_name = component_config[:class_name]
|
150
|
+
raise ArgumentError, "No class_name specified for component #{cmp} of #{composite.global_id}" if component_class_name.nil?
|
151
|
+
|
152
|
+
component_class = constantize_class_name(component_class_name)
|
153
|
+
raise ArgumentError, "Unknown constant #{component_class_name}" if component_class.nil?
|
154
|
+
|
155
|
+
instance_config = weak_children_config.merge(component_config).merge(strong_config).merge(:name => cmp)
|
156
|
+
|
157
|
+
composite = component_class.new(instance_config, composite) # params: config, parent
|
161
158
|
end
|
159
|
+
composite
|
162
160
|
end
|
163
161
|
|
162
|
+
memoize :component_instance # for performance
|
163
|
+
|
164
|
+
# All components that we depend on (used to render all necessary JavaScript and stylesheets)
|
164
165
|
def dependency_classes
|
165
166
|
res = []
|
166
167
|
|
167
|
-
|
168
|
+
eager_loaded_components.keys.each do |aggr|
|
168
169
|
res += component_instance(aggr).dependency_classes
|
169
170
|
end
|
170
171
|
|
@@ -174,19 +175,7 @@ module Netzke
|
|
174
175
|
res.uniq
|
175
176
|
end
|
176
177
|
|
177
|
-
|
178
|
-
# def dependencies
|
179
|
-
# @dependencies ||= begin
|
180
|
-
# non_late_components_component_classes = non_late_components.values.map{|v| v[:class_name]}
|
181
|
-
# (initial_dependencies + non_late_components_component_classes << self.class.short_component_class_name).uniq
|
182
|
-
# end
|
183
|
-
# end
|
184
|
-
|
185
|
-
# override this method if you need some extra dependencies, which are not the components
|
186
|
-
def initial_dependencies
|
187
|
-
[]
|
188
|
-
end
|
189
|
-
|
178
|
+
# DEPRECATED
|
190
179
|
def js_component(*args)
|
191
180
|
self.class.js_component(*args)
|
192
181
|
end
|
@@ -233,9 +222,9 @@ module Netzke
|
|
233
222
|
end
|
234
223
|
end
|
235
224
|
|
236
|
-
|
225
|
+
protected
|
237
226
|
|
238
|
-
def normalize_components(items)
|
227
|
+
def normalize_components(items) #:nodoc:
|
239
228
|
@component_index ||= 0
|
240
229
|
@items_with_normalized_components = items.each_with_index.map do |item, i|
|
241
230
|
if is_component_config?(item)
|
@@ -251,11 +240,11 @@ module Netzke
|
|
251
240
|
end
|
252
241
|
end
|
253
242
|
|
254
|
-
def normalize_components_in_items
|
243
|
+
def normalize_components_in_items #:nodoc:
|
255
244
|
normalize_components(config[:items]) if config[:items]
|
256
245
|
end
|
257
246
|
|
258
|
-
def is_component_config?(c)
|
247
|
+
def is_component_config?(c) #:nodoc:
|
259
248
|
!!(c.is_a?(Hash) && c[:class_name])
|
260
249
|
end
|
261
250
|
end
|
data/lib/netzke/configuration.rb
CHANGED
@@ -60,6 +60,10 @@ module Netzke
|
|
60
60
|
@strong_config ||= session_config.merge(weak_final_options).merge(strong_parent_config)
|
61
61
|
end
|
62
62
|
|
63
|
+
def configuration
|
64
|
+
final_config
|
65
|
+
end
|
66
|
+
|
63
67
|
# Resulting config that takes into account all possible ways to configure a component. *Read only*.
|
64
68
|
# Translates into something like this:
|
65
69
|
#
|
@@ -72,7 +76,7 @@ module Netzke
|
|
72
76
|
# Moved out to a separate method in order to provide for easy caching.
|
73
77
|
# *Do not override this method*, use +Base.config+ instead.
|
74
78
|
def config
|
75
|
-
@config ||=
|
79
|
+
@config ||= configuration
|
76
80
|
end
|
77
81
|
|
78
82
|
def flat_config(key = nil)
|
@@ -131,4 +135,4 @@ module Netzke
|
|
131
135
|
|
132
136
|
end
|
133
137
|
end
|
134
|
-
end
|
138
|
+
end
|
data/lib/netzke/core/version.rb
CHANGED
data/lib/netzke/core.rb
CHANGED
@@ -15,41 +15,48 @@ module Netzke
|
|
15
15
|
# The following configuration options are available:
|
16
16
|
# * ext_location - absolute path to your Ext code root
|
17
17
|
# * icons_uri - relative URI to the icons
|
18
|
-
# * javascript_on_main_page (true/false, defaults to false) - if you want the JS classes to be inserted into the code of the page,
|
19
|
-
# rather than into netzke.js (setting to true can be handy for debugging)
|
20
18
|
module Core
|
21
19
|
extend Session
|
22
20
|
extend Masquerading
|
23
21
|
|
22
|
+
# Ext or Touch
|
23
|
+
mattr_accessor :platform
|
24
|
+
@@platform = :ext
|
25
|
+
|
24
26
|
# set in Netzke::ControllerExtensions
|
25
27
|
mattr_accessor :controller
|
26
|
-
|
28
|
+
|
27
29
|
# set in Netzke::ControllerExtensions
|
28
30
|
mattr_accessor :session
|
29
31
|
@@session = {}
|
30
32
|
|
31
|
-
mattr_accessor :
|
32
|
-
@@
|
33
|
+
mattr_accessor :ext_javascripts
|
34
|
+
@@ext_javascripts = []
|
35
|
+
|
36
|
+
mattr_accessor :ext_stylesheets
|
37
|
+
@@ext_stylesheets = []
|
33
38
|
|
34
|
-
mattr_accessor :
|
35
|
-
@@
|
39
|
+
mattr_accessor :touch_javascripts
|
40
|
+
@@touch_javascripts = []
|
36
41
|
|
37
|
-
mattr_accessor :
|
38
|
-
@@
|
42
|
+
mattr_accessor :touch_stylesheets
|
43
|
+
@@touch_stylesheets = []
|
44
|
+
|
45
|
+
# Stylesheets that cannot be loaded dynamically along with the rest of the component, e.g. due to that relative paths are used in them
|
46
|
+
mattr_accessor :external_ext_css
|
47
|
+
@@external_ext_css = []
|
39
48
|
|
40
49
|
# Set in the Engine after_initialize callback
|
41
|
-
mattr_accessor :ext_location
|
50
|
+
mattr_accessor :ext_location # TODO: rename to ext_path
|
51
|
+
mattr_accessor :touch_location # TODO: rename to touch_path
|
42
52
|
mattr_accessor :with_icons
|
43
53
|
|
44
54
|
mattr_accessor :icons_uri
|
45
55
|
@@icons_uri = "/images/icons"
|
46
56
|
|
47
|
-
mattr_accessor :javascript_on_main_page
|
48
|
-
@@javascript_on_main_page = true
|
49
|
-
|
50
57
|
mattr_accessor :persistence_manager
|
51
58
|
@@persistence_manager = "NetzkeComponentState"
|
52
|
-
|
59
|
+
|
53
60
|
# Set in the Engine after_initialize callback
|
54
61
|
mattr_accessor :persistence_manager_class
|
55
62
|
|
@@ -61,4 +68,4 @@ module Netzke
|
|
61
68
|
Netzke::Core.session[:netzke_components].try(:clear)
|
62
69
|
end
|
63
70
|
end
|
64
|
-
end
|
71
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Netzke
|
2
|
+
module Javascript
|
3
|
+
module Scopes
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
# Given class name, e.g. GridPanelLib::Components::RecordFormWindow,
|
8
|
+
# returns its scope: "Components.RecordFormWindow"
|
9
|
+
def js_class_name_to_scope(name)
|
10
|
+
name.split("::")[0..-2].join(".")
|
11
|
+
end
|
12
|
+
|
13
|
+
# Top level scope which will be used to scope out Netzke classes
|
14
|
+
def js_default_scope
|
15
|
+
"Netzke.classes"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Scope of this component without default scope
|
19
|
+
# e.g.: GridPanelLib.Components
|
20
|
+
def js_scope
|
21
|
+
js_class_name_to_scope(short_component_class_name)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the scope of this component
|
25
|
+
# e.g. "Netzke.classes.GridPanelLib"
|
26
|
+
def js_full_scope
|
27
|
+
js_scope.empty? ? js_default_scope : [js_default_scope, js_scope].join(".")
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the full name of the JavaScript class, including the scopes *and* the common scope, which is
|
31
|
+
# Netzke.classes.
|
32
|
+
# E.g.: "Netzke.classes.Netzke.GridPanelLib.RecordFormWindow"
|
33
|
+
def js_full_class_name
|
34
|
+
[js_full_scope, short_component_class_name.split("::").last].join(".")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|