Aurelia -- Route Change on Form Submission Issue - aurelia

Aurelia newbie here and I have hit a wall.
So, this code works just fine and the route change happens, but it only happens after the Submit button on the home.html file is clicked TWICE. On the first Submit button click, I get the following error: ERROR [app-router] Error: Route not found: /anonymous-wow-armory-profile/.
My question is why does it work after two form submissions, but not the first one? I know I am missing something in the process here.
home.html
<template>
<div class="container-fluid">
<div class="row">
<div class="col-md-12 nav-home text-center">
Create Profile
Bug Report
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="logo">
<img src="dist/assets/images/logo.png" alt="Logo" />
</div>
</div>
</div>
<div class="row row-bottom-pad">
<div class="col-md-4"></div>
<div class="col-md-4">
<div class="profile-creation-box">
<div class="box-padding">
<strong>Masked Armory</strong> is the most well known anonymous World of Warcraft (WoW) profile source in the Real Money Trading (RMT) market. We take everything to the next level with offering alternate gear sets, sorted reputation display, Feat of Strength / Legacy achievement display, and much more!<br /><br />
Come make a profile at Masked Armory today and see that we are the best solution for all of your anonymous WoW Armory profile needs!
</div>
</div>
</div>
<div class="col-md-4"></div>
</div>
<div class="row">
<div class="col-md-4"></div>
<div class="col-md-4 container-bottom-pad">
<div class="profile-creation-box">
<div class="box-padding">
<form class="form-horizontal" role="form" submit.delegate="submit()">
<div class="form-group">
<label class="col-sm-3 control-label">Region</label>
<div class="col-sm-9">
<label class="radio-inline">
<input type="radio" name="region_name" value="us" checked.bind="postData.region"> United States
</label>
<label class="radio-inline">
<input type="radio" name="region_name" value="eu" checked.bind="postData.region"> Europe
</label>
</div>
</div>
<div class="form-group">
<label for="server_name" class="col-sm-3 control-label">Server</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="server_name" placeholder="Server Name" value.bind="postData.serverName">
</div>
</div>
<div class="form-group">
<label for="character_name" class="col-sm-3 control-label">Character</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="character_name" name="character_name" placeholder="Character Name" value.bind="postData.characterName">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<div class="checkbox">
<label>
<input type="checkbox" id="altgear" name="altgear"> Add Alternate Gearset
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<button type="submit" class="btn btn-danger">Create Armory Profile</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-4"></div>
</div>
</div>
</template>
home.js
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-http-client';
import {Router} from 'aurelia-router';
#inject(Router)
export class Home {
postData: Object = {};
data: string = '';
code: string = '';
loading: boolean = false;
http: HttpClient = null;
apiUrl: string = 'http://localhost:8000/api/v1';
constructor(router) {
this.http = new HttpClient().configure(x => {
x.withBaseUrl(this.apiUrl);
x.withHeader('Content-Type', 'application/json');
});
this.maRouter = router;
}
submit() {
console.log(this.postData);
this.http.post('/armory', JSON.stringify(this.postData)).then(response => {
this.data = response.content;
this.code = response.statusCode.toString();
this.loading = false;
});
this.maRouter.navigateToRoute('armory', {id: this.data});
}
}
armory.js
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-http-client';
export class Armory {
postData: Object = {};
data: string = '';
code: string = '';
loading: boolean = false;
http: HttpClient = null;
apiUrl: string = 'http://localhost:8000/api/v1';
profileId: number = 0;
constructor() {
this.loading = true;
this.http = new HttpClient().configure(x => {
x.withBaseUrl(this.apiUrl);
x.withHeader('Content-Type', 'application/json');
});
}
activate(params, routeConfig) {
this.profileId = params.id;
this.getArmoryData();
}
getArmoryData() {
return this.http.get("/armory/" + this.profileId).then(response => {
this.data = response.content;
console.log(this.data);
this.code = response.statusCode.toString();
this.loading = false;
});
}
}
What am I missing here?
Thanks for your help!

Please, provide your router configuration
Anyway I see some issues already. You try to navigate when this.data is not set, just wait for response:
this.http.post('/armory', JSON.stringify(this.postData)).then(response => {
this.data = response.content;
this.code = response.statusCode.toString();
this.loading = false;
this.maRouter.navigateToRoute('armory', {id: this.data});
});
and we do activate page only if this.getArmoryData() succeed here (if needed), also canActivate() maybe used too
activate(params, routeConfig) {
this.profileId = params.id;
return this.getArmoryData();
}
also would be better to set this.loading = true;, inside armory .activate() and in home.js in submit() before sending data

