motion-prime 0.4.3 → 0.4.4

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 (47) hide show
  1. checksums.yaml +6 -14
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile.lock +1 -1
  4. data/ROADMAP.md +9 -4
  5. data/doc/code/getting_started.rb +1 -2
  6. data/doc/code/screens.rb +54 -0
  7. data/doc/docs/getting_started.html +27 -6
  8. data/doc/docs/screens.html +166 -0
  9. data/files/Gemfile +1 -1
  10. data/files/Gemfile.lock +64 -0
  11. data/files/app/environment.rb +10 -0
  12. data/files/app/styles/sidebar.rb +3 -10
  13. data/files/resources/images/menu_button.png +0 -0
  14. data/files/resources/images/menu_button@2x.png +0 -0
  15. data/motion-prime/app_delegate.rb +19 -0
  16. data/motion-prime/core_ext/kernel.rb +4 -0
  17. data/motion-prime/elements/_content_text_mixin.rb +23 -11
  18. data/motion-prime/elements/_text_mixin.rb +54 -0
  19. data/motion-prime/elements/base_element.rb +19 -14
  20. data/motion-prime/elements/draw.rb +22 -1
  21. data/motion-prime/elements/draw/_draw_background_mixin.rb +28 -28
  22. data/motion-prime/elements/draw/image.rb +67 -48
  23. data/motion-prime/elements/draw/label.rb +59 -49
  24. data/motion-prime/elements/draw/view.rb +5 -3
  25. data/motion-prime/helpers/has_style_chain_builder.rb +1 -3
  26. data/motion-prime/models/association_collection.rb +8 -0
  27. data/motion-prime/models/finder.rb +8 -0
  28. data/motion-prime/mp.rb +4 -0
  29. data/motion-prime/screens/_navigation_mixin.rb +4 -0
  30. data/motion-prime/screens/base_screen.rb +7 -0
  31. data/motion-prime/screens/sidebar_container_screen.rb +2 -2
  32. data/motion-prime/sections/_cell_section_mixin.rb +44 -5
  33. data/motion-prime/sections/_draw_section_mixin.rb +120 -0
  34. data/motion-prime/sections/base_section.rb +29 -24
  35. data/motion-prime/sections/form.rb +48 -65
  36. data/motion-prime/sections/form/base_field_section.rb +2 -2
  37. data/motion-prime/sections/table.rb +143 -82
  38. data/motion-prime/sections/table/table_delegate.rb +48 -0
  39. data/motion-prime/styles/form.rb +1 -1
  40. data/motion-prime/support/mp_cell_with_section.rb +6 -2
  41. data/motion-prime/support/mp_view_with_section.rb +1 -1
  42. data/motion-prime/version.rb +1 -1
  43. data/motion-prime/views/_frame_calculator_mixin.rb +4 -8
  44. data/motion-prime/views/layout.rb +1 -0
  45. data/motion-prime/views/view_styler.rb +3 -12
  46. metadata +34 -26
  47. data/motion-prime/sections/_draw_mixin.rb +0 -66
@@ -18,20 +18,33 @@ module MotionPrime
18
18
  include HasAuthorization
19
19
  include HasNormalizer
20
20
  include HasClassFactory
21
- include DrawMixin
21
+ include DrawSectionMixin
22
22
 
23
23
  attr_accessor :screen, :model, :name, :options, :elements, :section_styles
24
- class_attribute :elements_options, :container_options, :keyboard_close_bindings, :cell_name
24
+ class_attribute :elements_options, :container_options, :keyboard_close_bindings
25
25
  define_callbacks :render
26
26
 
27
27
  def initialize(options = {})
28
28
  @options = options
29
- self.screen = options[:screen]
29
+ self.screen = options[:screen].try(:weak_ref)
30
30
  @model = options[:model]
31
31
  @name = options[:name] ||= default_name
32
32
  @options_block = options[:block]
33
33
  end
34
34
 
35
+ def dealloc
36
+ pp 'deallocating section. elements count: ', self.elements.try(:count)
37
+ self.elements = nil
38
+
39
+ NSNotificationCenter.defaultCenter.removeObserver self
40
+ self.delegate = nil if self.respond_to?(:delegate)
41
+ super
42
+ end
43
+
44
+ def container_bounds
45
+ options[:container_bounds] or raise "You must pass `container bounds` option to prerender base section"
46
+ end
47
+
35
48
  def container_options
