motion-prime 0.6.0 → 0.7.0

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.
Files changed (46) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +4 -0
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +6 -1
  5. data/ROADMAP.md +7 -6
  6. data/bin/prime +31 -16
  7. data/doc/code/models.rb +5 -1
  8. data/doc/code/tables.rb +54 -0
  9. data/doc/docs/getting_started.html +7 -0
  10. data/doc/docs/index.html +205 -0
  11. data/doc/docs/models.html +13 -0
  12. data/doc/docs/screens.html +7 -0
  13. data/doc/docs/sections.html +7 -0
  14. data/doc/docs/tables.html +128 -0
  15. data/files/Gemfile +3 -3
  16. data/lib/motion-prime.rb +2 -0
  17. data/motion-prime.gemspec +3 -2
  18. data/motion-prime/api_client.rb +35 -36
  19. data/motion-prime/config/base.rb +0 -2
  20. data/motion-prime/core_ext/kernel.rb +2 -1
  21. data/motion-prime/elements/base_element.rb +7 -2
  22. data/motion-prime/elements/spinner.rb +7 -0
  23. data/motion-prime/elements/table_view.rb +7 -0
  24. data/motion-prime/helpers/has_search_bar.rb +11 -5
  25. data/motion-prime/models/_finder_mixin.rb +5 -12
  26. data/motion-prime/models/_nano_bag_mixin.rb +1 -1
  27. data/motion-prime/models/_sync_mixin.rb +52 -37
  28. data/motion-prime/screens/_base_mixin.rb +4 -0
  29. data/motion-prime/screens/_navigation_mixin.rb +3 -3
  30. data/motion-prime/screens/screen.rb +1 -1
  31. data/motion-prime/sections/base_section.rb +12 -12
  32. data/motion-prime/sections/form/base_field_section.rb +1 -1
  33. data/motion-prime/sections/form/date_field_section.rb +5 -0
  34. data/motion-prime/sections/form/password_field_section.rb +1 -1
  35. data/motion-prime/sections/form/string_field_section.rb +1 -1
  36. data/motion-prime/sections/form/text_field_section.rb +1 -1
  37. data/motion-prime/sections/table.rb +7 -0
  38. data/motion-prime/sections/table/table_delegate.rb +14 -4
  39. data/motion-prime/support/mp_spinner.rb +16 -0
  40. data/motion-prime/support/mp_table_view.rb +6 -0
  41. data/motion-prime/support/tab_bar_controller.rb +3 -3
  42. data/motion-prime/support/temp_fixes.rb +6 -0
  43. data/motion-prime/version.rb +1 -1
  44. data/motion-prime/views/layout.rb +2 -1
  45. data/motion-prime/views/view_builder.rb +1 -1
  46. metadata +24 -2
@@ -107,7 +107,7 @@ module MotionPrime
107
107
  if retry_count == 3
108
108
  raise StoreError, e.description
109
109
  else
110
- send (:store=, store, retry_count + 1)
110
+ send(:store=, store, retry_count + 1)
111
111
  end
112
112
  end
113
113
 
@@ -9,8 +9,8 @@ module MotionPrime
9
9
  end
10
10
 
11
11
  # Get normalized sync url of this Prime::Model
12
- #
13
- # @param method [Symbol] http method
12
+ #
13
+ # @param method [Symbol] http method
14
14
  # @return url [String] url to use in model sync
15
15
  def sync_url(method = :get, options = {})
16
16
  url = self.class.sync_url
@@ -19,7 +19,7 @@ module MotionPrime
19
19
  end
20
20
 
21
21
  # Get normalized sync url of associated Prime::Model
22
- #
22
+ #
23
23
  # @param key [Symbol] association name
24
24
  # @return url [String] url to use in model association sync
25
25
  def association_sync_url(key, options)
@@ -29,7 +29,7 @@ module MotionPrime
29
29
  end
30
30
 
31
31
  # Destroy model on server and delete on local
32
- #
32
+ #
33
33
  # @param block [Proc] block to be executed after destroy
34
34
  # @return self[Prime::Model] deleted model.
35
35
  def destroy(&block)
@@ -41,13 +41,13 @@ module MotionPrime
41
41
  end
42
42
 
43
43
  # Fetch model from server and save on local
44
- #
44
+ #
45
45
  def fetch!(options = {}, &block)
46
46
  fetch(options.merge(save: true), &block)
47
47
  end
48
48
 
49
49
  # Fetch model from server
50
- #
50
+ #
51
51
  # @param options [Hash] fetch options
52
52
  # @option options [Symbol] :method Http method to calculate url, `:get` by default
53
53
  # @option options [Boolean] :associations Also fetch associations
@@ -60,8 +60,9 @@ module MotionPrime
60
60
 
61
61
  will_fetch_model = !url.blank?
62
62
  will_fetch_associations = !options.has_key?(:associations) || options[:associations]
63
+ will_fetch_associations = false unless has_associations_to_fetch?
63
64
 
64
- fetch_with_url url do |data, status_code|
65
+ fetch_with_url url, options do |data, status_code|
65
66
  save if options[:save]
66
67
  block.call(data, status_code, data) if use_callback && !will_fetch_associations
67
68
  end if will_fetch_model
@@ -98,10 +99,12 @@ module MotionPrime
98
99
  #
99
100
  # @param url [String] url to fetch
100
101
  # @param block [Proc] block to be executed after fetch
101
- def fetch_with_url(url, &block)
102
+ def fetch_with_url(url, options = {}, &block)
102
103
  use_callback = block_given?
103
104
  api_client.get(url) do |data, status_code|
104
- fetch_with_attributes(data, &block) if data.present?
105
+ if data.present?
106
+ fetch_with_attributes(data, save_associations: options[:save], &block)
107
+ end
105
108
  block.call(data, status_code, data) if use_callback
106
109
  end
107
110
  end
@@ -114,12 +117,10 @@ module MotionPrime
114
117
  use_callback = block_given?
115
118
  filtered_attributes = filtered_updatable_attributes(options)
116
119
 
120
+ attributes = attributes_to_post_data(model_name, filtered_attributes)
121
+
117
122
  post_data = options[:params_root] || {}
118
- post_data[:files] = {}
119
- filtered_attributes.delete(:files).each do |file_name, file|
120
- post_data[:files][[model_name, file_name].join] = file
121
- end
122
- post_data[model_name] = filtered_attributes
123
+ post_data.merge!(attributes)
123
124
 
124
125
  method = options[:method] || (persisted? ? :put : :post)
125
126
  api_client.send(method, url, post_data, options) do |data, status_code|
@@ -171,12 +172,17 @@ module MotionPrime
171
172
  self
172
173
  end
173
174
 
175
+ def associations
176
+ @associations ||= (self.class._associations || {}).clone
177
+ end
178
+
179
+ def associations_to_fetch
180
+ @associations_to_fetch ||= associations.select { |key, v| fetch_association?(key) }
181
+ end
182
+
174
183
  def fetch_associations(sync_options = {}, &block)
175
184
  use_callback = block_given?
176
- associations = self.class._associations || {}
177
- association_keys = associations.keys.select { |key| fetch_association?(key) }
178
-
179
- association_keys.each_with_index do |key, index|
185
+ associations_to_fetch.keys.each_with_index do |key, index|
180
186
  if use_callback && associations.count - 1 == index
181
187
  fetch_association(key, sync_options, &block)
182
188
  else
@@ -185,19 +191,23 @@ module MotionPrime
185
191
  end
186
192
  end
187
193
 
194
+ def has_associations_to_fetch?
195
+ associations_to_fetch.present?
196
+ end
197
+
188
198
  def has_association?(key)
189
- !(self.class._associations || {})[key.to_sym].nil?
199
+ !associations[key.to_sym].nil?
190
200
  end
191
201
 
192
202
  def fetch_association?(key)
