Vue.JS - Advanced Data Grid Component

In this example below you will see how to do a Vue.JS - Advanced Data Grid Component with some HTML / CSS and Javascript

A Vue datagrid component with advanced features

Thumbnail
This awesome code was written by andrewcourtice, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright andrewcourtice ©

Technologies

  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>Vue.JS - Advanced Data Grid Component</title>
  
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">

  <link rel='stylesheet prefetch' href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,400italic,600,600italic,700'>
<link rel='stylesheet prefetch' href='https://fonts.googleapis.com/icon?family=Material+Icons'>

      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  <script type="text/x-template" id="dropdown-template">
    <div class="dropdown" v-show="show" v-bind:class="originClass" transition="dropdown">
        <slot>No dropdown content!</slot>
    </div>
</script>

<script type="text/x-template" id="datagrid-template">
    <table id="{{ id }}" class="table-striped datagrid">
        <thead>
            <tr>
                <th class="datagrid-toggle-column" v-if="allowSelection">
                    <div class="toggle toggle-checkbox">
                        <input type="checkbox" id="allrows" name="allrows" v-model="selectAll">
                        <label for="allrows"></label>
                    </div>
                </th>
                <th v-for="(index, column) in columns" v-bind:style="{ width: getCellWidth(column) }">
                    <div class="control-group">
                        <div class="datagrid-header control control-fill" v-on:click="sortBy(column)">
                            <span>{{ column.name }}</span>
                            <span class="material-icons icon" v-show="sortingKey === column.key">{{ sortingDirection === 1 ? 'expand_more' : 'expand_less' }}</span>
                        </div>
                        <div class="button-group control" v-if="showOptions && index === (columns.length - 1)">
                            <a id="{{ id }}-options" class="icon-button">
                                <span class="material-icons icon">settings</span>
                            </a>
                            <dropdown v-bind:for="id + '-options'" origin="top left" v-bind:preserve-state="true">
                                <datagrid-options v-bind:grid-id="id" v-bind:columns="columns" v-bind:allow-selection.sync="allowSelection" v-bind:allow-edit.sync="allowEdit" v-bind:data-filter.sync="dataFilter" v-bind:grouping-column.sync="groupingColumn" v-bind:show-advanced-options="showAdvancedOptions">
                                </datagrid-options>
                            </dropdown>
                        </div>
                    </div>
                </th>
            </tr>
        </thead>
        <tbody v-for="(groupName, groupData) in data | filterBy dataFilter | orderBy sortingKey sortingDirection | groupBy groupingColumn.key">
            <tr v-if="groupData.length === 0">
                <td class="text-centre" colspan="{{ columnSpan }}"><strong>No Results</strong></td>
            </tr>
            <tr class="table-group-header" v-if="groupingColumn">
                <td colspan="{{ columnSpan }}">{{ formatData(groupingColumn, groupName) }}</td>
            </tr>
            <tr v-for="(index, row) in groupData">
                <td class="datagrid-toggle-column" v-if="allowSelection">
                    <div class="toggle toggle-checkbox">
                        <input type="checkbox" id="{{ getControlId(groupName, index) }}" name="{{ getControlId(groupName, index) }}" v-bind:value="row" v-model="selectedRows">
                        <label for="{{ getControlId(groupName, index) }}"></label>
                    </div>
                </td>
                <td v-for="column in columns">
                    <partial v-bind:name="getCellTemplate(column)"></partial>
                </td>
            </tr>
        </tbody>
        <tfoot v-if="showFooter">
            <tr>
                <td colspan="{{ columnSpan }}">
                    <ul class="chips">
                        <li class="chip chip-removable" v-show="selectedRows.length > 0" transition="chip">
                            <span class="chip-title">Selection</span>
                            <span class="chip-subtitle">{{ selectedRows.length }} rows selected</span>
                            <a class="chip-remove-button" v-on:click="resetSelection()"></a>
                        </li>
                        <li class="chip chip-removable" v-show="dataFilter" transition="chip">
                            <span class="chip-title">Filtering on</span>
                            <span class="chip-subtitle">{{ dataFilter }}</span>
                            <a class="chip-remove-button" v-on:click="resetFilter()"></a>
                        </li>
                        <li class="chip chip-removable" v-show="groupingColumn" transition="chip">
                            <span class="chip-title">Grouping on</span>
                            <span class="chip-subtitle">{{ groupingColumn.name }}</span>
                            <a class="chip-remove-button" v-on:click="resetGrouping()"></a>
                        </li>
                    </ul>
                </td>
            </tr>
        </tfoot>
    </table>
