Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
231 views
in Technique[技术] by (71.8m points)

javascript - Cannot set a global variable inside a onload method

I am building an Angular 9 app. In this app I got an image upload component.

In order to make sure the right things are uploaded I want to check the dimensions of selected images. I do this with the code below (which kind of works).

let img = new Image();
          img.src = window.URL.createObjectURL(fileList[index]);
          img.onload = () => {
            if (img.width < this.allowedWidth && img.height < this.allowedHeight) {
              this.uploadValid = true;
              this.isDoneUploading = false;
            } else {
              this.uploadValid = false;
              this.uploadErrorMessage = this.getTranslation('files.forms.labels.error_dimenstions');
            }
          }

I can access the this.allowedWidth and this.allowedHeight variables that I set at the top of the component page but strangly I cannot set the this.uploadValid and this.uploadErrorMessage variables.

Or at least I cannot set them at a "global" context of the component only within the If/else statement.

I tried the following code too but it does not help.

let img = new Image();
let that = this;
              img.src = window.URL.createObjectURL(fileList[index]);
              img.onload = () => {
                if (img.width < that.allowedWidth && img.height < that.allowedHeight) {
                  that.uploadValid = true;
                  that.isDoneUploading = false;
                } else {
                  that.uploadValid = false;
                  that.uploadErrorMessage = that.getTranslation('files.forms.labels.error_dimenstions');
                }
              }

Update

This is the whole method in which this method resides. It is called when a file is selected.

handleDrop(fileList: FileList) {
    this.files = [];
    this.isDropzoneActive = false;
    this.uploadingFileCount += fileList.length
    for (let index = 0; index < fileList.length; index++) {
      if (this.allowedTypes.includes(fileList[index].type)) {
        if (fileList[index].size < this.allowedSize) {
          let img = new Image();
          img.src = window.URL.createObjectURL(fileList[index]);
          img.onload = () => {
            if (img.width < this.allowedWidth && img.height < this.allowedHeight) {
              this.uploadValid = true;
              this.isDoneUploading = false;
            } else {
              this.uploadValid = false;
              this.uploadErrorMessage = this.getTranslation('files.forms.labels.error_dimenstions');
            }
          }
        } else {
          this.uploadValid = false;
          this.uploadErrorMessage = this.getTranslation('files.forms.labels.error_size');
        }
      } else {
        this.uploadValid = false;
        this.uploadErrorMessage = this.getTranslation('files.forms.labels.error_format');
      }
      const item = {
        id: null,
        file_name: fileList[index].name,
        file_type: fileList[index].type,
        file_size: fileList[index].size,
        file_url: null
      }
      this.uploadItems.push(item);
      const file = {
        id: Math.random().toString(36).substr(2, 15),
        file: fileList[index],
        uploaded: false,
        valid: this.uploadValid,
        error: this.uploadErrorMessage
      }
      console.log(file);
      this.uploadQueue.push(file);
    }
    // this.submitUploadForm();
  }

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Using a directive and a FormControl for a similar problem, my code looks like this:

directive:

// angular imports
import { Directive, HostBinding, HostListener, Output, EventEmitter } from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';

// models imports
import { FileHandle } from '../models';


@Directive({
 selector: '[appFilesDrop]'
})
export class FilesDropDirective {
  @Output() files: EventEmitter<FileHandle[]> = new EventEmitter();

  @HostBinding('style.background') private background = '#eee';

  constructor(
    private sanitizer: DomSanitizer
  ) { }

  @HostListener('dragover', ['$event']) public onDragOver(evt: DragEvent) {
    evt.preventDefault();
    evt.stopPropagation();
    this.background = '#999';
  }

  @HostListener('dragleave', ['$event']) public onDragLeave(evt: DragEvent) {
    evt.preventDefault();
    evt.stopPropagation();
    this.background = '#eee';
  }

  @HostListener('drop', ['$event']) public onDrop(evt: DragEvent) {
    evt.preventDefault();
    evt.stopPropagation();
    this.background = '#eee';

    const files: FileHandle[] = [];
    // tslint:disable-next-line: prefer-for-of
    for (let i = 0; i < evt.dataTransfer.files.length; i++) {
      const file = evt.dataTransfer.files[i];
      const url = this.sanitizer.bypassSecurityTrustUrl(window.URL.createObjectURL(file));
      files.push({ file, url });
    }
    if (files.length > 0) {
      this.files.emit(files);
    }
  }
}

models.ts:

export interface FileHandle {
  url: SafeUrl;
  file?: File;
}

html code:

<!-- Image upload field -->
        <ng-container *ngIf="formField.type === 'filesUpload'" class="example-full-width image-field-container">
          <mat-form-field
            appearance="outline"
            floatLabel="always"
            class=".example-full-width"
          >
              <mat-label>{{ objectName }} {{formField.label}}</mat-label>
              <!-- This block is only displayed if there's no image -->
              <!-- it's necessary because we need a formControlName in a mat-form-field -->
              <input
                *ngIf="formGroup.controls[formField.key].value === null"
                class="image-input"
                matInput
                [formControlName]="formField.key"
                type="text"
                appFilesDrop
                [placeholder]="formField.placeholder"
                autocomplete="off"
                (files)="filesDropped($event, formField.key)"
                readonly
              >
              <!-- Display errors if any -->
              <mat-error
                *ngIf="formGroup.controls[formField.key].hasError('required') && (formGroup.controls[formField.key].dirty || formGroup.controls[formField.key].touched)"
              >
                A {{formField.label.toLocaleLowerCase()}} is required
              </mat-error>
              <mat-error     *ngIf="formGroup.controls[formField.key].hasError('tooLongArray')">
                You must drop only one image, you dropped {{formGroup.controls[formField.key].errors.tooLongArray.length}} images.
              </mat-error>
              <mat-error     *ngIf="formGroup.controls[formField.key].hasError('forbiddenMime')">
                You must drop files of type {{formGroup.controls[formField.key].errors.forbiddenMime.forbidden.join(', ')}}.
                All your files must be of type {{formGroup.controls[formField.key].errors.forbiddenMime.allowed.join(', ')}}
              </mat-error>
              <mat-error     *ngIf="formGroup.controls[formField.key].hasError('tooLargeFile')">
                The file(s) {{formGroup.controls[formField.key].errors.tooLargeFile.forbidden.join(', ')}} is/are too big.
                The maximum allowed size is     {{formGroup.controls[formField.key].errors.tooLargeFile.allowed/1024 | number}}kb
              </mat-error>
              <!-- If we have an image we need to display it. -->
              <!-- We created a hidden mat-form-field and display the image -->
              <ng-container *ngIf="formGroup.controls[formField.key].value !== null">
                <div class="">
                  <input matInput [formControlName]="formField.key" style="visibility: hidden;" class="invisible">
                  <img [src]="formGroup.controls[formField.key].value[0].url" class="image-uploaded">
                </div>
                <button mat-mini-fab color="warn" (click)="removeFiles(formField.key)"><mat-icon>delete</mat-icon></button>
              </ng-container>
          </mat-form-field>
        </ng-container>

The overall validation logic is a bit complex because I'm using a service which is generating the FormGroup dynamically but the logic is easy to understand even if you can't copy/paste it:

// file fields
        case('filesUpload'): {
          const options = field.options as FileFieldOptions;

          // build the validators
          validators.push(mimeTypeValidator(options.allowedMimes));
          if (!options.multiple) { validators.push(arrayLengthValidator(1)); }
          if (options.maxSize) { validators.push(fileSizeValidator(options.maxSize)); }

          // get the initial value
          if (!options.multiple && initialValue && initialValue[field.key]) {
            // we only have 1 object, so we will have on url from the api and we can provide the formControl as a 1 object control
            initialFieldValue = [{
              url: initialValue[field.key]
            }] as Array<FileHandle>;
          }
          break;

Forgot to add the example of validators:

For the file size validator:
    // angular imports
import { ValidatorFn, AbstractControl, } from '@angular/forms';

// model imports
import { FileHandle } from '../models';

export function fileSizeValidator(maxFileSize: number): ValidatorFn {
  return (control: AbstractControl): {[key: string]: any} | null => {
    // validate when there's no value
    if (control.value === null || control.value === undefined ) { return     null; }
    // validate when we only have a url (it comes from server)
    if (control.value[0].file === undefined) { return null; }
    const fileHandles = control.value as Array<FileHandle>;
    const tooLargeFiles = fileHandles.filter(fileHandle => fileHandle.file.size > maxFileSize).map(fileHandle => fileHandle.file.name);
    return tooLargeFiles.length > 0 ? {tooLargeFile: {forbidden: tooLargeFiles, allowed: maxFileSize}} : null;
  };
}

and a MimeType validator:

// angular imports
import { ValidatorFn, AbstractControl, } from '@angular/forms';

// model imports
import { FileHandle } from '../models';

export function mimeTypeValidator(allowedMimeTypes: Array<string>): ValidatorFn {
  return (control: AbstractControl): {[key: string]: any} | null => {
    // validate when there's no value
    if (control.value === null || control.value === undefined ) { return null; }
    // validate when we only have a url (it comes from server)
    if (control.value[0].file === undefined) { return null; }
    const fileHandles = control.value as Array<FileHandle>;
    const forbiddenMimes = fileHandles.map(fileHandle => fileHandle.file.type).filter(mime => !allowedMimeTypes.includes(mime));
    return forbiddenMimes.length > 0 ? {forbiddenMime: {forbidden: forbiddenMimes, allowed: allowedMimeTypes}} : null;
  };
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...