diff --git a/zero.Backoffice.UI/app/components/ui-select-button.vue b/zero.Backoffice.UI/app/components/ui-select-button.vue
index b289c8f2..cfab598b 100644
--- a/zero.Backoffice.UI/app/components/ui-select-button.vue
+++ b/zero.Backoffice.UI/app/components/ui-select-button.vue
@@ -5,7 +5,6 @@
-
diff --git a/zero.Backoffice.UI/app/core/zeroRuntime.ts b/zero.Backoffice.UI/app/core/zeroRuntime.ts
index bdba34d0..bcbfd23b 100644
--- a/zero.Backoffice.UI/app/core/zeroRuntime.ts
+++ b/zero.Backoffice.UI/app/core/zeroRuntime.ts
@@ -29,7 +29,7 @@ import * as zeroOptions from '../options';
import plugins from '../plugins.generated';
plugins.push(
- countryPlugin, applicationPlugin, settingsPlugin, languagePlugin,
+ editorPlugin, countryPlugin, applicationPlugin, settingsPlugin, languagePlugin,
mediaPlugin, spacePlugin, pagePlugin, mailTemplatePlugin,
translationPlugin, integrationPlugin, userPlugin
);
diff --git a/zero.Backoffice.UI/app/editor/compile.ts b/zero.Backoffice.UI/app/editor/compile.ts
index aede6adb..8ab0b945 100644
--- a/zero.Backoffice.UI/app/editor/compile.ts
+++ b/zero.Backoffice.UI/app/editor/compile.ts
@@ -38,6 +38,15 @@ export interface ZeroCompiledEditorFieldset
}
+export interface ZeroCompiledEditorChangeEvent
+{
+ value: any;
+ oldValue: any;
+ model: any;
+ component: Component;
+}
+
+
export interface ZeroCompiledEditorField
{
path: string;
@@ -58,6 +67,7 @@ export interface ZeroCompiledEditorField
sort: number;
columns: number;
preview?: ZeroEditorFieldFilterPreview;
+ onChange: (event: ZeroCompiledEditorChangeEvent) => void;
}
@@ -128,7 +138,15 @@ export function compileField(zero: Zero, editor: ZeroEditor, field: ZeroEditorFi
icon: field.configuration.preview.icon || 'fth-square',
selected: field.configuration.preview.selected || (x => !!x),
value: field.configuration.preview.value || (x => x ? x : null)
- } : null
+ } : null,
+
+ onChange(event: ZeroCompiledEditorChangeEvent)
+ {
+ (field.configuration.changeHandlers || []).forEach(handler =>
+ {
+ handler(event);
+ });
+ }
} as ZeroCompiledEditorField;
diff --git a/zero.Backoffice.UI/app/editor/editor-field.ts b/zero.Backoffice.UI/app/editor/editor-field.ts
index ccb36acd..b821582c 100644
--- a/zero.Backoffice.UI/app/editor/editor-field.ts
+++ b/zero.Backoffice.UI/app/editor/editor-field.ts
@@ -96,6 +96,17 @@ export class ZeroEditorFieldImpl implements ZeroEditorField
this.options = options;
return this;
}
+
+
+ /**
+ * The expression argument is called when the value of the field changes
+ * @param {function} callback - function which is called
+ */
+ onChange(callback: Function): ZeroEditorField
+ {
+ this.configuration.changeHandlers.push(callback);
+ return this;
+ }
}
@@ -113,7 +124,8 @@ export function createDefaultFieldConfiguration(): ZeroEditorFieldConfiguration
classes: null,
horizontal: false,
sort: 0,
- preview: undefined
+ preview: undefined,
+ changeHandlers: []
} as ZeroEditorFieldConfiguration;
}
@@ -163,7 +175,11 @@ export interface ZeroEditorFieldConfiguration
/**
* Sort order for fields within the editor canvas
**/
- preview?: ZeroEditorFieldFilterPreview
+ preview?: ZeroEditorFieldFilterPreview,
+ /**
+ * Handlers which get called on value change
+ **/
+ changeHandlers?: Array
}
diff --git a/zero.Backoffice.UI/app/editor/editor-types.d.ts b/zero.Backoffice.UI/app/editor/editor-types.d.ts
index 9802dec4..4a45ac4c 100644
--- a/zero.Backoffice.UI/app/editor/editor-types.d.ts
+++ b/zero.Backoffice.UI/app/editor/editor-types.d.ts
@@ -79,5 +79,11 @@ declare module 'zero/schemas'
* @param {T} [options] = Custom options to pass to this editor
*/
component(component: Component, options?: any): ZeroEditorField;
+
+ /**
+ * The expression argument is called when the value of the field changes
+ * @param {function} callback - function which is called
+ */
+ onChange(callback: Function): ZeroEditorField;
}
}
\ No newline at end of file
diff --git a/zero.Backoffice.UI/app/editor/fields/createFields.ts b/zero.Backoffice.UI/app/editor/fields/createFields.ts
index b673e991..49255bea 100644
--- a/zero.Backoffice.UI/app/editor/fields/createFields.ts
+++ b/zero.Backoffice.UI/app/editor/fields/createFields.ts
@@ -16,4 +16,5 @@ export default function createFields(app: ZeroPluginOptions): void
app.fieldType('checklist', defineAsyncComponent(() => import('./checklist.vue')));
app.fieldType('inputlist', defineAsyncComponent(() => import('./inputlist.vue')));
app.fieldType('nested', defineAsyncComponent(() => import('./nested.vue')));
+ app.fieldType('datePicker', defineAsyncComponent(() => import('./datePicker.vue')));
}
\ No newline at end of file
diff --git a/zero.Backoffice.UI/app/editor/fields/datePicker.vue b/zero.Backoffice.UI/app/editor/fields/datePicker.vue
new file mode 100644
index 00000000..61523227
--- /dev/null
+++ b/zero.Backoffice.UI/app/editor/fields/datePicker.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
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 8765e62f..73bef1a9 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
@@ -75,9 +75,24 @@ declare module 'zero/schemas'
* @param {NestedFieldOptions} options - Custom options
*/
nested(options: NestedFieldOptions): ZeroEditorField;
+
+ /**
+ * Create a date picker
+ * @param {DatePickerFieldOptions} options - Custom options
+ */
+ datePicker(options?: DatePickerFieldOptions): ZeroEditorField;
}
+ export interface DatePickerFieldOptions
+ {
+ format?: string;
+ pickTime?: boolean;
+ maxDate?: string | Date;
+ minDate?: string | Date;
+ amPm?: boolean;
+ }
+
export interface ToggleFieldOptions
{
negative?: boolean | null;
diff --git a/zero.Backoffice.UI/app/editor/ui-editor-component.vue b/zero.Backoffice.UI/app/editor/ui-editor-component.vue
index 4a817564..98622028 100644
--- a/zero.Backoffice.UI/app/editor/ui-editor-component.vue
+++ b/zero.Backoffice.UI/app/editor/ui-editor-component.vue
@@ -151,15 +151,12 @@
this.$emit('input', this.value);
- // TODO
- //if (typeof this.field.configuration.onChange === 'function')
- //{
- // this.field.configuration.onChange(value, {
- // oldValue,
- // model: this.value,
- // component: this
- // });
- //}
+ this.field.onChange({
+ value: value,
+ model: this.value,
+ oldValue,
+ component: this
+ })
},
diff --git a/zero.Backoffice.UI/app/editor/ui-editor-overlay.vue b/zero.Backoffice.UI/app/editor/ui-editor-overlay.vue
index 97c32a0b..d7e09bc0 100644
--- a/zero.Backoffice.UI/app/editor/ui-editor-overlay.vue
+++ b/zero.Backoffice.UI/app/editor/ui-editor-overlay.vue
@@ -3,7 +3,7 @@
-
+
diff --git a/zero.Backoffice.UI/app/services/overlay.ts b/zero.Backoffice.UI/app/services/overlay.ts
index 70ee4a7c..d32843ec 100644
--- a/zero.Backoffice.UI/app/services/overlay.ts
+++ b/zero.Backoffice.UI/app/services/overlay.ts
@@ -71,7 +71,7 @@ export function confirmDelete(title?: string, text?: string, warningText?: strin
}
-export function confirm(title: string, text?: string)
+export function confirm(title: string, text?: string, softdismiss?: boolean)
{
return open({
alias: 'confirm',
@@ -80,10 +80,10 @@ export function confirm(title: string, text?: string)
text,
confirmLabel: '@ui.confirm',
confirmType: 'default',
- closeLabel: '@ui.close',
+ closeLabel: '@ui.cancel',
},
autoclose: false,
- softdismiss: true,
+ softdismiss: typeof softdismiss !== 'undefined' ? softdismiss : true,
component: () => import('../ui/overlays/confirm.vue')
});
}
@@ -104,6 +104,24 @@ export function message(title: string, text?: string)
}
+export function editor(editorAlias: string, model: any, title?: string, options?: OverlayOptions)
+{
+ return open(extendObject({
+ alias: 'editor',
+ display: 'editor',
+ model: {
+ editor: editorAlias,
+ value: model,
+ title: title || '@ui.edit.title',
+ },
+ autoclose: false,
+ softdismiss: false,
+ component: () => import('../editor/ui-editor-overlay.vue'),
+ width: 820
+ }, options || {}));
+}
+
+
export function closeAll()
{
emitter.emit(event_closeOverlays);
diff --git a/zero.Backoffice.UI/app/styles/Modules/_box.scss b/zero.Backoffice.UI/app/styles/Modules/_box.scss
index 8e7304aa..fc12f3ce 100644
--- a/zero.Backoffice.UI/app/styles/Modules/_box.scss
+++ b/zero.Backoffice.UI/app/styles/Modules/_box.scss
@@ -43,7 +43,7 @@
.ui-tabs + .ui-box.is-connected
{
margin-top: -32px;
- border-top: 1px solid var(--color-line-onbg);
+ border-top: 1px solid var(--color-line);
border-top-left-radius: 0;
border-top-right-radius: 0;
}
diff --git a/zero.Core/Rendering/RazorRenderer.cs b/zero.Core/Rendering/RazorRenderer.cs
index 41618baa..34f87391 100644
--- a/zero.Core/Rendering/RazorRenderer.cs
+++ b/zero.Core/Rendering/RazorRenderer.cs
@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
+using System.Diagnostics;
using System.IO;
using System.Text.Encodings.Web;
@@ -19,6 +20,8 @@ public class RazorRenderer : IRazorRenderer, IDisposable
{
protected IRazorViewEngine ViewEngine { get; set; }
+ protected IRazorPageActivator PageActivator { get; set; }
+
protected ITempDataDictionaryFactory TempDataDictionaryFactory { get; set; }
protected IModelMetadataProvider ModelMetadataProvider { get; set; }
@@ -30,10 +33,11 @@ public class RazorRenderer : IRazorRenderer, IDisposable
protected HtmlHelperOptions HtmlHelperOptions { get; set; }
- public RazorRenderer(IRazorViewEngine viewEngine, IHttpContextAccessor httpContextAccessor, ITempDataDictionaryFactory tempDataDictionaryFactory,
+ public RazorRenderer(IRazorViewEngine viewEngine, IRazorPageActivator pageActivator, IHttpContextAccessor httpContextAccessor, ITempDataDictionaryFactory tempDataDictionaryFactory,
IModelMetadataProvider modelMetadataProvider, IServiceProvider serviceProvider, IOptions mvcHelperOptions)
{
ViewEngine = viewEngine;
+ PageActivator = pageActivator;
HttpContextAccessor = httpContextAccessor;
TempDataDictionaryFactory = tempDataDictionaryFactory;
ModelMetadataProvider = modelMetadataProvider;
@@ -118,6 +122,43 @@ public class RazorRenderer : IRazorRenderer, IDisposable
}
+ ///
+ /// Renders a razor page to a string
+ ///
+ public async Task PageAsync(string view, object model = null)
+ {
+ return await PageAsync(BuildActionContext(), view, model);
+ }
+
+
+ ///
+ /// Renders a razor page to a string
+ ///
+ public async Task PageAsync(ActionContext context, string page, object model = null)
+ {
+ IRazorPage pageResult = FindPage(context, page);
+
+ using StringWriter stringWriter = new();
+
+ RazorView view = new RazorView(ViewEngine, PageActivator, new List(), pageResult, HtmlEncoder.Default, new DiagnosticListener("ViewRenderService"));
+
+ ViewContext viewContext = BuildViewContext(context, stringWriter, view);
+ viewContext.RouteData = context.RouteData;
+ viewContext.ViewData.Model = model;
+
+ Microsoft.AspNetCore.Mvc.RazorPages.Page razorPage = (Microsoft.AspNetCore.Mvc.RazorPages.Page)pageResult;
+ razorPage.PageContext = new Microsoft.AspNetCore.Mvc.RazorPages.PageContext(viewContext);
+ razorPage.ViewContext = viewContext;
+
+ PageActivator.Activate(razorPage, viewContext);
+
+ await razorPage.ExecuteAsync();
+ await stringWriter.FlushAsync();
+
+ return stringWriter.ToString();
+ }
+
+
///
/// Renders a razor view to a string
///
@@ -216,6 +257,30 @@ public class RazorRenderer : IRazorRenderer, IDisposable
}
+ ///
+ /// Tries to find a page
+ ///
+ protected virtual IRazorPage FindPage(ActionContext actionContext, string pageName)
+ {
+ RazorPageResult getPageResult = ViewEngine.GetPage(executingFilePath: null, pagePath: pageName);
+ if (getPageResult.Page != null)
+ {
+ return getPageResult.Page;
+ }
+
+ RazorPageResult findPageResult = ViewEngine.FindPage(actionContext, pageName: pageName);
+ if (findPageResult.Page != null)
+ {
+ return findPageResult.Page;
+ }
+
+ IEnumerable searchedLocations = getPageResult.SearchedLocations.Concat(findPageResult.SearchedLocations);
+ string errorMessage = String.Join(Environment.NewLine, new[] { $"Unable to find page '{pageName}'. The following locations were searched:" }.Concat(searchedLocations));
+
+ throw new InvalidOperationException(errorMessage);
+ }
+
+
class GenericController : ControllerBase { }
@@ -271,6 +336,16 @@ public interface IRazorRenderer
///
Task ComponentAsync(string componentName, ActionContext context, object args = null);
+ ///
+ /// Renders a razor page to a string
+ ///
+ Task PageAsync(string view, object model = null);
+
+ ///
+ /// Renders a razor page to a string
+ ///
+ Task PageAsync(ActionContext context, string page, object model = null);
+
///
/// Renders a razor view to a string
///