UI

Baked introduces a metadata generation template which can be used to transform the business domain to UI metadata to be used in dynamic UI applications.

app.Layers.AddUi();

UiLayer provides IComponentDescriptor, IComponentSchema, IData types for building UI page metadata. A page metadata will have a component descriptor (represented by a ComponentDescriptor instance) at the top in the hierarchy containing Type, Schema, Name and Data properties.

UI Application

Generated metadata is rendered by @mouseless/baked, a Nuxt module. Create a Nuxt application, install the module and add it to nuxt.config.ts;

npm install @mouseless/baked
import Aura from "@primeuix/themes/aura";

export default defineNuxtConfig({
  modules: ["@mouseless/baked"],
  baked: {
    apiBaseURL: "http://localhost:5151",
    primevue: {
      theme: Aura
    }
  }
});

Generated UI metadata files are saved to Ui folder at $(OutDir) of your project. To use them in the UI application, set below properties in your application's .csproj file, so that they are copied to .baked folder of the UI application on every build;

<PropertyGroup>
  <CopyComponentDescriptors>true</CopyComponentDescriptors>
  <UiAppDir>$(ProjectDir)..\admin</UiAppDir>
</PropertyGroup>

UI layer is only added when a theme is set, e.g., Monolith recipe adds it with options: o => o.Theme(c => c.Default(...)), see Theme

Configuration Targets

This layer provides AppDescriptor, ComponentExports, LayerDescriptors and PageDescriptors configuration target for registering pages using ComponentDescriptor instances.

AppDescriptor

This target is provided in Generate phase. To configure it in a feature;

configurator.Ui.ConfigureAppDescriptor(app =>
{
    ...
});

ComponentExports

This target is provided in Generate phase. To configure it in a feature;

configurator.Ui.ConfigureComponentExports(exports =>
{
    ...
});

LayoutDescriptors

This target is provided in Generate phase. To configure it in a feature;

configurator.Ui.ConfigureLayoutDescriptors(layouts =>
{
    ...
});

PageDescriptors

This target is provided in Generate phase. To configure it in a feature;

configurator.Ui.ConfigurePageDescriptors(pages =>
{
    ...
});

To access the localization function from a feature use below extension method.

configurator.Ui.UsingLocalization(l =>
{
    // use this function to add that key to UI project;
    // l("A localized message")
    ...
});

Each call to this function will result UiLayer to bring that value of given key for every language to the UI project under .baked/ folder, e.g., .baked/locale.en.json file.

To access which keys are going to be brought to UI project from a feature, you can use UsingLocaleTemplate function.

configurator.Ui.UsingLocaleTemplate(localeTemplate =>
{
    ...
});

Generator System

To generate a page descriptor from a domain model, we provide a generator system that is added through domain model conventions. There are two generator attributes for this purpose;

  1. ComponentGenerator<TComponentSchema>
  2. Generator<TSchema>

The page generator starts with a Page component path to render a domain model into a ComponentDescriptor<TComponentSchema> instance. To add a component to a domain model, for instance to a type, use the AddTypeComponent extension of IDomainModelConventionCollection;

configurator.Domain.ConfigureConventions(conventions =>
{
    conventions.AddTypeComponent(
        when: c => c.Type...,
        where: cc => cc.Path.EndsWith("Page"),
        component: (c, cc) => ...
    );
});
  • when: is a predicate function that takes TypeModelMetadataContext c as a parameter and serves as a query to filter types
  • where: is a predicate funtion that takes ComponentContext cc as a parameter and is a selector that determines at which component path this convention should be applied
  • component: is a generator function that takes TypeModelMetadataContext c and ComponentContext cc as parameters, and is expected to return a ComponentDescriptor<TComponentSchema> instance, such as ComponentDescriptor<TabbedPage>

AddPropertyComponent, AddMethodComponent and AddParameterComponent extensions works the same way.

Once you add a component for a domain model at a specific component path, it will return that component instance when asked at that component path. You may use GenerateComponent extension to ask for a child component during a page generation. For example, below call asks for a component at ../title path for the type;

type.GenerateComponent(cc.Drill("Title"))

ComponentContext has a Drill(...) method that enables you to navigate deeper into component paths.

GenerateRequiredComponent is also provided to ensure that a component exists at the speficied path for the domain model. Otherwise, it will cause a generation error during post-build phase of dotnet build.

For non-component schemas, similar extensions are provided for domain models;

  • AddTypeSchema
  • AddPropertySchema
  • AddMethodSchema
  • AddParameterSchema

Use these extensions to associate domain models with non-component schemas such as Tab or Input. Once you add a schema for a domain model, you can access it using GenerateSchema<TSchema> or GenerateSchemas<TSchema> extension methods.

GenerateRequiredSchema<TSchema> is also provided to ensure that a schema exists at that path for that domain model. Otherwise, it will cause a generation error during post-build phase of dotnet build.

Configuring Existing Schemas

To add a convention that configures an existing schema, there are Edit...Component<TComponentSchema> and Edit...Schema<TSchema> helpers. Edit conventions works exactly the same way add/set conventions. In addition, to make sure there is a schema to configure, configuration conventions automatically filter out the domain models that don't have the given TComponentSchema or TSchema at the expected component path.

configurator.Domain.ConfigureConventions(conventions =>
{
    // This convention will automatically apply only to the types that have a
    // `SimplePage` component
    conventions.EditTypeComponent<SimplePage>(
        component: sp =>
        {
            sp.Title = ...;
        }
    );
});

Bake.vue

Bake.vue is the core utility component that interprets a component descriptor and sets up the defined component so that it integrates with the rest of the page.

Data System

Data system allows Bake.vue to fetch data and pass it to the component. Use Baked.Ui.Fetch builder class to create different types of data for a component. Below you can find some example data usages.

...
// inline a static value
Fetch.Inline("a string");
Fetch.Inline(new { an: "object" });
// set a dynamic value that is fetched from an api endpoint
Fetch.Remote("some/endpoint");
// set a dynamic value fetched using a composable, `useRoute` in this case
Fetch.Computed.UseRoute("query");
// set a context value, model in this case
Fetch.Context.Model();
...

Action System

Action system allows Bake.vue to execute actions upon model change or submit event of the component. Use Baked.Ui.Execute builder class to create types of actions for a component. Below you can find some example action usages.

...
// publishes a page-wide event
Execute.Publish.Event("my-button-clicked");
// sets value to a page-wide context with given key
Execute.Publish.PageContextValue("my-value");
// redirects app to given route
Execute.Local.UseRedirect("/return-page");
// sends configured request to a remote endpoint
Execute.Remote("some/endpoint",
  // goes to a page after remote action
  postAction: Execute.Local.UseRedirect("/return-page")
);
...

Reaction System

Reaction system allows Bake.vue to react to publish actions, either event or page context value. Available reactions are Reload and Show. Below you can find some example action usages.

// reacts on an event
component.ReloadOn("my-button-clicked");
// reacts when a page context value changes
component.ReloadWhen("my-value");
// shows/hides component depending on the constraint
component.ShowOn("input-changed", Evaluate.Is("show"));
// shows when `selection` value is valid using a custom validator composable
// `useMyCustomValidator`
component.ShowWhen("selection",
  constraint: Evaluate.Composable.Use("MyCustomValidator")
);