finish implementing card layout

This commit is contained in:
busti 2019-11-15 20:00:40 +01:00
parent 46be8e8629
commit becae553b8
7 changed files with 157 additions and 39 deletions

View file

@ -1,8 +1,9 @@
<template> <template>
<div id="app"> <div id="app">
<Navbar/> <Navbar/>
<div class="container-fluid px-xl-5 mt-3">
<div class="container mt-2"> <div class="row" v-if="layout === 'table'">
<div class="col-xl-8 offset-xl-2">
<Table <Table
:columns="['uid', 'description', 'box', 'image']" :columns="['uid', 'description', 'box', 'image']"
:items="loadedItems" :items="loadedItems"
@ -10,17 +11,26 @@
/> />
</div> </div>
</div> </div>
<Cards
v-if="layout === 'cards'"
:columns="['uid', 'description', 'box', 'image']"
:items="loadedItems"
:keyName="'uid'"
/>
</div>
</div>
</template> </template>
<script> <script>
import Table from '@/components/Table'; import Table from '@/components/Table';
import Navbar from '@/components/Navbar'; import Navbar from '@/components/Navbar';
import Cards from '@/components/Cards';
import { mapState } from 'vuex'; import { mapState } from 'vuex';
export default { export default {
name: 'app', name: 'app',
components: { Navbar, Table }, components: { Navbar, Table, Cards },
computed: mapState(['loadedItems']) computed: mapState(['loadedItems', 'layout'])
}; };
</script> </script>

72
src/components/Cards.vue Normal file
View file

@ -0,0 +1,72 @@
<template>
<div class="row">
<div class="col-lg-3 col-xl-2">
<div class="card bg-dark text-light mb-2" id="filters">
<div class="card-body">
<h5 class="card-title text-info">Sort & Filter</h5>
<div class="form-group" v-for="(column, index) in columns" :key="index">
<label>{{ column }}</label>
<div class="input-group">
<div class="input-group-prepend">
<button
:class="[ 'btn', column === sortBy ? 'btn-outline-info' : 'btn-outline-secondary' ]"
type="button"
@click="toggleSort(column)">
<font-awesome-icon :icon="getSortIcon(column)"/>
</button>
</div>
<input
type="text"
class="form-control"
placeholder="filter"
:value="filters[column]"
@input="setFilter(column, $event.target.value)"
>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 col-xl-8">
<transition-group name="card-list" tag="div" class="card-columns">
<div class="card-list-item card bg-dark text-light" v-for="item in internalItems" :key="item.uid">
<img
:src="`https://picsum.photos/id/${item.uid + 50}/200/200`"
alt="item"
class="card-img-top img-fluid"
>
<div class="card-body">
<h6 class="card-title">{{ item.description }}</h6>
<h6 class="card-subtitle text-secondary">uid: {{ item.uid }} box: {{ item.box }}</h6>
</div>
</div>
</transition-group>
</div>
</div>
</template>
<script>
import DataContainer from '@/mixins/data-container';
export default {
name: 'Cards',
mixins: [DataContainer],
methods: {
random: () => Math.floor((Math.random() * 500) + 300)
}
};
</script>
<style>
.card-list-item {
transition: all 1s;
}
.card-list-enter, .card-list-leave-to {
opacity: 0;
}
.card-list-leave-active {
position: absolute;
}
</style>

View file

@ -15,6 +15,14 @@
<button type="button" class="btn mx-1 text-nowrap" v-for="(button, index) in buttons" v-bind:key="index" :class="['btn-' + button.color]"> <button type="button" class="btn mx-1 text-nowrap" v-for="(button, index) in buttons" v-bind:key="index" :class="['btn-' + button.color]">
<font-awesome-icon :icon="button.icon"/><span class="d-none d-md-inline">&nbsp;{{ button.title }}</span> <font-awesome-icon :icon="button.icon"/><span class="d-none d-md-inline">&nbsp;{{ button.title }}</span>
</button> </button>
<div class="btn-group btn-group-toggle">
<button :class="['btn', 'btn-info', { active: layout === 'cards' }]" @click="setLayout('cards')">
<font-awesome-icon icon="th"/>
</button>
<button :class="['btn', 'btn-info', { active: layout === 'table' }]" @click="setLayout('table')">
<font-awesome-icon icon="list"/>
</button>
</div>
</div> </div>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
@ -38,7 +46,7 @@
</template> </template>
<script> <script>
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions, mapMutations } from 'vuex';
export default { export default {
@ -48,14 +56,14 @@ export default {
buttons: [ buttons: [
{ title: 'Add', icon: 'plus', color: 'success' }, { title: 'Add', icon: 'plus', color: 'success' },
{ title: 'Refresh', icon: 'sync-alt', color: 'primary' }, { title: 'Refresh', icon: 'sync-alt', color: 'primary' },
{ title: 'Placeholder', icon: 'cat', color: 'warning' },
] ]
}), }),
computed: { computed: {
...mapState(['events', 'activeEvent']) ...mapState(['events', 'activeEvent', 'layout']),
}, },
methods: { methods: {
...mapActions(['changeEvent']) ...mapActions(['changeEvent']),
...mapMutations(['setLayout'])
} }
}; };
</script> </script>

View file

