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 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