module Lipstick module Helpers module FormHelper include ActionView::Helpers::FormTagHelper def field_block(html_opts = {}, &block) if flag_enabled?(:inside_inline_form_tag) html_opts[:class] = "#{html_opts[:class]} inline".strip end html_opts[:class] = "#{html_opts[:class]} field".strip content_tag('div', html_opts, &block) end alias_method :orig_form_tag, :form_tag def form_tag(form_opts, html_opts = {}, &block) html_opts[:class] = "#{html_opts[:class]} ui form".strip orig_form_tag(form_opts, html_opts, &block) end def inline_form_tag(form_opts, html_opts = {}, &block) with_flag_enabled(:inside_inline_form_tag) do html_opts[:class] = "#{html_opts[:class]} ui form inline-form".strip orig_form_tag(form_opts, html_opts, &block) end end def hidden_fields(&block) content_tag('div', style: 'display: none;', &block) end def text_field_tag(*args) content_tag('div', class: 'ui input') { super } end def field_help_text(text) icon_tag('field-help-text blue help', 'data-content' => text) end def button_tag(content_or_options = nil, options = nil, &block) add_class = ->(m) { m.dup.merge(class: "#{m[:class]} ui button".strip) } if content_or_options.is_a?(Hash) super(add_class.call(content_or_options), &block) else super(content_or_options, add_class.call(options || {}), &block) end end def delete_button_tag(url, text: true, **opts) css_class = 'ui tiny red icon delete button floating dropdown' content_tag('div', class: "#{css_class} #{opts[:class]}".strip) do concat(icon_tag('trash')) action = text && text.is_a?(String) ? text : 'Delete' concat(action) if text confirm = button_link_to(url, method: :delete, class: 'small') do "Confirm #{action}" end concat(content_tag('div', confirm, class: 'menu')) end end def error_messages_tag content_tag('div', '', class: 'ui error message') end def radio_button_tag(name, value, checked = false, options = {}, &block) field_block do content_tag('div', class: 'ui radio checkbox') do concat(super) concat(capture(&block)) end end end def form_for(obj, opts = {}, &block) opts[:builder] = SemanticFormBuilder opts[:html] ||= {} opts[:html][:class] = "#{opts[:html][:class]} ui form".strip super(obj, opts, &block) end def radio_button_block(&block) content_tag('div', class: 'grouped fields', &block) end # Generates the wrapping code for validating a form. The selector is # passed to jQuery, and must uniquely select the form being validated. # `sym` is the object name when using a `form_for` helper to generate the # form. # # e.g. # <%= validate_form('#new-test-object', :test_object) do -%> # <%= validate_field(:name, ...) %> Validate the test_object[name] field # <%- end -%> def validate_form(selector, sym = nil, &block) content_tag('script', type: 'text/javascript') do jquery_callback(validation_body(selector, sym, &block)).html_safe end end # Generates a validator for a field. `opts` is a Hash containing the # `type` and `prompt` for each desired validation per Semantic UI: # http://semantic-ui.com/modules/form.html # # e.g. # <%= validate_field(:email, email: 'Enter a valid email address') %> # <%= validate_field(:password, :'length[6]' => '6 characters minimum') %> # <%= validate_field(:multiple, empty: 'Not empty', url: 'Must be URL') %> def validate_field(name, opts) format('validations[%{name}] = ' \ '(function(v) { %{inner} })' \ '($.extend({rules: []}, validations[%{name}]));', name: name.to_json, inner: validation_for_field(name, opts)).html_safe end # Automatically generates validators for fields based on certain supported # validators from ActiveModel::Validations. The model must include the # Lipstick::AutoValidation module. # # class MyModel < ActiveRecord::Base # include Lipstick::AutoValidation # # validates :name, presence: true # validates :description, length: 1..255 # end # # <%= auto_validate(@object, :name, :description) %> def auto_validate(obj, *fields) unless obj.class.respond_to?(:lipstick_auto_validators) fail("#{obj.class.name} does not include Lipstick::AutoValidation") end validators = obj.class.lipstick_auto_validators capture do validators.slice(*fields).each do |name, opts| concat validate_field(name, opts) end end end private def validation_body(selector, sym, &block) 'var validations = {};' + validation_name_mapping_function(sym) + capture(&block) + validation_install(selector) end # When we're using form_for, we need to map ObjectType#name to a field # named like: 'object_type[name]' # Otherwise, we just use the name directly. def validation_name_mapping_function(sym) if sym.nil? 'var map_name = function(n) { return n; };' else 'var map_name = function(n) { ' \ "return #{sym.to_json} + '[' + n + ']';" \ '};' end end def validation_install(selector) format('$(%s).form(validations, { keyboardShortcuts: false });', selector.to_json) end def jquery_callback(body) format('jQuery(function($){%s});', body) end def validation_for_field(name, opts) rules = opts.map { |t, m| { type: t, prompt: m } } "v.rules = v.rules.concat(#{rules.to_json});" \ "v.identifier = map_name(#{name.to_json});" \ 'return v;' end def with_flag_enabled(flag) old = Thread.current[flag] begin Thread.current[flag] = true yield ensure Thread.current[flag] = old end end def flag_enabled?(flag) Thread.current[flag] end end end end