npm package: @quandis/qbo4.configuration
Description: The `@quandis/qbo4.Configuration.Web` package offers:
Latest version: 4.0.1-CI-20260410-172114

﻿# Overview

The `@quandis/qbo4.Configuration.Web` package offers:

- Management of options classes via dependency injection
- A `QboTemplate` web component base class that supports configuration driven rendering options

# Configuration Context

When designing web components that leverage API endpoints, one needs the URL of the API endpoint to be configurable.
The `qbo4` Razor Class Libraries provide these API endpoints, but they may be served from diffent paths in different installations:

|Example|Path|
|-|-|
|Test project|`/`|
|qbo4.Fintech.Web|`/core`|
|Client A|`/qbo/4`|

To accommodate this, `qbo4.configuration.js` defines a Lit Context `qboConfigContext`, which can be used as follows:

```typescript
import { consume } from "@lit/context";
import { IQboConfig, qboConfigContext } from "@quandis/qbo4.configuration";

export class MyComponent extends LitElement {
	...
    @consume({ context: qboConfigContext, subscribe: true })
    @property({ attribute: false })
    public qboConfig?: IQboConfig;

	@property()
    apiEndpoint: string | null = null;

	async connectedCallback() {
		super.connectedCallback();
		this.apiEndpoint ??= `${this.qboConfig?.baseHref}/my/api/endpoint`;
	}
	...
}
```

The `qboConfigContext` is populated by making a `HEAD` request to '/', and looking for a Http Header named `qbo4-BaseHref`.

You can emit such a header via `web.config` as follows:

```xml
<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="qbo4-BaseHref" value="/core" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>
```


# Options Classes

Similar to Microsoft's `IConfiguration`, the `qbo4.Configuration` package's classes manage configuration settings 
for a web application via dependency injection.

Including the `qbo4.Configuration.js` script will provide:

`qbo4.services`: a container for dependency injection (including `qbo4.services.container` for direct access to the `tsyringe` container)
`qbo4.configuration`: a set of classes to manage configuration settings
`IConfiguration`: an interface to manage configuration settings

It provides for dependency injections via the `tsyringe` package.

## Typescript Usage

```typescript
import 'reflect-metadata';
import { Configuration, IConfiguration, IConfigurationSource, IConfigurationSourceToken, IConfigurationToken, JsonConfigurationSource, services } from '@quandis/qbo4.configuration';


class OptionsA { public name: string = ''; }
class OptionsB { public count: number = 0; }

const source1 = new JsonConfigurationSource({ A: { name: 'Alice' }, B: { count: 27 } });
const source2 = new JsonConfigurationSource({ A: { name: 'Bob' }, C: { enabled: true } });
services.container.register<IConfigurationSource>(IConfigurationSourceToken, { useValue: source1 });
services.container.register<IConfigurationSource>(IConfigurationSourceToken, { useValue: source2 });

const config: IConfiguration = services.container.resolve<IConfiguration>(IConfigurationToken);
expect(config).not.null;
const a = config.getSection('A').bind(OptionsA);
expect(a.name).equal('Bob');

const b = config.getSection('B').bind(OptionsB);
expect(b.count).equal(27);
```

## Browser Usage

```html
<html>
  <head>
	<script src="//configuration/js/qbo4.Configuration.js"></script>
	<script type="text/javascript">
        const aiConfig = new qbo4.logging.JsonConfigurationSource({ 'ApplicationInsights': { instrumentationKey: '651cc99f-0b30-4f27-8918-e53dfed1a2c2' } });
        qbo4.services.container.register(qbo4.logging.IConfigurationSourceToken, { useValue: aiConfig });
		// now the configuration settings are available for dependency injection

		// later in your code, web components, or scripts:
		const instances = qbo4.services.container.resolveAll<SomeInterface>(SomeInterfaceToken);

    </script>
  </head>
  <body>
  </body>
</html>
```

## Example: ApplicationInsights Logger

Assume we want to inject an `ApplicationInsights` logger, where the `InstrumentationKey` is stored in the configuration settings.

```typescript
// An options class
export class ApplicationInsightsOptions {
	public InstrumentationKey: string = '';
}

// Prepare the options for dependency injection (a tsyring pattern)
export const ApplicationInsightsOptionsToken: InjectionToken<ApplicationInsightsOptions> = 'ApplicationInsightsOptions';

// The logger class
@injectable()
export class ApplicationInsights {
	constructor(
		@inject(ApplicationInsightsOptionsToken) private options: ApplicationInsightsOptions
	) {
		// Initialize the logger
		// ...
	}
}

// Prepare the class for dependency injection
export const ApplicationInsightsToken: InjectionToken<ApplicationInsights> = 'ApplicationInsights';
```

# QboTemplate Web Component

The `QboTemplate` web component enables power users to configure the rendering of a web component a runtime.
Assume we have a `Contact` web component that renders a contact's name, address, and other information,
and we choose to support different layouts based on a `type` attribute:

```html
<qbo-contact type="fullname"></qbo-contact>
```

should render:

```html
<input name="first" placeholder="First name"/>
<input name="middle" placeholder="Middle name"/>
<input name="last" placeholder="Last name"/>
```

Such a web component might look like this:

