bootstrap_form_legacy 4.0.1.dev

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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.travis.yml +29 -0
  4. data/CHANGELOG.md +207 -0
  5. data/CONTRIBUTING.md +57 -0
  6. data/Dangerfile +54 -0
  7. data/Gemfile +21 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +647 -0
  10. data/Rakefile +27 -0
  11. data/app/assets/stylesheets/rails_bootstrap_forms.css +10 -0
  12. data/bootstrap_form.gemspec +26 -0
  13. data/demo/README.md +17 -0
  14. data/demo/Rakefile +6 -0
  15. data/demo/app/controllers/application_controller.rb +2 -0
  16. data/demo/app/controllers/bootstrap_controller.rb +16 -0
  17. data/demo/app/helpers/bootstrap_helper.rb +23 -0
  18. data/demo/app/models/address.rb +3 -0
  19. data/demo/app/models/application_record.rb +3 -0
  20. data/demo/app/models/faux_user.rb +9 -0
  21. data/demo/app/models/super_user.rb +2 -0
  22. data/demo/app/models/user.rb +9 -0
  23. data/demo/app/views/bootstrap/form.html.erb +53 -0
  24. data/demo/app/views/layouts/application.html.erb +65 -0
  25. data/demo/bin/bundle +3 -0
  26. data/demo/bin/rails +4 -0
  27. data/demo/bin/rake +4 -0
  28. data/demo/bin/setup +36 -0
  29. data/demo/bin/update +31 -0
  30. data/demo/bin/yarn +11 -0
  31. data/demo/config.ru +5 -0
  32. data/demo/config/application.rb +24 -0
  33. data/demo/config/boot.rb +5 -0
  34. data/demo/config/database.yml +21 -0
  35. data/demo/config/environment.rb +5 -0
  36. data/demo/config/environments/development.rb +60 -0
  37. data/demo/config/environments/test.rb +48 -0
  38. data/demo/config/initializers/application_controller_renderer.rb +8 -0
  39. data/demo/config/initializers/assets.rb +14 -0
  40. data/demo/config/initializers/backtrace_silencers.rb +7 -0
  41. data/demo/config/initializers/cookies_serializer.rb +5 -0
  42. data/demo/config/initializers/filter_parameter_logging.rb +4 -0
  43. data/demo/config/initializers/inflections.rb +16 -0
  44. data/demo/config/initializers/mime_types.rb +4 -0
  45. data/demo/config/initializers/wrap_parameters.rb +14 -0
  46. data/demo/config/locales/en.yml +33 -0
  47. data/demo/config/puma.rb +56 -0
  48. data/demo/config/routes.rb +5 -0
  49. data/demo/config/spring.rb +6 -0
  50. data/demo/config/storage.yml +35 -0
  51. data/demo/db/schema.rb +24 -0
  52. data/demo/log/.keep +0 -0
  53. data/demo/package.json +5 -0
  54. data/demo/public/favicon.ico +0 -0
  55. data/lib/bootstrap_form.rb +13 -0
  56. data/lib/bootstrap_form/aliasing.rb +35 -0
  57. data/lib/bootstrap_form/form_builder.rb +564 -0
  58. data/lib/bootstrap_form/helper.rb +59 -0
  59. data/lib/bootstrap_form/helpers/bootstrap.rb +99 -0
  60. data/lib/bootstrap_form/version.rb +3 -0
  61. metadata +120 -0
