npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@quandis/qbo4.configuration

v4.0.1-CI-20241009-155903

Published

The `@quandis/qbo4.Configuration.Web` package offers:

Downloads

1,035

Readme

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

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

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>
  <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.

// 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:

<qbo-contact type="fullname"></qbo-contact>

should render:

<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:

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:

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:

<!-- 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 ↓ ] <- a datalist of available templates

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">