netzke-basepack 0.7.7 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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