</script>

<script type="text/x-template" id="datagrid-options-template">
    <div class="datagrid-options">
        <div class="datagrid-options-row">
            <input type="search" placeholder="Filter this dataset" v-model="dataFilter" />
        </div>
        <div class="datagrid-options-row" v-if="showAdvancedOptions">
            <div class="toggle toggle-switch">
                <input type="checkbox" id="{{ gridId }}-allow-selection" name="{{ gridId }}-allow-selection" value="" v-model="allowSelection">
                <label for="{{ gridId }}-allow-selection"></label>
            </div>
            <label for="{{ gridId }}-allow-selection">Allow Selection</label>
            <div class="toggle toggle-switch">
                <input type="checkbox" id="{{ gridId }}-allow-edit" name="{{ gridId }}-allow-edit" value="" v-model="allowEdit">
                <label for="{{ gridId }}-allow-edit"></label>
            </div>
            <label for="{{ gridId }}-allow-edit">Allow Edit</label>
        </div>
        <div class="table-wrapper datagrid-options-row">
            <table>
                <thead>
                    <tr>
                        <th>Column</th>
                        <th>Group By</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>All</td>
                        <td class="text-centre">
                            <div class="toggle toggle-radio">
                                <input type="radio" id="all" name="group-by" value="" v-model="groupingColumn">
                                <label for="all"></label>
                            </div>
                        </td>
                    </tr>
                    <tr v-for="column in columns">
                        <td>{{ column.name }}</td>
                        <td class="text-centre">
                            <div class="toggle toggle-radio">
                                <input type="radio" id="{{ getControlName(column.key, 'grp') }}" name="group-by" v-bind:value="column" v-model="groupingColumn">
                                <label for="{{ getControlName(column.key, 'grp') }}"></label>
                            </div>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</script>

<div id="index" class="container">
    <section>
        <h1>Vue.JS Datagrid</h1>
    </section>
   <section>
      <strong>Note:</strong> This datagrid uses version 1 of Vue. Version 2 has since been released and I have written a new datagrid component targeting version 2. See the updated version of this datagrid here:
      <a href="https://codepen.io/andrewcourtice/full/woQzpa/" target="_blank">https://codepen.io/andrewcourtice/full/woQzpa/</a>
   </section>
    <section>
        <h2>Features</h2>
        <ul>
            <li>Sorting (Click column header)</li>
            <li>Grouping (Use the advanced options)</li>
            <li>Filtering</li>
            <li>Toggle cell editing</li>
            <li>Toggle row selection</li>
            <li>Custom cell templates (override default for whole grid or just a specific column)</li>
            <li>Custom filters for cell content</li>
        </ul>
    </section>
    <section>
        <div class="table-wrapper">
            <datagrid id="customers-grid" 
                v-bind:columns="customers.columns" 
                v-bind:data="customers.data" 
                v-bind:show-advanced-options="true">

            </datagrid>
        </div>
    </section>
</div>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.12.0/moment.min.js'></script>

  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/andrewcourtice/vuejs-advanced-data-grid-component-VabXQV */
/* Colours */
/* Borders */
/* Inputs */
/* Some defaults */
html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

html {
  font-size: 14px;
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}

*, :before, :after {
  -moz-box-sizing: inherit;
  -webkit-box-sizing: inherit;
  box-sizing: inherit;
}

body {
  font-family: "Open Sans", sans-serif;
  font-weight: 400;
  line-height: 1.45;
  color: #414550;
  background-color: #ffffff;
}

section,
footer {
  margin-top: 64px;
}

a {
  color: #2196f3;
  text-decoration: none;
}
a:hover {
  color: #0c7cd5;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0.75em 0;
  line-height: 1.45;
  font-weight: 600;
  color: #363942;
}

h1 {
  margin-top: 0;
  font-size: 34px;
}

h2 {
  font-size: 27px;
}

ul,
ol {
  padding-left: 30px;
}

li {
  list-style: disc;
}

strong {
  font-weight: 600;
}

.text-centre {
  text-align: center;
}

/* Containers */
.container {
  width: 1160px;
  margin: 0 auto;
  padding: 0 15px;
}
.container.container-fluid {
  width: 100%;
}