@ -3,10 +3,10 @@
<thead> <thead>
<tr> <tr>
<th scope="col" v-for="(column, index) in columns" :key="index"> <th scope="col" v-for="(column, index) in columns" :key="index">
<button class="btn text-light" v-on:click="toggleSort(column)"> <button class="btn text-light" @click="toggleSort(column)">
{{ column }} {{ column }}
<span :class="{ 'text-info': column === sortBy }"> <span :class="{ 'text-info': column === sortBy }">
<font-awesome-icon :icon="sortIcon(column)"/> <font-awesome-icon :icon="getSortIcon(column)"/>
</span> </span>
</button> </button>
</th> </th>
@ -21,34 +21,11 @@
</template> </template>
<script> <script>
import * as R from 'ramda'; import DataContainer from '@/mixins/data-container';
export default { export default {
name: 'Table', name: 'Table',
props: ['columns', 'items', 'keyName'], mixins: [DataContainer]
data: (self) => ({
sortBy: self.keyName,
ascend: true
}),
computed: {
internalItems() {
const sortByOrd = R.sortBy(R.prop(this.sortBy));
const sorted = sortByOrd(this.items, [this.sortBy]);
return this.ascend ? sorted : R.reverse(sorted);
}
},
methods: {
sortIcon(column) {
if (column !== this.sortBy) return 'sort';
if (this.ascend) return 'sort-up';
return 'sort-down';
},
toggleSort(column) {
if (column === this.sortBy)
this.ascend = !this.ascend;
this.sortBy = column;
}
}
}; };
</script> </script>

View file

@ -9,10 +9,10 @@ import 'bootstrap/dist/js/bootstrap.min.js';
// fontawesome // fontawesome
import { library } from '@fortawesome/fontawesome-svg-core'; import { library } from '@fortawesome/fontawesome-svg-core';
import { faPlus, faCheckCircle, faEdit, faTrash, faCat, faSyncAlt, faSort, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons'; import { faPlus, faCheckCircle, faEdit, faTrash, faCat, faSyncAlt, faSort, faSortUp, faSortDown, faTh, faList } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
library.add(faPlus, faCheckCircle, faEdit, faTrash, faCat, faSyncAlt, faSort, faSortUp, faSortDown); library.add(faPlus, faCheckCircle, faEdit, faTrash, faCat, faSyncAlt, faSort, faSortUp, faSortDown, faTh, faList);
Vue.component('font-awesome-icon', FontAwesomeIcon); Vue.component('font-awesome-icon', FontAwesomeIcon);

View file

@ -0,0 +1,39 @@
import * as R from 'ramda';
export default {
props: ['columns', 'items', 'keyName'],
data: (self) => ({
sortBy: self.keyName,
ascend: true,
filters: R.fromPairs(self.columns.map(column => [column, '']))
}),
computed: {
internalItems() {
const filtered = this.items.filter(item => this.columns
.map(column => {
const field = item[column] + '';
const filter = this.filters[column];
return field.includes(filter);
}).reduce((acc, nxt) => acc && nxt, true)
);
const sortByOrd = R.sortBy(R.prop(this.sortBy));
const sorted = sortByOrd(filtered, [this.sortBy]);
return this.ascend ? sorted : R.reverse(sorted);
}
},
methods: {
getSortIcon(column) {
if (column !== this.sortBy) return 'sort';
if (this.ascend) return 'sort-up';
return 'sort-down';
},
toggleSort(column) {
if (column === this.sortBy)
this.ascend = !this.ascend;
this.sortBy = column;
},
setFilter(column, filter) {
this.filters[column] = filter;
}
}
};

View file

@ -7,6 +7,7 @@ export default new Vuex.Store({
state: { state: {
events: ['35c3', 'camp19', '36c3'], events: ['35c3', 'camp19', '36c3'],
activeEvent: '36c3', activeEvent: '36c3',
layout: 'cards',
loadedItems: [ loadedItems: [
{ uid: 1, description: 'sleeping bag', box: 7, image: 41 }, { uid: 1, description: 'sleeping bag', box: 7, image: 41 },
{ uid: 2, description: 'tent', box: 7, image: 23 }, { uid: 2, description: 'tent', box: 7, image: 23 },
@ -14,16 +15,27 @@ export default new Vuex.Store({
{ uid: 4, description: 'power supply black', box: 5, image: 62 }, { uid: 4, description: 'power supply black', box: 5, image: 62 },
{ uid: 5, description: 'pullover yellow "pesthörnchen"', box: 5, image: 84 }, { uid: 5, description: 'pullover yellow "pesthörnchen"', box: 5, image: 84 },
{ uid: 6, description: '"blue black second skin"', box: 6, image: 72 }, { uid: 6, description: '"blue black second skin"', box: 6, image: 72 },
{ uid: 7, description: '"the bike blog" bottle orange', box: 6, image: 71 } { uid: 7, description: '"the bike blog" bottle orange', box: 6, image: 71 },
{ uid: 8, description: 'tshirt guad3c', box: 6, image: 26 },
{ uid: 9, description: 'power supply dell', box: 6, image: 74 },
{ uid: 10, description: 'blanket green blue', box: 6, image: 25 },
{ uid: 11, description: 'cap "ega"', box: 6, image: 71 },
{ uid: 12, description: 'water bottle blue "sistema"', box: 3, image: 12 },
{ uid: 13, description: 'sun hat black', box: 5, image: 1 },
{ uid: 14, description: 'toy truck', box: 6, image: 51 }
] ]
}, },
mutations: { mutations: {
changeEvent(state, event) { changeEvent(state, event) {
state.activeEvent = event; state.activeEvent = event;
},
setLayout(state, layout) {
state.layout = layout;
} }
}, },
actions: { actions: {
changeEvent({ commit }, event) { changeEvent({ commit }, event) {
// todo: load items from server
// todo: load items from server // todo: load items from server
commit('changeEvent', event); commit('changeEvent', event);
} }