Help with implementing aurelia-validation (for pay)

Hi,

I have a fairly simple questionnaire that contains radio button groups that are required. I’d like help implementing aurelia-validation into the process.

Please and thanks,
Jason

Hi Jason, on Google I found a demo containing a required radio button group.

Demo radio button group validation

Hi there,

I found this example but still couldn’t get it integrated with my setup. I’ve got custom elements like this:

<input type=radio

I’d pay if you were able to make it happen.

Thanks,

Jason

Please use the code syntax to let us be able to see your actual code :wink: --> in the editor toolbar </>

This is a code block

Here it goes:

answer-questions.vm.html

<h2>Answer Questions</h2>
<questions questions.bind="questions" complete-callback.call="allQuestionsAnswered(items)"> . 
</questions>

questions.element.html

<form submit.delegate="submit()">
<div repeat.for="question of questions | groupBy:'area.name'">
  <question q.bind="question"></question>
</div>

questions.element.ts

export class QuestionsCustomElement {
  @bindable({ defaultBindingMode: bindingMode.twoWay }) public questions!: [null];
  public validationValid: boolean = false;
  public validationObject: any = {};
  private validationController: ValidationController;

 constructor(validationControllerFactory: ValidationControllerFactory) {
    this.logger = LogManager.getLogger('Questions Custom Element');
    this.validationController = validationControllerFactory.createForCurrentScope();
    this.validationController.validateTrigger = validateTrigger.manual;
 } 

 public questionsChanged(newValue, oldValue): void {
     this.logger.info('Questions custom element items changed:', newValue, oldValue);
 }

 public submit(): void {
     const items = this.questions;

     this.logger.info('Preparing to submit questions', items);

    this.validationController.validate().then(r => {
       this.logger.info('Validation result', r.valid, r);
    });
}

question.element.html

<template>
   <div class="row">
    <div class="col-xs-12 col-md-8">
       <h2>
         <i class="text-primary mr-2 align-middle" 
         class.bind="getClassForCategory(question.group.group)" aria-hidden="true"></i>
           ${question.group.group}
       </h2>
       <p class="lead text-left text-muted mb-4">${question.values[0].area.description}</p>
    </div>
 </div>

<div class="row">
  <div class="col-xs-12">
    <ul>
       <li repeat.for="error of validationController.errors">${error.message}</li>
     </ul>
     <fieldset class="mb-4" repeat.for="question of question.values">
         <div class="row">
           <div class="col-sm-8 text-left">
              <p class="lead text-left">
                 ${question.text}
             </p>

        <!--<radio-select items-source.bind="question.answerChoices" value.bind="question.selectedAnswer & validate" aria-labelledby="color1"
      ref="color1RadioSelect">
    </radio-select>-->

        <!--<input type="text" value.bind="question.selectedAnswer & validate" class="required-message" />-->

        <div class="error-message" style="visibility: hidden">A choice is required.</div>
           <div class="radio" repeat.for="choice of question.answerChoices">
              <label for="${choice}-${question.id}">
                  <input type="radio" name.bind="question.id" id="${choice}-${question.id}" value.bind="choice"
              checked.bind="question.selectedAnswer" role="group" required data-error- 
               class="${question.id}-error">
            ${choice}
          </label>
        </div>
        <!--<strong>Selected: ${question.selectedAnswer}</strong>-->
      </div>
      <div class="col-sm-4 text-left border-default border-left">
        <p><strong>Example</strong></p>
        <div class="text-muted" innerhtml.bind="question.example"></div>
      </div>
    </div>
  </fieldset>
</div>

question.element.ts

import './question.element.scss';
import { autoinject, bindable, bindingMode } from 'aurelia-framework';
import { LogManager, Logger } from './../../../services/logger.service';
import {
    validateTrigger, ValidationController, ValidationControllerFactory, ValidationRules
} from 'aurelia-validation';

@autoinject
export class QuestionCustomElement {
    @bindable({ defaultBindingMode: bindingMode.twoWay }) public q!: [null];
    public question: Question = new Question();

    public answerChoices = ['Yes', 'No', 'I Don\'t Know'];
    public validationValid: boolean = false;
   public validationObject: any = {};

   private logger: Logger;
   private validationController: ValidationController;
   private controls = [];

  constructor(validationControllerFactory: ValidationControllerFactory) {
      this.logger = LogManager.getLogger('Question Custom Element');

      this.validationController = validationControllerFactory.createForCurrentScope();
      this.validationController.validateTrigger = validateTrigger.manual;
      this.validationController.addObject(this.question);
 }

public qChanged(newValue, oldValue): void {
    this.logger.info('Question custom element items changed:', newValue, oldValue);

    if (newValue) {
       // console.log('newValue', newValue);

       this.question.group = newValue.group;
       this.question.values = [];

       newValue.values.forEach(item => {
          // console.log('item', item);
         const questionItem = new QuestionItem();
         questionItem.id = item.id;
         questionItem.answerChoices = item.answerChoices;
         questionItem.text = item.text;
        questionItem.example = item.example;
        questionItem.selectedAnswer = '';
        this.question.values.push(questionItem);
       });

     /*
     newValue.map((question: any) => {
     this.controls.push({
        bind: question.selectedAnswer,
        validation: '.required()'
        } as never);
      }); */

      // this.setupValidator();
    }
}

public setupValidator(): void {
    const rules = [];

    this.controls.map((control: any) => {
       this.logger.info('control:', control, control.validation);

       if (control.validation) {
           this.logger.info('control:', control, control.validation, control.bind);
           if (control.validation) {
              if (control.validation.match(/\.required\(\)/)) { 
                rules.push(ValidationRules.ensure(control.bind).required().rules[0] as never); }
              }
          }

        this.validationController.addObject(this.validationObject, rules);
      });
   }    
}

export class Question {
    public group: string = '';
    public values: QuestionItem[] = [];
}

export class QuestionItem {
    public id = 0;
    public text: string = '';
    public answerChoices: any[] = [];
    public selectedAnswer: string = '';
    public example: string = '';
}

ValidationRules
   .ensure('selectedAnswer').required()
    .on(QuestionItem);

Unfortunately the formatting is still not perfect. Next time you need to prefix every line with 4 spaces to use the code block formatting. That being said, let’s try as it is.

What I noticed, is that you bind the value “choice” to the same object as in the repeat.for. That is not corresponding to the example in the Guide. In your example you would need to bind it to question.selectedAnswer

Is the value changed as you expect? Perhaps use the Chrome Aurelia Inspector or some console.log to check that first.

Next thing. Is the element actually being validated? I was not able to find the this.validationController.validate() function, though I might have missed it.

I was hoping to be able to provide you the code (easy to get running) and then we could post the resolution here. Sorry for the formatting woes.

Sorry, I’m not able to fully debug and change for you. With my questions I tried to a) make your question more clear for anyone reading the issue and b) hint you into the proper direction to sort things out.