Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <link rel="stylesheet" type="text/css" href="https://unpkg.com/quasar-framework@^0.16.0/dist/umd/quasar.mat.min.css">
 <link
    rel="stylesheet"
    href="https://fonts.googleapis.com/icon?family=Material+Icons"
  >
<div id="q-app">
  <q-data-table-extended
    :data="tableData" 
    :columns="tableDefinition.columns" 
    row-key="name" 
    :loading="tableDefinition.isLoading"
    :rowscount.sync="tableDefinition.pagination.rowsPerPage" 
    :pagination.sync="tableDefinition.pagination" 
    :sort.sync="tableDefinition.sort" 
    @sorted="sortData" 
    @paged="pageData"
    >
    <!--
      Would like to put customized column slots here as if I were working directly with Quasar Data Table component, how to pass slots for custom rows, alternate rows, custom column templates, etc.
    
    -->
     <template slot="STATUS" slot-scope="props">
          <div>
            <q-icon v-if="props.row.INACTIVE" name="close" size="18px" color="red"></q-icon>
            <q-icon v-if="!props.row.INACTIVE" name="check" size="18px" color="green"></q-icon>
          </div>
        </template>
</div>

<template id="bob-grid-template">
  <div>
    <div ref="tableHeadScroller" style="display:block;overflow-y: hidden;overflow-x: hidden;margin-right: 17px;">
      <q-table ref="tableHeader" hide-bottom :data="[]" :pagination.sync="sortData" :columns="columns" />
    </div>
    <div ref="tableScroller" class="scroll" style="overflow-y: scroll;overflow-x: scroll;height: calc(-105px + 100vh);">
      
      <q-table 
        ref="tableBody" 
        hide-header 
        hide-bottom 
        :data="data" 
        :pagination="internalPageProps" 
        :columns="columns" 
        :row-key="rowKey">
        <!--
          How to pass slots from parent to body table and have render, and idea below, but not sure if it is the right way.
        -->
        <q-tr slot="body" slot-scope="props" :props="props" @click.native="rowClick(props.row)" class="cursor-pointer">
          <q-td v-for="col in props.cols" :key="col.name" :props="props">
            <slot :name="col.name" scope="props" :column="col" :row="props.row">
                 {{col.value}}
            </slot>
          </q-td>
        </q-tr>
      </q-table>
      <q-inner-loading :visible="loading">
        <q-spinner-gears size="50px" color="primary"></q-spinner-gears>
      </q-inner-loading>
    </div>
    <div class="q-table-bottom row items-center justify-end">
      <span class="q-table-bottom-item">
        Items per page
      </span>
      <q-select 
        :value="pagination.rowsPerPage" 
        @change="val => { pagination.rowsPerPage = val; handlePage(pagination.page); }" 
        :options="computedRowsPerPageOptions" 
        class="no-wrap inline q-table-bottom-item text-grey-8">
        </q-select>
   <q-pagination input :value="pagination.page" :max="pagination.totalPages" @change="val => { pagination.page = val; handlePage(val); }"></q-pagination>
    </div>
  </div>
</template>
              
            
!

CSS

              
                .q-table-container {
  -webkit-box-shadow: none !important;
  -moz-box-shadow: none !important;
  box-shadow: none !important;
}
.q-table-middle.scroll {
  /* override scrolling behavior within control */
  overflow: visible !important;
}

              
            
!

JS

              
                
// need some data to bind during mount to trigger column resize
let chance = new Chance(),
  data = [];

// use chance to generate some random rows
for (let i = 0; i < 555; i++) {
  data.push({
    CUSTNMBR: i,//chance.integer({ min: 200229, max: 299999 }),
    CUSTNAME: chance.sentence({ words: 8 }),
    ADDRESS1: chance.address(),
    ADDRESS2: chance.address(),
    ADDRESS3: chance.address(),
    CITY: chance.city(),
    STATE: chance.state({ full: true }),
    ZIP: chance.zip({ plusfour: true }),
    SLPRSNID: chance.name(),
    SALSTERR: chance.word({ syllables: 3 }),
    CUSTCLAS: chance.word({ syllables: 2 }),
    INACTIVE: chance.bool(),
    HOLD: chance.bool()
  });
}

