How can we get around these disadvantages of remote forms?

In order to be able to transfer to our website (including error messages for verification), we switched almost all of our forms to remote forms. Although this helps with the ability to translate error messages, we ran into other problems, for example:

  • If the user presses the submit button several times, the action is called several times. If we have a remote form for creating a new record in the database and provided that the user data is valid, each click will add a new object (with exactly the same content). Is there any way to make sure that such things cannot happen?

Somewhere I could read about best practices for remote forms? How can I deal with the problem of a few clicks? Does all forms translate into very large forms a very big mistake?

+4
source share
8 answers

The simplest solution would be to create a token for each form. Then, your create action can verify that it is not already in use and determine whether a record should be created.

This is how I will write this function. Please note that I have not actually tested this, but the concept should work.

1. Inside the new action, create a hash to identify the form request.

 def new @product = Product.new @form_token = session["form_token"] = SecureRandom.hex(15) end 

2. Add a hidden field to the form in which the form token is stored. This will be committed to the create action to ensure that the form has not previously been submitted.

 <%= hidden_field_tag :form_token, @form_token %> 

3. In the create action, you can verify that the form token matches between session and params variables. This will give you the opportunity to see if this is the first or second presentation.

 def create # delete the form token if it matches if session[:form_token] == params[:form_token] session[:form_token] = nil else # if it doesn't match then check if a record was created recently product = Product.where('created_at > ?', 3.minutes.ago).where(title: params[:product][:title]).last # if the product exists then show it # or just return because it is a remote form redirect_to product and return if product.present? end # normal create action here ... end 

Update: What I described above has a name, it is called a Synchronizer token (or Déjà vu). As described in this article , is the proper method of preventing double submission.

This strategy addresses the issue of duplication of forms. The synchronizer current is set in the user session and is included in each form returned to the client. When this form is submitted, the synchronizer token in the form is compared with the synchronization token in the session. Tokens must match when the form is first submitted. If the tokens do not match, then submitting the form may be prohibited and an error will be returned to the user. Token mismatch can occur when a user submits a form, then clicks the back button in the browser and tries to resubmit the same form.

On the other hand, if the two signs of the token coincide, we are sure that the control flow is exactly as expected. At this stage, the value of the token in the session changes to a new value and the form is accepted.

+1
source

There is a 3 rails option called :disable_with . Put this on the input elements to disable and re-mark them while the remote form is submitted. It adds a data disconnect tag to these inputs, and rails.js can select and link this functionality.

 submit_tag "Complete sale", :disable_with => "Please wait..." 

More information can be found here.

+3
source

Easy, and you can achieve this largely depending on your preferences:

  • Publish the form manually just by using an ajax request, and while you are waiting for an answer, disable / hide (or whatever you need) to make sure that the user cannot continue to make messages insane. Once you get a response from the server, again you can allow the user to post messages (first clear the form) or show something else or redirect to another page or again everything you need.

  • Use the link_to: remote => true link to submit the form and add a callback function to process the response, as well as disable / hide (or whatever you need) the form when submitting it

  • Add a js listener to the form to detect when it was submitted, and then disable / hide / regardless of the form

As you can see, there are many different ways to achieve what you need.

EDIT: If you need information on binding or processing a submit from js form, you will find very simple and interesting examples that can help you do what I suggested! jQuery Submit

+2
source

I have deleted forms widely, and in most cases I avoid them. But sometimes your layout or UX requires drop-down forms on the fly, without reloading or refreshing the full page.

So let me do this step by step.

1. Prevention of double message normal form

Even with a normal form, the user can double-click your button or click several times if the user does not receive a clear indication that the click has been registered and the action has begun.

There are many ways (e.g. javascript) to make this visible, but the easiest to use on rails:

 = f.button :submit, :disable_with => "Please wait..." 

This will disable the button after the first click, clearly indicating that the click has been registered and the action has been started.

2. Remote form processing

For a remote form, this is not so different, but the difference is likely: what happens next?

