All threads / How do I properly handle submit events in Vue + Rails?

Ask A Question

Notifications

You’re not receiving notifications from this thread.

How do I properly handle submit events in Vue + Rails?

Felipe André Malaquias asked in Javascript

Hi!

I'm learning Rails, Vue and JS and I've got a question on the proper handling of submit buttons on Rails using Vue component to validate it. I'm using mdbootstrap for the styles.

I built a form wizard which uses vee-validate for validating the fields and in some forms I want to perform some server side operations too (eg.: validate exact address with geocoding). I'm currently facing basically three issues.

1- Although I added a v-clock directive, I'm still seeing a little flicker every time the form wizard component gets loaded (eg.: page refresh).
2- I had to workaround the Rails automatic data-disable-with handling to get it working, and it looks not optimal to me, and I'd like to know if there's a better way to deal with it (I had to disable the submit event propagation and prevent default and do the disabling/enabling manually otherwise the handler from Rails UJS will receive it afterwards and disable the button forever).
3- Although the button gets enabled again, it gets brighter every time I click on it if validation fails (some handler from mdbootstrap maybe?). It happens only after I click on refresh button on the browser and I've noticed the following div is created after each click followed by an error during form validation, causing the button to become "brighter" as in a accumulated "disabled effect":

Therefore, indeed it looks like there is some mdb js going on there.

Anyone has ideas on how these issues could be solved? Thanks!

new.html.erb:

<div id="stepper">
    <div v-cloak>
        <transition-group name="fade">
            <div class="d-none d-lg-block"  key="progress_bar">
                <%= render 'spaces/forms/progress_bar' %>
            </div>
            <div id="step1" v-if="step === 1" key="step1">
                <%= render 'spaces/forms/description' %>
            </div>
            <div id="step2" v-if="step === 2" key="step2">
                <%= render 'spaces/forms/address' %>
            </div>
        </transition-group>
    </div>
</div>

_description.html.erb:

<template>
  <form 
    id="description-form" 
    data-vv-scope="description-form" 
    novalidate="true"
    @submit.prevent="next('description-form', $event)">

    <div class="row mb-5">
      <div class="col-lg-12 col-md-12">
        <div class="container">
          <div class="row" id="step-1">
            <div class="col-lg-6">
              <div class="max-height-80">
                <div class="mb-4">
                  <h4><%= t(:'step1.title') %></h4>
                </div>

                <div class="form-group">
                  <label 
                    for="name" 
                    class="control-label">
                    <%= t(:'step1.label.name') %>
                  </label>
                  <input 
                    id="name" 
                    name="name" 
                    type="text" 
                    class="form-control" 
                    placeholder="<%= t(:'step1.input.name') %>" 
                    v-validate="'required'" 
                    v-model="name" 
                    :class="{ 'is-invalid': errors.has('name','description-form') }" 
                    required/>
                  <div 
                    v-if="errors.has('name','description-form')" 
                    class="invalid-feedback">
                    <%= t(:'name.required') %>
                  </div>
                </div>

                <div class="form-group">
                  <label 
                    for="description" 
                    class="control-label">
                    <%= t(:'step1.label.description') %>
                  </label>
                  <textarea 
                    id="description" 
                    name="description" 
                    class="form-control" 
                    placeholder="<%= t(:'step1.input.description') %>" 
                    rows="11" 
                    v-validate="'required'" 
                    v-model="description" 
                    :class="{ 'is-invalid': errors.has('description','description-form') }" 
                    required>
                  </textarea>
                  <div 
                    v-if="errors.has('description','description-form')" 
                    class="invalid-feedback">
                    <%= t(:'description.required') %>
                  </div>
                </div>
              </div>
              <footer class="page-footer white fixed-bottom d-block d-sm-none z-depth-1" id="footer">
                <div class="d-flex justify-content-end">
                  <button class="btn btn-default pull-right" type="submit" data-remote="true" data-disable-with="<%= wait_spinner %>"><%= t(:'btn.next') %></button>
                </div>
              </footer>
              <div class="d-none d-sm-block">
                <div class="d-flex justify-content-end mt-2">
                  <button class="btn btn-default pull-right" type="submit" data-remote="true" data-disable-with="<%= wait_spinner %>"><%= t(:'btn.next') %></button>
                </div>
              </div>
            </div>
            <div class="col-lg-2"></div>
            <div class="col-lg-4 d-none d-lg-block mt-lg-5">
            </div>
          </div>
        </div>
      </div>
    </div>
  </form>
</template>

stepper.js

var element = document.getElementById("stepper");
if (element != null) {
    Vue.use(VeeValidate);
    Vue.use(VueResource);

    Vue.http.headers.common['X-CSRF-Token'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content');

    const stepper = new Vue({
        el: element,
        data() {
            return {
            /**
            * The step number (starting from 1).
            * @type {Integer}
            */
            step:1,
            name:null,
            description:null
            }
        }
    },

    methods: {
        /**
        * Goes back to previous stepp
        */
        prev() {
            this.step--;
        },
        /**
        * Triggers validation of current step and goes to the 
        * next step if validation succeeds
        * @param {String} scope The step scope used for validation.
        * @param {Object} Event that triggered the next step (form submit)
        */
        next(scope, event) {
            if (event != undefined) {
                event.stopImmediatePropagation();
                event.preventDefault();
                event.stopPropagation();
            }

            const form = event.currentTarget;
            $("button[type=submit]",form).each(function() {
                Rails.disableElement(this);
            });

            this.validateFields(scope, event);
        },
        validateFields(scope, event) {
            this.$validator.validateAll(scope).then(function (valid) {
                this.postFieldsValidation(valid, event);
            }.bind(this));
        },
        postFieldsValidation(valid, event) {

            if (valid) {
                stepper.step++;
            }

            const form = event.currentTarget;
            $("button[type=submit]",form).each(function() {
                Rails.enableElement(this);
            });

        },
        handleError(error) {
            alert(error)
        },
        submit() {

        }
    }
});
}

You can use Vue validation plugins for that. Some plugins that I am aware of are: Vee Validate, Vue form generator and Vue validator

Join the discussion

Want to stay up-to-date with Ruby on Rails?

Join 37,344+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.

    logo Created with Sketch.

    Ruby on Rails tutorials, guides, and screencasts for web developers learning Ruby, Rails, Javascript, Turbolinks, Stimulus.js, Vue.js, and more. Icons by Icons8

    © 2020 GoRails, LLC. All rights reserved.