Library to make web-component development a breeze
- Getting Started
- Install
- Usage
- Examples
- Constructor Object Example
- Decorator Example
- API
- Contructor Object
- ui
- events
- initialStates
- reactions
- props
- useShadowDOM
- asyncRendering
- Decorators
- @uiElement
- @uiElements
- @uiEvent
- @prop
- @tag
- @renderAsync
- Contributing
- Show your support
- š License
These instructions will get you a copy of the project up and running on your local machine for development purposes.
Get the project up and running is pretty straight forward:
``sh`
npm install @kluntje/core
And you are done.
Import the Kluntje core component in your js file and you can start using it:
`javascript
import { Component } from '@kluntje/Core';
class MyAmazingComponent extends Component {}
customElements.define('my-amazing-component', MyAmazingComponent);
`
And use it in your HTML file:
`html`
#### Constructor Object Example
To add all kinds kluntje-features, you can also provide a constructor object.
`javascript
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:
`javascript
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:
`html`
Since kluntje is based on the Custom Elements standard, our API extends the Custom Elements API.
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:
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)
`javascript`
constructor() {
super({
ui: {
inputs: "input", // all elements matching given selector
button: ".submit-btn :-one", // first element matching given selector (.submit-btn)
},
// ...
})
}
Array of event-definition-objects, mapping events to class-methods
`javascript`
constructor() {
super({
events: [
{
event: "click",
target: "button",
handler: "onFormSubmit",
},
{
event: "focusout",
target: "input",
handler: "enableInvalidStyling",
options: { once: true }
},
],
// ...
})
}
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
`javascript`
constructor() {
super({
initialStates: {
isValid: false,
},
// ...
})
}
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
`javascript`
constructor() {
super({
reactions: {
isValid: ["onValidChange"],
},
// ...
})
}
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. 'string' | 'boolean' | 'number' | 'object'
| 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' |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 | 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 | 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 |
`javascriptFancyDropdown.required = true
class FancyDropdown extends Component {
constructor() {
super({
props: {
// can be used by or boolean
// false will be the default value and implicitly has as it's type
required: false,
// property definition with all options used.
// usage: FancyDropdown.options = [4, 8, 12] and
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 ;
}
/* @overrides /
renderComponent(options) {
super.renderComponent();
console.log('rendered dropdown for: ', options);
}
}
customElements.define('fancy-dropdown', FancyDropdown);
`
`html`
#### 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.:
`javascript
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.
Boolean flag indicating, whether to use Shadow-DOM (defaults to false)
Boolean flag indicating, whether to use asyncRender method. Important for async ui-initialization
Another way to add functionality to your component is to use decorators (see second example).
Binds first ui-element matching the given selector to the decorated property
`javascript`
@uiElement(".handle-increment")
button: HTMLButtonElement;
Binds all ui-elements matching the given selector to the decorated property
`javascript`
@uiElements("input")
inputs: Array
Binds given event of given uiElement(s) to the decorated method
`javascript`
@uiEvent("button", "click")
handleClick() {
console.log("button clicked!")
}
Converts the property in a prop with attribute binding, type casting, reactions...
for more info see props segment from the constructor options.
`javascript
class MyComponent extends Component {
@prop
active = false;
@prop({ type: 'object', reactions: ['renderComponent'], attributeName: 'select-options' })
options = ['No options to select from'];
}
`
Define a new custom element for the provided tag-name in a declarative way.
`javascript`
@tag("fancy-button")
class extends Component {
// ...
}
instead of customElements.define('fancy-dropdown', class extends Component {});
Enables async rendering for the decorated component.
`javascript``
@renderAsync
class MyComponent extends Component {
// ...
}
Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.
š¤ Frederik Riewerts
Give a āļø if this project helped you!
This project is licensed under the Apache License 2.0 - see the LICENSE file for details