如何扩展核心Rails FormBuilder字段

时间:2014-03-04 21:31:47

标签: ruby-on-rails ruby forms ruby-on-rails-4 twitter-bootstrap-3

我正在使用带有Rails 4的Bootstrap 3,我想创建一个自定义FormBuilder来处理Bootstrap的一些独特的HTML语法。具体来说,我需要一个自定义帮助器,它将在表单字段周围创建form-group div包装器,因为Bootstrap applies error state to this wrapper, and not the field itself ...

<div class="form-group has-error">
  <label class="col-md-3 control-label" for="user_email">Email</label>
  <div class='col-md-9'>
    <input class="form-control required-input" id="user_email" name="user[email]" placeholder="peter@example.com" type="email" value="someone@example.com" />
  </div>
</div>

请注意外部div中的额外课程has-error ...

无论如何,我写了那个助手,它很棒!

def form_group(method, options={})
  class_def = 'form-group'
  class_def << ' has-error' unless @object.errors[method].blank?
  class_def << " #{options[:class]}" if options[:class].present?
  options[:class] = class_def
  @template.content_tag(:div, options) { yield }
end

# Here's a HAML sample...

= f.form_group :email do
  = f.label :email, nil, class: 'col-md-3 control-label'
  .col-md-9
    = f.email_field :email, class: 'form-control required-input', placeholder: t('sample.email')

现在我想利用Bootstrap的form help text来显示错误消息。这需要我扩展Rails本机助手(例如上面示例中的text_field),然后在f.form_group的块内调用它们。

解决方案似乎足够简单:调用父级,并将我的span块追加到最后......

def text_field(method, options={})
  @template.text_field(method, options)
  if !@object.errors[method].blank?
    @template.content_tag(:span, @object.errors.full_messages_for(method), class: 'help-block')
  end
end

只有它不会输出任何HTML,div只会显示为空。我尝试了一堆diff语法方法:

  • super vs text_field vs text_field_tag
  • concat - 结果 - @template.concat(@template.content_tag( [...] ))
  • 动态变量,例如def text_field(method, *args)然后options = args.extract_options!.symbolize_keys!

我只会遇到奇怪的语法错误或空div。在某些情况下,会出现input字段,但帮助文字span不会,或反之亦然。

我确定我搞砸了一些简单的东西,我只是没有看到它。

1 个答案:

答案 0 :(得分:9)

花了几天时间,但我最终偶然发现了正确的语法。希望它能拯救别人的理智!

Ruby的return自动化,结合Rails有时复杂的范围,让我有点不可思议。具体来说,@template.text_field绘制内容,但必须由辅助方法返回才能显示在调用块内。但是,我们必须返回两个调用的结果...

def text_field(method, options={})
  field_errors = object.errors[method].join(', ') if !@object.errors[method].blank?
  content = super
  content << (@template.content_tag(:span, @object.errors.full_messages_for(method), class: 'help-block') if field_errors)
  return content
end

我们必须返回父方法(通过super)加上我们的自定义@template.content_tag(:span,注入的结果。我们可以使用Ruby的加号+运算符来缩短它,这会将返回结果连接起来。

def text_field(method, options={})
  field_errors = object.errors[method].join(', ') if !@object.errors[method].blank?
  super + (@template.content_tag(:span, @object.errors.full_messages_for(method), class: 'help-block') if field_errors)
end

注意:表单是使用ActiveModel对象启动的,这就是我们有权访问@object的原因。实施form_for而不将其与模型相关联则需要您扩展text_field_tag

这是我完成的自定义FormBuilder

class BootstrapFormBuilder < ActionView::Helpers::FormBuilder

  def form_group(method, options={})
    class_def = 'form-group'
    class_def << ' has-error' unless @object.errors[method].blank?
    class_def << " #{options[:class]}" if options[:class].present?
    options[:class] = class_def
    @template.content_tag(:div, options) { yield }
  end

  def text_field(method, options={})
    field_errors = object.errors[method].join(', ') if !@object.errors[method].blank?
    super + (@template.content_tag(:span, @object.errors.full_messages_for(method), class: 'help-block') if field_errors)
  end

end

别忘了告诉form_for

form_for(:user, :builder => BootstrapFormBuilder [, ...])

编辑:这里有许多有用的链接,帮助我走上了启蒙的道路。 Link-juice对作者赞不绝口!