user13729875
user13729875

Reputation:

how can i do form wizard with validation using Vue.js

I'm working on a small project using Vue.Js, I followed a video tutorial to create the wizard, but now I try to validate the form using two css classes that I have already created 'is-valid' and 'is-invalid' but my validation doesn't works, this my code

<div class="modal-content tx-14">
              <div class="p-0 bg-ui-01">
                 <ul class="nav nav-tabs nav-justified" id="myTab3" role="tablist">
                    <li class="nav-item m-0">
                       <a class="nav-link border-0 rounded-0" id="home-tab3" data-toggle="tab" href="#home3" role="tab" aria-controls="home" aria-selected="true" :class="{'active':current_step == 1}" @click.prevent="goToStep(1)">Website</a>
                    </li>
                    <li class="nav-item m-0">
                       <a class="nav-link border-0 rounded-0" id="profile-tab3" data-toggle="tab" href="#profile3" role="tab" aria-controls="profile" aria-selected="false" :class="{'disabled':max_step < 2, 'active': current_step == 2}" @click.prevent="goToStep(2)">Location</a>
                    </li>
                    <li class="nav-item m-0">
                       <a class="nav-link border-0 rounded-0" id="contact-tab3" data-toggle="tab" href="#contact3" role="tab" aria-controls="contact" aria-selected="false" :class="{'disabled':max_step < 3, 'active': current_step == 3}" @click.prevent="goToStep(3)">Keywords</a>
                    </li>
                    <li class="nav-item m-0">
                       <a class="nav-link border-0 rounded-0" id="contact-tab3" data-toggle="tab" href="#contact4" role="tab" aria-controls="contact" aria-selected="false" :class="{'disabled':max_step < 4, 'active': current_step == 4}" @click.prevent="goToStep(4)">Crawler</a>
                    </li>
                    <li class="nav-item m-0">
                       <a class="nav-link border-0 rounded-0" id="contact-tab3" data-toggle="tab" href="#contact4" role="tab" aria-controls="contact" aria-selected="false" :class="{'disabled':max_step < 5, 'active': current_step == 5}" @click.prevent="goToStep(5)">Competition</a>
                    </li>
                 </ul>
              </div>
              <div class="modal-body">
                 <div class="tab-content" id="myTabContent">
                    <div class="fade show active" id="home" role="tabpanel" aria-labelledby="home-tab" v-show="current_step == 1">
                       <h4 class="text-center">Create Your Project</h4>
                       <p class="text-center">Enter your website informations, so you can track and improve your SEO traffic and keep an eye on the competition.</p>
                       <div class="row row-sm">
                          <div class="col-sm-12">
                             <div class="form-group">
                                <input type="url" id="project_url" v-model="rules.project_url.string" class="form-control" placeholder="Project URL">
                             </div>
                          </div>
                          <div class="col-sm-12">
                             <div class="form-group">
                                <input type="text" class="form-control" id="project_name" v-model="rules.project_name.string" placeholder="Project name">
                             </div>
                          </div>
                       </div>
                    </div>
                    <div id="location" role="tabpanel" aria-labelledby="profile-tab" v-show="current_step == 2">
                       <h4 class="text-center">Choose Your Location</h4>
                       <p class="text-center">
                          Enter all of the countries or cities you do business in or want traffic from. We recommend that you add at least 3 locations.
                       </p>
                       <div class="row row-sm">
                          <div class="col-sm-4">
                             <div class="form-group">
                                <select class="custom-select">
                                   <option selected>Select Engine</option>
                                   <option value="1">Google</option>
                                   <option value="2">Google Mobile</option>
                                </select>
                             </div>
                          </div>
                          <div class="col-sm-8">
                             <div class="form-group">
                                <input type="text" class="form-control" placeholder="Target Location">
                             </div>
                          </div>
                       </div>
                       <div class="row row-sm">
                          <div class="col-sm-4">
                             <div class="form-group">
                                <select class="custom-select">
                                   <option selected>Select Engine</option>
                                   <option value="1">Google</option>
                                   <option value="2">Google Mobile</option>
                                </select>
                             </div>
                          </div>
                          <div class="col-sm-8">
                             <div class="form-group">
                                <input type="text" class="form-control" placeholder="Target Location">
                             </div>
                          </div>
                       </div>
                    </div>
                    <div id="keywords" role="tabpanel" aria-labelledby="contact-tab" v-show="current_step == 3">
                       <h4 class="text-center">Add Keywords</h4>
                       <p class="text-center">
                          Choose the keywords you would like to track across the selected search engines. Track on the national level, or add locations to track on the local level. Add labels to group keywords by topic.
                       </p>
                       <textarea class="form-control" cols="30" rows="10" placeholder="Add separated keywords by comma ex: pasta,fish"></textarea>
                    </div>
                    <div id="crawling" role="tabpanel" aria-labelledby="contact-tab" v-show="current_step == 4">
                       <h4 class="text-center">Set Crawl Limit</h4>
                       <p class="text-center">
                          We crawl your site weekly to surface technical site issues that may be impacting your SEO performance. Select the number of pages you’d like to have crawled on this site.
                       </p>
                       <div class="row row-sm">
                          <div class="col-sm-4">
                             <div class="form-group">
                                <select class="custom-select">
                                   <option value="5000">5,000</option>
                                   <option value="10000">10,000</option>
                                   <option value="20000">20,000</option>
                                   <option value="30000">30,000</option>
                                   <option value="40000">40,000</option>
                                   <option value="50000">50,000</option>
                                   <option value="75000">75,000</option>
                                   <option value="100000">100,000</option>
                                   <option value="250000">250,000</option>
                                   <option value="450000">450,000</option>
                                </select>
                             </div>
                          </div>
                          <div class="col-sm-8">
                             <div class="form-group">
                                <select class="custom-select">
                                   <option selected>Whole Website</option>
                                   <option value="1">Exclude sub-domains</option>
                                   <option value="2">Follow sitemap</option>
                                </select>
                             </div>
                          </div>
                       </div>
                    </div>
                    <div id="crawling" role="tabpanel" aria-labelledby="contact-tab" v-show="current_step == 5">
                       <h4 class="text-center">Benchmark vs Competitors</h4>
                       <p class="text-center">
                          We will track competitive link and keyword metrics for these sites.
                       </p>
                       <div class="row row-sm">
                          <div class="col-sm-12">
                             <div class="form-group">
                                <input type="text" class="form-control" placeholder="Target Location">
                             </div>
                          </div>
                          <div class="col-sm-12">
                             <div class="form-group">
                                <input type="text" class="form-control" placeholder="Target Location">
                             </div>
                          </div>
                          <div class="col-sm-12">
                             <div class="form-group">
                                <input type="text" class="form-control" placeholder="Target Location">
                             </div>
                          </div>
                          <div class="col-sm-12">
                             <div class="form-group">
                                <input type="text" class="form-control" placeholder="Target Location">
                             </div>
                          </div>
                       </div>
                    </div>
                 </div>
              </div>
              <div class="modal-footer">
                 <button type="button" class="btn btn-secondary tx-13" data-dismiss="modal">Close</button>
                 <button type="button" class="btn btn-primary tx-13" @click="advanceStep">
                 <span v-if="max_step === 5">Submit</span>
                 <span v-else>Next</span>  
                 </button>
              </div>
           </div>

