Link to home
Start Free TrialLog in
Avatar of Adam Ehrenworth
Adam EhrenworthFlag for United States of America

asked on

Angular JS Support Tool - Make File Attachment Option - Not Required

I was handed over a support tool by our contractor that is based on Angular JS. I have some basic understanding of how it functions, but I am having trouble turning off a check on a particular module that is required that an attachment is included before the form can be submitted to a SharePoint list. I have included the code for the HTML as well as the associated code files. Please advise what need to be changed to allow for an attachment to be included but NOT have it be required.

HTML
<!--removed this from submit button [disabled]='!trgIssueForm.valid'-->
<div [@routerTransition] class="panel panel-primary">
    <!--<h2>Request Management- <small>{{_requestService?._selReq?.Title}}</small></h2>-->
    <div class="panel-heading">
        {{_requestService?._selReq?.Title}}
    </div>
    <!--<hr>-->
    <div class="panel-body">
        <form class="form-horizontal"
              novalidate
              (ngSubmit)="saveRequest()"
              [formGroup]="trgIssueForm" >
            <fieldset>
                <div class="checkbox">
                    <label class="formyself">
                        <input type="checkbox" formControlName="MyRequest">This request is for myself
                    </label>
                </div>
                <div class="form-group">
                    <mat-form-field class="col-md-6">
                        <input matInput class="form-control" 
                                id="requesterWWIDId" 
                                type="text" 
                                placeholder="WWID" 
                                value={{_impUser?.WWID}}
                                formControlName="RequesterWWID" />
                                <mat-hint align="start">{{_impUser?.RequesterName}}</mat-hint>
                    </mat-form-field>
                </div>
                <!--<div class="form-group">
                    <mat-form-field class="col-md-6">
                        <input matInput class="form-control" 
                                id="RequesterName" 
                                type="text" 
                                placeholder="Requester Name" 
                                value={{_impUser?.RequesterName}}
                                formControlName="RequesterName" />
                    </mat-form-field>
                </div>-->
                <div class="form-group">
                    <mat-form-field class="col-md-6">
                        <mat-select class="form-control"
                            id="ReqActSelect" 
                            placeholder="Select Issue Type"
                            formControlName="ReqActSelect">
                            <mat-option value="TrainingIssue">Completion issue</mat-option>
                            <mat-option value="TrainingIssue">Training not working</mat-option>
                            <mat-option value="TrainingIssue">Other</mat-option>
                            <!--<mat-option value="WhyTrg">Why did I get this assigned?</mat-option>-->
                        </mat-select>
                    </mat-form-field>
                </div>
                <div class="form-group">
                    <mat-form-field class="col-md-6">
                        <input matInput [matAutocomplete]="auto"
                                class="form-control" 
                                id="trainingId" 
                                type="text" 
                                placeholder="Training"
                                formControlName="Training" />
                                <mat-autocomplete #auto="matAutocomplete">
                                    <mat-option *ngFor="let trg of filteredTrainings | async" [value]="trg.TrainingCode">
                                        <span>Title: {{trg.Title}}</span> | Code: <span>{{ trg.TrainingCode }}</span>
                                    </mat-option>
                                </mat-autocomplete>
                    </mat-form-field>
                </div>
                <div class="form-group" [hidden]="_hideScreenshot">
                    <div class="col-md-6">
                        <input class="form-control file-attach" 
                            id="Screenshot" 
                            type="file" 
                            aria-describedby="fileHelp"
                            placeholder="Attache Screenshot" 
                            accept=".jpeg, .png"
                            formControlName="Screenshot" />
                        <!--<p *ngIf="trgIssueForm.controls.Screenshot.errors?.required" style="color: red">Screenshot is required!</p>-->
                        <label class="file-attach-label">
                            Attach a Screenshot
                        </label>
                    </div>
                </div>
                <div class="form-group">
                    <mat-form-field class="col-md-6">
                        <textarea matInput class="form-control" 
                                id="requestDescriptionId" 
                                placeholder="Description"
                                rows=3
                                formControlName="RequestDescription"></textarea>
                    </mat-form-field>
                </div>
                <div class="form-group">
                    <div class="col-md-4 col-md-offset-2">
                        <span>
                            <button mat-raised-button class="btn btn-primary"
                                    type="submit"
                                    style="width:80px;margin-right:10px"
                                    >
                                Submit
                            </button>
                        </span>
                        <span>
                            <a class="btn btn-default"
                               style="width:80px"
                               (click)="cancelRequest()">
                                Cancel
                            </a>
                        </span>
                     </div>
                </div>
            </fieldset>
        </form>
        <div class='has-error' style="color: red" *ngIf='errorMessage'>{{errorMessage}}</div>
    </div>
    <!--<ngb-alert [type]="alert.type" (close)="closeAlert(alert)" *ngFor="let alert of alerts">{{ alert.message }}</ngb-alert>-->
