A tiny dependency-free library to create flex-based table-like layouts, which can be resized by user
🚀 Live demo 🚀
This library can be used alongside any framework as well as vanilla Javascriptnpm i columns-resize
import ColumnsResize from 'columns-resize'
<script src="https://unpkg.com/columns-resize@latest"></script>
The layout consists of rows (which must have display:flex
style)
Rows contain columns that can be resized. When one column is resized, all columns with the same id are resized with it.
Columns contain some information and a resize handle that can be dragged to make adjustments to column size. Resize handles are optional: not all columns must have them, but if a resize handle is present, it must be nested within a column
This library relies on use of data-attributes.
Here's a list of data attributes that are important for us:
data-column-id="some-id"
- this attribute must be present on every column in your layout. The id must be unique for each columndata-resize-handle
- this attribute indicates elements that are used as handle bars which can be dragged to resize columns. Can be auto generated withautoResizeHandles
optiondata-no-auto-resize-handles
- this optional data attribute can be used along withautoResizeHandles
option to exclude rows from automatic handles generation
<!--
row is the container of columns.
each row must have display:flex
-->
<div class="row">
<!--
data-column-id must provide an id unique for each column
-->
<div class="column" data-column-id="name">
Godric Gryffindor
<!--
data-resize-handle indicates the handlebar element,
that can be dragged to resize columns.
such element must be nested inside of the column element
-->
<div data-resize-handle></div>
</div>
<div class="column" data-column-id="faculty">
Gryffindor
<div data-resize-handle></div>
</div>
<div class="column" data-column-id="long-number">
9847362594654783295483726543782
<!--
this is the last column in the row,
so it doesn't have a resize handle
-->
</div>
</div>
You need to provide the root element to limit the scope of each column set. This is the element from which the querySelector's fire, searching for all the data-attributed elements.
If you don't care about scope, or only have a single use case, feel free to provide document.body
as root
const rootElement = document.getElementById('wrapper')
const columns = new ColumnsResize(rootElement)
Along with the root element you can provide an options object
const columns = new ColumnsResize(rootElement, {
defaultMinWidth: 50,
minWidthByColumnId: {
'name': 100
},
minWidthFormat: 'px',
autoResizeHandles: true,
onResizeStart: () => console.log('resize start'),
onResizeEnd: () => console.log('resize end'),
logs: true
})
type: number
default: 50
A min width for each column unless a different min width is specified by minWidthByColumnId
The column will not shrink past its min width. If the user keeps resizing in the direction of a column that has reached its minimal width, the next column will start to shrink until there's no shrinkable columns left.
type: { [key: string]: number }
default: {}
Min widths for specific columns
type: 'px'|'0-1'
default: 'px'
Specifies the format of minWidthByColumnId
.
If set to 'px'
, min width values are treated as pixel sizes.
If set to '0-1'
, a value from 0
to 1
is expected and min widths are calculated based on total columns width.
type: boolean
default: false
Automatically create resize handles for every column (except the last one)
Note that positioning and styling of auto-generated handles is up to you (no styles are applied by default to the created elements)
If you need to exclude some rows from automatic resize handle generation, use data-no-auto-resize-handles
on that row
<div class="row" data-no-auto-resize-handles>
<!-- No resize handles in this row -->
<div class="column" data-column-id="name">John</div>
<div class="column" data-column-id="surname">Doe</div>
<div class="column" data-column-id="age">28</div>
</div>
<div class="row">
<div class="column" data-column-id="name">
Mary
<!-- Resize handle will be created here -->
</div>
<div class="column" data-column-id="surname">
Perry
<!-- And here -->
</div>
<div class="column" data-column-id="age">
27
<!-- But not here -->
</div>
</div>
type: () => void
default: undefined
Callback to be called when resize action is initiated
type: () => void
default: undefined
Callback to be called when resize action has ended
type: boolean
default: false
Whether or not to print logs (could be useful for debugging)
If the DOM tree is updated - for example, new rows are added, you need to reconnect the instance
columns.reconnect()
If for any reason at some point you need to disable the resizability of your layout
columns.disconnect()
If at some point you need to reset column widths to their initial values
columns.reset()
Note that initial widths are measured at the initialization of instance and are not affected by disconnect / reconnect unless new columns are added (or removed). In that case, initial widths get overwritten
Conditional classes are applied to the key elements for styling purposes
See Live demo (with color coding enabled) for better understanding of how the classes are applied
Here's the list of classes and the elements they're applied to:
Class name | Element(s) | Meaning |
---|---|---|
columns-resize-connected |
Root, all columns, all handlebars | Instance is connected, columns resizable |
columns-resize-active |
Root, columns currently being resized, handlebar currently being dragged | Resize action is happening right now, and this element is taking active part in it. You can expect exactly 1 handlebar and 2 columns to be active at any moment during every resize |
columns-resize-growing |
Column currently growing | This column is active & its size is being increased |
columns-resize-shrinking |
Column currently shrinking | This column is active & its size is being decreased |
Every row must be a flexbox
.row {
display: flex;
}
It is recommended for columns to have these two properties, however it depends on implementation
.column {
text-overflow: ellipsis;
white-space: nowrap;
}
Feel free to set the initial sizes of the columns with flex property
.column {
flex: 1 1 33%;
}
.column:nth-child(2) {
flex: 0 0 120px;
}
Note that evey element with data-column-id
on it will automatically get these styles:
overflow: hidden;
box-sizing: border-box;
Positioning and styling of the resize handles is up to you regardless of whether they're createdted automatically or marked up manually
It is recommended that each resize handle is positioned in the far-right part of the column, for example, like this:
[data-resize-handle] {
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 2px;
cursor: col-resize;
}
You may find it useful to increase the size of the handle to make it more clickable, while keeping the line itself thinner for aesthetics. Then you'll need to get a bit tricky, but one possible solution is to style a line as an ::after
element, while keeping the wider [data-resize-handle]
element transparent:
[data-resize-handle] {
width: 10px;
display: flex;
justify-content: center;
}
[data-resize-handle]::after {
display: block;
content: '';
width: 2px;
height: 100%;
background: black;
}
[data-resize-handle].columns-resize-active::after {
background: red;
}
You may want to set cursor to grabbing
(for example) on the entire page while the resize is in progress. This can be acheived by conditionally toggling a class on the body:
new ColumnsResize(rootElement, {
onResizeStart() {
document.body.classList.add('grabbing')
},
onResizeEnd() {
document.body.classList.remove('grabbing')
}
})
body.grabbing * {
cursor: grabbing;
}