36
49
  compute_container_options! unless @container_options
37
50
  @container_options
@@ -58,6 +71,7 @@ module MotionPrime
58
71
  @section_loading = true
59
72
  create_elements
60
73
  @section_loading = false
74
+
61
75
  return @section_loaded = true
62
76
  end
63
77
 
@@ -75,7 +89,7 @@ module MotionPrime
75
89
 
76
90
  if @table && !self.is_a?(BaseFieldSection)
77
91
  cell.setNeedsDisplay
78
- @table.table_view.reloadData
92
+ @table.reload_table_data
79
93
  end
80
94
  end
81
95
 
@@ -90,7 +104,7 @@ module MotionPrime
90
104
  def load_elements
91
105
  self.elements.values.each do |element|
92
106
  element.size_to_fit_if_needed if element.is_a?(LabelDrawElement)
93
- element.computed_options if element.respond_to?(:computed_options)
107
+ element.compute_options! if element.respond_to?(:computed_options) && !element.computed_options
94
108
  end
95
109
  end
96
110
 
@@ -127,9 +141,8 @@ module MotionPrime
127
141
  end
128
142
 
129
143
  def render(container_options = {})
130
- self.container_options.merge!(container_options)
131
144
  load_section
132
-
145
+ self.container_options.merge!(container_options)
133
146
  run_callbacks :render do
134
147
  render!
135
148
  end
@@ -170,12 +183,6 @@ module MotionPrime
170
183
  def keyboard_will_show; end
171
184
  def keyboard_will_hide; end
172
185
 
173
- def dealloc
174
- NSNotificationCenter.defaultCenter.removeObserver self
175
- self.delegate = nil if self.respond_to?(:delegate)
176
- super
177
- end
178
-
179
186
  def bind_keyboard_events
180
187
  NSNotificationCenter.defaultCenter.addObserver self,
181
188
  selector: :on_keyboard_show,
@@ -203,6 +210,14 @@ module MotionPrime
203
210
  views.compact.each(&:resignFirstResponder)
204
211
  end
205
212
 
213
+ def elements_to_draw
214
+ self.elements.select { |key, element| element.is_a?(DrawElement) }
215
+ end
216
+
217
+ def elements_to_render
218
+ self.elements.select { |key, element| element.is_a?(BaseElement) }
219
+ end
220
+
206
221
  protected
207
222
  def bind_keyboard_close
208
223
  return unless self.class.keyboard_close_bindings.present?
@@ -216,20 +231,13 @@ module MotionPrime
216
231
  def keyboard_close_bindings_options
217
232
  @keyboard_close_bindings_options ||= normalize_options(self.class.keyboard_close_bindings.clone, self)
218
233
  end
219
- def elements_to_draw
220
- self.elements.select { |key, element| element.is_a?(DrawElement) }
221
- end
222
-
223
- def elements_to_render
224
- self.elements.select { |key, element| element.is_a?(BaseElement) }
225
- end
226
234
 
227
235
  def build_options_for_element(opts)
228
236
  # we should clone options to prevent overriding options
229
237
  # in next element with same name in another class
230
238
  options = opts.clone
231
239
  options[:type] ||= (options[:text] || options[:attributed_text_options]) ? :label : :view
232
- options.merge(screen: screen, section: self)
240
+ options.merge(screen: screen.try(:weak_ref), section: self.weak_ref)
233
241
  end
234
242
 
235
243
  private
@@ -269,9 +277,6 @@ module MotionPrime
269
277
  def bind_keyboard_close(options)
270
278
  self.keyboard_close_bindings = options
271
279
  end
272
- def set_cell_name(value)
273
- self.cell_name = value
274
- end
275
280
  end
276
281
  after_render :bind_keyboard_events
277
282
  after_render :bind_keyboard_close
@@ -18,42 +18,17 @@ module MotionPrime
18
18
  # end
19
19
  #
20
20
 
