motion-prime 0.8.1 → 0.8.2

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 (41) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +1 -1
  4. data/ROADMAP.md +3 -0
  5. data/files/Gemfile +1 -1
  6. data/motion-prime/api_client.rb +5 -2
  7. data/motion-prime/core_ext/kernel.rb +36 -0
  8. data/motion-prime/elements/_content_text_mixin.rb +32 -11
  9. data/motion-prime/elements/base_element.rb +33 -16
  10. data/motion-prime/elements/draw.rb +10 -4
  11. data/motion-prime/elements/draw/image.rb +26 -15
  12. data/motion-prime/elements/map.rb +5 -0
  13. data/motion-prime/models/_association_mixin.rb +0 -3
  14. data/motion-prime/models/_base_mixin.rb +24 -3
  15. data/motion-prime/models/_filter_mixin.rb +28 -0
  16. data/motion-prime/models/_sync_mixin.rb +13 -10
  17. data/motion-prime/models/association_collection.rb +41 -16
  18. data/motion-prime/models/errors.rb +46 -24
  19. data/motion-prime/models/model.rb +8 -0
  20. data/motion-prime/screens/_sections_mixin.rb +1 -1
  21. data/motion-prime/screens/extensions/_indicators_mixin.rb +4 -2
  22. data/motion-prime/screens/extensions/_navigation_bar_mixin.rb +1 -1
  23. data/motion-prime/screens/screen.rb +4 -0
  24. data/motion-prime/sections/_async_form_mixin.rb +12 -0
  25. data/motion-prime/sections/_async_table_mixin.rb +193 -0
  26. data/motion-prime/sections/_cell_section_mixin.rb +6 -6
  27. data/motion-prime/sections/_draw_section_mixin.rb +13 -5
  28. data/motion-prime/sections/base_section.rb +62 -36
  29. data/motion-prime/sections/form.rb +19 -35
  30. data/motion-prime/sections/form/base_field_section.rb +42 -34
  31. data/motion-prime/sections/form/static_field_section.rb +9 -0
  32. data/motion-prime/sections/table.rb +143 -201
  33. data/motion-prime/sections/table/table_delegate.rb +15 -15
  34. data/motion-prime/services/table_data_indexes.rb +12 -2
  35. data/motion-prime/styles/base.rb +1 -1
  36. data/motion-prime/styles/form.rb +6 -6
  37. data/motion-prime/version.rb +1 -1
  38. data/motion-prime/views/layout.rb +5 -2
  39. data/motion-prime/views/view_styler.rb +2 -0
  40. data/spec/models/association_collection_spec.rb +28 -6
  41. metadata +6 -2
@@ -29,31 +29,26 @@ module MotionPrime
29
29
  end
30
30
  end
31
31
 
32
- def reload_cell(section)
32
+ def reload_cell_section(section)
33
33
  field = section.name.to_sym
34
- index = field_indexes[field].split('_').map(&:to_i)
35
- path = NSIndexPath.indexPathForRow(index.last, inSection: index.first)
34
+ path = field_indexes[field]
36
35
  section.cell.try(:removeFromSuperview)
37
36
 
38
37
  fields[field] = load_field(self.class.fields_options[field])
39
- fields[field].load_section
38
+ fields[field].create_elements
40
39
  if flat_data?
41
40
  @data[path.row] = fields[field]
42
41
  else
43
42
  @data[path.section][path.row] = fields[field]
44
43
  end
45
44
 
46
- set_data_stamp(field_indexes[field])
45
+ set_data_stamp(fields[field].object_id)
47
46
 
48
47
  # table_view.beginUpdates
49
48
  table_view.reloadRowsAtIndexPaths([path], withRowAnimation: UITableViewRowAnimationNone)
50
49
  # table_view.endUpdates
51
50
  end
52
51
 
53
- def reset_data_stamps
54
- set_data_stamp(self.field_indexes.values)
55
- end
56
-
57
52
  # Returns element based on field name and element name
58
53
  #
59
54
  # Examples:
@@ -169,18 +164,13 @@ module MotionPrime
169
164
 
170
165
  def reload_data
171
166
  @groups_count = nil
167
+ init_form_fields # must be before resetting to reflect changes on @data
172
168
  reset_data
173
- init_form_fields
174
169
  reload_table_data
175
170
  end
176
171
 
