table filter works

This commit is contained in:
2022-01-03 15:28:12 +01:00
parent 6350a7dc1b
commit 093a2583f0
15 changed files with 115 additions and 404 deletions
@@ -1,15 +1,15 @@
<template>
<ui-form class="ui-table-filter-overlay" ref="form" v-slot="form" @submit="onSubmit" @load="onLoad">
<ui-overlay-editor class="ui-module-overlay">
<ui-trinity class="ui-module-overlay">
<template v-slot:header>
<ui-header-bar :title="config.title" :back-button="false" :close-button="true" />
<ui-header-bar :title="model.title" :back-button="false" :close-button="true" />
</template>
<template v-slot:footer>
<ui-button type="light onbg" label="@ui.close" @click="config.hide"></ui-button>
<ui-button v-if="!config.isCreate" type="light onbg" label="@ui.remove" @click="onRemove"></ui-button>
<ui-button type="primary" :submit="true" label="@ui.confirm" :state="form.state" :disabled="loading || disabled"></ui-button>
<ui-button type="light onbg" label="@ui.close" @click="config.close"></ui-button>
<ui-button v-if="!model.isCreate" type="light onbg" label="@ui.remove" @click="onRemove"></ui-button>
<ui-button type="accent" :submit="true" label="@ui.confirm" :state="form.state" :disabled="loading || disabled"></ui-button>
</template>
<ui-loading v-if="loading" :is-big="true" />
@@ -21,13 +21,13 @@
</div>-->
<div v-if="!loading" class="ui-box">
<div v-for="(field, index) in fields" class="ui-table-filter-overlay-group" :class="{ 'is-open': activeFilter === index, 'has-value': field.hasValue(model[field.path]) }">
<div v-for="(field, index) in fields" class="ui-table-filter-overlay-group" :class="{ 'is-open': activeFilter === index, 'has-value': field.preview.selected(value[field.path]) }">
<div class="ui-table-filter-overlay-group-head">
<ui-select-button :label="getFieldLabel(field.field)" :icon="field.icon" :description="field.preview(model[field.path])" @click="toggleFilter(field, index)" />
<ui-select-button :label="field.label" :icon="field.preview.icon" :description="field.preview.value(value[field.path])" @click="toggleFilter(field, index)" />
<ui-button class="ui-table-filter-overlay-group-clear" type="blank" label="Clear" @click="clearFilter(field, index)" />
</div>
<div v-show="activeFilter === index" class="ui-table-filter-overlay-group-content">
<editor-component :config="field.field" :editor="editor" v-model="model" @input="onFieldChange" :disabled="disabled" />
<ui-editor-component :field="field" :value="value" @input="onFieldChange" :disabled="disabled" />
</div>
</div>
</div>
@@ -38,17 +38,18 @@
</ui-property>
</div>
</ui-overlay-editor>
</ui-trinity>
</ui-form>
</template>
<script>
import EditorComponent from 'zero/editor/editor-component.vue';
import { compileEditor } from '../editor/compile';
export default {
props: {
model: Object,
config: Object
},
@@ -61,9 +62,9 @@
},
data: () => ({
value: {},
loading: true,
disabled: false,
model: {},
template: null,
editor: null,
fields: [],
@@ -71,8 +72,6 @@
activeFilter: null
}),
components: { EditorComponent },
methods: {
toggleFilter(filter, index)
@@ -87,28 +86,35 @@
clearFilter(filter, index)
{
this.model[filter.path] = JSON.parse(JSON.stringify(this.template[filter.path]));
},
getFieldLabel(field)
{
return field.options.label || this.editor.templateLabel(field.path);
this.value[filter.path] = JSON.parse(JSON.stringify(this.template[filter.path]));
},
onLoad()
{
this.loading = true;
this.template = JSON.parse(JSON.stringify(this.config.template || {}));
this.model = JSON.parse(JSON.stringify(this.config.model.filter || this.template));
this.editor = this.config.editor;
this.filterName = this.config.model.name;
this.fields = this.editor.fields.map(x =>
this.template = JSON.parse(JSON.stringify(this.model.template || {}));
this.value = JSON.parse(JSON.stringify(this.model.value.filter || this.template));
this.editor = compileEditor(this.zero, this.model.editor);
this.filterName = this.model.value.name;
this.fields = [];
this.editor.tabs.forEach(tab =>
{
return {
field: x,
path: x.path,
...x.previewOptions
};
tab.fieldsets.forEach(set =>
{
set.fields.forEach(field =>
{
if (!field.preview)
{
console.warn('[zero] All fields in a filter need filled-out preview options');
}
else
{
this.fields.push(field);
}
});
});
});
this.loading = false;
@@ -117,7 +123,7 @@
onSubmit()
{
this.config.confirm({
model: this.model,
model: this.value,
filterName: this.filterName
});
},
@@ -14,12 +14,12 @@
</ui-dropdown>
<ui-button v-if="hasFilter && !storedFilters.length" type="light onbg" label="Filter" @click="addOrEditFilter()" />
<!--<ui-dropdown v-if="!hideSelection && selection.length > 0" align="right">
<ui-dropdown v-if="!hideSelection && selection.length > 0" align="right">
<template v-slot:button>
<ui-button type="light" :label="selectedText" caret="down" />
</template>
<slot name="actions"></slot>
</ui-dropdown>-->
</ui-dropdown>
<ui-dropdown v-if="actions && actions.length > 0" align="right">
<template v-slot:button>
@@ -34,6 +34,7 @@
<script>
//import Overlay from 'zero/helpers/overlay.js';
import { generateId } from '../utils/numbers';
import * as overlays from '../services/overlay';
//import FilterOverlay from './table-filter-overlay.vue';
const KEY_PREFIX = 'zero.ui-table-filter.';
@@ -62,6 +63,7 @@
filterOptions: null,
//hideFilter: true,
//hideSelection: true,
selection: [],
storedFilters: [],
currentFilter: null,
actions: []
@@ -178,7 +180,7 @@
},
addOrEditFilter(id)
async addOrEditFilter(id)
{
let model = { name: null, filter: this.filterOptions.template };
@@ -187,33 +189,39 @@
model = this.storedFilters.find(x => x.id === id);
}
//return Overlay.open({
// component: FilterOverlay,
// display: 'editor',
// title: 'Filter',
// editor: this.filterOptions.editor,
// template: this.filterOptions.template,
// model: model,
// isCreate: !id
//}).then(value =>
//{
// if (value.remove && id)
// {
// this.removeFilter(id);
// return;
// }
const result = await overlays.open({
component: () => import('./ui-table-filter-overlay.vue'),
display: 'editor',
model: {
title: 'Filter',
editor: this.filterOptions.editor,
template: this.filterOptions.template,
value: model,
isCreate: !id
}
});
// if (value.filterName)
// {
// id = this.saveFilter(value.filterName, value.model, id);
// }
if (result.eventType == 'confirm')
{
const value = result.value;
// this.setFilter({
// id,
// name: value.filterName,
// filter: value.model
// });
//});
if (value.remove && id)
{
this.removeFilter(id);
return;
}
if (value.filterName)
{
id = this.saveFilter(value.filterName, value.model, id);
}
this.setFilter({
id,
name: value.filterName,
filter: value.model
});
}
}
}
}
@@ -268,6 +268,7 @@
// set a new filter
setFilter(filter)
{
console.info('filter', filter);
this.query.filter = filter;
this.onChange();
},
@@ -1,17 +0,0 @@
<template>
<ui-countrypicker :value="value" @input="$emit('input', $event)" :limit="limit" :disabled="disabled" />
</template>
<script>
export default {
props: {
value: [String, Array],
disabled: Boolean,
limit: {
type: Number,
default: 1
}
}
}
</script>
@@ -1,43 +0,0 @@
<template>
<ui-mediapicker :config="{ limit, disallowSelect, disallowUpload, fileExtensions, maxFileSize, shared: meta.isShared }" :value="value" @input="$emit('input', $event)" :disabled="disabled" />
</template>
<script>
export default {
name: 'uiMediaField',
props: {
value: {
type: [String, Array],
default: null
},
disabled: {
type: Boolean,
default: false
},
limit: {
type: Number,
default: 1
},
disallowSelect: {
type: Boolean,
default: false
},
disallowUpload: {
type: Boolean,
default: false
},
fileExtensions: {
type: Array,
default: null
},
maxFileSize: {
type: Number,
default: 10
},
config: Object,
meta: Object
}
}
</script>
@@ -1,185 +0,0 @@
<template>
<div class="editor-nested" :depth="depth">
<div class="ui-pick-previews" v-if="items.length" v-sortable="{ onUpdate: onSortingUpdated }">
<div v-for="(item, index) in items" :key="item.id" class="ui-pick-preview">
<ui-select-button :icon="getIcon(item)" :label="getName(item)" :description="getDescription(item)" :disabled="disabled" @click="editItem(item)" />
<ui-icon-button v-if="!disabled" icon="fth-x" title="@ui.close" @click="removeItem(index)" :disabled="disabled" :size="14" />
</div>
</div>
<ui-select-button v-if="limit > items.length" icon="fth-plus" :label="addLabel || '@ui.add'" @click="addItem" :disabled="disabled" />
</div>
</template>
<script>
import UiEditor from '../editor.vue';
import UiEditorOverlay from '../editor-overlay.vue';
//import Overlay from 'zero/helpers/overlay.js';
import { convertHtmlToText, arrayMove } from '../../utils';
export default {
name: 'UiEditorFieldNested',
components: { UiEditor },
props: {
value: [Array, Object],
meta: Object,
depth: Number,
disabled: Boolean,
editor: {
type: Object,
required: true
},
entity: Object,
limit: {
type: Number,
default: 100
},
width: {
type: Number,
default: 820
},
title: String,
addLabel: String,
itemLabel: Function,
itemDescription: Function,
itemIcon: [String, Function],
template: {
type: Object,
required: true
}
},
watch: {
value: {
deep: true,
handler(val)
{
this.setup(val);
}
}
},
data: () => ({
items: [],
multiple: false
}),
mounted()
{
this.setup(this.value);
},
methods: {
setup(value)
{
this.items = JSON.parse(JSON.stringify(value)) || [];
this.multiple = this.limit > 1;
if (!this.multiple)
{
this.items = this.items ? [this.items] : [];
}
},
getNewItem()
{
return JSON.parse(JSON.stringify(this.template || {}));
// TODO we need to set a default ID here so we can sort based on this v-for key.
// the problem is we don't know if the object has an ID nor how long it should be.
// It is only generated on the server so we don't have access to it yet.
// the v-for key is necessary so the v-sortable works and correctly propagates changes, d'oh.
},
addItem()
{
if (this.limit <= this.items.length)
{
return;
}
this.editItem(this.getNewItem(), true);
this.onChange();
},
editItem(item, isAdd)
{
let parentModel = JSON.parse(JSON.stringify(this.entity));
if (this.meta && this.meta.parentModel)
{
parentModel.parentModel = this.meta.parentModel;
}
// open editing overlay
//return Overlay.open({
// component: UiEditorOverlay,
// display: 'editor',
// editor: this.editor,
// title: this.title || '@ui.edit.title',
// model: item,
// width: this.width,
// parentModel,
// create: isAdd
//}).then(value =>
//{
// if (isAdd)
// {
// this.items.push(value);
// }
// else
// {
// const index = this.items.indexOf(item);
// this.removeItem(index);
// this.items.splice(index, 0, value);
// }
// this.onChange();
//});
},
removeItem(index)
{
this.items.splice(index, 1);
this.onChange();
},
onChange()
{
this.$emit('input', this.multiple ? this.items : (this.items.length > 0 ? this.items[0] : null));
},
getName(item)
{
let name = typeof this.itemLabel === 'function' ? this.itemLabel(item) : null;
return convertHtmlToText(name ?? '@ui.item');
},
getDescription(item)
{
return convertHtmlToText(typeof this.itemDescription === 'function' ? this.itemDescription(item) : '');
},
getIcon(item)
{
return typeof this.itemIcon === 'function' ? this.itemIcon(item) : this.itemIcon;
},
onSortingUpdated(ev)
{
this.items = arrayMove(this.items, ev.oldIndex, ev.newIndex);
this.onChange();
//this.$emit('input', this.multiple ? result : (result.length > 0 ? result[0] : null));
},
}
}
</script>
@@ -1,82 +0,0 @@
<template>
<div class="ui-native-select" :disabled="disabled">
<select :value="value" @input="onChange" :disabled="disabled">
<option v-if="emptyOption"></option>
<option v-for="option in options" :value="option.key" v-localize="option.value"></option>
</select>
</div>
</template>
<script>
export default {
props: {
value: [String, Number, Object],
items: [Array, Function],
entity: {
type: Object,
required: true
},
disabled: Boolean,
emptyOption: {
type: Boolean,
default: false
}
},
data: () => ({
options: []
}),
created()
{
this.rebuild();
},
watch: {
items: {
deep: true,
handler()
{
this.rebuild();
this.onChange({ target: { value: this.value } });
}
},
entity: {
deep: true,
handler()
{
this.rebuild();
this.onChange({ target: { value: this.value } });
}
}
},
methods: {
rebuild()
{
let items = [];
if (!this.entity || !this.items)
{
this.options = items;
return;
}
if (typeof this.items === 'function')
{
this.options = this.items(this.entity);
}
else
{
this.options = [...this.items];
}
},
onChange(ev)
{
this.$emit('input', ev.target.value ? ev.target.value : null);
}
}
}
</script>
+8 -2
View File
@@ -4,7 +4,7 @@ import { Zero } from "../core";
import { ZeroEditor } from "./editor";
import { createBlueprintConfig } from "./editor-blueprint";
import { ZeroEditorCanvasBase, ZeroEditorTab } from "./editor-canvas";
import { ZeroEditorFieldConfiguration } from "./editor-field";
import { ZeroEditorFieldConfiguration, ZeroEditorFieldFilterPreview } from "./editor-field";
import { localize } from '../services/localization';
@@ -57,6 +57,7 @@ export interface ZeroCompiledEditorField
horizontal: boolean;
sort: number;
columns: number;
preview?: ZeroEditorFieldFilterPreview;
}
@@ -122,7 +123,12 @@ export function compileField(zero: Zero, editor: ZeroEditor, field: ZeroEditorFi
classes: field.configuration.classes,
horizontal: field.configuration.horizontal,
sort: field.configuration.sort,
columns: 12
columns: 12,
preview: field.configuration.preview ? {
icon: field.configuration.preview.icon || 'fth-square',
selected: field.configuration.preview.selected || (x => !!x),
value: field.configuration.preview.value || (x => x ? x : null)
} : null
} as ZeroCompiledEditorField;
@@ -1,6 +1,6 @@
<template>
<div class="ui-native-select" :disabled="disabled">
<select :value="value" @input="onChange" :disabled="disabled">
<div class="ui-native-select" :disabled="config.disabled">
<select :value="value" @input="onChange" :disabled="config.disabled">
<option v-if="emptyOption"></option>
<option v-for="option in options" :value="option.value" v-localize="option.label"></option>
</select>
@@ -38,7 +38,7 @@
this.onChange({ target: { value: this.value } });
}
},
'config.model': {
'config.model.addresses': {
deep: true,
handler()
{
@@ -71,7 +71,14 @@
onChange(ev)
{
this.$emit('input', ev.target.value ? ev.target.value : null);
let value = ev.target.value || null;
if (value && !this.options.find(x => x.value == value))
{
value = null;
}
this.$emit('input', value);
}
}
}
@@ -4,6 +4,7 @@ import { App, defineAsyncComponent } from 'vue';
export default function (app: App)
{
app.component('ui-editor', defineAsyncComponent(() => import('./ui-editor.vue')));
app.component('ui-editor-component', defineAsyncComponent(() => import('./ui-editor-component.vue')));
app.component('ui-editor-infos', defineAsyncComponent(() => import('./ui-editor-infos.vue')));
app.component('ui-editor-header', defineAsyncComponent(() => import('./ui-editor-header.vue')));
app.component('ui-editor-page', defineAsyncComponent(() => import('./ui-editor-page.vue')));
@@ -1,7 +1,10 @@
<template>
<div v-for="item in items" :key="item.id" class="ui-media-preview media-pattern" :title="item.name">
<img class="-image" v-if="item.preview" :src="item.preview" />
<span class="-icon" v-if="!item.preview"><ui-icon :symbol="(item.isFolder ? 'fth-folder' : 'fth-file')" :size="22" :stroke-width="2" /></span>
<div v-for="item in items" :key="item.id" class="ui-media-preview" :class="{ 'media-pattern': !item.error, 'has-error': item.error }" v-localize:title="{ key: item.error ? item.error : item.name, tokens: { id: item.id } }">
<template v-if="!item.error">
<img class="-image" v-if="item.preview" :src="item.preview" />
<span class="-icon" v-if="!item.preview"><ui-icon :symbol="(item.isFolder ? 'fth-folder' : 'fth-file')" :size="22" :stroke-width="2" /></span>
</template>
<ui-icon v-else symbol="fth-alert-circle" :size="22" />
<ui-icon-button class="-remove" type="action" icon="fth-x" :stroke="2.5" @click="$emit('remove', item)" />
</div>
</template>
@@ -86,7 +89,8 @@
if (!model)
{
model = {
error: 'notfound'
id,
error: '@media.notfound'
};
}
else
@@ -129,6 +133,12 @@
position: relative;
}
.ui-media-preview.has-error
{
background: var(--color-box-nested);
color: var(--color-negative);
}
.ui-media-preview .-image
{
width: 100%;
@@ -72,7 +72,6 @@
min-height: 130px;
border-radius: var(--radius);
align-self: flex-start;
box-shadow: var(--shadow-short);
border: 1px dashed var(--color-line-dashed-onbg);
color: var(--color-text);
position: relative;
@@ -21,7 +21,7 @@
textarea: 'A textarea\nfor you',
state: 'red',
images: ['media.zpkxfjkgciom', 'media.w2cntwlrcueb', 'media.1waxp46ho8hu'],
docs: null
docs: 'media.xoooo'
},
route: 'demo',
disabled: false
@@ -5,17 +5,16 @@ const editor = new ZeroEditor('translations');
editor.resourcePrefix = '@translation.fields.';
const set = editor.fieldset();
set.field('key').text({ maxLength: 300 });
set.field('display').state({
editor.field('key').text({ maxLength: 300 });
editor.field('display').state({
items: [
{ label: '@translation.display.text', value: 0 },
{ label: '@translation.display.html', value: 1 }
{ label: '@translation.display.text', value: 'text' },
{ label: '@translation.display.html', value: 'html' }
]
});
editor.field('value').setHidden(x => x.display === 0).rte();
editor.field('value').setHidden(x => x.display === 1).textarea();
editor.field('value').setHidden(x => x.display === 'text').rte();
editor.field('value').setHidden(x => x.display === 'html').textarea();
export default editor;
@@ -653,6 +653,7 @@
"addfile": "Upload files",
"addfile_text": "Add one or more files to the current filter"
},
"notfound": "Could not find media ({id})",
"selection": {
"clear": "Clear selection",
"selected": "{count} selected",