Skip to content

Commit

Permalink
Merge pull request #40 from CrowdStrike/simplify-working-with-columns
Browse files Browse the repository at this point in the history
Simplify working with columns
  • Loading branch information
NullVoxPopuli authored Nov 2, 2022
2 parents 63edb89 + fa3da69 commit ebb6675
Show file tree
Hide file tree
Showing 24 changed files with 861 additions and 293 deletions.
4 changes: 2 additions & 2 deletions docs/demos/kitchen-sink/demo/demo-a.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import { tracked } from '@glimmer/tracking';
import { htmlSafe } from '@ember/template';

import { headlessTable } from 'ember-headless-table';
import { meta } from 'ember-headless-table/plugins';
import { meta, columns } from 'ember-headless-table/plugins';
import {
ColumnResizing,
isResizing, resizeHandle
Expand Down Expand Up @@ -139,7 +139,7 @@ export default class extends Component {
@tracked sorts = [];

get columns() {
return meta.forTable(this.table, ColumnReordering).columns;
return columns.for(this.table);
}

get data() {
Expand Down
6 changes: 2 additions & 4 deletions docs/plugins/column-reordering/demo/dema-a.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,11 @@
import Component from '@glimmer/component';

import { headlessTable } from 'ember-headless-table';
import { meta } from 'ember-headless-table/plugins';
import { meta, columns } from 'ember-headless-table/plugins';
import {
ColumnReordering,
moveLeft, moveRight
} from 'ember-headless-table/plugins/column-reordering';
import { ColumnVisibility } from 'ember-headless-table/plugins/column-visibility';

import { DATA } from 'docs-app/sample-data';

Expand All @@ -54,12 +53,11 @@ export default class extends Component {
data: () => DATA,
plugins: [
ColumnReordering,
ColumnVisibility,
],
});

get columns() {
return meta.forTable(this.table, ColumnReordering).columns;
return columns.for(this.table);
}

/**
Expand Down
2 changes: 0 additions & 2 deletions docs/plugins/column-resizing/demo/demo-a.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ export default class extends Component {
],
data: () => DATA,
plugins: [
ColumnVisibility,
ColumnReordering,
ColumnResizing,
],
});
Expand Down
6 changes: 0 additions & 6 deletions docs/plugins/column-resizing/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,16 @@ API Documentation available [here][api-docs]

## Usage

Because this plugin operates or visible columns,
the `ColumnVisibility` plugin is required.

```js
import { headlessTable } from 'ember-headless-table';
import { ColumnResizing, resizeHandle } from 'ember-headless-table/plugins/column-resizing';
import { ColumnVisibility } from 'ember-headless-table/plugins/column-visibility';

// ...
// in a class
table = headlessTable(this, {
columns: () => [ /* ... */ ],
data: () => [ /* ... */ ],
plugins: [
ColumnVisibility,
ColumnResizing,
],
})
Expand Down Expand Up @@ -55,7 +50,6 @@ See the API Documentation [here][api-docs] for the full list of options and desc
table = headlessTable(this, {
columns: () => [ /* ... */ ],
plugins: [
ColumnVisibility,
ColumnResizing.with(() => ({ handlePosition: 'right' })),
],
})
Expand Down
4 changes: 2 additions & 2 deletions docs/plugins/column-visibility/demo/demo-a.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
import Component from '@glimmer/component';

import { headlessTable } from 'ember-headless-table';
import { meta } from 'ember-headless-table/plugins';
import { meta, columns } from 'ember-headless-table/plugins';
import { ColumnVisibility, hide, show } from 'ember-headless-table/plugins/column-visibility';

import { DATA } from 'docs-app/sample-data';
Expand All @@ -65,7 +65,7 @@ export default class extends Component {
});

get columns() {
return meta.forTable(this.table, ColumnVisibility).visibleColumns;
return columns.for(this.table);
}

