mirror of
https://github.com/janishutz/fundamentals-of-webengineering.git
synced 2025-11-25 05:44:24 +00:00
Task 80% complete
This commit is contained in:
@@ -59,11 +59,11 @@
|
|||||||
<p id="column-selected"></p>
|
<p id="column-selected"></p>
|
||||||
<h4>Data type</h4>
|
<h4>Data type</h4>
|
||||||
<p id="column-datatype"></p>
|
<p id="column-datatype"></p>
|
||||||
<h4>Different entries</h4>
|
<h4 id="title-column-entries">Different entries</h4>
|
||||||
<p id="column-entries"></p>
|
<p id="column-entries"></p>
|
||||||
<h4>Min</h4>
|
<h4 id="title-column-min">Min</h4>
|
||||||
<p id="column-min"></p>
|
<p id="column-min"></p>
|
||||||
<h4>Max</h4>
|
<h4 id="title-column-max">Max</h4>
|
||||||
<p id="column-max"></p>
|
<p id="column-max"></p>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -72,8 +72,8 @@
|
|||||||
<header>
|
<header>
|
||||||
<h2>Filter active column</h2>
|
<h2>Filter active column</h2>
|
||||||
</header>
|
</header>
|
||||||
<label for="filter">Filter column:</label><input type="search" id="filter" name="filter"
|
<label for="filter">Filter column:</label>
|
||||||
placeholder="Filter" disabled>
|
<input type="search" id="filter" name="filter" placeholder="Filter" disabled>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article style="width: 100%">
|
<article style="width: 100%">
|
||||||
@@ -83,7 +83,6 @@
|
|||||||
<table id="table-content">
|
<table id="table-content">
|
||||||
<thead id="table-header"></thead>
|
<thead id="table-header"></thead>
|
||||||
<tbody id="table-body">
|
<tbody id="table-body">
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import '@fortawesome/fontawesome-free/css/all.css';
|
import '@fortawesome/fontawesome-free/css/all.css';
|
||||||
import '../css/layout.css';
|
import '../css/layout.css';
|
||||||
import '@picocss/pico/css/pico.min.css';
|
import '@picocss/pico/css/pico.min.css';
|
||||||
|
import {
|
||||||
|
computeDifferent,
|
||||||
|
computeMinMax,
|
||||||
|
numberCheckPredicate, stringOrNumberCheckPredicate
|
||||||
|
} from './util';
|
||||||
import {
|
import {
|
||||||
listRef, ref
|
listRef, ref
|
||||||
} from './rendering';
|
} from './rendering';
|
||||||
@@ -11,6 +16,11 @@ import {
|
|||||||
readCSV
|
readCSV
|
||||||
} from './csv';
|
} from './csv';
|
||||||
|
|
||||||
|
|
||||||
|
// ┌ ┐
|
||||||
|
// │ Define refs │
|
||||||
|
// └ ┘
|
||||||
|
const tableHeaderElement = document.getElementById( 'table-header' )!;
|
||||||
const dataList = listRef<CSVRecord>(
|
const dataList = listRef<CSVRecord>(
|
||||||
document.getElementById( 'table-body' )!,
|
document.getElementById( 'table-body' )!,
|
||||||
[],
|
[],
|
||||||
@@ -22,7 +32,7 @@ const dataList = listRef<CSVRecord>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
const headerList = listRef<string>(
|
const headerList = listRef<string>(
|
||||||
document.getElementById( 'table-header' )!,
|
tableHeaderElement,
|
||||||
[],
|
[],
|
||||||
'table-header',
|
'table-header',
|
||||||
{
|
{
|
||||||
@@ -31,31 +41,56 @@ const headerList = listRef<string>(
|
|||||||
'children': []
|
'children': []
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const filter = ref<string>( [ document.getElementById( 'filter' )! ], '' );
|
const columnEntriesElement = document.getElementById( 'column-entries' )!;
|
||||||
|
const columnMaxElement = document.getElementById( 'column-max' )!;
|
||||||
|
const columnMinElement = document.getElementById( 'column-min' )!;
|
||||||
|
const filter = ref<string>( [], '' );
|
||||||
|
const filterInput = document.getElementById( 'filter' )! as HTMLInputElement;
|
||||||
const filename = ref<string>( [ document.getElementById( 'data-filename' )! ], '' );
|
const filename = ref<string>( [ document.getElementById( 'data-filename' )! ], '' );
|
||||||
const filetype = ref<string>( [ document.getElementById( 'data-filetype' )! ], '' );
|
const filetype = ref<string>( [ document.getElementById( 'data-filetype' )! ], '' );
|
||||||
const filesize = ref<string>( [ document.getElementById( 'data-filesize' )! ], '' );
|
const filesize = ref<string>( [ document.getElementById( 'data-filesize' )! ], '' );
|
||||||
const rowCount = ref<string>( [ document.getElementById( 'data-rowcount' )! ], '' );
|
const rowCount = ref<string>( [ document.getElementById( 'data-rowcount' )! ], '' );
|
||||||
const columnName = ref<string>( [ document.getElementById( 'column-selected' )! ], '' );
|
const columnName = ref<string>( [ document.getElementById( 'column-selected' )! ], '' );
|
||||||
const columnDatatype = ref<string>( [ document.getElementById( 'column-datatype' )! ], '' );
|
const columnDatatype = ref<string>( [ document.getElementById( 'column-datatype' )! ], '' );
|
||||||
const columnEntries = ref<string>( [ document.getElementById( 'column-entries' )! ], '' );
|
const columnEntries = ref<number>( [ columnEntriesElement ], 0 );
|
||||||
const columnMax = ref<string>( [ document.getElementById( 'column-max' )! ], '' );
|
const columnMax = ref<number>( [ columnMaxElement ], 0 );
|
||||||
const columnMin = ref<string>( [ document.getElementById( 'column-min' )! ], '' );
|
const columnMin = ref<number>( [ columnMinElement ], 0 );
|
||||||
const fileInput = document.getElementById( 'file-input' )! as HTMLInputElement;
|
const fileInput = document.getElementById( 'file-input' )! as HTMLInputElement;
|
||||||
|
const ascendingSort = ref<boolean>( [], 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 => {
|
fileInput.addEventListener( 'change', event => {
|
||||||
loadFile( event );
|
loadFile( event );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
||||||
const loadFile = ( event: Event ) => {
|
const loadFile = ( event: Event ) => {
|
||||||
if ( fileInput.files && fileInput.files.length > 0 ) {
|
if ( fileInput.files && fileInput.files.length > 0 ) {
|
||||||
const file = fileInput.files[0]!;
|
const file = fileInput.files[0]!;
|
||||||
|
|
||||||
filename.set( file.name );
|
filename.set( file.name );
|
||||||
filetype.set( file.type );
|
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 )
|
readCSV( event )
|
||||||
.then( data => {
|
.then( data => {
|
||||||
// Row count
|
// Row count
|
||||||
@@ -66,6 +101,33 @@ const loadFile = ( event: Event ) => {
|
|||||||
|
|
||||||
headerList.set( header );
|
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 ───
|
// ── Generate list. Need to first generate the correct template ───
|
||||||
// Reset, to not trigger expensive rerender
|
// Reset, to not trigger expensive rerender
|
||||||
dataList.set( [] );
|
dataList.set( [] );
|
||||||
@@ -92,3 +154,61 @@ const loadFile = ( event: Event ) => {
|
|||||||
alert( 'No file selected' );
|
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 );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|||||||
24
task_2_ts/ts/persistance.ts
Normal file
24
task_2_ts/ts/persistance.ts
Normal file
@@ -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 }` ];
|
||||||
|
};
|
||||||
9
task_2_ts/ts/types.d.ts
vendored
9
task_2_ts/ts/types.d.ts
vendored
@@ -2,3 +2,12 @@
|
|||||||
export type CSVRecord = Record<string, unknown>;
|
export type CSVRecord = Record<string, unknown>;
|
||||||
|
|
||||||
export type CSV_Data = CSVRecord[];
|
export type CSV_Data = CSVRecord[];
|
||||||
|
|
||||||
|
export interface PersistanceConfigEntry {
|
||||||
|
'sorted': string;
|
||||||
|
'active': string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PersistanceConfig {
|
||||||
|
[filename: `${ string }-${ number }`]: PersistanceConfigEntry
|
||||||
|
}
|
||||||
|
|||||||
48
task_2_ts/ts/util.ts
Normal file
48
task_2_ts/ts/util.ts
Normal file
@@ -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;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user