Welcome to @kluntje/core 👋
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",
button: ".submit-btn :-one",
},
})
}
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' |
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 |
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> |
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: {
required: false,
options: {
type: 'object',
required: false,
defaultValue: ['No options to select from'],
reactions: ['renderComponent'],
reactOnInit: true,
attributeName: 'select-options',
},
},
});
}
renderingTemplate() {
return `<select>
${this.options.map(`--${option}--`).join()}
</select>`;
}
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"}
}
});
}
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 frederik.riewerts@gmail.com
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