add row actions

This commit is contained in:
Anton Schubert 2017-08-29 23:10:03 +02:00
parent 76704b2c82
commit 82569aba22
11 changed files with 331 additions and 185 deletions

View File

@ -302,6 +302,48 @@
"code": 61702, "code": 61702,
"src": "fontawesome" "src": "fontawesome"
}, },
{
"uid": "e99461abfef3923546da8d745372c995",
"css": "cog",
"code": 59395,
"src": "fontawesome"
},
{
"uid": "d35a1d35efeb784d1dc9ac18b9b6c2b6",
"css": "pencil",
"code": 59396,
"src": "fontawesome"
},
{
"uid": "c8585e1e5b0467f28b70bce765d5840c",
"css": "docs",
"code": 61637,
"src": "fontawesome"
},
{
"uid": "7fd683b2c518ceb9e5fa6757f2276faa",
"css": "eye-off",
"code": 59397,
"src": "fontawesome"
},
{
"uid": "5211af474d3a9848f67f945e2ccaf143",
"css": "cancel",
"code": 59398,
"src": "fontawesome"
},
{
"uid": "12f4ece88e46abd864e40b35e05b11cd",
"css": "ok",
"code": 59399,
"src": "fontawesome"
},
{
"uid": "f48ae54adfb27d8ada53d0fd9e34ee10",
"css": "trash-empty",
"code": 59400,
"src": "fontawesome"
},
{ {
"uid": "efb8e2c22ada5ce5b02631e8b4be473d", "uid": "efb8e2c22ada5ce5b02631e8b4be473d",
"css": "ion-alert-circled", "css": "ion-alert-circled",

Binary file not shown.

View File

@ -12,6 +12,20 @@
<glyph glyph-name="key" unicode="&#xe802;" d="M464 564q0 45-31 76t-76 31-76-31-31-76q0-23 11-46-23 11-47 11-44 0-76-32t-31-76 31-75 76-32 76 32 31 75q0 24-10 47 23-11 46-11 45 0 76 31t31 76z m475-393q0-9-27-36t-37-28q-5 0-16 9t-20 19-22 22-13 14l-54-53 123-123q15-16 15-38 0-23-21-45t-46-22q-22 0-37 16l-375 374q-98-73-204-73-91 0-148 57t-57 149q0 89 53 174t138 139 175 53q91 0 148-58t57-148q0-105-73-203l198-199 54 54q-2 2-14 14t-23 21-18 21-9 15q0 10 27 37t37 28q7 0 13-6 3-3 26-25t45-44 49-48 40-44 16-23z" horiz-adv-x="1000" /> <glyph glyph-name="key" unicode="&#xe802;" d="M464 564q0 45-31 76t-76 31-76-31-31-76q0-23 11-46-23 11-47 11-44 0-76-32t-31-76 31-75 76-32 76 32 31 75q0 24-10 47 23-11 46-11 45 0 76 31t31 76z m475-393q0-9-27-36t-37-28q-5 0-16 9t-20 19-22 22-13 14l-54-53 123-123q15-16 15-38 0-23-21-45t-46-22q-22 0-37 16l-375 374q-98-73-204-73-91 0-148 57t-57 149q0 89 53 174t138 139 175 53q91 0 148-58t57-148q0-105-73-203l198-199 54 54q-2 2-14 14t-23 21-18 21-9 15q0 10 27 37t37 28q7 0 13-6 3-3 26-25t45-44 49-48 40-44 16-23z" horiz-adv-x="1000" />
<glyph glyph-name="cog" unicode="&#xe803;" d="M571 350q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" />
<glyph glyph-name="pencil" unicode="&#xe804;" d="M203-7l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
<glyph glyph-name="eye-off" unicode="&#xe805;" d="M310 105l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" />
<glyph glyph-name="cancel" unicode="&#xe806;" d="M724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
<glyph glyph-name="ok" unicode="&#xe807;" d="M933 534q0-22-16-38l-404-404-76-76q-16-15-38-15t-38 15l-76 76-202 202q-15 16-15 38t15 38l76 76q16 16 38 16t38-16l164-165 366 367q16 16 38 16t38-16l76-76q16-15 16-38z" horiz-adv-x="1000" />
<glyph glyph-name="trash-empty" unicode="&#xe808;" d="M286 439v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m143 0v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m142 0v-321q0-8-5-13t-12-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q7 0 12-5t5-13z m72-404v529h-500v-529q0-12 4-22t8-15 6-5h464q2 0 6 5t8 15 4 22z m-375 601h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q23 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" />
<glyph glyph-name="docs" unicode="&#xf0c5;" d="M946 636q23 0 38-16t16-38v-678q0-23-16-38t-38-16h-535q-23 0-38 16t-16 38v160h-303q-23 0-38 16t-16 38v375q0 22 11 49t27 42l228 228q15 16 42 27t49 11h232q23 0 38-16t16-38v-183q38 23 71 23h232z m-303-119l-167-167h167v167z m-357 214l-167-167h167v167z m109-361l176 176v233h-214v-233q0-22-15-37t-38-16h-233v-357h286v143q0 22 11 49t27 42z m534-449v643h-215v-232q0-22-15-38t-38-15h-232v-358h500z" horiz-adv-x="1000" />
<glyph glyph-name="menu" unicode="&#xf0c9;" d="M857 100v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" /> <glyph glyph-name="menu" unicode="&#xf0c9;" d="M857 100v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />
<glyph glyph-name="angle-left" unicode="&#xf104;" d="M350 546q0-7-6-12l-219-220 219-219q6-6 6-13t-6-13l-28-28q-5-5-12-5t-13 5l-260 261q-6 5-6 12t6 13l260 260q5 6 13 6t12-6l28-28q6-5 6-13z" horiz-adv-x="357.1" /> <glyph glyph-name="angle-left" unicode="&#xf104;" d="M350 546q0-7-6-12l-219-220 219-219q6-6 6-13t-6-13l-28-28q-5-5-12-5t-13 5l-260 261q-6 5-6 12t6 13l260 260q5 6 13 6t12-6l28-28q6-5 6-13z" horiz-adv-x="357.1" />

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -11,6 +11,7 @@
</div> </div>
<div v-else>{{ col.heading }}</div> <div v-else>{{ col.heading }}</div>
</div> </div>
<div class="table_cell cell_settings" v-if="actions.length"></div>
</div> </div>
</div> </div>
<div class="table_content" is="transition-group" name="list"> <div class="table_content" is="transition-group" name="list">
@ -19,6 +20,25 @@
<div v-if="col.prop">{{ getProp(row, col.prop) }}</div> <div v-if="col.prop">{{ getProp(row, col.prop) }}</div>
<div v-else-if="col.render">{{ col.render(row) }}</div> <div v-else-if="col.render">{{ col.render(row) }}</div>
</div> </div>
<div class="table_cell cell_settings" v-if="actions.length" ref="row_actions">
<div title="Open actions" class="actions_btn" @click="openActions">
<i class="icon icon-cog" aria-hidden="true"></i>
</div>
<ul class="actions_container">
<li v-for="(a, i) in actions">
<div :title="a.title" @click="a.action(row)">
<i :class="['icon', a.icon]"></i>
</div>
</li>
<li>
<div title="Close actions" @click="closeActions">
<i class="icon icon-cog"></i>
</div>
</li>
</ul>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -28,11 +48,11 @@
export default { export default {
name: 'My-Table', name: 'My-Table',
props: { props: {
currentOrderBy: { actions: {
type: String type: Array,
}, default() {
currentOrderDesc: { return [ ];
type: Boolean }
}, },
columns: { columns: {
type: Array, type: Array,
@ -45,6 +65,12 @@
default() { default() {
return [ ]; return [ ];
} }
},
currentOrderBy: {
type: String
},
currentOrderDesc: {
type: Boolean
} }
}, },
data() { data() {
@ -68,7 +94,19 @@
} }
}); });
return val; return val;
} },
openActions(event) {
const parent = event.currentTarget.parentNode;
this.$refs.row_actions.forEach((item) => {
if (item !== parent)
item.classList.remove("open")
});
parent.classList.add("open");
},
closeActions(event) {
const parent = event.currentTarget.parentNode.parentNode.parentNode;
parent.classList.remove("open");
}
} }
} }
</script> </script>
@ -150,44 +188,69 @@
*/ */
/* === Cell settings === */ /* === Cell settings === */
/*.cell_settings {text-align:right; width:150px; position:relative; right:0px;} .cell_settings {
.cell_settings a {color:#000; list-style-type:none;} .clearfix();
.cell_settings .fa-cog {font-size:20px;} position:relative;
.cell_settings_inner { width: 40px;
position:absolute; text-align: right;
width:205px; height:40px;
top:50%;
right:-3px;
margin-top:-21px;
border-radius:20px;
background:#e0e0e0;
padding:0px 10px;
z-index:-1;
overflow:hidden;
opacity:0;
-webkit-opacity:0;
-moz-opacity:0;
transition:all 0.3s;
}
.cell_settings_inner_container {float:right;}
.cell_settings_inner_section {float:left; border-right:solid 1px #999; padding:0px 10px;}
.cell_settings_inner_section_last {border:none; padding-right:3px;}
.cell_settings_inner_section ul {list-style-type:none;}
.cell_settings_inner_section ul li {float:left; padding-right:7px;}
.cell_settings_inner_section_last ul li {padding:0px;}
.cell_settings_inner_section ul li a {color:#000;}
.cell_settings_inner_section ul li .fa {line-height:40px; font-size:20px;}
.cell_settings_click, .cell_settings_close {cursor:pointer;} .actions_btn{
.cell_settings a:hover, .cell_settings_click:hover {color:#28b78d!important;} z-index: 20;
&:hover{
cursor: pointer;
color: @cms_brand_primary;
}
}
.cell_settings_inner_open { .icon {
opacity:1; font-size: 16px;
-webkit-opacity:1; }
-moz-opacity:1;
width:205px; .actions_container {
z-index:600; position: absolute;
transition:all 0.3s; z-index: -1;
}*/ top: -5px;
right: 0;
padding: 5px 10px;
opacity: 0;
overflow: hidden;
border-radius: 20px;
background: @cms_bg_lighter;
transition: all 0.3s;
white-space: nowrap;
li {
display: inline-block;
&:last-child{
border-left: 1px solid @text_color;
padding-left: 5px;
.icon:before{
margin-right: 0;
}
}
}
li+li{
margin-left: 5px;
}
.icon {
color: @text_color;
&:hover{
cursor: pointer;
color: @cms_brand_primary;
}
}
}
&.open {
.actions_container{
opacity: 1;
z-index: 1;
transition: all 0.3s;
}
}
}
} }
</style> </style>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="Scroll-Table"> <div class="Scroll-Table">
<my-input placeholder="Suche" type="text" @change="searchChanged" v-model="currentSearch"></my-input> <my-input placeholder="Suche" type="text" @change="searchChanged" v-model="currentSearch"></my-input>
<my-table :columns="columns" :rows="rows" <my-table :actions="actions" :columns="columns" :rows="rows"
:currentOrderBy="currentOrderBy" :currentOrderBy="currentOrderBy"
:currentOrderDesc="currentOrderDesc" :currentOrderDesc="currentOrderDesc"
@sort="sort"> @sort="sort">
@ -20,127 +20,132 @@ import MyInput from './my-input.vue';
import { ObserveVisibility } from 'vue-observe-visibility/dist/vue-observe-visibility'; import { ObserveVisibility } from 'vue-observe-visibility/dist/vue-observe-visibility';
export default { export default {
name: "ScrollTable", name: "ScrollTable",
directives: { directives: {
ObserveVisibility ObserveVisibility
}, },
components: { components: {
MyTable, MyTable,
MyInput MyInput
}, },
props: { props: {
columns: { actions: {
type: Array, type: Array,
default() { default() {
return [ ]; return [ ];
} }
}, },
newRows: { columns: {
type: Array, type: Array,
default() { default() {
return [ ]; return [ ];
} }
}, },
offset: { newRows: {
type: Number, type: Array,
default: 0 default() {
}, return [ ];
limit: { }
type: Number, },
default: 10 offset: {
}, type: Number,
orderBy: { default: 0
type: String },
}, limit: {
orderDesc: { type: Number,
type: Boolean, default: 10
default: false },
}, orderBy: {
search: { type: String
type: String },
}, orderDesc: {
loading: { type: Boolean,
type: Boolean, default: false
default: false },
}, search: {
hasMore: { type: String
type: Boolean, },
default: true loading: {
}, type: Boolean,
handler: { default: false
type: Function, },
default: () => {} hasMore: {
} type: Boolean,
}, default: true
watch: { },
newRows(rows) { handler: {
rows.forEach(row => { type: Function,
this.rowsToPush.push(row); default: () => {}
}); }
this.currentOffset += rows.length; },
watch: {
newRows(rows) {
rows.forEach(row => {
this.rowsToPush.push(row);
});
this.currentOffset += rows.length;
this.pushRows(); // for transition this.pushRows(); // for transition
}, },
loading(l) { loading(l) {
if (!l) { if (!l) {
// delay after loading for visibility observer // delay after loading for visibility observer
setTimeout(() => { setTimeout(() => {
this.loadingDelayed = false; this.loadingDelayed = false;
}, 200); }, 200);
} else { } else {
this.loadingDelayed = true; this.loadingDelayed = true;
} }
} }
}, },
data() { data() {
return { return {
rows: [ ], rows: [ ],
rowsToPush: [ ], rowsToPush: [ ],
currentOffset: this.offset, currentOffset: this.offset,
currentOrderBy: this.orderBy, currentOrderBy: this.orderBy,
currentOrderDesc: this.orderDesc, currentOrderDesc: this.orderDesc,
currentSearch: this.search, currentSearch: this.search,
loadingDelayed: false, loadingDelayed: false,
reloadTriggered: false reloadTriggered: false
} }
}, },
methods: { methods: {
visibilityChanged(isVisible, entry) { visibilityChanged(isVisible, entry) {
if (isVisible) { if (isVisible) {
// infinite scrolling // infinite scrolling
this.reloadTriggered = false; this.reloadTriggered = false;
this.handler(this.currentOffset, this.limit, this.currentOrderBy, this.currentOrderDesc, this.currentSearch); this.handler(this.currentOffset, this.limit, this.currentOrderBy, this.currentOrderDesc, this.currentSearch);
} }
}, },
pushRows() { pushRows() {
let row = this.rowsToPush.shift(); let row = this.rowsToPush.shift();
if (row) { if (row) {
this.rows.push(row); this.rows.push(row);
setTimeout(() => { setTimeout(() => {
this.pushRows(); this.pushRows();
}, 20); }, 20);
} }
}, },
clear() { clear() {
this.currentOffset = 0; this.currentOffset = 0;
this.rows = []; this.rows = [];
this.rowsToPush = []; this.rowsToPush = [];
}, },
sort(e) { sort(e) {
if (e.orderBy == this.currentOrderBy) { if (e.orderBy == this.currentOrderBy) {
this.currentOrderDesc = !this.currentOrderDesc; this.currentOrderDesc = !this.currentOrderDesc;
} else { } else {
this.currentOrderDesc = false; this.currentOrderDesc = false;
} }
this.currentOrderBy = e.orderBy; this.currentOrderBy = e.orderBy;
this.reloadTriggered = true; this.reloadTriggered = true;
this.clear(); this.clear();
}, },
searchChanged(e) { searchChanged(e) {
this.currentSearch = e; this.currentSearch = e;
this.reloadTriggered = true; this.reloadTriggered = true;
this.clear(); this.clear();
} }
} }
} }
</script> </script>

