I found this question which is getting the same error however in my case the model is structured using an interface and so I want to access it through this e.g .ValidationRules.ensure((c: Client) => c.client.clientLastName) but I think my syntax is incorrect.
its a typescript implementation.
I have this working in another form (login) which is a basic form but this one utilises an interface for inputs. If I use a simple bind without a variable based on an interface it works.
Here is my validationRules for a single text input - clientLastName
ValidationRules.ensure((c: Client) => c.client.clientLastName)
.displayName("Last name")
.required()
.on(Client);
aurelia-logging-console.js:47 ERROR [app-router] Error: Unable to parse accessor function:
function (c) { return c.client.clientLastName; }
The view is structured as follows:
<div class="col-md-6">
<div class="form-group">
<label class="control-label col-md-3">Last Name:</label>
<div class="col-md-9">
<input type="text" value.bind="client.clientLastName & validate" class="form-control" id="lastname" placeholder="Last Name...">
</div>
</div>
</div>
How do I do syntax for validation when the binding is through a variable set as an interface?
The interface:
interface ClientDetails {
clientId: number;
clientNo: number;
company: boolean;
companyName: string;
abn: string;
isWarrantyCompany: boolean;
requiresPartsPayment: boolean;
clientFirstName: string;
clientLastName: string;
email: string;
mobilePhone: string;
phone: string;
notes: string;
address: AddressDetails;
jobs: any;
bankName: string;
bankBSB: string;
bankAccount: string;
active: boolean;
deActivated: string;
activity: boolean;
dateCreated: string;
dateUpdated: string;
creatorId: number;
creatorName: string;
}
My view-model:
import { HttpClient } from "aurelia-fetch-client";
import { autoinject, inject, NewInstance, PLATFORM } from "aurelia-framework";
import { Router, activationStrategy } from "aurelia-router";
import {
ValidationControllerFactory,
ValidationController,
ValidationRules
} from "aurelia-validation";
import { BootstrapFormRenderer } from "../../../../services/bootstrapFormRenderer/bootstrapFormRenderer";
//import from '../../../../services/customValidationRules/customValidationRules'
import { AuthService } from "../../../../services/auth/auth-service"
#autoinject
export class Client {
controller: ValidationController;
client: ClientDetails;
hasClientId: boolean;
heading: string = "New Client";
headingIcon: string = "fa-user-plus";
constructor(
private authService: AuthService,
private router: Router,
private controllerFactory: ValidationControllerFactory
) {
this.router = router;
this.controller = controllerFactory.createForCurrentScope();
this.controller.addRenderer(new BootstrapFormRenderer());
}
// Required to reload new instance.
determineActivationStrategy() {
return activationStrategy.replace;
}
activate(parms, routeConfig) {
this.hasClientId = parms.id;
if (this.hasClientId) {
const headers = this.authService.header();
fetch("/api/Client/edit/" + parms.id, {
method: "GET",
headers
})
.then(response => response.json())
.then(data => {
console.log("data: ", data);
this.client = data
console.log("CLIENT: ", this.client);
})
this.heading = "Edit Client"; // An id was submitted in the route so we change the heading as well.
this.headingIcon = "fa-pencil-square-o";
}
return null;
}
submitClient() {
console.log("gets Here")
}
}
I think the
ValidationRules.ensure((c: Client) => c.client.clientLastName)
.displayName("Last name")
.required()
.on(Client);
should be
ValidationRules.ensure((c: ClientDetails) => c.clientLastName)
.displayName("Last name")
.required()
.on(ClientDetails);
see also here
Related
I have a searchbar where user can get characters filtered by his input.
//Dummy input to check if it works
<input
class="form-control"
type="text"
placeholder="Search"
v-model="searchPhrase"
/>
<Character
v-for="character in resultSearching"
:key="character.id"
:photo="character.image"
:characterID="character.id"
:name="character.name"
:gender="character.gender"
:species="character.species"
:lastEpisode="character.episode[character.episode.length - 1].episode"
:character="character"
/>
</div>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import Character from "./Character.vue";
import { CharactersApiI } from "#/models/models";
import { Getter } from "vuex-class";
#Component({
components: {
Character,
},
})
export default class Characters extends Vue {
#Getter("characters/getCharacters") characters!: CharactersApiI[];
#Getter("characters/getLoading") loading!: boolean;
#Getter("characters/getError") error!: boolean;
#Getter("characters/getSearchPhrase") searchPhrase!: string;
#Getter("characters/getHeaders") headers!: string[];
get resultSearching(): any {
return this.characters.filter((character) => {
return character.name
.toLowerCase()
.match(this.searchPhrase.toLowerCase());
});
}
set resultSearching(newValue: any) {
this.searchPhrase = newValue.target.value.toLowerCase();
}
I get state stored in characters.ts by #Getter from vuex-class and it gets its value with :value on input or v-model but it starts breaking instantly after I try to write something there.
Updated version due to LLai's answer(working):
//characters.ts
#Mutation
updateMessage(e: { target: {value: string}}) {
this.searchPhrase = e.target.value;
}
//characters.vue
export default class Characters extends Vue {
#Getter("characters/getCharacters") characters!: CharactersApiI[];
#Getter("characters/getSearchPhrase") searchPhrase!: string;
#Mutation("characters/updateMessage") updateMessage!: (e: {
target: { value: string };
}) => void;
get resultSearching(): CharactersApiI[] {
return this.characters.filter((character) => {
return character.name
.toLowerCase()
.match(this.searchPhrase.toLowerCase());
});
}
handleInput(e: {target: {value: string}}) {
this.updateMessage(e);
}
I have build a page that use a search bar to filter through an *ngFor array. When I type in the search bar it behaves normally, but when I delete or back space text it does not update. It works normally if I pull an array from a static list from a data service but not with the data I am pulling from an ApolloQueryResult. Any help would be greatly appreciated.
html
<ion-content padding>
<div *ngIf="loading">Loading...</div>
<div *ngIf="error">Error loading data</div>
<ion-toolbar>
<ion-searchbar [(ngModel)]="searchTerm" (ionChange)="setFilteredItems()" showCancelButton="focus"></ion-searchbar>
</ion-toolbar>
<ion-card *ngFor="let data of info">
<ion-card-content>
{{data.TypeOfNotification}}
</ion-card-content>
</ion-card>
</ion-content>
ts
import { Component, OnInit } from '#angular/core';
import { Apollo } from 'apollo-angular';
import { ApolloQueryResult } from 'apollo-client';
import { QueryTodoService } from '../../services/query-todo.service';
import { Storage } from '#ionic/storage';
#Component({
selector: 'app-tab-to-do',
templateUrl: './tab-to-do.page.html',
styleUrls: ['./tab-to-do.page.scss'],
})
export class TabToDoPage implements OnInit {
info: any;
error: any;
loading: boolean;
searchTerm: string;
constructor(
private apollo: Apollo,
private queryTodoService: QueryTodoService,
private storage: Storage
) { }
setFilteredItems() {
this.info = this.filterItems(this.searchTerm);
}
filterItems(searchTerm){
return this.info.filter((item) => {
return item.TypeOfNotification.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1;
});
}
// or
setFilteredItemsAlt(event) {
const searchTerm = event.srcElement.value;
if (!searchTerm) {
return;
}
this.info = this.info.filter(item => {
if (item.TypeOfNotification && searchTerm) {
if (item.TypeOfNotification.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1) {
return true;
}
return false;
}
});
}
ngOnInit() {
this.storage.get('AccessToken').then((_token) => {
this.apollo.watchQuery({
query:this.queryTodoService.ToDoQuery,
fetchPolicy: 'cache-first',
})
.valueChanges.subscribe((result: ApolloQueryResult<any> ) => {
this.loading = result.loading;
this.info = result.data.notifications.Notifications;
console.log('first info', this.info );
this.error = result.errors;
});
});
}
}
It's because you are overwriting this.info every time you fire setFilteredItems():
setFilteredItems() {
//Overwrite this.info with new filtered data set.
this.info = this.filterItems(this.searchTerm);
}
The old values were filtered out and no longer exist - which is why *ngFor="let data of info" is not displaying them.
What you can do is set a new variable equal to this.info in your ts file - e.g. "dataDisplay":
dataDisplay: Array<object> = this.info;
Set this variable during an Ionic lifecycle change like ionViewWillEnter or whenever this.info gets set.
Then swap out the variable in setFilteredItems():
setFilteredItems() {
this.dataDisplay = this.filterItems(this.searchTerm);
}
Now change your *ngFor to the new variable:
*ngFor="let data of dataDisplay"
This should do the trick for you, because now filterItems(searchTerm) is always filtering the full, original this.info data set.
I'm trying to do a simple form post to my controller but I keep getting the following error : cannot access username of undefined. As far as i can tell i'm initializing the usermodel in my login component but have no idea why it's still erroring.
Does anyone have any ideas?
html
<form #loginForm="ngForm" (ngSubmit) ="OnSubmit(loginForm)">
<div class="form-row">
<div class="form-group col-md-6">
<input type="text" name="username" #username="ngModel" [(ngModel)]="user.username" class="form-control" />
</div>
<div class="form-group col-md-6">
<input type="text" name="password" #password="ngModel" [(ngModel)]="user.password" class="form-control" />
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-block btn-lg">login</button>
</div>
user.model.ts
export class UserModel {
username: string;
password: string;
}
user.service.ts
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { UserModel } from './user.model';
import { Inject, Injectable } from '#angular/core';
#Injectable()
export class UserService {
constructor(private http: HttpClient) { }
postUser(user: UserModel, #Inject('BASE_URL') baseUrl: string) {
return this.http.post(baseUrl + '/Test/Register', new HttpHeaders({ 'username': user.username, 'password': user.password })).subscribe(result => {
console.error(result)
}, error => console.error(error));
}
login.component.ts
import { Component, Inject, OnInit } from '#angular/core';
import { NgForm } from '#angular/forms';
import { UserModel } from './user.model';
import { UserService } from './user.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
user: UserModel
constructor(private userService: UserService) { }
ngOnInit() {
this.resetForm();
}
resetForm(form?: NgForm) {
if (form != null) {
form.reset();
this.user = {
username: '',
password: ''
}
}
}
onSubmit(form: NgForm) {
this.userService.postUser(form.value, "https://localhost:44327/");
};
This is caused by that you did not initialize the this.user when loading this page.
Try this:
resetForm(form?: NgForm) {
if (this.user == undefined) {
this.user = new UserModel();
}
if (form != null) {
form.reset();
this.user = {
username: '',
password: ''
}
}
}
I have created login function accordingly but the login function is not working. Whenever I enter the sample input (phoneNumber and password) it did not directing me to the HomePage. Details about API has been provided in the pictures below. Is there anything I am missing out? Please HELP me to solve this out.
API details:
HTML CODE:
<ion-header>
<ion-navbar>
<ion-title>
Login
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-list>
<form name="form"(ngSubmit)="login()" novalidate>
<ion-item>
<ion-label fixed>Phone Number</ion-label>
<ion-input type="number" name="phoneNumber" value="" [(ngModel)]="model.phoneNumber" required></ion-input>
</ion-item>
<ion-item>
<ion-label fixed>Password</ion-label>
<ion-input type="password" name="password" value="" [(ngModel)]="model.password" required></ion-input>
</ion-item>
<ion-item>
<button ion-button>Enter</button>
</ion-item>
</form>
</ion-list>
</ion-content>
.TS CODE:
import { Component } from '#angular/core';
import { NavController, AlertController, LoadingController, ToastController } from 'ionic-angular';
import { HomePage } from '../home/home';
import { apiService } from '../../providers/api-service.service';
import { AuthService } from '../../providers/auth-service.service';
#Component({
selector: 'page-login',
templateUrl: 'login.html'
})
export class LoginPage {
model : any = {};
response: any[] = [];
constructor(public navCtrl: NavController, public apiService: apiService, public toastCtrl: ToastController, public alertCtrl: AlertController, public loadingCtrl: LoadingController, public AuthService: AuthService){}
loginUser(){
this.AuthService.login();
}
logoutUser(){
this.AuthService.logout();
}
nextPage(data){
this.navCtrl.push(HomePage,data).catch(err => {
let alert = this.alertCtrl.create({
title: 'Something',
subTitle: 'something something',
buttons: ['OK']
});
alert.present();
});
}
errorToast(){
let toast = this.toastCtrl.create({
message: 'Cannot login',
duration: 3000
});
toast.present();
}
logoutout(){
let loader = this.loadingCtrl.create({
content: "Loging out.....",
duration: 1000
})
loader.present();
this.loginUser();
}
login(){
this.apiService.apiCall(this.model.phoneNumber, this.model.password)
.then(data => {
this.logoutout();
this.nextPage(data);
})
.catch(error => {
this.errorToast();
})
}
}
PROVIDERS CODE:
api-service.service.ts
import {Injectable} from '#angular/core';
import {Http, Headers, RequestOptions} from '#angular/http';
import 'rxjs/add/operator/map'
#Injectable()
export class apiService{
constructor(private http: Http){}
apiCall(phoneNumber, password){
let headers = new Headers({
"X-Auth-PhoneNumber": '',
"X-Auth-Password": '',
"SW-Version": '',
"Device-Id": '',
"Device-Model": ''
})
let options = new RequestOptions({
headers: headers
});
return this.http.get('https://api.keyway.com.my/client/mobile/verification'+ phoneNumber + password, options)
.map(res => res.json())
.toPromise();
}
}
auth-service.service.ts
import {Injectable} from '#angular/core';
#Injectable()
export class AuthService{
private isLoggedIn = false;
constructor(){}
login(): void{
this.isLoggedIn = true;
}
logout(): void{
this.isLoggedIn = false;
}
authenticated():boolean{
return this.isLoggedIn;
}
}
Because you are sending username and password with request URL but as per request defination you need to send in headers so,please made some changes like below -
apiCall(phoneNumber, password){
let headers = new Headers({
"X-Auth-PhoneNumber": phoneNumber',
"X-Auth-Password": password,
"SW-Version": '',
"Device-Id": '',
"Device-Model": ''
})
let options = new RequestOptions({
headers: headers
});
return this.http.get('https://api.keyway.com.my/client/mobile/verification', options)
.map(res => res.json())
.toPromise();
}
Also try to console output after request.
login(){
this.apiService.apiCall(this.model.phoneNumber, this.model.password)
.then(data => {
console.log(data); // Console Output
this.logoutout();
this.nextPage(data);
})
.catch(error => {
console.log(error); // Console Error output
this.errorToast();
})
}
So I'm trying to set up a login page for a Vue application. The login component references the imported loginService.login() function, however, when I test the code I get an error "Cannot read property 'login' of undefined." So I logged the entire loginService to the console and it came back as undefined. Why is it that when I access the imported loginService in webstorm it can access the service just fine, but when I try to use it at runtime it's undefined? Here is the (condensed) login component:
<div class="text-center py-4 mt-3 mb-1">
<button class="btn btn-primary " type="submit" style="width:300px" #click="login">Login</button>
</div>
<script>
import {router} from '../../../main'
import { LoginService } from '../login/loginService';
import { StateStorageService } from '../auth/stateStorageService';
import toastr from 'toastr'
export default {
name:'login',
loginService: new LoginService(),
stateStorageService: StateStorageService,
data: function() {
return {
authenticationError: false,
password: '',
rememberMe: false,
username: '',
credentials: {}
}
},
methods:{
login() {
this.loginService.login({
username: this.username,
password: this.password,
rememberMe: this.rememberMe
}).then(() => {
this.authenticationError = false;
if (router.url === '/register' || (/^\/activate\//.test(router.url)) ||
(/^\/reset\//.test(this.router.url))) {
router.navigate(['']);
}
const redirect = StateStorageService.getUrl();
if (redirect) {
this.stateStorageService.storeUrl(null);
this.router.push('/search');
}
}).catch(() => {
this.authenticationError = true;
});
},
And here is the loginService.js
import { Principal } from '../auth/principalService';
import { AuthServerProvider } from '../auth/authJwtService';
export class LoginService {
constructor() {
this.principal = new Principal();
this.authServerProvider = new AuthServerProvider();
}
login(credentials, callback) {
const cb = callback || function() {};
return new Promise((resolve, reject) => {
this.authServerProvider.login(credentials).subscribe((data) => {
this.principal.identity(true).then((account) => {
resolve(data);
});
return cb();
}, (err) => {
this.logout();
reject(err);
return cb(err);
});
});
}
The this in the context of your login method is the current Vue instance, which isn't the same as the base object being exported in this file. That base object contains all of the info for the constructor for the Vue instance, but the properties being passed don't get directly mapped to the generated Vue instance.
In order to make a property available on this like you want, you should set it on the object returned in the data function, not the base object being exported.
So for your loginService and stateStorageService properties:
data: function() {
return {
loginService: new LoginService(),
stateStorageService: StateStorageService,
authenticationError: false,
password: '',
rememberMe: false,
username: '',
credentials: {}
}
},
But, you also don't need to set the loginService property on the Vue instance to have access to it in your login method. You can simply instantiate a new LoginSevice outside of the scope of your exported object and then reference that in your Vue method:
import { LoginService } from '../login/loginService';
const loginService = new LoginService();
export default {
...
methods: {
login() {
loginService.login({
...
})
},
},
}