Javascript

    <script>
   new Vue({
       el: "#page",
       delimiters: ['[[', ']]'],
         data (){
            return {
               rules: {
                  project_url: {
                     pattern: /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/,
                     string: ''
                  },
                  project_name: {
                     pattern: /^(?!\s*$).+/,
                     string: ''
                  }
               },
               current_step: 1,
               max_step: 1
            }
        },
        methods:{
            is_valid(rules){
               $.each(rules, function(index, value){
                  if (value.string.match(value.pattern)) {
                     $('#' + index).removeClass('is-invalid')
                     $('#' + index).addClass('is-valid')
                     nextStep =  true
                  } else {
                     $('#' + index).removeClass('is-valid')
                     $('#' + index).addClass('is-invalid')
                     nextStep =  false
                  }
               })
               return nextStep
            },
            validate(){
               if(this.current_step === 1){
                  if(this.is_valid(this.rules))
                     return true
               }
                if(this.current_step === 2)
                    return true
                if(this.current_step === 3)
                    return true
                if(this.current_step === 4)
                    return true
                if(this.current_step === 5)
                    return true
            },
            goToStep(value){
               if(!this.validate())
                  return
               this.current_step = value
            },
            submitForm(){
                alert("submit")
            },
            advanceStep(){
                if(!this.validate())
                    return
    
                if(this.max_step === 5)
                    return this.submitForm()
    
                this.current_step++
    
                if(this.max_step < this.current_step)
                    this.max_step = this.current_step
            }
        }
    })