.control-group {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: horizontal;
  -webkit-box-direction: normal;
  -webkit-flex-direction: row;
  -ms-flex-direction: row;
  flex-direction: row;
  -webkit-flex-wrap: nowrap;
  -ms-flex-wrap: nowrap;
  flex-wrap: nowrap;
  -webkit-box-pack: start;
  -webkit-justify-content: flex-start;
  -ms-flex-pack: start;
  justify-content: flex-start;
  -webkit-box-align: center;
  -webkit-align-items: center;
  -ms-flex-align: center;
  align-items: center;
}

.control {
  -webkit-flex-shrink: 0;
  -ms-flex-negative: 0;
  flex-shrink: 0;
  -webkit-box-flex: 0;
  -webkit-flex-grow: 0;
  -ms-flex-positive: 0;
  flex-grow: 0;
  margin-left: 0;
  margin-right: 10px;
}
.control:last-child {
  margin-right: 0;
}
.control.control-right {
  margin-left: auto;
}
.control.control-right:first-child {
  margin-right: 0;
}
.control.control-fill {
  -webkit-box-flex: 1;
  -webkit-flex-grow: 1;
  -ms-flex-positive: 1;
  flex-grow: 1;
  -webkit-flex-shrink: 1;
  -ms-flex-negative: 1;
  flex-shrink: 1;
}

/* Icons */
.icon {
  display: inline-block;
  font-size: 18px;
  vertical-align: text-bottom;
}
.icon.icon-24 {
  font-size: 24px;
}
.icon.icon-36 {
  font-size: 36px;
}
.icon.icon-48 {
  font-size: 48px;
}

/* Buttons */
.icon-button {
  display: inline-block;
  margin: 0 5px;
  color: #6f7688;
  cursor: pointer;
  outline: none;
  background: none;
  border: none;
  text-align: center;
  line-height: 1em;
}
.icon-button:hover {
  color: #363942;
}
.icon-button.button-accent:hover {
  color: #0c7cd5;
}

.button-group {
  display: inline-block;
  position: relative;
  margin-right: 5px;
}
.button-group .button {
  margin: 0;
}

/* Inputs */
input:not([type="submit"]):not([type="checkbox"]):not([type="radio"]),
select,
textarea {
  width: 100%;
  min-width: 150px;
  padding: 10px 12px;
  font-family: inherit;
  font-size: 14px;
  line-height: 1.45;
  color: #414550;
  background-color: #ffffff;
  border: 1px solid #dddddd;
  border-radius: 2px;
  outline: none;
  -webkit-transition: border 150ms ease-out;
  -moz-transition: border 150ms ease-out;
  -o-transition: border 150ms ease-out;
  transition: border 150ms ease-out;
}
input:not([type="submit"]):not([type="checkbox"]):not([type="radio"]):focus,
select:focus,
textarea:focus {
  border-color: #2196f3;
}
input:not([type="submit"]):not([type="checkbox"]):not([type="radio"]):disabled,
select:disabled,
textarea:disabled {
  background-color: #fafafc;
  cursor: not-allowed;
}
input:not([type="submit"]):not([type="checkbox"]):not([type="radio"]):invalid,
select:invalid,
textarea:invalid {
  border-color: #DE6060;
}
input:not([type="submit"]):not([type="checkbox"]):not([type="radio"]):invalid + .validation-message,
select:invalid + .validation-message,
textarea:invalid + .validation-message {
  display: block;
}

input:not([type="submit"]):not([type="checkbox"]):not([type="radio"]),
select {
  height: 42px;
}

input[type="checkbox"],
input[type="radio"] {
  opacity: 0;
}

