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.
- checksums.yaml +8 -8
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/ROADMAP.md +3 -0
- data/files/Gemfile +1 -1
- data/motion-prime/api_client.rb +5 -2
- data/motion-prime/core_ext/kernel.rb +36 -0
- data/motion-prime/elements/_content_text_mixin.rb +32 -11
- data/motion-prime/elements/base_element.rb +33 -16
- data/motion-prime/elements/draw.rb +10 -4
- data/motion-prime/elements/draw/image.rb +26 -15
- data/motion-prime/elements/map.rb +5 -0
- data/motion-prime/models/_association_mixin.rb +0 -3
- data/motion-prime/models/_base_mixin.rb +24 -3
- data/motion-prime/models/_filter_mixin.rb +28 -0
- data/motion-prime/models/_sync_mixin.rb +13 -10
- data/motion-prime/models/association_collection.rb +41 -16
- data/motion-prime/models/errors.rb +46 -24
- data/motion-prime/models/model.rb +8 -0
- data/motion-prime/screens/_sections_mixin.rb +1 -1
- data/motion-prime/screens/extensions/_indicators_mixin.rb +4 -2
- data/motion-prime/screens/extensions/_navigation_bar_mixin.rb +1 -1
- data/motion-prime/screens/screen.rb +4 -0
- data/motion-prime/sections/_async_form_mixin.rb +12 -0
- data/motion-prime/sections/_async_table_mixin.rb +193 -0
- data/motion-prime/sections/_cell_section_mixin.rb +6 -6
- data/motion-prime/sections/_draw_section_mixin.rb +13 -5
- data/motion-prime/sections/base_section.rb +62 -36
- data/motion-prime/sections/form.rb +19 -35
- data/motion-prime/sections/form/base_field_section.rb +42 -34
- data/motion-prime/sections/form/static_field_section.rb +9 -0
- data/motion-prime/sections/table.rb +143 -201
- data/motion-prime/sections/table/table_delegate.rb +15 -15
- data/motion-prime/services/table_data_indexes.rb +12 -2
- data/motion-prime/styles/base.rb +1 -1
- data/motion-prime/styles/form.rb +6 -6
- data/motion-prime/version.rb +1 -1
- data/motion-prime/views/layout.rb +5 -2
- data/motion-prime/views/view_styler.rb +2 -0
- data/spec/models/association_collection_spec.rb +28 -6
- metadata +6 -2
@@ -29,31 +29,26 @@ module MotionPrime
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
32
|
+
def reload_cell_section(section)
|
33
33
|
field = section.name.to_sym
|
34
|
-
|
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].
|
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(
|
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
|
-
|
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
|
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
|
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] =
|
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.
|
268
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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
|
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?
|
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.
|
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.
|
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
|
-
|
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
|
|
@@ -7,65 +7,116 @@ module MotionPrime
|
|
7
7
|
include HasStyleChainBuilder
|
8
8
|
include HasSearchBar
|
9
9
|
|
10
|
-
class_attribute :
|
10
|
+
class_attribute :group_header_options, :pull_to_refresh_block
|
11
11
|
|
12
|
-
attr_accessor :table_element, :did_appear, :
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
30
|
-
|
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
|
34
|
-
|
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
|
-
|
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
|
-
@
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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 +=
|
92
|
+
@data += sections
|
65
93
|
reload_table_data
|
66
94
|
end
|
67
95
|
|
68
|
-
|
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
|
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
|
-
#
|
97
|
-
type =
|
147
|
+
# table_cell_section_name = `title`
|
148
|
+
type = section.respond_to?(:cell_type) ? section.cell_type : 'cell'
|
98
149
|
suffixes = [type]
|
99
|
-
if
|
100
|
-
suffixes <<
|
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
|
158
|
+
if section.is_a?(BaseFieldSection)
|
108
159
|
# form cell: _<type>_<field_name> = `_field_email`
|
109
|
-
suffixes << :"#{type}_#{
|
110
|
-
elsif
|
111
|
-
# table cell: _<
|
112
|
-
suffixes <<
|
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_<
|
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 =
|
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
|
131
|
-
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
self.table_element = screen.table_view(
|
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
|
-
|
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
|
-
|
163
|
-
|
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.
|
169
|
-
self.
|
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
|
173
|
-
self.
|
174
|
-
self.
|
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
|
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
|
-
|
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
|
244
|
+
def cell_sections_for_group(section)
|
190
245
|
flat_data? ? data : data[section]
|
191
246
|
end
|
192
247
|
|
193
|
-
def
|
194
|
-
|
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 =
|
202
|
-
|
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
|
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 ==
|
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 =
|
274
|
+
section = cell_section_by_index(index)
|
275
|
+
section.create_elements
|
229
276
|
section.container_height
|
230
277
|
end
|
231
278
|
|
232
|
-
def
|
233
|
-
return unless header =
|
279
|
+
def header_cell_in_group(table, group)
|
280
|
+
return unless header = header_section_for_group(group)
|
234
281
|
|
235
|
-
reuse_identifier = "header_#{
|
282
|
+
reuse_identifier = "header_#{group}"
|
236
283
|
cached = table.dequeueReusableHeaderFooterViewWithIdentifier(reuse_identifier)
|
237
284
|
return cached if cached.present?
|
238
285
|
|
239
|
-
styles =
|
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
|
248
|
-
|
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
|
-
|
278
|
-
|
279
|
-
@data =
|
324
|
+
sections = fixed_table_data
|
325
|
+
prepare_table_cell_sections(sections)
|
326
|
+
@data = sections
|
280
327
|
reset_data_stamps
|
281
|
-
|
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
|
303
|
-
|
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 ||=
|
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
|
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
|
-
[*
|
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 =
|
340
|
-
|
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
|
353
|
-
return if async_data?
|
366
|
+
def create_section_elements
|
354
367
|
if flat_data?
|
355
|
-
data.each(&:
|
368
|
+
data.each(&:create_elements)
|
356
369
|
else
|
357
|
-
data.each { |
|
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.
|
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.
|
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.
|
387
|
+
self.group_header_options ||= []
|
446
388
|
section = options.delete(:id)
|
447
|
-
self.
|
389
|
+
self.group_header_options[section] = options
|
448
390
|
end
|
449
391
|
|
450
392
|
def pull_to_refresh(&block)
|