Lovelace Custom Fan Card Example

If you changed js file to use mwc-button, make sure you change on-tap to on-click

Tried the original code and the code switching <paper-button> to <mwc-button> in the code and on-tap to on-click . No luck.

class CustomFanCard extends Polymer.Element {

    static get template() {
        return Polymer.html`
            <style is="custom-style" include="iron-flex iron-flex-alignment"></style>
            <style>
                :host {
                    line-height: 1.5;
                }
                .speed {
                    min-width: 30px;
                    max-width: 30px;
					margin-left: 2px;
                    margin-right: 2px;
    	            //background-color:#546E7A;
					background-color:#759aaa;
	                border: 1px solid lightgrey; 
	                font-size: 10px !important;
	                float: right !important;   
                }
               </style>
            <hui-generic-entity-row hass="[[hass]]" config="[[_config]]">
                <div class='horizontal justified layout' on-click="stopPropagation">
                    <mwc-button
                        class='speed'
						style='[[_lowOnColor]]'
                        toggles name="low"
                        on-click='setSpeed'
                        disabled='[[_isOnLow]]'>LOW</mwc-button>
                    <mwc-button
                        class='speed'
						style='[[_medOnColor]]'
                        toggles name="medium"
                        on-click='setSpeed'
                        disabled='[[_isOnMed]]'>MED</mwc-button>
                    <mwc-button
                        class='speed'
						style='[[_highOnColor]]'
                        toggles name="high"
                        on-click='setSpeed'
                        disabled='[[_isOnHigh]]'>HIGH</mwc-button>
				    <mwc-button
                        class='speed'
						style='[[_offColor]]'
                        toggles name="off"
                        on-click='setSpeed'
                        disabled='[[_isOffState]]'>OFF</mwc-button>
                </div>
            </hui-generic-entity-row>
        `;
    }

    static get properties() {
        return {
            hass: {
                type: Object,
                observer: 'hassChanged'
            },
            _config: Object,
            _stateObj: Object,
			_lowOnColor: String,
			_medOnColor: String,
			_highOnColor: String,
			_offColor: String,
            _isOffState: Boolean,
            _isOnState: Boolean,
            _isOnLow: Boolean,
            _isOnMed: Boolean,
            _isOnHigh: Boolean
        }
    }

    setConfig(config) {
        this._config = config;
    }

    hassChanged(hass) {

        const config = this._config;
        const stateObj = hass.states[config.entity];

        let speed;
        if (stateObj && stateObj.attributes) {
            speed = stateObj.attributes.speed || 'off';
        }
		
		let low;
		let med;
		let high;
		let offstate;
		
		if (stateObj && stateObj.attributes) {
		    if (stateObj.state == 'on' && stateObj.attributes.speed == 'low') {
			    low = 'on';
		    } else if (stateObj.state == 'on' && stateObj.attributes.speed == 'medium') {
			    med = 'on';
		    } else if (stateObj.state == 'on' && stateObj.attributes.speed == 'high') {
			    high = 'on';
		    } else {
				offstate = 'on';
			}
		}
		
        let lowcolor;
		let medcolor;
		let hicolor;
		let offcolor;
		
		if (low == 'on') {
			lowcolor = 'background-color: #43A047';
		} else {
			lowcolor = '';
		}
		
		if (med == 'on') {
			medcolor = 'background-color: #43A047';
		} else {
			medcolor = '';
		}
		
		if (high == 'on') {
			hicolor = 'background-color: #43A047';
		} else {
			hicolor = '';
		}
		
		if (offstate == 'on') {
			//offcolor = 'background-color: #43A047';
			offcolor = 'background-color: #f44c09';
		} else {
			offcolor = '';
		}
		
		this.setProperties({
            _stateObj: stateObj,
			_isOffState: stateObj.state == 'off',
            _isOnLow: low === 'on',
			_isOnMed: med === 'on',
			_isOnHigh: high === 'on',
			_lowOnColor: lowcolor,
			_medOnColor: medcolor,
			_highOnColor: hicolor,
			_offColor: offcolor
        });
    }

    stopPropagation(e) {
        e.stopPropagation();
    }

    setSpeed(e) {
        const speed = e.currentTarget.getAttribute('name');
        this.hass.callService('fan', 'set_speed', {
            entity_id: this._config.entity, speed: speed
        });
    }

}

