motion-prime 0.2.1 → 0.3.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 +5 -0
- data/Gemfile.lock +14 -11
- data/README.md +8 -11
- data/Rakefile +2 -1
- data/bin/prime.rb +47 -0
- data/doc/FAQ.md +1 -1
- data/files/app/app_delegate.rb +1 -1
- data/files/app/config/base.rb +8 -4
- data/files/app/screens/application_screen.rb +1 -1
- data/files/app/screens/sidebar_screen.rb +1 -1
- data/files/app/sections/sidebar/action.rb +1 -1
- data/files/app/sections/sidebar/table.rb +1 -1
- data/files/app/styles/sidebar.rb +5 -5
- data/motion-prime.gemspec +1 -0
- data/motion-prime/api_client.rb +81 -0
- data/motion-prime/app_delegate.rb +22 -5
- data/motion-prime/config/base.rb +5 -0
- data/motion-prime/core_ext/kernel.rb +5 -0
- data/motion-prime/elements/_field_dimensions_mixin.rb +43 -0
- data/motion-prime/elements/_text_dimensions_mixin.rb +39 -0
- data/motion-prime/elements/base.rb +40 -17
- data/motion-prime/elements/button.rb +20 -0
- data/motion-prime/elements/draw.rb +2 -2
- data/motion-prime/elements/draw/image.rb +4 -2
- data/motion-prime/elements/draw/label.rb +1 -1
- data/motion-prime/elements/error_message.rb +3 -16
- data/motion-prime/elements/label.rb +13 -2
- data/motion-prime/elements/text_field.rb +1 -0
- data/motion-prime/helpers/cell_section.rb +9 -0
- data/motion-prime/helpers/has_authorization.rb +4 -3
- data/motion-prime/helpers/has_normalizer.rb +20 -6
- data/motion-prime/helpers/has_search_bar.rb +19 -7
- data/motion-prime/helpers/has_style_chain_builder.rb +7 -0
- data/motion-prime/models/association.rb +22 -9
- data/motion-prime/models/association_collection.rb +54 -23
- data/motion-prime/models/bag.rb +13 -12
- data/motion-prime/models/base.rb +2 -0
- data/motion-prime/models/errors.rb +23 -14
- data/motion-prime/models/finder.rb +4 -1
- data/motion-prime/models/model.rb +25 -5
- data/motion-prime/models/store_extension.rb +1 -7
- data/motion-prime/models/sync.rb +75 -43
- data/motion-prime/mp.rb +4 -0
- data/motion-prime/screens/_base_mixin.rb +18 -12
- data/motion-prime/screens/_navigation_bar_mixin.rb +15 -6
- data/motion-prime/screens/_navigation_mixin.rb +15 -16
- data/motion-prime/screens/base_screen.rb +5 -1
- data/motion-prime/screens/sidebar_container_screen.rb +37 -22
- data/motion-prime/sections/base.rb +82 -16
- data/motion-prime/sections/form.rb +144 -26
- data/motion-prime/sections/form/base_field_section.rb +62 -29
- data/motion-prime/sections/form/base_header_section.rb +27 -0
- data/motion-prime/sections/form/date_field_section.rb +2 -17
- data/motion-prime/sections/form/password_field_section.rb +3 -17
- data/motion-prime/sections/form/select_field_section.rb +4 -35
- data/motion-prime/sections/form/string_field_section.rb +3 -29
- data/motion-prime/sections/form/submit_field_section.rb +1 -7
- data/motion-prime/sections/form/switch_field_section.rb +3 -23
- data/motion-prime/sections/form/text_field_section.rb +3 -33
- data/motion-prime/sections/form/text_with_button_field_section.rb +4 -40
- data/motion-prime/sections/tabbed.rb +25 -5
- data/motion-prime/sections/table.rb +86 -22
- data/motion-prime/sections/table/refresh_mixin.rb +3 -1
- data/motion-prime/styles/base.rb +7 -89
- data/motion-prime/styles/form.rb +116 -0
- data/motion-prime/support/dm_button.rb +32 -5
- data/motion-prime/support/dm_text_field.rb +31 -7
- data/motion-prime/support/dm_text_view.rb +6 -3
- data/motion-prime/support/dm_view_controller.rb +3 -3
- data/motion-prime/support/ui_search_bar_custom.rb +1 -1
- data/motion-prime/version.rb +1 -1
- data/motion-prime/views/layout.rb +18 -10
- data/motion-prime/views/styles.rb +19 -9
- data/motion-prime/views/view_builder.rb +18 -2
- data/motion-prime/views/view_styler.rb +59 -5
- data/spec/models/errors_spec.rb +3 -3
- data/travis.sh +3 -2
- metadata +28 -5
- data/motion-prime/elements/_text_height_mixin.rb +0 -17
- data/motion-prime/sections/form/table_field_section.rb +0 -51
@@ -1,6 +1,9 @@
|
|
1
1
|
motion_require './table.rb'
|
2
|
+
motion_require '../helpers/has_style_chain_builder'
|
2
3
|
module MotionPrime
|
3
4
|
class FormSection < TableSection
|
5
|
+
include HasStyleChainBuilder
|
6
|
+
|
4
7
|
# MotionPrime::FormSection is container for Field Sections.
|
5
8
|
# Forms are located inside Screen and can contain multiple Field Sections.
|
6
9
|
# On render, each field will be added to parent screen.
|
@@ -18,46 +21,100 @@ module MotionPrime
|
|
18
21
|
#
|
19
22
|
|
20
23
|
class_attribute :text_field_limits, :text_view_limits
|
21
|
-
class_attribute :fields_options
|
22
|
-
attr_accessor :fields, :field_indexes, :keyboard_visible
|
24
|
+
class_attribute :fields_options, :section_header_options
|
25
|
+
attr_accessor :fields, :field_indexes, :keyboard_visible, :rendered_views, :section_headers
|
23
26
|
|
24
27
|
def table_data
|
25
|
-
|
28
|
+
if @groups_count == 1
|
29
|
+
fields.values
|
30
|
+
else
|
31
|
+
section_indexes = []
|
32
|
+
fields.inject([]) do |result, (key, field)|
|
33
|
+
section = self.class.fields_options[key][:group].to_i
|
34
|
+
|
35
|
+
section_indexes[section] ||= 0
|
36
|
+
result[section] ||= []
|
37
|
+
result[section][section_indexes[section]] = field
|
38
|
+
section_indexes[section] += 1
|
39
|
+
result
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def form_styles
|
45
|
+
base_styles = [:base_form]
|
46
|
+
base_styles << :base_form_with_sections unless flat_data?
|
47
|
+
item_styles = [name.to_sym]
|
48
|
+
{common: base_styles, specific: item_styles}
|
49
|
+
end
|
50
|
+
|
51
|
+
def field_styles(field)
|
52
|
+
suffixes = [:field]
|
53
|
+
if field.is_a?(BaseFieldSection)
|
54
|
+
suffixes << field.class_name_without_kvo.demodulize.underscore.gsub(/\_section$/, '')
|
55
|
+
end
|
56
|
+
|
57
|
+
styles = {
|
58
|
+
common: build_styles_chain(form_styles[:common], suffixes),
|
59
|
+
specific: build_styles_chain(form_styles[:specific], suffixes)
|
60
|
+
}
|
61
|
+
|
62
|
+
if field.respond_to?(:container_styles) && field.container_styles.present?
|
63
|
+
styles[:specific] += Array.wrap(field.container_styles)
|
64
|
+
end
|
65
|
+
styles
|
66
|
+
end
|
67
|
+
|
68
|
+
def header_styles(header)
|
69
|
+
suffixes = [:header, :"#{header.name}_header"]
|
70
|
+
styles = {
|
71
|
+
common: build_styles_chain(form_styles[:common], suffixes),
|
72
|
+
specific: build_styles_chain(form_styles[:specific], suffixes)
|
73
|
+
}
|
74
|
+
|
75
|
+
if header.respond_to?(:container_styles) && header.container_styles.present?
|
76
|
+
styles[:specific] += Array.wrap(header.container_styles)
|
77
|
+
end
|
78
|
+
styles
|
26
79
|
end
|
27
80
|
|
28
81
|
def render_table
|
29
82
|
init_form_fields
|
30
|
-
|
31
|
-
|
32
|
-
styles:
|
33
|
-
|
83
|
+
reset_data_stamps
|
84
|
+
options = {
|
85
|
+
styles: form_styles.values.flatten,
|
86
|
+
delegate: self,
|
87
|
+
dataSource: self,
|
88
|
+
style: (UITableViewStyleGrouped unless flat_data?)}
|
89
|
+
self.table_element = screen.table_view(options)
|
34
90
|
end
|
35
91
|
|
36
92
|
def render_cell(index, table)
|
37
|
-
|
38
|
-
styles
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
screen.table_view_cell styles: styles, reuse_identifier: cell_name(table, index) do |cell_element|
|
43
|
-
item.cell_element = cell_element if item.respond_to?(:cell_element)
|
44
|
-
item.render(to: screen)
|
93
|
+
field = rows_for_section(index.section)[index.row]
|
94
|
+
screen.table_view_cell styles: field_styles(field).values.flatten, reuse_identifier: cell_name(table, index), parent_view: table_view do |cell_view|
|
95
|
+
field.cell_view = cell_view if field.respond_to?(:cell_view)
|
96
|
+
field.render(to: screen)
|
45
97
|
end
|
46
98
|
end
|
47
99
|
|
48
100
|
def reload_cell(section)
|
49
101
|
field = section.name.to_sym
|
50
|
-
|
102
|
+
index = field_indexes[field].split('_').map(&:to_i)
|
103
|
+
path = NSIndexPath.indexPathForRow(index.last, inSection: index.first)
|
51
104
|
table_view.beginUpdates
|
52
|
-
section.cell.removeFromSuperview
|
105
|
+
section.cell.try(:removeFromSuperview)
|
53
106
|
|
54
107
|
fields[field] = load_field(self.class.fields_options[field])
|
55
108
|
@data = nil
|
56
|
-
set_data_stamp(
|
109
|
+
set_data_stamp(field_indexes[field])
|
57
110
|
table_view.reloadRowsAtIndexPaths([path], withRowAnimation: UITableViewRowAnimationNone)
|
58
111
|
table_view.endUpdates
|
59
112
|
end
|
60
113
|
|
114
|
+
def reset_data_stamps
|
115
|
+
set_data_stamp(self.field_indexes.values)
|
116
|
+
end
|
117
|
+
|
61
118
|
# Returns element based on field name and element name
|
62
119
|
#
|
63
120
|
# Examples:
|
@@ -89,6 +146,13 @@ module MotionPrime
|
|
89
146
|
fields.to_hash
|
90
147
|
end
|
91
148
|
|
149
|
+
def register_elements_from_section(section)
|
150
|
+
self.rendered_views ||= {}
|
151
|
+
section.elements.values.each do |element|
|
152
|
+
self.rendered_views[element.view] = {element: element, section: section}
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
92
156
|
# Set focus on field cell
|
93
157
|
#
|
94
158
|
# Examples:
|
@@ -98,7 +162,7 @@ module MotionPrime
|
|
98
162
|
# @return MotionPrime::BaseFieldSection field
|
99
163
|
def focus_on(field_name, animated = true)
|
100
164
|
# unfocus other field
|
101
|
-
data.each do |item|
|
165
|
+
data.flatten.each do |item|
|
102
166
|
item.blur
|
103
167
|
end
|
104
168
|
# focus on field
|
@@ -117,6 +181,19 @@ module MotionPrime
|
|
117
181
|
self.keyboard_visible = false
|
118
182
|
end
|
119
183
|
|
184
|
+
def keyboard_will_show
|
185
|
+
return if table_view.contentSize.height + table_view.top <= UIScreen.mainScreen.bounds.size.height
|
186
|
+
current_inset = table_view.contentInset
|
187
|
+
current_inset.bottom = KEYBOARD_HEIGHT_PORTRAIT + (self.table_element.computed_options[:bottom_content_offset] || 0)
|
188
|
+
table_view.contentInset = current_inset
|
189
|
+
end
|
190
|
+
|
191
|
+
def keyboard_will_hide
|
192
|
+
current_inset = table_view.contentInset
|
193
|
+
current_inset.bottom = self.table_element.computed_options[:bottom_content_offset] || 0
|
194
|
+
table_view.contentInset = current_inset
|
195
|
+
end
|
196
|
+
|
120
197
|
# ALIASES
|
121
198
|
def on_input_change(text_field); end
|
122
199
|
def on_input_edit(text_field); end
|
@@ -159,19 +236,57 @@ module MotionPrime
|
|
159
236
|
klass.new(field.merge(form: self))
|
160
237
|
end
|
161
238
|
|
162
|
-
def render_field?(name)
|
163
|
-
|
239
|
+
def render_field?(name, options)
|
240
|
+
condition = options.delete(:if)
|
241
|
+
if condition.nil?
|
242
|
+
true
|
243
|
+
elsif condition.is_a?(Proc)
|
244
|
+
self.instance_eval(&condition)
|
245
|
+
else
|
246
|
+
condition
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def render_header(section)
|
251
|
+
return unless options = self.class.section_header_options.try(:[], section)
|
252
|
+
self.section_headers[section] ||= BaseHeaderSection.new(options.merge(form: self))
|
253
|
+
end
|
254
|
+
|
255
|
+
def header_for_section(section)
|
256
|
+
self.section_headers ||= []
|
257
|
+
self.section_headers[section] || render_header(section)
|
258
|
+
end
|
259
|
+
|
260
|
+
def tableView(table, viewForHeaderInSection: section)
|
261
|
+
return unless header = header_for_section(section)
|
262
|
+
wrapper = MotionPrime::BaseElement.factory(:view, styles: header_styles(header).values.flatten, parent_view: table_view)
|
263
|
+
wrapper.render(to: screen) do |cell_view|
|
264
|
+
header.cell_view = cell_view if header.respond_to?(:cell_view)
|
265
|
+
header.render(to: screen)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def tableView(table, heightForHeaderInSection: section)
|
270
|
+
header_for_section(section).try(:container_height) || 0
|
164
271
|
end
|
165
272
|
|
166
273
|
class << self
|
167
|
-
def field(name, options = {})
|
274
|
+
def field(name, options = {}, &block)
|
168
275
|
options[:name] = name
|
169
276
|
options[:type] ||= :string
|
277
|
+
options[:block] = block
|
170
278
|
self.fields_options ||= {}
|
171
279
|
self.fields_options[name] = options
|
172
280
|
self.fields_options[name]
|
173
281
|
end
|
174
282
|
|
283
|
+
def group_header(name, options)
|
284
|
+
options[:name] = name
|
285
|
+
self.section_header_options ||= []
|
286
|
+
section = options.delete(:id)
|
287
|
+
self.section_header_options[section] = options
|
288
|
+
end
|
289
|
+
|
175
290
|
def limit_text_field_length(name, limit)
|
176
291
|
self.text_field_limits ||= {}
|
177
292
|
self.text_field_limits[name] = limit
|
@@ -186,12 +301,15 @@ module MotionPrime
|
|
186
301
|
def init_form_fields
|
187
302
|
self.fields = {}
|
188
303
|
self.field_indexes = {}
|
189
|
-
|
304
|
+
section_indexes = []
|
190
305
|
(self.class.fields_options || []).each do |key, field|
|
191
|
-
next unless render_field?(key)
|
306
|
+
next unless render_field?(key, field)
|
307
|
+
section_id = field[:group].to_i
|
308
|
+
section_indexes[section_id] ||= 0
|
309
|
+
@groups_count = [@groups_count || 1, section_id + 1].max
|
192
310
|
self.fields[key] = load_field(field)
|
193
|
-
self.field_indexes[key] =
|
194
|
-
|
311
|
+
self.field_indexes[key] = "#{section_id}_#{section_indexes[section_id]}"
|
312
|
+
section_indexes[section_id] += 1
|
195
313
|
end
|
196
314
|
end
|
197
315
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module MotionPrime
|
2
2
|
class BaseFieldSection < BaseSection
|
3
|
+
include CellSection
|
3
4
|
include BW::KVO
|
4
5
|
attr_accessor :form
|
5
6
|
|
@@ -7,18 +8,23 @@ module MotionPrime
|
|
7
8
|
|
8
9
|
def initialize(options = {})
|
9
10
|
@form = options.delete(:form)
|
10
|
-
if options[:
|
11
|
-
@observe_errors_for = self.send(:instance_eval, &options.delete(:observe_errors_for))
|
12
|
-
end
|
11
|
+
@errors_observer_options = normalize_options(options.delete(:observe_errors).clone, self) if options[:observe_errors]
|
13
12
|
super
|
14
|
-
@container_options = options.delete(:container)
|
15
13
|
observe_model_errors
|
16
14
|
end
|
17
15
|
|
16
|
+
def style_options
|
17
|
+
@style_options ||= Styles.for(section_styles.values.flatten)
|
18
|
+
end
|
19
|
+
|
20
|
+
def section_styles
|
21
|
+
form.try(:field_styles, self)
|
22
|
+
end
|
23
|
+
|
18
24
|
def render_element?(element_name)
|
19
25
|
case element_name.to_sym
|
20
26
|
when :error_message
|
21
|
-
|
27
|
+
has_errors?
|
22
28
|
when :label
|
23
29
|
not @options[:label] === false
|
24
30
|
else true
|
@@ -27,37 +33,28 @@ module MotionPrime
|
|
27
33
|
|
28
34
|
def on_section_render
|
29
35
|
@status_for_updated = :rendered
|
36
|
+
form.register_elements_from_section(self)
|
30
37
|
end
|
31
38
|
|
32
39
|
def observe_model_errors
|
33
|
-
return unless
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
+
return unless observing_errors?
|
41
|
+
errors_observer_fields.each do |field|
|
42
|
+
observe observing_errors_for.errors, observing_errors_for.errors.unique_key(field) do |old_value, new_value|
|
43
|
+
next if old_value == new_value
|
44
|
+
if @status_for_updated == :rendered
|
45
|
+
reload_section
|
46
|
+
else
|
47
|
+
load_section
|
48
|
+
form.table_view.reloadData
|
49
|
+
end
|
40
50
|
end
|
41
51
|
end
|
42
52
|
end
|
43
53
|
|
44
|
-
def clear_observer_and_reload
|
45
|
-
unobserve @observe_errors_for.errors, name
|
46
|
-
reload_section
|
47
|
-
end
|
48
|
-
|
49
|
-
def build_options_for_element(opts)
|
50
|
-
super.merge(observe_errors_for: @observe_errors_for)
|
51
|
-
end
|
52
|
-
|
53
54
|
def form_name
|
54
55
|
form.name
|
55
56
|
end
|
56
57
|
|
57
|
-
def container_options
|
58
|
-
@container_options || super
|
59
|
-
end
|
60
|
-
|
61
58
|
def focus(begin_editing = true)
|
62
59
|
# scroll to cell
|
63
60
|
path = form.table_view.indexPathForCell cell
|
@@ -72,7 +69,7 @@ module MotionPrime
|
|
72
69
|
end
|
73
70
|
self
|
74
71
|
rescue
|
75
|
-
puts "can't focus on element #{self.
|
72
|
+
puts "can't focus on element #{self.class_name_without_kvo}"
|
76
73
|
end
|
77
74
|
|
78
75
|
def blur
|
@@ -83,7 +80,7 @@ module MotionPrime
|
|
83
80
|
end
|
84
81
|
self
|
85
82
|
rescue
|
86
|
-
puts "can't blur on element #{self.
|
83
|
+
puts "can't blur on element #{self.class_name_without_kvo}"
|
87
84
|
end
|
88
85
|
|
89
86
|
def bind_text_input
|
@@ -94,13 +91,49 @@ module MotionPrime
|
|
94
91
|
view(:input).delegate = self.form
|
95
92
|
end
|
96
93
|
|
94
|
+
def observing_errors?
|
95
|
+
@errors_observer_options.present?
|
96
|
+
end
|
97
|
+
|
98
|
+
def has_errors?
|
99
|
+
return false unless observing_errors?
|
100
|
+
errors_observer_fields.any? do |field|
|
101
|
+
observing_errors_for.errors[field].present?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def errors_observer_fields
|
106
|
+
@errors_observer_fields ||= begin
|
107
|
+
fields = Array.wrap(@errors_observer_options[:fields])
|
108
|
+
fields << name if fields.empty?
|
109
|
+
fields.uniq
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def observing_errors_for
|
114
|
+
@errors_observer_options[:resource]
|
115
|
+
end
|
116
|
+
|
117
|
+
def all_errors
|
118
|
+
return [] unless observing_errors?
|
119
|
+
|
120
|
+
errors_observer_fields.map do |field|
|
121
|
+
observing_errors_for.errors[field]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
97
125
|
def reload_section
|
126
|
+
errors_observer_fields.each do |field|
|
127
|
+
unobserve observing_errors_for.errors, observing_errors_for.errors.unique_key(field)
|
128
|
+
end if observing_errors?
|
98
129
|
form.reload_cell(self)
|
99
130
|
end
|
100
131
|
|
101
132
|
def container_height
|
102
|
-
|
103
|
-
|
133
|
+
return 0 if container_options[:hidden]
|
134
|
+
element = element(:error_message)
|
135
|
+
error_height = element ? element.content_height + 5 : 0
|
136
|
+
super + error_height
|
104
137
|
end
|
105
138
|
end
|
106
139
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
class BaseHeaderSection < BaseSection
|
3
|
+
include CellSection
|
4
|
+
DEFAULT_HEADER_HEIGHT = 20
|
5
|
+
|
6
|
+
element :title, text: proc { @options[:title] }
|
7
|
+
|
8
|
+
attr_accessor :form
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
@form = options[:form]
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def style_options
|
16
|
+
@style_options ||= Styles.for(section_styles.values.flatten)
|
17
|
+
end
|
18
|
+
|
19
|
+
def section_styles
|
20
|
+
form.header_styles(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def container_height
|
24
|
+
container_options[:height] || DEFAULT_HEADER_HEIGHT
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -2,24 +2,9 @@ module MotionPrime
|
|
2
2
|
class DateFieldSection < BaseFieldSection
|
3
3
|
container height: 190
|
4
4
|
element :label, type: :label do
|
5
|
-
{
|
6
|
-
styles: [
|
7
|
-
:base_field_label,
|
8
|
-
:base_date_picker_field_label,
|
9
|
-
:"#{form_name}_field_label",
|
10
|
-
:"#{form_name}_#{name}_field_label"
|
11
|
-
]
|
12
|
-
}.merge(options[:label] || {})
|
13
|
-
end
|
14
|
-
element :date_picker, type: :date_picker do
|
15
|
-
{
|
16
|
-
styles: [
|
17
|
-
:base_date_picker,
|
18
|
-
:"#{form_name}_date_picker",
|
19
|
-
:"#{form_name}_#{name}_date_picker"
|
20
|
-
]
|
21
|
-
}
|
5
|
+
options[:label] || {}
|
22
6
|
end
|
7
|
+
element :date_picker, type: :date_picker
|
23
8
|
|
24
9
|
after_render :bind_date_picker
|
25
10
|
|