Skip to content

Commit

Permalink
feat(table): Enhancing editable tables
Browse files Browse the repository at this point in the history
  • Loading branch information
jgodi authored Jun 30, 2017
1 parent cd44c73 commit fe0fe73
Show file tree
Hide file tree
Showing 37 changed files with 483 additions and 170 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 @@ -171,7 +171,7 @@ export class FormDemoComponent {
this.numberControl = new TextBoxControl({ type: 'number', key: 'number', label: 'Number' });
this.currencyControl = new TextBoxControl({ type: 'currency', key: 'currency', label: 'Currency', currencyFormat: '$ USD' });
this.floatControl = new TextBoxControl({ type: 'float', key: 'float', label: 'Float' });
this.percentageControl = new TextBoxControl({ type: 'percentage', key: 'percentage', label: 'Percent' });
this.percentageControl = new TextBoxControl({ type: 'percentage', key: 'percentage', label: 'Percent', required: true });
this.quickNoteControl = new QuickNoteControl({ key: 'note', label: 'Note', config: this.quickNoteConfig, required: true, tooltip: 'Quicknote' });
this.textForm = formUtils.toFormGroup([this.textControl, this.emailControl, this.numberControl, this.currencyControl, this.floatControl, this.percentageControl, this.quickNoteControl]);

Expand Down
262 changes: 181 additions & 81 deletions demo/pages/elements/table/TableData.ts

Large diffs are not rendered by default.

36 changes: 24 additions & 12 deletions demo/pages/elements/table/TableDemo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ let SelectAllTableDemoTpl = require('./templates/SelectAllTableDemo.html');
let MovieTableDemoTpl = require('./templates/MovieTableDemo.html');
let TotalFooterTableDemoTpl = require('./templates/TotalFooterTableDemo.html');
// Vendor
import { DateCell, BaseRenderer, NovoTableElement, NovoTableConfig, TextBoxControl, TablePickerControl, SelectControl, NovoDropdownCell } from './../../../../index';
import { DateCell, PercentageCell, BaseRenderer, NovoTableElement, NovoTableConfig, TextBoxControl, TablePickerControl, SelectControl, NovoDropdownCell } from './../../../../index';

const template = `
<div class="container">
<h1>Table <small><a target="_blank" href="https://bullhorn.github.io/novo-elements/blob/master/src/elements/table">(source)</a></small></h1>
<h1>Table <small><a target="_blank" href="https://bullhorn.github.io/novo-elements/blob/master/src/elements/table">(source)</a></small></h1>
<p>Tables allow users to view date in a tabular format and perform actions such as Sorting and Filtering. Different configuration are possible for pagination or infinite scroll. Feature to be added include: Custom Item Renderers, etc...</p>
<h2>Types</h2>
Expand Down Expand Up @@ -186,6 +186,12 @@ export class TableDemoComponent implements OnInit {
filtering: true,
range: true
},
{
title: '%',
name: 'percent',
ordering: true,
renderer: PercentageCell
},
{
title: 'Salary',
name: 'salary',
Expand Down Expand Up @@ -318,26 +324,32 @@ export class TableDemoComponent implements OnInit {
];
this.editable = {
columns: [
{ title: 'Name', name: 'name', ordering: true, filtering: true, editor: new TablePickerControl({ key: 'name', config: { options: names } }) },
{ title: 'Job Type', name: 'jobType', ordering: true, filtering: true, editor: new SelectControl({ key: 'jobType', options: ['Freelance', 'Contract', 'Billable'] }) },
{
title: 'Name', name: 'name', ordering: true, filtering: true, editorType: 'TablePickerControl', editorConfig: { key: 'name', config: { options: names } }
},
{
title: 'Job Type', name: 'jobType', ordering: true, filtering: true, editorType: 'SelectControl', editorConfig: { key: 'jobType', options: ['Freelance', 'Contract', 'Billable'] }
},
{
title: 'Rate',
name: 'rate',
ordering: true,
filtering: true,
editor: new TextBoxControl({
renderer: PercentageCell,
editorType: 'TextBoxControl',
editorConfig: {
key: 'rate',
type: 'currency',
type: 'percentage',
required: true,
interactions: [
{
event: 'change',
script: (form) => {
console.log('Form Interaction Called!', form); // tslint:disable-line
if (form.value.rate) {
if (Number(form.value.rate) >= 1000) {
if (Number(form.value.rate) >= .75) {
form.controls.rating.setValue('High');
} else if (Number(form.value.rate) >= 100) {
} else if (Number(form.value.rate) >= .50) {
form.controls.rating.setValue('Medium');
} else {
form.controls.rating.setValue('Low');
Expand All @@ -346,14 +358,14 @@ export class TableDemoComponent implements OnInit {
}
}
]
})
}
},
{ title: 'Rating', name: 'rating' }
],
rows: new ArrayCollection([
{ id: 1, name: 'Joshua Godi', jobType: 'Freelance', rate: null, rating: 'Low' },
{ id: 2, name: 'Brian Kimball', jobType: 'Contact', rate: 100, rating: 'Medium' },
{ id: 3, name: 'Kameron Sween', jobType: 'Billable', rate: 1000, rating: 'High' }
{ id: 2, name: 'Brian Kimball', jobType: 'Contact', rate: .50, rating: 'Medium' },
{ id: 3, name: 'Kameron Sween', jobType: 'Billable', rate: 1.00, rating: 'High' }
]),
config: {
paging: {
Expand Down Expand Up @@ -446,7 +458,7 @@ export class TableDemoComponent implements OnInit {
// TODO - save data - fetch the data
setTimeout(() => {
table.displayToastMessage({ icon: 'check', theme: 'success', message: 'Saved!' }, 2000);
table.leaveEditMode();
table.saveChanges();
}, 2000);
} else {
console.log('ERRORS!', errorsOrData); // tslint:disable-line
Expand Down
1 change: 1 addition & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export { EntityPickerResult, EntityPickerResults } from './src/elements/picker/e
export { ChecklistPickerResults } from './src/elements/picker/extras/checklist-picker-results/ChecklistPickerResults';
export { BaseRenderer } from './src/elements/table/extras/base-renderer/BaseRenderer';
export { DateCell } from './src/elements/table/extras/date-cell/DateCell';
export { PercentageCell } from './src/elements/table/extras/percentage-cell/PercentageCell';
export { NovoDropdownCell, INovoDropdownCellConfig } from './src/elements/table/extras/dropdown-cell/DropdownCell';
export { FormValidators } from './src/elements/form/FormValidators';
export { FormUtils } from './src/utils/form-utils/FormUtils';
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"angular2"
],
"author": "Joshua Godi <joshuagodi@gmail.com>",
"contributors": [{
"contributors": [
{
"name": "Brian Kimball",
"email": "bkimball@bullhorn.com"
},
Expand Down Expand Up @@ -105,8 +106,8 @@
"expose-loader": "0.7.1",
"extract-text-webpack-plugin": "2.0.0-beta.4",
"file-loader": "0.9.0",
"gh-pages": "0.12.0",
"galaxy-parser": "^1.0.14",
"gh-pages": "0.12.0",
"html-webpack-plugin": "2.24.1",
"husky": "0.11.9",
"ie-shim": "0.1.0",
Expand Down Expand Up @@ -167,4 +168,4 @@
"lcov": "/coverage/lcov.info"
}
}
}
}
38 changes: 33 additions & 5 deletions src/elements/form/Control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ export class NovoCustomControlContainerElement {
<!--TODO prefix/suffix on the control-->
<div class="novo-control-input-container novo-control-input-with-label" *ngSwitchCase="'textbox'" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition">
<input *ngIf="control.type !== 'number'" [formControlName]="control.key" [id]="control.key" [type]="control.type" [placeholder]="control.placeholder" (input)="emitChange($event)" [maxlength]="control.maxlength" (focus)="handleFocus($event)" (blur)="handleBlur($event)">
<input *ngIf="control.type === 'number'" [formControlName]="control.key" [id]="control.key" [type]="control.type" [placeholder]="control.placeholder" (keypress)="restrictKeys($event)" (input)="emitChange($event)" [maxlength]="control.maxlength" (focus)="handleFocus($event)" (blur)="handleBlur($event)" step="any" (mousewheel)="numberInput.blur()" #numberInput>
<input *ngIf="control.type === 'number' && control.subType !== 'percentage'" [formControlName]="control.key" [id]="control.key" [type]="control.type" [placeholder]="control.placeholder" (keypress)="restrictKeys($event)" (input)="emitChange($event)" [maxlength]="control.maxlength" (focus)="handleFocus($event)" (blur)="handleBlur($event)" step="any" (mousewheel)="numberInput.blur()" #numberInput>
<input *ngIf="control.type === 'number' && control.subType === 'percentage'" [type]="control.type" [placeholder]="control.placeholder" (keypress)="restrictKeys($event)" [value]="percentValue" (input)="handlePercentChange($event)" (focus)="handleFocus($event)" (blur)="handleBlur($event)" step="any" (mousewheel)="percentInput.blur()" #percentInput>
<label class="input-label" *ngIf="control.subType === 'currency'">{{control.currencyFormat}}</label>
<label class="input-label" *ngIf="control.subType === 'percentage'">%</label>
</div>
Expand Down Expand Up @@ -187,8 +188,6 @@ export class NovoControlElement extends OutsideClick implements OnInit, OnDestro
@Input() condensed: boolean = false;
@Output() change: EventEmitter<any> = new EventEmitter();

valueChangeSubscription: any;

@Output('blur')
get onBlur(): Observable<FocusEvent> {
return this._blurEmitter.asObservable();
Expand All @@ -204,9 +203,12 @@ export class NovoControlElement extends OutsideClick implements OnInit, OnDestro
private _focused: boolean = false;
private _enteredText: string = '';
formattedValue: string = '';
percentValue: number;
maxLengthMet: boolean = false;
characterCount: number = 0;
private forceClearSubscription: any;
private percentChangeSubscription: any;
private valueChangeSubscription: any;

constructor(element: ElementRef, public labels: NovoLabelService, private toast: NovoToastService) {
super(element);
Expand Down Expand Up @@ -266,6 +268,16 @@ export class NovoControlElement extends OutsideClick implements OnInit, OnDestro
}
}
}
if (this.control && this.control.subType === 'percentage') {
if (!Helpers.isEmpty(this.control.value)) {
this.percentValue = Number((this.control.value * 100).toFixed(6).replace(/\.?0*$/, ''));
this.percentChangeSubscription = this.form.controls[this.control.key].displayValueChanges.subscribe(value => {
if (!Helpers.isEmpty(value)) {
this.percentValue = Number((value * 100).toFixed(6).replace(/\.?0*$/, ''));
}
});
}
}
}

executeInteraction(interaction) {
Expand All @@ -285,6 +297,10 @@ export class NovoControlElement extends OutsideClick implements OnInit, OnDestro
// Un-listen for clear events
this.forceClearSubscription.unsubscribe();
}
if (this.percentChangeSubscription) {
// Un-listen for clear events
this.percentChangeSubscription.unsubscribe();
}
super.ngOnDestroy();
}

Expand Down Expand Up @@ -412,8 +428,8 @@ export class NovoControlElement extends OutsideClick implements OnInit, OnDestro
}

restrictKeys(event) {
const NUMBERS_ONLY = /[0-9]/;
const NUMBERS_WITH_DECIMAL = /[0-9\.]/;
const NUMBERS_ONLY = /[0-9\-]/;
const NUMBERS_WITH_DECIMAL = /[0-9\.\-]/;
let key = String.fromCharCode(event.charCode);
//Types
if (this.control.subType === 'number' && !NUMBERS_ONLY.test(key)) {
Expand All @@ -427,6 +443,18 @@ export class NovoControlElement extends OutsideClick implements OnInit, OnDestro
}
}

handlePercentChange(event: KeyboardEvent) {
let value = event.target['value'];
let percent = Helpers.isEmpty(value) ? null : Number((value / 100).toFixed(6).replace(/\.?0*$/, ''));
if (percent) {
this.change.emit(percent);
this.form.controls[this.control.key].setValue(percent);
} else {
this.change.emit(null);
this.form.controls[this.control.key].setValue(null);
}
}

emitChange(value) {
this.change.emit(value);
this.checkMaxLength(value);
Expand Down
1 change: 1 addition & 0 deletions src/elements/form/FormControls.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './controls/BaseControl';
export * from './controls/ControlFactory';
export * from './controls/address/AddressControl';
export * from './controls/check-list/CheckListControl';
export * from './controls/checkbox/CheckboxControl';
Expand Down
4 changes: 4 additions & 0 deletions src/elements/form/NovoFormControl.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
// NG2
import { FormControl, Validators } from '@angular/forms';
import { EventEmitter } from '@angular/core';
// APP
import { NovoControlConfig } from './FormControls';
import { Helpers } from '../../utils/Helpers';

export class NovoFormControl extends FormControl {
displayValueChanges: EventEmitter<any> = new EventEmitter<any>();
hidden: boolean;
required: boolean;
readOnly: boolean;
Expand Down Expand Up @@ -88,6 +91,7 @@ export class NovoFormControl extends FormControl {
this.markAsDirty();
this.markAsTouched();
// TODO: Should we set defaults on these?
this.displayValueChanges.emit(value);
super.setValue(value, { onlySelf, emitEvent, emitModelToViewChange, emitViewToModelChange });
}

Expand Down
4 changes: 2 additions & 2 deletions src/elements/form/controls/BaseControl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe('Control: BaseControl', () => {

describe('Base Config', () => {
beforeEach(() => {
control = new BaseControl({});
control = new BaseControl();
});

it('should set the validators', () => {
Expand Down Expand Up @@ -61,7 +61,7 @@ describe('Control: BaseControl', () => {

describe('With Config', () => {
beforeEach(() => {
control = new BaseControl({
control = new BaseControl('BaseControl', {
validators: ['TEST_VALIDATORS'],
value: 'TEST_VALUE',
key: 'TEST_KEY',
Expand Down
7 changes: 6 additions & 1 deletion src/elements/form/controls/BaseControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export interface NovoControlConfig {
}

export class BaseControl {
__type: string = 'BaseControl';
__config: NovoControlConfig;

validators: Array<any>;
asyncValidators?: Array<any>;
value: any;
Expand Down Expand Up @@ -83,7 +86,9 @@ export class BaseControl {
customControlConfig?: any;
military?: boolean;

constructor(config: NovoControlConfig) {
constructor(type: string = 'BaseControl', config: NovoControlConfig = {}) {
this.__type = type;
this.__config = config;
this.validators = config.validators || [];
this.asyncValidators = config.asyncValidators || [];
this.value = config.value;
Expand Down
55 changes: 55 additions & 0 deletions src/elements/form/controls/ControlFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
AddressControl, CheckboxControl, CheckListControl, DateControl, DateTimeControl, EditorControl,
FileControl, NativeSelectControl, PickerControl, AppendToBodyPickerControl, TablePickerControl,
QuickNoteControl, RadioControl, ReadOnlyControl, TextAreaControl, TextBoxControl, SelectControl,
TilesControl, TimeControl
} from './index';
import { BaseControl } from './BaseControl';

export class ControlFactory {
static create(type: string, config: BaseControl): BaseControl {
switch (type) {
case 'AddressControl':
return new AddressControl(config);
case 'CheckboxControl':
return new CheckboxControl(config);
case 'CheckListControl':
return new CheckListControl(config);
case 'CheckListControl':
return new CheckListControl(config);
case 'DateTimeControl':
return new DateTimeControl(config);
case 'EditorControl':
return new EditorControl(config);
case 'FileControl':
return new FileControl(config);
case 'NativeSelectControl':
return new NativeSelectControl(config);
case 'PickerControl':
return new PickerControl(config);
case 'AppendToBodyPickerControl':
return new AppendToBodyPickerControl(config);
case 'TablePickerControl':
return new TablePickerControl(config);
case 'QuickNoteControl':
return new QuickNoteControl(config);
case 'RadioControl':
return new RadioControl(config);
case 'ReadOnlyControl':
return new ReadOnlyControl(config);
case 'TextAreaControl':
return new TextAreaControl(config);
case 'TextBoxControl':
return new TextBoxControl(config);
case 'SelectControl':
return new SelectControl(config);
case 'TilesControl':
return new TilesControl(config);
case 'TimeControl':
return new TimeControl(config);
default:
console.warn('[ControlFactory] - unable to find control for type. Make sure to set "editorType" and "editorConfig" on your column', type);
return null;
}
}
}
4 changes: 2 additions & 2 deletions src/elements/form/controls/address/AddressControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { FormValidators } from '../../FormValidators';

export class AddressControl extends BaseControl {
controlType = 'address';
constructor(config:NovoControlConfig) {
super(config);
constructor(config: NovoControlConfig) {
super('AddressControl', config);
this.validators.push(FormValidators.isValidAddress);
}
}
4 changes: 2 additions & 2 deletions src/elements/form/controls/check-list/CheckListControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { BaseControl, NovoControlConfig } from './../BaseControl';
export class CheckListControl extends BaseControl {
controlType = 'checklist';

constructor(config:NovoControlConfig) {
super(config);
constructor(config: NovoControlConfig) {
super('CheckListControl', config);
this.options = config.options || [];
}
}
4 changes: 2 additions & 2 deletions src/elements/form/controls/checkbox/CheckboxControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { BaseControl, NovoControlConfig } from './../BaseControl';
export class CheckboxControl extends BaseControl {
controlType = 'checkbox';

constructor(config:NovoControlConfig) {
super(config);
constructor(config: NovoControlConfig) {
super('CheckboxControl', config);
}
}
4 changes: 2 additions & 2 deletions src/elements/form/controls/date-time/DateTimeControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { BaseControl, NovoControlConfig } from './../BaseControl';
export class DateTimeControl extends BaseControl {
controlType = 'date-time';

constructor(config:NovoControlConfig) {
super(config);
constructor(config: NovoControlConfig) {
super('DateTimeControl', config);
}
}
Loading

0 comments on commit fe0fe73

Please sign in to comment.