├── .gitignore
├── .markdownlint.json
├── LICENSE
├── README.md
├── index.html
├── lib
└── main.ts
├── package.json
├── src
├── App.vue
├── VueScrollingTable.vue
└── main.ts
├── tsconfig.json
├── vite.config.js
└── vue-shim.d.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | package-lock.json
6 | debug.log
7 | .editorconfig
8 | *.tgz
--------------------------------------------------------------------------------
/.markdownlint.json:
--------------------------------------------------------------------------------
1 | {
2 | "no-hard-tabs": {
3 | "code_blocks": false
4 | },
5 | "line-length": {
6 | "line_length": 100,
7 | "tables": false
8 | },
9 | "no-bare-urls": false
10 | }
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Richard Tallent
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-scrolling-table
2 |
3 | > A Vue component to create tables with vertical and horizontal scrolling. Flexbox-based.
4 |
5 | ## Demo
6 |
7 | There is a live demo here:
8 | https://tallent.us/vue-scrolling-table
9 |
10 | The demo will allow you to play with various options.
11 |
12 | The repo for the demo application is here:
13 | https://github.com/richardtallent/vue-scrolling-table-sample
14 |
15 | ## Intro
16 |
17 | I recently needed a Vue component for a data grid in a desktop application I'm building. No need for
18 | responsiveness--in this case, I just need one huge table that scrolls vertically and horizontally,
19 | like a spreadsheet. I also needed the table to fit itself neatly into a flexbox layout, taking up
20 | any available space.
21 |
22 | "No problem, this is late 2017, we have modern browsers with CSS2 `sticky`!" Nope. Browser support
23 | for sticky is still buggy and incomplete, and I also needed the solution to work on IE11 to support
24 | outdated corporate environments.
25 |
26 | I found some very nice datagrid components, but none that suported everything on my wish list:
27 |
28 | - Flexbox sizing
29 | - Horizontal and vertical scrolling body
30 | - Flexibility to render my `
` and `
` cells however I want (which, for me, probably means
31 | lots of custom renderers, many of which will probably be Vue components of their own).
32 | - No built-in data model (I'd rather implement sorting, paging, etc. myself)
33 | - English documentation (Vue has global popularity, which is awesome, but occasionally that means
34 | some great components are out of reach.)
35 |
36 | So, I wrote this component. It is purposefully bare-bones, only drawing the table elements and
37 | synchronizing the horizontal scrolling of the header and body. It doesn't render any `
`, `
`,
38 | or `
` elements itself. Instead, your parent component/application should render those using
39 | named slots. This gives you complete control over how those are handled, and allows this component
40 | to just focus on its sole task of making the table fit its parent and allowing the body to scroll.
41 |
42 | If you're curious, before creating the Vue component, the proof of concept was done on CodePen:
43 | https://codepen.io/richardtallent/pen/rpWBQK
44 |
45 | ## Example Usage
46 |
47 | In your `main.js`, if you want to globally register the component and its CSS:
48 |
49 | ```JavaScript
50 | import VueScrollingTable from "vue-scrolling-table"
51 | import "/node_modules/vue-scrolling-table/dist/style.css"
52 | //...
53 | createApp(App)
54 | .component(VueScrollingTable.name, VueScrollingTable)
55 | .mount("#app")
56 | ```
57 |
58 | In your template:
59 |
60 | ```HTML
61 |
62 |
63 |
64 |
{{ col.title }}
67 |
68 |
69 |
70 |
71 |
{{ item[col.id] }}
74 |
75 |
76 |
77 | ```
78 |
79 | ## Properties
80 |
81 | ### deadAreaColor
82 |
83 | This is a **string** value. The default is `#CCC`. This is the color used for the "dead area" within
84 | any scrolling table that isn't used for the table contents. This dead area is possible because
85 | the table fits its parent container, but the rows or columns may not fill the entire space. This
86 | property accepts any legal CSS color expression (triplets, `rgb()`, etc.).
87 |
88 | ### includeFooter
89 |
90 | Boolean, defaults to `false`. Set this to `true` if you are providing content for a `tfoot` slot,
91 | otherwise the element will not be rendered.
92 |
93 | ### syncHeaderScroll
94 |
95 | Boolean, defaults to `true`. Set to `false` if you _do not_ want your header to scroll automatically
96 | when the user scrolls the body horizontally.
97 |
98 | ### syncFooterScroll
99 |
100 | Boolean, defaults to `true`. Set to `false` if you _do not_ want your footer to scroll automatically
101 | when the user scrolls the body horizontally.
102 |
103 | ### scrollHorizontal
104 |
105 | Boolean, defaults to `true`. Set to `false` if you _do not_ want the user to be able to scroll the
106 | body content horizontally (any overflow will be hidden).
107 |
108 | ### scrollVertical
109 |
110 | Boolean, defaults to `true`. Set to `false` if you _do not_ want the user to be able to scroll the
111 | body content vertically (any overflow will be hidden).
112 |
113 | ## Slots
114 |
115 | To render your actual rows and cells, you'll be using _named slots_. This gives you full control
116 | of how the table contents are rendered.
117 |
118 | ### thead
119 |
120 | Required. Use this slot to inject the `` element's contents. The component will freeze it at
121 | the top, and will synchronize its horizontal scrolling with `
` scroll (there may be a short
122 | delay).
123 |
124 | ### tbody
125 |
126 | Required. Use this slot to inject the `` element's contents. The component will make it
127 | scrollable.
128 |
129 | ### tfoot
130 |
131 | Optional. Use this slot if you want to inject contents for a `` element. The component will
132 | freeze it at the bottom, below the scrolled ``. For now, this element is not scrolled
133 | automatically with the body. If you include this, you'll also need to set the `includeFooter` prop
134 | to `true` so the component knows to render the `` element.
135 |
136 | ## Events
137 |
138 | A `scroll` event is emitted by this component when the user scrolls the body. This event passes
139 | four arguments: the `` `scrollTop`, `scrollLeft`, `scrollHeight`, and `scrollWidth`. You
140 | can use this to, for example, show icons indicating that the user can scroll (useful when the
141 | browser doesn't display a scrollbar). Since this is fired based on the DOM `scroll` event, the
142 | same usual caveat applies: this is a high-frequency event, so try not to do anything complicated
143 | in response (if you need to do so, debounce the events and/or use `requestAnimationFrame`).
144 |
145 | A `header-dragover` event is emitted as the user drags a draggable element around over the `THEAD`
146 | element. This may be needed to, for example, implement resizable columns. The `preventDefault`
147 | call is made automatically by this component.
148 |
149 | A `header-dragenter` event is emitted when the user drags a draggable element into the `THEAD`
150 | element. This may be needed to, for example, implement resizable columns.
151 |
152 | A `header-drop` event is emitted when the user drops a draggable element on the `THEAD` element.
153 | This may be needed to, for example, implement resizable columns.
154 |
155 | ## Browser Compatibility
156 |
157 | This component is compatible with modern browsers. It may be compatible with older browsers, but
158 | I don't test on them.
159 |
160 | ## Slot Markup and Styling Requirements
161 |
162 | An important requirement of this component is that **all `
` and `
` cells** must have a
163 | **specific width** set for them, either via CSS classes or style attributes. Cells can't auto-size
164 | based on contents because that would leave the header and body cells with different widths.
165 |
166 | While it's theoretically possible to update the header column widths to match the body and vice
167 | versa, it's tricky, because unlike with scrolling, there are _many events_ that can result in a
168 | table cell resize (content change, CSS change, window resize, layout resize, etc.). Most
169 | implementations, including one I've done in the past, just end up polling on a timer and checking
170 | for columns to resize.
171 |
172 | You can implement this sort of column-width-tracking in your parent component if you want, but
173 | otherwise, you'll need to set the `width`, `min-width`, and `max-width` for all `
` and `
`
174 | cells to guarantee the width of all rows for a given column are the same. By default, they are
175 | all set to `10em`. While you can't use percentage units, depending on your layout, you can use
176 | `vw` units to achieve a similar scaled effect.
177 |
178 | ## Customizing the Style
179 |
180 | What little default styling is provided on the table is purposefully _very_ basic, and is not
181 | scoped, so it's easy to override in your calling application. Use `table.scrolling` as the base
182 | selector.
183 |
184 | ## How do I "freeze" a column?
185 |
186 | Here's some sample CSS for freezing the first column in a table. Unfortunately, it only works
187 | in Chrome and Safari as of December 2017:
188 |
189 | ```CSS
190 | table.scrolling td:first-child,
191 | table.scrolling th:first-child {
192 | position: -webkit-sticky;
193 | position: sticky;
194 | left: 0;
195 | }
196 | ```
197 |
198 | Supporting this in every browser by simulating `sticky` is theoretically possible, but much
199 | more difficult than the scrolling implemented by this component due to differences in row
200 | height, etc. that would happen if the first column is removed from the normal flow to, say,
201 | use absolute positioning and update the scroll position with Javascript.
202 |
203 | ## Future plans
204 |
205 | I plan to actually use this on an upcoming project at work. It will be a good torture-test
206 | for the component. Some features I'm considering:
207 |
208 | - [x] Emitting events when the tbody is scrolled, so the caller can do other things.
209 | - [x] Optional footer scrolling.
210 | - [ ] Get rid of the need for the includeFooter prop.
211 | - [x] Option to disable/enable scrolling in either direction.
212 | - [ ] Avoid creating extra block on right of header if browser doesn't show scroll bars.
213 | - [x] Add TypeScript declarations (anyone know how to make Vite do this on build?)
214 |
215 | I'm open to other ideas, as long as they don't limit the flexibility of using slots for
216 | the header, body, and footer. But if someone wants to _build_ a data grid component that
217 | has this as a dependency, I'm all for it.
218 |
219 | ## Build Setup
220 |
221 | ```bash
222 | # install dependencies
223 | npm install
224 |
225 | # build for production with minification
226 | npm run build
227 | ```
228 |
229 | ## Release History
230 |
231 | | Date | Version | Notes |
232 | | ---------- | ------- | ------------------------------------------------------------------------------------ |
233 | | 2017.12.24 | 0.1.0 | First published version |
234 | | 2017.12.24 | 0.1.1 | Patch based on sample app deveopment |
235 | | 2017.12.24 | 0.1.2 | Fix: old version went to npm |
236 | | 2017.12.25 | 0.2.0 | Added lots of options, updated README, fixed some display bugs when less data shown. |
237 | | 2018.08-06 | 0.2.1 | Added `header-dragenter`, `header-dragover`, and `header-drop` events. |
238 | | 2018.08-06 | 0.2.2 | $emit. _sigh_ |
239 | | 2020.02.02 | 1.0.0 | Upgraded to Vue 3, Vite, TypeScript. BREAKING, DO NOT UPGRADE FOR VUE 2.x. |
240 | | 2020.02.02 | 1.0.1 | Fix CSS export? |
241 | | 2020.02.05 | 1.0.3 | Gave up on TS. Fix CSS export? |
242 | | 2020.02.10 | 1.0.4 | TS Fixed. CSS injection is not automatic for Vite, documented this. |
243 | | 2020.02.10 | 2.0.0 | Simplify implementation (requires Vue 3.2). Border/BG styles easier to override. |
244 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | vue-scrolling-table
8 |
13 |
14 |
15 |
16 |
17 |
31 |
32 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/lib/main.ts:
--------------------------------------------------------------------------------
1 | import VueScrollingTable from "../src/VueScrollingTable.vue"
2 |
3 | export default VueScrollingTable
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-scrolling-table",
3 | "version": "2.0.0",
4 | "description": "A Vue 3 component to create tables with vertical and horizontal scrolling. Flexbox-based.",
5 | "author": "richardtallent ",
6 | "license": "MIT",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/richardtallent/vue-scrolling-table"
10 | },
11 | "homepage": "https://tallent.us/vue-scrolling-table/",
12 | "private": false,
13 | "main": "./dist/vue-scrolling-table.umd.js",
14 | "module": "./dist/vue-scrolling-table.es.js",
15 | "types": "./dist/vue-scrolling-table.d.ts",
16 | "style": "./dist/style.css",
17 | "files": [
18 | "dist"
19 | ],
20 | "exports": {
21 | ".": {
22 | "import": "./dist/vue-scrolling-table.es.js",
23 | "require": "./dist/vue-scrolling-table.umd.js"
24 | }
25 | },
26 | "scripts": {
27 | "dev": "vite",
28 | "devs": "vite --https",
29 | "build": "vite build"
30 | },
31 | "dependencies": {
32 | "vue": "^3.2.31"
33 | },
34 | "devDependencies": {
35 | "@typescript-eslint/parser": "^5.14.0",
36 | "@vitejs/plugin-vue": "^2.2.4",
37 | "@vue/compiler-sfc": "^3.2.31",
38 | "autoprefixer": "^10.4.2",
39 | "eslint": "^8.11.0",
40 | "eslint-config-tabsanity": "^2.0.0",
41 | "eslint-plugin-prettier": "^4.0.0",
42 | "eslint-plugin-vue": "^8.5.0",
43 | "postcss": "^8.4.8",
44 | "prettier": "^2.5.1",
45 | "rollup-plugin-typescript2": "^0.31.2",
46 | "stylelint": "^14.5.3",
47 | "stylelint-config-standard": "^25.0.0",
48 | "typescript": "^4.6.2",
49 | "vite": "^2.8.6"
50 | },
51 | "eslintConfig": {
52 | "extends": [
53 | "plugin:vue/vue3-essential",
54 | "eslint:recommended",
55 | "@vue/prettier",
56 | "tabsanity"
57 | ],
58 | "parserOptions": {
59 | "parser": "@typescript-eslint/parser"
60 | }
61 | },
62 | "prettier": {
63 | "useTabs": true,
64 | "semi": false,
65 | "singleQuote": false,
66 | "bracketSpacing": true,
67 | "trailingComma": "es5",
68 | "printWidth": 180
69 | },
70 | "postcss": {
71 | "plugins": {
72 | "autoprefixer": {}
73 | }
74 | },
75 | "stylelint": {
76 | "extends": "stylelint-config-standard",
77 | "exclude": [
78 | "dist"
79 | ],
80 | "rules": {
81 | "indentation": "tab",
82 | "declaration-block-trailing-semicolon": null,
83 | "no-descending-specificity": null
84 | }
85 | },
86 | "browserslist": [
87 | "> 1%",
88 | "last 2 versions",
89 | "not ie < 11",
90 | "maintained node versions"
91 | ]
92 | }
93 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
vue-scrolling-table
7 |
Sample app for the VueScrollingTableComponent
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | vue-scrolling-table is a Vue that provides a flexbox-based table control, where the header, body, and footer of the control are rendered via named slots for
17 | maximum flexibility, and the component allows the body to be scrolled horizontally and vertically as needed.
18 |
26 | Note: this demo page uses the Bulma library for basic styling, and that styling overides a few details of the default vue-scrolling-table, notably removing the border
27 | from the header cells and adding rounded corners.
28 |