Need a better way to handle complex conditional form logic & validation with Ractive.js

586 views Asked by At

I am currently building a fairly complex form with a lot of conditional logic. by that I mean, when users check one option, 1 or more others maybe revealed or hidden - you've probably seen it many times before..

Using Ractive's mustache templates and many {{#if }} statements I have created the form, but the logic for validation and submission needs improvement. I need to enable the submit button only when all 'visible' fields are valid, so I have concluded that each field needs an isInUse property as well as isValid, see below for an example:

data: {
    foo: {
        isValid: false,
        isInUse: false,
        value: ''
    }
}

The reason for this is that a field could be made visible but then an option could hide it & it might still have a value that the user does not need to submit.

I have also determined that the only reliable way to change the isInUse property is to create a function in my data that can be accessed from my template, like so:

data: {
    foo: {
        isValid: false,
        isInUse: false,
        value: ''
    },

    isInUse: function (keypath, bool) {
        ractive.set(keypath, bool);
    }
}

which is used in my templates like so:

{{#if choice.email}}
      {{ isInUse('validate.email.isInUse', true) }}
      {{ isInUse('validate.phone.isInUse', false) }}

      <label for="email">Email</label>
      <input type="text" id="email" value="{{validate.email.value}}">
{{/if}}

This means I am able to change the value on the template-side.. which means I can check if each field is in use and valid.. now this is where I am questioning the implementation, is this a good idea?

I have created a simple version of the form on jsbin (which completely works with validation & submission), see here: http://jsbin.com/wasoxa/2/edit?html,js,output but my form is much more complex so I'd like to find a graceful way of handling all of this.

1

There are 1 answers

6
Rich Harris On

Calling isInUse from within the template is a very creative solution, but unfortunately very likely to break!

You should think of expressions in templates as being read-only - you can call a function within an expression, but only to get its value, never for side-effects such as setting another value (the one possible exception being to log output for debugging). The reason is that you're not in direct control of when the function is called - Ractive handles that on your behalf - so you can get unexpected results. In the example above, changing choice.email from true to false won't have the desired effect.

You probably want computed properties. These can be read inside the template just like regular properties, except that their value depends on other data (or other computed properties):

ractive = new Ractive({
  el: 'body',
  template: 'twice {{foo}} is {{doubleFoo}}',
  data: { foo: 1 },
  computed: {
    doubleFoo: function () {
      return 2 * this.get( 'foo' );
    }
  }
});

Whenever foo changes, doubleFoo knows (because we called this.get('foo') inside its definition) that it should recompute itself. You can use computed values just like you'd use any other value - e.g. ractive.observe('doubleFoo',doSomething).

This can be useful for validation:

var ractive = new Ractive({
  el: 'main',
  template: `
    <h2>contact type</h2>
    <label>
      <input type="radio" name="{{contactType}}" value="email"> email
    </label>
    <label>
      <input type="radio" name="{{contactType}}" value="telephone"> telephone
    </label>
  
    <h2>name</h2>
    <input type="text" value="{{name}}">
    <p>name is valid: {{nameIsValid}}</p>
  
    {{#if contactType === "email"}}
      <h2>email</h2>
      <input type="text" value="{{email}}">
      <p>email is valid: {{emailIsValid}}</p>
    {{/if}}`,
  computed: {
    nameIsValid: function () {
      return !!this.get( 'name' );
    },
    emailIsValid: function () {
      var email = this.get( 'email' );

      // never actually use this regex
      return /^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$/.test( email );
    }
  }
});
<script src="http://cdn.ractivejs.org/latest/ractive.js"></script>
<main></main>