/**
Expand Down
1 change: 0 additions & 1 deletion docs/plugins/sticky-column/demo/demo-a.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export default class extends Component {
plugins: [
StickyColumns,
ColumnResizing,
ColumnVisibility,
],
});

Expand Down
8 changes: 5 additions & 3 deletions docs/plugins/sticky-column/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ API Documentation available [here][api-docs]

```js
import { headlessTable } from 'ember-headless-table';
import { ColumnVisibility } from 'ember-headless-table/plugins/column-visibility';
import { StickyColumns } from 'ember-headless-table/plugins/sticky-columns';
import { ColumnResizing } from 'ember-headless-table/plugins/column-resizing';

Expand All @@ -26,13 +25,16 @@ import { ColumnResizing } from 'ember-headless-table/plugins/column-resizing';
],
data: () => [ /* ... */ ],
plugins: [
ColumnReordering,
ColumnVisibility,
ColumnResizing,
StickyColumns,
],
})
```

Note that the `ColumnResizing` plugin is required because `StickyColumns` needs a guarantee
that a `columnWidth` implementation exists so that columns may become sticky beyond just the
far left and far right columns.

### ColumnOptions

- `sticky`
Expand Down
1 change: 1 addition & 0 deletions docs/plugins/writing-your-own.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ The key properties to look at are:
- `headerCellModifier` - for each `<th>`
- `rowModifier` - for each `<tr>`
- `reset` -- a hook that the table will call on your plugin if you have state to revert to
- `columns` -- for overriding / altering column order / visibility / grouping / etc

With these capabilities, features for tables may be built in a way that relieves implementation complexity on the consumer, such as:

Expand Down
17 changes: 17 additions & 0 deletions ember-headless-table/src/-private/interfaces/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,23 @@ export interface Plugin<Signature = unknown> {
* If the plugin has state, this should be used to reset that state
*/
reset?: () => void;

/**
* @public
* @kind Table Hook
*
* A plugin may change the columns order, visibility, etc.
* By implementing this getter, this plugin's
* `columns` property will be used by other plugins via
* the `columns.for(table, RequestingPlugin)` api.
*
* For the end-consumer, they may choose to do
* `columns.for(table)`, which will aggregate all column modifications
* from all plugins.
*
* As always, `table.columns` is the way to get the unmodified list of columns.
*/
columns?: Column<any>[];
}

/**
Expand Down
11 changes: 2 additions & 9 deletions ember-headless-table/src/-private/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ export class Table<DataType = unknown> extends Resource<Signature<DataType>> {

/**
* @private
*
* used by other private APIs
*/
get config() {
return this.args.named;
Expand Down Expand Up @@ -234,15 +236,6 @@ export class Table<DataType = unknown> extends Resource<Signature<DataType>> {
},
});

/**
* @private
*
* TODO: what's this for?
*/
get value() {
return this;
}

/**
* @private
*/
Expand Down
191 changes: 191 additions & 0 deletions ember-headless-table/src/plugins/-private/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,197 @@ export const preferences = {
},
};

/**
* if a `requester` is not provided,
* Get the columns for the table, considering any and all plugins that could modify columns.
*
* If you are an end-consumer of ember-headless-table, this is the function to use.
* If you are a plugin-author, you'll want to pass your plugin class as the second parameter.
*
* For a given plugin, `requester`, determine what columns should be returned.
* Since multiple plugins could be used in a table, there is an implicit hierarchy of
* column modifications that can occur from each of those plugins.
*
* If a plugin defines other plugins as either *requirements* or *optional requirements*,
* and that upstream plugin defines a `columns` property, then those columns will be returned here.
*
* This works recursively up the plugin tree up until a plugin has no requirements, and then
* all columns from the table are returned.
*/
function columnsFor<DataType = any>(
table: Table<DataType>,
requester?: Plugin<any> | undefined
): Column<DataType>[] {
assert(`First argument passed to columns.for must be an instance of Table`, table[TABLE_KEY]);

let visibility = findPlugin(table.plugins, 'columnVisibility');
let reordering = findPlugin(table.plugins, 'columnOrder');

// TODO: actually resolve the graph, rather than use the hardcoded feature names
// atm, this only "happens" to work based on expectations of
// of the currently implemented plugins' capabilities and implied hierarchy.

if (requester) {
assert(
`[${requester.name}] requested columns from the table, but the plugin, ${requester.name}, ` +
`is not used in this table`,
table.plugins.some((plugin) => plugin instanceof (requester as Class<Plugin>))
);

if (visibility && visibility.constructor === requester) {
return table.columns.values();
}

if (reordering && reordering.constructor === requester) {
if (visibility) {
assert(
`<#${visibility.name}> defined a 'columns' property, but did not return valid data.`,
visibility.columns && Array.isArray(visibility.columns)
);

