Persoenliche Mediazentrale
ulrich
2021-04-07 3271f1608e851dedec0e53090af8ebd81c02b6b1
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;
f45e20 7
U 8   this.init = function () {
9     self.mediaPfad = '/';
10     self.ortPfad = '/';
11     self.cache = new Array();
12     self.appMenu = new AppMenu();
13     self.appMenu.init(
14             "data/menu/",
15             "hauptmenue.json",
16             "data/tpl/app-menu.tpl",
17             ".west",
18             "8em");
19
20     document.querySelector('.hamburger').addEventListener('click', function (e) {
21       self.menue_umschalten();
22     });
23     
24     self.addEvtListener('#mi-katalog', 'click', self.media_liste);
25     self.addEvtListener('#mi-orte', 'click', self.ablageort_liste);
26     self.addEvtListener('#mi-prefs', 'click', self.prefs_liste);
27     self.addEvtListener('#mi-player', 'click', self.abspieler_liste);
28     
29     self.fusszeile_umschalten();
30     self.seitenleiste_umschalten();
31     self.dialog_unten_zeigen();
32   };
33   
34   this.abspieler_auswahl_fuellen = function() {
35     self.http_get('../api/store/Abspieler/', function (responseText) {
36       /*
37        {"ArrayList": [{"name":"Wohnz","url":"http://rpi4-wz:9090/"},{"name":"Arbz","url":"http://rpi4-az:9090/"}]}
38        */
39       self.vorlage_laden_und_fuellen("data/tpl/abs_sel.tpl", JSON.parse(responseText), function (html) {
40         document.querySelector(".abs-sel").innerHTML = html;
41       });    
42     });
43   };
44
45   /* Unterer Einblendbereich */
46   
47   this.dialog_unten_zeigen = function() {
48     self.vorlage_laden_und_fuellen("data/tpl/ctrl.tpl", "", function (html) {
49       var dlg = document.querySelector(".dialog-unten");
50       dlg.style.height = '4.5em';
51       dlg.innerHTML = html;
52       self.abspieler_auswahl_fuellen();
53     });
54   };
55
86bbf7 56   
U 57   // auf der obersten Ebene werden die Kataloge angezeigt,
58   // darunter der Inhalt des aktuellen Pfades
59   this.media_liste = function() {
60     if(self.ortPfad === '/') {
61       // Kataloge listen
f45e20 62       self.http_get('../api/store/Ablageort/liste/', function (responseText) {
86bbf7 63         self.vorlage_laden_und_fuellen("data/tpl/katalog_root_liste.tpl", JSON.parse(responseText), function (html) {
U 64           document.querySelector(".zentraler-inhalt").innerHTML = html;
65           self.addEvtListener('.entity-eintrag', 'click', function (event) {
66             var t = event.target;
67             self.http_get('../api/store/Ablageort/' + t.textContent, function(responseText) {
68               var ablageort = JSON.parse(responseText);
69               self.ortPfad = ablageort.url;
70               self.media_liste();
71             });
72           });
73         });
74       });
75     } else {
76       // Pfad listen
3271f1 77       console.log("vorher ortPfad: " + self.ortPfad);
U 78       console.log("vorher mediaPfad: " + self.mediaPfad);
79       //self.http_get('..' + self.ortPfad + '/' + self.mediaPfad + '/', function(responseText) {
80       var url = '..' + self.ortPfad + self.mediaPfad;
81       if(!url.endsWith('/')) {
82         url = url + '/';
83       }
84       console.log("url: " + url);      
85       self.http_get(url, function(responseText) {
ca9872 86         //console.log(responseText);
86bbf7 87         self.vorlage_laden_und_fuellen("data/tpl/katalog_inhalt_liste.tpl", JSON.parse(responseText), function (html) {
U 88           document.querySelector(".zentraler-inhalt").innerHTML = html;
3271f1 89           console.log("mediaPfad bei Anzeige: " + self.mediaPfad);
86bbf7 90           self.addEvtListener('.entity-eintrag', 'click', function (event) {
U 91             var t = event.target;
3271f1 92             var tx = t.textContent;
U 93             console.log("tx: " + tx);
94             console.log("mediaPfad nach Auswahl: " + self.mediaPfad);
86bbf7 95             if(t.classList.contains("entity-typ-folder")) {
3271f1 96               if(self.mediaPfad.endsWith('/')) {
U 97                 self.mediaPfad = self.mediaPfad + tx;                
98               } else {
99                 self.mediaPfad = self.mediaPfad + '/' + tx;
100               }
101               //self.mediaPfad = self.mediaPfad + t.textContent;
102               console.log("mediaPfad neu: " + self.mediaPfad);
86bbf7 103               self.media_liste();
U 104             } else {
105               console.log("Media-Inhalt auswaehlen oder abspielen");
7c22a2 106               self.removeClassMulti('selected');
U 107               t.classList.add('selected');
86bbf7 108             }
U 109           });
110           self.addEvtListener('#zurueck-btn', 'click', function (event) {
111             if(self.mediaPfad === '/') {
112               self.ortPfad = '/';              
113             } else {
114               var pos = self.mediaPfad.lastIndexOf('/');
3271f1 115               var parent;
U 116               if(pos > 1) {
117                 parent = self.mediaPfad.substring(0, pos);
118               } else {
119                 parent = '/';
120               }
121               console.log("Parent: " + parent);
86bbf7 122               self.mediaPfad = parent;
U 123             }
124             self.media_liste();
125           });        
126         });
127       });
128     }
129   };
cf6509 130   
U 131   /* Die folgenden '_liste' Funktionen koennen noch vereinheitlicht werden */
cfa858 132
a43e1a 133   this.ablageort_liste = function() {
f45e20 134     self.http_get('../api/store/Ablageort/liste/', function (responseText) {
a43e1a 135       self.vorlage_laden_und_fuellen("data/tpl/ablageort_liste.tpl", JSON.parse(responseText), function (html) {
U 136         document.querySelector(".zentraler-inhalt").innerHTML = html;
137         self.addEvtListener('.entity-eintrag', 'click', function (event) {
138           var t = event.target;
90f5d4 139           self.http_get('../api/store/Ablageort/' + t.textContent, function(responseText){
86bbf7 140             var ablageort = JSON.parse(responseText);
90f5d4 141             self.ablageort_form(ablageort);
U 142           });
a43e1a 143         });
faab2d 144         self.addEvtListener('#neu-btn', 'click', function (event) {
U 145           eval("self.ablageort_form" + "(this)");
146         });        
cf6509 147       });
U 148     });
149   };
150
151   this.prefs_liste = function() {
f45e20 152     self.http_get('../api/store/Einstellung/liste/', function (responseText) {
cf6509 153       self.vorlage_laden_und_fuellen("data/tpl/einstellung_liste.tpl", JSON.parse(responseText), function (html) {
U 154         document.querySelector(".zentraler-inhalt").innerHTML = html;
155         self.addEvtListener('.entity-eintrag', 'click', function (event) {
156           var t = event.target;
157           self.http_get('../api/store/Einstellung/' + t.textContent, function(responseText){
158             var einstellung = JSON.parse(responseText);
159             self.prefs_form(einstellung);
160           });
161         });
162         self.addEvtListener('#neu-btn', 'click', function (event) {
163           eval("self.prefs_form" + "(this)");
164         });        
165       });
166     });
167   };
168
3d4bca 169   this.abspieler_liste = function() {
f45e20 170     self.http_get('../api/store/Abspieler/liste/', function (responseText) {
3d4bca 171       self.vorlage_laden_und_fuellen("data/tpl/abspieler_liste.tpl", JSON.parse(responseText), function (html) {
U 172         document.querySelector(".zentraler-inhalt").innerHTML = html;
173         self.addEvtListener('.entity-eintrag', 'click', function (event) {
174           var t = event.target;
175           self.http_get('../api/store/Abspieler/' + t.textContent, function(responseText){
176             var abspieler = JSON.parse(responseText);
177             self.abspieler_form(abspieler);
178           });
179         });
180         self.addEvtListener('#neu-btn', 'click', function (event) {
181           eval("self.abspieler_form" + "(this)");
182         });        
183       });
184     });
185   };
186
cf6509 187   /* Die folgenden '_form' Funktionen koennen noch vereinheitlicht werden */
3d4bca 188
U 189   this.abspieler_form = function(pl) {
190     self.vorlage_laden_und_fuellen("data/tpl/form_abspieler.tpl", pl, function (html) {
191       document.querySelector(".zentraler-inhalt").innerHTML = html;
192       self.addEvtListener('#ok-btn', 'click', function () {
193         var plname = document.querySelector('#abspieler-name').value;
194         plname = plname.replace(' ', '').replace(/[\W]+/g, '');
195         var abspieler = new Abspieler(
196           plname,
197           document.querySelector('#abspieler-url').value
198         );
199         var daten = JSON.stringify(abspieler);        
200         if(typeof pl === "undefined" || pl.key !== plname) {
201           // neu
202           self.http_post('../api/store/Abspieler', daten, function (responseText) {
203             // hier die Antwort verarbeiten
204           });
205         } else {
206           // aendern
207           self.http_put('../api/store/Abspieler', daten, function (responseText) {
208             // hier die Antwort verarbeiten
209           });
210         }
211         //document.querySelector(".zentraler-inhalt").innerHTML = '';
212         self.abspieler_liste();
213       });
214       self.addEvtListener('#cancel-btn', 'click', function () {
215         //document.querySelector(".zentraler-inhalt").innerHTML = '';
216         self.abspieler_liste();
217       });
218       self.addEvtListener('#loeschen-btn', 'click', function() {
219         var plname = document.querySelector('#abspieler-name').value;
220         var dlgdata = {"del-elem": plname};
221         self.dialog_laden_und_zeigen('data/tpl/dlg-loeschen.tpl', dlgdata, function() {
222           self.addEvtListener('#nein-btn', 'click', self.dialog_schliessen);
223           self.addEvtListener('#ja-btn', 'click', function() {
224             self.http_delete('../api/store/Abspieler/' + plname, '', function (responseText) {
225               // hier die Antwort verarbeiten
226               self.dialog_schliessen();
227               //document.querySelector(".zentraler-inhalt").innerHTML = '';
228               self.abspieler_liste();
229             });
230           });
231         });
232       });
233     });
234   };
cf6509 235
U 236   this.prefs_form = function(k) {
237     self.vorlage_laden_und_fuellen("data/tpl/form_einstellung.tpl", k, function (html) {
238       document.querySelector(".zentraler-inhalt").innerHTML = html;
239       self.addEvtListener('#ok-btn', 'click', function () {
240         var pkey = document.querySelector('#einstellung-key').value;
241         pkey = pkey.replace(' ', '').replace(/[\W]+/g, '');
242         var pref = new Einstellung(
243           pkey,
244           document.querySelector('#einstellung-value').value
245         );
246         var daten = JSON.stringify(pref);        
247         if(typeof k === "undefined" || k.key !== pkey) {
248           // neu
249           self.http_post('../api/store/Einstellung', daten, function (responseText) {
250             // hier die Antwort verarbeiten
251           });
252         } else {
253           // aendern
254           self.http_put('../api/store/Einstellung', daten, function (responseText) {
255             // hier die Antwort verarbeiten
256           });
257         }
258         //document.querySelector(".zentraler-inhalt").innerHTML = '';
259         self.prefs_liste();
260       });
261       self.addEvtListener('#cancel-btn', 'click', function () {
262         //document.querySelector(".zentraler-inhalt").innerHTML = '';
263         self.prefs_liste();
264       });
265       self.addEvtListener('#loeschen-btn', 'click', function() {
266         var pkey = document.querySelector('#einstellung-key').value;
267         var dlgdata = {"del-elem": pkey};
268         self.dialog_laden_und_zeigen('data/tpl/dlg-loeschen.tpl', dlgdata, function() {
269           self.addEvtListener('#nein-btn', 'click', self.dialog_schliessen);
270           self.addEvtListener('#ja-btn', 'click', function() {
271             self.http_delete('../api/store/Einstellung/' + pkey, '', function (responseText) {
272               // hier die Antwort verarbeiten
273               self.dialog_schliessen();
274               //document.querySelector(".zentraler-inhalt").innerHTML = '';
275               self.prefs_liste();
276             });
277           });
278         });
a43e1a 279       });
U 280     });
281   };
cfa858 282
90f5d4 283   /* 
U 284    * Ablageort-Formular anzeigen
285    * 
286    * {"name":"Katalog 2","ort":"/home/ulrich/Videos","url":"/media/kat2"}: 
287    * 
288    * @param {type} ablageort  der Ablageort, der bearbeitet werden soll, leer fuer neuen Ort
289    * @returns {undefined} kein Rueckgabewert
290    */
291   this.ablageort_form = function(ort) {
292     self.vorlage_laden_und_fuellen("data/tpl/form_ablageort.tpl", ort, function (html) {
b379f5 293       document.querySelector(".zentraler-inhalt").innerHTML = html;
f074f6 294       self.addEvtListener('#ok-btn', 'click', function () {
2597cd 295         var aName = document.querySelector('#ablageort-name').value;
U 296         aName = aName.replace(' ', '').replace(/[\W]+/g, '');
b379f5 297         var a = new Ablageort(
2597cd 298           aName,
b379f5 299           document.querySelector('#ablageort-ort').value,
U 300           document.querySelector('#ablageort-url').value
301         );
90f5d4 302         var daten = JSON.stringify(a);        
2597cd 303         if(typeof ort === "undefined" || ort.name !== aName) {
U 304           // neu
90f5d4 305           self.http_post('../api/store/Ablageort', daten, function (responseText) {
U 306             // hier die Antwort verarbeiten
307           });
308         } else {
2597cd 309           // aendern
90f5d4 310           self.http_put('../api/store/Ablageort', daten, function (responseText) {
U 311             // hier die Antwort verarbeiten
312           });
313         }
b29119 314         //document.querySelector(".zentraler-inhalt").innerHTML = '';
faab2d 315         self.ablageort_liste();
b379f5 316       });
f074f6 317       self.addEvtListener('#cancel-btn', 'click', function () {
b29119 318         //document.querySelector(".zentraler-inhalt").innerHTML = '';
faab2d 319         self.ablageort_liste();
5b7356 320       });
U 321       self.addEvtListener('#loeschen-btn', 'click', function() {
322         var aoname = document.querySelector('#ablageort-name').value;
323         var dlgdata = {"del-elem": aoname};
324         self.dialog_laden_und_zeigen('data/tpl/dlg-loeschen.tpl', dlgdata, function() {
325           self.addEvtListener('#nein-btn', 'click', self.dialog_schliessen);
326           self.addEvtListener('#ja-btn', 'click', function() {
90f5d4 327             self.http_delete('../api/store/Ablageort/' + aoname, '', function (responseText) {
U 328               // hier die Antwort verarbeiten
2597cd 329               self.dialog_schliessen();
b29119 330               //document.querySelector(".zentraler-inhalt").innerHTML = '';
faab2d 331               self.ablageort_liste();
90f5d4 332             });
5b7356 333           });
U 334         });
b379f5 335       });
U 336     });
337   };
338
7c22a2 339   this.addEvtListener = function(selector, eventName, func) {
U 340     document.querySelectorAll(selector).forEach(elem => { elem.addEventListener(eventName, func); });
341   };
342   
343   this.removeClassMulti = function(selector) {
344     document.querySelectorAll('.' + selector).forEach(elem => { elem.classList.remove(selector); });
345   };
346
347   /*
f074f6 348   this.addEvtListener = function (selector, eventName, func) {
b379f5 349     var elems = document.querySelectorAll(selector);
U 350     var index;
351     for (index = 0; index < elems.length; index++) {
352       elems[index].addEventListener(eventName, func);
353     }
354   };
7c22a2 355   */
U 356   
357   // document.querySelectorAll(selector).forEach(elem => { elem.addEventListener(eventName, func); });
f074f6 358
f45e20 359   /* --------------------- asynchroner HTTP Client ----------------- */
faab2d 360   
f074f6 361   this.http_get = function (u, cb) {
b379f5 362     self.http_call('GET', u, null, cb);
U 363   };
f074f6 364
U 365   this.http_post = function (u, data, cb) {
b379f5 366     self.http_call('POST', u, data, cb);
U 367   };
368
90f5d4 369   this.http_put = function (u, data, cb) {
U 370     self.http_call('PUT', u, data, cb);
371   };
372   
5b7356 373   this.http_delete = function (u, data, cb) {
2597cd 374     console.log("delete " + u);
5b7356 375     self.http_call('DELETE', u, data, cb);
U 376   };
377   
f074f6 378   this.http_call = function (method, u, data, scallback) {
b379f5 379     var xhr = new XMLHttpRequest();
U 380     var url = u;
f074f6 381     xhr.onreadystatechange = function () {
b379f5 382       if (this.readyState === 4 && this.status === 200) {
U 383         scallback(this.responseText);
384       }
385     };
386     xhr.open(method, url);
f074f6 387     if (method === 'GET') {
b379f5 388       xhr.send();
2597cd 389     } else if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
b379f5 390       xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
U 391       xhr.send(data);
392     }
393   };
8239d1 394   
f45e20 395   /* ------------------------ aus App-Vorlage -------------------  */
cfa858 396
f074f6 397   this.menue_umschalten = function () {
cfa858 398     var ham = document.querySelector(".hamburger");
U 399     ham.classList.toggle("is-active"); // hamburger-icon umschalten
400     self.appMenu.toggle(); // menue oeffnen/schliessen
401   };
402
f074f6 403   this.info_dialog_zeigen = function () {
cfa858 404     self.dialog_laden_und_zeigen('data/tpl/dlg-info.tpl', '');
U 405     self.menue_umschalten();
406   };
407
f074f6 408   this.seitenleiste_umschalten = function () {
cfa858 409     var ostDiv = document.querySelector('.ost');
f074f6 410     if (ostDiv.classList.contains('ost-open')) {
cfa858 411       ostDiv.classList.remove('ost-open');
f074f6 412       ostDiv.style.flexBasis = '0em';
cfa858 413     } else {
f074f6 414       ostDiv.classList.add('ost-open');
U 415       ostDiv.style.flexBasis = '6em';
cfa858 416     }
U 417     self.menue_umschalten();
418   };
419
f074f6 420   this.fusszeile_umschalten = function () {
cfa858 421     var suedDiv = document.querySelector('.sued');
f074f6 422     if (suedDiv.classList.contains('sued-open')) {
cfa858 423       suedDiv.classList.remove('sued-open');
f074f6 424       suedDiv.style.height = '0';
cfa858 425     } else {
U 426       suedDiv.classList.add('sued-open');
f074f6 427       suedDiv.style.height = '1.5em';
cfa858 428     }
U 429     self.menue_umschalten();
430   };
431
f074f6 432   this.menu_message = function (msg) {
cfa858 433     self.meldung_mit_timeout(msg, 1500);
U 434     var suedDiv = document.querySelector('.sued');
f074f6 435     if (suedDiv.classList.contains('sued-open')) {
cfa858 436     } else {
U 437       suedDiv.classList.add('sued-open');
f074f6 438       suedDiv.style.height = '1.5em';
cfa858 439     }
U 440     self.menue_umschalten();
441   };
442
f074f6 443   this.message_1 = function () {
cfa858 444     self.menu_message('Eine Mitteilung.');
U 445   };
446
f074f6 447   this.message_2 = function () {
cfa858 448     self.menu_message('Was wir schon immer sagen wollten.');
U 449   };
450
f074f6 451   this.message_3 = function (text) {
cfa858 452     self.menu_message(text);
U 453   };
454
f074f6 455   this.meldung_mit_timeout = function (meldung, timeout) {
cfa858 456     var s = document.querySelector('.sued');
a43e1a 457     s.classList.add('sued-open');
U 458     s.style.height = '1.5em';
cfa858 459     s.textContent = meldung;
f074f6 460     setTimeout(function () {
cfa858 461       s.textContent = 'Bereit.';
f074f6 462       setTimeout(function () {
cfa858 463         var suedDiv = document.querySelector('.sued');
f074f6 464         if (suedDiv.classList.contains('sued-open')) {
U 465           suedDiv.classList.remove('sued-open');
466           suedDiv.style.height = '0';
cfa858 467         }
U 468       }, 500);
469     }, timeout);
470   };
8239d1 471   
f45e20 472   /* --------------------- Dialog-Funktionen ------------------------ */
cfa858 473
U 474   /*
f074f6 475    Einen Dialog aus Vorlagen erzeugen
U 476    
477    vurl - URL zur Dialogvorlage
478    msgTpl - URL mit einer Vorlage eines Mitteilungstextes (optional)
479    */
5b7356 480   this.dialog_laden_und_zeigen = function (vurl, msgTpl, cb) {
U 481     var vorlage = self.cache[vurl];
482     if(vorlage === undefined) {
483       self.http_get(vurl, function(antwort) {
484         self.cache[vurl] = antwort;
485         self.dialog_zeigen(vurl, msgTpl, cb);
f074f6 486       });
cfa858 487     } else {
5b7356 488       self.dialog_zeigen(vurl, msgTpl, cb);
cfa858 489     }
U 490   };
491
5b7356 492   this.dialog_zeigen = function (vurl, inhalt, cb) {
U 493     var dlg = document.querySelector(".dialog");
2597cd 494     self.html_erzeugen(vurl, inhalt, function (html) {
U 495       dlg.style.height = '7em';
496       dlg.innerHTML = html;
497       document.querySelector('.close-btn').addEventListener('click', self.dialog_schliessen);
498       if(typeof(cb) !== 'function') {
499         // ..
500       } else {
501         cb();
502       }
503     });
5b7356 504   };
2597cd 505   
f45e20 506   this.dialog_schliessen = function () {
cfa858 507     document.querySelector('.close-btn').removeEventListener('click', self.dialog_schliessen);
U 508     var dlg = document.querySelector('.dialog');
509     dlg.style.height = '0';
510     dlg.innerHTML = '';
511   };
512
f45e20 513   /* ---------------------   Vorlagen   ---------------------- */
cfa858 514
U 515   /*
f074f6 516    Das HTML erzeugen, das entsteht, wenn eine Vorlage mit Inhalt
U 517    gefüllt wird
518    
519    Das Füllen erfolgt asynchron, d.h. der Programmlauf geht nach dem
520    Aufruf weiter ohne auf das Laden und Füllen der Vorlage zu warten.
521    Das fertige HTML wird der Callback-Funktion übergeben
522    sobald die Vorlage geladen und gefüllt ist, unabhängig davon, wo der
523    Programmlauf zu diesem Zeitpunkt mittlerweile ist.
524    
525    vurl - URL zur Vorlagendatei
526    inhalt - die JSON-Struktur, deren Inhalt in die
527    Vorlage gefüllt werden soll
528    cb - Callback-Funktion, die gerufen wird, wenn die Vorlage gefüllt ist.
529    Dieser Callback-Funktion wird das fertige HTML übergeben
530    */
531   this.html_erzeugen = function (vurl, inhalt, cb) {
cfa858 532     var vorlage = self.cache[vurl];
f074f6 533     if (vorlage === undefined) {
cfa858 534       self.vorlage_laden_und_fuellen(vurl, inhalt, cb);
U 535     } else {
536       self.vorlage_fuellen(vurl, inhalt, cb);
537     }
538   };
539
f074f6 540   this.vorlage_fuellen = function (vurl, inhalt, cb) {
cfa858 541     cb(Mustache.render(self.cache[vurl], inhalt));
U 542   };
543
544   /*
f074f6 545    Eine Vorlage vom Server in den lokalen Speicher laden
U 546    vurl - der URL unter dem die Vorlage zu finden ist
547    inhalt - die JSON-Struktur, deren Inhalt in die
548    Vorlage gefüllt werden soll
549    cb - callback: Diese Funktion wird gerufen, wenn die Vorlage mit dem
550    Inhalt gefüllt ist
551    */
552   this.vorlage_laden_und_fuellen = function (vurl, inhalt, cb) {
cfa858 553     var xmlhttp = new XMLHttpRequest();
f074f6 554     xmlhttp.onreadystatechange = function () {
cfa858 555       if (this.readyState == 4 && this.status == 200) {
U 556         self.cache[vurl] = this.responseText;
557         self.vorlage_fuellen(vurl, inhalt, cb);
558       }
559     };
560     xmlhttp.open("GET", vurl, true);
561     xmlhttp.send();
562   };
563
564
565 }
566
f45e20 567 /* ----------- Objekte ---------------- */
U 568
569 function Ablageort(n, o, u) {
570   this.name = n;
571   this.ort = o;
572   this.url = u;
573 }
574
575 function Einstellung(k, v) {
576   this.key = k;
577   this.value = v;
578 }
579
580 function Abspieler(n, u) {
581   this.name = n;
582   this.url = u;
583 }
584