21
- class_attribute :text_field_limits, :text_view_limits
22
- class_attribute :fields_options, :section_header_options
23
- attr_accessor :fields, :field_indexes, :keyboard_visible, :rendered_views, :section_headers, :section_header_options
21
+ class_attribute :fields_options, :text_field_limits, :text_view_limits
22
+ attr_accessor :fields, :field_indexes, :keyboard_visible, :rendered_views, :grouped_data
24
23
 
25
24
  def table_data
26
- if @has_groups
27
- section_indexes = []
28
- data = fields.inject([]) do |result, (key, field)|
29
- section = self.class.fields_options[key][:group].to_i
30
- section_indexes[section] ||= 0
31
- result[section] ||= []
32
- result[section][section_indexes[section]] = field
33
- section_indexes[section] += 1
34
- result
35
- end
36
- self.section_header_options.delete_if.each_with_index { |opts, id| data[id].nil? }
37
- data.compact
25
+ if has_many_sections?
26
+ grouped_data.compact
38
27
  else
39
28
  fields.values
40
29
  end
41
30
  end
42
31
 
43
- def data
44
- @data ||= table_data
45
- end
46
-
47
- def render_table
48
- init_form_fields
49
- options = {
50
- styles: table_styles.values.flatten,
51
- delegate: self,
52
- dataSource: self,
53
- style: (UITableViewStyleGrouped unless flat_data?)}
54
- self.table_element = screen.table_view(options)
55
- end
56
-
57
32
  def reload_cell(section)
58
33
  field = section.name.to_sym
59
34
  index = field_indexes[field].split('_').map(&:to_i)
@@ -223,27 +198,46 @@ module MotionPrime
223
198
  end
224
199
  end
225
200
 
226
- def render_header(section)
227
- return unless options = self.section_header_options.try(:[], section)
228
- self.section_headers[section] ||= BaseHeaderSection.new(options.merge(screen: screen, table: self))
201
+ def reload_data
202
+ @groups_count = nil
203
+ reset_data
204
+ init_form_fields
205
+ reload_table_data
206
+ end
207
+
208
+ def reset_data
209
+ super
210
+ self.fields.values.each(&:clear_observers)
211
+ end
212
+
213
+ def has_many_sections?
214
+ section_header_options.present? || grouped_data.count > 1
229
215
  end
230
216
 
231
- def header_for_section(section)
232
- self.section_headers ||= []
233
- self.section_headers[section] || render_header(section)
217
+ def render_table
218
+ init_form_fields unless self.fields.present?
219
+ super
234
220
  end
235
221
 
236
- def tableView(table, viewForHeaderInSection: section)
237
- return unless header = header_for_section(section)
238
- wrapper = MotionPrime::BaseElement.factory(:view, screen: screen, styles: cell_styles(header).values.flatten, parent_view: table_view)
239
- wrapper.render do |container_view, container_element|
240
- header.container_element = container_element
241
- header.render
222
+ def reload_table_data
223
+ return super unless async_data?
224
+ sections = NSMutableIndexSet.new
225
+ number_of_sections.times do |section_id|
226
+ sections.addIndex(section_id)
242
227
  end
228
+ table_view.reloadSections sections, withRowAnimation: UITableViewRowAnimationFade
243
229
  end
244
230
 
245
- def tableView(table, heightForHeaderInSection: section)
246
- header_for_section(section).try(:container_height) || 0
231
+ # Table View Delegate
232
+ # ---------------------
233
+
234
+ def number_of_sections(table = nil)
235
+ has_many_sections? ? grouped_data.compact.count : 1
236
+ end
237
+
238
+ def height_for_index(table, index)
239
+ section = load_cell_by_index(index, preload: false)
240
+ section.container_height
247
241
  end
248
242
 
249
243
  class << self
@@ -256,13 +250,6 @@ module MotionPrime
256
250
  self.fields_options[name]
257
251
  end
258
252
 
259
- def group_header(name, options)
260
- options[:name] = name
261
- self.section_header_options ||= []
262
- section = options.delete(:id)
263
- self.section_header_options[section] = options
264
- end
265
-
266
253
  def limit_text_field_length(name, limit)
267
254
  self.text_field_limits ||= {}