193
- options = self.class._associations[key]
203
+ options = associations[key.to_sym]
194
204
  return false if options[:if] && !options[:if].to_proc.call(self)
195
205
  association_sync_url(key, options).present?
196
206
  end
197
207
 
198
208
  def fetch_association(key, sync_options = {}, &block)
199
209
  return unless fetch_association?(key)
200
- options = self.class._associations[key]
210
+ options = associations[key.to_sym]
201
211
  if options[:type] == :many
202
212
  fetch_has_many(key, options, sync_options, &block)
203
213
  else
@@ -206,12 +216,12 @@ module MotionPrime
206
216
  end
207
217
 
208
218
  def fetch_association_with_attributes(key, data, sync_options = {})
209
- options = (self.class._associations || {})[key.to_sym]
219
+ options = associations[key.to_sym]
210
220
  return unless options
211
221
  if options[:type] == :many
212
- fetch_has_many_with_attributes(key, data, sync_options)
222
+ fetch_has_many_with_attributes(key, data || [], sync_options)
213
223
  else
214
- fetch_has_one_with_attributes(key, data, sync_options)
224
+ fetch_has_one_with_attributes(key, data || {}, sync_options)
215
225
  end
216
226
  end
217
227
 
@@ -290,13 +300,11 @@ module MotionPrime
290
300
  updatable_attributes = self.class.updatable_attributes
291
301
 
292
302
  if updatable_attributes.blank?
293
- attrs = slice_attributes ? attributes_hash.slice(*slice_attributes) : attributes_hash
294
- return attrs.merge(files: {})
303
+ return slice_attributes ? attributes_hash.slice(*slice_attributes) : attributes_hash
295
304
  end
296
305
 
297
306
  updatable_attributes = updatable_attributes.slice(*slice_attributes) if slice_attributes
298
- updatable_attributes.to_a.inject({files: {}}) do |hash, attribute|
299
- key, options = *attribute
307
+ updatable_attributes.inject({}) do |hash, (key, options)|
300
308
  next hash if options[:if] && !send(options[:if])
301
309
  value = if block = options[:block]
302
310
  block.call(self, hash)
@@ -304,14 +312,7 @@ module MotionPrime
304
312
  info[key]
305
313
  end
306
314
 
307
- if key.to_s.starts_with?('file_')
308
- value.to_a.each do |file_data|
309
- file_name, file = file_data.to_a
310
- hash[:files]["[#{key.partition('_').last}]#{file_name}"] = file
311
- end
312
- else
313
- hash.merge!(key => value)
314
- end
315
+ hash[key] = value
315
316
  hash
316
317
  end
317
318
  end
@@ -320,6 +321,20 @@ module MotionPrime
320
321
  normalize_object(url).to_s.gsub(':id', id.to_s)
321
322
  end
322
323
 
324
+ def attributes_to_post_data(root_name, attributes)
325
+ result = {:_files => [], root_name => attributes}
326
+
327
+ result[root_name].each do |name, field_attrs|
328
+ next unless field_attrs.is_a?(Hash)
329
+ files = Array.wrap(field_attrs.delete(:_files)).map do |file|
330
+ file[:name].insert(0, "#{root_name}[#{name}]")
331
+ file
332
+ end
333
+ result[:_files] += files
334
+ end
335
+ result
336
+ end
337
+
323
338
  module ClassMethods
324
339
  def fetch_all_with_attributes(data)
325
340
  data.map do |attrs|
@@ -348,7 +363,7 @@ module MotionPrime
348
363
  def updatable_attributes(*attrs)
349
364
  return self._updatable_attributes if attrs.blank?
350
365
  attrs.each do |attribute|
351
- updatable_attribute attribute
366
+ updatable_attribute(attribute)
352
367
  end
353
368
  end
354
369
 
@@ -17,6 +17,10 @@ module MotionPrime
17
17
  UIApplication.sharedApplication.delegate
18
18
  end
19
19
 
20
+ def parent_screen=(value)
21
+ @parent_screen = value.try(:weak_ref)
22
+ end
23
+
20
24
  # Setup the screen, this method will be called when you run MPViewController.new
