<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>WebInterface &#8211; SmartHomeNG | smarthome knx homematic mqtt hue 1wire home automation</title>
	<atom:link href="https://www.smarthomeng.de/tag/webinterface/feed" rel="self" type="application/rss+xml" />
	<link>https://www.smarthomeng.de</link>
	<description>Die Device Integrations-Plattform für Dein Smart Home</description>
	<lastBuildDate>Sun, 29 Mar 2020 16:25:46 +0000</lastBuildDate>
	<language>de</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.5.5</generator>

<image>
	<url>https://www.smarthomeng.de/wp-content/uploads/global/logo_small_152x152-150x150.png</url>
	<title>WebInterface &#8211; SmartHomeNG | smarthome knx homematic mqtt hue 1wire home automation</title>
	<link>https://www.smarthomeng.de</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Jetzt wird es bunt! Wie man ein Web-Interface für ein Plugin schreibt (ab SmartHomeNG 1.5)</title>
		<link>https://www.smarthomeng.de/jetzt-wird-es-bunt-wie-man-ein-plugin-webinterface-schreibt-in-kuerze-ab-smarthomeng-1-5</link>
					<comments>https://www.smarthomeng.de/jetzt-wird-es-bunt-wie-man-ein-plugin-webinterface-schreibt-in-kuerze-ab-smarthomeng-1-5#respond</comments>
		
		<dc:creator><![CDATA[Marc René Frieß]]></dc:creator>
		<pubDate>Tue, 26 Jun 2018 17:00:46 +0000</pubDate>
				<category><![CDATA[Beispiel-Implementierungen]]></category>
		<category><![CDATA[Plugins]]></category>
		<category><![CDATA[Bootstrap]]></category>
		<category><![CDATA[CherryPy]]></category>
		<category><![CDATA[Jinja2]]></category>
		<category><![CDATA[SmartHomeNG 1.5]]></category>
		<category><![CDATA[WebInterface]]></category>
		<guid isPermaLink="false">https://www.smarthomeng.de/?p=1740</guid>

					<description><![CDATA[Um SmartHomeNG einsteigerfreundlicher zu machen, bieten wir ab Version 1.5 die Möglichkeit, Plugins mit eigenen Web-Interfaces (ähnlich dem Backend Plugin) zu versehen. Web-Interface bezeichnet hierbei eine kleine Webseite, die Infos zum Plugin ausgibt oder die Bedienung und Konfiguration vereinfacht. Existierende Web-Interfaces von Plugins, lassen sich in der Plugin-Liste des Backend-Plugins<a class="moretag" href="https://www.smarthomeng.de/jetzt-wird-es-bunt-wie-man-ein-plugin-webinterface-schreibt-in-kuerze-ab-smarthomeng-1-5"> Weiterlesen&#8230;</a>]]></description>
										<content:encoded><![CDATA[<p>Um SmartHomeNG einsteigerfreundlicher zu machen, bieten wir ab Version 1.5 die Möglichkeit, Plugins mit eigenen Web-Interfaces (ähnlich dem Backend Plugin) zu versehen. Web-Interface bezeichnet hierbei eine kleine Webseite, die Infos zum Plugin ausgibt oder die Bedienung und Konfiguration vereinfacht.</p>
<p><span id="more-1740"></span></p>
<p>Existierende Web-Interfaces von Plugins, lassen sich in der Plugin-Liste des Backend-Plugins finden (siehe Artikel <a href="https://www.smarthomeng.de/das-backend-plugin" target="_blank" rel="noopener"><em>Das Backend-Plugin</em></a>). Ein Web-Interface ist instanzspezifisch, die URL wird dementsprechend aufgebaut.</p>
<p>Die Web-Interfaces des AVM, KNX, Wundergrund und Webservices Plugins sind nur einige Beispiele:<br />
<a href="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_1.jpg"><img decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_1-150x150.jpg" alt="" width="150" height="150" class="alignnone wp-image-1834 size-thumbnail" /></a> <a href="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_2.jpg"><img decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_2-150x150.jpg" alt="" width="150" height="150" class="alignnone wp-image-1835 size-thumbnail" /></a> <a href="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_3.jpg"><img decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_3-150x150.jpg" alt="" width="150" height="150" class="alignnone wp-image-1836 size-thumbnail" /></a> <a href="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_4.jpg"><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_4-150x150.jpg" alt="" width="150" height="150" class="alignnone wp-image-1837 size-thumbnail" /></a></p>
<p>Basis hierzu stellt die auf <a href="https://cherrypy.org" target="_blank" rel="noopener">CherryPy</a> (und <a href="http://jinja.pocoo.org/docs/2.10/" target="_blank" rel="noopener">Jinja2</a> als Template-Engine) basierende Implementierung im http module dar. Für detaillierte Infos zur Template Syntax wird auf die o.g. Doku von Jinja2 verwiesen. Dieser Artikel setzt Grundkenntnisse darin voraus.</p>
<h1>Frameworks</h1>
<p>SmartHomeNG bringt dabei für alle Plugins nutzbar eine ganze Reihe an Frameworks bereits mit. Diese finden derzeit vor allem im Backend Plugin ihren Einsatz.</p>
<p>Vorhanden sind per Default (Versionen Stand Release 1.5):</p>
<table border="1" style="border-collapse: collapse; width: 81.4293%;" height="457">
<tbody>
<tr style="height: 29px;">
<td style="width: 31.8412%; height: 29px;"><strong>Framework</strong></td>
<td style="width: 65.1885%; height: 29px;"><strong>Beschreibung / Link</strong></td>
</tr>
<tr style="height: 58px;">
<td style="width: 31.8412%; height: 58px;">Bootstrap 4.1.1</td>
<td style="width: 65.1885%; height: 58px;">Web GUI Framework<br />
<a href="http://getbootstrap.com/" target="_blank" rel="noopener">http://getbootstrap.com/</a></td>
</tr>
<tr style="height: 87px;">
<td style="width: 31.8412%; height: 87px;">Bootstrap Datepicker Widget 1.8.0</td>
<td style="width: 65.1885%; height: 87px;"><a href="https://github.com/uxsolutions/bootstrap-datepicker" target="_blank" rel="noopener">https://github.com/uxsolutions/bootstrap-datepicker</a></td>
</tr>
<tr>
<td style="width: 31.8412%;">Bootstrap Tree View<br />(selber angepasst auf Bootstrap 4)</td>
<td style="width: 65.1885%;"><a href="https://github.com/jonmiles/bootstrap-treeview" target="_blank" rel="noopener">https://github.com/jonmiles/bootstrap-treeview</a></td>
</tr>
<tr style="height: 58px;">
<td style="width: 31.8412%; height: 58px;">JQuery 3.3.1</td>
<td style="width: 65.1885%; height: 58px;">Javascript Framework<br />
<a href="https://jquery.org" target="_blank" rel="noopener">https://jquery.org</a></td>
</tr>
<tr style="height: 58px;">
<td style="width: 31.8412%; height: 58px;">Codemirror 5.39.0</td>
<td style="width: 65.1885%; height: 58px;">Online Code Editor<br />
<a href="https://codemirror.net/" target="_blank" rel="noopener">https://codemirror.net/</a></td>
</tr>
<tr style="height: 58px;">
<td style="width: 31.8412%; height: 58px;">Font Awesome 5.1.0</td>
<td style="width: 65.1885%; height: 58px;">Icon-Font für Webseiten<br />
<a href="http://fontawesome.com" target="_blank" rel="noopener">http://fontawesome.com</a></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>Abgelegt sind die Frameworks unter <code>/smarthome/modules/http/webif/gstatic</code> und können direkt in einer Webseite genutzt werden.</p>
<p>Am Beispiel Bootstrap sieht das wie folgt aus:</p>
<pre><code class="language-html">&lt;script src="/gstatic/bootstrap/js/bootstrap.min.js"&gt;&lt;/script&gt;</code></pre>
<h1>Beispielplugin mit Web-Interface</h1>
<p>Das jeweils aktuelle Beispiel für ein Plugin mit Web-Interface findet sich unter <code>/dev/sample_plugin_webif</code>.<br />
In der Methode init_webinterface (als Teil des Plugin-Codes in der Datei <code>__init__.py</code>) wird dabei das Web-Interface initialisiert:</p>
<pre><code class="language-python">
from lib.model.smartplugin import *
[...]
    def init_webinterface(self):
        """"
        Initialize the web interface for this plugin

        This method is only needed if the plugin is implementing a web interface
        """
        try:
            self.mod_http = Modules.get_instance().get_module('http')   # try/except to handle running in a core version that does not support modules
        except:
             self.mod_http = None
        if self.mod_http == None:
            self.logger.error("Plugin '{}': Not initializing the web interface".format(self.get_shortname()))
            return False
        
        # set application configuration for cherrypy
        webif_dir = self.path_join(self.get_plugin_dir(), 'webif')
        config = {
            '/': {
                'tools.staticdir.root': webif_dir,
            },
            '/static': {
                'tools.staticdir.on': True,
                'tools.staticdir.dir': 'static'
            }
        }
        
        # Register the web interface as a cherrypy app
        self.mod_http.register_webif(WebInterface(webif_dir, self), 
                                     self.get_shortname(), 
                                     config, 
                                     self.get_classname(), self.get_instance_name(),
                                     description='')
                                   
        return True
</code></pre>
<p>Diese Methode muss dann natürlich in der <code>def __init__</code> eingebunden werden:</p>
<pre><code class="language-python">
    def __init__(self, sh, *args, **kwargs):
        [...]
        if not self.init_webinterface():
            self._init_complete = False
        [...]</pre>
<p></code>Die eigentliche Implementierung befindet sich in einer eigenen Klasse, die ebenfalls in der <code>__init__.py</code> definiert wird:</p>
<pre><code class="language-python">
import cherrypy
from jinja2 import Environment, FileSystemLoader

class WebInterface(SmartPluginWebIf):


    def __init__(self, webif_dir, plugin):
        """
        Initialization of instance of class WebInterface
        
        :param webif_dir: directory where the webinterface of the plugin resides
        :param plugin: instance of the plugin
        :type webif_dir: str
        :type plugin: object
        """
        self.logger = logging.getLogger(__name__)
        self.webif_dir = webif_dir
        self.plugin = plugin
        self.tplenv = Environment(loader=FileSystemLoader(self.plugin.path_join( self.webif_dir, 'templates' ) ))


    @cherrypy.expose
    def index(self, reload=None):
        """
        Build index.html for cherrypy
        
        Render the template and return the html file to be delivered to the browser
            
        :return: contents of the template after beeing rendered 
        """
        tmpl = self.tplenv.get_template('index.html')
        return tmpl.render(plugin_shortname=self.plugin.get_shortname(), plugin_version=self.plugin.get_version(),
                           plugin_info=self.plugin.get_info(), p=self.plugin)
</code></pre>
<p>Der Ausdruck <code>@cherrypy.exposed</code> legt fest, dass die Methode über http-Requests aufrufbar (also über den Browser zugreifbar) ist und diese beantworten kann. Methoden bei denen dies nicht der Fall sein soll, brauchen diese Angabe nicht.<br />
In <code>self.tplenv.get_template</code> wird das <code>index.html</code> Template geladen, in <code>tmpl.render</code> werden Variablen übergeben und das Template als HTML gerendert.</p>
<p><strong>Tipp:</strong> Für ein minimales Plugin Web-Interface reicht es im Übrigen, nur die notwendigen Parameter der <code>tmpl.render</code> Methode zu übergeben und eine minimale <code>index.html</code> zu haben.</p>
<h1>Das Plugin gestalten</h1>
<p>Wie im nachfolgenden Bild am Beispiel des Web-Interfaces des Database Plugins zu sehen, werden statische Dateien wie etwa Bilder im Ordner <code>static</code> direkt unter dem Pluginordner abgelegt. Die eigentlichen Templates für das Web-Interface des Plugins liegen unter <code>templates</code>.</p>
<p><img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/06/plugin_webif.jpg" alt="" width="237" height="330" class="alignnone size-full wp-image-1758" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/06/plugin_webif.jpg 237w, https://www.smarthomeng.de/wp-content/uploads/2018/06/plugin_webif-215x300.jpg 215w" sizes="(max-width: 237px) 100vw, 237px" /></p>
<p>Einstiegs-Template ist die Datei <code>index.html</code> im <code>templates</code> Ordner, die oben in der Methode <code>index</code> definiert wurde. Das Default Template zu dieser Seite ist wie folgt aufgebaut:</p>
<ol>
<li>Mittels <code>{% extends "base_plugin.html" %}</code> wird von einer globalen Vorlage für Plugin-Web-Interfaces abgeleitet. Diese ist unter <code>/smarthome/modules/http/webif/gtemplates/base_plugin.html</code> zu finden. Über dieses Basis-Template werden bereits Bootstrap, JQuery, Font Awesome und CodeMirror eingebunden. Die Verwendung des Templates stellt sicher, dass Web-Interfaces für Plugins ein homogenes Look and Feel haben.</li>
<li>Da wir SmartHomeNG-weit auf Bootstrap 4 setzen, bitten wir, die Stilmittel und CSS Klassen von Bootstrap zu verwenden. Details siehe <a href="http://getbootstrap.com/" target="_blank" rel="noopener">http://getbootstrap.com/</a></li>
<li>Es gibt eine Reihe an Bereichen, die man nun in seiner <code>index.html</code> individuell ausgestalten kann. Diese sog. Blöcke sind <code>headtable</code>, <code>buttons</code> und <code>bodytab1</code> bis <code>bodytab4</code></li>
<li>Ein Block kann im Template wie folgt befüllt werden:
<pre class="language-django"><code>{% block headtable %}
...
{% endblock headtable %}</code></pre>
</li>
<li>Im Fall mehrerer <code>bodytab</code>'s, können auch noch <code>tab1title</code> bis <code>tab4title</code> vergeben werden. Wird dies nicht gemacht, werden Default Titel erzeugt.</li>
</ol>
<p>Nachfolgend sind die Blöcke am Beispiel des Web-Interfaces des AVM Plugins nochmals dargestellt:<br />
<img loading="lazy" decoding="async" src="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_blocks-1.jpg" alt="" width="1294" height="597" class="alignnone size-full wp-image-1813" srcset="https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_blocks-1.jpg 1294w, https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_blocks-1-300x138.jpg 300w, https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_blocks-1-768x354.jpg 768w, https://www.smarthomeng.de/wp-content/uploads/2018/06/webif_blocks-1-1024x472.jpg 1024w" sizes="(max-width: 1294px) 100vw, 1294px" /></p>
<h1>Tipps und Tricks</h1>
<h2>Eigene CSS und JS Dateien einbinden</h2>
<p>Will man eigene JavaScript oder CSS Files einbinden, so kann man die Blöcke des <code>base_plugin</code> einfach erweitern. Ein Beispiel wäre wie folgt:</p>
<pre class="language-django"><code>{%- block scripts %}
{{ super() }}
<span>&lt;script </span><span>src=</span><span>"/gstatic/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js"</span><span>&gt;&lt;/script&gt;
</span><span>&lt;script </span><span>src=</span><span>"/gstatic/bootstrap-datepicker/dist/locales/bootstrap-datepicker.de.min.js"</span><span>&gt;&lt;/script&gt;
</span><span>&lt;script </span><span>src=</span><span>"/gstatic/bootstrap-datepicker/dist/locales/bootstrap-datepicker.fr.min.js"</span><span>&gt;&lt;/script&gt;
</span>{%- endblock scripts %}

{%- block styles %}
{{ super() }}
<span>&lt;link </span><span>rel=</span><span>"stylesheet" </span><span>href=</span><span>"/gstatic/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css" </span><span>type=</span><span>"text/css"</span><span>/&gt;
</span>{%- endblock styles %}
</code></pre>
<p>Über <code>super()</code> wird der Block eines der übergeordneten Templates, in diesem Fall in der <code>../modules/http/webif/gtemplates/base.html</code>, die der <code>plugin_base.html</code> übergeordnet ist, aufgerufen. Die nach <code>super()</code> folgenden Teile, werden dem Blockinhalt angehängt. Würde man <code>super()</code> weglassen, würde der Block komplett überschrieben.</p>
<h2>Unterschiedliche Seiten / Aktionen über Seitenaufrufe auslösen.</h2>
<p>Will man nicht nur eine Seite für das Plugin haben, so kann man entweder analog zur Methode <code>def index(self)</code> in der <code>__init__.py</code> weitere Methoden (und damit Seiten) definieren. In jeder dieser Methoden muss via <code>get_template</code> ein jeweils anderes Templates aus dem <code>templates</code> Ordner des Plugins geladen werden.</p>
<p>Eine Alternative zu diesem Vorgehen ist, nur die <code>index</code> Methode zu verwenden und die Auswahl des Templates in der <code>def index(self)</code> via Kommando-Parameter zu steuern. Neue Parameter kann man einfach in der Methode ergänzen, also bspw. <code>def index(self, <i>cmd=None</i>)</code>. Die Vorbelegung mit <code>None</code> ist dafür, dass der Parameter auch weggelassen werden kann.</p>
<p>Über <code>cmd</code> kann man nun im Python Code Aktionen auslösen oder abhängig vom Wert in <code>cmd</code> via <code>tmpl = self.tplenv.get_template('...')</code> andere Templates auswählen. <code>cmd</code> muss in einem Link / Formular als GET-Parameter übergeben werden, als bspw. mit <code>?cmd=...</code> an eine URL angehängt werden.</p>
<p>Ein Beispiel dazu kann im Web-Interface des Database-Plugin gefunden werden.</p>
<p>Wurde eine Aktion erfolgreich ausgeführt, empfiehlt es sich, die Info in <code>tmpl.render</code> an das Template zu übergeben. Über Bootstrap kann man nun schöne Status-Meldungen anzeigen:</p>
<pre class="language-django"><code>{% if delete_triggered %}
&lt;div class="mb-2 alert alert-success alert-dismissible fade show" role="alert"&gt;
    &lt;strong&gt;Löschauftrag für die Einträge von Item ID {{ item_id }} in der Tabelle "log" wurde erfolgreich initiiert!&lt;/strong&gt;&lt;br/&gt;
    Das Löschen kann noch kurze Zeit dauern, da die Ausführung des Delete Queries erst nach Abschluß der bestehenden Transaktionen erfolgen kann.
    &lt;button type="button" class="close" data-dismiss="alert" aria-label="Close"&gt;
        &lt;span aria-hidden="true"&gt;&amp;times;&lt;/span&gt;
    &lt;/button&gt;
&lt;/div&gt;
{% endif %}
</code></pre>
<h2>Internationalisierung</h2>
<p>Das Übersetzungssystem von SmartHomeNG kann auch für Plugins genutzt werden. Dazu muss direkt im Pluginverzeichnis eine Datei <code>locale.yaml</code> angelegt werden. In dieser werden Übersetzungen wie folgt definiert:</p>
<pre class="language-yaml"><code>
plugin_translations:
    # Translations for the plugin specially for the web interface
    'Aktionen':        {'de': 'Aktionen', 'en': 'Actions'}
    'Verbunden':       {'de': 'Verbunden', 'en': 'Connected'}
    'Ja':              {'de': 'Ja', 'en': 'Yes'}
    'Nein':            {'de': 'Nein', 'en': 'No'}
</code></pre>
<p>Im Template kann nun über die Syntax <code>{{ _('<em>&lt;Schlüssel aus der language.yaml&gt;</em>') }}</code> darauf zugegriffen werden:</p>
<pre class="language-django"><code>{% if check %}{{ _('Ja') }}{% else %}{{ _('Nein') }}{% endif %}</code></pre>
<p><span style="color: #ff0000;"><strong>Wir bitten darum, Web-Interfaces für Plugins immer mindestens auf Englisch und Deutsch zu erstellen!</strong></span></p>
<p><em>(Die in diesem Artikel verwendeten Screenshots wurden selber erstellt. Das Titelbild ist unter der Creative Commons Zero (CC0) Lizenz veröffentlicht und wurde von www.pexels.com bezogen.)</em></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.smarthomeng.de/jetzt-wird-es-bunt-wie-man-ein-plugin-webinterface-schreibt-in-kuerze-ab-smarthomeng-1-5/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
