Subversion Repositories oidplus

Rev

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