177
- def reset_data
178
- super
179
- self.fields.values.each(&:clear_observers)
180
- end
181
-
182
172
  def has_many_sections?
183
- section_header_options.present? || grouped_data.count > 1
173
+ group_header_options.present? || grouped_data.count > 1
184
174
  end
185
175
 
186
176
  def render_table
@@ -188,27 +178,13 @@ module MotionPrime
188
178
  super
189
179
  end
190
180
 
191
- def reload_table_data
192
- return super unless async_data?
193
- sections = NSMutableIndexSet.new
194
- number_of_sections.times do |section_id|
195
- sections.addIndex(section_id)
196
- end
197
- table_view.reloadSections sections, withRowAnimation: UITableViewRowAnimationFade
198
- end
199
-
200
181
  # Table View Delegate
201
182
  # ---------------------
202
183
 
203
- def number_of_sections(table = nil)
184
+ def number_of_groups(table = nil)
204
185
  has_many_sections? ? grouped_data.compact.count : 1
205
186
  end
206
187
 
207
- def height_for_index(table, index)
208
- section = load_cell_by_index(index, preload: false)
209
- section.container_height
210
- end
211
-
212
188
  class << self
213
189
  def inherited(subclass)
214
190
  super
@@ -217,6 +193,11 @@ module MotionPrime
217
193
  subclass.text_view_limits = self.text_view_limits.try(:clone)
218
194
  end
219
195
 
196
+ def async_table_data(options = {})
197
+ super
198
+ self.send :include, Prime::AsyncFormMixin
199
+ end
200
+
220
201
  def field(name, options = {}, &block)
221
202
  options[:name] = name
222
203
  options[:type] ||= :string
@@ -237,9 +218,11 @@ module MotionPrime
237
218
  end
238
219
 
239
220
  private
240
- def load_sections; end
221
+ def create_section_elements; end
241
222
 
242
223
  def init_form_fields
224
+ self.fields.values.each(&:clear_observers) if fields.present?
225
+
243
226
  self.fields = {}
244
227
  self.field_indexes = {}
245
228
  self.grouped_data = []
@@ -254,7 +237,7 @@ module MotionPrime
254
237
 
255
238
  section = load_field(field)
256
239
  self.fields[key] = section
257
- self.field_indexes[key] = "#{section_id}_#{section_indexes[section_id]}"
240
+ self.field_indexes[key] = NSIndexPath.indexPathForRow(section_indexes[section_id], inSection: section_id)
258
241
  grouped_data[section_id][section_indexes[section_id]] = section
259
242
 
260
243
  section_indexes[section_id] += 1
@@ -264,8 +247,9 @@ module MotionPrime
264
247
  end
265
248
 
266
249
  def init_form_headers
267
- options = Array.wrap(self.class.section_header_options).clone
268
- self.section_header_options = options.delete_if.each_with_index { |opts, id| grouped_data[id].nil? }
250
+ options = Array.wrap(self.class.group_header_options).clone
251
+ options.compact.each { |opts| normalize_options(opts) }
252
+ self.group_header_options = options.delete_if.each_with_index { |opts, id| grouped_data[id].nil? }
269
253
  end
270
254
  end
271
255
  end
@@ -36,11 +36,11 @@ module MotionPrime
36
36
  # @param height [Integet] new height of field
37
37
  # @return [MotionPrime::BaseFieldSection]
38
38
  def update_height(height)
39
+ return if container_options[:height] == height
39
40
  container_options[:height] = height
40
- field_index = form.field_indexes[name]
41
- index = field_index.split('_').map(&:to_i)
42
- path = NSIndexPath.indexPathForRow(index.last, inSection: index.first)
43
- form.table_view.reloadRowsAtIndexPaths([path], withRowAnimation: UITableViewRowAnimationFade)
41
+ index = form.field_indexes[name]
42
+ form.send :set_data_stamp, self.object_id
43
+ form.table_view.reloadRowsAtIndexPaths([index], withRowAnimation: UITableViewRowAnimationFade)
44
44
  self
45
45
  end
46
46
 
@@ -52,18 +52,19 @@ module MotionPrime
52
52
  def observe_model_errors
53
53
  return unless observing_errors?
