Quasar facility
Experimental support
Experimental
The model contract, generated frontend structure and shell integration can change between releases. Evaluate generated output and runtime dependencies before using this facility in a production application.
The Quasar facility generates Vue/Quasar frontend fragments for modeled UX components. It does not generate a complete frontend project or application shell.
The experimental full-stack Maven Archetype supplies a ready-to-run manual Quasar shell around those generated fragments, together with a Spring Boot backend. This does not change the Facility's ownership boundary: the Facility itself still generates fragments, not the manual shell.
1. Maven artifact and platform
Add the facility to the model module:
<dependency>
<groupId>org.joinedworkz.facilities</groupId>
<artifactId>ux-quasar</artifactId>
<version>${joinedworkz.version}</version>
<scope>provided</scope>
</dependency>The artifact contributes profile package org.joinedworkz.facilities.ux.quasar, platform Quasar and QuasarCartridge. Quasar specializes Base, and the Maven artifact brings common-base transitively.
2. Supported modeling and generation
QuasarCartridge processes only components carrying the ux stereotype. For such a component<ux>, it can generate:
- a Vue page and page script for each modeled page;
- route configuration for modeled component routes;
- page content based on referenced widgets and data;
- REST calls for modeled resource-operation invocations; and
- a Pinia application store when component properties or generated page behavior require it.
Non-UX components do not produce Quasar output. Base OpenAPI and diagram generation remains inherited for relevant model content.
3. Configuration
Quasar declares no facility-specific public joinedworkz.properties key in this release. It inherits applicable Base configuration and the generic cartridge and outlet controls.
To move the generated frontend tree:
outlet.generatedFrontendSource.directory=../frontend/src/generatedThe target must remain a dedicated replaceable directory. Custom facilities can define additional properties; this page does not describe a closed JoinedWorkz-wide registry.
4. Output and ownership
The replaceable generatedFrontendSource outlet defaults to src/main/frontend/src/generated.
The cartridge can clean the complete configured output directory during a full clean generation. Never place manual shell files, custom widgets or runtime helpers inside that tree.
5. Application-provided widgets
Built-in widgets do not need a componentRef. To bind a model widget to a Vue component maintained by the application, declare the Quasar-specific property in a model that uses the Quasar platform:
package com.example.frontend.widgets
import org.joinedworkz.facilities.ux.quasar
platform Quasar
widget PropertyList
componentRef='components/widgets.PropertyList' {
}componentRef has the form <module-path>.<ComponentIdentifier>. The last dot separates its two parts. For the example above, JoinedWorkz generates:
import PropertyList from "components/widgets/PropertyList.vue";It also registers PropertyList and uses <PropertyList> as the Vue tag. The model widget name does not override the identifier selected by componentRef.
The application shell must provide a module that its Quasar/Vite import configuration resolves under that path, commonly src/components/widgets/PropertyList.vue. Keep the component outside the replaceable generatedFrontendSource tree. JoinedWorkz validates the reference syntax, but not whether the .vue file exists; the subsequent frontend build verifies that integration.
A declared reference must:
- be a non-blank string without leading or trailing whitespace;
- contain a non-blank module path before the last dot; and
- end in a valid, non-reserved JavaScript component identifier.
Whitespace, control characters, a double quote or a backslash make the module path invalid. Other path-resolution rules belong to the manual shell and its frontend toolchain.
Invalid definitions report quasar.invalidComponentRef on the widget before generation. The custom stereotype is unsupported for Quasar widgets and is rejected even when componentRef is also present. This restriction does not remove custom from services or change another facility's widget contract.
Only widget definitions owned by the current model are validated in that model. Imported widget models are separate processing and validation units; see Imported model processing and diagnostics.
Top-level, field and table-cell widgets use the same componentRef contract. A built-in table-cell widget without the property retains the normal components/<WidgetName>.vue fallback.
6. Minimal model
package com.example.frontend
import org.joinedworkz.facilities.ux.quasar
platform Quasar
component<ux> GreetingFrontend {
route /home HomePage
page HomePage {
}
}With the default outlet this model generates:
src/main/frontend/src/generated/greetingfrontend/pages/HomePage.js
src/main/frontend/src/generated/greetingfrontend/pages/HomePage.vue
src/main/frontend/src/generated/greetingfrontend/router/routes_generated.jsAn application store is generated only when the modeled component or pages require it.
6.1 Action aliases
An action declared on a generated page uses its CMN alias unchanged as a local JavaScript function binding and at generated call sites:
package com.example.frontend.actions
import org.joinedworkz.facilities.ux.quasar
platform Quasar
component<ux> CustomerFrontend {
page CustomerPage {
action refreshCustomers
}
}Use a valid, non-reserved JavaScript binding identifier such as refreshCustomers. Names such as class, delete or await are invalid in this generated context, and every Action on the same page must use a unique alias. An alias must also differ from every other binding that the Quasar Cartridge actually generates in that Page's setup() scope. Depending on the Page model, this includes Page fields, data and Conditions as well as generated event, query, filter, tree and lifecycle helpers. Imported and base Pages do not add names to the current Page's generated scope.
JoinedWorkz reports quasar.invalidActionAlias on the Action for an invalid, reserved or duplicate Action alias. It reports quasar.conflictingActionAlias when a valid Action alias collides with another binding generated for that Page. Both checks run before Cartridge output and leave valid aliases unchanged. The Action alias remains independent of a Resource's operationName property and generated method-name contract.
Every generated Quasar Action function returns a Promise. An Action without commands returns Promise.resolve(). For an Action with commands, JoinedWorkz supports this execution subset:
- assignments run synchronously;
- consumed Resource operations are awaited one after another; and
- alternatives select one branch from a text condition or a modeled Page Condition and apply the same rules recursively to that branch. The Quasar rendering contract for those Conditions is described in Page conditions.
Commands run in model order. The Promise resolves to the result of the last consumed Resource operation that ran, after any later synchronous assignments have completed. It resolves to undefined when no consumed Resource operation ran. A command or condition outside this supported subset reports quasar.unsupportedActionCommand on the owning Action before output.
Assignment value pipelines retain Quasar's table-selection support. When rows is the data source of a generated table, rows|?selected()|:id reads the currently selected rows before projecting their id field. selected() must be the first transformation in that data pipeline; subsequent supported field projections and collection compositions are applied to the selection.
When an Action has a stereotype, the generated Promise chain delegates the successful result, an error and finalization to the manual application's src/helper/CommandHelper. The success path retains the Action result. The error path invokes the helper and remains rejected; finalization receives the last successful result, or undefined when the Action failed before producing one. The manual shell must provide the helper when it uses this contract.
Only actions owned by pages generated from component<ux> declarations in the current model are checked by this Cartridge invocation. Imported models remain separate processing and validation units; see Imported model processing and diagnostics.
6.2 Page conditions
The platform-independent CMN Pages and Page Conditions reference defines Page structure, scope and modeling semantics. This section defines the separate Quasar output contract.
Quasar emits a direct Page Condition as a Vue computed value, returns it from the Page's setup() scope and can reuse it in supported template bindings and Action conditions. The documented expression subset here is a Boolean field of Page data; the complete CMN value-expression grammar is not implicitly supported.
The following complete model loads one Draft, uses the same editingLocked Condition for several UI properties and invokes an Action whose conditional code references the Condition again:
package com.example.frontend
import org.joinedworkz.facilities.common.base
import org.joinedworkz.facilities.ux.quasar
platform Quasar
type Draft {
title: String
locked: Boolean
}
methodtype load GET produces='*' success=200
widget Button {
property visible: Boolean
property disabled: Boolean
}
resource /draft as Draft {
load()
}
component<ux> ExampleFrontend {
page EditPage {
use draft: /draft.load()
content {
draft.title
Button 'Save' disabled=editingLocked
Button 'Unlock' visible=editingLocked -> unlockDraft()
}
conditions {
editingLocked: draft.locked
}
action unlockDraft {
if (editingLocked) {
draft.locked = false
}
}
}
}The relevant generated Condition is equivalent to:
const editingLocked = computed(() => {
return draftData.value.locked
})The modeled Button invocation and Page Condition generate the relevant control flow in this form:
const onUnlockButtonClick = async (ev) => {
unlockDraft()
}
const unlockDraft = () => {
let actionResult
return (async () => {
if (editingLocked.value) {
draftData.value.locked = false
}
return actionResult
})()
}The generated Page loads /draft when it is mounted. The endpoint must return one JSON object matching Draft; the manual shell supplies the request helper and backend connection described below.
Quasar supports these direct Page-Condition bindings:
| Model property | Generated Quasar binding |
|---|---|
visible=condition | v-if="condition" |
hidden=condition | v-if="!condition" |
disabled=condition | :disable="condition" |
enabled=condition | :disable="!condition" |
readOnly=condition | :readonly="condition" |
if (condition) in an Action | if (condition.value) in the generated script |
Whether a binding is available on a particular element depends on that element's Widget definition and on the corresponding Quasar generator path. These names are not global CMN keywords or a closed JoinedWorkz-wide Widget property registry. Another facility can define a different contract.
The table describes properties used directly on the affected Content Element. Quasar's separate container-to-child Widget propagation is not part of this Page-Condition contract and must not be confused with Profile-controlled CMN property propagation.
Existing direct Boolean property values remain available where the Widget model and Quasar generator support them. The Condition pattern is the recommended modeling style for shared or conceptually important UI rules; it does not deprecate compatible existing models.
The boundaries are:
editableis not a documented Quasar Widget flag. Use the documentedreadOnlycontract where it matches the intended behavior.- Condition-to-Condition rendering, comparison expressions, arbitrary value pipelines, operator precedence and complete generated-name collision validation are not part of this contract.
- The supported generator path uses the referenced Page data in
content, as the example does withdraft.title. Ausedeclaration referenced only by a Condition is not supported by this documented path. - Page-Condition inheritance is unavailable because inherited Conditions are not transferred into the Core Model.
- These statements describe generated Quasar source. Runtime behavior still requires the manual application shell described below.
7. Manual application shell
The generated files are application fragments. A runnable frontend must provide a manually maintained Quasar/Vue shell containing, as applicable:
package.jsonand a lockfile with compatible Vue, Quasar, Vue Router and Axios dependencies, plus Pinia when a store is generated;- Quasar/Vite configuration and application bootstrap;
- the root application, layouts and router integration;
- imports or composition of the generated route fragments;
- CSS, icons and other static assets;
- runtime configuration such as the backend base URL or development proxy; and
- helper components, custom widgets or request/pagination helpers referenced by the modeled pages.
Keep the shell outside generatedFrontendSource and commit it as manual project source. First generate the frontend fragments from the model module:
mvn clean generate-sourcesThen run the commands defined by the manual shell from its directory, commonly:
npm ci
npm run buildFor local development, a shell can also provide:
npm run devThe exact Node.js, npm and frontend dependency versions are part of the application-shell contract, not supplied by the JoinedWorkz facility.
The executable Quasar example applies this ownership boundary in a complete experimental application with a generated Spring Boot API and an H2 database.
8. Boundaries
- Quasar support is experimental and not part of the stable compatibility surface.
- The facility does not generate a backend. Referenced HTTP operations need a separately implemented or generated service.
- The facility does not create
package.json, router bootstrap, layouts or other files required to start an application. - Generated route files export route fragments; the manual shell must compose them into its Vue Router configuration.
- Generated pages can depend on modeled widgets and helper APIs that the manual shell must provide.
- The complete generated frontend tree is replaceable and has no protected-region or merge contract.
- Maven generation does not validate the shell's installation, lint, build or runtime flow.
