bootstrap_form_legacy 4.0.1.dev

Sign up to get free protection for your applications and to get access to all the features.
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