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.
Files changed (192) hide show
  1. data/.travis.yml +15 -10
  2. data/{CHANGELOG.rdoc → CHANGELOG.md} +146 -110
  3. data/LICENSE +7 -1
  4. data/README.md +47 -56
  5. data/Rakefile +5 -5
  6. data/config/before-travis.sh +10 -0
  7. data/javascripts/basepack.js +0 -130
  8. data/javascripts/netzkeremotecombo.js +59 -0
  9. data/lib/netzke/basepack.rb +9 -14
  10. data/lib/netzke/basepack/accordion.rb +45 -0
  11. data/lib/netzke/basepack/active_record.rb +12 -0
  12. data/lib/netzke/basepack/active_record/relation_extensions.rb +27 -0
  13. data/lib/netzke/basepack/columns.rb +309 -0
  14. data/lib/netzke/basepack/data_accessor.rb +22 -12
  15. data/lib/netzke/basepack/data_adapters/abstract_adapter.rb +75 -11
  16. data/lib/netzke/basepack/data_adapters/active_record_adapter.rb +154 -49
  17. data/lib/netzke/basepack/fields.rb +162 -0
  18. data/lib/netzke/basepack/form.rb +136 -0
  19. data/lib/netzke/basepack/{form_panel → form}/javascripts/comma_list_cbg.js +0 -1
  20. data/lib/netzke/basepack/{form_panel/javascripts/form_panel.js → form/javascripts/form.js} +20 -26
  21. data/lib/netzke/basepack/{form_panel → form}/javascripts/n_radio_group.js +0 -1
  22. data/lib/netzke/basepack/{form_panel → form}/javascripts/readonly_mode.js +0 -0
  23. data/lib/netzke/basepack/form/services.rb +115 -0
  24. data/lib/netzke/basepack/{form_panel → form}/stylesheets/readonly_mode.css +0 -0
  25. data/lib/netzke/basepack/grid.rb +355 -0
  26. data/lib/netzke/basepack/{grid_panel → grid}/javascripts/advanced_search.js +1 -1
  27. data/lib/netzke/basepack/{grid_panel → grid}/javascripts/check_column_fix.js +0 -0
  28. data/lib/netzke/basepack/{grid_panel → grid}/javascripts/edit_in_form.js +3 -3
  29. data/lib/netzke/basepack/{grid_panel → grid}/javascripts/event_handling.js +5 -2
  30. data/lib/netzke/basepack/{grid_panel/javascripts/grid_panel.js → grid/javascripts/grid.js} +120 -132
  31. data/lib/netzke/basepack/{grid_panel → grid}/javascripts/misc.js +0 -0
  32. data/lib/netzke/basepack/grid/services.rb +216 -0
  33. data/lib/netzke/basepack/item_persistence.rb +44 -0
  34. data/lib/netzke/basepack/item_persistence/events_plugin.rb +47 -0
  35. data/lib/netzke/basepack/{paging_form_panel.rb → paging_form.rb} +24 -30
  36. data/lib/netzke/basepack/{paging_form_panel/javascripts/paging_form_panel.js → paging_form/javascripts/paging_form.js} +2 -4
  37. data/lib/netzke/basepack/query_builder.rb +44 -73
  38. data/lib/netzke/basepack/query_builder/javascripts/query_builder.js +16 -2
  39. data/lib/netzke/basepack/record_form_window.rb +67 -0
  40. data/lib/netzke/basepack/search_panel.rb +22 -24
  41. data/lib/netzke/basepack/search_panel/javascripts/condition_field.js +2 -2
  42. data/lib/netzke/basepack/search_window.rb +47 -53
  43. data/lib/netzke/basepack/simple_app.rb +10 -13
  44. data/lib/netzke/basepack/simple_app/javascripts/simple_app.js +2 -8
  45. data/lib/netzke/basepack/tab_panel.rb +5 -4
  46. data/lib/netzke/basepack/tab_panel/javascripts/tab_panel.js +5 -5
  47. data/lib/netzke/basepack/version.rb +2 -2
  48. data/lib/netzke/basepack/viewport.rb +16 -0
  49. data/lib/netzke/basepack/window.rb +27 -18
  50. data/lib/netzke/basepack/window/javascripts/window.js +7 -1
  51. data/lib/netzke/basepack/wrap_lazy_loaded.rb +18 -18
  52. data/locales/en.yml +40 -24
  53. data/netzke-basepack.gemspec +51 -82
  54. data/stylesheets/basepack.css +0 -41
  55. data/test/basepack_test_app/Gemfile +9 -46
  56. data/test/basepack_test_app/Gemfile.lock +61 -96
  57. data/test/basepack_test_app/app/components/author_form.rb +8 -5
  58. data/test/basepack_test_app/app/components/author_grid.rb +2 -2
  59. data/test/basepack_test_app/app/components/book_form.rb +34 -31
  60. data/test/basepack_test_app/app/components/book_form_with_defaults.rb +6 -7
  61. data/test/basepack_test_app/app/components/book_form_with_file_upload.rb +10 -0
  62. data/test/basepack_test_app/app/components/book_form_with_nested_attributes.rb +5 -6
  63. data/test/basepack_test_app/app/components/book_grid.rb +19 -8
  64. data/test/basepack_test_app/app/components/book_grid_filtering.rb +4 -7
  65. data/test/basepack_test_app/app/components/book_grid_loader.rb +28 -15
  66. data/test/basepack_test_app/app/components/book_grid_with_custom_columns.rb +45 -21
  67. data/test/basepack_test_app/app/components/book_grid_with_default_values.rb +26 -8
  68. data/test/basepack_test_app/app/components/book_grid_with_excluded_columns.rb +11 -0
  69. data/test/basepack_test_app/app/components/book_grid_with_extra_feedback.rb +2 -2
  70. data/test/basepack_test_app/app/components/book_grid_with_extra_filters.rb +7 -6
  71. data/test/basepack_test_app/app/components/book_grid_with_mass_assignment_security.rb +9 -0
  72. data/test/basepack_test_app/app/components/book_grid_with_nested_attributes.rb +9 -9
  73. data/test/basepack_test_app/app/components/book_grid_with_overridden_columns.rb +5 -3
  74. data/test/basepack_test_app/app/components/book_grid_with_paging.rb +6 -8
  75. data/test/basepack_test_app/app/components/book_grid_with_persistence.rb +6 -4
  76. data/test/basepack_test_app/app/components/book_grid_with_scope.rb +6 -0
  77. data/test/basepack_test_app/app/components/book_grid_with_scoped_authors.rb +10 -7
  78. data/test/basepack_test_app/app/components/book_grid_with_virtual_attributes.rb +21 -13
  79. data/test/basepack_test_app/app/components/book_paging_form.rb +21 -0
  80. data/test/basepack_test_app/app/components/book_query_builder.rb +7 -6
  81. data/test/basepack_test_app/app/components/book_with_custom_primary_key_grid.rb +6 -7
  82. data/test/basepack_test_app/app/components/books_bound_to_author.rb +9 -7
  83. data/test/basepack_test_app/app/components/border_layout_panel_with_persistence.rb +12 -0
  84. data/test/basepack_test_app/app/components/double_book_grid.rb +19 -14
  85. data/test/basepack_test_app/app/components/form_without_model.rb +15 -16
  86. data/test/basepack_test_app/app/components/grid_with_initial_sorting.rb +7 -0
  87. data/test/basepack_test_app/app/components/grid_with_inline_data.rb +7 -0
  88. data/test/basepack_test_app/app/components/paging_form_with_search.rb +2 -2
  89. data/test/basepack_test_app/app/components/panel_with_persistent_regions.rb +35 -0
  90. data/test/basepack_test_app/app/components/query_builder.rb +7 -0
  91. data/test/basepack_test_app/app/components/simple_panel.rb +16 -11
  92. data/test/basepack_test_app/app/components/simple_window.rb +7 -6
  93. data/test/basepack_test_app/app/components/some_accordion.rb +18 -0
  94. data/test/basepack_test_app/app/components/some_auth_app.rb +5 -5
  95. data/test/basepack_test_app/app/components/some_border_layout.rb +20 -20
  96. data/test/basepack_test_app/app/components/some_search_panel.rb +6 -0
  97. data/test/basepack_test_app/app/components/some_simple_app.rb +30 -16
  98. data/test/basepack_test_app/app/components/some_tab_panel.rb +18 -15
  99. data/test/basepack_test_app/app/components/user_form.rb +18 -16
  100. data/test/basepack_test_app/app/components/user_form_with_default_fields.rb +5 -6
  101. data/test/basepack_test_app/app/components/user_grid.rb +11 -6
  102. data/test/basepack_test_app/app/components/user_grid_with_customized_form_fields.rb +5 -3
  103. data/test/basepack_test_app/app/components/window_component_loader.rb +25 -21
  104. data/test/basepack_test_app/app/models/address.rb +0 -26
  105. data/test/basepack_test_app/app/models/author.rb +0 -31
  106. data/test/basepack_test_app/app/models/book.rb +1 -42
  107. data/test/basepack_test_app/app/models/book_with_custom_primary_key.rb +1 -23
  108. data/test/basepack_test_app/app/models/role.rb +0 -21
  109. data/test/basepack_test_app/app/models/user.rb +0 -24
  110. data/test/basepack_test_app/app/views/layouts/components.html.erb +1 -1
  111. data/test/basepack_test_app/config/application.rb +1 -1
  112. data/test/basepack_test_app/config/database.yml.travis +2 -6
  113. data/test/basepack_test_app/config/initializers/netzke.rb +1 -6
  114. data/test/basepack_test_app/db/schema.rb +14 -14
  115. data/test/basepack_test_app/features/accordion_panel.feature +2 -2
  116. data/test/basepack_test_app/features/form_panel.feature +7 -7
  117. data/test/basepack_test_app/features/grid_panel.feature +93 -39
  118. data/test/basepack_test_app/features/grid_panel_with_custom_primary_key.feature +2 -1
  119. data/test/basepack_test_app/features/grid_sorting.feature +30 -6
  120. data/test/basepack_test_app/features/paging_form_panel.feature +7 -7
  121. data/test/basepack_test_app/features/persistent_regions.feature +30 -0
  122. data/test/basepack_test_app/features/search_in_grid.feature +5 -5
  123. data/test/basepack_test_app/features/simple_app.feature +6 -7
  124. data/test/basepack_test_app/features/step_definitions/form_panel_steps.rb +1 -1
  125. data/test/basepack_test_app/features/step_definitions/generic_steps.rb +109 -4
  126. data/test/basepack_test_app/features/step_definitions/grid_panel_steps.rb +8 -10
  127. data/test/basepack_test_app/features/step_definitions/window_steps.rb +27 -0
  128. data/test/basepack_test_app/features/tab_panel.feature +1 -1
  129. data/test/basepack_test_app/features/window.feature +17 -0
  130. data/test/unit/accordion_panel_test.rb +2 -2
  131. data/test/unit/grid_panel_test.rb +4 -4
  132. metadata +57 -83
  133. data/TODO.rdoc +0 -8
  134. data/lib/generators/netzke/basepack_generator.rb +0 -10
  135. data/lib/generators/netzke/templates/assets/ts-checkbox.gif +0 -0
  136. data/lib/generators/netzke/templates/create_netzke_field_lists.rb +0 -18
  137. data/lib/netzke/active_record.rb +0 -20
  138. data/lib/netzke/active_record/attributes.rb +0 -259
  139. data/lib/netzke/active_record/combobox_options.rb +0 -16
  140. data/lib/netzke/active_record/relation_extensions.rb +0 -37
  141. data/lib/netzke/basepack/accordion_panel.rb +0 -39
  142. data/lib/netzke/basepack/action_column.rb +0 -68
  143. data/lib/netzke/basepack/action_column/javascripts/action_column.js +0 -61
  144. data/lib/netzke/basepack/auth_app.rb +0 -159
  145. data/lib/netzke/basepack/basic_app.rb +0 -7
  146. data/lib/netzke/basepack/border_layout_panel.rb +0 -53
  147. data/lib/netzke/basepack/border_layout_panel/javascripts/border_layout_panel.js +0 -40
  148. data/lib/netzke/basepack/data_adapters/data_mapper_adapter.rb +0 -264
  149. data/lib/netzke/basepack/data_adapters/sequel_adapter.rb +0 -260
  150. data/lib/netzke/basepack/form_panel.rb +0 -144
  151. data/lib/netzke/basepack/form_panel/fields.rb +0 -208
  152. data/lib/netzke/basepack/form_panel/javascripts/misc.js +0 -4
  153. data/lib/netzke/basepack/form_panel/services.rb +0 -142
  154. data/lib/netzke/basepack/grid_panel.rb +0 -441
  155. data/lib/netzke/basepack/grid_panel/columns.rb +0 -400
  156. data/lib/netzke/basepack/grid_panel/javascripts/rows-dd.js +0 -281
  157. data/lib/netzke/basepack/grid_panel/record_form_window.rb +0 -41
  158. data/lib/netzke/basepack/grid_panel/services.rb +0 -235
  159. data/lib/netzke/basepack/panel.rb +0 -11
  160. data/lib/netzke/basepack/wrapper.rb +0 -28
  161. data/lib/netzke/data_mapper.rb +0 -18
  162. data/lib/netzke/data_mapper/attributes.rb +0 -273
  163. data/lib/netzke/data_mapper/combobox_options.rb +0 -11
  164. data/lib/netzke/data_mapper/relation_extensions.rb +0 -38
  165. data/lib/netzke/sequel.rb +0 -18
  166. data/lib/netzke/sequel/attributes.rb +0 -274
  167. data/lib/netzke/sequel/combobox_options.rb +0 -10
  168. data/lib/netzke/sequel/relation_extensions.rb +0 -40
  169. data/locales/zh-cn.yml +0 -79
  170. data/test/basepack_test_app/app/components/book_form_with_custom_fields.rb +0 -21
  171. data/test/basepack_test_app/app/components/book_grid_with_column_actions.rb +0 -15
  172. data/test/basepack_test_app/app/components/book_grid_with_defaults.rb +0 -6
  173. data/test/basepack_test_app/app/components/book_paging_form_panel.rb +0 -22
  174. data/test/basepack_test_app/app/components/generic_user_form.rb +0 -12
  175. data/test/basepack_test_app/app/components/simple_accordion.rb +0 -11
  176. data/test/basepack_test_app/app/components/simple_tab_panel.rb +0 -11
  177. data/test/basepack_test_app/app/components/simple_wrapper.rb +0 -7
  178. data/test/basepack_test_app/app/components/some_accordion_panel.rb +0 -22
  179. data/test/basepack_test_app/app/presenters/forms/generic_user.rb +0 -6
  180. data/test/basepack_test_app/app/views/components/loadable_window.html.erb +0 -9
  181. data/test/basepack_test_app/app/views/components/simple_panel.html.erb +0 -1
  182. data/test/basepack_test_app/features/components_in_view.feature +0 -11
  183. data/test/basepack_test_app/features/simple_panel.feature +0 -11
  184. data/test/basepack_test_app/features/validations_in_grid.feature +0 -13
  185. data/test/basepack_test_app/features/virtual_attributes.feature +0 -16
  186. data/test/basepack_test_app/spec/components/form_panel_spec.rb +0 -53
  187. data/test/basepack_test_app/spec/components/grid_panel_spec.rb +0 -10
  188. data/test/basepack_test_app/spec/data_adapter/adapter_spec.rb +0 -68
  189. data/test/basepack_test_app/spec/data_adapter/attributes_spec.rb +0 -56
  190. data/test/basepack_test_app/spec/data_adapter/relation_extensions_spec.rb +0 -125
  191. data/test/basepack_test_app/spec/factories.rb +0 -28
  192. 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
- {:name => column_name, :attr_type => @model_class.columns_hash[column_name].type}.tap do |c|
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| foreign_key_for_assoc(a) == c[:name] }
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} << foreign_key_for_assoc(assoc)
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 column_virtual? c
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
- # Returns options for comboboxes in grids/forms
105
- def combobox_options_for_column(column, method_options = {})
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
- # First, check if we have options for this column defined in persistent storage
109
- options = column[:combobox_options] && column[:combobox_options].split("\n")
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
- relation = relation.extend_with(method_options[:scope]) if method_options[:scope]
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
- # Options for a non-association attribute
132
- res=@model_class.netzke_combo_options_for(column[:name], method_options)
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