Skip to content

Commit

Permalink
feat(file input): Add layout options that allow different configurati… (
Browse files Browse the repository at this point in the history
  • Loading branch information
escarre authored and jgodi committed May 2, 2017
1 parent 89cea5d commit 67469d6
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 22 deletions.
2 changes: 1 addition & 1 deletion demo/pages/elements/form/FormDemo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export class FormDemoComponent {

// File input controls
this.fileControl = new FileControl({ key: 'file', name: 'myfile', label: 'File', tooltip: 'Files Control' });
this.multiFileControl = new FileControl({ key: 'files', name: 'myfiles', label: 'Multiple Files', multiple: true });
this.multiFileControl = new FileControl({ key: 'files', name: 'myfiles', label: 'Multiple Files', multiple: true, layoutOptions: { order: 'displayFilesBelow', download: false, labelStyle: 'no-box' }, value: [{ name: 'yourFile.pdf', loaded: true }] });
this.fileForm = formUtils.toFormGroup([this.fileControl, this.multiFileControl]);

// Calendar input controls
Expand Down
2 changes: 1 addition & 1 deletion src/elements/form/Control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class NovoCustomControlContainerElement {
<option *ngFor="let opt of control.options" [value]="opt.key">{{opt.value}}</option>
</select>
<!--File-->
<novo-file-input *ngSwitchCase="'file'" [formControlName]="control.key" [id]="control.key" [name]="control.key" [placeholder]="control.placeholder" [multiple]="control.multiple" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition"></novo-file-input>
<novo-file-input *ngSwitchCase="'file'" [formControlName]="control.key" [id]="control.key" [name]="control.key" [placeholder]="control.placeholder" [value]="control.value" [multiple]="control.multiple" [layoutOptions]="control.layoutOptions" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition"></novo-file-input>
<!--Tiles-->
<novo-tiles *ngSwitchCase="'tiles'" [options]="control.options" [formControlName]="control.key" (onChange)="modelChange($event)" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition"></novo-tiles>
<!--Picker-->
Expand Down
4 changes: 4 additions & 0 deletions src/elements/form/controls/BaseControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface NovoControlConfig {
description?: string;
tooltip?: string;
tooltipPosition?: string;
layoutOptions?: { order?: string, download?:boolean, labelStyle?: string };
customControl?: any;
customControlConfig?: any;
}
Expand Down Expand Up @@ -76,6 +77,7 @@ export class BaseControl {
description?: string;
tooltip?: string;
tooltipPosition?: string;
layoutOptions?: { order?: string, download?:boolean, labelStyle?: string };
customControl?: any;
customControlConfig?: any;

Expand All @@ -101,6 +103,8 @@ export class BaseControl {
this.forceClear = new EventEmitter();
this.readOnly = !!config.readOnly || !!config.disabled;
this.disabled = !!config.disabled;
this.layoutOptions = config.layoutOptions || {};

if (this.required) {
this.validators.push(Validators.required);
}
Expand Down
15 changes: 13 additions & 2 deletions src/elements/form/extras/file/FileInput.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ novo-file-input {
display: flex;
flex-flow: row wrap;

.files-below {
padding-top: 10px;
}

.file-output-group {
width: 100%;
margin-top: 15px;
.file-item {
background-color: $white;
box-shadow: 0 1px 4px rgba(#000, .15), 0 2px 10px rgba(#000, .09);
Expand Down Expand Up @@ -50,7 +55,7 @@ novo-file-input {

&:hover,
&.active {
label {
label.boxed {
border: 2px dashed $positive;
}
}
Expand All @@ -68,7 +73,6 @@ novo-file-input {
flex-flow: column;
align-items: center;
position: relative;
border: 2px dashed $grey;
padding: 15px;
cursor: pointer;
pointer-events: none; /*This for FF*/
Expand All @@ -78,6 +82,13 @@ novo-file-input {
small {
margin-top: 7px;
}
&.boxed {
border: 2px dashed $grey;
}

i {
font-size: 3em;
}
}
}

Expand Down
44 changes: 44 additions & 0 deletions src/elements/form/extras/file/FileInput.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,50 @@ describe('Elements: NovoFileInputElement', () => {
component.ngOnInit();
expect(component.element.nativeElement.addEventListener).toHaveBeenCalled();
});
it('should update layout', () => {
expect(component.ngOnInit).toBeDefined();
spyOn(component, 'updateLayout');
component.ngOnInit();
expect(component.updateLayout).toHaveBeenCalled();
});
it('should setup initial files list', () => {
expect(component.ngOnInit).toBeDefined();
spyOn(component, 'setInitialFileList');
component.ngOnInit();
expect(component.setInitialFileList).toHaveBeenCalled();
});
});
describe('Method: updateLayout()', () => {
it('should set layoutOptions and call insertTemplatesBasedOnLayout', () => {
expect(component.updateLayout).toBeDefined();
expect(component.layoutOptions).not.toBeDefined();
spyOn(component, 'insertTemplatesBasedOnLayout');
component.updateLayout();
expect(component.layoutOptions).toBeDefined();
expect(component.insertTemplatesBasedOnLayout).toHaveBeenCalled();
});
});
describe('Method: insertTemplatesBasedOnLayout()', () => {
beforeEach(() => {
component.layoutOptions = { order: 'default', download: true, labelStyle: 'default' };
});
it('should correctly insert templates by default', () => {
expect(component.insertTemplatesBasedOnLayout).toBeDefined();
spyOn(component.container, 'createEmbeddedView');
let expected = ['fileOutput', 'fileInput'];
let insertedOrder = component.insertTemplatesBasedOnLayout();
expect(component.container.createEmbeddedView).toHaveBeenCalled();
expect(insertedOrder).toEqual(expected);
});
it('should correctly insert template if order is displayFilesBelow', () => {
component.layoutOptions.order = 'displayFilesBelow';
expect(component.insertTemplatesBasedOnLayout).toBeDefined();
spyOn(component.container, 'createEmbeddedView');
let expected = ['fileInput', 'fileOutput'];
let insertedOrder = component.insertTemplatesBasedOnLayout();
expect(component.container.createEmbeddedView).toHaveBeenCalled();
expect(insertedOrder).toEqual(expected);
});
});
//
// describe('Method: ngOnDestroy()', () => {
Expand Down
85 changes: 67 additions & 18 deletions src/elements/form/extras/file/FileInput.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// NG2
import { Component, Input, ElementRef, forwardRef, OnInit, OnDestroy } from '@angular/core';
import { Component, Input, ElementRef, forwardRef, OnInit, OnDestroy, ViewChild, ViewChildren, ViewContainerRef, TemplateRef, QueryList, Pipe } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
// APP
import { NovoLabelService } from '../../../../services/novo-label-service';
Expand All @@ -12,36 +12,57 @@ const FILE_VALUE_ACCESSOR = {
multi: true
};

const LAYOUT_DEFAULTS = { order: 'default', download: true, labelStyle: 'default' };

@Component({
selector: 'novo-file-input',
providers: [FILE_VALUE_ACCESSOR],
template: `
<div class="file-output-group">
<div class="file-item" *ngFor="let file of files">
<label>{{ file.name | decodeURI }}</label>
<div class="actions" [attr.data-automation-id]="'file-actions'" *ngIf="file.loaded">
<button theme="icon" icon="save" (click)="download(file)" [attr.data-automation-id]="'file-download'"></button>
<button theme="icon" icon="close" (click)="remove(file)" [attr.data-automation-id]="'file-remove'"></button>
<div #container></div>
<template #fileInput>
<div class="file-input-group" [class.disabled]="disabled" [class.active]="active">
<input type="file" [name]="name" [attr.id]="name" (change)="check($event)" [attr.multiple]="multiple"/>
<section [ngSwitch]="layoutOptions.labelStyle">
<label *ngSwitchCase="'no-box'" [attr.for]="name">
<span> <i class="bhi-dropzone"></i>{{ placeholder || labels.chooseAFile }} {{ labels.or }} <strong class="link">{{ labels.clickToBrowse }}</strong></span>
</label>
<label *ngSwitchDefault [attr.for]="name" class="boxed">
<span>{{ placeholder || labels.chooseAFile }}</span>
<small>{{ labels.or }} <strong class="link">{{ labels.clickToBrowse }}</strong></small>
</label>
</section>
</div>
</template>
<template #fileOutput>
<div class="file-output-group">
<div class="file-item" *ngFor="let file of files">
<label>{{ file.name | decodeURI }}</label>
<div class="actions" [attr.data-automation-id]="'file-actions'" *ngIf="file.loaded">
<button *ngIf="layoutOptions.download" theme="icon" icon="save" (click)="download(file)" [attr.data-automation-id]="'file-download'"></button>
<button theme="icon" icon="close" (click)="remove(file)" [attr.data-automation-id]="'file-remove'"></button>
</div>
<novo-loading *ngIf="!file.loaded"></novo-loading>
</div>
<novo-loading *ngIf="!file.loaded"></novo-loading>
</div>
</div>
<div class="file-input-group" [class.disabled]="disabled" [class.active]="active">
<input type="file" [name]="name" [attr.id]="name" (change)="check($event)" [attr.multiple]="multiple"/>
<label [attr.for]="name">
<span>{{ placeholder || labels.chooseAFile }}</span>
<small>{{ labels.or }} <strong class="link">{{ labels.clickToBrowse }}</strong></small>
</label>
</div>
`
</template>`
})
export class NovoFileInputElement implements ControlValueAccessor, OnInit, OnDestroy {
@ViewChild('fileInput')
fileInput: TemplateRef<any>;
@ViewChild('fileOutput')
fileOutput: TemplateRef<any>;
@ViewChild('container', { read: ViewContainerRef })
container: ViewContainerRef;


@Input() name: string;
@Input() multiple: boolean = false;
@Input() disabled: boolean = false;
@Input() placeholder: string;
@Input() layoutOptions: { order?: string, download?: boolean, labelStyle?: string };
@Input() value: Array<any> = [];

value: Array<any> = [];
elements: Array<any> = [];
files: Array<any> = [];
model: any;
active: boolean = false;
Expand All @@ -67,6 +88,8 @@ export class NovoFileInputElement implements ControlValueAccessor, OnInit, OnDes
['dragenter', 'dragleave', 'dragover', 'drop'].forEach(type => {
this.element.nativeElement.addEventListener(type, this.commands[type]);
});
this.updateLayout();
this.setInitialFileList();
}

ngOnDestroy() {
Expand All @@ -75,6 +98,32 @@ export class NovoFileInputElement implements ControlValueAccessor, OnInit, OnDes
});
}

updateLayout() {
this.layoutOptions = Object.assign({}, LAYOUT_DEFAULTS, this.layoutOptions);
this.insertTemplatesBasedOnLayout();
}

insertTemplatesBasedOnLayout() {
let order;
switch (this.layoutOptions['order']) {
case 'displayFilesBelow':
order = ['fileInput', 'fileOutput'];
break;
default:
order = ['fileOutput', 'fileInput'];
}
order.forEach((template) => {
this.container.createEmbeddedView(this[template], 0);
});
return order;
}

setInitialFileList() {
if (this.value && this.value.length > 0) {
this.files = this.value;
}
}

dragEnterHandler(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
Expand Down

0 comments on commit 67469d6

Please sign in to comment.