Related

How can I make my section hide only after the submit button is pressed. right now the section disappears after I press one letter

vue.js, how can I make my section hide only after the submit button is pressed. right now the section disappears after I press one letter. I want the V-if and V-else to activate only after the user has submitted their request. or if routing the results on to a different page would easier id like to go that route also.
<template>
<div class="home">
<section id="whiteClawVideo" class="videoWrapper d-block w-100">
<div class="video-container fluid">
<iframe width="100%" height="600" src="https://www.youtube.com/embed/JORN2hkXLyM?
autoplay=1&loop=1" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope;
picture-in-picture" allowfullscreen></iframe>
</div>
</section>
<form #submit.prevent="SearchMovies()" class="search-box">
<input type="text" placeholder="What are you looking for? " v-model="search" />
<input type="submit" value="Search">
</form>
<div class="movies-list" v-if="search !== ''" >
<div class="container">
<div class="row">
<div class="col-3" v-for="movie in movies" :key="movie.imdbID">
<router-link :to="'/movie/'+movie.imdbID" class="movie-link">
<img class="movieImg" height="100%" :src="movie.Poster" alt="Movie Poster" />
<div class="type">{{ movie.Type }}</div>
<div class="detail">
<p class="year">{{movie.Year}}</p>
<h3>{{ movie.Title }}</h3>
<p>{{movie.imdbID}}</p>
</div>
</router-link>
</div>
</div>
</div>
</div>
<div class="container" v-else>
<MovieSection />
<SecondMovieSection />
</div>
</div>
</template>
import { ref } from 'vue';
import env from '#/env.js';
import MovieSection from '#/components/MovieSection.vue';
import SecondMovieSection from '#/components/SecondMovieSection.vue'
export default {
components: {
MovieSection,
SecondMovieSection
},
setup () {
const search = ref("");
const movies = ref([]);
const SearchMovies = () => {
if (search.value !== "") {
fetch(`API_HERE`)
.then(response => response.json())
.then(data => {
console.log(data)
movies.value = data.Search;
})
}
}
return {
search,
movies,
SearchMovies
}
}
}
Well, it closes once you type a single character because search is a model - it updates on every keypress you do within input it's bound to. What you wanna do instead is hide form based on whether you have entries in your movies array or not, so try changing v-if="search !== ''" to v-if="!movies.length"

Method Updating Data Twice on Form Submission

I have a component that is a form that I input a name and it updates a value on a field on the backend based on which input the name is. Right now I have two inputs (host and scout) and they work fine if I just fill one input. My problem is, when I fill both inputs, the name on the host input will always get updated twice while the name on the scout field will work just fine. Not sure if I was clear enough.
Here is the code for the component so far
<template>
<div class="add-wave">
<h3>Add Wave</h3>
<div class="row">
<form #click.prevent="addwave()" class="col s12">
<div class="row">
<div class="input-field col s12">
<input type="text" v-model="host" />
<label class="active">Host</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input type="text" v-model="scout" />
<label class="active">Scout</label>
</div>
</div>
<button type="submit" class="btn">Submit</button>
<router-link to="/member" class="btn grey">Cancel</router-link>
</form>
</div>
</div>
</template>
<script>
import { db, fv } from "../data/firebaseInit";
export default {
data() {
return {
host: null,
scout: null
};
},
methods: {
addwave() {
this.addhost();
db.collection("members")
.where("name", "==", this.scout)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
doc.ref.update({
scout: fv.increment(1),
total: fv.increment(1)
});
});
});
},
addhost() {
db.collection("members")
.where("name", "==", this.host)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
doc.ref.update({
host: fv.increment(1),
total: fv.increment(1)
});
});
});
}
}
};
</script>
I'm not sure why is it updating twice only when I fill both input fields.

Angular prefilled form data becomes null when submitting

