bc21661064bd09dee2931f13fce8074b43b35752
[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         // Is it JSON URL-encoded content?
156         if ((isJson != undefined) && (isJson == true)) {
157                 // Decode URL-encoding (for some reason it must be here ...)
158                 var localReply = decodeUrlEncoding(reply);
159
160                 // Then decode it, replace '%20' with space before because '%20' breakes JSON content
161                 var obj = jQuery.parseJSON(localReply.replace('%20', ' '));
162
163                 // ... and set it
164                 setAjaxContent(obj);
165         } else {
166                 // Handle the content over to decode it
167                 setAjaxDecodedContent(reply);
168         }
169 }
170
171 // Sends out an AJAX request
172 function sendAjaxRequest (level, doValue, extra, isJson) {
173         // By default all requests failed
174         setAjaxSuccess(false);
175
176         // Reset AJAX content
177         setAjaxContent('');
178
179         // Send out the raw request
180         $.ajax({
181                 url: 'ajax.php',
182                 type: 'POST',
183                 data: 'level=' + level + '&do=' + doValue + extra,
184                 dataType: 'json',
185                 async: false,
186
187                 // Called on success
188                 success: function (ajax_content) {
189                         // Is ajax_content set?
190                         if (ajax_content.reply_content == undefined) {
191                                 // This shall not happen
192                                 throw new 'ajax_content.reply_content not returned from ajax.php, please fix your scripts.';
193                         } else if (ajax_content.reply_content == null) {
194                                 // This shall not happen, too
195                                 throw new 'ajax_content.reply_content=null from ajax.php, please fix your scripts.';
196                         }
197
198                         // Set AJAX reply
199                         setAjaxReply(ajax_content.reply_content, isJson);
200
201                         // Mark it as success
202                         setAjaxSuccess(true);
203                 },
204
205                 // Called in case of an error (e.g. HTTP response status not '200 OK')
206                 error: function (ajax_content) {
207                         // Is ajax_content set?
208                         if (ajax_content.reply_content == undefined) {
209                                 // This shall not happen
210                                 throw new 'ajax_content.reply_content not returned from ajax.php, please fix your scripts.';
211                         } else if (ajax_content.reply_content == null) {
212                                 // This shall not happen, too
213                                 throw new 'ajax_content.reply_content=null from ajax.php, please fix your scripts.';
214                         }
215
216                         // Set AJAX reply
217                         setAjaxReply(ajax_content.reply_content, isJson);
218                 }
219         });
220
221         // Return status
222         return getAjaxSuccess();
223 }
224
225 // Enables footer navigation buttons
226 function enableFooterNavigation (prefix, tabId) {
227         // Reset both footer navigation first
228         resetFooterNavigation();
229
230         // Do the AJAX request (JSON as content is enabled)
231         if (sendAjaxRequest(prefix, 'footer_navigation', '&tab=' + tabId, true) == true) {
232                 // Parse the content
233                 $.each(getAjaxContent(), function (i, value) {
234                         // Enable current element
235                         enableFooterNavigationPage(value);
236                 });
237         } else {
238                 // Display error window
239                 displayErrorWindow(prefix, getAjaxContent());
240         }
241 }
242
243 // Requests an AJAX content
244 function requestAjaxContent (prefix, htmlId, tabId, footerNavigation) {
245         // Check if this request is disabled
246         if ($('#' + prefix + '_' + tabId).hasClass('tab_disabled')) {
247                 // Clicked on a disabled tabId so blur it
248                 //* DEBUG: */ alert('requestAjaxContent(): prefix=' + prefix + ',htmlId=' + htmlId + ',tabId=' + tabId + ' - DISABLED!');
249                 return;
250         } else if (formChanged == true) {
251                 // Has changed form , so output message to browser
252                 //* DEBUG: */ alert('requestAjaxContent(): prefix=' + prefix + ',htmlId=' + htmlId + ',tabId=' + tabId + ' - FORM CHANGED!');
253                 displayChangedWarningWindow(prefix, tabId);
254
255                 // Abort here
256                 return;
257         }
258
259         // Only request if content is different
260         //* DEBUG: */ alert('requestAjaxContent(): prefix=' + prefix + ',htmlId=' + htmlId + ',tabId=' + tabId + ',currentTabId=' + currentTabId);
261         if (tabId != currentTabId) {
262                 // Set tabId as current
263                 //* DEBUG: */ alert('requestAjaxContent(): prefix=' + prefix + ',htmlId=' + htmlId + ',tabId=' + tabId + ' - Calling setCurrentTabId()');
264                 setCurrentTabId(tabId);
265
266                 // Fade the old content out
267                 $('#' + htmlId).fadeOut('fast', function() {
268                         // Send AJAX request
269                         if (sendAjaxRequest(prefix, 'request_content', '&tab=' + tabId, false) == true) {
270                                 // Add the HTML content
271                                 $('#' + htmlId).html(getAjaxContent());
272
273                                 // Fade the content in
274                                 $('#' + htmlId).fadeIn('fast', function() {
275                                         // This differs, so mark the menu and request content
276                                         markTabNavigation(prefix, tabId);
277
278                                         // Is the footer navigation enabled?
279                                         if (footerNavigation == true) {
280                                                 // Change footer navigation as well
281                                                 enableFooterNavigation(prefix, tabId);
282                                         } // END - if
283                                 });
284                         } else {
285                                 // Display error window
286                                 displayErrorWindow(prefix, getAjaxContent());
287                         }
288                 });
289         } else {
290                 // Same id
291                 //* DEBUG: */ alert('SAME!');
292         }
293 }
294
295 // Displays a test window
296 function displayTestWindow (prefix, element) {
297         // Register click-event for error window
298         $('#' + prefix + '_error_close').click(function () {
299                 // Close the window
300                 closeErrorWindow(prefix);
301         });
302
303         // Register click-event for warning window
304         $('#' + prefix + '_warning_close').click(function () {
305                 // Close the window
306                 //* DEBUG: */ alert('displayTestWindow(): prefix=' + prefix + ' - calling closeWarningWindow()');
307                 closeWarningWindow(prefix);
308         });
309
310         // Request it from the AJAX backend
311         if (sendAjaxRequest(prefix, 'test', '', false) == true) {
312                 // Transfer the returned content to the prefix_warning_content id
313                 $('#' + prefix + '_warning_content').html(getAjaxContent());
314
315                 // Fade the warning in
316                 $('#' + prefix + '_warning').fadeIn('slow', function() {
317                         // Enable element
318                         enableElement(element);
319                 });
320
321                 // Mark 'warning' as displayed
322                 warningDisplayed = true;
323         } else {
324                 // Display error message
325                 displayErrorWindow(prefix, getAjaxContent());
326         }
327 }
328
329 // Displays a warning window above the form to warn about changed&unsafed fields
330 function displayChangedWarningWindow (prefix, button) {
331         // Fade out warning window, if open
332         //* DEBUG: */ alert('displayChangedWarningWindow(): prefix=' + prefix + ',button=' + button + ' - calling closeWarningWindow()');
333         closeWarningWindow(prefix);
334
335         // Fade error out for eye-candy, if open
336         closeErrorWindow(prefix);
337
338         // Abort here if warningDisplayed is still true
339         if (warningDisplayed == true) {
340                 // Make sure this doesn't happen
341                 return;
342         } // END - if
343
344         // Request it from the AJAX backend
345         if (sendAjaxRequest(prefix, 'change_warning', '&button=' + button + '&elements=' + changedElements.join(':')) == true) {
346                 // Transfer the returned content to the prefix_warning_content id
347                 $('#' + prefix + '_warning_content').html(getAjaxContent());
348
349                 // Fade the warning in
350                 $('#' + prefix + '_warning').fadeIn('slow', function() {
351                         // Mark warning as displayed
352                         warningDisplayed = true;
353                 });
354         } else {
355                 // Display error message
356                 displayErrorWindow(prefix, getAjaxContent());
357         }
358 }
359
360 // Displays the error window for given prefix and content
361 function displayErrorWindow (prefix, ajax_content) {
362         // Fade out warning window, if open
363         //* DEBUG: */ alert('displayErrorWindow(): prefix=' + prefix + ' - calling closeWarningWindow()');
364         closeWarningWindow(prefix);
365
366         // Fade it out for eye-candy
367         closeErrorWindow(prefix);
368
369         // Abort here if errorDisplayed is still true
370         if (errorDisplayed == true) {
371                 // Make sure this doesn't happen
372                 return;
373         } // END - if
374
375         // Copy the response text to the error variable
376         if (ajax_content.reply_content != undefined) {
377                 $('#' + prefix + '_error_content').html(ajax_content.reply_content);
378         } else {
379                 $('#' + prefix + '_error_content').html(ajax_content);
380         }
381
382         // Fade the error in
383         $('#' + prefix + '_error').fadeIn('slow', function() {
384                 // Mark error as displayed
385                 errorDisplayed = true;
386         });
387 }
388
389 // Waits until the window has been closed
390 function closeErrorLocked () {
391         // Has all been loaded?
392         if (errorDisplayed == false) {
393                 // Then release ready()
394                 $.holdReady(false);
395         } else {
396                 // Recursive call again
397                 window.setTimeout('closeErrorLocked()', 10);
398         }
399 }
400
401 // Closes an error window
402 function closeErrorWindow (prefix, waitClose, resetCurrentTabId) {
403         // Is the error displayed?
404         if (errorDisplayed == true) {
405                 // Shall we wait ("sync") until the animation has completed?
406                 if (waitClose == true) {
407                         // Hold the ready status
408                         $.holdReady(true);
409                 } // END - if
410
411                 // Yes, then fade it out
412                 $('#' + prefix + '_error').fadeOut('fast', function() {
413                         // Set current tab id to default
414                         if (resetCurrentTabId == true) {
415                                 setCurrentTabId(defaultTabId);
416                         } // END - if
417
418                         // Mark it as closed
419                         errorDisplayed = false;
420                 });
421
422                 // Shall this animation be "synchronized"?
423                 if (waitClose == true) {
424                         // Wait for the window has been closed
425                         closeErrorLocked();
426                 } // END - if
427         } // END - if
428 }
429
430 // Waits until the window has been closed
431 function closeWarningLocked () {
432         // Has all been loaded?
433         if (warningDisplayed == false) {
434                 // Then release ready()
435                 $.holdReady(false);
436         } else {
437                 // Recursive call again
438                 window.setTimeout('closeWarningLocked()', 10);
439         }
440 }
441
442 // Closes an warning window
443 function closeWarningWindow (prefix, waitClose, resetCurrentTabId) {
444         //* DEBUG: */ alert('prefix=' + prefix + ',waitClose=' + waitClose + ' - ENTERED!');
445         // Is the warning displayed?
446         if (warningDisplayed == true) {
447                 // Shall we wait ("sync") until the animation has completed?
448                 //* DEBUG: */ alert('prefix=' + prefix + ',waitClose=' + waitClose + ',warningDisplayed=true');
449                 if (waitClose == true) {
450                         // Hold the ready status
451                         $.holdReady(true);
452                 } // END - if
453
454                 // Yes, then fade it out
455                 $('#' + prefix + '_warning').fadeOut('fast', function() {
456                         // Set current tab id to default
457                         //* DEBUG: */ alert('closeWarningWindow(): prefix=' + prefix + ',waitClose=' + waitClose + ',defaultTab=' + defaultTabId + ' - Calling setCurrentTabId()');
458                         if (resetCurrentTabId == true) {
459                                 setCurrentTabId(defaultTabId);
460                         } // END - if
461
462                         // Mark it as closed
463                         warningDisplayed = false;
464                         //* DEBUG: */ alert('closeWarningWindow(): waitClose=' + waitClose + ',resetCurrentTabId=' + resetCurrentTabId + ',warningDisplayed=false');
465                 });
466
467                 // Shall this animation be "synchronized"?
468                 if (waitClose == true) {
469                         // Wait for the window has been closed
470                         //* DEBUG: */ alert('prefix=' + prefix + ',waitClose=' + waitClose + ' - LOCKED!');
471                         closeWarningLocked();
472                 } // END - if
473         } // END - if
474 }
475
476 // A footer navigation button has been clicked
477 function doFooterPage (prefix, htmlId, button) {
478         //* DEBUG: */ alert('doFooterPage(): prefix=' + prefix + ',htmlId=' + htmlId + ',button=' + button + ' - ENTERED!');
479         // Has something being changed?
480         if (formChanged == true) {
481                 // Output message to browser
482                 displayChangedWarningWindow(prefix, button);
483
484                 // Abort here
485                 return;
486         } // END - if
487
488         // Is there a 'next' entry?
489         //* DEBUG: */ alert('doFooterPage(): button=' + button + ',currentTabId=' + currentTabId + ',nextPage[currentTabId]=' + nextPage[currentTabId]);
490         if ((button == 'next') && (nextPage[currentTabId] != null)) {
491                 // Then call the AJAX requester
492                 var page = nextPage[currentTabId];
493         } else if ((button == 'previous') && (previousPage[currentTabId] != null)) {
494                 // Then call the AJAX requester
495                 var page = previousPage[currentTabId];
496         }
497
498         // Request AJAX content
499         requestAjaxContent(prefix, htmlId, page);
500
501         // Change the footer navigation
502         enableFooterNavigation(prefix, page);
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         // Is there 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                         // Is there '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 }