effective_bootstrap 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +93 -0
- data/app/assets/icons/activity.svg +13 -0
- data/app/assets/icons/airplay.svg +14 -0
- data/app/assets/icons/alert-circle.svg +15 -0
- data/app/assets/icons/alert-octagon.svg +15 -0
- data/app/assets/icons/alert-triangle.svg +15 -0
- data/app/assets/icons/align-center.svg +16 -0
- data/app/assets/icons/align-justify.svg +16 -0
- data/app/assets/icons/align-left.svg +16 -0
- data/app/assets/icons/align-right.svg +16 -0
- data/app/assets/icons/anchor.svg +15 -0
- data/app/assets/icons/aperture.svg +19 -0
- data/app/assets/icons/arrow-down-circle.svg +15 -0
- data/app/assets/icons/arrow-down-left.svg +14 -0
- data/app/assets/icons/arrow-down-right.svg +14 -0
- data/app/assets/icons/arrow-down.svg +14 -0
- data/app/assets/icons/arrow-left-circle.svg +15 -0
- data/app/assets/icons/arrow-left.svg +14 -0
- data/app/assets/icons/arrow-right-circle.svg +15 -0
- data/app/assets/icons/arrow-right.svg +14 -0
- data/app/assets/icons/arrow-up-circle.svg +15 -0
- data/app/assets/icons/arrow-up-left.svg +14 -0
- data/app/assets/icons/arrow-up-right.svg +14 -0
- data/app/assets/icons/arrow-up.svg +14 -0
- data/app/assets/icons/at-sign.svg +14 -0
- data/app/assets/icons/award.svg +14 -0
- data/app/assets/icons/bar-chart-2.svg +15 -0
- data/app/assets/icons/bar-chart.svg +15 -0
- data/app/assets/icons/battery-charging.svg +15 -0
- data/app/assets/icons/battery.svg +14 -0
- data/app/assets/icons/bell-off.svg +14 -0
- data/app/assets/icons/bell.svg +13 -0
- data/app/assets/icons/bluetooth.svg +13 -0
- data/app/assets/icons/bold.svg +14 -0
- data/app/assets/icons/book-open.svg +14 -0
- data/app/assets/icons/book.svg +14 -0
- data/app/assets/icons/bookmark.svg +13 -0
- data/app/assets/icons/box.svg +15 -0
- data/app/assets/icons/briefcase.svg +14 -0
- data/app/assets/icons/calendar.svg +16 -0
- data/app/assets/icons/camera-off.svg +14 -0
- data/app/assets/icons/camera.svg +14 -0
- data/app/assets/icons/cast.svg +14 -0
- data/app/assets/icons/check-circle.svg +14 -0
- data/app/assets/icons/check-square.svg +14 -0
- data/app/assets/icons/check.svg +13 -0
- data/app/assets/icons/chevron-down.svg +13 -0
- data/app/assets/icons/chevron-left.svg +13 -0
- data/app/assets/icons/chevron-right.svg +13 -0
- data/app/assets/icons/chevron-up.svg +13 -0
- data/app/assets/icons/chevrons-down.svg +14 -0
- data/app/assets/icons/chevrons-left.svg +14 -0
- data/app/assets/icons/chevrons-right.svg +14 -0
- data/app/assets/icons/chevrons-up.svg +14 -0
- data/app/assets/icons/chrome.svg +17 -0
- data/app/assets/icons/circle.svg +13 -0
- data/app/assets/icons/clipboard.svg +14 -0
- data/app/assets/icons/clock.svg +14 -0
- data/app/assets/icons/cloud-drizzle.svg +19 -0
- data/app/assets/icons/cloud-lightning.svg +14 -0
- data/app/assets/icons/cloud-off.svg +14 -0
- data/app/assets/icons/cloud-rain.svg +16 -0
- data/app/assets/icons/cloud-snow.svg +19 -0
- data/app/assets/icons/cloud.svg +13 -0
- data/app/assets/icons/code.svg +14 -0
- data/app/assets/icons/codepen.svg +17 -0
- data/app/assets/icons/command.svg +13 -0
- data/app/assets/icons/compass.svg +14 -0
- data/app/assets/icons/copy.svg +14 -0
- data/app/assets/icons/corner-down-left.svg +14 -0
- data/app/assets/icons/corner-down-right.svg +14 -0
- data/app/assets/icons/corner-left-down.svg +14 -0
- data/app/assets/icons/corner-left-up.svg +14 -0
- data/app/assets/icons/corner-right-down.svg +14 -0
- data/app/assets/icons/corner-right-up.svg +14 -0
- data/app/assets/icons/corner-up-left.svg +14 -0
- data/app/assets/icons/corner-up-right.svg +14 -0
- data/app/assets/icons/cpu.svg +22 -0
- data/app/assets/icons/credit-card.svg +14 -0
- data/app/assets/icons/crop.svg +14 -0
- data/app/assets/icons/crosshair.svg +17 -0
- data/app/assets/icons/database.svg +15 -0
- data/app/assets/icons/delete.svg +15 -0
- data/app/assets/icons/disc.svg +14 -0
- data/app/assets/icons/dollar-sign.svg +14 -0
- data/app/assets/icons/download-cloud.svg +15 -0
- data/app/assets/icons/download.svg +15 -0
- data/app/assets/icons/droplet.svg +13 -0
- data/app/assets/icons/edit-2.svg +13 -0
- data/app/assets/icons/edit-3.svg +14 -0
- data/app/assets/icons/edit.svg +14 -0
- data/app/assets/icons/external-link.svg +15 -0
- data/app/assets/icons/eye-off.svg +14 -0
- data/app/assets/icons/eye.svg +14 -0
- data/app/assets/icons/facebook.svg +13 -0
- data/app/assets/icons/fast-forward.svg +14 -0
- data/app/assets/icons/feather.svg +15 -0
- data/app/assets/icons/file-minus.svg +15 -0
- data/app/assets/icons/file-plus.svg +16 -0
- data/app/assets/icons/file-text.svg +17 -0
- data/app/assets/icons/file.svg +14 -0
- data/app/assets/icons/film.svg +20 -0
- data/app/assets/icons/filter.svg +13 -0
- data/app/assets/icons/flag.svg +14 -0
- data/app/assets/icons/folder-minus.svg +14 -0
- data/app/assets/icons/folder-plus.svg +15 -0
- data/app/assets/icons/folder.svg +13 -0
- data/app/assets/icons/git-branch.svg +16 -0
- data/app/assets/icons/git-commit.svg +15 -0
- data/app/assets/icons/git-merge.svg +15 -0
- data/app/assets/icons/git-pull-request.svg +16 -0
- data/app/assets/icons/github.svg +13 -0
- data/app/assets/icons/gitlab.svg +13 -0
- data/app/assets/icons/globe.svg +15 -0
- data/app/assets/icons/grid.svg +16 -0
- data/app/assets/icons/hard-drive.svg +16 -0
- data/app/assets/icons/hash.svg +16 -0
- data/app/assets/icons/headphones.svg +14 -0
- data/app/assets/icons/heart.svg +13 -0
- data/app/assets/icons/help-circle.svg +15 -0
- data/app/assets/icons/home.svg +14 -0
- data/app/assets/icons/image.svg +15 -0
- data/app/assets/icons/inbox.svg +14 -0
- data/app/assets/icons/info.svg +15 -0
- data/app/assets/icons/instagram.svg +15 -0
- data/app/assets/icons/italic.svg +15 -0
- data/app/assets/icons/layers.svg +15 -0
- data/app/assets/icons/layout.svg +15 -0
- data/app/assets/icons/life-buoy.svg +19 -0
- data/app/assets/icons/link-2.svg +14 -0
- data/app/assets/icons/link.svg +14 -0
- data/app/assets/icons/linkedin.svg +15 -0
- data/app/assets/icons/list.svg +18 -0
- data/app/assets/icons/loader.svg +20 -0
- data/app/assets/icons/lock.svg +14 -0
- data/app/assets/icons/log-in.svg +15 -0
- data/app/assets/icons/log-out.svg +15 -0
- data/app/assets/icons/mail.svg +14 -0
- data/app/assets/icons/map-pin.svg +14 -0
- data/app/assets/icons/map.svg +15 -0
- data/app/assets/icons/maximize-2.svg +16 -0
- data/app/assets/icons/maximize.svg +13 -0
- data/app/assets/icons/menu.svg +15 -0
- data/app/assets/icons/message-circle.svg +13 -0
- data/app/assets/icons/message-square.svg +13 -0
- data/app/assets/icons/mic-off.svg +17 -0
- data/app/assets/icons/mic.svg +16 -0
- data/app/assets/icons/minimize-2.svg +16 -0
- data/app/assets/icons/minimize.svg +13 -0
- data/app/assets/icons/minus-circle.svg +14 -0
- data/app/assets/icons/minus-square.svg +14 -0
- data/app/assets/icons/minus.svg +13 -0
- data/app/assets/icons/monitor.svg +15 -0
- data/app/assets/icons/moon.svg +13 -0
- data/app/assets/icons/more-horizontal.svg +15 -0
- data/app/assets/icons/more-vertical.svg +15 -0
- data/app/assets/icons/move.svg +18 -0
- data/app/assets/icons/music.svg +14 -0
- data/app/assets/icons/navigation-2.svg +13 -0
- data/app/assets/icons/navigation.svg +13 -0
- data/app/assets/icons/octagon.svg +13 -0
- data/app/assets/icons/package.svg +16 -0
- data/app/assets/icons/paperclip.svg +13 -0
- data/app/assets/icons/pause-circle.svg +15 -0
- data/app/assets/icons/pause.svg +14 -0
- data/app/assets/icons/percent.svg +15 -0
- data/app/assets/icons/phone-call.svg +13 -0
- data/app/assets/icons/phone-forwarded.svg +15 -0
- data/app/assets/icons/phone-incoming.svg +15 -0
- data/app/assets/icons/phone-missed.svg +15 -0
- data/app/assets/icons/phone-off.svg +14 -0
- data/app/assets/icons/phone-outgoing.svg +15 -0
- data/app/assets/icons/phone.svg +13 -0
- data/app/assets/icons/pie-chart.svg +14 -0
- data/app/assets/icons/play-circle.svg +14 -0
- data/app/assets/icons/play.svg +13 -0
- data/app/assets/icons/plus-circle.svg +15 -0
- data/app/assets/icons/plus-square.svg +15 -0
- data/app/assets/icons/plus.svg +14 -0
- data/app/assets/icons/pocket.svg +14 -0
- data/app/assets/icons/power.svg +14 -0
- data/app/assets/icons/printer.svg +15 -0
- data/app/assets/icons/radio.svg +14 -0
- data/app/assets/icons/refresh-ccw.svg +15 -0
- data/app/assets/icons/refresh-cw.svg +15 -0
- data/app/assets/icons/repeat.svg +16 -0
- data/app/assets/icons/rewind.svg +14 -0
- data/app/assets/icons/rotate-ccw.svg +14 -0
- data/app/assets/icons/rotate-cw.svg +14 -0
- data/app/assets/icons/rss.svg +15 -0
- data/app/assets/icons/save.svg +15 -0
- data/app/assets/icons/scissors.svg +17 -0
- data/app/assets/icons/search.svg +14 -0
- data/app/assets/icons/send.svg +14 -0
- data/app/assets/icons/server.svg +16 -0
- data/app/assets/icons/settings.svg +14 -0
- data/app/assets/icons/share-2.svg +17 -0
- data/app/assets/icons/share.svg +15 -0
- data/app/assets/icons/shield-off.svg +15 -0
- data/app/assets/icons/shield.svg +13 -0
- data/app/assets/icons/shopping-bag.svg +15 -0
- data/app/assets/icons/shopping-cart.svg +15 -0
- data/app/assets/icons/shuffle.svg +17 -0
- data/app/assets/icons/sidebar.svg +14 -0
- data/app/assets/icons/skip-back.svg +14 -0
- data/app/assets/icons/skip-forward.svg +14 -0
- data/app/assets/icons/slack.svg +17 -0
- data/app/assets/icons/slash.svg +14 -0
- data/app/assets/icons/sliders.svg +21 -0
- data/app/assets/icons/smartphone.svg +14 -0
- data/app/assets/icons/speaker.svg +15 -0
- data/app/assets/icons/spinner.svg +1 -0
- data/app/assets/icons/square.svg +13 -0
- data/app/assets/icons/star.svg +13 -0
- data/app/assets/icons/stop-circle.svg +14 -0
- data/app/assets/icons/sun.svg +21 -0
- data/app/assets/icons/sunrise.svg +20 -0
- data/app/assets/icons/sunset.svg +20 -0
- data/app/assets/icons/tablet.svg +22 -0
- data/app/assets/icons/tag.svg +14 -0
- data/app/assets/icons/target.svg +15 -0
- data/app/assets/icons/terminal.svg +14 -0
- data/app/assets/icons/thermometer.svg +13 -0
- data/app/assets/icons/thumbs-down.svg +13 -0
- data/app/assets/icons/thumbs-up.svg +13 -0
- data/app/assets/icons/toggle-left.svg +14 -0
- data/app/assets/icons/toggle-right.svg +14 -0
- data/app/assets/icons/trash-2.svg +16 -0
- data/app/assets/icons/trash.svg +14 -0
- data/app/assets/icons/trending-down.svg +14 -0
- data/app/assets/icons/trending-up.svg +14 -0
- data/app/assets/icons/triangle.svg +13 -0
- data/app/assets/icons/truck.svg +16 -0
- data/app/assets/icons/tv.svg +14 -0
- data/app/assets/icons/twitter.svg +13 -0
- data/app/assets/icons/type.svg +15 -0
- data/app/assets/icons/umbrella.svg +13 -0
- data/app/assets/icons/underline.svg +14 -0
- data/app/assets/icons/unlock.svg +14 -0
- data/app/assets/icons/upload-cloud.svg +16 -0
- data/app/assets/icons/upload.svg +15 -0
- data/app/assets/icons/user-check.svg +15 -0
- data/app/assets/icons/user-minus.svg +15 -0
- data/app/assets/icons/user-plus.svg +16 -0
- data/app/assets/icons/user-x.svg +16 -0
- data/app/assets/icons/user.svg +14 -0
- data/app/assets/icons/users.svg +16 -0
- data/app/assets/icons/video-off.svg +14 -0
- data/app/assets/icons/video.svg +14 -0
- data/app/assets/icons/voicemail.svg +15 -0
- data/app/assets/icons/volume-1.svg +14 -0
- data/app/assets/icons/volume-2.svg +14 -0
- data/app/assets/icons/volume-x.svg +15 -0
- data/app/assets/icons/volume.svg +13 -0
- data/app/assets/icons/watch.svg +15 -0
- data/app/assets/icons/wifi-off.svg +19 -0
- data/app/assets/icons/wifi.svg +16 -0
- data/app/assets/icons/wind.svg +13 -0
- data/app/assets/icons/x-circle.svg +15 -0
- data/app/assets/icons/x-square.svg +15 -0
- data/app/assets/icons/x.svg +14 -0
- data/app/assets/icons/zap-off.svg +16 -0
- data/app/assets/icons/zap.svg +13 -0
- data/app/assets/icons/zoom-in.svg +16 -0
- data/app/assets/icons/zoom-out.svg +15 -0
- data/app/assets/javascripts/effective_bootstrap.js +9 -0
- data/app/assets/javascripts/effective_bootstrap/base.js.coffee +37 -0
- data/app/assets/javascripts/effective_date/initialize.js.coffee +4 -0
- data/app/assets/javascripts/effective_date/input.js +5 -0
- data/app/assets/javascripts/effective_datetime/bootstrap-datetimepicker.js +2637 -0
- data/app/assets/javascripts/effective_datetime/initialize.js.coffee +4 -0
- data/app/assets/javascripts/effective_datetime/input.js +5 -0
- data/app/assets/javascripts/effective_datetime/moment.js +4535 -0
- data/app/assets/javascripts/effective_datetime/overrides.js.coffee +38 -0
- data/app/assets/javascripts/effective_datetime/turbolinks.js.coffee +5 -0
- data/app/assets/javascripts/effective_phone/initialize.js.coffee +4 -0
- data/app/assets/javascripts/effective_phone/input.js +2 -0
- data/app/assets/javascripts/effective_phone/jquery.maskedInput.js +182 -0
- data/app/assets/javascripts/effective_price/input.js.coffee +34 -0
- data/app/assets/javascripts/effective_select/initialize.js.coffee +45 -0
- data/app/assets/javascripts/effective_select/input.js +3 -0
- data/app/assets/javascripts/effective_select/overrides.js.coffee +45 -0
- data/app/assets/javascripts/effective_select/select2.js +5746 -0
- data/app/assets/javascripts/effective_time/initialize.js.coffee +4 -0
- data/app/assets/javascripts/effective_time/input.js +5 -0
- data/app/assets/stylesheets/effective_bootstrap.scss +8 -0
- data/app/assets/stylesheets/effective_bootstrap/base.scss +11 -0
- data/app/assets/stylesheets/effective_bootstrap/icons.scss +27 -0
- data/app/assets/stylesheets/effective_date/input.scss +1 -0
- data/app/assets/stylesheets/effective_datetime/bootstrap-datetimepicker.scss +374 -0
- data/app/assets/stylesheets/effective_datetime/input.scss +2 -0
- data/app/assets/stylesheets/effective_datetime/overrides.scss +28 -0
- data/app/assets/stylesheets/effective_select/bootstrap-theme.css +564 -0
- data/app/assets/stylesheets/effective_select/input.scss +3 -0
- data/app/assets/stylesheets/effective_select/overrides.scss +92 -0
- data/app/assets/stylesheets/effective_select/select2.css +484 -0
- data/app/assets/stylesheets/effective_time/input.scss +1 -0
- data/app/helpers/effective_bootstrap_helper.rb +52 -0
- data/app/helpers/effective_form_builder_helper.rb +23 -0
- data/app/helpers/effective_icons_helper.rb +46 -0
- data/app/models/effective/access_denied.rb +17 -0
- data/app/models/effective/form_builder.rb +100 -0
- data/app/models/effective/form_input.rb +319 -0
- data/app/models/effective/form_inputs/check_box.rb +62 -0
- data/app/models/effective/form_inputs/checks.rb +73 -0
- data/app/models/effective/form_inputs/collection_input.rb +144 -0
- data/app/models/effective/form_inputs/date_field.rb +22 -0
- data/app/models/effective/form_inputs/datetime_field.rb +53 -0
- data/app/models/effective/form_inputs/email_field.rb +15 -0
- data/app/models/effective/form_inputs/error_field.rb +47 -0
- data/app/models/effective/form_inputs/form_group.rb +26 -0
- data/app/models/effective/form_inputs/number_field.rb +6 -0
- data/app/models/effective/form_inputs/password_field.rb +24 -0
- data/app/models/effective/form_inputs/phone_field.rb +34 -0
- data/app/models/effective/form_inputs/price_field.rb +37 -0
- data/app/models/effective/form_inputs/radios.rb +74 -0
- data/app/models/effective/form_inputs/select.rb +87 -0
- data/app/models/effective/form_inputs/static_field.rb +20 -0
- data/app/models/effective/form_inputs/submit.rb +42 -0
- data/app/models/effective/form_inputs/text_area.rb +11 -0
- data/app/models/effective/form_inputs/text_field.rb +6 -0
- data/app/models/effective/form_inputs/time_field.rb +22 -0
- data/app/models/effective/form_inputs/url_field.rb +15 -0
- data/app/views/effective/style_guide/__fields.html.haml +42 -0
- data/app/views/effective/style_guide/__inline_fields.html.haml +4 -0
- data/app/views/effective/style_guide/_effective_form_with.html.haml +23 -0
- data/app/views/effective/style_guide/_feather_icons.html.haml +9 -0
- data/config/effective_bootstrap.rb +24 -0
- data/lib/effective_bootstrap.rb +31 -0
- data/lib/effective_bootstrap/engine.rb +11 -0
- data/lib/effective_bootstrap/version.rb +3 -0
- data/lib/generators/effective_bootstrap/install_generator.rb +14 -0
- metadata +419 -0
@@ -0,0 +1 @@
|
|
1
|
+
@import '../effective_datetime/input';
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Boostrap4 Helpers
|
2
|
+
|
3
|
+
module EffectiveBootstrapHelper
|
4
|
+
# Nav links and dropdowns
|
5
|
+
# Automatically puts in the 'active' class based on request path
|
6
|
+
|
7
|
+
# %ul.navbar-nav
|
8
|
+
# = nav_link_to 'Sign In', new_user_session_path
|
9
|
+
# = nav_dropdown 'Settings' do
|
10
|
+
# = nav_link_to 'Account Settings', user_settings_path
|
11
|
+
# = nav_dropdown_divider
|
12
|
+
# = nav_link_to 'Sign In', new_user_session_path, method: :delete
|
13
|
+
def nav_link_to(label, path, opts = {})
|
14
|
+
if @_nav_mode == :dropdown # We insert dropdown-items
|
15
|
+
return link_to(label, path, merge_class_key(opts, 'dropdown-item'))
|
16
|
+
end
|
17
|
+
|
18
|
+
# Regular nav link item
|
19
|
+
content_tag(:li, class: (request.fullpath.include?(path) ? 'nav-item active' : 'nav-item')) do
|
20
|
+
link_to(label, path, merge_class_key(opts, 'nav-link'))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def nav_dropdown(label, right: false, link_class: [], list_class: [], &block)
|
25
|
+
raise 'expected a block' unless block_given?
|
26
|
+
|
27
|
+
id = "dropdown-#{''.object_id}"
|
28
|
+
|
29
|
+
content_tag(:li, class: 'nav-item dropdown') do
|
30
|
+
content_tag(:a, class: 'nav-link dropdown-toggle', href: '#', id: id, role: 'button', 'data-toggle': 'dropdown', 'aria-haspopup': true, 'aria-expanded': false) do
|
31
|
+
label.html_safe
|
32
|
+
end + content_tag(:div, class: (right ? 'dropdown-menu dropdown-menu-right' : 'dropdown-menu'), 'aria-labelledby': id) do
|
33
|
+
@_nav_mode = :dropdown; yield; @_nav_mode = nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def nav_divider
|
39
|
+
content_tag(:div, '', class: 'dropdown-divider')
|
40
|
+
end
|
41
|
+
|
42
|
+
def merge_class_key(hash, value)
|
43
|
+
return { :class => value } unless hash.kind_of?(Hash)
|
44
|
+
|
45
|
+
if hash[:class].present?
|
46
|
+
hash.merge!(:class => "#{hash[:class]} #{value}")
|
47
|
+
else
|
48
|
+
hash.merge!(:class => value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module EffectiveFormBuilderHelper
|
2
|
+
def effective_form_with(**options, &block)
|
3
|
+
options[:class] = [options[:class], 'needs-validation', ('form-inline' if options[:layout] == :inline)].compact.join(' ')
|
4
|
+
|
5
|
+
without_error_proc do
|
6
|
+
form_with(**options.merge(builder: Effective::FormBuilder, html: { novalidate: true, onsubmit: 'return EffectiveBootstrap.validate(this);' }), &block)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# Disables a .fields_with_errors wrapping div when
|
13
|
+
def without_error_proc
|
14
|
+
original = ActionView::Base.field_error_proc
|
15
|
+
|
16
|
+
begin
|
17
|
+
ActionView::Base.field_error_proc = proc { |input, _| input }; yield
|
18
|
+
ensure
|
19
|
+
ActionView::Base.field_error_proc = original
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module EffectiveIconsHelper
|
2
|
+
|
3
|
+
def icon(svg, options = {})
|
4
|
+
svg = svg.to_s.chomp('.svg')
|
5
|
+
|
6
|
+
options.reverse_merge!(nocomment: true)
|
7
|
+
options[:class] = [options[:class], "eb-icon eb-icon-#{svg}"].compact.join(' ')
|
8
|
+
|
9
|
+
inline_svg(svg + '.svg', options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def icon_to(svg, url, options = {})
|
13
|
+
link_to(icon(svg), url, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def show_icon_to(path, options = {})
|
17
|
+
icon_to('eye-open', path, { title: 'Show' }.merge(options))
|
18
|
+
end
|
19
|
+
|
20
|
+
def edit_icon_to(path, options = {})
|
21
|
+
icon_to('edit', path, { title: 'Edit' }.merge(options))
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy_icon_to(path, options = {})
|
25
|
+
defaults = { title: 'Destroy', data: { method: :delete, confirm: 'Delete this item?' } }
|
26
|
+
icon_to('trash', path, defaults.merge(options))
|
27
|
+
end
|
28
|
+
|
29
|
+
def settings_icon_to(path, options = {})
|
30
|
+
icon_to('cog', path, { title: 'Settings' }.merge(options))
|
31
|
+
end
|
32
|
+
|
33
|
+
def ok_icon_to(path, options = {})
|
34
|
+
icon_to('ok', path, { title: 'OK' }.merge(options))
|
35
|
+
end
|
36
|
+
|
37
|
+
def approve_icon_to(path, options = {})
|
38
|
+
icon_to('ok', path, { title: 'Approve' }.merge(options))
|
39
|
+
end
|
40
|
+
|
41
|
+
def remove_icon_to(path, options = {})
|
42
|
+
icon_to('remove', path, { title: 'Remove' }.merge(options))
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
unless defined?(Effective::AccessDenied)
|
2
|
+
module Effective
|
3
|
+
class AccessDenied < StandardError
|
4
|
+
attr_reader :action, :subject
|
5
|
+
|
6
|
+
def initialize(message = nil, action = nil, subject = nil)
|
7
|
+
@message = message
|
8
|
+
@action = action
|
9
|
+
@subject = subject
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
@message || I18n.t(:'unauthorized.default', :default => 'Access Denied')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Effective
|
2
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
3
|
+
|
4
|
+
attr_accessor :layout, :template
|
5
|
+
|
6
|
+
delegate :content_tag, to: :template
|
7
|
+
|
8
|
+
def initialize(object_name, object, template, options)
|
9
|
+
@template = template
|
10
|
+
@layout = (options.delete(:layout) || :vertical).to_sym
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
alias_method :super_text_field, :text_field
|
15
|
+
|
16
|
+
def check_box(name, options = {})
|
17
|
+
Effective::FormInputs::CheckBox.new(name, options, builder: self).to_html { super(name, options) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def checks(name, choices = nil, *args)
|
21
|
+
options = args.extract_options!.merge!(collection: choices)
|
22
|
+
Effective::FormInputs::Checks.new(name, options, builder: self).to_html
|
23
|
+
end
|
24
|
+
|
25
|
+
def date_field(name, options = {})
|
26
|
+
Effective::FormInputs::DateField.new(name, options, builder: self).to_html { super(name, options) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def datetime_field(name, options = {})
|
30
|
+
Effective::FormInputs::DatetimeField.new(name, options, builder: self).to_html { super(name, options) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def email_field(name, options = {})
|
34
|
+
Effective::FormInputs::EmailField.new(name, options, builder: self).to_html { super(name, options) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def error(name = nil, options = {})
|
38
|
+
Effective::FormInputs::ErrorField.new(name, options, builder: self).to_html()
|
39
|
+
end
|
40
|
+
alias_method :errors, :error
|
41
|
+
|
42
|
+
def form_group(name = nil, options = {}, &block)
|
43
|
+
Effective::FormInputs::FormGroup.new(name, options, builder: self).to_html(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def number_field(name, options = {})
|
47
|
+
Effective::FormInputs::NumberField.new(name, options, builder: self).to_html { super(name, options) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def password_field(name, options = {})
|
51
|
+
Effective::FormInputs::PasswordField.new(name, options, builder: self).to_html { super(name, options) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def phone_field(name, options = {})
|
55
|
+
Effective::FormInputs::PhoneField.new(name, options, builder: self).to_html { super(name, options) }
|
56
|
+
end
|
57
|
+
alias_method :tel_field, :phone_field
|
58
|
+
alias_method :telephone_field, :phone_field
|
59
|
+
|
60
|
+
def price_field(name, options = {})
|
61
|
+
Effective::FormInputs::PriceField.new(name, options, builder: self).to_html { super(name, options) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def select(name, choices = nil, *args)
|
65
|
+
options = args.extract_options!.merge!(collection: choices)
|
66
|
+
Effective::FormInputs::Select.new(name, options, builder: self).to_html
|
67
|
+
end
|
68
|
+
|
69
|
+
def submit(name = 'Submit', options = {})
|
70
|
+
Effective::FormInputs::Submit.new(name, options, builder: self).to_html { super(name, options) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def static_field(name, options = {}, &block)
|
74
|
+
Effective::FormInputs::StaticField.new(name, options, builder: self).to_html(&block)
|
75
|
+
end
|
76
|
+
|
77
|
+
def radios(name, choices = nil, *args)
|
78
|
+
options = args.extract_options!.merge!(collection: choices)
|
79
|
+
Effective::FormInputs::Radios.new(name, options, builder: self).to_html
|
80
|
+
end
|
81
|
+
|
82
|
+
def text_area(name, options = {})
|
83
|
+
Effective::FormInputs::TextArea.new(name, options, builder: self).to_html { super(name, options) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def text_field(name, options = {})
|
87
|
+
Effective::FormInputs::TextField.new(name, options, builder: self).to_html { super(name, options) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def time_field(name, options = {})
|
91
|
+
Effective::FormInputs::TimeField.new(name, options, builder: self).to_html { super(name, options) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def url_field(name, options = {})
|
95
|
+
Effective::FormInputs::UrlField.new(name, options, builder: self).to_html { super(name, options) }
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
@@ -0,0 +1,319 @@
|
|
1
|
+
module Effective
|
2
|
+
class FormInput
|
3
|
+
attr_accessor :name, :options
|
4
|
+
|
5
|
+
BLANK = ''.html_safe
|
6
|
+
|
7
|
+
delegate :object, to: :@builder
|
8
|
+
delegate :capture, :content_tag, :link_to, :icon, to: :@template
|
9
|
+
|
10
|
+
# So this takes in the options for an entire form group.
|
11
|
+
def initialize(name, options, builder:, html_options: nil)
|
12
|
+
@builder = builder
|
13
|
+
@template = builder.template
|
14
|
+
|
15
|
+
@name = name
|
16
|
+
@options = extract_options!(options, html_options: html_options)
|
17
|
+
apply_input_options!
|
18
|
+
end
|
19
|
+
|
20
|
+
def input_group_options
|
21
|
+
{ input_group: { class: 'input-group' }, prepend: false, append: false }
|
22
|
+
end
|
23
|
+
|
24
|
+
def input_html_options
|
25
|
+
{ class: 'form-control' }
|
26
|
+
end
|
27
|
+
|
28
|
+
def input_js_options
|
29
|
+
{}
|
30
|
+
end
|
31
|
+
|
32
|
+
def label_options
|
33
|
+
case layout
|
34
|
+
when :horizontal
|
35
|
+
{ class: 'col-sm-2 col-form-label'}
|
36
|
+
when :inline
|
37
|
+
{ class: 'sr-only' }
|
38
|
+
else
|
39
|
+
{ }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def feedback_options
|
44
|
+
case layout
|
45
|
+
when :inline
|
46
|
+
false
|
47
|
+
else
|
48
|
+
{ valid: { class: 'valid-feedback' }, invalid: { class: 'invalid-feedback' } }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def hint_options
|
53
|
+
case layout
|
54
|
+
when :inline
|
55
|
+
{ tag: :small, class: 'text-muted', id: "#{tag_id}_hint" }
|
56
|
+
else
|
57
|
+
{ tag: :small, class: 'form-text text-muted', id: "#{tag_id}_hint" }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def wrapper_options
|
62
|
+
case layout
|
63
|
+
when :horizontal
|
64
|
+
{ class: 'form-group row' }
|
65
|
+
else
|
66
|
+
{ class: 'form-group' }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_html(&block)
|
71
|
+
wrap(&block)
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def wrap(&block)
|
77
|
+
case layout
|
78
|
+
when :inline
|
79
|
+
build_content(&block)
|
80
|
+
when :horizontal
|
81
|
+
build_wrapper do
|
82
|
+
(build_label.presence || content_tag(:div, '', class: 'col-sm-2')) +
|
83
|
+
content_tag(:div, build_content(&block), class: 'col-sm-10')
|
84
|
+
end
|
85
|
+
else # Vertical
|
86
|
+
build_wrapper { build_content(&block) }
|
87
|
+
end.html_safe
|
88
|
+
end
|
89
|
+
|
90
|
+
def build_wrapper(&block)
|
91
|
+
content_tag(:div, yield, options[:wrapper])
|
92
|
+
end
|
93
|
+
|
94
|
+
def build_content(&block)
|
95
|
+
return build_input_group_content(&block) if input_group?
|
96
|
+
|
97
|
+
if layout == :horizontal
|
98
|
+
build_input(&block) + build_feedback + build_hint
|
99
|
+
else
|
100
|
+
build_label + build_input(&block) + build_feedback + build_hint
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def build_input_group_content(&block)
|
105
|
+
if layout == :horizontal
|
106
|
+
build_input_group { build_input(&block) } + build_hint
|
107
|
+
else
|
108
|
+
build_label + build_input_group { build_input(&block) } + build_hint
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_input_group(&block) # Includes input and feedback
|
113
|
+
content_tag(:div, '', options[:input_group][:input_group]) do # Twice here, kind of weird.
|
114
|
+
[
|
115
|
+
(content_tag(:div, options[:input_group][:prepend], class: 'input-group-prepend') if options[:input_group][:prepend]),
|
116
|
+
build_input(&block),
|
117
|
+
(content_tag(:div, options[:input_group][:append], class: 'input-group-append') if options[:input_group][:append]),
|
118
|
+
build_feedback
|
119
|
+
].compact.join.html_safe
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def build_label
|
124
|
+
return BLANK if options[:label] == false
|
125
|
+
return BLANK if name.kind_of?(NilClass)
|
126
|
+
|
127
|
+
text = (options[:label].delete(:text) || (object.class.human_attribute_name(name) if object) || BLANK).html_safe
|
128
|
+
|
129
|
+
if options[:input][:id]
|
130
|
+
options[:label][:for] = options[:input][:id]
|
131
|
+
end
|
132
|
+
|
133
|
+
@builder.label(name, text, options[:label])
|
134
|
+
end
|
135
|
+
|
136
|
+
def build_input(&block)
|
137
|
+
capture(&block)
|
138
|
+
end
|
139
|
+
|
140
|
+
def build_hint
|
141
|
+
return BLANK unless options[:hint] && options[:hint][:text]
|
142
|
+
|
143
|
+
tag = options[:hint].delete(:tag)
|
144
|
+
text = options[:hint].delete(:text).html_safe
|
145
|
+
|
146
|
+
content_tag(tag, text, options[:hint])
|
147
|
+
end
|
148
|
+
|
149
|
+
def build_feedback
|
150
|
+
return BLANK if options[:feedback] == false
|
151
|
+
|
152
|
+
invalid = object.errors[name].to_sentence.presence if object.respond_to?(:errors)
|
153
|
+
invalid ||= options[:feedback][:invalid].delete(:text)
|
154
|
+
invalid ||= [("can't be blank" if options[:input][:required]), ('must be valid' if validated?(name))].compact.join(' and ')
|
155
|
+
invalid ||= 'is invalid'
|
156
|
+
|
157
|
+
valid = options[:feedback][:valid].delete(:text) || "Look's good!"
|
158
|
+
|
159
|
+
content_tag(:div, invalid, options[:feedback][:invalid]) +
|
160
|
+
content_tag(:div, valid, options[:feedback][:valid])
|
161
|
+
end
|
162
|
+
|
163
|
+
def has_error?(name = nil)
|
164
|
+
return false unless object.respond_to?(:errors)
|
165
|
+
name ? object.errors[name].present? : object.errors.present?
|
166
|
+
end
|
167
|
+
|
168
|
+
def required?(name)
|
169
|
+
return false unless object && name
|
170
|
+
|
171
|
+
obj = (object.class == Class) ? object : object.class
|
172
|
+
return false unless obj.respond_to?(:validators_on)
|
173
|
+
|
174
|
+
obj.validators_on(name).any? { |v| v.kind_of?(ActiveRecord::Validations::PresenceValidator) }
|
175
|
+
end
|
176
|
+
|
177
|
+
def validated?(name)
|
178
|
+
return false unless object && name
|
179
|
+
|
180
|
+
obj = (object.class == Class) ? object : object.class
|
181
|
+
return false unless obj.respond_to?(:validators_on)
|
182
|
+
|
183
|
+
obj.validators_on(name).any? { |v| !v.kind_of?(ActiveRecord::Validations::PresenceValidator) }
|
184
|
+
end
|
185
|
+
|
186
|
+
def input_group?
|
187
|
+
(options[:input_group][:append] || options[:input_group][:prepend]).present?
|
188
|
+
end
|
189
|
+
|
190
|
+
# Used for passwords and to not apply server side feedback
|
191
|
+
def reset_feedback?
|
192
|
+
return @reset_feedback unless @reset_feedback.nil?
|
193
|
+
@reset_feedback = options[:feedback].present? && (options[:feedback].delete(:reset) == true)
|
194
|
+
end
|
195
|
+
|
196
|
+
def value
|
197
|
+
object.public_send(name) if object.respond_to?(name)
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
# Here we split them into { wrapper: {}, label: {}, hint: {}, input: {} }
|
203
|
+
# And make sure to keep any additional options on the input: {}
|
204
|
+
def extract_options!(options, html_options: nil)
|
205
|
+
options.symbolize_keys!
|
206
|
+
html_options.symbolize_keys! if html_options
|
207
|
+
|
208
|
+
# effective_bootstrap specific options
|
209
|
+
layout = options.delete(:layout) # Symbol
|
210
|
+
wrapper = options.delete(:wrapper) # Hash
|
211
|
+
input_group = { append: options.delete(:append), prepend: options.delete(:prepend), input_group: options.delete(:input_group) }.compact
|
212
|
+
|
213
|
+
feedback = options.delete(:feedback) # Hash
|
214
|
+
label = options.delete(:label) # String or Hash
|
215
|
+
hint = options.delete(:hint) # String or Hash
|
216
|
+
|
217
|
+
input_html = options.delete(:input_html) || {} # Hash
|
218
|
+
input_js = options.delete(:input_js) || {} # Hash
|
219
|
+
|
220
|
+
# Every other option goes to input
|
221
|
+
@options = input = (html_options || options)
|
222
|
+
|
223
|
+
# Merge all the default objects, and intialize everything
|
224
|
+
wrapper = merge_defaults!(wrapper, wrapper_options)
|
225
|
+
input_group = merge_defaults!(input_group, input_group_options)
|
226
|
+
feedback = merge_defaults!(feedback, feedback_options)
|
227
|
+
|
228
|
+
label = merge_defaults!(label, label_options)
|
229
|
+
hint = merge_defaults!(hint, hint_options)
|
230
|
+
|
231
|
+
# Merge input_html: {}, defaults, and add all class: keys together
|
232
|
+
input.merge!(input_html.except(:class))
|
233
|
+
merge_defaults!(input, input_html_options.except(:class))
|
234
|
+
input[:class] = [input[:class], input_html[:class], input_html_options[:class]].compact.join(' ')
|
235
|
+
|
236
|
+
merge_defaults!(input_js, input_js_options)
|
237
|
+
|
238
|
+
if input_js.present?
|
239
|
+
merge_defaults!(input_js, input_js_options_method_name)
|
240
|
+
input['data-input-js-options'] = JSON.generate(input_js)
|
241
|
+
end
|
242
|
+
|
243
|
+
{ layout: layout, wrapper: wrapper, input_group: input_group, label: label, hint: hint, input: input, feedback: feedback }
|
244
|
+
end
|
245
|
+
|
246
|
+
def apply_input_options!
|
247
|
+
# Server side validation
|
248
|
+
if has_error?
|
249
|
+
if has_error?(name)
|
250
|
+
options[:input][:class] = [options[:input][:class], 'is-invalid'].compact.join(' ')
|
251
|
+
elsif reset_feedback?
|
252
|
+
# Nothing
|
253
|
+
else
|
254
|
+
options[:input][:class] = [options[:input][:class], 'is-valid'].compact.join(' ')
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
if required?(name) && (options[:input].delete(:required) != false)
|
259
|
+
options[:input][:required] = 'required'
|
260
|
+
end
|
261
|
+
|
262
|
+
if options[:input][:readonly]
|
263
|
+
options[:input][:readonly] = 'readonly'
|
264
|
+
options[:input][:class] = options[:input][:class].to_s.sub('form-control', 'form-control-plaintext')
|
265
|
+
end
|
266
|
+
|
267
|
+
if options[:hint] && options[:hint][:text] && options[:hint][:id]
|
268
|
+
options[:input].reverse_merge!('aria-describedby': options[:hint][:id])
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def merge_defaults!(obj, defaults)
|
273
|
+
defaults = {} if defaults.nil?
|
274
|
+
|
275
|
+
case obj
|
276
|
+
when false
|
277
|
+
false
|
278
|
+
when nil, true
|
279
|
+
defaults
|
280
|
+
when String
|
281
|
+
defaults.merge(text: obj)
|
282
|
+
when Hash
|
283
|
+
obj.reverse_merge!(defaults)
|
284
|
+
else
|
285
|
+
raise 'unexpected object'
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def layout
|
290
|
+
options[:layout] || @builder.layout
|
291
|
+
end
|
292
|
+
|
293
|
+
# https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/tags/base.rb#L120
|
294
|
+
# Not 100% sure best way to generate this
|
295
|
+
def tag_id(index = nil)
|
296
|
+
case
|
297
|
+
when @builder.object_name.empty?
|
298
|
+
sanitized_method_name.dup
|
299
|
+
when index
|
300
|
+
"#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
|
301
|
+
else
|
302
|
+
"#{sanitized_object_name}_#{sanitized_method_name}"
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def sanitized_object_name
|
307
|
+
@builder.object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
308
|
+
end
|
309
|
+
|
310
|
+
def sanitized_method_name
|
311
|
+
name.to_s.sub(/\?$/, "")
|
312
|
+
end
|
313
|
+
|
314
|
+
def input_js_options_method_name
|
315
|
+
{ method_name: "effective_#{self.class.name.split('::').last.underscore.chomp('_field')}" }
|
316
|
+
end
|
317
|
+
|
318
|
+
end
|
319
|
+
end
|