Skip to content

Instantly share code, notes, and snippets.

@mationai
Last active May 10, 2021 21:29
Show Gist options
  • Save mationai/2b39d046ea1efca393623fc8c0af5687 to your computer and use it in GitHub Desktop.
Save mationai/2b39d046ea1efca393623fc8c0af5687 to your computer and use it in GitHub Desktop.
My edited version of ExtendedTable (for https://github.com/dritter/extended-table/issues/10)
<script>
import { onMount } from 'svelte'
import { deepValue } from '@jsier/deep-value'
import stickybits from 'stickybits/dist/stickybits.es'
import { sortByDefinition, sortByColumn } from '../../node_modules/extended-table/src/sortBy'
import { getCellClasses, getRowClasses, getHeaderClasses } from './tableClassNames'
const defaultRowClickHandler = (_) => true
export let data = []
export let columns = []
export let config = {
multisort: false,
}
export let onRowClick = defaultRowClickHandler
export let style = ''
export let iconAsc = '↑' // col not auto-expanded unless icon is text based
export let iconDesc = '↓'
export let initialSortBy = null // PropertyPath
export let initialSortDirection = 'asc'
export let showSortIndicatorsOnInitialSort = true
export let collapsedPlaceholder = '...'
export let stickyHeaders = true
export let stickyOffset = 0
export let expandAll = false
export let autoCollapse = false
export let sortingFunction = null
let windowWidth
let table
if (!sortingFunction) {
sortingFunction = sortByColumn
}
const clearCaches = () => {
columns = columns
data = data
}
const onCellClick = (colCfg, d, iCol) => colCfg.clickHandler?.(d, iCol)
onMount(() => {
const heads = table.querySelectorAll('thead th')
if (stickyHeaders) {
stickybits(heads, {stickyBitStickyOffset: stickyOffset})
}
if (autoCollapse) {
const tableRect = table.getBoundingClientRect()
const viewportWidth = windowWidth - tableRect.left
let cumHeadWidths = 0
for (let i = 0; i < heads.length; i++) {
const rect = heads[i].getBoundingClientRect()
if (cumHeadWidths + rect.width > viewportWidth) {
if (expandAll) {
columns
.filter((column, index) => index >= (i - 1))
.map((column) => column.hidden = true)
columns[i].hidden = false
columns[i].collapsed = true
} else {
columns.filter((column, index) => index >= i)
.map((column) => column.collapsed = true)
}
clearCaches()
break
}
cumHeadWidths += rect.width
}
}
})
if (initialSortBy) {
if (showSortIndicatorsOnInitialSort) {
const initialSortColumn = columns.find((c) => c.propertyPath === initialSortBy)
initialSortColumn.propertyPath = initialSortBy
initialSortColumn.direction = initialSortDirection
}
const def = {}
def[initialSortDirection] = (u) => {
return deepValue(u, initialSortBy)
}
sortByDefinition(data, [def])
}
const expandColumn = (column) => {
if (expandAll) {
columns.map((column) => {
column.collapsed = false
column.hidden = false
})
} else {
column.collapsed = false
}
clearCaches()
}
let slots = new Set($$props.$$slots ? Object.getOwnPropertyNames($$props.$$slots) : [])
</script>
<style>
.mouse-pointer:hover {
cursor: pointer;
}
.overflow-container {
max-width: 100vw;
max-height: 100vh; /* doesn't work (ht is set on div wrapping the Comp) */
position: relative;
}
tbody tr:hover {
background-color: rgba(0, 0, 0, 0.1);
}
table td {
margin: 0;
padding: .5rem .625rem .625rem;
}
th {
background-color: rgb(255, 255, 255);
border-bottom: 2px solid rgb(51, 51, 51);
font-size: 110%;
font-weight: 700;
}
tbody .row-even {
background: rgba(153, 202, 255, 0.15);
border-bottom: 0;
}
tbody .col-even {
background: rgba(153, 202, 255, 0.15);
}
table {
border-spacing: 0;
display: block;
overflow-y: scroll;
}
.hidden {
display: none;
}
</style>
<svelte:window bind:innerWidth={windowWidth} />
<div class="overflow-container">
<table bind:this={table} class="et" {style}>
<thead>
<tr class='header'>
{#each config.columns as cfg, iCol}
<th on:click={() => sortingFunction(cfg, columns, data, config.multisort, clearCaches)}
class="{getHeaderClasses(iCol, cfg)}"
class:hidden={cfg.hidden}
>
{#if cfg.collapsed}
<div on:click|stopPropagation={expandColumn(cfg)}>
{@html collapsedPlaceholder}
</div>
{:else}
{@html cfg.title}
{#if cfg.direction === 'asc'}
{iconAsc}
{:else if cfg.direction === 'desc'}
{iconDesc}
{/if}
{/if}
</th>
{/each}
</tr>
<slot name='headerRow2'></slot>
</thead>
<tbody>
{#each data as d, iRow}
<tr on:click={() => onRowClick(d, iRow)}
class='{getRowClasses(d, iRow, config.rows)}'
class:mouse-pointer={onRowClick !== defaultRowClickHandler}
>
{#each config.columns as cfg, iCol}
<td on:click={() => onCellClick(cfg, d, iCol)}
class="{getCellClasses(d, iCol, iRow, cfg)}"
class:hidden={cfg.hidden}
>
{#if cfg.collapsed}
<div class="mouse-pointer" on:click|stopPropagation={expandColumn(cfg)}>
{@html collapsedPlaceholder}
</div>
{:else if data && slots.has('column-0') && iCol===0}
<slot name="column-0" data={d} iRow={iRow} iCol={iCol}/>
{:else if data && slots.has('column-2nd-last') && iCol===config.columns.length-2}
<slot name="column-2nd-last" data={d} iRow={iRow} iCol={iCol}/>
{:else if data && slots.has('column-last') && iCol===config.columns.length-1}
<slot name="column-last" data={d} iRow={iRow} iCol={iCol}/>
{:else if cfg.value}
{cfg.value(d, iRow, iCol)}
{:else if cfg.propertyPath}
{deepValue(d, cfg.propertyPath)}
{/if}
</td>
{/each}
</tr>
<slot name="extraRow" rowData={d} {iRow}></slot>
{/each}
</tbody>
</table>
</div>
import { deepValue } from '@jsier/deep-value'
const slugEx = new RegExp(/[^a-z0-9\-]/ig)
const sluggify = (input='') => (''+input).replace(slugEx, '_')
const getOddEvenClass = (index, prefix) => {
return index % 2 === 0 ? `${prefix}-even` : `${prefix}-odd`
}
export const getHeaderClasses = (iCol, cfg) => {
const classes = []
if (typeof cfg.propertyPath === 'string') {
classes.push('col-head-' + sluggify(cfg.propertyPath))
}
if (typeof cfg.headerClassName === 'string') {
classes.push(cfg.headerClassName)
}
if (typeof cfg.headerClassName?.value === 'function') {
classes.push(cfg.headerClassName.value(cfg, iCol))
}
if (cfg.sortable) {
classes.push('mouse-pointer')
}
return classes.join(' ')
}
export const getCellClasses = (data, iCol, iRow, cfg) => {
const classes = []
if (typeof cfg.propertyPath === 'string') {
classes.push(`col-${sluggify(cfg.propertyPath)}`)
}
if (typeof cfg.className === 'string') {
classes.push(cfg.className)
}
if (typeof cfg.className?.value === 'function') {
classes.push(cfg.className.value(data, iCol, iRow))
}
if (typeof cfg.className?.propertyPath === 'string') {
classes.push(sluggify(deepValue(data, cfg.className.propertyPath)))
}
classes.push(getOddEvenClass(iCol + 1, 'col'))
return classes.join(' ')
}
export const getRowClasses = (data, iRow, rowsCfg) => {
const classes = []
rowsCfg.forEach((cfg) => {
if (typeof cfg.className === 'string') {
classes.push(cfg.className)
}
if (typeof cfg.className?.value === 'function') {
classes.push(cfg.className.value(data, iRow))
}
if (typeof cfg.className?.propertyPath === 'string') {
classes.push(sluggify(deepValue(data, cfg.className.propertyPath)))
}
})
classes.push(getOddEvenClass(iRow + 1, 'row'))
return classes.join(' ')
}
<script>
const columns = [
{ propertyPath: 'acct', title: 'Acct', sortable: true },
...otherCols,
{ title: '...' },
{ title: 'Edit' },
]
const config = {
columns,
rows: [{
className: {
value: (d) => css.redGreen(d, 'pctPL', -.2, .2),
},
}],
}
</script>
<div>
<ExtTable data={rows} {config} style='height:{tableHt};' {onRowClick}>
<div slot='column-2nd-last' let:data={d} let:iRow={iRow}>
<IconToggler on:click={() => toggleView(d, iRow)} dir={viewStates[iRow]}/>
</div>
<div slot='column-last' let:data={d} let:iRow={iRow}>
<input type=checkbox on:change={(evt) => setChecked(evt, iRow)}/>
</div>
<tr slot='extraRow' let:rowData={rowData} let:iRow={iRow}>
{#if rowData.transactions && viewStates[iRow]==='up'}
<td colspan={columns.length}>
<Transactions data={rowData}/>
</td>
{/if}
</tr>
</ExtTable>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment