webface_rails 0.1.6
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +37 -0
- data/Rakefile +7 -0
- data/app/controllers/webface_error_report_controller.rb +12 -0
- data/app/helpers/webface_component_helper.rb +248 -0
- data/app/helpers/webface_form.rb +161 -0
- data/app/views/webface_component_templates/_button.html.haml +2 -0
- data/app/views/webface_component_templates/_dialog_window.html.haml +6 -0
- data/app/views/webface_component_templates/_modal_window.html.haml +5 -0
- data/app/views/webface_component_templates/_simple_notification.html.haml +4 -0
- data/app/views/webface_components/_button.html.haml +11 -0
- data/app/views/webface_components/_checkbox.html.haml +21 -0
- data/app/views/webface_components/_editable_select.html.haml +8 -0
- data/app/views/webface_components/_field_hints.html.haml +7 -0
- data/app/views/webface_components/_hidden_form_field.html.haml +1 -0
- data/app/views/webface_components/_hint.html.haml +9 -0
- data/app/views/webface_components/_hint_trigger.html.haml +15 -0
- data/app/views/webface_components/_numeric_form_field.html.haml +13 -0
- data/app/views/webface_components/_password_form_field.html.haml +12 -0
- data/app/views/webface_components/_post_button_link.html.haml +4 -0
- data/app/views/webface_components/_radio.html.haml +18 -0
- data/app/views/webface_components/_select.html.haml +34 -0
- data/app/views/webface_components/_text_form_field.html.haml +12 -0
- data/app/views/webface_components/_text_form_field_with_validation.html.haml +6 -0
- data/app/views/webface_components/_textarea_form_field.html.haml +11 -0
- data/app/views/webface_components/shared/_editable_select_base.html.haml +34 -0
- data/lib/generators/templates/application.js +53 -0
- data/lib/generators/templates/mocha.css +10 -0
- data/lib/generators/templates/mocha.pug +15 -0
- data/lib/generators/templates/my_component.test.js +15 -0
- data/lib/generators/templates/run_webface_test +11 -0
- data/lib/generators/templates/test_animator.js +66 -0
- data/lib/generators/templates/test_utils.js +6 -0
- data/lib/generators/templates/webface.test.js +2 -0
- data/lib/generators/templates/webface_init.js +10 -0
- data/lib/generators/templates/webface_test_server.js +68 -0
- data/lib/generators/webface_generator.rb +81 -0
- data/lib/tasks/webface_rails_tasks.rake +3 -0
- data/lib/webface_rails.rb +11 -0
- data/lib/webface_rails/version.rb +3 -0
- data/spec/helpers/webface_component_helper_spec.rb +133 -0
- data/spec/helpers/webface_form_spec.rb +58 -0
- data/spec/spec_helper.rb +15 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1cc42fc92095e740deb07c81294310846908ebd80d435bc1bc0873578185c978
|
4
|
+
data.tar.gz: 690639b7c31b20ca0e9ccda60410a6629e495d1bdecc3f05fb3a0cd7ad8dff4f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1fd031041de3a7740a6639ce52b45832399ea462766382b93d3237fb51b1ead330cdeb361cf97274850e91c81444eeb7ca2e5465042d9b8ee81915307fc00503
|
7
|
+
data.tar.gz: c9b256dde5c1acc4f734598ba188df1b0f99f2ef1da5196386b40b23b906cd11269a4d478db2edd0dea8ab78426a4cb31ffd612fabf9a873bed4d1c4a5b8bcaf
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2019 Roman Snitko
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# WebfaceRails
|
2
|
+
RubyOnRails integration with Webface.js
|
3
|
+
|
4
|
+
Installation
|
5
|
+
------------
|
6
|
+
add `gem 'webface_rails'` to Gemfile and run `bundle install`
|
7
|
+
|
8
|
+
Running generators
|
9
|
+
------------------
|
10
|
+
To run the generator:
|
11
|
+
|
12
|
+
`rails generate webface` or `rails g webface`
|
13
|
+
|
14
|
+
By default it uses `Rails.root` path to install the gem. You can pass `root_path` argument to redefine root path in your project. It might be helpful when installing the gem into Rails Engine, e.g.:
|
15
|
+
|
16
|
+
`rails g webface --root_path absolute_path_to_your_project`
|
17
|
+
|
18
|
+
The generator will give you necessary files and dir structure inside the `spec/` dir to
|
19
|
+
run webface tests. To be more specific, it will do the following
|
20
|
+
|
21
|
+
1. Install webface as a submodule under `app/assets/javascripts/webface.js`. If you already have it installed, it will use the path specified in .gitmodules
|
22
|
+
2. Create webface unit test dir under `spec/webface`
|
23
|
+
3. Copy there a bunch of files necessary to run unit tests
|
24
|
+
4. Add `node_modules` dirs to .gitignore
|
25
|
+
5. Symlink your `app/assets/javascripts` into `spec/webface/source` and `app/assets/javascripts` into `spec/webface/webface_source`. This is necessary to properly import the files you're testing.
|
26
|
+
6. Install node_modules in `app/assets/javascripts`. If it doesn't have a `package.json` file, it will copy the one used by webface.
|
27
|
+
7. Symlink `app/assets/javascripts/node_modules` to `spec/webface/node_modules`
|
28
|
+
8. And finally, symlink the `app/assets/javascripts/webface.js` (or whatever the path is) into `spec/webface/webface_source`
|
29
|
+
|
30
|
+
Running unit tests
|
31
|
+
------------------
|
32
|
+
After you run the generators, you should be able to run unit tests with
|
33
|
+
a) `spec/webface/run_test` or
|
34
|
+
b) by launching the test server with `node spec/webface/test_server.js` and navigating your browser to `localhost:8080`. You should see a sample test there.
|
35
|
+
|
36
|
+
Look at the `spec/webface/components/my_component.test.js` file for examples on how to write unit tests.
|
37
|
+
Unit tests are written with mocha & chai.
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
class WebfaceErrorReportController < ApplicationController
|
2
|
+
|
3
|
+
def report
|
4
|
+
puts "**********************************"
|
5
|
+
puts "Frontend error detected. Please make sure you redefine this action\n" +
|
6
|
+
"to actually report this error to something like Sentry!\n" +
|
7
|
+
"below are the params that were sent by Webface Logmaster:"
|
8
|
+
p params
|
9
|
+
puts "**********************************"
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
module WebfaceComponentHelper
|
2
|
+
|
3
|
+
def webface(tag_name, component: nil, attrs: {})
|
4
|
+
|
5
|
+
component = "#{component.to_s.camelize}Component" unless component.nil?
|
6
|
+
|
7
|
+
if attrs[:property_attr]
|
8
|
+
property_attrs = attrs[:property_attr].split(/,\s*?/)
|
9
|
+
property_attrs.map! { |a| "#{a.strip}:data-#{a.strip.gsub("_","-")}" }
|
10
|
+
attrs[:property_attr] = property_attrs.join(", ")
|
11
|
+
end
|
12
|
+
|
13
|
+
# convert short data attribute names to proper longer ones
|
14
|
+
webface_data = {}
|
15
|
+
webface_data[:component_class] = component
|
16
|
+
webface_data[:component_property] = attrs.delete(:property)
|
17
|
+
webface_data[:component_part] = attrs.delete(:part)
|
18
|
+
webface_data[:component_roles] = attrs.delete(:roles)
|
19
|
+
webface_data[:component_attribute_properties] = attrs.delete(:property_attr)
|
20
|
+
webface_data[:component_display_value] = attrs.delete(:display)
|
21
|
+
|
22
|
+
content_tag(tag_name, { data: webface_data.merge(attrs.delete(:data) || {})}.merge(attrs)) do
|
23
|
+
yield if block_given?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def component(name, attrs={}, &block)
|
28
|
+
render partial: "webface_components/#{name}", locals: { options: attrs.delete(:options) || {}, attrs: attrs, block: block }
|
29
|
+
end
|
30
|
+
|
31
|
+
def component_block(component, tag_name="div", attrs={}, &block)
|
32
|
+
webface(tag_name, component: component, attrs: attrs, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def property(property, tag_name="span", attrs={}, &block)
|
36
|
+
webface(tag_name, attrs: attrs.merge({ property: property}), &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def component_part(part_name, tag_name="div", attrs={}, &block)
|
40
|
+
webface(tag_name, attrs: attrs.merge({ part: part_name}), &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def button_link(caption, path, options={})
|
44
|
+
component_block (options[:confirmation] ? "confirmable_button" : "button"), "a", { class: "button", href: path, data: { prevent_native_click_event: "false", component_attribute_propertie: "lockable,disabled,confirmation,prevent_native_click_event", component_property: "caption" }}.merge(options) do
|
45
|
+
caption
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def component_form(model, action=nil, attrs={}, &block)
|
50
|
+
|
51
|
+
if model.new_record?
|
52
|
+
action = send("#{model.class.to_s.underscore.pluralize}_path") unless action
|
53
|
+
method_field = ""
|
54
|
+
else
|
55
|
+
action = send("#{model.class.to_s.underscore}_path", model.to_param) unless action
|
56
|
+
method_field = content_tag(:input, "", type: "hidden", name: "_method", value: "PATCH")
|
57
|
+
end
|
58
|
+
|
59
|
+
method_field = content_tag(:input, "", type: "hidden", name: "_method", value: attrs.delete(:method)) if attrs[:method]
|
60
|
+
|
61
|
+
auth_token_field = content_tag(:input, "", value: form_authenticity_token, type: "hidden", name: "authenticity_token")
|
62
|
+
|
63
|
+
f = WebfaceForm.new(model, self)
|
64
|
+
content = capture(f, &block)
|
65
|
+
content_tag(:form, { action: action, method: 'POST', "accept-charset" => "UTF-8", "enctype" => "multipart/form-data" }.merge(attrs)) do
|
66
|
+
concat content
|
67
|
+
concat method_field
|
68
|
+
concat auth_token_field
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def post_button_link(caption, path, verb, options={})
|
73
|
+
render partial: "webface_components/post_button_link", locals: { caption: caption, path: path, verb: verb, options: options }
|
74
|
+
end
|
75
|
+
|
76
|
+
# Attention! This method changes attrs passed to it,
|
77
|
+
# as if `attrs` was passed by reference! That's why we use `eval` and
|
78
|
+
# binding.
|
79
|
+
def data_attrs_and_values!(attrs, bdg, names=[])
|
80
|
+
data_hash = {}
|
81
|
+
names.each do |n|
|
82
|
+
data_hash[n] = eval("attrs.delete(:#{n})", bdg)
|
83
|
+
end
|
84
|
+
data_hash
|
85
|
+
end
|
86
|
+
|
87
|
+
def component_template(name, tag_name="div", attrs={}, &block)
|
88
|
+
attrs[:data] ||= {}
|
89
|
+
attrs[:data][:component_template] = "#{name.to_s.camelize}Component"
|
90
|
+
component_block(nil, tag_name, attrs, &block)
|
91
|
+
end
|
92
|
+
|
93
|
+
def prepare_select_collection(c, selected: nil, blank_option: false)
|
94
|
+
|
95
|
+
if c
|
96
|
+
collection = c.dup
|
97
|
+
else
|
98
|
+
return []
|
99
|
+
end
|
100
|
+
|
101
|
+
if collection.kind_of?(Array)
|
102
|
+
if !collection[0].kind_of?(Array)
|
103
|
+
collection.map! { |option| [option, option] }
|
104
|
+
end
|
105
|
+
elsif collection.kind_of?(Hash)
|
106
|
+
collection = collection.to_a
|
107
|
+
end
|
108
|
+
|
109
|
+
if blank_option
|
110
|
+
collection.insert(0,["null", t("views.select_boxes.no_value")])
|
111
|
+
end
|
112
|
+
|
113
|
+
collection.map! { |option| [option[0].to_s, option[1].to_s] }
|
114
|
+
collection
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
def select_component_get_selected_value(options, type=:input)
|
119
|
+
|
120
|
+
input_value = if options[:selected]
|
121
|
+
options[:selected]
|
122
|
+
elsif options[:name]
|
123
|
+
get_value_for_field_name(options)
|
124
|
+
end
|
125
|
+
|
126
|
+
if type == :input
|
127
|
+
input_value
|
128
|
+
else
|
129
|
+
if options[:collection].blank? && !get_value_for_field_name(options)
|
130
|
+
return get_value_for_field_name(options, from_params: true)
|
131
|
+
end
|
132
|
+
prepare_select_collection(options[:collection]).to_h[input_value]
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
def get_value_for_field_name(options, from_params: false)
|
138
|
+
|
139
|
+
@model_array_fields_counter ||= {}
|
140
|
+
|
141
|
+
if options[:name] && options[:name].include?("[")
|
142
|
+
names = get_field_names_chain_from_nested_field_param_and_model(options)
|
143
|
+
model_name = names.shift
|
144
|
+
|
145
|
+
if options[:model] && !from_params
|
146
|
+
field_name = options[:model_field_name] || options[:attr_name]
|
147
|
+
if field_name.blank?
|
148
|
+
return nil
|
149
|
+
else
|
150
|
+
if options[:nested_field]
|
151
|
+
model = get_target_model(names, options)
|
152
|
+
model.send(field_name) if model
|
153
|
+
else
|
154
|
+
field = options[:model].send(names.shift)
|
155
|
+
names.each do |n|
|
156
|
+
return nil if n.nil? || field.nil?
|
157
|
+
# This deals with fields that are arrays, for example quiz_question[answers][en][]
|
158
|
+
# In case field name ends with [] (empty string in `n`), we start counting how many fields
|
159
|
+
# with such a name exist and pull values from the model's array stored in that field.
|
160
|
+
if n.to_s == ""
|
161
|
+
@model_array_fields_counter[options[:name]] ||= 0
|
162
|
+
field = field[@model_array_fields_counter[options[:name]]]
|
163
|
+
@model_array_fields_counter[options[:name]] = @model_array_fields_counter[options[:name]] + 1
|
164
|
+
else
|
165
|
+
field = field[n]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
return field
|
169
|
+
end
|
170
|
+
end
|
171
|
+
else
|
172
|
+
field_name = options[:attr_name]
|
173
|
+
value = params[options[:model_name]]
|
174
|
+
names.each do |fn|
|
175
|
+
value = if fn.kind_of?(Array) && value
|
176
|
+
m = value["#{fn[0]}_attributes"]
|
177
|
+
m[options[:field_counter]] if m
|
178
|
+
else
|
179
|
+
value["#{fn}_attributes"] if value
|
180
|
+
end
|
181
|
+
end
|
182
|
+
return value[field_name] if value
|
183
|
+
end
|
184
|
+
else
|
185
|
+
params[options[:name]]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def get_error_for_field_name(options)
|
190
|
+
names = get_field_names_chain_from_nested_field_param_and_model(options)
|
191
|
+
model_name = names.shift
|
192
|
+
field_name = options[:attr_name]
|
193
|
+
return unless field_name
|
194
|
+
|
195
|
+
if !options[:model].blank? && model = get_target_model(names, options)
|
196
|
+
model.errors[field_name]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def get_field_names_chain_from_nested_field_param_and_model(options)
|
201
|
+
result = [options[:model].class.to_s.underscore]
|
202
|
+
|
203
|
+
# associated model
|
204
|
+
if assoc = options[:nested_field]
|
205
|
+
assoc.each do |a|
|
206
|
+
if a[1] == :has_one
|
207
|
+
result << a[0]
|
208
|
+
else
|
209
|
+
result << [a[0], a[2]]
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# serialized hash
|
214
|
+
elsif options[:name]
|
215
|
+
result = []
|
216
|
+
options[:name].split("[").map { |n| n.chomp("]") }.each do |n|
|
217
|
+
n = if n =~ /\A\d+\Z/
|
218
|
+
n.to_i
|
219
|
+
else
|
220
|
+
n.to_sym
|
221
|
+
end
|
222
|
+
result << n
|
223
|
+
end
|
224
|
+
end
|
225
|
+
result
|
226
|
+
end
|
227
|
+
|
228
|
+
def get_target_model(names, options)
|
229
|
+
model = options[:model]
|
230
|
+
return model unless options[:nested_field]
|
231
|
+
|
232
|
+
names.each_with_index do |fn,i|
|
233
|
+
return nil if model.nil?
|
234
|
+
model = if fn.kind_of?(Array)
|
235
|
+
collection = model.send(fn[0])
|
236
|
+
if fn[1]
|
237
|
+
collection.select { |m| fn[1] == m.id }.first
|
238
|
+
else
|
239
|
+
collection[options[:field_counter]]
|
240
|
+
end
|
241
|
+
else
|
242
|
+
model.send(fn)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
model
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
class WebfaceForm
|
2
|
+
|
3
|
+
OPTION_ATTRS = [:tabindex, :label, :suffix, :selected, :hint, :collection, :allow_custom_value, :fetch_url, :query_param_name, :disabled, :nested_field, :serialized_hash, :model_field_name, :model_name, :radio_options_id, :popup_hint, :has_blank]
|
4
|
+
|
5
|
+
class NoSuchFormFieldType < Exception;end
|
6
|
+
|
7
|
+
def initialize(model, view_context)
|
8
|
+
@model = model
|
9
|
+
@view_context = view_context
|
10
|
+
@field_counter = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(m, *args)
|
14
|
+
|
15
|
+
attrs = {}
|
16
|
+
options = {}
|
17
|
+
if args.last.kind_of?(Hash)
|
18
|
+
attrs = args.last
|
19
|
+
options = separate_option_attrs(attrs)
|
20
|
+
end
|
21
|
+
field_type = m
|
22
|
+
|
23
|
+
unless self.class.private_method_defined?("_#{field_type}")
|
24
|
+
raise NoSuchFormFieldType, "Field type `#{field_type}` not supported"
|
25
|
+
end
|
26
|
+
|
27
|
+
field_name = if args.first.blank? || args.first.to_s =~ /!\Z/
|
28
|
+
args.first.to_s.sub("!", "")
|
29
|
+
else
|
30
|
+
"#{@model.class.to_s.underscore}[#{args.first}]"
|
31
|
+
end
|
32
|
+
|
33
|
+
if options[:nested_field]
|
34
|
+
field_name = modify_name_for_nested_field(field_name, options[:nested_field])
|
35
|
+
elsif args.first && args.first.to_s.include?("[") && options[:nested_field].nil?
|
36
|
+
field_name = modify_name_for_serialized_field(field_name)
|
37
|
+
end
|
38
|
+
|
39
|
+
if @field_counter[field_name]
|
40
|
+
@field_counter[field_name] += 1
|
41
|
+
else
|
42
|
+
@field_counter[field_name] = 0
|
43
|
+
end
|
44
|
+
options[:field_counter] = @field_counter[field_name]
|
45
|
+
|
46
|
+
options[:radio_options_id] = options[:name] if options[:name]
|
47
|
+
|
48
|
+
options.merge!({ name: field_name, attr_name: args.first, model: @model, model_name: self.model_name})
|
49
|
+
attrs.delete_if { |k,v| OPTION_ATTRS.include?(k) }
|
50
|
+
attrs.merge!({ options: options })
|
51
|
+
|
52
|
+
if attrs[:options][:label].nil?
|
53
|
+
attrs[:options][:label] = I18n.t("models.#{field_name_for_i18n(field_name)}")
|
54
|
+
end
|
55
|
+
|
56
|
+
self.send("_#{field_type}", attrs)
|
57
|
+
end
|
58
|
+
|
59
|
+
def model_name
|
60
|
+
if @model
|
61
|
+
@model.class.to_s.underscore
|
62
|
+
else
|
63
|
+
options[:name].split("[").first
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def _text(attrs)
|
70
|
+
@view_context.component :text_form_field, attrs
|
71
|
+
end
|
72
|
+
|
73
|
+
def _password(attrs)
|
74
|
+
@view_context.component :password_form_field, attrs
|
75
|
+
end
|
76
|
+
|
77
|
+
def _hidden(attrs)
|
78
|
+
@view_context.component :hidden_form_field, attrs
|
79
|
+
end
|
80
|
+
|
81
|
+
def _numeric(attrs)
|
82
|
+
@view_context.component :numeric_form_field, attrs
|
83
|
+
end
|
84
|
+
|
85
|
+
def _textarea(attrs)
|
86
|
+
@view_context.component :textarea_form_field, attrs
|
87
|
+
end
|
88
|
+
|
89
|
+
def _checkbox(attrs)
|
90
|
+
@view_context.component :checkbox, attrs
|
91
|
+
end
|
92
|
+
|
93
|
+
def _select(attrs)
|
94
|
+
@view_context.component :select, attrs
|
95
|
+
end
|
96
|
+
|
97
|
+
def _editable_select(attrs)
|
98
|
+
@view_context.component :editable_select, attrs
|
99
|
+
end
|
100
|
+
|
101
|
+
def _radio(attrs)
|
102
|
+
@view_context.component :radio, attrs
|
103
|
+
end
|
104
|
+
|
105
|
+
def _submit(attrs)
|
106
|
+
attrs[:data] ||= {}
|
107
|
+
attrs[:data][:prevent_native_click_event] = "false"
|
108
|
+
@view_context.component :button, attrs.merge({options: { type: :submit}})
|
109
|
+
end
|
110
|
+
|
111
|
+
def separate_option_attrs(attrs)
|
112
|
+
option_attrs = {}
|
113
|
+
if attrs.kind_of?(Hash)
|
114
|
+
OPTION_ATTRS.each do |o|
|
115
|
+
option_attrs[o] = attrs[o] if !attrs[o].nil?
|
116
|
+
end
|
117
|
+
end
|
118
|
+
option_attrs
|
119
|
+
end
|
120
|
+
|
121
|
+
def modify_name_for_nested_field(name, association_name)
|
122
|
+
if association_name.kind_of?(Array)
|
123
|
+
association_name.each do |a|
|
124
|
+
name = send("modify_name_for_nested_#{a[1]}", name, a[0])
|
125
|
+
end
|
126
|
+
return name
|
127
|
+
else
|
128
|
+
modify_name_for_nested_has_many(name, association_name)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def modify_name_for_serialized_field(field_name)
|
133
|
+
model,remainder = field_name.split("[",2)
|
134
|
+
remainder.chomp!("]")
|
135
|
+
field_name,remainder = remainder.split("[",2)
|
136
|
+
"#{model}[#{field_name}][#{remainder}"
|
137
|
+
end
|
138
|
+
|
139
|
+
def modify_name_for_nested_has_one(name, nested_has_one_name)
|
140
|
+
# name == model[attribute]
|
141
|
+
# result == model[association][attribute]
|
142
|
+
model_name = name.scan(/\A.*?\[/)[0].sub("[", "")
|
143
|
+
field_names = name.scan(/\[.*?\]/)
|
144
|
+
actual_field_name = field_names.pop
|
145
|
+
"#{model_name}#{field_names.join("")}[#{nested_has_one_name}_attributes]#{actual_field_name}"
|
146
|
+
end
|
147
|
+
|
148
|
+
def modify_name_for_nested_has_many(name, nested_has_many_name)
|
149
|
+
# name == model[attribute]
|
150
|
+
# result == model[association][][attribute]
|
151
|
+
model_name = name.scan(/\A.*?\[/)[0].sub("[", "")
|
152
|
+
field_names = name.scan(/\[.*?\]/)
|
153
|
+
actual_field_name = field_names.pop
|
154
|
+
"#{model_name}#{field_names.join("")}[#{nested_has_many_name}_attributes][]#{actual_field_name}"
|
155
|
+
end
|
156
|
+
|
157
|
+
def field_name_for_i18n(fn)
|
158
|
+
fn = fn.split("[").map { |n| n.sub("]", "") }.join(".")
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|