diff --git a/zero.Backoffice.UI/app/components/ui-check-list.vue b/zero.Backoffice.UI/app/components/ui-check-list.vue index afa973bf..7c32f623 100644 --- a/zero.Backoffice.UI/app/components/ui-check-list.vue +++ b/zero.Backoffice.UI/app/components/ui-check-list.vue @@ -1,134 +1,151 @@  \ No newline at end of file diff --git a/zero.Backoffice.UI/app/components/ui-form.vue b/zero.Backoffice.UI/app/components/ui-form.vue index 68609226..eca72013 100644 --- a/zero.Backoffice.UI/app/components/ui-form.vue +++ b/zero.Backoffice.UI/app/components/ui-form.vue @@ -143,8 +143,6 @@ this.setState('loading'); this.clearErrors(); - console.info(response); - if (!response.success) { this.setState('error'); diff --git a/zero.Backoffice.UI/app/components/ui-pick.vue b/zero.Backoffice.UI/app/components/ui-pick.vue index 29d78867..6144786f 100644 --- a/zero.Backoffice.UI/app/components/ui-pick.vue +++ b/zero.Backoffice.UI/app/components/ui-pick.vue @@ -426,7 +426,7 @@ if (this.configuration.paging && typeof items === 'object' && !Array.isArray(items)) { // TODO we need to store metadata to allow pagination - items = items.items; + items = items.data; } if (needsFilter) diff --git a/zero.Backoffice.UI/app/core/types/zero.ts b/zero.Backoffice.UI/app/core/types/zero.ts index 367e9fec..ad23907f 100644 --- a/zero.Backoffice.UI/app/core/types/zero.ts +++ b/zero.Backoffice.UI/app/core/types/zero.ts @@ -1,6 +1,6 @@ import { Emitter, EventType } from "mitt"; import { Component } from "vue"; -import { ZeroSchema } from "zero/schemas"; +import { ZeroSchema, ZeroSchemaExtension } from "zero/schemas"; export interface Zero { @@ -27,6 +27,11 @@ export interface Zero **/ getSchema(alias: string): Promise; + /** + * get all defined extensions for a schema + **/ + getSchemaExtensions(alias: string): ZeroSchemaExtension[]; + /** * get all defined link areas **/ diff --git a/zero.Backoffice.UI/app/core/types/zeroPluginOptions.ts b/zero.Backoffice.UI/app/core/types/zeroPluginOptions.ts index 3725bf95..42880c67 100644 --- a/zero.Backoffice.UI/app/core/types/zeroPluginOptions.ts +++ b/zero.Backoffice.UI/app/core/types/zeroPluginOptions.ts @@ -3,6 +3,7 @@ import { RouteRecordRaw } from 'vue-router'; import { App, Component } from 'vue'; import { ZeroSchemaProp } from '../zero'; import { ZeroLinkArea } from './zero'; +import { ZeroSchema, ZeroSchemaExtension } from 'zero/schemas'; export interface ZeroPluginOptions { @@ -11,6 +12,8 @@ export interface ZeroPluginOptions route: (route: RouteRecordRaw) => void; schemas: Record; schema: (alias: string, schema: ZeroSchemaProp) => void; + schemaExtensions: ZeroSchemaExtension[]; + extendSchema: (alias: string, extension: ((schema: ZeroSchema) => void)) => void; fieldTypes: Record; fieldType: (alias: string, component: Component) => void; linkAreas: Record; diff --git a/zero.Backoffice.UI/app/core/zeroRuntime.ts b/zero.Backoffice.UI/app/core/zeroRuntime.ts index 97f68400..ddce5bd8 100644 --- a/zero.Backoffice.UI/app/core/zeroRuntime.ts +++ b/zero.Backoffice.UI/app/core/zeroRuntime.ts @@ -25,7 +25,7 @@ import pageModulePlugin } from '../modules'; import editorPlugin from '../editor/plugin'; -import { ZeroSchema } from 'zero/schemas'; +import { ZeroSchema, ZeroSchemaExtension } from 'zero/schemas'; import { ZeroSchemaProp } from './zero'; import * as zeroOptions from '../options'; import plugins from '../plugins.generated'; @@ -45,6 +45,7 @@ export class ZeroRuntime implements Zero _installOptions: ZeroInstallOptions; _routerConfig: RouterOptions; _schemas: Record = {}; + _schemaExtensions: ZeroSchemaExtension[] = []; _fieldTypes: Record = {}; _linkAreas: Record = {}; @@ -105,6 +106,7 @@ export class ZeroRuntime implements Zero vue: this._app, routes: this._routerConfig.routes, schemas: this._schemas, + schemaExtensions: this._schemaExtensions, fieldTypes: this._fieldTypes, linkAreas: this._linkAreas, route(route: RouteRecordRaw) @@ -115,6 +117,10 @@ export class ZeroRuntime implements Zero { this.schemas[alias] = schema; }, + extendSchema(alias: string, extension: ((schema: ZeroSchema) => void)) + { + this.schemaExtensions.push({ alias, extension }); + }, fieldType(alias: string, component: Component) // TODO v3 allow default field options { this.fieldTypes[alias] = component; @@ -171,7 +177,7 @@ export class ZeroRuntime implements Zero **/ async getSchema(alias: string): Promise { - const schema: ZeroSchemaProp = this._schemas[alias]; + let schema: ZeroSchemaProp = this._schemas[alias]; if (!schema) { @@ -181,13 +187,24 @@ export class ZeroRuntime implements Zero if (typeof schema === 'function') { const res = await schema(); - return res.default; + schema = res.default as ZeroSchema; } + schema.alias = alias; + return Promise.resolve(schema); } + /** + * get all defined extensions for a schema + **/ + getSchemaExtensions(alias: string): ZeroSchemaExtension[] + { + return this._schemaExtensions.filter(x => x.alias == alias); + } + + /** * get all defined link areas **/ diff --git a/zero.Backoffice.UI/app/editor/_fields/pagepicker.vue b/zero.Backoffice.UI/app/editor/_fields/pagepicker.vue deleted file mode 100644 index 54c997a6..00000000 --- a/zero.Backoffice.UI/app/editor/_fields/pagepicker.vue +++ /dev/null @@ -1,24 +0,0 @@ - - - - \ No newline at end of file diff --git a/zero.Backoffice.UI/app/editor/compile.ts b/zero.Backoffice.UI/app/editor/compile.ts index ba0e3c54..7a50bf14 100644 --- a/zero.Backoffice.UI/app/editor/compile.ts +++ b/zero.Backoffice.UI/app/editor/compile.ts @@ -8,6 +8,8 @@ import { ZeroEditorFieldConfiguration, ZeroEditorFieldFilterPreview } from "./ed import { localize } from '../services/localization'; +let appliedExtensionsTo: string[] = []; + export interface ZeroCompiledEditor { alias: string; @@ -162,6 +164,18 @@ export function compileEditor(zero: Zero, editor: ZeroEditor): ZeroCompiledEdito return null; } + if (appliedExtensionsTo.indexOf(editor.alias) < 0) + { + appliedExtensionsTo.push(editor.alias); + + let extensions = zero.getSchemaExtensions(editor.alias); + + extensions.forEach(extension => + { + extension.extension(editor); + }); + } + let model = { alias: editor.alias, blueprint: null, diff --git a/zero.Backoffice.UI/app/editor/editor-canvas.ts b/zero.Backoffice.UI/app/editor/editor-canvas.ts index 71f31215..4b32651e 100644 --- a/zero.Backoffice.UI/app/editor/editor-canvas.ts +++ b/zero.Backoffice.UI/app/editor/editor-canvas.ts @@ -21,6 +21,11 @@ export class ZeroEditorCanvasBase this.fields.push(proxy); return proxy; } + + getField(path: string): ZeroEditorField | undefined + { + return this.fields.find(x => x.path == path); + } } @@ -55,6 +60,11 @@ export class ZeroEditorCanvas extends ZeroEditorCanvasBaseWithFieldset this.tabs.push(tab); return tab; } + + getTab(alias: string): ZeroEditorTab | undefined + { + return this.tabs.find(x => x.alias == alias); + } } diff --git a/zero.Backoffice.UI/app/editor/fields/createFields.ts b/zero.Backoffice.UI/app/editor/fields/createFields.ts index cfc61938..c17f34ad 100644 --- a/zero.Backoffice.UI/app/editor/fields/createFields.ts +++ b/zero.Backoffice.UI/app/editor/fields/createFields.ts @@ -6,6 +6,7 @@ export default function createFields(app: ZeroPluginOptions): void { app.fieldType('text', defineAsyncComponent(() => import('./text.vue'))); app.fieldType('number', defineAsyncComponent(() => import('./number.vue'))); + app.fieldType('password', defineAsyncComponent(() => import('./password.vue'))); app.fieldType('toggle', defineAsyncComponent(() => import('./toggle.vue'))); app.fieldType('rte', defineAsyncComponent(() => import('./rte.vue'))); app.fieldType('output', defineAsyncComponent(() => import('./output.vue'))); diff --git a/zero.Backoffice.UI/app/editor/fields/editor-field-extensions.d.ts b/zero.Backoffice.UI/app/editor/fields/editor-field-extensions.d.ts index 357ac7f7..9cff9281 100644 --- a/zero.Backoffice.UI/app/editor/fields/editor-field-extensions.d.ts +++ b/zero.Backoffice.UI/app/editor/fields/editor-field-extensions.d.ts @@ -22,6 +22,12 @@ declare module 'zero/schemas' */ number(options?: NumberFieldOptions): ZeroEditorField; + /** + * Render a password input + * @param {TextFieldOptions} [options] - Custom options + */ + password(options?: TextFieldOptions): ZeroEditorField; + /** * Render a toggle * @param {ToggleFieldOptions} [options] - Custom options @@ -132,7 +138,7 @@ declare module 'zero/schemas' export interface SelectFieldOptions { - items: SelectFieldItem[]; + items: SelectFieldItem[] | ((model: any) => SelectFieldItem[]); emptyOption?: boolean | null; } @@ -156,6 +162,7 @@ declare module 'zero/schemas' limit?: number; reverse?: boolean | null; labelKey?: string; + descriptionKey?: string; idKey?: string; } diff --git a/zero.Backoffice.UI/app/editor/fields/nested.vue b/zero.Backoffice.UI/app/editor/fields/nested.vue index 7f71ec97..13963d98 100644 --- a/zero.Backoffice.UI/app/editor/fields/nested.vue +++ b/zero.Backoffice.UI/app/editor/fields/nested.vue @@ -127,8 +127,6 @@ width: this.width, }); - console.info('nested result: ', result); - if (result.eventType == 'confirm') { if (isAdd) @@ -141,6 +139,8 @@ this.removeItem(index); this.items.splice(index, 0, result.value); } + + this.onChange(); } }, @@ -154,7 +154,9 @@ onChange() { - this.$emit('input', this.multiple ? this.items : (this.items.length > 0 ? this.items[0] : null)); + let value = this.multiple ? this.items : (this.items.length > 0 ? this.items[0] : null) + this.$emit('input', value); + this.$emit('update:value', value); }, diff --git a/zero.Backoffice.UI/app/editor/_fields/password.vue b/zero.Backoffice.UI/app/editor/fields/password.vue similarity index 55% rename from zero.Backoffice.UI/app/editor/_fields/password.vue rename to zero.Backoffice.UI/app/editor/fields/password.vue index a2e215c7..214ea6ca 100644 --- a/zero.Backoffice.UI/app/editor/_fields/password.vue +++ b/zero.Backoffice.UI/app/editor/fields/password.vue @@ -1,28 +1,22 @@  \ No newline at end of file diff --git a/zero.Backoffice.UI/app/editor/fields/select.vue b/zero.Backoffice.UI/app/editor/fields/select.vue index 02645526..6ff22b54 100644 --- a/zero.Backoffice.UI/app/editor/fields/select.vue +++ b/zero.Backoffice.UI/app/editor/fields/select.vue @@ -32,13 +32,15 @@ watch: { items: { deep: true, - handler() + handler(val) { - this.rebuild(); - this.onChange({ target: { value: this.value } }); + if (typeof val !== 'function') + { + this.rebuild(); + } } }, - 'config.model.addresses': { + 'config.model': { deep: true, handler() { @@ -52,20 +54,24 @@ rebuild() { let items = []; + let options = []; if (!this.config.model || !this.items) { - this.options = items; - return; + options = items; } - - if (typeof this.items === 'function') + else if (typeof this.items === 'function') { - this.options = this.items(this.config.model); + options = this.items(this.config.model); } else { - this.options = [...this.items]; + options = [...this.items]; + } + + if (JSON.stringify(options) !== JSON.stringify(this.options)) + { + this.options = options; } }, @@ -78,7 +84,13 @@ value = null; } + if (this.value === value) + { + return; + } + this.$emit('input', value); + this.$emit('update:value', value); } } } diff --git a/zero.Backoffice.UI/app/schemas/schemas.d.ts b/zero.Backoffice.UI/app/schemas/schemas.d.ts index 2428209b..076f4f6f 100644 --- a/zero.Backoffice.UI/app/schemas/schemas.d.ts +++ b/zero.Backoffice.UI/app/schemas/schemas.d.ts @@ -5,4 +5,10 @@ declare module 'zero/schemas' { alias: string; } + + export interface ZeroSchemaExtension + { + alias: string; + extension: (schema: ZeroSchema) => void; + } } \ No newline at end of file diff --git a/zero.Core/Localization/CountryStore.cs b/zero.Core/Localization/CountryStore.cs index 0a69b030..a4dd5ab3 100644 --- a/zero.Core/Localization/CountryStore.cs +++ b/zero.Core/Localization/CountryStore.cs @@ -9,8 +9,8 @@ public class CountryStore : EntityStore, ICountryStore /// protected override void ValidationRules(ZeroValidator validator) { - validator.RuleFor(x => x.Code).Length(2).Unique(Session); - validator.RuleFor(x => x.Name).Length(2, 120); + validator.RuleFor(x => x.Code).NotEmpty().Length(2).Unique(Session); + validator.RuleFor(x => x.Name).NotEmpty().Length(2, 120); } }