Theme
This feature implementations configures DomainModel metadata attributes and
extracts component descriptors for ui theme pages and components of an
application.
Add this feature using AddTheme() extension;
app.Features.AddTheme(...);
This feature abstraction provides following extensions to
DomainModelConventionCollection;
- Executes after building index and
Orderis defaulted toTheme.Defaults.Addconventions.AddTypeComponent(...); conventions.AddPropertyComponent(...); conventions.AddMethodComponent(...); conventions.AddParameterComponent(...); conventions.AddTypeSchema(...); conventions.AddPropertySchema(...); conventions.AddMethodSchema(...); conventions.AddParameterSchema(...); conventions.RemoveTypeComponent(...); conventions.RemovePropertyComponent(...); conventions.RemoveMethodComponent(...); conventions.RemoveParameterComponent(...); conventions.RemoveTypeSchema(...); conventions.RemovePropertySchema(...); conventions.RemoveMethodSchema(...); conventions.RemoveParameterSchema(...); - Executes after building index and
Orderis defaulted toTheme.Defaults.Configureconventions.EditTypeComponent(...); conventions.EditPropertyComponent(...); conventions.EditMethodComponent(...); conventions.EditParameterComponent(...); conventions.EditTypeSchema(...); conventions.EditPropertySchema(...); conventions.EditMethodSchema(...); conventions.EditParameterSchema(...);
See Layers / Domain / Ordering Conventions for more information on convention order mechanism
Below you can find sample for adding convention using extensions;
configurator.Domain.ConfigureConventions(conventions =>
{
// Adding component via extensions
conventions.AddMethodComponent<Button>(
when: c => c.Method.Name is "Login"
);
// Adding component configuration via extensions
conventions.EditMethodComponent<Button>(
when: c => c.Method.Name is "Login",
component: b => ...
);
// Adding schema via extensions
conventions.AddPropertySchema<Input>(
when: c => c.Property.Name is "Id"
);
// Adding schema configuration via extensions
conventions.EditPropertySchema<Input>(
when: c => c.Property.Name is "Id",
schema: i => ...
);
}
Default
Default Theme feature introduces many components and sets defaults for the domain model. It also configures error pages, layouts, and pages.
To use this theme directly without any update use this;
c => c.Default(
index: ...,
routes: [...],
errorPageOptions: ep => ...,
sideMenuOptions: sm => ...,
headerOptions: h => ...,
debugComponentPaths: ...,
configure: configurator => ...
)
Or you may prefer to create your custom theme on top of this default theme. Give
your custom configuration to configure, it runs after default theme rules;
public static class CustomThemeFeature
{
public static IFeature<ThemeFeature.Configurator> Custom(this ThemeFeature.Configurator x) => x.Default(
index: r => r.Index(),
routes:
[
// All app routes
],
configure: Configure
);
static void Configure(LayerConfigurator configurator)
{
configurator.Domain.ConfigureConventions(conventions =>
{
// Your custom conventions and page overrides
});
configurator.Ui.ConfigureComponentExports(exports =>
{
// Add component exports from your own theme assembly
exports.AddFromAssembly(typeof(CustomThemeFeature));
});
configurator.Ui.ConfigurePageDescriptors(pages =>
{
// Add other pages like `auth/login.vue` etc.
});
}
}
Default Rules
Below you can find the rules that come with this theme. For other user experiences, see UX Feature.
Displaying Properties
| Property Type | Component |
|---|---|
string, Guid, MailAddress | Text |
types with Locatable or Primitive attribute | Text |
| enums | Tag, with localized text and severity |
Uri | TextLink |
bool | Check |
DateOnly | Date, as dd-MM-yyyy |
DateTime | Date, as dd-MM-yyyy HH:mm:ss |
Editing Parameters
| Parameter Type | Input |
|---|---|
string, types with Primitive attribute | InputText |
int, long | InputNumber |
decimal | InputMoney |
double | InputRate |
bool | InputCheckbox, indeterminate when nullable |
Uri | InputUrl |
MailAddress | InputMailAddress |
DateOnly | InputDate, with a date picker |
Types
SimplePageandTabbedPageare added for any type at component path/page/*- Tab titles are localized and titleized when there are more than one tabs
[id]route parameter is mapped to first data property withIdPropertyattributeFieldsettitle is taken from the property withLabelattributeNavLinkpath is taken fromUiRouteattribute of the type- Enum types render inline with
InlineDatausing localized labels, and their severities are exported toseverities.jsonto color theirTag
Properties
- All public properties get a
UiDataattribute with a camelized name and titleized label - Properties with
IdPropertyattribute are hidden - Data table columns and fieldset fields render the component of their property
- Property dialogs use titleized property name as their header
Methods
- All actions with
ApiActionattribute getUiActionattribute FormPageis added for any method at component path/page/*/*Contentschema is added to any method, andSimpleFormis added for non-GETactions under contents- Each method is wired as a remote data, and each action as a remote action
- Remote data of locatable types include
route.paramsin their route Context.Modelis the body of remote actions with parameters- Remote actions publish an event named after the method with the response as data
GET,DELETEandTRACEremote actions are not allowed to have a body
- Remote data of locatable types include
- Buttons are labeled with titleized method names, their dialogs use the same as header and render a form or a content
- Buttons in
DataContaineractions are small - Data table export uses
;as CSV separator,{Method}.ExportFileNameand{Method}.FooterLabelare the localization keys of file name and footer
Parameters
- Parameters are rendered as
Inputlist underSimpleFormandFormPage, parameters withApiParameterattribute useInputschema - Non-nullable parameters are required, default values are taken from parameters and enum defaults are camelized
- Numeric parameters are marked as numeric
- Input labels show optionality under
SimpleFormandFormPage
Selects
Selectshows clear andSelectButtonallows empty for nullable parameters- Data is taken from the parameter or its type, e.g., enum inline data
- Required selects and select buttons under
DataPanelselect their first item automatically SelectButtonlabels are hidden unless they are iniftamode
Page Title
PageTitleis added for any type at component path/page/*/*page/title, and for any method at/page/*/*/*page/title- Actions of type, except
GETand initializers, are added to page title actions, includingroute.paramsfor locatable types - Route icon is rendered as an
Avatarfor locatable and singleton types
Error Page
- Error page includes safe links for routes with
ErrorSafeLink - Predefined error messages are
403Access Denied,404Page Not Found,500Unexpected Error and999Application Error, they can be customized witherrorPageOptions:
Layouts and Pages
DefaultLayoutincludes a side menu with routes marked forSideMenuand a header sitemap with enabled routes, customizable withsideMenuOptions:andheaderOptions:ModalLayoutis also included- Pages are built from routes using domain model and localization, each route becomes a page if it can be resolved
Menu and Routes
routes: list is a list of builder functions that takes Router as a parameter
and returns Route instance. Three types of route is supported out of the box.
r => r.Index() with { ... },
r => r.Root("/my-parent", "MyParent", "pi pi-user") with { ... },
r => r.Child("/my-parent/my-child", "MyChild", "/my-parent") with { ... },
A route only defines a page. To render a type as a link to its page, or a method
as a button that navigates to a page, give them UiRoute attribute using below
extensions;
configurator.Domain.ConfigureConventions(conventions =>
{
conventions.SetTypeRoute<MyParent>("/my-parent/[id]");
conventions.SetMethodRoute<MyParents>(nameof(MyParents.New), "/my-parent/new");
});
Page Types
There are three types of page, .Implemented(), .Described(d => ...) and
.Generated(g => ...). Each route has a Page property that defines what page
to render at that route.
By default pages use .Implemented(), meaning you need to have a .vue page at
the given route, e.g. for /my-parent route to work you need to have
/pages/my-parent.vue file in the UI project.
r => r.Root("/my-parent", "MyParent", "pi pi-user") with { Page = p => p.Implemented() }
// or
r => r.Root("/my-parent", "MyParent", "pi pi-user")
.Described(d => ...) is appropriate when you decide to fully describe the page
manually from your theme feature. Add an extension method to Page.Describer
for your custom page and use it in routes.
r => r.Root("/my-parent", "MyParent", "pi pi-user") with { Page = p => p.Described(d => d.MyParent()) }
.Generated(g => ...) is appropriate when you have necessary conventions to
generate a page descriptor out of a domain model.
r => r.Root("/my-parent", "MyParent", "pi pi-user") with { Page = p => p.Generated(g => g.Type<MyParent, SimplePage>()) }
Debugging Page Generation
We provide two tools to debug page generation process during a generate phase.
Debugging Component Paths
Enable path debugging to see which component paths get hit during page generation.
To enable;
debugComponentPaths: true
To filter a specfic path;
debugComponentPaths: new() { Filter = path => path.EndsWith(...) }
To include full paths next to each node;
debugComponentPaths: new() { IncludeFullPaths = true }
Combined;
debugComponentPaths: new()
{
Filter = path => path.EndsWith(...),
IncludeFullPaths = true
}
Inspecting Values
This feature is still in experimentation and might print false-negative output, meaning it might not capture every change of the inspected component or schema.
E.g., A described page or component will not be inspected. If you describe a child component or schema from the convention of parent component or schema it won't be inspected.
Configure an inspect to watch a property value of a component or a schema so that you can see which UX or Theme feature sets what value and in which order.
configurator.Domain.ConfigureBuilder(builder =>
{
// To inspect a component or a schema on types
builder.Inspect.TypeComponent<MyComponent>( // or inspect.TypeSchema
when: c => c.Type..., // optional to inspect specific type models
where: cc => cc.Path..., // optional to inspect specific component paths
component: mc => mc.Value // optional to inspect just this value
);
// To inspect a component or a schema on properties
builder.Inspect.PropertyComponent<MyComponent>( // or inspect.PropertySchema
when: c => c.Property..., // optional to inspect specific property models
where: cc => cc.Path..., // optional to inspect specific component paths
component: mc => mc.Value // optional to inspect just this value
);
// To inspect a component or a schema on methods
builder.Inspect.MethodComponent<MyComponent>( // or inspect.MethodSchema
when: c => c.Method..., // optional to inspect specific method models
where: cc => cc.Path..., // optional to inspect specific component paths
component: mc => mc.Value // optional to inspect just this value
);
// To inspect a component or a schema on parameters
builder.Inspect.ParameterComponent<MyComponent>( // or inspect.ParameterSchema
when: c => c.Parameter..., // optional to inspect specific parameter models
where: cc => cc.Path..., // optional to inspect specific component path
component: mc => mc.Value // optional to inspect just this value
);
// To inspect a component or a schema on any member
builder.Inspect.Component<MyComponent>( // or inspect.Schema
when: c => c..., // optional to inspect specific members
where: cc => cc.Path..., // optional to inspect specific component paths
component: mc => mc.Value // optional to inspect just this value
);
});
Only one inspect is allowed. If you configure more than one,
InvalidOperationExceptionwill be thrown