Persoenliche Mediazentrale
ulrich
2021-04-16 155a44f5e924df42156e97cb816d7a89c285f901
commit | author | age
b379f5 1 function Mediazentrale() {
cfa858 2   var self = this;
U 3   var appMenu;
4   var cache; // mustache templates
86bbf7 5   var ortPfad;
U 6   var mediaPfad;
e60cff 7   var katUrl;
U 8   var selTitel;
78d707 9   var katName;
f45e20 10
U 11   this.init = function () {
12     self.mediaPfad = '/';
13     self.ortPfad = '/';
14     self.cache = new Array();
15     self.appMenu = new AppMenu();
16     self.appMenu.init(
17             "data/menu/",
18             "hauptmenue.json",
78d707 19             "data/tpl/app-menu.txt",
f45e20 20             ".west",
0866ae 21             "6em");
f45e20 22
U 23     document.querySelector('.hamburger').addEventListener('click', function (e) {
24       self.menue_umschalten();
25     });
26     
27     self.addEvtListener('#mi-katalog', 'click', self.media_liste);
28     self.addEvtListener('#mi-orte', 'click', self.ablageort_liste);
29     self.addEvtListener('#mi-prefs', 'click', self.prefs_liste);
30     self.addEvtListener('#mi-player', 'click', self.abspieler_liste);
8d7d35 31     self.addEvtListener('#mi-listen', 'click', self.abspielliste_liste);
U 32     self.addEvtListener('#mi-list', 'click', self.titel_liste);
d027b5 33     self.addEvtListener('#mi-live', 'click', self.livestream_liste);
3929b0 34     self.addEvtListener('#mi-devices', 'click', self.geraet_liste);
f45e20 35     
U 36     self.fusszeile_umschalten();
37     self.seitenleiste_umschalten();
38     self.dialog_unten_zeigen();
39   };
40   
8d7d35 41   /* ---------------- Entitaets-Listen ----------------- */
d027b5 42   
U 43   this.livestream_selection = function() {
44     document.querySelector('.breadcrumb-behaelter').textContent = '';
45     document.querySelector('.bereich-name').textContent = 'Livestream-Auswahl'; 
46     self.http_get('../api/store/Livestream/liste/', function(responseText) {
47       self.vorlage_laden_und_fuellen("data/tpl/livestream_liste.txt", JSON.parse(responseText), function (html) {
48         document.querySelector(".zentraler-inhalt").innerHTML = html;
49         self.addEvtListener('.entity-eintrag', 'click', function (event) {
50           var t = event.target;
51           self.removeClassMulti('selected');
52           t.classList.add('selected');          
53         });
54       });
55     });
56   };
8d7d35 57
86bbf7 58   // auf der obersten Ebene werden die Kataloge angezeigt,
U 59   // darunter der Inhalt des aktuellen Pfades
60   this.media_liste = function() {
78d707 61     self.reset_top_buttons(); 
095119 62     //console.log("ortPfad: " + self.ortPfad + ", mediaPfad: " + self.mediaPfad);
78d707 63     document.querySelector('.bereich-name').textContent = '';    
86bbf7 64     if(self.ortPfad === '/') {
78d707 65       var bb = document.querySelector('.breadcrumb-behaelter');
U 66       bb.textContent = "Kataloge";
86bbf7 67       // Kataloge listen
f45e20 68       self.http_get('../api/store/Ablageort/liste/', function (responseText) {
8d7d35 69         //document.querySelector('#top-up-btn').removeEventListener('click', self.media_liste_herauf);
78d707 70         self.vorlage_laden_und_fuellen("data/tpl/katalog_root_liste.txt", JSON.parse(responseText), function (html) {
86bbf7 71           document.querySelector(".zentraler-inhalt").innerHTML = html;
U 72           self.addEvtListener('.entity-eintrag', 'click', function (event) {
73             var t = event.target;
78d707 74             self.katName = t.textContent;
d027b5 75             if(self.katName !== "Livestreams") {
U 76               self.http_get('../api/store/Ablageort/' + t.textContent, function(responseText) {
77                 var ablageort = JSON.parse(responseText);
78                 self.ortPfad = ablageort.url;
79                 self.media_liste();
80               });
81             } else {
82               self.livestream_selection();
83             }
86bbf7 84           });
U 85         });
86       });
87     } else {
78d707 88       var bb = document.querySelector('.breadcrumb-behaelter');
U 89       var brPfad = self.katName + self.mediaPfad;
90       var breadcrumbs = brPfad.split('/');
91       var brLinks = "";
92       var brLinkPfad = "";
93       for(var index = 0; index < breadcrumbs.length; index++) {
94         // <a class="breadcrumb-link" href="#">breadcrumbs[index]</a>
95         if(index === 0) {
96           brLinkPfad = '/';
97         } else {
98           brLinkPfad = brLinkPfad + '/' + breadcrumbs[index];
99         }
100         brLinks = brLinks + "<a brlink='" + brLinkPfad + "' class='breadcrumb-link' href='#'>" + breadcrumbs[index] + "</a>";
101         //console.log('   breadcrumbs[' + index + ']: ' + breadcrumbs[index]);
102       }
103       bb.innerHTML = brLinks;
104       self.addEvtListener('.breadcrumb-link', 'click', function(event) {
095119 105         //console.log(event.target.attributes.brlink.nodeValue);
78d707 106         var neuerPfad = event.target.attributes.brlink.nodeValue;
U 107         self.mediaPfad = neuerPfad;
108         self.media_liste();
109       });
3271f1 110       var url = '..' + self.ortPfad + self.mediaPfad;
U 111       if(!url.endsWith('/')) {
112         url = url + '/';
113       }
114       self.http_get(url, function(responseText) {
78d707 115         self.vorlage_laden_und_fuellen("data/tpl/katalog_inhalt_liste.txt", JSON.parse(responseText), function (html) {
86bbf7 116           document.querySelector(".zentraler-inhalt").innerHTML = html;
U 117           self.addEvtListener('.entity-eintrag', 'click', function (event) {
118             var t = event.target;
3271f1 119             var tx = t.textContent;
86bbf7 120             if(t.classList.contains("entity-typ-folder")) {
3271f1 121               if(self.mediaPfad.endsWith('/')) {
U 122                 self.mediaPfad = self.mediaPfad + tx;                
123               } else {
124                 self.mediaPfad = self.mediaPfad + '/' + tx;
125               }
86bbf7 126               self.media_liste();
U 127             } else {
37eadf 128               if(t.classList.contains('selected')) {
78d707 129                 t.classList.add('added-to-playlist');
095119 130                 self.titelDazu();
37eadf 131               } else {
U 132                 self.removeClassMulti('selected');
133                 t.classList.add('selected');
134               }
e60cff 135               //self.selTitel = new Titel(t.textContent, self.ortPfad);       
86bbf7 136             }
U 137           });
8d7d35 138           self.addEvtListener('#top-up-btn', 'click', function(event) {
86bbf7 139             if(self.mediaPfad === '/') {
U 140               self.ortPfad = '/';              
141             } else {
142               var pos = self.mediaPfad.lastIndexOf('/');
3271f1 143               var parent;
U 144               if(pos > 1) {
145                 parent = self.mediaPfad.substring(0, pos);
146               } else {
147                 parent = '/';
148               }
86bbf7 149               self.mediaPfad = parent;
U 150             }
151             self.media_liste();
8d7d35 152           });
86bbf7 153         });
U 154       });
155     }
e60cff 156   };
245ac1 157   
3e5a56 158   this.geraet_schalt_liste = function() {
U 159     self.entitaet_liste('Geräte schalten','../api/store/Geraet/liste/', 
160       "data/tpl/geraet_schalt_liste.txt", '../api/store/Geraet/', 
161       "self.form_geraet_status", function(responseText) {
162         var geraet = JSON.parse(responseText);
163         self.geraet_status_form(geraet);
164       });
165   };
166   
3929b0 167   this.geraet_liste = function() {
U 168     self.entitaet_liste('Geräte','../api/store/Geraet/liste/', 
169       "data/tpl/geraet_liste.txt", '../api/store/Geraet/', 
170       "self.geraet_form", function(responseText) {
171         var geraet = JSON.parse(responseText);
172         self.geraet_form(geraet);
173       });
174   };
175
a43e1a 176   this.ablageort_liste = function() {
78d707 177     self.entitaet_liste('Kataloge','../api/store/Ablageort/liste/', 
U 178       "data/tpl/ablageort_liste.txt", '../api/store/Ablageort/', 
e44ed0 179       "self.ablageort_form", function(responseText) {
U 180         var ablageort = JSON.parse(responseText);
181         self.ablageort_form(ablageort);
182       });
cf6509 183   };
U 184
185   this.prefs_liste = function() {
50e53e 186     self.entitaet_liste('Einstellungen','../api/store/Einstellung/liste/', 
78d707 187       "data/tpl/einstellung_liste.txt", '../api/store/Einstellung/', 
e44ed0 188       "self.prefs_form", function(responseText) {
U 189         var einstellung = JSON.parse(responseText);
190         self.prefs_form(einstellung);
191       });
cf6509 192   };
U 193
3d4bca 194   this.abspieler_liste = function() {
50e53e 195     self.entitaet_liste('Abspieler','../api/store/Abspieler/liste/', 
78d707 196       "data/tpl/abspieler_liste.txt", '../api/store/Abspieler/', 
e44ed0 197       "self.abspieler_form", function(responseText) {
U 198         var abspieler = JSON.parse(responseText);
199         self.abspieler_form(abspieler);
200       });
8d7d35 201   };
U 202   
d027b5 203   this.livestream_liste = function() {
U 204     self.entitaet_liste('Livestream','../api/store/Livestream/liste/', 
205       "data/tpl/livestream_liste.txt", '../api/store/Livestream/', 
206       "self.livestream_form", function(responseText) {
207         var livestream = JSON.parse(responseText);
208         self.livestream_form(livestream);
209       });
210   };
211   
8d7d35 212   this.abspielliste_liste = function() {
50e53e 213     self.entitaet_liste('Abspielliste','../api/store/Abspielliste/liste/', 
78d707 214       "data/tpl/abspielliste_liste.txt", '../api/store/Abspielliste/', 
e44ed0 215       "self.abspielliste_form", function(responseText) {
095119 216         //console.log("responseTest: '" + responseText + "'");
e44ed0 217         var abspielliste = JSON.parse(responseText);
U 218         self.abspielliste_form(abspielliste);
219       });
8d7d35 220   };
U 221   
c7030d 222   /* -------------------- Entitaets-Formulare ------------------ */  
8d7d35 223   
U 224   this.abspielliste_form = function(al) {
50e53e 225     self.entitaet_form('Abspielliste', al, al.name,
78d707 226       "data/tpl/form_abspielliste.txt", '../api/store/Abspielliste/',
9865bd 227       '#abspielliste-name', function(event) {
095119 228           if(event !== undefined) {
U 229             event.preventDefault();
230           }
c7030d 231           self.abspielliste_auswahl_fuellen();
U 232           self.abspielliste_liste();
233     });
3d4bca 234   };
71def1 235   
3d4bca 236   this.abspieler_form = function(pl) {
50e53e 237     self.entitaet_form('Abspieler', pl, pl.key,
78d707 238       "data/tpl/form_abspieler.txt", '../api/store/Abspieler/',
c7030d 239       '#abspieler-name', function() { 
U 240           self.abspieler_auswahl_fuellen();
241           self.abspieler_liste();
d027b5 242     });
U 243   };
244
245   this.livestream_form = function(ls) {
246     self.entitaet_form('Livestream', ls, ls.name,
247       "data/tpl/form_livestream.txt", '../api/store/Livestream/',
248       '#livestream-name', function() { 
249           self.livestream_liste();
3929b0 250     });
U 251   };
252
253   this.geraet_form = function(ge) {
254     self.entitaet_form('Gerät', ge, ge.name,
255       "data/tpl/form_geraet.txt", '../api/store/Geraet/',
256       '#geraet-name', function() { 
257           self.geraet_liste();
3e5a56 258     });
U 259   };
260
261   this.geraet_status_form = function(ge) {
262     self.entitaet_form('Gerät', ge, ge.name,
263       "data/tpl/form_geraet_status.txt", '../api/store/Geraet/',
264       '#geraet-name', function() { 
265           self.geraet_schalt_liste();
c7030d 266     });
71def1 267   };
U 268
cf6509 269   this.prefs_form = function(k) {
78d707 270     self.entitaet_form('Einstellung', k, k.key,
U 271       "data/tpl/form_einstellung.txt", '../api/store/Einstellung/',
c7030d 272       '#einstellung-key', function() { 
U 273           self.prefs_liste();
274     });
a43e1a 275   };
cfa858 276
90f5d4 277   /* 
U 278    * Ablageort-Formular anzeigen
279    * 
280    * {"name":"Katalog 2","ort":"/home/ulrich/Videos","url":"/media/kat2"}: 
281    * 
282    * @param {type} ablageort  der Ablageort, der bearbeitet werden soll, leer fuer neuen Ort
283    * @returns {undefined} kein Rueckgabewert
284    */
285   this.ablageort_form = function(ort) {
78d707 286     self.entitaet_form('Katalog', ort, ort.name,
U 287       "data/tpl/form_ablageort.txt", '../api/store/Ablageort/',
c7030d 288       '#ablageort-name', function() { 
U 289         self.ablageort_liste();
290     });
b379f5 291   };
48f8f9 292   
748b6f 293   /* ------------------------------- UI-Dynamik ----------------------- */
48f8f9 294   
748b6f 295   self.reset_top_buttons = function() {
78d707 296     self.vorlage_laden_und_fuellen("data/tpl/top_btns.txt", '', function (html) {
748b6f 297       document.querySelector(".top-btns").innerHTML = html;
U 298     });
299   };
300   
301   this.abspieler_auswahl_fuellen = function() {
b56bb3 302     self.http_get('../api/store/Abspieler/liste/', function (responseText) {
78d707 303       self.vorlage_laden_und_fuellen("data/tpl/abs_sel.txt", JSON.parse(responseText), function (html) {
748b6f 304         document.querySelector(".abs-sel").innerHTML = html;
U 305       });    
306     });
307   };
308
309   this.abspielliste_auswahl_fuellen = function() {
310     self.http_get('../api/store/Abspielliste/', function (responseText) {
78d707 311       self.vorlage_laden_und_fuellen("data/tpl/pl_sel.txt", JSON.parse(responseText), function (html) {
748b6f 312         document.querySelector(".pl-sel").innerHTML = html;
b56bb3 313         self.addEvtListener('#playlist', 'change', function() {
U 314           self.titel_liste();
315         });
748b6f 316       });    
U 317     });
318   };
319   
320   /* Unterer Einblendbereich */
321   
322   this.dialog_unten_zeigen = function() {
78d707 323     self.vorlage_laden_und_fuellen("data/tpl/ctrl.txt", "", function (html) {
748b6f 324       var dlg = document.querySelector(".dialog-unten");
U 325       dlg.style.height = '4.5em';
326       dlg.innerHTML = html;
327       self.abspieler_auswahl_fuellen();
328       self.abspielliste_auswahl_fuellen();
095119 329       self.addEvtListener('#dazu-btn', 'click', self.titelDazu);
b56bb3 330       self.addEvtListener('#play-btn', 'click', self.play);
9e14ef 331       self.addEvtListener('#stop-btn', 'click', function() {
U 332         self.kommando('stop');
333       });
334       self.addEvtListener('#pause-btn', 'click', function() {
335         self.kommando('pause');
336       });
337       self.addEvtListener('#weiter-btn', 'click', function() {
338         self.kommando('weiter');
339       });
340       
341 /*
342     <button class="ctrl-btn ctrl-item" id="hier-btn" title="hier spielen"><i class="icon-tablet"></i></button>
343  */      
344       
095119 345       self.addEvtListener('#weg-btn', 'click', self.titelWeg);
U 346       self.addEvtListener('#leeren-btn', 'click', self.alleTitelEntfernen);
748b6f 347       self.media_liste();
U 348     });
349   };
350   
351   /* Titel einer Abspielliste */
352   
353   this.titel_liste = function() {
354     self.reset_top_buttons();
355     var plname = document.querySelector('#playlist').value;
78d707 356     document.querySelector('.bereich-name').textContent = 'Abspielliste ' + plname;
U 357     var bb = document.querySelector('.breadcrumb-behaelter');
358     bb.textContent = "";
748b6f 359     self.http_get('../api/alist/' + plname, function (responseText) {
78d707 360       self.vorlage_laden_und_fuellen("data/tpl/titel_liste.txt", JSON.parse(responseText), function (html) {
748b6f 361         document.querySelector(".zentraler-inhalt").innerHTML = html;
U 362         self.addEvtListener('.entity-eintrag', 'click', function (event) {
363           var t = event.target;
364           self.removeClassMulti('selected');
365           t.classList.add('selected');
366         });
367       });
368     });
369   };  
370   
b56bb3 371   /* ------------- Media-Steuerung ------------------------- */
U 372   
373   this.play = function() {
658c14 374     var bereichName = document.querySelector('.bereich-name').textContent;
U 375     if(bereichName === '') {
4f2589 376       var titel = self.titelErmitteln(document.querySelector(".selected"));
658c14 377       //var titelName = elem.textContent;
4f2589 378       /*
658c14 379       var titelName = elem.attributes.dateiName.nodeValue;
U 380       var album = elem.attributes.album.nodeValue;
381       var interpret = elem.attributes.interpret.nodeValue;
382       var anzName = elem.attributes.titelAnzName.nodeValue;
383       var titel;
384       if(self.mediaPfad.endsWith('/')) {
385         titel = new Titel(titelName, self.mediaPfad, self.ortPfad, interpret, anzName, album);
386       } else {
387         titel = new Titel(titelName, self.mediaPfad + '/', self.ortPfad, interpret, anzName, album);
388       }
4f2589 389       */
1c5fa4 390       var playername = document.querySelector('#abspieler').value;
U 391       /*      
392       HTTP POST /mz/api/strg/abspieler/play mit Titel im Body
658c14 393       
1c5fa4 394       url: /media/test/M/Muenchener-Freiheit/01-Ohne-Dich-schlaf-ich-heut-Nacht-nicht-ein.mp3      
658c14 395       */
U 396       console.log('plname: ' + playername + ' url: ' + titel.katalogUrl + titel.pfad + titel.name);
d027b5 397       self.http_post('../api/strg/' + playername + '/titel', JSON.stringify(titel), function(responseText) {
658c14 398         self.meldung_mit_timeout(responseText, 1500);
d027b5 399       });   
U 400     } else if(bereichName === 'Livestream-Auswahl') {
401       var streamName = document.querySelector(".selected").textContent;
402       var playername = document.querySelector('#abspieler').value;
403       var stream = new Livestream(streamName, '-');
404       self.http_post('../api/strg/' + playername + '/stream', JSON.stringify(stream), function(responseText) {
405         self.meldung_mit_timeout(responseText, 1500);
406       });   
658c14 407     } else {
U 408       var abs = document.querySelector('#abspieler').value;
409       var lst = document.querySelector('#playlist').value;
410       console.log(
411         "play playlist.value: " + document.querySelector('#playlist').value + 
412         ", abspieler.value: " + document.querySelector('#abspieler').value);
413       self.http_get('../api/strg/' + abs + '/play/liste/' + lst, function(responseText) {
414         self.meldung_mit_timeout(responseText, 1500);
415       });
416     }
095119 417   };
U 418   
9e14ef 419   this.kommando = function(kommando) {
U 420     var abs = document.querySelector('#abspieler').value;
421     self.http_get('../api/strg/' + abs + '/' + kommando, function(responseText) {
422       self.meldung_mit_timeout(responseText, 1500);
423     });
424   };
425     
095119 426   /* ------------- Verwaltungsfunktionen Abspielliste -------------------- */
U 427   
428   self.alleTitelEntfernen = function() {
429     var plname = document.querySelector('#playlist').value;
430     self.http_delete('../api/alist/' + plname + '/alle', '', function(responseText) {
431       // DELETE    http://localhost:9090/mz/api/alist/liste1/0
432       //self.meldung_mit_timeout(responseText, 1500);
433       self.titel_liste();
434     });
435   };
436   
437   this.titelDazu = function() {
4f2589 438     var titel = self.titelErmitteln(document.querySelector(".selected"));
095119 439     //var titelName = elem.textContent;
4f2589 440     /*
095119 441     var titelName = elem.attributes.dateiName.nodeValue;
U 442     var album = elem.attributes.album.nodeValue;
443     var interpret = elem.attributes.interpret.nodeValue;
444     var anzName = elem.attributes.titelAnzName.nodeValue;
445     var titel;
446     if(self.mediaPfad.endsWith('/')) {
447       titel = new Titel(titelName, self.mediaPfad, self.ortPfad, interpret, anzName, album);
448     } else {
449       titel = new Titel(titelName, self.mediaPfad + '/', self.ortPfad, interpret, anzName, album);
450     }
4f2589 451     */
095119 452     var plname = document.querySelector('#playlist').value;
U 453     self.http_put('../api/alist/' + plname, JSON.stringify(titel), function(responseText) {
454       //self.meldung_mit_timeout(responseText, 1500);
455     });
456   };  
457   
458   this.titelWeg = function() {
459     var elem = document.querySelector(".selected");
460     var parentElem = elem.parentNode;
461     //console.log("elem: " + elem.nodeName + ", parent: " + parentElem.nodeName + ", len: " + parentElem.childNodes.length);
462     var liElems = parentElem.getElementsByTagName(elem.nodeName); // nur die LI Elemente
463     //console.log("liElems.anz: " + liElems.length);
464     var gefunden = false;
465     for(var i = 0; i < liElems.length && !gefunden; i++) {
466       //console.log(liElems.item(i).textContent);
467       if(liElems.item(i).classList.contains("selected")) {
468         gefunden = true;
469         var index = i;
470         //console.log(elem.textContent + ' hat Index ' + i);
471       }
472     }
473     // /mz/api/alist/[pl-name]/[nr] 
474     var plname = document.querySelector('#playlist').value;
475     self.http_delete('../api/alist/' + plname + '/' + index,'', function(responseText) {
476       // DELETE    http://localhost:9090/mz/api/alist/liste1/0
477       //self.meldung_mit_timeout(responseText, 1500);
478       self.titel_liste();
479     });
480     
b56bb3 481   };
U 482   
748b6f 483   /* ------------- Helfer fuer Entitaets-Formulare ----------------------- */
14638b 484   
U 485   /*
486    * url: '../api/store/Ablageort/liste/'
78d707 487    * tpl: "data/tpl/ablageort_liste.txt"
14638b 488    * storeUrl: '../api/store/Ablageort/'
U 489    * formFunc: "self.ablageort_form"
490    * cb: etwas wie
491    *   function(responseText){
492    *     var ablageort = JSON.parse(responseText);
493    *     self.ablageort_form(ablageort);
494    *   });
495    */
50e53e 496   this.entitaet_liste = function(bname, listUrl, tpl, storeUrl, formFunc, cb) {
14638b 497     self.reset_top_buttons();
50e53e 498     document.querySelector('.bereich-name').textContent = bname;
78d707 499     var bb = document.querySelector('.breadcrumb-behaelter');
U 500     bb.textContent = "";
14638b 501     self.http_get(listUrl, function (responseText) {
U 502       self.vorlage_laden_und_fuellen(tpl, JSON.parse(responseText), function (html) {
503         document.querySelector(".zentraler-inhalt").innerHTML = html;
504         self.addEvtListener('.entity-eintrag', 'click', function (event) {
505           var t = event.target;
506           self.http_get(storeUrl + t.textContent, cb);
507         });
508         //self.addEvtListener('#neu-btn', 'click', function (event) {
509         self.addEvtListener('#top-neu-btn', 'click', function(event) {
510           eval(formFunc + "(this)");
511         });        
512       });
513     });
514   };  
748b6f 515       
48f8f9 516   /*
c7030d 517    * dat: gefuelltes Datenobjekt bei Aenderung
U 518    * key: der alte schluesselbegriff bei Aenderung (z.B. al.name)
78d707 519    * tpl: "data/tpl/form_abspielliste.txt"
c7030d 520    * url: '../api/store/Abspielliste/'
U 521    * selector: '#abspielliste-name'
522    * cbOk: etwas wie
523    *    function() {
524    *       self.abspielliste_auswahl_fuellen();
525    *       self.abspielliste_liste();
526    *     });
527    * delSelector: '#abspielliste-name'
528    * cbDel: etwas wie
529    *    function() {
530    *       self.abspielliste_auswahl_fuellen();
531    *       self.abspielliste_liste();
532    *     });
533    */
534
50e53e 535   this.entitaet_form = function(bname, dat, key, tpl, url, selector, cb) {
U 536     document.querySelector('.bereich-name').textContent = bname;
c7030d 537     self.vorlage_laden_und_fuellen(tpl, dat, function (html) {
U 538       document.querySelector(".zentraler-inhalt").innerHTML = html;
539       const form = document.querySelector('form');      
540       form.addEventListener('submit', function(event) {
541         self.handle_submit(event, key, url, selector, cb);
542       });
543       self.addEvtListener('#cancel-btn', 'click', cb);
544       self.addEvtListener('#loeschen-btn', 'click', function(event) {
545         event.preventDefault();
546         self.handle_del_btn(selector, url, cb);
547       });
548     });
549   };
550   
551   /*
48f8f9 552    * existingKey: wenn die Entitaet existiert und geandert werden soll
U 553    *                 leer, wenn neue Entitaet 
554    */
555   this.handle_submit = function(event, existingKey, putUrl, keySelector, cb) {
556     event.preventDefault();
557     const data = new FormData(event.target);
558     const value = Object.fromEntries(data.entries());
559     var daten = JSON.stringify(value);
560     var formkey = document.querySelector(keySelector).value;
561     formkey = formkey.replace(' ', '').replace(/[\W]+/g, '');
562     if(typeof existingKey === "undefined" ||  existingKey.length < 1) {
563       // neu
564       self.http_put(putUrl + formkey, daten, function (responseText) {
565         if(typeof(cb) !== 'function') {
566           // ..
567         } else {
568           cb();
569         }
570       });
571     } else {
572       // aendern
573       self.http_put(putUrl + existingKey, daten, function (responseText) {
574         if(typeof(cb) !== 'function') {
575           // ..
576         } else {
577           cb();
578         }
579       });
580     }
581   };
582   
583   this.handle_del_btn = function(selectorKey, delUrl, cb) {
584     var pkey = document.querySelector(selectorKey).value;
585     var dlgdata = {"del-elem": pkey};
78d707 586     self.dialog_laden_und_zeigen('data/tpl/dlg-loeschen.txt', dlgdata, function() {
48f8f9 587       self.addEvtListener('#nein-btn', 'click', self.dialog_schliessen);
U 588       self.addEvtListener('#ja-btn', 'click', function(event) {
095119 589         //console.log("loeschen geklickt.");
48f8f9 590         self.http_delete(delUrl + pkey, '', function (responseText) {
U 591           self.dialog_schliessen();
592           if(typeof(cb) !== 'function') {
593             // ..
594           } else {
595             cb();
596           }
597         });
598       });
599     });    
600   };  
748b6f 601   
U 602   /* ------------------ sonstige Helfer ----------------------- */
603       
7c22a2 604   this.addEvtListener = function(selector, eventName, func) {
U 605     document.querySelectorAll(selector).forEach(elem => { elem.addEventListener(eventName, func); });
606   };
607   
608   this.removeClassMulti = function(selector) {
609     document.querySelectorAll('.' + selector).forEach(elem => { elem.classList.remove(selector); });
610   };
4f2589 611   
U 612   self.titelErmitteln = function(elem) {
613     var titelName = elem.attributes.dateiName.nodeValue;
614     var album = elem.attributes.album.nodeValue;
615     var interpret = elem.attributes.interpret.nodeValue;
616     var anzName = elem.attributes.titelAnzName.nodeValue;
617     var titel;
618     if(self.mediaPfad.endsWith('/')) {
619       titel = new Titel(titelName, self.mediaPfad, self.ortPfad, interpret, anzName, album);
620     } else {
621       titel = new Titel(titelName, self.mediaPfad + '/', self.ortPfad, interpret, anzName, album);
622     }
623     return titel;
624   };
7c22a2 625
f45e20 626   /* --------------------- asynchroner HTTP Client ----------------- */
faab2d 627   
f074f6 628   this.http_get = function (u, cb) {
b379f5 629     self.http_call('GET', u, null, cb);
U 630   };
f074f6 631
U 632   this.http_post = function (u, data, cb) {
b379f5 633     self.http_call('POST', u, data, cb);
U 634   };
635
90f5d4 636   this.http_put = function (u, data, cb) {
U 637     self.http_call('PUT', u, data, cb);
638   };
639   
5b7356 640   this.http_delete = function (u, data, cb) {
5f7e0b 641     // console.log("delete " + u);
5b7356 642     self.http_call('DELETE', u, data, cb);
U 643   };
644   
f074f6 645   this.http_call = function (method, u, data, scallback) {
b379f5 646     var xhr = new XMLHttpRequest();
U 647     var url = u;
f074f6 648     xhr.onreadystatechange = function () {
b379f5 649       if (this.readyState === 4 && this.status === 200) {
U 650         scallback(this.responseText);
651       }
652     };
653     xhr.open(method, url);
f074f6 654     if (method === 'GET') {
b379f5 655       xhr.send();
2597cd 656     } else if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
b379f5 657       xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
U 658       xhr.send(data);
659     }
660   };
8239d1 661   
f45e20 662   /* ------------------------ aus App-Vorlage -------------------  */
cfa858 663
f074f6 664   this.menue_umschalten = function () {
cfa858 665     var ham = document.querySelector(".hamburger");
U 666     ham.classList.toggle("is-active"); // hamburger-icon umschalten
667     self.appMenu.toggle(); // menue oeffnen/schliessen
668   };
669
f074f6 670   this.info_dialog_zeigen = function () {
78d707 671     self.dialog_laden_und_zeigen('data/tpl/dlg-info.txt', '');
cfa858 672     self.menue_umschalten();
U 673   };
674
f074f6 675   this.seitenleiste_umschalten = function () {
cfa858 676     var ostDiv = document.querySelector('.ost');
f074f6 677     if (ostDiv.classList.contains('ost-open')) {
cfa858 678       ostDiv.classList.remove('ost-open');
f074f6 679       ostDiv.style.flexBasis = '0em';
cfa858 680     } else {
f074f6 681       ostDiv.classList.add('ost-open');
U 682       ostDiv.style.flexBasis = '6em';
cfa858 683     }
U 684     self.menue_umschalten();
685   };
686
f074f6 687   this.fusszeile_umschalten = function () {
cfa858 688     var suedDiv = document.querySelector('.sued');
f074f6 689     if (suedDiv.classList.contains('sued-open')) {
cfa858 690       suedDiv.classList.remove('sued-open');
f074f6 691       suedDiv.style.height = '0';
cfa858 692     } else {
U 693       suedDiv.classList.add('sued-open');
f074f6 694       suedDiv.style.height = '1.5em';
cfa858 695     }
U 696     self.menue_umschalten();
697   };
698
f074f6 699   this.menu_message = function (msg) {
cfa858 700     self.meldung_mit_timeout(msg, 1500);
U 701     var suedDiv = document.querySelector('.sued');
f074f6 702     if (suedDiv.classList.contains('sued-open')) {
cfa858 703     } else {
U 704       suedDiv.classList.add('sued-open');
f074f6 705       suedDiv.style.height = '1.5em';
cfa858 706     }
U 707     self.menue_umschalten();
708   };
709
f074f6 710   this.message_1 = function () {
cfa858 711     self.menu_message('Eine Mitteilung.');
U 712   };
713
f074f6 714   this.message_2 = function () {
cfa858 715     self.menu_message('Was wir schon immer sagen wollten.');
U 716   };
717
f074f6 718   this.message_3 = function (text) {
cfa858 719     self.menu_message(text);
U 720   };
721
f074f6 722   this.meldung_mit_timeout = function (meldung, timeout) {
cfa858 723     var s = document.querySelector('.sued');
a43e1a 724     s.classList.add('sued-open');
U 725     s.style.height = '1.5em';
cfa858 726     s.textContent = meldung;
f074f6 727     setTimeout(function () {
cfa858 728       s.textContent = 'Bereit.';
f074f6 729       setTimeout(function () {
cfa858 730         var suedDiv = document.querySelector('.sued');
f074f6 731         if (suedDiv.classList.contains('sued-open')) {
U 732           suedDiv.classList.remove('sued-open');
733           suedDiv.style.height = '0';
cfa858 734         }
U 735       }, 500);
736     }, timeout);
737   };
8239d1 738   
f45e20 739   /* --------------------- Dialog-Funktionen ------------------------ */
cfa858 740
U 741   /*
f074f6 742    Einen Dialog aus Vorlagen erzeugen
U 743    
744    vurl - URL zur Dialogvorlage
745    msgTpl - URL mit einer Vorlage eines Mitteilungstextes (optional)
746    */
5b7356 747   this.dialog_laden_und_zeigen = function (vurl, msgTpl, cb) {
U 748     var vorlage = self.cache[vurl];
749     if(vorlage === undefined) {
750       self.http_get(vurl, function(antwort) {
751         self.cache[vurl] = antwort;
752         self.dialog_zeigen(vurl, msgTpl, cb);
f074f6 753       });
cfa858 754     } else {
5b7356 755       self.dialog_zeigen(vurl, msgTpl, cb);
cfa858 756     }
U 757   };
758
5b7356 759   this.dialog_zeigen = function (vurl, inhalt, cb) {
U 760     var dlg = document.querySelector(".dialog");
2597cd 761     self.html_erzeugen(vurl, inhalt, function (html) {
U 762       dlg.style.height = '7em';
763       dlg.innerHTML = html;
764       document.querySelector('.close-btn').addEventListener('click', self.dialog_schliessen);
765       if(typeof(cb) !== 'function') {
766         // ..
767       } else {
768         cb();
769       }
770     });
5b7356 771   };
2597cd 772   
f45e20 773   this.dialog_schliessen = function () {
cfa858 774     document.querySelector('.close-btn').removeEventListener('click', self.dialog_schliessen);
U 775     var dlg = document.querySelector('.dialog');
776     dlg.style.height = '0';
777     dlg.innerHTML = '';
778   };
779
f45e20 780   /* ---------------------   Vorlagen   ---------------------- */
cfa858 781
U 782   /*
f074f6 783    Das HTML erzeugen, das entsteht, wenn eine Vorlage mit Inhalt
U 784    gefüllt wird
785    
786    Das Füllen erfolgt asynchron, d.h. der Programmlauf geht nach dem
787    Aufruf weiter ohne auf das Laden und Füllen der Vorlage zu warten.
788    Das fertige HTML wird der Callback-Funktion übergeben
789    sobald die Vorlage geladen und gefüllt ist, unabhängig davon, wo der
790    Programmlauf zu diesem Zeitpunkt mittlerweile ist.
791    
792    vurl - URL zur Vorlagendatei
793    inhalt - die JSON-Struktur, deren Inhalt in die
794    Vorlage gefüllt werden soll
795    cb - Callback-Funktion, die gerufen wird, wenn die Vorlage gefüllt ist.
796    Dieser Callback-Funktion wird das fertige HTML übergeben
797    */
798   this.html_erzeugen = function (vurl, inhalt, cb) {
cfa858 799     var vorlage = self.cache[vurl];
f074f6 800     if (vorlage === undefined) {
cfa858 801       self.vorlage_laden_und_fuellen(vurl, inhalt, cb);
U 802     } else {
803       self.vorlage_fuellen(vurl, inhalt, cb);
804     }
805   };
806
f074f6 807   this.vorlage_fuellen = function (vurl, inhalt, cb) {
cfa858 808     cb(Mustache.render(self.cache[vurl], inhalt));
U 809   };
810
811   /*
f074f6 812    Eine Vorlage vom Server in den lokalen Speicher laden
U 813    vurl - der URL unter dem die Vorlage zu finden ist
814    inhalt - die JSON-Struktur, deren Inhalt in die
815    Vorlage gefüllt werden soll
816    cb - callback: Diese Funktion wird gerufen, wenn die Vorlage mit dem
817    Inhalt gefüllt ist
818    */
819   this.vorlage_laden_und_fuellen = function (vurl, inhalt, cb) {
cfa858 820     var xmlhttp = new XMLHttpRequest();
f074f6 821     xmlhttp.onreadystatechange = function () {
cfa858 822       if (this.readyState == 4 && this.status == 200) {
U 823         self.cache[vurl] = this.responseText;
824         self.vorlage_fuellen(vurl, inhalt, cb);
825       }
826     };
827     xmlhttp.open("GET", vurl, true);
828     xmlhttp.send();
829   };
830
831
832 }
833
f45e20 834 /* ----------- Objekte ---------------- */
U 835
836 function Ablageort(n, o, u) {
837   this.name = n;
838   this.ort = o;
839   this.url = u;
840 }
841
842 function Einstellung(k, v) {
843   this.key = k;
844   this.value = v;
845 }
846
847 function Abspieler(n, u) {
848   this.name = n;
849   this.url = u;
8d7d35 850 }
U 851
d027b5 852 function Livestream(n, u) {
U 853   this.name = n;
854   this.url = u;
855 }
856
8d7d35 857 function Abspielliste(n) {
U 858   this.name = n;
e60cff 859 }
U 860
245ac1 861 function Titel(n, p, u, i, t, a) {
e60cff 862   this.katalogUrl = u;
2bdd78 863   this.pfad = p;
e60cff 864   this.name = n;
245ac1 865   this.interpret = i;
U 866   this.titelAnzName = t;
867   this.album  = a;
3929b0 868 }
U 869
870 function Geraet(n, e, a, s) {
871   this.name = n;
872   this.einUrl = e;
873   this.ausUrl = a;
874   this.statusUrl = s;
d6b78c 875 }