I have created an Angular form to update the user details. The fields values of the form are filled by the user details fetched from the backend when the form loads. If the user wishes to update any field they can update and submit the form. But the field values of the fields which user didn't change are set as null even though those fields are initialized at the beginning. Can someone please explain how to get the prefilled unchanged field values when submitting the form?
The HTML file is this,
<app-navbar></app-navbar>
<div class="container">
<form [formGroup]="profileForm" style="margin-top: 60px;" disabled="true" (ngSubmit)="onSubmit()">
<input type="file" id="file" (change)="onFileSelected($event)" accept="image\jpeg" formControlName="profPic">
<div class="row">
<!--Profile Picture-->
<div class="col-12 col-md-4 d-flex justify-content-center">
<label for="file">
<a (mouseenter)="hoverIdx = 1"
(mouseleave)="hoverIdx = -1"
id="overlay">
<span [ngClass]="{ 'overlay-icon': true, 'hide': hoverIdx !== 1 }"
class="rounded-circle">
<fa-icon [icon]="cameraIcon" class="icon"></fa-icon>
</span>
<img
[src]="profPic"
class="rounded-circle"
width="300"
height="300"
/>
</a>
</label>
<div class="col-md-2 align-self-end ml-auto p-2" id="deleteIcon">
<fa-icon [icon]="deleteIcon"></fa-icon>
</div>
</div>
<div class="col-12 col-md-8">
<div class="card" style="margin-bottom: 20px;">
<div class="card-body" >
<!---Intro-->
<div class="row" style="font-size: 60px;">
<div class="col-12">Hi, {{ fNme }}</div>
</div>
<!--first name & last name-->
<div class="row" style="margin-top: 10px;">
<div class="col-12 col-md-6">
<mat-form-field appearance="outline">
<input formControlName="fName"
matInput placeholder="First Name"
[value]="fNme"
(change)="fNmeChnge = true"/>
</mat-form-field>
</div>
<div class="col-12 col-md-6">
<mat-form-field appearance="outline">
<input formControlName="lName"
matInput placeholder="Last Name"
[value]="lNme"
(change)="lNmeChnge = true" />
</mat-form-field>
</div>
</div>
<!--row-->
<!-- email & country-->
<div class="row" style="margin-bottom: 25px;">
<div class="col-12 col-md-6">
<mat-form-field appearance="outline">
<input formControlName="email"
matInput placeholder="Email"
[value]="email"
(change)="emailChnge = true"/>
</mat-form-field>
</div>
<div class="col-12 col-md-6">
<mat-form-field appearance="outline" >
<input formControlName="country"
matInput placeholder="Country"
[value]="country"
(change)="countryChnge = true"/>
</mat-form-field>
</div>
</div>
<!--row-->
</div>
</div>
<button type="button" class="btn btn-primary float-right" style="margin-left:10px" type="submit">Save</button>
<button type="button" class="btn btn-outline-primary float-right" (click)="cancel()">Cancel</button>
</div><!--col-md-8-->
</div><!--row-->
</form>
</div><!--container-->
The component file is this,
import { User } from './../shared/user.model';
import { F_NAME, L_NAME, AUTHENTICATED_USER, PROF_PIC, BASE64URL } from './../../app.constants';
import { Component, OnInit, Inject } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { AuthenticationService } from '../service/authentication.service';
import { MAT_DIALOG_DATA, MatDialogRef, MatDialog } from '#angular/material/dialog';
import { DialogOverviewExampleDialog } from './dialog';
import { faCamera, faTrashAlt } from '#fortawesome/free-solid-svg-icons';
import { DomSanitizer } from '#angular/platform-browser';
#Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.css']
})
export class ProfileComponent implements OnInit {
user: User;
cameraIcon = faCamera;
deleteIcon = faTrashAlt;
profPic: any;
profileForm: FormGroup;
email: string;
fNme: string;
lNme: string;
country: any;
selectedFile: File = null;
base64Data: any;
fNmeChnge: any;
lNmeChnge: any;
countryChnge: any;
emailChnge: any;
title = 'Profile';
constructor(
private service: AuthenticationService,
public dialog: MatDialog,
private sanitizer: DomSanitizer
) {
}
ngOnInit() {
this.email = sessionStorage.getItem(AUTHENTICATED_USER);
this.profileForm = new FormGroup({
fName: new FormControl(null, [Validators.required]),
lName: new FormControl(null, Validators.required),
email: new FormControl(null, [Validators.required, Validators.email]),
country: new FormControl(null, Validators.required)
});
this.service.getUser(this.email)
.subscribe(res => {
this.fNme = res.fNme;
this.lNme = res.lNme;
this.country = res.country;
this.profPic = BASE64URL + res.profPic ;
});
}
openDialog(): void {
const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
width: '500px'
});
}
onFileSelected(event){
this.selectedFile = event.target.files[0] as File;
const reader = new FileReader();
reader.onload = e => this.profPic = reader.result;
reader.readAsDataURL(this.selectedFile);
}
onSubmit() {
const fd = new FormData();
fd.append('file', this.selectedFile);
this.service.uploadImage(fd, this.email);
this.user = new User(this.profileForm.get('fName').value,
this.profileForm.get('lName').value,
this.profileForm.get('country').value,
this.profileForm.get('email').value);
console.log(this.user);
this.service.updateProfile(this.user, this.email);
// .subscribe(res => {
// this.fNme = res.fNme;
// this.lNme = res.lNme;
// this.country = res.country;
// this.profPic = BASE64URL + res.profPic ;
// });
this.ngOnInit();
}
I gets the user data from the server via these codes,
uploadImage(file: FormData, email: string) {
this.http.put<any>(`${API_URL}/profile-picture/${email}`, file)
.subscribe(res => {console.log(res); });
}
updateProfile(user: User, email: string) {
this.http.put<any>(`${API_URL}/profile/${email}`, user).subscribe();
}
getUser(email: string) {
return this.http.get<any>(`${API_URL}/user/${email}`);
}

