motion-prime 0.3.3 → 0.4.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 (47) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +4 -0
  3. data/Gemfile.lock +1 -1
  4. data/ROADMAP.md +3 -3
  5. data/doc/code/getting_started.rb +6 -5
  6. data/files/app/screens/sidebar_screen.rb +2 -2
  7. data/files/app/sections/sidebar/action.rb +1 -1
  8. data/motion-prime/api_client.rb +1 -1
  9. data/motion-prime/core_ext/kernel.rb +4 -0
  10. data/motion-prime/elements/_content_padding_mixin.rb +12 -4
  11. data/motion-prime/elements/_content_text_mixin.rb +10 -2
  12. data/motion-prime/elements/{base.rb → base_element.rb} +16 -7
  13. data/motion-prime/elements/button.rb +1 -1
  14. data/motion-prime/elements/draw/_draw_background_mixin.rb +20 -9
  15. data/motion-prime/elements/draw/image.rb +2 -2
  16. data/motion-prime/elements/draw/label.rb +7 -6
  17. data/motion-prime/elements/draw.rb +8 -3
  18. data/motion-prime/elements/label.rb +2 -2
  19. data/motion-prime/elements/table_view_cell.rb +7 -0
  20. data/motion-prime/helpers/has_search_bar.rb +1 -1
  21. data/motion-prime/helpers/has_styles.rb +7 -7
  22. data/motion-prime/models/model.rb +1 -1
  23. data/motion-prime/models/sync.rb +10 -10
  24. data/motion-prime/screens/_navigation_mixin.rb +1 -1
  25. data/motion-prime/sections/_draw_mixin.rb +74 -0
  26. data/motion-prime/sections/{base.rb → base_section.rb} +104 -55
  27. data/motion-prime/sections/form/base_field_section.rb +6 -6
  28. data/motion-prime/sections/form/base_header_section.rb +2 -3
  29. data/motion-prime/sections/form.rb +38 -17
  30. data/motion-prime/sections/tabbed.rb +3 -3
  31. data/motion-prime/sections/table.rb +172 -80
  32. data/motion-prime/services/table_data_indexes.rb +51 -0
  33. data/motion-prime/styles/_mixins.rb +8 -0
  34. data/motion-prime/support/dm_cell_with_section.rb +1 -1
  35. data/motion-prime/support/dm_text_field.rb +17 -12
  36. data/motion-prime/support/dm_text_view.rb +10 -23
  37. data/motion-prime/version.rb +1 -1
  38. data/motion-prime/views/layout.rb +4 -2
  39. data/motion-prime/views/styles.rb +2 -0
  40. data/motion-prime/views/view_builder.rb +8 -2
  41. data/motion-prime/views/view_styler.rb +6 -2
  42. metadata +8 -9
  43. data/motion-prime/helpers/cell_section.rb +0 -9
  44. data/motion-prime/sections/draw.rb +0 -93
  45. data/motion-prime/sections/form/text_with_button_field_section.rb +0 -23
  46. data/motion-prime/sections/table/base_cell_section.rb +0 -5
  47. data/motion-prime/sections/table/draw_cell_section.rb +0 -5
@@ -15,42 +15,26 @@ module MotionPrime
15
15
  KEYBOARD_HEIGHT_LANDSCAPE = 162
16
16
  DEFAULT_CONTENT_HEIGHT = 65
17
17
  include ::MotionSupport::Callbacks
18
- include MotionPrime::HasAuthorization
19
- include MotionPrime::HasNormalizer
18
+ include HasAuthorization
19
+ include HasNormalizer
20
+ include DrawMixin
20
21
 
21
22
  attr_accessor :screen, :model, :name, :options, :elements, :section_styles
22
23
  class_attribute :elements_options, :container_options, :keyboard_close_bindings
23
24
  define_callbacks :render
24
25
 
25
26
  def initialize(options = {})
26
- super
27
27
  @options = options
28
+ self.screen = options[:screen]
28
29
  @model = options[:model]
29
30
  @name = options[:name] ||= default_name
30
31
  @options_block = options[:block]
31
- load_section
32
- end
33
-
34
- def style_options
35
- @style_options ||= if section_styles.present?
36
- Styles.for(section_styles.values.flatten)
37
- else
38
- {}
39
- end
40
32
  end
