sl-ember-components
sl-ember-components copied to clipboard
Define capabilities and API for sl-grid
This issue is where discussion about all of the things we want to change about the sl-grid
component should occur and the final definition of its capabilities and API are defined. As these items are discussed and vetted, issues will be created to work any individual tasks, with this issue serving as the overall source of progress.
To indicate that a capability of the component needs to be verified, add a comment with **VERIFY**
as the first line.
To propose a change to the component, add a comment with **PROPOSAL**
as the first line.
To indicate that something needs to be researched, add a comment with **RESEARCH**
as the first line.
As you are leaving comments regarding any of these VERIFY
, PROPOSAL
, or RESEARCH
comments, link back to the comments link in your own so that the conversation can be more easily followed (in absence of threaded conversation support in Github). This should be done on the first line of the comment via **RE: <url>**
Capabilities and API
- [ ] ARIA support
- [ ] ...
Tasks Adding them here until issues are created
- Update documentation for each change
- Complete review of documentation against capabilities
Issues tracking these efforts
https://github.com/softlayer/sl-ember-components/issues?q=is%3Aopen+is%3Aissue+milestone%3A%22v0.13.0+%28Final+push+for+a+1.0.0+release%29%22+label%3Asl-grid
PROPOSAL
Create a sort helper or mixin for consuming controllers
RESEARCH
Is there any inspiration to be had from https://github.com/shaunc/ember-grid or http://offirgolan.github.io/ember-light-table/#/ ? How is our grid to be any different/better than these?
Current limitations of the Grid
The current grid implementation has a number of limitations/issues, here are some of the main ones that need to be addressed:
- It does not allow for custom header content, e.g if a user would like to put a checkbox or custom markup in one of the header column they cannot currently do so.
- It does not allow for custom row cell content. The user should be able to customize and populate a row cell with whatever they like.
- It's too modular. At first glance this may seems like a good thing, but currently the user has to create and provide components for a lot of different parts of the grid, such as the detailHeader, detailFooter, detailComponent etc. The user should be able to provide any form of input such as inline text. Right now they are forced to create a component for each one of these parts, without a guarantee that they will ever reuse those components again. Creating components out of repeated code should be left up to the user and not enforced by us.
Proposal V1
Taking a declarative approach, leveraging contextual components and closure actions, we would be able to enhance the grid.
Things to consider
Taking a declarative approach means that we would have to split parts of the grid into different components, when doing so, we should keep in mind that some components need access to shared state.
When the user clicks on a row, if the details pane has been declared, it should open up. The open and closed state of the details pane is an example of state that needs to be shared. This state can be passed behind the scenes via contextual components so the user does not have to wire this up manually.
Simple grid without sorting
{{sl-grid columns=columns content=content}}
Simple grid with sorting
When column header is clicked, if sortable is set to true, then the sortColumn
action will be called on the controller/route. This is similar to how it works right now.
{{sl-grid columns=columns content=content sortColumn=(action "sortColumn")}}
Custom row cell
Imagine the user wants to place a checkbox in the first column of each row, like the image below:
The columns' array declaration would look as follows:
columns: [
{
checkboxColumn: true
},
{
title: 'Color',
valuePath: 'name'
},
{
headerClass: 'smallWidth',
sortable: true,
title: 'Hex Code',
valuePath: 'hexCode'
}
]
In the code above, the first column has an object with a key checkboxColumn
, this key could be named anything unique that can identify that column. The code below shows how to customize the cell for that column to display a checkbox.
{{#sl-grid columns=columns content=sortedModel as |grid|}}
{{#grid.table as |table|}}
{{table.header}}
{{#table.body as |body row column|}}
{{#if column.checkboxColumn}}
{{#body.cell}}
{{input type="checkbox"}}
{{/body.cell}}
{{else}}
{{body.cell record=row column=column}}
{{/if}}
{{/table.body}}
{{/grid.table}}
{{/sl-grid}}
Let's break down the code:
{{#sl-grid columns=columns content=sortedModel as |grid|}}
Above, an object named grid is yielded through. Here is what the template code for sl-grid.hbs
looks like:
{{#if hasBlock}}
{{yield
(hash
table=(
component "sl-grid-table"
rowClick=(action "rowClick")
columns=columns
content=content
)
details=(
component "sl-grid-details"
detailPaneOpen=detailPaneOpen
closeDetailPane=(action "closeDetailPane")
)
activeRecord=activeRecord
)
}}
{{else}}
{{sl-grid-table
sortColumn=sortColumn
rowClick=(action "rowClick")
columns=columns
content=content}}
{{/if}}
As you can see the object that is yielded through has three keys table
, details
and activeRecord
. grid.table
is nothing more than an sl-grid-table
with columns
, content
and rowClick
pre-populated. The same goes for grid.details
. The detailPaneOpen
state as well as the closeDetailPane
action is passed along.
After this, we declare the table:
{{#grid.table as |table|}}
The table declaration yields an object that we named table above. The table object has two keys header
and body
. Header and body are components that are passed columns
and content
and other actions behind the scenes using contextual components. Again, this is necessary so that the user does not have to deal with passing this data along.
Since no customization is needed to the table header in the use case above, the inline form of table.header
is used.
{{table.header}}
Later we will see, if the header needs to be customized, the block version of the header can be used.
The header is automatically passed the columns from the table so columns does not need to be set.
Moving onto the table body
{{#table.body as |body row column|}}
table.body
yields through an object called body
with the key cell
. Cell is an sl-grid-cell
component. The other arguments that are passed through are row
, column
and two other arguments that are not displayed in the code above rowIndex
and columnIndex
.
Further in the code, an if statement is used to determine if the current column being looped over is the row cell that needs to be customized.
{{#if column.checkboxColumn}}
{{#body.cell}}
{{input type="checkbox"}}
{{/body.cell}}
{{else}}
{{body.cell record=row column=column}}
{{/if}}
The checkboxColumn
key that was set on the column is checked, if it's true then body.cell
yields to anything the user provides, otherwise body.cell
is rendered with the current row
and column
data passed in.
Alternatively, we might consider passing in the row
and column
in the background to body.cell
, this would condense the body.cell
declaration to {{body.cell}}
from {{body.cell record=row column=column}}
. The tradeoff is that, it might be confusing how body.cell
is rendering without being passed any data.
The example above shows how the user can customize a row cell to display anything they desire, even other components.
Custom column header
Just as the user can customize a row cell, the user would be able to customize a column header cell. In the previous code examples you may remember that we used the inline version of the table header {{table.header}}
. The block version would give the user the ability to customize the header. The code below demonstrates this:
{{table.header as |header column index|}}
{{#if column.checkboxColumn}}
{{#header.cell}}
Custom column header
{{/header.cell}}
{{else}}
{{header.cell column=column}}
{{/if}}
{{/table.header}}
The header.cell
code block above in the else block, could possible be done in a more compact way e.g {{header.cell}}
, where column
is passed behind the scenes. Again, the tradeoff to this is that it might be confusing as to how the cell is getting populated with the data if it's not being passed explicitly.
When the user clicks on a column header, if the column is sortable, the click would trigger an action to get fired. This action would get wired up via the table.header
as the code below shows.
{{#table.header sortColumn=(action 'sortColumn') as |header column index|}}
{{#if column.checkboxColumn}}
{{#header.cell}}
Custom column header
{{/header.cell}}
{{else}}
{{header.cell column=column}}
{{/if}}
{{/table.header}}
The sortColumn
action on the controller/route would get called and passed the column that was clicked on, similar to how it works in the current implementation of the grid.
Record details
The current detailsHeaderComponent
, detailsFooterComponent
and detailComponent
would all be encapsulated under a sl-grid-details
component. If you recall the grid object that is yielded as an argument contains a details
key. Below is an example of how a user would supply a details body, header and footer.
{{#sl-grid columns=columns content=sortedModel as |grid|}}
{{grid.table}}
{{#grid.details as |details|}}
{{#details.header}}
<h3>grid.activeRecord.name</h3>
{{/details.header}}
{{#details.body}}
<h3>grid.activeRecord.fruit</h3>
{{/details.body}}
{{#details.footer}}
<h3>grid.activeRecord.hexCode</h3>
{{/details.footer}}
{{/grid.details}}
{{/sl-grid}}
The grid
object has an activeRecord
key that has a reference to the record that belongs to the row that was clicked on.
Filter panel
The filter panel implementation would be left up to the user. We could possible create a filter panel component for demo purposes so the user gets an idea of how it would work.
Pagination & footer
The pagination and footer would not be baked into the grid, but would instead be declared and wired up by the user. The details of how exactly the user would wire this up still needs to be discussed.
Proposal V2
In this version the header columns are declared inline instead of getting passed to the grid. The user will have to loop over the content array and declare the row and cells, much like declaring a HTML table.
{{#sl-grid as |grid|}}
{{#grid.table sortColumn=(action "sortColumn") as |table|}}
{{#table.header as |header|}}
{{header.column title="Fruit" key="fruit" sortable=true sorted="asc" class="smallWidth"}}
{{header.column title="Hex Code" key="hexCode" sortable=true}}
{{/table.header}}
{{#table.body as |body|}}
{{#each content as |row|}}
{{#body.row row=row}}
{{#body.cell}} {{row.fruit}} {{/body.cell}}
{{#body.cell}} {{row.hexCode}} {{/body.cell}}
{{/body.row}}
{{/each}}
{{/table.body}}
{{/grid.table}}
{{/sl-grid}}
Custom header
A custom header can be declared using the block version of {{header.column}}
like the code below shows:
{{#header.column}}
{{#header.cell}}
Custom
{{/header.cell}}
{{/header.column}}
If no customization is needed to the row, a one line declaration can be made as shown below:
{{#sl-grid as |grid|}}
{{#grid.table sortColumn=(action "sortColumn") as |table|}}
{{#table.header as |header|}}
{{header.column title="Fruit" key="fruit" sortable=true sorted="asc" class="smallWidth"}}
{{header.column title="Hex Code" key="hexCode" sortable=true}}
{{/table.header}}
{{table.body content=content}}
{{/grid.table}}
{{/sl-grid}}
The above might or might not be possible because of the way the grid currently works.
The rest of the parts of the grid would be the same as V1.
RE: https://github.com/softlayer/sl-ember-components/issues/1261#issuecomment-190405887
Another user has indicated interest in customizable cells - https://github.com/softlayer/sl-ember-components/issues/1652