netzke-basepack 0.7.7 → 0.8.0
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/.travis.yml +15 -10
- data/{CHANGELOG.rdoc → CHANGELOG.md} +146 -110
- data/LICENSE +7 -1
- data/README.md +47 -56
- data/Rakefile +5 -5
- data/config/before-travis.sh +10 -0
- data/javascripts/basepack.js +0 -130
- data/javascripts/netzkeremotecombo.js +59 -0
- data/lib/netzke/basepack.rb +9 -14
- data/lib/netzke/basepack/accordion.rb +45 -0
- data/lib/netzke/basepack/active_record.rb +12 -0
- data/lib/netzke/basepack/active_record/relation_extensions.rb +27 -0
- data/lib/netzke/basepack/columns.rb +309 -0
- data/lib/netzke/basepack/data_accessor.rb +22 -12
- data/lib/netzke/basepack/data_adapters/abstract_adapter.rb +75 -11
- data/lib/netzke/basepack/data_adapters/active_record_adapter.rb +154 -49
- data/lib/netzke/basepack/fields.rb +162 -0
- data/lib/netzke/basepack/form.rb +136 -0
- data/lib/netzke/basepack/{form_panel → form}/javascripts/comma_list_cbg.js +0 -1
- data/lib/netzke/basepack/{form_panel/javascripts/form_panel.js → form/javascripts/form.js} +20 -26
- data/lib/netzke/basepack/{form_panel → form}/javascripts/n_radio_group.js +0 -1
- data/lib/netzke/basepack/{form_panel → form}/javascripts/readonly_mode.js +0 -0
- data/lib/netzke/basepack/form/services.rb +115 -0
- data/lib/netzke/basepack/{form_panel → form}/stylesheets/readonly_mode.css +0 -0
- data/lib/netzke/basepack/grid.rb +355 -0
- data/lib/netzke/basepack/{grid_panel → grid}/javascripts/advanced_search.js +1 -1
- data/lib/netzke/basepack/{grid_panel → grid}/javascripts/check_column_fix.js +0 -0
- data/lib/netzke/basepack/{grid_panel → grid}/javascripts/edit_in_form.js +3 -3
- data/lib/netzke/basepack/{grid_panel → grid}/javascripts/event_handling.js +5 -2
- data/lib/netzke/basepack/{grid_panel/javascripts/grid_panel.js → grid/javascripts/grid.js} +120 -132
- data/lib/netzke/basepack/{grid_panel → grid}/javascripts/misc.js +0 -0
- data/lib/netzke/basepack/grid/services.rb +216 -0
- data/lib/netzke/basepack/item_persistence.rb +44 -0
- data/lib/netzke/basepack/item_persistence/events_plugin.rb +47 -0
- data/lib/netzke/basepack/{paging_form_panel.rb → paging_form.rb} +24 -30
- data/lib/netzke/basepack/{paging_form_panel/javascripts/paging_form_panel.js → paging_form/javascripts/paging_form.js} +2 -4
- data/lib/netzke/basepack/query_builder.rb +44 -73
- data/lib/netzke/basepack/query_builder/javascripts/query_builder.js +16 -2
- data/lib/netzke/basepack/record_form_window.rb +67 -0
- data/lib/netzke/basepack/search_panel.rb +22 -24
- data/lib/netzke/basepack/search_panel/javascripts/condition_field.js +2 -2
- data/lib/netzke/basepack/search_window.rb +47 -53
- data/lib/netzke/basepack/simple_app.rb +10 -13
- data/lib/netzke/basepack/simple_app/javascripts/simple_app.js +2 -8
- data/lib/netzke/basepack/tab_panel.rb +5 -4
- data/lib/netzke/basepack/tab_panel/javascripts/tab_panel.js +5 -5
- data/lib/netzke/basepack/version.rb +2 -2
- data/lib/netzke/basepack/viewport.rb +16 -0
- data/lib/netzke/basepack/window.rb +27 -18
- data/lib/netzke/basepack/window/javascripts/window.js +7 -1
- data/lib/netzke/basepack/wrap_lazy_loaded.rb +18 -18
- data/locales/en.yml +40 -24
- data/netzke-basepack.gemspec +51 -82
- data/stylesheets/basepack.css +0 -41
- data/test/basepack_test_app/Gemfile +9 -46
- data/test/basepack_test_app/Gemfile.lock +61 -96
- data/test/basepack_test_app/app/components/author_form.rb +8 -5
- data/test/basepack_test_app/app/components/author_grid.rb +2 -2
- data/test/basepack_test_app/app/components/book_form.rb +34 -31
- data/test/basepack_test_app/app/components/book_form_with_defaults.rb +6 -7
- data/test/basepack_test_app/app/components/book_form_with_file_upload.rb +10 -0
- data/test/basepack_test_app/app/components/book_form_with_nested_attributes.rb +5 -6
- data/test/basepack_test_app/app/components/book_grid.rb +19 -8
- data/test/basepack_test_app/app/components/book_grid_filtering.rb +4 -7
- data/test/basepack_test_app/app/components/book_grid_loader.rb +28 -15
- data/test/basepack_test_app/app/components/book_grid_with_custom_columns.rb +45 -21
- data/test/basepack_test_app/app/components/book_grid_with_default_values.rb +26 -8
- data/test/basepack_test_app/app/components/book_grid_with_excluded_columns.rb +11 -0
- data/test/basepack_test_app/app/components/book_grid_with_extra_feedback.rb +2 -2
- data/test/basepack_test_app/app/components/book_grid_with_extra_filters.rb +7 -6
- data/test/basepack_test_app/app/components/book_grid_with_mass_assignment_security.rb +9 -0
- data/test/basepack_test_app/app/components/book_grid_with_nested_attributes.rb +9 -9
- data/test/basepack_test_app/app/components/book_grid_with_overridden_columns.rb +5 -3
- data/test/basepack_test_app/app/components/book_grid_with_paging.rb +6 -8
- data/test/basepack_test_app/app/components/book_grid_with_persistence.rb +6 -4
- data/test/basepack_test_app/app/components/book_grid_with_scope.rb +6 -0
- data/test/basepack_test_app/app/components/book_grid_with_scoped_authors.rb +10 -7
- data/test/basepack_test_app/app/components/book_grid_with_virtual_attributes.rb +21 -13
- data/test/basepack_test_app/app/components/book_paging_form.rb +21 -0
- data/test/basepack_test_app/app/components/book_query_builder.rb +7 -6
- data/test/basepack_test_app/app/components/book_with_custom_primary_key_grid.rb +6 -7
- data/test/basepack_test_app/app/components/books_bound_to_author.rb +9 -7
- data/test/basepack_test_app/app/components/border_layout_panel_with_persistence.rb +12 -0
- data/test/basepack_test_app/app/components/double_book_grid.rb +19 -14
- data/test/basepack_test_app/app/components/form_without_model.rb +15 -16
- data/test/basepack_test_app/app/components/grid_with_initial_sorting.rb +7 -0
- data/test/basepack_test_app/app/components/grid_with_inline_data.rb +7 -0
- data/test/basepack_test_app/app/components/paging_form_with_search.rb +2 -2
- data/test/basepack_test_app/app/components/panel_with_persistent_regions.rb +35 -0
- data/test/basepack_test_app/app/components/query_builder.rb +7 -0
- data/test/basepack_test_app/app/components/simple_panel.rb +16 -11
- data/test/basepack_test_app/app/components/simple_window.rb +7 -6
- data/test/basepack_test_app/app/components/some_accordion.rb +18 -0
- data/test/basepack_test_app/app/components/some_auth_app.rb +5 -5
- data/test/basepack_test_app/app/components/some_border_layout.rb +20 -20
- data/test/basepack_test_app/app/components/some_search_panel.rb +6 -0
- data/test/basepack_test_app/app/components/some_simple_app.rb +30 -16
- data/test/basepack_test_app/app/components/some_tab_panel.rb +18 -15
- data/test/basepack_test_app/app/components/user_form.rb +18 -16
- data/test/basepack_test_app/app/components/user_form_with_default_fields.rb +5 -6
- data/test/basepack_test_app/app/components/user_grid.rb +11 -6
- data/test/basepack_test_app/app/components/user_grid_with_customized_form_fields.rb +5 -3
- data/test/basepack_test_app/app/components/window_component_loader.rb +25 -21
- data/test/basepack_test_app/app/models/address.rb +0 -26
- data/test/basepack_test_app/app/models/author.rb +0 -31
- data/test/basepack_test_app/app/models/book.rb +1 -42
- data/test/basepack_test_app/app/models/book_with_custom_primary_key.rb +1 -23
- data/test/basepack_test_app/app/models/role.rb +0 -21
- data/test/basepack_test_app/app/models/user.rb +0 -24
- data/test/basepack_test_app/app/views/layouts/components.html.erb +1 -1
- data/test/basepack_test_app/config/application.rb +1 -1
- data/test/basepack_test_app/config/database.yml.travis +2 -6
- data/test/basepack_test_app/config/initializers/netzke.rb +1 -6
- data/test/basepack_test_app/db/schema.rb +14 -14
- data/test/basepack_test_app/features/accordion_panel.feature +2 -2
- data/test/basepack_test_app/features/form_panel.feature +7 -7
- data/test/basepack_test_app/features/grid_panel.feature +93 -39
- data/test/basepack_test_app/features/grid_panel_with_custom_primary_key.feature +2 -1
- data/test/basepack_test_app/features/grid_sorting.feature +30 -6
- data/test/basepack_test_app/features/paging_form_panel.feature +7 -7
- data/test/basepack_test_app/features/persistent_regions.feature +30 -0
- data/test/basepack_test_app/features/search_in_grid.feature +5 -5
- data/test/basepack_test_app/features/simple_app.feature +6 -7
- data/test/basepack_test_app/features/step_definitions/form_panel_steps.rb +1 -1
- data/test/basepack_test_app/features/step_definitions/generic_steps.rb +109 -4
- data/test/basepack_test_app/features/step_definitions/grid_panel_steps.rb +8 -10
- data/test/basepack_test_app/features/step_definitions/window_steps.rb +27 -0
- data/test/basepack_test_app/features/tab_panel.feature +1 -1
- data/test/basepack_test_app/features/window.feature +17 -0
- data/test/unit/accordion_panel_test.rb +2 -2
- data/test/unit/grid_panel_test.rb +4 -4
- metadata +57 -83
- data/TODO.rdoc +0 -8
- data/lib/generators/netzke/basepack_generator.rb +0 -10
- data/lib/generators/netzke/templates/assets/ts-checkbox.gif +0 -0
- data/lib/generators/netzke/templates/create_netzke_field_lists.rb +0 -18
- data/lib/netzke/active_record.rb +0 -20
- data/lib/netzke/active_record/attributes.rb +0 -259
- data/lib/netzke/active_record/combobox_options.rb +0 -16
- data/lib/netzke/active_record/relation_extensions.rb +0 -37
- data/lib/netzke/basepack/accordion_panel.rb +0 -39
- data/lib/netzke/basepack/action_column.rb +0 -68
- data/lib/netzke/basepack/action_column/javascripts/action_column.js +0 -61
- data/lib/netzke/basepack/auth_app.rb +0 -159
- data/lib/netzke/basepack/basic_app.rb +0 -7
- data/lib/netzke/basepack/border_layout_panel.rb +0 -53
- data/lib/netzke/basepack/border_layout_panel/javascripts/border_layout_panel.js +0 -40
- data/lib/netzke/basepack/data_adapters/data_mapper_adapter.rb +0 -264
- data/lib/netzke/basepack/data_adapters/sequel_adapter.rb +0 -260
- data/lib/netzke/basepack/form_panel.rb +0 -144
- data/lib/netzke/basepack/form_panel/fields.rb +0 -208
- data/lib/netzke/basepack/form_panel/javascripts/misc.js +0 -4
- data/lib/netzke/basepack/form_panel/services.rb +0 -142
- data/lib/netzke/basepack/grid_panel.rb +0 -441
- data/lib/netzke/basepack/grid_panel/columns.rb +0 -400
- data/lib/netzke/basepack/grid_panel/javascripts/rows-dd.js +0 -281
- data/lib/netzke/basepack/grid_panel/record_form_window.rb +0 -41
- data/lib/netzke/basepack/grid_panel/services.rb +0 -235
- data/lib/netzke/basepack/panel.rb +0 -11
- data/lib/netzke/basepack/wrapper.rb +0 -28
- data/lib/netzke/data_mapper.rb +0 -18
- data/lib/netzke/data_mapper/attributes.rb +0 -273
- data/lib/netzke/data_mapper/combobox_options.rb +0 -11
- data/lib/netzke/data_mapper/relation_extensions.rb +0 -38
- data/lib/netzke/sequel.rb +0 -18
- data/lib/netzke/sequel/attributes.rb +0 -274
- data/lib/netzke/sequel/combobox_options.rb +0 -10
- data/lib/netzke/sequel/relation_extensions.rb +0 -40
- data/locales/zh-cn.yml +0 -79
- data/test/basepack_test_app/app/components/book_form_with_custom_fields.rb +0 -21
- data/test/basepack_test_app/app/components/book_grid_with_column_actions.rb +0 -15
- data/test/basepack_test_app/app/components/book_grid_with_defaults.rb +0 -6
- data/test/basepack_test_app/app/components/book_paging_form_panel.rb +0 -22
- data/test/basepack_test_app/app/components/generic_user_form.rb +0 -12
- data/test/basepack_test_app/app/components/simple_accordion.rb +0 -11
- data/test/basepack_test_app/app/components/simple_tab_panel.rb +0 -11
- data/test/basepack_test_app/app/components/simple_wrapper.rb +0 -7
- data/test/basepack_test_app/app/components/some_accordion_panel.rb +0 -22
- data/test/basepack_test_app/app/presenters/forms/generic_user.rb +0 -6
- data/test/basepack_test_app/app/views/components/loadable_window.html.erb +0 -9
- data/test/basepack_test_app/app/views/components/simple_panel.html.erb +0 -1
- data/test/basepack_test_app/features/components_in_view.feature +0 -11
- data/test/basepack_test_app/features/simple_panel.feature +0 -11
- data/test/basepack_test_app/features/validations_in_grid.feature +0 -13
- data/test/basepack_test_app/features/virtual_attributes.feature +0 -16
- data/test/basepack_test_app/spec/components/form_panel_spec.rb +0 -53
- data/test/basepack_test_app/spec/components/grid_panel_spec.rb +0 -10
- data/test/basepack_test_app/spec/data_adapter/adapter_spec.rb +0 -68
- data/test/basepack_test_app/spec/data_adapter/attributes_spec.rb +0 -56
- data/test/basepack_test_app/spec/data_adapter/relation_extensions_spec.rb +0 -125
- data/test/basepack_test_app/spec/factories.rb +0 -28
- data/test/basepack_test_app/spec/spec_helper.rb +0 -39
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module Netzke::Basepack::DataAdapters
|
|
2
|
+
# Implementation of Netzke::Basepack::DataAdapters::AbstractAdapter
|
|
2
3
|
class ActiveRecordAdapter < AbstractAdapter
|
|
3
4
|
def self.for_class?(model_class)
|
|
4
5
|
model_class <= ActiveRecord::Base
|
|
@@ -8,19 +9,15 @@ module Netzke::Basepack::DataAdapters
|
|
|
8
9
|
@model_class.primary_key.to_s
|
|
9
10
|
end
|
|
10
11
|
|
|
11
|
-
def attr_type(attr_name)
|
|
12
|
-
@model_class.association_attr?(attr_name) ? :integer : (@model_class.columns_hash[attr_name.to_s].try(:type) || :string)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
12
|
def model_attributes
|
|
16
13
|
@model_class.column_names.map do |column_name|
|
|
17
|
-
{:
|
|
14
|
+
{name: column_name, attr_type: @model_class.columns_hash[column_name].type}.tap do |c|
|
|
18
15
|
|
|
19
16
|
# If it's named as foreign key of some association, then it's an association column
|
|
20
|
-
assoc = @model_class.reflect_on_all_associations.detect { |a|
|
|
17
|
+
assoc = @model_class.reflect_on_all_associations.detect { |a| a.foreign_key == c[:name] }
|
|
21
18
|
|
|
22
19
|
if assoc && !assoc.options[:polymorphic]
|
|
23
|
-
candidates = %w{name title label} <<
|
|
20
|
+
candidates = %w{name title label} << assoc.foreign_key
|
|
24
21
|
assoc_method = candidates.detect{|m| (assoc.klass.instance_methods.map(&:to_s) + assoc.klass.column_names).include?(m) }
|
|
25
22
|
c[:name] = "#{assoc.name}__#{assoc_method}"
|
|
26
23
|
end
|
|
@@ -33,6 +30,28 @@ module Netzke::Basepack::DataAdapters
|
|
|
33
30
|
end
|
|
34
31
|
end
|
|
35
32
|
|
|
33
|
+
# WIP
|
|
34
|
+
def attribute_names
|
|
35
|
+
@model_class.column_names
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def primary_key_attr?(a)
|
|
39
|
+
a[:name].to_s == @model_class.primary_key.to_s
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def human_attribute_name(attr_name)
|
|
43
|
+
@model_class.human_attribute_name(attr_name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def primary_key
|
|
47
|
+
@model_class.primary_key
|
|
48
|
+
end
|
|
49
|
+
## E_WIP
|
|
50
|
+
|
|
51
|
+
def attr_type(attr_name)
|
|
52
|
+
association_attr?(attr_name) ? :integer : (@model_class.columns_hash[attr_name.to_s].try(:type) || :string)
|
|
53
|
+
end
|
|
54
|
+
|
|
36
55
|
def get_records(params, columns=[])
|
|
37
56
|
# build initial relation based on passed params
|
|
38
57
|
relation = get_relation(params)
|
|
@@ -90,7 +109,7 @@ module Netzke::Basepack::DataAdapters
|
|
|
90
109
|
end
|
|
91
110
|
end
|
|
92
111
|
|
|
93
|
-
def
|
|
112
|
+
def virtual_attribute?(c)
|
|
94
113
|
assoc_name, asso = c[:name].split('__')
|
|
95
114
|
assoc, assoc_method = assoc_and_assoc_method_for_attr(c[:name])
|
|
96
115
|
|
|
@@ -101,50 +120,35 @@ module Netzke::Basepack::DataAdapters
|
|
|
101
120
|
end
|
|
102
121
|
end
|
|
103
122
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
query = method_options[:query]
|
|
123
|
+
def combo_data(attr, query = "")
|
|
124
|
+
assoc, assoc_method = assoc_and_assoc_method_for_attr(attr[:name])
|
|
107
125
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if options
|
|
111
|
-
query ? options.select{ |o| o.index(/^#{query}/) }.map{ |el| [el] } : options
|
|
112
|
-
else
|
|
113
|
-
assoc, assoc_method = assoc_and_assoc_method_for_attr(column[:name])
|
|
114
|
-
|
|
115
|
-
if assoc
|
|
116
|
-
# Options for an asssociation attribute
|
|
117
|
-
|
|
118
|
-
relation = assoc.klass.scoped
|
|
126
|
+
if assoc
|
|
127
|
+
# Options for an asssociation attribute
|
|
119
128
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if assoc.klass.column_names.include?(assoc_method)
|
|
123
|
-
# apply query
|
|
124
|
-
relation = relation.where(["#{assoc_method} like ?", "%#{query}%"]) if query.present?
|
|
125
|
-
relation.all.map{ |r| [r.id, r.send(assoc_method)] }
|
|
126
|
-
else
|
|
127
|
-
relation.all.map{ |r| [r.id, r.send(assoc_method)] }.select{ |id,value| value =~ /^#{query}/ }
|
|
128
|
-
end
|
|
129
|
+
relation = assoc.klass.scoped
|
|
130
|
+
relation = relation.extend_with(attr[:scope]) if attr[:scope]
|
|
129
131
|
|
|
132
|
+
if assoc.klass.column_names.include?(assoc_method)
|
|
133
|
+
# apply query
|
|
134
|
+
relation = relation.where(["#{assoc_method} like ?", "%#{query}%"]) if query.present?
|
|
135
|
+
relation.all.map{ |r| [r.id, r.send(assoc_method)] }
|
|
130
136
|
else
|
|
131
|
-
#
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
# ensure it is an array-in-array, as Ext will fail otherwise
|
|
135
|
-
raise RuntimeError, "netzke_combo_options_for should return an Array" unless res.kind_of? Array
|
|
136
|
-
return [[]] if res.empty?
|
|
137
|
-
|
|
138
|
-
unless res.first.kind_of? Array
|
|
139
|
-
res=res.map do |v|
|
|
140
|
-
[v]
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
return res
|
|
137
|
+
# an expensive search!
|
|
138
|
+
relation.all.map{ |r| [r.id, r.send(assoc_method)] }.select{ |id,value| value =~ /^#{query}/ }
|
|
144
139
|
end
|
|
140
|
+
|
|
141
|
+
else
|
|
142
|
+
distinct_combo_values(attr, query)
|
|
145
143
|
end
|
|
146
144
|
end
|
|
147
145
|
|
|
146
|
+
def distinct_combo_values(attr, query)
|
|
147
|
+
records = query.empty? ? @model_class.find_by_sql("select distinct #{attr[:name]} from #{@model_class.table_name}") : @model_class.find_by_sql("select distinct #{attr[:name]} from #{@model_class.table_name} where #{attr[:name]} like '#{query}%'")
|
|
148
|
+
records.map{|r| [r.send(attr[:name]), r.send(attr[:name])]}
|
|
149
|
+
end
|
|
150
|
+
protected :distinct_combo_values
|
|
151
|
+
|
|
148
152
|
def foreign_key_for assoc_name
|
|
149
153
|
@model_class.reflect_on_association(assoc_name.to_sym).foreign_key
|
|
150
154
|
end
|
|
@@ -184,12 +188,110 @@ module Netzke::Basepack::DataAdapters
|
|
|
184
188
|
end
|
|
185
189
|
end
|
|
186
190
|
|
|
191
|
+
def record_to_array(r, attrs)
|
|
192
|
+
[].tap do |res|
|
|
193
|
+
attrs.each do |a|
|
|
194
|
+
res << record_value_for_attribute(r, a, a[:nested_attribute]) if a[:included] != false # :included ever used?..
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def record_to_hash(r, attrs)
|
|
200
|
+
{}.tap do |res|
|
|
201
|
+
attrs.each do |a|
|
|
202
|
+
res[a[:name].to_sym] = record_value_for_attribute(r, a, a[:nested_attribute]) if a[:included] != false
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# def assoc_values(r, attr_hash) #:nodoc:
|
|
208
|
+
# @_assoc_values ||= {}.tap do |values|
|
|
209
|
+
# attr_hash.each_pair do |name,c|
|
|
210
|
+
# values[name] = record_value_for_attribute(r, c, true) if association_attr?(c)
|
|
211
|
+
# end
|
|
212
|
+
# end
|
|
213
|
+
# end
|
|
214
|
+
|
|
215
|
+
def record_value_for_attribute(r, a, through_association = false)
|
|
216
|
+
v = if a[:getter]
|
|
217
|
+
a[:getter].call(r)
|
|
218
|
+
elsif r.respond_to?("#{a[:name]}")
|
|
219
|
+
r.send("#{a[:name]}")
|
|
220
|
+
elsif association_attr?(a)
|
|
221
|
+
split = a[:name].to_s.split(/\.|__/)
|
|
222
|
+
assoc = @model_class.reflect_on_association(split.first.to_sym)
|
|
223
|
+
if through_association
|
|
224
|
+
split.inject(r) do |r,m| # TODO: do we really need to descend deeper than 1 level?
|
|
225
|
+
if r.respond_to?(m)
|
|
226
|
+
r.send(m)
|
|
227
|
+
else
|
|
228
|
+
logger.debug "Netzke::Basepack: Wrong attribute name: #{a[:name]}" unless r.nil?
|
|
229
|
+
nil
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
else
|
|
233
|
+
r.send("#{assoc.options[:foreign_key] || assoc.name.to_s.foreign_key}")
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# a work-around for to_json not taking the current timezone into account when serializing ActiveSupport::TimeWithZone
|
|
238
|
+
v = v.to_datetime.to_s(:db) if [ActiveSupport::TimeWithZone].include?(v.class)
|
|
239
|
+
|
|
240
|
+
v
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def set_record_value_for_attribute(r, a, v, role = :default)
|
|
244
|
+
v = v.to_time_in_current_zone if v.is_a?(Date) # convert Date to Time
|
|
245
|
+
|
|
246
|
+
if a[:setter]
|
|
247
|
+
a[:setter].call(r, v)
|
|
248
|
+
elsif r.respond_to?("#{a[:name]}=") && attribute_mass_assignable?(a[:name], role)
|
|
249
|
+
r.send("#{a[:name]}=", v)
|
|
250
|
+
elsif association_attr?(a)
|
|
251
|
+
split = a[:name].to_s.split(/\.|__/)
|
|
252
|
+
if a[:nested_attribute]
|
|
253
|
+
# We want:
|
|
254
|
+
# set_value_for_attribute({:name => :assoc_1__assoc_2__method, :nested_attribute => true}, 100)
|
|
255
|
+
# =>
|
|
256
|
+
# r.assoc_1.assoc_2.method = 100
|
|
257
|
+
split.inject(r) { |r,m| m == split.last ? (r && r.send("#{m}=", v) && r.save) : r.send(m) }
|
|
258
|
+
else
|
|
259
|
+
if split.size == 2
|
|
260
|
+
# search for association and assign it to r
|
|
261
|
+
assoc = @model_class.reflect_on_association(split.first.to_sym)
|
|
262
|
+
assoc_method = split.last
|
|
263
|
+
if assoc
|
|
264
|
+
if assoc.macro == :has_one
|
|
265
|
+
assoc_instance = r.send(assoc.name)
|
|
266
|
+
if assoc_instance
|
|
267
|
+
assoc_instance.send("#{assoc_method}=", v)
|
|
268
|
+
assoc_instance.save # what should we do when this fails?..
|
|
269
|
+
else
|
|
270
|
+
# what should we do in this case?
|
|
271
|
+
end
|
|
272
|
+
else
|
|
273
|
+
|
|
274
|
+
# set the foreign key to the passed value
|
|
275
|
+
# not that if a negative value is passed, we reset the association (set it to nil)
|
|
276
|
+
r.send("#{assoc.foreign_key}=", v.to_i < 0 ? nil : v) if attribute_mass_assignable?(assoc.foreign_key, role)
|
|
277
|
+
end
|
|
278
|
+
else
|
|
279
|
+
logger.debug "Netzke::Basepack: Association #{assoc} is not known for class #{@data_class}"
|
|
280
|
+
end
|
|
281
|
+
else
|
|
282
|
+
logger.debug "Netzke::Basepack: Wrong attribute name: #{a[:name]}"
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
187
288
|
# Returns association and association method for a column
|
|
188
289
|
def assoc_and_assoc_method_for_attr(column_name)
|
|
189
290
|
assoc_name, assoc_method = column_name.split('__')
|
|
190
291
|
assoc = @model_class.reflect_on_association(assoc_name.to_sym) if assoc_method
|
|
191
292
|
[assoc, assoc_method]
|
|
192
293
|
end
|
|
294
|
+
protected :assoc_and_assoc_method_for_attr
|
|
193
295
|
|
|
194
296
|
|
|
195
297
|
# An ActiveRecord::Relation instance encapsulating all the necessary conditions.
|
|
@@ -200,11 +302,6 @@ module Netzke::Basepack::DataAdapters
|
|
|
200
302
|
|
|
201
303
|
relation = apply_column_filters(relation, params[:filter]) if params[:filter]
|
|
202
304
|
|
|
203
|
-
if params[:extra_conditions]
|
|
204
|
-
extra_conditions = normalize_extra_conditions(ActiveSupport::JSON.decode(params[:extra_conditions]))
|
|
205
|
-
relation = relation.extend_with_netzke_conditions(extra_conditions) if params[:extra_conditions]
|
|
206
|
-
end
|
|
207
|
-
|
|
208
305
|
query = params[:query] && ActiveSupport::JSON.decode(params[:query])
|
|
209
306
|
|
|
210
307
|
if query.present?
|
|
@@ -223,6 +320,7 @@ module Netzke::Basepack::DataAdapters
|
|
|
223
320
|
|
|
224
321
|
relation
|
|
225
322
|
end
|
|
323
|
+
protected :get_relation
|
|
226
324
|
|
|
227
325
|
# Parses and applies grid column filters, calling consequent "where" methods on the passed relation.
|
|
228
326
|
# Returns the updated relation.
|
|
@@ -282,6 +380,7 @@ module Netzke::Basepack::DataAdapters
|
|
|
282
380
|
|
|
283
381
|
res
|
|
284
382
|
end
|
|
383
|
+
protected :apply_column_filters
|
|
285
384
|
|
|
286
385
|
def predicates_for_and_conditions(conditions)
|
|
287
386
|
return nil if conditions.empty?
|
|
@@ -303,6 +402,12 @@ module Netzke::Basepack::DataAdapters
|
|
|
303
402
|
# join them by AND
|
|
304
403
|
predicates[1..-1].inject(predicates.first){ |r,p| r.and(p) }
|
|
305
404
|
end
|
|
405
|
+
protected :predicates_for_and_conditions
|
|
306
406
|
|
|
407
|
+
# Whether an attribute is mass assignable. As second argument optionally takes the role.
|
|
408
|
+
def attribute_mass_assignable?(attr_name, role = :default)
|
|
409
|
+
@model_class.accessible_attributes(role).empty? ? !@model_class.protected_attributes(role).include?(attr_name.to_s) : @model_class.accessible_attributes(role).include?(attr_name.to_s)
|
|
410
|
+
end
|
|
411
|
+
protected :attribute_mass_assignable?
|
|
307
412
|
end
|
|
308
413
|
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
module Netzke
|
|
2
|
+
module Basepack
|
|
3
|
+
# Because Form allows for arbitrary layout of fields, we need to have all fields configured in one place (the +fields+ method), and then have references to those fields from +items+.
|
|
4
|
+
module Fields
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
# Items with normalized fields (i.e. containing all the necessary attributes needed by Ext.form.Form to render a field)
|
|
8
|
+
def items
|
|
9
|
+
config.items || data_class && data_adapter.model_attributes || []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Hash of fully configured fields, that are referenced in the items. E.g.:
|
|
13
|
+
# {
|
|
14
|
+
# :role__name => {:xtype => 'netzkeremotecombo', :disabled => true, :value => "admin"},
|
|
15
|
+
# :created_at => {:xtype => 'datetime', :disabled => true, :value => "2010-10-10 10:10"}
|
|
16
|
+
# }
|
|
17
|
+
def fields
|
|
18
|
+
@fields ||= begin
|
|
19
|
+
# extract incomplete field configs from +config+
|
|
20
|
+
flds = fields_from_config
|
|
21
|
+
|
|
22
|
+
primary_key = data_adapter.primary_key_name
|
|
23
|
+
flds[primary_key.to_sym] ||= {name: primary_key}
|
|
24
|
+
|
|
25
|
+
# and merged them with fields from the model
|
|
26
|
+
deep_merge_existing_fields(flds, fields_from_model) if data_adapter
|
|
27
|
+
flds
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The array of fields as specified on the model level (using +netzke_attribute+ and alike)
|
|
32
|
+
def fields_array_from_model
|
|
33
|
+
data_adapter && data_adapter.model_attributes
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Hash of fields as specified on the model level
|
|
37
|
+
def fields_from_model
|
|
38
|
+
@fields_from_model ||= fields_array_from_model && fields_array_from_model.inject({}){ |hsh, f| hsh.merge(f[:name].to_sym => f) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Hash of normalized field configs extracted from :items, e.g.:
|
|
42
|
+
#
|
|
43
|
+
# {:role__name => {:xtype => "netzkeremotecombo"}, :password => {:xtype => "passwordfield"}}
|
|
44
|
+
def fields_from_config
|
|
45
|
+
@fields_from_config || (normalize_config || true) && @fields_from_config
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
protected
|
|
49
|
+
|
|
50
|
+
# This is where we expand our basic field config with all the defaults
|
|
51
|
+
def extend_item(field)
|
|
52
|
+
field = super
|
|
53
|
+
|
|
54
|
+
if is_field_config?(field)
|
|
55
|
+
# field can only be a string, a symbol, or a hash
|
|
56
|
+
if field.is_a?(Hash)
|
|
57
|
+
field = field.dup # we don't want to modify original hash
|
|
58
|
+
return field if field[:no_binding] # stop here if no normalization is needed
|
|
59
|
+
field[:name] = field[:name].to_s if field[:name] # all names should be strings
|
|
60
|
+
else
|
|
61
|
+
field = {:name => field.to_s}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# right place?
|
|
65
|
+
@fields_from_config[field[:name].to_sym] = field
|
|
66
|
+
|
|
67
|
+
field_from_model = fields_from_model && fields_from_model[field[:name].to_sym]
|
|
68
|
+
|
|
69
|
+
field_from_model && field.merge!(field_from_model)
|
|
70
|
+
|
|
71
|
+
detect_association_with_method(field) # xtype for an association field
|
|
72
|
+
set_default_field_label(field)
|
|
73
|
+
set_default_field_xtype(field) if field[:xtype].nil?
|
|
74
|
+
set_default_read_only(field)
|
|
75
|
+
|
|
76
|
+
# provide our special combobox with our id
|
|
77
|
+
field[:parent_id] = self.js_id if field[:xtype] == :netzkeremotecombo
|
|
78
|
+
|
|
79
|
+
field[:hidden] = field[:hide_label] = true if field[:hidden].nil? && data_adapter.try(:primary_key_attr?, field)
|
|
80
|
+
|
|
81
|
+
# checkbox setup
|
|
82
|
+
if field[:attr_type] == :boolean
|
|
83
|
+
field[:checked] = field[:value]
|
|
84
|
+
field[:unchecked_value] = false
|
|
85
|
+
field[:input_value] = true if field[:attr_type] == :boolean
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# date field format
|
|
89
|
+
if field[:attr_type] = :date
|
|
90
|
+
field[:submit_format] = "Y-m-d"
|
|
91
|
+
field[:format] ||= "Y-m-d"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
field
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Sets the proper xtype of an asociation field
|
|
99
|
+
def detect_association_with_method(c)
|
|
100
|
+
if c[:name].index('__')
|
|
101
|
+
assoc_name, method = c[:name].split('__').map(&:to_sym)
|
|
102
|
+
assoc_method_type = data_adapter.get_assoc_property_type(assoc_name, method)
|
|
103
|
+
if c[:nested_attribute]
|
|
104
|
+
c[:xtype] ||= xtype_for_attr_type(assoc_method_type)
|
|
105
|
+
else
|
|
106
|
+
c[:xtype] ||= assoc_method_type == :boolean ? xtype_for_attr_type(assoc_method_type) : xtype_for_association
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def is_field_config?(item)
|
|
112
|
+
item.is_a?(Symbol) || (item.is_a?(Hash) && item[:name]) # && !is_component_config?(item)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Deeply merges only those key/values at the top level that are already there
|
|
116
|
+
def deep_merge_existing_fields(dest, src)
|
|
117
|
+
dest.each_pair do |k,v|
|
|
118
|
+
v.deep_merge!(src[k] || {})
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def set_default_field_label(c)
|
|
123
|
+
# multiple spaces (in case of association attrs) get replaced with one
|
|
124
|
+
c[:field_label] ||= data_class ? data_class.human_attribute_name(c[:name]) : c[:name].humanize
|
|
125
|
+
c[:field_label].gsub!(/\s+/, " ")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def set_default_field_xtype(field)
|
|
129
|
+
field[:xtype] = xtype_for_attr_type(field[:attr_type]) unless xtype_for_attr_type(field[:attr_type]).nil?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def set_default_read_only(field)
|
|
133
|
+
enabled_if = !data_adapter || data_adapter.attribute_names.include?(field[:name])
|
|
134
|
+
enabled_if ||= data_class.instance_methods.map(&:to_s).include?("#{field[:name]}=")
|
|
135
|
+
enabled_if ||= record && record.respond_to?("#{field[:name]}=")
|
|
136
|
+
enabled_if ||= association_attr?(field[:name])
|
|
137
|
+
|
|
138
|
+
field[:read_only] = !enabled_if if field[:read_only].nil?
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def attr_type_to_xtype_map
|
|
142
|
+
{
|
|
143
|
+
:integer => :numberfield,
|
|
144
|
+
:boolean => config[:multi_edit] ? :tricheckbox : :checkboxfield,
|
|
145
|
+
:date => :datefield,
|
|
146
|
+
:datetime => :xdatetime,
|
|
147
|
+
:text => :textarea,
|
|
148
|
+
:json => :jsonfield,
|
|
149
|
+
:string => :textfield
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def xtype_for_attr_type(type)
|
|
154
|
+
attr_type_to_xtype_map[type] || :textfield
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def xtype_for_association
|
|
158
|
+
:netzkeremotecombo
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require "netzke/basepack/form/services"
|
|
2
|
+
|
|
3
|
+
module Netzke
|
|
4
|
+
module Basepack
|
|
5
|
+
# Ext.form.Panel-based component
|
|
6
|
+
#
|
|
7
|
+
# == Netzke-specific config options
|
|
8
|
+
#
|
|
9
|
+
# * +model+ - name of the ActiveRecord model that provides data to this Grid.
|
|
10
|
+
# * +record+ - record to be displayd in the form. Takes precedence over +:record_id+
|
|
11
|
+
# * +record_id+ - id of the record to be displayd in the form. Also see +:record+
|
|
12
|
+
# * +items+ - the layout of the fields as an array. See "Layout configuration".
|
|
13
|
+
# * +mode+ - render mode, accepted options:
|
|
14
|
+
# * +lockable+ - makes the form panel load initially in "display mode", then lets "unlock" it, change the values, and "lock" it again, while updating the values on the server
|
|
15
|
+
# * +updateMask+ - +Ext.LoadMask+ config options for the mask shown while the form is submitting its values
|
|
16
|
+
#
|
|
17
|
+
# === Layout configuration
|
|
18
|
+
#
|
|
19
|
+
# The layout of the form is configured by supplying the +item+ config option, same way it would be configured in Ext (thus allowing for complex form layouts). Form will expand fields by looking at their names (unless +no_binding+ set to +true+ is specified for a specific field).
|
|
20
|
+
#
|
|
21
|
+
# == Endpoints
|
|
22
|
+
# Form implements the following endpoints:
|
|
23
|
+
#
|
|
24
|
+
# * +netzke_load+ - loads a record with a given id from the server, e.g.:
|
|
25
|
+
#
|
|
26
|
+
# someForm.netzkeLoad({id: 100});
|
|
27
|
+
#
|
|
28
|
+
# * +netzke_submit+ - gets called when the form gets submitted (e.g. by pressing the Apply button, or by calling onApply)
|
|
29
|
+
# * +get_combobox_options+ - gets called when a 'remote' combobox field gets expanded
|
|
30
|
+
class Form < Netzke::Base
|
|
31
|
+
include self::Services
|
|
32
|
+
include Fields
|
|
33
|
+
include DataAccessor
|
|
34
|
+
include Netzke::Core::ConfigToDslDelegator
|
|
35
|
+
|
|
36
|
+
js_configure do |c|
|
|
37
|
+
c.extend = "Ext.form.Panel"
|
|
38
|
+
c.mixin
|
|
39
|
+
c.require :comma_list_cbg, :n_radio_group, :readonly_mode
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
delegates_to_dsl :model, :record_id
|
|
43
|
+
|
|
44
|
+
def js_configure(c)
|
|
45
|
+
super
|
|
46
|
+
|
|
47
|
+
configure_locked(c)
|
|
48
|
+
configure_bbar(c)
|
|
49
|
+
|
|
50
|
+
# prepend the primary key field if not present
|
|
51
|
+
if data_adapter
|
|
52
|
+
c.items = [extend_item(data_adapter.primary_key_name.to_sym), *c.items] if !includes_primary_key_field?(c.items)
|
|
53
|
+
c.pri = data_adapter.primary_key_name
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if !c.multi_edit
|
|
57
|
+
c.record = js_record_data if record
|
|
58
|
+
else
|
|
59
|
+
c.record_id = c.record = nil if c.multi_edit # never set record_id in multi-edit mode
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
action :apply do |a|
|
|
64
|
+
a.icon = :tick
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
action :edit do |a|
|
|
68
|
+
a.icon = :pencil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
action :cancel do |a|
|
|
72
|
+
a.icon = :cancel
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def configure_locked(c)
|
|
76
|
+
c[:locked] = c[:locked].nil? ? (c[:mode] == :lockable) : c[:locked]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def configure_bbar(c)
|
|
80
|
+
c[:bbar] = [:apply] if c[:bbar].nil? && !c[:read_only]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Extra JavaScripts and stylesheets
|
|
85
|
+
css_configure do |c|
|
|
86
|
+
c.require :readonly_mode
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# A hash of record data including the meta field
|
|
90
|
+
def js_record_data
|
|
91
|
+
data_adapter.record_to_hash(record, fields.values).merge(:meta => meta_field).literalize_keys
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def record
|
|
95
|
+
@record ||= config[:record] || config[:record_id] && data_adapter && data_adapter.find_record(config[:record_id])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
protected
|
|
99
|
+
|
|
100
|
+
def includes_primary_key_field?(items)
|
|
101
|
+
!!items.detect do |item|
|
|
102
|
+
(item.is_a?(Hash) ? item[:name] : item.to_s) == data_adapter.primary_key_name
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def normalize_config
|
|
107
|
+
config.items = items
|
|
108
|
+
@fields_from_config = {}
|
|
109
|
+
super
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def self.server_side_config_options
|
|
113
|
+
super + [:scope]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def meta_field
|
|
117
|
+
{}.tap do |res|
|
|
118
|
+
assoc_values = get_association_values
|
|
119
|
+
res[:association_values] = assoc_values.literalize_keys if record && !assoc_values.empty?
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def get_association_values
|
|
124
|
+
fields_that_need_associated_values = fields.select{ |k,v| k.to_s.index("__") && !fields[k][:nested_attribute] }
|
|
125
|
+
# Take care of Ruby 1.8.7
|
|
126
|
+
if fields_that_need_associated_values.is_a?(Array)
|
|
127
|
+
fields_that_need_associated_values = fields_that_need_associated_values.inject({}){|r,(k,v)| r.merge(k => v)}
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
fields_that_need_associated_values.each_pair.inject({}) do |r,(k,v)|
|
|
131
|
+
r.merge(k => data_adapter.record_value_for_attribute(record, fields_that_need_associated_values[k], true))
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|