Using Aurelia validation in model - aurelia

This is a similar to this but we want to define validation logic in model level but the following doesn't show validation message.
user-model.js (not working)
import {transient, inject} from 'aurelia-framework';
import {ensure} from 'aurelia-validation';
#transient()
export class UserModel {
#ensure(function(it) { it.isNotEmpty().hasLengthBetween(3,10) })
firstName = "";
constructor() {
this.firstName = "";
}
}
user.js (not working)
import {inject} from 'aurelia-framework';
import {Validation} from 'aurelia-validation';
import {UserModel} from 'models/user-model';
#inject(Validation, UserModel)
export class User {
constructor(validation, userModel) {
this.userModel = userModel;
this.validation = validation.on(this);
}
}
user.html (not working)
<form role="form" validate.bind="validation">
<div class="form-group">
<label>First Name</label>
<input type="text" validate="model.firstName" value.bind="model.firstName" class="form-control" >
</div>
</form>
Notice that validate="model.firstName" is used in user.html, which kind of makes the validation work, meaning I see 'has-success' class gets added to form-group div when user input is valid, but it doesn't display message when it's NOT valid input.
But, if I take out the validation logic outside of the user-model.js and put it in user.js like below, it works just fine.
user-model.js (working)
import {transient, inject} from 'aurelia-framework';
#transient()
export class UserModel {
constructor() {
this.firstName = "";
}
}
user.js (working)
import {inject} from 'aurelia-framework';
import {Validation} from 'aurelia-validation';
import {UserModel} from 'models/user-model';
#inject(Validation, UserModel)
export class User {
constructor(validation, userModel) {
this.model = userModel;
this.validation = validation.on(this)
.ensure('model.firstName')
.isNotEmpty()
.hasLengthBetween(3,10);
}
}
user.html (working)
<form role="form" validate.bind="validation">
<div class="form-group">
<label>First Name</label>
<input type="text" value.bind="model.firstName" class="form-control" >
</div>
</form>
We are trying to define validation logic in user model itself so that when we need to use it in other UIs, we have the centralized location to validate it instead of copy & paste the logic everywhere. It's possible that I am doing something wrong, but if anyone knows how to accomplish this, any help is appreciated!

From the aurelia-validation docs,
As your viewmodels become more complex or if you start using binding
converters, the binding path you used to set up the validation rules
might be different than the binding path you used in your view, so
you'll need to give the validate custom attribute some extra clues as
to which elements should be matched against which validation rules.
Consider this more complex example...
Basically, the validation rule was created against the firstName property in your UserModel, but the binding for the input element has a different binding path: value.bind="userModel.firstName". Because of this, you need to add a validate="firstName" attribute onto the input element to help aurelia-validation know which HTML element to match on for the validation messages.
Here's how you can do it (with Plunkr)
user-model.js
import {transient} from 'aurelia-framework';
import {ensure} from 'aurelia-validation';
#transient()
export class UserModel{
#ensure(function(it) { it.isNotEmpty().hasLengthBetween(3,10) })
firstName = "";
constructor() {
this.firstName = "";
}
}
user.js
import {inject} from 'aurelia-framework';
import {Validation} from 'aurelia-validation';
import {UserModel} from 'user-model';
#inject(Validation, UserModel)
export class User {
constructor(validation, userModel) {
this.userModel = userModel;
this.validation = validation.on(this.userModel);
}
}
user.html
<template>
<form role="form" validate.bind="validation">
<div class="form-group">
<label>First Name</label>
<input type="text" value.bind="userModel.firstName" validate="firstName" class="form-control">
</div>
</form>
</template>

Related

Use VeeValidate v4 common validators with cutomize error message

I am trying to use the common validators provided by #vee-validate/rules, it works well but I can't find the way to customize the error message.
Now my error message is : "CompanyName is not valid. " But I want to change the field name from "CompanyName" to "Company name" and change "is not valid" to something related to the rule like "is required".
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import i18n from './i18n'
createApp(App).use(router).use(i18n).mount('#app')
import { defineRule } from 'vee-validate';
import AllRules from '#vee-validate/rules';
Object.keys(AllRules).forEach(rule => {
defineRule(rule, AllRules[rule]);
});
vue file
<template>
<Form #submit="onSubmit" :validation-schema="schema" v-slot="{ errors }">
<div class="form-group">
<span class="lblSectionField">Company Name <span class="lblMandatory">*</span></span>
<Field name="CompanyName" class="form-control"/>
<span class="errorMsg">{{ errors.CompanyName }}</span>
</div>
<Form>
</template>
<script setup>
import { Form, Field } from 'vee-validate';
const schema = {
CompanyName: 'required',
};
</script>
I found the solution
<script setup>
import { localize } from '#vee-validate/i18n';
import { Form, Field, configure } from 'vee-validate';
localize('en', {
fields: {
CompanyName: {
required: 'Company name is required',
},
},
});
const schema = {
CompanyName: 'required',
};
</script>