@@ -0,0 +1,5 @@
1
+ # Load the Rails application.
2
+ require_relative 'application'
3
+
4
+ # Initialize the Rails application.
5
+ Rails.application.initialize!
@@ -0,0 +1,60 @@
1
+ Rails.application.configure do
2
+ # Settings specified here will take precedence over those in config/application.rb.
3
+
4
+ # In the development environment your application's code is reloaded on
5
+ # every request. This slows down response time but is perfect for development
6
+ # since you don't have to restart the web server when you make code changes.
7
+ config.cache_classes = false
8
+
9
+ # Do not eager load code on boot.
10
+ config.eager_load = false
11
+
12
+ # Show full error reports.
13
+ config.consider_all_requests_local = true
14
+
15
+ # Enable/disable caching. By default caching is disabled.
16
+ # Run rails dev:cache to toggle caching.
17
+ if Rails.root.join('tmp/caching-dev.txt').exist?
18
+ config.action_controller.perform_caching = true
19
+
20
+ config.cache_store = :memory_store
21
+ config.public_file_server.headers = {
22
+ 'Cache-Control' => "public, max-age=#{2.days.to_i}"
23
+ }
24
+ else
25
+ config.action_controller.perform_caching = false
26
+
27
+ config.cache_store = :null_store
28
+ end
29
+
30
+ # Store uploaded files on the local file system (see config/storage.yml for options)
31
+ if config.respond_to?(:active_storage)
32
+ config.active_storage.service = :local
33
+ end
34
+
35
+ # Don't care if the mailer can't send.
36
+ config.action_mailer.raise_delivery_errors = false
37
+
38
+ config.action_mailer.perform_caching = false
39
+
40
+ # Print deprecation notices to the Rails logger.
41
+ config.active_support.deprecation = :log
42
+
43
+ # Raise an error on page load if there are pending migrations.
44
+ config.active_record.migration_error = :page_load
45
+
46
+ # Debug mode disables concatenation and preprocessing of assets.
47
+ # This option may cause significant delays in view rendering with a large
48
+ # number of complex assets.
49
+ config.assets.debug = true
50
+
51
+ # Suppress logger output for asset requests.
52
+ config.assets.quiet = true
53
+
54
+ # Raises error for missing translations
55
+ # config.action_view.raise_on_missing_translations = true
56
+
57
+ # Use an evented file watcher to asynchronously detect changes in source code,
58
+ # routes, locales, etc. This feature depends on the listen gem.
59
+ # config.file_watcher = ActiveSupport::EventedFileUpdateChecker
60
+ end
@@ -0,0 +1,48 @@
1
+ Rails.application.configure do
2
+ # Settings specified here will take precedence over those in config/application.rb.
3
+
4
+ # The test environment is used exclusively to run your application's
5
+ # test suite. You never need to work with it otherwise. Remember that
6
+ # your test database is "scratch space" for the test suite and is wiped
7
+ # and recreated between test runs. Don't rely on the data there!
8
+ config.cache_classes = true
9
+
10
+ # Do not eager load code on boot. This avoids loading your whole application
11
+ # just for the purpose of running a single test. If you are using a tool that
12
+ # preloads Rails for running tests, you may have to set it to true.
13
+ config.eager_load = false
14
+
15
+ # Configure public file server for tests with Cache-Control for performance.
16
+ config.public_file_server.enabled = true
17
+ config.public_file_server.headers = {
18
+ 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
19
+ }
20
+
21
+ # Show full error reports and disable caching.
22
+ config.consider_all_requests_local = true
23
+ config.action_controller.perform_caching = false
24
+
25
+ # Raise exceptions instead of rendering exception templates.
26
+ config.action_dispatch.show_exceptions = false
27
+
28
+ # Disable request forgery protection in test environment.
29
+ config.action_controller.allow_forgery_protection = false
30
+
31
+ # Store uploaded files on the local file system in a temporary directory
32
+ if config.respond_to?(:active_storage)
33
+ config.active_storage.service = :test
34
+ end
35
+
36
+ config.action_mailer.perform_caching = false
37
+
38
+ # Tell Action Mailer not to deliver emails to the real world.
39
+ # The :test delivery method accumulates sent emails in the
40
+ # ActionMailer::Base.deliveries array.
41
+ config.action_mailer.delivery_method = :test
42
+
43
+ # Print deprecation notices to the stderr.
44
+ config.active_support.deprecation = :stderr
45
+
46
+ # Raises error for missing translations
47
+ # config.action_view.raise_on_missing_translations = true
48
+ end
@@ -0,0 +1,8 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # ActiveSupport::Reloader.to_prepare do
4
+ # ApplicationController.renderer.defaults.merge!(
5
+ # http_host: 'example.org',
6
+ # https: false
7
+ # )
8
+ # end
@@ -0,0 +1,14 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Version of your assets, change this if you want to expire all your assets.
4
+ Rails.application.config.assets.version = '1.0'
5
+
6
+ # Add additional assets to the asset load path.
7
+ # Rails.application.config.assets.paths << Emoji.images_path
8
+ # Add Yarn node_modules folder to the asset load path.
9
+ Rails.application.config.assets.paths << Rails.root.join('node_modules')
10
+
11
+ # Precompile additional assets.
12
+ # application.js, application.css, and all non-JS/CSS in the app/assets
13
+ # folder are already added.
14
+ # Rails.application.config.assets.precompile += %w( admin.js admin.css )
@@ -0,0 +1,7 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4
+ # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5
+
6
+ # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7
+ # Rails.backtrace_cleaner.remove_silencers!
@@ -0,0 +1,5 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Specify a serializer for the signed and encrypted cookie jars.
4
+ # Valid options are :json, :marshal, and :hybrid.
5
+ Rails.application.config.action_dispatch.cookies_serializer = :json
@@ -0,0 +1,4 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Configure sensitive parameters which will be filtered from the log file.
4
+ Rails.application.config.filter_parameters += [:password]
@@ -0,0 +1,16 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new inflection rules using the following format. Inflections
4
+ # are locale specific, and you may define rules for as many different
5
+ # locales as you wish. All of these examples are active by default:
6
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
7
+ # inflect.plural /^(ox)$/i, '\1en'
8
+ # inflect.singular /^(ox)en/i, '\1'
9
+ # inflect.irregular 'person', 'people'
10
+ # inflect.uncountable %w( fish sheep )
11
+ # end
12
+
13
+ # These inflection rules are supported but not enabled by default:
14
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
15
+ # inflect.acronym 'RESTful'
16
+ # end
@@ -0,0 +1,4 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new mime types for use in respond_to blocks:
4
+ # Mime::Type.register "text/richtext", :rtf
@@ -0,0 +1,14 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # This file contains settings for ActionController::ParamsWrapper which
4
+ # is enabled by default.
5
+
6
+ # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7
+ ActiveSupport.on_load(:action_controller) do
8
+ wrap_parameters format: [:json]
9
+ end
10
+
11
+ # To enable root element in JSON for ActiveRecord objects.
12
+ # ActiveSupport.on_load(:active_record) do
13
+ # self.include_root_in_json = true
14
+ # end
@@ -0,0 +1,33 @@
1
+ # Files in the config/locales directory are used for internationalization
2
+ # and are automatically loaded by Rails. If you want to use locales other
3
+ # than English, add the necessary files in this directory.
4
+ #
5
+ # To use the locales, use `I18n.t`:
6
+ #
7
+ # I18n.t 'hello'
8
+ #
9
+ # In views, this is aliased to just `t`:
10
+ #
11
+ # <%= t('hello') %>
12
+ #
13
+ # To use a different locale, set it with `I18n.locale`:
14
+ #
15
+ # I18n.locale = :es
16
+ #
17
+ # This would use the information in config/locales/es.yml.
18
+ #
19
+ # The following keys must be escaped otherwise they will not be retrieved by
20
+ # the default I18n backend:
21
+ #
22
+ # true, false, on, off, yes, no
23
+ #
24
+ # Instead, surround them with single quotes.
25
+ #
26
+ # en:
27
+ # 'true': 'foo'
28
+ #
29
+ # To learn more, please read the Rails Internationalization guide
30
+ # available at http://guides.rubyonrails.org/i18n.html.
31
+
32
+ en:
33
+ hello: "Hello world"
@@ -0,0 +1,56 @@
1
+ # Puma can serve each request in a thread from an internal thread pool.
2
+ # The `threads` method setting takes two numbers: a minimum and maximum.
3
+ # Any libraries that use thread pools should be configured to match
4
+ # the maximum value specified for Puma. Default is set to 5 threads for minimum
5
+ # and maximum; this matches the default thread size of Active Record.
6
+ #
7
+ threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8
+ threads threads_count, threads_count
9
+
10
+ # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
11
+ #
12
+ port ENV.fetch("PORT") { 3000 }
13
+
14
+ # Specifies the `environment` that Puma will run in.
15
+ #
16
+ environment ENV.fetch("RAILS_ENV") { "development" }
17
+
18
+ # Specifies the number of `workers` to boot in clustered mode.
19
+ # Workers are forked webserver processes. If using threads and workers together
20
+ # the concurrency of the application would be max `threads` * `workers`.
21
+ # Workers do not work on JRuby or Windows (both of which do not support
22
+ # processes).
23
+ #
24
+ # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
25
+
26
+ # Use the `preload_app!` method when specifying a `workers` number.
27
+ # This directive tells Puma to first boot the application and load code
28
+ # before forking the application. This takes advantage of Copy On Write
29
+ # process behavior so workers use less memory. If you use this option
30
+ # you need to make sure to reconnect any threads in the `on_worker_boot`
31
+ # block.
32
+ #
33
+ # preload_app!
34
+
35
+ # If you are preloading your application and using Active Record, it's
36
+ # recommended that you close any connections to the database before workers
37
+ # are forked to prevent connection leakage.
38
+ #
39
+ # before_fork do
40
+ # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
41
+ # end
42
+
43
+ # The code in the `on_worker_boot` will be called if you are using
44
+ # clustered mode by specifying a number of `workers`. After each worker
45
+ # process is booted, this block will be run. If you are using the `preload_app!`
46
+ # option, you will want to use this block to reconnect to any threads
47
+ # or connections that may have been created at application boot, as Ruby
48
+ # cannot share connections between processes.
49
+ #
50
+ # on_worker_boot do
51
+ # ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
52
+ # end
53
+ #
54
+
55
+ # Allow puma to be restarted by `rails restart` command.
56
+ plugin :tmp_restart
@@ -0,0 +1,5 @@
1
+ Dummy::Application.routes.draw do
2
+ resources :users
3
+
4
+ root to: "bootstrap#form"
5
+ end
@@ -0,0 +1,6 @@
1
+ %w[
2
+ .ruby-version
3
+ .rbenv-vars
4
+ tmp/restart.txt
5
+ tmp/caching-dev.txt
6
+ ].each { |path| Spring.watch(path) }
@@ -0,0 +1,35 @@
1
+ test:
2
+ service: Disk
3
+ root: <%= Rails.root.join("tmp/storage") %>
4
+
5
+ local:
6
+ service: Disk
7
+ root: <%= Rails.root.join("storage") %>
8
+
9
+ # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10
+ # amazon:
11
+ # service: S3
12
+ # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13
+ # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14
+ # region: us-east-1
15
+ # bucket: your_own_bucket
16
+
17
+ # Remember not to checkin your GCS keyfile to a repository
18
+ # google:
19
+ # service: GCS
20
+ # project: your_project
21
+ # keyfile: <%= Rails.root.join("path/to/gcs.keyfile") %>
22
+ # bucket: your_own_bucket
23
+
24
+ # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25
+ # microsoft:
26
+ # service: AzureStorage
27
+ # path: your_azure_storage_path
28
+ # storage_account_name: your_account_name
29
+ # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
30
+ # container: your_container_name
31
+
32
+ # mirror:
33
+ # service: Mirror
34
+ # primary: local
35
+ # mirrors: [ amazon, google, microsoft ]
data/demo/db/schema.rb ADDED
@@ -0,0 +1,24 @@
1
+ ActiveRecord::Schema.define(version: 1) do
2
+
3
+ create_table :addresses, force: :cascade do |t|
4
+ t.integer :user_id
5
+ t.string :street
6
+ t.string :city
7
+ t.string :state
8
+ t.string :zip_code
9
+ t.timestamps
10
+ end
11
+
12
+ create_table :users, force: :cascade do |t|
13
+ t.string :email
14
+ t.string :password
15
+ t.text :comments
16
+ t.string :status
17
+ t.string :misc
18
+ t.text :preferences
19
+ t.boolean :terms, default: false
20
+ t.string :type
21
+ t.timestamps
22
+ end
23
+
24
+ end
data/demo/log/.keep ADDED
File without changes
data/demo/package.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "dummy",
3
+ "private": true,
4
+ "dependencies": {}
5
+ }
File without changes
@@ -0,0 +1,13 @@
1
+ require 'bootstrap_form/form_builder'
2
+ require 'bootstrap_form/helper'
3
+
4
+ module BootstrapForm
5
+ module Rails
6
+ class Engine < ::Rails::Engine
7
+ end
8
+ end
9
+ end
10
+
11
+ ActiveSupport.on_load(:action_view) do
12
+ include BootstrapForm::Helper
13
+ end
@@ -0,0 +1,35 @@
1
+ module BootstrapForm
2
+ # This module implements the old ActiveSupport alias_method_chain feature
3
+ # with a new name, and without the deprecation warnings. In ActiveSupport 5+,
4
+ # this style of patching was deprecated in favor of Module.prepend. But
5
+ # Module.prepend is not present in Ruby 1.9, which we would still like to
6
+ # support. So we continue to use of alias_method_chain, albeit with a
7
+ # different name to avoid collisions.
8
+ module Aliasing
9
+ # This code is copied and pasted from ActiveSupport, but with :bootstrap
10
+ # hardcoded as the feature name, and with the deprecation warning removed.
11
+ def bootstrap_method_alias(target)
12
+ feature = :bootstrap
13
+
14
+ # Strip out punctuation on predicates, bang or writer methods since
15
+ # e.g. target?_without_feature is not a valid method name.
16
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
17
+ yield(aliased_target, punctuation) if block_given?
18
+
19
+ with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
20
+ without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
21
+
22
+ alias_method without_method, target
23
+ alias_method target, with_method
24
+
25
+ case
26
+ when public_method_defined?(without_method)
27
+ public target
28
+ when protected_method_defined?(without_method)
29
+ protected target
30
+ when private_method_defined?(without_method)
31
+ private target
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,564 @@
1
+ require_relative 'aliasing'
2
+ require_relative 'helpers/bootstrap'
3
+
4
+ module BootstrapForm
5
+ class FormBuilder < ActionView::Helpers::FormBuilder
6
+ extend BootstrapForm::Aliasing
7
+ include BootstrapForm::Helpers::Bootstrap
8
+
9
+ attr_reader :layout, :label_col, :control_col, :has_error, :inline_errors, :label_errors
10
+
11
+ FIELD_HELPERS = %w{color_field date_field datetime_field datetime_local_field
12
+ email_field month_field number_field password_field phone_field
13
+ range_field search_field telephone_field text_area text_field time_field
14
+ url_field week_field}
15
+
16
+ DATE_SELECT_HELPERS = %w{date_select time_select datetime_select}
17
+
18
+ delegate :content_tag, :capture, :concat, to: :@template
19
+
20
+ def initialize(object_name, object, template, options)
21
+ @layout = options[:layout]
22
+ @label_col = options[:label_col] || default_label_col
23
+ @control_col = options[:control_col] || default_control_col
24
+ @label_errors = options[:label_errors] || false
25
+ @inline_errors = if options[:inline_errors].nil?
26
+ @label_errors != true
27
+ else
28
+ options[:inline_errors] != false
29
+ end
30
+
31
+ super
32
+ end
33
+
34
+ FIELD_HELPERS.each do |method_name|
35
+ with_method_name = "#{method_name}_with_bootstrap"
36
+ without_method_name = "#{method_name}_without_bootstrap"
37
+
38
+ define_method(with_method_name) do |name, options = {}|
39
+ form_group_builder(name, options) do
40
+ send(without_method_name, name, options)
41
+ end
42
+ end
43
+
44
+ bootstrap_method_alias method_name
45
+ end
46
+
47
+ DATE_SELECT_HELPERS.each do |method_name|
48
+ with_method_name = "#{method_name}_with_bootstrap"
49
+ without_method_name = "#{method_name}_without_bootstrap"
50
+
51
+ define_method(with_method_name) do |name, options = {}, html_options = {}|
52
+ prevent_prepend_and_append!(options)
53
+ form_group_builder(name, options, html_options) do
54
+ content_tag(:div, send(without_method_name, name, options, html_options), class: control_specific_class(method_name))
55
+ end
56
+ end
57
+
58
+ bootstrap_method_alias method_name
59
+ end
60
+
61
+ def file_field_with_bootstrap(name, options = {})
62
+ prevent_prepend_and_append!(options)
63
+ options = options.reverse_merge(control_class: "custom-file-input")
64
+ form_group_builder(name, options) do
65
+ content_tag(:div, class: "custom-file") do
66
+ placeholder = options.delete(:placeholder) || "Choose file"
67
+ placeholder_opts = { class: "custom-file-label" }
68
+ placeholder_opts[:for] = options[:id] if options[:id].present?
69
+
70
+ input = file_field_without_bootstrap(name, options)
71
+ placeholder_label = label(name, placeholder, placeholder_opts)
72
+ concat(input)
73
+ concat(placeholder_label)
74
+ end
75
+ end
76
+ end
77
+
78
+ bootstrap_method_alias :file_field
79
+
80
+ def select_with_bootstrap(method, choices = nil, options = {}, html_options = {}, &block)
81
+ form_group_builder(method, options, html_options) do
82
+ select_without_bootstrap(method, choices, options, html_options, &block)
83
+ end
84
+ end
85
+
86
+ bootstrap_method_alias :select
87
+
88
+ def collection_select_with_bootstrap(method, collection, value_method, text_method, options = {}, html_options = {})
89
+ prevent_prepend_and_append!(options)
90
+ form_group_builder(method, options, html_options) do
91
+ collection_select_without_bootstrap(method, collection, value_method, text_method, options, html_options)
92
+ end
93
+ end
94
+
95
+ bootstrap_method_alias :collection_select
96
+
97
+ def grouped_collection_select_with_bootstrap(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
98
+ prevent_prepend_and_append!(options)
99
+ form_group_builder(method, options, html_options) do
100
+ grouped_collection_select_without_bootstrap(method, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
101
+ end
102
+ end
103
+
104
+ bootstrap_method_alias :grouped_collection_select
105
+
106
+ def time_zone_select_with_bootstrap(method, priority_zones = nil, options = {}, html_options = {})
107
+ prevent_prepend_and_append!(options)
108
+ form_group_builder(method, options, html_options) do
109
+ time_zone_select_without_bootstrap(method, priority_zones, options, html_options)
110
+ end
111
+ end
112
+
113
+ bootstrap_method_alias :time_zone_select
114
+
115
+ def check_box_with_bootstrap(name, options = {}, checked_value = "1", unchecked_value = "0", &block)
116
+ prevent_prepend_and_append!(options)
117
+ options = options.symbolize_keys!
118
+ check_box_options = options.except(:label, :label_class, :help, :inline, :custom, :hide_label, :skip_label)
119
+ check_box_classes = [check_box_options[:class]]
120
+ check_box_classes << "position-static" if options[:skip_label] || options[:hide_label]
121
+ if options[:custom]
122
+ validation = nil
123
+ validation = "is-invalid" if has_error?(name)
124
+ check_box_options[:class] = (["custom-control-input", validation] + check_box_classes).compact.join(' ')
125
+ else
126
+ check_box_options[:class] = (["form-check-input"] + check_box_classes).compact.join(' ')
127
+ end
128
+
129
+ checkbox_html = check_box_without_bootstrap(name, check_box_options, checked_value, unchecked_value)
130
+ label_content = block_given? ? capture(&block) : options[:label]
131
+ label_description = label_content || (object && object.class.human_attribute_name(name)) || name.to_s.humanize
132
+
133
+ label_name = name
134
+ # label's `for` attribute needs to match checkbox tag's id,
135
+ # IE sanitized value, IE
136
+ # https://github.com/rails/rails/blob/5-0-stable/actionview/lib/action_view/helpers/tags/base.rb#L123-L125
137
+ if options[:multiple]
138
+ label_name =
139
+ "#{name}_#{checked_value.to_s.gsub(/\s/, "_").gsub(/[^-[[:word:]]]/, "").mb_chars.downcase.to_s}"
140
+ end
141
+
142
+ label_classes = [options[:label_class]]
143
+ label_classes << hide_class if options[:hide_label]
144
+
145
+ if options[:custom]
146
+ div_class = ["custom-control", "custom-checkbox"]
147
+ div_class.append("custom-control-inline") if layout_inline?(options[:inline])
148
+ label_class = label_classes.prepend("custom-control-label").compact.join(" ")
149
+ content_tag(:div, class: div_class.compact.join(" ")) do
150
+ if options[:skip_label]
151
+ checkbox_html
152
+ else
153
+ # TODO: Notice we don't seem to pass the ID into the custom control.
154
+ checkbox_html.concat(label(label_name, label_description, class: label_class))
155
+ end
156
+ end
157
+ else
158
+ wrapper_class = "form-check"
159
+ wrapper_class += " form-check-inline" if layout_inline?(options[:inline])
160
+ label_class = label_classes.prepend("form-check-label").compact.join(" ")
161
+ content_tag(:div, class: wrapper_class) do
162
+ if options[:skip_label]
163
+ checkbox_html
164
+ else
165
+ checkbox_html
166
+ .concat(label(label_name,
167
+ label_description,
168
+ { class: label_class }.merge(options[:id].present? ? { for: options[:id] } : {})))
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ bootstrap_method_alias :check_box
175
+
176
+ def radio_button_with_bootstrap(name, value, *args)
177
+ prevent_prepend_and_append!(options)
178
+ options = args.extract_options!.symbolize_keys!
179
+ radio_options = options.except(:label, :label_class, :help, :inline, :custom, :hide_label, :skip_label)
180
+ radio_classes = [options[:class]]
181
+ radio_classes << "position-static" if options[:skip_label] || options[:hide_label]
182
+ if options[:custom]
183
+ radio_options[:class] = radio_classes.prepend("custom-control-input").compact.join(' ')
184
+ else
185
+ radio_options[:class] = radio_classes.prepend("form-check-input").compact.join(' ')
186
+ end
187
+ args << radio_options
188
+ radio_html = radio_button_without_bootstrap(name, value, *args)
189
+
190
+ disabled_class = " disabled" if options[:disabled]
191
+ label_classes = [options[:label_class]]
192
+ label_classes << hide_class if options[:hide_label]
193
+
194
+ if options[:custom]
195
+ div_class = ["custom-control", "custom-radio"]
196
+ div_class.append("custom-control-inline") if layout_inline?(options[:inline])
197
+ label_class = label_classes.prepend("custom-control-label").compact.join(" ")
198
+ content_tag(:div, class: div_class.compact.join(" ")) do
199
+ if options[:skip_label]
200
+ radio_html
201
+ else
202
+ # TODO: Notice we don't seem to pass the ID into the custom control.
203
+ radio_html.concat(label(name, options[:label], value: value, class: label_class))
204
+ end
205
+ end
206
+ else
207
+ wrapper_class = "form-check"
208
+ wrapper_class += " form-check-inline" if layout_inline?(options[:inline])
209
+ label_class = label_classes.prepend("form-check-label").compact.join(" ")
210
+ content_tag(:div, class: "#{wrapper_class}#{disabled_class}") do
211
+ if options[:skip_label]
212
+ radio_html
213
+ else
214
+ radio_html
215
+ .concat(label(name, options[:label], { value: value, class: label_class }.merge(options[:id].present? ? { for: options[:id] } : {})))
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ bootstrap_method_alias :radio_button
222
+
223
+ def collection_check_boxes_with_bootstrap(*args)
224
+ prevent_prepend_and_append!(options)
225
+ html = inputs_collection(*args) do |name, value, options|
226
+ options[:multiple] = true
227
+ check_box(name, options, value, nil)
228
+ end
229
+ hidden_field(args.first,{value: "", multiple: true}).concat(html)
230
+ end
231
+
232
+ bootstrap_method_alias :collection_check_boxes
233
+
234
+ def collection_radio_buttons_with_bootstrap(*args)
235
+ prevent_prepend_and_append!(options)
236
+ inputs_collection(*args) do |name, value, options|
237
+ radio_button(name, value, options)
238
+ end
239
+ end
240
+
241
+ bootstrap_method_alias :collection_radio_buttons
242
+
243
+ def form_group(*args, &block)
244
+ options = args.extract_options!
245
+ name = args.first
246
+
247
+ options[:class] = ["form-group", options[:class]].compact.join(' ')
248
+ options[:class] << " row" if get_group_layout(options[:layout]) == :horizontal
249
+ options[:class] << " form-inline" if field_inline_override?(options[:layout])
250
+ options[:class] << " #{feedback_class}" if options[:icon]
251
+
252
+ content_tag(:div, options.except(:append, :id, :label, :help, :icon, :input_group_class, :label_col, :control_col, :layout, :prepend)) do
253
+ label = generate_label(options[:id], name, options[:label], options[:label_col], options[:layout]) if options[:label]
254
+ control = prepend_and_append_input(name, options, &block).to_s
255
+
256
+ help = options[:help]
257
+ help_text = generate_help(name, help).to_s
258
+
259
+ if get_group_layout(options[:layout]) == :horizontal
260
+ control_class = options[:control_col] || control_col
261
+ unless options[:label]
262
+ control_offset = offset_col(options[:label_col] || @label_col)
263
+ control_class = "#{control_class} #{control_offset}"
264
+ end
265
+ control = content_tag(:div, control + help_text, class: control_class)
266
+ concat(label).concat(control)
267
+ else
268
+ concat(label).concat(control).concat(help_text)
269
+ end
270
+ end
271
+ end
272
+
273
+ def fields_for_with_bootstrap(record_name, record_object = nil, fields_options = {}, &block)
274
+ if record_object.is_a?(Hash) && record_object.extractable_options?
275
+ fields_options = record_object
276
+ record_object = nil
277
+ end
278
+ fields_options[:layout] ||= options[:layout]
279
+ fields_options[:label_col] = fields_options[:label_col].present? ? "#{fields_options[:label_col]}" : options[:label_col]
280
+ fields_options[:control_col] ||= options[:control_col]
281
+ fields_options[:inline_errors] ||= options[:inline_errors]
282
+ fields_options[:label_errors] ||= options[:label_errors]
283
+ fields_for_without_bootstrap(record_name, record_object, fields_options, &block)
284
+ end
285
+
286
+ bootstrap_method_alias :fields_for
287
+
288
+ # the Rails `fields` method passes its options
289
+ # to the builder, so there is no need to write a `bootstrap_form` helper
290
+ # for the `fields` method.
291
+
292
+ private
293
+
294
+ def layout_default?(field_layout = nil)
295
+ [:default, nil].include? layout_in_effect(field_layout)
296
+ end
297
+
298
+ def layout_horizontal?(field_layout = nil)
299
+ layout_in_effect(field_layout) == :horizontal
300
+ end
301
+
302
+ def layout_inline?(field_layout = nil)
303
+ layout_in_effect(field_layout) == :inline
304
+ end
305
+
306
+ def field_inline_override?(field_layout = nil)
307
+ field_layout == :inline && layout != :inline
308
+ end
309
+
310
+ # true and false should only come from check_box and radio_button,
311
+ # and those don't have a :horizontal layout
312
+ def layout_in_effect(field_layout)
313
+ field_layout = :inline if field_layout == true
314
+ field_layout = :default if field_layout == false
315
+ field_layout || layout
316
+ end
317
+
318
+ def get_group_layout(group_layout)
319
+ group_layout || layout
320
+ end
321
+
322
+ def default_label_col
323
+ "col-sm-2"
324
+ end
325
+
326
+ def offset_col(label_col)
327
+ label_col.sub(/^col-(\w+)-(\d)$/, 'offset-\1-\2')
328
+ end
329
+
330
+ def default_control_col
331
+ "col-sm-10"
332
+ end
333
+
334
+ def hide_class
335
+ "sr-only" # still accessible for screen readers
336
+ end
337
+
338
+ def control_class
339
+ "form-control"
340
+ end
341
+
342
+ def feedback_class
343
+ "has-feedback"
344
+ end
345
+
346
+ def control_specific_class(method)
347
+ "rails-bootstrap-forms-#{method.gsub(/_/, "-")}"
348
+ end
349
+
350
+ def has_error?(name)
351
+ object.respond_to?(:errors) && !(name.nil? || object.errors[name].empty?)
352
+ end
353
+
354
+ def required_attribute?(obj, attribute)
355
+
356
+ return false unless obj and attribute
357
+
358
+ target = (obj.class == Class) ? obj : obj.class
359
+
360
+ target_validators = if target.respond_to? :validators_on
361
+ target.validators_on(attribute).map(&:class)
362
+ else
363
+ []
364
+ end
365
+
366
+ has_presence_validator = target_validators.include?(
367
+ ActiveModel::Validations::PresenceValidator)
368
+
369
+ if defined? ActiveRecord::Validations::PresenceValidator
370
+ has_presence_validator |= target_validators.include?(
371
+ ActiveRecord::Validations::PresenceValidator)
372
+ end
373
+
374
+ has_presence_validator
375
+ end
376
+
377
+ def form_group_builder(method, options, html_options = nil)
378
+ options.symbolize_keys!
379
+ html_options.symbolize_keys! if html_options
380
+
381
+ # Add control_class; allow it to be overridden by :control_class option
382
+ css_options = html_options || options
383
+ control_classes = css_options.delete(:control_class) { control_class }
384
+ css_options[:class] = [control_classes, css_options[:class]].compact.join(" ")
385
+ css_options[:class] << " is-invalid" if has_error?(method)
386
+
387
+ wrapper_class = css_options.delete(:wrapper_class)
388
+ wrapper_options = css_options.delete(:wrapper)
389
+ help = options.delete(:help)
390
+ icon = options.delete(:icon)
391
+ label_col = options.delete(:label_col)
392
+ control_col = options.delete(:control_col)
393
+ layout = get_group_layout(options.delete(:layout))
394
+ form_group_options = {
395
+ id: options[:id],
396
+ help: help,
397
+ icon: icon,
398
+ label_col: label_col,
399
+ control_col: control_col,
400
+ layout: layout,
401
+ class: wrapper_class
402
+ }
403
+
404
+ form_group_options[:append] = options.delete(:append) if options[:append]
405
+ form_group_options[:prepend] = options.delete(:prepend) if options[:prepend]
406
+ form_group_options[:input_group_class] = options.delete(:input_group_class) if options[:input_group_class]
407
+
408
+ if wrapper_options.is_a?(Hash)
409
+ form_group_options.merge!(wrapper_options)
410
+ end
411
+
412
+ unless options.delete(:skip_label)
413
+ if options[:label].is_a?(Hash)
414
+ label_text = options[:label].delete(:text)
415
+ label_class = options[:label].delete(:class)
416
+ options.delete(:label)
417
+ end
418
+ label_class ||= options.delete(:label_class)
419
+ label_class = hide_class if options.delete(:hide_label)
420
+
421
+ if options[:label].is_a?(String)
422
+ label_text ||= options.delete(:label)
423
+ end
424
+
425
+ form_group_options[:label] = {
426
+ text: label_text,
427
+ class: label_class,
428
+ required: options[:required],
429
+ skip_required: options.delete(:skip_required)
430
+ }.merge(css_options[:id].present? ? { for: css_options[:id] } : {})
431
+ end
432
+
433
+ form_group(method, form_group_options) do
434
+ yield
435
+ end
436
+ end
437
+
438
+ def convert_form_tag_options(method, options = {})
439
+ unless @options[:skip_default_ids]
440
+ options[:name] ||= method
441
+ options[:id] ||= method
442
+ end
443
+ options
444
+ end
445
+
446
+ def generate_label(id, name, options, custom_label_col, group_layout)
447
+ classes = [options[:class]]
448
+
449
+ if layout_horizontal?(group_layout)
450
+ classes << "col-form-label"
451
+ classes << (custom_label_col || label_col)
452
+ elsif layout_inline?(group_layout)
453
+ classes << "mr-sm-2"
454
+ end
455
+
456
+ unless options.delete(:skip_required)
457
+ classes << "required" if options.delete(:required) || required_attribute?(object, name)
458
+ end
459
+
460
+ options[:class] = classes.compact.join(" ").strip
461
+ options.delete(:class) if options[:class].empty?
462
+
463
+ if label_errors && has_error?(name)
464
+ error_messages = get_error_messages(name)
465
+ label_text = (options[:text] || object.class.human_attribute_name(name)).to_s.concat(" #{error_messages}")
466
+ options[:class] = [options[:class], "text-danger"].compact.join(" ")
467
+ label(name, label_text, options.except(:text))
468
+ else
469
+ label(name, options[:text], options.except(:text))
470
+ end
471
+ end
472
+
473
+ def has_inline_error?(name)
474
+ has_error?(name) && inline_errors
475
+ end
476
+
477
+ def generate_error(name)
478
+ if has_inline_error?(name)
479
+ help_text = get_error_messages(name)
480
+ help_klass = 'invalid-feedback'
481
+ help_tag = :div
482
+
483
+ content_tag(help_tag, help_text, class: help_klass)
484
+ end
485
+ end
486
+
487
+ def generate_help(name, help_text)
488
+ return if help_text == false || has_inline_error?(name)
489
+
490
+ help_klass ||= 'form-text text-muted'
491
+ help_text ||= get_help_text_by_i18n_key(name)
492
+ help_tag ||= :small
493
+
494
+ content_tag(help_tag, help_text, class: help_klass) if help_text.present?
495
+ end
496
+
497
+ def get_error_messages(name)
498
+ object.errors[name].join(", ")
499
+ end
500
+
501
+ def inputs_collection(name, collection, value, text, options = {}, &block)
502
+ options[:inline] ||= layout_inline?(options[:layout])
503
+ form_group_builder(name, options) do
504
+ inputs = ""
505
+
506
+ collection.each do |obj|
507
+ input_options = options.merge(label: text.respond_to?(:call) ? text.call(obj) : obj.send(text))
508
+
509
+ input_value = value.respond_to?(:call) ? value.call(obj) : obj.send(value)
510
+ if checked = input_options[:checked]
511
+ input_options[:checked] = checked == input_value ||
512
+ Array(checked).try(:include?, input_value) ||
513
+ checked == obj ||
514
+ Array(checked).try(:include?, obj)
515
+ end
516
+
517
+ if disabled = input_options[:disabled]
518
+ input_options[:disabled] = if disabled.is_a?(Array)
519
+ disabled.include?(input_value)
520
+ else
521
+ disabled
522
+ end
523
+ end
524
+
525
+ input_options.delete(:class)
526
+ inputs << block.call(name, input_value, input_options)
527
+ end
528
+
529
+ inputs.html_safe
530
+ end
531
+ end
532
+
533
+ def get_help_text_by_i18n_key(name)
534
+ if object
535
+
536
+ if object.class.respond_to?(:model_name)
537
+ partial_scope = object.class.model_name.name
538
+ else
539
+ partial_scope = object.class.name
540
+ end
541
+
542
+ underscored_scope = "activerecord.help.#{partial_scope.underscore}"
543
+ downcased_scope = "activerecord.help.#{partial_scope.downcase}"
544
+ # First check for a subkey :html, as it is also accepted by i18n, and the simple check for name would return an hash instead of a string (both with .presence returning true!)
545
+ help_text = I18n.t("#{name}.html", scope: underscored_scope, default: '').html_safe.presence
546
+ help_text ||= if text = I18n.t("#{name}.html", scope: downcased_scope, default: '').html_safe.presence
547
+ warn "I18n key '#{downcased_scope}.#{name}' is deprecated, use '#{underscored_scope}.#{name}' instead"
548
+ text
549
+ end
550
+ help_text ||= I18n.t(name, scope: underscored_scope, default: '').presence
551
+ help_text ||= if text = I18n.t(name, scope: downcased_scope, default: '').presence
552
+ warn "I18n key '#{downcased_scope}.#{name}' is deprecated, use '#{underscored_scope}.#{name}' instead"
553
+ text
554
+ end
555
+ help_text ||= I18n.t("#{name}_html", scope: underscored_scope, default: '').html_safe.presence
556
+ help_text ||= if text = I18n.t("#{name}_html", scope: downcased_scope, default: '').html_safe.presence
557
+ warn "I18n key '#{downcased_scope}.#{name}' is deprecated, use '#{underscored_scope}.#{name}' instead"
558
+ text
559
+ end
560
+ help_text
561
+ end
562
+ end
563
+ end
564
+ end