Data sent from one Vue component to another remains reactive

I have an input-component which has a form which collects start and finish times, job number and a select option.
This is attached to a data property with v-model.
This is then emitted with Event.$emit('addedData', this.hours)
In the display-component the Event.$on takes this data and checks an attribute and based on the check adds it to another data property (array) with this.todays_hours.push().
The template then displays this reactively using v-for in the template.
To this point all works fine. However when I then attempt to add another line of hours the hours already displayed change reactively with the input.
As my input-component also posts to a database with axios if I reload the page all is displayed correctly.
input-component
<template>
<div>
<div class="row">
<div class="col-2">
<input hidden="" v-model="hours.day">
</div>
<div class="col-2" >
<input type="time" v-model="hours.start">
</div>
<div class="col-2" >
<input type="time" v-model="hours.finish">
</div>
<div class="col-2" >
<input type="number" v-model="hours.job_number">
</div>
<div class="col-2" >
<select v-model="hours.climbing">
<option selected="selected" value="0">No</option>
<option value="1">Yes</option>
</select>
</div>
<div class="col-2" >
<button #click="onSave" class="btn-success btn-sm">Save</button>
</div>
</div>
<hr>
</div>
</template>
<script>
export default {
name: 'InputHoursComponent',
props: ['employeeId', 'dayCheck', 'weekEnding'],
data() {
return {
hours: {
start: "",
finish: "",
job_number: "",
climbing: 0,
day: this.dayCheck
},
climbing_select: ['No', 'Yes'],
}
},
methods: {
onSave()
{
axios.post('/payroll', {
employee_id: this.employeeId,
week_ending: this.weekEnding,
start: this.hours.start,
finish: this.hours.finish,
job_number: this.hours.job_number,
climbing: this.hours.climbing,
day: this.dayCheck
})
.then(response => {})
.catch(e => {this.errors.push(e)});
let data = this.normalizeProp(this.hours, s, true)
Event.$emit('onAddedEntry', data);
console.log("passed data:", this.hours);
}
}
}
</script>
display-component
<template>
<div>
<div v-for="item in todays_hours">
<div class="row">
<div class="col-2">
<div hidden="" ></div>
</div>
<div class="col-2" >
<div v-text="item.start"></div>
</div>
<div class="col-2" >
<div v-text="item.finish"></div>
</div>
<div class="col-2" >
<div v-text="item.job_number"></div>
</div>
<div class="col-2" >
<div v-text="(item.climbing)?'Yes':'No'"></div>
</div>
<div class="col-2" >
<button #click="onEdit" class="btn-warning btn-sm mb-1">Edit</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DisplayHoursComponent',
props: ['dayCheck', 'hoursWorked'],
data() {
return {
hours_list: this.hoursWorked,
todays_hours: []
}
},
mounted() {
for (var i = 0; i < this.hours_list.length; i++) {
if (this.hours_list[i].day === this.dayCheck) {
this.todays_hours.push(this.hours_list[i])
}
}
Event.$on('onAddedEntry', (check) => {
if(check.day === this.dayCheck){
this.todays_hours.push(check);
}
})
},
methods: {
onEdit()
{
}
}
}
</script>
Can someone please help me?
Try pushing a copy of check instead of check itself.
Event.$on('onAddedEntry', (check) => {
if(check.day === this.dayCheck){
this.todays_hours.push({...check});
}
})
You could also make the copy when you emit the event instead.

Aurelia custom element access data from child to parent view model

