Trigger form validation

Hi, does anyone know how to trigger a form to validate without submitting? My use case is: I have a multi-step form where I have “Next” buttons. Before the user can go to the next page, I want the error validations to show up, for example if there are inputs that are not filled in.

I can’t figure out how to do this without form submit. Things I’ve tried:

  1. triggering the individual form input validations using the “click” dynamic event on the “Next” button, however that didn’t stop it from moving to the next page, and I also don’t like the idea of having to trigger each input individually
  2. coding some custom javascript to iterate through the form and validate each field, but that didn’t work either - not sure if I have a syntax issue. The script I used is as follows:
    ‘function validateVisibleInputs() {
    var arr = document.getElementsByTagName(“form”);
    for (var i = 0; i < arr.length; i++) {
    arr[i].reportValidity();
    }
    }’

Note that this half-works: it triggers a different validation format (I’m assuming this is the default HTML5 validation rather than the normal validation I want that comes up on form submit) and it also doesn’t stop the “Next” button from moving the user to the next page.

I saw this forum post but it didn’t solve my problem: Validating multi-step form

I would love to know if there is a way to programmatically validate the visible fields in the form, before the “Next” button moves the user to the next page. Is there a way to trigger form validation using javascript? I’m just not sure how to link it to the dmx validator…

The following can be done using the UI. For brevity I have shown the code.

<form id="form">
    <div class="form-group md-3 mb-3">
        <input type="text" class="form-control" id="input1" name="input1" aria-describedby="input1_help" placeholder="Enter Firstname" required="" dmx-on:blur="input1.validate()">
    </div>
    <div class="form-group md-3 mb-3">
        <input type="text" class="form-control" id="input2" name="input2" aria-describedby="input2_help" placeholder="Enter Lastname" required="" dmx-on:blur="input2.validate()">
    </div>
    <div class="form-group md-3 mb-3">
        <input type="text" class="form-control" id="input3" name="input3" aria-describedby="input2_help" placeholder="Enter email address" required="" data-rule-email="" dmx-on:blur="input3.validate()">
    </div>
    <button id="btn1" class="btn btn-success" dmx-bind:disabled="(input1.value.length() == 0 || input2.value.length() == 0 || input3.value.length() == 0 || input3.invalid)">next</button>
</form>

Thanks @ben, however I didn’t want it on blur as the following scenario would not be catered for: if a user did not click onto the input in the first place and missed filling in a required input altogether.

The next button will not work if no input has been entered.

Thanks, I might have to leave the next button disabled as you suggested if I can’t find a way to form validate on click without disabling. I’m just worried that users won’t realise what’s not filled in without a validation.

You could always place an asterisk next to a required field.

True, I was hoping there was a better solution than having the user look individually. And also from the coding perspective, I would have to specify all the inputs individually, which means it’s more prone to error if I add new fields (and to add more complexity I have some hidden/non-hidden inputs depending on the answers to certain inputs)

Is there really no way to trigger form validation other than by form submit? I keep thinking I must be missing some connection or some way to trigger validation by linking it in using JS.

I typically use a data store to hold the data from multi step forms and use a hidden form to post that to the server when ready.

I then use a completely empty api as the form action for all the user input forms with a submit button for Next. The validation is then performed on Next and on success of the empty api, I put the data inputs into the data store and move to next step.

When everything is ready, submit your hidden form, which can have a single input inside of it, with a value that is the stringified data store. The form action does the actual work of saving to db, etc. you just use a set value to parse the json back to an object and reference as you normally would downstream. There’s some posts in the forum with more details on doing this I believe.

4 Likes

Thank you @mebeingken! This is great :slight_smile:

I’ll try this out and look at the documentation on data store (didn’t even know this functionality existed before). I may have further questions, but if I manage to make it work I’ll mark this as solved!

1 Like

Working like a charm so far @mebeingken! :slight_smile:

I do have a question… I’m currently building the “write to DB” functionality using the method @ben outlined in this post: Copy data from Data Store into database

I was wondering if there was an easier way, as I am having to specify each input individually in the hidden form and bind the names and values manually. I couldn’t find the stringified data store and parsing json back to an object method you were referring to in your post. Much appreciated!!!

