dolibarr 25.0.0-alpha
lib_foot.js.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2017 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
4 * Copyright (C) 2026 MDW <mdeweerd@users.noreply.github.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 * or see https://www.gnu.org/
19 */
20
26if (!defined('NOREQUIRESOC')) {
27 define('NOREQUIRESOC', '1');
28}
29if (!defined('NOCSRFCHECK')) {
30 define('NOCSRFCHECK', 1);
31}
32if (!defined('NOTOKENRENEWAL')) {
33 define('NOTOKENRENEWAL', 1);
34}
35if (!defined('NOLOGIN')) {
36 define('NOLOGIN', 1);
37}
38if (!defined('NOREQUIREMENU')) {
39 define('NOREQUIREMENU', 1);
40}
41if (!defined('NOREQUIREHTML')) {
42 define('NOREQUIREHTML', 1);
43}
44if (!defined('NOREQUIREAJAX')) {
45 define('NOREQUIREAJAX', '1');
46}
47
48session_cache_limiter('public');
49
50require_once '../../main.inc.php';
57/*
58 * View
59 */
60
61// Define javascript type
62top_httphead('text/javascript; charset=UTF-8');
63// Important: Following code is to avoid page request by browser and PHP CPU at each Dolibarr page access.
64header('Cache-Control: max-age=10800, public, must-revalidate');
65
66
67$jsConst = [
68 'DOL_URL_ROOT' => DOL_URL_ROOT,
69 'classfortooltiponclicktextWidth' => ($conf->browser->layout == 'phone' ? max((empty($_SESSION['dol_screenwidth']) ? 0 : $_SESSION['dol_screenwidth']) - 20, 320) : 700),
70 'dol_no_mouse_hover' => !empty($conf->dol_no_mouse_hover)
71];
72
73
74include __DIR__ . '/lib_initTooltips.js';
75
76?>
77
78// Wrapper to show tooltips (html or onclick popup)
79/* JS CODE TO ENABLE Tooltips on all object with class classfortooltip */
80jQuery(function() {
81
82 const footerConst = <?php print json_encode($jsConst); ?>;
83
84
85 if(!footerConst.dol_no_mouse_hover) {
86 // --------------------------------
87 // Initial page load
88 // --------------------------------
89 initTooltips(document.body); // initialize all tooltips on page load
90 initAjaxTooltips(document.body, footerConst.DOL_URL_ROOT); // initialize all AJAX tooltips on page load
91
92
93 // --------------------------------
94 // Dynamic reload via Dolibarr hook
95 // --------------------------------
96 if (typeof Dolibarr !== "undefined" && Dolibarr.on) {
97 // Because Dolibarr context isn't in all Dolibarr page and lib_foot.js.php can be loaded everywhere
98 // it useful to check Dolibarr context is loaded before but we are already in a jQuery(function() so event Dolibarr:Ready can't be used here
99 Dolibarr.on('initNewContent', ({targets}) => {
100 targets.forEach(container => {
101 initTooltips(container);
102 initAjaxTooltips(container, footerConst.DOL_URL_ROOT);
103 })
104 });
105 };
106 }
107
108 // --------------------------------
109 // Initial page load
110 // --------------------------------
111 jQuery(function() {
112 initTooltipDialogs(document.body);
113 });
114
115 // --------------------------------
116 // Dynamic reload via Dolibarr hook
117 // --------------------------------
118
119 if (typeof Dolibarr !== "undefined" && Dolibarr.on) {
120 // Because Dolibarr context isn't in all Dolibarr page and lib_foot.js.php can be loaded everywhere
121 // it useful to check Dolibarr context is loaded before but we are already in a jQuery(function() so event Dolibarr:Ready can't be used here
122 Dolibarr.on('initNewContent', ({ targets }) => {
123 targets.forEach(container => initTooltipDialogs(container, footerConst.classfortooltiponclicktextWidth));
124 });
125 };
126
127});
128
129
130<?php
131// Wrapper to manage dropdown
132if (!defined('JS_JQUERY_DISABLE_DROPDOWN')) {
133 print "\n/* JS CODE TO ENABLE dropdown (hamburger, linkto, ...) */\n";
134 print ' jQuery(document).ready(function () {
135 var lastopendropdown = null;
136
137 // Click onto the link "link to" or "hamburger", toggle dropdown
138 $(document).on(\'click\', \'.dropdown dt a\', function () {
139 console.log("toggle dropdown dt a");
140
141 //$(this).parent().parent().find(\'dd ul\').slideToggle(\'fast\');
142 $(".ulselectedfields").removeClass("open");
143 $(this).parent().parent().find(\'dd ul\').toggleClass("open");
144
145 if ($(this).parent().parent().find(\'dd ul\').hasClass("open")) {
146 lastopendropdown = $(this).parent().parent().find(\'dd ul\');
147 //console.log(lastopendropdown);
148 } else {
149 // We closed the dropdown for hamburger selectfields
150 if ($("input:hidden[name=formfilteraction]").val() == "listafterchangingselectedfields") {
151 console.log("resubmit the form saved into lastopendropdown after clicking on hamburger");
152 //$(".dropdown dt a").parents(\'form:first\').submit();
153 //$(".dropdown dt a").closest("form").submit();
154 lastopendropdown.closest("form").submit();
155 }
156 }
157
158 // Note: Did not find a way to get exact height (value is update at exit) so i calculate a generic from nb of lines
159 heigthofcontent = 21 * $(this).parent().parent().find(\'dd div ul li\').length;
160 if (heigthofcontent > 300) heigthofcontent = 300; // limited by max-height on css .dropdown dd ul
161 posbottom = $(this).parent().parent().find(\'dd\').offset().top + heigthofcontent + 8;
162 var scrollBottom = $(window).scrollTop() + $(window).height();
163 diffoutsidebottom = (posbottom - scrollBottom);
164 console.log("heigthofcontent="+heigthofcontent+", diffoutsidebottom (posbottom="+posbottom+" - scrollBottom="+scrollBottom+") = "+diffoutsidebottom);
165 if (diffoutsidebottom > 0)
166 {
167 pix = "-"+(diffoutsidebottom+8)+"px";
168 console.log("We reposition top by "+pix);
169 $(this).parent().parent().find(\'dd\').css("top", pix);
170 }
171 });
172
173 // Click on a link into the popup "link to" or other dropdown that ask to close drop down on element click, so close dropdown
174 $(".dropdowncloseonclick").on(\'click\', function () {
175 console.log("Link has class dropdowncloseonclick, so we close/hide the popup ul");
176 //$(this).parent().parent().hide(); // $(this).parent().parent() is ul
177 $(this).parent().parent().removeClass("open"); // $(this).parent().parent() is ul
178 });
179
180 // Click outside of any dropdown
181 $(document).bind(\'click\', function (e) {
182 var $clicked = $(e.target); // This is element we click on
183 if (!$clicked.parents().hasClass("dropdown")) {
184 //console.log("close dropdown dd ul - we click outside");
185 //$(".dropdown dd ul").hide();
186 $(".dropdown dd ul").removeClass("open");
187
188 if ($("input:hidden[name=formfilteraction]").val() == "listafterchangingselectedfields") {
189 console.log("resubmit form saved into lastopendropdown after clicking outside of dropdown and having change selectlist from selectlist field of hamburger dropdown");
190 //$(".dropdown dt a").parents(\'form:first\').submit();
191 //$(".dropdown dt a").closest("form").submit();
192 lastopendropdown.closest("form").submit();
193 }
194 }
195 });
196 });
197 ';
198}
199
200// Wrapper to manage document_preview
201if ($conf->browser->layout != 'phone') {
202 print "\n/* JS CODE TO ENABLE document_preview */\n"; // Function document_preview is into header
203 print ' jQuery(document).ready(function () {
204 // Click on the preview picto
205 jQuery(".documentpreview").click(function () {
206 console.log("We click on preview for element with href="+$(this).attr(\'href\')+" mime="+$(this).attr(\'mime\'));
207 var titledocpreview = $(this).attr(\'data-title\');
208 if (titledocpreview == undefined || titledocpreview == "") {
209 titledocpreview = \''.dol_escape_js($langs->transnoentities("Preview")).'\'
210 }
211 document_preview($(this).attr(\'href\'), $(this).attr(\'mime\'), titledocpreview);
212 return false;
213 });
214 });'."\n";
215}
216
217// Code to manage reposition
218print "\n/* JS CODE TO ENABLE reposition management (does not work if a redirect is done after action of submission) */\n";
219print '
220 jQuery(document).ready(function() {
221 /* If page_y set, we set scrollbar with it */
222 page_y = getParameterByName("page_y", 0); /* search in GET parameter */
223 if (page_y == 0) page_y = jQuery("#page_y").text(); /* search in POST parameter that is filed at bottom of page */
224 if (page_y > 0) {
225 console.log("page_y found is "+page_y);
226 jQuery("html, body").scrollTop(page_y);
227 }
228
229 /* Set handler to add page_y param on output (click on href links or submit button) */
230 jQuery(".reposition").click(function(event) {
231 var page_y = jQuery(document).scrollTop();
232 if (page_y > 0) {
233 if (this.href) {
234 console.log("We click on tag with .reposition class. this.ref was "+this.href);
235 var url = new URL(this.href, window.location.origin);
236 url.searchParams.delete("page_y"); /* remove page_y param if already present */
237 url.searchParams.set("page_y", page_y);
238 this.href = url.toString();
239 console.log("We click on tag with .reposition class. this.ref is now "+this.href);
240 } else {
241 console.log("We click on tag with .reposition class but element is not an <a> html tag, so we try to update input form field with name=page_y with value "+page_y);
242 jQuery("input[type=hidden][name=page_y]").val(page_y);
243 }
244 }
245 });
246 });
247' . "\n";
248
249// Code to manage Copy To Clipboard click
250print "\n/* JS CODE TO ENABLE ClipBoard copy paste */\n";
251// Suppress because dol_escape_js is wrapped in '' @phan-suppress-next-line FunctionMissingSingleQuoteWrapping
252print '
253 jQuery(document).ready(function() {
254 jQuery(\'.clipboardCPShowOnHover\').hover(
255 function() {
256 console.log("We hover a value with a copy paste feature");
257 $(this).children(".clipboardCPButton, .clipboardCPText").css("display", "inline-block"); /* better than .show() because the show set the display to "inline" */
258 },
259 function() {
260 console.log("We hover out the value with a copy paste feature");
261 $(this).children(".clipboardCPButton, .clipboardCPText").hide();
262 }
263 );
264
265 jQuery(\'.clipboardCPValue, .clipboardCPButton, .clipboardCPValueToPrint\').click(function() {
266 console.log("We click on a clipboardCPButton or clipboardCPValueToPrint class and we want to copy content of clipboardCPValue class");
267
268 if (window.getSelection) {
269 jqobj=$(this).parent().children(".clipboardCPValue");
270 console.log(jqobj.html());
271
272 selection = window.getSelection(); /* get the object used for selection */
273 selection.removeAllRanges(); /* clear current selection */
274
275 /* We select the value to print using the parentNode.firstChild */
276 /* We should use the class clipboardCPValue but it may have several element with copy/paste so class to select is not enough */
277 range = document.createRange();
278 range.selectNodeContents(this.parentNode.firstChild);
279 selection.addRange(range); /* make the new selection with the value to copy */
280
281 /* copy selection into clipboard */
282 var succeed;
283 try {
284 console.log("We set the style display to unset for the span so the copy will work");
285 jqobj.css("display", "unset"); /* Because copy does not work on "block" object */
286
287 succeed = document.execCommand(\'copy\');
288
289 console.log("We set the style display back to inline-block");
290 jqobj.css("display", "inline-block"); /* better than .show() because the show set the display to "inline" */
291 } catch(e) {
292 succeed = false;
293 }
294
295 /* Remove the selection to avoid to see the hidden field to copy selected */
296 window.getSelection().removeAllRanges();
297 }
298
299 /* Show result - message */
300 var lastparent = $(this).parent(); /* .parent is clipboardCP */
301 var lastchild = this.parentNode.lastChild; /* .parentNode is clipboardCP and last child is clipboardCPText */
302 var tmp = lastchild.innerHTML
303 if (succeed) {
304 $(this).parent().children(".clipboardCPButton").hide();
305 $(this).parent().children(".clipboardCPTick").css("display", "inline-block"); /* better than .show() because the show set the display to "inline" */
306 //lastchild.innerHTML = \'<div class="clipboardCPTextDivInside opacitymedium">'.dolPrintHTML($langs->trans('CopiedToClipboard')).'</div>\';
307 } else {
308 lastchild.innerHTML = \'<div class="clipboardCPTextDivInside opacitymedium">'.dolPrintHTML($langs->trans('Error')).'</div>\';
309 }
310 setTimeout(() => { lastchild.innerHTML = tmp; lastparent.children(".clipboardCPTick").hide(); }, 2000);
311 });
312 });'."\n";
313
314// Code to manage clicktodial
315print "\n/* JS CODE TO ENABLE clicktodial call of an URL */\n";
316print '
317 jQuery(document).ready(function() {
318 jQuery(".cssforclicktodial").click(function() {
319 event.preventDefault();
320 var currenttoken = jQuery("meta[name=anti-csrf-currenttoken]").attr("content");
321 console.log("We click on a cssforclicktodial class with href="+this.href);
322 $.ajax({
323 url: this.href,
324 type: \'GET\',
325 data: { token: currenttoken }
326 }).done(function(xhr, textStatus, errorThrown) {
327 /* do nothing */
328 }).fail(function(xhr, textStatus, errorThrown) {
329 alert("Error: "+textStatus);
330 });
331 return false;
332 });
333 });'."\n";
334
335
336// Code to manage the confirm dialog box
337print "\n/* JS CODE TO ENABLE DIALOG CONFIRM POPUP ON ACTION BUTTON */\n";
338print '
339 jQuery(document).ready(function() {
340 $(document).on("click", \'.butActionConfirm\', function(event) {
341 event.preventDefault();
342
343 // I don\'t use jquery $(this).data(\'confirm-url\'); to get $(this).attr(\'data-confirm-url\'); because .data() can doesn\'t work with ajax
344 var confirmUrl = $(this).attr(\'data-confirm-url\');
345 var confirmTitle = $(this).attr(\'data-confirm-title\');
346 var confirmContent = $(this).attr(\'data-confirm-content\');
347 var confirmActionBtnLabel = $(this).attr(\'data-confirm-action-btn-label\');
348 var confirmCancelBtnLabel = $(this).attr(\'data-confirm-cancel-btn-label\');
349 var confirmModal = $(this).attr(\'data-confirm-modal\');
350 if(confirmModal == undefined){ confirmModal = false; }
351
352 var confirmId = \'confirm-dialog-box\';
353 if($(this).attr(\'id\') != undefined){ var confirmId = confirmId + "-" + $(this).attr(\'id\'); }
354 if($("#" + confirmId) != undefined) { $(\'#\' + confirmId).remove(); }
355
356 // Create modal box
357
358 var $confirmBox = $(\'<div/>\', {
359 id: confirmId,
360 title: confirmTitle
361 }).appendTo(\'body\');
362
363 $confirmBox.dialog({
364 autoOpen: true,
365 modal: confirmModal,
366 //width: Math.min($( window ).width() - 50, 1700),
367 width: \'auto\',
368 dialogClass: \'confirm-dialog-box\',
369 buttons: [
370 {
371 text: confirmActionBtnLabel,
372 "class": \'ui-state-information\',
373 click: function () {
374 window.location.replace(confirmUrl);
375 }
376 },
377 {
378 text: confirmCancelBtnLabel,
379 "class": \'ui-state-information\',
380 click: function () {
381 $(this).dialog("close");
382 }
383 }
384 ],
385 close: function( event, ui ) {
386 $(\'#\'+confirmBox).remove();
387 },
388 open: function( event, ui ) {
389 $confirmBox.html(confirmContent);
390 }
391 });
392 });
393 });
394'."\n";
395
396
397// JS CODE USED by form::getSearchFilterToolInput
398include __DIR__ . '/lib_foot_search_tool.js';
399include __DIR__ . '/lib_tooltip-freeze-by-alt-keypress.js';
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!defined( 'NOREQUIREMENU')) if(!empty(GETPOST('seteventmessages', 'alpha'))) if(!function_exists("llxHeader")) top_httphead($contenttype='text/html', $forcenocache=0)
Show HTTP header.