Add formatted form builder

This commit is contained in:
benji 2015-02-09 11:58:43 +01:00
parent 39e96305b6
commit b260d90328
38 changed files with 217 additions and 314 deletions

View file

@ -1,6 +1,4 @@
class OrdersController < ApplicationController
include OrdersHelper
load_and_authorize_resource
def new

View file

@ -0,0 +1,172 @@
class FormattedFormBuilder < ActionView::Helpers::FormBuilder
include ActionView::Helpers::TextHelper
include ActionView::Context
include ActionView::Helpers::NumberHelper
FIELD_HELPERS = %w[text_field number_field file_field password_field]
delegate :content_tag, to: :@template
def initialize(object_name, object, template, options)
@inline_errors = true
super
end
FIELD_HELPERS.each do |method_name|
with_method_name = "#{method_name}_with_format"
without_method_name = "#{method_name}_without_format"
define_method(with_method_name) do |name, options = {}|
form_group_builder(name, options) do
send(without_method_name, name, options)
end
end
alias_method_chain method_name, :format
end
def price_field(name, options = {})
options[:min] ||= 0
options[:value] ||= number_with_precision(object[name], precision: 2)
form_group_builder(name, options) do
number_field_without_format(name, options)
end
end
def check_box_with_format(name, options = {}, checked_value = "1", unchecked_value = "0", &block)
options.symbolize_keys!
checkbox = check_box_without_format(name, options.except(:label), checked_value, unchecked_value)
label_content = block_given? ? capture(&block) : options[:label]
content_tag :div, class: control_wrapper_class do
checkbox + " " + label(name, label_content)
end
end
def counter(name, options = {})
form_group_builder(name, options) do
counter_button("btn-dec", "glyphicon-minus") +
text_field_without_format(name, options) +
counter_button("btn-inc", "glyphicon-plus")
end
end
alias_method_chain :check_box, :format
def collection_select_with_format(name, collection, value_method = :to_s, text_method = :titlecase, options = {}, html_options = {})
form_group_builder(name, options, html_options) do
collection_select_without_format(name, collection, value_method, text_method, options, html_options)
end
end
alias_method_chain :collection_select, :format
def submit_with_format(name = nil, options = {})
options[:class] = submit_class unless options[:class]
content_tag :div, class: submit_wrapper_class do
submit_without_format(name, options)
end
end
alias_method_chain :submit, :format
def error_messages
if object.errors.any?
content_tag :div, class: "panel panel-danger form-errors" do
content_tag(:div, class: "panel-heading") do
"#{pluralize(object.errors.count, "error")} prohibited this #{object.class.name.downcase} from being saved:"
end +
content_tag(:div, class: "panel-body") do
content_tag :ul do
object.errors.full_messages.map do |msg|
content_tag :li, msg
end.join.html_safe
end
end
end
end
end
private
def label_class
"control-label"
end
def control_class
"form-control"
end
def control_wrapper_class
"form-group"
end
def submit_class
"btn btn-primary"
end
def submit_wrapper_class
"actions"
end
def form_group(*args, &block)
options = args.extract_options!
name = args.first
options[:class] = [control_wrapper_class, options[:class]].compact.join(' ')
content_tag(:div, options.except(:label)) do
label = generate_label(name, options[:label]) if options[:label]
control = capture(&block).to_s
if label
label + control
else
control
end
end
end
def form_group_builder(method, options, html_options = nil)
options.symbolize_keys!
css_options = html_options || options
css_options[:class] = [control_class, css_options[:class]].compact.join(" ")
wrapper_class = css_options.delete(:wrapper_class)
wrapper_options = css_options.delete(:wrapper)
form_group_options = {
class: wrapper_class
}
if wrapper_options.is_a?(Hash)
form_group_options.merge!(wrapper_options)
end
unless options.delete(:skip_label)
form_group_options.reverse_merge!(label: {
text: options.delete(:label),
class: label_class
})
end
form_group(method, form_group_options) do
yield
end
end
def generate_label(name, options = {})
label("#{name}:", options[:text], options)
end
def counter_button(button, glyphicon)
content_tag :span, class: "input-group-btn" do
content_tag :button, class: "btn btn-default #{button}" do
content_tag :span, "", class: "glyphicon #{glyphicon}"
end
end
end
end

