openrat-cms

# OpenRat Content Management System
git clone http://git.code.weiherhei.de/openrat-cms.git
Log | Files | Refs

openrat.js (33360B)


      1 
      2 var OR_THEMES_EXT_DIR = 'modules/cms-ui/themes/';
      3 
      4 // Execute after DOM ready:
      5 $( function() {
      6     // JS is available.
      7     $('html').removeClass('nojs');
      8 
      9     /* Fade in all elements. */
     10     $('.initial-hidden').removeClass('initial-hidden');
     11 
     12     registerWorkbenchEvents();
     13 
     14 
     15     // Listening to the "popstate" event:
     16     window.onpopstate = function (ev) {
     17         Navigator.navigateTo(ev.state);
     18     };
     19 
     20     initActualHistoryState();
     21 
     22     Workbench.initialize();
     23     Workbench.reloadAll();
     24 
     25     registerNavigation();
     26 
     27     // Binding aller Sondertasten.
     28     $('.keystroke').each( function() {
     29     	let keystrokeElement = $(this);
     30         let keystroke = keystrokeElement.text();
     31         if (keystroke.length == 0)
     32         	return; // No Keybinding.
     33     	let keyaction = function() {
     34     		keystrokeElement.click();
     35 		};
     36     	// Keybinding ausfuehren.
     37         $(document).bind('keydown', keystroke, keyaction );
     38 	} );
     39 
     40 
     41 
     42     // Per Klick wird die Notice entfernt.
     43     $('#noticebar .notice .image-icon--menu-close').click(function () {
     44         $(this).closest('.notice').fadeOut('fast', function () {
     45             $(this).remove();
     46         });
     47     });
     48     // Die Notices verschwinden automatisch.
     49     $('#noticebar .notice').each(function () {
     50     	let noticeToClose = this;
     51 		setTimeout( function() {
     52 			$(noticeToClose).fadeOut('slow', function() { $(this).remove(); } );
     53 		},30/*seconds*/ *1000 );
     54 
     55     });
     56 
     57 
     58     registerOpenClose($('section.toggle-open-close'));
     59 
     60     $('section.toggle-open-close .on-click-open-close').click(function () {
     61         var section = $(this).closest('section');
     62 
     63         // disabled sections are ignored.
     64         if (section.hasClass('disabled'))
     65             return;
     66 
     67         // if view is empty, lets load the content.
     68         var view = section.find('div.view-loader');
     69         if (view.children().length == 0)
     70             Workbench.loadNewActionIntoElement(view);
     71     });
     72 });
     73 
     74 
     75 function initActualHistoryState() {
     76 	var state = {};
     77 	state.name   = window.document.title;
     78 
     79 	var params = new URLSearchParams( window.location.search );
     80 
     81     if (params.has('action')){
     82 
     83         state.action = params.get('action');
     84         state.id     = params.get('id'    );
     85         state.name   = window.document.title;
     86 
     87         state.data   = {};
     88 
     89         //Iterate the search parameters.
     90 
     91         var params = Array.from( params.entries() );
     92 		for( var entry in params ) {
     93             state.data[params[entry][0]] = params[entry][1];
     94         };
     95 
     96         Navigator.toActualHistory( state );
     97 
     98     }
     99 }
    100 
    101 
    102 function registerNavigation() {
    103     $(document).on('orNewAction',function(event, data) {
    104 
    105         let url = './api/?action=tree&subaction=path&id=' + Workbench.state.id + '&type=' + Workbench.state.action + '&output=json';
    106 
    107         // Die Inhalte des Zweiges laden.
    108         $.getJSON(url, function (json) {
    109 
    110             $('nav .or-navtree-node').removeClass('or-navtree-node--selected');
    111 
    112             let output = json['output'];
    113             $.each(output.path, function (idx, path) {
    114 
    115                 $nav = $('nav .or-navtree-node[data-type='+path.type+'][data-id='+path.id+'].or-navtree-node--is-closed .or-navtree-node-control');
    116                 $nav.click();
    117             });
    118             if   ( output.actual )
    119                 $('nav .or-navtree-node[data-type='+output.actual.type+'][data-id='+output.actual.id+']').addClass('or-navtree-node--selected');
    120 
    121         }).fail(function (e) {
    122             // Ups... aber was können wir hier schon tun, außer hässliche Meldungen anzeigen.
    123             console.warn(e);
    124             console.warn('failed to load path from '+url);
    125         }).always(function () {
    126 
    127         });
    128     });
    129 }
    130 
    131 
    132 /**
    133  * Navigation.
    134  */
    135 var Navigator = new function () {
    136 	'use strict';
    137 
    138 	/**
    139 	 * Navigiert zu einer Action, aber ohne ein neues History-Element einzufügen.
    140 	 */
    141 	this.navigateTo = function(state) {
    142 		Workbench.loadNewActionState(state);
    143 	}
    144 
    145 
    146     /**
    147 	 *
    148      * Navigiert zu einer neue Action und fügt einen neuen History-Eintrag hinzu.
    149      */
    150 	this.navigateToNew = function(obj) {
    151 
    152 		Workbench.loadNewActionState(obj);
    153 		window.history.pushState(obj,obj.name,'./#/'+obj.action+(obj.id?'/'+obj.id:'') );
    154     }
    155 
    156     this.navigateToNewAction = function(action, method, id, params ) {
    157         var state = {action:action,method:method,id:Number(id),data:params};
    158         this.navigateToNew(state);
    159 	}
    160 
    161     /**
    162 	 * Setzt den State für den aktuellen History-Eintrag.
    163      * @param obj
    164      */
    165     this.toActualHistory = function(obj) {
    166         window.history.replaceState(obj,obj.name,createUrl(obj.action,null,obj.id,obj.data,false) );
    167     }
    168 }
    169 
    170 
    171 var Workbench = new function()
    172 {
    173     'use strict'; // Strict mode
    174 
    175 
    176     /**
    177 	 * Initializes the Workbench.
    178      */
    179 	this.initialize = function() {
    180 
    181 		// Initialze Ping timer.
    182 		this.initializePingTimer();
    183         this.initializeState();
    184         this.initializeMenues();
    185         this.openModalDialog();
    186     }
    187 
    188 
    189     /**
    190      * Starts a dialog, if necessary.
    191      */
    192     this.openModalDialog = function () {
    193 
    194         if   ( $('#dialog').data('action') ) {
    195             startDialog('',$('#dialog').data('action'),$('#dialog').data('action'),0,{})
    196         }
    197     }
    198 
    199 
    200     this.initializeMenues = function () {
    201 
    202         filterMenus();
    203     }
    204 
    205 
    206     /**
    207      * Sets the workbench state with action/id.
    208      *
    209      * Example: #/name/1 is translated to the state {action:name,id:1}
    210      */
    211     this.initializeState = function () {
    212 
    213         let parts = window.location.hash.split('/');
    214         let state = { action:'index',id:0 };
    215 
    216         if   ( parts.length >= 2 )
    217             state.action = parts[1].toLowerCase();
    218 
    219         if   ( parts.length >= 3 )
    220             state.id = Number(parts[2]);
    221 
    222         Workbench.state = state;
    223 
    224         // TODO: Remove this sometimes.... only state.
    225         $('#editor').attr('data-action',state.action);
    226         $('#editor').attr('data-id'    ,state.id    );
    227         $('#editor').attr('data-extra' ,'{}'  );
    228 
    229     }
    230 
    231     /**
    232 	 *  Registriert den Ping-Timer für den Sitzungserhalt.
    233      */
    234 	this.initializePingTimer = function() {
    235 
    236         /**
    237          * Ping den Server. Führt keine Aktion aus, aber sorgt dafür, dass die Sitzung erhalten bleibt.
    238          *
    239          * "Geben Sie mir ein Ping, Vasily. Und bitte nur ein einziges Ping!" (aus: Jagd auf Roter Oktober)
    240          */
    241         var ping = function()
    242         {
    243             $.ajax( createUrl('title','ping',0) );
    244             //window.console && console.log("session-ping");
    245         }
    246 
    247         // Alle 5 Minuten pingen.
    248 		var timeoutMinutes = 5;
    249 
    250         window.setInterval( ping, timeoutMinutes*60*1000 );
    251     }
    252 
    253 
    254 
    255     this.loadNewActionState = function(state) {
    256 
    257 	    Workbench.state = state;
    258 		Workbench.loadNewAction(state.action,state.id,state.data);
    259 
    260 
    261         filterMenus();
    262 
    263         $(document).trigger('orNewAction');
    264 	}
    265 
    266     /**
    267 	 *
    268      */
    269 
    270     this.loadNewAction = function(action, id, params ) {
    271 
    272     	$('#editor').attr('data-action',action);
    273     	$('#editor').attr('data-id'    ,id    );
    274     	$('#editor').attr('data-extra' ,JSON.stringify(params));
    275 
    276         this.reloadViews();
    277     }
    278 
    279 
    280     /**
    281      *
    282      */
    283 
    284     this.reloadViews = function() {
    285 
    286         // View in geschlossenen Sektionen löschen, damit diese nicht stehen bleiben.
    287         $('#workbench section.closed .view-loader').empty();
    288 
    289         Workbench.loadViews( $('#workbench section.open .view-loader') );
    290     }
    291 
    292 
    293     this.reloadAll = function() {
    294 
    295     	// View in geschlossenen Sektionen löschen, damit diese nicht stehen bleiben.
    296         $('#workbench .view').empty();
    297 
    298         Workbench.loadViews( $('#workbench .view') );
    299     }
    300 
    301 
    302 
    303     this.loadViews = function( $views )
    304     {
    305 
    306         $views.each(function (idx) {
    307 
    308             let $targetDOMElement = $(this);
    309 
    310             Workbench.loadNewActionIntoElement( $targetDOMElement )
    311         });
    312     }
    313 
    314 
    315 
    316     this.loadNewActionIntoElement = function( $viewElement )
    317     {
    318         let action;
    319         if   ( $viewElement.is('.view-static') )
    320             // Static views have always the same action.
    321             action = $viewElement.attr('data-action');
    322         else
    323             action = $('#editor').attr('data-action');
    324 
    325         let id     = $('#editor').attr('data-id'    );
    326         let params = $('#editor').attr('data-extra' );
    327 
    328         let method = $viewElement.data('method');
    329 
    330         let view = new View( action,method,id,params );
    331         view.start( $viewElement );
    332     }
    333 
    334 
    335 }
    336 
    337 
    338 
    339 
    340 /**
    341  * Registriert alle Events, die in der Workbench laufen sollen.
    342  */
    343 function registerWorkbenchEvents()
    344 {
    345 	// Modalen Dialog erzeugen.
    346 
    347 	/*
    348 	if	( $('#workbench div.panel.modal').length > 0 )
    349 	{
    350 		$('#workbench div.panel.modal').parent().addClass('modal');
    351 		$('div#filler').fadeTo(500,0.5);
    352 		$('#workbench').addClass('modal');
    353 	}
    354 	*/
    355 	
    356 	
    357 	$('div.header').dblclick( function()
    358 	{
    359 		fullscreen( this );
    360 	} );
    361 }
    362 
    363 
    364 /**
    365  * Laden einer View.
    366  *
    367  * @param contentEl
    368  * @param action
    369  * @param method
    370  * @param id
    371  * @param params
    372  */
    373 function loadView(contentEl,action,method,id,params  )
    374 {
    375 	Navigator.navigateToNewAction( action,method,id,params );
    376 }
    377 
    378 
    379 
    380 /**
    381  * Registriert alle Handler für den Inhalt einer View.
    382  *
    383  * @param viewEl DOM-Element der View
    384  */
    385 function afterViewLoaded(viewEl )
    386 {
    387 
    388 	// Die Section deaktivieren, wenn die View keinen Inhalt hat.
    389     var section = $(viewEl).closest('section');
    390 
    391     //var viewHasContent = $(viewEl).children().length > 0;
    392 	//section.toggleClass('disabled',!viewHasContent);
    393 	section.toggleClass('is-empty',$(viewEl).is(':empty'));
    394 
    395     $(viewEl).trigger('orViewLoaded');
    396 
    397 	// Untermenüpunkte aus der View in das Fenstermenü kopieren...
    398 	$(viewEl).closest('div.panel').find('div.header div.dropdown div.entry.perview').remove(); // Alte Einträge löschen
    399 
    400 	$(viewEl).find('.toggle-nav-open-close').click( function() {
    401 		$('nav').toggleClass('open');
    402 	});
    403 
    404 	$(viewEl).find('.toggle-nav-small').click( function() {
    405 		$('nav').toggleClass('small');
    406 	});
    407 
    408 	$(viewEl).find('div.headermenu > a').each( function(idx,el)
    409 	{
    410 		// Jeden Untermenüpunkt zum Fenstermenü hinzufügen.
    411 		
    412 		// Nein, Untermenüs erscheinen jetzt in der View selbst.
    413 		// $(el).wrap('<div class="entry clickable modal perview" />').parent().appendTo( $(viewEl).closest('div.panel').find('div.header div.dropdown').first() );
    414 	} );
    415 	
    416 	$(viewEl).find('div.header > a.back').each( function(idx,el)
    417 	{
    418 		// Zurück-Knopf zum Fenstermenü hinzufügen.
    419 		$(el).removeClass('button').wrap('<div class="entry perview" />').parent().appendTo( $(viewEl).closest('div.panel').find('div.header div.dropdown').first() );
    420 	} );
    421 	//$(viewEl).find('div.header').html('<!-- moved to window-menu -->');
    422 	
    423 //	$(viewEl).find('input,select,textarea').focus( function() {
    424 //		$(this).closest('div.panel').find('div.command').css('visibility','visible').fadeIn('slow');
    425 //	});
    426 
    427 
    428 	// Selectors (Einzel-Ausahl für Dateien) initialisieren
    429 	// Wurzel des Baums laden
    430 	$(viewEl).find('div.selector.tree').each( function() {
    431 		var selectorEl = this;
    432 		$(this).orTree( { type:'project',selectable:$(selectorEl).attr('data-types').split(','),id:$(selectorEl).attr('data-init-folderid'),onSelect:function(name,type,id) {
    433 		
    434 			var selector = $(selectorEl).parent();
    435 			
    436 			//console.log( 'Selected: '+name+" #"+id );
    437 			$(selector).find('input[type=text]'  ).attr( 'value',name );
    438 			$(selector).find('input[type=hidden]').attr( 'value',id   );
    439 		} });
    440 	} );
    441 	
    442 
    443 	registerDragAndDrop(viewEl);
    444 	
    445 	
    446 	// Bei Änderungen in der View das Tab als 'dirty' markieren
    447 	$(viewEl).find('input').change( function() {
    448 		$(this).parent('div.view').addClass('dirty');
    449 	});
    450 
    451 	// Theme-Auswahl mit Preview
    452     $(viewEl).find('.or-theme-chooser').change( function() {
    453         setUserStyle( this.value );
    454     });
    455 
    456 }
    457 
    458 
    459 
    460 function registerDragAndDrop(viewEl)
    461 {
    462 
    463     registerDraggable(viewEl);
    464     registerDroppable(viewEl);
    465 }
    466 
    467 
    468 
    469 
    470 function registerDraggable(viewEl) {
    471 
    472 // Drag n Drop: Inhaltselemente (Dateien,Seiten,Ordner,Verknuepfungen) koennen auf Ordner gezogen werden.
    473 
    474     $(viewEl).find('.or-draggable').draggable(
    475         {
    476             helper: 'clone',
    477             opacity: 0.7,
    478             zIndex: 2,
    479             distance: 10,
    480             cursor: 'move',
    481             revert: 'false'
    482         }
    483     );
    484 
    485 }
    486 
    487 function registerTreeBranchEvents(viewEl)
    488 {
    489     registerDraggable(viewEl);
    490 }
    491 
    492 
    493 function registerDroppable(viewEl) {
    494 
    495     /*
    496     $(viewEl).find('div.header > a.back').each( function(idx,el) {
    497         $('div.content li.object > .entry[data-type=\'folder\']').droppable({
    498             accept: 'li.object', hoverClass: 'drophover', activeClass: 'dropactive', drop: function (event, ui) {
    499                 let dropped = ui.draggable;
    500                 let droppedOn = $(this).parent();
    501 
    502                 //alert('Moving '+$(dropped).attr('data-id')+' to folder '+$(droppedOn).attr('data-id') );
    503                 startDialog($(this).text(), $(dropped).attr('data-type'), 'copy', $(droppedOn).attr('data-id'), {
    504                     'action': $(dropped).attr('data-type'),
    505                     'subaction': 'copy',
    506                     'id': $(dropped).attr('data-id'),
    507                     'targetFolderId': $(droppedOn).attr('data-id')
    508                 });
    509                 //$(dropped).css({top: 0,left: 0}); // Nicht auf das eigene Fenster fallen lassen.
    510                 $(dropped).detach().css({top: 0, left: 0}).appendTo(droppedOn).click();
    511             }
    512         });
    513     }
    514 */
    515 
    516     $(viewEl).find('.or-droppable').droppable({
    517         accept: '.or-draggable',
    518         hoverClass: 'or-droppable--hover',
    519         activeClass: 'or-droppable--active',
    520 
    521         drop: function (event, ui) {
    522 
    523             let dropped = ui.draggable;
    524 
    525             $(this).find('.or-selector-link-value').val( dropped.data('id') );
    526             $(this).find('.or-selector-link-name' ).val( dropped.data('id') );
    527             // Id übertragen
    528             //$(this).value(dropped.data('id'));
    529         }
    530     });
    531 }
    532 
    533 
    534 
    535 function registerMenuEvents($element )
    536 {
    537     //$e = $($element);
    538 
    539     // Mit der Maus irgendwo hin geklickt, das Menü muss schließen.
    540     $('body').click( function() {
    541         $('.toolbar-icon.menu').parents('.or-menu').removeClass('open');
    542     });
    543     // Mit der Maus geklicktes Menü aktivieren.
    544     $($element).find('.toolbar-icon.menu').click( function(event) {
    545         event.stopPropagation();
    546         $(this).parents('.or-menu').toggleClass('open');
    547     });
    548 
    549     // Mit der Maus überstrichenes Menü aktivieren.
    550     $($element).find('.toolbar-icon.menu').mouseover( function() {
    551 
    552         // close other menus.
    553         $(this).parents('.or-menu').find('.toolbar-icon.menu').removeClass('open');
    554         // open the mouse-overed menu.
    555         $(this).addClass('open');
    556     });
    557 
    558 }
    559 
    560 function registerSearch($element )
    561 {
    562     //$e = $($element);
    563 	$($element).find('.search input').orSearch( { dropdown:'#title div.search div.dropdown' } );
    564 
    565 }
    566 
    567 
    568 
    569 function registerTree(element) {
    570 
    571     // Klick-Funktionen zum Öffnen/Schließen des Zweiges.
    572     $(element).find('.or-navtree-node').orTree();
    573 
    574 }
    575 
    576 
    577 
    578 /**
    579  * Schaltet die Vollbildfunktion an oder aus.
    580  * 
    581  * @param element Das Element, auf dem die Vollbildfunktion ausgeführt wurde
    582  */
    583 function fullscreen( element ) {
    584 	$(element).closest('div.panel').fadeOut('fast', function()
    585 	{
    586 		$(this).toggleClass('fullscreen').fadeIn('fast');
    587 	} );
    588 }
    589 
    590 
    591 
    592 
    593 /**
    594  * Setzt neue View und aktualisiert alle Fenster.
    595  * @param element
    596  * @param action Action
    597  * @param id Id
    598  * @deprecated
    599  */
    600 
    601 function submitUrl( element,url )
    602 {
    603 	postUrl( url,element );
    604 	
    605 	// Alle refresh-fähigen Views mit dem neuen Objekt laden.
    606 	//refreshAllRefreshables();
    607 }
    608 
    609 
    610 /**
    611  * @deprecated
    612 
    613  * @param url
    614  * @param element
    615  */
    616 function postUrl(url,element)
    617 {
    618 	url += '&output=json';
    619 	$.ajax( { 'type':'POST',url:url, data:{}, success:function(data, textStatus, jqXHR)
    620 		{
    621 			$('div.panel div.status div.loader').html('&nbsp;');
    622 			doResponse(data,textStatus,element);
    623 		} } );
    624 
    625 }
    626 
    627 
    628 /**
    629  * Form.
    630  *
    631  * @constructor
    632  */
    633 function Form() {
    634 
    635     this.setLoadStatus = function( isLoading ) {
    636         $(this.element).closest('div.content').toggleClass('loader',isLoading);
    637     }
    638 
    639     this.initOnElement = function( element ) {
    640         this.element = element;
    641 
    642         let form = this;
    643 
    644         // Autosave in Formularen.
    645         // Bei Veränderungen von Checkboxen wird das Formular sofort abgeschickt.
    646         $(element).find('form[data-autosave="true"] input[type="checkbox"]').click( function() {
    647             form.submit();
    648         });
    649 
    650         // After click to "OK" the form is submitted.
    651         // Why this?? input type=submit will submit!
    652         /*
    653         $(event.target).find('input.submit.ok').click( function() {
    654             $(this).closest('form').submit();
    655         });
    656         */
    657 
    658         $(element).find('.or-form-btn--cancel').click( function() {
    659             form.cancel();
    660 
    661         });
    662         $(element).find('.or-form-btn--reset').click( function() {
    663             form.rollback();
    664 
    665         });
    666 
    667         // Submithandler for the whole form.
    668         $(element).submit( function( event ) {
    669 
    670             //
    671             if   ($(this).data('target')=='view')
    672             {
    673                 form.submit();
    674                 event.preventDefault();
    675             }
    676             // target=top will load the native way without javascript.
    677         });
    678     }
    679 
    680     this.cancel = function() {
    681         //$(this.element).html('').parent().removeClass('is-open');
    682         this.close();
    683     }
    684 
    685 
    686     this.rollback = function() {
    687         this.element.trigger('reset');
    688     }
    689 
    690     this.close = function() {
    691 
    692     }
    693 
    694     this.submit = function() {
    695 
    696 
    697         // Show progress
    698         let status = $('<div class="notice info"><div class="text loader"></div></div>');
    699         $('#noticebar').prepend(status); // Notice anhängen.
    700         $(status).show();
    701 
    702         // Alle vorhandenen Error-Marker entfernen.
    703         // Falls wieder ein Fehler auftritt, werden diese erneut gesetzt.
    704         $(this.element).find('.error').removeClass('error');
    705 
    706         var params = $(this.element).serializeArray();
    707         var data = {};
    708         $(params).each(function(index, obj){
    709             data[obj.name] = obj.value;
    710         });
    711 
    712         // If form does not contain action/id, get it from the workbench.
    713         if   (!data.id)
    714             data.id = Workbench.state.id;
    715         if   (!data.action)
    716             data.action = Workbench.state.action;
    717 
    718         let formMethod = $(this.element).attr('method').toUpperCase();
    719 
    720         if	( formMethod == 'GET' )
    721         {
    722             // Mehrseitiges Formular
    723             // Die eingegebenen Formulardaten werden zur nächsten Action geschickt.
    724             //Workbench.loadViewIntoElement( $(form).parent('.view'),data.action, data.subaction,data.id,data );
    725             this.forwardTo( data.action, data.subaction,data.id,data );
    726         }
    727         else
    728         {
    729             let url    = './api/'; // Alle Parameter befinden sich im Formular
    730 
    731             // POST-Request
    732             this.setLoadStatus(true);
    733             //url += '?output=json';
    734             url += '';
    735             //params['output'] = 'json';// Irgendwie geht das nicht.
    736             data.output = 'json';
    737 
    738             if	( $(this.element).data('async') || $(this.element).data('async')=='true')
    739             {
    740                 // Verarbeitung erfolgt asynchron, das heißt, dass der evtl. geöffnete Dialog
    741                 // beendet wird.
    742                 this.close();
    743                 // Async: Window is closed, but the action will be startet now.
    744             }
    745 
    746             let form = this;
    747             $.ajax( { 'type':'POST',url:url, data:data, success:function(data, textStatus, jqXHR)
    748                 {
    749                     form.setLoadStatus(false);
    750                     $(status).remove();
    751 
    752                     doResponse(data,textStatus,form.element);
    753                 },
    754                 error:function(jqXHR, textStatus, errorThrown) {
    755                     form.setLoadStatus(false);
    756                     $(status).remove();
    757 
    758                     try
    759                     {
    760                         let error = jQuery.parseJSON( jqXHR.responseText );
    761                         notify('','','error',error.error,[error.description]);
    762                     }
    763                     catch( e )
    764                     {
    765                         let msg = jqXHR.responseText;
    766                         notify('','','error','Server Error',[msg]);
    767                     }
    768 
    769 
    770                 }
    771 
    772             } );
    773             $(form.element).fadeIn();
    774         }
    775 
    776     }
    777 }
    778 
    779 
    780 
    781 /**
    782  * View.
    783  * Eine View ist ein HTML-Fragment, in das eine Action geladen wird.
    784  * Das Erzeugen der View, das Laden vom Server sowie das Schließen sind hier gekapselt.
    785  *
    786  * @param action
    787  * @param method
    788  * @param id
    789  * @param params
    790  * @constructor
    791  */
    792 function View( action,method,id,params ) {
    793 
    794     this.action = action;
    795     this.method = method;
    796     this.id = id;
    797     this.params = params;
    798 
    799     this.before = function() {};
    800 
    801     this.start = function( element ) {
    802         this.before();
    803         this.element = element;
    804         this.loadView();
    805     }
    806 
    807     this.afterLoad = function() {
    808 
    809     }
    810 
    811     this.close = function() {
    812     }
    813 
    814 
    815     function registerViewEvents(element) {
    816 
    817         registerMenuEvents( element );
    818         registerSearch    ( element );
    819         registerTree      ( element );
    820         afterViewLoaded   ( element );
    821 
    822     }
    823 
    824 
    825     this.loadView = function() {
    826 
    827         let url = createUrl( this.action,this.method,this.id,this.params,true); // URL für das Laden erzeugen.
    828         let element = this.element;
    829         let view = this;
    830 
    831         $(this.element).empty().fadeTo(1,0.7).addClass('loader').html('').load(url,function(response, status, xhr) {
    832 
    833             $(element).fadeTo(350,1);
    834 
    835             $(element).removeClass("loader");
    836 
    837             $(element).find('form').each( function() {
    838                 let form = new Form();
    839                 form.close = function() {
    840                     view.close();
    841                 }
    842                 form.initOnElement(this);
    843 
    844             });
    845             if	( status == "error" )
    846             {
    847                 // Seite nicht gefunden.
    848                 $(element).html("");
    849 
    850                 notify('','','error','Server Error',['Server Error while requesting url '+url, response]);
    851                 return;
    852             }
    853 
    854             registerViewEvents( element );
    855 
    856         });
    857 
    858     }
    859 
    860 }
    861 
    862 
    863 /**
    864  * Setzt neuen modalen Dialog und aktualisiert alle Fenster.
    865  * @param name
    866  * @param action Action
    867  * @param method
    868  * @param id Id
    869  * @param params
    870  */
    871 function startDialog( name,action,method,id,params )
    872 {
    873 	// Attribute aus dem aktuellen Editor holen, falls die Daten beim Aufrufer nicht angegeben sind.
    874 	if (!action)
    875 		action = $('#editor').attr('data-action');
    876 
    877     if  (!id)
    878         id = $('#editor').attr('data-id');
    879 
    880 	let view = new View( action,method,id,params );
    881 
    882     view.before = function() {
    883         $('#dialog > .view').html('<div class="header"><img class="icon" title="" src="./themes/default/images/icon/'+method+'.png" />'+name+'</div>');
    884         $('#dialog > .view').data('id',id);
    885         $('#dialog').removeClass('is-closed').addClass('is-open');
    886 
    887         let view = this;
    888 
    889         this.escapeKeyClosingHandler = function (e) {
    890             if (e.keyCode == 27) { // ESC keycode
    891                 view.close();
    892 
    893                 $(document).off('keyup'); // de-register.
    894             }
    895         };
    896 
    897         $(document).keyup(this.escapeKeyClosingHandler);
    898 
    899         // Nicht-Modale Dialoge durch Klick auf freie Fläche schließen.
    900         $('#dialog .filler').click( function()
    901         {
    902              view.close();
    903         });
    904 
    905     }
    906 
    907     view.close = function() {
    908 
    909         // Strong modal dialogs are unable to close.
    910         // Really?
    911         if	( $('div#dialog').hasClass('modal') )
    912             return;
    913 
    914         $('#dialog .view').fadeOut('fast').html('');
    915         $('#dialog').removeClass('is-open').addClass('is-closed'); // Dialog schließen
    916 
    917         $(document).unbind('keyup',this.escapeKeyClosingHandler); // Cleanup ESC-Key-Listener
    918     }
    919 
    920 	view.start( $('div#dialog > .view') );
    921 }
    922 
    923 
    924 /**
    925  * Starts a non-modal editing dialog.
    926  * @param name
    927  * @param action
    928  * @param method
    929  * @param id
    930  * @param params
    931  */
    932 function startEdit( name,action,method,id,params )
    933 {
    934 	// Attribute aus dem aktuellen Editor holen, falls die Daten beim Aufrufer nicht angegeben sind.
    935 	if (!action)
    936 		action = Workbench.state.action;
    937 
    938 	if  (!id)
    939         id = Workbench.state.id;
    940 
    941     let view = new View( action,method,id,params );
    942 
    943     view.before = function() {
    944 
    945         let view = this;
    946 
    947         $edit = $('#edit');
    948         $edit.addClass('is-open');
    949 
    950         $('#editor').addClass('is-closed');
    951 
    952         // Dialog durch Klick auf freie Fläche schließen.
    953         $('#edit .filler').click( function()
    954         {
    955             view.close();
    956         });
    957 
    958     };
    959 
    960     view.close = function() {
    961         $edit.removeClass('is-open');
    962         $('#editor').removeClass('is-closed');
    963     }
    964 
    965 	view.start( $('#edit > .view') );
    966 }
    967 
    968 
    969 /**
    970  * Setzt einen Fenster-Titel für die ganze Anwendung. 
    971  */
    972 function setTitle( title )
    973 {
    974 	if	( title )
    975 		$('head > title').text( title + ' - ' + $('head > title').data('default') );
    976 	else
    977 		$('head > title').text( $('head > title').data('default') );
    978 }
    979 
    980 /**
    981  * Setzt neue Action und aktualisiert alle Fenster.
    982  * 
    983  * @param action Action
    984  * @param id Id
    985  */
    986 function openNewAction( name,action,id,extraId )
    987 {
    988 	// Im Mobilmodus soll das Menü verschwinden, wenn eine neue Action geoeffnet wird.
    989     $('nav').removeClass('open');
    990 
    991     setTitle( name ); // Title setzen.
    992 	
    993 	setNewAction( action,id,extraId );
    994 }
    995 
    996 
    997 function filterMenus()
    998 {
    999     let action = Workbench.state.action;
   1000     let id     = Workbench.state.id;
   1001     $('div.clickable').addClass('active');
   1002     $('div.clickable.filtered').removeClass('active').addClass('inactive');
   1003 
   1004 	$('div.clickable.filtered.on-action-'+action).addClass('active').removeClass('inactive');
   1005 
   1006     // Jeder Menüeintrag bekommt die Id und Parameter.
   1007     $('div.clickable.filtered a').attr('data-id'    ,id    );
   1008     /*
   1009         $('div.clickable.filtered a').attr('data-action',action);
   1010     */
   1011 
   1012 }
   1013 
   1014 
   1015 
   1016 /**
   1017  * Setzt neue Action und aktualisiert alle Fenster.
   1018  * 
   1019  * @param action Action
   1020  * @param id Id
   1021  */
   1022 function setNewAction( action,id,extraId )
   1023 {
   1024 	Navigator.navigateToNewAction(action,'edit',id,extraId);
   1025 	// Alle refresh-fähigen Views mit dem neuen Objekt laden.
   1026 	//refreshAllRefreshables();
   1027 }
   1028 
   1029 
   1030 /**
   1031  * Setzt neue Id und aktualisiert alle Fenster.
   1032  * @param id Id
   1033  */
   1034 function setNewId( id ) {
   1035 }
   1036 
   1037 
   1038 
   1039 
   1040 
   1041 /**
   1042  * Notification im Browser anzeigen.
   1043  * Quelle: https://developer.mozilla.org/en-US/docs/Web/API/notification
   1044  * @param text Text der Nachricht.
   1045  */
   1046 function notifyBrowser(text)
   1047 {
   1048 	  // Let's check if the browser supports notifications
   1049 	  if (!("Notification" in window)) {
   1050 		return;
   1051 	    //alert("This browser does not support desktop notification");
   1052 	  }
   1053 
   1054 	  // Let's check if the user is okay to get some notification
   1055 	  else if (Notification.permission === "granted") {
   1056 	    // If it's okay let's create a notification
   1057 	    let notification = new Notification(text);
   1058 	  }
   1059 
   1060 	  // Otherwise, we need to ask the user for permission
   1061 	  else if (Notification.permission !== 'denied') {
   1062 	    Notification.requestPermission(function (permission) {
   1063 	      // If the user is okay, let's create a notification
   1064 	      if (permission === "granted") {
   1065 	        let notification = new Notification(text);
   1066 	      }
   1067 	    });
   1068 	  }
   1069 
   1070 	  // At last, if the user already denied any notification, and you 
   1071 	  // want to be respectful there is no need to bother them any more.
   1072 }
   1073 
   1074 
   1075 
   1076 /**
   1077  * Setzt einen neuen Theme.
   1078  * @param styleName
   1079  * @returns
   1080  */
   1081 function setUserStyle( styleName )
   1082 {
   1083 	var html = $('html');
   1084 	var classList = html.attr('class').split(/\s+/);
   1085 	$.each(classList, function(index, item) {
   1086 	    if (item.startsWith('theme-')) {
   1087 	        html.removeClass(item);
   1088 	    }
   1089 	});
   1090 	html.addClass( 'theme-' + styleName.toLowerCase() );
   1091 }
   1092 
   1093 
   1094 
   1095 
   1096 //Quelle:
   1097 //http://aktuell.de.selfhtml.org/tippstricks/javascript/bbcode/
   1098 function insert(tagName, aTag, eTag)
   1099 {
   1100 var input = document.forms[0].elements[tagName];
   1101 input.focus();
   1102 /* IE */
   1103 if(typeof document.selection != 'undefined') {
   1104  /* Einfuegen des Formatierungscodes */
   1105 // alert('IE');
   1106  var range = document.selection.createRange();
   1107  var insText = range.text;
   1108  range.text = aTag + insText + eTag;
   1109  /* Anpassen der Cursorposition */
   1110  range = document.selection.createRange();
   1111  if (insText.length == 0) {
   1112    range.move('character', -eTag.length);
   1113  } else {
   1114    range.moveStart('character', aTag.length + insText.length + eTag.length);      
   1115  }
   1116  range.select();
   1117 }
   1118 /* Gecko */
   1119 else if(typeof input.selectionStart != 'undefined')
   1120 {
   1121 // alert('Gecko');
   1122  /* Einfuegen des Formatierungscodes */
   1123  var start = input.selectionStart;
   1124  var end = input.selectionEnd;
   1125  var insText = input.value.substring(start, end);
   1126  input.value = input.value.substr(0, start) + aTag + insText + eTag + input.value.substr(end);
   1127  /* Anpassen der Cursorposition */
   1128  var pos;
   1129  if (insText.length == 0) {
   1130    pos = start + aTag.length;
   1131  } else {
   1132    pos = start + aTag.length + insText.length + eTag.length;
   1133  }
   1134  input.selectionStart = pos;
   1135  input.selectionEnd = pos;
   1136 }
   1137 /* uebrige Browser */
   1138 else
   1139 {
   1140  /* Abfrage der Einfuegeposition */
   1141  
   1142  /*
   1143  var pos;
   1144  var re = new RegExp('^[0-9]{0,3}$');
   1145  while(!re.test(pos)) {
   1146    pos = prompt("Position (0.." + input.value.length + "):", "0");
   1147  }
   1148  if(pos > input.value.length) {
   1149    pos = input.value.length;
   1150  }
   1151 	*/
   1152  pos = input.value.length;
   1153  
   1154  /* Einfuegen des Formatierungscodes */
   1155  var insText = prompt("Text");
   1156  input.value = input.value.substr(0, pos) + aTag + insText + eTag + input.value.substr(pos);
   1157 }
   1158 }
   1159 
   1160 
   1161 
   1162 
   1163 /**
   1164  * Erzeugt eine URL, um die gewünschte Action vom Server zu laden.
   1165  * 
   1166  * @param action
   1167  * @param subaction
   1168  * @param id
   1169  * @param extraid
   1170  * @returns URL
   1171  */
   1172 function createUrl(action,subaction,id,extraid,embed)
   1173 {
   1174 	var url = './';
   1175 
   1176 	url += '?';
   1177 
   1178     if(action)
   1179     	url += '&action='+action;
   1180     if(subaction)
   1181     	url += '&subaction='+subaction;
   1182     if(id)
   1183     	url += '&id='+id;
   1184 
   1185 	if	( typeof extraid === 'string')
   1186 	{
   1187 		extraid = extraid.replace(/'/g,'"'); // Replace ' with ".
   1188 		var extraObject = jQuery.parseJSON(extraid);
   1189 		jQuery.each(extraObject, function(name, value) {
   1190 			url = url + '&' + name + '=' + value;
   1191 		});
   1192 	}
   1193 	else if	( typeof extraid === 'object')
   1194 	{
   1195 		jQuery.each(extraid, function(name, field) {
   1196 			url = url + '&' + name + '=' + field;
   1197 		});
   1198 	}
   1199 	else
   1200 	{
   1201 	}
   1202 	return url;
   1203 }
   1204 
   1205 
   1206 /**
   1207  * Setzt Breite/Höhe für einen Container in der Workbench.
   1208  * 
   1209  * Sind weitere Container enthalten, werden diese rekursiv angepasst.
   1210  * 
   1211  * @param container
   1212  */
   1213 function resizeWorkbenchContainer( container )
   1214 {
   1215 }
   1216 
   1217 
   1218 
   1219 /**
   1220  * Fenstergröße wurde verändert, nun die Größe der DIVs neu berechnen.
   1221  */
   1222 function resizeWorkbench()
   1223 {
   1224 }
   1225 
   1226 
   1227 /**
   1228  * Größe der TABs pro Frame neu berechnen.
   1229  */
   1230 function resizeTabs( panel ) 
   1231 {
   1232 }
   1233 
   1234 
   1235 function help(el,url,suffix)
   1236 {
   1237 	var action = $(el).closest('div.panel').find('li.action.active').attr('data-action');
   1238 	var method = $(el).closest('div.panel').find('li.action.active').attr('data-method');
   1239 	
   1240 	window.open(url + action + '/'+ method + suffix, 'OpenRat_Help', 'location=no,menubar=no,scrollbars=yes,toolbar=no,resizable=yes');
   1241 }
   1242 
   1243 
   1244 /**
   1245  * Show a notice bubble in the UI.
   1246  * @param type
   1247  * @param name
   1248  * @param status
   1249  * @param msg
   1250  * @param log
   1251  */
   1252 function notify( type,name,status,msg,log=[] )
   1253 {
   1254 	// Notice-Bar mit dieser Meldung erweitern.
   1255 
   1256 	let notice = $('<div class="notice '+status+'"></div>');
   1257 
   1258 	let toolbar = $('<div class="or-notice-toolbar"></div>');
   1259 	if   ( log.length )
   1260         $(toolbar).append('<i class="or-action-full image-icon image-icon--menu-fullscreen"></i>');
   1261 	$(toolbar).append('<i class="or-action-close image-icon image-icon--menu-close"></i>');
   1262 	$(notice).append(toolbar);
   1263 
   1264 	id = 0; // TODO
   1265 	if	(name)
   1266 		$(notice).append('<div class="name clickable"><a href="" data-type="open" data-action="'+type+'" data-id="'+id+'"><i class="or-action-full image-icon image-icon--action-'+type+'"></i> '+name+'</a></div>');
   1267 
   1268 	$(notice).append( '<div class="text">'+htmlEntities(msg)+'</div>');
   1269 
   1270 	if (log.length) {
   1271 
   1272         let logLi = log.reduce((result, item) => {
   1273             result += '<li><pre>'+htmlEntities(item)+'</pre></li>';
   1274             return result;
   1275         }, '');
   1276         $(notice).append('<div class="log"><ul>'+logLi+'</ul></div>');
   1277     }
   1278 
   1279 	$('#noticebar').prepend(notice); // Notice anhängen.
   1280     $(notice).orLinkify(); // Enable links
   1281 
   1282 
   1283 	// Toogle Fullscreen for notice
   1284     $(notice).find('.or-action-full').click( function() {
   1285         $(notice).toggleClass('full');
   1286     });
   1287 
   1288     // Close the notice on click
   1289     $(notice).find('.or-action-close').click( function() {
   1290 		$(notice).fadeOut('fast',function() { $(notice).remove(); } );
   1291 	});
   1292 
   1293 	// Fadeout the notice after a while.
   1294 	let timeout = 1;
   1295 	if ( status == 'ok'     ) timeout = 20;
   1296 	if ( status == 'info'   ) timeout = 60;
   1297 	if ( status == 'warning') timeout = 120;
   1298 	if ( status == 'error'  ) timeout = 120;
   1299 
   1300 	if (timeout > 0)
   1301     	setTimeout( function() {
   1302     		$(notice).fadeOut('slow', function() { $(this).remove(); } );
   1303     	},timeout*1000 );
   1304 }
   1305 
   1306 
   1307 function htmlEntities(str) {
   1308     return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
   1309 }
   1310 
   1311 function registerOpenClose( $el )
   1312 {
   1313     $($el).children('.on-click-open-close').click( function() {
   1314         $(this).closest('.toggle-open-close').toggleClass('open closed');
   1315     });
   1316 
   1317 }