54
54
  on_error_change = proc { |old_value, new_value|
55
- next if old_value == new_value
56
- if @status_for_updated == :rendered
57
- reload_section
58
- else
59
- load_section!
60
- form.reload_table_data
55
+ changes = observing_errors_for.errors.changes
56
+ errors_observer_fields.each do |field|
57
+ next unless changes.has_key?(field)
58
+ if @status_for_updated == :rendered
59
+ reload_section
60
+ else
61
+ create_elements!
62
+ form.reload_table_data
63
+ end
61
64
  end
62
65
  }.weak!
63
66
 
64
- errors_observer_fields.each do |field|
65
- observe observing_errors_for.errors, observing_errors_for.errors.unique_key(field), &on_error_change
66
- end
67
+ observe observing_errors_for.errors, :info, &on_error_change
67
68
  end
68
69
 
69
70
  def dealloc
@@ -75,18 +76,32 @@ module MotionPrime
75
76
  form.name
76
77
  end
77
78
 
78
- def focus(begin_editing = true)
79
- # scroll to cell
80
- path = form.table_view.indexPathForCell(cell)
81
- form.table_view.scrollToRowAtIndexPath path,
82
- atScrollPosition: UITableViewScrollPositionTop, animated: true
79
+ def focus(begin_editing = false)
83
80
  # focus on text field
84
- return unless begin_editing
85
- elements.values.each do |element|
81
+ elements.values.detect do |element|
86
82
  if element.view.is_a?(UITextField) || element.view.is_a?(UITextView)
87
- element.view.becomeFirstResponder and return
83
+ element.view.becomeFirstResponder
88
84
  end
85
+ end if begin_editing
86
+
87
+ # scroll to cell
88
+ # path = form.table_view.indexPathForCell(cell)
89
+ # form.table_view.scrollToRowAtIndexPath path,
90
+ # atScrollPosition: UITableViewScrollPositionTop, animated: true
91
+ return self unless input_view = element(:input).try(:view)
92
+ origin = input_view.frame.origin
93
+ point = container_view.convertPoint(origin, toView: form.table_view)
94
+
95
+ nav_bar_height = screen.navigationController ? screen.navigationController.navigationBar.frame.size.height : 0
96
+ nav_bar_height += 20 # status bar height
97
+
98
+ offset = form.table_view.contentOffset
99
+ new_top_offset = point.y - nav_bar_height
100
+ unless new_top_offset == offset.y
101
+ offset.y = new_top_offset
102
+ form.table_view.setContentOffset(offset, animated: true)
89
103
  end
104
+
90
105
  self
91
106
  rescue
92
107
  NSLog("can't focus on element #{self.class_name_without_kvo}")
@@ -104,7 +119,7 @@ module MotionPrime
104
119
  end
105
120
 
106
121
  def default_label_options
107
- label_options = options[:label]
122
+ label_options = options[:label] || {}
108
123
  if label_options.has_key?(:text)
109
124
  label_options
110
125
  else
@@ -114,7 +129,7 @@ module MotionPrime
114
129
 
115
130
  def bind_text_input
116
131
  view(:input).on :change do |view|
117
- focus
132
+ focus if options[:auto_focus]
118
133
  form.on_input_change(view(:input))
119
134
  end
120
135
  end
@@ -125,9 +140,7 @@ module MotionPrime
125
140
 
126
141
  def has_errors?
127
142
  return false unless observing_errors?
128
- errors_observer_fields.any? do |field|
129
- observing_errors_for.errors[field].present?
130
- end
143
+ observing_errors_for.errors.info.slice(*errors_observer_fields).values.any?(&:present?)
131
144
  end
132
145
 
133
146
  def errors_observer_fields
@@ -145,23 +158,18 @@ module MotionPrime
145
158
  def all_errors
146
159
  return [] unless observing_errors?
147
160
 
148
- errors_observer_fields.map do |field|
149
- observing_errors_for.errors[field]
150
- end
161
+ observing_errors_for.errors.info.slice(*errors_observer_fields).values.flatten
151
162
  end
152
163
 
153
164
  def reload_section
154
165
  clear_observers
155
- form.reload_cell(self)
166
+ form.reload_cell_section(self)
156
167
  end
157
168
 
158
169
  def clear_observers
159
170
  return unless observing_errors?
160
171
  # unobserve_all cause double dealloc, following code is a replacement
161
- block = proc { |field|
162
- unobserve observing_errors_for.errors, observing_errors_for.errors.unique_key(field)
163
- }.weak!
164
- errors_observer_fields.each(&block)
172
+ unobserve observing_errors_for.errors, :info
165
173
  # TODO: clear 'on' events