Does Ionic2 RestApi Promise work only with arrays?

I'm newbie in Ionic2 typescript angular2. I've developed an example app to get data from Rest API.
I've used a Page component which implements a Service Provider which provides all the data in a promise to an array like:
UserService.ts
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class UserService {
data: any;
constructor(private http: Http) {
this.data = null;
}
load() {
if (this.data) {
// already loaded data
return Promise.resolve(this.data);
}
// don't have the data yet
return new Promise(resolve => {
this.http.get('https://randomuser.me/api/?results=25')
.map(res => res.json())
.subscribe(data => {
this.data = data.results;
resolve(this.data);
});
});
}
}
HomePage.ts
import {Component} from '#angular/core';
import {NavController} from 'ionic-angular';
import {UserService} from '../../providers/user-service';
#Component({
selector: 'page-home',
templateUrl: 'home.html',
})
export class HomePage {
users: any[] = [];
constructor(
public navCtrl: NavController,
public userService: UserService
) {
this.userService.load()
.then(data => {
this.users = data;
}) ;
}
}
home.html
<ion-header>
<ion-navbar color="primary">
<ion-title>
Demo 103
</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item *ngFor="let user of users">
<ion-avatar item-left>
<img [src]="user.picture.medium">
</ion-avatar>
<h2>{{ user.name.first | uppercase }}</h2>
<p>{{ user.email }}</p>
</ion-item>
</ion-list>
</ion-content>
Now, in the template with ngFor, everything works perfectly. But the problem comes, when I try to get the details of one of them, instead of a list of users.
When I replace users: any[] = []; to user:any; I get an error saying the model is undefined.
It is like if I don't get an array. My template doesn't wait the promise in order to render the page. Can't I use promise with an object? Is it only for arrays?
You can you Promises with objects as well. But notice that while the template is rendering your object "user" is uninitialized, when you declare it like that:
user:any
That's why you get an error. You can prevent the error by adding "?" mark after variable in your template.
<ion-list>
<ion-item>
<ion-avatar item-left>
<img [src]="user?.picture.medium">
</ion-avatar>
<h2>{{ user?.name.first | uppercase }}</h2>
<p>{{ user?.email }}</p>
</ion-item>
</ion-list>
NOTE: Your template is already rendered even before the promise is resolved for API data. Which is correct behavior. The template gets refreshed again when the data is loaded.
At first when the data is not loaded and the template is already rendered without any data. Your *ngFor="let user of users" tries to check for any data and it does not find any. So, it works and renders nothing. As soon as API promise is resolved and you have data, it refreshes to render your template correctly.
Now, when you try to access user (after doing user:any) directly like this without ngFor or ngIf:
<ion-item>
<ion-avatar item-left>
<img [src]="user.picture.medium">
</ion-avatar>
<h2>{{ user.name.first | uppercase }}</h2>
<p>{{ user.email }}</p>
</ion-item>
user:any does not have any property named as picture, name, etc. at the moment (before promise is resolved). So, it cannot access any data. And so the error.
This is not related to if it is an object or an array issue. It is how you are handling it.
If you want to correct this behavior, use any if condition indicating "if the data is present".
E.g.
<ion-item *ngIf="user">
<ion-avatar item-left>
<img [src]="user.picture.medium">
</ion-avatar>
<h2>{{ user.name.first | uppercase }}</h2>
<p>{{ user.email }}</p>
</ion-item>
Also, I do not understand how you are accessing user.picture or any property for that matter, if your API results in an array. Isn't user[<index>].picture will be correct? (Again, you need to make sure that user has data with <index>).
Now I get it... I didn't realize that and didn't know even about the "?".
Thank u both!

Binding complex object to a component

