Finish table specs

This commit is contained in:
Marcos Moura 2016-11-14 18:53:06 -02:00
parent 1efaf695c7
commit 0cb64cb8ec
14 changed files with 364 additions and 151 deletions

View file

@ -32,7 +32,7 @@ Vue.component('code-block', CodeBlock);
Vue.use(VueRouter);
let router = new VueRouter({
hashbang: false,
base: '#/',
routes
});

View file

@ -23,7 +23,9 @@
</md-table-row>
</md-table-body>
</md-table>
</demo-example>
<demo-example label="Within cards with Pagination and Inline Edit">
<md-table-card>
<md-toolbar>
<h1 class="md-title">Nutrition</h1>
@ -36,13 +38,14 @@
</md-button>
</md-toolbar>
<md-table md-row-selection md-sort="calories">
<md-table md-sort="dessert" md-sort-type="desc" @select="onSelect" @sort="onSort">
<md-table-header>
<md-table-row>
<md-table-head md-sort-by="dessert">Dessert (100g serving)</md-table-head>
<md-table-head md-sort-by="calories" md-numeric md-tooltip="The total amount of food energy and the given serving size">Calories (g)</md-table-head>
<md-table-head md-sort-by="fat" md-numeric>Fat (g)</md-table-head>
<md-table-head md-sort-by="carbs" md-numeric>Carbs (g)</md-table-head>
<md-table-head md-sort-by="protein" md-numeric>Protein (g)</md-table-head>
<md-table-head>
<md-icon>message</md-icon>
<span>Comments</span>
@ -51,24 +54,49 @@
</md-table-header>
<md-table-body>
<md-table-row v-for="(row, index) in 5" :key="index" md-auto-select>
<md-table-cell>Dessert Name</md-table-cell>
<md-table-cell v-for="(col, index) in 3" :key="index" md-numeric>10</md-table-cell>
<md-table-cell>
<md-table-row v-for="(row, rowIndex) in nutrition" :key="rowIndex" :md-item="row" md-auto-select md-selection>
<md-table-cell v-for="(column, columnIndex) in row" :key="columnIndex" :md-numeric="columnIndex !== 'dessert' && columnIndex !== 'comment'" v-if="columnIndex !== 'type'">
<md-table-edit
v-model="comment"
:md-name="comment + index"
:md-id="comment + index"
:md-name="'comment' + columnIndex"
:md-id="'comment' + columnIndex"
md-placeholder="Add a comment"
md-maxlength="120"></md-table-edit>
md-maxlength="120"
v-model="nutrition[rowIndex].comment"
v-if="columnIndex === 'comment'"></md-table-edit>
<span v-if="columnIndex !== 'comment'">{{ column }}</span>
</md-table-cell>
</md-table-row>
</md-table-body>
</md-table>
<md-table-pagination></md-table-pagination>
<md-table-pagination
md-size="10"
md-total="5"
md-page="1"
md-label="Rows"
md-separator="of"
:md-page-options="[5, 10, 25, 50]"
@pagination="onPagination"></md-table-pagination>
</md-table-card>
<div class="output">
<h2 class="md-title">Selected Data</h2>
<pre>{{ selectedData }}</pre>
</div>
<div class="output">
<h2 class="md-title">Current Sort</h2>
<pre>{{ sort }}</pre>
</div>
<div class="output">
<h2 class="md-title">Current Pagination</h2>
<pre>{{ page }}</pre>
</div>
</demo-example>
<demo-example label="Inline Menus and Edit Icon">
<md-table-card>
<md-toolbar>
<h1 class="md-title">Nutrition</h1>
@ -91,7 +119,7 @@
</md-button>
</md-table-alternate-header>
<md-table md-row-selection md-sort="calories">
<md-table md-sort="calories">
<md-table-header>
<md-table-row>
<md-table-head md-sort-by="dessert">Dessert (100g serving)</md-table-head>
@ -99,6 +127,7 @@
<md-table-head md-sort-by="calories" md-numeric md-tooltip="The total amount of food energy and the given serving size">Calories (g)</md-table-head>
<md-table-head md-sort-by="fat" md-numeric>Fat (g)</md-table-head>
<md-table-head md-sort-by="carbs" md-numeric>Carbs (g)</md-table-head>
<md-table-head md-sort-by="protein" md-numeric>Protein (g)</md-table-head>
<md-table-head>
<md-icon>message</md-icon>
<span>Comments</span>
@ -107,21 +136,26 @@
</md-table-header>
<md-table-body>
<md-table-row v-for="(row, index) in 5" :key="index">
<md-table-cell>Dessert Name</md-table-cell>
<md-table-cell>
<md-select placeholder="Type" :name="'type' + index" :id="'type' + index" v-model="type">
<md-option value="Ice Cream">Ice Cream</md-option>
<md-option value="Pastry">Pastry</md-option>
<md-option value="Other">Other</md-option>
</md-select>
</md-table-cell>
<md-table-cell v-for="(col, index) in 3" :key="index" md-numeric>10</md-table-cell>
<md-table-cell>
<span>Super tasty</span>
<md-button class="md-icon-button">
<md-table-row v-for="(row, rowIndex) in nutrition" :key="rowIndex" :md-item="row" md-selection>
<md-table-cell v-for="(column, columnIndex) in row" :key="columnIndex" :md-numeric="columnIndex !== 'dessert' && columnIndex !== 'comment' && columnIndex !== 'type'">
<span v-if="columnIndex === 'comment'">{{ column }}</span>
<md-button class="md-icon-button" v-if="columnIndex === 'comment'">
<md-icon>edit</md-icon>
</md-button>
<md-select
placeholder="Type"
:name="'type' + columnIndex"
:id="'type' + columnIndex"
v-model="nutrition[rowIndex].type"
v-if="columnIndex === 'type'">
<md-option value="ice_cream">Ice Cream</md-option>
<md-option value="pastry">Pastry</md-option>
<md-option value="other">Other</md-option>
</md-select>
<span v-if="columnIndex !== 'type' && columnIndex !== 'comment'">{{ column }}</span>
</md-table-cell>
</md-table-row>
</md-table-body>
@ -145,15 +179,81 @@
.md-table-card + .md-table-card {
margin-top: 24px;
}
.output {
margin-top: 24px;
.md-title {
font-size: 20px;
}
}
</style>
<script>
export default {
data() {
return {
comment: null,
type: null
};
data: () => ({
nutrition: [
{
dessert: 'Frozen yogurt',
type: 'ice_cream',
calories: '159',
fat: '6.0',
carbs: '24',
protein: '4.0',
comment: 'Icy'
},
{
dessert: 'Ice cream sandwich',
type: 'ice_cream',
calories: '237',
fat: '9.0',
carbs: '37',
protein: '4.3',
comment: 'Super Tasty'
},
{
dessert: 'Eclair',
type: 'pastry',
calories: '262',
fat: '16.0',
carbs: '24',
protein: '6.0',
comment: ''
},
{
dessert: 'Cupcake',
type: 'pastry',
calories: '305',
fat: '3.7',
carbs: '67',
protein: '4.3',
comment: ''
},
{
dessert: 'Gingerbread',
type: 'other',
calories: '356',
fat: '16.0',
carbs: '49',
protein: '3.9',
comment: ''
}
],
selectedData: [],
sort: {},
page: {}
}),
methods: {
onSelect(data) {
this.selectedData = data;
this.$forceUpdate();
},
onSort(sort) {
this.sort = sort;
},
onPagination(page) {
this.page = page;
}
}
};
</script>

View file

@ -1,10 +1,10 @@
export default {
props: {
value: [String, Number],
value: String,
disabled: Boolean,
required: Boolean,
maxlength: [String, Number],
placeholder: [String, Number]
maxlength: [Number, String],
placeholder: String
},
watch: {
value() {

View file

@ -30,7 +30,7 @@
}),
computed: {
isSelected() {
return this.value === this.parentSelect.value;
return this.value && this.parentSelect.value && this.value.toString() === this.parentSelect.value.toString();
},
classes() {
return {
@ -40,16 +40,16 @@
}
},
methods: {
selectOption() {
selectOption(changed) {
if (!this.parentSelect.multiple) {
this.parentSelect.selectOption(this.value, this.$refs.item.textContent);
this.parentSelect.selectOption(this.value, this.$refs.item.textContent, changed);
} else {
this.check = !this.check;
}
},
selectIfValueMatches() {
if (this.isSelected) {
this.selectOption();
this.selectOption(true);
}
}
},

View file

@ -128,21 +128,6 @@
margin-left: -16px;
}
&.md-direction-bottom-left {
margin-top: -15px;
margin-left: 16px;
}
&.md-direction-top-right {
margin-top: 15px;
margin-left: -16px;
}
&.md-direction-top-left {
margin-top: 15px;
margin-left: 16px;
}
.md-menu-item .md-list-item-holder {
overflow: visible;
justify-content: flex-start;

View file

@ -1,7 +1,7 @@
<template>
<div class="md-select" :class="classes">
<md-menu :md-close-on-select="!multiple">
<span class="md-select-value" md-menu-trigger ref="value">{{ selectedValue || multiplevalue }}</span>
<span class="md-select-value" md-menu-trigger ref="value">{{ selectedValue || multiplevalue || placeholder }}</span>
<md-menu-content class="md-select-content" :class="contentClasses">
<slot></slot>
@ -26,7 +26,9 @@
multiple: Boolean,
value: [String, Number, Array],
id: String,
disabled: Boolean
disabled: Boolean,
placeholder: String,
mdMenuClass: String
},
data() {
return {
@ -43,15 +45,18 @@
};
},
contentClasses() {
return {
'md-multiple': this.multiple
};
if (this.multiple) {
return 'md-multiple ' + this.mdMenuClass;
}
return this.mdMenuClass;
}
},
methods: {
changeValue(value, parentValue) {
this.$emit('change', value);
this.$emit('input', value);
changeValue(value, parentValue, changed) {
if (changed) {
this.$emit('change', value);
}
if (this.parentContainer) {
this.$parent.setValue(parentValue || value);
@ -76,9 +81,9 @@
this.multiplevalue = output.join(', ');
this.changeValue(values, this.multiplevalue);
},
selectOption(value, text) {
selectOption(value, text, changed) {
this.selectedValue = text;
this.changeValue(value);
this.changeValue(value, null, changed);
}
},
mounted() {

View file

@ -296,42 +296,6 @@
}
}
.md-table-pagination {
height: 56px;
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
border-top: 1px solid #e0e0e0;
color: rgba(#000, .54);
font-size: 12px;
.md-table-pagination-previous {
margin-right: 2px;
margin-left: 18px;
}
.md-select {
width: auto;
min-width: 36px;
margin: 0 32px;
&:after {
margin-top: 0;
}
.md-select-value {
padding: 0;
border: none;
font-size: 13px;
}
}
.md-button {
color: rgba(#000, .87);
}
}
.md-table-card {
overflow: visible;
@ -344,6 +308,60 @@
flex: 1;
font-size: 20px;
}
.md-table-pagination {
height: 56px;
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
border-top: 1px solid #e0e0e0;
color: rgba(#000, .54);
font-size: 12px;
.md-table-pagination-previous {
margin-right: 2px;
margin-left: 18px;
}
.md-select {
width: auto;
min-width: 36px;
margin: 0 32px;
&:after {
margin-top: 0;
}
.md-select-value {
padding: 0;
border: none;
font-size: 13px;
}
}
.md-button {
&:not([disabled]) {
color: rgba(#000, .87);
}
&[disabled] {
.md-icon {
color: rgba(#000, .26);
}
}
}
}
}
.md-pagination-select {
&.md-direction-bottom-right {
margin-top: -16px;
}
.md-list-item-holder {
font-size: 13px;
}
}
.md-table-alternate-header {

View file

@ -1,6 +1,7 @@
.THEME_NAME {
.md-table,
&.md-table {
}
.md-table-card,

View file

@ -9,9 +9,10 @@
<style lang="scss" src="./mdTable.scss"></style>
<script>
import getClosestVueParent from '../../core/utils/getClosestVueParent';
export default {
props: {
mdRowSelection: Boolean,
mdSortType: String,
mdSort: String
},
@ -19,6 +20,8 @@
return {
sortType: this.mdSortType,
sortBy: this.mdSort,
hasRowSelection: false,
data: [],
numberOfRows: 0,
numberOfSelected: 0,
selectedRows: {}
@ -27,12 +30,20 @@
methods: {
emitSort(name) {
this.sortBy = name;
this.$emit('sort', name);
this.$emit('sort', {
name,
type: this.sortType
});
},
emitSelection() {
this.$emit('select', this.selectedRows);
}
},
mounted() {
if (this.$parent.$el.classList.contains('md-table-card')) {
this.$parent.tableInstance = this;
this.parentCard = getClosestVueParent(this.$parent, 'md-table-card');
if (this.parentCard) {
this.parentCard.tableInstance = this;
}
}
};

View file

@ -12,6 +12,8 @@
</template>
<script>
import getClosestVueParent from '../../core/utils/getClosestVueParent';
export default {
props: {
mdSelectedLabel: String
@ -23,8 +25,10 @@
};
},
mounted() {
this.parentCard = getClosestVueParent(this.$parent, 'md-table-card');
this.$nextTick(() => {
this.tableInstance = this.$parent.tableInstance;
this.tableInstance = this.parentCard.tableInstance;
this.$watch('tableInstance.numberOfSelected', () => {
this.$refs.counter.textContent = this.tableInstance.numberOfSelected;

View file

@ -22,11 +22,11 @@
<script>
export default {
props: {
value: [String, Number, Boolean],
value: String,
mdLarge: Boolean,
mdId: [String, Number, Boolean],
mdName: [String, Number, Boolean],
mdPlaceholder: [String, Number, Boolean],
mdId: String,
mdName: String,
mdPlaceholder: String,
mdMaxlength: [Number, String]
},
data() {
@ -45,6 +45,9 @@
'md-active': this.active,
'md-large': this.mdLarge
};
},
realValue() {
console.log(this.value);
}
},
methods: {

View file

@ -13,6 +13,8 @@
</template>
<script>
import getClosestVueParent from '../../core/utils/getClosestVueParent';
export default {
props: {
mdNumeric: Boolean,
@ -22,12 +24,13 @@
data() {
return {
sortType: null,
sorted: false
sorted: false,
parentTable: {}
};
},
computed: {
classes() {
let matchSort = this.$parent.$parent.sortBy === this.mdSortBy;
const matchSort = this.hasMatchSort();
if (!matchSort) {
this.sorted = false;
@ -42,19 +45,30 @@
}
},
methods: {
hasMatchSort() {
return this.parentTable.sortBy === this.mdSortBy;
},
changeSort() {
let parent = this.$parent.$parent;
if (this.mdSortBy) {
if (this.sortType === 'asc' && this.sorted) {
this.sortType = 'desc';
} else {
this.sortType = 'asc';
}
if (this.sortType === 'asc' && this.sorted) {
this.sortType = 'desc';
} else {
this.sortType = 'asc';
this.sorted = true;
this.parentTable.sortType = this.sortType;
this.parentTable.emitSort(this.mdSortBy);
}
}
},
mounted() {
this.parentTable = getClosestVueParent(this.$parent, 'md-table');
if (this.hasMatchSort()) {
this.sorted = true;
parent.sortType = this.sortType;
parent.emitSort(this.mdSortBy);
this.sortType = this.parentTable.sortType;
}
}
};

View file

@ -1,21 +1,18 @@
<template>
<div class="md-table-pagination">
<span class="md-table-pagination-label">Rows per page:</span>
<span class="md-table-pagination-label">{{ mdLabel }}:</span>
<md-select v-model="currentSize" @change="$emit('size')">
<md-option value="10">10</md-option>
<md-option value="25">25</md-option>
<md-option value="50">50</md-option>
<md-option value="100">100</md-option>
<md-select v-model="currentSize" md-menu-class="md-pagination-select" @change="changeSize" v-if="mdPageOptions">
<md-option v-for="amount in mdPageOptions" :value="amount">{{ amount }}</md-option>
</md-select>
<span>1-10 of {{ mdTotal }}</span>
<span>{{ (currentSize - currentSize + 1) * currentPage }}-{{ currentSize }} {{ mdSeparator }} {{ mdTotal }}</span>
<md-button class="md-icon-button md-table-pagination-previous">
<md-button class="md-icon-button md-table-pagination-previous" @click="changePage" :disabled="currentPage === 1">
<md-icon>keyboard_arrow_left</md-icon>
</md-button>
<md-button class="md-icon-button md-table-pagination-next">
<md-button class="md-icon-button md-table-pagination-next" @click="changePage" :disabled="currentSize * currentPage >= mdTotal">
<md-icon>keyboard_arrow_right</md-icon>
</md-button>
</div>
@ -25,23 +22,66 @@
export default {
props: {
mdSize: {
type: Number,
type: [Number, String],
default: 10
},
mdPageOptions: [Array, Boolean],
mdPage: {
type: Number,
type: [Number, String],
default: 1
},
mdTotal: {
type: [Number, String],
default: 'Many'
},
mdLabel: {
type: String,
default: 'Rows per page'
},
mdSeparator: {
type: String,
default: 'of'
}
},
data() {
return {
currentSize: this.mdSize,
currentPage: this.mdPage
currentSize: parseInt(this.mdSize, 10),
currentPage: parseInt(this.mdPage, 10)
};
},
computed: {
lastPage() {
return false;
}
},
methods: {
emitPaginationEvent() {
if (this.canFireEvents) {
this.$emit('pagination', {
size: this.currentSize,
page: this.currentPage
});
}
},
changeSize() {
if (this.canFireEvents) {
this.$emit('size', this.currentSize);
this.emitPaginationEvent();
}
},
changePage() {
if (this.canFireEvents) {
this.$emit('page', this.currentPage);
this.emitPaginationEvent();
}
}
},
mounted() {
this.$nextTick(() => {
this.mdPageOptions = this.mdPageOptions || [10, 25, 50, 100];
this.currentSize = this.mdPageOptions[0];
this.canFireEvents = true;
});
}
};
</script>

View file

@ -1,7 +1,7 @@
<template>
<tr class="md-table-row" :class="classes" @click="autoSelect">
<md-table-cell class="md-table-selection" v-if="$parent.mdRowSelection">
<md-checkbox v-model="checkbox" @change="select"></md-checkbox>
<md-table-cell class="md-table-selection" v-if="hasSelection">
<md-checkbox v-model="checkbox" :disabled="isDisabled" @change="select"></md-checkbox>
</md-table-cell>
<slot></slot>
@ -9,19 +9,31 @@
</template>
<script>
import getClosestVueParent from '../../core/utils/getClosestVueParent';
const transitionClass = 'md-transition-off';
export default {
props: {
mdAutoSelect: Boolean
mdAutoSelect: Boolean,
mdSelection: Boolean,
mdItem: Object
},
data() {
return {
parentTable: {},
headRow: false,
checkbox: false,
index: 0
};
},
computed: {
isDisabled() {
return !this.mdSelection && !this.headRow;
},
hasSelection() {
return this.mdSelection || this.headRow && this.parentTable.hasRowSelection;
},
classes() {
return {
'md-selected': this.checkbox
@ -31,58 +43,78 @@
methods: {
setSelectedRow(value, index) {
if (value) {
this.$parent.selectedRows[index] = value;
++this.$parent.numberOfSelected;
this.parentTable.selectedRows[index] = this.parentTable.data[index];
++this.parentTable.numberOfSelected;
} else {
delete this.$parent.selectedRows[index];
--this.$parent.numberOfSelected;
delete this.parentTable.selectedRows[index];
--this.parentTable.numberOfSelected;
}
},
handleSingleSelection(value) {
this.setSelectedRow(value, this.index);
this.$parent.$children[0].checkbox = this.$parent.numberOfSelected === this.$parent.numberOfRows;
this.setSelectedRow(value, this.index - 1);
this.parentTable.$children[0].checkbox = this.parentTable.numberOfSelected === this.parentTable.numberOfRows;
},
handleMultipleSelection(value) {
if (this.$parent.numberOfRows > 25) {
this.$parent.$el.classList.add(transitionClass);
if (this.parentTable.numberOfRows > 25) {
this.parentTable.$el.classList.add(transitionClass);
}
this.$parent.$children.forEach((row) => {
this.parentTable.$children.forEach((row, index) => {
row.checkbox = value;
if (!row.headRow) {
this.setSelectedRow(value, index - 1);
}
});
if (value) {
/*this.$parent.selectedRows = {}; //and so on, this can be lazly created the first time or on component boot*/
this.$parent.numberOfSelected = this.$parent.numberOfRows;
this.parentTable.numberOfSelected = this.parentTable.numberOfRows;
} else {
/*this.$parent.selectedRows = {};*/
this.$parent.numberOfSelected = 0;
this.parentTable.numberOfSelected = 0;
}
window.setTimeout(() => this.$parent.$el.classList.remove(transitionClass));
window.setTimeout(() => this.parentTable.$el.classList.remove(transitionClass));
},
select(value) {
if (this.$parent.mdRowSelection) {
if (this.hasSelection) {
if (this.headRow) {
this.handleMultipleSelection(value);
} else {
this.handleSingleSelection(value);
}
this.parentTable.emitSelection();
}
},
autoSelect() {
if (this.mdAutoSelect) {
if (this.mdAutoSelect && this.hasSelection) {
this.checkbox = !this.checkbox;
this.handleSingleSelection(this.checkbox);
this.parentTable.emitSelection();
}
}
},
watch: {
data() {
this.parentTable.data[this.index] = this.item;
}
},
mounted() {
this.parentTable = getClosestVueParent(this.$parent, 'md-table');
if (this.$el.parentNode.tagName.toLowerCase() === 'thead') {
this.headRow = true;
} else {
this.$parent.numberOfRows++;
this.index = this.$parent.numberOfRows;
this.parentTable.numberOfRows++;
this.index = this.parentTable.numberOfRows;
if (this.mdSelection) {
this.parentTable.hasRowSelection = true;
}
if (this.mdItem) {
this.parentTable.data.push(this.mdItem);
}
}
}
};