import Component from '../component';
+import {EMPTY_DIV} from '../dom-patcher';
+
+/**
+ * @deprecated
+ * ControlledComponent is deprecated. Compose with a normal component and controller
+ *
+ * @example
+ * constructor() {
+ * super(...arguments);
+ * this.controller = new ExampleController({store: this});
+ * this.setConfig(`defaultState`, this.controller.defaultState);
+ * }
+ */
+
+
+export default class ControlledComponent extends Component {
+ constructor() {
+ super(...arguments);
+ this.controller = this.getConfig(`controller`);
+ if (!this.controller) {
+ throw Error(`"controller" must be set in config of a ControlledComponent`);
+ }
+ // Don't allow component's update directly
+ this._update = this.update;
+ this.update = () => { throw new Error(`update() not allowed from component. Use controller`); };
+ this._updateListener = () => this._update();
+ this.controller.subscribeUpdates(this._updateListener);
+ }
+
+ disconnectedCallback() {
+ if (!this.initialized) {
+ return;
+ }
+
+ super.disconnectedCallback();
+ this.controller.unsubscribeUpdates(this._updateListener);
+ }
+
+ attributeChangedCallback() {
+ // Do nothing, component should explicitly pass this to controller for an update
+ // Super class calls this.update() which will throw an error
+ }
+
+ _render() {
+ if (this.shouldUpdate()) {
+ try {
+ // Pass in $controller to jade.
+ // Template should use something like $controller.getDisplayX() to get the state it needs
+ this._rendered = this.getConfig(`template`)({
+ $component: this,
+ $helpers: this.helpers,
+ $controller: this.controller,
+ });
+ } catch (e) {
+ this.logError(`Error while rendering ${this.toString()}`, this, e.stack);
+ }
+ }
+ return this._rendered || EMPTY_DIV;
+ }
+}
+
+ new Componen
Source:
@@ -696,7 +696,7 @@ Properties
Source:
@@ -783,7 +783,7 @@ Type:
Source:
@@ -816,6 +816,137 @@ Methods
+
+ _updateAttr(attr)
+
+
+
+
+
+
+ Parses html attribute using type information from attrsSchema and updates this.attrs
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
Name | + + +Type | + + + + + +Description | +
---|---|---|
attr |
+
+
+ + + +string + + + + | + + + + + +attribute name | +
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- Source: +
- + component.js, line 426 +
+
+
+
+
+
+
+
+
child(tagName, configopt) → {object}
@@ -974,7 +1105,7 @@Parameters:
Parameters:
Parameters:
Parameters:
Parameters:
Parameters:
Parameters:
Parameters:
Example
diff --git a/docs/ProxyComponent.html b/docs/ProxyComponent.html index dd4a7ad8..7e12f455 100644 --- a/docs/ProxyComponent.html +++ b/docs/ProxyComponent.html @@ -23,7 +23,7 @@
@@ -673,7 +673,7 @@ Source:
@@ -765,7 +765,7 @@ Source:
@@ -941,6 +941,142 @@
+
+
+
+
+
+
+
+
+Source:
@@ -1433,7 +1569,7 @@ Source:
@@ -1609,7 +1745,7 @@ Source:
@@ -1938,7 +2074,7 @@ Source:
@@ -2098,7 +2234,7 @@ Source:
@@ -2242,7 +2378,7 @@ Source:
@@ -2431,7 +2567,7 @@ Source:
@@ -2596,7 +2732,7 @@ Source:
@@ -2645,7 +2781,7 @@
diff --git a/docs/StateController.html b/docs/StateController.html index 9f08180e..f5dbd55f 100644 --- a/docs/StateController.html +++ b/docs/StateController.html @@ -23,7 +23,7 @@
Properties
Type:
Methods
+ +_updateAttr(attr)
+ + + + + +
+ Parses html attribute using type information from attrsSchema and updates this.attrs
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+ + +Name | + + +Type | + + + + + +Description | +
---|---|---|
attr |
+
+
+ + + +string + + + + | + + + + + +attribute name | +
-
+
+
+
+
+
+
+
- Inherited From: +
- + + + + + + + + + + + + + + + + + + + + + +
- Source: +
- + component.js, line 426 +
+
+
+
+
+
+
+
+
allowEvent(ev) → {boolean}
@@ -1269,7 +1405,7 @@Parameters:
Parameters:
Parameters:
Parameters:
Parameters:
Parameters:
Parameters:
Parameters:
Example
diff --git a/docs/StateController.html b/docs/StateController.html index 9f08180e..f5dbd55f 100644 --- a/docs/StateController.html +++ b/docs/StateController.html @@ -23,7 +23,7 @@
@@ -167,7 +167,7 @@ new St
new St
diff --git a/docs/StateStore.html b/docs/StateStore.html
index 3184612e..b919df0a 100644
--- a/docs/StateStore.html
+++ b/docs/StateStore.html
@@ -23,7 +23,7 @@
@@ -163,7 +163,7 @@ new StateSt
diff --git a/docs/component-utils_controlled-component.js.html b/docs/component-utils_controlled-component.js.html
new file mode 100644
index 00000000..4e916ebc
--- /dev/null
+++ b/docs/component-utils_controlled-component.js.html
@@ -0,0 +1,120 @@
+
+
+
+
+ component-utils/controlled-component.js - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ component-utils/controlled-component.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/component-utils_index.js.html b/docs/component-utils_index.js.html
index e7ca6f9f..7a64c927 100644
--- a/docs/component-utils_index.js.html
+++ b/docs/component-utils_index.js.html
@@ -23,7 +23,7 @@
@@ -80,7 +80,7 @@ component-utils/index.js
diff --git a/docs/component-utils_proxy-component.js.html b/docs/component-utils_proxy-component.js.html
index 7cfe303b..3b92a0d9 100644
--- a/docs/component-utils_proxy-component.js.html
+++ b/docs/component-utils_proxy-component.js.html
@@ -23,7 +23,7 @@
@@ -49,7 +49,7 @@ component-utils/proxy-component.js
* @extends Component
*/
class ProxyComponent extends Component {
- get config(){
+ get config() {
return Object.assign({
template: ({$component}) => {
return h($component.getTargetElementTag(), {
@@ -112,7 +112,7 @@ component-utils/proxy-component.js
* }
* }
*/
- allowEvent(ev) {
+ allowEvent(ev) { // eslint-disable-line no-unused-vars
return true;
}
@@ -165,7 +165,7 @@ component-utils/proxy-component.js
diff --git a/docs/component-utils_state-controller.js.html b/docs/component-utils_state-controller.js.html
index ebfb10f9..07ac29b6 100644
--- a/docs/component-utils_state-controller.js.html
+++ b/docs/component-utils_state-controller.js.html
@@ -23,7 +23,7 @@
@@ -89,7 +89,7 @@ component-utils/state-controller.js
diff --git a/docs/component-utils_state-store.js.html b/docs/component-utils_state-store.js.html
index 1c5990ca..63cf549b 100644
--- a/docs/component-utils_state-store.js.html
+++ b/docs/component-utils_state-store.js.html
@@ -23,7 +23,7 @@
@@ -80,7 +80,7 @@ component-utils/state-store.js
diff --git a/docs/component.js.html b/docs/component.js.html
index d7f3a3cd..f309090f 100644
--- a/docs/component.js.html
+++ b/docs/component.js.html
@@ -23,7 +23,7 @@
@@ -39,10 +39,9 @@ component.js
import cuid from 'cuid';
-import pick from 'lodash.pick';
import WebComponent from 'webcomponent';
-import { EMPTY_DIV, DOMPatcher, h } from './dom-patcher';
+import {EMPTY_DIV, DOMPatcher, h} from './dom-patcher';
import Router from './router';
const DOCUMENT_FRAGMENT_NODE = 11;
@@ -148,7 +147,7 @@ component.js
*/
findPanelParentByTagName(tagName) {
tagName = tagName.toLowerCase();
- for (let node = this.$panelParent; !!node; node = node.$panelParent) {
+ for (let node = this.$panelParent; node; node = node.$panelParent) {
if (node.tagName.toLowerCase() === tagName) {
return node;
}
@@ -206,7 +205,7 @@ component.js
* return state.largeResultSetID !== this._cachedResultID;
* }
*/
- shouldUpdate(state) {
+ shouldUpdate(state) { // eslint-disable-line no-unused-vars
return true;
}
@@ -242,10 +241,10 @@ component.js
this.panelID = cuid();
this._config = Object.assign({}, {
- css: '',
+ css: ``,
helpers: {},
routes: {},
- template: () => { throw Error('No template provided by Component subclass'); },
+ template: () => { throw Error(`No template provided by Component subclass`); },
updateSync: false,
useShadowDom: false,
}, this.config);
@@ -254,6 +253,7 @@ component.js
// appState and isStateShared of child components will be overwritten by parent/root
// when the component is connected to the hierarchy
this.state = {};
+ this.attrs = {};
this.appState = this.getConfig(`appState`);
if (!this.appState) {
this.appState = {};
@@ -262,13 +262,13 @@ component.js
this.isStateShared = false;
}
- if (this.getConfig('useShadowDom')) {
- this.el = this.attachShadow({mode: 'open'});
- this.styleTag = document.createElement('style');
- this.styleTag.innerHTML = this.getConfig('css');
+ if (this.getConfig(`useShadowDom`)) {
+ this.el = this.attachShadow({mode: `open`});
+ this.styleTag = document.createElement(`style`);
+ this.styleTag.innerHTML = this.getConfig(`css`);
this.el.appendChild(this.styleTag);
- } else if (this.getConfig('css')) {
- throw Error('"useShadowDom" config option must be set in order to use "css" config.');
+ } else if (this.getConfig(`css`)) {
+ throw Error(`"useShadowDom" config option must be set in order to use "css" config.`);
} else {
this.el = this;
}
@@ -288,11 +288,11 @@ component.js
this.$panelChildren = new Set();
- if (typeof this.$panelParentID !== 'undefined') {
+ if (typeof this.$panelParentID !== `undefined`) {
this.isPanelChild = true;
// find $panelParent
for (let node = this.parentNode; node && !this.$panelParent; node = node.parentNode) {
- if (node.nodeType === DOCUMENT_FRAGMENT_NODE) { // handle shadow-root
+ if (node.nodeType === DOCUMENT_FRAGMENT_NODE) { // handle shadow-root
node = node.host;
}
if (node.panelID === this.$panelParentID) {
@@ -320,20 +320,22 @@ component.js
const newState = Object.assign(
{},
- this.getConfig('defaultState'),
+ this.getConfig(`defaultState`),
this.state,
- this.getJSONAttribute('data-state'),
+ this.getJSONAttribute(`data-state`),
this._stateFromAttributes()
);
+
Object.assign(this.state, newState);
+ Object.keys(this.constructor.attrsSchema).forEach(attr => this._updateAttr(attr));
- if (Object.keys(this.getConfig('routes')).length) {
+ if (Object.keys(this.getConfig(`routes`)).length) {
this.router = new Router(this, {historyMethod: this.historyMethod});
this.navigate(window.location.hash);
}
this.domPatcher = new DOMPatcher(this.state, this._render.bind(this), {
- updateMode: this.getConfig('updateSync') ? 'sync': 'async',
+ updateMode: this.getConfig(`updateSync`) ? `sync`: `async`,
});
this.el.appendChild(this.domPatcher.el);
this.initialized = true;
@@ -355,22 +357,41 @@ component.js
this.initialized = false;
}
+ /**
+ * Attributes schema that defines the component's html attributes and their types
+ * Panel auto parses attribute changes into this.attrs object and $attrs template helper
+ *
+ * @typedef {object} AttrSchema
+ * @prop {'string' | 'number' | 'boolean' | 'json'} type - type of the attribute
+ * if not set, the attr parser will interpret it as 'string'
+ * @prop {string} default - value if the attr is not defined
+ * @prop {number} description - description of the attribute, what it does e.t.c
+ *
+ * @type {Object.<string, AttrSchema>}
+ */
+ static get attrsSchema() {
+ return {};
+ }
+
static get observedAttributes() {
- return [`style-override`];
+ return [`style-override`].concat(Object.keys(this.attrsSchema));
}
attributeChangedCallback(attr, oldVal, newVal) {
- if (attr === 'style-override') {
+ this._updateAttr(attr);
+
+ if (attr === `style-override`) {
this._applyStyles(newVal);
}
+
if (this.isPanelRoot && this.initialized) {
this.update();
}
}
_applyStyles(styleOverride) {
- if (this.getConfig('useShadowDom')) {
- this.styleTag.innerHTML = this.getConfig('css') + (styleOverride || '');
+ if (this.getConfig(`useShadowDom`)) {
+ this.styleTag.innerHTML = this.getConfig(`css`) + (styleOverride || ``);
}
}
@@ -381,20 +402,21 @@ component.js
toString() {
try {
return `${this.tagName}#${this.panelID}`;
- } catch(e) {
- return 'UNKNOWN COMPONENT';
+ } catch (e) {
+ return `UNKNOWN COMPONENT`;
}
}
_render(state) {
if (this.shouldUpdate(state)) {
try {
- this._rendered = this.getConfig('template')(Object.assign({}, state, {
+ this._rendered = this.getConfig(`template`)(Object.assign({}, state, {
$app: this.appState,
$component: this,
$helpers: this.helpers,
+ $attrs: this.attrs,
}));
- } catch(e) {
+ } catch (e) {
this.logError(`Error while rendering ${this.toString()}`, this, e.stack);
}
}
@@ -437,6 +459,33 @@ component.js
return state;
}
+ /**
+ * Parses html attribute using type information from attrsSchema and updates this.attrs
+ * @param {string} attr - attribute name
+ */
+ _updateAttr(attr) {
+ const attrsSchema = this.constructor.attrsSchema;
+ if (attrsSchema.hasOwnProperty(attr)) {
+ const attrSchema = attrsSchema[attr];
+ const attrType = attrSchema.type || `string`;
+ let attrValue = null;
+
+ if (!this.hasAttribute(attr) && attrSchema.hasOwnProperty(`default`)) {
+ attrValue = attrSchema.default;
+ } else if (attrType === `string`) {
+ attrValue = this.getAttribute(attr);
+ } else if (attrType === `boolean`) {
+ attrValue = this.isAttributeEnabled(attr);
+ } else if (attrType === `number`) {
+ attrValue = this.getNumberAttribute(attr);
+ } else if (attrType === `json`) {
+ attrValue = this.getJSONAttribute(attr);
+ }
+
+ this.attrs[attr] = attrValue;
+ }
+ }
+
// update helpers
// Update a given state store (this.state or this.appState), with option
@@ -451,7 +500,7 @@ component.js
} else {
// update DOM, router, descendants etc.
- const updateHash = '$fragment' in stateUpdate && stateUpdate.$fragment !== this[store].$fragment;
+ const updateHash = `$fragment` in stateUpdate && stateUpdate.$fragment !== this[store].$fragment;
const cascadeFromRoot = cascade && !this.isPanelRoot;
const updateOptions = {cascade, store};
const rootOptions = {exclude: this, cascade, store};
@@ -514,7 +563,7 @@ component.js
diff --git a/docs/global.html b/docs/global.html
new file mode 100644
index 00000000..72a3f5ec
--- /dev/null
+++ b/docs/global.html
@@ -0,0 +1,318 @@
+
+
+
+
+ Global - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Global
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Type Definitions
+
+
+
+AttrSchema
+
+
+
+
+
+ Attributes schema that defines the component's html attributes and their types
+Panel auto parses attribute changes into this.attrs object and $attrs template helper
+
+
+
+
+ Type:
+
+ -
+
+object
+
+
+
+
+
+
+
+
+
+ Properties:
+
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ type
+
+
+
+
+
+'string'
+|
+
+'number'
+|
+
+'boolean'
+|
+
+'json'
+
+
+
+
+
+
+
+
+
+ type of the attribute
+ if not set, the attr parser will interpret it as 'string'
+
+
+
+
+
+
+ default
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+ value if the attr is not defined
+
+
+
+
+
+
+ description
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+ description of the attribute, what it does e.t.c
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+ -
+ component.js, line 320
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/index.html b/docs/index.html
index f9a1857a..3370bd32 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -23,7 +23,7 @@
@@ -61,7 +61,7 @@ Home
Classes
new StateSt
diff --git a/docs/component-utils_controlled-component.js.html b/docs/component-utils_controlled-component.js.html
new file mode 100644
index 00000000..4e916ebc
--- /dev/null
+++ b/docs/component-utils_controlled-component.js.html
@@ -0,0 +1,120 @@
+
+
+
+
+ component-utils/controlled-component.js - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ component-utils/controlled-component.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/component-utils_index.js.html b/docs/component-utils_index.js.html
index e7ca6f9f..7a64c927 100644
--- a/docs/component-utils_index.js.html
+++ b/docs/component-utils_index.js.html
@@ -23,7 +23,7 @@
@@ -80,7 +80,7 @@ component-utils/index.js
diff --git a/docs/component-utils_proxy-component.js.html b/docs/component-utils_proxy-component.js.html
index 7cfe303b..3b92a0d9 100644
--- a/docs/component-utils_proxy-component.js.html
+++ b/docs/component-utils_proxy-component.js.html
@@ -23,7 +23,7 @@
@@ -49,7 +49,7 @@ component-utils/proxy-component.js
* @extends Component
*/
class ProxyComponent extends Component {
- get config(){
+ get config() {
return Object.assign({
template: ({$component}) => {
return h($component.getTargetElementTag(), {
@@ -112,7 +112,7 @@ component-utils/proxy-component.js
* }
* }
*/
- allowEvent(ev) {
+ allowEvent(ev) { // eslint-disable-line no-unused-vars
return true;
}
@@ -165,7 +165,7 @@ component-utils/proxy-component.js
diff --git a/docs/component-utils_state-controller.js.html b/docs/component-utils_state-controller.js.html
index ebfb10f9..07ac29b6 100644
--- a/docs/component-utils_state-controller.js.html
+++ b/docs/component-utils_state-controller.js.html
@@ -23,7 +23,7 @@
@@ -89,7 +89,7 @@ component-utils/state-controller.js
diff --git a/docs/component-utils_state-store.js.html b/docs/component-utils_state-store.js.html
index 1c5990ca..63cf549b 100644
--- a/docs/component-utils_state-store.js.html
+++ b/docs/component-utils_state-store.js.html
@@ -23,7 +23,7 @@
@@ -80,7 +80,7 @@ component-utils/state-store.js
diff --git a/docs/component.js.html b/docs/component.js.html
index d7f3a3cd..f309090f 100644
--- a/docs/component.js.html
+++ b/docs/component.js.html
@@ -23,7 +23,7 @@
@@ -39,10 +39,9 @@ component.js
import cuid from 'cuid';
-import pick from 'lodash.pick';
import WebComponent from 'webcomponent';
-import { EMPTY_DIV, DOMPatcher, h } from './dom-patcher';
+import {EMPTY_DIV, DOMPatcher, h} from './dom-patcher';
import Router from './router';
const DOCUMENT_FRAGMENT_NODE = 11;
@@ -148,7 +147,7 @@ component.js
*/
findPanelParentByTagName(tagName) {
tagName = tagName.toLowerCase();
- for (let node = this.$panelParent; !!node; node = node.$panelParent) {
+ for (let node = this.$panelParent; node; node = node.$panelParent) {
if (node.tagName.toLowerCase() === tagName) {
return node;
}
@@ -206,7 +205,7 @@ component.js
* return state.largeResultSetID !== this._cachedResultID;
* }
*/
- shouldUpdate(state) {
+ shouldUpdate(state) { // eslint-disable-line no-unused-vars
return true;
}
@@ -242,10 +241,10 @@ component.js
this.panelID = cuid();
this._config = Object.assign({}, {
- css: '',
+ css: ``,
helpers: {},
routes: {},
- template: () => { throw Error('No template provided by Component subclass'); },
+ template: () => { throw Error(`No template provided by Component subclass`); },
updateSync: false,
useShadowDom: false,
}, this.config);
@@ -254,6 +253,7 @@ component.js
// appState and isStateShared of child components will be overwritten by parent/root
// when the component is connected to the hierarchy
this.state = {};
+ this.attrs = {};
this.appState = this.getConfig(`appState`);
if (!this.appState) {
this.appState = {};
@@ -262,13 +262,13 @@ component.js
this.isStateShared = false;
}
- if (this.getConfig('useShadowDom')) {
- this.el = this.attachShadow({mode: 'open'});
- this.styleTag = document.createElement('style');
- this.styleTag.innerHTML = this.getConfig('css');
+ if (this.getConfig(`useShadowDom`)) {
+ this.el = this.attachShadow({mode: `open`});
+ this.styleTag = document.createElement(`style`);
+ this.styleTag.innerHTML = this.getConfig(`css`);
this.el.appendChild(this.styleTag);
- } else if (this.getConfig('css')) {
- throw Error('"useShadowDom" config option must be set in order to use "css" config.');
+ } else if (this.getConfig(`css`)) {
+ throw Error(`"useShadowDom" config option must be set in order to use "css" config.`);
} else {
this.el = this;
}
@@ -288,11 +288,11 @@ component.js
this.$panelChildren = new Set();
- if (typeof this.$panelParentID !== 'undefined') {
+ if (typeof this.$panelParentID !== `undefined`) {
this.isPanelChild = true;
// find $panelParent
for (let node = this.parentNode; node && !this.$panelParent; node = node.parentNode) {
- if (node.nodeType === DOCUMENT_FRAGMENT_NODE) { // handle shadow-root
+ if (node.nodeType === DOCUMENT_FRAGMENT_NODE) { // handle shadow-root
node = node.host;
}
if (node.panelID === this.$panelParentID) {
@@ -320,20 +320,22 @@ component.js
const newState = Object.assign(
{},
- this.getConfig('defaultState'),
+ this.getConfig(`defaultState`),
this.state,
- this.getJSONAttribute('data-state'),
+ this.getJSONAttribute(`data-state`),
this._stateFromAttributes()
);
+
Object.assign(this.state, newState);
+ Object.keys(this.constructor.attrsSchema).forEach(attr => this._updateAttr(attr));
- if (Object.keys(this.getConfig('routes')).length) {
+ if (Object.keys(this.getConfig(`routes`)).length) {
this.router = new Router(this, {historyMethod: this.historyMethod});
this.navigate(window.location.hash);
}
this.domPatcher = new DOMPatcher(this.state, this._render.bind(this), {
- updateMode: this.getConfig('updateSync') ? 'sync': 'async',
+ updateMode: this.getConfig(`updateSync`) ? `sync`: `async`,
});
this.el.appendChild(this.domPatcher.el);
this.initialized = true;
@@ -355,22 +357,41 @@ component.js
this.initialized = false;
}
+ /**
+ * Attributes schema that defines the component's html attributes and their types
+ * Panel auto parses attribute changes into this.attrs object and $attrs template helper
+ *
+ * @typedef {object} AttrSchema
+ * @prop {'string' | 'number' | 'boolean' | 'json'} type - type of the attribute
+ * if not set, the attr parser will interpret it as 'string'
+ * @prop {string} default - value if the attr is not defined
+ * @prop {number} description - description of the attribute, what it does e.t.c
+ *
+ * @type {Object.<string, AttrSchema>}
+ */
+ static get attrsSchema() {
+ return {};
+ }
+
static get observedAttributes() {
- return [`style-override`];
+ return [`style-override`].concat(Object.keys(this.attrsSchema));
}
attributeChangedCallback(attr, oldVal, newVal) {
- if (attr === 'style-override') {
+ this._updateAttr(attr);
+
+ if (attr === `style-override`) {
this._applyStyles(newVal);
}
+
if (this.isPanelRoot && this.initialized) {
this.update();
}
}
_applyStyles(styleOverride) {
- if (this.getConfig('useShadowDom')) {
- this.styleTag.innerHTML = this.getConfig('css') + (styleOverride || '');
+ if (this.getConfig(`useShadowDom`)) {
+ this.styleTag.innerHTML = this.getConfig(`css`) + (styleOverride || ``);
}
}
@@ -381,20 +402,21 @@ component.js
toString() {
try {
return `${this.tagName}#${this.panelID}`;
- } catch(e) {
- return 'UNKNOWN COMPONENT';
+ } catch (e) {
+ return `UNKNOWN COMPONENT`;
}
}
_render(state) {
if (this.shouldUpdate(state)) {
try {
- this._rendered = this.getConfig('template')(Object.assign({}, state, {
+ this._rendered = this.getConfig(`template`)(Object.assign({}, state, {
$app: this.appState,
$component: this,
$helpers: this.helpers,
+ $attrs: this.attrs,
}));
- } catch(e) {
+ } catch (e) {
this.logError(`Error while rendering ${this.toString()}`, this, e.stack);
}
}
@@ -437,6 +459,33 @@ component.js
return state;
}
+ /**
+ * Parses html attribute using type information from attrsSchema and updates this.attrs
+ * @param {string} attr - attribute name
+ */
+ _updateAttr(attr) {
+ const attrsSchema = this.constructor.attrsSchema;
+ if (attrsSchema.hasOwnProperty(attr)) {
+ const attrSchema = attrsSchema[attr];
+ const attrType = attrSchema.type || `string`;
+ let attrValue = null;
+
+ if (!this.hasAttribute(attr) && attrSchema.hasOwnProperty(`default`)) {
+ attrValue = attrSchema.default;
+ } else if (attrType === `string`) {
+ attrValue = this.getAttribute(attr);
+ } else if (attrType === `boolean`) {
+ attrValue = this.isAttributeEnabled(attr);
+ } else if (attrType === `number`) {
+ attrValue = this.getNumberAttribute(attr);
+ } else if (attrType === `json`) {
+ attrValue = this.getJSONAttribute(attr);
+ }
+
+ this.attrs[attr] = attrValue;
+ }
+ }
+
// update helpers
// Update a given state store (this.state or this.appState), with option
@@ -451,7 +500,7 @@ component.js
} else {
// update DOM, router, descendants etc.
- const updateHash = '$fragment' in stateUpdate && stateUpdate.$fragment !== this[store].$fragment;
+ const updateHash = `$fragment` in stateUpdate && stateUpdate.$fragment !== this[store].$fragment;
const cascadeFromRoot = cascade && !this.isPanelRoot;
const updateOptions = {cascade, store};
const rootOptions = {exclude: this, cascade, store};
@@ -514,7 +563,7 @@ component.js
diff --git a/docs/global.html b/docs/global.html
new file mode 100644
index 00000000..72a3f5ec
--- /dev/null
+++ b/docs/global.html
@@ -0,0 +1,318 @@
+
+
+
+
+ Global - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Global
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Type Definitions
+
+
+
+AttrSchema
+
+
+
+
+
+ Attributes schema that defines the component's html attributes and their types
+Panel auto parses attribute changes into this.attrs object and $attrs template helper
+
+
+
+
+ Type:
+
+ -
+
+object
+
+
+
+
+
+
+
+
+
+ Properties:
+
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ type
+
+
+
+
+
+'string'
+|
+
+'number'
+|
+
+'boolean'
+|
+
+'json'
+
+
+
+
+
+
+
+
+
+ type of the attribute
+ if not set, the attr parser will interpret it as 'string'
+
+
+
+
+
+
+ default
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+ value if the attr is not defined
+
+
+
+
+
+
+ description
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+ description of the attribute, what it does e.t.c
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+ -
+ component.js, line 320
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/index.html b/docs/index.html
index f9a1857a..3370bd32 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -23,7 +23,7 @@
@@ -61,7 +61,7 @@ Home
Classes
component-utils/controlled-component.js
+ + + + + + + +component-utils/index.js
diff --git a/docs/component-utils_proxy-component.js.html b/docs/component-utils_proxy-component.js.html index 7cfe303b..3b92a0d9 100644 --- a/docs/component-utils_proxy-component.js.html +++ b/docs/component-utils_proxy-component.js.html @@ -23,7 +23,7 @@
@@ -49,7 +49,7 @@
diff --git a/docs/component-utils_state-controller.js.html b/docs/component-utils_state-controller.js.html index ebfb10f9..07ac29b6 100644 --- a/docs/component-utils_state-controller.js.html +++ b/docs/component-utils_state-controller.js.html @@ -23,7 +23,7 @@
component-utils/proxy-component.js
* @extends Component */ class ProxyComponent extends Component { - get config(){ + get config() { return Object.assign({ template: ({$component}) => { return h($component.getTargetElementTag(), { @@ -112,7 +112,7 @@component-utils/proxy-component.js
* } * } */ - allowEvent(ev) { + allowEvent(ev) { // eslint-disable-line no-unused-vars return true; } @@ -165,7 +165,7 @@component-utils/proxy-component.js
diff --git a/docs/component-utils_state-controller.js.html b/docs/component-utils_state-controller.js.html index ebfb10f9..07ac29b6 100644 --- a/docs/component-utils_state-controller.js.html +++ b/docs/component-utils_state-controller.js.html @@ -23,7 +23,7 @@
@@ -89,7 +89,7 @@
diff --git a/docs/component-utils_state-store.js.html b/docs/component-utils_state-store.js.html index 1c5990ca..63cf549b 100644 --- a/docs/component-utils_state-store.js.html +++ b/docs/component-utils_state-store.js.html @@ -23,7 +23,7 @@
component-utils/state-controller.js
diff --git a/docs/component-utils_state-store.js.html b/docs/component-utils_state-store.js.html index 1c5990ca..63cf549b 100644 --- a/docs/component-utils_state-store.js.html +++ b/docs/component-utils_state-store.js.html @@ -23,7 +23,7 @@
@@ -80,7 +80,7 @@
diff --git a/docs/component.js.html b/docs/component.js.html index d7f3a3cd..f309090f 100644 --- a/docs/component.js.html +++ b/docs/component.js.html @@ -23,7 +23,7 @@
component-utils/state-store.js
diff --git a/docs/component.js.html b/docs/component.js.html index d7f3a3cd..f309090f 100644 --- a/docs/component.js.html +++ b/docs/component.js.html @@ -23,7 +23,7 @@
@@ -39,10 +39,9 @@
component.js
import cuid from 'cuid';
-import pick from 'lodash.pick';
import WebComponent from 'webcomponent';
-import { EMPTY_DIV, DOMPatcher, h } from './dom-patcher';
+import {EMPTY_DIV, DOMPatcher, h} from './dom-patcher';
import Router from './router';
const DOCUMENT_FRAGMENT_NODE = 11;
@@ -148,7 +147,7 @@ component.js
*/
findPanelParentByTagName(tagName) {
tagName = tagName.toLowerCase();
- for (let node = this.$panelParent; !!node; node = node.$panelParent) {
+ for (let node = this.$panelParent; node; node = node.$panelParent) {
if (node.tagName.toLowerCase() === tagName) {
return node;
}
@@ -206,7 +205,7 @@ component.js
* return state.largeResultSetID !== this._cachedResultID;
* }
*/
- shouldUpdate(state) {
+ shouldUpdate(state) { // eslint-disable-line no-unused-vars
return true;
}
@@ -242,10 +241,10 @@ component.js
this.panelID = cuid();
this._config = Object.assign({}, {
- css: '',
+ css: ``,
helpers: {},
routes: {},
- template: () => { throw Error('No template provided by Component subclass'); },
+ template: () => { throw Error(`No template provided by Component subclass`); },
updateSync: false,
useShadowDom: false,
}, this.config);
@@ -254,6 +253,7 @@ component.js
// appState and isStateShared of child components will be overwritten by parent/root
// when the component is connected to the hierarchy
this.state = {};
+ this.attrs = {};
this.appState = this.getConfig(`appState`);
if (!this.appState) {
this.appState = {};
@@ -262,13 +262,13 @@ component.js
this.isStateShared = false;
}
- if (this.getConfig('useShadowDom')) {
- this.el = this.attachShadow({mode: 'open'});
- this.styleTag = document.createElement('style');
- this.styleTag.innerHTML = this.getConfig('css');
+ if (this.getConfig(`useShadowDom`)) {
+ this.el = this.attachShadow({mode: `open`});
+ this.styleTag = document.createElement(`style`);
+ this.styleTag.innerHTML = this.getConfig(`css`);
this.el.appendChild(this.styleTag);
- } else if (this.getConfig('css')) {
- throw Error('"useShadowDom" config option must be set in order to use "css" config.');
+ } else if (this.getConfig(`css`)) {
+ throw Error(`"useShadowDom" config option must be set in order to use "css" config.`);
} else {
this.el = this;
}
@@ -288,11 +288,11 @@ component.js
this.$panelChildren = new Set();
- if (typeof this.$panelParentID !== 'undefined') {
+ if (typeof this.$panelParentID !== `undefined`) {
this.isPanelChild = true;
// find $panelParent
for (let node = this.parentNode; node && !this.$panelParent; node = node.parentNode) {
- if (node.nodeType === DOCUMENT_FRAGMENT_NODE) { // handle shadow-root
+ if (node.nodeType === DOCUMENT_FRAGMENT_NODE) { // handle shadow-root
node = node.host;
}
if (node.panelID === this.$panelParentID) {
@@ -320,20 +320,22 @@ component.js
const newState = Object.assign(
{},
- this.getConfig('defaultState'),
+ this.getConfig(`defaultState`),
this.state,
- this.getJSONAttribute('data-state'),
+ this.getJSONAttribute(`data-state`),
this._stateFromAttributes()
);
+
Object.assign(this.state, newState);
+ Object.keys(this.constructor.attrsSchema).forEach(attr => this._updateAttr(attr));
- if (Object.keys(this.getConfig('routes')).length) {
+ if (Object.keys(this.getConfig(`routes`)).length) {
this.router = new Router(this, {historyMethod: this.historyMethod});
this.navigate(window.location.hash);
}
this.domPatcher = new DOMPatcher(this.state, this._render.bind(this), {
- updateMode: this.getConfig('updateSync') ? 'sync': 'async',
+ updateMode: this.getConfig(`updateSync`) ? `sync`: `async`,
});
this.el.appendChild(this.domPatcher.el);
this.initialized = true;
@@ -355,22 +357,41 @@ component.js
this.initialized = false;
}
+ /**
+ * Attributes schema that defines the component's html attributes and their types
+ * Panel auto parses attribute changes into this.attrs object and $attrs template helper
+ *
+ * @typedef {object} AttrSchema
+ * @prop {'string' | 'number' | 'boolean' | 'json'} type - type of the attribute
+ * if not set, the attr parser will interpret it as 'string'
+ * @prop {string} default - value if the attr is not defined
+ * @prop {number} description - description of the attribute, what it does e.t.c
+ *
+ * @type {Object.<string, AttrSchema>}
+ */
+ static get attrsSchema() {
+ return {};
+ }
+
static get observedAttributes() {
- return [`style-override`];
+ return [`style-override`].concat(Object.keys(this.attrsSchema));
}
attributeChangedCallback(attr, oldVal, newVal) {
- if (attr === 'style-override') {
+ this._updateAttr(attr);
+
+ if (attr === `style-override`) {
this._applyStyles(newVal);
}
+
if (this.isPanelRoot && this.initialized) {
this.update();
}
}
_applyStyles(styleOverride) {
- if (this.getConfig('useShadowDom')) {
- this.styleTag.innerHTML = this.getConfig('css') + (styleOverride || '');
+ if (this.getConfig(`useShadowDom`)) {
+ this.styleTag.innerHTML = this.getConfig(`css`) + (styleOverride || ``);
}
}
@@ -381,20 +402,21 @@ component.js
toString() {
try {
return `${this.tagName}#${this.panelID}`;
- } catch(e) {
- return 'UNKNOWN COMPONENT';
+ } catch (e) {
+ return `UNKNOWN COMPONENT`;
}
}
_render(state) {
if (this.shouldUpdate(state)) {
try {
- this._rendered = this.getConfig('template')(Object.assign({}, state, {
+ this._rendered = this.getConfig(`template`)(Object.assign({}, state, {
$app: this.appState,
$component: this,
$helpers: this.helpers,
+ $attrs: this.attrs,
}));
- } catch(e) {
+ } catch (e) {
this.logError(`Error while rendering ${this.toString()}`, this, e.stack);
}
}
@@ -437,6 +459,33 @@ component.js
return state;
}
+ /**
+ * Parses html attribute using type information from attrsSchema and updates this.attrs
+ * @param {string} attr - attribute name
+ */
+ _updateAttr(attr) {
+ const attrsSchema = this.constructor.attrsSchema;
+ if (attrsSchema.hasOwnProperty(attr)) {
+ const attrSchema = attrsSchema[attr];
+ const attrType = attrSchema.type || `string`;
+ let attrValue = null;
+
+ if (!this.hasAttribute(attr) && attrSchema.hasOwnProperty(`default`)) {
+ attrValue = attrSchema.default;
+ } else if (attrType === `string`) {
+ attrValue = this.getAttribute(attr);
+ } else if (attrType === `boolean`) {
+ attrValue = this.isAttributeEnabled(attr);
+ } else if (attrType === `number`) {
+ attrValue = this.getNumberAttribute(attr);
+ } else if (attrType === `json`) {
+ attrValue = this.getJSONAttribute(attr);
+ }
+
+ this.attrs[attr] = attrValue;
+ }
+ }
+
// update helpers
// Update a given state store (this.state or this.appState), with option
@@ -451,7 +500,7 @@ component.js
} else {
// update DOM, router, descendants etc.
- const updateHash = '$fragment' in stateUpdate && stateUpdate.$fragment !== this[store].$fragment;
+ const updateHash = `$fragment` in stateUpdate && stateUpdate.$fragment !== this[store].$fragment;
const cascadeFromRoot = cascade && !this.isPanelRoot;
const updateOptions = {cascade, store};
const rootOptions = {exclude: this, cascade, store};
@@ -514,7 +563,7 @@ component.js
diff --git a/docs/global.html b/docs/global.html
new file mode 100644
index 00000000..72a3f5ec
--- /dev/null
+++ b/docs/global.html
@@ -0,0 +1,318 @@
+
+
+
+
+ Global - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Global
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Type Definitions
+
+
+
+AttrSchema
+
+
+
+
+
+ Attributes schema that defines the component's html attributes and their types
+Panel auto parses attribute changes into this.attrs object and $attrs template helper
+
+
+
+
+ Type:
+
+ -
+
+object
+
+
+
+
+
+
+
+
+
+ Properties:
+
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ type
+
+
+
+
+
+'string'
+|
+
+'number'
+|
+
+'boolean'
+|
+
+'json'
+
+
+
+
+
+
+
+
+
+ type of the attribute
+ if not set, the attr parser will interpret it as 'string'
+
+
+
+
+
+
+ default
+
+
+
+
+
+string
+
+
+
+
+
+
+
+
+
+ value if the attr is not defined
+
+
+
+
+
+
+ description
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+ description of the attribute, what it does e.t.c
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+ -
+ component.js, line 320
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/index.html b/docs/index.html
index f9a1857a..3370bd32 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -23,7 +23,7 @@
@@ -61,7 +61,7 @@ Home
Classes