Introduction
My goal is to create custom element in aurelia so that I can reuse it across the application.
In this context I've created component called operator-detail (operator-detail.html and operator-detail.js) which will holds information about operator and my plan is to reuse it in several places in application.
In this use case I have electornicRegistrationForm object which holds reference to operatorDetails and legalRepresentative. Both instances are injected into electornicRegistrationForm module and will be used as part of wizard allowing user to create a document which will be printed later on.
This electronicRegistraionForm is injected into operatorStep component.
operator-detail.html component I've included in operatorStep.html and confirm that component has been rendered correctly.
Problem
How to pass (bind) property operator from operatorStep.js to operator-detail.html component so that values from object operator are displayed (binded) in a two way binding manner.
In following example, value from this.operator.firstName 'First name from operator step' don't get displayed in operator-detail.html component.
Source code
electronicRegistrationForm.js:
import { OperatorDetail } from 'common/operator-detail';
import { LegalRepresentative } from 'common/legalRepresentative';
import { inject } from 'aurelia-framework';
#inject(OperatorDetail, LegalRepresentative)
export class ElectronicRegistrationForm {
constructor(operatorDetail, legalRepresentative) {
this.operator = operatorDetail;
this.legalRepresentative = legalRepresentative;
}
}
operator-detail.js
import {inject, NewInstance} from 'aurelia-framework';
import {ValidationRules, ValidationController} from 'aurelia-validation';
#inject(NewInstance.of(ValidationController))
export class OperatorDetail {
constructor(validationController) {
this.validationController = validationController;
this.firstName = '';
}
attached() {
ValidationRules
.ensure('firstName').displayName('Ime').required()
.on(this);
}
}
operator-detail.html
<template bindable="operatorvalue">
<div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="firstName" t='first_name'></label>
<input type="text" class="form-control" id="firstName" value.bind="operatorvalue.firstName ">
</div>
</div>
</div>
</template>
operatorStep.js
import { ElectronicRegistrationForm } from 'model/electronicRegistrationForm';
import { inject, NewInstance } from 'aurelia-framework';
import { RegistrationWizard } from 'registration/registrationWizard';
import { WizardStep } from 'registrationSteps/wizardStep';
import { ValidationController } from 'aurelia-validation';
import {bindable, bindingMode} from 'aurelia-framework';
#inject(ElectronicRegistrationForm, RegistrationWizard, NewInstance.of(ValidationController))
export class OperatorStep extends WizardStep {
#bindable({ defaultBindingMode: bindingMode.twoWay }) operator;
constructor(electronicRegistrationForm, regWiz, validationController) {
super(electronicRegistrationForm, regWiz, validationController);
this.operator = electronicRegistrationForm.operator;
this.operator.firstName='First name from operator step';
this.representative = electronicRegistrationForm.legalRepresentative;
}
}
operatorStep.html
<template>
<require from="common/operator-detail"></require>
<form validation-renderer="bootstrap-form">
<operator-detail operatorvalue.bind="operator"></operator-detail>
</form>
</template>
Declaring a bindable property on a template is for when you have a View without a ViewModel.
The bindable="operatorvalue" in your operator-detail.html doesn't work because you also have a ViewModel defined for this element. If you want to keep it this way then simply remove the bindable="operatorvalue" from the template and instead declare it in your ViewModel like so:
import {inject, NewInstance, bindable} from 'aurelia-framework';
import {ValidationRules, ValidationController} from 'aurelia-validation';
#inject(NewInstance.of(ValidationController))
export class OperatorDetail {
#bindable operatorvalue;
constructor(validationController) {
this.validationController = validationController;
this.firstName = '';
}
attached() {
ValidationRules
.ensure('firstName').displayName('Ime').required()
.on(this);
}
}
operator-detail.html would then become:
<template>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="firstName" t='first_name'></label>
<input type="text" class="form-control" id="firstName" value.bind="operatorvalue.firstName ">
</div>
</div>
</div>
</template>
Alternatively, you could make the bindable property declaration in the template work by simply deleting operator-detail.js and putting the validation stuff elsewhere. Then, rather than injecting OperatorDetail into the registration form you could new up the operator object like so:
import { LegalRepresentative } from 'common/legalRepresentative';
import { inject } from 'aurelia-framework';
#inject(LegalRepresentative)
export class ElectronicRegistrationForm {
constructor(legalRepresentative) {
this.operator = {};
this.legalRepresentative = legalRepresentative;
}
}
Both ways will work.
Working without ViewModels means putting the validation logic in electronicRegistrationForm.js for instance, which also means less code to write.
Working with ViewModels gives you more encapsulation, which is generally preferable if your view-specific logic is more complex than what you've shown here.
EDIT
In case you're using Google Chrome, I highly recommend using the aurelia context extension. You could then inspect the html and see that operator was defined in operator-step, but operatorvalue was undefined in operator-detail. This would tell you that the operatorvalue bindable declaration must be wrong, rather than something else.

