Custom validator and dinamic data in Angular8 - javascript

The first thing to say is that I have been searching for a long time, questions similar to the one I am going to ask and although I have found some, none have helped me with my problem. Having said this, I will explain the problem in question.
I am creating a custom validator for the file upload component. This validator must be able to validate the size of the files to be uploaded. At the moment I have this:
file-validator.ts
export function fileSizeValidator(maxFileSize: number, files: any[]) {
console.log(files)
const maxFileSizeToKb = maxFileSize * 1024;
return (): { [key: string]: any } | null => {
let forbidden = true;
files.forEach(file => {
if (file.file.size > maxFileSizeToKb) {
forbidden = false;
}
});
return forbidden ? { inValidSize: true } : null;
};
}
file-uploader.component.ts
...
ngOnInit() {
this.uploadForm = this.formBuilder.group({
filesToUpload: ['', fileSizeValidator(this.data.maxMbSize, this.rawFiles)],
});
}
...
file-uploader.component.html
...
<form [formGroup]="uploadForm" (ngSubmit)="onFormSubmit()">
<ng-container *ngIf="canUploadMultiple; else unique">
<input #file formControlName="filesToUpload" type="file" [accept]="allowedFilesToString" (click)="fileInputClick($event)" id="{{fileId}}" name="file" multiple (change)="onUploadFile($event.target.files)" />
</ng-container>
<ng-template #unique>
<input #file type="file" formControlName="filesToUpload" [accept]="allowedFilesToString" (click)="fileInputClick($event)" id="{{fileId}}" name="file" (change)="onUploadFile($event.target.files)"/>
</ng-template>
...
<button type="submit" [disabled]="!uploadForm.valid">Submit</button>
</form>
...
The problem is that when I add files to upload and the validator function is executed, the array of files always appears empty.
I have tried many things to make it work. I have tried to look for the value that I need inside the object control: AbstractControl that is passed to the function to see if from there I could find the array that is supposed to be passed to it when executing the validator function, but the only thing I find from array is the name of the files.
I have also tried to do a .bind() and an arrow function like this:
this.uploadForm = this.formBuilder.group({
filesToUpload: ['', fileSizeValidator(this.data.maxMbSize, this.rawFiles).bind(this)],
});
this.uploadForm = this.formBuilder.group({
filesToUpload: ['', fileSizeValidator(this.data.maxMbSize, (() => this.rawFiles)],
});
Another thing that I also thought might work is to put the validator back into the form once the dynamic array has been modified:
...
this.rawFiles.push(this.formData.getAll('files'));
this.uploadForm.controls['filesToUpload'].setValidators(Validators.compose([fileSizeValidator(this.data.maxMbSize, this.rawFiles)]));
...
But none of this works for me, I have tried other things that I have found on the internet, but they have not worked either. The only thing I can think of is to move the validator function from file-validator.ts to files-uploader.component.ts in order to have direct access to the dynamic array.
Edit: I tried that last option and dont't work too.

Related

Event firing multiple times in Vuejs

So, I wrote a function that using which the user can upload multiple files.
<input multiple type="file" id="inputFile" ref="fileSelect" #click="uploadfiles"/>
uploadfiles(){
'use strict'
const inputFile = document.getElementById('inputFile')
console.log(inputFile)
inputFile!.addEventListener('change',async e => {
const files = (e.target as HTMLInputElement).files
const reader = new FileReader()
console.log(files)
console.log(reader)
// New set of results each time.
this.results.splice(0)
for (const file of files!){
if(file["type"] == "application/json"){
this.results.push(await this.readjsonfile(reader, file))}
else if(file["type"]=="text/csv"){
this.results.push(await this.readcsvfile(reader, file))
}
}
})}
Now when I upload a file for the 1st time it works fine, but when I click on the choose files button again to upload another file, it uploads that file twice.
I think the problem is that when I click again on choose files I "activate" the uploadfiles function again, which causes the Event Listener to fire twice, but I am not sure how to do it any other way.
I am new to JavaScript and vuejs, so any suggestions are greatly appreciated.
Thanks to #Daniel I finally ended up figuring out how to fix this.
My final code is
<input multiple type="file" id="inputFile" ref="fileSelect" #change="uploadfiles"/>
uploadfiles(event){
'use strict'
console.log(event)
//console.log(event.target.files)
const q = [event.target.files]
q.forEach(async w => {
const reader = new FileReader()
this.results=[]
for(const file of w){
if(file["type"] == "application/json"){
this.results.push(await this.readjsonfile(reader, file))}
else if(file["type"]=="text/csv"){
this.results.push(await this.readcsvfile(reader, file))
}
}
})
console.log(this.results)
}
The reason is that you're adding inputFile!.addEventListener every time the user clicks on the input form
Instead, if you were to use #change, instead of #click, that should not be happening any more.
example:
<script setup>
import {ref} from "vue"
let files = ref([])
function uploadfiles(event){
files.value.splice(0);
[...event.target.files].forEach(async file => {
// do the logic here
files.value.push(file)
})
}
</script>
<template>
<input multiple type="file" id="inputFile" ref="fileSelect" #change="uploadfiles"/>
<pre>{{files}}</pre>
</template>
SFC playground

Populate Input Field using FormControl Angular

I have an input field where I need to populate array data inside it which is coming
from API, I have used FormControl to populate the data but not able to achieve the same.I am getting the response on console but not able to populate it on UI. Below is my code if any anyone could guide me as I have spent 2 entire days and new in Angular. Can anyone please help me here.
HTML Code:
<div formArrayName="ints" *ngFor="let inCompany of insurance.controls; let i = index">
<div [formGroupName] = "i">
<ion-card *ngFor="let eq of ef;let i=index;">
<ion-item>
<ion-input formControlName="iCompany"></ion-input>
</ion-item>
</ion-card>
</div>
</div>
TS Code:
ionViewWillEnter(){
this.loadData();
}
ngOnInit() {
this.sForm = this.formBuilder.group({
ints: this.formBuilder.array([]),
})
}
get ints(): FormArray {
return this.sForm.get('ints') as FormArray;
}
get formGroup(): FormGroup {
return this.formBuilder.group({
name: ['justTest'],
});
}
loadData(){
this.service.getefDetails(data).subscribe((response: any) => {
this.ef= response.data;
var formArray = this.sForm.get('ints') as FormArray;
for (let i = 0; i < this.ef.length; i++) {
console.log(this.ef.length, this.ef[i].percentage)
var chec=this.ef[i].percentage
formArray.push(this.formGroup);
formArray.controls[i].patchValue(chec);
}
)}
}
Array Type:
[{name:"test", percentage: "29"},{name:"abc", percentage: "45"}, {name:"def", percentage: "63"}]
First of all, I suggest you re-think your approach as mentioned in the earlier comment it seems you have made it unnecessarily complicated.
Also, I would think of renaming your variables it is quite confusing and will be a pain to maintain later on.
To answer your question and get the "ion-input" printed on the screen do the following changes to the HTML.
You can not assign <div [formGroupName] = "i"> i to the formGroup since it is not of type formGroup as it is assigned to the index.
The solution is to assign <div [formGroupName] = "insuranceCompany[0]">
So that a form group will be assigned.
Again I suggest that you rename the variable "insuranceCompany" for clarity purposes as there is a control named "insuranceCompany" as well.
Here is a working example of your code minus the ionic tags.
https://stackblitz.com/edit/angular-ivy-b3gtly?file=src/app/app.component.ts
Hope I made myself clear, and hope it helps.
You can use patchValue directly to an form control in order to do this.
Your current setup seems too complicated unless there are a bunch of other values in the form that's not displayed here.
However, when you get a response from API, you can simply get the reactive form element, and set value.
this.suitablityForm.insurance.insuranceCompany.patchValue('VAL_YOU_WANT');

Multiple upload inputs using ng2-uploader in Angular

I'm using https://www.npmjs.com/package/ng2-uploader package for file upload in angular, everything works fine for single input. But I want more than one input with different options and urls like this
<input type="file"
ngFileSelect
[options]="options1"
(onUpload)="handleUpload($event)"
(beforeUpload)="beforeUpload($event)">
<input type="file"
ngFileSelect
[options]="options2"
(onUpload)="handleUpload($event)"
(beforeUpload)="beforeUpload($event)">
Configuration for file upload I'm trying to do is
this.options1 = {
url: 'url for first input'
fieldName: 'first input field name',
method: 'PUT'
};
this.options2 = {
url: 'url for second input'
fieldName: 'second input field name',
method: 'PUT'
};
handleUpload(data): void {
if (data && data.response) {
data = JSON.parse(data.response);
this.uploadFile = data;
//code
}
}
fileOverBase(e:any):void {
this.hasBaseDropZoneOver = e;
}
beforeUpload(uploadingFile): void {
if (uploadingFile.size > this.sizeLimit) {
uploadingFile.setAbort();
alert('File is too large');
}
}
This results in always picking up the latter options i.e 'options2' and 'options1' donot have any effect. How do I implement this?
I had similar issue.
And in my case, I had duplicates of input's ids and label's for picked the wrong one.
By the way ng2-uploader was renamed to ngx-uploader. Recently its api changed a lot, but there was versions compatible with ng2-uploader, with some bugs fixed.

How to toggle a class based on a variable passed from Laravel using a Vuejs component

This is the 3rd attempt for me to get some help on this issue, for so long I've been trying to toggle an id using Vuejs, I just want that id to change based on a boolean value passed on from Laravel, I managed to do it without components but I ran into a problem, there's multiple buttons on the page, when one gets their id updated the others do as well, so I thought maybe a component could solve this, I just can't get it to work.
Here's the blade template, this is inside a table inside a foreach loop that has access to a $courses variable:
courses.blade:
<form method="POST" action="{{ route('course.completed', $course->name) }}" id="form-submit">
{{ csrf_field() }}
#if ($course->pivot->completed == true)
<course-buttons id="this.greenClass.aClass">Done</course-buttons>
#else
<course-buttons id="this.redClass.aClass">Not Yet</course-buttons>
#endif
</form>
this is app.js:
require('./bootstrap');
Vue.component('course-buttons', require('./components/course-buttons.vue'))
var vm = new Vue({
el: '#app'
});
And this is the course-buttons.vue file:
<template>
<button #click.prevent="onSubmit({{ $course }})" type="button" class="btn btn-sm" :id=id><slot></slot></button>
</template>
<script>
export default {
props: ['id'],
data: function() {
return {
greenClass: {aClass: 'coursetrue', text: 'Done!'},
redClass: {aClass: 'coursefalse', text: 'Not Yet!'}
};
},
methods: {
onSubmit: function(course) {
axios.post('/MyCourses/' + course.name)
.then(function (response){
console.log(response.data.course.pivot.completed)
if (response.data.course.pivot.completed == true){
return [
vm.redClass.aClass = 'coursetrue',
vm.redClass.text = 'Done!',
vm.greenClass.aClass = 'coursetrue',
vm.greenClass.text = 'Done!'
]
} else {
return [
vm.greenClass.aClass = 'coursefalse',
vm.greenClass.text = 'Not Yet',
vm.redClass.aClass = 'coursefalse',
vm.redClass.text = 'Not Yet'
]
}
});
}
}
}
</script>
First I know that my code isn't good, that's why I asked for help many times but with no answers at all, so if you have any tips that might help me get this code cleaner, even change it totally but just get the job done, I'm all ears.
The errors I'm getting right now is first the #click.prevent is an invalid expression, tried moving that to the tag, it doesn't do anything over there so I had that going before and now I lost it as well, also I get an error that the id is not defined on the instance, although I defined the props and data in the vue component.
if you're wondering why do I even assign the id on the tag itself and not the component, it's because of how my code is structured,e.g "If the value is true then load the tag with that id, otherwise load it with that id", once again if you can help me do this in a completely different way I'll be grateful.
One error I see is you have to use v-bind:id here, as you are assigning a vue variable:
<course-buttons v-bind:id="this.greenClass.aClass">Done</course-buttons>
One more is in onSubmit method, you dont have to return anything, and also you are using vm there, instead of that you can just do following:
onSubmit: function(course) {
axios.post('/MyCourses/' + course.name)
.then((response) => {
console.log(response.data.course.pivot.completed)
if (response.data.course.pivot.completed == true){
this.redClass.aClass = 'coursetrue',
this.redClass.text = 'Done!',
this.greenClass.aClass = 'coursetrue',
this.greenClass.text = 'Done!'
} else {
this.greenClass.aClass = 'coursefalse',
this.greenClass.text = 'Not Yet',
this.redClass.aClass = 'coursefalse',
this.redClass.text = 'Not Yet'
}
});
}
Here I have also used arrow syntax, here is why.

Form with JQuery Steps using aui:form tag in Liferay submits no data

I built a portlet and added a entity named Idea. There are two JSPs, one is the view and one the edit.
In the view there is only a button to create a new Idea and a table showing all ideas. Clicking on the button shows the edit jsp.
There is a form with two fieldsets and input stuff.
The "problem" is i cannot use the <aui:form ... stuff because it won't work with JQuery steps (or better, i cannot get it working). So i am using normal tag and also JQuery steps is providing the submit button which is only a <a href="#finish" ...>. So that wont bring the form to submit and the data being in the database.
So I tried to do it within the javascript code of the definition of jquery steps like here:
$(document).ready(function(){
var form = $("#wizard").show();
form.steps(
{
headerTag : "h3",
bodyTag : "fieldset",
transitionEffect : "slideLeft",
onFinishing: function (event, currentIndex) {
alert("Submitted!");
var data = jQuery("#wizard").serialize();
alert(data);
jQuery("#wizard").submit();
form.submit();[/b]
},
onFinished: function (event, currentIndex) {
//I tried also here..
},
});
});
But even if i declare the data explicitely it wont put it in the db.
So my idea was that the "controller" class which calls the "addIdea" function is never called.
How am I solving the problem?
Here is also my jsp code for the form part:
<aui:form id="wizard" class="wizard" action="<%= editIdeaURL %>" method="POST" name="fm">
<h3>Idea</h3>
<aui:fieldset>
<aui:input name="redirect" type="hidden" value="<%= redirect %>" />
<aui:input name="ideaId" type="hidden" value='<%= idea == null ? "" : idea.getIdeaId() %>'/>
<aui:input name="ideaName" />
</aui:fieldset>
<h3>Idea desc</h3>
<aui:fieldset>
<aui:input name="ideaDescription" />
</aui:fieldset>
<aui:button-row>
<aui:button type="submit" />
<aui:button onClick="<%= viewIdeaURL %>" type="cancel" />
</aui:button-row>
</aui:form>
Is there a way to "teach" JQuery Steps the <aui:*** tags? I tried it already while initializing the form but it won't work. To get it working using the aui tags would be great. Because otherwise the Liferay portal wont get the data or it would get it only with hacks right?
€dit: What I forgot, when I submit the form using javascript submit, it creates a new dataentry in the db but no actual data in it.
€dit2:
The editIdeaURL is referenced a bit over the form here:
<portlet:actionURL name='<%=idea == null ? "addIdea" : "updateIdea"%>'
var="editIdeaURL" windowState="normal" />
and the addIdea code looks as follows:
In the IdeaCreation class first this:
public void addIdea(ActionRequest request, ActionResponse response)
throws Exception {
_updateIdea(request);
sendRedirect(request, response);
}
Where _updateIdea() is:
private Idea _updateIdea(ActionRequest request)
throws PortalException, SystemException {
long ideaId = (ParamUtil.getLong(request, "ideaId"));
String ideaName = (ParamUtil.getString(request, "ideaName"));
String ideaDescription = (ParamUtil.getString(request, "ideaDescription"));
ServiceContext serviceContext = ServiceContextFactory.getInstance(
Idea.class.getName(), request);
Idea idea = null;
if (ideaId <= 0) {
idea = IdeaLocalServiceUtil.addIdea(
serviceContext.getUserId(),
serviceContext.getScopeGroupId(), ideaName, ideaDescription,
serviceContext);
} else {
idea = IdeaLocalServiceUtil.getIdea(ideaId);
idea = IdeaLocalServiceUtil.updateIdea(
serviceContext.getUserId(), ideaId, ideaName, ideaDescription,
serviceContext);
}
return idea;
}
And to finally put the data using IdeaLocalServiceImpl:
public Idea addIdea(
long userId, long groupId, String ideaName, String ideaDescription,
ServiceContext serviceContext)
throws PortalException, SystemException {
User user = userPersistence.findByPrimaryKey(userId);
Date now = new Date();
long ideaId =
counterLocalService.increment(Idea.class.getName());
Idea idea = ideaPersistence.create(ideaId);
idea.setIdeaName(ideaName);
idea.setIdeaDescription(ideaDescription);
idea.setGroupId(groupId);
idea.setCompanyId(user.getCompanyId());
idea.setUserId(user.getUserId());
idea.setCreateDate(serviceContext.getCreateDate(now));
idea.setModifiedDate(serviceContext.getModifiedDate(now));
super.addIdea(idea);
return idea;
}
Any ideas?

Categories