166
174
  end
167
175
 
@@ -0,0 +1,9 @@
1
+ module MotionPrime
2
+ class StaticFieldSection < Section
3
+ include CellSectionMixin
4
+
5
+ def form
6
+ table
7
+ end
8
+ end
9
+ end
@@ -7,65 +7,116 @@ module MotionPrime
7
7
  include HasStyleChainBuilder
8
8
  include HasSearchBar
9
9
 
10
- class_attribute :async_data_options, :section_header_options, :pull_to_refresh_block
10
+ class_attribute :group_header_options, :pull_to_refresh_block
11
11
 
12
- attr_accessor :table_element, :did_appear, :section_headers, :section_header_options
12
+ attr_accessor :table_element, :did_appear, :group_header_sections, :group_header_options
13
13
  attr_reader :decelerating
14
14
  before_render :render_table
15
15
  after_render :init_pull_to_refresh
16
16
  delegate :init_pull_to_refresh, to: :table_delegate
17
17
 
18
+
19
+ # Return sections which will be used to render as table cells.
20
+ #
21
+ # This method should be redefined in your table section and must return array.
22
+ # @return [Array<Prime::Section>] array of sections
18
23
  def table_data
19
24
  @model || []
20
25
  end
21
26
 
22
- def dealloc
23
- Prime.logger.dealloc_message :table, self, self.table_view.to_s
24
- table_delegate.clear_delegated
25
- table_view.setDataSource nil
26
- super
27
+ # Returns cached version of table data
28
+ #
29
+ # @return [Array<Prime::Section>] cached array of sections
30
+ def data
31
+ @data || set_table_data
27
32
  end
28
33
 
29
- def async_data?
30
- self.class.async_data_options
34
+ # IMPORTANT: when you use #map in table_data,
35
+ # then #dealloc of Prime::Section will not be called to section created on that #map.
36
+ # We did not find yet why this happening, for now just using hack.
37
+ def fixed_table_data
38
+ table_data.to_enum.to_a
31
39
  end
32
40
 
33
- def data
34
- @data || set_table_data
41
+ def dealloc
42
+ Prime.logger.dealloc_message :table, self, self.table_element.try(:view).to_s
43
+ table_delegate.clear_delegated
44
+ table_view.setDataSource nil
45
+ super
35
46
  end
36
47
 
48
+ # Reset all table data and reload table view
49
+ #
50
+ # @return [Boolean] true
37
51
  def reload_data
38
52
  reset_data
39
- @async_loaded_data = table_data if async_data?
40
53
  reload_table_data
41
54
  end
42
55
 
56
+ # Reload table view
57
+ #
58
+ # @return [Boolean] true
43
59
  def reload_table_data
44
60
  table_view.reloadData
61
+ true
45
62
  end
46
63
 
64
+ # Reload table view if data was empty before.
65
+ #
66
+ # @return [Boolean] true if reload was happened
47
67
  def refresh_if_needed
48
- reload_table_data if @data.nil?
68
+ @data.nil? ? reload_table_data : false
49
69
  end
50
70
 
71
+ # Reset all table data.
72
+ #
73
+ # @return [Boolean] true
51
74
  def reset_data
52
75
  @did_appear = false
53
76
  @data = nil
54
- @async_loaded_data = nil
55
- @preloader_next_starts_from = nil
56
- @preloader_cancelled = false
57
77
  @data_stamp = nil
58
- @preloader_queue[-1] = :cancelled if @preloader_queue.present?
59
- end
60
-
61
- def add_cells(cells)
62
- prepare_table_cells(cells)
78
+ @reusable_cells.each do |object_id, cell|
79
+ cell.reuseIdentifier = nil
80
+ end if @reusable_cells
81
+ @reusable_cells = nil
82
+ true
83
+ end
84
+
85
+ # Add cells to table view and reload table view.
86
+ #
87
+ # @param cell sections [Prime::Section, Array<Prime::Section>] cells which will be added to table view.
88
+ # @return [Boolean] true
89
+ def add_cell_sections(sections)
90
+ prepare_table_cell_sections(sections)
63
91
  @data ||= []
64
- @data += cells
92
+ @data += sections
65
93
  reload_table_data
66
94
  end
67
95
 