.toggle {
  display: inline-block;
  position: relative;
  margin-right: 5px;
  background-color: #fafafc;
  border: 1px solid #dddddd;
  vertical-align: text-bottom;
}
.toggle label {
  position: absolute;
  display: block;
  content: " ";
  margin: 0;
  cursor: pointer;
}
.toggle + label {
  display: inline-block;
  margin: 0 15px 0 0;
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

.toggle-switch {
  width: 32px;
  height: 18px;
  border-radius: 12px;
}
.toggle-switch label {
  top: 0;
  left: 0;
  width: 32px;
  height: 18px;
}
.toggle-switch label:after {
  position: absolute;
  display: block;
  content: " ";
  width: 12px;
  height: 12px;
  top: 2px;
  left: 2px;
  background-color: #6f7688;
  border-radius: 50%;
  -webkit-transition: all 200ms ease-out;
  -moz-transition: all 200ms ease-out;
  -o-transition: all 200ms ease-out;
  transition: all 200ms ease-out;
}
.toggle-switch:hover label:after {
  background-color: #2196f3;
}
.toggle-switch input[type="checkbox"]:focus label:after {
  background-color: #2196f3;
}
.toggle-switch input[type="checkbox"]:checked + label:after {
  left: 16px;
  background-color: #2196f3;
}
.toggle-switch input[type="checkbox"]:disabled + label:after {
  background-color: #6f7688;
}

.toggle-checkbox {
  width: 18px;
  height: 18px;
  border: none;
}
.toggle-checkbox label {
  width: 18px;
  height: 18px;
  top: 0;
  left: 0;
  border: 1px solid #dddddd;
  border-radius: 2px;
  -webkit-transition: border 150ms ease-out;
  -moz-transition: border 150ms ease-out;
  -o-transition: border 150ms ease-out;
  transition: border 150ms ease-out;
}
.toggle-checkbox label:after {
  position: absolute;
  display: block;
  content: " ";
  width: 6px;
  height: 12px;
  top: 1px;
  left: 5px;
  border-right: 3px solid transparent;
  border-bottom: 3px solid transparent;
  -webkit-transform: rotate(45deg);
  -moz-transform: rotate(45deg);
  -ms-transform: rotate(45deg);
  -o-transform: rotate(45deg);
  transform: rotate(45deg);
  -webkit-transition: all 200ms ease-out;
  -moz-transition: all 200ms ease-out;
  -o-transition: all 200ms ease-out;
  transition: all 200ms ease-out;
}
.toggle-checkbox label:hover {
  border-color: #2196f3;
}
.toggle-checkbox input[type="checkbox"]:focus label {
  border-color: #2196f3;
}
.toggle-checkbox input[type="checkbox"]:checked + label {
  background-color: #2196f3;
  border-color: #2196f3;
}
.toggle-checkbox input[type="checkbox"]:checked + label:after {
  border-color: #ffffff;
}

.toggle-radio {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  -webkit-transition: border 150ms ease-out;
  -moz-transition: border 150ms ease-out;
  -o-transition: border 150ms ease-out;
  transition: border 150ms ease-out;
}
.toggle-radio label {
  top: 0;
  left: 0;
  width: 18px;
  height: 18px;
}
.toggle-radio label:after {
  position: absolute;
  display: block;
  content: " ";
  width: 10px;
  height: 10px;
  top: 3px;
  left: 3px;
  background-color: #6f7688;
  border-radius: 50%;
  -webkit-transition: all 200ms ease-out;
  -moz-transition: all 200ms ease-out;
  -o-transition: all 200ms ease-out;
  transition: all 200ms ease-out;
}
.toggle-radio:hover {
  border-color: #2196f3;
}
.toggle-radio input[type="radio"]:focus label:after {
  background-color: #2196f3;
}
.toggle-radio input[type="radio"]:checked + label:after {
  background-color: #2196f3;
}

/* Tables */
.table-wrapper {
  background-color: #ffffff;
  border: 1px solid #dddddd;
  border-radius: 2px;
}
.table-wrapper.table-wrapper-responsive {
  max-width: 100%;
  overflow-x: auto;
}
.table-wrapper.table-wrapper-responsive table, .table-wrapper.table-wrapper-responsive .table {
  table-layout: auto;
  width: auto;
  min-width: 100%;
}
.table-wrapper table, .table-wrapper .table {
  border: none;
}

table,
.table {
  display: table;
  table-layout: fixed;
  border-collapse: collapse;
  border-spacing: 0;
  width: 100%;
  border: 1px solid #dddddd;
}

.table-striped tbody tr:nth-child(odd) td, .table-striped tbody tr:nth-child(odd) .td, .table-striped tbody .tr:nth-child(odd) td, .table-striped tbody .tr:nth-child(odd) .td, .table-striped .tbody tr:nth-child(odd) td, .table-striped .tbody tr:nth-child(odd) .td, .table-striped .tbody .tr:nth-child(odd) td, .table-striped .tbody .tr:nth-child(odd) .td {
  background-color: #fdfdfd;
}

thead,
.thead {
  display: table-header-group;
}

tbody,
.tbody {
  display: table-row-group;
}
tbody:last-of-type tr:last-child td, tbody:last-of-type tr:last-child .td,
.tbody:last-of-type tr:last-child td,
.tbody:last-of-type tr:last-child .td {
  border-bottom: none;
}
tbody td, tbody .td,
.tbody td,
.tbody .td {
  border-bottom: 1px solid #eeeeee;
}
tbody td > input:not([type="submit"]):not([type="checkbox"]):not([type="radio"]), tbody .td > input:not([type="submit"]):not([type="checkbox"]):not([type="radio"]),
.tbody td > input:not([type="submit"]):not([type="checkbox"]):not([type="radio"]),
.tbody .td > input:not([type="submit"]):not([type="checkbox"]):not([type="radio"]) {
  background-color: transparent;
  border: none;
  height: auto;
  padding: 0;
}

tfoot,
.tfoot {
  display: table-footer-group;
}
tfoot td, tfoot .td,
.tfoot td,
.tfoot .td {
  background-color: #fafafc;
  border-top: 1px solid #dddddd;
}

th,
.th,
td,
.td {
  display: table-cell;
  padding: 8px 15px;
  text-align: left;
  vertical-align: middle;
}
th:not(:last-of-type),
.th:not(:last-of-type),
td:not(:last-of-type),
.td:not(:last-of-type) {
  border-right: 1px solid #dddddd;
}

th,
.th {
  padding: 15px;
  font-weight: 600;
  color: #363942;
  background-color: #fafafc;
  border-bottom: 1px solid #dddddd;
  white-space: nowrap;
}
th .toggle,
.th .toggle {
  background-color: #ffffff;
}

tr.table-group-header td,
.tr.table-group-header td {
  font-weight: 600;
  background-color: #fafafc;
}

/* Dropdown */
.dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 150px;
  margin-top: 5px;
  background-color: #ffffff;
  border: 1px solid #dddddd;
  border-radius: 2px;
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.15);
  -webkit-transform-origin: top left;
  -moz-transform-origin: top left;
  -ms-transform-origin: top left;
  -o-transform-origin: top left;
  transform-origin: top left;
  z-index: 98;
  overflow: hidden;
  overflow-y: auto;
}
.dropdown.dropdown-top-left, .dropdown.dropdown-bottom-left {
  left: auto;
  right: 0;
}
.dropdown.dropdown-bottom-left, .dropdown.dropdown-bottom-right {
  top: auto;
  bottom: 100%;
  margin-top: 0;
  margin-bottom: 5px;
}
.dropdown.dropdown-top-left {
  -webkit-transform-origin: top right;
  -moz-transform-origin: top right;
  -ms-transform-origin: top right;
  -o-transform-origin: top right;
  transform-origin: top right;
}
.dropdown.dropdown-bottom-left {
  left: auto;
  right: 0;
  -webkit-transform-origin: bottom right;
  -moz-transform-origin: bottom right;
  -ms-transform-origin: bottom right;
  -o-transform-origin: bottom right;
  transform-origin: bottom right;
}
.dropdown.dropdown-bottom-right {
  -webkit-transform-origin: bottom left;
  -moz-transform-origin: bottom left;
  -ms-transform-origin: bottom left;
  -o-transform-origin: bottom left;
  transform-origin: bottom left;
}
.dropdown .menu-item {
  padding: 10px 15px;
  cursor: pointer;
}
.dropdown .menu-item:hover {
  background-color: #f2f2f7;
}