41
33
 
42
34
  def container_options
43
35
  @container_options ||= (style_options.delete(:container) || {}).merge(base_container_options)
44
36
  end
45
37
 
46
- def base_container_options
47
- @base_container_options ||= begin
48
- container_options = self.class.container_options.try(:clone) || {}
49
- container_options.merge!(options.delete(:container) || {})
50
- normalize_options(container_options)
51
- end
52
- end
53
-
54
38
  def container_height
55
39
  container_options[:height] || DEFAULT_CONTENT_HEIGHT
56
40
  end
@@ -68,16 +52,33 @@ module MotionPrime
68
52
  end
69
53
 
70
54
  def load_section
55
+ return if @section_loaded
56
+ if @section_loading
57
+ sleep 0.1
58
+ return @section_loaded ? false : load_section
59
+ end
60
+ @section_loading = true
71
61
  create_elements
72
- self.instance_eval(&@options_block) if @options_block.is_a?(Proc)
62
+ @section_loading = false
63
+ return @section_loaded = true
73
64
  end
74
65
 
75
- def reload_section
76
- self.elements.values.map(&:view).flatten.compact.each { |view| view.removeFromSuperview }
66
+ def load_section!
67
+ @section_loaded = false
77
68
  load_section
69
+ end
70
+
71
+ def reload_section
72
+ self.elements_to_render.values.map(&:view).flatten.compact.each { |view| view.removeFromSuperview }
73
+ load_section!
78
74
  run_callbacks :render do
79
75
  render!
80
76
  end
77
+
78
+ if @table && !self.is_a?(BaseFieldSection)
79
+ cell.setNeedsDisplay
80
+ @table.table_view.reloadData
81
+ end
81
82
  end
82
83
 
83
84
  def create_elements
@@ -85,15 +86,29 @@ module MotionPrime
85
86
  elements_options.each do |key, opts|
86
87
  add_element(key, opts)
87
88
  end
89
+ self.instance_eval(&@options_block) if @options_block.is_a?(Proc)
90
+ end
91
+
92
+ def load_elements
93
+ self.elements.values.each do |element|
94
+ element.computed_options if element.respond_to?(:computed_options)
95
+ end
88
96
  end
89
97
 
90
- def add_element(key, opts)
98
+ def add_element(key, options)
91
99
  return unless render_element?(key)
100
+ opts = options.clone
92
101
  index = opts.delete(:at)
93
102
  options = build_options_for_element(opts)
94
103
  options[:name] ||= key
95
- options[:type] ||= (options[:text] || options[:attributed_text_options]) ? :label : :view
96
- element = MotionPrime::BaseElement.factory(options.delete(:type), options)
104
+
105
+ type = options.delete(:type)
106
+ element = if self.is_a?(BaseFieldSection) || self.is_a?(BaseHeaderSection) || options.delete(:as).to_s == 'view'
107
+ MotionPrime::BaseElement.factory(type, options)
108
+ else
109
+ MotionPrime::DrawElement.factory(type, options) || MotionPrime::BaseElement.factory(type, options)
110
+ end
111
+
97
112
  if index
98
113
  self.elements = Hash[self.elements.to_a.insert index, [key, element]]
99
114
  else
@@ -105,29 +120,25 @@ module MotionPrime
105
120
  true
106
121
  end
107
122
 
108
- def build_options_for_element(opts)
109
- # we should clone options to prevent overriding options
110
- # in next element with same name in another class
111
- options = opts.clone
112
- options.merge(section: self)
113
- end
114
-
115
123
  def cell
116
- first_element = elements.values.first
117
- first_element.view.superview.superview
124
+ container_view || begin
125
+ first_element = elements.values.first
126
+ first_element.view.superview.superview
127
+ end
118
128
  end
119
129
 
120
130
  def render(container_options = {})
121
131
  self.container_options.merge!(container_options)
122
- self.screen = container_options.delete(:to)
132
+ load_section
133
+
123
134
  run_callbacks :render do
124
135
  render!
125
136
  end
126
137
  end
127
138
 
128
139
  def render!
129
- elements.each do |key, element|
130
- element.render(to: screen)
140
+ elements_to_render.each do |key, element|
141
+ element.render
131
142
  end
132
143
  end
133
144
 
