diff --git a/task_2_ts/index.html b/task_2_ts/index.html
index 39bf0b3..5d5fb61 100644
--- a/task_2_ts/index.html
+++ b/task_2_ts/index.html
@@ -59,11 +59,11 @@
Data type
- Different entries
+ Different entries
- Min
+ Min
- Max
+ Max
@@ -72,8 +72,8 @@
-
+
+
@@ -83,7 +83,6 @@
diff --git a/task_2_ts/ts/main.ts b/task_2_ts/ts/main.ts
index cad5699..b9a3369 100644
--- a/task_2_ts/ts/main.ts
+++ b/task_2_ts/ts/main.ts
@@ -1,6 +1,11 @@
import '@fortawesome/fontawesome-free/css/all.css';
import '../css/layout.css';
import '@picocss/pico/css/pico.min.css';
+import {
+ computeDifferent,
+ computeMinMax,
+ numberCheckPredicate, stringOrNumberCheckPredicate
+} from './util';
import {
listRef, ref
} from './rendering';
@@ -11,6 +16,11 @@ import {
readCSV
} from './csv';
+
+// ┌ ┐
+// │ Define refs │
+// └ ┘
+const tableHeaderElement = document.getElementById( 'table-header' )!;
const dataList = listRef(
document.getElementById( 'table-body' )!,
[],
@@ -22,7 +32,7 @@ const dataList = listRef(
}
);
const headerList = listRef(
- document.getElementById( 'table-header' )!,
+ tableHeaderElement,
[],
'table-header',
{
@@ -31,31 +41,56 @@ const headerList = listRef(
'children': []
}
);
-const filter = ref( [ document.getElementById( 'filter' )! ], '' );
+const columnEntriesElement = document.getElementById( 'column-entries' )!;
+const columnMaxElement = document.getElementById( 'column-max' )!;
+const columnMinElement = document.getElementById( 'column-min' )!;
+const filter = ref( [], '' );
+const filterInput = document.getElementById( 'filter' )! as HTMLInputElement;
const filename = ref( [ document.getElementById( 'data-filename' )! ], '' );
const filetype = ref( [ document.getElementById( 'data-filetype' )! ], '' );
const filesize = ref( [ document.getElementById( 'data-filesize' )! ], '' );
const rowCount = ref( [ document.getElementById( 'data-rowcount' )! ], '' );
const columnName = ref( [ document.getElementById( 'column-selected' )! ], '' );
const columnDatatype = ref( [ document.getElementById( 'column-datatype' )! ], '' );
-const columnEntries = ref( [ document.getElementById( 'column-entries' )! ], '' );
-const columnMax = ref( [ document.getElementById( 'column-max' )! ], '' );
-const columnMin = ref( [ document.getElementById( 'column-min' )! ], '' );
+const columnEntries = ref( [ columnEntriesElement ], 0 );
+const columnMax = ref( [ columnMaxElement ], 0 );
+const columnMin = ref( [ columnMinElement ], 0 );
const fileInput = document.getElementById( 'file-input' )! as HTMLInputElement;
+const ascendingSort = ref( [], true );
+
+let selectedColumn = '';
+
+filterInput.disabled = true;
+filterInput.value = '';
-// Bind to file input event
+// ┌ ┐
+// │ conditional rendering of some elements │
+// └ ┘
+columnDatatype.addConditionalElementBind( columnMinElement, numberCheckPredicate );
+columnDatatype.addConditionalElementBind( columnMaxElement, numberCheckPredicate );
+columnDatatype.addConditionalElementBind( document.getElementById( 'title-column-min' )!, numberCheckPredicate );
+columnDatatype.addConditionalElementBind( document.getElementById( 'title-column-max' )!, numberCheckPredicate );
+columnDatatype.addConditionalElementBind( columnEntriesElement, stringOrNumberCheckPredicate );
+columnDatatype.addConditionalElementBind( document.getElementById( 'title-column-entries' )!,
+ stringOrNumberCheckPredicate );
+
+
+// ┌ ┐
+// │ Bind to file input event │
+// └ ┘
fileInput.addEventListener( 'change', event => {
loadFile( event );
} );
+
const loadFile = ( event: Event ) => {
if ( fileInput.files && fileInput.files.length > 0 ) {
const file = fileInput.files[0]!;
filename.set( file.name );
filetype.set( file.type );
- filesize.set( String( file.size ) + 'B' ); // TODO: KB / MB conversion stuff
+ filesize.set( String( file.size ) + 'B' ); // TODO: KB / MB conversion stuff?
readCSV( event )
.then( data => {
// Row count
@@ -66,6 +101,33 @@ const loadFile = ( event: Event ) => {
headerList.set( header );
+
+ // Initialize sorting
+ for ( let i = 0; i < header.length; i++ ) {
+ const column = header[ i ]!;
+
+ document.getElementById( 'table-header--' + i )!.addEventListener( 'click', () => {
+ // TODO: Decide on sorting cycling
+ // TODO: Add indicator as well
+ if ( selectedColumn === column ) {
+ ascendingSort.set( !ascendingSort.get() );
+ } else {
+ // This column will now be the active column
+ selectedColumn = column;
+ ascendingSort.set( true );
+ const dtype = typeof dataList.get()[0]![ column ];
+
+ columnDatatype.set( dtype );
+ columnName.set( column );
+
+ if ( dtype === 'string' )
+ filterInput.disabled = false;
+ else
+ filterInput.disabled = true;
+ }
+ } );
+ }
+
// ── Generate list. Need to first generate the correct template ───
// Reset, to not trigger expensive rerender
dataList.set( [] );
@@ -92,3 +154,61 @@ const loadFile = ( event: Event ) => {
alert( 'No file selected' );
}
};
+
+// ┌ ┐
+// │ Sorting │
+// └ ┘
+const doSort = () => {
+ filter.set( '' );
+
+ if ( columnDatatype.get() === 'string' ) {
+ columnEntries.set( computeDifferent( dataList.get(), selectedColumn ) );
+
+ if ( ascendingSort.get() ) {
+ dataList.sort( ( a, b ) => {
+ return ( a[ selectedColumn ] as string ).localeCompare( b[ selectedColumn ] as string );
+ } );
+ } else {
+ dataList.sort( ( a, b ) => {
+ return ( b[ selectedColumn ] as string ).localeCompare( a[ selectedColumn ] as string );
+ } );
+ }
+ } else if ( columnDatatype.get() === 'number' ) {
+ const stats = computeMinMax( dataList.get(), selectedColumn );
+
+ columnMin.set( stats[ 0 ] );
+ columnMax.set( stats[ 1 ] );
+ columnEntries.set( stats[ 2 ] );
+
+ if ( ascendingSort.get() ) {
+ dataList.sort( ( a, b ) => {
+ return ( a[ selectedColumn ] as number ) - ( b[ selectedColumn ] as number );
+ } );
+ } else {
+ dataList.sort( ( a, b ) => {
+ return ( b[ selectedColumn ] as number ) - ( a[ selectedColumn ] as number );
+ } );
+ }
+ }
+};
+
+columnName.onChange( doSort );
+ascendingSort.onChange( doSort );
+
+
+// ┌ ┐
+// │ Filtering │
+// └ ┘
+// Bind filter ref to element
+filter.bind( filterInput, val => val );
+
+// Add listener to change of filter value.
+filter.onChange( () => {
+ if ( columnDatatype.get() === 'string' ) {
+ dataList.filter( a => {
+ return ( a[ selectedColumn ] as string ).includes( filter.get() );
+ } );
+ } else {
+ dataList.filter( () => true );
+ }
+} );
diff --git a/task_2_ts/ts/persistance.ts b/task_2_ts/ts/persistance.ts
new file mode 100644
index 0000000..168ba95
--- /dev/null
+++ b/task_2_ts/ts/persistance.ts
@@ -0,0 +1,24 @@
+import {
+ PersistanceConfig
+} from './types';
+
+// Using localStorage for persistance
+const persistanceStore: PersistanceConfig = JSON.parse( localStorage.getItem( 'persistance' ) ?? '{}' );
+
+export const store = (
+ filename: string,
+ size: number,
+ sorted: string,
+ active: string
+) => {
+ persistanceStore[ `${ filename }-${ size }` ] = {
+ 'active': active,
+ 'sorted': sorted
+ };
+ localStorage.setItem( 'persistance', JSON.stringify( persistanceStore ) );
+};
+
+
+export const get = ( filename: string, size: number ) => {
+ return persistanceStore[ `${ filename }-${ size }` ];
+};
diff --git a/task_2_ts/ts/types.d.ts b/task_2_ts/ts/types.d.ts
index 407cefc..6cb0c42 100644
--- a/task_2_ts/ts/types.d.ts
+++ b/task_2_ts/ts/types.d.ts
@@ -2,3 +2,12 @@
export type CSVRecord = Record;
export type CSV_Data = CSVRecord[];
+
+export interface PersistanceConfigEntry {
+ 'sorted': string;
+ 'active': string;
+}
+
+export interface PersistanceConfig {
+ [filename: `${ string }-${ number }`]: PersistanceConfigEntry
+}
diff --git a/task_2_ts/ts/util.ts b/task_2_ts/ts/util.ts
new file mode 100644
index 0000000..ef6faf3
--- /dev/null
+++ b/task_2_ts/ts/util.ts
@@ -0,0 +1,48 @@
+import {
+ CSVRecord
+} from './types';
+
+export const numberCheckPredicate = ( value: string ) => {
+ return value === 'number';
+};
+
+export const stringOrNumberCheckPredicate = ( value: string ) => {
+ return value === 'string' || value === 'number';
+};
+
+
+export const computeMinMax = ( list: CSVRecord[], selectedColumn: string ): [number, number, number] => {
+ const containsList: number[] = [];
+
+ let min = Number.MAX_VALUE;
+ let max = Number.MIN_VALUE;
+
+ for ( let i = 0; i < list.length; i++ ) {
+ const el = list[i]!;
+ const curr = el[ selectedColumn ]! as number;
+
+ if ( curr < min ) min = curr;
+ else if ( curr > max ) max = curr;
+
+ if ( !containsList.includes( curr ) ) containsList.push( curr );
+ }
+
+ return [
+ min,
+ max,
+ containsList.length
+ ];
+};
+
+export const computeDifferent = ( list: CSVRecord[], selectedColumn: string ): number => {
+ const containsList: string[] = [];
+
+ for ( let i = 0; i < list.length; i++ ) {
+ const el = list[i]!;
+ const curr = el[ selectedColumn ]! as string;
+
+ if ( !containsList.includes( curr ) ) containsList.push( curr );
+ }
+
+ return containsList.length;
+};