motion-prime 0.1.7 → 0.2.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 +1 -1
- data/files/app/config/base.rb +7 -4
- data/files/app/styles/sidebar.rb +4 -4
- data/lib/motion-prime.rb +1 -0
- data/motion-prime/config/base.rb +5 -0
- data/motion-prime/elements/_text_height_mixin.rb +4 -4
- data/motion-prime/elements/base.rb +11 -5
- data/motion-prime/elements/draw.rb +105 -26
- data/motion-prime/elements/draw/image.rb +2 -2
- data/motion-prime/elements/draw/label.rb +20 -3
- data/motion-prime/elements/error_message.rb +23 -0
- data/motion-prime/elements/label.rb +8 -0
- data/motion-prime/models/association.rb +0 -1
- data/motion-prime/models/base.rb +8 -192
- data/motion-prime/models/errors.rb +62 -1
- data/motion-prime/models/exceptions.rb +3 -0
- data/motion-prime/models/model.rb +33 -1
- data/motion-prime/models/sync.rb +233 -0
- data/motion-prime/screens/_base_mixin.rb +4 -1
- data/motion-prime/screens/_navigation_mixin.rb +5 -5
- data/motion-prime/sections/base.rb +18 -5
- data/motion-prime/sections/form.rb +75 -16
- data/motion-prime/sections/form/base_field_section.rb +52 -1
- data/motion-prime/sections/form/select_field_section.rb +14 -1
- data/motion-prime/sections/form/string_field_section.rb +20 -5
- data/motion-prime/sections/form/switch_field_section.rb +33 -0
- data/motion-prime/sections/form/table_field_section.rb +40 -0
- data/motion-prime/sections/form/text_field_section.rb +23 -6
- data/motion-prime/sections/table.rb +25 -8
- data/motion-prime/styles/base.rb +31 -4
- data/motion-prime/support/dm_button.rb +3 -2
- data/motion-prime/version.rb +1 -1
- data/motion-prime/views/layout.rb +1 -1
- data/motion-prime/views/view_builder.rb +82 -66
- data/motion-prime/views/view_styler.rb +2 -0
- data/resources/fonts/ubuntu.ttf +0 -0
- data/spec/helpers/models.rb +1 -1
- data/spec/models/association_spec.rb +1 -1
- data/spec/models/errors_spec.rb +29 -0
- data/spec/models/finder_spec.rb +1 -1
- data/spec/models/{base_model_spec.rb → model_spec.rb} +30 -1
- data/spec/models/store_extension_spec.rb +1 -1
- data/spec/models/store_spec.rb +1 -1
- metadata +12 -4
@@ -1,3 +1,64 @@
|
|
1
1
|
module MotionPrime
|
2
|
-
class
|
2
|
+
class Errors
|
3
|
+
attr_accessor :keys
|
4
|
+
attr_accessor :errors
|
5
|
+
|
6
|
+
def initialize(model)
|
7
|
+
@keys = []
|
8
|
+
@errors = {}
|
9
|
+
model.class.attributes.map(&:to_sym).each do |key|
|
10
|
+
initialize_for_key key
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize_for_key(key)
|
15
|
+
@keys << key.to_sym unless @keys.include?(key.to_sym)
|
16
|
+
@errors[key.to_sym] ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(key)
|
20
|
+
initialize_for_key(key)
|
21
|
+
@errors[key.to_sym]
|
22
|
+
end
|
23
|
+
|
24
|
+
def set(key, errors)
|
25
|
+
initialize_for_key(key)
|
26
|
+
@errors[key.to_sym] = Array.wrap(errors)
|
27
|
+
end
|
28
|
+
|
29
|
+
def add(key, error)
|
30
|
+
initialize_for_key(key)
|
31
|
+
@errors[key.to_sym] << error
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](key)
|
35
|
+
get(key)
|
36
|
+
end
|
37
|
+
|
38
|
+
def []=(key, errors)
|
39
|
+
set(key, errors)
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset
|
43
|
+
@keys.each do |key|
|
44
|
+
set(key, [])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def messages
|
49
|
+
errors.values.compact.flatten
|
50
|
+
end
|
51
|
+
|
52
|
+
def blank?
|
53
|
+
messages.blank?
|
54
|
+
end
|
55
|
+
|
56
|
+
def present?
|
57
|
+
!blank?
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
messages.join(';')
|
62
|
+
end
|
63
|
+
end
|
3
64
|
end
|
@@ -2,7 +2,6 @@ module MotionPrime
|
|
2
2
|
module ModelMethods
|
3
3
|
def save
|
4
4
|
raise StoreError, 'No store provided' unless self.store
|
5
|
-
|
6
5
|
error_ptr = Pointer.new(:id)
|
7
6
|
self.store.addObject(self, error: error_ptr)
|
8
7
|
raise StoreError, error_ptr[0].description if error_ptr[0]
|
@@ -21,6 +20,39 @@ module MotionPrime
|
|
21
20
|
def store
|
22
21
|
super || self.class.store
|
23
22
|
end
|
23
|
+
|
24
|
+
def assign_attributes(new_attributes, options = {})
|
25
|
+
attributes = new_attributes.symbolize_keys
|
26
|
+
attributes.each do |k, v|
|
27
|
+
if respond_to?("#{k}=")
|
28
|
+
send("#{k}=", v) unless options[:skip_nil_values] && v.nil?
|
29
|
+
elsif options[:check_attribute_presence]
|
30
|
+
puts "unknown attribute: #{k}"
|
31
|
+
else
|
32
|
+
raise(NoMethodError, "unknown attribute: #{k}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def attributes_hash
|
38
|
+
self.info.to_hash.symbolize_keys
|
39
|
+
end
|
40
|
+
|
41
|
+
def new_record?
|
42
|
+
id.blank?
|
43
|
+
end
|
44
|
+
|
45
|
+
def persisted?
|
46
|
+
!new_record?
|
47
|
+
end
|
48
|
+
|
49
|
+
def model_name
|
50
|
+
self.class.name.underscore
|
51
|
+
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}> " + MotionPrime::JSON.generate(info)
|
55
|
+
end
|
24
56
|
end
|
25
57
|
|
26
58
|
module ModelClassMethods
|
@@ -0,0 +1,233 @@
|
|
1
|
+
module MotionPrime
|
2
|
+
module ModelSyncMethods
|
3
|
+
def self.included(base)
|
4
|
+
base.class_attribute :_sync_url
|
5
|
+
base.class_attribute :_updatable_attributes
|
6
|
+
base.class_attribute :_associations
|
7
|
+
end
|
8
|
+
|
9
|
+
def sync_url(method = :get)
|
10
|
+
url = self.class.sync_url
|
11
|
+
url = url.call(method) if url.is_a?(Proc)
|
12
|
+
normalize_sync_url(url)
|
13
|
+
end
|
14
|
+
|
15
|
+
# destroy on server and delete on local
|
16
|
+
def destroy(&block)
|
17
|
+
use_callback = block_given?
|
18
|
+
api_client.delete(sync_url(:delete)) do
|
19
|
+
block.call() if use_callback
|
20
|
+
end
|
21
|
+
delete
|
22
|
+
end
|
23
|
+
|
24
|
+
# sync with server and save on local
|
25
|
+
def sync!(sync_options = {}, &block)
|
26
|
+
sync(sync_options.merge(save: true), &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
# sync with with server
|
30
|
+
# TODO: order of fetch/update should be based on updated time?
|
31
|
+
def sync(sync_options = {}, &block)
|
32
|
+
use_callback = block_given?
|
33
|
+
should_fetch = sync_options[:fetch]
|
34
|
+
should_update = sync_options[:update]
|
35
|
+
should_fetch_associations = if sync_options.has_key?(:fetch_associations)
|
36
|
+
sync_options[:fetch_associations]
|
37
|
+
else # do not need to fetch unless this is a GET request
|
38
|
+
should_fetch
|
39
|
+
end
|
40
|
+
|
41
|
+
method = if should_update
|
42
|
+
persisted? ? :put : :post
|
43
|
+
else
|
44
|
+
:get
|
45
|
+
end
|
46
|
+
url = sync_url(method)
|
47
|
+
|
48
|
+
if url.blank?
|
49
|
+
should_fetch = false
|
50
|
+
should_update = false
|
51
|
+
end
|
52
|
+
|
53
|
+
should_fetch = !new_record? if should_fetch.nil?
|
54
|
+
should_update = new_record? if should_update.nil?
|
55
|
+
|
56
|
+
fetch_with_url url do
|
57
|
+
save if sync_options[:save]
|
58
|
+
block.call if use_callback
|
59
|
+
end if should_fetch
|
60
|
+
|
61
|
+
update_with_url url, sync_options do |data, status_code|
|
62
|
+
save if sync_options[:save] && status_code.to_s =~ /20\d/
|
63
|
+
# run callback only if it wasn't run on fetch
|
64
|
+
block.call(data, status_code) if use_callback && !should_fetch
|
65
|
+
end if should_update
|
66
|
+
|
67
|
+
fetch_associations(sync_options) do
|
68
|
+
# run callback only if it wasn't run on fetch or update
|
69
|
+
block.call if use_callback && !should_fetch && !should_update
|
70
|
+
end if should_fetch_associations
|
71
|
+
end
|
72
|
+
|
73
|
+
# fetch from server using url
|
74
|
+
def fetch_with_url(url, &block)
|
75
|
+
api_client.get(url) do |data|
|
76
|
+
if data.present?
|
77
|
+
fetch_with_attributes(data, &block)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# update on server using url
|
83
|
+
def update_with_url(url, sync_options = nil, &block)
|
84
|
+
use_callback = block_given?
|
85
|
+
post_data = { model_name => filtered_updatable_attributes(sync_options)}
|
86
|
+
api_client.send(id ? :put : :post, url, post_data) do |data, status_code|
|
87
|
+
if status_code.to_s =~ /20\d/ && data.is_a?(Hash)
|
88
|
+
self.id ||= data['id']
|
89
|
+
accessible_attributes = self.class.attributes.map(&:to_sym) - [:id]
|
90
|
+
attrs = data.symbolize_keys.slice(*accessible_attributes)
|
91
|
+
fetch_with_attributes(attrs)
|
92
|
+
end
|
93
|
+
block.call(data, status_code) if use_callback
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# set attributes, using fetch
|
98
|
+
def fetch_with_attributes(attrs, &block)
|
99
|
+
attrs.each do |key, value|
|
100
|
+
if respond_to?(:"fetch_#{key}")
|
101
|
+
self.send(:"fetch_#{key}", value)
|
102
|
+
elsif respond_to?(:"#{key}=")
|
103
|
+
self.send(:"#{key}=", value)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
block.call(self) if block_given?
|
107
|
+
end
|
108
|
+
|
109
|
+
def fetch_associations(sync_options = {}, &block)
|
110
|
+
use_callback = block_given?
|
111
|
+
associations = self.class._associations || {}
|
112
|
+
|
113
|
+
associations.keys.each_with_index do |key, index|
|
114
|
+
if use_callback && associations.count - 1 == index
|
115
|
+
fetch_association(key, sync_options, &block)
|
116
|
+
else
|
117
|
+
fetch_association(key, sync_options)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def fetch_association(key, sync_options = {}, &block)
|
123
|
+
options = self.class._associations[key]
|
124
|
+
return unless options[:sync_url]
|
125
|
+
options[:type] == :many ?
|
126
|
+
fetch_has_many(key, options, sync_options, &block) :
|
127
|
+
fetch_has_one(key, options, sync_options, &block)
|
128
|
+
end
|
129
|
+
|
130
|
+
def fetch_has_many(key, options = {}, sync_options = {}, &block)
|
131
|
+
old_collection = self.send(key)
|
132
|
+
use_callback = block_given?
|
133
|
+
puts "SYNC: started sync for #{key} in #{self.class.name}"
|
134
|
+
api_client.get normalize_sync_url(options[:sync_url]) do |data|
|
135
|
+
data = data[options[:sync_key]] if options[:sync_key]
|
136
|
+
if data.present?
|
137
|
+
# Update/Create existing records
|
138
|
+
data.each do |attributes|
|
139
|
+
model = old_collection.detect{ |model| model.id == attributes[:id]}
|
140
|
+
unless model
|
141
|
+
model = key.singularize.to_s.classify.constantize.new
|
142
|
+
self.send(:"#{key}_bag") << model
|
143
|
+
end
|
144
|
+
model.fetch_with_attributes(attributes)
|
145
|
+
model.save if sync_options[:save]
|
146
|
+
end
|
147
|
+
old_collection.each do |old_model|
|
148
|
+
model = data.detect{ |model| model[:id] == old_model.id}
|
149
|
+
unless model
|
150
|
+
old_model.delete
|
151
|
+
end
|
152
|
+
end
|
153
|
+
save if sync_options[:save]
|
154
|
+
puts "SYNC: finished sync for #{key} in #{self.class.name}"
|
155
|
+
block.call if use_callback
|
156
|
+
else
|
157
|
+
puts "SYNC ERROR: failed sync for #{key} in #{self.class.name}"
|
158
|
+
block.call if use_callback
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def fetch_has_one(key, options = {}, &block)
|
164
|
+
use_callback = block_given?
|
165
|
+
puts "SYNC: started sync for #{key} in #{self.class.name}"
|
166
|
+
api_client.get normalize_sync_url(options[:sync_url]) do |data|
|
167
|
+
data = data[options[:sync_key]] if options[:sync_key]
|
168
|
+
if data.present?
|
169
|
+
model = self.send(key)
|
170
|
+
unless model
|
171
|
+
model = key.singularize.to_s.classify.constantize.new
|
172
|
+
self.send(:"#{key}_bag") << model
|
173
|
+
end
|
174
|
+
model.fetch_with_attributes(data)
|
175
|
+
model.save if sync_options[:save]
|
176
|
+
block.call if use_callback
|
177
|
+
else
|
178
|
+
puts "SYNC ERROR: failed sync for #{key} in #{self.class.name}"
|
179
|
+
block.call if use_callback
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def filtered_updatable_attributes(options = {})
|
185
|
+
slice_attributes = options[:updatable_attributes].map(&:to_sym) if options.has_key?(:updatable_attributes)
|
186
|
+
updatable_attributes = self.class.updatable_attributes
|
187
|
+
|
188
|
+
if updatable_attributes.blank?
|
189
|
+
attrs = attributes_hash.slice(*slice_attributes) if slice_attributes
|
190
|
+
return attrs
|
191
|
+
end
|
192
|
+
|
193
|
+
updatable_attributes = updatable_attributes.slice(*slice_attributes) if slice_attributes
|
194
|
+
updatable_attributes.to_a.inject({}) do |hash, attribute|
|
195
|
+
key, options = *attribute
|
196
|
+
return hash if options[:if] && !send(options[:if])
|
197
|
+
value = if block = options[:block]
|
198
|
+
block.call(self)
|
199
|
+
else
|
200
|
+
info[key]
|
201
|
+
end
|
202
|
+
hash.merge(key => value)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def normalize_sync_url(url)
|
207
|
+
url.to_s.gsub(':id', id.to_s)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
module ModelSyncClassMethods
|
212
|
+
def sync_url(url = nil, &block)
|
213
|
+
if url || block_given?
|
214
|
+
self._sync_url = url || block
|
215
|
+
else
|
216
|
+
self._sync_url
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def updatable_attributes(*attrs)
|
221
|
+
return self._updatable_attributes if attrs.blank?
|
222
|
+
attrs.each do |attribute|
|
223
|
+
updatable_attribute attribute
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def updatable_attribute(attribute, options = {}, &block)
|
228
|
+
options[:block] = block if block_given?
|
229
|
+
self._updatable_attributes ||= {}
|
230
|
+
self._updatable_attributes[attribute] = options
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -37,7 +37,10 @@ module MotionPrime
|
|
37
37
|
self.send("#{k}=", v) if self.respond_to?("#{k}=")
|
38
38
|
end
|
39
39
|
|
40
|
-
|
40
|
+
if @wrap_in_navigation = args[:navigation]
|
41
|
+
self.add_navigation_controller
|
42
|
+
end
|
43
|
+
|
41
44
|
self.on_init if respond_to?(:on_init)
|
42
45
|
self
|
43
46
|
end
|
@@ -10,9 +10,9 @@ module MotionPrime
|
|
10
10
|
ensure_wrapper_controller_in_place(screen, args)
|
11
11
|
screen.send(:on_screen_load) if screen.respond_to?(:on_screen_load)
|
12
12
|
if args[:modal]
|
13
|
-
present_modal_view_controller screen, (args[:animated]
|
13
|
+
present_modal_view_controller screen, (args.has_key?(:animated) ? args[:animated] : true)
|
14
14
|
elsif has_navigation?
|
15
|
-
push_view_controller screen
|
15
|
+
push_view_controller screen, args
|
16
16
|
else
|
17
17
|
app_delegate.open_screen(screen.main_controller)
|
18
18
|
end
|
@@ -32,7 +32,7 @@ module MotionPrime
|
|
32
32
|
|
33
33
|
def close_screen(args = {})
|
34
34
|
args ||= {}
|
35
|
-
args[:animated]
|
35
|
+
args[:animated] = args.has_key?(:animated) ? args[:animated] : true
|
36
36
|
# Pop current view, maybe with arguments, if in navigation controller
|
37
37
|
if modal?
|
38
38
|
close_modal_screen args
|
@@ -52,8 +52,8 @@ module MotionPrime
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
def push_view_controller(vc)
|
56
|
-
navigation_controller.pushViewController(vc, animated: true)
|
55
|
+
def push_view_controller(vc, args = {})
|
56
|
+
navigation_controller.pushViewController(vc, animated: (args.has_key?(:animated) ? args[:animated] : true))
|
57
57
|
end
|
58
58
|
|
59
59
|
protected
|
@@ -23,11 +23,15 @@ module MotionPrime
|
|
23
23
|
def initialize(options = {})
|
24
24
|
@options = options
|
25
25
|
@model = options[:model]
|
26
|
-
@name = options[:name] ||=
|
26
|
+
@name = options[:name] ||= default_name
|
27
27
|
create_elements
|
28
28
|
self.hide if container_options[:hidden]
|
29
29
|
end
|
30
30
|
|
31
|
+
def default_name
|
32
|
+
self.class.name.demodulize.underscore.gsub(/\_section$/, '')
|
33
|
+
end
|
34
|
+
|
31
35
|
def elements_options
|
32
36
|
self.class.elements_options || {}
|
33
37
|
end
|
@@ -35,14 +39,23 @@ module MotionPrime
|
|
35
39
|
def create_elements
|
36
40
|
self.elements = {}
|
37
41
|
elements_options.each do |key, opts|
|
38
|
-
|
39
|
-
|
40
|
-
options = opts.clone
|
41
|
-
options[:section] = self
|
42
|
+
next unless render_element?(key)
|
43
|
+
options = build_options_for_element(opts)
|
42
44
|
self.elements[key] = MotionPrime::BaseElement.factory(options.delete(:type), options)
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
48
|
+
def render_element?(element_name)
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
def build_options_for_element(opts)
|
53
|
+
# we should clone options to prevent overriding options
|
54
|
+
# in next element with same name in another class
|
55
|
+
options = opts.clone
|
56
|
+
options.merge(section: self)
|
57
|
+
end
|
58
|
+
|
46
59
|
def render(container_options = {})
|
47
60
|
self.container_options.merge!(container_options)
|
48
61
|
self.screen = container_options.delete(:to)
|
@@ -20,6 +20,7 @@ module MotionPrime
|
|
20
20
|
KEYBOARD_HEIGHT_PORTRAIT = 216
|
21
21
|
KEYBOARD_HEIGHT_LANDSCAPE = 162
|
22
22
|
|
23
|
+
class_attribute :text_field_limits, :text_view_limits
|
23
24
|
class_attribute :fields_options
|
24
25
|
attr_accessor :fields, :field_indexes, :keyboard_visible
|
25
26
|
|
@@ -30,8 +31,8 @@ module MotionPrime
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def render_table
|
33
|
-
@data_stamp = Time.now.to_i
|
34
34
|
init_form_fields
|
35
|
+
set_data_stamp(self.field_indexes.values)
|
35
36
|
self.table_view = screen.table_view(
|
36
37
|
styles: [:base_form, name.to_sym], delegate: self, dataSource: self
|
37
38
|
).view
|
@@ -39,12 +40,29 @@ module MotionPrime
|
|
39
40
|
|
40
41
|
def render_cell(index, table)
|
41
42
|
item = data[index.row]
|
42
|
-
|
43
|
-
|
43
|
+
styles = [:base_form_field, :"#{name}_field"]
|
44
|
+
if item.respond_to?(:container_styles) && item.container_styles.present?
|
45
|
+
styles += Array.wrap(item.container_styles)
|
46
|
+
end
|
47
|
+
screen.table_view_cell styles: styles, reuse_identifier: cell_name(table, index) do |cell_element|
|
48
|
+
item.cell_element = cell_element if item.respond_to?(:cell_element)
|
44
49
|
item.render(to: screen)
|
45
50
|
end
|
46
51
|
end
|
47
52
|
|
53
|
+
def reload_cell(section)
|
54
|
+
field = section.name.to_sym
|
55
|
+
path = table_view.indexPathForRowAtPoint(section.cell.center) # do not use indexPathForCell here as field may be invisibe
|
56
|
+
table_view.beginUpdates
|
57
|
+
section.cell.removeFromSuperview
|
58
|
+
|
59
|
+
fields[field] = load_field(self.class.fields_options[field])
|
60
|
+
@data = nil
|
61
|
+
set_data_stamp([field_indexes[field]])
|
62
|
+
table_view.reloadRowsAtIndexPaths([path], withRowAnimation: UITableViewRowAnimationNone)
|
63
|
+
table_view.endUpdates
|
64
|
+
end
|
65
|
+
|
48
66
|
# Returns element based on field name and element name
|
49
67
|
#
|
50
68
|
# Examples:
|
@@ -88,16 +106,6 @@ module MotionPrime
|
|
88
106
|
field(field_name).focus
|
89
107
|
end
|
90
108
|
|
91
|
-
class << self
|
92
|
-
def field(name, options = {})
|
93
|
-
options[:name] = name
|
94
|
-
options[:type] ||= :string
|
95
|
-
self.fields_options ||= {}
|
96
|
-
self.fields_options[name] = options
|
97
|
-
self.fields_options[name]
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
109
|
def set_height_with_keyboard
|
102
110
|
return if keyboard_visible
|
103
111
|
self.table_view.height -= KEYBOARD_HEIGHT_PORTRAIT
|
@@ -148,15 +156,66 @@ module MotionPrime
|
|
148
156
|
on_input_edit(text_field)
|
149
157
|
end
|
150
158
|
|
151
|
-
|
159
|
+
def textView(text_view, shouldChangeTextInRange:range, replacementText:string)
|
160
|
+
limit = (self.class.text_view_limits || {}).find do |field_name, limit|
|
161
|
+
view("#{field_name}:input")
|
162
|
+
end.try(:last)
|
163
|
+
return true unless limit
|
164
|
+
allow_string_replacement?(text_view, limit, range, string)
|
165
|
+
end
|
166
|
+
|
167
|
+
def textField(text_field, shouldChangeCharactersInRange:range, replacementString:string)
|
168
|
+
limit = (self.class.text_field_limits || {}).find do |field_name, limit|
|
169
|
+
view("#{field_name}:input")
|
170
|
+
end.try(:last)
|
171
|
+
return true unless limit
|
172
|
+
allow_string_replacement?(text_field, limit, range, string)
|
173
|
+
end
|
174
|
+
|
175
|
+
def allow_string_replacement?(target, limit, range, string)
|
176
|
+
if string.length.zero? || (range.length + limit - target.text.length) >= string.length
|
177
|
+
true
|
178
|
+
else
|
179
|
+
target.text.length < limit
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def load_field(field)
|
184
|
+
klass = "MotionPrime::#{field[:type].classify}FieldSection".constantize
|
185
|
+
klass.new(field.merge(form: self))
|
186
|
+
end
|
187
|
+
|
188
|
+
def render_field?(name)
|
189
|
+
true
|
190
|
+
end
|
191
|
+
|
192
|
+
class << self
|
193
|
+
def field(name, options = {})
|
194
|
+
options[:name] = name
|
195
|
+
options[:type] ||= :string
|
196
|
+
self.fields_options ||= {}
|
197
|
+
self.fields_options[name] = options
|
198
|
+
self.fields_options[name]
|
199
|
+
end
|
152
200
|
|
201
|
+
def limit_text_field_length(name, limit)
|
202
|
+
self.text_field_limits ||= {}
|
203
|
+
self.text_field_limits[name] = limit
|
204
|
+
end
|
205
|
+
def limit_text_view_length(name, limit)
|
206
|
+
self.text_view_limits ||= {}
|
207
|
+
self.text_view_limits[name] = limit
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
153
212
|
def init_form_fields
|
154
213
|
self.fields = {}
|
155
214
|
self.field_indexes = {}
|
156
215
|
index = 0
|
157
216
|
(self.class.fields_options || []).each do |key, field|
|
158
|
-
|
159
|
-
self.fields[key] =
|
217
|
+
next unless render_field?(key)
|
218
|
+
self.fields[key] = load_field(field)
|
160
219
|
self.field_indexes[key] = index
|
161
220
|
index += 1
|
162
221
|
end
|