@@ -140,14 +151,18 @@ module MotionPrime
140
151
  end
141
152
 
142
153
  def hide
143
- elements.values.each do |element|
144
- element.view.hidden = true
154
+ if container_view
155
+ container_view.hidden = true
156
+ else
157
+ elements.values.each(&:hide)
145
158
  end
146
159
  end
147
160
 
148
161
  def show
149
- elements.values.each do |element|
150
- element.view.hidden = false
162
+ if container_view
163
+ container_view.hidden = false
164
+ else
165
+ elements.values.each(&:show)
151
166
  end
152
167
  end
153
168
 
@@ -181,19 +196,6 @@ module MotionPrime
181
196
  object: nil
182
197
  end
183
198
 
184
- def bind_keyboard_close
185
- return unless self.class.keyboard_close_bindings.present?
186
- Array.wrap(self.instance_eval(&self.class.keyboard_close_bindings[:tap_on])).each do |view|
187
- gesture_recognizer = UITapGestureRecognizer.alloc.initWithTarget(self, action: :hide_keyboard)
188
- view.addGestureRecognizer(gesture_recognizer)
189
- gesture_recognizer.cancelsTouchesInView = false
190
- end
191
- end
192
-
193
- def keyboard_close_bindings_options
194
- @keyboard_close_bindings_options ||= normalize_options(self.class.keyboard_close_bindings.clone, self)
195
- end
196
-
197
199
  def hide_keyboard
198
200
  elements = Array.wrap(keyboard_close_bindings_options[:elements])
199
201
  views = Array.wrap(keyboard_close_bindings_options[:views])
@@ -202,6 +204,53 @@ module MotionPrime
202
204
  (views + Array.wrap(keyboard_close_bindings_options[:views])).compact.each(&:resignFirstResponder)
203
205
  end
204
206
 
207
+ protected
208
+ def bind_keyboard_close
209
+ return unless self.class.keyboard_close_bindings.present?
210
+ Array.wrap(self.instance_eval(&self.class.keyboard_close_bindings[:tap_on])).each do |view|
211
+ gesture_recognizer = UITapGestureRecognizer.alloc.initWithTarget(self, action: :hide_keyboard)
212
+ view.addGestureRecognizer(gesture_recognizer)
213
+ gesture_recognizer.cancelsTouchesInView = false
214
+ end
215
+ end
216
+
217
+ def keyboard_close_bindings_options
218
+ @keyboard_close_bindings_options ||= normalize_options(self.class.keyboard_close_bindings.clone, self)
219
+ end
220
+ def elements_to_draw
221
+ self.elements.select { |key, element| element.is_a?(DrawElement) }
222
+ end
223
+
224
+ def elements_to_render
225
+ self.elements.select { |key, element| element.is_a?(BaseElement) }
226
+ end
227
+
228
+ def build_options_for_element(opts)
229
+ # we should clone options to prevent overriding options
230
+ # in next element with same name in another class
231
+ options = opts.clone
232
+ options[:type] ||= (options[:text] || options[:attributed_text_options]) ? :label : :view
233
+ options.merge(screen: screen, section: self)
234
+ end
235
+
236
+ private
237
+ def style_options
238
+ @style_options ||= if section_styles.present?
239
+ # TODO: pass through normalizer?
240
+ Styles.for(section_styles.values.flatten)
241
+ else
242
+ {}
243
+ end
244
+ end
245
+
246
+ def base_container_options
247
+ @base_container_options ||= begin
248
+ container_options = self.class.container_options.try(:clone) || {}
249
+ container_options.merge!(options.delete(:container) || {})
250
+ normalize_options(container_options)
251
+ end
252
+ end
253
+
205
254
  class << self
206
255
  def element(name, options = {}, &block)
207
256
  options[:name] ||= name
@@ -1,6 +1,6 @@
1
- motion_require '../table/base_cell_section'
2
1
  module MotionPrime
3
- class BaseFieldSection < BaseCellSection
2
+ class BaseFieldSection < BaseSection
3
+ include CellSectionMixin
4
4
  include BW::KVO
5
5
 
6
6
  attr_reader :form
@@ -36,7 +36,7 @@ module MotionPrime
36
36
  if @status_for_updated == :rendered
37
37
  reload_section
38
38
  else
