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.
- checksums.yaml +8 -8
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/ROADMAP.md +3 -3
- data/doc/code/getting_started.rb +6 -5
- data/files/app/screens/sidebar_screen.rb +2 -2
- data/files/app/sections/sidebar/action.rb +1 -1
- data/motion-prime/api_client.rb +1 -1
- data/motion-prime/core_ext/kernel.rb +4 -0
- data/motion-prime/elements/_content_padding_mixin.rb +12 -4
- data/motion-prime/elements/_content_text_mixin.rb +10 -2
- data/motion-prime/elements/{base.rb → base_element.rb} +16 -7
- data/motion-prime/elements/button.rb +1 -1
- data/motion-prime/elements/draw/_draw_background_mixin.rb +20 -9
- data/motion-prime/elements/draw/image.rb +2 -2
- data/motion-prime/elements/draw/label.rb +7 -6
- data/motion-prime/elements/draw.rb +8 -3
- data/motion-prime/elements/label.rb +2 -2
- data/motion-prime/elements/table_view_cell.rb +7 -0
- data/motion-prime/helpers/has_search_bar.rb +1 -1
- data/motion-prime/helpers/has_styles.rb +7 -7
- data/motion-prime/models/model.rb +1 -1
- data/motion-prime/models/sync.rb +10 -10
- data/motion-prime/screens/_navigation_mixin.rb +1 -1
- data/motion-prime/sections/_draw_mixin.rb +74 -0
- data/motion-prime/sections/{base.rb → base_section.rb} +104 -55
- data/motion-prime/sections/form/base_field_section.rb +6 -6
- data/motion-prime/sections/form/base_header_section.rb +2 -3
- data/motion-prime/sections/form.rb +38 -17
- data/motion-prime/sections/tabbed.rb +3 -3
- data/motion-prime/sections/table.rb +172 -80
- data/motion-prime/services/table_data_indexes.rb +51 -0
- data/motion-prime/styles/_mixins.rb +8 -0
- data/motion-prime/support/dm_cell_with_section.rb +1 -1
- data/motion-prime/support/dm_text_field.rb +17 -12
- data/motion-prime/support/dm_text_view.rb +10 -23
- data/motion-prime/version.rb +1 -1
- data/motion-prime/views/layout.rb +4 -2
- data/motion-prime/views/styles.rb +2 -0
- data/motion-prime/views/view_builder.rb +8 -2
- data/motion-prime/views/view_styler.rb +6 -2
- metadata +8 -9
- data/motion-prime/helpers/cell_section.rb +0 -9
- data/motion-prime/sections/draw.rb +0 -93
- data/motion-prime/sections/form/text_with_button_field_section.rb +0 -23
- data/motion-prime/sections/table/base_cell_section.rb +0 -5
- 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
|
19
|
-
include
|
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
|
-
|
62
|
+
@section_loading = false
|
63
|
+
return @section_loaded = true
|
73
64
|
end
|
74
65
|
|
75
|
-
def
|
76
|
-
|
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,
|
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
|
-
|
96
|
-
|
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
|
-
|
117
|
-
|
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
|
-
|
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
|
-
|
130
|
-
element.render
|
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
|
-
|
144
|
-
|
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
|
-
|
150
|
-
|
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 <
|
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
|
-
|
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
|
-
|
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.
|
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 <
|
4
|
-
include
|
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 @
|
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 |
|
61
|
-
field.
|
62
|
-
field.render
|
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
|
-
|
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
|
230
|
-
header.
|
231
|
-
header.render
|
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
|
-
|
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
|
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
|
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
|