netzke-basepack 0.5.5.1 → 0.5.6
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 +16 -1
- data/README.rdoc +31 -56
- data/Rakefile +2 -2
- data/TODO.rdoc +3 -0
- data/javascripts/basepack.js +70 -30
- data/lib/app/models/netzke_auto_column.rb +1 -53
- data/lib/app/models/netzke_auto_field.rb +4 -0
- data/lib/app/models/netzke_auto_table.rb +61 -0
- data/lib/netzke-basepack.rb +13 -7
- data/lib/netzke/active_record/basepack.rb +28 -0
- data/lib/netzke/active_record/data_accessor.rb +2 -0
- data/lib/netzke/basic_app.rb +3 -4
- data/lib/netzke/{basic_app_extras → basic_app}/statusbar_ext.js +0 -0
- data/lib/netzke/data_accessor.rb +2 -0
- data/lib/netzke/ext.rb +1 -1
- data/lib/netzke/fields_configurator.rb +22 -6
- data/lib/netzke/form_panel.rb +20 -11
- data/lib/netzke/form_panel/form_panel_api.rb +78 -0
- data/lib/netzke/form_panel/form_panel_js.rb +143 -0
- data/lib/netzke/{form_panel_extras → form_panel}/javascripts/netzkefileupload.js +0 -0
- data/lib/netzke/{form_panel_extras → form_panel}/javascripts/xcheckbox.js +0 -0
- data/lib/netzke/grid_panel.rb +33 -24
- data/lib/netzke/grid_panel/grid_panel_api.rb +358 -0
- data/lib/netzke/grid_panel/grid_panel_js.rb +747 -0
- data/lib/netzke/{grid_panel_extras → grid_panel}/javascripts/filters.js +0 -0
- data/lib/netzke/{grid_panel_extras → grid_panel}/javascripts/rows-dd.js +0 -0
- data/lib/netzke/grid_panel/record_form_window.rb +44 -0
- data/lib/netzke/panel.rb +1 -3
- data/lib/netzke/property_editor.rb +2 -0
- data/lib/netzke/{property_editor_extras → property_editor}/helper_model.rb +2 -2
- data/lib/netzke/search_panel.rb +3 -3
- data/lib/netzke/window.rb +9 -3
- data/test/unit/grid_panel_test.rb +0 -2
- data/test/unit/helper_model_test.rb +2 -2
- metadata +16 -15
- data/lib/netzke/field_model.rb +0 -131
- data/lib/netzke/form_panel_api.rb +0 -78
- data/lib/netzke/form_panel_js.rb +0 -141
- data/lib/netzke/grid_panel_api.rb +0 -356
- data/lib/netzke/grid_panel_extras/record_form_window.rb +0 -46
- data/lib/netzke/grid_panel_js.rb +0 -725
@@ -1,3 +1,5 @@
|
|
1
|
+
require "activerecord"
|
2
|
+
|
1
3
|
module Netzke::ActiveRecord
|
2
4
|
# Provides extensions to all ActiveRecord-based classes
|
3
5
|
module Basepack
|
@@ -59,6 +61,32 @@ module Netzke::ActiveRecord
|
|
59
61
|
super
|
60
62
|
end
|
61
63
|
end
|
64
|
+
|
65
|
+
# Make respond_to? return true for association assignment method, like "genre__name="
|
66
|
+
def respond_to?(method, include_private = false)
|
67
|
+
split = method.to_s.split(/__/)
|
68
|
+
if split.size > 1
|
69
|
+
if split.last =~ /=$/
|
70
|
+
if split.size == 2
|
71
|
+
# search for association and assign it to self
|
72
|
+
assoc = self.class.reflect_on_association(split.first.to_sym)
|
73
|
+
assoc_method = split.last.chop
|
74
|
+
if assoc
|
75
|
+
assoc.klass.respond_to?("find_by_#{assoc_method}")
|
76
|
+
else
|
77
|
+
super
|
78
|
+
end
|
79
|
+
else
|
80
|
+
super
|
81
|
+
end
|
82
|
+
else
|
83
|
+
# self.respond_to?(split.first) ? self.send(split.first).respond_to?(split[1..-1].join("__")) : false
|
84
|
+
super
|
85
|
+
end
|
86
|
+
else
|
87
|
+
super
|
88
|
+
end
|
89
|
+
end
|
62
90
|
|
63
91
|
module ClassMethods
|
64
92
|
|
data/lib/netzke/basic_app.rb
CHANGED
@@ -14,8 +14,8 @@ module Netzke
|
|
14
14
|
def self.include_js
|
15
15
|
res = []
|
16
16
|
ext_examples = Netzke::Base.config[:ext_location] + "/examples/"
|
17
|
-
res << ext_examples + "ux/StatusBar.js"
|
18
|
-
res << "#{File.dirname(__FILE__)}/
|
17
|
+
res << ext_examples + "ux/statusbar/StatusBar.js"
|
18
|
+
res << "#{File.dirname(__FILE__)}/basic_app/statusbar_ext.js"
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.js_base_class
|
@@ -57,7 +57,6 @@ module Netzke
|
|
57
57
|
:xtype => 'toolbar',
|
58
58
|
:region => 'north',
|
59
59
|
:height => 25
|
60
|
-
# :items => ["-"]
|
61
60
|
},{
|
62
61
|
:id => 'main-statusbar',
|
63
62
|
:xtype => 'statusbar',
|
@@ -115,10 +114,10 @@ module Netzke
|
|
115
114
|
// var position = toolbar.items.getCount() - 2;
|
116
115
|
// position = position < 0 ? 0 : position;
|
117
116
|
// toolbar.insertButton(position, newMenu);
|
118
|
-
|
119
117
|
toolbar.add(item);
|
120
118
|
// this.menus[owner.id].push(newMenu); // TODO: remember the menus from this owner in some other way
|
121
119
|
}, this);
|
120
|
+
toolbar.doLayout(); // required since Ext 3.0.3
|
122
121
|
}
|
123
122
|
END_OF_JAVASCRIPT
|
124
123
|
|
File without changes
|
data/lib/netzke/data_accessor.rb
CHANGED
data/lib/netzke/ext.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Netzke
|
2
2
|
# Ext-related module. Some constants provide meta-information about the Ext.library.
|
3
3
|
module Ext
|
4
|
-
FORM_FIELD_XTYPES = %w{ textfield numberfield textarea combobox checkbox xdatetime }
|
4
|
+
FORM_FIELD_XTYPES = %w{ textfield numberfield textarea combobox xcheckbox checkbox xdatetime }
|
5
5
|
COLUMN_XTYPES = %w{ gridcolumn booleancolumn numbercolumn datecolumn }
|
6
6
|
end
|
7
7
|
end
|
@@ -8,13 +8,24 @@ module Netzke
|
|
8
8
|
|
9
9
|
def initialize(*args)
|
10
10
|
super
|
11
|
-
|
11
|
+
@auto_table_klass = is_for_grid? ? NetzkeAutoColumn : NetzkeAutoField
|
12
|
+
@auto_table_klass.widget = client_widget
|
13
|
+
end
|
14
|
+
|
15
|
+
# widget that uses us
|
16
|
+
def client_widget
|
17
|
+
@passed_config[:widget]
|
18
|
+
end
|
19
|
+
|
20
|
+
# is our client widget a grid (as opposed to a form)?
|
21
|
+
def is_for_grid?
|
22
|
+
client_widget.class.ancestors.include?(GridPanel)
|
12
23
|
end
|
13
24
|
|
14
25
|
def default_config
|
15
26
|
super.deep_merge({
|
16
27
|
:name => 'columns',
|
17
|
-
:data_class_name => "NetzkeAutoColumn",
|
28
|
+
:data_class_name => is_for_grid? ? "NetzkeAutoColumn" : "NetzkeAutoField",
|
18
29
|
:ext_config => {
|
19
30
|
:header => false,
|
20
31
|
:enable_extended_search => false,
|
@@ -32,7 +43,7 @@ module Netzke
|
|
32
43
|
end
|
33
44
|
|
34
45
|
def default_bbar
|
35
|
-
%w{ edit apply - defaults }
|
46
|
+
%w{ add edit apply del - defaults }
|
36
47
|
end
|
37
48
|
|
38
49
|
def predefined_columns
|
@@ -69,13 +80,13 @@ module Netzke
|
|
69
80
|
|
70
81
|
def load_defaults(params)
|
71
82
|
config[:widget].persistent_config[:layout__columns] = config[:widget].default_columns
|
72
|
-
|
83
|
+
@auto_table_klass.rebuild_table
|
73
84
|
{:load_store_data => get_data}
|
74
85
|
end
|
75
86
|
|
76
87
|
def commit(params)
|
77
88
|
defaults_hash = config[:widget].class.config_columns.inject({}){ |r, c| r.merge!(c[:name] => c[:default]) }
|
78
|
-
config[:widget].persistent_config[:layout__columns] =
|
89
|
+
config[:widget].persistent_config[:layout__columns] = @auto_table_klass.all_columns.map do |c|
|
79
90
|
# reject all keys that are 1) same as defaults, 2) 'position'
|
80
91
|
c.reject!{ |k,v| defaults_hash[k.to_sym].to_s == v.to_s || k == 'position'}
|
81
92
|
c = c["name"] if c.keys.count == 1 # denormalize the column
|
@@ -86,7 +97,12 @@ module Netzke
|
|
86
97
|
|
87
98
|
# each time that we are loaded into the app, rebuild the table
|
88
99
|
def before_load
|
89
|
-
|
100
|
+
@auto_table_klass.rebuild_table
|
101
|
+
end
|
102
|
+
|
103
|
+
# Don't show the config tool
|
104
|
+
def config_tool_needed?
|
105
|
+
false
|
90
106
|
end
|
91
107
|
|
92
108
|
end
|
data/lib/netzke/form_panel.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
+
require "netzke/form_panel/form_panel_js"
|
2
|
+
require "netzke/form_panel/form_panel_api"
|
3
|
+
require "netzke/plugins/configuration_tool"
|
4
|
+
require "netzke/data_accessor"
|
5
|
+
|
1
6
|
module Netzke
|
2
7
|
# = FormPanel
|
3
8
|
#
|
4
9
|
# Represents Ext.form.FormPanel
|
5
10
|
#
|
6
11
|
# == Configuration
|
7
|
-
# * <tt>:
|
12
|
+
# * <tt>:model</tt> - name of the ActiveRecord model that provides data to this GridPanel.
|
8
13
|
# * <tt>:record</tt> - record to be displayd in the form. Takes precedence over <tt>:record_id</tt>
|
9
14
|
# * <tt>:record_id</tt> - id of the record to be displayd in the form. Also see <tt>:record</tt>
|
10
15
|
#
|
@@ -12,8 +17,8 @@ module Netzke
|
|
12
17
|
#
|
13
18
|
# * <tt>:mode</tt> - when set to <tt>:config</tt>, FormPanel loads in configuration mode
|
14
19
|
class FormPanel < Base
|
15
|
-
include
|
16
|
-
include
|
20
|
+
include FormPanelJs # javascript (client-side)
|
21
|
+
include FormPanelApi # API (server-side)
|
17
22
|
include Netzke::DataAccessor # some code shared between GridPanel, FormPanel, and other widgets that use database attributes
|
18
23
|
|
19
24
|
# Class-level configuration with defaults
|
@@ -43,9 +48,9 @@ module Netzke
|
|
43
48
|
# Extra javascripts
|
44
49
|
def self.include_js
|
45
50
|
[
|
46
|
-
"#{File.dirname(__FILE__)}/
|
47
|
-
Netzke::Base.config[:ext_location] + "/examples/ux/FileUploadField.js",
|
48
|
-
"#{File.dirname(__FILE__)}/
|
51
|
+
"#{File.dirname(__FILE__)}/form_panel/javascripts/xcheckbox.js",
|
52
|
+
Netzke::Base.config[:ext_location] + "/examples/ux/fileuploadfield/FileUploadField.js",
|
53
|
+
"#{File.dirname(__FILE__)}/form_panel/javascripts/netzkefileupload.js"
|
49
54
|
]
|
50
55
|
end
|
51
56
|
|
@@ -56,11 +61,14 @@ module Netzke
|
|
56
61
|
def initialize(*args)
|
57
62
|
super
|
58
63
|
apply_helpers
|
59
|
-
@record = config[:record] || data_class && data_class.
|
64
|
+
@record = config[:record] || config[:record_id] && data_class && data_class.find(:first, :conditions => {data_class.primary_key => config[:record_id]})
|
60
65
|
end
|
61
66
|
|
67
|
+
# (We can't memoize this method because at some point we extend it, e.g. in Netzke::DataAccessor)
|
62
68
|
def data_class
|
63
|
-
|
69
|
+
::ActiveSupport::Deprecation.warn("data_class_name option is deprecated. Use model instead", caller) if config[:data_class_name]
|
70
|
+
model_name = config[:model] || config[:data_class_name]
|
71
|
+
@data_class ||= model_name && model_name.constantize
|
64
72
|
end
|
65
73
|
|
66
74
|
def configuration_widgets
|
@@ -98,7 +106,8 @@ module Netzke
|
|
98
106
|
def js_config
|
99
107
|
res = super
|
100
108
|
res.merge!(:clmns => columns)
|
101
|
-
res.merge!(:data_class_name =>
|
109
|
+
res.merge!(:data_class_name => data_class.name) if data_class
|
110
|
+
res.merge!(:pri => data_class.primary_key) if data_class
|
102
111
|
res
|
103
112
|
end
|
104
113
|
|
@@ -108,10 +117,10 @@ module Netzke
|
|
108
117
|
{:name => :name, :type => :string, :editor => :combobox, :width => 200},
|
109
118
|
{:name => :hidden, :type => :boolean, :editor => :checkbox, :width => 40, :header => "Excl"},
|
110
119
|
{:name => :disabled, :type => :boolean, :editor => :checkbox, :width => 40, :header => "Dis"},
|
111
|
-
{:name => :xtype, :type => :string},
|
120
|
+
{:name => :xtype, :type => :string, :editor => {:xtype => :combobox, :options => Netzke::Ext::FORM_FIELD_XTYPES}},
|
112
121
|
{:name => :value, :type => :string},
|
113
122
|
{:name => :field_label, :type => :string},
|
114
|
-
{:name => :input_type, :type => :string}
|
123
|
+
{:name => :input_type, :type => :string, :hidden => true}
|
115
124
|
]
|
116
125
|
end
|
117
126
|
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Netzke
|
2
|
+
class FormPanel < Base
|
3
|
+
module FormPanelApi
|
4
|
+
# API handling form submission
|
5
|
+
def netzke_submit(params)
|
6
|
+
success = create_or_update_record(params)
|
7
|
+
|
8
|
+
if success
|
9
|
+
{:set_form_values => array_of_values, :set_result => "ok"}
|
10
|
+
else
|
11
|
+
# flash eventual errors
|
12
|
+
@record.errors.each_full do |msg|
|
13
|
+
flash :error => msg
|
14
|
+
end
|
15
|
+
{:feedback => @flash}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Creates/updates a record from hash
|
20
|
+
def create_or_update_record(params)
|
21
|
+
hsh = ActiveSupport::JSON.decode(params[:data])
|
22
|
+
hsh.merge!(config[:strong_default_attrs]) if config[:strong_default_attrs]
|
23
|
+
@record ||= data_class.find(:first, :conditions => {data_class.primary_key => hsh.delete(data_class.primary_key)}) # only pick up the record specified in the params if it was not provided in the configuration
|
24
|
+
success = true
|
25
|
+
|
26
|
+
@record = data_class.new if @record.nil?
|
27
|
+
|
28
|
+
hsh.each_pair do |k,v|
|
29
|
+
begin
|
30
|
+
@record.send("#{k}=",v)
|
31
|
+
rescue StandardError => exc
|
32
|
+
logger.debug "!!! FormPanelApi#create_or_update_record exception: #{exc.inspect}\n"
|
33
|
+
flash :error => exc.message
|
34
|
+
success = false
|
35
|
+
break
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# did we have complete success?
|
40
|
+
success && @record.save
|
41
|
+
end
|
42
|
+
|
43
|
+
# API handling form load
|
44
|
+
# def load(params)
|
45
|
+
# klass = config[:data_class_name].constantize
|
46
|
+
# case params[:neighbour]
|
47
|
+
# when "previous" then @record = klass.previous(params[:id])
|
48
|
+
# when "next" then @record = klass.next(params[:id])
|
49
|
+
# else @record = klass.find(params[:id])
|
50
|
+
# end
|
51
|
+
# {:data => [array_of_values]}
|
52
|
+
# end
|
53
|
+
|
54
|
+
def netzke_load(params)
|
55
|
+
@record = data_class && data_class.find_by_id(params[:id])
|
56
|
+
{:set_form_values => array_of_values}
|
57
|
+
end
|
58
|
+
|
59
|
+
# API that returns options for a combobox
|
60
|
+
def get_combobox_options(params)
|
61
|
+
column = params[:column]
|
62
|
+
query = params[:query]
|
63
|
+
|
64
|
+
{:data => data_class.options_for(column, query).map{|s| [s]}}
|
65
|
+
end
|
66
|
+
|
67
|
+
def configuration_panel__fields__get_combobox_options(params)
|
68
|
+
query = params[:query]
|
69
|
+
{:data => (predefined_columns.map{ |c| c[:name].to_s }).grep(/^#{query}/).map{ |n| [n] }}.to_nifty_json
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns array of form values according to the configured columns
|
73
|
+
def array_of_values
|
74
|
+
@record && @record.to_array(columns, self)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Netzke
|
2
|
+
class FormPanel < Base
|
3
|
+
module FormPanelJs
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def js_base_class
|
10
|
+
"Ext.FormPanel"
|
11
|
+
end
|
12
|
+
|
13
|
+
def js_extend_properties
|
14
|
+
{
|
15
|
+
:body_style => 'padding:5px 5px 0',
|
16
|
+
:auto_scroll => true,
|
17
|
+
:label_width => 150,
|
18
|
+
:default_type => 'textfield',
|
19
|
+
|
20
|
+
:init_component => <<-END_OF_JAVASCRIPT.l,
|
21
|
+
function(){
|
22
|
+
var recordFields = []; // Record
|
23
|
+
this.items = [];
|
24
|
+
var index = 0;
|
25
|
+
|
26
|
+
// Process columns
|
27
|
+
Ext.each(this.clmns, function(field){
|
28
|
+
if (typeof field == 'string') field = {name:field}; // normalize field
|
29
|
+
if (!field.hidden || field.name == this.pri) {
|
30
|
+
recordFields.push({name:field.name, mapping:index++});
|
31
|
+
|
32
|
+
var defaultColumnConfig = Ext.apply({}, this.defaultColumnConfig);
|
33
|
+
var columnConfig = Ext.apply(defaultColumnConfig, field);
|
34
|
+
|
35
|
+
// apply dynamically defined properties
|
36
|
+
Ext.apply(columnConfig, {
|
37
|
+
fieldLabel: columnConfig.fieldLabel || columnConfig.name.humanize(),
|
38
|
+
hideLabel: columnConfig.hidden, // completely hide fields marked "hidden"
|
39
|
+
parentId: this.id,
|
40
|
+
name: columnConfig.name,
|
41
|
+
checked: columnConfig.xtype == "xcheckbox" ? columnConfig.value : null // checkbox state
|
42
|
+
});
|
43
|
+
|
44
|
+
this.items.push(columnConfig);
|
45
|
+
}
|
46
|
+
}, this);
|
47
|
+
|
48
|
+
var Record = Ext.data.Record.create(recordFields);
|
49
|
+
this.reader = new Ext.data.RecordArrayReader({root:"data"}, Record);
|
50
|
+
|
51
|
+
delete this.clmns; // we don't need them anymore
|
52
|
+
|
53
|
+
// Now let Ext.form.FormPanel do the rest
|
54
|
+
#{js_full_class_name}.superclass.initComponent.call(this);
|
55
|
+
|
56
|
+
// Apply event
|
57
|
+
this.addEvents('apply');
|
58
|
+
}
|
59
|
+
END_OF_JAVASCRIPT
|
60
|
+
|
61
|
+
# Defaults for each field
|
62
|
+
:defaults => {
|
63
|
+
:anchor => '-20', # to leave some space for the scrollbar
|
64
|
+
# :width => 180, # we do NOT want fixed size because it doesn't look nice when resizing
|
65
|
+
:listeners => {
|
66
|
+
# On "return" key, submit the form
|
67
|
+
:specialkey => {
|
68
|
+
:fn => <<-END_OF_JAVASCRIPT.l
|
69
|
+
function(field, event){
|
70
|
+
if (event.getKey() == 13) this.ownerCt.onApply();
|
71
|
+
}
|
72
|
+
END_OF_JAVASCRIPT
|
73
|
+
}
|
74
|
+
}
|
75
|
+
},
|
76
|
+
|
77
|
+
:default_column_config => config_columns.inject({}){ |r, c| r.merge!({
|
78
|
+
c[:name] => c[:default]
|
79
|
+
}) },
|
80
|
+
|
81
|
+
:set_form_values => <<-END_OF_JAVASCRIPT.l,
|
82
|
+
function(values){
|
83
|
+
this.form.loadRecord(this.reader.readRecords({data:[values]}).records[0]);
|
84
|
+
}
|
85
|
+
END_OF_JAVASCRIPT
|
86
|
+
|
87
|
+
:load_record => <<-END_OF_JAVASCRIPT.l,
|
88
|
+
function(id, neighbour){
|
89
|
+
this.netzkeLoad({id:id});
|
90
|
+
}
|
91
|
+
END_OF_JAVASCRIPT
|
92
|
+
|
93
|
+
# :previous => <<-END_OF_JAVASCRIPT.l,
|
94
|
+
# function() {
|
95
|
+
# var currentId = this.form.getValues().id;
|
96
|
+
# this.loadRecord(currentId, 'previous');
|
97
|
+
# }
|
98
|
+
# END_OF_JAVASCRIPT
|
99
|
+
#
|
100
|
+
# :next => <<-END_OF_JAVASCRIPT.l,
|
101
|
+
# function() {
|
102
|
+
# var currentId = this.form.getValues().id;
|
103
|
+
# this.loadRecord(currentId, 'next');
|
104
|
+
# }
|
105
|
+
# END_OF_JAVASCRIPT
|
106
|
+
|
107
|
+
:on_apply => <<-END_OF_JAVASCRIPT.l
|
108
|
+
function() {
|
109
|
+
if (this.fireEvent('apply', this)) {
|
110
|
+
var values = this.getForm().getValues();
|
111
|
+
for (var k in values) {
|
112
|
+
if (values[k] == "") {delete values[k]}
|
113
|
+
}
|
114
|
+
if (this.fileUpload) {
|
115
|
+
// Not a Netzke's standard API call, because the form is multipart
|
116
|
+
this.getForm().submit({
|
117
|
+
url: this.buildApiUrl("netzke_submit"),
|
118
|
+
params: {
|
119
|
+
data: Ext.encode(values)
|
120
|
+
},
|
121
|
+
failure: function(form, action){
|
122
|
+
// It will always be failure, as we don't play along with the Ext success indication (not returning {success: true})
|
123
|
+
this.bulkExecute(Ext.decode(action.response.responseText));
|
124
|
+
this.fireEvent('submitsuccess');
|
125
|
+
},
|
126
|
+
scope: this
|
127
|
+
});
|
128
|
+
} else {
|
129
|
+
// Submit the data and process the result
|
130
|
+
this.netzkeSubmit(Ext.apply((this.baseParams || {}), {data:Ext.encode(values)}), function(result){
|
131
|
+
if (result === "ok") {this.fireEvent("submitsuccess")};
|
132
|
+
}, this);
|
133
|
+
}
|
134
|
+
}
|
135
|
+
}
|
136
|
+
END_OF_JAVASCRIPT
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|