View file

@ -1,2 +0,0 @@
module AdminsHelper
end

View file

@ -13,49 +13,8 @@ module ApplicationHelper
"#{number_with_precision f, precision: 2}"
end
# Form helpers
def form_errors(object)
render partial: "form_errors", locals: {object: object}
def f_form_for(record, options = {}, &block)
options[:builder] = FormattedFormBuilder
form_for(record, options, &block)
end
def form_text_field(f, tag)
render partial: "form_text_field", locals: {f: f, tag: tag}
end
def form_password_field(f, tag)
render partial: "form_password_field", locals: {f: f, tag: tag}
end
def form_text_area(f, tag)
render partial: "form_text_area", locals: {f: f, tag: tag}
end
def form_fancy_text_area(f, tag)
render partial: "form_fancy_text_area", locals: {f: f, tag: tag}
end
def form_email_field(f, tag)
render partial: "form_email_field", locals: {f: f, tag: tag}
end
def form_date_field(f, tag, id, value)
render partial: "form_date_field", locals: {f: f, tag: tag, id: id, value: value}
end
def form_number_field(f, tag)
render partial: "form_number_field", locals: {f: f, tag: tag}
end
def form_collection_select(f, *args)
# This line enable passing optional arguments such as include_blank to the
# partial. If nothing is passed, an empty options hash is appended.
args << {} if args.length < 5
render partial: "form_collection_select", locals: {f: f, args: args}
end
def form_check_box(f, tag)
render partial: "form_check_box", locals: {f: f, tag: tag}
end
end

View file

@ -1,21 +0,0 @@
module DeviseHelper
def devise_error_messages!
return '' if resource.errors.empty?
messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
sentence = I18n.t('errors.messages.not_saved',
count: resource.errors.count,
resource: resource.class.model_name.human.downcase)
html = <<-HTML
<div class="alert alert-danger alert-dismissable">
<button type="button" class="close" data-dismiss="alert">&times;</button>
<strong>Error!</strong> #{sentence}
#{messages}
</div>
HTML
html.html_safe
end
end

View file

@ -1,2 +0,0 @@
module OrdersHelper
end

View file

@ -1,2 +0,0 @@
module ProductsHelper
end

View file

@ -1,2 +0,0 @@
module UsersHelper
end

View file

@ -1,5 +0,0 @@
<span class="input-group-btn">
<button class="btn btn-default btn-dec" type="button">
<span class="glyphicon glyphicon-minus"></span>
</button>
</span>

View file

@ -1,5 +0,0 @@
<span class="input-group-btn">
<button class="btn btn-default btn-inc" type="button">
<span class="glyphicon glyphicon-plus"></span>
</button>
</span>

View file

@ -1,14 +0,0 @@
<% if model.errors.any? %>
<div class="panel panel-danger form-errors">
<div class="panel-heading">
<%= pluralize(model.errors.count, "error") %> prohibited this <%= model.class.name.downcase %> from being saved:
</div>
<div class="panel-body">
<ul>
<% model.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
</div>
<% end %>

View file

@ -1,6 +0,0 @@
<div class="checkbox">
<label>
<%= f.label tag %>
</label>
<%= f.check_box tag %>
</div>

View file

@ -1,5 +0,0 @@
<%# To pass options to the collection_select, add {options} as the last argument %>
<div class="form-group">
<%= f.label args.first %>:
<%= f.collection_select *args, {class: 'form-control'} %>
</div>

View file

@ -1,7 +0,0 @@
<div class="form-group">
<%= f.label tag %>:
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
<%= f.text_field tag, class: 'form-control', id: id, value: value %>
</div>
</div>

View file

@ -1,4 +0,0 @@
<div class="form-group">
<%= f.label tag %>:
<%= f.email_field tag, class: 'form-control' %>
</div>

View file

@ -1,14 +0,0 @@
<% if object.errors.any? %>
<div class="panel panel-danger form-errors">
<div class="panel-heading">
<%= pluralize(object.errors.count, "error") %> prohibited this <%= object.class.name.downcase %> from being saved:
</div>
<div class="panel-body">
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
</div>
<% end %>

View file

@ -1,4 +0,0 @@
<div class="form-group">
<%= f.label tag %>:
<%= f.text_area tag, class: 'form-control ckeditor' %>
</div>

