Subversion Repositories oidplus

Rev

Rev 699 | Rev 781 | Go to most recent revision | View as "text/javascript" | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. /*
  2.  * OIDplus 2.0
  3.  * Copyright 2019 - 2021 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 current_node = "";
  23. var popstate_running = false;
  24. // DEFAULT_LANGUAGE will be set by oidplus.min.js.php
  25. // language_messages will be set by oidplus.min.js.php
  26. // language_tblprefix will be set by oidplus.min.js.php
  27. // csrf_token will be set by oidplus.min.js.php
  28. // samesite_policy will bet set by oidplus.min.js.php
  29.  
  30. var pageChangeCallbacks = [];
  31. var pageChangeRequestCallbacks = [];
  32.  
  33. function isInternetExplorer() {
  34.         // see also includes/functions.inc.php
  35.         var ua = window.navigator.userAgent;
  36.         return ((ua.indexOf("MSIE ") > 0) || (ua.indexOf("Trident/") > 0));
  37. }
  38.  
  39. String.prototype.explode = function (separator, limit) {
  40.         // https://stackoverflow.com/questions/4514323/javascript-equivalent-to-php-explode
  41.         const array = this.split(separator);
  42.         if (limit !== undefined && array.length >= limit) {
  43.                 array.push(array.splice(limit - 1).join(separator));
  44.         }
  45.         return array;
  46. };
  47.  
  48. String.prototype.htmlentities = function () {
  49.         return this.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');//"
  50. };
  51.  
  52. String.prototype.html_entity_decode = function () {
  53.         return $('<textarea />').html(this).text();
  54. };
  55.  
  56. function getMeta(metaName) {
  57.         const metas = $('meta[name='+metaName+']');
  58.         return (metas.length == 0) ? '' : metas[0].content;
  59. }
  60.  
  61. function getOidPlusSystemTitle() {
  62.         return getMeta('OIDplus-SystemTitle'); // do not translate
  63. }
  64.  
  65. function combine_systemtitle_and_pagetitle(systemtitle, pagetitle) {
  66.         // Please also change the function in index.php
  67.         if (systemtitle == pagetitle) {
  68.                 return systemtitle;
  69.         } else {
  70.                 return pagetitle + ' - ' + systemtitle;
  71.         }
  72. }
  73.  
  74. function getSystemUrl(relative) {
  75.         var url = new URL(window.location.href);
  76.         if (relative) {
  77.                 return url.pathname;
  78.         } else {
  79.                 return url.href.substr(0, url.href.length-url.search.length);
  80.         }
  81. }
  82.  
  83. function getTreeLoadURL() {
  84.         var url = new URL(window.location.href);
  85.         var goto = url.searchParams.get("goto");
  86.         return (goto != null) ? "ajax.php?csrf_token="+encodeURIComponent(csrf_token)+"&action=tree_load&anticache="+Date.now()+"&goto="+encodeURIComponent(goto)
  87.                               : "ajax.php?csrf_token="+encodeURIComponent(csrf_token)+"&action=tree_load&anticache="+Date.now();
  88. }
  89.  
  90. function reloadContent() {
  91.         // window.location.href = "?goto="+encodeURIComponent(current_node);
  92.         if (openOidInPanel(current_node, false)) {
  93.                 if(!$('#oidtree').jstree(true).get_node(current_node)) {
  94.                         // Avoid that a language change at "oidplus:srvreg_status" won't redirect the user to "oidplus:srv_registration" because of the reselection during refresh
  95.                         $('#oidtree').jstree("deselect_all");
  96.                 }
  97.  
  98.                 $('#oidtree').jstree("refresh");
  99.         }
  100. }
  101.  
  102. function x_rec(x_data, i) {
  103.         $('#oidtree').jstree('open_node', x_data[i], function(e, data) {
  104.                 if (i+1 < x_data.length) {
  105.                         x_rec(x_data, i+1);
  106.                 } else {
  107.                         popstate_running = true; // don't call openOidInPanel again
  108.                         try {
  109.                                 $('#oidtree').jstree('select_node', x_data[i]);
  110.                         } catch (err) {
  111.                                 popstate_running = false;
  112.                         } finally {
  113.                                 popstate_running = false;
  114.                         }
  115.                 }
  116.         });
  117. }
  118.  
  119. function performCloseQueryCB() {
  120.         for (var i=0; i<pageChangeRequestCallbacks.length; i++) {
  121.                 if (!pageChangeRequestCallbacks[i][0](pageChangeRequestCallbacks[i][1])) return false;
  122.         }
  123.         pageChangeRequestCallbacks = [];
  124.         return true; // may close
  125. }
  126.  
  127. function performCloseCB() {
  128.         for (var i=0; i<pageChangeCallbacks.length; i++) {
  129.                 pageChangeCallbacks[i][0](pageChangeCallbacks[i][1]);
  130.         }
  131.         pageChangeCallbacks = [];
  132. }
  133.  
  134. function openOidInPanel(id, reselect/*=false*/, anchor/*=''*/, force/*=false*/) {
  135.         reselect = (typeof reselect === 'undefined') ? false : reselect; // do not translate
  136.         anchor = (typeof anchor === 'undefined') ? '' : anchor; // do not translate
  137.         force = (typeof force === 'undefined') ? false : force; // do not translate
  138.  
  139.         var mayClose = performCloseQueryCB();
  140.         if (!force && !mayClose) return false;
  141.  
  142.         performCloseCB();
  143.  
  144.         $.xhrPool.abortAll();
  145.  
  146.         if (reselect) {
  147.                 $('#oidtree').jstree('deselect_all');
  148.  
  149.                 popstate_running = true; // don't call openOidInPanel during tree selection
  150.                 try {
  151.                         // If the node is already loaded in the tree, select it
  152.                         if (!$('#oidtree').jstree('select_node', id)) {
  153.                                 // If the node is not loaded, then we try to search it.
  154.                                 // If it can be found, then open all parent nodes and select the node
  155.                                 $.ajax({
  156.                                         url:"ajax.php",
  157.                                         method:"POST",
  158.                                         beforeSend: function(jqXHR, settings) {
  159.                                                 //$.xhrPool.abortAll();
  160.                                                 $.xhrPool.add(jqXHR);
  161.                                         },
  162.                                         complete: function(jqXHR, text) {
  163.                                                 $.xhrPool.remove(jqXHR);
  164.                                         },
  165.                                         data:{
  166.                                                 csrf_token:csrf_token,
  167.                                                 action:"tree_search",
  168.                                                 search:id,
  169.                                                 anticache:Date.now()
  170.                                         },
  171.                                         error:function(jqXHR, textStatus, errorThrown) {
  172.                                                 if (errorThrown == "abort") return;
  173.                                                 console.error(_L("Error: %1",errorThrown));
  174.                                         },
  175.                                         success:function(data) {
  176.                                                 if ("error" in data) {
  177.                                                         console.error(data);
  178.                                                 } else if ((data instanceof Array) && (data.length > 0)) {
  179.                                                         x_rec(data, 0);
  180.                                                 } else {
  181.                                                         console.error(data);
  182.                                                 }
  183.                                         }
  184.                                 });
  185.                         }
  186.                 } catch (err) {
  187.                         popstate_running = false;
  188.                 } finally {
  189.                         popstate_running = false;
  190.                 }
  191.         }
  192.  
  193.         // This loads the actual content
  194.  
  195.         // document.title = ""; // <-- we may not do this, otherwise Firefox won't
  196.         //                            show titles in the browser history (right-click
  197.         //                            on back-button), although document.title() is
  198.         //                            set inside the AJAX-callback [Firefox bug?!]
  199.  
  200.         $('#real_title').html("&nbsp;");
  201.         $('#real_content').html(_L("Loading..."));
  202.         $('#static_link').attr("href", "index.php?goto="+encodeURIComponent(id));
  203.         $("#gotoedit").val(id);
  204.  
  205.         // Normal opening of a description
  206.         $.ajax({
  207.                 url:"ajax.php",
  208.                 method:"GET",
  209.                 beforeSend: function(jqXHR, settings) {
  210.                         //$.xhrPool.abortAll();
  211.                         $.xhrPool.add(jqXHR);
  212.                 },
  213.                 complete: function(jqXHR, text) {
  214.                         $.xhrPool.remove(jqXHR);
  215.                 },
  216.                 data:{
  217.                         csrf_token:csrf_token,
  218.                         action:"get_description",
  219.                         id:id,
  220.                         anticache:Date.now()
  221.                 },
  222.                 error:function(jqXHR, textStatus, errorThrown) {
  223.                         if (errorThrown == "abort") return;
  224.                         alert(_L("Failed to load content: %1",errorThrown));
  225.                         console.error(_L("Error: %1",errorThrown));
  226.                 },
  227.                 success:function(data) {
  228.                         if ("error" in data) {
  229.                                 alert(_L("Failed to load content: %1",data.error));
  230.                                 console.error(data.error);
  231.                         } else if (data.status >= 0) {
  232.                                 data.id = id;
  233.  
  234.                                 var state = {
  235.                                         "node_id":id,
  236.                                         "titleHTML":(data.icon ? '<img src="'+data.icon+'" width="48" height="48" alt="'+data.title.htmlentities()+'"> ' : '') + data.title.htmlentities(),
  237.                                         "textHTML":data.text,
  238.                                         "staticlinkHREF":"index.php?goto="+encodeURIComponent(id),
  239.                                 };
  240.                                 if (current_node != id) {
  241.                                         window.history.pushState(state, data.title, "?goto="+encodeURIComponent(id));
  242.                                 } else {
  243.                                         window.history.replaceState(state, data.title, "?goto="+encodeURIComponent(id));
  244.                                 }
  245.  
  246.                                 document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.title);
  247.  
  248.                                 if (data.icon) {
  249.                                         $('#real_title').html('<img src="'+data.icon+'" width="48" height="48" alt="'+data.title.htmlentities()+'"> ' + data.title.htmlentities());
  250.                                 } else {
  251.                                         $('#real_title').html(data.title.htmlentities());
  252.                                 }
  253.                                 $('#real_content').html(data.text);
  254.                                 document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.title);
  255.                                 current_node = id;
  256.  
  257.                                 if (anchor != '') {
  258.                                         jumpToAnchor(anchor);
  259.                                 }
  260.                         } else {
  261.                                 alert(_L("Failed to load content: %1",data.status));
  262.                                 console.error(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.         window.onbeforeunload = function(e) {
  320.                 // TODO: This won't be called because TinyMCE overrides it??
  321.                 // TODO: when the user accepted the query in performCloseQueryCB(), then the message will be shown again by the browser!
  322.                 if (!performCloseQueryCB()) {
  323.                         // Cancel the event
  324.                         e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
  325.                         // Chrome requires returnValue to be set
  326.                         e.returnValue = '';
  327.                 } else {
  328.                         // the absence of a returnValue property on the event will guarantee the browser unload happens
  329.                         delete e['returnValue'];
  330.                 }
  331.         };
  332.         */
  333.  
  334.         // --- JsTree
  335.  
  336.         $('#oidtree')
  337.         .jstree({
  338.                 plugins: ['massload','search','conditionalselect'],
  339.                 'core' : {
  340.                         'data' : {
  341.                                 "url" : getTreeLoadURL(),
  342.                                 "data" : function (node) {
  343.                                         return { "id" : node.id };
  344.                                 }
  345.                         },
  346.                         "multiple": false
  347.                 },
  348.                 'conditionalselect' : function (node) {
  349.                         if (node.original.conditionalselect !== undefined) {
  350.                                 return eval(node.original.conditionalselect);
  351.                         } else {
  352.                                 return performCloseQueryCB();
  353.                         }
  354.                 },
  355.         })
  356.         .on('ready.jstree', function (e, data) {
  357.                 var url = new URL(window.location.href);
  358.                 var goto = url.searchParams.get("goto");
  359.                 if (goto == null) goto = "oidplus:system"; // the page was not called with ?goto=...
  360.                 $("#gotoedit").val(goto);
  361.  
  362.                 // 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)
  363.                 // But then we need to set the history state manually
  364.                 current_node = goto;
  365.                 window.history.replaceState({
  366.                         "node_id":goto,
  367.                         "titleHTML":$('#real_title').html(),
  368.                         "textHTML":$('#real_content').html(),
  369.                         "staticlinkHREF":"index.php?goto="+encodeURIComponent(goto),
  370.                 }, $('#real_title').html(), "?goto="+encodeURIComponent(goto));
  371.  
  372.                 if (goto != null) data.instance.select_node([goto]);
  373.  
  374.                 setTimeout(glayoutWorkaroundAC, 100);
  375.                 setTimeout(glayoutWorkaroundB, 100);
  376.         })
  377.         .on('select_node.jstree', function (node, selected, event) {
  378.                 mobileNavClose();
  379.  
  380.                 var id = selected.node.id;
  381.                 if ((!popstate_running) && (current_node != id)) {
  382.                         // 4th argument: we force the reload (because in the
  383.                         // conditional select above, we already asked if
  384.                         // tinyMCE needs to be saved)
  385.                         openOidInPanel(id, false, '', true);
  386.                 }
  387.         });
  388.  
  389.         // --- Layout
  390.  
  391.         $("#system_title_menu")[0].style.display = "block";
  392.  
  393.         var tmpObjectTree = _L("OBJECT TREE").replace(/(.{1})/g,"$1<br>");
  394.         tmpObjectTree = tmpObjectTree.substring(0, tmpObjectTree.length-"<br>".length);
  395.  
  396.         $('#oidtree').addClass('ui-layout-west');
  397.         $('#content_window').addClass('ui-layout-center');
  398.         $('#system_title_bar').addClass('ui-layout-north');
  399.         glayout = $('#frames').layout({
  400.                 north__size:                  40,
  401.                 north__slidable:              false,
  402.                 north__closable:              false,
  403.                 north__resizable:             false,
  404.                 west__size:                   450,
  405.                 west__spacing_closed:         20,
  406.                 west__togglerLength_closed:   230,
  407.                 west__togglerAlign_closed:    "center", // TODO: does not work! The text "OBJECT TREE" is still at the top and nearly cut off by the title bar!!!
  408.                 west__togglerContent_closed:  tmpObjectTree,
  409.                 west__togglerTip_closed:      _L("Open & Pin Menu"),
  410.                 west__sliderTip:              _L("Slide Open Menu"),
  411.                 west__slideTrigger_open:      "mouseover",
  412.                 center__maskContents:         true // IMPORTANT - enable iframe masking
  413.         });
  414.  
  415.         $("#gotobox").addClass("mobilehidden");
  416.         $("#languageBox").addClass("mobilehidden");
  417.         $("#gotobox")[0].style.display = "block";
  418.         $('#gotoedit').keypress(function(event) {
  419.                 var keycode = (event.keyCode ? event.keyCode : event.which);
  420.                 if (keycode == '13') {
  421.                         gotoButtonClicked();
  422.                 }
  423.         });
  424. });
  425.  
  426. function glayoutWorkaroundAC() {
  427.         // "Bug A": Sometimes, the design is completely destroyed after reloading the page. It does not help when glayout.resizeAll()
  428.         //          is called at the beginning (e.g. during the ready function), and it does not help if we wait 500ms.
  429.         //          So we do it all the time. It has probably something to do with slow loading times, since the error
  430.         //          does only appear when the page is "blank" for a short while while it is loading.
  431.         glayout.resizeAll();
  432.  
  433.         // "Bug C": With Firefox (And sometimes with Chrome), there is a gap between the content-window (including scroll bars)
  434.         //          and the right corner of the screen. Removing the explicit width solves this problem.
  435.         $("#content_window")[0].style.removeProperty("width");
  436.  
  437.         setTimeout(glayoutWorkaroundAC, 100);
  438. }
  439.  
  440. function glayoutWorkaroundB() {
  441.         // "Bug B": Sometimes, after reload, weird space between oidtree and content window, because oidtree has size of 438px
  442.         $("#oidtree")[0].style.width = "450px";
  443. }
  444.  
  445. function mobileNavClose() {
  446.         if ($("#system_title_menu").is(":hidden")) {
  447.                 return;
  448.         }
  449.  
  450.         $("#oidtree").slideUp("medium").promise().done(function() {
  451.                 $("#oidtree").addClass("ui-layout-west");
  452.                 $("#oidtree").show();
  453. //              $("#gotobox").hide();
  454. //              $("#languageBox").hide();
  455.                 $("#gotobox").addClass("mobilehidden");
  456.                 $("#languageBox").addClass("mobilehidden");
  457.         });
  458.         $("#system_title_menu").removeClass("active");
  459. }
  460.  
  461. function mobileNavOpen() {
  462.         $("#oidtree").hide();
  463.         $("#oidtree").removeClass("ui-layout-west");
  464.         $("#oidtree").slideDown("medium");
  465. //      $("#gotobox").show();
  466. //      $("#languageBox").show();
  467.         $("#gotobox").removeClass("mobilehidden");
  468.         $("#languageBox").removeClass("mobilehidden");
  469.         $("#system_title_menu").addClass("active");
  470. }
  471.  
  472. function mobileNavButtonClick(sender) {
  473.         if ($("#oidtree").hasClass("ui-layout-west")) {
  474.                 mobileNavOpen();
  475.         } else {
  476.                 mobileNavClose();
  477.         }
  478. }
  479.  
  480. function mobileNavButtonHover(sender) {
  481.         sender.classList.toggle("hover");
  482. }
  483.  
  484. function gotoButtonClicked() {
  485.         openOidInPanel($("#gotoedit").val(), true);
  486. }
  487.  
  488. function jumpToAnchor(anchor) {
  489.         window.location.href = "#" + anchor;
  490. }
  491.  
  492. function getCookie(cname) {
  493.         // Source: https://www.w3schools.com/js/js_cookies.asp
  494.         var name = cname + "=";
  495.         var decodedCookie = decodeURIComponent(document.cookie);
  496.         var ca = decodedCookie.split(';');
  497.         for(var i = 0; i <ca.length; i++) {
  498.                 var c = ca[i];
  499.                 while (c.charAt(0) == ' ') {
  500.                         c = c.substring(1);
  501.                 }
  502.                 if (c.indexOf(name) == 0) {
  503.                         return c.substring(name.length, c.length);
  504.                 }
  505.         }
  506.         return undefined;
  507. }
  508.  
  509. function setCookie(cname, cvalue, exdays, path) {
  510.         var d = new Date();
  511.         d.setTime(d.getTime() + (exdays*24*60*60*1000));
  512.         var expires = exdays == 0 ? "" : "; expires="+d.toUTCString();
  513.         document.cookie = cname + "=" + cvalue + expires + ";path=" + path + ";SameSite=" + samesite_policy;
  514. }
  515.  
  516. function setLanguage(lngid) {
  517.         setCookie('LANGUAGE', lngid, 0/*Until browser closes*/, location.pathname);
  518.  
  519.         $(".lng_flag").each(function(){
  520.                 $(this).addClass("picture_ghost");
  521.         });
  522.         $("#lng_flag_"+$.escapeSelector(lngid)).removeClass("picture_ghost");
  523.  
  524.         if (isInternetExplorer()) {
  525.                 // Internet Explorer has problems with sending new cookies to new AJAX requests, so we reload the page completely
  526.                 window.location.reload();
  527.         } else {
  528.                 // TODO: Small detail: The "Go" button also needs to be re-translated
  529.                 reloadContent();
  530.                 mobileNavClose();
  531.         }
  532. }
  533.  
  534. function getCurrentLang() {
  535.         // Note: If the argument "?lang=" is used, PHP will automatically set a Cookie, so it is OK when we only check for the cookie
  536.         var lang = getCookie('LANGUAGE'); // do not translate
  537.         return (typeof lang != 'undefined') ? lang : DEFAULT_LANGUAGE; // do not translate
  538. }
  539.  
  540. function _L() {
  541.         var args = Array.prototype.slice.call(arguments);
  542.         var str = args.shift().trim();
  543.  
  544.         var tmp = "";
  545.         if (typeof language_messages[getCurrentLang()] == 'undefined') { // do not translate
  546.                 tmp = str;
  547.         } else {
  548.                 var msg = language_messages[getCurrentLang()][str];
  549.                 if (typeof msg != 'undefined') { // do not translate
  550.                         tmp = msg;
  551.                 } else {
  552.                         tmp = str;
  553.                 }
  554.         }
  555.  
  556.         tmp = tmp.replace('###', language_tblprefix);
  557.  
  558.         var n = 1;
  559.         while (args.length > 0) {
  560.                 var val = args.shift();
  561.                 tmp = tmp.replace("%"+n, val);
  562.                 n++;
  563.         }
  564.  
  565.         tmp = tmp.replace("%%", "%");
  566.  
  567.         return tmp;
  568. }
  569.  
  570. function show_waiting_anim() {
  571.         $("#loading").show();
  572. }
  573.  
  574. function hide_waiting_anim() {
  575.         $("#loading").hide();
  576. }
  577.  
  578. /* Mini-framework to abort all AJAX requests if a new request is made */
  579.  
  580. $.xhrPool = [];
  581. $.xhrPool.add = function(jqXHR) {
  582.         $.xhrPool.push(jqXHR);
  583. }
  584. $.xhrPool.remove = function(jqXHR) {
  585.         var index = $.xhrPool.indexOf(jqXHR);
  586.         if (index > -1) {
  587.                 $.xhrPool.splice(index, 1);
  588.         }
  589. };
  590. $.xhrPool.abortAll = function() {
  591.         var calls = Array.from($.xhrPool);
  592.         $.each(calls, function(key, value) {
  593.                 value.abort();
  594.         });
  595. }
  596.