@kluntje/core
v2.1.0
Published
Library to make web-component development a breeze
Downloads
4,232
Readme
Table of Contents
Getting Started
These instructions will get you a copy of the project up and running on your local machine for development purposes.
Install
Get the project up and running is pretty straight forward:
npm install @kluntje/core
And you are done.
Usage
Import the Kluntje core component in your js file and you can start using it:
import { Component } from '@kluntje/Core';
class MyAmazingComponent extends Component {}
customElements.define('my-amazing-component', MyAmazingComponent);
And use it in your HTML file:
<my-amazing-component></my-amazing-component>
Examples
Constructor Object Example
To add all kinds kluntje-features, you can also provide a constructor object.
import { Component } from "@kluntje/core";
class IncrementInput extends Component {
constructor() {
super({
ui: {
input: ".input"
button: ".handle-increment",
},
events: [
{
event: "click",
target: "button",
handler: "handleClick",
},
],
props: {
steps: {
type: "number",
defaultValue: 1,
}
}
initialStates: {
value: 0,
},
reactions: {
value: ["handleIncrement"],
},
});
}
afterComponentRender() {
this.ui.input.value = this.state.value;
}
handleClick() {
this.setState({ value: this.state.value + this.props.steps});
}
handleIncrement({value}) {
this.ui.input.value = value;
}
}
customElements.define("increment-input", IncrementInput);
Decorator Example
You can also use decorators to query elements, define props and bind events. Using decorators, our increment-input component could look like this:
import { Component, uiElement, uiEvent, prop } from '@kluntje/core';
class IncrementInput extends Component {
@uiElement('input')
input: HTMLInputElement;
@uiElement('.handle-increment')
button: HTMLButtonElement;
@prop({ defaultValue: 0, reactions: ['handleIncrement'], reactOnInit: true })
incrementValue: number;
@uiEvent('button', 'click')
handleClick() {
this.incrementValue += 1;
}
handleIncrement() {
this.input.value = this.incrementValue.toString();
}
}
customElements.define('increment-input', IncrementInput);
And our HTML will looks like:
<increment-input steps="5">
<input type="number" class="input" />
<button class="handle-increment">Increment</button>
</increment-input>
API
Since kluntje is based on the Custom Elements standard, our API extends the Custom Elements API.
Constructor Object
One way to add functionality to your component is to add a configuration-object to the Component-constructor (see first example). It is possible to add the following keys:
ui
Object containing key-value-string-pairs, mapping ui-elements from the component-dom to a key of the class property ui (e.g. this.ui.input)
constructor() {
super({
ui: {
inputs: "input", // all elements matching given selector
button: ".submit-btn :-one", // first element matching given selector (.submit-btn)
},
// ...
})
}
events
Array of event-definition-objects, mapping events to class-methods
constructor() {
super({
events: [
{
event: "click",
target: "button",
handler: "onFormSubmit",
},
{
event: "focusout",
target: "input",
handler: "enableInvalidStyling",
options: { once: true }
},
],
// ...
})
}
initialStates
Object to set initial values of states. States can later be changed via setState-method (e.g. this.setState({ value: 2 })). The current state can always be retrieved via this.state
constructor() {
super({
initialStates: {
isValid: false,
},
// ...
})
}
reactions
Object to define how to react to a state-change. The key defines the state to subscribe to, the value should be a string-array of class-methods to call on state-change
constructor() {
super({
reactions: {
isValid: ["onValidChange"],
},
// ...
})
}
props
A list of properties with corresponding attributes to be generated as accessors, optional type casting, default values and reaction when prop/attribute is changed.
simply pass the property name and the default value. Or pass the PropDefinition
object which has these default values:
| option | description | type | default value |
| --------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------- | --------------------------: |
| type
| when reading from attribute cast to this type. type hint "object"
is used for anything that can be parsed as JSON
. "boolean"
checks for the existence for the attribute not if the value is the string "true"
. "number"
will return NaN
when the attribute is missing. use 0
as default value if you prefer 0
instead of NaN
| 'string' | 'boolean' | 'number' | 'object'
| 'string'
|
| required
| component needs this attribute and can't fallback to a default value. warn when during the connectedCallback
the attribute is not on the custom element | boolean | false
|
| defaultValue
| default value to be returned when the attribute is not set | string | boolean | number | Record<string, unknown> | undefined | null
| undefined
|
| reactions
| a list of function, or the name of components methods to be called when the prop/attribute has changed. these methods will be invoked with the new value of the prop | Array<keyof T | Function> | null
| null
|
| reactOnInit
| call the reactions when during the connectedCallback live-cycle of the component the attribute is present in the markup | boolean
| false
|
| attributeName
| name of the attribute connected to the prop. | string
| kebab-case of the prop name |
class FancyDropdown extends Component {
constructor() {
super({
props: {
// can be used by `FancyDropdown.required = true` or `<fancy-dropdown required></component-name>`
// false will be the default value and implicitly has `boolean` as it's type
required: false,
// property definition with all options used.
// usage: `FancyDropdown.options = [4, 8, 12]` and `<fancy-dropdown select-options="[0.33, 0.5]"`
options: {
type: 'object', // use "object" for Array or any other JSON structure
required: false,
defaultValue: ['No options to select from'],
reactions: ['renderComponent'],
reactOnInit: true,
attributeName: 'select-options', // instead of default "options" as corresponding attribute.
},
},
// ...
});
}
/** @overrides */
renderingTemplate() {
return `<select>
${this.options.map(`--${option}--`).join()}
</select>`;
}
/** @overrides */
renderComponent(options) {
super.renderComponent();
console.log('rendered dropdown for: ', options);
}
}
customElements.define('fancy-dropdown', FancyDropdown);
<fancy-dropdown required select-options="[0.33, 0.5]"></fancy-dropdown>
custom types
Currently there is only build-in casting for these types: string | boolean | number | object
. But you can simply extend with your own types. e.g.:
class extends Component {
constructor() {
super({
props: {
startDay: {type: "date"}
}
});
}
/**
* adds type casting for "date"
*
* @overrides
* @param {(string | null)} attributeValue
* @param {string} type
* @returns {any}
*/
castFromAttribute(attributeValue, type) {
return type === "date" && attributeValue !== null ? new Date(attributeValue) : super.castFromAttribute(attributeValue, type)
}
}
Make sure when using your own custom types, and you set the prop (e.g. this.startDay = new Date()
), it's .toString()
generates a string that your custom castFromAttribute
override can cast back from.
useShadowDOM
Boolean flag indicating, whether to use Shadow-DOM (defaults to false)
asyncRendering
Boolean flag indicating, whether to use asyncRender method. Important for async ui-initialization
Decorators
Another way to add functionality to your component is to use decorators (see second example).
@uiElement
Binds first ui-element matching the given selector to the decorated property
@uiElement(".handle-increment")
button: HTMLButtonElement;
@uiElements
Binds all ui-elements matching the given selector to the decorated property
@uiElements("input")
inputs: Array<HTMLInputElement>;
@uiEvent
Binds given event of given uiElement(s) to the decorated method
@uiEvent("button", "click")
handleClick() {
console.log("button clicked!")
}
@prop
Converts the property in a prop with attribute binding, type casting, reactions... for more info see props segment from the constructor options.
class MyComponent extends Component {
@prop
active = false;
@prop({ type: 'object', reactions: ['renderComponent'], attributeName: 'select-options' })
options = ['No options to select from'];
}
@tag
Define a new custom element for the provided tag-name in a declarative way.
@tag("fancy-button")
class extends Component {
// ...
}
instead of customElements.define('fancy-dropdown', class extends Component {});
@renderAsync
Enables async rendering for the decorated component.
@renderAsync
class MyComponent extends Component {
// ...
}
Contributing
Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.
👤 Frederik Riewerts [email protected]
Show your support
Give a ⭐️ if this project helped you!
📝 License
This project is licensed under the Apache License 2.0 - see the LICENSE file for details