Skip to content
This repository has been archived by the owner on Oct 2, 2019. It is now read-only.

[Delivers #98157690] add client-side form validation #658

Merged
merged 18 commits into from
Oct 27, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ gem 'workflow'
group :test, :development do
gem 'bullet', require: false # use BULLET_ENABLED=true
gem 'database_cleaner'
gem 'konacha'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

niiiice

gem 'pry-byebug'
gem 'pry-rails'
gem 'rspec-rails'
Expand All @@ -61,6 +62,7 @@ end

group :development do
gem 'guard-rspec', require: false
gem 'guard-shell', require: false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like this is specific to a particular dev setup and not needed in the Gemfile.

dependencies of a developer utility should not be able to interfere with the dependencies of your app.

ddollar/foreman#437 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind if we have that discussion separately?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure!

gem 'mail_view'
gem 'railroady'
gem 'letter_opener'
Expand Down
13 changes: 13 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.9.1.1)
colorize (0.7.7)
columnize (0.9.0)
css_parser (1.3.7)
addressable
Expand Down Expand Up @@ -180,6 +181,9 @@ GEM
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
guard-shell (0.7.1)
guard (>= 2.0.0)
guard-compat (~> 1.0)
haml (4.0.7)
tilt
has_scope (0.6.0)
Expand Down Expand Up @@ -213,6 +217,13 @@ GEM
kaminari (0.16.3)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
konacha (3.7.0)
actionpack (>= 3.1, < 5)
capybara
colorize
railties (>= 3.1, < 5)
sprockets (>= 2, < 4)
tilt
launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.4.1)
Expand Down Expand Up @@ -466,11 +477,13 @@ DEPENDENCIES
font-awesome-sass
foreman
guard-rspec
guard-shell
haml
hashdiff
html_pipeline_rails
jquery-rails
jquery-turbolinks
konacha
letter_opener
letter_opener_web
mail_view
Expand Down
5 changes: 5 additions & 0 deletions Guardfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ guard :rspec, cmd: 'bin/rspec' do
# Capybara features specs
watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
end

guard :shell do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not using guard locally - I am fine with keeping this (and guard-shell) gem locally, but just an FYI that it isn't necessary for all (read: my) dev environments

watch(%r{^app/assets/javascripts/(.+)\.js(\.coffee)?$}) { |m| `bin/rake konacha:run SPEC=#{m[1]}_spec` }
watch(%r{^spec/javascripts/(.+)\.js(\.coffee)?$}) { |m| `bin/rake konacha:run SPEC=#{m[1]}` }
end
4 changes: 3 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ require File.expand_path('../config/application', __FILE__)
C2::Application.load_tasks

desc 'Run the test suite'
task default: "./bin/rspec"
# RSpec task gets included automatically
# http:https://stackoverflow.com/a/28886514/358804
task default: 'konacha:run'
30 changes: 30 additions & 0 deletions app/assets/javascripts/field_filter.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class @FieldFilter
constructor: (@$fieldOrWrappers) ->

isInput: ->
@$fieldOrWrappers.is(':input')

toggleVisibility: (showOrHide) ->
# https://www.paciellogroup.com/blog/2012/05/html5-accessibility-chops-hidden-and-aria-hidden/
@$fieldOrWrappers.attr('aria-hidden', !showOrHide)

toggleEnabled: (enableOrDisable) ->
@$fieldOrWrappers.attr('disabled', !enableOrDisable)

toggleChildInputs: (enableOrDisable) ->
@$fieldOrWrappers.find(':input').attr('disabled', !enableOrDisable)

toggle: (showOrHide) ->
@toggleVisibility(showOrHide)

# hidden inputs need to be disabled, so they aren't submitted with the form
if @isInput()
@toggleEnabled(showOrHide)
else
@toggleChildInputs(showOrHide)

show: ->
@toggle(true)