.dropdown-transition {
  -webkit-transition: opacity 100ms ease-out, transform 100ms ease-out;
  -moz-transition: opacity 100ms ease-out, transform 100ms ease-out;
  -moz-transition: opacity 100ms ease-out, transform 100ms ease-out;
  -o-transition: opacity 100ms ease-out, transform 100ms ease-out;
  transition: opacity 100ms ease-out, transform 100ms ease-out;
  opacity: 1;
  -webkit-transform: translate(0, 0) scale(1);
  -moz-transform: translate(0, 0) scale(1);
  -ms-transform: translate(0, 0) scale(1);
  -o-transform: translate(0, 0) scale(1);
  transform: translate(0, 0) scale(1);
}

.dropdown-enter,
.dropdown-leave {
  opacity: 0;
  -webkit-transform: translate(0, -15px) scale(0.85);
  -moz-transform: translate(0, -15px) scale(0.85);
  -ms-transform: translate(0, -15px) scale(0.85);
  -o-transform: translate(0, -15px) scale(0.85);
  transform: translate(0, -15px) scale(0.85);
}

/* Chips */
.chips {
  display: inline-block;
  margin: 0;
  padding: 0;
  list-style: none;
}

.chip {
  display: inline-block;
  position: relative;
  margin: 5px 5px 5px 0;
  padding: 5px 15px;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.45;
  color: #363942;
  background-color: #fafafc;
  border: 1px solid #dddddd;
  border-radius: 2px;
  vertical-align: middle;
}
.chip.chip-removable {
  padding-right: 48px;
}
.chip.chip-accent .chip-title, .chip.chip-accent .chip-subtitle, .chip.chip-business .chip-title, .chip.chip-business .chip-subtitle, .chip.chip-danger .chip-title, .chip.chip-danger .chip-subtitle, .chip.chip-warning .chip-title, .chip.chip-warning .chip-subtitle, .chip.chip-success .chip-title, .chip.chip-success .chip-subtitle {
  color: #ffffff;
}
.chip.chip-accent .chip-remove-button:before, .chip.chip-accent .chip-remove-button:after, .chip.chip-business .chip-remove-button:before, .chip.chip-business .chip-remove-button:after, .chip.chip-danger .chip-remove-button:before, .chip.chip-danger .chip-remove-button:after, .chip.chip-warning .chip-remove-button:before, .chip.chip-warning .chip-remove-button:after, .chip.chip-success .chip-remove-button:before, .chip.chip-success .chip-remove-button:after {
  background-color: #ffffff;
}
.chip.chip-accent {
  background-color: #2196f3;
  border-color: #2196f3;
}
.chip.chip-accent .chip-remove-button:hover {
  background-color: #0c7cd5;
}