Vue.component("QDataTableExtended", {
  props: {
    data: {
      type: Array,
      default: () => []
    },
    columns: Array,
    sort: {
      type: Object,
      default: () => {
        return {
          sortBy: null,
          descending: false
        };
      }
    },
    pagination: {
      type: Object,
      default: () => {
        return {
          page: 1,
          rowsPerPage: 50,
          totalItems: 0,
          totalPages: 0
        };
      }
    },
    rowKey: String,
    loading: {
      type: Boolean,
      default: false
    },
    tabledefinition: Object,
    rowscount: {
      type: Number,
      default: 5
    },
    rowsPerPageOptions: {
      type: Array,
      default: () => [25, 50, 100, 300]
    }
  },
  data() {
    return {};
  },
  computed: {
    // yanked straight from the source, probably easier way to accomplish
    computedRowsPerPageOptions() {
      return this.rowsPerPageOptions.map(count => ({
        label: count === 0 ? this.$q.i18n.table.allRows : "" + count,
        value: count
      }));
    },
    // only want sort data
    sortData: {
      get: function() {
        return this.sort;
      },
      set: function(val) {
        this.sort.sortBy = val.sortBy;
        this.sort.descending = val.descending;
      }
    },
    // quick hack because we only want hold our max row count for the body internals, but do not to get paging involved as we are handling it via events and server side
    internalPageProps: {
      get: function() {
        return {
          rowsPerPage: this.rowscount
        };
      },
      set: function(val) {
        this.rowscount = val.rowsPerPage;
      }
    }
  },
  mounted() {
    let self = this;
    // we need to keep the scrollbar in sync between the body table and header scroll containers
    self.$refs.tableScroller.onscroll = function() {
      let bodyScroller = self.$refs.tableScroller;
      let headScroller = self.$refs.tableHeadScroller;
      headScroller.scrollLeft = bodyScroller.scrollLeft;
    };
  },
  watch: {
    // we need to deep watch for sort changes fired by the header grid
    sort: {
      handler(val) {
        this.$emit("sorted", val);
      },
      deep: true
    },
    // watch our table data for changes
    data: function(newVal, oldVal) {
      let self = this;
      self.$nextTick(function() {
        // header table reference
        let tableHeader = this.$refs.tableHeader.$el.getElementsByTagName(
          "table"
        )[0].tHead;

        // body table reference
        let tableBody = this.$refs.tableBody.$el.getElementsByTagName(
          "table"
        )[0].tBodies[0];

        if (tableBody.rows.length) {
          for (let i = 0; i < tableHeader.rows[0].cells.length; i++) {
            let td = tableBody.rows[0].cells[i];
            let th = tableHeader.rows[0].cells[i];

            // get our computeted styles
            let computedTh = window.getComputedStyle(th);
            let computedTd = window.getComputedStyle(td);

            // get the max of either the header column
            let width = Math.max(
              computedTh.width.replace("px", ""),
              computedTd.width.replace("px", "")
            );

            // assign the width to both the header and first table column
            // currently does not display correctly on IE 11, needs work
            th.style["min-width"] = width + "px";
            td.style["min-width"] = width + "px";
          }
        }
        // scroll up since we are getting updated data
        self.$refs.tableScroller.scrollTop = 0;
      });
    }
  },
  methods: {
    // called when the pager value changes
    handlePage(value) {
      this.$emit("paged", value);
    }
  },
  template: "#bob-grid-template"
});

new Vue({
  el: "#q-app",
  mounted: function() {
    this.mockData();
    // this.tableData = data;
  },
  data: function() {
    return {
      tableDefinition: {
        isLoading: false,
        pagination: {
          page: 1,
          rowsPerPage: 50,
          totalItems: 0,
          totalPages: 0
        },
        sort: {
          sortBy: null,
          descending: false
        },
        columns: [
          {
            name: "STATUS",
            required: true,
            label: "",
            align: "left",
            sortable: false
          },
          {
            name: "CUSTNMBR",
            required: true,
            label: "Customer",
            align: "left",
            field: "CUSTNMBR",
            sortable: true,
            style: {}
          },
          {
            name: "CUSTNAME",
            required: true,
            label: "Name",
            align: "left",
            field: "CUSTNAME",
            sortable: true
          },
          {
            name: "SLPRSNID",
            required: true,
            label: "Sales Person",
            align: "left",
            field: "SLPRSNID",
            sortable: true
          },
          {
            name: "CUSTCLAS",
            required: true,
            label: "Class",
            align: "left",
            field: "CUSTCLAS",
            sortable: true
          },
          {
            name: "ADDRESS1",
            required: true,
            label: "Address 1",
            align: "left",
            field: "ADDRESS1",
            sortable: true
          },
          {
            name: "ADDRESS2",
            required: true,
            label: "Address 2",
            align: "left",
            field: "ADDRESS2",
            sortable: true
          },
          {
            name: "ADDRESS3",
            required: true,
            label: "Address 3",
            align: "left",
            field: "ADDRESS3",
            sortable: true
          },
          {
            name: "CITY",
            required: true,
            label: "City",
            align: "left",
            field: "CITY",
            sortable: true
          },
          {
            name: "STATE",
            required: true,
            label: "State",
            align: "left",
            field: "STATE",
            sortable: true
          },
          {
            name: "ZIP",
            required: true,
            label: "Zip Code",
            align: "left",
            field: "ZIP",
            sortable: true
          },
          {
            name: "SALSTERR",
            required: true,
            label: "Territory",
            align: "left",
            field: "SALSTERR",
            sortable: true
          },
          {
            name: "INACTIVE",
            required: true,
            label: "Inactive",
            align: "left",
            field: "INACTIVE",
            sortable: true
          },
          {
            name: "HOLD",
            required: true,
            label: "Hold",
            align: "left",
            field: "HOLD",
            sortable: true
          }
        ]
      },
      tableData: []
    };
  },
  methods: {
    mockData() {
      // Thrown together mock page and sort handler
      let page = this.tableDefinition.pagination.page,
        size = this.tableDefinition.pagination.rowsPerPage,
        pageData = [];

      // check our sort and use lodash to mock sort the data
      if (this.tableDefinition.sort.sortBy) {
        pageData = _.orderBy(
          data,
          [this.tableDefinition.sort.sortBy],
          [this.tableDefinition.sort.descending ? "desc" : "asc"]
        );
      } else {
        //
        pageData = data;
      }
      
      this.tableData = pageData.slice((page - 1) * size, (page + 1) * size);
      this.tableDefinition.pagination.totalItems = data.length;
      this.tableDefinition.pagination.totalPages = Math.ceil(data.length/size);
    },
    sortData(data) {
      /////////////////////////////////////////////
      //// server call to handle the sort goes here
      /////////////////////////////////////////////
      this.mockData();
    },
    pageData(data) {
      this.mockData();
      ///////////////////////////////////////////
      //// server call to handle paging goes here
      ///////////////////////////////////////////
    }
  }
});

              
            
!
999px

Console