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(' '); 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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); 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 }