Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make disable_with default in submit_tag #21135

Merged
merged 1 commit into from Aug 17, 2015
Merged

Conversation

@DropsOfSerenity
Copy link
Contributor

@DropsOfSerenity DropsOfSerenity commented Aug 5, 2015

I heard on the "Bike Shed" podcast that the idea of possibly making disable_with a default for submit_tag to prevent double submission by default, thought I would draft up a pull request for it and take a stab at it.

@DropsOfSerenity DropsOfSerenity force-pushed the DropsOfSerenity:master branch 2 times, most recently to cd06a6a Aug 5, 2015
#
def submit_tag(value = "Save changes", options = {})
options = options.stringify_keys

tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options)
tag :input, { "data" => {"disable_with": "Please wait..."}, "type" => "submit", "name" => "commit", "value" => value }.update(options)

This comment has been minimized.

@kaspth

kaspth Aug 5, 2015
Member

Add a space after { and before }, like the outer hash has.

This comment has been minimized.

@kaspth

kaspth Aug 5, 2015
Member

Also we've just stringified the keys above. So we need to use a string key here as well.

* Make `disable_with` the default behavior for submit tags. Disables the
button on submit to prevent double submits.
*Justin Schiff*

This comment has been minimized.

@kaspth

kaspth Aug 5, 2015
Member

New entries go at the top of the file.

This comment has been minimized.

@kaspth

kaspth Aug 6, 2015
Member

Can you move your changelog entry to the top of this file? 😄

@DropsOfSerenity DropsOfSerenity force-pushed the DropsOfSerenity:master branch from cd06a6a Aug 5, 2015
@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 6, 2015

@kaspth updated with your style changes sir :)

@rafaelfranca
rafaelfranca reviewed Aug 6, 2015
View changes
actionview/test/template/form_helper_test.rb Outdated
@@ -2226,7 +2226,7 @@ def test_submit_with_object_as_new_record_and_locale_strings
end

expected = whole_form('/posts', 'new_post', 'new_post') do
"<input name='commit' type='submit' value='Create Post' />"
"<input name='commit' data-disable-with='Please wait...' type='submit' value='Create Post' />"

This comment has been minimized.

@rafaelfranca

rafaelfranca Aug 6, 2015
Member

This indentation is wrong now

@rafaelfranca
rafaelfranca reviewed Aug 6, 2015
View changes
actionview/lib/action_view/helpers/form_tag_helper.rb Outdated
#
def submit_tag(value = "Save changes", options = {})
options = options.stringify_keys

tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options)
tag :input, { "data" => { "disable_with" => "Please wait..." }, "type" => "submit", "name" => "commit", "value" => value }.update(options)

This comment has been minimized.

@rafaelfranca

rafaelfranca Aug 6, 2015
Member

If someone pass 'data-disable-with' as options how would it behaves? Also data: { disable_with: 'foo' }

@rafaelfranca
Copy link
Member

@rafaelfranca rafaelfranca commented Aug 6, 2015

thank you for the work. It makes sense but I think we may have problems with with the hash update. Could you add tests that it is working with the inputs that I pointed?

@DropsOfSerenity DropsOfSerenity force-pushed the DropsOfSerenity:master branch to f3fc3fa Aug 6, 2015
@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 6, 2015

Ok yeah, now that I fixed the tests in form_tag_helper_test I got a failing test when any other data attributes were passed in. I fixed by using deep_merge! instead of update and wrote a test to cover the base case as well.

@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 6, 2015

ah I see that passing "data-disable-with" to the options has will also result in problems, writing a test and working on a solution.

@DropsOfSerenity DropsOfSerenity force-pushed the DropsOfSerenity:master branch from f3fc3fa to 4f8ca34 Aug 6, 2015
@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 6, 2015

Alright I've updated the code and written some tests to ensure that a user specified option is not overridden.


tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options)
tag :input, tag_options

This comment has been minimized.

@kaspth

kaspth Aug 6, 2015
Member

submit_tag has changed enough that I think we need a benchmark to make sure there isn't any serious performance regression.

This comment has been minimized.

@DropsOfSerenity

DropsOfSerenity Aug 6, 2015
Author Contributor

Sounds good, I appreciate the feedback, let me take some time tonight solidifying and benchmarking everything.

tag_options = { "type" => "submit", "name" => "commit", "value" => value }.update(options)