39
- load_section
39
+ load_section!
40
40
  form.table_view.reloadData
41
41
  end
42
42
  end
@@ -61,7 +61,7 @@ module MotionPrime
61
61
  end
62
62
  self
63
63
  rescue
64
- puts "can't focus on element #{self.class_name_without_kvo}"
64
+ NSLog("can't focus on element #{self.class_name_without_kvo}")
65
65
  end
66
66
 
67
67
  def blur
@@ -72,7 +72,7 @@ module MotionPrime
72
72
  end
73
73
  self
74
74
  rescue
75
- puts "can't blur on element #{self.class_name_without_kvo}"
75
+ NSLog("can't blur on element #{self.class_name_without_kvo}")
76
76
  end
77
77
 
78
78
  def bind_text_input
@@ -124,7 +124,7 @@ module MotionPrime
124
124
  def container_height
125
125
  return 0 if container_options[:hidden]
126
126
  element = element(:error_message)
127
- error_height = element ? element.content_height + 5 : 0
127
+ error_height = element ? element.cached_content_height + 5 : 0
128
128
  super + error_height
129
129
  end
130
130
  end
@@ -1,7 +1,6 @@
1
- motion_require '../table/base_cell_section'
2
1
  module MotionPrime
3
- class BaseHeaderSection < BaseCellSection
4
- include CellSection
2
+ class BaseHeaderSection < BaseSection
3
+ include CellSectionMixin
5
4
  DEFAULT_HEADER_HEIGHT = 20
6
5
 
7
6
  element :title, text: proc { @options[:title] }
@@ -23,9 +23,7 @@ module MotionPrime
23
23
  attr_accessor :fields, :field_indexes, :keyboard_visible, :rendered_views, :section_headers, :section_header_options
24
24
 
25
25
  def table_data
26
- if @groups_count == 1
27
- fields.values
28
- else
26
+ if @has_groups
29
27
  section_indexes = []
30
28
  data = fields.inject([]) do |result, (key, field)|
31
29
  section = self.class.fields_options[key][:group].to_i
@@ -37,6 +35,8 @@ module MotionPrime
37
35
  end
38
36
  self.section_header_options.delete_if.each_with_index { |opts, id| data[id].nil? }
39
37
  data.compact
38
+ else
39
+ fields.values
40
40
  end
41
41
  end
42
42
 
@@ -46,7 +46,6 @@ module MotionPrime
46
46
 
47
47
  def render_table
48
48
  init_form_fields