.chip-title,
.chip-subtitle {
  display: block;
}

.chip-title {
  font-size: 14px;
  font-weight: 600;
  color: #363942;
}

.chip-subtitle {
  font-size: 11px;
  font-weight: 400;
  color: #6f7688;
}

.chip-remove-button {
  position: absolute;
  top: 50%;
  right: 15px;
  width: 18px;
  height: 18px;
  margin-top: -9px;
  cursor: pointer;
  border-radius: 2px;
  -moz-transition: background 150ms ease-out;
  -o-transition: background 150ms ease-out;
  -webkit-transition: background 150ms ease-out;
  transition: background 150ms ease-out;
}
.chip-remove-button:before, .chip-remove-button:after {
  display: block;
  position: absolute;
  content: " ";
  top: 50%;
  left: 50%;
  width: 16px;
  height: 2px;
  margin-top: -1px;
  margin-left: -8px;
  background-color: #414550;
  -moz-transform-origin: center center;
  -ms-transform-origin: center center;
  -o-transform-origin: center center;
  -webkit-transform-origin: center center;
  transform-origin: center center;
  -moz-transition: background 150ms ease-out;
  -o-transition: background 150ms ease-out;
  -webkit-transition: background 150ms ease-out;
  transition: background 150ms ease-out;
}
.chip-remove-button:before {
  -moz-transform: rotate(-45deg);
  -ms-transform: rotate(-45deg);
  -o-transform: rotate(-45deg);
  -webkit-transform: rotate(-45deg);
  transform: rotate(-45deg);
}
.chip-remove-button:after {
  -moz-transform: rotate(45deg);
  -ms-transform: rotate(45deg);
  -o-transform: rotate(45deg);
  -webkit-transform: rotate(45deg);
  transform: rotate(45deg);
}
.chip-remove-button:hover {
  background-color: #2196f3;
}
.chip-remove-button:hover:before, .chip-remove-button:hover:after {
  background-color: #ffffff;
}

.chip-transition {
  -webkit-transition: transform 100ms ease-out;
  -moz-transition: transform 100ms ease-out;
  -o-transition: transform 100ms ease-out;
  transition: transform 100ms ease-out;
  opacity: 1;
  -webkit-transform: scale(1);
  -moz-transform: scale(1);
  -ms-transform: scale(1);
  -o-transform: scale(1);
  transform: scale(1);
}

.chip-enter,
.chip-leave {
  opacity: 0;
  -webkit-transform: scale(0);
  -moz-transform: scale(0);
  -ms-transform: scale(0);
  -o-transform: scale(0);
  transform: scale(0);
}

/* Datagrid */
.datagrid .chip {
  background-color: #ffffff;
}

