Repository: andrewcourtice/vuetiful Branch: master Commit: 3bea2aaca0ad Files: 114 Total size: 243.7 KB Directory structure: gitextract_hb937lqj/ ├── .babelrc ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .vscode/ │ └── settings.json ├── build/ │ ├── app.js │ └── components.js ├── dist/ │ └── components/ │ ├── app.style.css │ └── components.bundle.js ├── index.html ├── license.txt ├── package.json ├── postcss.config.js ├── readme.md ├── src/ │ ├── aggregators/ │ │ ├── aggregators.js │ │ ├── average.js │ │ ├── count.js │ │ ├── max.js │ │ ├── median.js │ │ ├── min.js │ │ ├── standard-deviation.js │ │ ├── total.js │ │ └── variance.js │ ├── assets/ │ │ └── styles/ │ │ ├── abstract/ │ │ │ ├── _functions.scss │ │ │ ├── _mixins.scss │ │ │ └── _variables.scss │ │ ├── core/ │ │ │ ├── _animations.scss │ │ │ ├── _base.scss │ │ │ ├── _buttons.scss │ │ │ ├── _inputs.scss │ │ │ ├── _labels.scss │ │ │ ├── _layout.scss │ │ │ ├── _menus.scss │ │ │ ├── _tables.scss │ │ │ └── _typography.scss │ │ └── site.scss │ ├── components/ │ │ ├── calendar/ │ │ │ └── calendar.vue │ │ ├── chip/ │ │ │ └── chip.vue │ │ ├── components.js │ │ ├── datatable/ │ │ │ ├── datatable-cell.js │ │ │ ├── datatable-collection.vue │ │ │ ├── datatable-column.vue │ │ │ ├── datatable.vue │ │ │ └── readme.md │ │ ├── datetime-picker/ │ │ │ └── datetime-picker.vue │ │ ├── dynamic/ │ │ │ └── dynamic.js │ │ ├── floating-panel/ │ │ │ └── floating-panel.vue │ │ ├── modal/ │ │ │ └── modal.vue │ │ ├── paginator/ │ │ │ ├── paginator.vue │ │ │ └── readme.md │ │ ├── panel/ │ │ │ └── panel.vue │ │ ├── tab-control/ │ │ │ ├── tab-control.vue │ │ │ └── tab-pane.vue │ │ └── toggles/ │ │ ├── checkbox.vue │ │ ├── radio.vue │ │ ├── readme.md │ │ └── toggle.vue │ ├── directives/ │ │ ├── directives.js │ │ └── v-drag.js │ ├── formatters/ │ │ ├── currency.js │ │ ├── date-long.js │ │ ├── date-short.js │ │ ├── datetime.js │ │ └── formatters.js │ ├── main.js │ ├── maps/ │ │ ├── currencies.js │ │ └── maps.js │ ├── mixins/ │ │ └── checkable.js │ ├── polyfills.js │ ├── services/ │ │ ├── calendar.js │ │ ├── event-emitter.js │ │ └── http-client.js │ ├── utilities/ │ │ ├── average-of.js │ │ ├── base/ │ │ │ ├── aggregator.js │ │ │ ├── type-converter.js │ │ │ └── type-validator.js │ │ ├── filter-by.js │ │ ├── first-of.js │ │ ├── group-by.js │ │ ├── last-of.js │ │ ├── map-values.js │ │ ├── max-of.js │ │ ├── median-of.js │ │ ├── min-of.js │ │ ├── mode-of.js │ │ ├── nest.js │ │ ├── page-by.js │ │ ├── page.js │ │ ├── sort-by.js │ │ ├── standard-deviation-of.js │ │ ├── total-of.js │ │ └── variance-of.js │ └── views/ │ ├── components/ │ │ ├── buttons.vue │ │ ├── components.js │ │ ├── datatables.vue │ │ ├── modals.vue │ │ ├── paginators.vue │ │ ├── panels.vue │ │ ├── tab-controls.vue │ │ ├── toggles.vue │ │ └── typography.vue │ └── views.js ├── test/ │ ├── mocha.opts │ ├── test-data.js │ └── utilities/ │ ├── average.js │ ├── group-by.js │ ├── max-of.js │ ├── median-of.js │ ├── min-of.js │ ├── page.js │ ├── sort-by.js │ ├── total-of.js │ └── validators.js └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ ["es2015"] ] } ================================================ FILE: .eslintrc.json ================================================ { "env": { "browser": true, "commonjs": true, "es6": true, "node": true }, "parserOptions": { "ecmaFeatures": { "jsx": false }, "sourceType": "module" }, "rules": { "no-const-assign": "warn", "no-this-before-super": "warn", "no-undef": "warn", "no-unreachable": "warn", "no-unused-vars": "warn", "constructor-super": "warn", "valid-typeof": "warn" } } ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain ================================================ FILE: .gitignore ================================================ .DS_Store node_modules/ dist/app/ dist/*/report.html npm-debug.log.* *.log ================================================ FILE: .vscode/settings.json ================================================ // Place your settings in this file to overwrite default and user settings. { "eslint.enable": true } ================================================ FILE: build/app.js ================================================ // Import Vue and Vue plugins import Vue from "vue"; import VueRouter from "vue-router"; import Vuetiful from "../src/main"; import views from "../src/views/views"; function registerPlugins() { Vue.use(Vuetiful); Vue.use(VueRouter); } function buildRoutes() { let routes = []; for (let directoryName in views) { let directory = views[directoryName]; for (let viewName in directory) { let view = directory[viewName]; let path = `/${directoryName}/${viewName}`; if (view.params) { path += "/:" + view.params.join("/:"); } routes.push({ path: path, component: view.component }); } } return routes; } function buildRootInstance() { let routes = buildRoutes(); let menuItems = [ { name: "Buttons", route: "/components/buttons" }, { name: "Datatables", route: "/components/datatables" }, { name: "Modals", route: "/components/modals" }, { name: "Paginators", route: "/components/paginators" }, { name: "Panels", route: "/components/panels" }, { name: "Tab Controls", route: "/components/tabcontrols" }, { name: "Toggles", route: "/components/toggles" }, { name: "Typography", route: "/components/typography" } ]; new Vue({ el: '#app', data() { return { menuItems: menuItems } }, router: new VueRouter({ routes: routes }) }); } function bootstrap() { registerPlugins(); buildRootInstance(); } bootstrap(); module.exports = Vuetiful; ================================================ FILE: build/components.js ================================================ import Vuetiful from "../src/main"; if (typeof window !== 'undefined' && window.Vue) { window.Vue.use(Vuetiful); } module.exports = Vuetiful; ================================================ FILE: dist/components/app.style.css ================================================ html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}[layout]{display:-ms-flexbox;display:-webkit-box;display:flex}[layout*=column],[layout*=row]{width:100%;max-width:100%}[layout^=row]{-ms-flex-direction:row;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row}[layout^=column]{-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column}[layout*=row][layout*=reverse]{-ms-flex-direction:row-reverse;-webkit-box-orient:horizontal;-webkit-box-direction:reverse;flex-direction:row-reverse}[layout*=column][layout*=reverse]{-ms-flex-direction:column-reverse;-webkit-box-orient:vertical;-webkit-box-direction:reverse;flex-direction:column-reverse}[layout*=columns],[layout*=rows]{-ms-flex-wrap:wrap;flex-wrap:wrap}[layout=none]{-ms-flex:none;-webkit-box-flex:0;flex:none}[layout*=column][layout*=top-],[layout*=row][layout*=-left]{-ms-flex-pack:start;-webkit-box-pack:start;justify-content:flex-start}[layout*=column][layout*=center-],[layout*=row][layout*=-center],[layout~=centered]{-ms-flex-pack:center;-webkit-box-pack:center;justify-content:center}[layout*=column][layout*=bottom-],[layout*=row][layout*=-right]{-ms-flex-pack:end;-webkit-box-pack:end;justify-content:flex-end}[layout*=column][layout*=spread-],[layout*=row][layout*=-spread]{-ms-flex-pack:distribute;justify-content:space-around}[layout*=column][layout*=justify-],[layout*=row][layout*=-justify]{-ms-flex-pack:justify;-webkit-box-pack:justify;justify-content:space-between}[layout*=column][layout*=-left],[layout*=row][layout*=top-]{-ms-flex-align:start;-ms-grid-row-align:flex-start;-webkit-box-align:start;align-items:flex-start}[layout*=column][layout*=-center],[layout*=row][layout*=center-],[layout~=centered]{-ms-flex-align:center;-ms-grid-row-align:center;-webkit-box-align:center;align-items:center}[layout*=column][layout*=-right],[layout*=row][layout*=bottom-]{-ms-flex-align:end;-ms-grid-row-align:flex-end;-webkit-box-align:end;align-items:flex-end}[layout*=column][layout*=-stretch],[layout*=row][layout*=stretch-]{-ms-flex-align:stretch;-ms-grid-row-align:stretch;-webkit-box-align:stretch;align-items:stretch}[layout*=columns][layout*=-left],[layout*=rows][layout*=top-]{-ms-flex-line-pack:start;align-content:flex-start}[layout*=columns][layout*=-right],[layout*=rows][layout*=bottom-]{-ms-flex-line-pack:end;align-content:flex-end}[layout*=columns][layout*=-center],[layout*=rows][layout*=center-]{-ms-flex-line-pack:center;align-content:center}[layout*=columns][layout*=-justify],[layout*=rows][layout*=justify-]{-ms-flex-line-pack:justify;align-content:space-between}[layout*=columns][layout*=-spread],[layout*=rows][layout*=spread-]{-ms-flex-line-pack:distribute;align-content:space-around}[layout*=columns][layout*=-stretch],[layout*=rows][layout*=stretch-]{-ms-flex-line-pack:stretch;align-content:stretch}@media (-ms-high-contrast:none),screen and (-ms-high-contrast:active){[layout*=column]:not([layout*=row])>*{max-width:auto}[layout*=column][self*=top]{height:auto!important}[self~=size-]>*{height:auto}}[layout*=column]:not([layout*=row]) [self*=left],[layout*=row]:not([layout*=column]) [self*=top]{-ms-flex-item-align:start;align-self:flex-start}[self~=center]{-ms-flex-item-align:center;-ms-grid-row-align:center;align-self:center}[layout*=column]:not([layout*=row]) [self*=right],[layout*=row]:not([layout*=column]) [self*=bottom]{-ms-flex-item-align:end;align-self:flex-end}[self*=stretch]{-ms-flex-item-align:stretch;-ms-grid-row-align:stretch;align-self:stretch}[layout][self*=center]{margin-left:auto;margin-right:auto}[layout][self*=right]{margin-right:0}[layout][self*=left]{margin-left:0}[layout*=column] [self*=bottom]{margin-top:auto}[layout*=column] [self*=top]{margin-bottom:auto}[layout*=row] [self*=left]{margin-right:auto}[layout*=row] [self*=right]{margin-left:auto}[self~=size-1of5]{width:20%}[self~=size-1of4]{width:25%}[self~=size-1of3]{width:33.33333%}[self~=size-2of5]{width:40%}[self~=size-1of2]{width:50%}[self~=size-3of5]{width:60%}[self~=size-2of3]{width:66.6666%}[self~=size-3of4]{width:75%}[self~=size-4of5]{width:80%}[self~=size-1of1]{width:100%}[layout*=column][layout*=stretch-]>:not([self*=size-]),[layout*=row][layout*=-stretch]>:not([self*=size-]),[self~=size-x1]{-ms-flex:1 0 0%!important;-webkit-box-flex:1!important;flex:1 0 0%!important}[self~=size-x2]{-ms-flex:2 0 0%!important;-webkit-box-flex:2!important;flex:2 0 0%!important}[self~=size-x3]{-ms-flex:3 0 0%!important;-webkit-box-flex:3!important;flex:3 0 0%!important}[self~=size-x4]{-ms-flex:4 0 0%!important;-webkit-box-flex:4!important;flex:4 0 0%!important}[self~=size-x5]{-ms-flex:5 0 0%!important;-webkit-box-flex:5!important;flex:5 0 0%!important}[self~=size-x6]{-ms-flex:6 0 0%!important;-webkit-box-flex:6!important;flex:6 0 0%!important}[self~=size-x7]{-ms-flex:7 0 0%!important;-webkit-box-flex:7!important;flex:7 0 0%!important}[self~=size-x8]{-ms-flex:8 0 0%!important;-webkit-box-flex:8!important;flex:8 0 0%!important}[self~=size-x9]{-ms-flex:9 0 0%!important;-webkit-box-flex:9!important;flex:9 0 0%!important}[self*=size-auto]{-ms-flex:1 1 auto;-webkit-box-flex:1;flex:1 1 auto}[self*=size-x0]{-ms-flex:0 0 auto;-webkit-box-flex:0;flex:0 0 auto}[self~=size-xxlarge]{max-width:1440px;width:100%}[self~=size-xlarge]{max-width:1200px;width:100%}[self~=size-large]{max-width:960px;width:100%}[self~=size-larger]{max-width:840px;width:100%}[self~=size-medium]{max-width:720px;width:100%}[self~=size-smaller]{max-width:600px;width:100%}[self~=size-small]{max-width:480px;width:100%}[self~=size-xsmall]{max-width:360px;width:100%}[self~=size-xxsmall]{max-width:240px;width:100%}[self*=size-x]:not([self*=small]):not([self*=large]){-ms-flex-negative:1;flex-shrink:1}[self~=first]{-ms-flex-order:-1;-webkit-box-ordinal-group:0;order:-1}[self~=order-1]{-ms-flex-order:1;-webkit-box-ordinal-group:2;order:1}[self~=order-2]{-ms-flex-order:2;-webkit-box-ordinal-group:3;order:2}[self~=order-3]{-ms-flex-order:3;-webkit-box-ordinal-group:4;order:3}[self~=last]{-ms-flex-order:999;-webkit-box-ordinal-group:1000;order:999}[layout*=column]:not([layout*=row])>*{-ms-flex-negative:0;flex-shrink:0;-ms-flex-preferred-size:auto;flex-basis:auto}@media screen and (max-width:64em){[layout*=lg-row]{-ms-flex-direction:row;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row}[layout*=lg-column]{-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column}[layout*=lg-columns],[layout*=lg-rows]{-ms-flex-wrap:wrap;flex-wrap:wrap}}@media screen and (max-width:52em){[layout*=md-row]{-ms-flex-direction:row;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row}[layout*=md-column]{-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column}[layout*=md-columns],[layout*=md-rows]{-ms-flex-wrap:wrap;flex-wrap:wrap}}@media screen and (max-width:40em){[layout*=sm-row]{-ms-flex-direction:row;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row}[layout*=sm-column]{-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column}[layout*=sm-columns],[layout*=sm-rows]{-ms-flex-wrap:wrap;flex-wrap:wrap}}@media screen and (max-width:64em){[self*=lg-full]{-ms-flex:1 1 100%!important;-webkit-box-flex:1!important;flex:1 1 100%!important;width:100%;max-width:100%}[self*=lg-half]{-ms-flex:1 1 50%!important;-webkit-box-flex:1!important;flex:1 1 50%!important;width:50%;max-width:50%}[self~=lg-first]{-ms-flex-order:-1;-webkit-box-ordinal-group:0;order:-1}[self~=lg-last]{-ms-flex-order:999;-webkit-box-ordinal-group:1000;order:999}[self~=lg-hide]{display:none}[self~=lg-show]{display:inherit}}@media screen and (max-width:52em){[self*=md-full]{-ms-flex:1 1 100%!important;-webkit-box-flex:1!important;flex:1 1 100%!important;width:100%;max-width:100%}[self*=md-half]{-ms-flex:1 1 50%!important;-webkit-box-flex:1!important;flex:1 1 50%!important;width:50%;max-width:50%}[self~=md-first]{-ms-flex-order:-1;-webkit-box-ordinal-group:0;order:-1}[self~=md-last]{-ms-flex-order:999;-webkit-box-ordinal-group:1000;order:999}[self~=md-hide]{display:none}[self~=md-show]{display:inherit}}@media screen and (max-width:40em){[self*=sm-full]{-ms-flex:1 1 100%!important;-webkit-box-flex:1!important;flex:1 1 100%!important;width:100%;max-width:100%}[self*=sm-half]{-ms-flex:1 1 50%!important;-webkit-box-flex:1!important;flex:1 1 50%!important;width:50%;max-width:50%}[self~=sm-first]{-ms-flex-order:-1;-webkit-box-ordinal-group:0;order:-1}[self~=sm-last]{-ms-flex-order:999;-webkit-box-ordinal-group:1000;order:999}[self~=sm-hide]{display:none}[self~=sm-show]{display:inherit}}body,html{width:100%;height:100%;margin:0;padding:0}html{font-size:16px;box-sizing:border-box}*,:after,:before{box-sizing:inherit}body{font-family:Open Sans,sans-serif;font-weight:400;line-height:1.5em;color:#32394f;background-color:#fff}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}h1,h2,h3,h4,h5,h6{margin-top:1em;margin-bottom:.25em;font-weight:600;line-height:1.5em;color:#32394f}h1{font-size:3.15733em}h2{font-size:2.36859em}h3{font-size:1.77689em}h4{font-size:1.333em}h5{font-size:1em}h6{font-size:.75019em}p{margin:.75em 0}small{font-size:.75019em}strong{font-weight:600}em{font-style:italic}a{color:#2196f3;text-decoration:none}a:hover{color:#0c7cd5}a.escape-link,a.escape-link:hover{color:inherit}label{font-size:.75019em}blockquote,label{display:block;font-weight:600}blockquote{margin:1em 0;padding:0 1em;color:#888;border-left:4px solid #2196f3}code{display:block;margin:1em 0;padding:1em;font-family:monospace;white-space:pre;background-color:#fafafa;border:1px solid #dde;border-radius:2px}.container{width:100%;max-width:1170px;margin:0 auto}.grid-row{margin-left:-.5rem;margin-right:-.5rem}.grid-row+.grid-row{margin-top:1rem}.grid-cell{padding-left:.5rem;padding-right:.5rem}.button{display:inline-block;margin:0;padding:.75rem 1.5rem;font-weight:600;line-height:inherit;text-align:center;color:#fff;background-color:#2196f3;border:none;border-radius:2px;outline:none;cursor:pointer}.button:active,.button:hover{color:#fff;background-color:#0c7cd5}.button-blue{background-color:#2196f3}.button-blue:active,.button-blue:hover{color:#fff;background-color:#0c7cd5}.button-green{background-color:#00b378}.button-green:active,.button-green:hover{color:#fff;background-color:#008056}.button-red{background-color:#f44336}.button-red:active,.button-red:hover{color:#fff;background-color:#ea1c0d}.button-#ff0{background-color:#ffc107}.button-#ff0:active,.button-#ff0:hover{color:#fff;background-color:#d39e00}.button-orange{background-color:#ff9800}.button-orange:active,.button-orange:hover{color:#fff;background-color:#cc7a00}.button-purple{background-color:#673ab7}.button-purple:active,.button-purple:hover{color:#fff;background-color:#512e90}input[type=date],input[type=datetime],input[type=password],input[type=search],input[type=text],input[type=time],select{display:inline-block;width:250px;height:3rem;padding:0 .75rem;background-color:#fff;border:1px solid #dde;border-radius:2px;outline:none}input[type=date]:active,input[type=date]:focus,input[type=datetime]:active,input[type=datetime]:focus,input[type=password]:active,input[type=password]:focus,input[type=search]:active,input[type=search]:focus,input[type=text]:active,input[type=text]:focus,input[type=time]:active,input[type=time]:focus,select:active,select:focus{border-color:#2196f3}.label{display:inline-block;min-width:1.9995em;padding:0 .5em;font-size:.75019em;font-weight:600;line-height:inherit;text-align:center;color:#32394f;background-color:#fafafc;border-radius:2px}.label-blue,.label-primary{color:#fff;background-color:#2196f3}.label-green{color:#fff;background-color:#00b378}.label-red{color:#fff;background-color:#f44336}.label-#ff0{color:#fff;background-color:#ffc107}.label-orange{color:#fff;background-color:#ff9800}.label-purple{color:#fff;background-color:#673ab7}.menu-group:not(:last-child){margin-bottom:1.5rem}.menu-group-title,.menu-item{padding-left:1rem;padding-right:1rem}.menu-group-title{display:block;font-weight:600;margin-bottom:.5rem}.menu-item{padding-top:.25rem;padding-bottom:.25rem}.menu-item:hover{background-color:#fafafc}.table-wrapper{display:block;width:100%;border:1px solid #dde;border-radius:2px}.table-wrapper table{border:none}table{table-layout:fixed;width:100%;background-color:#fff;border-collapse:collapse;border:1px solid #dde}.table-striped tr:nth-child(2n)>td{background-color:#fafafa}td,th{text-align:left}td:last-of-type,th:last-of-type{border-right:none}th{padding:.75rem 1rem;font-weight:600;background-color:#fafafc;border-right:1px solid #dde;border-bottom:1px solid #dde}td{padding:.5rem 1rem;border-right:1px solid #eee;border-bottom:1px solid #eee;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}tbody:last-of-type tr:last-of-type>td,tfoot:last-of-type tr:last-of-type>td{border-bottom:none}tfoot td{font-weight:600}tfoot tr:first-of-type td{border-top:1px solid #dde}.fade-enter-active,.fade-leave-active{transition:opacity .25s ease-out}.fade-enter,.fade-leave{opacity:0}.checkbox{display:inline-block;margin-right:1rem;vertical-align:baseline}.checkbox label{position:relative;display:inline-block;width:1.25rem;height:1.25rem;margin-right:.25rem;background-color:#fff;border:1px solid #dde;border-radius:2px;vertical-align:text-bottom;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox label:after{position:absolute;display:block;content:" ";width:.375rem;height:.75rem;top:.125rem;left:.4rem;border-right:.2rem solid #fff;border-bottom:.2rem solid #fff;opacity:0;-webkit-transform:rotate(45deg);transform:rotate(45deg);transition:opacity .15s ease-out}.checkbox input[type=checkbox]{display:none!important}.checkbox input[type=checkbox]:checked+label{background-color:#2196f3;border-color:#2196f3}.checkbox input[type=checkbox]:checked+label:after{opacity:1}.checkbox input[type=checkbox]:disabled+label{background-color:#fafafa;cursor:not-allowed}.chip{display:inline-block;min-width:1.9995em;text-align:left;color:#32394f;background-color:#fafafc;border-radius:2px}.chip-body,.chip-footer{padding:.25rem .5rem}.chip-footer{background-color:#f2f2f7}.chip-close-button{position:relative;width:1.25rem;height:1.25rem;border-radius:2px;cursor:pointer}.chip-close-button:hover{background-color:#dadae9}.chip-close-button:after,.chip-close-button:before{display:block;position:absolute;content:" ";top:50%;left:10%;width:80%;height:2px;margin-top:-1px;background-color:currentColor;-webkit-transform-origin:center center;transform-origin:center center}.chip-close-button:before{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.chip-close-button:after{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.datatable th{padding:0}.datatable-linenumber-cell,.datatable-linenumber-column{text-align:center}.datatable-aggregate-cell,.datatable-linenumber-cell{font-weight:600;background-color:#fafafc!important;border-right-color:#dde}.datatable-group-chip{margin-right:.5rem}.datatable-collection .datatable-collection .datatable-resultset{border-top:1px solid #dde}.datatable-group{padding:0;background-color:#fff}.datatable-group,.datatable-groups-header{border-bottom:1px solid #dde}.datatable-group-header{padding:.5rem 1rem;background-color:#fafafc}.datatable-grouping-over{box-shadow:0 0 0 2px #2196f3}.datatable-row-indent{display:inline-block;width:1.5rem;height:1em}.datatable-group-label{font-weight:600}.datatable-info-cell{text-align:center;font-weight:600}.datatable-aggregators .datatable-info-cell{border-bottom:1px solid #dde}.datatable-options{padding:.75rem 1rem;background-color:#fafafc;border-top:1px solid #dde}.datatable-editable .datatable-cell{position:relative;padding:0!important;overflow:visible}.datatable-editable .datatable-cell input,.datatable-editable .datatable-cell select{display:block;width:100%;height:auto;padding:.5rem 1rem;background-color:transparent;border:none;border-radius:0}.datatable-editable .datatable-cell input:active,.datatable-editable .datatable-cell input:focus,.datatable-editable .datatable-cell select:active,.datatable-editable .datatable-cell select:focus{box-shadow:0 0 0 2px #2196f3}.datatable-column{padding:.75rem 1rem;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.datatable-sort-arrow{width:0;height:0;border:.375rem solid transparent}.datatable-sort-arrow-asc{border-bottom-color:currentColor;-webkit-transform:translateY(-.1875rem);transform:translateY(-.1875rem)}.datatable-sort-arrow-desc{border-top-color:currentColor;-webkit-transform:translateY(.1875rem);transform:translateY(.1875rem)}.float{display:inline-block;position:relative}.float-panel{display:block;position:absolute;top:100%;left:0;min-width:250px;min-height:50px;margin-top:5px;background-color:#fff;border-radius:2px;box-shadow:0 2px 3px 0 rgba(0,0,0,.2);z-index:10;-webkit-transform-origin:top left;transform-origin:top left}.float-enter,.float-leave-active{opacity:0;-webkit-transform:scale(0);transform:scale(0)}.float-enter-active,.float-leave-active{transition:opacity .2s cubic-bezier(.4,0,.2,1),-webkit-transform .3s cubic-bezier(.4,0,.2,1);transition:opacity .2s cubic-bezier(.4,0,.2,1),transform .3s cubic-bezier(.4,0,.2,1);transition:opacity .2s cubic-bezier(.4,0,.2,1),transform .3s cubic-bezier(.4,0,.2,1),-webkit-transform .3s cubic-bezier(.4,0,.2,1)}.modal-transition-enter-active,.modal-transition-leave-active{transition:opacity .2s ease-out}.modal-transition-enter-active .modal,.modal-transition-leave-active .modal{transition:-webkit-transform .2s ease-out;transition:transform .2s ease-out;transition:transform .2s ease-out,-webkit-transform .2s ease-out}.modal-transition-enter,.modal-transition-leave{opacity:0}.modal-transition-enter .modal,.modal-transition-leave .modal{-webkit-transform:scale(.8);transform:scale(.8)}.modal-shade{position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.3);z-index:1000}.modal{width:640px;background-color:#fff;border-radius:2px;box-shadow:0 0 1px 2px rgba(0,0,0,.1);overflow:hidden}.modal-body,.modal-footer,.modal-header{padding:1rem}.modal-footer{background-color:#fafafc;border-top:1px solid #eee}.modal-title{font-weight:600}.paginator{border:1px solid #dde;border-radius:2px}.paginator-footer{padding:1rem;background-color:#fafafc;border-top:1px solid #dde}.paginator-button{display:inline-block;min-width:1.5em;padding:0 .5rem;font-weight:600;background-color:#dde;border-radius:2px;cursor:pointer}.paginator-button.active{color:#fff;background-color:#2196f3}.paginator-page-number{margin:0 .25rem}.panel{width:100%;min-height:150px;background-color:#fff;border:1px solid #dde;border-radius:2px}.panel-body,.panel-footer,.panel-header{padding:.75rem 1rem}.panel-footer,.panel-header{background-color:#fafafc}.panel-header{border-bottom:1px solid #dde}.panel-footer{border-top:1px solid #dde}.panel-title{font-weight:600}.radio{display:inline-block;margin-right:1rem;vertical-align:baseline}.radio label{position:relative;display:inline-block;width:1.25rem;height:1.25rem;background-color:#fff;border:1px solid #dde;border-radius:50%;vertical-align:text-bottom;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.radio label:after{position:absolute;display:block;content:" ";width:.5rem;height:.5rem;top:50%;left:50%;margin-top:-.25rem;margin-left:-.25rem;background-color:#fff;border-radius:50%}.radio input[type=radio]{display:none!important}.radio input[type=radio]:checked+label{background-color:#2196f3;border-color:#2196f3}.radio input[type=radio]:disabled+label{background-color:#fafafa;cursor:not-allowed}.tab-control{display:block;border:1px solid #dde;border-radius:2px}.tabs-list{background-color:#fafafa;border-bottom:1px solid #dde}.tab-item{position:relative;padding:.75rem 1rem;font-weight:600;border-right:1px solid #dde;cursor:pointer}.tab-item:after{position:absolute;display:none;content:" ";bottom:-1px;left:0;width:100%;height:1px}.tab-item.active,.tab-item:after{background-color:#fff}.tab-item.active:after{display:block}.tab-pane{min-height:2rem;padding:1rem}.toggle{display:inline-block;margin-right:1rem;vertical-align:baseline}.toggle label{position:relative;display:inline-block;width:2.15rem;height:1.25rem;background-color:#fff;border:1px solid #dde;border-radius:.625rem;vertical-align:text-bottom;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.toggle label:after{position:absolute;display:block;content:" ";width:.8rem;height:.8rem;top:50%;left:.25rem;background-color:#dde;border-radius:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);transition:-webkit-transform .15s ease-out;transition:transform .15s ease-out;transition:transform .15s ease-out,-webkit-transform .15s ease-out}.toggle input[type=checkbox]{display:none!important}.toggle input[type=checkbox]:checked+label{background-color:#2196f3;border-color:#2196f3}.toggle input[type=checkbox]:checked+label:after{background-color:#fff;-webkit-transform:translate(.75rem,-50%);transform:translate(.75rem,-50%)}.toggle input[type=checkbox]:disabled+label{background-color:#fafafa;cursor:not-allowed} /*# sourceMappingURL=app.style.css.map*/ ================================================ FILE: dist/components/components.bundle.js ================================================ var vuetiful=function(t){function e(r){if(n[r])return n[r].exports;var a=n[r]={i:r,l:!1,exports:{}};return t[r].call(a.exports,a,a.exports,e),a.l=!0,a.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="/dist/",e(e.s=64)}([function(t,e){t.exports=function(t,e,n,r){var a,o=t=t||{},u=typeof t.default;"object"!==u&&"function"!==u||(a=t,o=t.default);var i="function"==typeof o?o.options:o;if(e&&(i.render=e.render,i.staticRenderFns=e.staticRenderFns),n&&(i._scopeId=n),r){var s=Object.create(i.computed||null);Object.keys(r).forEach(function(t){var e=r[t];s[t]=function(){return e}}),i.computed=s}return{esModule:a,exports:o,options:i}}},function(t,e,n){function r(t,e){if(c(t))return new Date(t.getTime());if("string"!=typeof t)return new Date(t);var n=e||{},r=n.additionalDigits;r=null==r?p:Number(r);var l=a(t),f=o(l.date,r),v=f.year,g=f.restDateString,h=u(g,v);if(h){var m,y=h.getTime(),b=0;return l.time&&(b=i(l.time)),l.timezone?m=s(l.timezone):(m=new Date(y+b).getTimezoneOffset(),m=new Date(y+b+m*d).getTimezoneOffset()),new Date(y+b+m*d)}return new Date(t)}function a(t){var e,n={},r=t.split(v);if(g.test(r[0])?(n.date=null,e=r[0]):(n.date=r[0],e=r[1]),e){var a=P.exec(e);a?(n.time=e.replace(a[1],""),n.timezone=a[1]):n.time=e}return n}function o(t,e){var n,r=m[e],a=b[e];if(n=y.exec(t)||a.exec(t)){var o=n[1];return{year:parseInt(o,10),restDateString:t.slice(o.length)}}if(n=h.exec(t)||r.exec(t)){var u=n[1];return{year:100*parseInt(u,10),restDateString:t.slice(u.length)}}return{year:null}}function u(t,e){if(null===e)return null;var n,r,a,o;if(0===t.length)return r=new Date(0),r.setUTCFullYear(e),r;if(n=_.exec(t))return r=new Date(0),a=parseInt(n[1],10)-1,r.setUTCFullYear(e,a),r;if(n=x.exec(t)){r=new Date(0);var u=parseInt(n[1],10);return r.setUTCFullYear(e,0,u),r}if(n=M.exec(t)){r=new Date(0),a=parseInt(n[1],10)-1;var i=parseInt(n[2],10);return r.setUTCFullYear(e,a,i),r}if(n=D.exec(t))return o=parseInt(n[1],10)-1,l(e,o);if(n=w.exec(t)){o=parseInt(n[1],10)-1;return l(e,o,parseInt(n[2],10)-1)}return null}function i(t){var e,n,r;if(e=S.exec(t))return(n=parseFloat(e[1].replace(",",".")))%24*f;if(e=O.exec(t))return n=parseInt(e[1],10),r=parseFloat(e[2].replace(",",".")),n%24*f+r*d;if(e=C.exec(t)){n=parseInt(e[1],10),r=parseInt(e[2],10);var a=parseFloat(e[3].replace(",","."));return n%24*f+r*d+1e3*a}return null}function s(t){var e,n;return(e=j.exec(t))?0:(e=N.exec(t))?(n=60*parseInt(e[2],10),"+"===e[1]?-n:n):(e=T.exec(t),e?(n=60*parseInt(e[2],10)+parseInt(e[3],10),"+"===e[1]?-n:n):0)}function l(t,e,n){e=e||0,n=n||0;var r=new Date(0);r.setUTCFullYear(t,0,4);var a=r.getUTCDay()||7,o=7*e+n+1-a;return r.setUTCDate(r.getUTCDate()+o),r}var c=n(60),f=36e5,d=6e4,p=2,v=/[T ]/,g=/:/,h=/^(\d{2})$/,m=[/^([+-]\d{2})$/,/^([+-]\d{3})$/,/^([+-]\d{4})$/],y=/^(\d{4})/,b=[/^([+-]\d{4})/,/^([+-]\d{5})/,/^([+-]\d{6})/],_=/^-(\d{2})$/,x=/^-?(\d{3})$/,M=/^-?(\d{2})-?(\d{2})$/,D=/^-?W(\d{2})$/,w=/^-?W(\d{2})-?(\d{1})$/,S=/^(\d{2}([.,]\d*)?)$/,O=/^(\d{2}):?(\d{2}([.,]\d*)?)$/,C=/^(\d{2}):?(\d{2}):?(\d{2}([.,]\d*)?)$/,P=/([Z+-].*)$/,j=/^(Z)$/,N=/^([+-])(\d{2})$/,T=/^([+-])(\d{2}):?(\d{2})$/;t.exports=r},function(t,e,n){var r=n(29)("wks"),a=n(12),o=n(3).Symbol,u="function"==typeof o;(t.exports=function(t){return r[t]||(r[t]=u&&o[t]||(u?o:a)("Symbol."+t))}).store=r},function(t,e){var n=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},function(t,e,n){var r=n(8),a=n(17);t.exports=n(7)?function(t,e,n){return r.f(t,e,a(1,n))}:function(t,e,n){return t[e]=n,t}},function(t,e,n){var r=n(48),a=n(21);t.exports=function(t){return r(a(t))}},function(t,e,n){t.exports=!n(15)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,e,n){var r=n(14),a=n(47),o=n(31),u=Object.defineProperty;e.f=n(7)?Object.defineProperty:function(t,e,n){if(r(t),e=o(e,!0),r(n),a)try{return u(t,e,n)}catch(t){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(t[e]=n.value),t}},function(t,e){var n=t.exports={version:"2.4.0"};"number"==typeof __e&&(__e=n)},function(t,e){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,e,n){var r=n(3),a=n(5),o=n(4),u=n(12)("src"),i=Function.toString,s=(""+i).split("toString");n(9).inspectSource=function(t){return i.call(t)},(t.exports=function(t,e,n,i){var l="function"==typeof n;l&&(o(n,"name")||a(n,"name",e)),t[e]!==n&&(l&&(o(n,u)||a(n,u,t[e]?""+t[e]:s.join(String(e)))),t===r?t[e]=n:i?t[e]?t[e]=n:a(t,e,n):(delete t[e],a(t,e,n)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[u]||i.call(this)})},function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+r).toString(36))}},function(t,e,n){"use strict";function r(t,e,n){var r=n||0,a=!0,o=!1,u=void 0;try{for(var i,s=t[Symbol.iterator]();!(a=(i=s.next()).done);a=!0){var l=i.value,c=e.call(this,r,l,t);if(!1===c)return!1;r=c}}catch(t){o=!0,u=t}finally{try{!a&&s.return&&s.return()}finally{if(o)throw u}}return r}Object.defineProperty(e,"__esModule",{value:!0}),e.default=r},function(t,e,n){var r=n(10);t.exports=function(t){if(!r(t))throw TypeError(t+" is not an object!");return t}},function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e,n){var r=n(54),a=n(22);t.exports=Object.keys||function(t){return r(t,a)}},function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},function(t,e,n){"use strict";function r(t,e){return(0,a.format)(t,e)}Object.defineProperty(e,"__esModule",{value:!0}),e.default=r;var a=n(62)},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={model:{prop:"source",event:"change"},props:{id:{type:String,required:!0},source:{required:!0},value:{required:!1},disabled:{type:Boolean,default:!1}},data:function(){return{proxy:!1}},computed:{checked:{get:function(){return this.source},set:function(t){this.proxy=t,this.$emit("change",this.proxy)}}}}},function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)}},function(t,e){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on "+t);return t}},function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,e,n){var r=n(3),a=n(9),o=n(5),u=n(11),i=n(45),s=function(t,e,n){var l,c,f,d,p=t&s.F,v=t&s.G,g=t&s.S,h=t&s.P,m=t&s.B,y=v?r:g?r[e]||(r[e]={}):(r[e]||{}).prototype,b=v?a:a[e]||(a[e]={}),_=b.prototype||(b.prototype={});v&&(n=e);for(l in n)c=!p&&y&&void 0!==y[l],f=(c?y:n)[l],d=m&&c?i(f,r):h&&"function"==typeof f?i(Function.call,f):f,y&&u(y,l,f,t&s.U),b[l]!=f&&o(b,l,d),h&&_[l]!=f&&(_[l]=f)};r.core=a,s.F=1,s.G=2,s.S=4,s.P=8,s.B=16,s.W=32,s.U=64,s.R=128,t.exports=s},function(t,e){t.exports={}},function(t,e){t.exports=!1},function(t,e){e.f={}.propertyIsEnumerable},function(t,e,n){var r=n(8).f,a=n(4),o=n(2)("toStringTag");t.exports=function(t,e,n){t&&!a(t=n?t:t.prototype,o)&&r(t,o,{configurable:!0,value:e})}},function(t,e,n){var r=n(29)("keys"),a=n(12);t.exports=function(t){return r[t]||(r[t]=a(t))}},function(t,e,n){var r=n(3),a=r["__core-js_shared__"]||(r["__core-js_shared__"]={});t.exports=function(t){return a[t]||(a[t]={})}},function(t,e){var n=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:n)(t)}},function(t,e,n){var r=n(10);t.exports=function(t,e){if(!r(t))return t;var n,a;if(e&&"function"==typeof(n=t.toString)&&!r(a=n.call(t)))return a;if("function"==typeof(n=t.valueOf)&&!r(a=n.call(t)))return a;if(!e&&"function"==typeof(n=t.toString)&&!r(a=n.call(t)))return a;throw TypeError("Can't convert object to primitive value")}},function(t,e,n){var r=n(3),a=n(9),o=n(25),u=n(33),i=n(8).f;t.exports=function(t){var e=a.Symbol||(a.Symbol=o?{}:r.Symbol||{});"_"==t.charAt(0)||t in e||i(e,t,{value:u.f(t)})}},function(t,e,n){e.f=n(2)},function(t,e,n){function r(t){return a(t,{weekStartsOn:1})}var a=n(153);t.exports=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={AED:"د.إ",AFN:"؋",ALL:"L",ANG:"ƒ",AOA:"Kz",ARS:"$",AUD:"$",AWG:"ƒ",AZN:"₼",BAM:"KM",BBD:"$",BDT:"৳",BGN:"лв",BHD:".د.ب",BIF:"FBu",BMD:"$",BND:"$",BOB:"Bs.",BRL:"R$",BSD:"$",BTN:"Nu.",BWP:"P",BYR:"p.",BZD:"BZ$",CAD:"$",CDF:"FC",CHF:"Fr.",CLP:"$",CNY:"¥",COP:"$",CRC:"₡",CUC:"$",CUP:"₱",CVE:"$",CZK:"Kč",DJF:"Fdj",DKK:"kr",DOP:"RD$",DZD:"دج",EEK:"kr",EGP:"£",ERN:"Nfk",ETB:"Br",EUR:"€",FJD:"$",FKP:"£",GBP:"£",GEL:"₾",GGP:"£",GHC:"₵",GHS:"GH₵",GIP:"£",GMD:"D",GNF:"FG",GTQ:"Q",GYD:"$",HKD:"$",HNL:"L",HRK:"kn",HTG:"G",HUF:"Ft",IDR:"Rp",ILS:"₪",IMP:"£",INR:"₹",IQD:"ع.د",IRR:"﷼",ISK:"kr",JEP:"£",JMD:"J$",JPY:"¥",KES:"KSh",KGS:"лв",KHR:"៛",KMF:"CF",KPW:"₩",KRW:"₩",KYD:"$",KZT:"₸",LAK:"₭",LBP:"£",LKR:"₨",LRD:"$",LSL:"M",LTL:"Lt",LVL:"Ls",MAD:"MAD",MDL:"lei",MGA:"Ar",MKD:"ден",MMK:"K",MNT:"₮",MOP:"MOP$",MUR:"₨",MVR:"Rf",MWK:"MK",MXN:"$",MYR:"RM",MZN:"MT",NAD:"$",NGN:"₦",NIO:"C$",NOK:"kr",NPR:"₨",NZD:"$",OMR:"﷼",PAB:"B/.",PEN:"S/.",PGK:"K",PHP:"₱",PKR:"₨",PLN:"zł",PYG:"Gs",QAR:"﷼",RMB:"¥",RON:"lei",RSD:"Дин.",RUB:"₽",RWF:"R₣",SAR:"﷼",SBD:"$",SCR:"₨",SDG:"ج.س.",SEK:"kr",SGD:"$",SHP:"£",SLL:"Le",SOS:"S",SRD:"$",SSP:"£",STD:"Db",SVC:"$",SYP:"£",SZL:"E",THB:"฿",TJS:"SM",TMT:"T",TND:"د.ت",TOP:"T$",TRL:"₤",TRY:"₺",TTD:"TT$",TVD:"$",TWD:"NT$",TZS:"TSh",UAH:"₴",UGX:"USh",USD:"$",UYU:"$U",UZS:"лв",VEF:"Bs",VND:"₫",VUV:"VT",WST:"WS$",XAF:"FCFA",XBT:"Ƀ",XCD:"$",XOF:"CFA",XPF:"₣",YER:"﷼",ZAR:"R",ZWD:"Z$",BTC:"฿"}},function(t,e,n){"use strict";function r(t,e){var n=(0,o.default)(t,e);return!!n&&n/t.length}Object.defineProperty(e,"__esModule",{value:!0}),e.default=r;var a=n(42),o=function(t){return t&&t.__esModule?t:{default:t}}(a)},function(t,e,n){"use strict";function r(t){var e=!0,n=!1,r=void 0;try{for(var a,o=i[Symbol.iterator]();!(e=(a=o.next()).done);e=!0){var u=a.value;if(u.test(t))return u.toNumber}}catch(t){n=!0,r=t}finally{try{!e&&o.return&&o.return()}finally{if(n)throw r}}return function(t){return t}}Object.defineProperty(e,"__esModule",{value:!0}),e.default=r;var a=n(38),o=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e.default=t,e}(a),u=/^(\-|\+)?([0-9]+(\.[0-9]+)?|Infinity)$/,i=[{test:function(t){return!!o.isNumber(t)||u.test(t)},toNumber:parseFloat},{test:function(t){if(o.isDate(t))return!0;var e=new Date(t);return o.isDate(e)},toNumber:function(t){return new Date(t).getTime()}}]},function(t,e,n){"use strict";function r(t,e){return e===Object.prototype.toString.call(t)}function a(t){for(var e=!1,n=0,r=arguments.length,a=Array(r>1?r-1:0),o=1;o-1){n.push(u);break}}}}return n}Object.defineProperty(e,"__esModule",{value:!0});var a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};e.default=r;var o=["string","number","boolean"]},function(t,e,n){"use strict";function r(t,e){e=e||function(t){return t};var n={},r=!0,a=!1,o=void 0;try{for(var u,i=t[Symbol.iterator]();!(r=(u=i.next()).done);r=!0){var s=u.value,l=e.call(t,s);n.hasOwnProperty(l)||(n[l]=[]),n[l].push(s)}}catch(t){a=!0,o=t}finally{try{!r&&i.return&&i.return()}finally{if(a)throw o}}return n}Object.defineProperty(e,"__esModule",{value:!0}),e.default=r},function(t,e,n){"use strict";function r(t){if(Array.isArray(t)){for(var e=0,n=Array(t.length);eu?1:0)*n}),a}Object.defineProperty(e,"__esModule",{value:!0}),e.default=a},function(t,e,n){"use strict";function r(t,e){var n=parseFloat(e);return!isNaN(n)&&t+n}function a(t,e){return e=e||function(t){return t},(0,u.default)(t,function(t,n,a){var o=e.call(a,n);return r.call(a,t,o)})}Object.defineProperty(e,"__esModule",{value:!0}),e.default=a;var o=n(13),u=function(t){return t&&t.__esModule?t:{default:t}}(o)},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}function a(t,e){var n=t.length,r=(0,s.default)(t,e),a=(0,u.default)(t,function(t,n,a){var o=e.call(a,n);return t+Math.pow(o-r,2)});return!!a&&a/n}Object.defineProperty(e,"__esModule",{value:!0}),e.default=a;var o=n(13),u=r(o),i=n(36),s=r(i)},function(t,e,n){var r=n(2)("unscopables"),a=Array.prototype;void 0==a[r]&&n(5)(a,r,{}),t.exports=function(t){a[r][t]=!0}},function(t,e,n){var r=n(112);t.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,a){return t.call(e,n,r,a)}}return function(){return t.apply(e,arguments)}}},function(t,e,n){var r=n(10),a=n(3).document,o=r(a)&&r(a.createElement);t.exports=function(t){return o?a.createElement(t):{}}},function(t,e,n){t.exports=!n(7)&&!n(15)(function(){return 7!=Object.defineProperty(n(46)("div"),"a",{get:function(){return 7}}).a})},function(t,e,n){var r=n(20);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==r(t)?t.split(""):Object(t)}},function(t,e,n){var r=n(20);t.exports=Array.isArray||function(t){return"Array"==r(t)}},function(t,e,n){"use strict";var r=n(25),a=n(23),o=n(11),u=n(5),i=n(4),s=n(24),l=n(120),c=n(27),f=n(127),d=n(2)("iterator"),p=!([].keys&&"next"in[].keys()),v=function(){return this};t.exports=function(t,e,n,g,h,m,y){l(n,e,g);var b,_,x,M=function(t){if(!p&&t in O)return O[t];switch(t){case"keys":case"values":return function(){return new n(this,t)}}return function(){return new n(this,t)}},D=e+" Iterator",w="values"==h,S=!1,O=t.prototype,C=O[d]||O["@@iterator"]||h&&O[h],P=C||M(h),j=h?w?M("entries"):P:void 0,N="Array"==e?O.entries||C:C;if(N&&(x=f(N.call(new t)))!==Object.prototype&&(c(x,D,!0),r||i(x,d)||u(x,d,v)),w&&C&&"values"!==C.name&&(S=!0,P=function(){return C.call(this)}),r&&!y||!p&&!S&&O[d]||u(O,d,P),s[e]=P,s[D]=v,h)if(b={values:w?P:M("values"),keys:m?P:M("keys"),entries:j},y)for(_ in b)_ in O||o(O,_,b[_]);else a(a.P+a.F*(p||S),e,b);return b}},function(t,e,n){var r=n(14),a=n(124),o=n(22),u=n(28)("IE_PROTO"),i=function(){},s=function(){var t,e=n(46)("iframe"),r=o.length;for(e.style.display="none",n(119).appendChild(e),e.src="javascript:",t=e.contentWindow.document,t.open(),t.write(" ================================================ FILE: license.txt ================================================ MIT License Copyright (c) 2017 Andrew Courtice Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: package.json ================================================ { "name": "vuetiful", "description": "Component framework written on top of the Vue reactive library", "version": "0.1.0", "author": "Andrew Courtice", "repository": "https://github.com/andrewcourtice/vuetiful.git", "license": "MIT", "private": true, "scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server --open --inline --hot", "build:components": "cross-env NODE_ENV=production SCOPE=components webpack --progress --hide-modules", "build:app": "cross-env NODE_ENV=production SCOPE=app webpack --progress --hide-modules", "start": "npm run dev", "test": "mocha" }, "dependencies": { "core-js": "^2.4.1", "date-fns": "^1.28.2", "flex-layout-attribute": "^1.0.3", "vue": "^2.2.6", "vue-router": "^2.3.1" }, "devDependencies": { "babel-core": "^6.24.0", "babel-loader": "^6.4.1", "babel-polyfill": "^6.23.0", "babel-preset-es2015": "^6.24.0", "chai": "^3.5.0", "cross-env": "^3.0.0", "css-loader": "^0.27.3", "extract-text-webpack-plugin": "^2.0.0", "file-loader": "^0.10.1", "mocha": "^3.2.0", "node-sass": "^4.5.2", "postcss-loader": "^1.3.3", "sass-loader": "^6.0.3", "style-loader": "^0.16.1", "vue-loader": "^11.3.4", "vue-template-compiler": "^2.2.6", "webpack": "^2.3.2", "webpack-bundle-analyzer": "^2.2.0", "webpack-dev-server": "^2.4.2" } } ================================================ FILE: postcss.config.js ================================================ module.exports = { plugins: [ require("autoprefixer") ] }; ================================================ FILE: readme.md ================================================ # Vuetiful Vuetiful is a component framework written on top of the Vue reactive library. It is primarily designed for creating business/administration applications where the displaying of data is paramount. Although targeted primarily at business applications, Vuetiful is extremely flexible and easily themed making it trivial to integrate into any website. **Update:** *Due to time limitations this project has been discontinued. Please feel free to use or distribute any of the code in this repo as per the license below.* ## Getting started This project is still very much in the early days of development so it is not currently available on npm (but will be soon). To use the components simply download the `components.bundle.js` and `app.style.css` files from the `/dist` directory and link to them in your html. Alternatively, if you would like to see a demo of the current set of components and styles you can clone this repo and build it to see a demo application. After cloning the repo run: ```bash npm install npm start ``` A browser window will automatically open and load the demo app. ## License MIT License Copyright (c) 2017 Andrew Courtice Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: src/aggregators/aggregators.js ================================================ import min from "./min"; import max from "./max"; import count from "./count"; import average from "./average"; import total from "./total"; import variance from "./variance"; import standardDeviation from "./standard-deviation"; import median from "./median"; export default { min, max, count, average, total, variance, standardDeviation, median } ================================================ FILE: src/aggregators/average.js ================================================ import averageOf from "../utilities/average-of"; export default { label: "Average", callback: averageOf, format: true } ================================================ FILE: src/aggregators/count.js ================================================ export default { label: "Count", callback: (array) => array.length } ================================================ FILE: src/aggregators/max.js ================================================ import maxOf from "../utilities/max-of"; export default { label: "Maximum", callback: maxOf, format: true } ================================================ FILE: src/aggregators/median.js ================================================ import medianOf from "../utilities/median-of"; export default { label: "Median", callback: medianOf } ================================================ FILE: src/aggregators/min.js ================================================ import minOf from "../utilities/min-of"; export default { label: "Minimum", callback: minOf, format: true } ================================================ FILE: src/aggregators/standard-deviation.js ================================================ import standardDeviationOf from "../utilities/standard-deviation-of"; export default { label: "Standard Deviation", callback: standardDeviationOf } ================================================ FILE: src/aggregators/total.js ================================================ import totalOf from "../utilities/total-of"; export default { label: "Total", callback: totalOf, format: true } ================================================ FILE: src/aggregators/variance.js ================================================ import varianceOf from "../utilities/variance-of"; export default { label: "Variance", callback: varianceOf } ================================================ FILE: src/assets/styles/abstract/_functions.scss ================================================ @function pow($base, $exponent: 2) { $value: $base; @for $i from 1 through ($exponent - 1) { $value: ($value * $base); } @return $value; } ================================================ FILE: src/assets/styles/abstract/_mixins.scss ================================================ @mixin truncate-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } ================================================ FILE: src/assets/styles/abstract/_variables.scss ================================================ $base-point-size: 8px; $typography-ratio: 1.333; $font-size: 16px; $line-height: 1.5em; $colour-font: #32394F; $colour-font-negative: #FFFFFF; $colour-background: #FFFFFF; $colour-background-medium: #FAFAFC; $colour-border: #DDDDEE; $colour-border-light: #EEEEEE; $colour-primary: #2196F3; $colour-accents: ( blue: #2196F3, green: #00B378, red: #F44336, yellow: #FFC107, orange: #FF9800, purple: #673AB7 ); $colour-variation: 10%; $border-radius: 2px; ================================================ FILE: src/assets/styles/core/_animations.scss ================================================ .fade { &-enter-active, &-leave-active { transition: opacity 250ms ease-out; } &-enter, &-leave { opacity: 0; } } ================================================ FILE: src/assets/styles/core/_base.scss ================================================ html, body { width: 100%; height: 100%; margin: 0; padding: 0; } html { font-size: $font-size; box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } body { font-family: "Open Sans", sans-serif; font-weight: 400; line-height: $line-height; color: $colour-font; background-color: $colour-background; } button, input, textarea, select, optgroup { color: inherit; font: inherit; margin: 0; } ================================================ FILE: src/assets/styles/core/_buttons.scss ================================================ .button { display: inline-block; margin: 0; padding: 0.75rem 1.5rem; font-weight: 600; line-height: inherit; text-align: center; color: $colour-font-negative; background-color: $colour-primary; border: none; border-radius: $border-radius; outline: none; cursor: pointer; &:hover, &:active { color: $colour-font-negative; background-color: darken($colour-primary, $colour-variation); } } @each $name, $colour in $colour-accents { .button-#{ $name } { background-color: $colour; &:hover, &:active { color: $colour-font-negative; background-color: darken($colour, $colour-variation); } } } ================================================ FILE: src/assets/styles/core/_inputs.scss ================================================ input[type="text"], input[type="date"], input[type="time"], input[type="datetime"], input[type="password"], input[type="search"], select { display: inline-block; width: 250px; height: 3rem; padding: 0 0.75rem; background-color: $colour-background; border: 1px solid $colour-border; border-radius: 2px; outline: none; &:focus, &:active { border-color: $colour-primary; } } ================================================ FILE: src/assets/styles/core/_labels.scss ================================================ .label { display: inline-block; min-width: 1.5em * $typography-ratio; padding: 0 0.5em; font-size: 1em/$typography-ratio; font-weight: 600; line-height: inherit; text-align: center; color: $colour-font; background-color: $colour-background-medium; border-radius: $border-radius; } .label-primary { color: $colour-font-negative; background-color: $colour-primary; } @each $name, $colour in $colour-accents { .label-#{ $name } { color: $colour-font-negative; background-color: $colour; } } ================================================ FILE: src/assets/styles/core/_layout.scss ================================================ .container { width: 100%; max-width: 1170px; margin: 0 auto; } .grid-row { margin-left: -0.5rem; margin-right: -0.5rem; & + .grid-row { margin-top: 1rem; } } .grid-cell { padding-left: 0.5rem; padding-right: 0.5rem; } ================================================ FILE: src/assets/styles/core/_menus.scss ================================================ .menu { } .menu-group { &:not(:last-child) { margin-bottom: 1.5rem; } } .menu-group-title, .menu-item { padding-left: 1rem; padding-right: 1rem; } .menu-group-title { display: block; font-weight: 600; margin-bottom: 0.5rem; } .menu-item { padding-top: 0.25rem; padding-bottom: 0.25rem; &:hover { background-color: $colour-background-medium; } } ================================================ FILE: src/assets/styles/core/_tables.scss ================================================ $color-table-row-alt: #FAFAFA; .table-wrapper { display: block; width: 100%; border: 1px solid $colour-border; border-radius: 2px; & table { border: none; } } table { table-layout: fixed; width: 100%; background-color: $colour-background; border-collapse: collapse; border: 1px solid $colour-border; } .table-striped { & tr { &:nth-child(even) { & > td { background-color: $color-table-row-alt; } } } } th, td { text-align: left; &:last-of-type { border-right: none; } } th { padding: 0.75rem 1rem; font-weight: 600; background-color: $colour-background-medium; border-right: 1px solid $colour-border; border-bottom: 1px solid $colour-border; } td { padding: 0.5rem 1rem; border-right: 1px solid $colour-border-light; border-bottom: 1px solid $colour-border-light; @include truncate-text; } tbody, tfoot { &:last-of-type { & tr { &:last-of-type { & > td { border-bottom: none; } } } } } tfoot { & td { font-weight: 600; } & tr { &:first-of-type { & td { border-top: 1px solid $colour-border; } } } } ================================================ FILE: src/assets/styles/core/_typography.scss ================================================ h1, h2, h3, h4, h5, h6 { margin-top: 1em; margin-bottom: 0.25em; font-weight: 600; line-height: $line-height; color: $colour-font; } h1 { font-size: #{ pow($typography-ratio, 4) }em; } h2 { font-size: #{ pow($typography-ratio, 3) }em; } h3 { font-size: #{ pow($typography-ratio) }em; } h4 { font-size: $typography-ratio * 1em; } h5 { font-size: 1em; } h6 { font-size: 1em/$typography-ratio; } p { margin: 0.75em 0; } small { font-size: 1em/$typography-ratio; } strong { font-weight: 600; } em { font-style: italic; } a { color: $colour-primary; text-decoration: none; &:hover { color: darken($colour-primary, $colour-variation); } &.escape-link { color: inherit; &:hover { color: inherit; } } } label { display: block; font-weight: 600; font-size: 1em/$typography-ratio; } blockquote { display: block; margin: 1em 0; padding: 0 1em; color: #888; font-weight: 600; border-left: 4px solid #2196F3; } code { display: block; margin: 1em 0; padding: 1em; font-family: monospace; white-space: pre; background-color: #FAFAFA; border: 1px solid $colour-border; border-radius: $border-radius; } ================================================ FILE: src/assets/styles/site.scss ================================================ @import "./abstract/_variables.scss"; @import "./abstract/_functions.scss"; @import "./abstract/_mixins.scss"; @import "./core/_base.scss"; @import "./core/_typography.scss"; @import "./core/_layout.scss"; @import "./core/_buttons.scss"; @import "./core/_inputs.scss"; @import "./core/_labels.scss"; @import "./core/_menus.scss"; @import "./core/_tables.scss"; @import "./core/_animations.scss"; ================================================ FILE: src/components/calendar/calendar.vue ================================================ ================================================ FILE: src/components/chip/chip.vue ================================================ ================================================ FILE: src/components/components.js ================================================ import Calendar from "./calendar/calendar.vue"; import Checkbox from "./toggles/checkbox.vue"; import Chip from "./chip/chip.vue"; import DataTable from "./datatable/datatable.vue"; import DataTableColumn from "./datatable/datatable-column.vue"; import DateTimePicker from "./datetime-picker/datetime-picker.vue"; import Dynamic from "./dynamic/dynamic"; import FloatingPanel from "./floating-panel/floating-panel.vue"; import Modal from "./modal/modal.vue"; import Paginator from "./paginator/paginator.vue"; import Panel from "./panel/panel.vue"; import Radio from "./toggles/radio.vue"; import TabControl from "./tab-control/tab-control.vue"; import TabPane from "./tab-control/tab-pane.vue"; import Toggle from "./toggles/toggle.vue"; export default { calendar: Calendar, checkbox: Checkbox, chip: Chip, datatable: DataTable, datatableColumn: DataTableColumn, datetimePicker: DateTimePicker, dynamic: Dynamic, floatingPanel: FloatingPanel, modal: Modal, paginator: Paginator, panel: Panel, radio: Radio, toggle: Toggle, tabControl: TabControl, tabPane: TabPane } ================================================ FILE: src/components/datatable/datatable-cell.js ================================================ const defaultTemplate = "{{ column.formatData(row[column.id]) }}"; const editableTemplate = ''; const optimizedEditableTemplate = ''; function getChildComponent(editable, optimize) { let component = { template: defaultTemplate, props: ["row", "column"] }; if (editable) { component.template = optimize ? optimizedEditableTemplate : editableTemplate; } return component; } export default { functional: true, name: "datatable-cell", props: { row: Object, column: Object, editable: { type: Boolean, default: false }, optimize: { type: Boolean, default: false } }, render(createElement, context) { let row = context.props.row; let column = context.props.column; let cell = "td"; let cellProps = { class: { "datatable-cell": true }, style: column.columnStyles }; if (column.template) { let vNode = column.template({ row, column, value: row[column.id] }); return createElement(cell, cellProps, [vNode]); } let child = getChildComponent(context.props.editable, context.props.optimize); let childProps = { props: { row, column } }; return createElement(cell, cellProps, [ createElement(child, childProps) ]); } } ================================================ FILE: src/components/datatable/datatable-collection.vue ================================================ ================================================ FILE: src/components/datatable/datatable-column.vue ================================================ ================================================ FILE: src/components/datatable/datatable.vue ================================================ ================================================ FILE: src/components/datatable/readme.md ================================================ # Datatable The datatable component is essentially a "glorified table" designed to efficiently display dense data. Datatables aren't a heavily used component but they are fundamental to business applications - particularly ones displaying financial data or dashboards. A good datatable is hard to come by these days. Often they are too bulky, poorly designed, rigid, lack features or have too many dependencies on other frameworks. I have aimed to mitigate most of these problems with the Vuetiful datatable but I expect there will be features lacking that you need. If you would like a feature added to the datatable or if you find a bug, please open an issue on this repo. **Note:** The line numbering is a little bit quirky in multi-group mode. I'll hopefully have it fixed soon. To see a demo of this component in action check out this codepen example: [http://codepen.io/andrewcourtice/full/woQzpa](http://codepen.io/andrewcourtice/full/woQzpa). ![General](https://cloud.githubusercontent.com/assets/11718453/24491089/f2641d34-1568-11e7-8b84-dd4fd53ed0ff.png) - [Getting Started](#getting-started) - [Props](#props) - [Datatable](#datatable-1) - [Datatable Column](#datatable-column) - [Formatting Data](#formatting-data) - [Sorting Data](#sorting-data) - [Grouping Data](#grouping-data) - [Editing Data](#editing-data) - [Customizing the Datatable](#customizing-the-datatable) - [Header Templates](#header-templates) - [Cell Templates](#cell-templates) - [View Mode](#view-mode) - [Edit Mode](#edit-mode) - [Aggregation](#aggregation) - [Pagination](#pagination) - [Optimization](#optimization) ## Getting Started Using the datatable is trivial. Just drop a `datatable` element in your html and start defining some `datatable-columns`. Refer to the **props** section below to see what props you can use with this component. I'll also talk about customizing cell and header templates using slots later. Here's a basic example: ```html ``` You can also use Vue's list rendering features to define columns in your viewmodel. ```javascript new Vue({ el: "#app", data: function() { return { columns: [ { id: "datatable-column-1", label: "Column 1" }, { id: "datatable-column-2", label: "Column 2" }, { id: "datatable-column-3", label: "Column 3" } ] }; } }); ``` ```html ``` ## Props ### Datatable ```html ``` | Prop | Type | Required | Default | Description | | ---- | ---- | :------: | ------- | ----------- | | source | array[object] | no | [] | An array of objects to display in the table. a.k.a. - the data. This is often JSON fetched from your application and parsed | | striped | boolean | no | true | Whether the table should display alternate row coloring | | filterable | boolean | no | true | Whether the table should display a textbox in the footer for filtering the current dataset | | editable | boolean | no | false | Whether the table should be displayed in edit mode. See [Editing Data](#editing-data) | | line-numbers | boolean | no | true | Whether the table should display line numbers on the left of each row | | threshold | number | no | 50 | The maximum number of rows before the datatable enables performance optimizations. See [Optimization](#optimization). | **Note:** For specifying literal true values on props you can use a shorthand version. eg. instead of writing `editable="true"` you can just use the existence of the prop to define it's value eg. `editable`. ### Datatable Column ```html ``` | Prop | Type | Required | Default | Description | | ---- | ---- | :------: | ------- | ----------- | | id | string | yes | | The uniqie id of this column. The id must correspond to a property on the objects in your data source | | label | string | no | this.id | The label of this column to display in the header of the table. If none is specified, the id of this column will be used | | width | string or number | no | | The width of the column. If a number is supplied the component will assume a percentage (eg. 25 = 25%). If a string is supplied the component will interpret it literally (eg. 3px, 3.5rem, 4em etc.) | | sortable | boolean | no | true | Whether this row can be used for sorting | | groupable | boolean | no | true | Whether this row can be used for grouping | | aggregators | array | no | | A set of aggregate functions the column should use to process data in this column. See [Aggregators](#aggregators) for more information | | formatter | function | no | | A function used to format the data in this column before it is displayed. This is particularly useful for dates and numeric value. See [Formatting Data](#formatting-data) | ## Formatting Data More often than not you will likely run into a situation where you need to display data in a different format than it's raw form. This is common for dates and numbers. As seen above, each column has a `formatter` prop which allows you to specify a function that formats the data for that cell. There are several ways you could do this but using our basic example above, let's take a look at one way how you would implement a custom formatter: ```javascript new Vue({ el: "#app", data: function() { return { /* replace with real data */ data: [] }; }, methods: { formatCurrency: function(value) { var currency = parseFloat(value); if (isNaN(currency)) { return value; } return "$" + currency.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, "$1,"); } } }); ``` ```html ``` The format function signature expects one parameter which will be the value of the cell. Vuetiful comes with a few formatters already built in such as **currency**, **datetime**, **dateShort**, **dateLong**, **datetimeShort**, **datetimeLong**. **Note:** The **datetime** formatter requires a format string parameter (eg. "DD MMM YYYY hh:mm"). Because the datatable only sends one parameter through to the formatter function you would have to wrap the formatter in a function before being bound to a column. eg: ```javascript var formatters = vuteiful.formatters; new Vue({ el: "#app", data: function() { return { /* replace with real data */ data: [] }; }, methods: { /* We need to wrap the datetime formatter in a function that just expects the value parameter. This way we can tell the datetime formatter which format we'd like the data displayed in. */ customDatetimeFormatter: function(value) { return formatters.datetime(value, "DD MMMM YYYY h:mm a"); } } }); ``` ## Sorting Data The datatable allows you to easily sort your data. To sort by a particular column, click the column header at the top of the table. The data will be displayed in ascending order by default. Clicking the same column header again will reverse the sort order (descending). ## Grouping Data In addition to sorting, the datatable allows you to group your data by multiple columns. This probably best illustrated with an example. Let's say you have an online T-Shirt business and want to see specific profiles for purchases. Your purchases dataset may have the following columns: **Name**, **Email**, **Product Name**, **Quantity**, **Purchase Date**. Now that you have your data you may want to know which customers bought more than one of a particular T-Shirt on any given day. The process for this would be to group the data by **Purchase Date**, then group those groups by **Product Name**, then finally group the resulting group by **Quantity**. This can be repeated for as many columns as you have in any order. To group by a particular column, simply drag the column header from the top of the table into the middle of the table. Keep repeating this step for each subsequent column you would like to group by. The columns being grouped on will be displayed at the top of the table under the headers. To remove a column from the grouping, click the **x** button on the corresponding group at the top of the table. ![Grouping](https://cloud.githubusercontent.com/assets/11718453/24491151/52d4ef22-1569-11e7-9f62-9f2235467cf4.png) ## Editing Data A quick note on editing data. When `editable` is set to true on the datatable, the default bahaviour of the table is to convert all the cells into textboxes and remove data formatting. In other words the user will be able to edit the raw data. Typing into the textbox will mutate (change) the data in the array bound to the `source` prop. ## Customizing the Datatable The datatable is very flexible with customizing how your users interact with the component. One of the ways you can change the component to suit your requirements is templating. This component makes use of Vue's scoped templates to allow you to define your own markup (and custom components) for column headers and cells. ### Header Templates The default slot on the `datatable-column` component allows you to easily customize the content displayed in the column header. Let's look at an example where we want to have a column to allow users to select rows using a checkbox. ```html ``` **Note:** The `checkbox` component used in the example above is also part of the Vuetiful component framework. Refer to **components/toggles** for how to use `checkbox`, `radio` and `toggle` components. ### Cell Templates Here is where the flexibility of this component really comes in handy. Sometimes you may want to go beyond just using the default cell template and a custom formatter. Custom cell templates allow you to define specific types of markup or custom components to be used for a particular column instead of the default one built into the component. By default, each cell gets a different template for view and edit modes respectively. Here's what gets rendered into each cell by default: #### View Mode ```html {{ column.formatData(row[column.id]) }} ``` #### Edit Mode ```html ``` To override this all we have to do is define a template in our datatable and tell it to slot into the column we want. Here's an example extending on the custom header template example above: ```html ``` Notice how the `slot="select-all"` prop on the `template` matches the `id="select-all"` prop of the column we want to define a custom template for. Within your custom template a `cell` variable will be available for binding. Here's an outline of the properties available on the `cell` variable: | Property | Type | Description | | -------- | ---- | ----------- | | row | object | The current object that represents this row. This object will have all the properties on it as defined in your data source. This is handy for accessing values for other columns in the current row. | | column | datatable-column | This is the viewmodel of the current column that the repeater is using to get the value in the row. Calling `formatData(value)` on the column will call the formatter function defined in your `datatable-column`. | | value | any | This is just some sugar to simplify getting the value for the cell. It is equivalent to calling `row[column.id]` | At this point you may be wondering how to define a different template for when the datatable is in edit mode. The short answer is: you don't have to. Just use Vue's `v-if` and `v-else` conditional bindings to change the content of the cell. Using this approach means you are in complete control over what gets rendered for the cell. You could even change the components based on the type of value that is in the current cell. Let's see what conditional rendering based on the datatables `editable` prop would look like: ```html ``` ## Aggregation The datatable component supports aggregate functions out-of-the-box. The aggregate functions included are listed below. | Function | Supported Types | Formatted Output | | -------- | --------------- | ----------- | | min | number, date | yes | | max | number, date | yes | | count | n/a | no | | average (mean) | number | yes | | median | number | yes | | total | number | yes | | variance | number | no | | standard deviation | number | no | **Note:** *Formatted Output tells the aggregator to format the result for the current column before rendering it in the view.* If an aggregate function is applied to a column with an unsupported type in it, a null value will be displayed. To apply aggregation to a column(s) you simply bind an array of aggregators to each column you want to aggregate. Here's how you would use some of the built-in aggregate functions to calculate the **min**, **max** and **total**. ```javascript var aggregators = vuetiful.aggregators; new Vue({ el: "#app", data: function() { return { /* ... */ aggregators: [ aggregators.min, aggregators.max, aggregators.total ] }; } }); ``` Then in your view you would bind `:aggregators="aggregators"` like so on any column you would like to apply these aggregate functions to. Of course you can apply different combinations of aggregation to different columns. ![Aggregation](https://cloud.githubusercontent.com/assets/11718453/24491113/0b46ad26-1569-11e7-9eeb-706b3539f770.png) ## Pagination Pagination is particularly useful for when you need to display a large set of data but don't want to take up large amounts of screen real-estate displaying it all. It also means our datatable has to do less work by rendering smaller chunks of data at a time. You may have noticed that there is no option on the datatable for pagination. This is because pagination is not built into the datatable component. The `paginator` is actually a standalone component that allows you to paginate data and inject any child component into it's scope. Given that the `paginator` is a separate component I won't go into too much detail here on how to use it but I will show you a simple example of bundling it with the `datatable`. To see how the `paginator` component works in detail, check out the **components/paginator** folder. Here's a basic example of using the paginator with the `datatable`: ```html ``` All we've done here is bound the `paginator` to the root data source instead of the `datatable`. The `datatable` source is then bound to the data exposed by the current page. ## Optimization Because the datatable component is designed to display large amounts of complex data it wouldn't make sense to have everything running in real-time on large sets of data. Doing so would result in a noticable impact on performance. For this reason you can provide a `threshold` value on the datatable. The threshold tells the datatable to enable performace optimizations as soon as the number of rows in the dataset exceeds the threshold value. Once the threshold is exceeded a few things will happen: 1. Typing in the filter box at the bottom of the table will no longer filter the data in real-time. The filter won't be applied until the enter key is pressed or the textbox loses focus. 2. Typing in an editable cell will no longer edit data in real-time. Changes won;t be applied to the data until the enter key is pressed or the cell loses focus. ================================================ FILE: src/components/datetime-picker/datetime-picker.vue ================================================ ================================================ FILE: src/components/dynamic/dynamic.js ================================================ export default { functional: true, props: { component: { type: Object, required: true } }, render: (createElement, context) => { let component = context.props.component; if (!component.node) { console.warn("Dynamic element not rendered. No node name specified."); return; } let definition = { attrs: component.attrs, props: component.props, domProps: component.domProps, on: component.on }; if (!component.children) { return createElement(component.node, definition); } let children = component.children.map(child => { return createElement("dynamic", { props: { component: child } }); }); return createElement(component.node, definition, children); } } ================================================ FILE: src/components/floating-panel/floating-panel.vue ================================================ ================================================ FILE: src/components/modal/modal.vue ================================================ ================================================ FILE: src/components/paginator/paginator.vue ================================================ ================================================ FILE: src/components/paginator/readme.md ================================================ ================================================ FILE: src/components/panel/panel.vue ================================================ ================================================ FILE: src/components/tab-control/tab-control.vue ================================================ ================================================ FILE: src/components/tab-control/tab-pane.vue ================================================ ================================================ FILE: src/components/toggles/checkbox.vue ================================================ ================================================ FILE: src/components/toggles/radio.vue ================================================ ================================================ FILE: src/components/toggles/readme.md ================================================ # Toggles This docucumentation will cover 3 components which all share the *toggle* behaviour ================================================ FILE: src/components/toggles/toggle.vue ================================================ ================================================ FILE: src/directives/directives.js ================================================ import Drag from "./v-drag"; export default { drag: Drag } ================================================ FILE: src/directives/v-drag.js ================================================ function genericHandler(event) { } const eventHandlerMap = { start: { eventName: "dragstart", draggable: true, callback: genericHandler }, drag: { eventName: "drag", draggable: true, callback: genericHandler }, enter: { eventName: "dragenter", callback: genericHandler }, leave: { eventName: "dragleave", callback: genericHandler }, over: { eventName: "dragover", callback: genericHandler }, drop: { eventName: "drop", callback: genericHandler }, end: { eventName: "dragend", draggable: true, callback: genericHandler } }; export default { bind(element, binding, vNode) { let eventType = binding.arg.toLowerCase(); if (!(eventType in eventHandlerMap)) { console.warn("Event not supported"); return; } let handler = eventHandlerMap[eventType]; if (handler.draggable) { element.setAttribute("draggable", true); } let callback = binding.value; if (typeof (callback) !== "function") { callback = function (event) { }; } element.addEventListener(handler.eventName, event => { handler.callback.call(element, event); callback.call(element, event); return false; }, false); } } ================================================ FILE: src/formatters/currency.js ================================================ import currencies from "../maps/currencies"; export default function currency(value, precision, currency) { precision = precision || 2; let parsed = parseFloat(value); if (isNaN(parsed)) { return value; } let symbol = currency ? currencies[currency.toUpperCase()] : currencies.USD; return symbol + parsed.toFixed(precision).replace(/(\d)(?=(\d{3})+\.)/g, "$1,"); } ================================================ FILE: src/formatters/date-long.js ================================================ import datetime from "./datetime"; export default function dateShort(value) { return datetime(value, "D MMMM YYYY"); } ================================================ FILE: src/formatters/date-short.js ================================================ import datetime from "./datetime"; export default function dateShort(value) { return datetime(value, "DD/MM/YYYY"); } ================================================ FILE: src/formatters/datetime.js ================================================ import { format } from "date-fns"; export default function datetime(value, pattern) { return format(value, pattern); } ================================================ FILE: src/formatters/formatters.js ================================================ import currency from "./currency"; import datetime from "./datetime"; import dateShort from "./date-short"; import dateLong from "./date-long"; export default { currency, datetime, dateShort, dateLong } ================================================ FILE: src/main.js ================================================ import "./polyfills"; import "flex-layout-attribute"; import "./assets/styles/site.scss"; import components from "./components/components"; import directives from "./directives/directives"; // Exposed modules import aggregators from "../src/aggregators/aggregators"; import formatters from "../src/formatters/formatters"; import maps from "../src/maps/maps"; function registerComponents(Vue) { for (let component in components) { let definition = components[component]; Vue.component(component, definition); } } function registerDirectives(Vue) { for (let directive in directives) { let definition = directives[directive]; Vue.directive(directive, definition); } } export default { install(Vue) { registerComponents(Vue); registerDirectives(Vue); }, // Expose to global scope aggregators, formatters, maps } ================================================ FILE: src/maps/currencies.js ================================================ export default { AED: "د.إ", AFN: "؋", ALL: "L", ANG: "ƒ", AOA: "Kz", ARS: "$", AUD: "$", AWG: "ƒ", AZN: "₼", BAM: "KM", BBD: "$", BDT: "৳", BGN: "лв", BHD: ".د.ب", BIF: "FBu", BMD: "$", BND: "$", BOB: "Bs.", BRL: "R$", BSD: "$", BTN: "Nu.", BWP: "P", BYR: "p.", BZD: "BZ$", CAD: "$", CDF: "FC", CHF: "Fr.", CLP: "$", CNY: "¥", COP: "$", CRC: "₡", CUC: "$", CUP: "₱", CVE: "$", CZK: "Kč", DJF: "Fdj", DKK: "kr", DOP: "RD$", DZD: "دج", EEK: "kr", EGP: "£", ERN: "Nfk", ETB: "Br", EUR: "€", FJD: "$", FKP: "£", GBP: "£", GEL: "₾", GGP: "£", GHC: "₵", GHS: "GH₵", GIP: "£", GMD: "D", GNF: "FG", GTQ: "Q", GYD: "$", HKD: "$", HNL: "L", HRK: "kn", HTG: "G", HUF: "Ft", IDR: "Rp", ILS: "₪", IMP: "£", INR: "₹", IQD: "ع.د", IRR: "﷼", ISK: "kr", JEP: "£", JMD: "J$", JPY: "¥", KES: "KSh", KGS: "лв", KHR: "៛", KMF: "CF", KPW: "₩", KRW: "₩", KYD: "$", KZT: "₸", LAK: "₭", LBP: "£", LKR: "₨", LRD: "$", LSL: "M", LTL: "Lt", LVL: "Ls", MAD: "MAD", MDL: "lei", MGA: "Ar", MKD: "ден", MMK: "K", MNT: "₮", MOP: "MOP$", MUR: "₨", MVR: "Rf", MWK: "MK", MXN: "$", MYR: "RM", MZN: "MT", NAD: "$", NGN: "₦", NIO: "C$", NOK: "kr", NPR: "₨", NZD: "$", OMR: "﷼", PAB: "B/.", PEN: "S/.", PGK: "K", PHP: "₱", PKR: "₨", PLN: "zł", PYG: "Gs", QAR: "﷼", RMB: "¥", RON: "lei", RSD: "Дин.", RUB: "₽", RWF: "R₣", SAR: "﷼", SBD: "$", SCR: "₨", SDG: "ج.س.", SEK: "kr", SGD: "$", SHP: "£", SLL: "Le", SOS: "S", SRD: "$", SSP: "£", STD: "Db", SVC: "$", SYP: "£", SZL: "E", THB: "฿", TJS: "SM", TMT: "T", TND: "د.ت", TOP: "T$", TRL: "₤", TRY: "₺", TTD: "TT$", TVD: "$", TWD: "NT$", TZS: "TSh", UAH: "₴", UGX: "USh", USD: "$", UYU: "$U", UZS: "лв", VEF: "Bs", VND: "₫", VUV: "VT", WST: "WS$", XAF: "FCFA", XBT: "Ƀ", XCD: "$", XOF: "CFA", XPF: "₣", YER: "﷼", ZAR: "R", ZWD: "Z$", BTC: "฿" }; ================================================ FILE: src/maps/maps.js ================================================ import currencies from "./currencies"; export default { currencies } ================================================ FILE: src/mixins/checkable.js ================================================ export default { model: { prop: "source", event: "change" }, props: { id: { type: String, required: true }, source: { required: true }, value: { required: false }, disabled: { type: Boolean, default: false } }, data() { return { proxy: false }; }, computed: { checked: { get() { return this.source; }, set(value) { this.proxy = value; this.$emit("change", this.proxy); } } } } ================================================ FILE: src/polyfills.js ================================================ // Symbols import "core-js/fn/symbol"; import "core-js/fn/symbol/iterator"; // Arrays import "core-js/fn/array/find"; ================================================ FILE: src/services/calendar.js ================================================ import isValid from "date-fns/is_valid"; import isWithinRange from "date-fns/is_within_range"; import startOfMonth from "date-fns/start_of_month"; import endOfMonth from "date-fns/end_of_month"; import eachDay from "date-fns/each_day"; import addMonths from "date-fns/add_months"; import subMonths from "date-fns/sub_months"; import setMonth from "date-fns/set_month"; import pageBy from "../utilities/page-by"; import firstOf from "../utilities/first-of.js"; import lastOf from "../utilities/last-of.js"; const EPOCH_MIN = new Date(-8640000000000000); const EPOCH_MAX = new Date(8640000000000000); const weekdays = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]; function cleanDate(date) { return isValid(date) ? date : new Date(); } export default class CalendarMonth { get weekdays() { return weekdays; } get minDate() { return this._minDate; } set minDate(date) { this._minDate = cleanDate(date); } get maxDate() { return this._maxDate; } set maxDate(date) { this._maxDate = cleanDate(date); } get startDate() { return this._startDate; } set startDate(date) { this._startDate = cleanDate(date); if (!isWithinRange(this._startDate, this._minDate, this._maxDate)) { this._startDate = this.minDate; } } get paddingStart() { let firstWeek = firstOf(this.weeks); let firstDay = firstOf(firstWeek); return firstDay.getDay(); } get paddingEnd() { let weeks = this.weeks; let lastWeek = lastOf(weeks); let lastDay = lastOf(lastWeek); return 6 - lastDay.getDay(); } generate() { let monthStart = startOfMonth(this.startDate); let monthEnd = endOfMonth(this.startDate); let days = eachDay(monthStart, monthEnd); this.weeks = pageBy(days, day => { let weekPosition = day.getDay() + 1; let monthPosition = day.getDate(); let position = (13 - weekPosition + monthPosition) / 7; return Math.floor(position); }); return this.weeks; } previousMonth() { this.startDate = subMonths(this.startDate, 1); return this.generate(); } nextMonth() { this.startDate = addMonths(this.startDate, 1); return this.generate(); } goToMonth(month) { this.startDate = setMonth(this.startDate, month); return this.generate(); } constructor(startDate, minDate, maxDate) { this.minDate = minDate || EPOCH_MIN; this.maxDate = maxDate || EPOCH_MAX; this.startDate = startDate || new Date(); this.generate(); } } ================================================ FILE: src/services/event-emitter.js ================================================ import Vue from "vue"; // Here we can just wrap Vue's built-in pub-sub system // Saves me from writing my own or importing another dependency :) export class EventEmitter { constructor() { this.emitter = new Vue(); } emit(event, ...args) { this.emitter.$emit.apply(this.emitter, event, args); } on(event, callback) { this.emitter.$on(event, callback); } off(event, callback) { this.emitter.$off(event, callback); } } export default new EventEmitter(); ================================================ FILE: src/services/http-client.js ================================================ class HttpRequest { constructor(url, method) { this.url = url; this.method = method; this.type = "text/json"; this.headers = []; } execute() { return new Promise((resolve, reject) => { let request = new XMLHttpRequest(); request.onload = (event) => { resolve(); }; }); } } export class HttpClient { constructor() { } } ================================================ FILE: src/utilities/average-of.js ================================================ import totalOf from "./total-of"; export default function averageOf(array, callback) { let value = totalOf(array, callback); return value ? value / array.length : false; } ================================================ FILE: src/utilities/base/aggregator.js ================================================ export default function aggregate(array, callback, startValue) { let accumulator = startValue || 0; for (let item of array) { let result = callback.call(this, accumulator, item, array); if (result === false) { return false; } accumulator = result; } return accumulator; } ================================================ FILE: src/utilities/base/type-converter.js ================================================ import * as typeValidator from "./type-validator"; const floatTest = /^(\-|\+)?([0-9]+(\.[0-9]+)?|Infinity)$/; const evaluators = [ { test: value => { return typeValidator.isNumber(value) ? true : floatTest.test(value); }, toNumber: parseFloat }, { test: value => { if (typeValidator.isDate(value)) { return true; } let date = new Date(value); return typeValidator.isDate(date); }, toNumber: value => (new Date(value)).getTime() } ] export default function toNumber(value) { for (let evaluator of evaluators) { let result = evaluator.test(value); if (result) { return evaluator.toNumber; } } return (value => value); } ================================================ FILE: src/utilities/base/type-validator.js ================================================ const types = { array: "[object Array]", boolean: "[object Boolean]", date: "[object Date]", null: "[object Null]", number: "[object Number]", object: "[object Object]", string: "[object String]", undefined: "[object Undefined]" }; function isType(value, type) { return type === Object.prototype.toString.call(value); } export function isAny(value, ...validators) { let any = false; let index = 0; while (!any && index < validators.length) { any = validators[index].call(this, value); index++ } return any; } export function isArray(value) { return isType(value, types.array); } export function isBoolean(value) { return isType(value, types.boolean); } export function isDate(value) { return isType(value, types.date) && !isNaN(value.getTime()); } export function isNull(value) { return isType(value, types.null); } export function isNumber(value) { return isType(value, types.number); } export function isObject(value) { return isType(value, types.object); } export function isString(value) { return isType(value, types.string); } export function isUndefined(value) { return isType(value, types.undefined); } export function isPrimitive(value) { return !isAny(value, isArray, isDate, isObject); } export function isCollection(value) { return isAny(value, isArray, isObject); } ================================================ FILE: src/utilities/filter-by.js ================================================ // Need to add support for searching dates const SEARCHABLE_TYPES = ["string", "number", "boolean"]; /** * Filter an array of objects by the given phrase * * @export * @param {Array} array * @param {String} filter * @returns Array */ export default function filterBy(array, filter) { if (!filter) { return array; } let filtered = []; for (let i = 0; i < array.length; i++) { let item = array[i]; for (let prop in item) { let value = item[prop]; // Ensure the value is of a searchable type // This will automatically handle null values if (SEARCHABLE_TYPES.indexOf(typeof (value)) < 0) { continue; } // Normalise the value to get a consistent match let normalised = value.toString().toLowerCase(); if (normalised.indexOf(filter.toLowerCase()) > -1) { filtered.push(item); break; } } } return filtered; } ================================================ FILE: src/utilities/first-of.js ================================================ export default function firstOf(array) { if (!array.length || array.length < 1) { return; } return array[0]; } ================================================ FILE: src/utilities/group-by.js ================================================ /** * Group and array of objects by a given key * * @export * @param {Array} array The array to group * @param {String} key The key to group the array by * @returns Object */ export default function groupBy(array, callback) { callback = callback || (item => item); let groups = {}; for (let item of array) { let value = callback.call(array, item); if (!groups.hasOwnProperty(value)) { groups[value] = []; } groups[value].push(item); } return groups; } ================================================ FILE: src/utilities/last-of.js ================================================ export default function lastOf(array) { if (!array.length || array.length < 1) { return; } return array[array.length - 1]; } ================================================ FILE: src/utilities/map-values.js ================================================ export default function mapValues(object, callback) { let mapped = {}; for (let prop in object) { mapped[prop] = callback(object[prop]); } return mapped; } ================================================ FILE: src/utilities/max-of.js ================================================ import aggregate from "./base/aggregator"; import toNumber from "./base/type-converter"; function max(accumulator, value, converter) { let converted = converter(value); return isNaN(converted) ? false : Math.max(accumulator, converted); } export default function minOf(array, callback) { callback = callback || (item => item); // Let's assume (in a perfect world) that the data type of the first item // is the same throughout the whole array and use the same converter let firstValue = callback.call(array, array[0]); let converter = toNumber(firstValue); return aggregate(array, (accumulator, item, array) => { let value = callback.call(array, item); return max(accumulator, value, converter); }, -Infinity); } ================================================ FILE: src/utilities/median-of.js ================================================ import sortBy from "./sort-by"; function getValue(item, callback) { return callback.call(this, item); } export default function medianOf(array, callback) { let sorted = sortBy(array, callback); let count = sorted.length; let half = Math.floor(count / 2); let primaryMedian = getValue(sorted[half], callback); if (count % 2) { return primaryMedian; } let secondaryMedian = getValue(sorted[half - 1], callback); return (primaryMedian + secondaryMedian) / 2; } ================================================ FILE: src/utilities/min-of.js ================================================ import aggregate from "./base/aggregator"; import toNumber from "./base/type-converter"; function min(accumulator, value, converter) { let converted = converter(value); return isNaN(converted) ? false : Math.min(accumulator, converted); } export default function minOf(array, callback) { callback = callback || (item => item); // Let's assume (in a perfect world) that the data type of the first item // is the same throughout the whole array and use the same converter let firstValue = callback.call(array, array[0]); let converter = toNumber(firstValue); return aggregate(array, (accumulator, item, array) => { let value = callback.call(array, item); return min(accumulator, value, converter); }, Infinity); } ================================================ FILE: src/utilities/mode-of.js ================================================ ================================================ FILE: src/utilities/nest.js ================================================ import mapValues from "./map-values"; export default function nest(array, keys) { if (!keys.length) { return array; } let key = keys.shift(); let group = groupBy(array, key); return mapValues(group, (value, prop) => { return nest(value, [...keys]); }); } ================================================ FILE: src/utilities/page-by.js ================================================ import groupBy from "./group-by"; export default function pageBy(array, callback) { let groups = groupBy(array, callback); let pages = []; for (let group in groups) { pages.push(groups[group]); } return pages; } ================================================ FILE: src/utilities/page.js ================================================ export default function page(array, pageSize) { let pages = []; let start = 0; let length = array.length; while (start < length) { let end = start + pageSize; if (length - start < pageSize) { end = start + (length - start); } let page = array.slice(start, end); pages.push(page); start += pageSize; } return pages; } ================================================ FILE: src/utilities/sort-by.js ================================================ // Consider making this immutable. I'm undecided at the moment. /** * Sort an array of objects by the given key and direction * * @export * @param {Array} array The array to be sorted * @param {Function} callback The callback function to invoke to receive the key to sort on * @param {Number} direction The direction of the sort */ export default function sortBy(array, callback, direction) { direction = direction || 1; callback = callback || (item => item); if (Math.abs(direction) !== 1) { throw new Error("Sort direction must be either 1 (ascending) or -1 (descending)"); } let sortArray = [...array]; sortArray.sort((a, b) => { let valueA = callback.call(array, a); let valueB = callback.call(array, b); let outcome = valueA < valueB ? -1 : valueA > valueB ? 1 : 0; return outcome * direction; }); return sortArray; } ================================================ FILE: src/utilities/standard-deviation-of.js ================================================ import varianceOf from "./variance-of"; export default function standardDeviationOf(array, callback) { let variance = varianceOf(array, callback); return variance ? Math.sqrt(variance) : false; } ================================================ FILE: src/utilities/total-of.js ================================================ import aggregate from "./base/aggregator"; function total(accumulator, value) { let num = parseFloat(value); return isNaN(num) ? false : accumulator + num; } export default function totalOf(array, callback) { callback = callback || (item => item); return aggregate(array, (accumulator, item, array) => { let value = callback.call(array, item); return total.call(array, accumulator, value); }); } ================================================ FILE: src/utilities/variance-of.js ================================================ import aggregate from "./base/aggregator"; import averageOf from "./average-of"; export default function varianceOf(array, callback) { let count = array.length; let average = averageOf(array, callback); let totalVariance = aggregate(array, (accumulator, item, array) => { let value = callback.call(array, item); return accumulator + Math.pow(value - average, 2); }); return totalVariance ? totalVariance / count : false; } ================================================ FILE: src/views/components/buttons.vue ================================================ ================================================ FILE: src/views/components/components.js ================================================ import Buttons from "./buttons.vue"; import Datatables from "./datatables.vue"; import Modals from "./modals.vue"; import Paginators from "./paginators.vue"; import Panels from "./panels.vue"; import TabControls from "./tab-controls.vue"; import Toggles from "./toggles.vue"; import Typography from "./typography.vue"; export default { buttons: { component: Buttons }, datatables: { component: Datatables }, modals: { component: Modals }, paginators: { component: Paginators }, panels: { component: Panels }, tabControls: { component: TabControls }, toggles: { component: Toggles }, typography: { component: Typography } } ================================================ FILE: src/views/components/datatables.vue ================================================ ================================================ FILE: src/views/components/modals.vue ================================================ ================================================ FILE: src/views/components/paginators.vue ================================================ ================================================ FILE: src/views/components/panels.vue ================================================ ================================================ FILE: src/views/components/tab-controls.vue ================================================ ================================================ FILE: src/views/components/toggles.vue ================================================ ================================================ FILE: src/views/components/typography.vue ================================================ ================================================ FILE: src/views/views.js ================================================ import Components from "./components/components"; export default { components: Components } ================================================ FILE: test/mocha.opts ================================================ --compilers js:babel-register --require babel-polyfill --recursive ================================================ FILE: test/test-data.js ================================================ export const arrays = { simple: { numeric: { data: [5, 6, 5, 3, 9, 4, 7, 1, 4, 9, 0, 3, 5], expected: { first: 5, last: 5, min: 0, max: 9, average: 0, total: 0 } }, mixed: { data: [5, 6, "john", 3, 9, 4, new Date(), 1, 4, 9, false, 3, 5] } }, complex: { data: [{ id: "0584e8d2-984c-4ce0-a20f-8b9e21cd2c00", purchasorName: "Nancy Fuller", purchasorEmail: "nfuller0@about.me", purchaseDate: "2002-01-02T04:45:48Z", purchaseAmount: 1166.14, quantity: 15 }, { id: "f4769183-38af-4c22-8383-6dea302466fd", purchasorName: "Melissa Meyer", purchasorEmail: "mmeyer1@angelfire.com", purchaseDate: "2010-05-15T08:13:59Z", purchaseAmount: 6123.50, quantity: 15 }, { id: "e171c9fb-2438-4f23-8d0d-011b2d8e95bc", purchasorName: "Larry Rose", purchasorEmail: "lrose2@cdbaby.com", purchaseDate: "2014-11-23T09:18:18Z", purchaseAmount: 8288.27, quantity: 15 }, { id: "3cad078d-083b-416e-9dd4-2f1470c3458d", purchasorName: "Jack Simpson", purchasorEmail: "jsimpson3@mayoclinic.com", purchaseDate: "2002-01-02T04:45:48Z", purchaseAmount: 1215.03, quantity: 15 }, { id: "ef7ff12c-90e5-4bfb-8fdd-9f20e4206afa", purchasorName: "Ernest Watson", purchasorEmail: "ewatson4@nytimes.com", purchaseDate: "2002-01-02T04:45:48Z", purchaseAmount: 9455.16, quantity: 15 }, { id: "b243be08-6b9c-4ebd-bb8b-b59256ad4956", purchasorName: "Adam Castillo", purchasorEmail: "acastillo5@dailymotion.com", purchaseDate: "2014-08-22T08:14:28Z", purchaseAmount: 9988.45, quantity: 15 }, { id: "a491adf5-8129-4f93-9442-98522fbd1e90", purchasorName: "Wayne Wilson", purchasorEmail: "wwilson6@indiegogo.com", purchaseDate: "2012-03-07T22:16:08Z", purchaseAmount: 4563.87, quantity: 15 }, { id: "497a6cca-5c9c-4b93-af8e-63c93de3eacf", purchasorName: "Roy Coleman", purchasorEmail: "rcoleman7@independent.co.uk", purchaseDate: "2010-09-14T05:05:17Z", purchaseAmount: 4563.87, quantity: 15 }, { id: "ea34a698-fb86-44a5-b80e-57087d48737c", purchasorName: "Betty Diaz", purchasorEmail: "bdiaz8@dropbox.com", purchaseDate: "2012-03-07T22:16:08Z", purchaseAmount: 7527.62, quantity: 15 }, { id: "c48e5e68-cae5-4a2e-96b2-8509fca19ddb", purchasorName: "Sharon Gardner", purchasorEmail: "sgardner9@seesaa.net", purchaseDate: "2004-10-14T14:59:00Z", purchaseAmount: 1166.14, quantity: 15 } ], expected: { purchaseAmount: { first: 1166.14, last: 1166.14, min: 1166.14, max: 9988.45 } } } }; ================================================ FILE: test/utilities/average.js ================================================ import { assert } from "chai"; import averageOf from "../../src/utilities/average-of"; import totalOf from "../../src/utilities/total-of"; import { arrays } from "../test-data"; describe("Average", () => { it("should find the average of an array of simple numeric structures", () => { const testData = arrays.simple.numeric; let average = averageOf(testData.data); let total = totalOf(testData.data); assert.isNumber(average); assert.equal(average, total / testData.data.length); }); it("should find the average of an array of complex numeric structures", () => { const testData = arrays.complex; const callback = item => item.purchaseAmount; let average = averageOf(testData.data, callback); let total = totalOf(testData.data, callback); assert.isNumber(average); assert.equal(average, total / testData.data.length); }); }); ================================================ FILE: test/utilities/group-by.js ================================================ import { assert } from "chai"; import groupBy from "../../src/utilities/group-by"; import { arrays } from "../test-data"; describe("Group By", () => { it("should group a simple array", () => { const testData = arrays.simple.numeric; let grouped = groupBy(testData.data); assert.isObject(grouped); assert.lengthOf(grouped["3"], 2); assert.lengthOf(grouped["4"], 2); assert.lengthOf(grouped["9"], 2); assert.lengthOf(grouped["5"], 3); }); it("should group a complex array", () => { const testData = arrays.complex; let grouped = groupBy(testData.data, item => item.purchaseAmount); assert.isObject(grouped); assert.lengthOf(grouped["1166.14"], 2); assert.lengthOf(grouped["4563.87"], 2); }); }); ================================================ FILE: test/utilities/max-of.js ================================================ import { assert } from "chai"; import maxOf from "../../src/utilities/max-of"; import { arrays } from "../test-data"; describe("Max Of", () => { it("should calculate the maximum value of a simple array with numeric values", () => { const testData = arrays.simple.numeric; let max = maxOf(testData.data); assert.isNumber(max); assert.equal(max, testData.expected.max); }); it("should calculate the maximum value of a complex array with numeric values", () => { const testData = arrays.complex; let max = maxOf(testData.data, item => item.purchaseAmount); assert.isNumber(max); assert.equal(max, testData.expected.purchaseAmount.max); }); it("should return false if the array contains a non-numeric value", () => { const testData = arrays.simple.mixed; let max = maxOf(testData.data); assert.isNotNumber(max); assert.isFalse(max); }); }); ================================================ FILE: test/utilities/median-of.js ================================================ ================================================ FILE: test/utilities/min-of.js ================================================ import { assert } from "chai"; import minOf from "../../src/utilities/min-of"; import { arrays } from "../test-data"; describe("Min Of", () => { it("should calculate the minimum value of a simple array with numeric values", () => { const testData = arrays.simple.numeric; let min = minOf(testData.data); assert.isNumber(min); assert.equal(min, testData.expected.min); }); it("should calculate the minimum value of a complex array with numeric values", () => { const testData = arrays.complex; let min = minOf(testData.data, item => item.purchaseAmount); assert.isNumber(min); assert.equal(min, testData.expected.purchaseAmount.min); }); it("should return false if the array contains a non-numeric value", () => { const testData = arrays.simple.mixed; let min = minOf(testData.data); assert.isNotNumber(min); assert.isFalse(min); }); }); ================================================ FILE: test/utilities/page.js ================================================ import { assert } from "chai"; import page from "../../src/utilities/page"; const array = [5, 6, 5, 3, 9, 4, 7, 1, 4, 9, 0, 3, 5]; describe("Page", () => { it("should page an array by a chunk size", () => { let paged = page(array, 3); assert.isArray(paged); assert.lengthOf(paged[0], 3); assert.lengthOf(paged[paged.length - 1], 1); }); }); ================================================ FILE: test/utilities/sort-by.js ================================================ import { assert } from "chai"; import sortBy from "../../src/utilities/sort-by"; const simpleArray = [5, 6, 1, 9, 4]; const complexArray = [ { name: "Robert", age: 21 }, { name: "Cindy", age: 18 }, { name: "Samantha", age: 35 }, { name: "Phillip", age: 52 }, { name: "Scott", age: 23 } ]; const comparitiveOperators = { lt: (a, b) => a < b, gt: (a, b) => a > b }; function verifySortedArray(sortedArray, comparitiveOperator, key) { return sortedArray.every((item, index, array) => { let valueA = array[index]; let valueB = array[index + 1] || valueA; if (key) { valueA = valueA[key]; valueB = valueB[key]; } return index < array.length - 1 ? comparitiveOperator(valueA, valueB) : valueA; }); } describe("Sort By", () => { it("should sort a simple array in ascending order", () => { let sortedArray = sortBy(simpleArray); let isSorted = verifySortedArray(sortedArray, comparitiveOperators.lt); assert.isTrue(isSorted); }); it("should sort a simple array in descending order", () => { let sortedArray = sortBy(simpleArray, null, -1); let isSorted = verifySortedArray(sortedArray, comparitiveOperators.gt); assert.isTrue(isSorted); }); it("should sort a complex array in ascending order", () => { const key = "age"; let sortedArray = sortBy(complexArray, item => item.age); let isSorted = verifySortedArray(sortedArray, comparitiveOperators.lt, key); assert.isTrue(isSorted); }); it("should sort a complex array in descending order", () => { const key = "age"; let sortedArray = sortBy(complexArray, item => item.age, -1); let isSorted = verifySortedArray(sortedArray, comparitiveOperators.gt, key); assert.isTrue(isSorted); }); }); ================================================ FILE: test/utilities/total-of.js ================================================ import { assert } from "chai"; import totalOf from "../../src/utilities/total-of"; const simpleArray = [5, 6, 1, 9, 4]; const complexArray = [ { name: "Robert", age: 21 }, { name: "Cindy", age: 18 }, { name: "Samantha", age: 35 }, { name: "Phillip", age: 52 }, { name: "Scott", age: 23 } ]; describe("Total", () => { it("should calculate the total of a simple array with numeric values", () => { const array = [5, 10, 7, 4]; let total = totalOf(array); assert.isNumber(total); assert.equal(total, 26); }); it("should return false if the array contains a non-numeric value", () => { const array = [5, "hello", 7, 4]; let total = totalOf(array); assert.isNotNumber(total); assert.isFalse(total); }); }); ================================================ FILE: test/utilities/validators.js ================================================ import { assert } from "chai"; import * as typeValidator from "../../src/utilities/base/type-validator"; describe("Type Validator", () => { it("should validate the type of an array", () => { let result = typeValidator.isArray([]); assert.isTrue(result); }); it("should validate the type of a boolean", () => { let result = typeValidator.isBoolean(true); assert.isTrue(result); }); it("should validate the type of a date", () => { let result = typeValidator.isDate(new Date()); assert.isTrue(result); }); it("should validate the type of null", () => { let result = typeValidator.isNull(null); assert.isTrue(result); }); it("should validate the type of a number", () => { let result = typeValidator.isNumber(5); assert.isTrue(result); }); it("should validate the type of an object", () => { let result = typeValidator.isObject({}); assert.isTrue(result); }); it("should validate primitive types", () => { let arrayResult = typeValidator.isPrimitive([]); let boolResult = typeValidator.isPrimitive(true); let dateResult = typeValidator.isPrimitive(new Date()); let numberResult = typeValidator.isPrimitive(5); let objectResult = typeValidator.isPrimitive({}); let stringResult = typeValidator.isPrimitive("hello"); assert.isFalse(arrayResult); assert.isTrue(boolResult); assert.isFalse(dateResult); assert.isTrue(numberResult); assert.isFalse(objectResult); assert.isTrue(stringResult); }); }); ================================================ FILE: webpack.config.js ================================================ var path = require("path"); var webpack = require("webpack"); var ExtractTextPlugin = require("extract-text-webpack-plugin"); var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; var config = { entry: "./build/app.js", output: { path: path.resolve(__dirname, "./dist/app"), publicPath: "/dist/", filename: "app.bundle.js", library: "vuetiful", libraryTarget: "var" }, module: { rules: [{ test: /\.vue$/, loader: "vue-loader", options: { loaders: { // Since sass-loader (weirdly) has SCSS as its default parse mode, we map // the "scss" and "sass" values for the lang attribute to the right configs here. // other preprocessors should work out of the box, no loader config like this nessessary. "css": ExtractTextPlugin.extract({ loader: "css-loader!postcss-loader", fallbackLoader: "vue-style-loader" }), "scss": ExtractTextPlugin.extract({ loader: "css-loader!postcss-loader!sass-loader", fallbackLoader: "vue-style-loader" }), "sass": ExtractTextPlugin.extract({ loader: "css-loader!postcss-loader!sass-loader?indentedSyntax", fallbackLoader: "vue-style-loader" }) } // other vue-loader options go here } }, { test: /\.css$/, loaders: ExtractTextPlugin.extract({ loader: "css-loader!postcss-loader", fallbackLoader: "style-loader" }) }, { test: /\.scss$/, loaders: ExtractTextPlugin.extract({ loader: "css-loader!sass-loader", fallbackLoader: "style-loader" }), exclude: /node_modules/ }, { test: /\.js$/, loader: "babel-loader", exclude: /node_modules/ }, { test: /\.(png|jpg|gif|svg)$/, loader: "file-loader", options: { name: "[name].[ext]?[hash]" } }] }, resolve: { alias: { "vue$": "vue/dist/vue.common.js" } }, devServer: { historyApiFallback: true, noInfo: true, compress: true }, devtool: "#eval-source-map", watch: true, plugins: [ new ExtractTextPlugin("app.style.css"), new BundleAnalyzerPlugin({ analyzerMode: "static", reportFileName: "bundle.report.html", openAnalyzer: false }) ] }; if (process.env.NODE_ENV === "production") { config.devtool = "#source-map"; // http://vue-loader.vuejs.org/en/workflow/production.html config.watch = false; config.plugins = (config.plugins || []).concat([ new webpack.DefinePlugin({ "process.env": { NODE_ENV: '"production"' } }), new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } }), new webpack.LoaderOptionsPlugin({ debug: false, minimize: true }) ]); } if (process.env.SCOPE === "components") { config.entry = "./build/components.js"; config.output.path = path.resolve(__dirname, "./dist/components"); config.output.filename = "components.bundle.js"; config.externals = { "Vue": "vue", "date-fns": "dateFns" }; } module.exports = config;