Here is an example:

First, you need to create a custom formatter. If you have not done this yet, create a custom_formatters.js file in your /public/js folder. Add to that file:

dmx.Formatter('array', 'stringify', function (val) {
    return JSON.stringify(val);
});

and add the following into the head tag of your layout if not already there:

<script src="/js/custom_formatters.js" defer></script>

Then create a content page that uses the layout:

<!-- Wappler include head-page="layouts/main" fontawesome_5="cdn" bootstrap5="local" is="dmx-app" id="test" appconnect="local" components="{dmxDatastore:{}}" -->
<script is="dmx-flow" id="flow_page_load" type="text/dmx-flow" autorun>{
  run: {
    action: "{{datastore_users.insert({profile: {first_name: 'John'+datastore_users.data.length, last_name: 'Smith'+datastore_users.data.length}, email: datastore_users.data.length+'john@smith.com', settings: {role: 'admin', new_user: true}})}}"
  }
}</script>
<meta name="ac:route" content="/test">
<dmx-datastore id="datastore_users" session="true"></dmx-datastore>
<form id="form_upsert_users" is="dmx-serverconnect-form" method="post" action="/api/upsert_user">
    <input id="hidden_user" name="users_stringified" type="hidden" class="form-control" dmx-bind:value="datastore_users.data.stringify()">
    <button id="btn1" class="btn btn-primary" type="submit">Submit</button>
</form>

For this example, I’m just using a page flow to create a data store entry on each page load, but you can add entries to the data store, however you need. So the first time it loads, there will be 1 entry, then on refresh 2, etc.

Here is the datastore schema:

There is a form with a single hidden input and Its value is set to the datastore data using the custom formatter:

dmx-bind:value="datastore_users.data.stringify()"

So when you submit that form to a server connect api, you will have a payload like this:

The api can then simply reverse the stringify process and return a complex object which you can use in your api like any other data point:

In your api, you just always refer to “users” rather than the POST value. You won’t be able to see it all in the picker (unless you add that schema to the set value), so you will have to type in the element within users you want, but when you are dealing with dozens or even hundreds of data points, this makes for a very manageable structure.

3 Likes

Genius, this is amazing, thanks again so much! :slight_smile: Really really appreciate it.

I had to do my own new multi step form yesterday and tried another approach.

This time I used a single form that has containers for each step that I show/hide as needed. The “next step” buttons are submit buttons. The form action is dynamically set—for all but the final submit, the form action is set to the empty “just success” api. Then on the success of the form I move to the next step, etc. Validation is then also dynamic—required fields are only required if the step is currently visible.

It is another way to get standard form validation on interim steps. This eliminates the need for temporary data store and parsing json.

Just another option.

2 Likes

Ohh makes sense, that’s another interesting way. I might try that for my next multi step form too!

In terms of submitting to the database if you had “repeat”, which way did you find easier, this way or using a data store? And for ease of use and future-proofing which way did you find easier? The one downside of data store that I can think of is that you have to write each of the field names out when setting values.

Thanks for sharing again!

I think there are use cases for both (and others). A single form, no data store I think is easier, but it really depends on the exact use case.

1 Like

Thanks, how did you dynamically set the form action to different apis? The only way I know how to do it is by updating the api/server action by including conditional if and thens based on hidden inputs or variables.

On your form you use something like this:

dmx-bind:action="join_step.value &lt; 3 ? 'api/utils/just_success' : 'api/reg/reg'"

Which is simply saying, if the variable which tracks the step counter (join_step for me) is less than 3, then use just_success, otherwise use the actual form api.

2 Likes

Wow thanks! I would love to learn how to use these dmx bindings, but I’ve found it hard to research. Is there any documentation I can use? At least for other more widely used languages, it’s relatively easy to google search.

The forum is your best tool here. I know it’s hard what you don’t really know what you are looking for, but search the forum when you are trying to do something, and if you don’t find an answer, post up your own question.

1 Like

Thank you, you’ve been immensely helpful! :slight_smile:

1 Like