.datagrid-header {
  cursor: pointer;
  -moz-user-select: none;
  -ms-user-select: none;
  -webkit-user-select: none;
  user-select: none;
}

.datagrid-toggle-column {
  width: 64px;
  text-align: center;
}
.datagrid-toggle-column .toggle {
  margin: 0;
}

.datagrid-options {
  padding: 15px;
}
.datagrid-options table, .datagrid-options .table {
  table-layout: auto;
}

.datagrid-options-row:not(:first-child) {
  margin-top: 15px;
}

#index {
  margin-bottom: 64px;
}

@media screen and (max-width: 1177px) {
  .container {
    width: 100% !important;
  }
}


/*Downloaded from https://www.codeseek.co/andrewcourtice/vuejs-advanced-data-grid-component-VabXQV */
Vue.filter("groupBy", function(value, key) {
    var groups = {
        data: value
    };

    if (key) {
        groups = {};
        for (var i = 0; i < value.length; i++) {
            var row = value[i];
            var cell = row[key];

            if (!groups.hasOwnProperty(cell)) {
                groups[cell] = [];
            }

            groups[cell].push(row);
        }

    }
    return groups;
});

Vue.filter("date", function(value, format) {
    var date = moment(value);

    if (!date.isValid()) {
        return value;
    }

    return date.format(format);
});

Vue.component("dropdown", {
    template: "#dropdown-template",
    props: {

        for: {
            type: String,
            required: true
        },

        origin: {
            type: String,
            default: "top right"
        },

        preserveState: {
            type: Boolean,
            default: false
        }

    },
    computed: {

        originClass: function() {
            switch (this.origin) {
                case "top left":
                    return "dropdown-top-left";
                case "bottom left":
                    return "dropdown-bottom-left";
                case "bottom right":
                    return "dropdown-bottom-right";
            }
        }

    },
    data: function() {
        return {
            show: false
        };
    },
    ready: function() {
        var _this = this;

        var element = document.getElementById(_this.for);

        var hide = function(event) {
            event.stopPropagation();

            if (!(_this.preserveState && _this.$el.contains(event.target))) {
                _this.show = false;
                document.body.removeEventListener("click", hide);
            }

        };

        var show = function(event) {
            event.preventDefault();
            event.stopPropagation();

            var dropdowns = [].slice.call(document.querySelectorAll(".dropdown"));

            dropdowns.forEach(function(dropdown) {
                dropdown.__vue__.show = false;
            });

            if (!_this.show) {
                _this.show = true;

                document.body.addEventListener("click", hide);
            }
        };

        element.addEventListener("click", show);
    }
});

Vue.component("datagridOptions", {
    template: "#datagrid-options-template",
    props: {

        gridId: {
            type: String,
            required: true
        },

        columns: {
            type: Array,
            required: true
        },

        allowSelection: {
            type: Boolean
        },

        allowEdit: {
            type: Boolean
        },

        groupingColumn: {
            type: Object,
            required: true
        },

        dataFilter: {
            type: String,
            required: true
        },

        showAdvancedOptions: {
            type: Boolean
        }

    },
    methods: {

        getControlName(columnKey, suffix) {
            return this.gridId + "-" + columnKey + "-" + suffix;
        }

    }
});

