mailer project continued:
[mailer.git] / js / ajax-common.js
1 /**
2  * Common JavaScript functions for AJAX requests (they are general purpose).
3  * --------------------------------------------------------------------
4  * $Revision::                                                        $
5  * $Date::                                                            $
6  * $Tag:: 0.2.1-FINAL                                                 $
7  * $Author::                                                          $
8  * --------------------------------------------------------------------
9  * Copyright (c) 2003 - 2009 by Roland Haeder
10  * Copyright (c) 2009 - 2012 by Mailer Developer Team
11  * For more information visit: http://mxchange.org
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
26  * MA  02110-1301  USA
27  */
28
29 // Init variables
30 var currentTabId       = null;
31 var errorDisplayed     = false;
32 var warningDisplayed   = false;
33 var defaultTabId       = null;
34 var footerElements     = new Array();
35 var changedElements    = new Array();
36 var formChanged        = false;
37 var saveChangesId      = null;
38 var lastErrorMessage   = null;
39 var saveChangesPending = false;
40
41 // Add all footer navigation elements
42 footerElements[0] = 'next';
43 footerElements[1] = 'previous';
44
45 // Setter for current tab id
46 function setCurrentTabId (tabId) {
47         // Set it
48         //* DEBUG: */ alert('currentTabId=' + currentTabId + ',tabId=' + tabId);
49         currentTabId = tabId;
50 }
51
52 // Marks a tab navigation entry
53 function markTabNavigation (prefix, tab) {
54         // Get all li-tags
55         var li = document.getElementsByTagName('li');
56
57         // Set current tab name
58         var currentTabName = 'tab_enabled';
59
60         // Set tab name
61         var tabName = prefix + '_' + tab;
62
63         // Search for all menus
64         for (var i = 0; i < li.length; i++) {
65                 // Debug message
66                 //* DEBUG: */ document.write('className=' + li[i].className + ',prefix=' + prefix + ',tab=' + tab + '<br />');
67
68                 // Check for valid tab
69                 if (li[i].className.substr(0, currentTabName.length) == currentTabName) {
70                         // Okay does match (don't change any others)
71                         if (li[i].id == tabName) {
72                                 // Mark this menu
73                                 $('li#' + tabName).addClass('tab_active');
74                                 //li[i].className += ' tab_active';
75                         } else {
76                                 // Unmark this menu
77                                 $('li#' + li[i].id).removeClass('tab_active');
78                                 //li[i].className = currentTabName;
79                         }
80                 } // END - if
81         } // END - for
82 }
83
84 // Enables a given element
85 function enableElement (element) {
86         // Remove class and attribute
87         $(element).removeClass('disabled');
88         $(element).removeAttr('disabled');
89 }
90
91 // Disables a given element
92 function disableElement (element) {
93         // Add class and attribute
94         $(element).addClass('disabled');
95         $(element).attr('disabled', 'disabled');
96
97         // Blur the element
98         $(element).blur();
99 }
100
101 // Enables a given footer navigation element
102 function enableFooterNavigationPage (element) {
103         // Remove the 'disabled' class and attribute
104         enableElement('input#' + element + '_page');
105 }
106
107 // Resets footer navigation by adding CSS class 'disabled'
108 function resetFooterNavigation () {
109         // Remove the 'disabled' class and attribute
110         for (var i = 0; i < footerElements.length; i++) {
111                 $('input#' + footerElements[i] + '_page').addClass('disabled');
112                 $('input#' + footerElements[i] + '_page').attr('disabled', 'disabled');
113                 $('input#' + footerElements[i] + '_page').blur();
114         } // END - for
115 }
116
117 // Getter for AJAX content
118 function getAjaxContent () {
119         // Is it defined?
120         if ($('body').data('ajax_content') == undefined) {
121                 // Not set
122                 throw new 'ajax_content requested but not set.';
123         } // END - if
124
125         // Return it
126         return $('body').data('ajax_content');
127 }
128
129 // "Setter" for AJAX content but does decode the content
130 function setAjaxDecodedContent (ajax_content) {
131         // Decode URL-encoded data ...
132         var decoded = decodeUrlEncoding(ajax_content);
133
134         // ... and set it
135         setAjaxContent(decoded);
136 }
137
138 // Setter for AJAX content
139 function setAjaxContent (ajax_content) {
140         $('body').data('ajax_content', ajax_content);
141 }
142
143 // Getter for AJAX success
144 function getAjaxSuccess () {
145         return $('body').data('ajax_success');
146 }
147
148 // Setter for AJAX success
149 function setAjaxSuccess (success) {
150         $('body').data('ajax_success', success);
151 }
152
153 // Set AJAX reply and decode JSON if requested
154 function setAjaxReply (reply, isJson) {
155         // Copy reply to local variable
156         var localReply = reply;
157
158         // Is it JSON URL-encoded content?
159         if ((isJson != undefined) && (isJson == true)) {
160                 // Then decode it, replace '%20' with space before because '%20' breakes JSON content
161                 var obj = jQuery.parseJSON(reply.replace('%20', ' '));
162
163                 // Is reply_content there
164                 if (obj.reply_content == undefined) {
165                         // Not defined
166                         throw new 'obj.reply_content not returned from ajax.php, please fix your scripts.';
167                 } // END - if
168
169                 // ... and set it
170                 setAjaxDecodedContent(obj.reply_content);
171         } else {
172                 // Handle the content over to decode it
173                 setAjaxDecodedContent(localReply);
174         }
175 }
176
177 // Sends out an AJAX request
178 function sendAjaxRequest (level, doValue, extra, isJson) {
179         // By default all requests failed
180         setAjaxSuccess(false);
181
182         // Reset AJAX content
183         setAjaxContent('');
184
185         // Send out the raw request
186         $.ajax({
187                 url: 'ajax.php',
188                 type: 'POST',
189                 data: 'level=' + level + '&do=' + doValue + extra,
190                 dataType: 'json',
191                 async: false,
192
193                 // Called on success
194                 success: function (ajax_content) {
195                         // Is ajax_content set?
196                         if (ajax_content.responseText == undefined) {
197                                 // This shall not happen
198                                 throw new 'ajax_content.responseText not returned from ajax.php, please fix your scripts.';
199                         } else if (ajax_content.responseText == null) {
200                                 // This shall not happen, too
201                                 throw new 'ajax_content.responseText=null from ajax.php, please fix your scripts.';
202                         }
203
204                         // Set AJAX reply
205                         setAjaxReply(ajax_content.responseText, isJson);
206
207                         // Mark it as success
208                         setAjaxSuccess(true);
209                 },
210
211                 // Called in case of an error (e.g. HTTP response status not '200 OK')
212                 error: function (ajax_content) {
213                         // Is ajax_content set?
214                         if (ajax_content.responseText == undefined) {
215                                 // This shall not happen
216                                 throw new 'ajax_content.responseText not returned from ajax.php, please fix your scripts.';
217                         } else if (ajax_content.responseText == null) {
218                                 // This shall not happen, too
219                                 throw new 'ajax_content.responseText=null from ajax.php, please fix your scripts.';
220                         }
221
222                         // Set AJAX reply
223                         setAjaxReply(ajax_content.responseText, isJson);
224                 }
225         });
226
227         // Return status
228         return getAjaxSuccess();
229 }
230
231 // Enables footer navigation buttons
232 function enableFooterNavigation (prefix, tabId) {
233         // Reset both footer navigation first
234         resetFooterNavigation();
235
236         // Do the AJAX request (JSON as content is enabled)
237         if (sendAjaxRequest(prefix, 'footer_navigation', '&tab=' + tabId, true) == true) {
238                 // Parse the content
239                 $.each(getAjaxContent(), function (i, value) {
240                         // Enable current element
241                         enableFooterNavigationPage(value);
242                 });
243         } else {
244                 // Display error window
245                 displayErrorWindow(prefix, getAjaxContent());
246         }
247 }
248
249 // Requests an AJAX content
250 function requestAjaxContent (prefix, htmlId, tabId, footerNavigation) {
251         // Check if this request is disabled
252         if ($('#' + prefix + '_' + tabId).hasClass('tab_disabled')) {
253                 // Clicked on a disabled tabId so blur it
254                 //* DEBUG: */ alert('requestAjaxContent(): prefix=' + prefix + ',htmlId=' + htmlId + ',tabId=' + tabId + ' - DISABLED!');
255                 return;
256         } else if (formChanged == true) {
257                 // Has changed form , so output message to browser
258                 //* DEBUG: */ alert('requestAjaxContent(): prefix=' + prefix + ',htmlId=' + htmlId + ',tabId=' + tabId + ' - FORM CHANGED!');
259                 displayChangedWarningWindow(prefix, tabId);
260
261                 // Abort here
262                 return;
263         }
264
265         // Only request if content is different
266         //* DEBUG: */ alert('requestAjaxContent(): prefix=' + prefix + ',htmlId=' + htmlId + ',tabId=' + tabId + ',currentTabId=' + currentTabId);
267         if (tabId != currentTabId) {
268                 // Set tabId as current
269                 //* DEBUG: */ alert('requestAjaxContent(): prefix=' + prefix + ',htmlId=' + htmlId + ',tabId=' + tabId + ' - Calling setCurrentTabId()');
270                 setCurrentTabId(tabId);
271
272                 // Fade the old content out
273                 $('#' + htmlId).fadeOut('fast', function() {
274                         // Send AJAX request
275                         if (sendAjaxRequest(prefix, 'request_content', '&tab=' + tabId, true) == true) {
276                                 // Add the HTML content
277                                 $('#' + htmlId).html(getAjaxContent());
278
279                                 // Fade the content in
280                                 $('#' + htmlId).fadeIn('fast', function() {
281                                         // This differs, so mark the menu and request content
282                                         markTabNavigation(prefix, tabId);
283
284                                         // Is the footer navigation enabled?
285                                         if (footerNavigation == true) {
286                                                 // Change footer navigation as well
287                                                 enableFooterNavigation(prefix, tabId);
288                                         } // END - if
289                                 });
290                         } else {
291                                 // Display error window
292                                 displayErrorWindow(prefix, getAjaxContent());
293                         }
294                 });
295         } else {
296                 // Same id
297                 //* DEBUG: */ alert('SAME!');
298         }
299 }
300
301 // Displays a test window
302 function displayTestWindow (prefix, element) {
303         // Register click-event for error window
304         $('#' + prefix + '_error_close').click(function () {
305                 // Close the window
306                 closeErrorWindow(prefix);
307         });
308
309         // Register click-event for warning window
310         $('#' + prefix + '_warning_close').click(function () {
311                 // Close the window
312                 //* DEBUG: */ alert('displayTestWindow(): prefix=' + prefix + ' - calling closeWarningWindow()');
313                 closeWarningWindow(prefix);
314         });
315
316         // Request it from the AJAX backend
317         if (sendAjaxRequest(prefix, 'test', '') == true) {
318                 // Transfer the returned content to the prefix_warning_content id
319                 $('#' + prefix + '_warning_content').html(getAjaxContent());
320
321                 // Fade the warning in
322                 $('#' + prefix + '_warning').fadeIn('slow', function() {
323                         // Enable element
324                         enableElement(element);
325                 });
326
327                 // Mark 'warning' as displayed
328                 warningDisplayed = true;
329         } else {
330                 // Display error message
331                 displayErrorWindow(prefix, getAjaxContent());
332         }
333 }
334
335 // Displays a warning window above the form to warn about changed&unsafed fields
336 function displayChangedWarningWindow (prefix, button) {
337         // Fade out warning window, if open
338         //* DEBUG: */ alert('displayChangedWarningWindow(): prefix=' + prefix + ',button=' + button + ' - calling closeWarningWindow()');
339         closeWarningWindow(prefix);
340
341         // Fade error out for eye-candy, if open
342         closeErrorWindow(prefix);
343
344         // Abort here if warningDisplayed is still true
345         if (warningDisplayed == true) {
346                 // Make sure this doesn't happen
347                 return;
348         } // END - if
349
350         // Request it from the AJAX backend
351         if (sendAjaxRequest(prefix, 'change_warning', '&button=' + button + '&elements=' + changedElements.join(':')) == true) {
352                 // Transfer the returned content to the prefix_warning_content id
353                 $('#' + prefix + '_warning_content').html(getAjaxContent());
354
355                 // Fade the warning in
356                 $('#' + prefix + '_warning').fadeIn('slow', function() {
357                         // Mark warning as displayed
358                         warningDisplayed = true;
359                 });
360         } else {
361                 // Display error message
362                 displayErrorWindow(prefix, getAjaxContent());
363         }
364 }
365
366 // Displays the error window for given prefix and content
367 function displayErrorWindow (prefix, ajax_content) {
368         // Fade out warning window, if open
369         //* DEBUG: */ alert('displayErrorWindow(): prefix=' + prefix + ' - calling closeWarningWindow()');
370         closeWarningWindow(prefix);
371
372         // Fade it out for eye-candy
373         closeErrorWindow(prefix);
374
375         // Abort here if errorDisplayed is still true
376         if (errorDisplayed == true) {
377                 // Make sure this doesn't happen
378                 return;
379         } // END - if
380
381         // Copy the response text to the error variable
382         if (ajax_content.responseText != undefined) {
383                 $('#' + prefix + '_error_content').html(ajax_content.responseText);
384         } else {
385                 $('#' + prefix + '_error_content').html(ajax_content);
386         }
387
388         // Fade the error in
389         $('#' + prefix + '_error').fadeIn('slow', function() {
390                 // Mark error as displayed
391                 errorDisplayed = true;
392         });
393 }
394
395 // Waits until the window has been closed
396 function closeErrorLocked () {
397         // Has all been loaded?
398         if (errorDisplayed == false) {
399                 // Then release ready()
400                 $.holdReady(false);
401         } else {
402                 // Recursive call again
403                 window.setTimeout('closeErrorLocked()', 10);
404         }
405 }
406
407 // Closes an error window
408 function closeErrorWindow (prefix, waitClose, resetCurrentTabId) {
409         // Is the error displayed?
410         if (errorDisplayed == true) {
411                 // Shall we wait ("sync") until the animation has completed?
412                 if (waitClose == true) {
413                         // Hold the ready status
414                         $.holdReady(true);
415                 } // END - if
416
417                 // Yes, then fade it out
418                 $('#' + prefix + '_error').fadeOut('fast', function() {
419                         // Set current tab id to default
420                         if (resetCurrentTabId == true) {
421                                 setCurrentTabId(defaultTabId);
422                         } // END - if
423
424                         // Mark it as closed
425                         errorDisplayed = false;
426                 });
427
428                 // Shall this animation be "synchronized"?
429                 if (waitClose == true) {
430                         // Wait for the window has been closed
431                         closeErrorLocked();
432                 } // END - if
433         } // END - if
434 }
435
436 // Waits until the window has been closed
437 function closeWarningLocked () {
438         // Has all been loaded?
439         if (warningDisplayed == false) {
440                 // Then release ready()
441                 $.holdReady(false);
442         } else {
443                 // Recursive call again
444                 window.setTimeout('closeWarningLocked()', 10);
445         }
446 }
447
448 // Closes an warning window
449 function closeWarningWindow (prefix, waitClose, resetCurrentTabId) {
450         //* DEBUG: */ alert('prefix=' + prefix + ',waitClose=' + waitClose + ' - ENTERED!');
451         // Is the warning displayed?
452         if (warningDisplayed == true) {
453                 // Shall we wait ("sync") until the animation has completed?
454                 //* DEBUG: */ alert('prefix=' + prefix + ',waitClose=' + waitClose + ',warningDisplayed=true');
455                 if (waitClose == true) {
456                         // Hold the ready status
457                         $.holdReady(true);
458                 } // END - if
459
460                 // Yes, then fade it out
461                 $('#' + prefix + '_warning').fadeOut('fast', function() {
462                         // Set current tab id to default
463                         //* DEBUG: */ alert('closeWarningWindow(): prefix=' + prefix + ',waitClose=' + waitClose + ',defaultTab=' + defaultTabId + ' - Calling setCurrentTabId()');
464                         if (resetCurrentTabId == true) {
465                                 setCurrentTabId(defaultTabId);
466                         } // END - if
467
468                         // Mark it as closed
469                         warningDisplayed = false;
470                         //* DEBUG: */ alert('closeWarningWindow(): waitClose=' + waitClose + ',resetCurrentTabId=' + resetCurrentTabId + ',warningDisplayed=false');
471                 });
472
473                 // Shall this animation be "synchronized"?
474                 if (waitClose == true) {
475                         // Wait for the window has been closed
476                         //* DEBUG: */ alert('prefix=' + prefix + ',waitClose=' + waitClose + ' - LOCKED!');
477                         closeWarningLocked();
478                 } // END - if
479         } // END - if
480 }
481
482 // A footer navigation button has been clicked
483 function doFooterPage (prefix, htmlId, button) {
484         //* DEBUG: */ alert('doFooterPage(): prefix=' + prefix + ',htmlId=' + htmlId + ',button=' + button + ' - ENTERED!');
485         // Has something being changed?
486         if (formChanged == true) {
487                 // Output message to browser
488                 displayChangedWarningWindow(prefix, button);
489
490                 // Abort here
491                 return;
492         } // END - if
493
494         // Do we have a 'next' entry?
495         //* DEBUG: */ alert('doFooterPage(): button=' + button + ',currentTabId=' + currentTabId + ',nextPage[currentTabId]=' + nextPage[currentTabId]);
496         if ((button == 'next') && (nextPage[currentTabId] != null)) {
497                 // Then call the AJAX requester
498                 requestAjaxContent(prefix, htmlId, nextPage[currentTabId]);
499         } else if ((button == 'previous') && (previousPage[currentTabId] != null)) {
500                 // Then call the AJAX requester
501                 requestAjaxContent(prefix, htmlId, previousPage[currentTabId]);
502         }
503 }
504
505 // Allows to save made changes (this will be called if the onchange event has been triggered)
506 function allowSaveChanges (element) {
507         // Mark element as changed, unmark as failed
508         $('#' + element).addClass('field_changed');
509         $('#' + element).removeClass('field_failed');
510
511         // Is the element not there?
512         if (!in_array(element, changedElements)) {
513                 // Mark the form as changed
514                 formChanged = true;
515
516                 // Make the 'Save changes' button clickable
517                 enableElement('#' + saveChangesId);
518
519                 // Remember this for later AJAX call
520                 changedElements.push(element);
521         } // END - if
522 }
523
524 // Marks all elements as unchanged/not failed
525 function markAllElementsAsUnchanged () {
526         // Remove status from all fields
527         for (var i = 0; i < changedElements.length; i++) {
528                 // Mark the element as changed
529                 $('#' + changedElements[i]).removeClass('field_changed');
530                 $('#' + changedElements[i]).removeClass('field_failed');
531         } // END - for
532 }
533
534 // Function to reset the form
535 function resetMailerAjaxForm () {
536         // Debug message
537         //* DEBUG: */ alert('resetMailerAjaxForm(): changedElements()=' + changedElements.length + ',saveChangesId=' + saveChangesId + ' - ENTERED!');
538
539         // Mark all elements as unchanged
540         markAllElementsAsUnchanged();
541
542         // Clear all changed elements
543         disableElement('input#' + saveChangesId);
544
545         // Reset changed elements and mark form as not changed
546         changedElements = new Array();
547         formChanged     = false;
548 }
549
550 // Mark given fields as failed
551 function markFormFieldsFailed (failedFields) {
552         // Mark all elements as failed
553         $.each(failedFields, function (i, field) {
554                 // Mark the element as 'failed'
555                 $('#' + field).removeClass('field_changed');
556                 $('#' + field).addClass('field_failed');
557
558                 // Also register it as 'changed', if not found
559                 if (!in_array(field, changedElements)) {
560                         // Okay, not yet added, so push it on the array
561                         changedElements.push(field);
562                 } // END - if
563         });
564 }
565
566 // Processes the content from AJAX call
567 function processAjaxResponseContent (prefix, ajax_content) {
568         // By default all is failed
569         var isResponseDone = false;
570
571         // Is 'status' and 'message' set?
572         if ((ajax_content.status == undefined) || (ajax_content.message == undefined)) {
573                 // No status/message set
574                 lastErrorMessage = 'Returned content does not provide &#36;status&#36; or &#36;message&#36; which is required.';
575         } else if ((ajax_content.status != 'done') && (ajax_content.status != 'failed')) {
576                 // This is also not good, only 'failed' or 'done' is supported
577                 lastErrorMessage = ajax_content.message + '<br />\nAdditionally an unknown status ' + ajax_content.status + ' was detected.';
578         } else if (ajax_content.status == 'failed') {
579                 // Something bad went wrong so copy the message
580                 lastErrorMessage = ajax_content.message;
581         } else {
582                 // All fine
583                 isResponseDone = true;
584         }
585
586         // Return status
587         return isResponseDone;
588 }
589
590 // Saves changes by sending the data to the AJAX backend script
591 function saveChanges (prefix) {
592         // Mark all elements as unchanged
593         markAllElementsAsUnchanged();
594
595         // Do we have changed elements
596         if (changedElements.length == 0) {
597                 // This should not happen
598                 displayErrorWindow(prefix, '<div class="ajax_error_message">saveChanges() called with no changed elements.</div>');
599         } else if (saveChangesId == null) {
600                 // saveChangesId is not det
601                 displayErrorWindow(prefix, '<div class="ajax_error_message">saveChangesId is not set. Please add <em>saveChanges = \'foo_bar\';</em> to your code.</div>');
602         }
603
604         // Serialize the whole form
605         var serializedData = $('form').serialize();
606
607         // Hold the ready status
608         $.holdReady(true);
609         saveChangesPending = true;
610
611         /*
612          * Send the request to the AJAX backend, it doesn't matter from which page
613          * this was requested.
614          */
615         if (sendAjaxRequest(prefix, 'save_changes', '&tab=' + currentTabId + '&' + serializedData, true) == true) {
616                 // Get the content
617                 var ajax_content = getAjaxContent();
618
619                 // Process the returned content
620                 if (processAjaxResponseContent(prefix, ajax_content) == true) {
621                         // Mark all elements as unchanged
622                         markAllElementsAsUnchanged();
623
624                         // Reset form
625                         resetMailerAjaxForm();
626                 } else {
627                         // Do we have 'failed_fields' set?
628                         if ((ajax_content.failed_fields != undefined) && (ajax_content.message != undefined)) {
629                                 // Mark all fields as 'failed'
630                                 markFormFieldsFailed(ajax_content.failed_fields);
631
632                                 // Display the error message
633                                 displayErrorWindow(prefix, '<div class="ajax_error_message">' + ajax_content.message + '</div>');
634                         } else {
635                                 // This didn't work, why?
636                                 displayErrorWindow(prefix, '<div class="ajax_error_message">processAjaxResponseContent() failed, please fix this.<br />\n' + lastErrorMessage + '</div>');
637                         }
638                 }
639
640                 // Saving changes may have worked
641                 saveChangesPending = false;
642         } else {
643                 // Mark all elements as unchanged
644                 markAllElementsAsUnchanged();
645
646                 // Display error message
647                 displayErrorWindow(prefix, getAjaxContent());
648
649                 // Saving changes didn't work
650                 saveChangesPending = false;
651         }
652
653         // Wait for all has finished
654         saveChangesLocked();
655 }
656
657 // Waiting for resources being loaded
658 function saveChangesLocked () {
659         // Has all been loaded?
660         if (saveChangesPending == false) {
661                 // Then release ready()
662                 $.holdReady(false);
663         } else {
664                 // Recursive call again
665                 window.setTimeout('saveChangesLocked()', 10);
666         }
667 }
668
669 // Saves changed settings and continues with given page (next/previous)
670 function doSaveChangesPage (prefix, htmlId, page) {
671         // Save the changes
672         saveChanges(prefix);
673
674         // Close the window
675         //* DEBUG: */ alert('doSaveChangesPage(): prefix=' + prefix + ',htmlId=' + htmlId + ',page=' + page + ' - calling closeWarningWindow()');
676         closeWarningWindow(prefix, true, false);
677
678         // Load requested page
679         doFooterPage(prefix, htmlId, page);
680 }
681
682 // Saves changed settings and continues with given tab
683 function doSaveChangesContinue (prefix, htmlId, tab) {
684         // Save the changes
685         saveChanges(prefix);
686
687         // Close the window
688         //* DEBUG: */ alert('doSaveChangesPage(): prefix=' + prefix + ',htmlId=' + htmlId + ',tab=' + tab + ' - calling closeWarningWindow()');
689         closeWarningWindow(prefix, true, false);
690
691         // Load requested content
692         requestAjaxContent(prefix, htmlId, tab);
693 }