├── .babelrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── dist
└── .gitkeep
├── package.json
├── src
├── Components
│ └── v-data-table.vue
└── main.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", { "modules": false }],
4 | "es2015"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log
4 | yarn-error.log
5 | dist/*
6 | !dist/.gitkeep
7 | dist-module/
8 |
9 | # Editor directories and files
10 | .idea
11 | *.suo
12 | *.ntvs*
13 | *.njsproj
14 | *.sln
15 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log
4 | yarn-error.log
5 |
6 | # Editor directories and files
7 | .idea
8 | *.suo
9 | *.ntvs*
10 | *.njsproj
11 | *.sln
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 mikemenaker
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 | []() []()
2 |
3 | # vue data-table
4 | Smart table using vue.js - sorting columns, filter by string, child rows, customs columns, custom row data
5 |
6 | 
7 |
8 | ## Demo:
9 |
10 | https://jsfiddle.net/mikemenaker/zuyvwvms/
11 |
12 | ## Installation
13 | ### With npm:
14 | ```bash
15 | npm i v-data-table --save
16 | ```
17 |
18 | ### With a CDN:
19 | ```html
20 |
21 |
22 |
23 |
24 | ```
25 |
26 | ## Usage
27 | ### With an ES6 bundler (via npm)
28 | In your index file
29 | ```js
30 | import DataTable from 'v-data-table'
31 | Vue.use(DataTable)
32 | ```
33 |
34 | ### With a CDN
35 | ```html
36 |
43 | ```
44 |
45 | ## Props:
46 |
47 | - data
48 | - Array
49 | - Data to create table from
50 | - Needs to be object based (no primitives like strings, numerical, boolean)
51 | - Array change detection needs to adhere to: https://vuejs.org/v2/guide/list.html#Array-Change-Detection
52 | - columnsToDisplay
53 | - Array
54 | - Which columns to display in table
55 | - columnsToNotDisplay
56 | - Array
57 | - Which columns to not display in table (cannot be used with columnsToDisplay)
58 | - aggregateColumns
59 | - Boolean
60 | - Walk all objects instead of just first object to get list of columns (cannot be used with columnsToDisplay)
61 | - displayNames
62 | - Object
63 | - Mapping of column name -> display name
64 | - filterKey
65 | - String
66 | - Filter data for string
67 | - childHideable
68 | - Boolean
69 | - Are child rows hideable (double click open/close)
70 | - childInitHide
71 | - Boolean
72 | - If child rows are expandable, should they be hidden initially
73 | - columnsToSort
74 | - Array
75 | - What columns should be sortable (columnsToNotSort will take precedence if both are provided)
76 | - columnsToNotSort
77 | - Array
78 | - What columns should not be sortable
79 | - childTransitionClass
80 | - String
81 | - CSS class to use in transition
82 | - itemsPerPage
83 | - Numbers
84 | - Enables pagination
85 |
86 | ## Slots:
87 |
88 | - caption
89 | - Any caption that should be inserted before the header
90 | - child
91 | - Any sub row of child detail data
92 | - column
93 | - Any template for a column
94 | - nodata
95 | - Slot to display if the data provided is empty
96 |
97 | ## Styling:
98 | - Selected columns have the class "active"
99 | - Arrows are a span with class "arrow"
100 | - Ascending/descending arrows also have class "asc"/"dsc"
101 |
102 | ```css
103 | th.active .arrow.asc {
104 | border-bottom: 4px solid #4d4d4d;
105 | }
106 |
107 | th.active .arrow.dsc {
108 | border-top: 4px solid #4d4d4d;
109 | }
110 |
111 | .arrow {
112 | display: inline-block;
113 | vertical-align: middle;
114 | width: 0;
115 | height: 0;
116 | margin-left: 5px;
117 | }
118 |
119 | .arrow.asc {
120 | border-left: 4px solid transparent;
121 | border-right: 4px solid transparent;
122 | border-bottom: 4px solid #cdc;
123 | }
124 |
125 | .arrow.dsc {
126 | border-left: 4px solid transparent;
127 | border-right: 4px solid transparent;
128 | border-top: 4px solid #cdc;
129 | }
130 | ```
131 |
132 | or with Font Awesome
133 |
134 | ```css
135 | .arrow.asc {
136 | position: relative;
137 | }
138 | .arrow.asc:before {
139 | content: "\f062";
140 | font-family: FontAwesome;
141 | position: absolute;
142 | left: -5px;
143 | }
144 |
145 | .arrow.dsc {
146 | position: relative;
147 | }
148 | .arrow.dsc:before {
149 | content: "\f063";
150 | font-family: FontAwesome;
151 | position: absolute;
152 | left: -5px;
153 | }
154 | ```
155 |
156 | -For pagination next page/previous page spans will have class "nextPage"/"previousPage"
157 | ```css
158 | .previousPage {
159 | position: relative;
160 | }
161 | .previousPage:before {
162 | content: "\f104";
163 | font-family: FontAwesome;
164 | position: absolute;
165 | }
166 |
167 | .nextPage {
168 | position: relative;
169 | }
170 | .nextPage:before {
171 | content: "\f105";
172 | font-family: FontAwesome;
173 | position: absolute;
174 | left: 5px;
175 | }
176 | ```
177 |
178 | ## Examples
179 | Basic table:
180 | ```html
181 |
182 |
183 |
184 |
185 | ```
186 |
187 | ```javascript
188 | var demo = new Vue({
189 | el: '#demo',
190 | data: {
191 | gridData: [{
192 | name: 'Chuck Norris',
193 | power: Infinity
194 | }, {
195 | name: 'Bruce Lee',
196 | power: 9000
197 | }, {
198 | name: 'Jackie Chan',
199 | power: 7000
200 | }, {
201 | name: 'Jet Li',
202 | power: 8000
203 | }]
204 | }
205 | })
206 | ```
207 |
208 | Only display certain columns:
209 | ```html
210 |
211 |
212 |
213 |
214 | ```
215 |
216 | ```javascript
217 | var demo = new Vue({
218 | el: '#demo',
219 | data: {
220 | gridColumns: ['name', 'power'],
221 | gridData: [{
222 | name: 'Chuck Norris',
223 | power: Infinity
224 | }, {
225 | name: 'Bruce Lee',
226 | power: 9000
227 | }, {
228 | name: 'Jackie Chan',
229 | power: 7000
230 | }, {
231 | name: 'Jet Li',
232 | power: 8000
233 | }]
234 | }
235 | })
236 | ```
237 |
238 | Bind to search string:
239 | ```html
240 |
241 |
245 |
246 |
247 |
248 | ```
249 |
250 | ```javascript
251 | var demo = new Vue({
252 | el: '#demo',
253 | data: {
254 | searchQuery: '',
255 | gridData: [{
256 | name: 'Chuck Norris',
257 | power: Infinity
258 | }, {
259 | name: 'Bruce Lee',
260 | power: 9000
261 | }, {
262 | name: 'Jackie Chan',
263 | power: 7000
264 | }, {
265 | name: 'Jet Li',
266 | power: 8000
267 | }]
268 | }
269 | })
270 | ```
271 |
272 | Map display names of columns:
273 | ```html
274 |
275 |
276 |
277 |
278 | ```
279 |
280 | ```javascript
281 | var demo = new Vue({
282 | el: '#demo',
283 | data: {
284 | displayNames: {
285 | 'power': 'Super Powers'
286 | },
287 | gridData: [{
288 | name: 'Chuck Norris',
289 | power: Infinity
290 | }, {
291 | name: 'Bruce Lee',
292 | power: 9000
293 | }, {
294 | name: 'Jackie Chan',
295 | power: 7000
296 | }, {
297 | name: 'Jet Li',
298 | power: 8000
299 | }]
300 | }
301 | })
302 | ```
303 |
304 | Add a caption:
305 | ```html
306 |
307 |
308 | This is my caption
309 |
310 |
311 | ```
312 |
313 | ```javascript
314 | var demo = new Vue({
315 | el: '#demo',
316 | data: {
317 | gridData: [{
318 | name: 'Chuck Norris',
319 | power: Infinity
320 | }, {
321 | name: 'Bruce Lee',
322 | power: 9000
323 | }, {
324 | name: 'Jackie Chan',
325 | power: 7000
326 | }, {
327 | name: 'Jet Li',
328 | power: 8000
329 | }]
330 | }
331 | })
332 | ```
333 |
334 | Use template for a column (template name must match column name):
335 | ```html
336 |
337 |
338 |
339 | {{props.entry.name}}
340 |
341 |
344 |
345 |
346 |
347 | ```
348 |
349 | ```javascript
350 | var demo = new Vue({
351 | el: '#demo',
352 | data: {
353 | gridData: [{
354 | name: 'Chuck Norris',
355 | power: Infinity
356 | }, {
357 | name: 'Bruce Lee',
358 | power: 9000
359 | }, {
360 | name: 'Jackie Chan',
361 | power: 7000
362 | }, {
363 | name: 'Jet Li',
364 | power: 8000
365 | }]
366 | },
367 | methods: {
368 | showPower(power) {
369 | alert(power);
370 | }
371 | }
372 | })
373 | ```
374 |
375 | Add a child row, each section will be a tbody of 2 rows (data row, child row):
376 | ```html
377 |
378 |
379 |
380 | This is my child row: {{props.entry.name}}
381 |
382 |
383 |
384 | ```
385 |
386 | ```javascript
387 | var demo = new Vue({
388 | el: '#demo',
389 | data: {
390 | gridData: [{
391 | name: 'Chuck Norris',
392 | power: Infinity
393 | }, {
394 | name: 'Bruce Lee',
395 | power: 9000
396 | }, {
397 | name: 'Jackie Chan',
398 | power: 7000
399 | }, {
400 | name: 'Jet Li',
401 | power: 8000
402 | }]
403 | }
404 | })
405 | ```
406 |
407 | Add ability to toggle child row (double click to open/close):
408 | ```html
409 |
410 |
411 |
412 | This is my child row: {{props.entry.name}}
413 |
414 |
415 |
416 | ```
417 |
418 | ```javascript
419 | var demo = new Vue({
420 | el: '#demo',
421 | data: {
422 | gridData: [{
423 | name: 'Chuck Norris',
424 | power: Infinity
425 | }, {
426 | name: 'Bruce Lee',
427 | power: 9000
428 | }, {
429 | name: 'Jackie Chan',
430 | power: 7000
431 | }, {
432 | name: 'Jet Li',
433 | power: 8000
434 | }]
435 | }
436 | })
437 | ```
438 |
439 | Add ability to toggle child row (double click main row to open, double click child to close) and default to children rows closed:
440 | ```html
441 |
442 |
443 |
444 | This is my child row: {{props.entry.name}}
445 |
446 |
447 |
448 | ```
449 |
450 | ```javascript
451 | var demo = new Vue({
452 | el: '#demo',
453 | data: {
454 | gridData: [{
455 | name: 'Chuck Norris',
456 | power: Infinity
457 | }, {
458 | name: 'Bruce Lee',
459 | power: 9000
460 | }, {
461 | name: 'Jackie Chan',
462 | power: 7000
463 | }, {
464 | name: 'Jet Li',
465 | power: 8000
466 | }]
467 | }
468 | })
469 | ```
470 |
471 |
472 |
--------------------------------------------------------------------------------
/dist/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemenaker/vue-data-table/032db18149cdc6aa992850a4c00231cb5216ca05/dist/.gitkeep
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "v-data-table",
3 | "description": "Vue.js 2.0 data table",
4 | "version": "2.1.0",
5 | "main": "dist-module/main.js",
6 | "scripts": {
7 | "prepublish": "npm run build && npm run browser-build",
8 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
9 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
10 | "browser-build": "cross-env NODE_ENV=production browserify -g envify -p [ vueify/plugins/extract-css -o dist/v-data-table.css ] -e src/main.js | uglifyjs -c warnings=false -m > dist/v-data-table.js"
11 | },
12 | "browserify": {
13 | "transform": [
14 | "babelify",
15 | "vueify"
16 | ]
17 | },
18 | "browser": {
19 | "vue": "vue/dist/vue.common.js"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/mikemenaker/vue-data-table.git"
24 | },
25 | "keywords": [],
26 | "author": "Mike Menaker",
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/mikemenaker/vue-data-table/issues"
30 | },
31 | "homepage": "https://github.com/mikemenaker/vue-data-table#readme",
32 | "dependencies": {
33 | "vue": "^2.3.3"
34 | },
35 | "devDependencies": {
36 | "babel-cli": "^6.24.1",
37 | "babel-core": "^6.0.0",
38 | "babel-loader": "^6.0.0",
39 | "babel-preset-env": "^1.5.1",
40 | "babel-preset-es2015": "^6.24.1",
41 | "browserify": "^14.4.0",
42 | "cross-env": "^3.0.0",
43 | "css-loader": "^0.25.0",
44 | "file-loader": "^0.9.0",
45 | "node-sass": "^4.5.0",
46 | "sass-loader": "^5.0.1",
47 | "vue-loader": "^12.1.0",
48 | "vue-template-compiler": "^2.3.3",
49 | "vueify": "^9.4.1",
50 | "webpack": "^2.6.1",
51 | "webpack-dev-server": "^2.4.5",
52 | "babel-plugin-transform-runtime": "^6.0.0",
53 | "babel-preset-stage-2": "^6.0.0",
54 | "babel-runtime": "^6.0.0",
55 | "babelify": "^7.2.0",
56 | "browserify-hmr": "^0.3.1",
57 | "envify": "^3.4.1",
58 | "http-server": "^0.9.0",
59 | "npm-run-all": "^2.3.0",
60 | "phantomjs-prebuilt": "^2.1.3",
61 | "proxyquireify": "^3.0.1",
62 | "uglify-js": "^2.5.0",
63 | "watchify": "^3.4.0"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Components/v-data-table.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{ key | getDisplayName(displayNames) }}
10 |
11 | |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{ key | getDisplayName(displayNames) }}
31 |
32 |
33 | |
34 |
35 |
36 |
37 |
38 |
39 |
40 | {{entry[key]}}
41 |
42 | |
43 |
44 |
45 |
46 |
47 |
48 | |
49 |
50 |
51 |
52 |
53 | {{ currentPage * itemsPerPage - itemsPerPage + 1 }} - {{ Math.min(currentPage * itemsPerPage, filteredData.length) }} of {{ filteredData.length }}
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | {{ key | getDisplayName(displayNames) }}
66 |
67 |
68 | |
69 |
70 |
71 |
72 |
73 |
74 |
75 | {{entry[key]}}
76 |
77 | |
78 |
79 |
80 |
81 | {{ currentPage * itemsPerPage - itemsPerPage + 1 }} - {{ Math.min(currentPage * itemsPerPage, filteredData.length) }} of {{ filteredData.length }}
82 |
83 |
84 |
85 |
86 |
87 |
285 |
286 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import DataTableComp from './Components/v-data-table.vue'
2 |
3 | const DataTable = {
4 | install(Vue, options = {}) {
5 | Vue.component('data-table', DataTableComp)
6 | },
7 | }
8 |
9 | if (typeof window !== 'undefined' && window.Vue) {
10 | window.Vue.use(DataTable);
11 | }
12 |
13 | window.DataTable = DataTable
14 |
15 | export { DataTable }
16 | export default DataTable
17 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var webpack = require('webpack')
3 |
4 | module.exports = {
5 | entry: './src/main.js',
6 | output: {
7 | path: path.resolve(__dirname, './dist-module'),
8 | publicPath: '/dist-module/',
9 | filename: 'main.js',
10 | library: 'es6Module',
11 | libraryTarget: 'umd'
12 | },
13 | module: {
14 | rules: [
15 | {
16 | test: /\.vue$/,
17 | loader: 'vue-loader',
18 | options: {
19 | loaders: {
20 | // Since sass-loader (weirdly) has SCSS as its default parse mode, we map
21 | // the "scss" and "sass" values for the lang attribute to the right configs here.
22 | // other preprocessors should work out of the box, no loader config like this necessary.
23 | 'scss': 'vue-style-loader!css-loader!sass-loader',
24 | 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'
25 | }
26 | // other vue-loader options go here
27 | }
28 | },
29 | {
30 | test: /\.js$/,
31 | loader: 'babel-loader',
32 | exclude: /node_modules/
33 | },
34 | {
35 | test: /\.(png|jpg|gif|svg)$/,
36 | loader: 'file-loader',
37 | options: {
38 | name: '[name].[ext]?[hash]'
39 | }
40 | }
41 | ]
42 | },
43 | resolve: {
44 | alias: {
45 | 'vue$': 'vue/dist/vue.esm.js'
46 | }
47 | },
48 | devServer: {
49 | historyApiFallback: true,
50 | noInfo: true
51 | },
52 | performance: {
53 | hints: false
54 | },
55 | devtool: '#eval-source-map'
56 | }
57 |
58 | if (process.env.NODE_ENV === 'production') {
59 | module.exports.devtool = '#source-map'
60 | // http://vue-loader.vuejs.org/en/workflow/production.html
61 | module.exports.plugins = (module.exports.plugins || []).concat([
62 | new webpack.DefinePlugin({
63 | 'process.env': {
64 | NODE_ENV: '"production"'
65 | }
66 | }),
67 | new webpack.optimize.UglifyJsPlugin({
68 | sourceMap: true,
69 | compress: {
70 | warnings: false
71 | }
72 | }),
73 | new webpack.LoaderOptionsPlugin({
74 | minimize: true
75 | })
76 | ])
77 | }
78 |
--------------------------------------------------------------------------------