Package Exports
- skinny-widgets
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (skinny-widgets) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Skinny Widgets
Skinnable component set based on W3C standards compilant technologies.
Allows you to develop ui layer abstract code and easily switch between layouts w/o rewriting the code.
If you are looking for web components based data grid library check out gridy-grid project.
Examples
To see examples run http server to serve files
npm startopen one of examples in browser, e.g. http://127.0.0.1:8080/examples/compat/browser-compatible.html
For more examples check skinny-widgets-samples project.
Configuration
All-Inclusive Elements autoloading
If you don't need to do advanced configurations you can enable sk elements autoloading by adding sk-auto and sk-theme-{theme} html or body attribute. Elements autoregister will run at the end of skinny-widgets-bundle.js loading so to start using skinny-widgets components you will have to just add one script and one attribute to your document.
<html class="sk-auto sk-theme-jquery">
<script src="dist/sk-compat-bundle.js"></script>
<sk-button>Hello</sk-button>
</html>Template preload can be disabled by adding class sk-auto-notpl.
Modular loading
As browsers still doesn't support relative dependency paths and importmaps you may need some code to resolve dependency on dateutils and other possible libraries. And you're still free to not load the code that relies on them.
<script defer src="/node_modules/es-module-shims/dist/es-module-shims.js"></script>
<script type="importmap-shim">
{
"imports": {
"dateutils": "/node_modules/dateutils/src/global.js",
"dateutils/": "/node_modules/dateutils/"
}
}
</script>
<script type="module-shim">
import { SkDatePicker } from '/node_modules/skinny-widgets/src/sk-datepicker.js';
customElements.define('sk-datepicker', SkDatePicker);
</script>
<sk-datepicker theme="antd" id="myDatePicker" value="12/25/2019"></sk-datepicker>Although you're still free to use static scripts with bundle that exposes skinny web components into global scope with transpilation for older browsers. You may still need to load polyfills before bundle. Check examples for more info.
<script src="/dist/skinny-widgets-bundle.js"></script>
<script type="module">
customElements.define('sk-config', SkConfig);
customElements.define('sk-button', SkButton);
</script>sk-config element is acts as common configuration host service, elements tries to find it on connect and request necessary configurations, so you don't have to repeat params for every.
<sk-config
styles='{"default.css": "/node_modules/skinny-widgets/src/theme/default/default.css"}'
theme="jquery"
base-path="/node_modules/skinny-widgets/src"
></sk-config>You can override config search selector for every element by defining config-sl attribute:
<sk-config
id="alternativeConfigSelector"
theme="jquery"
base-path="/node_modules/skinny-widgets/src"
></sk-config>
<sk-input config-sl="#alternativeConfigSelector"></sk-input>Attributes
theme - one of supported themes: e.g. 'antd' or 'jquery' (default: 'default')
base-path - path to sources and resources root, (default: '/node_modules/skinny-widgets/src/')
styles - set of style definitions used by elements as Object { 'name': 'path' }. Commonly styles are mounted into shadow root by given path. This allows to provide CSS isolation with file browser cashing. Theme code has style defaults, so you you don't override them configuration is not needed, but if you need a way to override styles programmatically mounted and not hardcoded in templates this is the option.
use-shadow-root - specifies if Shadow Root encapsulation is enabled for elements (default: 'true')
lang - locale code, currently used only for datepicker (default: 'en_US')
reflective - element auto re-render on external events, currently sk-config attrs change (default: true)
Skins
antd - and.design framework styles are used for layout
jquery - web components styled for jquery-ui
default - web components and standard native browser elements and technologies are used for layout
Templates preload
http/2+ allows to do request multiplexing that means a lot of smaller requests to the same host are better than big blocking requests to multiple hosts. But if you think you still need to load all in one bundle, you can use aggregated template bundles loaded with js or included to page by server. You can rely on sk-auto template preloading or do manual preload.
<script>
const loadTemplates = async () => {
const response = await fetch('node_modules/skinny-widgets/dist/antd.templates.html')
.then(response => response.text())
.then(text => document.body.insertAdjacentHTML('beforeend', text));
};
loadTemplates();
</script>Widgets
sk-button
<sk-button id="myButton" button-type="primary">
MyButton
</sk-button>slots
default (not specified) - contents inside button
label - the same as label
sk-input
<sk-input id="myInput1" value="foobar"></sk-input>attributes
value - value syncronized with internal native element
disabled - disabled attribute syncronized with internal native element
list - datalist attribute for input
slots
default (not specified) - draws label for input
label - draws label for input
<sk-input id="myInput1">
<span slot="label">Some Label</span>
</sk-input>sk-checkbox
slots
default (not specified) - draws label for input
label - draws label for input
attributes
value - value syncronized with internal native element
disabled - disabled attribute syncronized with internal native element
<sk-checkbox theme="antd" base-path="/node_modules/skinny-widgets/src" id="myCheckbox"></sk-checkbox>sk-datepicker
<sk-datepicker base-path="/node_modules/skinny-widgets/src" id="myDatePicker" value="12/25/2019"></sk-datepicker>slots
default (not specified) - draws label for input
label - draws label for input
attributes
open - present if datepicker calendar widget is currently opened (for native datepicker only represents focus state)
fmt - date value format (default: 'm/d/Y')
- {Date} d [01-31]
- {Short_Day_Name} D [Su, Mo, Tu, We, Th, Fr, Sa]
- {Date} j [1-31]
- {Full_day_name} l [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
- {Week_day_number} w 0=Sunday, 1=Monday, 2=Tuesday etc...
- {Nth_day_of_year} z [1-365] except leap years
- {Full_month_name} F [January, February, ...]
- {Month_number} m [01-12]
- {Month_name_stripped_to_three_letters} M [Jan, Feb, ...]
- {Month_number} n [1-12]
- {Days_in_current_month} t [28-31]
- {Full_year} Y [1900, ...]
- {Last_two_digits_of_a_year} y [01-99]
- {Time_postfix} a [am|pm]
- {Time_postfix} A [AM|PM]
- {Hours_in_12h_format} g [1-12]
- {Hours_in_24h_format} G [0-23]
- {Hour_in_12h_format_with_padding} h [01-12]
- {Hours_in_24h_format_with_padding} H [00-23]
- {Minutes_with_padding} i [00-59]
- {Seconds_with_padding} s [00-59]
- {Timezone} Z 2 for GMT+2
sk-alert
<sk-alert type="error">Error ! Error !</sk-alert>slots
default (not specified) - alert contents
attributes
closable - close button
sk-dialog
<sk-button theme="antd" base-path="/node_modules/skinny-widgets/src" id="myButton" button-type="primary">
<span slot="label">Show Dialog</span>
</sk-button>
<sk-dialog theme="antd" base-path="/node_modules/skinny-widgets/src" id="myDialog" title="Some Title" type="confirm">
Hello Dialog !
</sk-dialog>
<script type="module">
import { SkButton } from '/node_modules/skinny-widgets/sk-button.js';
import { SkDialog } from '/node_modules/skinny-widgets/sk-dialog.js';
customElements.define('sk-button', SkButton);
customElements.define('sk-dialog', SkDialog);
myButton.addEventListener('click', (event) => {
myDialog.open();
});
myDialog.onconfirm = function(event) {
console.log('confirmed');
this.close();
};
myDialog.oncancel = function(event) {
console.log('cancelled');
this.close();
};
</script>As most of sk-elements sk-dialog by default renders it's contents inside Shadow DOM. So in case you want access some its data you will have to to extend SkDialog class, implement getters that will access internals and return values and register class as new Custom Element. It is quite easy and gives a better solution for code structure;)
slots
label - dialog window title
sk-tabs
<sk-config
styles='{"antd.css": "/node_modules/skinny-widgets/src/theme/antd/antd.min.css"}'
theme="antd"
base-path="/node_modules/skinny-widgets/src"
lang="ru"
></sk-config>
<sk-tabs id="tabs">
<sk-tab title="foo">
some foo tab contents
</sk-tab>
<sk-tab title="bar">
some bar tab contents
</sk-tab>
<sk-tab title="baz">
some baz tab contents
</sk-tab>
</sk-tabs>sk-select
<sk-config
styles='{"antd.css": "/node_modules/skinny-widgets/src/theme/antd/antd.min.css"}'
theme="antd"
base-path="/node_modules/skinny-widgets/src"
lang="ru"
id="skConfig"
></sk-config>
<sk-select id="skSelect">
<option value="default">default</option>
<option value="antd">antd</option>
<option value="jquery">jquery</option>
</sk-select>
<script type="module">
import { SkConfig } from '/node_modules/skinny-widgets/sk-config.js';
import { SkSelect } from '/node_modules/skinny-widgets/mysk-select.js';
customElements.define('sk-config', SkConfig);
customElements.define('sk-select', SkSelect);
skSelect.value = myConfig.getAttribute('theme');
skSelect.addEventListener('change', (event) => {
skConfig.setAttribute('theme', event.target.value);
}, false);
</script>attributes
multiple - sk-select draws multiple selection widget and represent selected options as comma-separated list.
value-type - for multiselect native - show only first selected value as native elements, selectOptions prop will give you all of them, default -- comma-separated list of values.
sk-switch
<sk-config
styles='{"antd.css": "/node_modules/skinny-widgets/src/theme/antd/antd.min.css"}'
theme="antd"
base-path="/node_modules/skinny-widgets/src"
></sk-config>
<sk-switch id="mySwitch" checked value="11"></sk-switch>
<script type="module">
import { SkConfig } from '/node_modules/skinny-widgets/sk-config.js';
import { SkSwitch } from '/node_modules/skinny-widgets/sk-switch.js';
customElements.define('sk-config', SkConfig);
customElements.define('sk-switch', SkSwitch);
</script>sk-tree
<sk-tree link-tpl-str='<a href="?categoryId={{ id }}">{{ name }}</a>' tree-data='[{"id": 1, "name": "category1", "parentId": 0}, {"id": 2, "name": "category2", "parentId": 0}, {"id": 3, "name": "category11", "parentId": 1}, {"id": 4, "name": "category111", "parentId": 3}]'></sk-tree>attributes
expanded - "false" value collapses the tree and renders it as collapsible, "true" renders as expanded but collapsible
selected-id id of the element that's branch will be forced to be extended
sk-spinner
<sk-config
styles='{"antd.css": "/node_modules/skinny-widgets/src/theme/antd/antd.min.css"}'
theme="antd"
base-path="/node_modules/skinny-widgets/src"
lang="ru"
></sk-config>
<sk-button id="skButton" button-type="primary">
<span slot="label">Show Dialog</span>
</sk-button>
<sk-spinner id="skSpinner"></sk-spinner>
<script type="module">
import { SkSpinner } from '/node_modules/skinny-widgets/sk-spinner.js';
import { SkButton } from '/node_modules/skinny-widgets/sk-button.js';
customElements.define('sk-spinner', SkSpinner);
customElements.define('sk-button', SkButton);
skButton.addEventListener('click', (event) => {
skSpinner.dispatchEvent(new CustomEvent('toggle'));
});
</script>sk-form
sk-form sends it's fields as FormData (multipart request), attributes are the same as for form element type
to have data submission working sk-form must have sk-button with type="submit" inside
supported form element types:
sk-input, sk-checkbox, sk-select, sk-datepicker, input, textarea
if novalidate attribute is not set validation is performed on submit
if validation is not passed forminvalid event is emitted with info in event.detail.errors
if validation successfull formvalid event is emitted and form attempts to send data
it throws formsubmitsuccess event on 200 or any success response
and formsubmiterror
request/response data is stored in event.detail.request callback argument field
<sk-form action="/submit" method="POST" id="myForm">
<span slot="fields">
<sk-input name="firstName" id="formInput1">
<span slot="label">First Name</span>
</sk-input>
<sk-input name="secondName" id="formInput2">
<span slot="label">Second Name</span>
</sk-input>
<sk-input name="address" id="formInput3">
<span slot="label">Address</span>
</sk-input>
<sk-checkbox name="isSingle" id="formCheckbox"><span slot="label">Single</span></sk-checkbox>
<sk-select name="gender" id="formSelect">
<option value="male">Male</option>
<option value="female">Female</option>
</sk-select>
<sk-button id="formButton" type="submit" button-type="primary">
<span slot="label">Submit</span>
</sk-button>
</span>
</sk-form>
<script type="module">
import { SkButton } from '/node_modules/skinny-widgets/src/sk-button.js';
import { SkInput } from '/node_modules/skinny-widgets/src/sk-input.js';
import { SkSelect } from '/node_modules/skinny-widgets/src/mysk-select.js';
import { SkForm } from '/node_modules/skinny-widgets/src/sk-form.js';
import { SkCheckbox } from '/node_modules/skinny-widgets/src/sk-checkbox.js';
customElements.define('sk-button', SkButton);
customElements.define('sk-input', SkInput);
customElements.define('sk-select', SkSelect);
customElements.define('sk-form', SkForm);
customElements.define('sk-checkbox', SkCheckbox);
myForm.addEventListener('formsubmitsuccess', (event) => {
console.log('form submit success handled', event);
});
myForm.addEventListener('formsubmiterror', (event) => {
console.log('form submit error handled', event);
});
</script>Field Validation
You can bind custom validation function for every field:
<script>
function validateFirstName(el) {
if (el.value == '1') {
return true;
} else {
return 'Wrong value';
}
}
</script>
<sk-input name="firstName" id="formInput1" form-validation="validateFirstName" form-validation-msg="{'someError': 'This field has some error'}">First Name</sk-input>form-validation attribute specifies validation function name, it can be binded to global (window) or field class. Validation function returns error message or validation key to load from form-validation-msg or true in case value is valid.
To enable validation labels display use validation-label attribute of an element or sk-form, use value "disabled" to force it's switch off.
no-live-validation attribute disables field revalidation on input
Sk Validators
Sk Widgets has a set of validators you can combine with native and your custom validators. At least they have no 'initially invalid' behaviour. Validators are added to elements as attributes just as Validation API validators, but they have sk- prefix in names.
sk-required - checks that field is required.
sk-min - checks that field not less than value. The number of symbols is checked in case of string.
sk-max - checks that field is greater than value. The number of symbols is checked in case of string.
sk-email - checks that field is matches email validation regex.
sk-pattern - checks that field is matches provided validation regex.
<sk-input name="firstName" id="formInput1" sk-pattern="^[A-Za-z]+$">First Name</sk-input>Note: All behaviour is forwarded to Validation API Custom Error and Custom Validation Message mechanics.
sk-app
sk-app is route-to render component that can help you feel like with popular framework SPA app. It allows to map one or more areas rendering to router path changes. It uses navigo library as runtime dependency to you have to have it loaded before sk-widgets.
<script src="/node_modules/navigo/lib/navigo.min.js"></script>
<script>
class PageAConfA extends HTMLElement {
connectedCallback() {
this.insertAdjacentHTML('beforeend', '<div>PageAConfA</div>');
}
}
class PageAConfB extends HTMLElement {
connectedCallback() {
this.insertAdjacentHTML('beforeend', '<div>PageAConfB</div>');
}
}
class PageBConfA extends HTMLElement {
connectedCallback() {
this.insertAdjacentHTML('beforeend', '<div>PageBConfA</div>');
}
}
customElements.define('page-a-confa', PageAConfA);
customElements.define('page-a-confb', PageAConfB);
customElements.define('page-b-confa', PageBConfA);
</script>
<script src="/dist/sk-compat-bundle.js"></script>
<a href="page-a" data-navigo>Page A</a>
<a href="page-b" data-navigo>Page B</a>
<sk-config
theme="jquery"
base-path="/src"
lang="en"
id="configA"
routes='{"page-a": "page-a-confa", "page-b": "page-b-confa"}'
></sk-config>
<sk-app configSl="#configA"></sk-app>
<sk-config
theme="jquery"
base-path="/src"
lang="en"
id="configB"
routes='{"page-a": "page-a-confb", "page-b": "page-a-confb"}'
></sk-config>
<sk-app configSl="#configB"></sk-app>
</body>Pages can be loaded automatically with the following mapping:
<sk-config
theme="jquery"
base-path="/src"
lang="en"
id="configA"
routes='{"page-a": {"PageAConfA": "/examples/spa/page-a-confa.js"}, "page-b": "page-b-confa"}'
></sk-config>Render auto binds
Sk Elements support auto rerendering bindings with specially prefixed class names.
Use classes with name sk-prop-in-{prop-name} to bind element's innerHTML update to class property change.
Use classes with name sk-prop-at-{prop-name} to bind element's attribute update to class property change.
Use classes with name sk-prop-at-{prop-name} to bind callback to class property change. Method with name "on{PropName}Change" will be called with binded element and value as arguments.
Note: class names are translated to property names just as it's done for data attributes, e.g. prop-name suffix will be matched with propName class property.
If element instance property is not defined, it will be defined with setter that uses _prefixed class property to store value. Properties are binded to objects (class instances), not to classes (static values).
<sk-button id="myButton" button-type="primary">
Button
</sk-button>
<template id="SkButtonTpl"><link rel="stylesheet" href="{{ themePath }}/antd.min.css">
<link rel="stylesheet" href="{{ themePath }}/antd-theme.css">
<button type="button" class="ant-btn">
<slot name="label" class="sk-prop-in-my-label"></slot>
<slot></slot>
</button>
</template>
<script>
var i = 0;
setInterval(() => {
i++;
myButton.myLabel = i;
}, 1000);
</script>You may meet unpredictiable behaviour when using slots, as this is acutally alternative way to change inner contents, but you still can override element's template without slots:)
Building
You can rebuild bundle with build command
npm run buildDevelopment
Add your own element
Sk Widgets allows you to create own subset of themed widgets using inheritance. To figure out how to do that, check out DI example. In short you will have to create own file structure similar to original that may extend built-in themes or base classes. To add new own element, ensure you are done with the following steps:
- create element class extending sk-element, override impl getter to fill it with your themes
- create common implementation class in impl/ and concrete implementations extending in for every theme you use, you can extend built-in themes and classes, some base methods you may have to override your in each element class to have them working properly.
- add templates for each theme and define them in theme's templates.json to be build in template bundles
- add element and impl classes to index.js to be build in javascript bundles
- add element to sk-bootstrap.js to enable autoloading and register to custom element tags
- rebuild project
Add new version tag
git tag -a v0.1.25
fill in comment without v in version number
complets library vbump
skinny-widgets has non-npm dependency on complets library that contain some web component and portlet related utilities. To update it to lates version use the following git command:
git submodule update --remote --mergeTests
Web Component Tests
- run http-server on project root
cd skinny-widgets
npm start- open /test/all.html in browser with developer console opened