horprogs / Just-validate

Modern, simple, lightweight (~5kb gzip) form validation library written in Typescript, with no dependencies (no JQuery!). Support a wide range of predefined rules, async, files, dates validation, custom error messages and styles, localization. Support writing custom rules and plugins.
https://just-validate.dev/
Other
511 stars 92 forks source link

QUESTION - How to use `revalidate` to stop user navigation if form is invalid? #133

Closed PiemP closed 11 months ago

PiemP commented 11 months ago

I'm using bootstrap 5 tabs to design an inteface that allow user to insert some data distributed over each tab. Every input have its validation rules. I can't interact with the onclick event over the tabs and I'm trying to use show.bs.tab event. I try to avoid the switch between tabs if form is invalid, but I can't understand how I can prevent the switch to be done if I have to "wait" the promise to know the form status.

Thank you.

horprogs commented 11 months ago

Hey, why do you need to wait for the promise? I think you could use onSuccess, onFail callbacks, set your state variable and based on this enable and disable your tabs. If you create a small example, probably I could help

PiemP commented 11 months ago

Sorry but I don't want disable tabs...it could make interface less understandable by the user and close a way to interact with the form.

Like I said before, I can't work on the click event of the tabs because I have to redesign a too much things in the code and this function is not a requirement that have all of this priority.

The only event that remain is show.bs.tab (or hide.bs.tab) that is called before the destination tab is made visible. The thing that I try to do is this:

tab.addEventListener('show.bs.tab', function (event) {
        //pause event execution

        //call revalidate function 
        _validator.revalidate()
        .then((isValid) => { 
          //if form is valid resume event execution
        });

I don't have experience with javascript Promise and it's difficult to me understand how to use them. For what I have read is not possible to do what I want to do in the example.

I thank you for your interest.

horprogs commented 11 months ago

from your example show.bs.tab is a custom event. Usually you could prevent it, like event.preventDefault(), but then you should trigger it manually, like document.querySelector('#yourtab').click(), will it work?

PiemP commented 11 months ago

Thank you @horprogs,

I have tested this code:

  tab.addEventListener('hide.bs.tab', function (event) {
        //to lock switch beetwen tabs use the code below
        event.preventDefault()

        //validate
       _validator.revalidate().then((isValid) => { 
          if (isValid)
            event.relatedTarget.click() // related target contains the tab clicked 
        });

        ...
  })

but if the form is valid it goes in loop (event, preventDefault(), form valid, call click, event, preventDefault(), form valid, call click ...).

horprogs commented 11 months ago

Fair enough. You could try these 2 solutions:

  1. Using defaultPrevented property, like:

    tab.addEventListener('hide.bs.tab', function (event) {
         if (event.defaultPrevented) {
            // do nothing, allow the clicking
            return
         }
        //to lock switch beetwen tabs use the code below
        event.preventDefault()
    
        //validate
       _validator.revalidate().then((isValid) => { 
          if (isValid)
            event.relatedTarget.click() // related target contains the tab clicked 
        });
    
        ...
    })
  2. Using an additional variable:

    let tabValid = false
    tab.addEventListener('hide.bs.tab', function (event) {
         if (tabValid) {
            // do nothing, allow the clicking
            return
         }
        //to lock switch beetwen tabs use the code below
        event.preventDefault()
    
        //validate
       _validator.revalidate().then((isValid) => { 
          if (isValid) {
                         tabValid = true
                         event.relatedTarget.click() // related target contains the tab clicked 
           }
    
        });
    
        ...
    })
PiemP commented 11 months ago

Done! I have implemented this code:

    let tabValid = false
    _tabs = document.querySelectorAll('a[data-bs-toggle="tab"]');
    _tabs.forEach(function (tab, index, arr) {

      tab.addEventListener('hide.bs.tab', function (event) {
        //if validation is ok allow tab moving 
        if (tabValid) {
          //do something on valid form case
         tabValid = false;
          return
        }

        //to lock switch beetwen tabs use the code below
        event.preventDefault()

        //validate
        _validator.revalidate().then((isValid) => {
          if (isValid) {
            tabValid = true
            event.relatedTarget.click() // related target contains the tab clicked 
          }
        });
      })

Thanks for your support

PiemP commented 11 months ago

Nothing 😔...if you switch fast between tabs seems tabValid doesn't have the right value... I see the debugger execute tabValid = false;, if form is valid, but when I click fast over the next tab the check if(tabValid) report the true value instead of false.