Subversion Repositories oidplus

Rev

Rev 399 | Go to most recent revision | View as "text/javascript" | Blame | Last modification | View Log | RSS feed

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