Skip to content

Commit

Permalink
Merge pull request #328 from renehamburger/develop
Browse files Browse the repository at this point in the history
Move to latest Angular & Handstontable version
  • Loading branch information
rene-leanix authored Feb 23, 2017
2 parents ab762fc + 9776404 commit 0954746
Show file tree
Hide file tree
Showing 36 changed files with 809 additions and 2,099 deletions.
9 changes: 7 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
/demo/lib
npm-debug.log

# WebStorm
.idea
# IDEs
/.idea
/.vscode

# ignore build and dist for now
/build
Expand All @@ -18,3 +19,7 @@ npm-debug.log
/components/**/*.js.map

/logs

# AoT generated files
/**/*.metadata.json
/**/*.ngfactory.ts
23 changes: 23 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
language: node_js
node_js:
- "6"

#before_install:
# - export CHROME_BIN=chromium-browser
# - export DISPLAY=:99.0
# - sh -e /etc/init.d/xvfb start

script:
- npm run test

#after_success:
# - ./node_modules/.bin/codecov

addons:
# firefox: "latest"
apt:
sources:
- ubuntu-toolchain-r-test
# required by node-gyp to build some packages
packages:
- g++-4.8
12 changes: 12 additions & 0 deletions components/handsontable/handsontable.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';

import { HotTable } from './handsontable';

@NgModule({
imports: [CommonModule],
declarations: [HotTable],
exports: [HotTable]
})
export class HotTableModule {
}
199 changes: 139 additions & 60 deletions components/handsontable/handsontable.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {OnInit, OnDestroy, Directive, EventEmitter, ElementRef} from '@angular/core';
import Handsontable = require('handsontable/dist/handsontable.full.js');
import {OnInit, OnDestroy, OnChanges, SimpleChanges, Directive, EventEmitter,
ElementRef, Input, NgZone} from '@angular/core';
import {Observable, Subscription} from 'rxjs';
import * as Handsontable from 'handsontable/dist/handsontable.full.js';