```typescript
import { html } from 'lit';
import { customElement } from 'lit/decorators.js';

@customElement('qbo-contact')
export class ContactComponent extends LitElement {
    jsonData: object = {
        'Contact': {
            'First': 'James',
            'Middle': 'Tiberious',
			'Last': 'Kirk'
        }
    }

	render() {
		return html`
			<input name="first" placeholder="First name" value="${this.jsonData['Contact']['First']}"/>
			<input name="middle" placeholder="Middle name" value="${this.jsonData['Contact']['Middle']}"/>
			<input name="last" placeholder="Last name" value="${this.jsonData['Contact']['Last']}"/>
		`;
	}
}
```

Futher assume that you want to allow a power user to configure the layout of the `qbo-contact` web component at runtime.
To accomplish this, shift the `ContactComponent` to derive from a `QboTemplate` class:

```typescript
import { html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { templates, TemplateFunction, QboTemplate } from '@quandis/qbo4.configuration';

export class ContactComponent extends QboTemplate {
    constructor() {
        super();  
        this.type ??= 'default'; 
    }

    jsonData: object = {
        'Contact': {
            'First': 'James',
            'Middle': 'Tiberious',
			'Last': 'Kirk'
        }
    }

	// this will map to the 'default' type, specified by the decorator parameter.
	@template('default')
	renderDefault(component: any) {
		return html`<input name="first" placeholder="First name" value="${component.jsonData['Contact']['First']}"/>
			<input name="middle" placeholder="Middle name" value="${component.jsonData['Contact']['Middle']}"/>
			<input name="last" placeholder="Last name" value="${component.jsonData['Contact']['Last']}"/>`;
	}

	// this will map to the 'reverse' type, becaue no parameter is passed to the decorator
	@template()
	reverse(component: any) {
		return html`<input name="last" placeholder="Last name" value="${component.jsonData['Contact']['First']}"/>,
			<input name="first" placeholder="Last name" value="${component.jsonData['Contact']['Last']}"/>
			<input name="middle" placeholder="Middle name" value="${component.jsonData['Contact']['Middle']}"/>`;
	}
	// Notice: no render method here - see below.
}
```

Now you can render the `qbo-contact` web component with a `type` attribute:

```html
<!-- This will render last name, first name, middle name -->
<qbo-contact type="reverse"></qbo-contact>

<!-- This will render first name, middle name, last name -->
<qbo-contact type="default"></qbo-contact>

<!-- So will this, because we set type = 'default' in the constructor -->
<qbo-contact></qbo-contact>
```

> Note that the template functions accept a `component` parameter, which is the instance of the `ContactComponent` class.
Ensure you reference your component's properties via `component` rather than `this`.

Mapping `type` to different rendering functions is all well and good, but not particularly interesting.

The interesting part is allowing a power user to create new rendering functions (or edit existing ones) at runtime.

The `QboTemplate` class will listen for a `ctrl-dblclick` event if it is contained in an element with a `qbo-design` class
(indicating that we are in 'design' mode).

When the `ctrl-dblclick` event is triggered, a dialog will open, presenting the user with something like:

---
**Edit Template**

[ default &#8595; ] <- *a datalist of available templates*

```html
1 <input name="first" placeholder="First name" value="${component.jsonData.Contact.First]}"/>
2 <input name="middle" placeholder="Middle name" value="${component.jsonData.Contact.Middle]}"/>
3 <input name="last" placeholder="Last name" value="${component.jsonData.Contact.Last}"/>
```
^ *an editor for editing the rendering function*

[ *Ask AI to code...* ]

[ Save ] [ Cancel ]

---

Feature include:

- As the power user modifies the template, the underlying UI will update in real-time.
  - Any syntax errors will be trapped and displayed in the editor
- The editor can be dragged and resized as needed
- The `type` at the top can be selected from a `datalist`, and new types can be entered.
- The `Ask AI to code...` input will apply Generative AI to modify the code as you describe.
- Clicking `Save` will save the template in configuration (server-side), and be available for use.
- Clicking `Cancel` will discard any changes, reverting to the original rendering.

## How it Works

Deployment of `@qbo4.Configuration` web components is paired with deployment of the `qbo4.Configuration.Web` Razor Class Library,
which includes server-side functionality to store are retrieve template code. 
The `QboTemplate` class will interact with the server-side `/template` endpoint to store and retrieve templates.

The `QboTemplate` class will render the web component based on the `type` attribute, using the `templates` map.
If the `type` is not found in the `templates` map, the component will `fetch` templates stored in configuration
from the `/template/search/{ComponentClassName}` endpoint.

> If a component's `Typescript` class defines a `type` that also exists in configuration, the configuration will take precedence.

## Spiderman Clause

With great power comes great responsibility. Only trusted users should be allowed to edit templates. 
The ultimate control of this remains server-side with the authorization policies of the `/template` endpoints.
For the front-end, the `QboTemplate` class will only allow editing of templates if the containing element has a `qbo-design` class.
We recommend that the `qbo-design` class be added to the `body` for trusted power users only. 
This will prevent accidental editing attempts of templates by regular users.

As with all qbo-based configuration, enable and test in a lower environment before deploying to production.

# RoadMap

- add intellisense to CodeMirror for component-specific properties (including Json data)
- shift the Editor to a separate web component
- enable custom Editors, to simplify changes to complex controls
- create a GUI designer that detects available web components via DI
- propagate changes to a components `type` property to parent components
  - if a user creates a new `type` of an address control called `streetview`, and the address control is part of a property control, save the property control such that it uses `<qbo-address type="streetview">`