268
255
  self.text_field_limits[name] = limit
@@ -273,40 +260,36 @@ module MotionPrime
273
260
  end
274
261
  end
275
262
 
276
- def reload_data
277
- @groups_count = nil
278
- reset_data
279
- init_form_fields
280
- table_view.reloadData
281
- end
282
-
283
- def reset_data
284
- super
285
- self.fields.values.each(&:clear_observers)
286
- end
287
-
288
263
  private
264
+ def load_sections; end
265
+
289
266
  def init_form_fields
290
267
  self.fields = {}
291
268
  self.field_indexes = {}
269
+ self.grouped_data = []
292
270
  section_indexes = []
293
271
  (self.class.fields_options || {}).each do |key, field|
294
272
  next unless render_field?(key, field)
295
273
  section_id = field[:group].to_i
296
274
  @groups_count = [@groups_count || 1, section_id + 1].max
297
- self.fields[key] = load_field(field)
298
275
 
276
+ grouped_data[section_id] ||= []
299
277
  section_indexes[section_id] ||= 0
278
+
279
+ section = load_field(field)
280
+ self.fields[key] = section
300
281
  self.field_indexes[key] = "#{section_id}_#{section_indexes[section_id]}"
282
+ grouped_data[section_id][section_indexes[section_id]] = section
283
+
301
284
  section_indexes[section_id] += 1
302
285
  end
303
286
  init_form_headers
304
- @has_groups = section_header_options.present? || @groups_count > 1
305
287
  reset_data_stamps
306
288
  end
307
289
 
308
290
  def init_form_headers
309
- self.section_header_options = Array.wrap(self.class.section_header_options).clone
291
+ options = Array.wrap(self.class.section_header_options).clone
292
+ self.section_header_options = options.delete_if.each_with_index { |opts, id| grouped_data[id].nil? }
310
293
  end
311
294
  end
312
295
  end
@@ -7,7 +7,7 @@ module MotionPrime
7
7
  after_render :on_section_render
8
8
 
9
9
  def initialize(options = {})
10
- @form = options[:table]
10
+ @form = options[:table].try(:weak_ref)
11
11
  @errors_observer_options = normalize_options(options.delete(:observe_errors).clone, self) if options[:observe_errors]
12
12
  super
13
13
  observe_model_errors
@@ -37,7 +37,7 @@ module MotionPrime
37
37
  reload_section
38
38
  else
39
39
  load_section!
40
- form.table_view.reloadData
40
+ form.reload_table_data
41
41
  end
42
42
  end
43
43
  end
@@ -1,14 +1,24 @@
1
1
  motion_require './table/refresh_mixin'
2
+ motion_require './table/table_delegate'
3
+
2
4
  module MotionPrime
3
5
  class TableSection < BaseSection
4
6
  include TableSectionRefreshMixin
5
7
  include HasStyleChainBuilder
6
8
  include HasSearchBar
7
9
 
8
- class_attribute :async_data_options
9
- attr_accessor :table_element, :did_appear
10
+ class_attribute :async_data_options, :section_header_options
11
+ attr_accessor :table_element, :did_appear, :section_headers, :section_header_options
12
+ attr_reader :decelerating
10
13
  before_render :render_table
11
14
 
15
+ def dealloc
16
+ pp 'deallocating table. sections count:', @data.try(:count)
17
+ @data = nil
18
+ @async_loaded_data = nil
19
+ super
20
+ end
21
+
12
22
  def table_data
13
23
  []
14
24
  end
@@ -24,11 +34,15 @@ module MotionPrime
24
34
  def reload_data
25
35
  reset_data
26
36
  @async_loaded_data = table_data if async_data?
37
+ reload_table_data
38
+ end
39
+
40
+ def reload_table_data
27
41
  table_view.reloadData
28
42
  end
29
43
 
30
44
  def refresh_if_needed
31
- table_view.reloadData if @data.nil?
45
+ reload_table_data if @data.nil?
32
46
  end
33
47
 
34
48
  def reset_data
@@ -36,14 +50,16 @@ module MotionPrime
36
50
  @data = nil
37
51
  @async_loaded_data = nil