49
- reset_data_stamps
50
49
  options = {
51
50
  styles: table_styles.values.flatten,
52
51
  delegate: self,
@@ -57,9 +56,9 @@ module MotionPrime
57
56
 
58
57
  def render_cell(index, table)
59
58
  field = rows_for_section(index.section)[index.row]
60
- screen.table_view_cell section: field, reuse_identifier: cell_name(table, index), parent_view: table_view do |cell_view|
61
- field.cell_view = cell_view if field.respond_to?(:cell_view)
62
- field.render(to: screen)
59
+ screen.table_view_cell section: field, reuse_identifier: cell_name(table, index), parent_view: table_view do |container_view, container_element|
60
+ field.container_element = container_element
61
+ field.render
63
62
  end
64
63
  end
65
64
 
@@ -67,14 +66,21 @@ module MotionPrime
67
66
  field = section.name.to_sym
68
67
  index = field_indexes[field].split('_').map(&:to_i)
69
68
  path = NSIndexPath.indexPathForRow(index.last, inSection: index.first)
70
- table_view.beginUpdates
71
69
  section.cell.try(:removeFromSuperview)
72
70
 
73
71
  fields[field] = load_field(self.class.fields_options[field])
74
- @data = nil
72
+ fields[field].load_section
73
+ if flat_data?
74
+ @data[path.row] = fields[field]
75
+ else
76
+ @data[path.section][path.row] = fields[field]
77
+ end
78
+
75
79
  set_data_stamp(field_indexes[field])
80
+
81
+ # table_view.beginUpdates
76
82
  table_view.reloadRowsAtIndexPaths([path], withRowAnimation: UITableViewRowAnimationNone)
77
- table_view.endUpdates
83
+ # table_view.endUpdates
78
84
  end
79
85
 
80
86
  def reset_data_stamps
@@ -178,6 +184,18 @@ module MotionPrime
178
184
  def textViewDidBeginEditing(text_view)
179
185
  on_input_edit(text_view)
180
186
  end
187
+ def textViewDidChange(text_view) # bug in iOS 7 - cursor is out of textView bounds
188
+ line = text_view.caretRectForPosition(text_view.selectedTextRange.start)
189
+ overflow = line.origin.y + line.size.height -
190
+ (text_view.contentOffset.y + text_view.bounds.size.height - text_view.contentInset.bottom - text_view.contentInset.top)
191
+ if overflow > 0
192
+ offset = text_view.contentOffset
193
+ offset.y += overflow + text_view.textContainerInset.bottom
194
+ UIView.animate(duration: 0.2) do
195
+ text_view.setContentOffset(offset)
196
+ end
197
+ end
198
+ end
181
199
 
182
200
  def textView(text_view, shouldChangeTextInRange:range, replacementText:string)
183
201
  textField(text_view, shouldChangeCharactersInRange:range, replacementString:string)
@@ -201,7 +219,7 @@ module MotionPrime
201
219
 
202
220
  def load_field(field)
203
221
  klass = "MotionPrime::#{field[:type].classify}FieldSection".constantize
204
- klass.new(field.merge(table: self))
222
+ klass.new(field.merge(screen: screen, table: self))
205
223
  end
206
224
 
207
225
  def render_field?(name, options)
@@ -215,7 +233,7 @@ module MotionPrime
215
233
 
216
234
  def render_header(section)
217
235
  return unless options = self.section_header_options.try(:[], section)
218
- self.section_headers[section] ||= BaseHeaderSection.new(options.merge(table: self))
236
+ self.section_headers[section] ||= BaseHeaderSection.new(options.merge(screen: screen, table: self))
219
237
  end
220
238
 
221
239
  def header_for_section(section)
@@ -225,10 +243,10 @@ module MotionPrime
225
243
 
226
244
  def tableView(table, viewForHeaderInSection: section)
227
245
  return unless header = header_for_section(section)
228
- wrapper = MotionPrime::BaseElement.factory(:view, styles: cell_styles(header).values.flatten, parent_view: table_view)
229
- wrapper.render(to: screen) do |cell_view|
230
- header.cell_view = cell_view if header.respond_to?(:cell_view)
231
- header.render(to: screen)
246
+ wrapper = MotionPrime::BaseElement.factory(:view, screen: screen, styles: cell_styles(header).values.flatten, parent_view: table_view)
247
+ wrapper.render do |container_view, container_element|
248
+ header.container_element = container_element
249
+ header.render
232
250
  end
233
251
  end
234
252
 
@@ -265,8 +283,9 @@ module MotionPrime
265
283
 
266
284
  def reload_data
267
285
  @groups_count = nil
286
+ reset_data
268
287
  init_form_fields
269
- super
288
+ table_view.reloadData
270
289
  end
271
290
 
272
291
  private
@@ -285,6 +304,8 @@ module MotionPrime
285
304
  section_indexes[section_id] += 1
286
305
  end
287
306
  init_form_headers
307
+ @has_groups = section_header_options.present? || @groups_count > 1
308
+ reset_data_stamps
288
309
  end
289
310
 
290
311
  def init_form_headers
@@ -7,7 +7,7 @@ module MotionPrime
7
7
  # tab :info, default: true, page_section: :info_tab
8
8
  # tab :map, page_section: :map_tab
9
9
  # # page_section options will be converted to section class and added to section.
10
- # # e.g. in this sample: InfoTabSection.new(model: model).render(to: screen)
10
+ # # e.g. in this sample: InfoTabSection.new(screen: screen, model: model).render
11
11
  # end
12
12
  #
13
13
  include MotionPrime::HasNormalizer
@@ -104,8 +104,8 @@ module MotionPrime
104
104
  index = 0
105
105
  tab_options.each do |key, options|
106
106
  section_class = options[:page_section].classify
107
- page = "::#{section_class}Section".constantize.new(model: model)
108
- page.render(to: screen)
107
+ page = "::#{section_class}Section".constantize.new(screen: screen, model: model)
108
+ page.render
109
109
  page.hide if index != tab_default
110
110
  self.tab_pages << page
111
111
  index += 1