Organize createElement (#1348)

* Organize createElement

* Fix line-height
This commit is contained in:
Paulus Schoutsen 2018-06-27 14:18:05 -04:00 committed by GitHub
parent f2d30ad850
commit 20e92893e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 172 additions and 105 deletions

View File

@ -0,0 +1,57 @@
// Polymer legacy event helpers used courtesy of the Polymer project.
//
// Copyright (c) 2017 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* Dispatches a custom event with an optional detail value.
*
* @param {string} type Name of event type.
* @param {*=} detail Detail value containing event-specific
* payload.
* @param {{ bubbles: (boolean|undefined),
cancelable: (boolean|undefined),
composed: (boolean|undefined) }=}
* options Object specifying options. These may include:
* `bubbles` (boolean, defaults to `true`),
* `cancelable` (boolean, defaults to false), and
* `node` on which to fire the event (HTMLElement, defaults to `this`).
* @return {Event} The new event that was fired.
*/
export default function fire(node, type, detail, options) {
options = options || {};
detail = (detail === null || detail === undefined) ? {} : detail;
const event = new Event(type, {
bubbles: options.bubbles === undefined ? true : options.bubbles,
cancelable: Boolean(options.cancelable),
composed: options.composed === undefined ? true : options.composed
});
event.detail = detail;
node.dispatchEvent(event);
return event;
}

View File

@ -0,0 +1,20 @@
// From: https://davidwalsh.name/javascript-debounce-function
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
export default function debounce(func, wait, immediate) {
let timeout;
return function (...args) {
const context = this;
const later = () => {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}

View File

@ -1,5 +1,7 @@
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
import fireEvent from '../common/dom/fire_event.js';
// Polymer legacy event helpers used courtesy of the Polymer project.
//
// Copyright (c) 2017 The Polymer Authors. All rights reserved.
@ -33,31 +35,22 @@ import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
/* @polymerMixin */
export default dedupingMixin(superClass => class extends superClass {
/**
* Dispatches a custom event with an optional detail value.
*
* @param {string} type Name of event type.
* @param {*=} detail Detail value containing event-specific
* payload.
* @param {{ bubbles: (boolean|undefined),
cancelable: (boolean|undefined),
composed: (boolean|undefined) }=}
* options Object specifying options. These may include:
* `bubbles` (boolean, defaults to `true`),
* `cancelable` (boolean, defaults to false), and
* `node` on which to fire the event (HTMLElement, defaults to `this`).
* @return {Event} The new event that was fired.
*/
* Dispatches a custom event with an optional detail value.
*
* @param {string} type Name of event type.
* @param {*=} detail Detail value containing event-specific
* payload.
* @param {{ bubbles: (boolean|undefined),
cancelable: (boolean|undefined),
composed: (boolean|undefined) }=}
* options Object specifying options. These may include:
* `bubbles` (boolean, defaults to `true`),
* `cancelable` (boolean, defaults to false), and
* `node` on which to fire the event (HTMLElement, defaults to `this`).
* @return {Event} The new event that was fired.
*/
fire(type, detail, options) {
options = options || {};
detail = (detail === null || detail === undefined) ? {} : detail;
const event = new Event(type, {
bubbles: options.bubbles === undefined ? true : options.bubbles,
cancelable: Boolean(options.cancelable),
composed: options.composed === undefined ? true : options.composed
});
event.detail = detail;
const node = options.node || this;
node.dispatchEvent(event);
return event;
return fireEvent(options.node || this, type, detail, options);
}
});

View File

@ -1,7 +1,8 @@
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import computeStateDomain from '../../../common/entity/compute_state_domain.js';
import createCardElement from '../common/create-card-element';
import createCardElement from '../common/create-card-element.js';
import createErrorCardConfig from '../common/create-error-card-config.js';
class HuiEntitiesCard extends PolymerElement {
static get properties() {
@ -17,12 +18,6 @@ class HuiEntitiesCard extends PolymerElement {
};
}
constructor() {
super();
this._whenDefined = {};
this.elementNotDefinedCallback = this.elementNotDefinedCallback.bind(this);
}
getCardSize() {
return this.lastChild ? this.lastChild.getCardSize() : 1;
}
@ -63,23 +58,29 @@ class HuiEntitiesCard extends PolymerElement {
if (this.lastChild) {
this.removeChild(this.lastChild);
}
let error;
let element;
if (!config.filter || !Array.isArray(config.filter)) {
error = 'Incorrect filter config.';
} else if (!config.card) {
config.card = { type: 'entities' };
config = Object.assign({}, config, {
card: { type: 'entities' }
});
} else if (!config.card.type) {
config.card.type = 'entities';
config = Object.assign({}, config, {
card: Object.assign({}, config.card, { type: 'entities' })
});
}
let element;
if (error) {
element = createCardElement(config, this.elementNotDefinedCallback, error);
element = createCardElement(createErrorCardConfig(error, config.card));
} else {
element = createCardElement(config.card, this.elementNotDefinedCallback, null);
element.config = this._computeCardConfig(config);
element = createCardElement(config.card);
element._filterRawConfig = config.card;
this._updateCardConfig(element);
element.hass = this.hass;
}
this.appendChild(element);
@ -87,25 +88,17 @@ class HuiEntitiesCard extends PolymerElement {
_hassChanged(hass) {
const element = this.lastChild;
if (!element || element.tagName === 'HUI-ERROR-CARD') return;
this._updateCardConfig(element);
element.hass = hass;
element.config = this._computeCardConfig(this.config);
}
_computeCardConfig(config) {
return Object.assign(
_updateCardConfig(element) {
if (!element || element.tagName === 'HUI-ERROR-CARD') return;
element.config = Object.assign(
{},
config.card,
{ entities: this._getEntities(this.hass, config.filter) }
element._filterRawConfig,
{ entities: this._getEntities(this.hass, this.config.filter) }
);
}
elementNotDefinedCallback(tag) {
if (!(tag in this._whenDefined)) {
this._whenDefined[tag] = customElements.whenDefined(tag)
.then(() => this._configChanged(this.config));
}
}
}
customElements.define('hui-entity-filter-card', HuiEntitiesCard);

View File

@ -12,15 +12,14 @@ class HuiErrorCard extends PolymerElement {
padding: 8px;
}
</style>
[[error]]
<pre>[[_toStr(config)]]</pre>
[[config.error]]
<pre>[[_toStr(config.origConfig)]]</pre>
`;
}
static get properties() {
return {
config: Object,
error: String
};
}

View File

@ -27,13 +27,12 @@ class HuiPictureElementsCard extends LocalizeMixin(EventsMixin(PolymerElement))
#root {
position: relative;
overflow: hidden;
line-height: 0;
}
#root img {
display: block;
width: 100%;
}
.element {
line-height: initial;
white-space: nowrap;
position: absolute;
transform: translate(-50%, -50%);

View File

@ -1,3 +1,5 @@
import fireEvent from '../../../common/dom/fire_event.js';
import '../cards/hui-camera-preview-card.js';
import '../cards/hui-entities-card.js';
import '../cards/hui-entity-filter-card.js';
@ -13,11 +15,14 @@ import '../cards/hui-plant-status-card.js';
import '../cards/hui-weather-forecast-card';
import '../cards/hui-error-card.js';
import createErrorCardConfig from './create-error-card-config.js';
const CARD_TYPES = [
'camera-preview',
'entities',
'entity-filter',
'entity-picture',
'error',
'glance',
'history-graph',
'iframe',
@ -31,33 +36,41 @@ const CARD_TYPES = [
const CUSTOM_TYPE_PREFIX = 'custom:';
export default function
createCardElement(config, elementNotDefinedCallback = null, invalidConfig = null) {
let error = invalidConfig;
let tag;
if (!error && config && typeof config === 'object' && config.type) {
if (CARD_TYPES.includes(config.type)) {
tag = `hui-${config.type}-card`;
} else if (config.type.startsWith(CUSTOM_TYPE_PREFIX)) {
tag = config.type.substr(CUSTOM_TYPE_PREFIX.length);
}
if (tag) {
if (!customElements.get(tag)) {
error = 'Custom element doesn\'t exist.';
if (elementNotDefinedCallback) elementNotDefinedCallback(tag);
}
} else {
error = 'Unknown card type encountered.';
}
} else {
error = 'No card type configured.';
}
if (error) tag = 'hui-error-card';
function _createElement(tag, config) {
const element = document.createElement(tag);
if (error) element.error = error;
element.config = config;
return element;
}
function _createErrorElement(error, config) {
return _createElement('hui-error-card', createErrorCardConfig(error, config));
}
export default function createCardElement(config) {
let tag;
if (!config || typeof config !== 'object' || !config.type) {
return _createErrorElement('No card type configured.', config);
}
if (config.type.startsWith(CUSTOM_TYPE_PREFIX)) {
tag = config.type.substr(CUSTOM_TYPE_PREFIX.length);
if (customElements.get(tag)) {
return _createElement(tag, config);
}
const element = _createErrorElement(`Custom element doesn't exist: ${tag}.`, config);
customElements.whenDefined(tag)
.then(() => fireEvent(element, 'rebuild-view'));
return element;
}
if (!CARD_TYPES.includes(config.type)) {
return _createErrorElement(`Unknown card type encountered: ${config.type}.`, config);
}
return _createElement(`hui-${config.type}-card`, config);
}

View File

@ -0,0 +1,7 @@
export default function createErrorConfig(error, origConfig) {
return {
type: 'error',
error,
origConfig,
};
}

View File

@ -2,6 +2,8 @@ import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import applyThemesOnElement from '../../common/dom/apply_themes_on_element.js';
import debounce from '../../common/util/debounce.js';
import createCardElement from './common/create-card-element';
class HUIView extends PolymerElement {
@ -51,7 +53,7 @@ class HUIView extends PolymerElement {
}
}
</style>
<div id='columns'></div>
<div id='columns' on-rebuild-view='_debouncedConfigChanged'></div>
`;
}
static get properties() {
@ -76,20 +78,7 @@ class HUIView extends PolymerElement {
constructor() {
super();
this._elements = [];
this._whenDefined = {};
this.elementNotDefinedCallback = this.elementNotDefinedCallback.bind(this);
}
_getElements(cards) {
const elements = [];
for (let i = 0; i < cards.length; i++) {
const element = createCardElement(cards[i], this.elementNotDefinedCallback, null);
element.hass = this.hass;
elements.push(element);
}
return elements;
this._debouncedConfigChanged = debounce(this._configChanged, 100);
}
_configChanged() {
@ -105,7 +94,11 @@ class HUIView extends PolymerElement {
return;
}
const elements = this._getElements(config.cards);
const elements = config.cards.map((cardConfig) => {
const element = createCardElement(cardConfig);
element.hass = this.hass;
return element;
});
let columns = [];
const columnEntityCount = [];
@ -159,13 +152,6 @@ class HUIView extends PolymerElement {
this._elements[i].hass = hass;
}
}
elementNotDefinedCallback(tag) {
if (!(tag in this._whenDefined)) {
this._whenDefined[tag] = customElements.whenDefined(tag)
.then(() => this._configChanged());
}
}
}
customElements.define('hui-view', HUIView);