Vue.component("datagrid", {
    template: "#datagrid-template",
    components: ["datagridOptions"],
    props: {

        id: {
            type: String,
            required: true
        },

        columns: {
            type: Array,
            required: true
        },

        data: {
            type: Array
        },

        cellTemplate: {
            type: String,
            required: false,
            default: "defaultGridCell"
        },

        allowSelection: {
            type: Boolean,
            required: false,
            default: false
        },

        allowEdit: {
            type: Boolean,
            required: false,
            default: false
        },

        showDefaultOptions: {
            type: Boolean,
            required: false,
            default: true
        },

        showAdvancedOptions: {
            type: Boolean,
            required: false,
            default: false
        }

    },
    computed: {

        columnSpan: function() {
            return this.allowSelection ? this.columns.length + 1 : this.columns.length;
        },

        showOptions: function() {
            return this.showDefaultOptions || this.showAdvancedOptions;
        },

        showFooter: function() {
            return this.dataFilter || this.groupingColumn || this.selectedRows.length > 0;
        }

    },
    data: function() {

        return {
            sortingKey: null,
            sortingDirection: 1,
            groupingColumn: null,
            dataFilter: null,
            selectedRows: [],
            selectAll: false
        };

    },
    methods: {

        getCellTemplate: function(column) {
            return this.allowEdit ? "editableGridCell" : (column.template || this.cellTemplate);
        },

        getCellWidth: function(column) {
            if (!column.width) {
                return;
            }

            return column.width + (isNaN(column.width) ? "" : "%");
        },

        getControlId: function(groupName, index, suffix) {
            return groupName + "-" + index + (suffix ? "-" + suffix : "");
        },

        sortBy: function(column) {
            if (column.key === this.sortingKey) {
                this.sortingDirection *= -1;
                return;
            }

            this.sortingKey = column.key;
            this.sortingDirection = 1;
        },

        groupBy: function(column) {
            this.groupingColumn = column;
        },

        resetFilter() {
            this.dataFilter = null;
        },

        resetGrouping() {
            this.groupingColumn = null;
        },

        resetSelection() {
            this.selectedRows = [];
            this.selectAll = false;
        },

        formatData: function(column, value) {
            if (column.hasOwnProperty("filter")) {
                var filter = Vue.filter(column.filter.name);
                var args = [].concat(value, column.filter.args);
                return filter.apply(this, args);
            }
            return value;
        }
    },
    watch: {

        "selectAll": function(value) {
            this.selectedRows = value ? [].concat(this.data) : [];
        }

    }
});

Vue.partial("defaultGridCell", "<span>{{ formatData(column, row[column.key]) }}</span>");
Vue.partial("editableGridCell", "<input type=\"text\" v-model=\"row[column.key]\" lazy/>");
Vue.partial("linkedGridCell", "<a href=\"https://www.google.com?q={{ row.GivenName }}\"><partial name=\"defaultGridCell\"></partial></a>");

var vue = new Vue({
    el: "#index",
    data: {
        customers: {
            columns: [{
                key: "GivenName",
                name: "Given Name",
                template: "linkedGridCell"
            }, {
                key: "Surname",
                name: "Surname"
            }, {
                key: "Email",
                name: "Email",
                width: 33
            }, {
                key: "DateOfBirth",
                name: "Date of Birth",
                filter: {
                    name: "date",
                    args: ["DD MMMM YYYY"]
                }
            }],
            data: [{
                "ID": 0,
                "GivenName": "John",
                "Surname": "Smith",
                "DateOfBirth": "1986-10-03T00:00:00",
                "Email": "john.smith@smithsteel.com",
                "JobTitle": "Co-Founder and CEO",
                "Company": "Smith Steel Pty Ltd"
            }, {
                "ID": 1,
                "GivenName": "Jane",
                "Surname": "Smith",
                "DateOfBirth": "1988-05-28T00:00:00",
                "Email": "jane.smith@smithsteel.com",
                "JobTitle": "Co-Founder and CEO",
                "Company": "Smith Steel Pty Ltd"
            }, {
                "ID": 2,
                "GivenName": "Richard",
                "Surname": "Swanston",
                "DateOfBirth": "1972-08-15T00:00:00",
                "Email": "rswanston@telco.com",
                "JobTitle": "Purchasing Officer",
                "Company": "Cortana Mining Co"
            }, {
                "ID": 3,
                "GivenName": "Robert",
                "Surname": "Brown",
                "DateOfBirth": "1968-01-18T00:00:00",
                "Email": "robbrown@othertelco.com",
                "JobTitle": "Sales Manager",
                "Company": "Powerhouse Marketing"
            }, {
                "ID": 4,
                "GivenName": "Phillip",
                "Surname": "Zucco",
                "DateOfBirth": "1991-06-28T00:00:00",
                "Email": "phil.zucco@workplace.com",
                "JobTitle": "Applications Developer",
                "Company": "Workplace Pty Ltd"
            }, {
                "ID": 5,
                "GivenName": "James",
                "Surname": "Caldwell",
                "DateOfBirth": "1988-07-27T00:00:00",
                "Email": "james.caldwell@random.com",
                "JobTitle": "Purchasing Officer",
                "Company": "Random Industries Ltd."
            }, {
                "ID": 6,
                "GivenName": "Rachael",
                "Surname": "O'Reilly",
                "DateOfBirth": "1972-08-15T00:00:00",
                "Email": "roreilly@energy.com",
                "JobTitle": "Workplace Health and Safety Officer",
                "Company": "Energy Company"
            }]
        }
    }
});

Comments