hide: ->
@toggle(false)
57 changes: 20 additions & 37 deletions app/assets/javascripts/filter.js.coffee
Original file line number Diff line number Diff line change
@@ -1,45 +1,28 @@
class Filter
constructor: ($root, @key) ->
@$ = (selector) -> $root.find(selector)
class @Filter
constructor: (@$root, @$control) ->
key = @$control.data('filter-control')
val = @$control.val()
@set = new FilterSet(@$root, key, val)

addInput: ($el) ->
$el.click () => @filter($el)
# Initial state
if $el.is(":checked")
@filter($el)
isSelected: ->
@$control.is(':checked')

addRadios: () ->
@$("input:radio[data-filter-control=#{ @key }]").each (idx, control) =>
@addInput($(control))
update: ->
if @isSelected()
@set.show()
else
@set.hide()

addChkBoxes: () ->
@$("input:checkbox[data-filter-control=#{ @key }]").each (idx, control) =>
@addInput($(control))

filter: ($el) ->
value = $el.val()
if !$el.is(":checked")
value = "!" + value
@$("[data-filter-key=#{ @key }]").each (idx, el) ->
hidden = el.getAttribute("data-filter-value") != value
el.setAttribute("aria-hidden", hidden.toString())

hideAll: () ->
@$("[data-filter-key=#{ @key }]").attr("aria-hidden", true)
enable: ->
@update()
@$control.change => @update()

@generateIn = ($scope) ->
filters = {}
$scope.find("[data-filter-control]").each (idx, el) ->
key = el.getAttribute('data-filter-control')
if !filters.hasOwnProperty(key)
filters[key] = new Filter($scope, key)
filters
$scope.find('[data-filter-control]').map (idx, control) ->
new Filter($scope, $(control))

$ ->
# @todo - better scope
$scope = $(document)
$scope = $(document.body)
filters = Filter.generateIn($scope)
for key, filter of filters
filter.hideAll()
filter.addRadios()
filter.addChkBoxes()
for filter in filters
filter.enable()
27 changes: 27 additions & 0 deletions app/assets/javascripts/filter_set.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class @FilterSet
constructor: (@$root, @key, @val) ->

$: (selector) ->
@$root.find(selector)

children: ->
@$("[data-filter-key=#{ @key }][data-filter-value=#{ @val }]")

cousins: ->
@$("[data-filter-key=#{ @key }][data-filter-value!=#{ @val }]")

showChildren: ->
filter = new FieldFilter(@children())
filter.show()

hideCousins: ->
filter = new FieldFilter(@cousins())
filter.hide()

show: ->
@showChildren()
@hideCousins()

hide: ->
filter = new FieldFilter(@children())
filter.hide()
3 changes: 1 addition & 2 deletions app/assets/javascripts/required_for_submit.js.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ class RequiredForSubmit
@$submit.prop 'disabled', !@$controller.val()

$ ->
# @todo - better scope
$scope = $(document)
$scope = $(document.body)
$scope.find("[data-disable-if-empty]").each (idx, el) ->
new RequiredForSubmit($scope, $(el))
4 changes: 4 additions & 0 deletions app/assets/javascripts/selectizer.js.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ class Selectizer
@$el = $(el)
@dataAttr = @$el.attr('data-attr') || 'default_field'

# `required` inputs don't work with Selectize
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think linking to the gh issue is great for documenation; summary of gh issue not necesssary

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary, but I think it's helpful in this case.

# https://github.com/brianreavis/selectize.js/issues/733
@$el.removeAttr('required')

isFreeForm: ->
@$el.is('input')