38
52
  @next_portion_starts_from = nil
53
+ @preloader_cancelled = false
39
54
  @data_stamp = nil
55
+ @queue_states[-1] = :cancelled if @queue_states.present?
40
56
  end
41
57
 
42
58
  def table_styles
43
59
  type = self.is_a?(FormSection) ? :base_form : :base_table
44
60
 
45
61
  base_styles = [type]
46
- base_styles << :"#{type}_with_sections" unless flat_data?
62
+ base_styles << :"#{type}_with_sections" #unless flat_data?
47
63
  item_styles = [name.to_sym]
48
64
  item_styles << @styles if @styles.present?
49
65
  {common: base_styles, specific: item_styles}
@@ -92,10 +108,11 @@ module MotionPrime
92
108
  end
93
109
 
94
110
  def render_table
111
+ delegate = TableDelegate.new(section: self)
95
112
  options = {
96
113
  styles: table_styles.values.flatten,
97
- delegate: self,
98
- data_source: self,
114
+ delegate: delegate,
115
+ data_source: delegate,
99
116
  style: (UITableViewStyleGrouped unless flat_data?)
100
117
  }
101
118
  if async_data? && self.class.async_data_options.has_key?(:estimated_row_height)
@@ -118,38 +135,71 @@ module MotionPrime
118
135
 
119
136
  def render_cell(index, table)
120
137
  section = rows_for_section(index.section)[index.row]
121
- cell_element = container_element_for(index)
122
- cell_view = cell_element.render do
123
- section.render
138
+ element = section.container_element || section.init_container_element(container_element_options_for(index))
139
+
140
+ view = element.render do
141
+ rows_for_section(index.section)[index.row].render
124
142
  end
125
143
 
126
- @rendered_cells[index.section][index.row] = cell_view
127
- on_row_render(cell_view, index)
144
+ @rendered_cells[index.section][index.row] = view
145
+ on_row_render(view, index)
128
146
 
129
147
  preload_sections_for(index)
130
148
 
131
- cell_view
149
+ view
150
+ end
151
+
152
+ def render_header(section)
153
+ return unless options = self.section_header_options.try(:[], section)
154
+ self.section_headers[section] ||= BaseHeaderSection.new(options.merge(screen: screen, table: self))
155
+ end
156
+
157
+ def header_for_section(section)
158
+ self.section_headers ||= []
159
+ self.section_headers[section] || render_header(section)
132
160
  end
133
161
 
134
162
  def on_row_render(cell, index); end
135
163
  def on_appear; end
136
164
  def on_click(table, index); end
137
165
 
138
- # ALIASES
139
- # ---------------------
166
+ def has_many_sections?
167
+ section_header_options.present? || data.try(:first).is_a?(Array)
168
+ end
169
+
170
+ def flat_data?
171
+ !has_many_sections?
172
+ end
173
+
174
+ def rows_for_section(section)
175
+ flat_data? ? data : data[section]
176
+ end
177
+
178
+ def row_by_index(index)
179
+ rows_for_section(index.section)[index.row]
180
+ end
181
+
182
+ def on_async_data_loaded; end
183
+ def on_async_data_preloaded(loaded_index); end
184
+
185
+ def cell_name(table, index)
186
+ record = row_by_index(index)
187
+ if record && record.model &&
188
+ record.model.respond_to?(:id) && record.model.id.present?
189
+ "cell_#{record.model.id}_#{data_stamp_for("#{index.section}_#{index.row}")}"
190
+ else
191
+ "cell_#{index.section}_#{index.row}_#{data_stamp_for("#{index.section}_#{index.row}")}"
192
+ end
193
+ end
140
194
 
141
- # def tableView(table, viewForFooterInSection: section) # cause bug in ios7.0.0-7.0.2
142
- # UIView.new
143
- # end
144
- # def tableView(table, heightForFooterInSection: section)
145
- # 0.1
146
- # end
195
+ # Table View Delegate
196
+ # ---------------------
147
197
 
148
- def numberOfSectionsInTableView(tableView)
149
- number_of_sections
198
+ def number_of_sections(table = nil)
199
+ has_many_sections? ? data.count : 1
150
200
  end
151
201
 
