Dialoge als Templates in Fhem per Ajax laden

Vorwort

Als Webentwickler war ich es gewöhnt, Dialoge und Inhalte einer Webseite dynamisch via Ajax nachzuladen. Unter Fhem fand ich keine einfache Möglichkeit. Daher hier ein Lösungsansatz. Er ist bewusst flach gehalten und verzichtet auf externe Bibliotheken wir jquery, prototype usw.

Perl | HTML - Templates

Modul für Perl installieren

Mein HTML ist meist recht komplex. Daher war es sinnvoll HTML und Perl zu trennen. Perl bietet selbst eine nette Templates-Engine.

Bei mir läuft das ganze unter Linux daher auch nur die Anleitung dafür. Das Perl-Modul HTML::Template lädt man wie folgt nach...

  1. sudo -s
  2. cpan
  3. cpan>install HTML::Template

unter /opt/fhem/www/ habe ich nun einen Ordner tpl angelegt, wo ich meine Templates ablegen möchte. Achtung, ich lege alle Dateien in UTF8 ab. Gängige Editoren wie Notepad++ unterstützen diesen Zeichensatz.

Nutzung eigener Templates

Die Datei /opt/fhem/FHEM/99_Utils.pm eignet sich dafür um eigenen Perl-Funktionen abzulegen.

##############################################
# $Id: ajax_dialoge.html,v 1.7 2015/10/26 15:27:10 pi Exp $
package main;

use strict;
use warnings;
use POSIX;
use Time::Local;
use HTML::Template;

sub
DialogInit
{
   my $tpl = HTML::Template->new( filename => '/opt/fhem/www/tpl/dialog_init.tpl' );
   return  $tpl->output;
}

sub
MeinNeuerAjaxDialog
{
   my $tpl = HTML::Template->new( filename => '/opt/fhem/www/tpl/mein_dialog_template.tpl' );
   return  $tpl->output;
}

In der Perl Funktion DialogInit() sollen alle notwendigen Javascript, CSS, HTML aus der Datei /opt/fhem/www/tpl/dialog_init.tpl gerendert werden, damit der Dialog angezeigt werden kann.

Die Perl Funktion MeinNeuerAjaxDialog() soll den eigentlichen Dialog aus Datei /opt/fhem/www/tpl/mein_dialog_template.tpl rendern

Inhalte der Templates

Vorbereitung: Javascript zum Ajax-Laden, CSS-Styles und HTML

Die Datei dialog_init.tpl enthält alle wichtigen Javascript, CSS und HTML - Kram um den Dialog anzeigen zu können. Man könnte natürlich alles in separate Dateien auslagern. Der Code soll nur veranschaulichen wie es funktioniert.

Das div-Element mit der Id idCommonDialog soll unser Container sein, in der später der Dialog geladen werden soll. Der spätere Dialog soll einige Ebenen höher als der Floorplan liegen, daher das CSS-Style mit dem z-index:2000

cDialog.show(url) lädt nun den Inhalt der angegebenen url in den genannten Container und führt gefundenes Javascript aus. Der Container wird zum Schluß nur noch sichtbar gemacht. Natürlich könnte man hier noch Funktionen zum Zentrieren oder Verschieben des Dialogs implementieren. Doch für den Fall soll es erst ein mal reichen.

<!--  Inhalt von /opt/fhem/www/tpl/dialog_init.tpl -->

<div id="idCommonDialog" style="display:none"></div>

<style type="text/css">
div.dialog
{
   z-index:2000;
}
</style>


<script type="text/javascript">

cDialog =
{
   show : function(url)
   {
		// klappt besser mit synchronen Requests, da polling sonst dazwischen funkt
		asynchronous = false;

		xhr = new XMLHttpRequest();
		xhr.open("GET", url, asynchronous );
		xhr.setRequestHeader('Content-Type', 'application/html; charset=UTF-8');

		//xhr.setRequestHeader('Access-Control-Allow-Credentials', 'true');
		//xhr.setRequestHeader('Access-Control-Origin', '*');

		if (asynchronous ) xhr.timeout =500;



      xhr.onreadystatechange = function ()
      {
         if (xhr.readyState == 4 && xhr.status == 200)
         {
            idCommonDialog = document.getElementById('idCommonDialog');

            txt = xhr.responseText;
            idCommonDialog.innerHTML = txt;
            idCommonDialog.style.display = 'block';
            var scripts = [], sc_re = /<script[^>]*>((?:.|[\n\r])*?)<\/script>/ig, sc; while ((sc = sc_re.exec(txt)) !== null){ scripts.push(sc[1]); } txt = txt.replace(sc_re, "");
            for (var i = 0; i < scripts.length; i++){eval(scripts[i]); }

            return -1;
         }
         else
         {
          if (xhr.status!=200)
            console.log("fehler state " + xhr.readyState +" status "+xhr.status);
         }
      }
      xhr.send();
      return -1;
   },

   hide: function()
   {
      idCommonDialog = document.getElementById('idCommonDialog');
      idCommonDialog.style.display = 'none';
      idCommonDialog.innerHTML = '';
   }


}

 </script>

Basis-HTML eines Dialogs

<!--  Inhalt von /opt/fhem/www/tpl/mein_dialog_template.tpl -->

<div style="display:block;position:absolute;left:346px;top:42px; width:366px; height:410px;" class="dialog">
   <b>Mein Dialog</b><br>
   Hier kann man reinpacken was man möchte.

   Mit dem Javascript-Befehl FW_cmd könnte man auch von hier aus Befehle zum
   Fehm-Server schicken.
</div>

Fhem | Floorplan

Einbindung in einen Floorplan

Wenn ich einen Dialog in einen Flurplan anzeigen lassen möchte, muss ich sicher stellen, dass mein Javascript, HTML und CSS auch im Flurplan landet. Das geht wie folgt (im Beispiel Floorplan All)

# Initialisieren der Dialog-Funktion im  Floorplan All
define DialogInit weblink htmlCode {DialogInit()}
attr DialogInit fp_All 1,1,0,

Dialog im Floorplan öffnen

Hier noch eine kleine Schaltfläche mit der man nun den Dialog öffnen kann

# Knopf zum Dialog im  Floorplan All
define KnopfZumDialog weblink htmlCode <div style="height:64px;;width:64px;;background-color:red;z-index:150" onclick="cDialog.show('fhem?cmd={MeinNeuerAjaxDialog()}&XHR=1')"><br></div>
attr KnopfZumDialog fp_All 463,466,0,

Hier ein Screenshot wie es bei mir in der Haussteuerung dann aussieht

Kompatibilität

Artikel von Karsten Grüttner, letzte Änderung 6.10.2015