68
- def reload_cell(section)
96
+ # Delete cells from table data and remove them from table view with animation.
97
+ #
98
+ # @param cell sections [Prime::Section, Array<Prime::Section>] cells which will be removed from table view.
99
+ # @return [Array<NSIndexPath>] index paths of removed cells.
100
+ def delete_cell_sections(sections)
101
+ paths = []
102
+ Array.wrap(sections).each do |section|
103
+ paths << NSIndexPath.indexPathForRow(@data.index(section), inSection: 0)
104
+ delete_from_data(section)
105
+ end
106
+ table_view.beginUpdates
107
+ table_view.deleteRowsAtIndexPaths(paths, withRowAnimation: UITableViewRowAnimationLeft)
108
+ table_view.endUpdates
109
+ paths
110
+ end
111
+
112
+ def delete_from_data(section)
113
+ # section will not deallocate if you'll just write @data.delete(section)
114
+ index = @data.index(section)
115
+ @data[index] = nil
116
+ @data.delete_at(index)
117
+ end
118
+
119
+ def reload_cell_section(section)
69
120
  section.elements.values.each(&:compute_options!)
70
121
  section.cached_draw_image = nil
71
122
  # TODO: reset date stamps, reload row
@@ -81,7 +132,7 @@ module MotionPrime
81
132
  {common: base_styles, specific: item_styles}
82
133
  end
83
134
 
84
- def cell_styles(cell)
135
+ def cell_section_styles(section)
85
136
  # type = [`cell`, `header`, `field`]
86
137
 
87
138
  # UserFormSection example: field :email, type: :string
@@ -93,29 +144,29 @@ module MotionPrime
93
144
  # CategoriesTableSection example: table is a `CategoryTableSection`, cell is a `CategoryTitleSection`, element :icon, type: :image
94
145
  # table_name = `categories`
95
146
  # type = `cell` (always true)
96
- # table_cell_name = `title`
97
- type = cell.respond_to?(:cell_type) ? cell.cell_type : 'cell'
147
+ # table_cell_section_name = `title`
148
+ type = section.respond_to?(:cell_type) ? section.cell_type : 'cell'
98
149
  suffixes = [type]
99
- if cell.is_a?(BaseFieldSection)
100
- suffixes << cell.default_name
150
+ if section.is_a?(BaseFieldSection)
151
+ suffixes << section.default_name
101
152
  end
102
153
 
103
154
  styles = {}
104
155
  # table: base_table_<type>
105
156
  # form: base_form_<type>, base_form_<field_type>
106
157
  styles[:common] = build_styles_chain(table_styles[:common], suffixes)
107
- if cell.is_a?(BaseFieldSection)
158
+ if section.is_a?(BaseFieldSection)
108
159
  # form cell: _<type>_<field_name> = `_field_email`
109
- suffixes << :"#{type}_#{cell.name}" if cell.name
110
- elsif cell.respond_to?(:cell_name) # cell section came from table
111
- # table cell: _<table_cell_name> = `_title`
112
- suffixes << cell.cell_name
160
+ suffixes << :"#{type}_#{section.name}" if section.name
161
+ elsif section.respond_to?(:cell_section_name) # cell section came from table
162
+ # table cell: _<table_cell_section_name> = `_title`
163
+ suffixes << section.cell_section_name
113
164
  end
114
- # table: <table_name>_table_<type>, <table_name>_table_<table_cell_name> = `categories_table_cell`, `categories_table_title`
165
+ # table: <table_name>_table_<type>, <table_name>_table_<table_cell_section_name> = `categories_table_cell`, `categories_table_title`
115
166
  # form: <form_name>_form_<type>, <form_name>_form_<field_type>, user_form_<type>_email = `user_form_field`, `user_form_string_field`, `user_form_field_email`
116
167
  styles[:specific] = build_styles_chain(table_styles[:specific], suffixes)
117
168
 
118
- container_options_styles = cell.container_options[:styles]
169
+ container_options_styles = section.container_options[:styles]
119
170
  if container_options_styles.present?
120
171
  styles[:specific] += Array.wrap(container_options_styles)
121
172
  end
@@ -127,17 +178,18 @@ module MotionPrime
127
178
  @table_delegate ||= TableDelegate.new(section: self)
128
179
  end
129
180
 
