├── .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 | [![GitHub release](https://img.shields.io/github/release/mikemenaker/vue-data-table.svg)]() [![license](https://img.shields.io/github/license/mikemenaker/vue-data-table.svg)]() 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 | ![Alt text](https://ibin.co/3WXQDL4I3P29.gif "Data Table") 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 | 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 | 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 | 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 | 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 | 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 | 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 | --------------------------------------------------------------------------------