Skip to content

Commit fe0fe73

Browse files
authored
feat(table): Enhancing editable tables
1 parent cd44c73 commit fe0fe73

37 files changed

+483
-170
lines changed

demo/pages/elements/form/FormDemo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export class FormDemoComponent {
171171
this.numberControl = new TextBoxControl({ type: 'number', key: 'number', label: 'Number' });
172172
this.currencyControl = new TextBoxControl({ type: 'currency', key: 'currency', label: 'Currency', currencyFormat: '$ USD' });
173173
this.floatControl = new TextBoxControl({ type: 'float', key: 'float', label: 'Float' });
174-
this.percentageControl = new TextBoxControl({ type: 'percentage', key: 'percentage', label: 'Percent' });
174+
this.percentageControl = new TextBoxControl({ type: 'percentage', key: 'percentage', label: 'Percent', required: true });
175175
this.quickNoteControl = new QuickNoteControl({ key: 'note', label: 'Note', config: this.quickNoteConfig, required: true, tooltip: 'Quicknote' });
176176
this.textForm = formUtils.toFormGroup([this.textControl, this.emailControl, this.numberControl, this.currencyControl, this.floatControl, this.percentageControl, this.quickNoteControl]);
177177

demo/pages/elements/table/TableData.ts

Lines changed: 181 additions & 81 deletions
Large diffs are not rendered by default.

demo/pages/elements/table/TableDemo.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ let SelectAllTableDemoTpl = require('./templates/SelectAllTableDemo.html');
1111
let MovieTableDemoTpl = require('./templates/MovieTableDemo.html');
1212
let TotalFooterTableDemoTpl = require('./templates/TotalFooterTableDemo.html');
1313
// Vendor
14-
import { DateCell, BaseRenderer, NovoTableElement, NovoTableConfig, TextBoxControl, TablePickerControl, SelectControl, NovoDropdownCell } from './../../../../index';
14+
import { DateCell, PercentageCell, BaseRenderer, NovoTableElement, NovoTableConfig, TextBoxControl, TablePickerControl, SelectControl, NovoDropdownCell } from './../../../../index';
1515

1616
const template = `
1717
<div class="container">
18-
<h1>Table <small><a target="_blank" href="https://bullhorn.github.io/novo-elements/blob/master/src/elements/table">(source)</a></small></h1>
18+
<h1>Table <small><a target="_blank" href="https://bullhorn.github.io/novo-elements/blob/master/src/elements/table">(source)</a></small></h1>
1919
<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>
2020
<h2>Types</h2>
2121
@@ -186,6 +186,12 @@ export class TableDemoComponent implements OnInit {
186186
filtering: true,
187187
range: true
188188
},
189+
{
190+
title: '%',
191+
name: 'percent',
192+
ordering: true,
193+
renderer: PercentageCell
194+
},
189195
{
190196
title: 'Salary',
191197
name: 'salary',
@@ -318,26 +324,32 @@ export class TableDemoComponent implements OnInit {
318324
];
319325
this.editable = {
320326
columns: [
321-
{ title: 'Name', name: 'name', ordering: true, filtering: true, editor: new TablePickerControl({ key: 'name', config: { options: names } }) },
322-
{ title: 'Job Type', name: 'jobType', ordering: true, filtering: true, editor: new SelectControl({ key: 'jobType', options: ['Freelance', 'Contract', 'Billable'] }) },
327+
{
328+
title: 'Name', name: 'name', ordering: true, filtering: true, editorType: 'TablePickerControl', editorConfig: { key: 'name', config: { options: names } }
329+
},
330+
{
331+
title: 'Job Type', name: 'jobType', ordering: true, filtering: true, editorType: 'SelectControl', editorConfig: { key: 'jobType', options: ['Freelance', 'Contract', 'Billable'] }
332+
},
323333
{
324334
title: 'Rate',
325335
name: 'rate',
326336
ordering: true,
327337
filtering: true,
328-
editor: new TextBoxControl({
338+
renderer: PercentageCell,
339+
editorType: 'TextBoxControl',
340+
editorConfig: {
329341
key: 'rate',
330-
type: 'currency',
342+
type: 'percentage',
331343
required: true,
332344
interactions: [
333345
{
334346
event: 'change',
335347
script: (form) => {
336348
console.log('Form Interaction Called!', form); // tslint:disable-line
337349
if (form.value.rate) {
338-
if (Number(form.value.rate) >= 1000) {
350+
if (Number(form.value.rate) >= .75) {
339351
form.controls.rating.setValue('High');
340-
} else if (Number(form.value.rate) >= 100) {
352+
} else if (Number(form.value.rate) >= .50) {
341353
form.controls.rating.setValue('Medium');
342354
} else {
343355
form.controls.rating.setValue('Low');
@@ -346,14 +358,14 @@ export class TableDemoComponent implements OnInit {
346358
}
347359
}
348360
]
349-
})
361+
}
350362
},
351363
{ title: 'Rating', name: 'rating' }
352364
],
353365
rows: new ArrayCollection([
354366
{ id: 1, name: 'Joshua Godi', jobType: 'Freelance', rate: null, rating: 'Low' },
355-
{ id: 2, name: 'Brian Kimball', jobType: 'Contact', rate: 100, rating: 'Medium' },
356-
{ id: 3, name: 'Kameron Sween', jobType: 'Billable', rate: 1000, rating: 'High' }
367+
{ id: 2, name: 'Brian Kimball', jobType: 'Contact', rate: .50, rating: 'Medium' },
368+
{ id: 3, name: 'Kameron Sween', jobType: 'Billable', rate: 1.00, rating: 'High' }
357369
]),
358370
config: {
359371
paging: {
@@ -446,7 +458,7 @@ export class TableDemoComponent implements OnInit {
446458
// TODO - save data - fetch the data
447459
setTimeout(() => {
448460
table.displayToastMessage({ icon: 'check', theme: 'success', message: 'Saved!' }, 2000);
449-
table.leaveEditMode();
461+
table.saveChanges();
450462
}, 2000);
451463
} else {
452464
console.log('ERRORS!', errorsOrData); // tslint:disable-line

index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export { EntityPickerResult, EntityPickerResults } from './src/elements/picker/e
5454
export { ChecklistPickerResults } from './src/elements/picker/extras/checklist-picker-results/ChecklistPickerResults';
5555
export { BaseRenderer } from './src/elements/table/extras/base-renderer/BaseRenderer';
5656
export { DateCell } from './src/elements/table/extras/date-cell/DateCell';
57+
export { PercentageCell } from './src/elements/table/extras/percentage-cell/PercentageCell';
5758
export { NovoDropdownCell, INovoDropdownCellConfig } from './src/elements/table/extras/dropdown-cell/DropdownCell';
5859
export { FormValidators } from './src/elements/form/FormValidators';
5960
export { FormUtils } from './src/utils/form-utils/FormUtils';

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"angular2"
3131
],
3232
"author": "Joshua Godi <joshuagodi@gmail.com>",
33-
"contributors": [{
33+
"contributors": [
34+
{
3435
"name": "Brian Kimball",
3536
"email": "bkimball@bullhorn.com"
3637
},
@@ -105,8 +106,8 @@
105106
"expose-loader": "0.7.1",
106107
"extract-text-webpack-plugin": "2.0.0-beta.4",
107108
"file-loader": "0.9.0",
108-
"gh-pages": "0.12.0",
109109
"galaxy-parser": "^1.0.14",
110+
"gh-pages": "0.12.0",
110111
"html-webpack-plugin": "2.24.1",
111112
"husky": "0.11.9",
112113
"ie-shim": "0.1.0",
@@ -167,4 +168,4 @@
167168
"lcov": "/coverage/lcov.info"
168169
}
169170
}
170-
}
171+
}

src/elements/form/Control.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ export class NovoCustomControlContainerElement {
8989
<!--TODO prefix/suffix on the control-->
9090
<div class="novo-control-input-container novo-control-input-with-label" *ngSwitchCase="'textbox'" [tooltip]="tooltip" [tooltipPosition]="tooltipPosition">
9191
<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)">
92-
<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>
92+
<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>
93+
<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>
9394
<label class="input-label" *ngIf="control.subType === 'currency'">{{control.currencyFormat}}</label>
9495
<label class="input-label" *ngIf="control.subType === 'percentage'">%</label>
9596
</div>
@@ -187,8 +188,6 @@ export class NovoControlElement extends OutsideClick implements OnInit, OnDestro
187188
@Input() condensed: boolean = false;
188189
@Output() change: EventEmitter<any> = new EventEmitter();
189190

190-
valueChangeSubscription: any;
191-
192191
@Output('blur')
193192
get onBlur(): Observable<FocusEvent> {
194193
return this._blurEmitter.asObservable();
@@ -204,9 +203,12 @@ export class NovoControlElement extends OutsideClick implements OnInit, OnDestro
204203
private _focused: boolean = false;
205204
private _enteredText: string = '';
206205
formattedValue: string = '';
206+
percentValue: number;
207207
maxLengthMet: boolean = false;
208208
characterCount: number = 0;
209209
private forceClearSubscription: any;
210+
private percentChangeSubscription: any;
211+
private valueChangeSubscription: any;
210212

211213
constructor(element: ElementRef, public labels: NovoLabelService, private toast: NovoToastService) {
212214
super(element);
@@ -266,6 +268,16 @@ export class NovoControlElement extends OutsideClick implements OnInit, OnDestro
266268
}
267269
}
268270
}
271+
if (this.control && this.control.subType === 'percentage') {
272+
if (!Helpers.isEmpty(this.control.value)) {
273+
this.percentValue = Number((this.control.value * 100).toFixed(6).replace(/\.?0*$/, ''));
274+
this.percentChangeSubscription = this.form.controls[this.control.key].displayValueChanges.subscribe(value => {
275+
if (!Helpers.isEmpty(value)) {
276+
this.percentValue = Number((value * 100).toFixed(6).replace(/\.?0*$/, ''));
277+
}
278+
});
279+
}
280+
}
269281
}
270282

271283
executeInteraction(interaction) {
@@ -285,6 +297,10 @@ export class NovoControlElement extends OutsideClick implements OnInit, OnDestro
285297
// Un-listen for clear events
286298
this.forceClearSubscription.unsubscribe();
287299
}
300+
if (this.percentChangeSubscription) {
301+
// Un-listen for clear events
302+
this.percentChangeSubscription.unsubscribe();
303+
}
288304
super.ngOnDestroy();
289305
}
290306

@@ -412,8 +428,8 @@ export class NovoControlElement extends OutsideClick implements OnInit, OnDestro
412428
}
413429

414430
restrictKeys(event) {
415-
const NUMBERS_ONLY = /[0-9]/;
416-
const NUMBERS_WITH_DECIMAL = /[0-9\.]/;
431+
const NUMBERS_ONLY = /[0-9\-]/;
432+
const NUMBERS_WITH_DECIMAL = /[0-9\.\-]/;
417433
let key = String.fromCharCode(event.charCode);
418434
//Types
419435
if (this.control.subType === 'number' && !NUMBERS_ONLY.test(key)) {
@@ -427,6 +443,18 @@ export class NovoControlElement extends OutsideClick implements OnInit, OnDestro
427443
}
428444
}
429445

446+
handlePercentChange(event: KeyboardEvent) {
447+
let value = event.target['value'];
448+
let percent = Helpers.isEmpty(value) ? null : Number((value / 100).toFixed(6).replace(/\.?0*$/, ''));
449+
if (percent) {
450+
this.change.emit(percent);
451+
this.form.controls[this.control.key].setValue(percent);
452+
} else {
453+
this.change.emit(null);
454+
this.form.controls[this.control.key].setValue(null);
455+
}
456+
}
457+
430458
emitChange(value) {
431459
this.change.emit(value);
432460
this.checkMaxLength(value);

src/elements/form/FormControls.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './controls/BaseControl';
2+
export * from './controls/ControlFactory';
23
export * from './controls/address/AddressControl';
34
export * from './controls/check-list/CheckListControl';
45
export * from './controls/checkbox/CheckboxControl';

src/elements/form/NovoFormControl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
// NG2
22
import { FormControl, Validators } from '@angular/forms';
3+
import { EventEmitter } from '@angular/core';
34
// APP
45
import { NovoControlConfig } from './FormControls';
6+
import { Helpers } from '../../utils/Helpers';
57

68
export class NovoFormControl extends FormControl {
9+
displayValueChanges: EventEmitter<any> = new EventEmitter<any>();
710
hidden: boolean;
811
required: boolean;
912
readOnly: boolean;
@@ -88,6 +91,7 @@ export class NovoFormControl extends FormControl {
8891
this.markAsDirty();
8992
this.markAsTouched();
9093
// TODO: Should we set defaults on these?
94+
this.displayValueChanges.emit(value);
9195
super.setValue(value, { onlySelf, emitEvent, emitModelToViewChange, emitViewToModelChange });
9296
}
9397

src/elements/form/controls/BaseControl.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ describe('Control: BaseControl', () => {
66

77
describe('Base Config', () => {
88
beforeEach(() => {
9-
control = new BaseControl({});
9+
control = new BaseControl();
1010
});
1111

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

6262
describe('With Config', () => {
6363
beforeEach(() => {
64-
control = new BaseControl({
64+
control = new BaseControl('BaseControl', {
6565
validators: ['TEST_VALIDATORS'],
6666
value: 'TEST_VALUE',
6767
key: 'TEST_KEY',

src/elements/form/controls/BaseControl.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ export interface NovoControlConfig {
4545
}
4646

4747
export class BaseControl {
48+
__type: string = 'BaseControl';
49+
__config: NovoControlConfig;
50+
4851
validators: Array<any>;
4952
asyncValidators?: Array<any>;
5053
value: any;
@@ -83,7 +86,9 @@ export class BaseControl {
8386
customControlConfig?: any;
8487
military?: boolean;
8588

86-
constructor(config: NovoControlConfig) {
89+
constructor(type: string = 'BaseControl', config: NovoControlConfig = {}) {
90+
this.__type = type;
91+
this.__config = config;
8792
this.validators = config.validators || [];
8893
this.asyncValidators = config.asyncValidators || [];
8994
this.value = config.value;

0 commit comments

Comments
 (0)