mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-20 07:46:37 +00:00
Organize createElement (#1348)
* Organize createElement * Fix line-height
This commit is contained in:
parent
f2d30ad850
commit
20e92893e0
57
src/common/dom/fire_event.js
Normal file
57
src/common/dom/fire_event.js
Normal 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;
|
||||
}
|
20
src/common/util/debounce.js
Normal file
20
src/common/util/debounce.js
Normal 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);
|
||||
};
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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%);
|
||||
|
@ -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);
|
||||
}
|
||||
|
7
src/panels/lovelace/common/create-error-card-config.js
Normal file
7
src/panels/lovelace/common/create-error-card-config.js
Normal file
@ -0,0 +1,7 @@
|
||||
export default function createErrorConfig(error, origConfig) {
|
||||
return {
|
||||
type: 'error',
|
||||
error,
|
||||
origConfig,
|
||||
};
|
||||
}
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user