let eventNames:Array<string> = ['afterCellMetaReset', 'afterChange',
let eventNames: Array<string> = ['afterCellMetaReset', 'afterChange',
'afterCreateCol', 'afterCreateRow', 'afterDeselect',
'afterDestroy', 'afterDocumentKeyDown', 'afterGetCellMeta', 'afterGetColHeader', 'afterGetRowHeader',
'afterInit', 'afterIsMultipleSelectionCheck', 'afterLoadData',
Expand All @@ -18,74 +20,149 @@ let eventNames:Array<string> = ['afterCellMetaReset', 'afterChange',

@Directive({
selector: 'hot-table',
properties: [
'data',
'colHeaders',
'columns',
'colWidths',
'options'
],
events: eventNames
outputs: eventNames
})
export class HotTable implements OnInit, OnDestroy {
private inst:any;
private view:any;

private data:Array<any> = [];
private colHeaders:Array<string>;
private columns:Array<any>;
private colWidths:Array<number>;
private options:any;

constructor(private element:ElementRef) {
export class HotTable implements OnInit, OnDestroy, OnChanges {
@Input() private data: Array<any> = [];
@Input('pagedData') private pagedData$: Observable<Array<any>>;
@Input('col-headers') protected colHeaders: Array<string>;
@Input() protected columns: Array<any>;
@Input('col-widths') protected colWidths: Array<number>;
@Input() private options: any;

private inst: any;
private view: any;
private pagedDataSubscription: Subscription;
private zoneQueue: (() => void)[] = [];
private zoneQueueTimeout: number = 0;

constructor(private element: ElementRef, private ngZone: NgZone) {
// fill events dynamically
eventNames.forEach(eventName => {
this[eventName] = new EventEmitter();
});
}

parseAutoComplete(column, dataSet) {
private parseAutoComplete(options: any) {
let inst = this.inst;

if (typeof column.source === 'string') {
let relatedField:string = column.source;
column.source = function (query, process) {
let row:number = inst.getSelected()[0];
let data:any = dataSet[row];

if (!data) {
return;
}

let fieldParts:Array<string> = relatedField.split('.');
let o:any = data;
for (let i = 0; i < fieldParts.length; i++) {
o = o[fieldParts[i]];
const columns = this.columns || options.columns;
const dataSet = options.data;

if (columns) {
columns.forEach(column => {
if (typeof column.source === 'string') {
let relatedField: string = column.source;
column.source = function (_query, process) {
let row: number = inst.getSelected()[0];
let data: any = dataSet[row];

if (!data) {
return;
}

let fieldParts: Array<string> = relatedField.split('.');
let o: any = data;
for (let i = 0; i < fieldParts.length; i++) {
o = o[fieldParts[i]];
}

process(o.map(item => {
return !column.optionField ? item : item[column.optionField];
}));
};
}

process(o.map(item => {
return !column.optionField ? item : item[column.optionField];
}));
};
});
}
}

ngOnInit() {
this.checkInputs();

this.view = document.createElement('div');
this.view.class = 'handsontable-container';
this.element.nativeElement.appendChild(this.view);

let htOptions:any = {
data: this.data
const options = this.getCurrentOptions();

this.ngZone.runOutsideAngular(() => {
this.inst = new Handsontable(this.view, options);
});

this.parseAutoComplete(options);

if (this.pagedData$) {
this.data = [];
this.pagedDataSubscription = this.pagedData$.subscribe((newPagedData) => {
Array.prototype.push.apply(this.data, newPagedData);
this.inst.loadData(this.data);
this.parseAutoComplete(options);
this.inst.updateSettings(options);
});
}
}

ngOnDestroy() {
if (this.view) {
this.view.remove();
}
if (this.pagedDataSubscription) {
this.pagedDataSubscription.unsubscribe();
}
if (this.inst) {
this.inst.destroy();
}
}

ngOnChanges(changes: SimpleChanges) {
if ('options' in changes && this.inst) {
this.inst.updateSettings(this.getCurrentOptions());
}
// tslint:disable-next-line:no-string-literal
if (changes['data'] && !changes['data'].isFirstChange()) {
this.inst.loadData(this.data);
}
}

private checkInputs(): boolean {
const dataCount = Number(!!this.pagedData$) + Number(!!this.data) +
Number(!!(this.options && this.options.data));
if (dataCount > 1) {
console.error('[pagedData], [data] and [options.data] are all mutually' +
' exclusive');
return false;
} else if (dataCount === 0) {
console.error('One of [pagedData], [data] and [options.data] needs' +
' to be provided');
return false;
}
}

private getCurrentOptions(): any {
let htOptions: any = {
data: this.data || null
};

eventNames.forEach(eventName => {
htOptions[eventName] = data => {
this[eventName].next(data);
};
// Only register the event if the emitter has an observer (i.e., if the output is actually being used)
if (<EventEmitter<any[]>>this[eventName].observers.length) {
htOptions[eventName] = (...args) => {
let data: any[] = [];
// Handsontable event handlers are always called with 6 arguments. Cut off any trailing undefined values.
for (let index = args.length; index >= 0; index--) {
if (args[index] !== void 0) {
data = args.slice(0, index + 1);
break;
}
}
// Queue all emissions to only cause 1 Zone.run() call per tick.
this.queueForRunningInZone(() => {
this[eventName].emit(data);
});
};
}
});

let additionalFields:Array<string> = ['colHeaders', 'colWidths', 'columns'];
let additionalFields: Array<string> = ['colHeaders', 'colWidths', 'columns'];
additionalFields.forEach(field => {
if (this[field]) {
Object.assign(htOptions, {
Expand All @@ -98,20 +175,22 @@ export class HotTable implements OnInit, OnDestroy {
Object.assign(htOptions, this.options);
}

this.inst = Handsontable(this.view, htOptions);

if (this.columns && this.columns.length) {
this.columns.forEach(column => {
this.parseAutoComplete(column, this.data);
});
}
return htOptions;
}

ngOnDestroy() {
if (this.view) {
this.view.remove();
private queueForRunningInZone(fn: () => void): void {
if (this.zoneQueueTimeout) {
clearTimeout(this.zoneQueueTimeout);
}
this.zoneQueue.push(fn);
this.zoneQueueTimeout = setTimeout(() => {
this.ngZone.run(() => {
this.zoneQueue.forEach(f => f());
});
this.zoneQueue = [];
this.zoneQueueTimeout = 0;
});
}
}

export const handsontable:Array<any> = [HotTable];
export const handsontable: Array<any> = [HotTable];
2 changes: 1 addition & 1 deletion components/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/// <reference path="../tsd.d.ts" />
export * from './handsontable/handsontable';
export * from './handsontable/handsontable.module';
37 changes: 37 additions & 0 deletions demo/components/demo.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

import {Component} from '@angular/core';
import {HandsontableSection} from './components/handsontable-section';
// import {DemoHeader} from './components/demo-header';

let gettingStarted = require('../getting-started.md');

@Component({
selector: 'app',
template: `
<main class="bd-pageheader">
<div class="container">
<h1>ng2-handsontable</h1>
<p>The Angular2 directive for <a href="https://github.com/handsontable/handsontable" target="_blank">Handsontable</a></p>
<a class="btn btn-primary" href="https://github.com/valor-software/ng2-handsontable">View on GitHub</a>
<div class="row">
<div class="col-lg-1"><iframe src="https://ghbtns.com/github-btn.html?user=valor-software&repo=ng2-handsontable&type=star&count=true" frameborder="0" scrolling="0" width="170px" height="20px"></iframe></div>
<div class="col-lg-1"><iframe src="https://ghbtns.com/github-btn.html?user=valor-software&repo=ng2-handsontable&type=fork&count=true" frameborder="0" scrolling="0" width="170px" height="20px"></iframe></div>
</div>
</div>
</main>
<div class="container">
<section id="getting-started">${gettingStarted}</section>
<handsontable-section class="col-md-12"></handsontable-section>
</div>
<footer class="footer">
<div class="container">
<p class="text-muted text-center"><a href="https://github.com/valor-software/ng2-handsontable">ng2-handsontable</a> is maintained by <a href="https://github.com/valor-software">valor-software</a>.</p>
</div>
</footer>
`
})
export class DemoComponent {
}
Loading

0 comments on commit 0954746

Please sign in to comment.