1/** 2 * @file utils_msg.js 3 * @brief Provides a comprehensive suite of functions for displaying messages, dialogs, popups, and notifications. 4 * @fileOverview This file defines the `HEURIST4.msg` namespace, which includes a variety of utilities for user interaction. 5 * These utilities cover: 6 * - Standard error message dialogs (`showMsgErr`, `showMsgErrJson`). 7 * - General purpose dialogs with customizable buttons and titles (`showMsgDlg`, `showMsg`). 8 * - Dialogs for loading external URL content (`showMsgDlgUrl`, `showDialog`). 9 * - Prompt dialogs for user input (`showPrompt`). 10 * - Flash messages and tooltips for temporary notifications (`showMsgFlash`, `showTooltipFlash`). 11 * - Input validation helpers that display messages (`checkLength`, `checkLength2`). 12 * - Progress indicators (`showProgress`, `hideProgress`). 13 * - Specialized dialogs like exit warnings (`showMsgOnExit`) and messages for parent record operations (`prepareParentRecordMsg`). 14 * The functions often leverage jQuery UI Dialog for their implementation and provide options for styling and behavior. 15 * @project Heurist academic knowledge management system 16 * 17 * @link https://HeuristNetwork.org 18 * @copyright (C) 2005-2023 University of Sydney, (C) 2024 onwards Heurist Network 19 * @license https://www.gnu.org/licenses/gpl-3.0.txt GNU License 3.0 20 * @author Artem Osmakov <[email protected]> 21 * @author Ian Johnson <[email protected]> 22 * @since 4.0 23 */ 24 25if (!window.hWin.HEURIST4){ 26 window.hWin.HEURIST4 = {}; 27} 28 29/** 30 * @namespace HEURIST4.msg 31 * @memberof HEURIST4 32 * @description Provides a comprehensive set of functions for displaying various types of messages, 33 * dialogs, popups, tooltips, and progress indicators within the Heurist application. 34 * This includes error handling, informational messages, user prompts, modal dialogs for 35 * iframe content, and temporary flash notifications. 36 */ 37if (! window.hWin.HEURIST4.msg) window.hWin.HEURIST4.msg = { 38 39 /** 40 * Displays an error message, specifically handling JSON-related error responses. 41 * If the response is a string (implying a JSON parsing error), it shows a generic JSON parse error. 42 * Otherwise, it passes the response object to `showMsgErr`. 43 * 44 * @param {Object|string} response - The error response object, or a string message if JSON parsing failed. 45 * If an object, it's passed to `showMsgErr`. 46 * @memberof HEURIST4.msg 47 * @returns {void} 48 */ 49 showMsgErrJson: function(response){ 50 if(typeof response === "string"){ 51 window.hWin.HEURIST4.msg.showMsgErr(null); // TODO: Review if passing null is intended or should be a specific message 52 }else{ 53 // Assuming response is an error object from a failed JSON.parse, not the unparsed string. 54 // If response IS the unparsed string that failed, this message might be confusing. 55 // For now, documenting as is. 56 window.hWin.HEURIST4.msg.showMsgErr(window.hWin.HR('Error_Json_Parse')+': '+String(response).slice(0,255)+'...'); 57 } 58 }, 59 60 /** 61 * Displays a detailed error message dialog based on a response object or string. 62 * It handles different error statuses (system fatal, invalid request, access denied, DB error, etc.), 63 * formats the message, sets an appropriate title, and may include system messages or a prompt to login. 64 * Uses `showMsgDlg` for the actual display. 65 * 66 * @memberof HEURIST4.msg 67 * @param {Object|string} response - The error response object or a simple error message string. 68 * If an object, it can have properties like `message`, `status`, 69 * `sysmsg`, `error_title`, `request_code`. 70 * @param {boolean} [needlogin=false] - If true and the error indicates an access denied due to not being logged in, 71 * it may trigger a login prompt after closing the dialog. 72 * @param {Object} [ext_options] - Additional options passed to `showMsgDlg` for customizing the dialog appearance. 73 * Typically includes `default_palette_class: 'ui-heurist-error'`. 74 * @returns {string} The formatted error message string that was displayed. 75 */ 76 showMsgErr: function(response, needlogin, ext_options){ 77 let msg = ''; 78 let dlg_title = null; 79 let show_login_dlg = false;
80 81 window.hWin.HEURIST4.msg.sendCoverallToBack(true); 82 window.hWin.HEURIST4.msg.closeMsgFlash(); 83 84 if(typeof response === "string"){ 85 msg = response; 86 }else{ 87 let request_code = null; 88 89 if($.isPlainObject(response.message)){ 90 response = response.message; 91 } 92 93 if(window.hWin.HEURIST4.util.isempty(response.message) || response.message.trim().toLowerCase() == 'error'){ 94 95 msg = 'Error_Empty_Message'; 96 if(response){ 97 if(response.status==window.hWin.ResponseStatus.REQUEST_DENIED ){ 98 msg = ''; 99 }else if (!window.hWin.HEURIST4.util.isempty(response.request_code)){ 100 request_code = response.request_code; 101 } 102 } 103 }else{ 104 msg = response.message; 105 } 106 msg = window.hWin.HR(msg); 107 108 dlg_title = response.error_title?response.error_title:''; 109 110 if(response.sysmsg && response.status!=window.hWin.ResponseStatus.REQUEST_DENIED){ 111 //sysmsg for REQUEST_DENIED is current user id - it allows to check if session is expired 112 msg = msg + '<br><br>System error:<br>'; 113 if(typeof response.sysmsg['join'] === "function"){ 114 msg = msg + response.sysmsg.join('<br>'); 115 }else{ 116 msg = msg + response.sysmsg; 117 } 118 119 } 120 121 let error_report_team = ''; 122 if(window.hWin?.HAPI4 && window.hWin.HAPI4?.sysinfo){ 123 let admin_email = window.hWin.HAPI4.sysinfo.sysadmin_email; 124 error_report_team = '<br><br>'+window.hWin.HR('Error_Report_Team').replace('#sysadmin_email#', admin_email) 125 } 126 127 if(response.status==window.hWin.ResponseStatus.SYSTEM_FATAL 128 || response.status==window.hWin.ResponseStatus.SYSTEM_CONFIG 129 || response.status==window.hWin.ResponseStatus.NETWORK_ERROR){ 130 131 let def_title = window.hWin.ResponseStatus.SYSTEM_FATAL ? 'Fatal error' :'System misconfiguration'; 132 dlg_title = window.hWin.HEURIST4.util.isempty(dlg_title) ? def_title : dlg_title; 133 msg = msg + '<br><br>'+window.hWin.HR('Error_System_Config'); 134 135 }else if(response.status==window.hWin.ResponseStatus.INVALID_REQUEST){ 136 137 dlg_title = window.hWin.HEURIST4.util.isempty(dlg_title) ? 'Invalid request made' : dlg_title; 138 139 msg = msg + '<br><br>' + window.hWin.HR('Error_Wrong_Request') 140 +'<br><br>' + error_report_team; 141 142 }else if(response.status==window.hWin.ResponseStatus.REQUEST_DENIED){ 143 144 if(msg!='') msg = msg + '<br><br>'; 145 146 if(window.hWin && window.hWin.HAPI4){ 147 dlg_title = 'Login required to access '+window.hWin.HAPI4.database; 148 response.sysmsg = (window.hWin.HAPI4.currentUser['ugr_ID']==0)?0:1; 149 }else{ 150 dlg_title = 'Access denied'; 151 } 152 153 if(msg=='' || (needlogin && response.sysmsg==0)){ 154 msg = msg + window.hWin.HR('Session expired'); 155 show_login_dlg = true; 156 }else if(response.sysmsg==0){ 157 msg = msg + 'You must be logged in'; 158 }else{ 159 dlg_title = 'Access denied'; 160 msg = msg + 'This action is not allowed for your current permissions'; 161 } 162 163 }else if(response.status==window.hWin.ResponseStatus.DB_ERROR){ 164 dlg_title = window.hWin.HEURIST4.util.isempty(dlg_title) ? 'Database error' : dlg_title; 165 msg = msg + error_report_team; 166 }else if(response.status==window.hWin.ResponseStatus.ACTION_BLOCKED){ 167 // No enough rights or action is blocked by constraints 168 dlg_title = window.hWin.HEURIST4.util.isempty(dlg_title) ? 'Action blocked' : dlg_title; 169 }else if(response.status==window.hWin.ResponseStatus.NOT_FOUND){ 170 // The requested object not found. 171 dlg_title = window.hWin.HEURIST4.util.isempty(dlg_title) ? 'Request not found' : dlg_title; 172 }else if(response.status==window.hWin.ResponseStatus.UNKNOWN_ERROR){ 173 // An unknown/un-handled error 174 dlg_title = window.hWin.HEURIST4.util.isempty(dlg_title) ? 'An unknown error has occurred' : dlg_title; 175 msg += error_report_team; 176 } 177 178 if(request_code!=null){ 179 msg = msg + '<br>'+window.hWin.HR('Error_Report_Code')+': "' 180 +(request_code.script+' ' 181 +(window.hWin.HEURIST4.util.isempty(request_code.action)?'':request_code.action)).trim()+'"'; 182 } 183 } 184 185 if(window.hWin.HEURIST4.util.isempty(msg) || msg.trim().toLowerCase() == 'error'){
186 msg = window.hWin.HR('Error_Empty_Message'); 187 } 188 if(window.hWin.HEURIST4.util.isempty(dlg_title)){ 189 dlg_title = 'Error_Title'; 190 } 191 dlg_title = window.hWin.HEURIST4.util.isFunction(window.hWin.HR)?window.hWin.HR(dlg_title):'Heurist'; 192 193 let buttons = {}; 194 buttons[window.hWin.HR('OK')] = function() { 195 let $dlg = window.hWin.HEURIST4.msg.getMsgDlg(); 196 $dlg.dialog( "close" ); 197 if(show_login_dlg){ 198 if(window.hWin?.HAPI4){ 199 window.hWin.HAPI4.setCurrentUser(null); 200 $(window.hWin.document).trigger(window.hWin.HAPI4.Event.ON_CREDENTIALS); 201 } 202 } 203 }; 204 205 if(!ext_options) ext_options = {}; 206 //else if(!ext_options['default_palette_class']) 207 ext_options['default_palette_class'] = 'ui-heurist-error'; 208 209 window.hWin.HEURIST4.msg.showMsgDlg(msg, buttons, dlg_title, ext_options); 210 211 return msg; 212 }, 213 214 /** 215 * Loads content from a specified URL into a dialog and displays it. 216 * The dialog is created using `getPopupDlg` or `getMsgDlg` based on options. 217 * After loading, it calls `showMsgDlg` to configure and show the dialog. 218 * 219 * @memberof HEURIST4.msg 220 * @param {string} url - The URL from which to load content. 221 * @param {Object<string, function>|function} [buttons] - Buttons to display on the dialog, in the format expected by jQuery UI Dialog, 222 * or a callback function for default Yes/No buttons. See `showMsgDlg`. 223 * @param {string|Object<string, string>} [title] - The title for the dialog or an object with localized title/button labels. See `showMsgDlg`. 224 * @param {Object} [options] - Additional options for the dialog. 225 * @param {boolean} [options.isPopupDlg=false] - If true, uses `getPopupDlg`; otherwise, `getMsgDlg`. 226 * @param {string} [options.container] - If provided with `isPopupDlg`, specifies the container for `getPopupDlg`. 227 * @param {boolean} [options.use_doc_title=false] - If true, sets the dialog title from the `<title>` tag of the loaded document. 228 * Other options are passed to `showMsgDlg`. 229 * @returns {jQuery|undefined} The jQuery object representing the dialog element, or undefined if URL is not provided. 230 */ 231 showMsgDlgUrl: function(url, buttons, title, options){ 232 233 let $dlg; 234 if(url){ 235 let isPopupDlg = (options && (options.isPopupDlg || options.container)); 236 $dlg = isPopupDlg 237 ?window.hWin.HEURIST4.msg.getPopupDlg( options.container ) 238 :window.hWin.HEURIST4.msg.getMsgDlg(); 239 $dlg.load(url, function(){ 240 window.hWin.HEURIST4.msg.showMsgDlg(null, buttons, title, options); 241 242 if(options && options.use_doc_title){ 243 $dlg.dialog('option', 'title', $dlg.find('title').text()); 244 } 245 }); 246 } 247 return $dlg; 248 }, 249 250 /** 251 * Displays a standardized error message when a dynamic script loading fails. 252 * The message informs the user about a program error and advises them to report it. 253 * 254 * @memberof HEURIST4.msg 255 * @param {boolean} isdlg - If true, displays the message in a modal dialog (`showMsgDlg`). 256 * If false, displays it as a temporary flash message (`showMsgFlash`). 257 * @param {string} [message="this feature"] - A string to insert into the error message, indicating 258 * what feature encountered the loading problem. 259 * @returns {void} 260 */ 261 showMsg_ScriptFail: function( isdlg, message ){ 262 263 if(window.hWin.HEURIST4.util.isempty(message)){ 264 message = "this feature"; 265 } 266 267 message = 268'Unfortunately we have encountered a program error. Please report this to us using ' 269+'create ticket under Help at the top right of the main screen, or via email '
270+'to [email protected] so that we can fix it immediately.' 271 272+'<br><br>Please remember to tell us the context in which this occurred. ' 273+'A screenshot including the URL is very useful.'; 274 275 if(isdlg){ 276 window.hWin.HEURIST4.msg.showMsgDlg(message, null, "Work in Progress"); 277 }else{ 278 window.hWin.HEURIST4.msg.showMsgFlash(message, 4000, {title:'Work in Progress',height:160}); 279 } 280 281 }, 282 283 /** 284 * Displays a dialog prompting the user for input. 285 * The dialog contains a message and an input field. If the provided message 286 * does not include an element with ID `dlg-prompt-value`, an input field is automatically created. 287 * 288 * @memberof HEURIST4.msg 289 * @param {string} message - The message or HTML content to display in the prompt. 290 * If it contains an element with `id="dlg-prompt-value"`, that element is used for input. 291 * Otherwise, a new text input is created. 292 * @param {function} callbackFunc - A function to call when the user submits the dialog (e.g., clicks OK). 293 * This function will be called with the value from the input field. 294 * @param {string} [sTitle="Specify value"] - The title for the prompt dialog. 295 * @param {Object} [ext_options] - Additional options passed to `showMsgDlg`. 296 * @param {string} [ext_options.dialogId="dialog-common-messages"] - The ID of the dialog element to use. 297 * @param {boolean} [ext_options.password=false] - If true, the created input field will be of type "password". 298 * @returns {jQuery} The jQuery object representing the dialog element. 299 */ 300 showPrompt: function(message, callbackFunc, sTitle, ext_options){ 301 302 if(message.indexOf('dlg-prompt-value')<0){ 303 const value = window.hWin.HEURIST4.util.htmlEscape(ext_options?.value??''); 304 305 message = message+'<input id="dlg-prompt-value" class="text ui-corner-all" ' 306 + (ext_options?.password?'type="password"':'') 307 + ` style="max-width: 250px; min-width: 10em; width: 250px; margin-left:0.2em" value="${value}"/>`; 308 309 } 310 311 let dlg_id = ext_options?.dialogId??'dialog-common-messages'; 312 313 return window.hWin.HEURIST4.msg.showMsgDlg( message, 314 function(){ // This is the OK button callback for showMsgDlg 315 if(window.hWin.HEURIST4.util.isFunction(callbackFunc)){ 316 let $dlg = window.hWin.HEURIST4.msg.getMsgDlg(dlg_id); 317 let ele = $dlg.find('#dlg-prompt-value'); 318 let val = ''; 319 if(ele.attr('type')!='checkbox' || ele.is(':checked')){ // Also handles checkbox type input 320 val = ele.val(); 321 } 322 callbackFunc.call(this, val); // Call the user-provided callback with the value 323 } 324 }, 325 window.hWin.HEURIST4.util.isempty(sTitle)?'Specify value':sTitle, ext_options); 326 327 }, 328 329 /** 330 * Retrieves or creates the primary jQuery UI Dialog element used for common messages. 331 * If the dialog element with the specified ID (default "dialog-common-messages") does not exist, 332 * it is created and appended to the body. 333 * 334 * @memberof HEURIST4.msg 335 * @param {string} [dialogId="dialog-common-messages"] - The ID of the dialog element to get or create. 336 * If a leading '#' is present, it's stripped. 337 * @returns {jQuery} The jQuery object representing the dialog div. 338 */ 339 getMsgDlg: function(dialogId){ 340 341 if(window.hWin.HEURIST4.util.isempty(dialogId)){ 342 dialogId = "dialog-common-messages"; 343 }else if(dialogId[0] == '#'){ 344 dialogId = dialogId.slice(0, 1); 345 } 346 347 let $dlg = $( "#" + dialogId ); 348 if($dlg.length==0){ 349 $dlg = $('<div>',{id: dialogId}) 350 .css({'min-wdith':'380px','max-width':'640px'}) //,padding:'1.5em 1em' 351 .appendTo( $('body') ); 352 } 353 return $dlg.removeClass('ui-heurist-border'); 354 }, 355 356 /** 357 * Retrieves or creates the jQuery UI Dialog element used for flash messages. 358 * If the dialog element with ID "dialog-flash-messages" does not exist, 359 * it is created and appended to the body. 360 * 361 * @memberof HEURIST4.msg 362 * @returns {jQuery} The jQuery object representing the flash message dialog div. 363 */ 364 getMsgFlashDlg: function(){ 365 let $dlg = $( "#dialog-flash-messages" ); 366 if($dlg.length==0){
367 $dlg = $('<div>',{id:'dialog-flash-messages'}).css({'min-wdith':'380px','max-width':'640px'}) 368 .appendTo('body'); //$(window.hWin.document) 369 } 370 return $dlg.removeClass('ui-heurist-border'); 371 }, 372 373 /** 374 * Retrieves or creates a generic jQuery UI Dialog element, typically used for popups that might require more space 375 * or custom styling (e.g., no width limit by default). 376 * If the dialog element with the specified ID (default "dialog-popup") does not exist, 377 * it is created and appended to the body. 378 * 379 * @memberof HEURIST4.msg 380 * @param {string} [element_id="dialog-popup"] - The ID of the dialog element to get or create. 381 * @returns {jQuery} The jQuery object representing the popup dialog div. 382 */ 383 getPopupDlg: function(element_id){ 384 if(!element_id) element_id = 'dialog-popup'; 385 let $dlg = $( '#'+element_id ); 386 if($dlg.length==0){ 387 $dlg = $('<div>',{id:element_id}) 388 .css({'padding':'2em','min-wdith':'380px',overflow:'hidden'}).appendTo('body'); 389 $dlg.removeClass('ui-heurist-border'); 390 } 391 return $dlg; 392 }, 393 394 395 /** 396 * Displays a jQuery UI tooltip associated with a given element for a specified duration. 397 * The tooltip content is taken from the `message` parameter. 398 * 399 * @memberof HEURIST4.msg 400 * @param {string} message - The message to display in the tooltip. This will be translated using `window.hWin.HR`. 401 * @param {number} [timeout=1000] - The duration in milliseconds for which the tooltip should be visible. Defaults to 1000ms. 402 * @param {jQuery|HTMLElement|Object} to_element - The target element for the tooltip. 403 * Can be a jQuery object, DOM element, or an object specifying 404 * `{of: targetElement, my: "...", at: "..."}` for custom positioning. 405 * If not an object, default positioning is "left top" at "left bottom". 406 * @returns {void} 407 */ 408 showTooltipFlash: function(message, timeout, to_element){ 409 410 if(!window.hWin.HEURIST4.util.isFunction(window.hWin.HR)){ 411 alert(message); // Fallback if HR (translation) function is not available 412 return; 413 } 414 415 if(window.hWin.HEURIST4.util.isempty(message) || window.hWin.HEURIST4.util.isnull(to_element)){ 416 return; 417 } 418 419 let position; 420 421 if($.isPlainObject(to_element)){ // Custom position provided 422 position = { my:to_element.my, at:to_element.at}; 423 to_element = to_element.of; 424 }else{ // Default position relative to the element 425 position = { my: "left top", at: "left bottom", of: $(to_element) }; 426 } 427 428 if (!(timeout>200)) { 429 timeout = 1000; 430 } 431 432 $( to_element ).attr('title',window.hWin.HR(message)); // Set title attribute for jQuery UI Tooltip 433 $( to_element ).tooltip({ 434 position: position, 435 // content: '<span>'+window.hWin.HR(message)+'</span>', // Alternative way to set content if needed 436 hide: { effect: "explode", duration: 500 } // Optional hide effect 437 }); 438 439 $( to_element ).tooltip('open'); // Open the tooltip 440 441 setTimeout(function(){ // Set timeout to close and clean up tooltip 442 $( to_element ).tooltip('close'); 443 $( to_element ).attr('title',null); // Remove title to prevent native tooltip 444 }, timeout); 445 446 }, 447 448 /** 449 * Displays a buttonless dialog (flash message) that automatically closes after a specified timeout. 450 * The dialog's content is the provided message. Styling and positioning can be customized. 451 * 452 * @memberof HEURIST4.msg 453 * @param {string|null} message - The message to display. If null, the function returns early. 454 * This message will be translated using `window.hWin.HR`. 455 * @param {number|boolean}
455 [timeout=1000] - Duration in milliseconds before the dialog auto-closes. 456 * If `false`, the dialog will not auto-close. Defaults to 1000ms. 457 * @param {Object|string} [options] - Configuration options for the dialog. 458 * If a string, it's treated as the dialog title. 459 * If an object, can include: 460 * @param {string} [options.title] - Title for the dialog. If null, title bar is hidden. 461 * @param {number} [options.height] - Height of the dialog. If not set, auto-calculated. 462 * Other jQuery UI dialog options can also be included. 463 * @param {jQuery|HTMLElement|Object} [position_to_element] - Element or position object to position the dialog relative to. 464 * If an object, e.g., `{ my: "center", at: "center", of: window }`. 465 * Defaults to centering on the document. 466 * @returns {void} 467 */ 468 showMsgFlash: function(message, timeout, options, position_to_element){ 469 470 if(!window.hWin.HEURIST4.util.isFunction(window.hWin.HR)){ 471 alert(message); // Fallback if HR (translation) function is not available 472 return; 473 } 474 475 if(!$.isPlainObject(options)){ 476 options = {title:options}; 477 } 478 479 let $dlg = window.hWin.HEURIST4.msg.getMsgFlashDlg(); 480 481 if(message==null){ 482 return; 483 } 484 485 $dlg.empty(); 486 let content = $('<span>'+window.hWin.HR(message)+'</span>') 487 .css({'overflow':'hidden','font-weight':'bold','font-size':'1.2em'}); 488 489 $dlg.append(content); 490 491 let hideTitle = (options.title==null); 492 if(options.title){ 493 options.title = window.hWin.HR(options.title); 494 } 495 496 $.extend(options, { 497 resizable: false, 498 width: 'auto', 499 modal: false, 500 //height: 80, 501 buttons: {} 502 }); 503 504 if(position_to_element){ 505 if($.isPlainObject(position_to_element)){ 506 options.position = position_to_element.position; 507 }else{ 508 options.position = { my: "left top", at: "left bottom", of: $(position_to_element) }; 509 } 510 }else{ 511 options.position = { my: "center center", at: "center center", of: $(document) }; 512 } 513 options.position.collision = 'none'; //FF fix 514 515 $dlg.dialog(options); 516 517 if(!(options.height>0)){ 518 let height = $(content).height()+90; 519 520 $dlg.dialog({height:height}); 521 } 522 523 524 content.position({ my: "center center", at: "center center", of: $dlg }); 525 526 if(hideTitle){ 527 $dlg.parent().find('.ui-dialog-titlebar').hide(); 528 } 529 530 $dlg.parent().css({background: '#95A7B7', 'border-radius': '6px', //#7092BE 531 'outline-style':'none', outline:'none'}) 532 $dlg.css({color:'white', border:'none !important', overflow:'hidden' }); 533 534 if(timeout!==false){ 535 536 if (!(timeout>200)) { 537 timeout = 1000; 538 } 539 setTimeout(window.hWin.HEURIST4.msg.closeMsgFlash, timeout); 540 541 } 542 }, 543 544 /** 545 * Closes the flash message dialog (`#dialog-flash-messages`). 546 * It also ensures the title bar is shown if it was previously hidden. 547 * This function respects a `coverallKeep` flag; if true, it won't close the dialog. 548 * 549 * @memberof HEURIST4.msg 550 * @returns {void} 551 */ 552 closeMsgFlash: function(){ 553 554 if(window.hWin.HEURIST4.msg.coverallKeep===true) return; 555 556 let $dlg = window.hWin.HEURIST4.msg.getMsgFlashDlg(); 557 if($dlg.dialog('instance')) $dlg.dialog('close'); 558 $dlg.parent().find('.ui-dialog-titlebar').show(); // Ensure title bar is visible for next time 559 }, 560 561 /**
562 * Displays a simple error dialog with the given message and an "Error" title. 563 * This is a convenience wrapper around `showMsgDlg`. 564 * 565 * @memberof HEURIST4.msg 566 * @param {string} message - The error message to display. 567 * @returns {void} 568 * @todo The name `redirectToError` is misleading as it doesn't actually redirect. Consider renaming or clarifying its purpose. 569 */ 570 redirectToError: function(message){ 571 window.hWin.HEURIST4.msg.showMsgDlg(message, null, 'Error'); 572 }, 573 574 /** 575 * Checks if the length of an input field's value is within a specified range [min, max]. 576 * If the length is outside the range, it displays a flash error message. 577 * 578 * Uses `checkLength2` to get the error message text and add error class. 579 * 580 * @memberof HEURIST4.msg 581 * @param {jQuery} input - jQuery object representing the input element. 582 * @param {string} title - A title or name for the input field, used in the error message (e.g., "Username"). 583 * @param {string|jQuery|null} message - If a jQuery object, its text is updated with the error message 584 * and 'ui-state-error' class is added/removed (this part is commented out). 585 * If a string, this custom error message is used for the flash display, 586 * overriding the default generated by `checkLength2`. 587 * If null or empty, `checkLength2`'s message is used. 588 * @param {number} min - The minimum allowed length for the input value. 589 * @param {number} max - The maximum allowed length for the input value. Use 0 or negative for no max limit. 590 * @returns {boolean} True if the length is valid, false otherwise. 591 */ 592 checkLength: function( input, title, message, min, max ) { 593 let message_text = window.hWin.HEURIST4.msg.checkLength2( input, title, min, max ); 594 if(message_text!=''){ 595 // Use custom message string if provided 596 if(!window.hWin.HEURIST4.util.isempty(message) && typeof message === 'string') message_text = message; 597 598 window.hWin.HEURIST4.msg.showMsgFlash('<span style="padding:10px;border:0;">'+message_text+'</span>', 3000); 599 600 return false; 601 }else{ 602 return true; 603 } 604 605 }, 606 607 /** 608 * Generates an error message if an input field's value length is outside a specified range. 609 * It also adds or removes the "ui-state-error" class from the input field based on validity. 610 * 611 * @memberof HEURIST4.msg 612 * @param {jQuery} input - jQuery object representing the input element. 613 * @param {string} title - A title or name for the input field, used in constructing the error message (e.g., "Username"). 614 * @param {number} min - The minimum allowed length. 615 * @param {number} max - The maximum allowed length. If 0 or negative, no maximum is enforced by this part of the logic, 616 * but the message might still reflect "between min and max" if min > 1. 617 * @returns {string} An error message string if the length is invalid, or an empty string if valid. 618 */ 619 checkLength2: function( input, title, min, max ) { 620 621 let len = input.val().length; 622 let message_text = ''; 623 624 if ( (max>0 && len > max) || len < min ) { 625 input.addClass( "ui-state-error" ); 626 if(max>0 && min>1){ 627 message_text = window.hWin.HR(title)+" "+window.hWin.HR("length must be between ") + 628 min + " "+window.hWin.HR("and")+" " + max + ". "; 629 if(len>=min){ // This condition seems off if len > max, it will always be true. 630 // The message should probably distinguish between too short and too long. 631 message_text = message_text + (len-max) + window.hWin.HR(" characters over"); 632 } 633 634 }else if(min>1){ // Only min length is specified (or max <= 0) 635 message_text = window.hWin.HR(title)+" "+window.hWin.HR('. At least '+min+' characters required'); 636 }else if(min==1){ // Required field (min length 1) 637 message_text = window.hWin.HR(title)+" "+window.hWin.HR(" is required field"); 638 } 639 // If only max is specified (min is 0 or 1, and it's too long), current logic doesn't explicitly state "too long". 640 // It might fall into the "length must be between" if min > 1, or no specific message if min <=1. 641 642 return message_text; 643 644 } else { 645 input.removeClass( "ui-state-error" ); 646 return ''; 647 } 648 }, 649 650 651 /**
652 * Displays a URL content within an iframe embedded in a jQuery UI popup dialog. 653 * This function is highly configurable through the `options` parameter. 654 * It handles creating the dialog, loading the iframe, and setting up communication 655 * (e.g., overriding `alert`, providing `close` and `doDialogResize` functions to the iframe content). 656 * 657 * @memberof HEURIST4.msg 658 * @param {string} url - The URL to load into the iframe. If empty, behavior might be undefined or rely on other options. 659 * @param {Object} [options] - Configuration options for the dialog and iframe. 660 * @param {string} [options.dialogid] - A unique ID for the dialog. If provided, the dialog div is reused/reopened 661 * instead of being removed on close. 662 * @param {boolean} [options.force_reload=false] - If `dialogid` is used and the dialog already exists, 663 * force iframe content to reload even if URL is the same. 664 * @param {jQuery} [options.container] - If provided, uses `showDialogInDiv` to render the dialog inline within this container. 665 * @param {string} [options.title=''] - Title for the dialog. 666 * @param {Window} [options.window=window] - The opener window object. 667 * @param {Object} [options.params] - Parameters to pass to the iframe's `assignParameters` function, if it exists. 668 * @param {function} [options.onpopupload] - Callback function executed after the iframe content has loaded. 669 * Receives the iframe DOM element as an argument. 670 * @param {function} [options.callback] - Callback function executed when the iframe attempts to close (e.g., via `contentWindow.close()`). 671 * If it returns `false`, the dialog closing is prevented. 672 * @param {function} [options.afterclose] - Callback function executed after the dialog has been closed. 673 * @param {boolean} [options.modal=true] - Whether the dialog should be modal. 674 * @param {function} [options.onmouseover] - Event listener for mouseover on the dialog title bar or iframe. 675 * @param {string} [options.default_palette_class] - A CSS class to apply to the dialog for theming. 676 * @param {string} [options.padding] - CSS padding for the dialog content area. 677 * @param {string} [options.padding-content] - CSS padding for the direct ui-dialog-content parent of the iframe. 678 * @param {boolean} [options.allowfullscreen=false] - If true, adds `allowfullscreen` attributes to the iframe. 679 * @param {boolean} [options.noClose=false] - If true, hides the close button on the dialog title bar. 680 * @param {boolean} [options.borderless=false] - If true, removes dialog border and title bar. 681 * @param {string|number} [options.width='640'] - Initial width of the dialog. 682 * @param {string|number} [options.height='480'] - Initial height of the dialog. 683 * @param {boolean} [options.resizable=true] - Whether the dialog is resizable. 684 * @param {boolean} [options.draggable=true] - Whether the dialog is draggable. 685 * @param {boolean} [options.closeOnEscape=true] - Whether to close the dialog on ESC key. 686 * @param {function} [options.beforeClose] - Callback function executed before the dialog closes. 687 * @param {function} [options.onOpen] - Callback function executed when the dialog opens. 688 * @param {string} [options.class] - Additional CSS class for the dialog main div. 689 * @param {boolean} [options.is_h6style=false] - If true, applies Heurist 6 specific styling and layout adjustments. 690 * @param {Object} [options.position] - jQuery UI position object for dialog placement. 691 * @param {boolean} [options.maximize=false] - If true (and not `is_h6style`), attempts to maximize the dialog. 692 * @param {string} [options.coverMsg] - Message to display on the loading coverall while iframe loads. 693 * @returns {void} (The function primarily has side effects by creating and showing a dialog). 694 * If `options.container` is provided, it effectively calls `showDialogInDiv` which also returns void. 695 * The created dialog jQuery object is not directly returned. 696 */ 697 showDialog: function(url, options){ 698 699 if(!options) options = {}; 700 701 if(options.container){ 702 window.hWin.HEURIST4.msg.showDialogInDiv(url, options); 703 return; 704 } 705 706 707 if(!options.title) options.title = ''; // removed 'Information' which is not a particualrly useful title 708
709 let opener = options['window']?options['window'] :window; 710 711 //.appendTo( that.document.find('body') ) 712 let $dlg = []; 713 714 if(options['dialogid']){ 715 $dlg = $(opener.document).find('body #'+options['dialogid']); 716 } 717 718 719 let $dosframe; 720 721 722 function __canAccessIframe(iframe) { 723 try { 724 return Boolean(iframe.contentDocument); 725 } 726 catch(e){ 727 return false; 728 } 729 } 730 731 if($dlg.length>0){ 732 //reassign dialog onclose and call new parameters 733 734 $dlg.dialog('open'); 735 736 $dosframe = $dlg.find('iframe'); 737 if(__canAccessIframe($dosframe[0])) 738 { 739 let content = $dosframe[0].contentWindow; 740 741 //close dialog from inside of frame - need redifine each time 742 content.close = function() { 743 let rval = true; 744 let closeCallback = options['callback']; 745 if(window.hWin.HEURIST4.util.isFunction(closeCallback)){ 746 rval = closeCallback.apply(opener, arguments); 747 } 748 if ( rval===false ){ //!rval && rval !== undefined){ 749 return false; 750 } 751 $dlg.dialog('close'); 752 return true; 753 }; 754 755 // if content in iframe has function "assignParameters" we may pass parameters 756 if(options['params'] && window.hWin.HEURIST4.util.isFunction(content.assignParameters)) { 757 content.assignParameters(options['params']); 758 } 759 } 760 if(options['height']>0 && options['width']>0){ 761 $dlg.dialog('option', 'width', options['width']); 762 $dlg.dialog('option', 'height', options['height']); 763 } 764 765 if($dosframe.attr('src')!=url || options['force_reload']){ // hide previous content 766 $dosframe.hide(); 767 $dlg.addClass('loading'); 768 } 769 770 }else{ 771 772 773 $dlg = $('<div>') 774 .addClass('loading') 775 .appendTo( $(opener.document).find('body') ); 776 777 if(options['dialogid']){ 778 $dlg.attr('id', options['dialogid']); 779 }else{ 780 $dlg.uniqueId(); 781 } 782 783 784 if(options.class){ 785 $dlg.addClass(options.class); 786 } 787 788 $dosframe = $( "<iframe>").attr('parent-dlg-id', $dlg.attr('id')) 789 .css({border:'none', overflow: 'none !important', width:'100% !important'}).appendTo( $dlg ); 790 791 if(options['allowfullscreen']){ 792 $dosframe.attr('allowfullscreen',true); 793 $dosframe.attr('webkitallowfullscreen',true); 794 $dosframe.attr('mozallowfullscreen',true); 795 796 797 } 798 799 $dosframe.hide(); 800 //callback function to resize dialog from internal frame functions 801 $dosframe[0].doDialogResize = function(width, height) { 802 803 /* 804 let body = $(this.document).find('body'); 805 let dim = { h: Math.max(400, body.innerHeight()-10), w:Math.max(400, body.innerWidth()-10) }; 806 807 if(width>0) 808 $dlg.dialog('option','width', Math.min(dim.w, width)); 809 if(height>0) 810 $dlg.dialog('option','height', Math.min(dim.h, height)); 811 */ 812 }; 813 814 //on load content event listener 815 $dosframe.on('load', function(){ 816 if(window.hWin.HEURIST4.util.isempty($dosframe.attr('src'))){ 817 return; 818 } 819 820 window.hWin.HEURIST4.msg.sendCoverallToBack(); 821 822 let has_access = __canAccessIframe($dosframe[0]); 823 824 if(has_access) 825 { 826 827 let content = $dosframe[0].contentWindow; 828 try{ 829 //replace standard "alert" to Heurist dialog 830 content.alert = function(txt){ 831 let $dlg_alert = window.hWin.HEURIST4.msg.showMsgDlg(txt, null, ""); // Title was an unhelpful and inelegant "Info" 832 $dlg_alert.dialog('open'); 833 return true; 834 } 835 }catch(e){ 836 console.error(e); 837 } 838 839 if(!options["title"]){ 840 $dlg.dialog( "option", "title", content.document.title ); 841 } 842 843 /* 844 content.confirm = function(txt){ 845 var resConfirm = false, 846 isClosed = false;
847 848 var $confirm_dlg = window.hWin.HEURIST4.msg.showMsgDlg(txt, function(){ 849 resConfirm = true; 850 }, "Confirm"); 851 852 $confirm_dlg.dialog('option','close', 853 function(){ 854 isClosed = true; 855 }); 856 857 while(!isClosed){ 858 $.wait(1000); 859 } 860 861 return resConfirm; 862 }*/ 863 864 865 866 //functions in internal document 867 //content.close = $dosframe[0].close; // make window.close() do what we expect 868 869 //close dialog from inside of frame 870 content.close = function() { 871 let rval = true; 872 let closeCallback = options['callback']; 873 if(window.hWin.HEURIST4.util.isFunction(closeCallback)){ 874 rval = closeCallback.apply(opener, arguments); 875 } 876 if ( rval===false ){ //!rval && rval !== undefined){ 877 return false; 878 } 879 $dlg.dialog('close'); 880 return true; 881 }; 882 883 884 content.doDialogResize = $dosframe[0].doDialogResize; 885 886 } 887 888 $dlg.removeClass('loading'); 889 $dosframe.show(); 890 891 let onloadCallback = options['onpopupload']; 892 if(onloadCallback){ 893 onloadCallback.call(opener, $dosframe[0]); 894 } 895 896 if(has_access){ 897 let content = $dosframe[0].contentWindow; 898 899 if(window.hWin.HEURIST4.util.isFunction(content.onFirstInit)) { //see mapPreview 900 content.onFirstInit(); 901 } 902 //pass parameters to frame 903 if(options['params'] && window.hWin.HEURIST4.util.isFunction(content.assignParameters)) { 904 content.assignParameters(options['params']); 905 } 906 } 907 908 if(options['onmouseover']){ 909 $dlg.parent().find('.ui-dialog-titlebar').on('mouseover', function(){ 910 options['onmouseover'].call(); 911 }); 912 $dosframe.on('mouseover', function(){ 913 options['onmouseover'].call(); 914 }); 915 } 916 917 918 }); 919 920 options.width = window.hWin.HEURIST4.msg._setDialogDimension(options, 'width'); 921 options.height = window.hWin.HEURIST4.msg._setDialogDimension(options, 'height'); 922 923 let opts = { 924 autoOpen: true, 925 width : options.width, 926 height: options.height, 927 modal: (options['modal']!==false), 928 resizable: (options.resizable!==false), 929 draggable: (options.draggable!==false), 930 title: options["title"], 931 resizeStop: function( event, ui ) { 932 $dosframe.css('width','100%'); 933 }, 934 closeOnEscape: options.closeOnEscape, 935 beforeClose: options.beforeClose, 936 close: function(event, ui){ 937 let closeCallback = options['afterclose']; 938 if(window.hWin.HEURIST4.util.isFunction(closeCallback)){ 939 closeCallback.apply(); 940 } 941 if(!options['dialogid']){ 942 $dlg.remove(); 943 } 944 }, 945 open: options.onOpen 946 }; 947 $dlg.dialog(opts); 948 949 if($dlg.attr('data-palette')) 950 $dlg.parent().removeClass($dlg.attr('data-palette')); 951 if(options.default_palette_class){ 952 $dlg.attr('data-palette', options.default_palette_class); 953 $dlg.parent().addClass(options.default_palette_class); 954 }else{ 955 $dlg.attr('data-palette', null); 956 } 957 958 $dlg.parent().find(".ui-dialog-title").html(options["title"]); 959 $dlg.parent().find('.ui-dialog-content').css({'overflow':'hidden'}); 960 961 if(options.noClose){ 962 $dlg.parent().find('.ui-dialog-titlebar').find('.ui-icon-closethick').parent().hide(); 963 } 964 965 966 if(!window.hWin.HEURIST4.util.isempty(options['padding'])){ //by default 2em 967 $dlg.css('padding', options.padding); 968 } 969 if(!window.hWin.HEURIST4.util.isempty(options['padding-content'])){ 970 $dlg.parent().find('.ui-dialog-content').css('padding', options['padding-content']); 971 } 972 973 if(!options.is_h6style && options.maximize){ 974 function __maximizeOneResize(){ 975 let dialog_height = window.innerHeight; 976 $dlg.dialog( 'option', 'height', dialog_height); 977 $dlg.dialog( 'option', 'width', '100%'); //dialog_width 978 } 979 __maximizeOneResize(); 980 } 981 if(options.borderless){ 982 $dlg.css('padding',0); 983 $dlg.parent() //s(".ui-dialog") 984 .css("border", "0 none") 985 .find(".ui-dialog-titlebar").remove(); 986 } 987 988 } 989 990 991 if(options.is_h6style) 992 { 993 994 $dlg.parent().addClass('ui-dialog-heurist ui-heurist-explore'); 995 996 if(options.container){ 997 998 $dlg.dialog( 'option', 'position', 999 { my: "left top", at: "left top", of:options.container, collision:'none'}); 1000
1001 function __adjustOneResize(e){ 1002 let ele = e ?$(e.target) :options.container; 1003 1004 let dialog_height = ele.height(); 1005 $dlg.dialog( 'option', 'height', dialog_height); 1006 let dialog_width = ele.width(); 1007 $dlg.dialog( 'option', 'width', dialog_width); 1008 } 1009 //$(window).on('onresize',__adjustOneResize) 1010 options.container.off('resize'); 1011 options.container.on('resize', __adjustOneResize); 1012 __adjustOneResize(); 1013 1014 }else 1015 if(options.position){ 1016 $dlg.dialog( 'option', 'position', options.position ); 1017 if(options.maximize){ 1018 function __maximizeOneResize(){ 1019 let dialog_height = window.innerHeight - $dlg.parent().position().top - 5; 1020 $dlg.dialog( 'option', 'height', dialog_height); 1021 let dialog_width = window.innerWidth - $dlg.parent().position().left - 5; 1022 $dlg.dialog( 'option', 'width', dialog_width); 1023 } 1024 __maximizeOneResize(); 1025 }else{ 1026 if($dlg.parent().position().left<0){ 1027 $dlg.parent().css({left:0}); 1028 }else{ 1029 let max_width = window.innerWidth - $dlg.parent().position().left - 5; 1030 let dlg_width = $dlg.dialog( 'option', 'width'); 1031 if(max_width<380 || $dlg.parent().position().left<0){ 1032 $dlg.parent().css({left:0}); 1033 $dlg.dialog( 'option', 'width', 380); 1034 }else if(dlg_width>max_width){ 1035 $dlg.dialog( 'option', 'width', max_width); 1036 } 1037 } 1038 } 1039 } 1040 } 1041 1042 //start content loading 1043 if(url!='' && ($dosframe.attr('src')!=url || options['force_reload'])){ 1044 if(options.coverMsg){ 1045 window.hWin.HEURIST4.msg.bringCoverallToFront($dlg, {'font-size': '16px', color: 'white'}, options.coverMsg); 1046 } 1047 $dosframe.attr('src', url); 1048 } 1049 1050 }, 1051 1052 /** 1053 * Displays URL content within an iframe embedded directly (inline) into a specified container element, 1054 * styled according to Heurist 6 UI guidelines. This is an alternative to popup dialogs. 1055 * 1056 * @memberof HEURIST4.msg 1057 * @param {string} url - The URL to load into the iframe. 1058 * @param {Object} [options] - Configuration options. 1059 * @param {jQuery} options.container - The jQuery object of the container element where the iframe will be placed. 1060 * @param {string} [options.title=' '] - Title to display in a header bar above the iframe content. 1061 * If empty, the header bar might be hidden or take less space. 1062 * @param {string} [options.context_help] - URL for a context-sensitive help page, linked via an info button in the header. 1063 * @param {boolean} [options.show_help_on_init=true] - If `context_help` is provided, whether to open the help panel immediately. 1064 * @param {Object} [options.params] - Parameters to pass to the iframe's `assignParameters` function. 1065 * @param {function} [options.doDialogResize] - A function to be made available to the iframe content for resizing itself. 1066 * @param {function} [options.onContentLoad] - Callback executed after iframe content loads. Receives iframe DOM element. 1067 * @param {function} [options.beforeClose] - Callback executed before the container/dialog is hidden or closed. 1068 * If it returns `false`, closing is prevented. 1069 * @param {function} [options.afterClose] - Callback executed after the container/dialog is hidden or closed. 1070 * @param {string} [options.padding] - CSS padding for the main container. 1071 * @returns {jQuery|undefined} The jQuery object of the iframe if it's reused, otherwise undefined. 1072 * The function primarily modifies the DOM and sets up event handlers. 1073 */ 1074 showDialogInDiv: function(url, options){ 1075 1076 if(!options) options = {}; 1077 1078 if(!options.title) options.title = ' '; 1079 1080 let $container = options['container']; 1081 1082 let $dosframe = $container.find('iframe'); 1083 let _innerTitle = $container.children('.ui-heurist-header'); 1084 let frame_container = $container.children('.ent_content_full'); 1085 let $info_button; 1086 1087 if($dosframe.length==0) 1088 { 1089 $container.empty(); 1090 //add h6 style header 1091 _innerTitle = $('<div>').addClass('ui-heurist-header') 1092 .appendTo($container); 1093
1094 frame_container = $('<div>').addClass('ent_content_full') 1095 .css({'top':'37px','bottom':'1px', 'overflow':'hidden'}) 1096 .appendTo($container); 1097 1098 1099 $dosframe = $( "<iframe>") 1100 .css({height:'100%', width:'100%'}) 1101 .appendTo( frame_container ); 1102 }else{ 1103 if($dosframe.attr('src')==url){ //not changed 1104 $container.show(); 1105 return $dosframe; 1106 } 1107 } 1108 1109 _innerTitle.empty(); 1110 if(options.title){ 1111 _innerTitle.text(options['title']); 1112 _innerTitle.show(); 1113 frame_container.css('top','37px'); 1114 }else{ 1115 _innerTitle.text(''); 1116 _innerTitle.hide(); 1117 frame_container.css('top','0px'); 1118 } 1119 1120 function __onDialogClose() { 1121 1122 let canClose = true; 1123 if(window.hWin.HEURIST4.util.isFunction(options['beforeClose'])){ 1124 canClose = options['beforeClose'].call( $dosframe[0], arguments ); 1125 } 1126 if(canClose===false){ 1127 return false; 1128 }else{ 1129 $container.hide(); 1130 if(window.hWin.HEURIST4.util.isFunction(options['afterClose'])){ 1131 canClose = options['afterClose'].call( $dosframe[0], arguments ); 1132 } 1133 return true; 1134 } 1135 }; 1136 1137 //init close button 1138 $('<button>').button({icon:'ui-icon-closethick',showLabel:false, title:'Close'}) 1139 //classes:'ui-corner-all ui-dialog-titlebar-close'}) 1140 .css({'position':'absolute', 'right':'4px', 'top':'6px', height:24, width:24}) 1141 .appendTo(_innerTitle) 1142 .on({click:function(){ 1143 __onDialogClose(); 1144 }}); 1145 1146 //init help button 1147 if( options['context_help'] && window.hWin.HEURIST4.ui ){ 1148 1149 $info_button = $('<button>') 1150 .button({icon:'ui-icon-circle-help', showLabel:false, label:'Help'}) 1151 .addClass('ui-helper-popup-button') 1152 .css({'position':'absolute', 'right':'34px', 'top':'6px', height:24, width:24}) 1153 .appendTo(_innerTitle); 1154 1155 window.hWin.HEURIST4.ui.initHelper({ 1156 button:$info_button, 1157 url: window.hWin.HRes(options['context_help']), 1158 position:{my:'right top', at:'right top', of:$container}, 1159 container: $container, 1160 is_open_at_once: options['show_help_on_init']===false ? false : true 1161 }); 1162 }else{ 1163 //hide helper div 1164 frame_container.css({right:1}); 1165 $container.children('.ui-helper-popup').hide(); 1166 } 1167 1168 if(navigator.userAgent.indexOf('Firefox')<0){ 1169 $dosframe.hide(); 1170 } 1171 1172 frame_container.addClass('loading'); 1173 1174 //callback function to resize dialog from internal iframe functions 1175 1176 1177 $dosframe.off('load'); 1178 //on load content event listener 1179 $dosframe.on('load', function(){ 1180 1181 if(window.hWin.HEURIST4.util.isempty($dosframe.attr('src'))){ 1182 return; 1183 } 1184 1185 let content = $dosframe[0].contentWindow; 1186 1187 //replace native alert 1188 try{ 1189 content.alert = function(txt){ 1190 let $dlg_alert = window.hWin.HEURIST4.msg.showMsgDlg(txt, null, ""); // Title was an unhelpful and inelegant "Info" 1191 $dlg_alert.dialog('open'); 1192 return true; 1193 } 1194 }catch(e){ 1195 console.error(e); 1196 } 1197 1198 frame_container.removeClass('loading'); 1199 $dosframe.show(); 1200 1201 //close dialog from inside of frame 1202 content.close = __onDialogClose; 1203 1204 1205 if(window.hWin.HEURIST4.util.isFunction(options['doDialogResize'])){ 1206 content.doDialogResize = options['doDialogResize']; 1207 } 1208 if(window.hWin.HEURIST4.util.isFunction(options['onContentLoad'])){ 1209 options['onContentLoad'].call(this, $dosframe[0]); 1210 } 1211 1212 //pass params into iframe 1213 if(options['params'] && window.hWin.HEURIST4.util.isFunction(content.assignParameters)) { 1214 content.assignParameters(options['params']); 1215 } 1216 1217 }); //onload 1218 1219 1220 if(!window.hWin.HEURIST4.util.isempty(options['padding'])){ 1221 //by default 2em 1222 $container.css('padding', options.padding); 1223 } 1224 1225 //start content loading 1226 $dosframe.attr('src', url); 1227 1228 1229 }, 1230 1231 // 1232 // 1233 // 1234 _setDialogDimension: function(options, axis){ 1235
1236 let opener = options['window']?options['window'] :window; 1237 1238 let wp = 0; 1239 1240 if(axis=='width'){ 1241 wp = (opener && opener.innerWidth>0)? opener.innerWidth 1242 :(window.hWin?window.hWin.innerWidth:window.innerWidth); 1243 }else{ 1244 wp = (opener && opener.innerHeight>0)? opener.innerHeight 1245 :(window.hWin?window.hWin.innerHeight:window.innerHeight); 1246 } 1247 1248 let res; 1249 1250 if(typeof options[axis]==='string'){ 1251 let isPercent = (options[axis].indexOf('%')>0); 1252 1253 res = parseInt(options[axis], 10); 1254 1255 if(isPercent){ 1256 res = wp*res/100; 1257 } 1258 }else{ 1259 res = options[axis]; 1260 } 1261 1262 if(isNaN(res) || res<100){ 1263 res = (axis=='width')?640:480; 1264 } 1265 1266 if(res > wp){ 1267 res = wp; 1268 } 1269 1270 return res; 1271 1272 }, 1273 1274 /** 1275 * Displays an existing DOM element within a jQuery UI popup dialog. 1276 * The element is temporarily detached from its original position in the DOM, 1277 * shown in the dialog, and then re-appended to its original parent when the dialog is closed. 1278 * The dialog itself is removed from the DOM after closing, unless `onCloseCalback` returns false. 1279 * 1280 * @memberof HEURIST4.msg 1281 * @param {Object} options - Configuration options for the dialog. 1282 * @param {HTMLElement} options.element - The DOM element to display in the dialog. 1283 * @param {Window} [options.window=window] - The window object where the dialog will be created. 1284 * @param {string|number} [options.width='640'] - Initial width of the dialog. 1285 * @param {string|number} [options.height='480'] - Initial height of the dialog. 1286 * @param {boolean} [options.autoOpen=true] - Whether to open the dialog immediately. 1287 * @param {boolean} [options.modal=true] - Whether the dialog should be modal. 1288 * @param {boolean} [options.resizable=true] - Whether the dialog is resizable. 1289 * @param {string} [options.title] - Title for the dialog. 1290 * @param {Object<string, function>} [options.buttons] - Buttons for the dialog. 1291 * @param {function} [options.open] - Callback executed when the dialog opens. 1292 * @param {function} [options.beforeClose] - Callback executed before the dialog closes. 1293 * @param {function} [options.close] - Callback executed after the dialog closes and the element is reattached. 1294 * If this callback returns `false`, the dialog div is not removed from DOM. 1295 * @param {Object} [options.position] - jQuery UI position object for dialog placement. 1296 * @param {boolean} [options.borderless=false] - If true, removes dialog border and title bar. 1297 * @param {string} [options.default_palette_class] - A CSS class to apply to the dialog for theming. 1298 * @param {string} [options.h6style_class] - Additional class for Heurist 6 styling. 1299 * @returns {jQuery} The jQuery object representing the created dialog. 1300 */ 1301 showElementAsDialog: function(options){ 1302 1303 let opener = options['window']?options['window'] :window; 1304 1305 let $dlg = $('<div>') 1306 .appendTo( $(opener.document).find('body') ); 1307 1308 let element = options['element']; 1309 let originalParentNode = element.parentNode; 1310 originalParentNode.removeChild(element); 1311 1312 $(element).show().appendTo($dlg); 1313 1314 let dimW = window.hWin.HEURIST4.msg._setDialogDimension(options, 'width'); 1315 let dimH = window.hWin.HEURIST4.msg._setDialogDimension(options, 'height'); 1316 1317 let onCloseCalback = (options['close'])?options.close:null; 1318 1319 let opts = { 1320 autoOpen:(options['autoOpen']!==false), 1321 width : dimW, 1322 height: dimH, 1323 modal: (options.modal!==false), 1324 resizable: (options.resizable!==false), 1325 //draggable: false, 1326 title: options["title"], 1327 buttons: options["buttons"], 1328 open: options.open, //callback 1329 beforeClose: options.beforeClose, 1330 close: function(event, ui){ 1331 1332 let need_remove = true; 1333 if(window.hWin.HEURIST4.util.isFunction(onCloseCalback)){ 1334 need_remove = onCloseCalback.call(this, event, ui); 1335 } 1336 1337 if(need_remove!==false){ 1338 element.parentNode.removeChild(element); 1339 element.style.display = "none"; 1340 originalParentNode.appendChild(element); 1341 1342 $dlg.remove(); 1343 } 1344 } 1345 }; 1346 if(options["position"]) opts["position"] = options["position"]; 1347 1348 $dlg.dialog(opts); 1349 1350 if(options.borderless){ 1351 $dlg.css('padding',0); 1352 $dlg.parent() //s(".ui-dialog") 1353 .css("border", "0 none") 1354 .find(".ui-dialog-titlebar").remove(); 1355 } 1356 1357 if(options.default_palette_class){ 1358 $dlg.attr('data-palette', options.default_palette_class); 1359 $dlg.parent().addClass(options.default_palette_class); 1360 }else { 1361 $dlg.attr('data-palette', null); 1362 if (options.h6style_class){ 1363 $dlg.parent().addClass('ui-dialog-heurist '+options.h6style_class); 1364 } 1365 } 1366 1367 1368 1369 return $dlg; 1370 }, 1371 1372 /** 1373 * Creates an HTML string for a styled alert message div. 1374 * The div includes an alert icon and the provided message, styled with "ui-state-error". 1375 * 1376 * @memberof HEURIST4.msg 1377 * @param {string} msg - The message content for the alert. 1378 * @returns {string} HTML string representing the alert div. 1379 */ 1380 createAlertDiv: function(msg){ 1381 1382 return '<div class="ui-state-error" style="width:90%;margin:auto;margin-top:10px;padding:10px;">'+ 1383 '<span class="ui-icon ui-icon-alert" style="float: left; margin-right: .3em;"></span>'+ 1384 msg+'</div>'; 1385 }, 1386 1387 /**
1388 * Displays a "coverall" overlay, typically used as a loading indicator that blocks UI interaction. 1389 * It creates or reuses a div element, styles it, sets a message, and appends it to a specified element or body. 1390 * 1391 * @memberof HEURIST4.msg 1392 * @param {jQuery|HTMLElement} [ele=jQuery('body')] - The element to append the coverall to. Defaults to document body. 1393 * @param {Object} [styles] - CSS styles to apply to the coverall div. 1394 * Defaults to an opacity, background color, text color, font size, and font weight. 1395 * @param {string} [message="Loading Content..."] - The message to display within the coverall. 1396 * Translated using `window.hWin.HR` if available. 1397 * @returns {void} 1398 */ 1399 bringCoverallToFront: function(ele, styles, message) { 1400 if (! window.hWin.HEURIST4.msg.coverall ) { 1401 window.hWin.HEURIST4.msg.coverall = 1402 $('<div><div class="internal_msg" style="position: absolute;top: 30px;left: 30px;">' 1403 +'</div></div>').addClass('coverall-div') 1404 .css({ 1405 'zIndex': 60000, // Higher zIndex to cover most elements 1406 'font-size': '1.2em' 1407 }); 1408 }else{ 1409 window.hWin.HEURIST4.msg.coverall.detach(); // Detach if exists to re-append later, ensuring it's on top or in new context 1410 } 1411 1412 if(!message){ 1413 message = 'Loading Content'; 1414 message = (window.hWin.HEURIST4.util.isFunction(window.hWin.HR)?window.hWin.HR(message):message)+'...'; 1415 } 1416 window.hWin.HEURIST4.msg.coverall.find('.internal_msg').html(message); 1417 1418 let $appendTo = ele ? $(ele) : $('body'); 1419 if($appendTo.find('.coverall-div').length==0) { // Append only if not already there (e.g. if ele is specific) 1420 window.hWin.HEURIST4.msg.coverall.appendTo($appendTo); 1421 } 1422 1423 if(!styles){ 1424 styles = {opacity: '0.6', 'background-color': "rgb(0, 0, 0)", color: 'rgb(0, 190, 0)', 'font-size': '20px', 'font-weight': 'bold'}; 1425 } 1426 window.hWin.HEURIST4.msg.coverall.css( styles ); 1427 1428 $(window.hWin.HEURIST4.msg.coverall).show(); 1429 }, 1430 1431 /** 1432 * Hides the "coverall" overlay. 1433 * This function respects a `coverallKeep` flag; if true, it won't hide the overlay unless `force_close` is also true. 1434 * 1435 * @memberof HEURIST4.msg 1436 * @param {boolean} [force_close=false] - If true, forces the `coverallKeep` flag to false, ensuring the overlay is hidden. 1437 * @returns {void} 1438 */ 1439 sendCoverallToBack: function(force_close) { 1440 if(force_close===true) window.hWin.HEURIST4.msg.coverallKeep = false; 1441 if(window.hWin.HEURIST4.msg.coverallKeep===true) return; // Don't hide if explicitly kept 1442 if(window.hWin.HEURIST4.msg.coverall) $(window.hWin.HEURIST4.msg.coverall).hide(); // Hide only if coverall exists 1443 }, 1444 1445 1446 /** 1447 * A simplified method to show a message dialog. It's a wrapper around `showMsgDlg`. 1448 * This function is intended for displaying a simple message with default "OK" button behavior. 1449 * 1450 * @memberof HEURIST4.msg 1451 * @param {string|jQuery|Object} message - The message content to display. Can be a string, HTML, or jQuery object. 1452 * @param {Object} [options] - Additional options passed directly to `showMsgDlg`. 1453 * These options can customize aspects like title, buttons (though typically not used here), 1454 * positioning, and styling. See `showMsgDlg` for more details on `ext_options`. 1455 * @returns {jQuery} The jQuery object representing the dialog element. 1456 */ 1457 showMsg: function(message, options){ 1458 return window.hWin.HEURIST4.msg.showMsgDlg(message, null, null, options ) 1459 }, 1460 1461 /** 1462 * Displays a general-purpose jQuery UI dialog with a message and customizable buttons. 1463 * This is a core function for creating various types of dialogs (info, confirmation, error). 1464 * 1465 * @memberof HEURIST4.msg 1466 * @param {string|jQuery|Object|null} message - The content to display in the dialog. 1467 * Can be a string (HTML is allowed), a jQuery object, or a DOM element. 1468 * If null, the dialog is created/opened, but content must be set separately. 1469 * @param {Object<string, function>|function} [buttons] - Defines the buttons for the dialog. 1470 * - If an object: Keys are button labels (will be translated by `HR`), 1471 * values are click handler functions. 1472 * - If a function: Assumed to be a callback for a default "Yes"/"No" button set. 1473 * "Yes" executes the callback, "No" closes the dialog. 1474 * - If null/undefined: A default "OK" button is shown which closes the dialog. 1475 * @param {string|Object<string, string>} [labels] - Custom labels for title and buttons. 1476 * - If a string: Used as the dialog title (will be translated by `HR`). 1477 * - If an object: Can contain `title`, `yes`, `no`, `ok`, `cancel` properties 1478 * for localized strings (will be translated by `HR`). 1479 * Default title is "·" (was 'Info'). 1480 * @param {Object} [ext_options] - Extended options for jQuery UI Dialog and custom behavior. 1481 * @param {string} [ext_options.default_palette_class] - CSS class for dialog theming. 1482 * @param {Object} [ext_options.position] - jQuery UI position object. Defaults to center of window. 1483 * Can also be specified via `my`, `at`, `of` properties directly in `ext_options`. 1484 * @param {jQuery} [ext_options.container] - If provided, uses `getPopupDlg` with this container for dialog creation. 1485 * @param {boolean} [ext_options.isPopupDlg=false] - If true (or `container` is set), uses `getPopupDlg`. 1486 * @param {string} [ext_options.dialogId="dialog-common-messages"] - ID for the dialog element. 1487 * @param {boolean} [ext_options.removeOnClose=false] - If true, the dialog DOM element is removed when closed. 1488 * @param {boolean} [ext_options.hideTitle=false] - If true, hides the dialog title bar. 1489 * @param {number|string} [ext_options.width='auto'] - Dialog width. For non-popup, defaults to 'auto'. For popups, defaults to 705. 1490 * @param {number|string} [ext_options.height] - Dialog height. For popups, defaults to 515. 1491 * @param {boolean} [ext_options.resizable=false] - Whether the dialog is resizable. (Popup default: true). 1492 * @param {boolean} [ext_options.modal=true] - Whether the dialog is modal. 1493 * @returns {jQuery} The jQuery object representing the dialog element. 1494 */ 1495 showMsgDlg: function(message, buttons, labels, ext_options){ 1496 1497 if(!window.hWin.HEURIST4.util.isFunction(window.hWin.HR)){ // Fallback if HR (translation) function is not available 1498 alert(message); 1499 return; 1500 } 1501 1502 if(!ext_options) ext_options = {}; 1503 1504 1505 if(ext_options['buttons']){ 1506 buttons = ext_options['buttons']; 1507 delete ext_options['buttons']; 1508 } 1509 if(ext_options['labels']){ 1510 labels = ext_options['labels']; 1511 delete ext_options['labels']; 1512 } 1513 1514 let isPopupDlg = (ext_options.isPopupDlg || ext_options.container); 1515 let dialogId = (!ext_options.dialogId) ? 'dialog-common-messages' : ext_options.dialogId; 1516
1517 let $dlg = isPopupDlg //show popup in specified container 1518 ?window.hWin.HEURIST4.msg.getPopupDlg(ext_options.container) 1519 :window.hWin.HEURIST4.msg.getMsgDlg(dialogId); 1520 1521 if(message!=null){ 1522 1523 let isobj = (typeof message === "object"); 1524 1525 if(!isobj){ 1526 isPopupDlg = isPopupDlg || (message.indexOf('#')===0 && $(message).length>0); 1527 } 1528 1529 if(isPopupDlg){ 1530 1531 $dlg = window.hWin.HEURIST4.msg.getPopupDlg( ext_options.container ); 1532 if(isobj){ 1533 $dlg.append(message); 1534 }else if(message.indexOf('#')===0 && $(message).length>0){ 1535 //it seems it is in Digital Harlem only 1536 $dlg.html($(message).html()); 1537 }else{ 1538 $dlg.html(message); 1539 } 1540 1541 }else{ 1542 1543 $dlg.empty(); 1544 1545 if(isobj){ 1546 $dlg.append(message); 1547 }else{ 1548 $dlg.append('<span>'+window.hWin.HR(message)+'</span>'); 1549 } 1550 } 1551 } 1552 1553 let title = '·', // 'Info' removed - it's a useless popup window title, better to have none at all 1554 lblYes = window.hWin.HR('Yes'), 1555 lblNo = window.hWin.HR('No'), 1556 lblOk = window.hWin.HR('OK'), 1557 lblCancel = window.hWin.HR('Cancel'); 1558 1559 if($.isPlainObject(labels)){ 1560 if(labels.title) title = labels.title; 1561 if(labels.yes) lblYes = window.hWin.HR(labels.yes); 1562 if(labels.no) lblNo = window.hWin.HR(labels.no); 1563 if(labels.ok) lblOk = window.hWin.HR(labels.ok); 1564 if(labels.cancel) lblCancel = window.hWin.HR(labels.cancel); 1565 }else if (labels!=''){ 1566 title = labels; 1567 } 1568 1569 if (window.hWin.HEURIST4.util.isFunction(buttons)){ //}typeof buttons === "function"){ 1570 1571 let callback = buttons; 1572 1573 buttons = {}; 1574 buttons[lblYes] = function() { 1575 $dlg.dialog( "close" ); 1576 callback.call(); 1577 }; 1578 buttons[lblNo] = function() { 1579 $dlg.dialog( "close" ); 1580 }; 1581 }else if(!buttons){ //buttons are not defined - the only one OK button 1582 1583 buttons = {}; 1584 buttons[lblOk] = function() { 1585 $dlg.dialog( "close" ); 1586 }; 1587 1588 } 1589 1590 let options = { 1591 title: window.hWin.HR(title), 1592 resizable: false, 1593 modal: true, 1594 closeOnEscape: true, 1595 buttons: buttons 1596 1597 }; 1598 1599 if(ext_options){ 1600 1601 if( $.isPlainObject(ext_options) ){ 1602 $.extend(options, ext_options); 1603 } 1604 if(ext_options.my && ext_options.at && ext_options.of){ 1605 options.position = {my:ext_options.my, at:ext_options.at, of:ext_options.of}; 1606 } 1607 else if(!ext_options.options && !$.isPlainObject(ext_options)){ 1608 //it seems this is not in use 1609 let posele = $(ext_options); 1610 if(posele.length>0) 1611 options.position = { my: "left top", at: "left bottom", of: $(ext_options) }; 1612 } 1613 1614 if(ext_options['removeOnClose']){ 1615 options.close = function(event, ui){ $dlg.remove(); }; 1616 } 1617 1618 } 1619 if(!options.position){ 1620 options.position = { my: "center center", at: "center center", of: window }; 1621 } 1622 options.position.collision = 'none'; //FF fix 1623 1624 1625 if(isPopupDlg){ 1626 1627 if(!options.open){ 1628 options.open = function(event, ui){ 1629 $dlg.scrollTop(0); 1630 }; 1631 } 1632 1633 if(!options.height) options.height = 515; 1634 if(!options.width) options.width = 705; 1635 if(window.hWin.HEURIST4.util.isempty(options.resizable)) options.resizable = true; 1636 /* auto height dialog 1637 if(options.resizable === true){ 1638 options.resizeStop = function( event, ui ) { 1639 1640 var nh = $dlg.parent().height() 1641 - $dlg.parent().find('.ui-dialog-titlebar').height() - $dlg.parent().find('.ui-dialog-buttonpane').height(); //-20 1642 1643 $dlg.css({overflow: 'none !important','width':'100%', 'height':nh }); 1644 }; 1645 }*/ 1646 }else if(!options.width){ 1647 options.width = 'auto'; 1648 } 1649 1650 1651 $dlg.dialog(options); 1652 1653 if(options.hideTitle){ 1654 $dlg.parent().find('.ui-dialog-titlebar').hide(); 1655 }else{ 1656 $dlg.parent().find('.ui-dialog-titlebar').show(); 1657 } 1658 1659 1660 if($dlg.attr('data-palette')) 1661 $dlg.parent().removeClass($dlg.attr('data-palette')); 1662 if(ext_options.default_palette_class){ 1663 $dlg.attr('data-palette', ext_options.default_palette_class); 1664 $dlg.parent().addClass(ext_options.default_palette_class); 1665 }else{ 1666 $dlg.attr('data-palette', null); 1667 } 1668 1669 if(options.enable_buttons_after>0){ 1670 const btns = $dlg.parent().find('.ui-dialog-buttonset > button'); 1671 window.hWin.HEURIST4.util.setDisabled(btns, true); 1672 setTimeout(()=>{ window.hWin.HEURIST4.util.setDisabled(btns, false); }, options.enable_buttons_after) 1673 } 1674 if(options.noClose){ 1675 $dlg.parent().find('.ui-dialog-titlebar').find('.ui-icon-closethick').parent().hide(); 1676 } 1677 1678 return $dlg; 1679 1680 1681 1682 //'#8ea9b9 none repeat scroll 0 0 !important' none !important','background-color':'none !important 1683 }, 1684 1685 // for progress message 1686 _progressInterval: 0, // Internal: Stores interval ID for progress polling 1687 _progressDiv: null, // Internal: Stores jQuery object of the progress display div 1688 _progressPopup: null, // Internal: Stores jQuery object of the progress popup dialog 1689 _progressWidgetEl: null, 1690 1691 /**
1692 * Displays a progress indicator, either in a popup dialog or an existing container. 1693 * It periodically polls a progress URL to update the status, steps, and progress bar. 1694 * 1695 * @memberof HEURIST4.msg 1696 * @param {Object} options - Configuration options for the progress display. 1697 * @param {jQuery} [options.container] - If provided, the progress indicator is shown within this jQuery element. 1698 * Otherwise, a popup dialog is created. 1699 * @param {string|false} [options.content] - Custom HTML content for the progress display area. 1700 * - If a string: Used as the HTML content. 1701 * - If `false`: The function uses the `options.container`'s existing content. 1702 * - If undefined/null: Default content with steps and progress bar is generated. 1703 * @param {Array<string>} [options.steps] - An array of strings representing the steps of the progress, 1704 * used if default content is generated. 1705 * @param {function} [options.onComplete] - Callback function to execute when the progress is terminated/completed. 1706 * @param {string|number} [options.session_id] - A unique session ID for the progress tracking. 1707 * If not provided, a random one is generated. 1708 * @param {number} [options.interval=900] - The interval in milliseconds for polling the progress URL. Defaults to 900ms. 1709 * @param {number} [options.width=500] - Width of the popup dialog, if applicable. 1710 * @param {boolean} [options.hideTitle=true] - Whether to hide the title of the popup dialog, if applicable. 1711 * @returns {string|undefined} The session ID used for the progress tracking, or undefined if a previous progress 1712 * operation is still active (indicated by `_progressInterval > 0`). 1713 */ 1714 showProgress: function( options ){ 1715 options = options || {}; 1716 1717 let $el; 1718 if(options.container && options.container.length>0){ //container element 1719 options.showDialog = false; 1720 $el = $(options.container); 1721 //$el.css('border','2px solid red'); 1722 }else{ 1723 options.showDialog = true; 1724 $el = $('<div>'); 1725 } 1726 1727 // if already has instance, destroy first to mimic old singleton behavior 1728 const inst = $el.progressReport('instance'); 1729 if (inst) { 1730 $el.progressReport('destroy'); 1731 } 1732 1733 const session_id = $el.progressReport(options).progressReport('start'); 1734 1735 this._progressWidgetEl = $el; 1736 1737 return session_id; 1738 }, 1739 1740 hideProgress: function(instance){ 1741 1742 // allow hideProgress() with no args to close the singleton (old callers) 1743 let $el = instance ? $(instance) : this._progressWidgetEl; 1744 1745 if ($el && $el.length && $el.progressReport('instance')) { 1746 $el.progressReport('destroy'); 1747 } 1748 this._progressWidgetEl = null; 1749 }, 1750 1751 showProgress2: function( options ){ 1752 if(window.hWin.HEURIST4.msg._progressInterval>0){ 1753 console.log('previous progress is not completed'); // Log and exit if another progress is running 1754 return 0; 1755 } 1756 1757 let $progress_div; 1758 let content = options?.content; 1759 let onComplete = options?.onComplete; 1760 let is_popup = true; 1761 1762 if(!options){ 1763 options = {}; 1764 } 1765 1766 if(options?.container){ //container element 1767 is_popup = false; 1768 $progress_div = options.container; 1769 } 1770 1771 if (window.hWin.HEURIST4.util.isempty(content)) { 1772 //default content 1773 content = ''; 1774 1775 if(Array.isArray(options?.steps)){ 1776 1777 content = '<ol type="1" style="font-size:12px;height:80%;padding-top:20px;" class="progress-steps">'; 1778 1779 options.steps.forEach((item)=>{ 1780 content += `<li style="color:gray">${item}</li>`; 1781 }); 1782 1783 content += '</ol>'; 1784 }else{ 1785 content = '<div class="loading" style="height:80%;min-height:150px"></div>'; 1786 } 1787 1788 content += '<div style="display:none;width:80%;height:40px;padding:5px;text-align:center;margin:auto;margin-top:10px">' 1789 +'<div id="progressbar"><div class="progress-label">Processing data...</div></div>' 1790 +'<div class="progress_stop" style="text-align:center;margin-top:4px">Abort</div>' 1791 +'</div>'; 1792 1793 }else if(typeof content !== 'string'){ 1794 content = false;
1795 } 1796 1797 if(is_popup){ 1798 options['buttons'] = {}; //no buttons 1799 options['width'] = 500; 1800 options['hideTitle'] = true; 1801 1802 window.hWin.HEURIST4.msg._progressPopup = window.hWin.HEURIST4.msg.showMsg( 1803 '<div class="progressbar_div" style="margin:10px"></div>', options ); 1804 1805 $progress_div = window.hWin.HEURIST4.msg._progressPopup.find('div.progressbar_div'); 1806 } 1807 1808 1809 let progress_url = window.hWin.HAPI4.baseURL + "hserv/controller/progress.php"; 1810 1811 let session_id = options.session_id; 1812 if(!(session_id>0)) session_id = window.hWin.HEURIST4.util.random(); 1813 1814 let t_interval = options.interval; 1815 if(!(t_interval>0)) t_interval = 900; 1816 1817 window.hWin.HEURIST4.msg._progressDiv = $progress_div; 1818 $progress_div.show(); 1819 document.body.style.cursor = 'progress'; 1820 1821 //add progress bar content 1822 if(content){ 1823 $progress_div.html(content); 1824 } 1825 1826 1827 //elements 1828 let btn_stop = $progress_div.find('.progress_stop').button(); 1829 1830 // termination 1831 btn_stop.on({click:function(){ 1832 let request = {terminate:1, t:(new Date()).getMilliseconds(), session:session_id}; 1833 window.hWin.HEURIST4.util.sendRequest(progress_url, request, null, function(response){ 1834 window.hWin.HEURIST4.msg.hideProgress(); 1835 }); 1836 }}); 1837 1838 let progressSteps = $progress_div.find('.progress-steps'); 1839 let div_loading = $progress_div.find('.loading').show(); 1840 let pbar = $progress_div.find('#progressbar'); 1841 let progressLabel = pbar.find('.progress-label').text(''); 1842 pbar.progressbar({value:0}); 1843 1844 let elapsed = 0; 1845 1846 window.hWin.HEURIST4.msg._progressInterval = setInterval(function(){ 1847 1848 let request = {t:Date.now(), session:session_id}; 1849 1850 window.hWin.HEURIST4.util.sendRequest(progress_url, request, null, function(response){ 1851 1852 if(response?.status==window.hWin.ResponseStatus.UNKNOWN_ERROR){ 1853 window.hWin.HEURIST4.msg.hideProgress(); 1854 }else if(response){ 1855 //it may return terminate,done, 1856 1857 let resp = response.split(','); 1858 if(response=='terminate' || !(resp.length>=2)){ 1859 //first or last response 1860 if(response=='terminate'){ 1861 1862 if(onComplete){ 1863 onComplete.call(); 1864 } 1865 1866 window.hWin.HEURIST4.msg.hideProgress(); 1867 }else{ 1868 div_loading.show(); 1869 } 1870 }else{ 1871 div_loading.hide(); 1872 pbar.parent().show(); 1873 1874 if(resp.length==3 || resp.length==1){ 1875 let newStep = resp.shift(); 1876 let percentage = 0; 1877 progressSteps.show(); 1878 1879 if(window.hWin.HEURIST4.util.isNumber(newStep)){ 1880 newStep = parseInt(newStep); 1881 let all_li = progressSteps.find('li'); 1882 if(newStep>0){ 1883 let arr = all_li.slice(0,newStep); 1884 arr.css('color','black'); 1885 arr.find('span.processing').remove(); //remove rotation icon 1886 } 1887 if(newStep<all_li.length){ 1888 if(percentage>0){ 1889 if(percentage>100) percentage = 100; 1890 percentage = percentage+'%'; 1891 }else{ 1892 percentage = 'processing...' 1893 } 1894 let ele = $(all_li[newStep]).find('span.percentage'); 1895 if(ele.length==0){ 1896 percentage = '<span class="percentage">'+percentage+'</span>'; 1897 $('<span class="processing"> <span class="ui-icon ui-icon-loading-status-balls"></span> ' 1898 +percentage+'</span>') 1899 .appendTo( $(all_li[newStep]) ); 1900 $(all_li[newStep]).css('color','black'); 1901 }else{ 1902 ele.text(percentage); 1903 } 1904 } 1905 }else{ 1906 let container = progressSteps.find('ol'); 1907 let li_ele = container.find('li:contains("'+newStep+'")'); 1908 if(li_ele.length==0){ //not added yet 1909 $('<li>'+newStep+'</li>').appendTo(container); 1910 } 1911 } 1912 } 1913 1914 let cnt=0, total=0; 1915 if(resp.length>1){ 1916 cnt = resp[0]; 1917 total = resp[1]; 1918 } 1919 1920 if(cnt>0 && total>0){ 1921 const val = cnt*100/total; 1922 pbar.progressbar( "value", val ); 1923 1924 elapsed += t_interval; 1925 let est_remaining = (elapsed / resp[0]) * (resp[1] - resp[0]); 1926 1927 if(est_remaining < 10000){ // less than 10 seconds 1928 est_remaining = '10 seconds'; 1929 }else if(est_remaining < 60000){ // less than a minute 1930 est_remaining = `${Math.ceil(est_remaining / 1000)} seconds`; 1931 }else{ 1932 est_remaining = `${Math.ceil(est_remaining / 60000)} minutes`; 1933 } 1934 1935 progressLabel.text(`${resp[0]} of ${resp[1]} ${(resp.length==3?resp[2]:'')} (approximately ${est_remaining} remaining)`); 1936 }else{ 1937 progressLabel.text('preparing...'); 1938 pbar.progressbar( "value", 0 ); 1939 } 1940 } 1941 1942 } 1943 },'text'); 1944 1945 1946 }, t_interval); 1947 1948 return session_id; 1949 }, 1950 1951 /** 1952 * Hides the currently active progress indicator. 1953 * It clears the polling interval, hides the progress div, and closes the progress popup
1953if it was used. 1954 * Also resets the mouse cursor from 'progress' to 'auto'. 1955 * 1956 * @memberof HEURIST4.msg 1957 * @returns {void} 1958 */ 1959 hideProgress2: function(){ 1960 $('body').css('cursor','auto'); 1961 1962 if(window.hWin.HEURIST4.msg._progressInterval!=null){ 1963 clearInterval(window.hWin.HEURIST4.msg._progressInterval); 1964 window.hWin.HEURIST4.msg._progressInterval = null; 1965 } 1966 if(window.hWin.HEURIST4.msg._progressDiv){ 1967 window.hWin.HEURIST4.msg._progressDiv.hide(); 1968 window.hWin.HEURIST4.msg._progressDiv = null; 1969 } 1970 if(window.hWin.HEURIST4.msg._progressPopup){ 1971 if (window.hWin.HEURIST4.msg._progressPopup.dialog('instance')) { 1972 window.hWin.HEURIST4.msg._progressPopup.dialog( "close" ); 1973 } 1974 window.hWin.HEURIST4.msg._progressPopup = null; 1975 } 1976 1977 }, 1978 1979 1980 /** 1981 * Displays a warning dialog prompting the user to save or ignore changes before exiting. 1982 * Typically used when there are unsaved modifications. 1983 * 1984 * @memberof HEURIST4.msg 1985 * @param {string} sMessage - The message content for the dialog (e.g., "You have unsaved changes..."). 1986 * This message is translated using `window.hWin.HR`. 1987 * Note: The `sMessage` parameter is defined but not directly used in the current implementation; 1988 * instead, a hardcoded "Warn_Lost_Data" key is used for the message. 1989 * @param {function} onSave - Callback function to execute if the user chooses to "Save". 1990 * @param {function} onIgnore - Callback function to execute if the user chooses to "Ignore and close". 1991 * @returns {void} 1992 */ 1993 showMsgOnExit: function(sMessage, onSave, onIgnore){ // sMessage seems unused 1994 let $dlg, buttons = {}; 1995 buttons[window.hWin.HR('Save')] = function(){ // Using HR for button labels directly based on other patterns 1996 onSave(); 1997 $dlg.dialog('close'); 1998 }; 1999 buttons[window.hWin.HR('Ignore and close')] = function(){ 2000 onIgnore(); 2001 $dlg.dialog('close'); 2002 }; 2003 2004 $dlg = window.hWin.HEURIST4.msg.showMsgDlg( 2005 window.hWin.HR('Warn_Lost_Data'), // sMessage is passed but 'Warn_Lost_Data' is used 2006 buttons, 2007 {title: window.hWin.HR('Confirm'), 2008 // yes: window.hWin.HR('Save'), // These are covered by direct button definition above 2009 // no: window.hWin.HR('Ignore and close') 2010 }, 2011 {default_palette_class:'ui-heurist-design'}); 2012 2013 }, 2014 2015 /** 2016 * Checks if an error message string indicates the use of a disabled PHP function in a Smarty template (custom report). 2017 * If such an error is detected, it displays a warning dialog informing the user and suggesting they request 2018 * the function to be enabled by submitting a ticket. 2019 * 2020 * @memberof HEURIST4.msg 2021 * @param {string} txt - The error message string to check. 2022 * @returns {boolean} True if a warning dialog was shown (meaning the specific error was detected), false otherwise. 2023 */ 2024 showWarningAboutDisabledFunction: function(txt){ 2025 2026 if(txt.indexOf('Exception on execution: Syntax error in template')==0 2027 && txt.indexOf('not allowed by security setting')>0){ 2028 2029 let buttons = null; 2030 let $dlgm; // Define $dlgm here to be accessible in button callbacks 2031 if(window.hWin.HAPI4 && window.hWin.HAPI4.actionHandler){ // Check HAPI4 exists 2032 buttons = 2033 { 2034 [window.hWin.HR('Send Bug Report')]: function() { 2035 window.hWin.HAPI4.actionHandler.executeActionById('menu-help-bugreport'); 2036 if ($dlgm) $dlgm.dialog( 'close' ); 2037 }, 2038 [window.hWin.HR('Cancel')]:function() { // Translate button label 2039 if ($dlgm) $dlgm.dialog( 'close' ); 2040 } 2041 }; 2042 } 2043 2044 $dlgm = window.hWin.HEURIST4.msg.showMsgDlg( 2045 '<p>Sorry, native php functions in custom reports are disabled by default<br>' 2046 +'as a security precaution. </p>' 2047 +'<p>Please use the Create ticket function to ask that this function be enabled. </p>', 2048 buttons, 2049 window.hWin.HR('Warning')); // Translate title 2050 2051 return true; 2052 2053 } 2054 2055 return false;
2056 2057 }, 2058 2059 /** 2060 * Prepares and optionally displays a detailed message about parent record pointers 2061 * that could not be automatically removed due to existing reverse child record pointers. 2062 * The message explains the situation and provides links to view the affected records. 2063 * 2064 * @memberof HEURIST4.msg 2065 * @param {Object} details - An object where keys are parent record IDs. Each value is an object: 2066 * `{title: string, type: string, restored: Array<Object>}`. 2067 * `restored` is an array of child info: `{field: string, id: string, title: string, type: string}`. 2068 * @param {boolean} [create_popup=false] - If true, displays the generated message in a dialog. 2069 * If false, returns an object with message components. 2070 * @returns {jQuery|Object|string} 2071 * - If `create_popup` is true: Returns the jQuery dialog object. 2072 * - If `create_popup` is false and details are found: Returns an object 2073 * `{message: string, title: string, handlers: Object}` for manual dialog creation. 2074 * - If `create_popup` is false and no relevant details found: Returns an empty string. 2075 */ 2076 prepareParentRecordMsg: function(details, create_popup = false){ 2077 2078 let link_styling = "text-decoration: underline; cursor: pointer; color: black;"; 2079 2080 let title = 'Parent record pointers which could not be removed'; 2081 let msg = 'Not all parent record pointers could be deleted, because some have a child record pointer in the reverse direction.<br><br>' 2082 + 'To delete these parent record pointers you will need to delete the child record pointers in the affected parent records, or<br>' 2083 + 'change the Child Record Pointer field(s) in the parent record types to a standard Record pointer field (by editing the field in Structure Modification mode and unchecking the Child checkbox)<br><br>' 2084 + `Click <span style="${link_styling}" id="parent_list">here</span> for a list of two-way pointers which were not deleted.<br><br>` 2085 + `Click <span style="${link_styling}" id="parent_search">here</span> for a search result of all parent records containing such pointers.<br><br>` 2086 + 'Note: we do not delete the existing parent-child relations because of the risk that this unintentionally deletes data<br>' 2087 + 'that is still required (as simple record pointer connections). A manual process as above is much safer.' 2088 2089 let icon_bg = `${window.hWin.HAPI4.baseURL}hclient/assets/16x16.gif`; 2090 2091 let list = '<div>'; 2092 for(const parent_ID in details){ 2093 2094 const parent_title = details[parent_ID].title; 2095 const parent_type = details[parent_ID].type; 2096 let restored_children = details[parent_ID].restored; 2097 2098 if(window.hWin.HEURIST4.util.isempty(restored_children)){ 2099 continue; 2100 } 2101 2102 list += '<div>' 2103 + `<img src="${icon_bg}" class="rt-icon" style="background-image:url('${window.hWin.HAPI4.iconBaseURL}${parent_type}');">` 2104 + `<span style="${link_styling} vertical-align: 4px;" class="record_search_link" data-recid=="${parent_ID}">${parent_title}</span>` 2105 + '<div style="margin: 5px 15px 10px;">'; 2106 2107 for(const idx in restored_children){ 2108 2109 let child = restored_children[idx]; 2110 2111 list += `<em style="vertical-align: 4px;">${$Db.rst(parent_type, child.field, 'rst_DisplayName')}</em> <span style="vertical-align: 4px;">⇒</span> ` 2112 + `<img src="${icon_bg}" class="rt-icon" style="background-image:url('${window.hWin.HAPI4.iconBaseURL}${child.type}');">` 2113 + `<span style="${link_styling} vertical-align: 4px;" class="record_search_link" data-recid=="${child.id}">${child.title}</span><br>`; 2114 } 2115 2116 list += '</div></div>'; 2117 } 2118 if(list.length === 5){ 2119 return ''; 2120 } 2121 list += '</div>'; 2122 2123 let handlers = { 2124 '#parent_list': () => { 2125 2126 let $dlg = window.hWin.HEURIST4.msg.showMsgDlg(list, null, {title: 'List of pointers not deleted'}, {default_palette_class: 'ui-heurist-explore', dialogId: 'retained-parents'}); 2127 2128 $dlg.on('click', (event) => { 2129 let recID = $(event.target).attr('data-recid'); 2130 let url = `${window.hWin.HAPI4.baseURL}viewers/record/renderRecordData.php?db=${window.hWin.HAPI4.database}&recID=${recID}`; 2131 window.open(url, '_blank'); 2132 }) 2133 }, 2134 '#parent_search': () => { 2135 let query = `ids:${Object.keys(details).join(',')}`; 2136 let url = `${window.hWin.HAPI4.baseURL}?db=${window.hWin.HAPI4.database}&q=${encodeURIComponent(query)}`; 2137 window.open(url, '_blank'); 2138 } 2139 }; 2140 2141 if(!create_popup){ 2142 return { 2143 message: msg, 2144 title: title, 2145 handlers: handlers 2146 }; 2147 } 2148 2149 let $dlg = window.hWin.HEURIST4.msg.showMsgDlg(msg, null, {title: title}, {default_palette_class: 'ui-heurist-populate', dialogId: 'restore-parent-records'}); 2150 2151 $dlg.find('#parent_list').on('click', handlers['#parent_list']); 2152 $dlg.find('#parent_search').on('click', handlers['#parent_search']); 2153 2154 $dlg.find('.record_search_link').on('click', handlers['#parent_list.record_search_link']); 2155 2156 return $dlg; 2157 } 2158};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.