I am new to Aurelia and need help accessing values from custom element in another view.
I have a scenario where I would like to share input file for attachment in multiple forms. One of the approach I am taking is to create custom element for input files that can be shared with multiple forms in web application.
Below is my code:
Custom element called FilePicker which will be shared between multiple forms. In my Request paged I inserted the custom element called:
<file-picker router.bind="router" accept="image/*" multiple="true" ></file-picker>
My requirement is to access property called validFiles which is an array. I would like to use values from validFiles to custom object in Request view model called formData['attachment']
TS:
import { customElement, useView, bindable, bindingMode } from 'aurelia-framework';
#customElement('file-picker')
#useView('./file-picker.html')
export class FilePicker {
#bindable accept = '';
#bindable multiple = false;
#bindable({ defaultBindingMode: bindingMode.twoWay }) files: FileList;
validFiles = [];
input: HTMLInputElement;
// Validate function for on change event to input file.
public filesChanged() {
if (!this.files) {
this.clearSelection();
}
if (this.files) {
this.processFiles();
}
}
// Trigger on button click
public triggerInputClick() {
this.input.click();
}
// Find value in array of object
private findValinArrObj(arr, key, val) {
return arr.map(function (v) { return v[key] }).indexOf(val);
}
// Process file for each new files uploaded via browser
private processFiles() {
if (this.files) {
for (let i = 0; i < this.files.length; i++) {
let findFile = this.findValinArrObj(this.validFiles, 'name', this.files.item(i).name);
if (findFile === -1) {
this.validFiles.push(this.files.item(i));
}
}
this.clearSelection();
}
}
// Remove file from fileNames and validFiles array
public removeByFileName(fileName) {
if (this.validFiles) {
for (let i = 0; i < this.validFiles.length; i++) {
if (this.validFiles[i].name === fileName) {
this.validFiles.splice(i, 1);
}
}
}
}
// Clear input file in DOM
private clearSelection() {
this.input.type = '';
this.input.type = 'file';
}
}
HTML:
<template>
<input type="file" accept.bind="accept" multiple.bind="multiple"
files.bind="files" ref="input"
style="visibility: hidden; width: 0; height: 0;">
<button class="btn btn-primary" click.delegate="triggerInputClick()">
<slot>Browse...</slot>
</button>
<ul class="list-group" if.bind="validFiles">
<li class="list-group-item" repeat.for="file of validFiles" style="padding: 0; border:none;">
${file.name} <span class="glyphicon glyphicon-remove text-danger" style="cursor: pointer;" click.delegate="removeByFileName(file.name)"></span>
</li>
</ul>
</template>
Parent View Model:
TS:
export class Request {
pageTitle: string = "Request Page";
title: string = '';
description: string = '';
businessValue: string = '';
emails: string = '';
formData: object = {};
public postData() {
this.formData['title'] = this.title;
this.formData['description'] = this.description;
this.formData['businessValue'] = this.businessValue;
this.formData['emails'] = this.emails;
this.formData['attachment'] = [];
console.log(this.formData);
}
}
HTML:
<template>
<require from="./request.css"></require>
<require from="../../resources/elements/file-picker"></require>
<div class="panel panel-primary">
<div class="panel-heading">${pageTitle}</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label for="title" class="col-sm-2 control-label">Title</label>
<div class="col-sm-10">
<input type="text" value.two-way="title" class="form-control" id="title" placeholder="Brief one-line summary of the request">
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-2 control-label">Description</label>
<div class="col-sm-10">
<input type="text" value.two-way="description" class="form-control" id="description" placeholder="Detailed description of the request">
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-2 control-label">Business Value</label>
<div class="col-sm-10">
<input type="text" value.two-way="businessValue" class="form-control" id="description" placeholder="Description of how this offers business value">
</div>
</div>
<div class="form-group">
<label for="emails" class="col-sm-2 control-label">Email</label>
<div class="col-sm-10">
<input type="text" value.two-way="emails" class="form-control" id="emails" placeholder="Provide email address">
</div>
</div>
<div class="form-group">
<label for="exampleInputFile" class="col-sm-2 control-label">Attachment(s)</label>
<div class="col-sm-10">
<file-picker router.bind="router" accept="image/*" multiple="true" ></file-picker>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<button type="submit" class="btn btn-default pull-right" click.trigger="postData()">Submit</button>
</div>
</div>
</form>
</div>
</div>
</template>
Any help is really appreciated. :)
You can set up two-way-binding on your Request view, to bind the validFiles in your child view (file-picker) to a variable in your parent view (Request):
request.html:
<file-picker valid-files.two-way="validFiles"></file-picker>
request.ts:
public validFiles: File[];
file-picker.ts:
#bindable
public validFiles: File[];
This way any changes made to the validFiles object in either the child or the parent will update the object in both viewmodels.