152
- def tableView(table, cellForRowAtIndexPath:index)
202
+ def cell_for_index(table, index)
153
203
  @rendered_cells ||= []
154
204
  @rendered_cells[index.section] ||= []
155
205
 
@@ -162,58 +212,48 @@ module MotionPrime
162
212
  cell.is_a?(UIView) ? cell : cell.view
163
213
  end
164
214
 
165
- def tableView(table, numberOfRowsInSection:section)
166
- rows_for_section(section).length
215
+ def height_for_index(table, index)
216
+ section = load_cell_by_index(index, preload: true)
217
+ section.container_height
167
218
  end
168
219
 
169
- def tableView(table, didSelectRowAtIndexPath:index)
170
- on_click(table, index)
171
- end
172
-
173
- def tableView(table, heightForRowAtIndexPath: index)
174
- load_cell_by_index(index)
175
- cell = rows_for_section(index.section)[index.row]
176
- cell.container_height
177
- end
220
+ def view_for_header_in_section(table, section)
221
+ return unless header = header_for_section(section)
178
222
 
179
- def number_of_sections
180
- has_many_sections? ? data.count : 1
181
- end
223
+ reuse_identifier = "header_#{section}"
224
+ cached = table.dequeueReusableHeaderFooterViewWithIdentifier(reuse_identifier)
225
+ return cached if cached.present?
182
226
 
183
- def has_many_sections?
184
- data.any? && data.first.is_a?(Array)
227
+ styles = cell_styles(header).values.flatten
228
+ wrapper = MotionPrime::BaseElement.factory(:table_view_header_footer_view, screen: screen, styles: styles, parent_view: table_view, reuse_identifier: reuse_identifier)
229
+ wrapper.render do |container_view, container_element|
230
+ header.container_element = container_element
231
+ header.render
232
+ end
185
233
  end
186
234
 
187
- def flat_data?
188
- !has_many_sections?
235
+ def height_for_header_in_section(table, section)
236
+ header_for_section(section).try(:container_height) || 0
189
237
  end
190
238
 
191
- def row_by_index(index)
192
- rows_for_section(index.section)[index.row]
239
+ def scroll_view_will_begin_dragging(scroll)
240
+ @decelerating = true
193
241
  end
194
242
 
195
- def rows_for_section(section)
196
- flat_data? ? data : data[section]
243
+ def scroll_view_did_end_decelerating(scroll)
244
+ @decelerating = false
245
+ display_pending_cells
197
246
  end
198
247
 
199
- def self.async_table_data(options = {})
200
- self.async_data_options = options
248
+ def scroll_view_did_end_dragging(scroll, willDecelerate: will_decelerate)
249
+ display_pending_cells unless @decelerating = will_decelerate
201
250
  end
202
251
 
203
- def on_async_data_loaded; end
204
- def on_async_data_preloaded(loaded_index); end
205
-
206
- def cell_name(table, index)
207
- record = row_by_index(index)
208
- if record && record.model &&
209
- record.model.respond_to?(:id) && record.model.id.present?
210
- "cell_#{record.model.id}_#{data_stamp_for("#{index.section}_#{index.row}")}"
211
- else
212
- "cell_#{index.section}_#{index.row}_#{data_stamp_for("#{index.section}_#{index.row}")}"
252
+ private
253
+ def display_pending_cells
254
+ table_view.visibleCells.each { |cell_view| cell_view.section.display if cell_view.section.pending_display }
213
255
  end
214
- end
215
256
 
216
- private
217
257
  def set_table_data
218
258
  cells = async_data? ? load_sections_async : table_data
219
259
  prepare_table_cells(cells)
@@ -228,7 +268,7 @@ module MotionPrime
228
268
  BW::Reactor.schedule_on_main do
229
269
  @async_loaded_data = table_data
230
270
  @data = nil
231
- table_view.reloadData
271
+ reload_table_data
232
272
  on_async_data_loaded
233
273
  end
234
274
  []
@@ -240,31 +280,29 @@ module MotionPrime
240
280
  table.dequeueReusableCellWithIdentifier(cell_name(table, index))
241
281
  end
242
282
 