130
- def render_table
131
- options = {
181
+ def table_element_options
182
+ {
183
+ section: self.weak_ref,
132
184
  styles: table_styles.values.flatten,
133
185
  delegate: table_delegate,
134
186
  data_source: table_delegate,
135
187
  style: (UITableViewStyleGrouped unless flat_data?)
136
188
  }
137
- if async_data? && self.class.async_data_options.has_key?(:estimated_row_height)
138
- options[:estimated_row_height] = self.class.async_data_options[:estimated_row_height]
139
- end
140
- self.table_element = screen.table_view(options)
189
+ end
190
+
191
+ def render_table
192
+ self.table_element = screen.table_view(table_element_options)
141
193
  end
142
194
 
143
195
  def table_view
@@ -152,91 +204,86 @@ module MotionPrime
152
204
  table_view.try(:show)
153
205
  end
154
206
 
155
- def render_cell(index, table)
156
- section = rows_for_section(index.section)[index.row]
207
+ def render_cell(index, table = nil)
208
+ table ||= table_view
209
+ section = cell_sections_for_group(index.section)[index.row]
157
210
  element = section.container_element || section.init_container_element(container_element_options_for(index))
158
211
 
159
212
  view = element.render do
160
213
  section.render
161
214
  end
162
- on_row_render(view, index)
163
- preload_sections_after(index)
215
+
216
+ @reusable_cells ||= {}
217
+ @reusable_cells[section.object_id] = view
218
+ on_cell_render(view, index)
164
219
  view
165
220
  end
166
221
 
167
222
  def render_header(section)
168
- return unless options = self.section_header_options.try(:[], section)
169
- self.section_headers[section] ||= BaseHeaderSection.new(options.merge(screen: screen, table: self.weak_ref))
223
+ return unless options = self.group_header_options.try(:[], section)
224
+ self.group_header_sections[section] ||= BaseHeaderSection.new(options.merge(screen: screen, table: self.weak_ref))
170
225
  end
171
226
 
172
- def header_for_section(section)
173
- self.section_headers ||= []
174
- self.section_headers[section] || render_header(section)
227
+ def header_section_for_group(section)
228
+ self.group_header_sections ||= []
229
+ self.group_header_sections[section] || render_header(section)
175
230
  end
176
231
 
177
- def on_row_render(cell, index); end
232
+ def on_cell_render(cell, index); end
178
233
  def on_appear; end
179
234
  def on_click(table, index); end
180
235
 
181
236
  def has_many_sections?
182
- section_header_options.present? || data.try(:first).is_a?(Array)
237
+ group_header_options.present? || data.try(:first).is_a?(Array)
183
238
  end
184
239
 
185
240
  def flat_data?
186
241
  !has_many_sections?
187
242
  end
188
243
 
189
- def rows_for_section(section)
244
+ def cell_sections_for_group(section)
190
245
  flat_data? ? data : data[section]
191
246
  end
192
247
 
193
- def row_by_index(index)
194
- rows_for_section(index.section)[index.row]
248
+ def cell_section_by_index(index)
249
+ cell_sections_for_group(index.section)[index.row]
195
250
  end
196
251
 
197
- def on_async_data_loaded; end
198
- def on_async_data_preloaded(loaded_index); end
199
-
200
252
  def cell_name(table, index)
201
- record = row_by_index(index)
202
- if record && record.model &&
203
- record.model.respond_to?(:id) && record.model.id.present?
204
- "cell_#{record.model.id}_#{data_stamp_for("#{index.section}_#{index.row}")}"
205
- else
206
- "cell_#{index.section}_#{index.row}_#{data_stamp_for("#{index.section}_#{index.row}")}"
207
- end
253
+ record = cell_section_by_index(index)
254
+ "cell_#{record.object_id}_#{@data_stamp[record.object_id]}"
208
255
  end
209
256
 
210
257
  # Table View Delegate
211
258
  # ---------------------
212
259
 
213
- def number_of_sections(table = nil)
260
+ def number_of_groups(table = nil)
214
261
  has_many_sections? ? data.count : 1
215
262
  end
216
263
 
217
264
  def cell_for_index(table, index)
218
265
  cell = cached_cell(index, table) || render_cell(index, table)
219
-
220
266
  # run table view is appeared callback if needed
221
- if !@did_appear && index.row == rows_for_section(index.section).size - 1
267
+ if !@did_appear && index.row == cell_sections_for_group(index.section).size - 1
222
268
  on_appear
223
269
  end
224
270
  cell.is_a?(UIView) ? cell : cell.view
225
271
  end
226
272
 
227
273
  def height_for_index(table, index)
228
- section = load_cell_by_index(index, preload: true)
274
+ section = cell_section_by_index(index)
275
+ section.create_elements
229
276
  section.container_height
230
277
  end
231
278
 
232
- def view_for_header_in_section(table, section)
233
- return unless header = header_for_section(section)
279
+ def header_cell_in_group(table, group)
280
+ return unless header = header_section_for_group(group)
234
281
 
235
- reuse_identifier = "header_#{section}"
282
+ reuse_identifier = "header_#{group}"
236
283
  cached = table.dequeueReusableHeaderFooterViewWithIdentifier(reuse_identifier)
237
284
  return cached if cached.present?
238
285
 
239
- styles = cell_styles(header).values.flatten
286
+ styles = cell_section_styles(header).values.flatten
240
287
  wrapper = MotionPrime::BaseElement.factory(:table_view_header_footer_view, screen: screen, styles: styles, parent_view: table_view, reuse_identifier: reuse_identifier)
241
288
  wrapper.render do |container_view, container_element|
242
289
  header.container_element = container_element
@@ -244,8 +291,8 @@ module MotionPrime
244
291
  end
245
292
  end
246
293
 
247
- def height_for_header_in_section(table, section)
248
- header_for_section(section).try(:container_height) || 0
294
+ def height_for_header_in_group(table, group)
295
+ header_section_for_group(group).try(:container_height) || 0
249
296
  end
250
297
 
251
298
  def scroll_view_will_begin_dragging(scroll)
@@ -274,49 +321,27 @@ module MotionPrime
274
321
  end
275
322
 
276
323
  def set_table_data
277
- cells = async_data? ? load_sections_async : table_data
278
- prepare_table_cells(cells)
279
- @data = cells
324
+ sections = fixed_table_data
325
+ prepare_table_cell_sections(sections)
326
+ @data = sections
280
327
  reset_data_stamps
281
- load_sections unless async_data?
328
+ create_section_elements
282
329
  @data
283
330
  end
284
331
 
285
- def load_sections_async
286
- @async_loaded_data || begin
287
- BW::Reactor.schedule_on_main do
288
- @async_loaded_data = table_data
289
- @data = nil
290
- reload_table_data
291
- on_async_data_loaded
292
- end
293
- []
294
- end
295
- end
296
-
297
332
  def cached_cell(index, table = nil)
298
333
  table ||= self.table_view
299
334
  table.dequeueReusableCellWithIdentifier(cell_name(table, index))
300
335
  end
301
336
 
302
- def prepare_table_cells(cell)
303
- if cell.is_a?(Array)
304
- cell.each { |c| prepare_table_cells(c) }
305
- else
337
+ def prepare_table_cell_sections(cells)
338
+ Array.wrap(cells.flatten).each do |cell|
306
339
  Prime::Config.prime.cell_section.mixins.each do |mixin|
307
340
  cell.class.send(:include, mixin)
308
341
  end
309
342
  cell.screen ||= screen
310
- cell.table ||= WeakRef.new(self) if cell.respond_to?(:table=)
311
- end
312
- end
313
-
314
- def load_cell_by_index(index, options = {})
315
- section = rows_for_section(index.section)[index.row]
316
- if section.load_section && options[:preload] && !section.container_element && async_data? # perform only if just loaded
317
- section.load_container_with_elements(container: container_element_options_for(index))
343
+ cell.table ||= self.weak_ref if cell.respond_to?(:table=)
318
344
  end
319
- section
320
345
  end
321
346
 
322
347
  def container_element_options_for(index)
@@ -326,125 +351,42 @@ module MotionPrime
326
351
  }
