mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 16:26:43 +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 { 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.
|
// Polymer legacy event helpers used courtesy of the Polymer project.
|
||||||
//
|
//
|
||||||
// Copyright (c) 2017 The Polymer Authors. All rights reserved.
|
// Copyright (c) 2017 The Polymer Authors. All rights reserved.
|
||||||
@ -33,31 +35,22 @@ import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
|
|||||||
/* @polymerMixin */
|
/* @polymerMixin */
|
||||||
export default dedupingMixin(superClass => class extends superClass {
|
export default dedupingMixin(superClass => class extends superClass {
|
||||||
/**
|
/**
|
||||||
* Dispatches a custom event with an optional detail value.
|
* Dispatches a custom event with an optional detail value.
|
||||||
*
|
*
|
||||||
* @param {string} type Name of event type.
|
* @param {string} type Name of event type.
|
||||||
* @param {*=} detail Detail value containing event-specific
|
* @param {*=} detail Detail value containing event-specific
|
||||||
* payload.
|
* payload.
|
||||||
* @param {{ bubbles: (boolean|undefined),
|
* @param {{ bubbles: (boolean|undefined),
|
||||||
cancelable: (boolean|undefined),
|
cancelable: (boolean|undefined),
|
||||||
composed: (boolean|undefined) }=}
|
composed: (boolean|undefined) }=}
|
||||||
* options Object specifying options. These may include:
|
* options Object specifying options. These may include:
|
||||||
* `bubbles` (boolean, defaults to `true`),
|
* `bubbles` (boolean, defaults to `true`),
|
||||||
* `cancelable` (boolean, defaults to false), and
|
* `cancelable` (boolean, defaults to false), and
|
||||||
* `node` on which to fire the event (HTMLElement, defaults to `this`).
|
* `node` on which to fire the event (HTMLElement, defaults to `this`).
|
||||||
* @return {Event} The new event that was fired.
|
* @return {Event} The new event that was fired.
|
||||||
*/
|
*/
|
||||||
fire(type, detail, options) {
|
fire(type, detail, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
detail = (detail === null || detail === undefined) ? {} : detail;
|
return fireEvent(options.node || this, type, detail, options);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||||
|
|
||||||
import computeStateDomain from '../../../common/entity/compute_state_domain.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 {
|
class HuiEntitiesCard extends PolymerElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@ -17,12 +18,6 @@ class HuiEntitiesCard extends PolymerElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this._whenDefined = {};
|
|
||||||
this.elementNotDefinedCallback = this.elementNotDefinedCallback.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCardSize() {
|
getCardSize() {
|
||||||
return this.lastChild ? this.lastChild.getCardSize() : 1;
|
return this.lastChild ? this.lastChild.getCardSize() : 1;
|
||||||
}
|
}
|
||||||
@ -63,23 +58,29 @@ class HuiEntitiesCard extends PolymerElement {
|
|||||||
if (this.lastChild) {
|
if (this.lastChild) {
|
||||||
this.removeChild(this.lastChild);
|
this.removeChild(this.lastChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
let error;
|
let error;
|
||||||
let element;
|
|
||||||
|
|
||||||
if (!config.filter || !Array.isArray(config.filter)) {
|
if (!config.filter || !Array.isArray(config.filter)) {
|
||||||
error = 'Incorrect filter config.';
|
error = 'Incorrect filter config.';
|
||||||
} else if (!config.card) {
|
} else if (!config.card) {
|
||||||
config.card = { type: 'entities' };
|
config = Object.assign({}, config, {
|
||||||
|
card: { type: 'entities' }
|
||||||
|
});
|
||||||
} else if (!config.card.type) {
|
} else if (!config.card.type) {
|
||||||
config.card.type = 'entities';
|
config = Object.assign({}, config, {
|
||||||
|
card: Object.assign({}, config.card, { type: 'entities' })
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let element;
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
element = createCardElement(config, this.elementNotDefinedCallback, error);
|
element = createCardElement(createErrorCardConfig(error, config.card));
|
||||||
} else {
|
} else {
|
||||||
element = createCardElement(config.card, this.elementNotDefinedCallback, null);
|
element = createCardElement(config.card);
|
||||||
element.config = this._computeCardConfig(config);
|
element._filterRawConfig = config.card;
|
||||||
|
this._updateCardConfig(element);
|
||||||
element.hass = this.hass;
|
element.hass = this.hass;
|
||||||
}
|
}
|
||||||
this.appendChild(element);
|
this.appendChild(element);
|
||||||
@ -87,25 +88,17 @@ class HuiEntitiesCard extends PolymerElement {
|
|||||||
|
|
||||||
_hassChanged(hass) {
|
_hassChanged(hass) {
|
||||||
const element = this.lastChild;
|
const element = this.lastChild;
|
||||||
if (!element || element.tagName === 'HUI-ERROR-CARD') return;
|
this._updateCardConfig(element);
|
||||||
|
|
||||||
element.hass = hass;
|
element.hass = hass;
|
||||||
element.config = this._computeCardConfig(this.config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_computeCardConfig(config) {
|
_updateCardConfig(element) {
|
||||||
return Object.assign(
|
if (!element || element.tagName === 'HUI-ERROR-CARD') return;
|
||||||
|
element.config = Object.assign(
|
||||||
{},
|
{},
|
||||||
config.card,
|
element._filterRawConfig,
|
||||||
{ entities: this._getEntities(this.hass, config.filter) }
|
{ 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);
|
customElements.define('hui-entity-filter-card', HuiEntitiesCard);
|
||||||
|
@ -12,15 +12,14 @@ class HuiErrorCard extends PolymerElement {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
[[error]]
|
[[config.error]]
|
||||||
<pre>[[_toStr(config)]]</pre>
|
<pre>[[_toStr(config.origConfig)]]</pre>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
config: Object,
|
config: Object,
|
||||||
error: String
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,13 +27,12 @@ class HuiPictureElementsCard extends LocalizeMixin(EventsMixin(PolymerElement))
|
|||||||
#root {
|
#root {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
line-height: 0;
|
|
||||||
}
|
}
|
||||||
#root img {
|
#root img {
|
||||||
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.element {
|
.element {
|
||||||
line-height: initial;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translate(-50%, -50%);
|
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-camera-preview-card.js';
|
||||||
import '../cards/hui-entities-card.js';
|
import '../cards/hui-entities-card.js';
|
||||||
import '../cards/hui-entity-filter-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-weather-forecast-card';
|
||||||
import '../cards/hui-error-card.js';
|
import '../cards/hui-error-card.js';
|
||||||
|
|
||||||
|
import createErrorCardConfig from './create-error-card-config.js';
|
||||||
|
|
||||||
const CARD_TYPES = [
|
const CARD_TYPES = [
|
||||||
'camera-preview',
|
'camera-preview',
|
||||||
'entities',
|
'entities',
|
||||||
'entity-filter',
|
'entity-filter',
|
||||||
'entity-picture',
|
'entity-picture',
|
||||||
|
'error',
|
||||||
'glance',
|
'glance',
|
||||||
'history-graph',
|
'history-graph',
|
||||||
'iframe',
|
'iframe',
|
||||||
@ -31,33 +36,41 @@ const CARD_TYPES = [
|
|||||||
|
|
||||||
const CUSTOM_TYPE_PREFIX = 'custom:';
|
const CUSTOM_TYPE_PREFIX = 'custom:';
|
||||||
|
|
||||||
export default function
|
function _createElement(tag, config) {
|
||||||
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';
|
|
||||||
const element = document.createElement(tag);
|
const element = document.createElement(tag);
|
||||||
if (error) element.error = error;
|
|
||||||
element.config = config;
|
element.config = config;
|
||||||
return element;
|
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 { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||||
|
|
||||||
import applyThemesOnElement from '../../common/dom/apply_themes_on_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';
|
import createCardElement from './common/create-card-element';
|
||||||
|
|
||||||
class HUIView extends PolymerElement {
|
class HUIView extends PolymerElement {
|
||||||
@ -51,7 +53,7 @@ class HUIView extends PolymerElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div id='columns'></div>
|
<div id='columns' on-rebuild-view='_debouncedConfigChanged'></div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@ -76,20 +78,7 @@ class HUIView extends PolymerElement {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this._elements = [];
|
this._elements = [];
|
||||||
this._whenDefined = {};
|
this._debouncedConfigChanged = debounce(this._configChanged, 100);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_configChanged() {
|
_configChanged() {
|
||||||
@ -105,7 +94,11 @@ class HUIView extends PolymerElement {
|
|||||||
return;
|
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 = [];
|
let columns = [];
|
||||||
const columnEntityCount = [];
|
const columnEntityCount = [];
|
||||||
@ -159,13 +152,6 @@ class HUIView extends PolymerElement {
|
|||||||
this._elements[i].hass = hass;
|
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);
|
customElements.define('hui-view', HUIView);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user