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