new Componen
Source:
@@ -696,7 +696,7 @@ Properties
Source:
@@ -783,7 +783,7 @@ Type:
Source:
@@ -816,6 +816,88 @@ Methods
+
+ _syncAttrs()
+
+
+
+
+
+
+ Validates attrsSchema and syncs element attributes defined in attrsSchema with this.attrs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+ -
+ component.js, line 432
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
_updateAttr(attr)
@@ -917,7 +999,7 @@ Parameters:
Source:
@@ -1105,7 +1187,7 @@ Parameters:
Source:
@@ -1264,7 +1346,7 @@ Parameters:
Source:
@@ -1435,7 +1517,7 @@ Parameters:
Source:
@@ -1638,7 +1720,7 @@ Parameters:
Source:
@@ -1793,7 +1875,7 @@ Parameters:
Source:
@@ -1932,7 +2014,7 @@ Parameters:
Source:
@@ -2116,7 +2198,7 @@ Parameters:
Source:
@@ -2276,7 +2358,7 @@ Parameters:
Source:
@@ -2325,7 +2407,7 @@ Example
diff --git a/docs/ProxyComponent.html b/docs/ProxyComponent.html
index 7e12f455..d9581068 100644
--- a/docs/ProxyComponent.html
+++ b/docs/ProxyComponent.html
@@ -23,7 +23,7 @@
@@ -673,7 +673,7 @@ Properties
Source:
@@ -765,7 +765,7 @@ Type:
Source:
@@ -941,6 +941,93 @@ Methods
+
+ _syncAttrs()
+
+
+
+
+
+
+ Validates attrsSchema and syncs element attributes defined in attrsSchema with this.attrs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Inherited From:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+ -
+ component.js, line 432
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
_updateAttr(attr)
@@ -1047,7 +1134,7 @@ Parameters:
Source:
@@ -1405,7 +1492,7 @@ Parameters:
Source:
@@ -1569,7 +1656,7 @@ Parameters:
Source:
@@ -1745,7 +1832,7 @@ Parameters:
Source:
@@ -2074,7 +2161,7 @@ Parameters:
Source:
@@ -2234,7 +2321,7 @@ Parameters:
Source:
@@ -2378,7 +2465,7 @@ Parameters:
Source:
@@ -2567,7 +2654,7 @@ Parameters:
Source:
@@ -2732,7 +2819,7 @@ Parameters:
Source:
@@ -2781,7 +2868,7 @@ Example
diff --git a/docs/StateController.html b/docs/StateController.html
index f5dbd55f..4684a580 100644
--- a/docs/StateController.html
+++ b/docs/StateController.html
@@ -23,7 +23,7 @@
@@ -167,7 +167,7 @@ new St
diff --git a/docs/StateStore.html b/docs/StateStore.html
index b919df0a..11de1e4d 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
index 4e916ebc..70a14a22 100644
--- a/docs/component-utils_controlled-component.js.html
+++ b/docs/component-utils_controlled-component.js.html
@@ -23,7 +23,7 @@
@@ -77,9 +77,12 @@ component-utils/controlled-component.js
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
+ attributeChangedCallback(attr) {
+ // super.attributeChangedCallback class calls this.update() which will throw an error
+ this._updateAttr(attr);
+ if (this.initialized) {
+ this._update();
+ }
}
_render() {
@@ -91,6 +94,7 @@ component-utils/controlled-component.js
$component: this,
$helpers: this.helpers,
$controller: this.controller,
+ $attrs: this.attrs,
});
} catch (e) {
this.logError(`Error while rendering ${this.toString()}`, this, e.stack);
@@ -111,7 +115,7 @@ component-utils/controlled-component.js
diff --git a/docs/component-utils_index.js.html b/docs/component-utils_index.js.html
index 7a64c927..acb633ee 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 3b92a0d9..25f78c4a 100644
--- a/docs/component-utils_proxy-component.js.html
+++ b/docs/component-utils_proxy-component.js.html
@@ -23,7 +23,7 @@
@@ -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 07ac29b6..80113112 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 63cf549b..5abcf657 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 f309090f..126d554b 100644
--- a/docs/component.js.html
+++ b/docs/component.js.html
@@ -23,7 +23,7 @@
@@ -45,6 +45,7 @@ component.js
import Router from './router';
const DOCUMENT_FRAGMENT_NODE = 11;
+const ATTR_TYPE_DEFAULTS = {string: ``, boolean: false, number: 0, json: null};
/**
* Definition of a Panel component/app, implemented as an HTML custom element.
@@ -240,6 +241,10 @@ component.js
super();
this.panelID = cuid();
+
+ this.attrs = {};
+ this._syncAttrs(); // constructor sync ensures default properties are present on this.attrs
+
this._config = Object.assign({}, {
css: ``,
helpers: {},
@@ -253,8 +258,8 @@ 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 = {};
this.isStateShared = true;
@@ -327,7 +332,6 @@ component.js
);
Object.assign(this.state, newState);
- Object.keys(this.constructor.attrsSchema).forEach(attr => this._updateAttr(attr));
if (Object.keys(this.getConfig(`routes`)).length) {
this.router = new Router(this, {historyMethod: this.historyMethod});
@@ -347,6 +351,9 @@ component.js
return;
}
+ if (this.router) {
+ this.router.unregisterListeners();
+ }
if (this.$panelParent) {
this.$panelParent.$panelChildren.delete(this);
}
@@ -384,7 +391,7 @@ component.js
this._applyStyles(newVal);
}
- if (this.isPanelRoot && this.initialized) {
+ if (this.initialized) {
this.update();
}
}
@@ -417,7 +424,7 @@ component.js
$attrs: this.attrs,
}));
} catch (e) {
- this.logError(`Error while rendering ${this.toString()}`, this, e.stack);
+ this.logError(`Error while rendering ${this.toString()}`, this, `\n`, e);
}
}
return this._rendered || EMPTY_DIV;
@@ -459,21 +466,77 @@ component.js
return state;
}
+ /**
+ * Validates attrsSchema and syncs element attributes defined in attrsSchema with this.attrs
+ */
+ _syncAttrs() {
+ // maintain local validated map where all schema keys are defined
+ this._attrsSchema = {};
+ const attrsSchema = this.constructor.attrsSchema;
+
+ for (const attr of Object.keys(attrsSchema)) {
+ // convert type shorthand to object
+ let attrSchema = attrsSchema[attr];
+ if (typeof attrSchema === `string`) {
+ attrSchema = {type: attrSchema};
+ }
+
+ // Ensure attr type is valid
+ const attrType = attrSchema.type;
+ if (!ATTR_TYPE_DEFAULTS.hasOwnProperty(attrType)) {
+ throw new Error(
+ `Invalid type: ${attrType} for attr: ${attr} in attrsSchema. ` +
+ `Only (${Object.keys(ATTR_TYPE_DEFAULTS).map(v => `'${v}'`).join(` | `)}) is valid.`
+ );
+ }
+
+ const attrSchemaObj = {
+ type: attrType,
+ default: attrSchema.hasOwnProperty(`default`) ? attrSchema.default : ATTR_TYPE_DEFAULTS[attrType],
+ };
+
+ // convert enum to a set for perf
+ if (attrSchema.hasOwnProperty(`enum`)) {
+ const attrEnum = attrSchema.enum;
+ if (!Array.isArray(attrEnum)) {
+ throw new Error(`Enum not an array for attr: ${attr}`);
+ }
+
+ const enumSet = new Set(attrEnum);
+ enumSet.add(attrSchema.default);
+ attrSchemaObj.enumSet = enumSet;
+ }
+
+ this._attrsSchema[attr] = attrSchemaObj;
+ this._updateAttr(attr);
+ }
+
+ return this.attrs;
+ }
+
/**
* Parses html attribute using type information from attrsSchema and updates this.attrs
* @param {string} attr - attribute name
*/
_updateAttr(attr) {
- const attrsSchema = this.constructor.attrsSchema;
+ const attrsSchema = this._attrsSchema;
if (attrsSchema.hasOwnProperty(attr)) {
const attrSchema = attrsSchema[attr];
- const attrType = attrSchema.type || `string`;
+ const attrType = attrSchema.type;
let attrValue = null;
- if (!this.hasAttribute(attr) && attrSchema.hasOwnProperty(`default`)) {
+ if (!this.hasAttribute(attr)) {
attrValue = attrSchema.default;
} else if (attrType === `string`) {
attrValue = this.getAttribute(attr);
+ const enumSet = attrSchema.enumSet;
+
+ if (enumSet && !enumSet.has(attrValue)) {
+ throw new Error(
+ `Invalid value: '${attrValue}' for attr: ${attr}. ` +
+ `Only (${Array.from(enumSet).map(v => `'${v}'`).join(` | `)}) is valid.`
+ );
+ }
} else if (attrType === `boolean`) {
attrValue = this.isAttributeEnabled(attr);
} else if (attrType === `number`) {
@@ -563,7 +626,7 @@ component.js
diff --git a/docs/global.html b/docs/global.html
index 72a3f5ec..02127b93 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -23,7 +23,7 @@
@@ -277,7 +277,7 @@ Properties:
Source:
@@ -309,7 +309,7 @@ Properties:
diff --git a/docs/index.html b/docs/index.html
index 3370bd32..6f774771 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -23,7 +23,7 @@
@@ -61,7 +61,7 @@ Home
Classes
- + component.js, line 432 +
Properties
Type:
Methods
+ +_syncAttrs()
+ + + + + +
+ Validates attrsSchema and syncs element attributes defined in attrsSchema with this.attrs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+-
+
+
+
+
+
+
+
- Inherited From: +
- + + + + + + + + + + + + + + + + + + + + + +
- Source: +
- + component.js, line 432 +
+
+
+
+
+
+
+
+
_updateAttr(attr)
@@ -1047,7 +1134,7 @@Parameters:
Parameters:
Parameters:
Parameters:
Parameters:
Parameters:
Parameters:
Parameters:
Parameters:
Example
diff --git a/docs/StateController.html b/docs/StateController.html index f5dbd55f..4684a580 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 b919df0a..11de1e4d 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
index 4e916ebc..70a14a22 100644
--- a/docs/component-utils_controlled-component.js.html
+++ b/docs/component-utils_controlled-component.js.html
@@ -23,7 +23,7 @@
@@ -77,9 +77,12 @@ component-utils/controlled-component.js
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
+ attributeChangedCallback(attr) {
+ // super.attributeChangedCallback class calls this.update() which will throw an error
+ this._updateAttr(attr);
+ if (this.initialized) {
+ this._update();
+ }
}
_render() {
@@ -91,6 +94,7 @@ component-utils/controlled-component.js
$component: this,
$helpers: this.helpers,
$controller: this.controller,
+ $attrs: this.attrs,
});
} catch (e) {
this.logError(`Error while rendering ${this.toString()}`, this, e.stack);
@@ -111,7 +115,7 @@ component-utils/controlled-component.js
diff --git a/docs/component-utils_index.js.html b/docs/component-utils_index.js.html
index 7a64c927..acb633ee 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 3b92a0d9..25f78c4a 100644
--- a/docs/component-utils_proxy-component.js.html
+++ b/docs/component-utils_proxy-component.js.html
@@ -23,7 +23,7 @@
@@ -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 07ac29b6..80113112 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 63cf549b..5abcf657 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 f309090f..126d554b 100644
--- a/docs/component.js.html
+++ b/docs/component.js.html
@@ -23,7 +23,7 @@
@@ -45,6 +45,7 @@ component.js
import Router from './router';
const DOCUMENT_FRAGMENT_NODE = 11;
+const ATTR_TYPE_DEFAULTS = {string: ``, boolean: false, number: 0, json: null};
/**
* Definition of a Panel component/app, implemented as an HTML custom element.
@@ -240,6 +241,10 @@ component.js
super();
this.panelID = cuid();
+
+ this.attrs = {};
+ this._syncAttrs(); // constructor sync ensures default properties are present on this.attrs
+
this._config = Object.assign({}, {
css: ``,
helpers: {},
@@ -253,8 +258,8 @@ 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 = {};
this.isStateShared = true;
@@ -327,7 +332,6 @@ component.js
);
Object.assign(this.state, newState);
- Object.keys(this.constructor.attrsSchema).forEach(attr => this._updateAttr(attr));
if (Object.keys(this.getConfig(`routes`)).length) {
this.router = new Router(this, {historyMethod: this.historyMethod});
@@ -347,6 +351,9 @@ component.js
return;
}
+ if (this.router) {
+ this.router.unregisterListeners();
+ }
if (this.$panelParent) {
this.$panelParent.$panelChildren.delete(this);
}
@@ -384,7 +391,7 @@ component.js
this._applyStyles(newVal);
}
- if (this.isPanelRoot && this.initialized) {
+ if (this.initialized) {
this.update();
}
}
@@ -417,7 +424,7 @@ component.js
$attrs: this.attrs,
}));
} catch (e) {
- this.logError(`Error while rendering ${this.toString()}`, this, e.stack);
+ this.logError(`Error while rendering ${this.toString()}`, this, `\n`, e);
}
}
return this._rendered || EMPTY_DIV;
@@ -459,21 +466,77 @@ component.js
return state;
}
+ /**
+ * Validates attrsSchema and syncs element attributes defined in attrsSchema with this.attrs
+ */
+ _syncAttrs() {
+ // maintain local validated map where all schema keys are defined
+ this._attrsSchema = {};
+ const attrsSchema = this.constructor.attrsSchema;
+
+ for (const attr of Object.keys(attrsSchema)) {
+ // convert type shorthand to object
+ let attrSchema = attrsSchema[attr];
+ if (typeof attrSchema === `string`) {
+ attrSchema = {type: attrSchema};
+ }
+
+ // Ensure attr type is valid
+ const attrType = attrSchema.type;
+ if (!ATTR_TYPE_DEFAULTS.hasOwnProperty(attrType)) {
+ throw new Error(
+ `Invalid type: ${attrType} for attr: ${attr} in attrsSchema. ` +
+ `Only (${Object.keys(ATTR_TYPE_DEFAULTS).map(v => `'${v}'`).join(` | `)}) is valid.`
+ );
+ }
+
+ const attrSchemaObj = {
+ type: attrType,
+ default: attrSchema.hasOwnProperty(`default`) ? attrSchema.default : ATTR_TYPE_DEFAULTS[attrType],
+ };
+
+ // convert enum to a set for perf
+ if (attrSchema.hasOwnProperty(`enum`)) {
+ const attrEnum = attrSchema.enum;
+ if (!Array.isArray(attrEnum)) {
+ throw new Error(`Enum not an array for attr: ${attr}`);
+ }
+
+ const enumSet = new Set(attrEnum);
+ enumSet.add(attrSchema.default);
+ attrSchemaObj.enumSet = enumSet;
+ }
+
+ this._attrsSchema[attr] = attrSchemaObj;
+ this._updateAttr(attr);
+ }
+
+ return this.attrs;
+ }
+
/**
* Parses html attribute using type information from attrsSchema and updates this.attrs
* @param {string} attr - attribute name
*/
_updateAttr(attr) {
- const attrsSchema = this.constructor.attrsSchema;
+ const attrsSchema = this._attrsSchema;
if (attrsSchema.hasOwnProperty(attr)) {
const attrSchema = attrsSchema[attr];
- const attrType = attrSchema.type || `string`;
+ const attrType = attrSchema.type;
let attrValue = null;
- if (!this.hasAttribute(attr) && attrSchema.hasOwnProperty(`default`)) {
+ if (!this.hasAttribute(attr)) {
attrValue = attrSchema.default;
} else if (attrType === `string`) {
attrValue = this.getAttribute(attr);
+ const enumSet = attrSchema.enumSet;
+
+ if (enumSet && !enumSet.has(attrValue)) {
+ throw new Error(
+ `Invalid value: '${attrValue}' for attr: ${attr}. ` +
+ `Only (${Array.from(enumSet).map(v => `'${v}'`).join(` | `)}) is valid.`
+ );
+ }
} else if (attrType === `boolean`) {
attrValue = this.isAttributeEnabled(attr);
} else if (attrType === `number`) {
@@ -563,7 +626,7 @@ component.js
diff --git a/docs/global.html b/docs/global.html
index 72a3f5ec..02127b93 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -23,7 +23,7 @@
@@ -277,7 +277,7 @@ Properties:
Source:
@@ -309,7 +309,7 @@ Properties:
diff --git a/docs/index.html b/docs/index.html
index 3370bd32..6f774771 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
index 4e916ebc..70a14a22 100644
--- a/docs/component-utils_controlled-component.js.html
+++ b/docs/component-utils_controlled-component.js.html
@@ -23,7 +23,7 @@
@@ -77,9 +77,12 @@ component-utils/controlled-component.js
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
+ attributeChangedCallback(attr) {
+ // super.attributeChangedCallback class calls this.update() which will throw an error
+ this._updateAttr(attr);
+ if (this.initialized) {
+ this._update();
+ }
}
_render() {
@@ -91,6 +94,7 @@ component-utils/controlled-component.js
$component: this,
$helpers: this.helpers,
$controller: this.controller,
+ $attrs: this.attrs,
});
} catch (e) {
this.logError(`Error while rendering ${this.toString()}`, this, e.stack);
@@ -111,7 +115,7 @@ component-utils/controlled-component.js
diff --git a/docs/component-utils_index.js.html b/docs/component-utils_index.js.html
index 7a64c927..acb633ee 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 3b92a0d9..25f78c4a 100644
--- a/docs/component-utils_proxy-component.js.html
+++ b/docs/component-utils_proxy-component.js.html
@@ -23,7 +23,7 @@
@@ -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 07ac29b6..80113112 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 63cf549b..5abcf657 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 f309090f..126d554b 100644
--- a/docs/component.js.html
+++ b/docs/component.js.html
@@ -23,7 +23,7 @@
@@ -45,6 +45,7 @@ component.js
import Router from './router';
const DOCUMENT_FRAGMENT_NODE = 11;
+const ATTR_TYPE_DEFAULTS = {string: ``, boolean: false, number: 0, json: null};
/**
* Definition of a Panel component/app, implemented as an HTML custom element.
@@ -240,6 +241,10 @@ component.js
super();
this.panelID = cuid();
+
+ this.attrs = {};
+ this._syncAttrs(); // constructor sync ensures default properties are present on this.attrs
+
this._config = Object.assign({}, {
css: ``,
helpers: {},
@@ -253,8 +258,8 @@ 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 = {};
this.isStateShared = true;
@@ -327,7 +332,6 @@ component.js
);
Object.assign(this.state, newState);
- Object.keys(this.constructor.attrsSchema).forEach(attr => this._updateAttr(attr));
if (Object.keys(this.getConfig(`routes`)).length) {
this.router = new Router(this, {historyMethod: this.historyMethod});
@@ -347,6 +351,9 @@ component.js
return;
}
+ if (this.router) {
+ this.router.unregisterListeners();
+ }
if (this.$panelParent) {
this.$panelParent.$panelChildren.delete(this);
}
@@ -384,7 +391,7 @@ component.js
this._applyStyles(newVal);
}
- if (this.isPanelRoot && this.initialized) {
+ if (this.initialized) {
this.update();
}
}
@@ -417,7 +424,7 @@ component.js
$attrs: this.attrs,
}));
} catch (e) {
- this.logError(`Error while rendering ${this.toString()}`, this, e.stack);
+ this.logError(`Error while rendering ${this.toString()}`, this, `\n`, e);
}
}
return this._rendered || EMPTY_DIV;
@@ -459,21 +466,77 @@ component.js
return state;
}
+ /**
+ * Validates attrsSchema and syncs element attributes defined in attrsSchema with this.attrs
+ */
+ _syncAttrs() {
+ // maintain local validated map where all schema keys are defined
+ this._attrsSchema = {};
+ const attrsSchema = this.constructor.attrsSchema;
+
+ for (const attr of Object.keys(attrsSchema)) {
+ // convert type shorthand to object
+ let attrSchema = attrsSchema[attr];
+ if (typeof attrSchema === `string`) {
+ attrSchema = {type: attrSchema};
+ }
+
+ // Ensure attr type is valid
+ const attrType = attrSchema.type;
+ if (!ATTR_TYPE_DEFAULTS.hasOwnProperty(attrType)) {
+ throw new Error(
+ `Invalid type: ${attrType} for attr: ${attr} in attrsSchema. ` +
+ `Only (${Object.keys(ATTR_TYPE_DEFAULTS).map(v => `'${v}'`).join(` | `)}) is valid.`
+ );
+ }
+
+ const attrSchemaObj = {
+ type: attrType,
+ default: attrSchema.hasOwnProperty(`default`) ? attrSchema.default : ATTR_TYPE_DEFAULTS[attrType],
+ };
+
+ // convert enum to a set for perf
+ if (attrSchema.hasOwnProperty(`enum`)) {
+ const attrEnum = attrSchema.enum;
+ if (!Array.isArray(attrEnum)) {
+ throw new Error(`Enum not an array for attr: ${attr}`);
+ }
+
+ const enumSet = new Set(attrEnum);
+ enumSet.add(attrSchema.default);
+ attrSchemaObj.enumSet = enumSet;
+ }
+
+ this._attrsSchema[attr] = attrSchemaObj;
+ this._updateAttr(attr);
+ }
+
+ return this.attrs;
+ }
+
/**
* Parses html attribute using type information from attrsSchema and updates this.attrs
* @param {string} attr - attribute name
*/
_updateAttr(attr) {
- const attrsSchema = this.constructor.attrsSchema;
+ const attrsSchema = this._attrsSchema;
if (attrsSchema.hasOwnProperty(attr)) {
const attrSchema = attrsSchema[attr];
- const attrType = attrSchema.type || `string`;
+ const attrType = attrSchema.type;
let attrValue = null;
- if (!this.hasAttribute(attr) && attrSchema.hasOwnProperty(`default`)) {
+ if (!this.hasAttribute(attr)) {
attrValue = attrSchema.default;
} else if (attrType === `string`) {
attrValue = this.getAttribute(attr);
+ const enumSet = attrSchema.enumSet;
+
+ if (enumSet && !enumSet.has(attrValue)) {
+ throw new Error(
+ `Invalid value: '${attrValue}' for attr: ${attr}. ` +
+ `Only (${Array.from(enumSet).map(v => `'${v}'`).join(` | `)}) is valid.`
+ );
+ }
} else if (attrType === `boolean`) {
attrValue = this.isAttributeEnabled(attr);
} else if (attrType === `number`) {
@@ -563,7 +626,7 @@ component.js
diff --git a/docs/global.html b/docs/global.html
index 72a3f5ec..02127b93 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -23,7 +23,7 @@
@@ -277,7 +277,7 @@ Properties:
Source:
@@ -309,7 +309,7 @@ Properties:
diff --git a/docs/index.html b/docs/index.html
index 3370bd32..6f774771 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -23,7 +23,7 @@
@@ -61,7 +61,7 @@ Home
Classes
component-utils/controlled-component.js
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 + attributeChangedCallback(attr) { + // super.attributeChangedCallback class calls this.update() which will throw an error + this._updateAttr(attr); + if (this.initialized) { + this._update(); + } } _render() { @@ -91,6 +94,7 @@component-utils/controlled-component.js
$component: this, $helpers: this.helpers, $controller: this.controller, + $attrs: this.attrs, }); } catch (e) { this.logError(`Error while rendering ${this.toString()}`, this, e.stack); @@ -111,7 +115,7 @@component-utils/controlled-component.js
diff --git a/docs/component-utils_index.js.html b/docs/component-utils_index.js.html index 7a64c927..acb633ee 100644 --- a/docs/component-utils_index.js.html +++ b/docs/component-utils_index.js.html @@ -23,7 +23,7 @@
@@ -80,7 +80,7 @@
diff --git a/docs/component-utils_proxy-component.js.html b/docs/component-utils_proxy-component.js.html index 3b92a0d9..25f78c4a 100644 --- a/docs/component-utils_proxy-component.js.html +++ b/docs/component-utils_proxy-component.js.html @@ -23,7 +23,7 @@
component-utils/index.js
diff --git a/docs/component-utils_proxy-component.js.html b/docs/component-utils_proxy-component.js.html index 3b92a0d9..25f78c4a 100644 --- a/docs/component-utils_proxy-component.js.html +++ b/docs/component-utils_proxy-component.js.html @@ -23,7 +23,7 @@
@@ -165,7 +165,7 @@
diff --git a/docs/component-utils_state-controller.js.html b/docs/component-utils_state-controller.js.html index 07ac29b6..80113112 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
diff --git a/docs/component-utils_state-controller.js.html b/docs/component-utils_state-controller.js.html index 07ac29b6..80113112 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 63cf549b..5abcf657 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 63cf549b..5abcf657 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 f309090f..126d554b 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 f309090f..126d554b 100644 --- a/docs/component.js.html +++ b/docs/component.js.html @@ -23,7 +23,7 @@
@@ -45,6 +45,7 @@
diff --git a/docs/global.html b/docs/global.html index 72a3f5ec..02127b93 100644 --- a/docs/global.html +++ b/docs/global.html @@ -23,7 +23,7 @@
component.js
import Router from './router'; const DOCUMENT_FRAGMENT_NODE = 11; +const ATTR_TYPE_DEFAULTS = {string: ``, boolean: false, number: 0, json: null}; /** * Definition of a Panel component/app, implemented as an HTML custom element. @@ -240,6 +241,10 @@component.js
super(); this.panelID = cuid(); + + this.attrs = {}; + this._syncAttrs(); // constructor sync ensures default properties are present on this.attrs + this._config = Object.assign({}, { css: ``, helpers: {}, @@ -253,8 +258,8 @@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 = {}; this.isStateShared = true; @@ -327,7 +332,6 @@component.js
); Object.assign(this.state, newState); - Object.keys(this.constructor.attrsSchema).forEach(attr => this._updateAttr(attr)); if (Object.keys(this.getConfig(`routes`)).length) { this.router = new Router(this, {historyMethod: this.historyMethod}); @@ -347,6 +351,9 @@component.js
return; } + if (this.router) { + this.router.unregisterListeners(); + } if (this.$panelParent) { this.$panelParent.$panelChildren.delete(this); } @@ -384,7 +391,7 @@component.js
this._applyStyles(newVal); } - if (this.isPanelRoot && this.initialized) { + if (this.initialized) { this.update(); } } @@ -417,7 +424,7 @@component.js
$attrs: this.attrs, })); } catch (e) { - this.logError(`Error while rendering ${this.toString()}`, this, e.stack); + this.logError(`Error while rendering ${this.toString()}`, this, `\n`, e); } } return this._rendered || EMPTY_DIV; @@ -459,21 +466,77 @@component.js
return state; } + /** + * Validates attrsSchema and syncs element attributes defined in attrsSchema with this.attrs + */ + _syncAttrs() { + // maintain local validated map where all schema keys are defined + this._attrsSchema = {}; + const attrsSchema = this.constructor.attrsSchema; + + for (const attr of Object.keys(attrsSchema)) { + // convert type shorthand to object + let attrSchema = attrsSchema[attr]; + if (typeof attrSchema === `string`) { + attrSchema = {type: attrSchema}; + } + + // Ensure attr type is valid + const attrType = attrSchema.type; + if (!ATTR_TYPE_DEFAULTS.hasOwnProperty(attrType)) { + throw new Error( + `Invalid type: ${attrType} for attr: ${attr} in attrsSchema. ` + + `Only (${Object.keys(ATTR_TYPE_DEFAULTS).map(v => `'${v}'`).join(` | `)}) is valid.` + ); + } + + const attrSchemaObj = { + type: attrType, + default: attrSchema.hasOwnProperty(`default`) ? attrSchema.default : ATTR_TYPE_DEFAULTS[attrType], + }; + + // convert enum to a set for perf + if (attrSchema.hasOwnProperty(`enum`)) { + const attrEnum = attrSchema.enum; + if (!Array.isArray(attrEnum)) { + throw new Error(`Enum not an array for attr: ${attr}`); + } + + const enumSet = new Set(attrEnum); + enumSet.add(attrSchema.default); + attrSchemaObj.enumSet = enumSet; + } + + this._attrsSchema[attr] = attrSchemaObj; + this._updateAttr(attr); + } + + return this.attrs; + } + /** * Parses html attribute using type information from attrsSchema and updates this.attrs * @param {string} attr - attribute name */ _updateAttr(attr) { - const attrsSchema = this.constructor.attrsSchema; + const attrsSchema = this._attrsSchema; if (attrsSchema.hasOwnProperty(attr)) { const attrSchema = attrsSchema[attr]; - const attrType = attrSchema.type || `string`; + const attrType = attrSchema.type; let attrValue = null; - if (!this.hasAttribute(attr) && attrSchema.hasOwnProperty(`default`)) { + if (!this.hasAttribute(attr)) { attrValue = attrSchema.default; } else if (attrType === `string`) { attrValue = this.getAttribute(attr); + const enumSet = attrSchema.enumSet; + + if (enumSet && !enumSet.has(attrValue)) { + throw new Error( + `Invalid value: '${attrValue}' for attr: ${attr}. ` + + `Only (${Array.from(enumSet).map(v => `'${v}'`).join(` | `)}) is valid.` + ); + } } else if (attrType === `boolean`) { attrValue = this.isAttributeEnabled(attr); } else if (attrType === `number`) { @@ -563,7 +626,7 @@component.js
diff --git a/docs/global.html b/docs/global.html index 72a3f5ec..02127b93 100644 --- a/docs/global.html +++ b/docs/global.html @@ -23,7 +23,7 @@
@@ -277,7 +277,7 @@ Source:
@@ -309,7 +309,7 @@
diff --git a/docs/index.html b/docs/index.html index 3370bd32..6f774771 100644 --- a/docs/index.html +++ b/docs/index.html @@ -23,7 +23,7 @@
Properties:
Properties:
diff --git a/docs/index.html b/docs/index.html index 3370bd32..6f774771 100644 --- a/docs/index.html +++ b/docs/index.html @@ -23,7 +23,7 @@
@@ -61,7 +61,7 @@