if not tag_options.include?("data-disable-with") || (tag_options["data"] && tag_options["data"]["disable_with"])
tag_options.deep_merge!({ "data" => { "disable_with": "Please wait..." } })

This comment has been minimized.

@kaspth

kaspth Aug 6, 2015
Member

This is still creating a symbol in the hash:

irb(main):001:0> options = { 'disable_with': 'hello' }
=> {:disable_with=>"hello"}
irb(main):002:0> options[:disable_with]
=> "hello"
options = options.deep_stringify_keys
tag_options = { "type" => "submit", "name" => "commit", "value" => value }.update(options)

if not tag_options.include?("data-disable-with") || (tag_options["data"] && tag_options["data"]["disable_with"])

This comment has been minimized.

@kaspth

kaspth Aug 6, 2015
Member

We prefer unless over if not.

You're checking for "data-disable-with" but your documentation above keeps referring to "data-disabled-with". Which is it? 😄

@DropsOfSerenity DropsOfSerenity force-pushed the DropsOfSerenity:master branch 2 times, most recently from a52680b to b370989 Aug 7, 2015
@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 7, 2015

@kaspth So I've done some benchmarking and applied a fix.

I found that deep_stringify_keys was causing a drop of roughly 30,000 iterations per second, a loss of 37% performance approx.

After changing it to expect the symbol key for the nested hash (which ruby does anyway so it's not necessary to deep stringify) the performance normalized back to 80k ip/s.

# Rails#master
Calculating -------------------------------------
      submit_tag old     7.988k i/100ms
-------------------------------------------------
      submit_tag old     83.042k (± 2.8%) i/s -    415.376k

# Rails with updated submit_tag code
Calculating -------------------------------------
      submit_tag new     7.711k i/100ms
-------------------------------------------------
      submit_tag new     80.468k (± 2.2%) i/s -    408.683k

I think the last thing i'm trying to wrap my head around is that nested hashes by default right now are being stringified only on the base level, and not on the nested level.

e.g.

irb(main):004:0> { "data" => { "disable_with": "Please wait..." } }.stringify_keys
=> {"data"=>{:disable_with=>"Please wait..."}}

irb(main):007:0> {}.update({ "data" => { "disable_with": "Please wait..." } }.stringify_keys)
=> {"data"=>{:disable_with=>"Please wait..."}}

This would mean this is happening for other data attributes being passed in already as well. Am i missing something here?

@bogdan
Copy link
Contributor

@bogdan bogdan commented Aug 7, 2015

  1. The default string can be longer then original label of submit. It will take extra space when change the label. It can be a problem in tough designs where there is no empty space.
  2. It needs to be I18n-ed by default.
@rafaelfranca
Copy link
Member

@rafaelfranca rafaelfranca commented Aug 7, 2015

it needs to be I18n-ed by default.

It doesn't. If you need i18n you can just explicitly set disable_with.

@rafaelfranca
Copy link
Member

@rafaelfranca rafaelfranca commented Aug 7, 2015

The default string can be longer then original label of submit. It will take extra space when change the label. It can be a problem in tough designs where there is no empty space.

If that is the case they can just override the default.

@rafaelfranca
Copy link
Member

@rafaelfranca rafaelfranca commented Aug 7, 2015

A problem that I think is: is it possible to remove disable_with now that we are setting a default?

@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 7, 2015

Maybe using "Saving..." instead of "Please wait..." would be a little more device friendly?

@sgrif
Copy link
Contributor

@sgrif sgrif commented Aug 7, 2015

Based on this implementation, no it doesn't look like there's a way to opt-out. Adding disable_with: false, "data-disable-with": false, or data: { "disable-with": false } should all opt-out of this behavior.

@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 7, 2015

Alright, I'll add this functionality :)

@bogdan
Copy link
Contributor

@bogdan bogdan commented Aug 7, 2015

No I18n makes the feature only available for en apps. Others will be forced to specify something everywhere in a project. Also I18n makes it possible to disable the feature and make upgrading easier.

@rafaelfranca
Copy link
Member

@rafaelfranca rafaelfranca commented Aug 7, 2015

We don't have I18n support right now and I want to keep it out. I18n by default will slow down a lot submit_tag for applications that only use english (I bet it is something like 80% of the apps)

@DropsOfSerenity DropsOfSerenity force-pushed the DropsOfSerenity:master branch from 6cfe688 to 7467fda Aug 8, 2015
@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 8, 2015

What do you guys think about adding a globally configurable action_view settings like so?

# Specify whether submit_tag should automatically disable on click
cattr_accessor :automatically_disable_submit_tag
@@automatically_disable_submit_tag = true

So then users that want to disable the feature for whatever reason can set the preference in application.rb

config.action_view.automatically_disable_submit_tag = false
@sgrif
Copy link
Contributor

@sgrif sgrif commented Aug 8, 2015

I'm 👍 for adding a config to opt out, if only to aid migration.

On Fri, Aug 7, 2015, 8:26 PM Justin notifications@github.com wrote:

What do you guys think about adding a globally configurable action_view
settings like so?

Specify whether submit_tag should automatically disable on click

cattr_accessor :automatically_disable_submit_tag@@automatically_disable_submit_tag = true

So then users that want to disable the feature for whatever reason can set
the preference in application.rb

config.action_view.automatically_disable_submit_tag = false


Reply to this email directly or view it on GitHub
#21135 (comment).

@DropsOfSerenity DropsOfSerenity force-pushed the DropsOfSerenity:master branch from 7467fda to 60302db Aug 8, 2015
@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 8, 2015

Added opt out settings + a test for it.

@DropsOfSerenity DropsOfSerenity force-pushed the DropsOfSerenity:master branch from 60302db Aug 8, 2015
@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 11, 2015

Do I need to do anything further for this?

@kaspth
kaspth reviewed Aug 11, 2015
View changes
actionview/lib/action_view/helpers/form_tag_helper.rb Outdated
# disabled version of the submit button when the form is submitted. This feature is
# provided by the unobtrusive JavaScript driver.
# provided by the unobtrusive JavaScript driver. To disable this feature for a single submit tag
# pass <tt>:data => { :disable_with => false }</tt>

This comment has been minimized.

@kaspth

kaspth Aug 11, 2015
Member

New hash syntax

@kaspth
kaspth reviewed Aug 11, 2015
View changes
actionview/lib/action_view/helpers/form_tag_helper.rb Outdated

tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options)
if ActionView::Base.automatically_disable_submit_tag
disable_with_text = value.clone