243
- def prepare_table_cells(cells)
244
- cells.each do |cell|
245
- cell.send(:extend, CellSectionMixin)
246
- cell.screen = screen
247
- cell.table = self if cell.respond_to?(:table=)
283
+ def prepare_table_cells(cell)
284
+ if cell.is_a?(Array)
285
+ cell.each { |c| prepare_table_cells(c) }
286
+ else
287
+ cell.class.send(:include, CellSectionMixin)
288
+ cell.screen ||= screen
289
+ cell.table ||= WeakRef.new(self) if cell.respond_to?(:table=)
248
290
  end
249
291
  end
250
292
 
251
- def load_cell_by_index(index)
252
- cell = rows_for_section(index.section)[index.row]
253
- return unless cell.load_section # return if already loaded
254
- cell.load_elements
255
- if async_data?
256
- container_element = container_element_for(index)
257
- container_element.computed_options # compute options
293
+ def load_cell_by_index(index, options = {})
294
+ section = rows_for_section(index.section)[index.row]
295
+ if section.load_section && options[:preload] && !section.container_element && async_data? # perform only if just loaded
296
+ section.load_container_element(container_element_options_for(index))
258
297
  end
298
+ section
259
299
  end
260
300
 
261
- def container_element_for(index)
262
- cell = rows_for_section(index.section)[index.row]
263
- options = {
301
+ def container_element_options_for(index)
302
+ {
264
303
  reuse_identifier: cell_name(table_view, index),
265
304
  parent_view: table_view
266
305
  }
267
- cell.load_container_element(options)
268
306
  end
269
307
 
270
308
  def data_stamp_for(id)
@@ -295,7 +333,7 @@ module MotionPrime
295
333
  if flat_data?
296
334
  data.each(&:load_section)
297
335
  else
298
- data.count.times { |section_data| section_data.each(&:load_section) }
336
+ data.each { |section_data| section_data.each(&:load_section) }
299
337
  end
300
338
  end
301
339
 
@@ -306,7 +344,6 @@ module MotionPrime
306
344
  load_limit = self.class.async_data_options.try(:[], :preload_rows_count)
307
345
  @next_portion_starts_from ||= index
308
346
  start_preload_when_index_loaded = service.sum_index(@next_portion_starts_from, load_limit ? -load_limit/2 : 0)
309
-
310
347
  if service.compare_indexes(index, start_preload_when_index_loaded) >= 0
311
348
  section = @next_portion_starts_from.section
312
349
  next_row = @next_portion_starts_from.row
@@ -315,12 +352,23 @@ module MotionPrime
315
352
  load_count = [left_to_load, load_limit].compact.min
316
353
 
317
354
  next_index = @next_portion_starts_from
318
- BW::Reactor.schedule do
319
- load_count.times do |offset|
320
- load_cell_by_index(next_index)
355
+ @preloader_cancelled = false
356
+
357
+ @queue_states ||= []
358
+
359
+ BW::Reactor.schedule(@queue_states.count) do |queue_id|
360
+ @queue_states[queue_id] = :in_progress
361
+
362
+ result = load_count.times do |offset|
363
+ break if @queue_states[queue_id] == :cancelled
364
+ load_cell_by_index(next_index, preload: true)
321
365
  next_index = service.sum_index(next_index, 1) unless offset == load_count - 1
322
366
  end
323
- on_async_data_preloaded(next_index)
367
+
368
+ if result
369
+ on_async_data_preloaded(next_index)
370
+ @queue_states[queue_id] = :completed
371
+ end
324
372
  end
325
373
 
326
374
  @next_portion_starts_from = service.sum_index(@next_portion_starts_from, load_count, false)
@@ -330,5 +378,18 @@ module MotionPrime
330
378
  def index_service
331
379
  TableDataIndexes.new(@data)
332
380
  end
381
+
382
+ class << self
383
+ def async_table_data(options = {})
384
+ self.async_data_options = options
385
+ end
386
+
387
+ def group_header(name, options)
388
+ options[:name] = name
389
+ self.section_header_options ||= []
390
+ section = options.delete(:id)
391
+ self.section_header_options[section] = options
392
+ end
393
+ end
333
394
  end
334
395
  end