motion-prime 0.6.0 → 0.7.0

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