</div>

Open in new window


TS Components
import { Component, OnInit, AfterViewInit, OnDestroy, ViewChildren, ElementRef, Input } from '@angular/core';
import { FormBuilder, FormGroup, FormControl, FormArray, Validators, FormControlName } from '@angular/forms';
import { routerTransition } from '../../router.animations';
import { RequestService, FileValidator } from 'app/core';
import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
import { GenericValidator } from '../../shared/generic-validator';
import { TrgIssueService } from './trgissue.service';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import {startWith} from 'rxjs/operators/startWith';

import { DISABLED } from '@angular/forms/src/model';
import { HttpClient } from '@angular/common/http';
import { IImpUser } from 'app/reqmgmt/dtos';
import { map, filter, debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';

@Component({
  selector: 'app-req-whytrg',
  templateUrl: './trgissue.component.html',
  animations: [routerTransition()]
})
export class TrgIssueComponent implements OnInit {

  currentUrl: string;
  @ViewChildren(FormControlName, { read: ElementRef }) formInputElements: ElementRef[];

  // pageTitle = 'Product Edit';
  // errorMessage: string;
  trgIssueForm: FormGroup;
  selTraining: any = null;
  public errorMessage = null;
  public _hideScreenshot = true;
  public _impUser: IImpUser = <IImpUser>{};
  // private sub: Subscription;
  MyRequest: FormControl = new FormControl();
  private isMyrequest = true;
  // MyRerequesterWWIDId: FormControl = new FormControl();
  // Use with the generic validation message class
  // displayMessage: { [key: string]: string } = {};
  // private validationMessages: { [key: string]: { [key: string]: string } };
  private genericValidator: GenericValidator;
  filteredTrainings: Observable<any[]>;
  constructor(private _router: Router, public _requestService: RequestService,
    private fb: FormBuilder,
    private _http: HttpClient,
    private route: ActivatedRoute,
    private router: Router,
    private _trgIssueService: TrgIssueService ) {
      _router.events.subscribe((event) => {
        if (event instanceof NavigationEnd ) {
            this.currentUrl = event.url;
            console.log('TrgIssueComponent- constructor - this.currentUrl', this.currentUrl);
        }
    });
    // this.genericValidator = new GenericValidator(this.validationMessages);
  }
  filterTrainings(TrainingCode: string) {
    console.log('TrgIssueComponent -> filterTrainings: TrainingCode', TrainingCode);
      return this._requestService._distTrgs.filter(trg =>
        trg.TrainingCode.toLowerCase().indexOf(TrainingCode.toLowerCase()) === 0);
  }
  buildForm() {
    this.trgIssueForm = this.fb.group({
      MyRequest: true,
      RequesterWWID: new FormControl(this._impUser.WWID, {
        validators: [Validators.required], updateOn: 'blur'}),
      // RequesterName: new FormControl(this._impUser.RequesterName, {
      //  validators: [Validators.required], updateOn: 'blur'}),
      ReqActSelect: ['', Validators.required],
      Screenshot: ['', FileValidator.validate],
      Training: ['', Validators.required],
      RequestDescription: ''
    });
  }
  ngOnInit() {
    console.log('TrgIssueComponent Component OnInit');
    this.setImpUserToDefault();
    // If user directly came to thispage, we need to get Selected Request Item
    if ( !this._requestService._selReq) {
      // filter this request type from reqTypes collection
      this._requestService._selReq = Object.assign({}, this._requestService._requestTypes.filter(req =>
        req.ReqLink === this.currentUrl)[0]);
        console.log('TrgIssueComponent->ngOnInit-_selReq',  this._requestService._selReq);
    }
    // get all the data
    // this._requestService.getTrainings();
    this.buildForm();
    this.trgIssueForm.get('RequesterWWID').disable();
    // this.trgIssueForm.get('RequesterName').disable();
    this.filteredTrainings = this.trgIssueForm.get('Training').valueChanges
      .pipe(
        startWith(''),
        map(trg => trg ? this.filterTrainings(trg) : this._requestService._distTrgs)
      );
    this.trgIssueForm.get('MyRequest').valueChanges
        .subscribe(value => this.onChangeMyRequest(value));
    this.trgIssueForm.get('ReqActSelect').valueChanges
    .subscribe(value => this.onReqActionChange(value));
    this.trgIssueForm.get('RequesterWWID').valueChanges.
      subscribe(inputval => {
        console.log('TrgIssueComponent->ngOnInit- getWWIDDataByWWID -> inputval', inputval );
        if (!this.trgIssueForm.get('MyRequest').value) {
          this._requestService.getWWIDDataByWWID(inputval).then(res => {
            console.log('TrgIssueComponent->ngOnInit- getWWIDDataByWWID -> res', res);
            if (res && res.length === 1 ) {
              const tUser: IImpUser = <IImpUser>{};
              tUser.WWID = res[0].Title;
              tUser.RequesterName = res[0].FirstName + ' ' + res[0].LastName ;
              tUser.Email = res[0].Email;
              this._impUser = tUser;
              // this.trgIssueForm.get('RequesterName').setValue(this._impUser.RequesterName);
              console.log('TrgIssueComponent->ngOnInit- getWWIDDataByWWID -> this._impUser', this._impUser);
              // this.trgIssueForm.get('RequesterWWID').setValue(this._impUser.RequesterName);
            } else {
              this._impUser = null;
              this.trgIssueForm.get('RequesterWWID').setErrors({
                'required': true });
            }
        });
      }
    });
  }
  setImpUserToDefault() {
    const tUser: IImpUser = <IImpUser>{};
    tUser.WWID = this._requestService._userProfile.WWID;
    tUser.RequesterName = this._requestService._userProfile.DisplayName;
    tUser.Email = this._requestService._userProfile.Email;
    this._impUser = tUser;
  }
onReqActionChange(action: String) {
  console.log('TrgIssueComponent -> onReqActionChange: action', action);
  if (action && action.toLowerCase() === 'whytrg') {
    this.trgIssueForm.get('Screenshot').setValidators([]);
    this.trgIssueForm.get('Screenshot').updateValueAndValidity();
    this._hideScreenshot = true;
  } else if (action && action.toLowerCase() === 'trainingissue') {
    this.trgIssueForm.get('Screenshot').setValidators([FileValidator.validate]);
    this.trgIssueForm.get('Screenshot').updateValueAndValidity();
    this._hideScreenshot = false;
  }
}

  // (change)="onChangeMyRequest($event.target.checked)"
  onChangeMyRequest(isChecked: boolean): void {
    console.log('TrgIssueComponent -> onChangeMyRequest: _impUser', isChecked, this._impUser);
    if ( isChecked ) {
      // Set ImpUser to default
      this.setImpUserToDefault();
      this.trgIssueForm.get('RequesterWWID').setValue(this._impUser.WWID, {emitEvent: false});
      this.trgIssueForm.get('RequesterWWID').disable();
    } else {
      this.trgIssueForm.get('RequesterWWID').enable();
      this._impUser = null;
    }

  }
  saveRequest(): void {
    console.log('TrgIssueComponent -> saveRequest -');
    if (this.trgIssueForm.dirty && this.trgIssueForm.valid) {
        // Copy the form values over the product object values
        const p = Object.assign({}, this.trgIssueForm.value);
        // get selected trainng detail
        this.selTraining = this._requestService._trgUgInv.filter(trg =>
          trg.TrainingCode.toLowerCase().indexOf(p.Training.toLowerCase()) === 0)[0];
          console.log('TrgIssueComponent -> saveRequest-> this.selTraining', this.selTraining);
        p['TrainingName'] = this.selTraining.TrainingName;
        p['TrainingCode'] = this.selTraining.TrainingCode;
        p['Title'] = this._requestService._selReq.Title + ' - ' + p['ReqActSelect'];
        p['ReqSupportMailbox'] = this._requestService._selReq.ReqSupportMailbox;
        p['RequesterWWID'] = this._impUser.WWID;
        p['RequesterName'] = this._impUser.RequesterName;
        p['RequesterEmail'] = this._impUser.Email;
        p['ReqContentTypeId'] = this._requestService._selReq.ReqContentTypeId;
        console.log('TrgIssueComponent -> saveRequest-> p', p);
        this._trgIssueService.saveRequest(p).subscribe(
          result => {
            console.log('TrgIssueComponent ->saveRequest- > result', result);
            this.trgIssueForm.reset();
            this.router.navigate(['/reqmgmt/confirm']);
        },
        error => {
          this.errorMessage = error;
          console.log('TrgIssueComponent ->saveRequest- > error', error);
        }
      );
    }
}
cancelRequest(): void {
  this.router.navigate(['/home']);
}
  /*ngAfterViewInit(): void {
    // Watch for the blur event from any input element on the form.
    const controlBlurs: Observable<any>[] = this.formInputElements
        .map((formControl: ElementRef) => Observable.fromEvent(formControl.nativeElement, 'blur'));

    // Merge the blur event observable with the valueChanges observable
    Observable.merge(this.trgIssueForm.valueChanges, ...controlBlurs).debounceTime(800).subscribe(value => {
        this.displayMessage = this.genericValidator.processMessages(this.trgIssueForm);
    });
  }*/
}

Open in new window


TS Services
import {Injectable, OnInit} from '@angular/core';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of';
import { sp, Web } from "@pnp/sp";
import { environment } from '../../../environments/environment';
import { RequestService } from 'app/core';
import { Response } from '@angular/http';
import { fromPromise } from 'rxjs/observable/fromPromise';

@Injectable()
export class TrgIssueService {
    constructor(private _reqService: RequestService) {
    }
    // public _reqTypes;
    saveRequest(req): Observable<any> {
        console.log('TrgIssueService -> saveRequest->req', req);
        if (!req.ID) {
            return this.createItem(req);
        }
        // return this.updateRequest(reqWhyTrg);
    }
    private createItem(_req):  Observable<any> {
        console.log('TrgIssueService -> createRequest->_req', _req);
        let _result = null;
        return fromPromise(sp.web.lists.getByTitle('Requests').items.add({
            Title: _req.Title,
            Action: _req.ReqActSelect,
            TrainingCode: _req.Training,
            TrainingName: _req.TrainingName,
            ReqSupportMailbox: _req.ReqSupportMailbox,
            ContentTypeId: _req.ReqContentTypeId,
            RequestDescription: _req.RequestDescription,
            RequesterName: _req.RequesterName,
            RequesterEmail: _req.RequesterEmail,
            RequesterWWID: _req.RequesterWWID,
        }).then((iar: any) => {
            console.log('TrgIssueService -> createItem-> iar', iar);
            if (_req.Screenshot[0]) {
            return sp.web.lists.getByTitle('Requests').items.getById(iar.data.ID).
                attachmentFiles.add(_req.Screenshot[0].name, _req.Screenshot[0]).then(file => {
                    _result = file;
                    return _result;
                });
            } else {
                //return iar;
            }
        }));
    }
    private updateRequest(_req) {
        let _result = null;
        sp.web.lists.getByTitle('Requests').items.add({
            Title: _req.AssignedTrg + ' - ' + _req.RequesterName,
            Training: _req.AssignedTrg,
            RequestDescription: _req.RequestDescription,
            RequesterName: _req.RequesterName,
            RequesterEmail: _req.RequestrEmail
        }).then((iar: any) => {
            console.log('TrgIssueService -> cre qateRequest-> iar', iar);
            _result = iar;
        });
    }
    private extractData(response: Response) {
        const body = response.json();
        return body.data || {};
    }
    private handleError(error: Response): Observable<any> {
        // in a real world app, we may send the server to some remote logging infrastructure
        // instead of just logging it to the console
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }
}

Open in new window


Thanks for your help.
Avatar of Julian Hansen
Julian Hansen
Flag of South Africa image

Firstup Angular JS is 1.x of Angular - the code above is Angular 2,4 or 5 (We leave the JS off).

Secondly the file attachement input (I am assuming it is the file input you are referring to) has this validator specified

 Screenshot: ['', FileValidator.validate],

Open in new window

Which is defined here
import { RequestService, FileValidator } from 'app/core';

Open in new window

So unless this validator does something other than check for existance you can simply remove it from the FormControl

 Screenshot: [''],

Open in new window

Avatar of Adam Ehrenworth

ASKER

Thank you for the clarification.

I made this change to the Form Control and attempted to submit a request without an attachment and it still will not proceed. No error is being triggered in console (when I view with F12).

If I attach a file and submit it works fine.

Is there something else I am missing?
ASKER CERTIFIED SOLUTION
Avatar of Julian Hansen
Julian Hansen
Flag of South Africa image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Yes, that was the issue. I noticed that when I added your first suggestion.

Thank you for the help! I am slowly learning Angular based on an urgent business need - but hopefully, it will help me out in the future as well.
You are welcome.

Good luck with it Angular is a great framework but it does take some time to get to grips with the ins and outs of it - hang in there.