--- layout: page title: "Components" description: "List of the built-in components of Home Assistant." date: 2014-12-21 13:35 sidebar: false comments: false sharing: true footer: true is_homepage: true hide_github_edit: true body_id: components-page regenerate: false --- {%- comment -%}Can't use where to count nil because of https://github.com/jekyll/jekyll/issues/6038{%- endcomment -%} {%- assign tot = 0 -%} {%- for comp in site.components -%} {%- if comp.ha_category -%} {%- if comp.ha_category.first -%} {%- assign tot = tot | plus: comp.ha_category.size -%} {%- else -%} {%- assign tot = tot | plus: 1 -%} {%- endif -%} {%- endif %} {%- endfor -%} {%- assign components = site.components | sort: 'title' -%} {%- assign categories = components | map: 'ha_category' | join: ',' | join: ',' | split: ',' | uniq | sort -%} {%- capture current_version -%}{{ site.current_major_version }}.{{ site.current_minor_version }}{% endcapture -%} {%- assign added_one_ago_minor_version = site.current_minor_version|minus: 1 -%} {%- capture added_one_ago_version -%}{{ site.current_major_version }}.{{ added_one_ago_minor_version }}{% endcapture -%} {%- assign added_two_ago_minor_version = site.current_minor_version|minus: 2 -%} {%- capture added_two_ago_version -%}{{ site.current_major_version }}.{{ added_two_ago_minor_version }}{% endcapture -%} {%- assign current_version_components_count = site.components | where: 'ha_release', current_version | size -%} {%- assign one_ago_version_components_count = site.components | where: 'ha_release', added_one_ago_version | size -%} {%- assign two_ago_version_components_count = site.components | where: 'ha_release', added_two_ago_version | size -%} <p class='note'> Support for these components is provided by the Home Assistant community. </p> <div class="grid"> <div class="grid__item one-sixth lap-one-whole palm-one-whole"> <div class="filter-button-group"> <a href='#all' class="btn">All ({{tot}})</a> <a href='#featured' class="btn featured">Featured</a> <a href='#version/{{ current_version }}' class="btn added_in_current_version">Added in {{ current_version }} ({{ current_version_components_count }})</a> <a href='#version/{{ added_one_ago_version }}' class="btn added_one_version_ago">Added in {{ added_one_ago_version }} ({{ one_ago_version_components_count }})</a> <a href='#version/{{ added_two_ago_version }}' class="btn added_two_versions_ago">Added in {{ added_two_ago_version }} ({{ two_ago_version_components_count }})</a> {%- for category in categories -%} {%- assign components_count = components | where: 'ha_category', category | size -%} {%- if category and category != 'Other' and components_count != 0 -%} <a href='#{{ category | slugify }}' class="btn">{{ category }} ({{ components_count }})</a> {%- endif -%} {%- endfor -%} <a href='#other' class="btn">Other ({{ components | where: 'ha_category', 'Other' | size }})</a> </div> </div> <div class="grid__item five-sixths lap-one-whole palm-one-whole"> <div class="component-search"> <form onsubmit="event.preventDefault(); return false"> <input type="text" name="search" id="search" class="search" placeholder="Search components..."> </form> </div> <div class="hass-option-cards" id="componentContainer"> </div> </div> </div> {% comment %} ## Pages without categories {%- for component in components -%} {% unless component.ha_category %} <p>{{ component.title }}</p> {% endunless %} {%- endfor -%} {% endcomment %} <script src="https://code.jquery.com/jquery-2.2.4.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/vanilla-lazyload/10.17.0/lazyload.min.js"></script> {% raw %} <script id="component-template" type="text/x-custom-template"> {{#components}} <a href="{{url}}" class="option-card"> <div class="img-container">{{{image}}}</div> <div class='title'>{{title}}</div> <div class='category'>{{cat}}</div> </a> {{/components}} {{^components}} <p class='note'>Nothing found!</p> {{/components}} </script> {% endraw %} <script type="text/javascript"> // This object contains all components we have var allComponents = [ {%- for component in components -%} {%- if component.ha_category -%} {%- assign sliced_version = component.ha_release | split: '.' -%} {%- assign minor_version = sliced_version[1]|plus: 0 -%} {%- assign major_version = sliced_version[0]|plus: 0 -%} {%- assign featured_first = true -%} {%- for ha_category in component.ha_category -%} {url:"{{ component.url }}", title:"{{component.title}}", cat:"{{ha_category | slugify}}", featured: {% if component.featured and featured_first %}true{% else %}false{% endif %}, v: "{{major_version}}.{{minor_version}}", logo: "{{component.logo}}"}, {%- assign featured_first = false -%} {%- endfor -%} {% endif -%} {%- endfor -%} false ]; allComponents.pop(); // remove placeholder element at the end </script> <script type="text/javascript"> (function(){ var template = $('#component-template').html(); Mustache.parse(template); // make future calls to render faster function init() { // do the lowerCase transformation once for (i=0; i < (allComponents.length); i++) { allComponents[i].titleLC = allComponents[i].title.toLowerCase(); allComponents[i].catLC = allComponents[i].cat.toLowerCase(); } // sort the components alphabetically allComponents.sort(function(a, b){ return a.titleLC.localeCompare(b.titleLC); }); if (location.hash !== '' && location.hash.indexOf('#search/') === 0) { // set default value in search from URL jQuery('.component-search input').val(decodeURIComponent(location.hash).substring(8)); } // add focus to the search field - even on IE setTimeout(function () { jQuery('.component-search input').focus(); }, 1); } init(); /** * filter all components, based on the location's hash and render them into the component box */ function applyFilter() { var logoLazyLoad = new LazyLoad({ elements_selector: ".option-card img" }); var rendered, i, filter, search; var hash = location.hash || ''; var data = { components: [], image: function () { if(this.logo === '') { return ''; } else { return '<img data-src="/images/supported_brands/' + this.logo + '">'; } } }; // fade-out css effect on the old elements. This is actually not visible on fast browsers $('#componentContainer').addClass('remove-items'); if (hash.indexOf('#search/') === -1) { // reset search box when not searching jQuery('.component-search input').val(null); } if (hash === '#all') { // shortcut: no need to filter data.components = allComponents; } else { if (hash.indexOf('#search/') === 0) { // search through title and category search = decodeURIComponent(hash).substring(8).toLowerCase(); filter = function(comp) { return (comp.titleLC.indexOf(search) !== -1) || (comp.catLC.indexOf(search) !== -1); }; } else if(hash === '#featured' || hash === '') { // only show those with featured = true filter = function(comp) { return comp.featured; }; } else if(hash.indexOf('#version/') === 0) { // compare against a version search = decodeURIComponent(hash).substring(9).toLowerCase(); filter = function(comp) { // compare version string against version js return comp.v === search; }; } else { // regular filter categories search = hash.substring(1); filter = function(comp) { return comp.catLC === search; }; } // filter all components using the filter function for (i=0; i < (allComponents.length); i++) { if (filter(allComponents[i])) { data.components.push(allComponents[i]); } } } rendered = Mustache.render(template, data); // remove previous elements and css classes, add the new stuff and then trigger the fade-in css animation $('#componentContainer').html('').removeClass('show-items remove-items').html(rendered).addClass('show-items'); logoLazyLoad.update(); } /** * update the browser location hash. This enables users to use the browser-history */ function updateHash(newHash) { if ('replaceState' in history) { history.replaceState('', '', newHash); } else { location.hash = newHash; } } // update view by filter selection jQuery('.filter-button-group a').click(function() { updateHash(this.getAttribute('href')); applyFilter(); return false; }); /** * Simple debounce implementation, based on http://davidwalsh.name/javascript-debounce-function */ function debounce(func, wait, immediate) { var timeout; return function() { var context = this, args = arguments; var later = function() { timeout = null; if (!immediate) { func.apply(context, args); } }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) { func.apply(context, args); } }; }; // update view by search text $('.component-search input').keyup(debounce(function() { var text = $(this).val(); // sanitize input text = text.replace(/[(\?|\&\{\}\(\))]/gi, ''); updateHash('#search/' + text); applyFilter(); }, 500)); window.addEventListener('hashchange', applyFilter); applyFilter(); })(); </script> <noscript> <ul> {%- for component in components -%} {%- if component.ha_category -%} <li><a href='{{ component.url }}'>{{ component.title }}</a></li> {%- endif -%} {%- endfor -%} </ul> </noscript>