327
352
  end
328
353
 
329
- def data_stamp_for(id)
330
- @data_stamp[id]
331
- end
332
-
333
- def set_data_stamp(cell_ids)
354
+ def set_data_stamp(section_ids)
334
355
  @data_stamp ||= {}
335
- [*cell_ids].each { |id| @data_stamp[id] = Time.now.to_f }
356
+ [*section_ids].each do |id|
357
+ @data_stamp[id] = Time.now.to_f
358
+ end
336
359
  end
337
360
 
338
361
  def reset_data_stamps
339
- keys = @data.each_with_index.map do |row, id|
340
- if row.is_a?(Array)
341
- section = id
342
- rows = (0...row.count)
343
- else
344
- section = 0
345
- rows = [id]
346
- end
347
- rows.map { |row| "#{section}_#{row}" }
348
- end
349
- set_data_stamp(keys.flatten)
362
+ keys = data.flatten.map(&:object_id)
363
+ set_data_stamp(keys)
350
364
  end
351
365
 
352
- def load_sections
353
- return if async_data?
366
+ def create_section_elements
354
367
  if flat_data?
355
- data.each(&:load_section)
368
+ data.each(&:create_elements)
356
369
  else
357
- data.each { |section_data| section_data.each(&:load_section) }
370
+ data.each { |group_sections| group_sections.each(&:create_elements) }
358
371
  end