21
25
  # @param options [hash] Options passed to setup
22
26
  # @return [MotionPrime::Screen] Ready to use screen
@@ -49,7 +49,7 @@ module MotionPrime
49
49
  end
50
50
 
51
51
  def wrap_in_navigation?
52
- options[:navigation] || options[:navigation].nil?
52
+ options.fetch(:navigation, true)
53
53
  end
54
54
 
55
55
  def wrap_in_navigation
@@ -63,11 +63,11 @@ module MotionPrime
63
63
  end
64
64
 
65
65
  def navigation_controller
66
- @navigation_controller ||= self.navigationController
66
+ @navigation_controller ||= self.navigationController.try(:weak_ref)
67
67
  end
68
68
 
69
69
  def navigation_controller=(val)
70
- @navigation_controller = val
70
+ @navigation_controller = val.try(:weak_ref)
71
71
  end
72
72
 
73
73
  private
@@ -47,7 +47,7 @@ module MotionPrime
47
47
  def dealloc
48
48
  pp 'Deallocating Screen', self.object_id, self.to_s
49
49
  # FIXME: calling instance_eval in title method (_base_screen_mixin) instance variables need to be cleared manually
50
- clear_instance_variables
50
+ clear_instance_variables(except: [:_search_bar])
51
51
  super
52
52
  end
53
53
 
@@ -37,7 +37,7 @@ module MotionPrime
37
37
  end
38
38
 
39
39
  def dealloc
40
- # pp 'deallocating section', self.name, self.elements.try(:count), self.to_s, self.object_id
40
+ # pp 'Deallocating section', self.name, self.to_s, self.object_id
41
41
  NSNotificationCenter.defaultCenter.removeObserver self # unbinding events created in bind_keyboard_events
42
42
  super
43
43
  end
@@ -55,7 +55,7 @@ module MotionPrime
55
55
  end
56
56
 
57
57
  # Get computed container height
58
- #
58
+ #
59
59
  # @example
60
60
  # class MySection < Prime::Section
61
61
  # container height: proc { element(:title).content_outer_height }
@@ -63,42 +63,42 @@ module MotionPrime
63
63
  # end
64
64
  # section = MySection.new
65
65
  # section.container_height # => 46
66
- #
66
+ #
67
67
  # @return height [Float, Integer] computed height
68
68
  def container_height
69
69
  container_options[:height] || DEFAULT_CONTENT_HEIGHT
70
70
  end
71
71
 
72
72
  # Get section default name, based on class name
73
- #
73
+ #
74
74
  # @example
75
75
  # class ProfileSection < Prime::Section
76
76
  # end
77
- #
77
+ #
78
78
  # section = ProfileSection.new
79
79
  # section.default_name # => 'profile'
80
80
  # section.name # => 'profile'
81
- #
81
+ #
82
82
  # another_section = ProfileSection.new(name: 'another')
83
83
  # another_section.default_name # => 'profile'
84
84
  # another_section.name # => 'another'
85
- #
85
+ #
86
86
  # @return name [String] section default name
87
87
  def default_name
88
88
  self.class_name_without_kvo.demodulize.underscore.gsub(/\_section$/, '')
89
89
  end
90
90
 
91
91
  # Get section elements options, where the key is element name.
92
- #
92
+ #
93
93
  # @return options [Hash] elements options
94
94
  def elements_options
95
95
  self.class.elements_options || {}
96
96
  end
97
97
 
98
98
  # Create elements if they are not created yet.
99
- # This will not cause rendering elements,
99
+ # This will not cause rendering elements,
100
100
  # they will be rendered immediately after that or rendered async later, based on type of section.
101
- #
101
+ #
102
102
  # @return result [Boolean] true if has been loaded by this thread.
103
103
  def load_section
104
104
  return if @section_loaded
@@ -113,7 +113,7 @@ module MotionPrime
113
113
  end
114
114
 
115
115
  # Force load section