Aurelia Custom Elements Inside of Custom Elements & Sharing Variables

How do I access & share variables between custom elements? I have the following files...
tip.html
<template>
<div class="tip-container">
<content select="tip-trigger"></content>
<content select="tip-content"></content>
</div>
</template>
tip.js
export class Tip {}
tip-trigger.html
<template>
<span class="tip-trigger" click.trigger="showTip()">
<content></content>
</span>
</template>
tip-trigger.js
export class TipTrigger {
showTip() {
console.debug(this);
}
}
tip-content.html
<template>
<span class="tip">
<content></content>
<span class="tip__close tip__close--default">×</span>
<span class="tip__color"></span>
</span>
</template>
tip-content.js
export class TipContent {}
In my Tip class I would like to have a variable name visible. When showTip is triggered visible would be set to true, which I would then use to add a class in tip-content.html. How can I share variables between these custom elements to do this?
The idea is to create an element to show tip pop-ups where any type of content can be the trigger and any type of content can be displayed when triggered. Basic example:
<tip>
<tip-trigger><button>?</button></tip-trigger>
<tip-content><div>Here is some helpful info...</div></tip-content>
</tip>
Here is a solution to your problem in Plunker.
Note that the tip-trigger and tip-content elements are just replaceable parts of the template. They don't needed to be components themselves (that confused me a lot in the "original" custom elements article).
app.html:
<template>
<require from="tip"></require>
<tip>
<tip-trigger><button>?</button></tip-trigger>
<tip-content><div>Here is some helpful info...</div></tip-content>
</tip>
</template>
tip.html:
<template>
<div class="tip-container">
<div>
<div click.trigger="showContent()">
<content select="tip-trigger"></content>
</div>
</div>
<div show.bind="contentVisible">
tip content:
<content select="tip-content"></content>
</div>
</div>
</template>
tip.js:
export class Tip {
showContent(){
this.contentVisible = !this.contentVisible;
}
}
Do you just need to turn Tip into a service-like class and import it?
export class Tip {
constructor() {
this.visible = false;
}
show() {
this.visible = true; // Or whatever to show the content
}
hide() {
this.visible = false;
}
}
Then:
import {inject} from 'aurelia-framework';
import {Tip} from './tip';
#inject(Tip)
export class TipTrigger {
constructor(tip) {
this.tip = tip;
}
showTip() {
this.tip.show();
// Or I suppose you could access 'visible' directly
// but I like the implementation details a method call offers.
}
}
*Disclaimer: This is untested.

Aurelia Validation Matches

I am trying to work out how I would do a startsWith? This is a custom element, it needs to validate that the string starts with a "\"
<template>
<input disabled.bind="readonly" type="text" class="form-control" value.bind="value">
</template>
import {customElement, bindable, inject, bindingMode} from 'aurelia-framework';
import {activationStrategy} from 'aurelia-router';
import $ from 'jquery';
import {Validation} from 'aurelia-validation';
#customElement('url')
#bindable({name: 'value', attribute: 'value', defaultValue: '', defaultBindingMode: bindingMode.twoWay})
#bindable({name: 'readonly', attribute: 'disabled', defaultValue: false, defaultBindingMode: bindingMode.oneWay})
#inject(Element, Validation)
export class Url {
constructor(element, validation) {
this.element = element;
this.validation = validation.on(this)
.ensure(this.element)
.isNotEmpty()
.containsNoSpaces()
.matches('/^[\].*/');
}
bind(){
$('.input', this.element).val(this.value);
if(this.readonly){
$('.input', this.element).attr('readonly', 'readonly');
}
}
}
I've looked at http://aurelia.io/validation/#/logical-operators and I think Im doing it right but it throws an error: inner error: TypeError: path.split is not a function
The function ensure() accepts the name of validated field, not the element
No need to surround your regex with quotes
It should be something like
this.validation = validation.on(this)
.ensure('value')
.isNotEmpty()
.containsNoSpaces()
.matches(/^\\.*$/);