Subversion Repositories oidplus

Rev

Rev 1408 | View as "text/javascript" | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. /*
  2.  * OIDplus 2.0
  3.  * Copyright 2019 - 2022 Daniel Marschall, ViaThinkSoft
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17.  
  18. /*jshint esversion: 6 */
  19.  
  20. // $('#html').jstree();
  21.  
  22. var bs5Utils = undefined;
  23.  
  24. var current_node = "";
  25. var popstate_running = false;
  26. // DEFAULT_LANGUAGE will be set by oidplus.min.js.php
  27. // language_messages will be set by oidplus.min.js.php
  28. // language_tblprefix will be set by oidplus.min.js.php
  29. // csrf_token will be set by oidplus.min.js.php
  30. // samesite_policy will bet set by oidplus.min.js.php
  31.  
  32. var pageChangeCallbacks = [];
  33. var pageChangeRequestCallbacks = [];
  34.  
  35. var pageLoadedCallbacks= {
  36.         "anyPageLoad":         [], // this is processed inside both AJAX successful reload and document.ready (at the very end)
  37.         "ajaxPageLoad":        [], // inside AJAX successful reload only
  38.         "documentReadyBefore": [], // inside document.ready, in the very beginning of the function
  39.         "documentReadyAfter":  []  // inside document.ready, in the very end of the function
  40. };
  41.  
  42. var oidplus_menu_width = 450; // In pixels. You can change this at runtime because of the glayoutWorkaroundB() workaround
  43.  
  44. function executeAllCallbacks(functionsArray) {
  45.         functionsArray.forEach(
  46.                 function(fel) {
  47.                         if (typeof fel == 'function') fel();
  48.                 }
  49.         );
  50. }
  51.  
  52. function getOidPlusSystemTitle() {
  53.         return getMeta('OIDplus-SystemTitle'); // do not translate
  54. }
  55.  
  56. function combine_systemtitle_and_pagetitle(systemtitle, pagetitle) {
  57.         // Please also change the function in index.php
  58.         if (systemtitle == pagetitle) {
  59.                 return systemtitle;
  60.         } else {
  61.                 return pagetitle + ' - ' + systemtitle;
  62.         }
  63. }
  64.  
  65. function getSystemUrl(relative) {
  66.         relative = (typeof relative === 'undefined') ? false : relative; // do not translate
  67.         var url = new URL(window.location.href);
  68.         var res = relative ? url.pathname : url.href.substr(0, url.href.length-url.search.length);
  69.         if (res.endsWith("index.php")) res = res.substring(0, res.lastIndexOf('/')) + "/";
  70.         return res;
  71. }
  72.  
  73. function getTreeLoadURL() {
  74.         var url = new URL(window.location.href);
  75.         var goto = url.searchParams.get("goto");
  76.         return (goto != null) ? "ajax.php?csrf_token="+encodeURIComponent(csrf_token)+"&action=tree_load&anticache="+Date.now()+"&goto="+encodeURIComponent(goto)
  77.                               : "ajax.php?csrf_token="+encodeURIComponent(csrf_token)+"&action=tree_load&anticache="+Date.now();
  78. }
  79.  
  80. function reloadContent() {
  81.         // window.location.href = "?goto="+encodeURIComponent(current_node);
  82.         if (openOidInPanel(current_node, false)) {
  83.                 if(!$('#oidtree').jstree(true).get_node(current_node)) {
  84.                         // Avoid that a language change at "oidplus:srvreg_status" won't redirect the user to "oidplus:srv_registration" because of the reselection during refresh
  85.                         $('#oidtree').jstree("deselect_all");
  86.                 }
  87.  
  88.                 $('#oidtree').jstree("refresh");
  89.         }
  90. }
  91.  
  92. function x_rec(x_data, i) {
  93.         $('#oidtree').jstree('open_node', x_data[i], function(e, data) {
  94.                 if (i+1 < x_data.length) {
  95.                         x_rec(x_data, i+1);
  96.                 } else {
  97.                         popstate_running = true; // don't call openOidInPanel again
  98.                         try {
  99.                                 $('#oidtree').jstree('select_node', x_data[i]);
  100.                         } catch (err) {
  101.                                 popstate_running = false;
  102.                         } finally {
  103.                                 popstate_running = false;
  104.                         }
  105.                 }
  106.         });
  107. }
  108.  
  109. function performCloseQueryCB() {
  110.         for (var i=0; i<pageChangeRequestCallbacks.length; i++) {
  111.                 if (!pageChangeRequestCallbacks[i][0](pageChangeRequestCallbacks[i][1])) return false;
  112.         }
  113.         pageChangeRequestCallbacks = [];
  114.         return true; // may close
  115. }
  116.  
  117. function performCloseCB() {
  118.         for (var i=0; i<pageChangeCallbacks.length; i++) {
  119.                 pageChangeCallbacks[i][0](pageChangeCallbacks[i][1]);
  120.         }
  121.         pageChangeCallbacks = [];
  122. }
  123.  
  124. function openOidInPanel(id, reselect/*=false*/, anchor/*=''*/, force/*=false*/) {
  125.         reselect = (typeof reselect === 'undefined') ? false : reselect; // do not translate
  126.         anchor = (typeof anchor === 'undefined') ? '' : anchor; // do not translate
  127.         force = (typeof force === 'undefined') ? false : force; // do not translate
  128.  
  129.         var mayClose = performCloseQueryCB();
  130.         if (!force && !mayClose) return false;
  131.  
  132.         performCloseCB();
  133.  
  134.         $.xhrPool.abortAll();
  135.  
  136.         if (reselect) {
  137.                 $('#oidtree').jstree('deselect_all');
  138.  
  139.                 popstate_running = true; // don't call openOidInPanel during tree selection
  140.                 try {
  141.                         // If the node is already loaded in the tree, select it
  142.                         if (!$('#oidtree').jstree('select_node', id)) {
  143.                                 // If the node is not loaded, then we try to search it.
  144.                                 // If it can be found, then open all parent nodes and select the node
  145.                                 $.ajax({
  146.                                         url:"ajax.php",
  147.                                         method:"POST",
  148.                                         beforeSend: function(jqXHR, settings) {
  149.                                                 //$.xhrPool.abortAll();
  150.                                                 $.xhrPool.add(jqXHR);
  151.                                         },
  152.                                         complete: function(jqXHR, text) {
  153.                                                 $.xhrPool.remove(jqXHR);
  154.                                         },
  155.                                         data:{
  156.                                                 csrf_token:csrf_token,
  157.                                                 action:"tree_search",
  158.                                                 search:id,
  159.                                                 anticache:Date.now()
  160.                                         },
  161.                                         error:function(jqXHR, textStatus, errorThrown) {
  162.                                                 if (errorThrown == "abort") return;
  163.                                                 console.error("Tree search failed");
  164.                                                 console.error(_L("Error: %1",errorThrown));
  165.                                         },
  166.                                         success:function(data) {
  167.                                                 if (typeof data === "object" && "error" in data) {
  168.                                                         console.error("Tree search failed");
  169.                                                         console.error(data);
  170.                                                 } else if ((data instanceof Array) && (data.length > 0)) {
  171.                                                         x_rec(data, 0);
  172.                                                 } else {
  173.                                                         console.error("Tree search failed");
  174.                                                         console.error(data);
  175.                                                 }
  176.                                         }
  177.                                 });
  178.                         }
  179.                 } catch (err) {
  180.                         popstate_running = false;
  181.                 } finally {
  182.                         popstate_running = false;
  183.                 }
  184.         }
  185.  
  186.         // This loads the actual content
  187.  
  188.         // document.title = ""; // <-- we may not do this, otherwise Firefox won't
  189.         //                            show titles in the browser history (right-click
  190.         //                            on back-button), although document.title() is
  191.         //                            set inside the AJAX-callback [Firefox bug?!]
  192.  
  193.         $('#real_title').html("&nbsp;");
  194.         $('#real_content').html(_L("Loading..."));
  195.         $('#static_link').attr("href", oidplus_webpath_absolute_canonical+"?goto="+encodeURIComponent(id));
  196.         $("#gotoedit").val(id);
  197.  
  198.         // Normal opening of a description
  199.         $.ajax({
  200.                 url:"ajax.php",
  201.                 method:"GET",
  202.                 beforeSend: function(jqXHR, settings) {
  203.                         //$.xhrPool.abortAll();
  204.                         $.xhrPool.add(jqXHR);
  205.                 },
  206.                 complete: function(jqXHR, text) {
  207.                         $.xhrPool.remove(jqXHR);
  208.                 },
  209.                 data:{
  210.                         csrf_token:csrf_token,
  211.                         action:"get_description",
  212.                         id:id,
  213.                         anticache:Date.now()
  214.                 },
  215.                 error:function(jqXHR, textStatus, errorThrown) {
  216.                         if (errorThrown == "abort") return;
  217.                         alertError(_L("Failed to load content: %1",errorThrown));
  218.                         console.error(_L("Error: %1",errorThrown));
  219.                 },
  220.                 success:function(data) {
  221.                         // TODO: Use oidplus_ajax_success(), since this checks the existance of "error" in data, and checks if status>=0
  222.                         if (typeof data === "object" && "error" in data) {
  223.                                 console.error(data.error);
  224.                                 alertError(_L("Failed to load content: %1",data.error));
  225.                         } else if (typeof data === "object" && "status" in data && data.status >= 0) {
  226.                                 if (!("id" in data)) data.id = id;
  227.  
  228.                                 var state = {
  229.                                         "node_id":data.id,
  230.                                         "titleHTML":(data.icon ? '<img src="'+data.icon+'" width="48" height="48" alt="'+data.title.htmlentities()+'"> ' : '') + data.title.htmlentities(),
  231.                                         "textHTML":data.text,
  232.                                         "staticlinkHREF":oidplus_webpath_absolute_canonical+"?goto="+encodeURIComponent(data.id),
  233.                                 };
  234.                                 if (current_node != data.id) {
  235.                                         window.history.pushState(state, data.title, "?goto="+encodeURIComponent(data.id));
  236.                                 } else {
  237.                                         window.history.replaceState(state, data.title, "?goto="+encodeURIComponent(data.id));
  238.                                 }
  239.  
  240.                                 document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.title);
  241.  
  242.                                 if (data.icon) {
  243.                                         $('#real_title').html('<img src="'+data.icon+'" width="48" height="48" alt="'+data.title.htmlentities()+'"> ' + data.title.htmlentities());
  244.                                 } else {
  245.                                         $('#real_title').html(data.title.htmlentities());
  246.                                 }
  247.                                 $('#real_content').html(data.text);
  248.                                 document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.title);
  249.                                 current_node = data.id;
  250.  
  251.                                 executeAllCallbacks(pageLoadedCallbacks.anyPageLoad);
  252.                                 executeAllCallbacks(pageLoadedCallbacks.ajaxPageLoad);
  253.  
  254.                                 if (anchor != '') {
  255.                                         jumpToAnchor(anchor);
  256.                                 }
  257.                         } else if (typeof data === "object" && "status" in data && data.status < 0) {
  258.                                 console.error(data);
  259.                                 alertError(_L("Failed to load content: %1",data.status));
  260.                         } else {
  261.                                 console.error(data);
  262.                                 alertError(_L("Failed to load content: %1",data));
  263.                         }
  264.                 }
  265.         });
  266.  
  267.         return true;
  268. }
  269.  
  270. // This function opens the "parentID" node, and then selects the "childID" node (which should be beneath the parent node)
  271. function openAndSelectNode(childID, parentID) {
  272.         if ($('#oidtree').jstree(true).get_node(parentID)) {
  273.                 $('#oidtree').jstree('open_node', parentID, function(e, data) { // open parent node
  274.                         if ($('#oidtree').jstree(true).get_node(childID)) { // is the child there?
  275.                                 $('#oidtree').jstree('deselect_all').jstree('select_node', childID); // select it
  276.                         } else {
  277.                                 // This can happen if the content page contains brand new items which are not in the treeview yet
  278.                                 $("#gotoedit").val(childID);
  279.                                 window.location.href = "?goto="+encodeURIComponent(childID);
  280.                         }
  281.                 }, true);
  282.         } else {
  283.                 // This should usually not happen
  284.                 $("#gotoedit").val(childID);
  285.                 window.location.href = "?goto="+encodeURIComponent(childID);
  286.         }
  287. }
  288.  
  289. $(window).on("popstate", function(e) {
  290.         if (!performCloseQueryCB()) {
  291.                 // TODO: does not work!!! The "back/forward" action will be cancelled, but the browser still thinks it was successful,
  292.                 // so if you do it again, you will then jump 2 pages back, etc!
  293.                 // This does also not help:
  294.                 //window.history.pushState(e.originalEvent.state, e.originalEvent.title, e.originalEvent.url);
  295.                 //window.history.forward();
  296.                 return;
  297.         }
  298.  
  299.         popstate_running = true;
  300.         try {
  301.                 var data = e.originalEvent.state;
  302.  
  303.                 current_node = data.node_id;
  304.                 $("#gotoedit").val(current_node);
  305.                 $('#oidtree').jstree('deselect_all').jstree('select_node', data.node_id);
  306.                 $('#real_title').html(data.titleHTML);
  307.                 $('#real_content').html(data.textHTML);
  308.                 $('#static_link').attr("href", data.staticlinkHREF);
  309.                 document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.titleHTML.html_entity_decode());
  310.         } catch (err) {
  311.                 popstate_running = false;
  312.         } finally {
  313.                 popstate_running = false;
  314.         }
  315. });
  316.  
  317. $(document).ready(function () {
  318.  
  319.         executeAllCallbacks(pageLoadedCallbacks.documentReadyBefore);
  320.  
  321.         /*
  322.         window.onbeforeunload = function(e) {
  323.                 // TODO: This won't be called because TinyMCE overrides it??
  324.                 // TODO: when the user accepted the query in performCloseQueryCB(), then the message will be shown again by the browser!
  325.                 if (!performCloseQueryCB()) {
  326.                         // Cancel the event
  327.                         e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
  328.                         // Chrome requires returnValue to be set
  329.                         e.returnValue = '';
  330.                 } else {
  331.                         // the absence of a returnValue property on the event will guarantee the browser unload happens
  332.                         delete e['returnValue'];
  333.                 }
  334.         };
  335.         */
  336.  
  337.         if (typeof oidplus_menu_width_uservalue !== 'undefined') {
  338.                 oidplus_menu_width = oidplus_menu_width_uservalue;
  339.         }
  340.  
  341.         // --- JsTree
  342.  
  343.         if ($('#oidtree').length > 0) $('#oidtree')
  344.         .jstree({
  345.                 plugins: ['massload','search','conditionalselect'],
  346.                 'core' : {
  347.                         'data' : {
  348.                                 "url" : getTreeLoadURL(),
  349.                                 "data" : function (node) {
  350.                                         return { "id" : node.id };
  351.                                 }
  352.                         },
  353.                         "multiple": false
  354.                 },
  355.                 'conditionalselect' : function (node) {
  356.                         if (node.original.conditionalselect !== undefined) {
  357.                                 return eval(node.original.conditionalselect);
  358.                         } else {
  359.                                 return performCloseQueryCB();
  360.                         }
  361.                 },
  362.         })
  363.         .on('ready.jstree', function (e, data) {
  364.                 var url = new URL(window.location.href);
  365.                 var goto = url.searchParams.get("goto");
  366.                 if (goto == null) goto = "oidplus:system"; // the page was not called with ?goto=...
  367.                 if ($('#gotoedit').length > 0) $("#gotoedit").val(goto);
  368.  
  369.                 // By setting current_node, select_node() will not cause ajax.php?action=get_description to load (since we already loaded the first static content via PHP, for search engines mainly)
  370.                 // But then we need to set the history state manually
  371.                 current_node = goto;
  372.                 window.history.replaceState({
  373.                         "node_id":goto,
  374.                         "titleHTML":$('#real_title').html(),
  375.                         "textHTML":$('#real_content').html(),
  376.                         "staticlinkHREF":oidplus_webpath_absolute_canonical+"?goto="+encodeURIComponent(goto),
  377.                 }, $('#real_title').html(), "?goto="+encodeURIComponent(goto));
  378.  
  379.                 if (goto != null) data.instance.select_node([goto]);
  380.  
  381.                 setTimeout(glayoutWorkaroundAC, 100);
  382.                 setTimeout(glayoutWorkaroundB, 100);
  383.         })
  384.         .on('select_node.jstree', function (node, selected, event) {
  385.                 mobileNavClose();
  386.  
  387.                 var id = selected.node.id;
  388.                 if ((!popstate_running) && (current_node != id)) {
  389.                         // 4th argument: we force the reload (because in the
  390.                         // conditional select above, we already asked if
  391.                         // tinyMCE needs to be saved)
  392.                         openOidInPanel(id, false, '', true);
  393.                 }
  394.         });
  395.  
  396.         // --- Layout
  397.  
  398.         if ($('#system_title_menu').length > 0) $("#system_title_menu")[0].style.display = "block";
  399.  
  400.         var tmpObjectTree = _L("OBJECT TREE").replace(/(.{1})/g,"$1<br>");
  401.         tmpObjectTree = tmpObjectTree.substring(0, tmpObjectTree.length-"<br>".length);
  402.  
  403.         if ($('#oidtree').length > 0) $('#oidtree').addClass('ui-layout-west');
  404.         if ($('#content_window').length > 0) $('#content_window').addClass('ui-layout-center');
  405.         if ($('#system_title_bar').length > 0) $('#system_title_bar').addClass('ui-layout-north');
  406.         if ($('#frames').length > 0) glayout = $('#frames').layout({
  407.                 north__size:                  40,
  408.                 north__slidable:              false,
  409.                 north__closable:              false,
  410.                 north__resizable:             false,
  411.                 west__size:                   oidplus_menu_width,
  412.                 west__spacing_closed:         20,
  413.                 west__togglerLength_closed:   230,
  414.                 west__togglerAlign_closed:    "center",
  415.                 west__togglerContent_closed:  tmpObjectTree,
  416.                 west__togglerTip_closed:      _L("Open & Pin Menu"),
  417.                 west__sliderTip:              _L("Slide Open Menu"),
  418.                 west__slideTrigger_open:      "mouseover",
  419.                 west__enableCursorHotkey:     false, // disable Ctrl+Shift+LeftArrow hotkey ( see https://github.com/danielmarschall/oidplus/issues/28 )
  420.                 center__maskContents:         true, // IMPORTANT - enable iframe masking
  421.                 onresize_start:               function() { if (typeof handle_glayout_onresize_start == 'function') handle_glayout_onresize_start(); }
  422.         });
  423.  
  424.         if ($('#gotobox').length == 0) $("#languageBox").css('right', '20px'); // Language Box to the right if there is no goto-box
  425.  
  426.         if ($('#gotobox').length > 0) $("#gotobox").addClass("mobilehidden");
  427.         if ($('#languageBox').length > 0) $("#languageBox").addClass("mobilehidden");
  428.         if ($('#gotobox').length > 0) $("#gotobox")[0].style.display = "block";
  429.         if ($('#gotoedit').length > 0) $('#gotoedit').keypress(function(event) {
  430.                 var keycode = (event.keyCode ? event.keyCode : event.which);
  431.                 if (keycode == '13') {
  432.                         gotoButtonClicked();
  433.                 }
  434.         });
  435.  
  436.         if (typeof Bs5Utils !== "undefined") {
  437.                 Bs5Utils.defaults.toasts.position = 'top-center';
  438.                 Bs5Utils.defaults.toasts.stacking = true;
  439.                 bs5Utils = new Bs5Utils();
  440.         }
  441.  
  442.         executeAllCallbacks(pageLoadedCallbacks.anyPageLoad);
  443.         executeAllCallbacks(pageLoadedCallbacks.documentReadyAfter);
  444. });
  445.  
  446. // can be overridden if necessary
  447. var handle_glayout_onresize_start = undefined;
  448.  
  449. function glayoutWorkaroundAC() {
  450.         // "Bug A": Sometimes, the design is completely destroyed after reloading the page. It does not help when glayout.resizeAll()
  451.         //          is called at the beginning (e.g. during the ready function), and it does not help if we wait 500ms.
  452.         //          So we do it all the time. It has probably something to do with slow loading times, since the error
  453.         //          does only appear when the page is "blank" for a short while while it is loading.
  454.         glayout.resizeAll();
  455.  
  456.         // "Bug C": With Firefox (And sometimes with Chrome), there is a gap between the content-window (including scroll bars)
  457.         //          and the right corner of the screen. Removing the explicit width solves this problem.
  458.         $("#content_window")[0].style.removeProperty("width");
  459.  
  460.         setTimeout(glayoutWorkaroundAC, 100);
  461. }
  462.  
  463. function glayoutWorkaroundB() {
  464.         // "Bug B": Sometimes, after reload, weird space between oidtree and content window, because oidtree has size of 438px
  465.         $("#oidtree")[0].style.width = oidplus_menu_width + "px";
  466. }
  467.  
  468. function mobileNavClose() {
  469.         if ($("#system_title_menu").is(":hidden")) {
  470.                 return;
  471.         }
  472.  
  473.         $("#oidtree").slideUp("medium").promise().done(function() {
  474.                 $("#oidtree").addClass("ui-layout-west");
  475.                 $("#oidtree").show();
  476. //              $("#gotobox").hide();
  477. //              $("#languageBox").hide();
  478.                 $("#gotobox").addClass("mobilehidden");
  479.                 $("#languageBox").addClass("mobilehidden");
  480.         });
  481.         $("#system_title_menu").removeClass("active");
  482. }
  483.  
  484. function mobileNavOpen() {
  485.         $("#oidtree").hide();
  486.         $("#oidtree").removeClass("ui-layout-west");
  487.         $("#oidtree").slideDown("medium");
  488. //      $("#gotobox").show();
  489. //      $("#languageBox").show();
  490.         $("#gotobox").removeClass("mobilehidden");
  491.         $("#languageBox").removeClass("mobilehidden");
  492.         $("#system_title_menu").addClass("active");
  493. }
  494.  
  495. function mobileNavButtonClick(sender) {
  496.         if ($("#oidtree").hasClass("ui-layout-west")) {
  497.                 mobileNavOpen();
  498.         } else {
  499.                 mobileNavClose();
  500.         }
  501. }
  502.  
  503. function mobileNavButtonHover(sender) {
  504.         sender.classList.toggle("hover");
  505. }
  506.  
  507. function gotoButtonClicked() {
  508.         openOidInPanel($("#gotoedit").val(), true);
  509. }
  510.  
  511. function setLanguage(lngid) {
  512.         setCookie('LANGUAGE', lngid, 0/*Until browser closes*/, oidplus_webpath_relative);
  513.  
  514.         if (current_node == "") return false; // Happens for Setup. Open URL instead.
  515.  
  516.         $(".lng_flag").each(function(){
  517.                 $(this).addClass("picture_ghost");
  518.         });
  519.         $("#lng_flag_"+$.escapeSelector(lngid)).removeClass("picture_ghost");
  520.  
  521.         // TODO: Small detail: The "Go" button also needs to be re-translated
  522.         reloadContent();
  523.         mobileNavClose();
  524.  
  525.         return true; // we have handled it. Do not follow href=""
  526. }
  527.  
  528. function show_waiting_anim() {
  529.         $("#loading").show();
  530. }
  531.  
  532. function hide_waiting_anim() {
  533.         $("#loading").hide();
  534. }
  535.  
  536. /* Mini-framework to abort all AJAX requests if a new request is made */
  537.  
  538. $.xhrPool = [];
  539. $.xhrPool.add = function(jqXHR) {
  540.         $.xhrPool.push(jqXHR);
  541. }
  542. $.xhrPool.remove = function(jqXHR) {
  543.         var index = $.xhrPool.indexOf(jqXHR);
  544.         if (index > -1) {
  545.                 $.xhrPool.splice(index, 1);
  546.         }
  547. };
  548. $.xhrPool.abortAll = function() {
  549.         var calls = Array.from($.xhrPool);
  550.         $.each(calls, function(key, value) {
  551.                 value.abort();
  552.         });
  553. }
  554.  
  555. /* Individual alert types */
  556. /* TODO: alert() blocks program flow, but alertSuccess() not! Will the mass change have negative effects (e.g. a redirect without the user seeing the toast)? */
  557.  
  558. function alertSuccess(txt) {
  559.         if (typeof bs5Utils !== "undefined") {
  560.                 bs5Utils.Snack.show('success', _L(txt), delay = 5000, dismissible = true);
  561.         } else {
  562.                 alert(txt);
  563.         }
  564. }
  565.  
  566. function alertWarning(txt) {
  567.         // TODO: as toast?
  568.         alert(txt);
  569. }
  570.  
  571. function alertError(txt) {
  572.         // TODO: as toast?
  573.         alert(txt);
  574. }
  575.  
  576. /* AJAX success/error-handling */
  577.  
  578. function oidplus_ajax_error(jqXHR, textStatus, errorThrown) {
  579.         if (errorThrown == "abort") return;
  580.         console.error(errorThrown, jqXHR);
  581.         alertError(_L("Error: %1", errorThrown));
  582. }
  583.  
  584. function oidplus_ajax_success(data, cb) {
  585.         if (typeof data === "object" && "error" in data) {
  586.                 console.error(data);
  587.                 alertError(_L("Error: %1", data.error));
  588.         } else if (typeof data === "object" && "status" in data && data.status >= 0) {
  589.                 cb(data);
  590.         } else if (typeof data === "object" && "status" in data && data.status < 0) {
  591.                 console.error(data);
  592.                 alertError(_L("Error: %1", data.status));
  593.         } else {
  594.                 console.error(data);
  595.                 alertError(_L("Error: %1", data));
  596.         }
  597. }
  598.