In a remote form, you have several options:

  • In case of error: you are updating the form with errors.
  • you leave the form open, allowing users to continue to enter data (I think this is your case?)
  • You are redirecting users to some place.

Let me handle these cases. Please understand that these three cases are completely standard in normal form. But not with a successful call.

2.1 In case of error

To set up the remote form correctly, you need to do a little more magic. Not much, but not much.

When using haml, you will have a view called edit.js.haml , which will look something like this:

 :plain $('#your-form-id').replaceWith('#{j render(:partial => '_form') }'); 

What this means: replace full haml with just the form. You will need to structure your views in order to do this job. It is not difficult, but simply necessary.

2.2. Form cleaning

You have two options: * re-render the form completely, as with errors. Just make sure that you visualize the form from a new element, and not just published! * just send the following javascript instead:

 $('#your-form-id').reset(); 

This will close the form and, as a rule, will cause any click to be useless (some client check may block the publication until some fields are filled).

2.3 Redirection

Since you are using a remote form, you cannot just redirect. This should happen on the client side, so this is a little more complicated.

Using haml again, it will be something like

 :plain document.location.href = '#{@redirect_uri}'; 

Conclusion

To prevent double (triple, fourfold, more) messages using remote forms you will have to

  • disable the button after the first click (use :disable_with )
  • clear form after successful submission (reset form or rendering with a new element)

Hope this helps.

+2
source

I'm sorry to say this, but it looks like you came up with a cure that is worse than the disease.

Why not use i18n for translations? It will probably be a "Rails way" ...

If you need to continue this route, you will need to start using Javascript. Remote forms are usually for small “AJAXy things,” such as voices or comments. Creating entire objects without leaving the page is useful when people may want to create many of them in a line (the exact problem you are trying to solve).

Once you start using AJAX, you will have to deal with the fact that you have to deal with some JS. This is client material and therefore not a Rail specialty.

If you feel that you have traveled so far along this road that you cannot go back, I would suggest that the AJAX answer should be at least reset. This could stop people creating the same thing more than once by mistake.

From a UI / UX perspective, it should also trigger a flash message, letting users know that they have successfully created the object.

So, if you can afford the time, git reset and start using i18n, if you cannot, make ajax reset link back and set the flash message.

Edit: It just occurred to me that you could even get AJAX to redirect the page for you (but you have to process the flash messages yourself). However, using the remote form, which then redirects through javascript, FUGLY ...

+1
source

I had similar problems using a popup when hovering over the mouse, and I did not want to queue multiple requests. To get more control, it might be easier for you to use javascript / coffeescript directly instead of UJS (like me).

As I decided, he assigned the Ajax call to the variable and checked if the variable was assigned. In my situation, I would interrupt the ajax call, but you probably want to return from the function and set the variable to null after the successful ajax call.

This coffeescript example is from my popup that uses "GET", but theoretically it should be the same for "POST" or "PUT".

eg.

 jQuery -> ajaxCall = null $("#popupContent").html "&nbsp;" $("#popup").live "mouseover", -> if ajaxCall return ajaxCall = $.ajax( type: "GET" url: "/whatever_url" beforeSend: -> $("#popupContent").prepend "<p class=\"loading-text\">Loading..please wait...</p>" success: (data) -> $("#popupContent").empty().append(data) complete: -> $"(.loading-text").remove() ajaxCall = null ) 

I left my mouseout and timer for short.

+1
source

You can try something similar for ajax requests.

Set true block variable for ajax requests

  before_filter :xhr_blocker def xhr_blocker if request.xhr? if session[:xhr_blocker] respond_to do |format| format.json, status: :unprocessable_entity end else session[:xhr_blocker] = true end end end 

Clear xhr_blocker variable with after filter method

  after_filter :clear_xhr_blocker def clear_xhr_blocker session[:xhr_blocker] = nil end 
+1
source

I would bind ajax: complete (or ajax: success and ajax: error) to redirect or update the DOM to remove / change the form as needed when the request is complete.

0
source

All Articles