View file

@ -1,4 +0,0 @@
<div class="form-group">
<%= f.label tag %>:
<%= f.number_field tag, class: 'form-control', min: 0 %>
</div>

View file

@ -1,4 +0,0 @@
<div class="form-group">
<%= f.label tag %>:
<%= f.password_field tag, class: 'form-control' %>
</div>

View file

@ -1,4 +0,0 @@
<div class="form-group">
<%= f.label tag %>:
<%= f.text_area tag, class: 'form-control' %>
</div>

View file

@ -1,4 +0,0 @@
<div class="form-group">
<%= f.label tag %>:
<%= f.text_field tag, class: 'form-control' %>
</div>

View file

@ -1,16 +0,0 @@
<h2>Resend confirmation instructions</h2>
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
<%= devise_error_messages! %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true %>
</div>
<div class="actions">
<%= f.submit "Resend confirmation instructions" %>
</div>
<% end %>
<%= render "devise/shared/links" %>

View file

@ -1,5 +0,0 @@
<p>Welcome <%= @email %>!</p>
<p>You can confirm your account email through the link below:</p>
<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>

View file

@ -1,8 +0,0 @@
<p>Hello <%= @resource.email %>!</p>
<p>Someone has requested a link to change your password. You can do this through the link below.</p>
<p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>
<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>

View file

@ -1,7 +0,0 @@
<p>Hello <%= @resource.email %>!</p>
<p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
<p>Click the link below to unlock your account:</p>
<p><%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %></p>

View file

@ -1,16 +0,0 @@
<h2>Change your password</h2>
<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<%= f.hidden_field :reset_password_token %>
<div><%= f.label :password, "New password" %><br />
<%= f.password_field :password, :autofocus => true %></div>
<div><%= f.label :password_confirmation, "Confirm new password" %><br />
<%= f.password_field :password_confirmation %></div>
<div><%= f.submit "Change my password" %></div>
<% end %>
<%= render "devise/shared/links" %>

View file

@ -1,10 +0,0 @@
<h2>Forgot your password?</h2>
<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %>
<%= devise_error_messages! %>
<%= form_email_field f, :email %>
<%= f.submit "Send me reset password instructions", class: "btn btn-primary" %>
<% end %>
<%= render "devise/shared/links" %>

View file

@ -1,22 +1,17 @@
<h2>Edit <%= resource_name.to_s.humanize %></h2>
<%= render 'flash' %>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<%= f_form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
<%= f.error_messages %>
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
<% end %>
<%= f.password_field :password %>
<%= f.password_field :password_confirmation %>
<%= form_password_field f, :password %>
<%= form_password_field f, :password_confirmation %>
<%= f.password_field :current_password %>
<%= form_password_field f, :current_password %>
<%= f.label :avatar %>
<%= f.file_field :avatar %>
<%= f.submit "Update", class: 'btn btn-primary' %>
<%= f.submit "Update" %>
<% end %>
<% if current_user.dagschotel.present? %>

View file

@ -1,20 +1,18 @@
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<%= f_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= f.error_messages %>
<%= form_text_field f, :nickname %>
<%= form_text_field f, :name %>
<%= form_text_field f, :last_name %>
<%= f.text_field :nickname %>
<%= f.text_field :name %>
<%= f.text_field :last_name %>
<%= form_password_field f, :password %>
<%= form_password_field f, :password_confirmation %>
<%= f.password_field :password %>
<%= f.password_field :password_confirmation %>
<%= f.label :avatar %>
<%= f.file_field :avatar %>
<br />
<%= f.submit "Sign up", class: 'btn btn-primary' %>
<%= f.submit "Sign up" %>
<% end %>
<%= render "devise/shared/links" %>

View file

@ -2,16 +2,16 @@
<%= render partial: 'flash' %>
<div class="sign-in">
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
<%= f_form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
<%= form_text_field f, :nickname %>
<%= form_password_field f, :password %>
<%= f.text_field :nickname %>
<%= f.password_field :password %>
<% if devise_mapping.rememberable? %>
<%= form_check_box f, :remember_me %>
<%= f.check_box :remember_me %>
<% end %>
<%= f.submit "Sign in", class: "btn btn-primary" %>
<%= f.submit "Sign in" %>
<% end %>
</div>