359
372
  end
360
373
 
361
-
362
- # Preloads sections after rendering cell in current sheduled index or given index.
363
- # TODO: probably should be in separate class.
364
- #
365
- # @param index [NSIndexPath] Value of first index to load if current sheduled index not exists.
366
- # @return [NSIndexPath, Boolean] Index of next sheduled index.
367
- def preload_sections_after(index)
368
- return if !async_data? || @preloader_next_starts_from == false
369
- service = preloader_index_service
370
-
371
- load_limit = self.class.async_data_options.try(:[], :preload_rows_count)
372
- @preloader_next_starts_from ||= index
373
- index_to_start_preloading = service.sum_index(@preloader_next_starts_from, load_limit ? -load_limit/2 : 0)
374
- if service.compare_indexes(index, index_to_start_preloading) >= 0
375
- load_count = preload_sections_schedule_next(@preloader_next_starts_from, load_limit)
376
- @preloader_next_starts_from = service.sum_index(@preloader_next_starts_from, load_count, false)
377
- else
378
- false
379
- end
380
- end
381
-
382
- # Schedules preloading sections starting with given index with given limit.
383
- # TODO: probably should be in separate class.
384
- #
385
- # @param index [NSIndexPath] Value of first index to load.
386
- # @param limit [Integer] Limit of sections to load.
387
- # @return [Integer] Count of sections scheduled to load.
388
- def preload_sections_schedule_next(index, limit)
389
- service = preloader_index_service
390
-
391
- left_to_load = rows_for_section(index.section).count - index.row
392
- load_count = [left_to_load, limit].compact.min
393
- @preloader_cancelled = false
394
- @preloader_queue ||= []
395
- @strong_refs ||= []
396
-
397
- # TODO: we do we need to keep screen ref too?
398
- queue_id = @preloader_queue.count
399
- @strong_refs[queue_id] = [screen.strong_ref, screen.main_controller.strong_ref]
400
- @preloader_queue[queue_id] = :in_progress
401
- BW::Reactor.schedule(queue_id) do |queue_id|
402
- result = load_count.times do |offset|
403
- if @preloader_queue[queue_id] == :cancelled
404
- @strong_refs[queue_id] = nil
405
- break
406
- end
407
- if screen.main_controller.retainCount == 1
408
- @strong_refs[queue_id] = nil
409
- @preloader_queue[queue_id] = :dealloc
410
- break
411
- end
412
-
413
- load_cell_by_index(index, preload: true)
414
- unless offset == load_count - 1
415
- index = service.sum_index(index, 1)
416
- end
417
- end
418
-
419
- if result
420
- on_async_data_preloaded(index)
421
- @preloader_queue[queue_id] = :completed
422
- end
423
- @strong_refs[queue_id] = nil
424
- end
425
- load_count
426
- end
427
-
428
- def preloader_index_service
429
- TableDataIndexes.new(@data)
430
- end
431
-
432
374
  class << self
433
375
  def inherited(subclass)
434
376
  super
435
- subclass.async_data_options = self.async_data_options.try(:clone)
436
- subclass.section_header_options = self.section_header_options.try(:clone)
377
+ subclass.group_header_options = self.group_header_options.try(:clone)
437
378
  end
438
379
 
439
380
  def async_table_data(options = {})
440
- self.async_data_options = options
381
+ self.send :include, Prime::AsyncTableMixin
382
+ self.set_async_data_options options
441
383
  end
442
384
 
443
385
  def group_header(name, options)
444
386
  options[:name] = name
445
- self.section_header_options ||= []
387
+ self.group_header_options ||= []
446
388
  section = options.delete(:id)
447
- self.section_header_options[section] = options
389
+ self.group_header_options[section] = options
448
390
  end
449
391
 
450
392
  def pull_to_refresh(&block)