116
- #
116
+ #
117
117
  # @return result [Boolean] true if has been loaded by this thread.
118
118
  def load_section!
119
119
  @section_loaded = false
@@ -267,7 +267,7 @@ module MotionPrime
267
267
  elements = Array.wrap(keyboard_close_bindings_options[:elements])
268
268
  views = Array.wrap(keyboard_close_bindings_options[:views])
269
269
 
270
- elements.each do |el|
270
+ elements.each do |el|
271
271
  views << el.view if %w[text_field text_view].include?(el.view_name) && el.view
272
272
  end
273
273
  views.compact.each(&:resignFirstResponder)
@@ -108,7 +108,6 @@ module MotionPrime
108
108
  focus
109
109
  form.on_input_change(view(:input))
110
110
  end
111
- view(:input).delegate = self.form.table_delegate
112
111
  end
113
112
 
114
113
  def observing_errors?
@@ -154,6 +153,7 @@ module MotionPrime
154
153
  unobserve observing_errors_for.errors, observing_errors_for.errors.unique_key(field)
155
154
  }.weak!
156
155
  errors_observer_fields.each(&block)
156
+ # TODO: clear 'on' events
157
157
  end
158
158
 
159
159
  def container_height
@@ -20,5 +20,10 @@ module MotionPrime
20
20
  form.send(options[:action]) if options[:action]
21
21
  end
22
22
  end
23
+
24
+ def dealloc
25
+ picker.setDelegate nil
26
+ super
27
+ end
23
28
  end
24
29
  end
@@ -3,7 +3,7 @@ module MotionPrime
3
3
  element :label, type: :label do
4
4
  options[:label] || {}
5
5
  end
6
- element :input, type: :text_field do
6
+ element :input, type: :text_field, delegate: proc { form.table_delegate } do
7
7
  {secure_text_entry: true}.merge(options[:input] || {})
8
8
  end
9
9
  element :error_message, type: :error_message, text: proc { all_errors.join("\n") if observing_errors? }
@@ -4,7 +4,7 @@ module MotionPrime
4
4
  options[:label] || {}
5
5
  end
6
6
 
7
- element :input, type: :text_field do
7
+ element :input, type: :text_field, delegate: proc { form.table_delegate } do
8
8
  options[:input] || {}
9
9
  end
10
10
 
@@ -3,7 +3,7 @@ module MotionPrime
3
3
  element :label, type: :label do
4
4
  options[:label] || {}
5
5
  end
6
- element :input, type: :text_view do
6
+ element :input, type: :text_view, delegate: proc { form.table_delegate } do
7
7
  {editable: true}.merge(options[:input] || {})
8
8
  end
9
9
 
@@ -19,6 +19,13 @@ module MotionPrime
19
19
  []
20
20
  end
21
21
 
22
+ def dealloc
23
+ pp 'Deallocating table', self.to_s, self.table_view.to_s
24
+ table_delegate.clear_delegated
25
+ table_view.setDataSource nil
26
+ super
27
+ end
28
+
22
29
  def async_data?
23
30
  self.class.async_data_options
24
31
  end
@@ -3,12 +3,22 @@ module MotionPrime
3
3
  attr_accessor :table_section
4
4
  def initialize(options)
5
5
  self.table_section = options[:section].try(:weak_ref)
6
+ @section_instance = table_section.to_s
6
7
  end
7
8
 
8
- # def dealloc
9
- # pp '@@ dealloc table_delegate'
10
- # super
11
- # end
9
+ def delegated_by(view)
10
+ @delegated_views ||= []
11
+ @delegated_views << view
12
+ end
13
+
14
+ def clear_delegated
15
+ Array.wrap(@delegated_views).each { |view| view.setDelegate(nil) }
16
+ end
17
+
18
+ def dealloc
19
+ pp 'Deallocating table_delegate for ', @section_instance
20
+ super
21
+ end
12
22
 
13
23
  def init_pull_to_refresh
14
24
  return unless block = table_section.class.pull_to_refresh_block