View file

@ -1,25 +0,0 @@
<%- if controller_name != 'sessions' %>
<%= link_to "Sign in", new_session_path(resource_name) %><br />
<% end -%>
<% if devise_mapping.registerable? && controller_name != 'registrations' %>
<% link_to "Sign up", new_registration_path(resource_name) %>
<% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %><br />
<% end -%>
<% end -%>

View file

@ -1,16 +0,0 @@
<h2>Resend unlock instructions</h2>
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
<%= devise_error_messages! %>
<div class="field">
<%= f.label :nickname %><br />
<%= f.email_field :nickname, autofocus: true %>
</div>
<div class="actions">
<%= f.submit "Resend unlock instructions" %>
</div>
<% end %>
<%= render "devise/shared/links" %>

View file

@ -5,16 +5,11 @@
<div class="caption">
<h3><%= product.name %> - <%= content_tag :span, euro(product.price) %></h3>
<p>
<div class="input-group">
<%= render 'btn_dec' %>
<%= f.text_field :count, class: 'form-control row_counter', value: 0 %>
<%= render 'btn_inc' %>
<%= f.fields_for :product do |product| %>
<%= product.hidden_field :price_cents, class: :price %>
<%= product.hidden_field :stock, class: :stock %>
<% end %>
</div>
<%= f.counter :count, skip_label: true, wrapper_class: "input-group", class: "row_counter" %>
<%= f.fields_for :product do |product| %>
<%= product.hidden_field :price_cents, class: :price %>
<%= product.hidden_field :stock, class: :stock %>
<% end %>
</p>
</div>
</div>

View file

@ -2,7 +2,7 @@
<%= f.label :total_price %>
<div class="input-group">
<span class="input-group-addon">&euro;</span>
<%= f.number_field :total_price, step: :any, class: 'form-control input-lg' %>
<%= f.number_field :total_price, skip_label: true, step: :any, class: 'form-control input-lg' %>
<span class="input-group-btn">
<%= f.submit "Order!", class: "btn btn-primary input-lg" %>
</span>

View file

@ -1,8 +1,8 @@
<h3>Order for <%= @user.nickname %> (<%= euro(@user.balance) %>)</h3>
<div class="row">
<%= form_for @order, url: user_orders_path(@user) do |f| %>
<%= render 'application/errors', model: @order %>
<%= form_for @order, builder: FormattedFormBuilder, url: user_orders_path(@user) do |f| %>
<%= f.error_messages %>
<div class="col-md-12">
<%= f.fields_for :order_products do |op_field| %>

View file

@ -1,22 +1,15 @@
<div class="row">
<div class="col-md-6 col-md-offset-3 sign-in">
<%= form_for @product, html: { multipart: true } do |f| %>
<%= render 'application/errors', model: @product %>
<%= form_for @product, builder: FormattedFormBuilder, html: { multipart: true } do |f| %>
<%= f.error_messages %>
<%= form_text_field f, :name %>
<%= f.text_field :name %>
<%= f.price_field :price %>
<%= f.collection_select :category, Product.categories.keys %>
<%= f.number_field :stock %>
<%= f.file_field :avatar %>
<%= f.label :price %>
<%= f.number_field :price, value: number_with_precision(f.object.price, precision: 2), class: 'form-control form-field', placeholder: "0.00", step: :any %>
<%= form_collection_select f, :category, Product.categories.keys, :to_s, :titlecase %>
<%= f.label :stock %>
<%= f.number_field :stock, class: 'form-control form-field' %>
<%= f.label :avatar %>
<%= f.file_field :avatar , class: "form-field" %>
<%= f.submit class: "btn btn-primary" %>
<%= f.submit %>
<% end %>
</div>
</div>

View file

@ -0,0 +1,5 @@
class ChangeDefaultOrderProductCountToZero < ActiveRecord::Migration
def change
change_column_default :order_products, :count, 0
end
end

View file

@ -11,12 +11,12 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150204155216) do
ActiveRecord::Schema.define(version: 20150209101442) do
create_table "order_products", force: true do |t|
t.integer "order_id"
t.integer "product_id"
t.integer "count", default: 1
t.integer "count", default: 0
end
create_table "orders", force: true do |t|