motion-prime 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|