1/** 2 * webtrees: online genealogy 3 * Copyright (C) 2019 webtrees development team 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * You should have received a copy of the GNU General Public License 13 * along with this program. If not, see <http://www.gnu.org/licenses/>. 14 */ 15 16function TreeViewHandler(treeview_instance, ged) { 17 var tv = this; // Store "this" for usage within jQuery functions where "this" is not this ;-) 18 19 this.treeview = $("#" + treeview_instance + "_in"); 20 this.loadingImage = $("#" + treeview_instance + "_loading"); 21 this.toolbox = $("#tv_tools"); 22 this.buttons = $(".tv_button:first", this.toolbox); 23 this.zoom = 100; // in percent 24 this.boxWidth = 180; // default family box width 25 this.boxExpandedWidth = 250; // default expanded family box width 26 this.cookieDays = 3; // lifetime of preferences memory, in days 27 this.ajaxUrl1 = "index.php?route=/module/tree/"; 28 this.ajaxUrl2 = "&ged=" + encodeURIComponent(ged) + "&instance=" + treeview_instance; 29 30 this.container = this.treeview.parent(); // Store the container element ("#" + treeview_instance + "_out") 31 this.auto_box_width = false; 32 this.updating = false; 33 34 // Restore user preferences 35 if (readCookie("compact") === "true") { 36 tv.compact(); 37 } 38 39 /////////////////////////////////////////////// 40 // Based on https://codepen.io/chriscoyier/pen/zdsty 41 (function($) { 42 $.fn.drags = function(opt) { 43 var $el = this; 44 45 return $el.css('cursor', 'move').on("mousedown", function(e) { 46 var $drag = $(this); 47 var drg_h = $drag.outerHeight(); 48 var drg_w = $drag.outerWidth(); 49 var pos_y = $drag.offset().top + drg_h - e.pageY; 50 var pos_x = $drag.offset().left + drg_w - e.pageX; 51 52 $drag.addClass('draggable'); 53 54 $(document) 55 .on("mousemove", function(e) { 56 $('.draggable').offset({ 57 top:e.pageY + pos_y - drg_h, 58 left:e.pageX + pos_x - drg_w 59 }).on("mouseup", function() { 60 $drag.removeClass('draggable'); 61 }); 62 }).on("mouseup", function() { 63 $drag.removeClass('draggable'); 64 tv.updateTree(); 65 }); 66 e.preventDefault(); 67 }); 68 69 } 70 })(jQuery); 71 72 tv.treeview.drags(); 73 /////////////////////////////////////////////// 74 75 // Add click handlers to buttons 76 tv.toolbox.find("#tvbCompact").each(function (index, tvCompact) { 77 tvCompact.onclick = function () { 78 tv.compact(); 79 }; 80 }); 81 // If we click the "hide/show all partners" button, toggle the setting before reloading the page 82 tv.toolbox.find("#tvbAllPartners").each(function (index, tvAllPartners) { 83 tvAllPartners.onclick = function () { 84 createCookie("allPartners", readCookie("allPartners") === "true" ? "false" : "true", tv.cookieDays); 85 document.location = document.location; 86 }; 87 }); 88 tv.toolbox.find("#tvbOpen").each(function (index, tvbOpen) { 89 var b = $(tvbOpen, tv.toolbox); 90 tvbOpen.onclick = function () { 91 b.addClass("tvPressed"); 92 tv.setLoading(); 93 var e = jQuery.Event("click"); 94 tv.treeview.find(".tv_box:not(.boxExpanded)").each(function (index, box) { 95 var pos = $(box, tv.treeview).offset(); 96 if (pos.left >= tv.leftMin && pos.left <= tv.leftMax && pos.top >= tv.topMin && pos.top <= tv.topMax) { 97 tv.expandBox(box, e); 98 } 99 }); 100 b.removeClass("tvPressed"); 101 tv.setComplete(); 102 }; 103 }); 104 tv.toolbox.find("#tvbClose").each(function (index, tvbClose) { 105 var b = $(tvbClose, tv.toolbox); 106 tvbClose.onclick = function () { 107 b.addClass("tvPressed"); 108 tv.setLoading(); 109 tv.treeview.find(".tv_box.boxExpanded").each(function (index, box) { 110 $(box).css("display", "none").removeClass("boxExpanded").parent().find(".tv_box.collapsedContent").css("display", "block"); 111 }); 112 b.removeClass("tvPressed"); 113 tv.setComplete(); 114 }; 115 }); 116 117 tv.centerOnRoot(); // fire ajax update if needed, which call setComplete() when all is loaded 118} 119/** 120 * Class TreeView setLoading method 121 */ 122TreeViewHandler.prototype.setLoading = function () { 123 this.treeview.css("cursor", "wait"); 124 this.loadingImage.css("display", "block"); 125}; 126/** 127 * Class TreeView setComplete method 128 */ 129TreeViewHandler.prototype.setComplete = function () { 130 this.treeview.css("cursor", "move"); 131 this.loadingImage.css("display", "none"); 132}; 133 134/** 135 * Class TreeView getSize method 136 * Store the viewport current size 137 */ 138TreeViewHandler.prototype.getSize = function () { 139 var tv = this; 140 // retrieve the current container bounding box 141 var container = tv.container.parent(); 142 var offset = container.offset(); 143 tv.leftMin = offset.left; 144 tv.leftMax = tv.leftMin + container.innerWidth(); 145 tv.topMin = offset.top; 146 tv.topMax = tv.topMin + container.innerHeight(); 147 /* 148 var frm = $("#tvTreeBorder"); 149 tv.treeview.css("width", frm.width()); 150 tv.treeview.css("height", frm.height());*/ 151}; 152 153/** 154 * Class TreeView updateTree method 155 * Perform ajax requests to complete the tree after drag 156 * param boolean @center center on root person when done 157 */ 158TreeViewHandler.prototype.updateTree = function (center, button) { 159 var tv = this; // Store "this" for usage within jQuery functions where "this" is not this ;-) 160 var to_load = []; 161 var elts = []; 162 this.getSize(); 163 164 // check which td with datafld attribute are within the container bounding box 165 // and therefore need to be dynamically loaded 166 tv.treeview.find("td[abbr]").each(function (index, el) { 167 el = $(el, tv.treeview); 168 var pos = el.offset(); 169 if (pos.left >= tv.leftMin && pos.left <= tv.leftMax && pos.top >= tv.topMin && pos.top <= tv.topMax) { 170 to_load.push(el.attr("abbr")); 171 elts.push(el); 172 } 173 }); 174 // if some boxes need update, we perform an ajax request 175 if (to_load.length > 0) { 176 tv.updating = true; 177 tv.setLoading(); 178 jQuery.ajax({ 179 url: tv.ajaxUrl1 + "Persons" + tv.ajaxUrl2, 180 dataType: "json", 181 data: "q=" + to_load.join(";"), 182 success: function (ret) { 183 var nb = elts.length; 184 var root_element = $(".rootPerson", this.treeview); 185 var l = root_element.offset().left; 186 for (var i = 0; i < nb; i++) { 187 elts[i].removeAttr("abbr").html(ret[i]); 188 } 189 // we now ajust the draggable treeview size to its content size 190 tv.getSize(); 191 }, 192 complete: function () { 193 if (tv.treeview.find("td[abbr]").length) { 194 tv.updateTree(center, button); // recursive call 195 } 196 // the added boxes need that in mode compact boxes 197 if (tv.auto_box_width) { 198 tv.treeview.find(".tv_box").css("width", "auto"); 199 } 200 tv.updating = true; // avoid an unuseful recursive call when all requested persons are loaded 201 if (center) { 202 tv.centerOnRoot(); 203 } 204 if (button) { 205 button.removeClass("tvPressed"); 206 } 207 tv.setComplete(); 208 tv.updating = false; 209 }, 210 timeout: function () { 211 if (button) { 212 button.removeClass("tvPressed"); 213 } 214 tv.updating = false; 215 tv.setComplete(); 216 } 217 }); 218 } else { 219 if (button) { 220 button.removeClass("tvPressed"); 221 } 222 tv.setComplete(); 223 } 224 return false; 225}; 226 227/** 228 * Class TreeView compact method 229 */ 230TreeViewHandler.prototype.compact = function () { 231 var tv = this; 232 var b = $("#tvbCompact", tv.toolbox); 233 tv.setLoading(); 234 if (tv.auto_box_width) { 235 var w = tv.boxWidth * (tv.zoom / 100) + "px"; 236 var ew = tv.boxExpandedWidth * (tv.zoom / 100) + "px"; 237 tv.treeview.find(".tv_box:not(boxExpanded)", tv.treeview).css("width", w); 238 tv.treeview.find(".boxExpanded", tv.treeview).css("width", ew); 239 tv.auto_box_width = false; 240 if (readCookie("compact")) { 241 createCookie("compact", false, tv.cookieDays); 242 } 243 b.removeClass("tvPressed"); 244 } else { 245 tv.treeview.find(".tv_box").css("width", "auto"); 246 tv.auto_box_width = true; 247 if (!readCookie("compact")) { 248 createCookie("compact", true, tv.cookieDays); 249 } 250 if (!tv.updating) { 251 tv.updateTree(); 252 } 253 b.addClass("tvPressed"); 254 } 255 tv.setComplete(); 256 return false; 257}; 258 259/** 260 * Class TreeView centerOnRoot method 261 */ 262TreeViewHandler.prototype.centerOnRoot = function () { 263 this.loadingImage.css("display", "block"); 264 var tv = this; 265 var tvc = this.container; 266 var tvc_width = tvc.innerWidth() / 2; 267 if (isNaN(tvc_width)) { 268 return false; 269 } 270 var tvc_height = tvc.innerHeight() / 2; 271 var root_person = $(".rootPerson", this.treeview); 272 273 if (!this.updating) { 274 tv.setComplete(); 275 } 276 return false; 277}; 278 279/** 280 * Class TreeView expandBox method 281 * param string @box the person box element 282 * param string @event the call event 283 * param string @pid the person id 284 * 285 * called ONLY for elements which have NOT the class tv_link to avoid unuseful requests to the server 286 */ 287TreeViewHandler.prototype.expandBox = function (box, event) { 288 var t = $(event.target); 289 if (t.hasClass("tv_link")) { 290 return false; 291 } 292 293 var box = $(box, this.treeview); 294 var bc = box.parent(); // bc is Box Container 295 var pid = box.attr("abbr"); 296 var tv = this; // Store "this" for usage within jQuery functions where "this" is not this ;-) 297 var expanded; 298 var collapsed; 299 300 if (bc.hasClass("detailsLoaded")) { 301 collapsed = bc.find(".collapsedContent"); 302 expanded = bc.find(".tv_box:not(.collapsedContent)"); 303 } else { 304 // Cache the box content as an hidden person's box in the box's parent element 305 expanded = box; 306 collapsed = box.clone(); 307 bc.append(collapsed.addClass("collapsedContent").css("display", "none")); 308 // we add a waiting image at the right side of the box 309 var loading_image = this.loadingImage.find("img").clone().addClass("tv_box_loading").css("display", "block"); 310 box.prepend(loading_image); 311 tv.updating = true; 312 tv.setLoading(); 313 // perform the Ajax request and load the result in the box 314 box.load(tv.ajaxUrl1 + "Details&pid=" + pid + tv.ajaxUrl2, function () { 315 // If Lightbox module is active, we reinitialize it for the new links 316 if (typeof CB_Init === "function") { 317 CB_Init(); 318 } 319 box.css("width", tv.boxExpandedWidth * (tv.zoom / 100) + "px"); 320 loading_image.remove(); 321 bc.addClass("detailsLoaded"); 322 tv.setComplete(); 323 tv.updating = false; 324 }); 325 } 326 if (box.hasClass("boxExpanded")) { 327 expanded.css("display", "none"); 328 collapsed.css("display", "block"); 329 box.removeClass("boxExpanded"); 330 } else { 331 expanded.css("display", "block"); 332 collapsed.css("display", "none"); 333 expanded.addClass("boxExpanded"); 334 } 335 // we must ajust the draggable treeview size to its content size 336 this.getSize(); 337 return false; 338}; 339 340function createCookie(name, value, days) { 341 if (days) { 342 var date = new Date(); 343 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 344 document.cookie = name + "=" + value + "; expires=" + date.toGMTString() + "; path=/"; 345 } else { 346 document.cookie = name + "=" + value + "; path=/"; 347 } 348} 349 350function readCookie(name) { 351 var name_equals = name + "="; 352 var ca = document.cookie.split(';'); 353 for (var i = 0; i < ca.length; i++) { 354 var c = ca[i]; 355 while (c.charAt(0) === ' ') { 356 c = c.substring(1, c.length); 357 } 358 if (c.indexOf(name_equals) === 0) { 359 return c.substring(name_equals.length, c.length); 360 } 361 } 362 return null; 363} 364