motion-prime 0.4.3 → 0.4.4

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