View File

@ -1,7 +1,7 @@
@import "mixins"; @import "mixins";
@iconfont_path: "~/assets/fonts/"; @iconfont_path: "~/assets/fonts/";
@iconfont_version: 3; @iconfont_version: 4;
@font-face { @font-face {
@ -19,6 +19,13 @@
.icon-search:before { content: '\e800'; } /* '' */ .icon-search:before { content: '\e800'; } /* '' */
.icon-user:before { content: '\e801'; } /* '' */ .icon-user:before { content: '\e801'; } /* '' */
.icon-key:before { content: '\e802'; } /* '' */ .icon-key:before { content: '\e802'; } /* '' */
.icon-cog:before { content: '\e803'; } /* '' */
.icon-pencil:before { content: '\e804'; } /* '' */
.icon-eye-off:before { content: '\e805'; } /* '' */
.icon-cancel:before { content: '\e806'; } /* '' */
.icon-ok:before { content: '\e807'; } /* '' */
.icon-trash-empty:before { content: '\e808'; } /* '' */
.icon-docs:before { content: '\f0c5'; } /* '' */
.icon-menu:before { content: '\f0c9'; } /* '' */ .icon-menu:before { content: '\f0c9'; } /* '' */
.icon-angle-left:before { content: '\f104'; } /* '' */ .icon-angle-left:before { content: '\f104'; } /* '' */
.icon-angle-right:before { content: '\f105'; } /* '' */ .icon-angle-right:before { content: '\f105'; } /* '' */

View File

@ -3,6 +3,9 @@
@screen-md: 1024px; @screen-md: 1024px;
@screen-lg: 1200px; @screen-lg: 1200px;
@text_color: black;
@cms_bg_lighter: #e0e0e0;
@cms_bg_light: #1a2e3b; @cms_bg_light: #1a2e3b;
@cms_dark_border: #152129; @cms_dark_border: #152129;

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="Userlist"> <div class="Userlist">
<h1>Userlist</h1> <h1>Userlist</h1>
<scroll-table :columns="columns" :new-rows="newRows" :has-more="hasMore" :loading="loading" :handler="more" orderBy="id" :orderDesc="true" limit="50"></scroll-table> <scroll-table :actions="actions" :columns="columns" :new-rows="newRows" :has-more="hasMore" :loading="loading" :handler="more" orderBy="username" :orderDesc="false" limit="50"></scroll-table>
</div> </div>
</template> </template>
@ -15,51 +15,63 @@ export default {
}, },
data() { data() {
return { return {
columns: [ actions: [{
{ title: 'Edit',
heading: 'ID', icon: 'icon-pencil',
prop: 'ID', action(row) {
orderBy: 'id' console.log("yay edit", row);
}, }
{ }, {
heading: 'Benutzername', title: 'Duplicate',
prop: 'Username', icon: 'icon-docs',
orderBy: 'username' action(row) {
}, console.log("yay duplicate", row);
{ }
heading: 'Firma', }, {
prop: 'Company', title: 'Hide',
orderBy: 'company' icon: 'icon-eye-off',
}, action(row) {
{ console.log("yay hide", row);
heading: 'Nachname', }
prop: 'Lastname', }],
orderBy: 'lastname' columns: [{
}, heading: 'ID',
{ prop: 'ID',
heading: 'Vorname', orderBy: 'id',
prop: 'Firstname', align: 'left'
orderBy: 'firstname' }, {
}, heading: 'Benutzername',
{ prop: 'Username',
heading: 'Email', orderBy: 'username',
prop: 'Email', align: 'center'
orderBy: 'email' }, {
}, heading: 'Firma',
{ prop: 'Company',
heading: 'Reseller', orderBy: 'company'
prop: 'Reseller.Username' }, {
}, heading: 'Nachname',
{ prop: 'Lastname',
heading: 'Render', orderBy: 'lastname'
render(row) { }, {
return 'ID:' + row.ID; heading: 'Vorname',
} prop: 'Firstname',
} orderBy: 'firstname'
], }, {
newRows: [ ], heading: 'Email',
hasMore: true, prop: 'Email',
loading: false orderBy: 'email'
}, {
heading: 'Reseller',
prop: 'Reseller.Username'
}, {
heading: 'Render',
render(row) {
return 'ID:' + row.ID;
}
}],
newRows: [ ],
hasMore: true,
loading: false
} }
}, },
methods: { methods: {