</script>

can anyone clean my code and help me to add form validation

Upvotes: 1

Views: 1451

Answers (1)

muka.gergely
muka.gergely

Reputation: 8329

The basic idea is that you don't validate the form itself, but the data that is displayed in the form.

You might think that "but they are the same" or "it's a subtle difference", but no.

With jQuery you are going through the form UI - a part of the application, that is THE END RESULT of whole lot of other things. Don't do that. Store your form data in Vue's data option, and only display the result of validations.

Vue.component('InputField', {
  props: ['error', 'value'],
  computed: {
    inputValue: {
      get() {
        return this.value
      },
      set(val) {
        this.$emit("update:value", val)
      }
    },
  },
  template: `
    <div
      class="input-field-wrapper"
      :class="{ error: error }"
    >
      <label>
        INPUT: 
        <input
          type="text"
          placeholder="Type in something"
          v-model="inputValue"
        />
        <span v-if="error">this field is required!</span>
      </label>
    </div>
  `
})

new Vue({
  el: "#app",
  data() {
    return {
      form: [],
      canSubmit: false,
    }
  },
  methods: {
    handleAddInput(id) {
      this.form.push({
        id,
        error: null,
        value: null,
      })
    },
    handleFormValidate() {
      this.form = this.form.map(({ id, error, value }) => {
        return {
          id,
          error: !value,
          value,
        }
      })
      return this.form.every(({
        error
      }) => !error)
    },
    handleResetValidation() {
      this.form = this.form.map(field => {
        return {
          ...field,
          error: false
        }
      })
    },
    handleSubmitForm() {
      this.canSubmit = this.handleFormValidate()
    },
  },
  template: `
    <div>
      <button
        @click="handleAddInput(form.length + 1)"
      >ADD INPUT FIELD +</button>
      <button
        @click="handleFormValidate"
      >VALIDATE FORM</button>
      <button
        @click="handleResetValidation"
      >RESET VALIDATION</button>
      <hr />
      <form>
        <input-field
          v-for="(inputField, i) in form"
          :key="inputField.id"
          :error="inputField.error"
          :value.sync="inputField.value"
        ></input-field>
        <hr />
        <button type="submit" @click.prevent="handleSubmitForm">SUBMIT FORM</button>
      </form>
      FORM CAN BE SUBMITTED: {{ canSubmit }}
    </div>
  `
})
.input-field-wrapper {
  padding: 8px 16px;
  color: black;
}
.input-field-wrapper.error {
  color: white;
  background: red;
  transition: all 0.1s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

HOW IT WORKS

The snippet above is pretty simple:

  • it has data attribute (form) that stores all the data we want to get through our form & the very important error state
  • on ADD INPUT FIELD + a new object is pushed in the form attribute
  • the template renders an InputField component for every object that it finds in the form data attribute
  • the InputField component does nothing, really, only renders a text input element (with an error class, if the input field doesn't validate) & emits the value of the text input back to the parent (so that can store it in its form data)
  • on VALIDATE FORM we cycle through the form data, check the value of every element, and set the error if the value doesn't validate (here the rule is a simple "does it have a value at all?", but any validation could be put in place). The error state is passed down on the InputField component (:error="inputField.error") & that component reacts immediately to the state change
  • on SUBMIT FORM we run the validation method and output the result of validation (it could be a guard on actually submitting the form)
  • on RESET VALIDATION we clear out the error state of every form item (thus resetting the error display in the InputFields component)

You can see that it's not necessary to directly touch the DOM if you set up your components in a "data-driven" way (it is very rare, that you really can't avoid meddling with the DOM with a reasonable amount of work). You have much more freedom to work out the logic of your application if you decouple data & UI.

And you don't need jQuery to do the work.

Upvotes: 1

Related Questions