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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- OWYyOGRiMGJjNjc2MjdkM2JiOTBlMWZjMzk2YTFjZTgwNTUzMDA2YQ==
4
+ ODMzMDQ2MmI4YTBmYTY4NWZiOTQzZjQ5NGVmMGEyNTFiN2NiZDhhOQ==
5
5
  data.tar.gz: !binary |-
6
- MWU3OTRmN2RjZjRlZDc1M2Y2ZjAwZDI1MDRjYjI4MDk5ODU4N2E1Zg==
6
+ MDRmZmM2Y2QwY2Q5ZDI0MzY4OTkyZjg4MTQ1YTQ3NDFkMjA0YmM5ZA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- Y2U5NzJkZjkxMTMyMTNjMzk2NGJlYTRkYjZjNDljN2QwODI5NDg2OWYxNWI4
10
- ZGU3ZTI3MWVlYWFiMGJjMjNhOGFmMzA2YWUyZmUwNzE0YjZmNTM4NzA0OWNl
11
- ZDNmZDI4NjA5YWM5NWM4NWJlMDllZWEyN2ZkMGI0NjAzN2NjMTA=
9
+ ZDJlMGEwMzAxNjE5ZTVlMzIxYTJiMWI5ODg4N2JhYjIwMjQzMDg2NGVjYjA0
10
+ YWVkMmZhNDA5MjgwMjZhMGU3YzNhM2UyNjBkZTJjZWJmZDRjNDU2NjdjOWNm
11
+ ZTEwYzUwMGRjMjlmYTc5OGEzOTlkMmEwODQ0NzRmM2Q4ZWNiMzY=
12
12
  data.tar.gz: !binary |-
13
- Zjc5MDQ2MjJhYWY2MzU1YTIwYWJmZmE0NDhiOWVjNmY5Njg0ZmE5NTA3ZDg2
14
- YmM2NmNhNWNmNmQ4NjQwYzQ3MTZkNDljNTFiY2EyMzljZDI0OGYzOTc2YjM3
15
- YzU3ODg5MTNjNWJmNzMzZTNkMzVhNzE2YmRiOWRlM2FjZjE5ZGI=
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- motion-prime (0.9.8)
4
+ motion-prime (0.9.9)
5
5
  activesupport
6
6
  afmotion (~> 2.1.0)
7
7
  bubble-wrap (~> 1.6.0)
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.
@@ -5,7 +5,7 @@ class <%= @p_class_name %>IndexTableSection < Prime::TableSection
5
5
  end
6
6
  end
7
7
 
8
- def on_click(table, index)
8
+ def on_click(index)
9
9
  section = data[index.row]
10
10
  screen.open_screen '<%= @p_name %>#show', params: { model: section.model }
11
11
  end
@@ -109,8 +109,10 @@ module MotionPrime
109
109
  rerender!
110
110
  end
111
111
 
112
- def update_options(options)
113
- ViewStyler.new(view, view.superview.try(:bounds), options).apply
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
@@ -53,7 +53,7 @@ module MotionPrime
53
53
 
54
54
  def rerender!
55
55
  section.cached_draw_image = nil
56
- view.setNeedsDisplay
56
+ view.try(:setNeedsDisplay)
57
57
  end
58
58
 
59
59
  protected
@@ -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
- line_break_mode_name = options.fetch(:line_break_mode, :tail_truncation)
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
- return _bags[name] if _bags[name]
32
- bag_key = self.info[name]
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).clear
73
- self.send(:"#{bag_name}") << value
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, &block|
208
- track_changed_attributes do
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
- end
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? { |field, value| entity.info[field] == value }
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
- prepared = prepare_for_store(object_or_array)
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? && self.store && self.find(id: object.id, bag_key: self.key).any?
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
- should_save = options[:save_associations]
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
- api_client.get association_sync_url(key, options, sync_options) do |response, status_code|
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
- old_collection = self.send(key)
249
- model_class = key.classify.constantize
250
- self.store.save_interval = data.present? ? data.count : 1
251
- # Update/Create existing records
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
- self.send(key).add(model)
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.delete
300
+ models_to_delete << old_model
266
301
  end
267
302
  end unless sync_options[:append]
268
303
  end
269
- save if sync_options[:save] && has_changed?
270
- self.store.save_interval = 1
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
- api_client.get association_sync_url(key, options, sync_options) do |response, status_code|
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
- data = bag.store.present? ? find(*args) : filter(*args)
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 bag.store.present?
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 ||= {}
@@ -1,6 +1,7 @@
1
1
  module Prime
2
2
  module AsyncFormMixin
3
3
  def reload_table_data
4
+ # FIXME: duplicated cells (see cached_cell error)
4
5
  return super unless async_data?
5
6
  sections = NSMutableIndexSet.new
6
7
  number_of_groups.times do |section_id|
@@ -31,7 +31,7 @@ module Prime
31
31
  @preloader_next_starts_from = nil
32
32
  end
33
33
 
34
- def height_for_index(table, 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, table)
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.delete(:container) || {})
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
- field = section.name.to_sym
34
- path = field_indexes[field]
35
- section.cell.try(:removeFromSuperview)
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[field] = load_field(self.class.fields_options[field])
38
- fields[field].create_elements
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[field]
40
+ @data[path.row] = fields[field_name]
41
41
  else
42
- @data[path.section][path.row] = fields[field]
42
+ @data[path.section][path.row] = fields[field_name]
43
43
  end
44
44
 
45
- set_data_stamp(fields[field].object_id)
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(table = nil)
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 += sections
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
- set_data_stamp(section.object_id)
137
+ deque_cell(section, at: path) # deque cached
138
138
  section.reload
139
139
  end
140
- table_view.reloadRowsAtIndexPaths(paths, withRowAnimation: UITableViewRowAnimationFade)
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, table = nil)
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(table, index); end
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(table, index)
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(table = nil)
323
+ def number_of_groups
319
324
  has_many_sections? ? data.count : 1
320
325
  end
321
326
 
322
- def cell_for_index(table, index)
323
- cell = cached_cell(index, table) || render_cell(index, table)
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(table, 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(table, 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 = table.dequeueReusableHeaderFooterViewWithIdentifier(reuse_identifier)
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(table, 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, table = nil)
407
- table ||= self.table_view
408
- table.dequeueReusableCellWithIdentifier(cell_name(table, index))
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(table_view, index),
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(table)
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(table, 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(table, 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(table, index)
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(table, 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(table, group)
57
+ table_section.height_for_header_in_group(group)
58
58
  end
59
59
 
60
60
  def scrollViewDidScroll(scroll)
@@ -1,3 +1,3 @@
1
1
  module MotionPrime
2
- VERSION = "0.9.8"
2
+ VERSION = "0.9.9"
3
3
  end
@@ -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 "UIControlContentHorizontalAlignment#{value.camelize}".constantize, forKey: camelize_factory(key)
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)
@@ -5,7 +5,7 @@ class TasksIndexTableSection < Prime::TableSection
5
5
  end
6
6
  end
7
7
 
8
- def on_click(table, index)
8
+ def on_click(index)
9
9
  section = data[index.row]
10
10
  screen.open_screen 'tasks#show', params: { model: section.model }
11
11
  end
@@ -1,5 +1,5 @@
1
1
  describe "Prime::Model Associations" do
2
- before do
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.8
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-17 00:00:00.000000000 Z
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