motion-prime 0.9.8 → 0.9.9
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 +5 -0
- data/Gemfile.lock +1 -1
- data/ROADMAP.md +0 -1
- data/generators/templates/scaffold/table.rb +1 -1
- data/motion-prime/elements/base_element.rb +4 -2
- data/motion-prime/elements/draw.rb +1 -1
- data/motion-prime/elements/draw/label.rb +4 -2
- data/motion-prime/models/_association_mixin.rb +11 -7
- data/motion-prime/models/_base_mixin.rb +5 -3
- data/motion-prime/models/_dirty_mixin.rb +2 -0
- data/motion-prime/models/_filter_mixin.rb +7 -1
- data/motion-prime/models/_nano_bag_mixin.rb +10 -5
- data/motion-prime/models/_sync_mixin.rb +55 -15
- data/motion-prime/models/association_collection.rb +8 -2
- data/motion-prime/sections/_async_form_mixin.rb +1 -0
- data/motion-prime/sections/_async_table_mixin.rb +2 -2
- data/motion-prime/sections/_cell_section_mixin.rb +2 -1
- data/motion-prime/sections/base_section.rb +1 -1
- data/motion-prime/sections/form.rb +9 -10
- data/motion-prime/sections/table.rb +43 -26
- data/motion-prime/sections/table/table_delegate.rb +6 -6
- data/motion-prime/version.rb +1 -1
- data/motion-prime/views/view_styler.rb +6 -1
- data/spec/factories/scaffold/sections/tasks/index_table.rb +1 -1
- data/spec/unit/models/associations_spec.rb +10 -1
- data/spec/unit/models/bag_spec.rb +20 -0
- data/spec/unit/models/model_spec.rb +45 -6
- data/spec/unit/support/filter_mixin_spec.rb +25 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ODMzMDQ2MmI4YTBmYTY4NWZiOTQzZjQ5NGVmMGEyNTFiN2NiZDhhOQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MDRmZmM2Y2QwY2Q5ZDI0MzY4OTkyZjg4MTQ1YTQ3NDFkMjA0YmM5ZA==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZDJlMGEwMzAxNjE5ZTVlMzIxYTJiMWI5ODg4N2JhYjIwMjQzMDg2NGVjYjA0
|
10
|
+
YWVkMmZhNDA5MjgwMjZhMGU3YzNhM2UyNjBkZTJjZWJmZDRjNDU2NjdjOWNm
|
11
|
+
ZTEwYzUwMGRjMjlmYTc5OGEzOTlkMmEwODQ0NzRmM2Q4ZWNiMzY=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MGYxMmZiZGU1YzM2NWIzOTI2OGI0Zjc5MjlhYzQ2ZDU5YjM2ZWJhYTI1MjBj
|
14
|
+
YTE5Nzc4MzdhN2JjYThjN2VmYTc5YjdiMWQ3MDY2YzM5ZmVmNmYwM2QyM2Zl
|
15
|
+
MWNmMzY1MGU4MzA3NGRjOTdiNGMxYWNmNWNlZGE4ZjdhZDRjMmY=
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
=== 0.9.9
|
2
|
+
* BREAKING CHANGE: table delegate methods in table section does not accept table_view as first param.
|
3
|
+
See https://github.com/droidlabs/motion-prime/commit/c8f7b2e4fd1665a45309585484ac211381854f62
|
4
|
+
* Bug fixes
|
5
|
+
|
1
6
|
=== 0.9.8
|
2
7
|
* Ability to fetch one record / all records from server using Model.fetch(id) or Model.fetch_all
|
3
8
|
* Bug fixes
|
data/Gemfile.lock
CHANGED
data/ROADMAP.md
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
=== 1.0.0
|
2
|
-
* bug: size_to_fit makes draw label multiline by default.
|
3
2
|
* bug: content_vertical_alignment conflicts with padding.
|
4
3
|
* bug: content_vertical_alignment has not ideal centering.
|
5
4
|
* bug: if mp label do not have text and was set as hidden, it should unhide after setting text.
|
@@ -109,8 +109,10 @@ module MotionPrime
|
|
109
109
|
rerender!
|
110
110
|
end
|
111
111
|
|
112
|
-
def update_options(
|
113
|
-
|
112
|
+
def update_options(new_options)
|
113
|
+
options.merge!(new_options)
|
114
|
+
return unless view
|
115
|
+
ViewStyler.new(view, view.superview.try(:bounds), new_options).apply
|
114
116
|
end
|
115
117
|
|
116
118
|
def update
|
@@ -13,7 +13,9 @@ module MotionPrime
|
|
13
13
|
|
14
14
|
text_alignment_name = options.fetch(:text_alignment, :left)
|
15
15
|
text_alignment = text_alignment_name.nstextalignment
|
16
|
-
|
16
|
+
|
17
|
+
default_break_mode = options[:size_to_fit] ? :word_wrap : :tail_truncation
|
18
|
+
line_break_mode_name = options.fetch(:line_break_mode, default_break_mode)
|
17
19
|
line_break_mode = line_break_mode_name.uilinebreakmode
|
18
20
|
|
19
21
|
top_left_corner = CGPointMake(frame_inner_left, frame_inner_top)
|
@@ -52,7 +54,7 @@ module MotionPrime
|
|
52
54
|
|
53
55
|
UIGraphicsPushContext(context)
|
54
56
|
options = draw_options
|
55
|
-
if options[:is_html] || options[:line_spacing] ||
|
57
|
+
if options[:is_html] || options[:line_spacing] ||
|
56
58
|
options[:line_height] || options[:underline] || options[:force_attributed]
|
57
59
|
prepared_text = options[:is_html] ? html_string(options) : attributed_string(options)
|
58
60
|
|
@@ -6,6 +6,10 @@ module MotionPrime
|
|
6
6
|
@_bags ||= {}
|
7
7
|
end
|
8
8
|
|
9
|
+
def bag_key_for(bag_name)
|
10
|
+
self.info[bag_name]
|
11
|
+
end
|
12
|
+
|
9
13
|
# Saves model and all associations to store.
|
10
14
|
#
|
11
15
|
# @return [Prime::Model] model
|
@@ -16,9 +20,7 @@ module MotionPrime
|
|
16
20
|
end
|
17
21
|
super
|
18
22
|
rescue StoreError => e
|
19
|
-
if Prime.env.development?
|
20
|
-
raise StoreError, e.description
|
21
|
-
end
|
23
|
+
raise e if Prime.env.development?
|
22
24
|
end
|
23
25
|
|
24
26
|
module ClassMethods
|
@@ -28,8 +30,9 @@ module MotionPrime
|
|
28
30
|
# @return [Nil]
|
29
31
|
def bag(name)
|
30
32
|
define_method(name) do |*args, &block|
|
31
|
-
|
32
|
-
|
33
|
+
# use users_bag(true) to reload bag
|
34
|
+
return _bags[name] if _bags[name] && args[0] != true
|
35
|
+
bag_key = bag_key_for(name)
|
33
36
|
if bag_key.present?
|
34
37
|
bag = self.class.store.bagsWithKeysInArray([bag_key]).first
|
35
38
|
end
|
@@ -69,8 +72,9 @@ module MotionPrime
|
|
69
72
|
self._associations[association_name] = options.merge(type: :one)
|
70
73
|
|
71
74
|
define_method("#{association_name}=") do |value|
|
72
|
-
self.send(bag_name)
|
73
|
-
|
75
|
+
bag = self.send(bag_name)
|
76
|
+
bag.clear
|
77
|
+
bag << value
|
74
78
|
value
|
75
79
|
end
|
76
80
|
define_method("#{association_name}_attributes=") do |value|
|
@@ -204,14 +204,16 @@ module MotionPrime
|
|
204
204
|
attributes << name.to_sym
|
205
205
|
attributes.uniq!
|
206
206
|
|
207
|
-
define_method(:"#{name}=") do |value
|
208
|
-
|
207
|
+
define_method(:"#{name}=") do |value|
|
208
|
+
tracking_block = proc {
|
209
209
|
if options[:convert] || !options.has_key?(:convert)
|
210
210
|
self.info[name] = attribute_convert_in(value, options[:type])
|
211
211
|
else
|
212
212
|
self.info[name] = value
|
213
213
|
end
|
214
|
-
|
214
|
+
}
|
215
|
+
return tracking_block.call if @_tracking_changes
|
216
|
+
track_changed_attributes(&tracking_block)
|
215
217
|
end
|
216
218
|
|
217
219
|
define_method(name.to_sym) do
|
@@ -7,6 +7,7 @@ module MotionPrime
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def track_changed_attributes(&block)
|
10
|
+
@_tracking_changes = true
|
10
11
|
@_changed_attributes ||= {}
|
11
12
|
old_attrs = self.info.clone
|
12
13
|
result = block.call
|
@@ -22,6 +23,7 @@ module MotionPrime
|
|
22
23
|
@_changed_attributes[key.to_s] = old_attrs[key]
|
23
24
|
end
|
24
25
|
end
|
26
|
+
@_tracking_changes = false
|
25
27
|
result
|
26
28
|
end
|
27
29
|
|
@@ -2,7 +2,13 @@ module MotionPrime
|
|
2
2
|
module FilterMixin
|
3
3
|
def filter_array(data, find_options = {}, sort_options = nil)
|
4
4
|
data = data.select do |entity|
|
5
|
-
find_options.all?
|
5
|
+
find_options.all? do |field, value|
|
6
|
+
if value.is_a?(Array)
|
7
|
+
value.include?(entity.info[field].to_s)
|
8
|
+
else
|
9
|
+
entity.info[field] == value
|
10
|
+
end
|
11
|
+
end
|
6
12
|
end if find_options.present?
|
7
13
|
|
8
14
|
data.sort! do |a, b|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
motion_require './_finder_mixin.rb'
|
2
2
|
module MotionPrime
|
3
3
|
module NanoBagMixin
|
4
|
+
include FilterMixin
|
5
|
+
|
4
6
|
def self.included(base)
|
5
7
|
base.class_eval do
|
6
8
|
alias_method :saved, :savedObjects
|
@@ -30,9 +32,11 @@ module MotionPrime
|
|
30
32
|
# Add an object or array of objects to bag
|
31
33
|
#
|
32
34
|
# @return self [Prime::Model]
|
33
|
-
def add(object_or_array)
|
35
|
+
def add(object_or_array, options = {})
|
34
36
|
error_ptr = Pointer.new(:id)
|
35
|
-
|
37
|
+
options[:existed_ids] ||= []
|
38
|
+
options[:existed_ids] += filter_array(self.to_a, bag_key: self.key).map(&:id)
|
39
|
+
prepared = prepare_for_store(object_or_array, options)
|
36
40
|
|
37
41
|
if object_or_array.is_a?(Array)
|
38
42
|
self.addObjectsFromArray(prepared, error:error_ptr)
|
@@ -45,12 +49,13 @@ module MotionPrime
|
|
45
49
|
alias_method :+, :add
|
46
50
|
alias_method :<<, :add
|
47
51
|
|
48
|
-
def prepare_for_store(object)
|
52
|
+
def prepare_for_store(object, options = {})
|
49
53
|
if object.is_a?(Array)
|
50
|
-
object.map { |entity| prepare_for_store(entity) }.compact
|
54
|
+
object.map { |entity| prepare_for_store(entity, options) }.compact
|
51
55
|
else
|
52
56
|
object.bag_key = self.key
|
53
|
-
if object.id.present? &&
|
57
|
+
if object.id.present? && Array.wrap(options[:existed_ids]).include?(object.id)
|
58
|
+
return if options[:silent_validation]
|
54
59
|
raise StoreError, "duplicated item added `#{object.class_name_without_kvo}` with `id` = #{object.id}"
|
55
60
|
end
|
56
61
|
object
|
@@ -162,10 +162,10 @@ module MotionPrime
|
|
162
162
|
if respond_to?(:"fetch_#{key}")
|
163
163
|
self.send(:"fetch_#{key}", value)
|
164
164
|
elsif has_association?(key) && (value.is_a?(Hash) || value.is_a?(Array))
|
165
|
-
|
166
|
-
fetch_association_with_attributes(key, value, save: should_save)
|
165
|
+
fetch_association_with_attributes(key, value, save: options[:save_associations])
|
167
166
|
elsif respond_to?(:"#{key}=")
|
168
167
|
self.send(:"#{key}=", value)
|
168
|
+
# TODO: self.info[:"#{key}"] = value is much faster, maybe we could use it
|
169
169
|
end
|
170
170
|
end
|
171
171
|
end
|
@@ -231,11 +231,13 @@ module MotionPrime
|
|
231
231
|
def fetch_has_many(key, options = {}, sync_options = {}, &block)
|
232
232
|
use_callback = block_given?
|
233
233
|
NSLog("SYNC: started sync for #{key} in #{self.class_name_without_kvo}")
|
234
|
-
|
234
|
+
|
235
|
+
params = (options[:params] || {}).deep_merge(sync_options[:params] || {})
|
236
|
+
api_client.get association_sync_url(key, options, sync_options), params do |response, status_code|
|
235
237
|
data = options[:sync_key] && response ? response[options[:sync_key]] : response
|
236
238
|
if data
|
237
|
-
fetch_has_many_with_attributes(key, data, sync_options)
|
238
239
|
NSLog("SYNC: finished sync for #{key} in #{self.class_name_without_kvo}")
|
240
|
+
fetch_has_many_with_attributes(key, data, sync_options)
|
239
241
|
block.call(data, status_code, response) if use_callback
|
240
242
|
else
|
241
243
|
NSLog("SYNC ERROR: failed sync for #{key} in #{self.class_name_without_kvo}")
|
@@ -244,36 +246,74 @@ module MotionPrime
|
|
244
246
|
end
|
245
247
|
end
|
246
248
|
|
249
|
+
def update_storage(bags_options, sync_options = {})
|
250
|
+
should_save = sync_options[:save]
|
251
|
+
if should_save
|
252
|
+
models_to_save = bags_options.inject([]) { |result, (key, bag_options)| result + bag_options[:save] }
|
253
|
+
models_to_delete = bags_options.inject([]) { |result, (key, bag_options)| result + bag_options[:delete] }
|
254
|
+
self.store.save_interval = [models_to_save.count, 1].max
|
255
|
+
|
256
|
+
models_to_save.each(&:save)
|
257
|
+
models_to_delete.each(&:delete)
|
258
|
+
end
|
259
|
+
|
260
|
+
bags_changed = false
|
261
|
+
bags_options.each do |bag_key, bag_options|
|
262
|
+
next if bag_options[:add].empty? && bag_options[:delete].empty?
|
263
|
+
bags_changed = true
|
264
|
+
bag = self.send(:"#{bag_key}_bag")
|
265
|
+
bag.add(bag_options[:add], silent_validation: true)
|
266
|
+
bag.save if should_save
|
267
|
+
end
|
268
|
+
|
269
|
+
save if should_save && (bags_changed || has_changed?)
|
270
|
+
|
271
|
+
self.store.save_interval = 1
|
272
|
+
end
|
273
|
+
|
247
274
|
def fetch_has_many_with_attributes(key, data, sync_options = {})
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
275
|
+
# TODO: should we skip add/delete/save unless should_save?
|
276
|
+
should_save = sync_options[:save]
|
277
|
+
|
278
|
+
models_to_add = []
|
279
|
+
models_to_save = []
|
280
|
+
models_to_delete = []
|
281
|
+
|
252
282
|
track_changed_attributes do
|
283
|
+
old_collection = self.send(key)
|
284
|
+
model_class = key.classify.constantize
|
285
|
+
|
253
286
|
data.each do |attributes|
|
254
287
|
model = old_collection.detect{ |model| model.id == attributes[:id]}
|
255
288
|
unless model
|
256
289
|
model = model_class.new
|
257
|
-
|
290
|
+
models_to_add << model
|
291
|
+
end
|
292
|
+
model.fetch_with_attributes(attributes, save_associations: should_save)
|
293
|
+
if should_save && model.has_changed?
|
294
|
+
models_to_save << model
|
258
295
|
end
|
259
|
-
model.fetch_with_attributes(attributes, save_associations: sync_options[:save])
|
260
|
-
model.save if sync_options[:save] && model.has_changed?
|
261
296
|
end
|
262
297
|
old_collection.each do |old_model|
|
263
298
|
model = data.detect{ |model| model[:id] == old_model.id}
|
264
299
|
unless model
|
265
|
-
old_model
|
300
|
+
models_to_delete << old_model
|
266
301
|
end
|
267
302
|
end unless sync_options[:append]
|
268
303
|
end
|
269
|
-
|
270
|
-
|
304
|
+
|
305
|
+
update_storage({key => {
|
306
|
+
save: models_to_save,
|
307
|
+
delete: models_to_delete,
|
308
|
+
add: models_to_add
|
309
|
+
}}, sync_options)
|
271
310
|
end
|
272
311
|
|
273
312
|
def fetch_has_one(key, options = {}, sync_options = {}, &block)
|
274
313
|
use_callback = block_given?
|
275
314
|
NSLog("SYNC: started sync for #{key} in #{self.class_name_without_kvo}")
|
276
|
-
|
315
|
+
params = (options[:params] || {}).deep_merge(sync_options[:params] || {})
|
316
|
+
api_client.get association_sync_url(key, options, sync_options), params do |response, status_code|
|
277
317
|
data = options.has_key?(:sync_key) ? response[options[:sync_key]] : response
|
278
318
|
if data.present?
|
279
319
|
fetch_has_one_with_attributes(key, data, save_associations: sync_options[:save])
|
@@ -17,7 +17,9 @@ module MotionPrime
|
|
17
17
|
options[:class_name] == inverse_relation.class_name_without_kvo
|
18
18
|
end.try(:first)
|
19
19
|
|
20
|
-
|
20
|
+
# when use #find() nested children will be reallocated and their bags will be empty
|
21
|
+
# data = stored? ? find(*args) : filter(*args)
|
22
|
+
data = filter(*args)
|
21
23
|
super data
|
22
24
|
end
|
23
25
|
|
@@ -69,7 +71,7 @@ module MotionPrime
|
|
69
71
|
# @params sort_options [Hash] sorting options.
|
70
72
|
# @return Array<MotionPrime::Model> association records
|
71
73
|
def find(find_options = {}, sort_options = nil)
|
72
|
-
raise "Use `filter` method when bag has not been saved yet" unless
|
74
|
+
raise "Use `filter` method when bag has not been saved yet" unless stored?
|
73
75
|
|
74
76
|
find_options = build_find_options(find_options)
|
75
77
|
sort_options = build_sort_options(sort_options)
|
@@ -103,6 +105,10 @@ module MotionPrime
|
|
103
105
|
all.each { |obj| obj.delete }
|
104
106
|
end
|
105
107
|
|
108
|
+
def stored?
|
109
|
+
bag.store.present?
|
110
|
+
end
|
111
|
+
|
106
112
|
private
|
107
113
|
def build_find_options(options)
|
108
114
|
options ||= {}
|
@@ -31,7 +31,7 @@ module Prime
|
|
31
31
|
@preloader_next_starts_from = nil
|
32
32
|
end
|
33
33
|
|
34
|
-
def height_for_index(
|
34
|
+
def height_for_index(index)
|
35
35
|
section = cell_section_by_index(index)
|
36
36
|
unless section
|
37
37
|
Prime.logger.debug "could not find section with index #{index} for #{self.to_s}"
|
@@ -41,7 +41,7 @@ module Prime
|
|
41
41
|
section.container_height
|
42
42
|
end
|
43
43
|
|
44
|
-
def render_cell(index
|
44
|
+
def render_cell(index)
|
45
45
|
preload_sections_after(index)
|
46
46
|
super
|
47
47
|
end
|
@@ -3,7 +3,7 @@ module MotionPrime
|
|
3
3
|
module CellSectionMixin
|
4
4
|
extend ::MotionSupport::Concern
|
5
5
|
|
6
|
-
include SectionWithContainerMixin
|
6
|
+
# include SectionWithContainerMixin # already included in draw_section_mixin
|
7
7
|
|
8
8
|
attr_writer :table
|
9
9
|
attr_reader :pending_display
|
@@ -64,6 +64,7 @@ module MotionPrime
|
|
64
64
|
def cell
|
65
65
|
container_view || begin
|
66
66
|
first_element = elements.values.first
|
67
|
+
return unless first_element.is_a?(BaseElement) && first_element.view
|
67
68
|
first_element.view.superview.superview
|
68
69
|
end
|
69
70
|
end
|
@@ -376,7 +376,7 @@ module MotionPrime
|
|
376
376
|
def compute_container_options!
|
377
377
|
raw_options = {}
|
378
378
|
raw_options.merge!(self.class.container_options.try(:clone) || {})
|
379
|
-
raw_options.merge!(options
|
379
|
+
raw_options.merge!(options[:container] || {})
|
380
380
|
|
381
381
|
# allow to pass styles as proc
|
382
382
|
normalize_options(raw_options, elements_eval_object, nil, [:styles])
|
@@ -30,20 +30,19 @@ module MotionPrime
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def hard_reload_cell_section(section)
|
33
|
-
|
34
|
-
path = field_indexes[
|
35
|
-
section
|
33
|
+
field_name = section.name.to_sym
|
34
|
+
path = field_indexes[field_name]
|
35
|
+
deque_cell(section, at: path) # deque cached
|
36
36
|
|
37
|
-
fields[
|
38
|
-
fields[
|
37
|
+
fields[field_name] = load_field(self.class.fields_options[field_name])
|
38
|
+
fields[field_name].create_elements
|
39
39
|
if flat_data?
|
40
|
-
@data[path.row] = fields[
|
40
|
+
@data[path.row] = fields[field_name]
|
41
41
|
else
|
42
|
-
@data[path.section][path.row] = fields[
|
42
|
+
@data[path.section][path.row] = fields[field_name]
|
43
43
|
end
|
44
44
|
|
45
|
-
|
46
|
-
table_view.reloadRowsAtIndexPaths([path], withRowAnimation: UITableViewRowAnimationNone)
|
45
|
+
self.performSelectorOnMainThread(:reload_cells, withObject: path, waitUntilDone: false)
|
47
46
|
end
|
48
47
|
|
49
48
|
# Returns element based on field name and element name
|
@@ -201,7 +200,7 @@ module MotionPrime
|
|
201
200
|
# Table View Delegate
|
202
201
|
# ---------------------
|
203
202
|
|
204
|
-
def number_of_groups
|
203
|
+
def number_of_groups
|
205
204
|
has_many_sections? ? grouped_data.reject(&:nil?).count : 1
|
206
205
|
end
|
207
206
|
|
@@ -80,12 +80,11 @@ module MotionPrime
|
|
80
80
|
# @return [Boolean] true
|
81
81
|
def reset_table_data
|
82
82
|
@did_appear = false
|
83
|
+
Array.wrap(@data).flatten.each do |section|
|
84
|
+
section.container_element.try(:update_options, reuse_identifier: nil)
|
85
|
+
end
|
83
86
|
@data = nil
|
84
87
|
@data_stamp = nil
|
85
|
-
@reusable_cells.each do |object_id, cell|
|
86
|
-
cell.reuseIdentifier = nil
|
87
|
-
end if @reusable_cells
|
88
|
-
@reusable_cells = nil
|
89
88
|
true
|
90
89
|
end
|
91
90
|
|
@@ -93,10 +92,11 @@ module MotionPrime
|
|
93
92
|
#
|
94
93
|
# @param cell sections [Prime::Section, Array<Prime::Section>] cells which will be added to table view.
|
95
94
|
# @return [Boolean] true
|
96
|
-
def add_cell_sections(sections)
|
95
|
+
def add_cell_sections(sections, index = nil)
|
97
96
|
prepare_table_cell_sections(sections)
|
98
97
|
@data ||= []
|
99
|
-
@data
|
98
|
+
index ||= @data.count
|
99
|
+
@data.insert([index, @data.count].min, *sections)
|
100
100
|
reload_table_data
|
101
101
|
end
|
102
102
|
|
@@ -134,13 +134,21 @@ module MotionPrime
|
|
134
134
|
next Prime.logger.debug("Reload section: `#{section.name}` is not in the list") unless index
|
135
135
|
paths << index
|
136
136
|
block.call(section, index, counter)
|
137
|
-
|
137
|
+
deque_cell(section, at: path) # deque cached
|
138
138
|
section.reload
|
139
139
|
end
|
140
|
-
|
140
|
+
self.performSelectorOnMainThread(:reload_cells, withObject: paths, waitUntilDone: false)
|
141
141
|
paths
|
142
142
|
end
|
143
143
|
|
144
|
+
# Forces TableView to reload Rows by index paths
|
145
|
+
#
|
146
|
+
# @param [Array<NSIndexPath>] index paths of cells to reload.
|
147
|
+
def reload_cells(*paths)
|
148
|
+
table_view.reloadRowsAtIndexPaths(Array.wrap(paths), withRowAnimation: UITableViewRowAnimationFade)
|
149
|
+
table_view.reloadData # do not use reload_table_data (due to async_form_mixin)
|
150
|
+
end
|
151
|
+
|
144
152
|
# Changes height of cells with animation.
|
145
153
|
#
|
146
154
|
# @param cell sections [Prime::Section, Array<Prime::Section>] cells which will be updated.
|
@@ -262,8 +270,7 @@ module MotionPrime
|
|
262
270
|
table_view.try(:show)
|
263
271
|
end
|
264
272
|
|
265
|
-
def render_cell(index
|
266
|
-
table ||= table_view
|
273
|
+
def render_cell(index)
|
267
274
|
section = cell_sections_for_group(index.section)[index.row]
|
268
275
|
element = section.container_element || section.init_container_element(container_element_options_for(index))
|
269
276
|
|
@@ -271,8 +278,6 @@ module MotionPrime
|
|
271
278
|
section.render
|
272
279
|
end
|
273
280
|
|
274
|
-
@reusable_cells ||= {}
|
275
|
-
@reusable_cells[section.object_id] = view
|
276
281
|
on_cell_render(view, index)
|
277
282
|
view
|
278
283
|
end
|
@@ -289,7 +294,7 @@ module MotionPrime
|
|
289
294
|
|
290
295
|
def on_cell_render(cell, index); end
|
291
296
|
def on_appear; end
|
292
|
-
def on_click(
|
297
|
+
def on_click(index); end
|
293
298
|
|
294
299
|
def has_many_sections?
|
295
300
|
group_header_options.present? || data.try(:first).is_a?(Array)
|
@@ -307,7 +312,7 @@ module MotionPrime
|
|
307
312
|
cell_sections_for_group(index.section)[index.row]
|
308
313
|
end
|
309
314
|
|
310
|
-
def cell_name(
|
315
|
+
def cell_name(index)
|
311
316
|
record = cell_section_by_index(index)
|
312
317
|
"cell_#{record.object_id}_#{@data_stamp[record.object_id]}"
|
313
318
|
end
|
@@ -315,12 +320,12 @@ module MotionPrime
|
|
315
320
|
# Table View Delegate
|
316
321
|
# ---------------------
|
317
322
|
|
318
|
-
def number_of_groups
|
323
|
+
def number_of_groups
|
319
324
|
has_many_sections? ? data.count : 1
|
320
325
|
end
|
321
326
|
|
322
|
-
def cell_for_index(
|
323
|
-
cell = cached_cell(index
|
327
|
+
def cell_for_index(index)
|
328
|
+
cell = cached_cell(index) || render_cell(index)
|
324
329
|
# run table view is appeared callback if needed
|
325
330
|
if !@did_appear && index.row == cell_sections_for_group(index.section).size - 1
|
326
331
|
on_appear
|
@@ -328,17 +333,17 @@ module MotionPrime
|
|
328
333
|
cell.is_a?(UIView) ? cell : cell.view
|
329
334
|
end
|
330
335
|
|
331
|
-
def height_for_index(
|
336
|
+
def height_for_index(index)
|
332
337
|
section = cell_section_by_index(index)
|
333
338
|
section.create_elements
|
334
339
|
section.container_height
|
335
340
|
end
|
336
341
|
|
337
|
-
def header_cell_in_group(
|
342
|
+
def header_cell_in_group(group)
|
338
343
|
return unless header = header_section_for_group(group)
|
339
344
|
|
340
345
|
reuse_identifier = "header_#{group}_#{@header_stamp}"
|
341
|
-
cached =
|
346
|
+
cached = table_view.dequeueReusableHeaderFooterViewWithIdentifier(reuse_identifier)
|
342
347
|
return cached if cached.present?
|
343
348
|
|
344
349
|
styles = cell_section_styles(header).values.flatten
|
@@ -355,7 +360,7 @@ module MotionPrime
|
|
355
360
|
end
|
356
361
|
end
|
357
362
|
|
358
|
-
def height_for_header_in_group(
|
363
|
+
def height_for_header_in_group(group)
|
359
364
|
header_section_for_group(group).try(:container_height) || 0
|
360
365
|
end
|
361
366
|
|
@@ -403,15 +408,27 @@ module MotionPrime
|
|
403
408
|
@data
|
404
409
|
end
|
405
410
|
|
406
|
-
def cached_cell(index
|
407
|
-
|
408
|
-
|
411
|
+
def cached_cell(index)
|
412
|
+
table_view.dequeueReusableCellWithIdentifier(cell_name(index)) || begin
|
413
|
+
section = cell_section_by_index(index)
|
414
|
+
cell = section.try(:cell)
|
415
|
+
if cell.try(:superview)
|
416
|
+
Prime.logger.error "cell already exists: #{section.name}: #{cell}"
|
417
|
+
nil
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
def deque_cell(section, at: index)
|
423
|
+
table_view.dequeueReusableCellWithIdentifier(cell_name(index))
|
424
|
+
section.cell.try(:removeFromSuperview)
|
425
|
+
set_data_stamp(section.object_id)
|
409
426
|
end
|
410
427
|
|
411
428
|
def prepare_table_cell_sections(cells)
|
412
429
|
Array.wrap(cells.flatten).each do |cell|
|
413
430
|
Prime::Config.prime.cell_section.mixins.each do |mixin|
|
414
|
-
cell.class.send(:include, mixin)
|
431
|
+
cell.class.send(:include, mixin) unless (class << cell; self; end).included_modules.include?(mixin)
|
415
432
|
end
|
416
433
|
cell.screen ||= screen
|
417
434
|
cell.table ||= self.weak_ref if cell.respond_to?(:table=)
|
@@ -421,7 +438,7 @@ module MotionPrime
|
|
421
438
|
def container_element_options_for(index)
|
422
439
|
cell_section = cell_section_by_index(index)
|
423
440
|
{
|
424
|
-
reuse_identifier: cell_name(
|
441
|
+
reuse_identifier: cell_name(index),
|
425
442
|
parent_view: table_view,
|
426
443
|
bounds: {height: cell_section.container_height}
|
427
444
|
}
|
@@ -20,7 +20,7 @@ module MotionPrime
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def numberOfSectionsInTableView(table)
|
23
|
-
table_section.number_of_groups
|
23
|
+
table_section.number_of_groups
|
24
24
|
end
|
25
25
|
|
26
26
|
def tableView(table, cellForRowAtIndexPath: index)
|
@@ -34,7 +34,7 @@ module MotionPrime
|
|
34
34
|
@prev_call_time = cur_call_time
|
35
35
|
@prev_call_offset = cur_call_offset
|
36
36
|
|
37
|
-
table_section.cell_for_index(
|
37
|
+
table_section.cell_for_index(index)
|
38
38
|
end
|
39
39
|
|
40
40
|
def tableView(table, numberOfRowsInSection: group)
|
@@ -42,19 +42,19 @@ module MotionPrime
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def tableView(table, heightForRowAtIndexPath: index)
|
45
|
-
table_section.height_for_index(
|
45
|
+
table_section.height_for_index(index)
|
46
46
|
end
|
47
47
|
|
48
48
|
def tableView(table, didSelectRowAtIndexPath:index)
|
49
|
-
table_section.on_click(
|
49
|
+
table_section.on_click(index)
|
50
50
|
end
|
51
51
|
|
52
52
|
def tableView(table, viewForHeaderInSection: group)
|
53
|
-
table_section.header_cell_in_group(
|
53
|
+
table_section.header_cell_in_group(group)
|
54
54
|
end
|
55
55
|
|
56
56
|
def tableView(table, heightForHeaderInSection: group)
|
57
|
-
table_section.height_for_header_in_group(
|
57
|
+
table_section.height_for_header_in_group(group)
|
58
58
|
end
|
59
59
|
|
60
60
|
def scrollViewDidScroll(scroll)
|
data/motion-prime/version.rb
CHANGED
@@ -44,6 +44,11 @@ module MotionPrime
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def prepare_options!
|
47
|
+
if options[:size_to_fit]
|
48
|
+
options[:line_break_mode] ||= :word_wrap
|
49
|
+
options[:number_of_lines] ||= 0
|
50
|
+
end
|
51
|
+
|
47
52
|
if options.slice(:html, :line_spacing, :line_height, :underline, :fragment_color).any?
|
48
53
|
text_options = extract_attributed_text_options(options)
|
49
54
|
|
@@ -148,7 +153,7 @@ module MotionPrime
|
|
148
153
|
view.setValue "UIControlContentHorizontalAlignment#{value.camelize}".constantize, forKey: camelize_factory(key)
|
149
154
|
true
|
150
155
|
elsif key == 'content_vertical_alignment' && value.is_a?(Symbol) && %[top bottom center fill].include?(value.to_s)
|
151
|
-
view.setValue "
|
156
|
+
view.setValue "UIControlContentVerticalAlignment#{value.camelize}".constantize, forKey: camelize_factory(key)
|
152
157
|
true
|
153
158
|
elsif key.end_with?('alignment') && value.is_a?(Symbol)
|
154
159
|
view.setValue value.nstextalignment, forKey: camelize_factory(key)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
describe "Prime::Model Associations" do
|
2
|
-
|
2
|
+
before do
|
3
3
|
MotionPrime::Store.connect
|
4
4
|
end
|
5
5
|
|
@@ -27,6 +27,15 @@ describe "Prime::Model Associations" do
|
|
27
27
|
todo.items.is_a?(MotionPrime::Bag).should == true
|
28
28
|
todo.items.size.should == 0
|
29
29
|
end
|
30
|
+
|
31
|
+
it "allows to reload bag" do
|
32
|
+
todo = Todo.create(:title => "Today Tasks")
|
33
|
+
todo.items = [TodoItem.new(:text => "Hi")]
|
34
|
+
todo.items.save
|
35
|
+
todo.items << [TodoItem.new(:text => "Foo"), TodoItem.new(:text => "Bar")]
|
36
|
+
todo.items.count.should == 3
|
37
|
+
todo.items(true).count.should == 1
|
38
|
+
end
|
30
39
|
end
|
31
40
|
|
32
41
|
describe "#save" do
|
@@ -24,6 +24,26 @@ describe "Prime::Model Bag" do
|
|
24
24
|
bag.saved.count.should.be == 1
|
25
25
|
bag.changed?.should.be.false
|
26
26
|
end
|
27
|
+
|
28
|
+
it "should raise error if item already exists" do
|
29
|
+
bag = Bag.bag
|
30
|
+
page = Page.new(:text => "Hello", :id => 1)
|
31
|
+
bag << page
|
32
|
+
lambda {
|
33
|
+
bag << page
|
34
|
+
}.should.raise(Prime::StoreError)
|
35
|
+
lambda {
|
36
|
+
bag << [page, page]
|
37
|
+
}.should.raise(Prime::StoreError)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should not add duplicated items without an error" do
|
41
|
+
bag = Bag.bag
|
42
|
+
page = Page.new(:text => "Hello", :id => 1)
|
43
|
+
bag << page
|
44
|
+
bag.add([page, page], silent_validation: true)
|
45
|
+
bag.count.should == 1
|
46
|
+
end
|
27
47
|
end
|
28
48
|
|
29
49
|
describe "#+" do
|
@@ -41,9 +41,9 @@ describe MotionPrime::Model do
|
|
41
41
|
it "throw error when invalid parameter and validate_attribute_presence=true" do
|
42
42
|
lambda {
|
43
43
|
user = User.new({
|
44
|
-
name: "Eddie",
|
45
|
-
age: 12,
|
46
|
-
birthday: Time.now,
|
44
|
+
name: "Eddie",
|
45
|
+
age: 12,
|
46
|
+
birthday: Time.now,
|
47
47
|
gender: "m",
|
48
48
|
}, validate_attribute_presence: true )
|
49
49
|
}.should.raise(::MotionPrime::StoreError)
|
@@ -52,9 +52,9 @@ describe MotionPrime::Model do
|
|
52
52
|
it "creates model when invalid parameter and validate_attribute_presence=false" do
|
53
53
|
Prime.logger.disabled = true
|
54
54
|
user = User.new({
|
55
|
-
name: "Eddie",
|
56
|
-
age: 12,
|
57
|
-
birthday: Time.now,
|
55
|
+
name: "Eddie",
|
56
|
+
age: 12,
|
57
|
+
birthday: Time.now,
|
58
58
|
gender: "m",
|
59
59
|
})
|
60
60
|
user.name.should == "Eddie"
|
@@ -250,4 +250,43 @@ describe MotionPrime::Model do
|
|
250
250
|
autobot.release_at.should == release
|
251
251
|
end
|
252
252
|
end
|
253
|
+
|
254
|
+
describe "#fetch_has_many_with_attributes" do
|
255
|
+
before do
|
256
|
+
@organization = Organization.create(name: "Droid Labs")
|
257
|
+
@organization.projects << Project.new({id: 2, title: 'to_delete'})
|
258
|
+
@organization.projects << Project.new({id: 1, title: 'something'})
|
259
|
+
@organization.save
|
260
|
+
end
|
261
|
+
|
262
|
+
def process
|
263
|
+
@organization.fetch_has_many_with_attributes :projects, [{id: 1, title: 'to_save'}, {id: 3, title: 'to_add'}, {id: 4, title: 'to_add'}], save: true
|
264
|
+
end
|
265
|
+
|
266
|
+
it "should call #update_storage with valid params" do
|
267
|
+
@organization.mock!(:update_storage) do |bag_options, sync_options|
|
268
|
+
options = bag_options[:projects]
|
269
|
+
options[:delete].count.should == 1
|
270
|
+
options[:delete].first[:title].should == 'to_delete'
|
271
|
+
|
272
|
+
options[:add].count.should == 2
|
273
|
+
options[:add].all? { |to_add| to_add.title == 'to_add' }.should.be.true
|
274
|
+
|
275
|
+
options[:save].count.should == 3
|
276
|
+
to_save = options[:save] - options[:add]
|
277
|
+
to_save.count.should == 1
|
278
|
+
to_save.first[:title].should == 'to_save'
|
279
|
+
to_save.first[:id].should == 1
|
280
|
+
end
|
281
|
+
process
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should update_storage" do
|
285
|
+
process
|
286
|
+
@organization.projects.count.should == 3
|
287
|
+
@organization.projects(id: 1).first.title.should == 'to_save'
|
288
|
+
@organization.projects(id: 3).first.title.should == 'to_add'
|
289
|
+
@organization.projects(id: 4).first.title.should == 'to_add'
|
290
|
+
end
|
291
|
+
end
|
253
292
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
describe MotionPrime::FilterMixin do
|
2
|
+
def data
|
3
|
+
model_stub = Struct.new(:info, :id) # :id is used for sorting
|
4
|
+
data_array.map do |item|
|
5
|
+
model_stub.new(item, item[:id])
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def data_array
|
10
|
+
[{id: 4, name: 'iPhone'}, {id: 5, name: 'MacBook'}]
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should filter array by inclusion" do
|
14
|
+
MotionPrime::FilterMixin.new.filter_array(data, {id: %w[4 5]}).count.should.equal 2
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should find a single record (case sensitive)" do
|
18
|
+
MotionPrime::FilterMixin.new.filter_array(data, {id: 4, name: 'iPhone'}).count.should.equal 1
|
19
|
+
end
|
20
|
+
|
21
|
+
it "order filtered records" do
|
22
|
+
result = MotionPrime::FilterMixin.new.filter_array(data, {id: %w[4 5]}, sort: {id: :desc})
|
23
|
+
result.first[:id].should.equal 5
|
24
|
+
end
|
25
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motion-prime
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Iskander Haziev
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-05-
|
12
|
+
date: 2014-05-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -408,6 +408,7 @@ files:
|
|
408
408
|
- spec/unit/prime/logger.rb
|
409
409
|
- spec/unit/screens/screen_spec.rb
|
410
410
|
- spec/unit/sections/section_spec.rb
|
411
|
+
- spec/unit/support/filter_mixin_spec.rb
|
411
412
|
- travis.sh
|
412
413
|
homepage: ''
|
413
414
|
licenses:
|
@@ -465,3 +466,4 @@ test_files:
|
|
465
466
|
- spec/unit/prime/logger.rb
|
466
467
|
- spec/unit/screens/screen_spec.rb
|
467
468
|
- spec/unit/sections/section_spec.rb
|
469
|
+
- spec/unit/support/filter_mixin_spec.rb
|