customElements.define('custom-fan-card', CustomFanCard);

Not sure. That is a modified version of what I original posted and is different than what I am running. I just changed those 2 things on mine and it is working. I am on 0.88.0.

Try 88.1 as itā€™s working for me on that.

I guess itā€™s almost time to take the v88.x plunge. :worried:

EDIT: I guess I wonā€™t update yetā€¦Thereā€™s no V88.1 for docker yet.

I updated to 0.88.1 and didnā€™t work, so I cleared the Safari cache and refreshed. Now its working!

The text and boxes are pretty off. I wish I could help fix it, but I donā€™t know how to code.

Thanks for all help.

Can you please share your final working card code. I tried some of the other codes on this page and Iā€™m getting same error from before Custom Element doesnā€™t exist .

Struggling to get the layout right

image

This seems to have fixed it. Changed the max-width

            .speed {
                min-width: 20px;
                max-width: 85px;
                margin-left: 2px;
                margin-right: 2px;
                float: right !important;

image

if you shrink the window or view it on a mobile device does it stay like that. I found if I did that the buttons wouldnā€™t resize.

I just posted another thread about it here and you can see what I mean:

Unfortunately they donā€™t resize and end up overlapping. I havenā€™t figured out how to make them smaller

Yeah, did you see the other thread?

this really sucks. it was really looking pretty good before and now weā€™re back to no good options for a compact four button fan control. :rage:

Can you post all your styles, or the entire file, whatever is easier?

I know you didnā€™t ask me but here is the code Iā€™m using right now. Iā€™m really close to getting it back to useful. the only things I canā€™t change are the font color to match the theme and the text size and alignment to stay within the boundaries of the button. The way it is right now it will resize properly and the buttons all function.

ex1

ex2

the code:

class CustomFanCard extends Polymer.Element {

    static get template() {
        return Polymer.html`
            <style is="custom-style" include="iron-flex iron-flex-alignment"></style>
            <style>
                :host {
                    line-height: inherit;
                }
                .speed {
                    min-width: 30px;
					max-width: 30px;
					max-height: 30px;
					margin-left: 2px;
                    margin-right: 2px;
    	            background-color:#759aaa;
	                border: 1px solid lightgrey; 
					border-radius: 4px;
	                font-size: 2px !important;
	                float: right !important;
					text-align: center;
					color: #FFFFFF;
                }
               </style>
            <hui-generic-entity-row hass="[[hass]]" config="[[_config]]">
                <div class='horizontal justified layout' on-click="stopPropagation">
                    <mwc-button
						class='speed'
                        style='[[_lowOnColor]]'
						label='LOW'
                        toggles name="low"
                        on-click='setSpeed'
                        disabled='[[_isOnLow]]'></mwc-button>
                    <mwc-button
						class='speed'
                        style='[[_medOnColor]]'
                        toggles name="medium"
						label='MED'
                        on-click='setSpeed'
                        disabled='[[_isOnMed]]'></mwc-button>
                    <mwc-button
						class='speed'
                        style='[[_highOnColor]]'
						label='HIGH'
                        toggles name="high"
                        on-click='setSpeed'
                        disabled='[[_isOnHigh]]'></mwc-button>
				    <mwc-button
						class='speed'
                        style='[[_offColor]]'
						label='OFF'
                        toggles name="off"
                        on-click='setSpeed'
                        disabled='[[_isOffState]]'></mwc-button>
                </div>
            </hui-generic-entity-row>
        `;
    }

    static get properties() {
        return {
            hass: {
                type: Object,
                observer: 'hassChanged'
            },
            _config: Object,
            _stateObj: Object,
			_lowOnColor: String,
			_medOnColor: String,
			_highOnColor: String,
			_offColor: String,
            _isOffState: Boolean,
            _isOnState: Boolean,
            _isOnLow: Boolean,
            _isOnMed: Boolean,
            _isOnHigh: Boolean
        }
    }

    setConfig(config) {
        this._config = config;
    }

    hassChanged(hass) {

        const config = this._config;
        const stateObj = hass.states[config.entity];

        let speed;
        if (stateObj && stateObj.attributes) {
            speed = stateObj.attributes.speed || 'off';
        }
		
		let low;
		let med;
		let high;
		let offstate;
		
		if (stateObj && stateObj.attributes) {
		    if (stateObj.state == 'on' && stateObj.attributes.speed == 'low') {
			    low = 'on';
		    } else if (stateObj.state == 'on' && stateObj.attributes.speed == 'medium') {
			    med = 'on';
		    } else if (stateObj.state == 'on' && stateObj.attributes.speed == 'high') {
			    high = 'on';
		    } else {
				offstate = 'on';
			}
		}
		
        let lowcolor;
		let medcolor;
		let hicolor;
		let offcolor;
		
		if (low == 'on') {
			lowcolor = 'background-color: #43A047';
		} else {
			lowcolor = '';
		}
		
		if (med == 'on') {
			medcolor = 'background-color: #43A047';
		} else {
			medcolor = '';
		}
		
		if (high == 'on') {
			hicolor = 'background-color: #43A047';
		} else {
			hicolor = '';
		}
		
		if (offstate == 'on') {
			//offcolor = 'background-color: #43A047';
			offcolor = 'background-color: #f44c09';
		} else {
			offcolor = '';
		}
		
		this.setProperties({
            _stateObj: stateObj,
			_isOffState: stateObj.state == 'off',
            _isOnLow: low === 'on',
			_isOnMed: med === 'on',
			_isOnHigh: high === 'on',
			_lowOnColor: lowcolor,
			_medOnColor: medcolor,
			_highOnColor: hicolor,
			_offColor: offcolor
        });
    }

    stopPropagation(e) {
        e.stopPropagation();
    }

    setSpeed(e) {
        const speed = e.currentTarget.getAttribute('name');
        this.hass.callService('fan', 'set_speed', {
            entity_id: this._config.entity, speed: speed
        });
    }

}

customElements.define('custom-fan-card', CustomFanCard);

there are a few of the style options Iā€™ve tried still in the code but they wonā€™t affect the button style at all.

@slipx06 Can you share your code?

class CustomFanCard extends Polymer.Element {

static get template() {
    return Polymer.html`
        <style is="custom-style" include="iron-flex iron-flex-alignment"></style>
        <style>
            :host {
                line-height: 1.5;
            }
            .speed {
                min-width: 20px;
                max-width: 85px;
                margin-left: 2px;
                margin-right: 2px;
                float: right !important;
            }
        </style>
        <hui-generic-entity-row hass="[[hass]]" config="[[_config]]">
            <div class='horizontal justified layout' on-click="stopPropagation">
                <mwc-button
                    class='speed'
                    raised noink name="off"
                    on-click='setSpeed'
                    disabled='[[_isOff]]'>Off</mwc-button>
                <mwc-button
                    class='speed'
                    raised noink name="low"
                    on-click='setSpeed'
                    disabled='[[_isLowSpeed]]'>Low</mwc-button>
                <mwc-button
                    class='speed'
                    raised noink name="medium"
                    on-click='setSpeed'
                    disabled='[[_isMedSpeed]]'>Med</mwc-button>
                <mwc-button
                    class='speed'
                    raised noink name="high"
                    on-click='setSpeed'
                    disabled='[[_isHghSpeed]]'>High</mwc-button>
            </div>
        </hui-generic-entity-row>
    `;
}

static get properties() {
    return {
        hass: {
            type: Object,
            observer: 'hassChanged'
        },
        _config: Object,
        _stateObj: Object,
        _isOff: Boolean,
        _isLowSpeed: Boolean,
        _isMedSpeed: Boolean,
        _isHghSpeed: Boolean
    }
}

setConfig(config) {
    this._config = config;
}

hassChanged(hass) {

    const config = this._config;
    const stateObj = hass.states[config.entity];

    let speed;
    if (stateObj && stateObj.attributes) {
        speed = stateObj.attributes.speed || 'off';
    }

    this.setProperties({
        _stateObj: stateObj,
        _isOff: stateObj.state  === 'off',
        _isLowSpeed: speed === 'low' && stateObj.state === 'on',
        _isMedSpeed: speed === 'medium' && stateObj.state === 'on',
        _isHghSpeed: speed == 'high' && stateObj.state === 'on'
    });
}

stopPropagation(e) {
    e.stopPropagation();
}

setSpeed(e) {
    const speed = e.currentTarget.getAttribute('name');
    this.hass.callService('fan', 'set_speed', {
        entity_id: this._config.entity, speed: speed
    });
}

}

customElements.define(ā€˜custom-fan-cardā€™, CustomFanCard);

Made some more changes to the style using a flex container and now the buttons do not overlap when resizing. Still need to figure out how to make them smaller

class CustomFanCard extends Polymer.Element {

    static get template() {
        return Polymer.html`
            <style>
            .flex-container {
              display: flex;
              justify-content: center;
              align-items: center;
            }

            .flex-container > div {
              margin: 0px;
              padding: 1px;
            }
            </style>

            <hui-generic-entity-row hass="[[hass]]" config="[[_config]]">
                <div class='flex-container' on-click="stopPropagation">
                   <div><mwc-button
                        raised noink name="off"
                        on-click='setSpeed'
                        disabled='[[_isOff]]'>Off</mwc-button></div> 
                    <div><mwc-button
                        raised noink name="low"
                        on-click='setSpeed'
                        disabled='[[_isLowSpeed]]'>Lo</mwc-button></div>
                    <div><mwc-button
                        raised noink name="medium"
                        on-click='setSpeed'
                        disabled='[[_isMedSpeed]]'>Med</mwc-button></div>
                    <div><mwc-button
                        raised noink name="high"
                        on-click='setSpeed'
                        disabled='[[_isHghSpeed]]'>Hi</mwc-button></div>
                </div>
            </hui-generic-entity-row>
        `;
    }

    static get properties() {
        return {
            hass: {
                type: Object,
                observer: 'hassChanged'
            },
            _config: Object,
            _stateObj: Object,
            _isOff: Boolean,
            _isLowSpeed: Boolean,
            _isMedSpeed: Boolean,
            _isHghSpeed: Boolean
        }
    }

    setConfig(config) {
        this._config = config;
    }

    hassChanged(hass) {

        const config = this._config;
        const stateObj = hass.states[config.entity];

        let speed;
        if (stateObj && stateObj.attributes) {
            speed = stateObj.attributes.speed || 'off';
        }

        this.setProperties({
            _stateObj: stateObj,
            _isOff: stateObj.state  === 'off',
            _isLowSpeed: speed === 'low' && stateObj.state === 'on',
            _isMedSpeed: speed === 'medium' && stateObj.state === 'on',
            _isHghSpeed: speed == 'high' && stateObj.state === 'on'
        });
    }

    stopPropagation(e) {
        e.stopPropagation();
    }

    setSpeed(e) {
        const speed = e.currentTarget.getAttribute('name');
        this.hass.callService('fan', 'set_speed', {
            entity_id: this._config.entity, speed: speed
        });
    }

}

customElements.define('custom-fan-card', CustomFanCard);

image

image

Can make them thinner using dense=""
image

Thanks for sharing. Came out like that on mine.

Well, after many, many hours trying to figure out why none of the regular styling options work on the new mwc-button I think (maybeā€¦) I figured out what is happening. Iā€™m not a web developer so take this for the value in what you paid for itā€¦:wink:

It looks to me like when the new mwc-button was implemented it created a thing called a ā€œshadowDOMā€ that locks up the available style options as soon as the ā€œmwc-buttonā€ is called in the code. Looking thru the dev-console the values for things like text size, text color, and background colors (along with a bunch of other stuff) are fixed by the shadowDOM that gets imported when the mdc-button class is used. From what I have learned there is no way to turn it off either.

But, I learned in my digging around there might be a way to override those values by creating a new shadowDOM later in the code but I donā€™t understand web development enough to figure out how to do that.

Unless someone smarter than me can figure out how to override the imposed style then I think weā€™re stuck with the way it looks above. Or Iā€™ll have to move to a different method of button. That really sucks because I never like any of the other button options available. To me they all looked way too ā€œblockyā€. Kind of like these do now. :slightly_frowning_face:

Drat!!

I might put in a bug report to see if the restrictions could be removed but I may be in the dog house with the developers right now so maybe someone else might get better traction doing it.

That looks right. Iā€™m using a theme so my colours are a little different.