Expand Down
2 changes: 1 addition & 1 deletion app/helpers/ncr/work_orders_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def vendor_options(vendor = nil)
def expense_type_radio_button(form, expense_type)
content_tag :div, class: 'radio' do
form.label :expense_type, value: expense_type do
radio = form.radio_button(:expense_type, expense_type, 'data-filter-control' => 'expense-type')
radio = form.radio_button(:expense_type, expense_type, 'data-filter-control' => 'expense-type', required: true)
radio + expense_type
end
end
Expand Down
4 changes: 2 additions & 2 deletions app/views/ncr/work_orders/form.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
= simple_form_for @model_instance, html: { multipart: true } do |f|
= f.input :project_title
= f.input :description
= field_set_tag "Expense type", class: "required" do
= field_set_tag "Expense type", class: 'required' do
= expense_type_radio_button(f, 'BA60')
= expense_type_radio_button(f, 'BA61')
= f.input :emergency, disabled: @model_instance.persisted?, wrapper_html: { data: { filter_key: 'expense-type', filter_value: 'BA61' } }
Expand All @@ -21,7 +21,7 @@
= f.input :amount, as: :currency, label_html: { class: 'sr-only' }, input_html: { data: popover_data_attrs('ncr_amount') }
= f.input :not_to_exceed, as: :radio_buttons, collection: [['Exact', false], ['Not to exceed', true]], label: false
= f.input :direct_pay
= f.input :approving_official_email, collection: approver_options, include_blank: true, disabled: @model_instance.approver_email_frozen?, prompt: :translate, input_html: { class: 'js-selectize' }
= f.input :approving_official_email, collection: approver_options, include_blank: true, disabled: @model_instance.approver_email_frozen?, prompt: :translate, input_html: { class: 'js-selectize' }, required: true
- if @model_instance.new_record?
= render partial: 'attachments'
- else
Expand Down
6 changes: 6 additions & 0 deletions config/initializers/konacha.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
if defined?(Konacha)
Konacha.configure do |config|
require 'capybara/poltergeist'
config.driver = :poltergeist
end
end
3 changes: 1 addition & 2 deletions config/initializers/simple_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,5 @@
# The asterisk for required fields is added by CSS - make it simply be the label text
config.label_text = ->(label, _required, _explicit_label) { label }

# TODO enable, but make sure the filtered fields are only required when visible
config.browser_validations = false
config.browser_validations = true
end
2 changes: 1 addition & 1 deletion doc/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ have it in your PATH. This is used for javascript and interface testing.
### Running the entire suite once

```bash
rake
./bin/rake
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rake is so much easier to type! what about including konacha:run in default rake task so we can use it!?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rake is so much easier to type!

...but it doesn't run via Spring, which makes the startup significantly faster.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about including konacha:run in default rake task so we can use it!?

👍

```

### Running tests as corresponding files are changed
Expand Down
73 changes: 73 additions & 0 deletions spec/javascripts/field_filter_spec.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#= require jquery
#= require field_filter

describe 'FieldFilter', ->
describe '#show()', ->
it "enables inputs", ->
$input = $('<input disabled="disabled">')

filter = new FieldFilter($input)
filter.show()

expect($input.is(':disabled')).to.be.false

it "enables text areas", ->
$textarea = $('<textarea disabled="disabled">')

filter = new FieldFilter($textarea)
filter.show()

expect($textarea.is(':disabled')).to.be.false

it "enables nested inputs", ->
$content = $('<div><input disabled="disabled"></div>')

filter = new FieldFilter($content)
filter.show()

$input = $content.find('input')
expect($input.is(':disabled')).to.be.false

it "enables nested text areas", ->
$content = $('<div><textarea disabled="disabled"></div>')

filter = new FieldFilter($content)
filter.show()

$textarea = $content.find('textarea')
expect($textarea.is(':disabled')).to.be.false

describe '#hide()', ->
it "disables inputs", ->
$input = $('<input>')

filter = new FieldFilter($input)
filter.hide()

expect($input.is(':disabled')).to.be.true

it "disables text areas", ->
$textarea = $('<textarea>')

filter = new FieldFilter($textarea)
filter.hide()

expect($textarea.is(':disabled')).to.be.true

it "disables nested inputs", ->
$content = $('<div><input></div>')

filter = new FieldFilter($content)
filter.hide()

$input = $content.find('input')
expect($input.is(':disabled')).to.be.true

it "disables nested text areas", ->
$content = $('<div><textarea></div>')

filter = new FieldFilter($content)
filter.hide()

$textarea = $content.find('textarea')
expect($textarea.is(':disabled')).to.be.true
Loading