12ebb07b4SGreg Roach/** 22ebb07b4SGreg Roach * webtrees: online genealogy 3d11be702SGreg Roach * Copyright (C) 2023 webtrees development team 42ebb07b4SGreg Roach * This program is free software: you can redistribute it and/or modify 52ebb07b4SGreg Roach * it under the terms of the GNU General Public License as published by 62ebb07b4SGreg Roach * the Free Software Foundation, either version 3 of the License, or 72ebb07b4SGreg Roach * (at your option) any later version. 82ebb07b4SGreg Roach * This program is distributed in the hope that it will be useful, 92ebb07b4SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 102ebb07b4SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 112ebb07b4SGreg Roach * GNU General Public License for more details. 122ebb07b4SGreg Roach * You should have received a copy of the GNU General Public License 132ebb07b4SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>. 142ebb07b4SGreg Roach */ 152ebb07b4SGreg Roach 162ebb07b4SGreg Roachfunction TreeViewHandler (treeview_instance, ged) { 172ebb07b4SGreg Roach var tv = this; // Store "this" for usage within jQuery functions where "this" is not this ;-) 182ebb07b4SGreg Roach 19efd89170SGreg Roach this.treeview = $('#' + treeview_instance + '_in'); 20efd89170SGreg Roach this.loadingImage = $('#' + treeview_instance + '_loading'); 21efd89170SGreg Roach this.toolbox = $('#tv_tools'); 22efd89170SGreg Roach this.buttons = $('.tv_button:first', this.toolbox); 232ebb07b4SGreg Roach this.zoom = 100; // in percent 242ebb07b4SGreg Roach this.boxWidth = 180; // default family box width 252ebb07b4SGreg Roach this.boxExpandedWidth = 250; // default expanded family box width 262ebb07b4SGreg Roach this.cookieDays = 3; // lifetime of preferences memory, in days 27efd89170SGreg Roach this.ajaxDetails = document.getElementById(treeview_instance + '_out').dataset.urlDetails + '&instance=' + encodeURIComponent(treeview_instance); 28efd89170SGreg Roach this.ajaxPersons = document.getElementById(treeview_instance + '_out').dataset.urlIndividuals + '&instance=' + encodeURIComponent(treeview_instance); 292ebb07b4SGreg Roach 302ebb07b4SGreg Roach this.container = this.treeview.parent(); // Store the container element ("#" + treeview_instance + "_out") 312ebb07b4SGreg Roach this.auto_box_width = false; 322ebb07b4SGreg Roach this.updating = false; 332ebb07b4SGreg Roach 342ebb07b4SGreg Roach // Restore user preferences 35efd89170SGreg Roach if (readCookie('compact') === 'true') { 362ebb07b4SGreg Roach tv.compact(); 372ebb07b4SGreg Roach } 382ebb07b4SGreg Roach 3942584a2bSGreg Roach // Drag handlers for the treeview canvas 4042584a2bSGreg Roach (function () { 4142584a2bSGreg Roach let dragging = false; 42*b53fbe6aSFranz Frese let isDown = false; 4342584a2bSGreg Roach let drag_start_x; 4442584a2bSGreg Roach let drag_start_y; 452ebb07b4SGreg Roach 4642584a2bSGreg Roach tv.treeview.on('mousedown touchstart', function (event) { 472ebb07b4SGreg Roach 4842584a2bSGreg Roach let pageX = (event.type === 'touchstart') ? event.touches[0].pageX : event.pageX; 4942584a2bSGreg Roach let pageY = (event.type === 'touchstart') ? event.touches[0].pageY : event.pageY; 502ebb07b4SGreg Roach 5142584a2bSGreg Roach drag_start_x = tv.treeview.offset().left - pageX; 5242584a2bSGreg Roach drag_start_y = tv.treeview.offset().top - pageY; 53*b53fbe6aSFranz Frese isDown = true; 542ebb07b4SGreg Roach }); 5542584a2bSGreg Roach 5642584a2bSGreg Roach $(document).on('mousemove touchmove', function (event) { 57*b53fbe6aSFranz Frese if (isDown) { 5842584a2bSGreg Roach event.preventDefault(); 59*b53fbe6aSFranz Frese dragging = true; 6042584a2bSGreg Roach 6142584a2bSGreg Roach let pageX = (event.type === 'touchmove') ? event.touches[0].pageX : event.pageX; 6242584a2bSGreg Roach let pageY = (event.type === 'touchmove') ? event.touches[0].pageY : event.pageY; 6342584a2bSGreg Roach 6442584a2bSGreg Roach tv.treeview.offset({ 6542584a2bSGreg Roach left: pageX + drag_start_x, 6642584a2bSGreg Roach top: pageY + drag_start_y, 6742584a2bSGreg Roach }); 6842584a2bSGreg Roach } 6942584a2bSGreg Roach }); 7042584a2bSGreg Roach 7142584a2bSGreg Roach $(document).on('mouseup touchend', function (event) { 72*b53fbe6aSFranz Frese isDown = false; 7342584a2bSGreg Roach if (dragging) { 7442584a2bSGreg Roach event.preventDefault(); 7542584a2bSGreg Roach dragging = false; 762ebb07b4SGreg Roach tv.updateTree(); 7742584a2bSGreg Roach } 782ebb07b4SGreg Roach }); 7942584a2bSGreg Roach })(); 802ebb07b4SGreg Roach 812ebb07b4SGreg Roach // Add click handlers to buttons 82efd89170SGreg Roach tv.toolbox.find('#tvbCompact').each(function (index, tvCompact) { 832ebb07b4SGreg Roach tvCompact.onclick = function () { 842ebb07b4SGreg Roach tv.compact(); 852ebb07b4SGreg Roach }; 862ebb07b4SGreg Roach }); 872ebb07b4SGreg Roach // If we click the "hide/show all partners" button, toggle the setting before reloading the page 88efd89170SGreg Roach tv.toolbox.find('#tvbAllPartners').each(function (index, tvAllPartners) { 892ebb07b4SGreg Roach tvAllPartners.onclick = function () { 90efd89170SGreg Roach createCookie('allPartners', readCookie('allPartners') === 'true' ? 'false' : 'true', tv.cookieDays); 912ebb07b4SGreg Roach document.location = document.location; 922ebb07b4SGreg Roach }; 932ebb07b4SGreg Roach }); 94efd89170SGreg Roach tv.toolbox.find('#tvbOpen').each(function (index, tvbOpen) { 952ebb07b4SGreg Roach var b = $(tvbOpen, tv.toolbox); 962ebb07b4SGreg Roach tvbOpen.onclick = function () { 97efd89170SGreg Roach b.addClass('tvPressed'); 982ebb07b4SGreg Roach tv.setLoading(); 99efd89170SGreg Roach var e = jQuery.Event('click'); 100efd89170SGreg Roach tv.treeview.find('.tv_box:not(.boxExpanded)').each(function (index, box) { 1012ebb07b4SGreg Roach var pos = $(box, tv.treeview).offset(); 1022ebb07b4SGreg Roach if (pos.left >= tv.leftMin && pos.left <= tv.leftMax && pos.top >= tv.topMin && pos.top <= tv.topMax) { 1032ebb07b4SGreg Roach tv.expandBox(box, e); 1042ebb07b4SGreg Roach } 1052ebb07b4SGreg Roach }); 106efd89170SGreg Roach b.removeClass('tvPressed'); 1072ebb07b4SGreg Roach tv.setComplete(); 1082ebb07b4SGreg Roach }; 1092ebb07b4SGreg Roach }); 110efd89170SGreg Roach tv.toolbox.find('#tvbClose').each(function (index, tvbClose) { 1112ebb07b4SGreg Roach var b = $(tvbClose, tv.toolbox); 1122ebb07b4SGreg Roach tvbClose.onclick = function () { 113efd89170SGreg Roach b.addClass('tvPressed'); 1142ebb07b4SGreg Roach tv.setLoading(); 115efd89170SGreg Roach tv.treeview.find('.tv_box.boxExpanded').each(function (index, box) { 116efd89170SGreg Roach $(box).css('display', 'none').removeClass('boxExpanded').parent().find('.tv_box.collapsedContent').css('display', 'block'); 1172ebb07b4SGreg Roach }); 118efd89170SGreg Roach b.removeClass('tvPressed'); 1192ebb07b4SGreg Roach tv.setComplete(); 1202ebb07b4SGreg Roach }; 1212ebb07b4SGreg Roach }); 1222ebb07b4SGreg Roach 1232ebb07b4SGreg Roach tv.centerOnRoot(); // fire ajax update if needed, which call setComplete() when all is loaded 1242ebb07b4SGreg Roach} 1252ebb07b4SGreg Roach/** 1262ebb07b4SGreg Roach * Class TreeView setLoading method 1272ebb07b4SGreg Roach */ 1282ebb07b4SGreg RoachTreeViewHandler.prototype.setLoading = function () { 129efd89170SGreg Roach this.treeview.css('cursor', 'wait'); 130efd89170SGreg Roach this.loadingImage.css('display', 'block'); 1312ebb07b4SGreg Roach}; 1322ebb07b4SGreg Roach/** 1332ebb07b4SGreg Roach * Class TreeView setComplete method 1342ebb07b4SGreg Roach */ 1352ebb07b4SGreg RoachTreeViewHandler.prototype.setComplete = function () { 136efd89170SGreg Roach this.treeview.css('cursor', 'move'); 137efd89170SGreg Roach this.loadingImage.css('display', 'none'); 1382ebb07b4SGreg Roach}; 1392ebb07b4SGreg Roach 1402ebb07b4SGreg Roach/** 1412ebb07b4SGreg Roach * Class TreeView getSize method 1422ebb07b4SGreg Roach * Store the viewport current size 1432ebb07b4SGreg Roach */ 1442ebb07b4SGreg RoachTreeViewHandler.prototype.getSize = function () { 1452ebb07b4SGreg Roach var tv = this; 1462ebb07b4SGreg Roach // retrieve the current container bounding box 1472ebb07b4SGreg Roach var container = tv.container.parent(); 1482ebb07b4SGreg Roach var offset = container.offset(); 1492ebb07b4SGreg Roach tv.leftMin = offset.left; 1502ebb07b4SGreg Roach tv.leftMax = tv.leftMin + container.innerWidth(); 1512ebb07b4SGreg Roach tv.topMin = offset.top; 1522ebb07b4SGreg Roach tv.topMax = tv.topMin + container.innerHeight(); 1532ebb07b4SGreg Roach /* 1542ebb07b4SGreg Roach var frm = $("#tvTreeBorder"); 1552ebb07b4SGreg Roach tv.treeview.css("width", frm.width()); 1562ebb07b4SGreg Roach tv.treeview.css("height", frm.height()); */ 1572ebb07b4SGreg Roach}; 1582ebb07b4SGreg Roach 1592ebb07b4SGreg Roach/** 1602ebb07b4SGreg Roach * Class TreeView updateTree method 1612ebb07b4SGreg Roach * Perform ajax requests to complete the tree after drag 1622ebb07b4SGreg Roach * param boolean @center center on root person when done 1632ebb07b4SGreg Roach */ 1642ebb07b4SGreg RoachTreeViewHandler.prototype.updateTree = function (center, button) { 1652ebb07b4SGreg Roach var tv = this; // Store "this" for usage within jQuery functions where "this" is not this ;-) 1662ebb07b4SGreg Roach var to_load = []; 1672ebb07b4SGreg Roach var elts = []; 1682ebb07b4SGreg Roach this.getSize(); 1692ebb07b4SGreg Roach 1702ebb07b4SGreg Roach // check which td with datafld attribute are within the container bounding box 1712ebb07b4SGreg Roach // and therefore need to be dynamically loaded 172efd89170SGreg Roach tv.treeview.find('td[abbr]').each(function (index, el) { 1732ebb07b4SGreg Roach el = $(el, tv.treeview); 1742ebb07b4SGreg Roach var pos = el.offset(); 1752ebb07b4SGreg Roach if (pos.left >= tv.leftMin && pos.left <= tv.leftMax && pos.top >= tv.topMin && pos.top <= tv.topMax) { 176efd89170SGreg Roach to_load.push(el.attr('abbr')); 1772ebb07b4SGreg Roach elts.push(el); 1782ebb07b4SGreg Roach } 1792ebb07b4SGreg Roach }); 1802ebb07b4SGreg Roach // if some boxes need update, we perform an ajax request 1812ebb07b4SGreg Roach if (to_load.length > 0) { 1822ebb07b4SGreg Roach tv.updating = true; 1832ebb07b4SGreg Roach tv.setLoading(); 1842ebb07b4SGreg Roach jQuery.ajax({ 18506f42609SGreg Roach url: tv.ajaxPersons, 186efd89170SGreg Roach dataType: 'json', 187efd89170SGreg Roach data: 'q=' + to_load.join(';'), 1882ebb07b4SGreg Roach success: function (ret) { 1892ebb07b4SGreg Roach var nb = elts.length; 190efd89170SGreg Roach var root_element = $('.rootPerson', this.treeview); 1912ebb07b4SGreg Roach var l = root_element.offset().left; 1922ebb07b4SGreg Roach for (var i = 0; i < nb; i++) { 193efd89170SGreg Roach elts[i].removeAttr('abbr').html(ret[i]); 1942ebb07b4SGreg Roach } 195a2c8afeaSAlejandro Criado-Pérez // we now adjust the draggable treeview size to its content size 1962ebb07b4SGreg Roach tv.getSize(); 1972ebb07b4SGreg Roach }, 1982ebb07b4SGreg Roach complete: function () { 199efd89170SGreg Roach if (tv.treeview.find('td[abbr]').length) { 2002ebb07b4SGreg Roach tv.updateTree(center, button); // recursive call 2012ebb07b4SGreg Roach } 2022ebb07b4SGreg Roach // the added boxes need that in mode compact boxes 2032ebb07b4SGreg Roach if (tv.auto_box_width) { 204efd89170SGreg Roach tv.treeview.find('.tv_box').css('width', 'auto'); 2052ebb07b4SGreg Roach } 2062ebb07b4SGreg Roach tv.updating = true; // avoid an unuseful recursive call when all requested persons are loaded 2072ebb07b4SGreg Roach if (center) { 2082ebb07b4SGreg Roach tv.centerOnRoot(); 2092ebb07b4SGreg Roach } 2102ebb07b4SGreg Roach if (button) { 211efd89170SGreg Roach button.removeClass('tvPressed'); 2122ebb07b4SGreg Roach } 2132ebb07b4SGreg Roach tv.setComplete(); 2142ebb07b4SGreg Roach tv.updating = false; 2152ebb07b4SGreg Roach }, 2162ebb07b4SGreg Roach timeout: function () { 2172ebb07b4SGreg Roach if (button) { 218efd89170SGreg Roach button.removeClass('tvPressed'); 2192ebb07b4SGreg Roach } 2202ebb07b4SGreg Roach tv.updating = false; 2212ebb07b4SGreg Roach tv.setComplete(); 2222ebb07b4SGreg Roach } 2232ebb07b4SGreg Roach }); 2242ebb07b4SGreg Roach } else { 2252ebb07b4SGreg Roach if (button) { 226efd89170SGreg Roach button.removeClass('tvPressed'); 2272ebb07b4SGreg Roach } 2282ebb07b4SGreg Roach tv.setComplete(); 2292ebb07b4SGreg Roach } 2302ebb07b4SGreg Roach return false; 2312ebb07b4SGreg Roach}; 2322ebb07b4SGreg Roach 2332ebb07b4SGreg Roach/** 2342ebb07b4SGreg Roach * Class TreeView compact method 2352ebb07b4SGreg Roach */ 2362ebb07b4SGreg RoachTreeViewHandler.prototype.compact = function () { 2372ebb07b4SGreg Roach var tv = this; 238efd89170SGreg Roach var b = $('#tvbCompact', tv.toolbox); 2392ebb07b4SGreg Roach tv.setLoading(); 2402ebb07b4SGreg Roach if (tv.auto_box_width) { 241efd89170SGreg Roach var w = tv.boxWidth * (tv.zoom / 100) + 'px'; 242efd89170SGreg Roach var ew = tv.boxExpandedWidth * (tv.zoom / 100) + 'px'; 243efd89170SGreg Roach tv.treeview.find('.tv_box:not(boxExpanded)', tv.treeview).css('width', w); 244efd89170SGreg Roach tv.treeview.find('.boxExpanded', tv.treeview).css('width', ew); 2452ebb07b4SGreg Roach tv.auto_box_width = false; 246efd89170SGreg Roach if (readCookie('compact')) { 247efd89170SGreg Roach createCookie('compact', false, tv.cookieDays); 2482ebb07b4SGreg Roach } 249efd89170SGreg Roach b.removeClass('tvPressed'); 2502ebb07b4SGreg Roach } else { 251efd89170SGreg Roach tv.treeview.find('.tv_box').css('width', 'auto'); 2522ebb07b4SGreg Roach tv.auto_box_width = true; 253efd89170SGreg Roach if (!readCookie('compact')) { 254efd89170SGreg Roach createCookie('compact', true, tv.cookieDays); 2552ebb07b4SGreg Roach } 2562ebb07b4SGreg Roach if (!tv.updating) { 2572ebb07b4SGreg Roach tv.updateTree(); 2582ebb07b4SGreg Roach } 259efd89170SGreg Roach b.addClass('tvPressed'); 2602ebb07b4SGreg Roach } 2612ebb07b4SGreg Roach tv.setComplete(); 2622ebb07b4SGreg Roach return false; 2632ebb07b4SGreg Roach}; 2642ebb07b4SGreg Roach 2652ebb07b4SGreg Roach/** 2662ebb07b4SGreg Roach * Class TreeView centerOnRoot method 2672ebb07b4SGreg Roach */ 2682ebb07b4SGreg RoachTreeViewHandler.prototype.centerOnRoot = function () { 269efd89170SGreg Roach this.loadingImage.css('display', 'block'); 2702ebb07b4SGreg Roach var tv = this; 2712ebb07b4SGreg Roach var tvc = this.container; 2722ebb07b4SGreg Roach var tvc_width = tvc.innerWidth() / 2; 27380d699d6SGreg Roach if (Number.isNaN(tvc_width)) { 2742ebb07b4SGreg Roach return false; 2752ebb07b4SGreg Roach } 2762ebb07b4SGreg Roach var tvc_height = tvc.innerHeight() / 2; 277efd89170SGreg Roach var root_person = $('.rootPerson', this.treeview); 2782ebb07b4SGreg Roach 2792ebb07b4SGreg Roach if (!this.updating) { 2802ebb07b4SGreg Roach tv.setComplete(); 2812ebb07b4SGreg Roach } 2822ebb07b4SGreg Roach return false; 2832ebb07b4SGreg Roach}; 2842ebb07b4SGreg Roach 2852ebb07b4SGreg Roach/** 2862ebb07b4SGreg Roach * Class TreeView expandBox method 287220f62c2SGreg Roach * Called ONLY for elements which have NOT the class tv_link to avoid un-useful requests to the server 288220f62c2SGreg Roach * @param {string} box - the person box element 289220f62c2SGreg Roach * @param {string} event - the call event 2902ebb07b4SGreg Roach */ 2912ebb07b4SGreg RoachTreeViewHandler.prototype.expandBox = function (box, event) { 2922ebb07b4SGreg Roach var t = $(event.target); 293efd89170SGreg Roach if (t.hasClass('tv_link')) { 2942ebb07b4SGreg Roach return false; 2952ebb07b4SGreg Roach } 2962ebb07b4SGreg Roach 2972ebb07b4SGreg Roach var box = $(box, this.treeview); 2982ebb07b4SGreg Roach var bc = box.parent(); // bc is Box Container 299efd89170SGreg Roach var pid = box.attr('abbr'); 3002ebb07b4SGreg Roach var tv = this; // Store "this" for usage within jQuery functions where "this" is not this ;-) 3012ebb07b4SGreg Roach var expanded; 3022ebb07b4SGreg Roach var collapsed; 3032ebb07b4SGreg Roach 304efd89170SGreg Roach if (bc.hasClass('detailsLoaded')) { 305efd89170SGreg Roach collapsed = bc.find('.collapsedContent'); 306efd89170SGreg Roach expanded = bc.find('.tv_box:not(.collapsedContent)'); 3072ebb07b4SGreg Roach } else { 3082ebb07b4SGreg Roach // Cache the box content as an hidden person's box in the box's parent element 3092ebb07b4SGreg Roach expanded = box; 3102ebb07b4SGreg Roach collapsed = box.clone(); 311efd89170SGreg Roach bc.append(collapsed.addClass('collapsedContent').css('display', 'none')); 3122ebb07b4SGreg Roach // we add a waiting image at the right side of the box 313efd89170SGreg Roach var loading_image = this.loadingImage.find('img').clone().addClass('tv_box_loading').css('display', 'block'); 3142ebb07b4SGreg Roach box.prepend(loading_image); 3152ebb07b4SGreg Roach tv.updating = true; 3162ebb07b4SGreg Roach tv.setLoading(); 3172ebb07b4SGreg Roach // perform the Ajax request and load the result in the box 318efd89170SGreg Roach box.load(tv.ajaxDetails + '&pid=' + encodeURIComponent(pid), function () { 3192ebb07b4SGreg Roach // If Lightbox module is active, we reinitialize it for the new links 320efd89170SGreg Roach if (typeof CB_Init === 'function') { 3212ebb07b4SGreg Roach CB_Init(); 3222ebb07b4SGreg Roach } 323efd89170SGreg Roach box.css('width', tv.boxExpandedWidth * (tv.zoom / 100) + 'px'); 3242ebb07b4SGreg Roach loading_image.remove(); 325efd89170SGreg Roach bc.addClass('detailsLoaded'); 3262ebb07b4SGreg Roach tv.setComplete(); 3272ebb07b4SGreg Roach tv.updating = false; 3282ebb07b4SGreg Roach }); 3292ebb07b4SGreg Roach } 330efd89170SGreg Roach if (box.hasClass('boxExpanded')) { 331efd89170SGreg Roach expanded.css('display', 'none'); 332efd89170SGreg Roach collapsed.css('display', 'block'); 333efd89170SGreg Roach box.removeClass('boxExpanded'); 3342ebb07b4SGreg Roach } else { 335efd89170SGreg Roach expanded.css('display', 'block'); 336efd89170SGreg Roach collapsed.css('display', 'none'); 337efd89170SGreg Roach expanded.addClass('boxExpanded'); 3382ebb07b4SGreg Roach } 339a2c8afeaSAlejandro Criado-Pérez // we must adjust the draggable treeview size to its content size 3402ebb07b4SGreg Roach this.getSize(); 3412ebb07b4SGreg Roach return false; 3422ebb07b4SGreg Roach}; 3432ebb07b4SGreg Roach 344220f62c2SGreg Roach/** 345220f62c2SGreg Roach * @param {string} name 346220f62c2SGreg Roach * @param {string} value 347220f62c2SGreg Roach * @param {number} days 348220f62c2SGreg Roach */ 3492ebb07b4SGreg Roachfunction createCookie (name, value, days) { 3502ebb07b4SGreg Roach if (days) { 3512ebb07b4SGreg Roach var date = new Date(); 3522ebb07b4SGreg Roach date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 353efd89170SGreg Roach document.cookie = name + '=' + value + '; expires=' + date.toGMTString() + '; path=/'; 3542ebb07b4SGreg Roach } else { 355efd89170SGreg Roach document.cookie = name + '=' + value + '; path=/'; 3562ebb07b4SGreg Roach } 3572ebb07b4SGreg Roach} 3582ebb07b4SGreg Roach 359220f62c2SGreg Roach/** 360220f62c2SGreg Roach * @param {string} name 361220f62c2SGreg Roach * @returns {string|null} 362220f62c2SGreg Roach */ 3632ebb07b4SGreg Roachfunction readCookie (name) { 364efd89170SGreg Roach var name_equals = name + '='; 3652ebb07b4SGreg Roach var ca = document.cookie.split(';'); 3662ebb07b4SGreg Roach for (var i = 0; i < ca.length; i++) { 3672ebb07b4SGreg Roach var c = ca[i]; 3682ebb07b4SGreg Roach while (c.charAt(0) === ' ') { 3692ebb07b4SGreg Roach c = c.substring(1, c.length); 3702ebb07b4SGreg Roach } 3712ebb07b4SGreg Roach if (c.indexOf(name_equals) === 0) { 3722ebb07b4SGreg Roach return c.substring(name_equals.length, c.length); 3732ebb07b4SGreg Roach } 3742ebb07b4SGreg Roach } 3752ebb07b4SGreg Roach return null; 3762ebb07b4SGreg Roach} 377