return visibility.columns;
}

return table.columns.values();
}

if (reordering) {
assert(
`<#${reordering.name}> defined a 'columns' property, but did not return valid data.`,
reordering.columns && Array.isArray(reordering.columns)
);

return reordering.columns;
}

if (visibility) {
assert(
`<#${visibility.name}> defined a 'columns' property, but did not return valid data.`,
visibility.columns && Array.isArray(visibility.columns)
);

return visibility.columns;
}

return table.columns.values();
}

/**
* This flow is the inverse of when we have a requester
*/

if (reordering) {
assert(
`<#${reordering.name}> defined a 'columns' property, but did not return valid data.`,
reordering.columns && Array.isArray(reordering.columns)
);

return reordering.columns;
}

if (visibility) {
assert(
`<#${visibility.name}> defined a 'columns' property, but did not return valid data.`,
visibility.columns && Array.isArray(visibility.columns)
);

return visibility.columns;
}

return table.columns.values();
}

export const columns = {
for: columnsFor,

/**
* for a given current or reference column, return the column that
* is immediately next, or to the right of that column.
*
* If a plugin class is provided, the hierarchy of column list modifications
* will be respected.
*/
next: <Data = unknown>(
current: Column<Data>,
requester?: Plugin<any>
): Column<Data> | undefined => {
let columns = requester ? columnsFor(current.table, requester) : columnsFor(current.table);

let referenceIndex = columns.indexOf(current);

assert(
`index of reference column must be >= 0. column likely not a part of the table`,
referenceIndex >= 0
);

/**
* There can be nothing after the last column
*/
if (referenceIndex >= columns.length - 1) {
return undefined;
}

return columns[referenceIndex + 1];
},

/**
* for a given current or reference column, return the column that
* is immediately previous, or to the left of that column.
*
* If a plugin class is provided, the hierarchy of column list modifications
* will be respected.
*/
previous: <Data = unknown>(
current: Column<Data>,
requester?: Plugin<any>
): Column<Data> | undefined => {
let columns = requester ? columnsFor(current.table, requester) : columnsFor(current.table);
let referenceIndex = columns.indexOf(current);

assert(
`index of reference column must be >= 0. column likely not a part of the table`,
referenceIndex >= 0
);

/**
* There can be nothing before the first column
*/
if (referenceIndex === 0) {
return undefined;
}

return columns[referenceIndex - 1];
},
/**
* for a given current or reference column, return the columns that
* should appear before, or to the left of that column.
*
* if a plugin class is provided, the hierarchy of column list modifications
* will be respected.
*/
before: <Data = unknown>(current: Column<Data>, requester?: Plugin<any>): Column<Data>[] => {
let columns = requester ? columnsFor(current.table, requester) : columnsFor(current.table);

let referenceIndex = columns.indexOf(current);

return columns.slice(0, referenceIndex);
},
/**
* for a given current or reference column, return the columns that
* should appear after, or to the right of that column.
*
* if a plugin class is provided, the hierarchy of column list modifications
* will be respected.
*/
after: <Data = unknown>(current: Column<Data>, requester?: Plugin<any>): Column<Data>[] => {
let columns = requester ? columnsFor(current.table, requester) : columnsFor(current.table);

let referenceIndex = columns.indexOf(current);

return columns.slice(referenceIndex + 1);
},
};

export const meta = {
/**
* @public
Expand Down
Loading

0 comments on commit ebb6675

Please sign in to comment.