This comment has been minimized.

@kaspth

kaspth Aug 11, 2015
Member

Writing it this way means we're doing work we don't have to if users have passed disable with themselves.

@kaspth
kaspth reviewed Aug 11, 2015
View changes
actionview/lib/action_view/helpers/form_tag_helper.rb Outdated
if ActionView::Base.automatically_disable_submit_tag
disable_with_text = value.clone
disable_with_text = tag_options["data"][:disable_with] if tag_options["data"] && tag_options["data"].has_key?(:disable_with)
disable_with_text = tag_options["data-disable-with"] if tag_options.has_key?("data-disable-with")

This comment has been minimized.

@kaspth

kaspth Aug 11, 2015
Member

How about doing it this way? 😄

disable_with_text = tag_options["data-disable-with"]
disable_with_text ||= tag_options["data"][:disable_with] if tag_options["data"]
disable_with_text ||= value.clone

This comment has been minimized.

@DropsOfSerenity

DropsOfSerenity Aug 11, 2015
Author Contributor

We have to allow false to be passed in, ||= will ignore

This comment has been minimized.

@kaspth

kaspth Aug 11, 2015
Member

Then the variable name feels a little off 😉 And even so, there should be a simpler way to write the above.

This comment has been minimized.

@DropsOfSerenity

DropsOfSerenity Aug 11, 2015
Author Contributor

Ah yeah it's definitely a little unclear, I have tests wrapping it so i'll see what I can do to make it cleared logically and name-wise.

@kaspth
kaspth reviewed Aug 11, 2015
View changes
actionview/lib/action_view/helpers/form_tag_helper.rb Outdated
if disable_with_text
tag_options.deep_merge!({ "data" => { "disable_with" => disable_with_text } })
else
tag_options.extract!("data-disable-with")

This comment has been minimized.

@kaspth

kaspth Aug 11, 2015
Member

extract! creates and returns a new hash, which we don't care about at all here. It seems we're counting on delete being called and should just use that directly (we don't need to care about the key? checks, delete can handle missing keys).

@kaspth
kaspth reviewed Aug 11, 2015
View changes
actionview/lib/action_view/helpers/form_tag_helper.rb Outdated
tag_options.deep_merge!({ "data" => { "disable_with" => disable_with_text } })
else
tag_options.extract!("data-disable-with")
tag_options["data"].extract!(:disable_with) if tag_options["data"]

This comment has been minimized.

@kaspth

kaspth Aug 11, 2015
Member

Use delete instead of extract! here too.

@kaspth
kaspth reviewed Aug 11, 2015
View changes
actionview/lib/action_view/helpers/form_tag_helper.rb Outdated
@@ -412,36 +412,51 @@ def radio_button_tag(name, value, checked = false, options = {})
# * <tt>confirm: 'question?'</tt> - If present the unobtrusive JavaScript
# drivers will provide a prompt with the question specified. If the user accepts,
# the form is processed normally, otherwise no action is taken.
# * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a
# * <tt>:disable_with</tt> - Defaults to value attribute. Value of this parameter will be used as the value for a

This comment has been minimized.

@kaspth

kaspth Aug 11, 2015
Member

You haven't explained what :disable_with does yet, and then you say it defaults to value - how should users know what that means? 😄

Lets put it last in the description.

This comment has been minimized.

@DropsOfSerenity

DropsOfSerenity Aug 11, 2015
Author Contributor

Yeah good point :D

@kaspth
kaspth reviewed Aug 11, 2015
View changes
actionview/lib/action_view/helpers/form_tag_helper.rb Outdated
disable_with_text = tag_options["data-disable-with"] if tag_options.has_key?("data-disable-with")

if disable_with_text
tag_options.deep_merge!({ "data" => { "disable_with" => disable_with_text } })

This comment has been minimized.

@kaspth

kaspth Aug 11, 2015
Member

Remove the braces around the data hash.

@DropsOfSerenity DropsOfSerenity force-pushed the DropsOfSerenity:master branch Aug 11, 2015
Prevents double submission by making disable_with the default.

Default disable_with option will only be applied if user has not
specified her/his own disable_with option, whether that is in the
`data-disable-with` string form or the
`:data => { :disable_with => "Saving..." }` hash form. disable_with
will default to the value attribute.

A configuration option was added to opt out of this functionality if
the user so desires.
`config.action_view.automatically_disable_submit_tag = false`
@DropsOfSerenity DropsOfSerenity force-pushed the DropsOfSerenity:master branch to 3822a32 Aug 11, 2015
@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 11, 2015

Ok so in summary of recent changes:

  • Used new hash syntax in comment on how to disable.
  • Made an initial check to see if the user has disabled the feature on this specific submit tag before doing work.
  • Updated the logic using ||= now that the case of the user disabling the feature has been taken care of beforehand.
  • Used Hash#delete instead of Hash#extract!
  • Moved explanation of what disable_with defaults to at the end of the doc.
  • Removed unnecessary braces.
@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 14, 2015

Any other ideas guys?

sgrif added a commit that referenced this pull request Aug 17, 2015
make disable_with default in submit_tag
@sgrif sgrif merged commit 249a06d into rails:master Aug 17, 2015
1 check passed
1 check passed
continuous-integration/travis-ci/pr The Travis CI build passed
Details
@sgrif
Copy link
Contributor

@sgrif sgrif commented Aug 17, 2015

Can you add this issue number to the upgrade guide meta issue?

@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 19, 2015

@sgrif Where can I find that?

@sgrif
Copy link
Contributor

@sgrif sgrif commented Aug 19, 2015

Just did it. Was on my phone earlier and didn't have time to search.

@DropsOfSerenity
Copy link
Contributor Author

@DropsOfSerenity DropsOfSerenity commented Aug 19, 2015

Ah thankyou I kind of had no idea what you were talking about since this is my first time contributing to rails :P but thanks :D

tjgrathwell added a commit to railsbridge/bridge_troll that referenced this pull request Sep 22, 2015
Should be able to remove when upgrading to Rails 5 thanks to
rails/rails#21135
@claudiob
Copy link
Member

@claudiob claudiob commented Mar 18, 2016

It looks like this does not work with the button_to helper, where the generated code is something on the line of:

<form class="button_to" method="post" action="">
  <button type="submit"></button>
</form>

@DropsOfSerenity @sgrif I think we should make that happen… what do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

7 participants
You can’t perform that action at this time.