aaf-lipstick 1.1.0 → 2.0.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 +4 -4
- data/Rakefile +7 -15
- data/app/assets/images/aaf-icon.png +0 -0
- data/app/assets/images/logo.png +0 -0
- data/app/assets/javascripts/aaf-lipstick.js +90 -0
- data/app/assets/stylesheets/aaf-lipstick.css.scss +304 -0
- data/app/views/layouts/email_branding.html.erb +35 -36
- data/lib/aaf-lipstick.rb +1 -0
- data/lib/lipstick.rb +4 -2
- data/lib/lipstick/auto_validation.rb +26 -22
- data/lib/lipstick/email_message.rb +1 -0
- data/lib/lipstick/engine.rb +3 -0
- data/lib/lipstick/filterable.rb +57 -0
- data/lib/lipstick/helpers.rb +6 -2
- data/lib/lipstick/helpers/bootstrap_form_builder.rb +61 -0
- data/lib/lipstick/helpers/compatibility_hacks.rb +39 -0
- data/lib/lipstick/helpers/form_helper.rb +117 -136
- data/lib/lipstick/helpers/form_validation_builder.rb +70 -0
- data/lib/lipstick/helpers/layout_helper.rb +98 -57
- data/lib/lipstick/helpers/nav_helper.rb +24 -29
- data/lib/lipstick/helpers/pagination_link_renderer.rb +131 -0
- data/lib/lipstick/images.rb +1 -0
- data/lib/lipstick/images/email_banner.rb +10 -4
- data/lib/lipstick/images/processor.rb +10 -4
- data/lib/lipstick/sprockets_asset_data_url_helper.rb +14 -0
- data/lib/lipstick/version.rb +2 -1
- metadata +75 -14
- data/app/assets/javascripts/aaf-layout.js +0 -35
- data/app/assets/stylesheets/aaf-layout.css.scss +0 -204
- data/lib/lipstick/action_view_tilt_template.rb +0 -17
- data/lib/lipstick/helpers/semantic_form_builder.rb +0 -2
data/lib/aaf-lipstick.rb
CHANGED
data/lib/lipstick.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Lipstick
|
2
3
|
def self.asset_path
|
3
4
|
File.expand_path(File.join('..', 'app', 'assets'), File.dirname(__FILE__))
|
@@ -7,6 +8,7 @@ end
|
|
7
8
|
require 'lipstick/helpers'
|
8
9
|
require 'lipstick/email_message'
|
9
10
|
require 'lipstick/images'
|
10
|
-
require 'lipstick/
|
11
|
+
require 'lipstick/filterable' if defined?(Arel)
|
12
|
+
require 'lipstick/auto_validation' if defined?(ActiveModel)
|
11
13
|
require 'lipstick/engine' if defined?(Rails::Engine)
|
12
|
-
require 'lipstick/
|
14
|
+
require 'lipstick/version'
|
@@ -1,12 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
3
2
|
module Lipstick
|
4
3
|
module AutoValidation
|
5
4
|
module ClassMethods
|
6
5
|
def self.extended(base)
|
7
6
|
return if base.respond_to?(:validators)
|
8
7
|
|
9
|
-
|
8
|
+
raise('Lipstick::AutoValidation requires a class which responds' \
|
10
9
|
' to the `validators` method. For example, as provided by' \
|
11
10
|
' ActiveModel::Validations')
|
12
11
|
end
|
@@ -14,7 +13,7 @@ module Lipstick
|
|
14
13
|
def lipstick_auto_validators
|
15
14
|
validators.each_with_object({}) do |validator, map|
|
16
15
|
validator.attributes.each do |attr|
|
17
|
-
out =
|
16
|
+
out = lipstick_validator(attr, validator)
|
18
17
|
next if out.nil?
|
19
18
|
|
20
19
|
map[attr.to_sym] ||= {}
|
@@ -23,46 +22,51 @@ module Lipstick
|
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
25
|
+
def lipstick_field_name(attr)
|
26
|
+
map = @lipstick_field_names || {}
|
27
|
+
return map[attr] if map.key?(attr)
|
28
|
+
attr.to_s.humanize(capitalize: false)
|
29
|
+
end
|
30
|
+
|
26
31
|
private
|
27
32
|
|
28
33
|
v = ActiveModel::Validations
|
29
34
|
VALIDATOR_TRANSLATORS = {
|
30
|
-
v::PresenceValidator => :
|
31
|
-
v::LengthValidator => :
|
32
|
-
v::NumericalityValidator => :
|
33
|
-
}
|
35
|
+
v::PresenceValidator => :lipstick_presence_validator,
|
36
|
+
v::LengthValidator => :lipstick_length_validator,
|
37
|
+
v::NumericalityValidator => :lipstick_numericality_validator
|
38
|
+
}.freeze
|
34
39
|
private_constant :VALIDATOR_TRANSLATORS
|
35
40
|
|
36
|
-
def
|
41
|
+
def lipstick_validator(attr, validator)
|
37
42
|
VALIDATOR_TRANSLATORS.each do |klass, sym|
|
38
43
|
next unless validator.is_a?(klass)
|
39
|
-
return send(sym, attr, validator,
|
40
|
-
attr.to_s.humanize(capitalize: false))
|
44
|
+
return send(sym, attr, validator, lipstick_field_name(attr.to_sym))
|
41
45
|
end
|
42
46
|
nil
|
43
47
|
end
|
44
48
|
|
45
|
-
def
|
46
|
-
{
|
49
|
+
def lipstick_presence_validator(_attr, _validator, humanized)
|
50
|
+
{ required: { message: "Please enter a value for #{humanized}" } }
|
47
51
|
end
|
48
52
|
|
49
|
-
def
|
53
|
+
def lipstick_length_validator(_attr, validator, humanized)
|
50
54
|
min = validator.options[:minimum]
|
51
55
|
max = validator.options[:maximum]
|
52
56
|
|
53
|
-
min_message = "Please enter a longer value for #{humanized}" \
|
54
|
-
"
|
55
|
-
max_message = "Please enter a shorter value for #{humanized}" \
|
56
|
-
"
|
57
|
+
min_message = "Please enter a longer value for #{humanized} " \
|
58
|
+
"(minimum #{min} characters)"
|
59
|
+
max_message = "Please enter a shorter value for #{humanized} " \
|
60
|
+
"(maximum #{max} characters)"
|
57
61
|
|
58
62
|
{}.tap do |out|
|
59
|
-
out[
|
60
|
-
out[
|
63
|
+
out[:minlength] = { param: min, message: min_message } if min
|
64
|
+
out[:maxlength] = { param: max, message: max_message } if max
|
61
65
|
end
|
62
66
|
end
|
63
67
|
|
64
|
-
def
|
65
|
-
{
|
68
|
+
def lipstick_numericality_validator(_attr, _validator, humanized)
|
69
|
+
{ digits: { message: "Please enter a numeric value for #{humanized}" } }
|
66
70
|
end
|
67
71
|
end
|
68
72
|
|
data/lib/lipstick/engine.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lipstick
|
4
|
+
module Filterable
|
5
|
+
# Provides case-insensitive matching on a case-sensitive MySQL database
|
6
|
+
# (which is mandated by Gumboot)
|
7
|
+
class CollatedArelAttribute < Arel::Nodes::Node
|
8
|
+
include Arel::Predications
|
9
|
+
|
10
|
+
attr_reader :attribute, :collation
|
11
|
+
|
12
|
+
def initialize(attribute, collation)
|
13
|
+
@attribute = attribute
|
14
|
+
@collation = collation
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module VisitCollatedArelAttribute
|
19
|
+
# rubocop:disable Style/MethodName
|
20
|
+
def visit_Lipstick_Filterable_CollatedArelAttribute(o, collector)
|
21
|
+
visit(o.attribute, collector)
|
22
|
+
collector << ' COLLATE ' << o.collation
|
23
|
+
end
|
24
|
+
# rubocop:enable Style/MethodName
|
25
|
+
end
|
26
|
+
|
27
|
+
Arel::Visitors::ToSql.include(VisitCollatedArelAttribute)
|
28
|
+
|
29
|
+
module ClassMethods
|
30
|
+
attr_reader :filterable_fields
|
31
|
+
|
32
|
+
def filterable_by(*fields)
|
33
|
+
@filterable_fields = fields
|
34
|
+
end
|
35
|
+
|
36
|
+
def filter(query)
|
37
|
+
filter_terms(query).reduce(all) do |scope, term|
|
38
|
+
conds = filterable_fields.map do |f|
|
39
|
+
CollatedArelAttribute.new(arel_table[f], 'utf8_unicode_ci')
|
40
|
+
.matches(term)
|
41
|
+
end
|
42
|
+
scope.where(conds.reduce { |a, e| a.or(e) })
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def filter_terms(query)
|
49
|
+
query.to_s.downcase.split(/\s+/).map { |s| "*#{s}*".gsub(/[%*]+/, '%') }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.included(base)
|
54
|
+
base.extend(ClassMethods)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/lipstick/helpers.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Lipstick
|
2
3
|
module Helpers
|
3
4
|
end
|
@@ -5,5 +6,8 @@ end
|
|
5
6
|
|
6
7
|
require 'lipstick/helpers/layout_helper'
|
7
8
|
require 'lipstick/helpers/nav_helper'
|
8
|
-
require 'lipstick/helpers/
|
9
|
-
require 'lipstick/helpers/
|
9
|
+
require 'lipstick/helpers/form_validation_builder'
|
10
|
+
require 'lipstick/helpers/bootstrap_form_builder' if defined?(ActionView)
|
11
|
+
require 'lipstick/helpers/form_helper' if defined?(ActionView)
|
12
|
+
require 'lipstick/helpers/pagination_link_renderer'
|
13
|
+
require 'lipstick/helpers/compatibility_hacks'
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# rubocop:disable Metrics/ParameterLists
|
3
|
+
|
4
|
+
class BootstrapFormBuilder < ActionView::Helpers::FormBuilder
|
5
|
+
attr_reader :template
|
6
|
+
|
7
|
+
def date_field(field, **opts)
|
8
|
+
add_css_class(opts, 'date-picker')
|
9
|
+
text_field(field, opts)
|
10
|
+
end
|
11
|
+
|
12
|
+
def text_field(field, **opts)
|
13
|
+
add_css_class(opts, 'form-control')
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def password_field(field, **opts)
|
18
|
+
add_css_class(opts, 'form-control')
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def text_area(method, **opts)
|
23
|
+
add_css_class(opts, 'form-control')
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def check_box(*)
|
28
|
+
template.content_tag('div', class: 'checkbox') do
|
29
|
+
template.content_tag('label') do
|
30
|
+
template.concat(super)
|
31
|
+
template.concat(template.capture { yield })
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def radio_button(*)
|
37
|
+
template.content_tag('div', class: 'radio') do
|
38
|
+
template.content_tag('label') do
|
39
|
+
template.concat(super)
|
40
|
+
template.concat(template.capture { yield })
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def select(method, choices = nil, opts = {}, html_opts = {})
|
46
|
+
add_css_class(html_opts, 'form-control')
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def collection_select(method, collection, value_method, text_method,
|
51
|
+
opts = {}, html_opts = {})
|
52
|
+
add_css_class(html_opts, 'form-control')
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def add_css_class(opts, class_name)
|
59
|
+
opts[:class] = "#{opts[:class]} #{class_name}".strip
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Lipstick
|
3
|
+
module Helpers
|
4
|
+
module CompatibilityHacks
|
5
|
+
def concat(content)
|
6
|
+
@_out_buf << content
|
7
|
+
end
|
8
|
+
|
9
|
+
def content_tag(name, content_or_opts = nil, opts = nil, &block)
|
10
|
+
opts = content_or_opts if block_given?
|
11
|
+
content = block_given? ? capture(&block) : content_or_opts
|
12
|
+
"<#{name}#{html_attrs(opts)}>#{content}</#{name}>"
|
13
|
+
end
|
14
|
+
|
15
|
+
def tag(name, opts = nil)
|
16
|
+
"<#{name}#{html_attrs(opts)}/>"
|
17
|
+
end
|
18
|
+
|
19
|
+
def capture
|
20
|
+
old = @_out_buf
|
21
|
+
@_out_buf = io = StringIO.new
|
22
|
+
result = yield
|
23
|
+
io_length = io.length
|
24
|
+
io_length.positive? ? io.string : result
|
25
|
+
ensure
|
26
|
+
@_out_buf = old
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def html_attrs(opts)
|
32
|
+
return '' if opts.nil?
|
33
|
+
opts.reduce('') do |a, (k, v)|
|
34
|
+
%(#{a} #{k}="#{v}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,28 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Lipstick
|
2
3
|
module Helpers
|
3
4
|
module FormHelper
|
4
5
|
include ActionView::Helpers::FormTagHelper
|
5
6
|
|
6
|
-
def field_block(html_opts = {}
|
7
|
-
|
8
|
-
|
7
|
+
def field_block(html_opts = {})
|
8
|
+
add_css_class(html_opts, 'form-group')
|
9
|
+
content_tag('div', html_opts) { yield }
|
10
|
+
end
|
11
|
+
|
12
|
+
def radio_button_tag(name, value, checked = false, options = {})
|
13
|
+
content_tag('div', class: 'radio') do
|
14
|
+
content_tag('label') do
|
15
|
+
concat(super)
|
16
|
+
concat(capture { yield })
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def check_box_tag(*)
|
22
|
+
content_tag('div', class: 'checkbox') do
|
23
|
+
content_tag('label') do
|
24
|
+
concat(super)
|
25
|
+
concat(capture { yield })
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
alias orig_form_tag form_tag
|
31
|
+
|
32
|
+
def inline_form_tag(url_for_options = {}, options = {})
|
33
|
+
add_css_class(options, 'form-inline')
|
34
|
+
form_tag(url_for_options, options) { yield }
|
35
|
+
end
|
36
|
+
|
37
|
+
def search_form_tag(filter, url: nil)
|
38
|
+
form_tag(url, method: :get) do
|
39
|
+
field_block { search_form_input_tag(filter) }
|
9
40
|
end
|
41
|
+
end
|
10
42
|
|
11
|
-
|
12
|
-
content_tag('div',
|
43
|
+
def search_form_input_tag(filter)
|
44
|
+
content_tag('div', class: 'row') do
|
45
|
+
content_tag('div', grouped_search_field(filter), class: 'col-lg-12')
|
46
|
+
end
|
13
47
|
end
|
14
48
|
|
15
|
-
|
49
|
+
def search_filter_text_field(filter)
|
50
|
+
orig_text_field_tag(:filter, filter,
|
51
|
+
placeholder: 'Search within these entries',
|
52
|
+
autocomplete: 'off',
|
53
|
+
class: 'form-control')
|
54
|
+
end
|
16
55
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
56
|
+
def search_button
|
57
|
+
button_tag(type: 'submit') do
|
58
|
+
concat(icon_tag('search'))
|
59
|
+
concat(' Search')
|
60
|
+
end
|
20
61
|
end
|
21
62
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
63
|
+
def grouped_search_field(filter)
|
64
|
+
content_tag('div', class: 'input-group') do
|
65
|
+
concat(search_filter_text_field(filter))
|
66
|
+
concat(content_tag('span', search_button, class: 'input-group-btn'))
|
26
67
|
end
|
27
68
|
end
|
28
69
|
|
@@ -30,167 +71,107 @@ module Lipstick
|
|
30
71
|
content_tag('div', style: 'display: none;', &block)
|
31
72
|
end
|
32
73
|
|
33
|
-
|
34
|
-
|
74
|
+
alias orig_text_field_tag text_field_tag
|
75
|
+
|
76
|
+
def text_field_tag(name, value = nil, opts = {})
|
77
|
+
add_css_class(opts, 'form-control')
|
78
|
+
super
|
35
79
|
end
|
36
80
|
|
37
|
-
def
|
38
|
-
|
81
|
+
def text_area_tag(name, content = nil, opts = {})
|
82
|
+
add_css_class(opts, 'form-control')
|
83
|
+
super
|
39
84
|
end
|
40
85
|
|
41
|
-
def
|
42
|
-
|
86
|
+
def date_field_tag(name, value = nil, **opts)
|
87
|
+
opts[:class] = "#{opts[:class]} date-picker".strip
|
88
|
+
text_field_tag(name, value, opts)
|
89
|
+
end
|
90
|
+
|
91
|
+
def select_tag(name, option_tags = nil, **opts)
|
92
|
+
add_css_class(opts, 'form-control')
|
93
|
+
super
|
94
|
+
end
|
43
95
|
|
96
|
+
def button_tag(content_or_options = nil, options = nil, &block)
|
44
97
|
if content_or_options.is_a?(Hash)
|
45
|
-
|
98
|
+
content_or_options[:class] ||= 'btn-default'
|
99
|
+
add_css_class(content_or_options, 'btn')
|
100
|
+
super
|
46
101
|
else
|
47
|
-
|
102
|
+
options ||= {}
|
103
|
+
options[:class] ||= 'btn-default'
|
104
|
+
add_css_class(options, 'btn')
|
105
|
+
super(content_or_options, options, &block)
|
48
106
|
end
|
49
107
|
end
|
50
108
|
|
51
109
|
def delete_button_tag(url, text: true, **opts)
|
52
|
-
|
53
|
-
content_tag('div', class: "#{css_class} #{opts[:class]}".strip) do
|
54
|
-
concat(icon_tag('trash'))
|
55
|
-
action = text && text.is_a?(String) ? text : 'Delete'
|
56
|
-
concat(action) if text
|
57
|
-
confirm = button_link_to(url, method: :delete, class: 'small') do
|
58
|
-
"Confirm #{action}"
|
59
|
-
end
|
60
|
-
concat(content_tag('div', confirm, class: 'menu'))
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def error_messages_tag
|
65
|
-
content_tag('div', '', class: 'ui error message')
|
66
|
-
end
|
110
|
+
action = text && text.is_a?(String) ? text : 'Delete'
|
67
111
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
concat(super)
|
72
|
-
concat(capture(&block))
|
73
|
-
end
|
112
|
+
content_tag('div', class: 'btn-group') do
|
113
|
+
concat(delete_dropdown_opener(text && action, opts))
|
114
|
+
concat(confirm_delete_dropdown(url, action))
|
74
115
|
end
|
75
116
|
end
|
76
117
|
|
77
118
|
def form_for(obj, opts = {}, &block)
|
78
|
-
opts[:builder] =
|
79
|
-
opts[:html] ||= {}
|
80
|
-
opts[:html][:class] = "#{opts[:html][:class]} ui form".strip
|
119
|
+
opts[:builder] = BootstrapFormBuilder
|
81
120
|
super(obj, opts, &block)
|
82
121
|
end
|
83
122
|
|
84
|
-
def radio_button_block(&block)
|
85
|
-
content_tag('div', class: 'grouped fields', &block)
|
86
|
-
end
|
87
|
-
|
88
123
|
# Generates the wrapping code for validating a form. The selector is
|
89
124
|
# passed to jQuery, and must uniquely select the form being validated.
|
90
125
|
# `sym` is the object name when using a `form_for` helper to generate the
|
91
126
|
# form.
|
92
127
|
#
|
93
128
|
# e.g.
|
94
|
-
# <%=
|
95
|
-
#
|
96
|
-
#
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
# <%= validate_field(:email, email: 'Enter a valid email address') %>
|
109
|
-
# <%= validate_field(:password, :'length[6]' => '6 characters minimum') %>
|
110
|
-
# <%= validate_field(:multiple, empty: 'Not empty', url: 'Must be URL') %>
|
111
|
-
def validate_field(name, opts)
|
112
|
-
format('validations[%{name}] = ' \
|
113
|
-
'(function(v) { %{inner} })' \
|
114
|
-
'($.extend({rules: []}, validations[%{name}]));',
|
115
|
-
name: name.to_json,
|
116
|
-
inner: validation_for_field(name, opts)).html_safe
|
117
|
-
end
|
118
|
-
|
119
|
-
# Automatically generates validators for fields based on certain supported
|
120
|
-
# validators from ActiveModel::Validations. The model must include the
|
121
|
-
# Lipstick::AutoValidation module.
|
122
|
-
#
|
123
|
-
# class MyModel < ActiveRecord::Base
|
124
|
-
# include Lipstick::AutoValidation
|
125
|
-
#
|
126
|
-
# validates :name, presence: true
|
127
|
-
# validates :description, length: 1..255
|
128
|
-
# end
|
129
|
-
#
|
130
|
-
# <%= auto_validate(@object, :name, :description) %>
|
131
|
-
def auto_validate(obj, *fields)
|
132
|
-
unless obj.class.respond_to?(:lipstick_auto_validators)
|
133
|
-
fail("#{obj.class.name} does not include Lipstick::AutoValidation")
|
134
|
-
end
|
135
|
-
|
136
|
-
validators = obj.class.lipstick_auto_validators
|
137
|
-
capture do
|
138
|
-
validators.slice(*fields).each do |name, opts|
|
139
|
-
concat validate_field(name, opts)
|
140
|
-
end
|
129
|
+
# <%=
|
130
|
+
# validate_form('#new-test-object', :test_object) do |v|
|
131
|
+
# v.validate_field(:name, ...) # Validate the test_object[name] field
|
132
|
+
# end
|
133
|
+
# %>
|
134
|
+
def validate_form(selector, sym = nil)
|
135
|
+
opts = {
|
136
|
+
type: 'application/vnd.aaf.lipstick.validations+json',
|
137
|
+
'data-target': selector,
|
138
|
+
class: 'lipstick-validations'
|
139
|
+
}
|
140
|
+
|
141
|
+
content_tag('script', opts) do
|
142
|
+
validation_json(sym) { |v| yield v }.html_safe
|
141
143
|
end
|
142
144
|
end
|
143
145
|
|
144
146
|
private
|
145
147
|
|
146
|
-
def
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
validation_install(selector)
|
148
|
+
def validation_json(sym)
|
149
|
+
v = FormValidationBuilder.new(sym)
|
150
|
+
yield v
|
151
|
+
JSON.generate(v.to_h)
|
151
152
|
end
|
152
153
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
def validation_name_mapping_function(sym)
|
157
|
-
if sym.nil?
|
158
|
-
'var map_name = function(n) { return n; };'
|
159
|
-
else
|
160
|
-
'var map_name = function(n) { ' \
|
161
|
-
"return #{sym.to_json} + '[' + n + ']';" \
|
162
|
-
'};'
|
163
|
-
end
|
164
|
-
end
|
154
|
+
def delete_dropdown_opener(label, **opts)
|
155
|
+
opts = { 'aria-expanded': 'false', 'data-toggle': 'dropdown',
|
156
|
+
type: 'button', 'aria-haspopup': 'true' }.merge(opts)
|
165
157
|
|
166
|
-
|
167
|
-
format('$(%s).form(validations, { keyboardShortcuts: false });',
|
168
|
-
selector.to_json)
|
169
|
-
end
|
170
|
-
|
171
|
-
def jquery_callback(body)
|
172
|
-
format('jQuery(function($){%s});', body)
|
173
|
-
end
|
158
|
+
add_css_class(opts, 'btn-small btn-danger dropdown-toggle')
|
174
159
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
160
|
+
button_tag(opts) do
|
161
|
+
concat(icon_tag('trash'))
|
162
|
+
concat(' ')
|
163
|
+
concat(label)
|
164
|
+
end
|
180
165
|
end
|
181
166
|
|
182
|
-
def
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
yield
|
187
|
-
ensure
|
188
|
-
Thread.current[flag] = old
|
189
|
-
end
|
167
|
+
def confirm_delete_dropdown(url, action)
|
168
|
+
link = link_to("Confirm #{action}", url, class: 'confirm-delete')
|
169
|
+
item = content_tag('li', link)
|
170
|
+
content_tag('ul', item, class: 'dropdown-menu')
|
190
171
|
end
|
191
172
|
|
192
|
-
def
|
193
|
-
|
173
|
+
def add_css_class(opts, class_name)
|
174
|
+
opts[:class] = "#{opts[:class]} #{class_name}".strip
|
194
175
|
end
|
195
176
|
end
|
196
177
|
end
|