Created
June 5, 2019 15:56
-
-
Save gkemmey/36043b3322b30c55e06dede2bf496591 to your computer and use it in GitHub Desktop.
Rails remote form error handling
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ------------------------------------------------------------------------------------------------- | |
| # monkey patches ActionView::Helpers::Tags module and adds a wrapper tag. additionally, | |
| # patches ActionView::Helpers::FormBuilder to add a `wrapper` method. this will allow you to | |
| # use the following in your forms: | |
| # | |
| # <%= f.wrapper :email, :div, class: "control" do %> | |
| # <%= f.email_field :email, placeholder: "you@example.com", class: "input is-danger" %> | |
| # <% end %> | |
| # | |
| # we do it this way, so the wrapper is aware of the model object it's building, and thus the | |
| # wrapped html is passed to the `field_error_proc` so we can customize it if the field has an error | |
| # | |
| # this is heavily synthesized from the ActionView::Helpers::Tags::Label code that's invoked | |
| # when you use `f.label :email ...` in a form here: | |
| # https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/tags/label.rb | |
| # | |
| # other relevant files: | |
| # https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/form_helper.rb -- where | |
| # the ActionView::Helpers::FormBuilder class and the ActionView::Helpers::FormHelper module are | |
| # defined, and of course the `FormBuilder#label` method | |
| # | |
| module ActionView | |
| module Helpers | |
| module Tags | |
| class Wrapper < Base | |
| def initialize(object_name, method_name, template_object, tag, options) | |
| @tag = tag | |
| super(object_name, method_name, template_object, options) | |
| end | |
| def render(&block) | |
| if block_given? | |
| content_tag @tag, @template_object.capture(&block), @options | |
| else | |
| content_tag @tag, nil, @options | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end | |
| class ActionView::Helpers::FormBuilder | |
| def wrapper(method, tag = :div, options = {}, &block) | |
| tag, options = :div, tag if tag.is_a?(Hash) | |
| ActionView::Helpers::Tags::Wrapper.new(@object_name, method, @template, tag, objectify_options(options)).render(&block) | |
| end | |
| end | |
| # ------------------------------------------------------------------------------------------------- | |
| # inspired by: https://rubyplus.com/articles/3401-Customize-Field-Error-in-Rails-5 | |
| ActionView::Base.field_error_proc = Proc.new do |html_tag, instance| | |
| result = html_tag | |
| if instance.is_a?(ActionView::Helpers::Tags::Wrapper) # ok, we're dealing with our custom helper 💪 | |
| _html_tag = Nokogiri::HTML::DocumentFragment.parse(html_tag) | |
| wrapper = _html_tag.elements.first | |
| wrapper['class'] = [wrapper['class'], "is-danger"].compact.join(" ") | |
| Array(instance.error_message).map(&:humanize).uniq.each do |error| | |
| _html_tag.add_child(%(<p class="help is-danger">#{error}</p>)) | |
| end | |
| result = _html_tag.to_s | |
| else | |
| form_fields = ['textarea', 'input', 'select'] | |
| elements = Nokogiri::HTML::DocumentFragment.parse(html_tag).css((['label'] + form_fields).join(', ')) | |
| elements.each do |e| | |
| if e.node_name == 'label' | |
| # not doing anything, but wanted to capture how to do something for futures 🔮 | |
| elsif form_fields.include?(e.node_name) | |
| _html_tag = Nokogiri::HTML::DocumentFragment.parse(html_tag) | |
| form_field = _html_tag.elements.first | |
| form_field['class'] = [form_field['class'], "is-danger"].compact.join(" ") | |
| result = _html_tag.to_s | |
| end | |
| end | |
| end | |
| result.html_safe | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // stolen from: https://github.com/turbolinks/turbolinks-rails/pull/20#issuecomment-332909770 | |
| // more relavant conversation conversation here: https://github.com/turbolinks/turbolinks/issues/85 | |
| (function(document, window, Turbolinks) { | |
| document.addEventListener("turbolinks:load", function() { | |
| for (let element of document.querySelectorAll("form[data-remote='true']")) { | |
| element.addEventListener("ajax:send", function(event) { | |
| document.activeElement.blur() | |
| Turbolinks.dispatch("turbolinks:click", event) | |
| }); | |
| // the original script mentioned above, used 'ajax:complete' and looked for a "Location" | |
| // header to decide, "hey i need to redirect". if there wasn't a "Location" header, it | |
| // assumed we must want to re-render with errors so do that. however, that may not always | |
| // be the case for instance if you're responding with server-rendered javascript. | |
| // | |
| // anyway, thinking if you want the page re-rendered you need to indicate the form submission | |
| // failed in the controller with something like `render :new, status: 422`. if the status | |
| // is 200, we'll assume you had your own plans and opt not to do anything here. | |
| // | |
| // idk, if the "Location" header checking is necessary, now, but leaving for now. | |
| element.addEventListener("ajax:error", function(event) { | |
| let xhr = event.detail.filter((object) => ( object instanceof XMLHttpRequest ))[0] | |
| // if the server responds with javascript, we wanna run it, so do nothing and let | |
| // rails-ujs pick it up | |
| if ((xhr.getResponseHeader("Content-Type") || '').match(/javascript/)) { | |
| return; | |
| } | |
| if (!xhr.getResponseHeader("Location")) { | |
| current_snapshot = Turbolinks.Snapshot.fromHTMLElement(document) | |
| new_snapshot = Turbolinks.Snapshot.wrap(xhr.responseText) | |
| Turbolinks.controller.currentVisit = Turbolinks.controller.createVisit(window.location.href, "replace", {}) | |
| Turbolinks.SnapshotRenderer.render( | |
| Turbolinks.controller, | |
| function() { | |
| Turbolinks.dispatch("turbolinks:load") | |
| // you may want to prioitize scrolling to a flash message vs focussing the first error | |
| // stolen from: https://github.com/turbolinks/turbolinks/issues/85#issuecomment-297528382 | |
| // if ($("#flash_message").length > 0) { | |
| // scrollTo(0,0); | |
| // } | |
| // else { | |
| // $(".has-error input, .has-error select").first().focus(); | |
| // } | |
| let first_error = document.body.querySelector(".has-error input, .has-error select") | |
| if (first_error) { first_error.focus() } | |
| }, | |
| current_snapshot, | |
| new_snapshot | |
| ) | |
| } | |
| }); | |
| } | |
| }); | |
| })(document, window, Turbolinks); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment