1 /**
  2  * @fileOverview
  3  * @author <a href="https://www.labkey.org">LabKey</a> (<a href="mailto:info@labkey.com">info@labkey.com</a>)
  4  * @license Copyright (c) 2010-2016 LabKey Corporation
  5  * <p/>
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  * <p/>
 10  * http://www.apache.org/licenses/LICENSE-2.0
 11  * <p/>
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing permissions and
 16  * limitations under the License.
 17  * <p/>
 18  */
 19 (function($) {
 20 
 21     /**
 22      * @description Portal class to allow programmatic administration of portal pages.
 23      * @class Portal class to allow programmatic administration of portal pages.
 24      *            <p>Additional Documentation:
 25      *              <ul>
 26      *                  <li><a href= "https://www.labkey.org/wiki/home/Documentation/page.view?name=projects">Project and Folder Administration</a></li>
 27      *                  <li><a href= "https://www.labkey.org/wiki/home/Documentation/page.view?name=addModule">Add Web Parts</a></li>
 28      *                  <li><a href= "https://www.labkey.org/wiki/home/Documentation/page.view?name=manageWebParts">Manage Web Parts</a></li>
 29      *              </ul>
 30      *           </p>
 31      */
 32     LABKEY.Portal = new function()
 33     {
 34         // private methods:
 35         var MOVE_ACTION = 'move';
 36         var REMOVE_ACTION = 'remove';
 37         var MOVE_UP = 0;
 38         var MOVE_DOWN = 1;
 39         var MOVE_LEFT = 0;
 40         var MOVE_RIGHT = 1;
 41 
 42         function wrapSuccessCallback(userSuccessCallback, action, webPartId, direction)
 43         {
 44             return function(webparts, responseObj, options)
 45             {
 46                 updateDOM(webparts, action, webPartId, direction);
 47                 // after update, call the user's success function:
 48                 if (userSuccessCallback)
 49                     userSuccessCallback(webparts, responseObj, options);
 50             }
 51         }
 52 
 53         function updateDOM(webparts, action, webPartId, direction)
 54         {
 55             // First build up a list of valid webpart table DOM IDs.  This allows us to skip webpart tables that are embedded
 56             // within others (as in the case of using the APIs to asynchronously render nested webparts).  This ensures that
 57             // we only rearrange top-level webparts.
 58             var validWebpartTableIds = {}, regionParts, i;
 59             for (var region in webparts)
 60             {
 61                 if (webparts.hasOwnProperty(region))
 62                 {
 63                     regionParts = webparts[region];
 64                     for (i = 0; i < regionParts.length; i++)
 65                     {
 66                         validWebpartTableIds['webpart_' + regionParts[i].webPartId] = true;
 67                     }
 68                 }
 69             }
 70 
 71             // would be nice to use getElementsByName('webpart') here, but this isn't supported in IE.
 72             var tables = document.getElementsByTagName('table');
 73             var webpartTables = [];
 74             var targetTable;
 75             var targetTableIndex;
 76             for (var tableIndex = 0; tableIndex < tables.length; tableIndex++)
 77             {
 78                 var table = tables[tableIndex];
 79                 // a table is possibly affected by a delete action if it's of type 'webpart' (whether it's in the current set
 80                 // of active webparts or not). It's possibly affected by a move action only if it's in the active set of webparts.
 81                 var possiblyAffected = ((action == REMOVE_ACTION && table.getAttribute('name') == 'webpart') || validWebpartTableIds[table.id]);
 82                 if (possiblyAffected)
 83                 {
 84                     webpartTables[webpartTables.length] = table;
 85                     if (table.id == 'webpart_' + webPartId)
 86                     {
 87                         targetTableIndex = webpartTables.length - 1;
 88                         targetTable = table;
 89                     }
 90                 }
 91             }
 92 
 93             if (targetTable)
 94             {
 95                 if (action == MOVE_ACTION)
 96                 {
 97                     var swapTable = webpartTables[direction == MOVE_UP ? targetTableIndex - 1 : targetTableIndex + 1];
 98                     if (swapTable)
 99                     {
100                         var parentEl = targetTable.parentNode;
101                         var insertPoint = swapTable.nextSibling;
102                         var swapPoint = targetTable.nextSibling;
103 
104                         // Need to make sure the element is actually a child before trying to remove
105                         for (var node = 0; node < parentEl.childNodes.length; node++) {
106                             if (parentEl.childNodes[node] === swapTable) {
107                                 parentEl.removeChild(targetTable);
108                                 parentEl.removeChild(swapTable);
109                                 parentEl.insertBefore(targetTable, insertPoint);
110                                 parentEl.insertBefore(swapTable, swapPoint);
111                                 break;
112                             }
113                         }
114                     }
115                 }
116                 else if (action == REMOVE_ACTION)
117                 {
118                     var breakEl   = targetTable.previousElementSibling;
119                     var breakNode = targetTable.previousSibling;
120                     targetTable.parentNode.removeChild(breakEl || breakNode); // TODO: Does not properly remove in IE7
121                     targetTable.parentNode.removeChild(targetTable);
122                 }
123             }
124             updateButtons(webparts);
125         }
126 
127 
128         function removeImgHref(imageEl, newImageCls)
129         {
130             var href = imageEl.parentNode;
131             var hrefParent = href.parentNode;
132              imageEl.className = newImageCls;
133             // replace href with imageEl to remove the link entirely:
134             hrefParent.replaceChild(imageEl, href);
135             hrefParent.className = "labkey-wp-icon-button-inactive";
136         }
137 
138         function addImgHref(imageEl, href, newImageCls)
139         {
140             var hrefEl = document.createElement("a");
141             hrefEl.href = href;
142             imageEl.className = newImageCls;
143             imageEl.parentNode.className = "labkey-wp-icon-button-active";
144             imageEl.parentNode.replaceChild(hrefEl, imageEl);
145             hrefEl.appendChild(imageEl);
146         }
147 
148         function updateButtons(webparts)
149         {
150             var moveUpImage = 'fa fa-caret-square-o-up labkey-fa-portal-nav';
151             var moveUpDisabledImage = 'fa fa-caret-square-o-up x4-btn-default-toolbar-small-disabled labkey-fa-portal-nav';
152             var moveDownImage = 'fa fa-caret-square-o-down labkey-fa-portal-nav';
153             var moveDownDisabledImage = 'fa fa-caret-square-o-down x4-btn-default-toolbar-small-disabled labkey-fa-portal-nav';
154             for (var region in webparts)
155             {
156                 if (!webparts.hasOwnProperty(region))
157                     continue;
158 
159                 var regionParts = webparts[region];
160 
161                 // get the webpart table elements from the DOM here; it's possible that some configured webparts may
162                 // not actually be in the document (if the webpartfactory returns null for security reasons, for example.)
163                 var confirmedWebparts = [];
164                 var confirmedWebpartTables = [];
165                 var index;
166                 for (index = 0; index < regionParts.length; index++)
167                 {
168                     var testWebpart = regionParts[index];
169                     var testTable = document.getElementById('webpart_' + testWebpart.webPartId);
170                     if (testTable)
171                     {
172                         confirmedWebparts[confirmedWebparts.length] = testWebpart;
173                         confirmedWebpartTables[confirmedWebpartTables.length] = testTable;
174                     }
175                 }
176 
177                 for (index = 0; index < confirmedWebpartTables.length; index++)
178                 {
179                     var webpartTable = confirmedWebpartTables[index];
180                     var webpart = confirmedWebparts[index];
181                     var disableUp = index == 0;
182                     var disableDown = index == confirmedWebparts.length - 1;
183                     var imgChildren = webpartTable.getElementsByClassName('labkey-fa-portal-nav');
184 
185                     for (var imageIndex = 0; imageIndex < imgChildren.length; imageIndex++)
186                     {
187                         var imageEl = imgChildren[imageIndex];
188                         if (imageEl.className.indexOf(moveUpImage) >= 0 && disableUp)
189                             removeImgHref(imageEl, moveUpDisabledImage);
190                         else if (imageEl.className.indexOf(moveUpDisabledImage) >= 0 && !disableUp)
191                             addImgHref(imageEl, "javascript:LABKEY.Portal.moveWebPartUp({webPartId:" + webpart.webPartId + ",updateDOM:true});", moveUpImage);
192                         else if (imageEl.className.indexOf(moveDownImage) >= 0 && disableDown)
193                             removeImgHref(imageEl, moveDownDisabledImage);
194                         else if (imageEl.className.indexOf(moveDownDisabledImage) >= 0 && !disableDown)
195                             addImgHref(imageEl, "javascript:LABKEY.Portal.moveWebPartDown({webPartId:" + webpart.webPartId + ",updateDOM:true});", moveDownImage);
196                     }
197                 }
198             }
199         }
200 
201         function wrapErrorCallback(userErrorCallback)
202         {
203             return function(exceptionObj, responseObj, options)
204             {
205                 // after update, call the user's success function:
206                 return userErrorCallback(exceptionObj, responseObj, options);
207             }
208         }
209 
210         function defaultErrorHandler(exceptionObj, responseObj, options)
211         {
212             LABKEY.Utils.displayAjaxErrorResponse(responseObj, exceptionObj);
213         }
214 
215         function mapIndexConfigParameters(config, action, direction)
216         {
217             var params = {};
218 
219             LABKEY.Utils.applyTranslated(params, config, {
220                 success: false,
221                 failure: false,
222                 scope: false
223             });
224 
225             if (direction == MOVE_UP || direction == MOVE_DOWN)
226                 params.direction = direction;
227 
228             // These layered callbacks are confusing.  The outermost (second wrapper, below) de-JSONs the response, passing
229             // native javascript objects to the success wrapper function defined by wrapErrorCallback (wrapSuccessCallback
230             // below).  The wrapErrorCallback/wrapSuccessCallback function is responsible for updating the DOM, if necessary,
231             // closing the wait dialog, and then calling the API developer's success callback function, if one exists.  If
232             // no DOM update is requested, we skip the middle callback layer.
233             var errorCallback = LABKEY.Utils.getOnFailure(config) || defaultErrorHandler;
234 
235             if (config.updateDOM)
236                 errorCallback = wrapErrorCallback(errorCallback);
237             errorCallback = LABKEY.Utils.getCallbackWrapper(errorCallback, config.scope, true);
238 
239             // do the same double-wrap with the success callback as with the error callback:
240             var successCallback = config.success;
241             if (config.updateDOM)
242                 successCallback = wrapSuccessCallback(LABKEY.Utils.getOnSuccess(config), action, config.webPartId, direction);
243             successCallback = LABKEY.Utils.getCallbackWrapper(successCallback, config.scope);
244 
245             return {
246                 params: params,
247                 success: successCallback,
248                 error: errorCallback
249             };
250         }
251 
252         // TODO: This should be considered 'Native UI' and be migrated away from ExtJS
253         var showEditTabWindow = function(title, handler, name)
254         {
255             LABKEY.requiresExt4Sandbox(function() {
256                 Ext4.onReady(function() {
257                     var nameTextField = Ext4.create('Ext.form.field.Text', {
258                         xtype: 'textfield',
259                         fieldLabel: 'Name',
260                         labelWidth: 50,
261                         width: 250,
262                         name: 'tabName',
263                         value: name ? name : '',
264                         maxLength: 64,
265                         enforceMaxLength: true,
266                         enableKeyEvents: true,
267                         labelSeparator: '',
268                         listeners: {
269                             scope: this,
270                             keypress: function(field, event){
271                                 if (event.getKey() == event.ENTER) {
272                                     handler(nameTextField.getValue(), editTabWindow);
273                                 }
274                             }
275                         }
276                     });
277 
278                     var editTabWindow = Ext4.create('Ext.window.Window', {
279                         title: title,
280                         closeAction: 'destroy',
281                         modal: true,
282                         border: false,
283                         items: [{
284                             xtype: 'panel',
285                             border: false,
286                             frame: false,
287                             bodyPadding: 5,
288                             items: [nameTextField]
289                         }],
290                         buttons: [{
291                             text: 'Ok',
292                             scope: this,
293                             handler: function(){handler(nameTextField.getValue(), editTabWindow);}
294                         },{
295                             text: 'Cancel',
296                             scope: this,
297                             handler: function(){
298                                 editTabWindow.close();
299                             }
300                         }]
301                     });
302 
303                     editTabWindow.show(false, function(){nameTextField.focus();}, this);
304                 });
305             });
306         };
307 
308         var showPermissions = function(webpartID, permission, containerPath) {
309 
310             var display = function() {
311                 Ext4.onReady(function() {
312                     Ext4.create('LABKEY.Portal.WebPartPermissionsPanel', {
313                         webPartId: webpartID,
314                         permission: permission,
315                         containerPath: containerPath,
316                         autoShow: true
317                     });
318                 });
319             };
320 
321             var loader = function() {
322                 LABKEY.requiresExt4Sandbox(function() {
323                     LABKEY.requiresScript('WebPartPermissionsPanel.js', display, this);
324                 }, this);
325             };
326 
327             // Require a webpartID for any action
328             if (webpartID) {
329                 if (LABKEY.Portal.WebPartPermissionsPanel) {
330                     display();
331                 }
332                 else {
333                     loader();
334                 }
335             }
336         };
337 
338         // public methods:
339         /** @scope LABKEY.Portal.prototype */
340         return {
341 
342             /**
343              * Move an existing web part up within its portal page, identifying the web part by its unique web part ID.
344              * @param config An object which contains the following configuration properties.
345              * @param {String} [config.pageId] Reserved for a time when multiple portal pages are allowed per container.
346              * If not provided, main portal page for the container will be queried.
347              * @param {String} [config.containerPath] Specifies the container in which the web part query should be performed.
348              * If not provided, the method will operate on the current container.
349              * @param {Function} config.success
350              Function called when the this function completes successfully.
351              This function will be called with the following arguments:
352              <ul>
353              <li>webparts: an object with one property for each page region, generally 'body' and 'right'.  The value
354              of each property is an ordered array of objects indicating the current web part configuration
355              on the page.  Each object has the following properties:
356              <ul>
357              <li>name: the name of the web part</li>
358              <li>index: the index of the web part</li>
359              <li>webPartId: the unique integer ID of this web part.</li>
360              </ul>
361              </li>
362              <li>responseObj: the XMLHttpResponseObject instance used to make the AJAX request</li>
363              <li>options: the options used for the AJAX request</li>
364              </ul>
365              * @param {Function} [config.failure] Function called when execution fails.
366              *       This function will be called with the following arguments:
367              <ul>
368              <li>exceptionObj: A JavaScript Error object caught by the calling code.</li>
369              <li>responseObj: The XMLHttpRequest object containing the response data.</li>
370              <li>options: the options used for the AJAX request</li>
371              </ul>
372              */
373             getWebParts : function(config)
374             {
375                 LABKEY.Ajax.request({
376                     url: LABKEY.ActionURL.buildURL('project', 'getWebParts', config.containerPath),
377                     method : 'GET',
378                     success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
379                     failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
380                     params: config
381                 });
382             },
383 
384             /**
385              * Move an existing web part up within its portal page, identifying the web part by index.
386              * @param config An object which contains the following configuration properties.
387              * @param {String} [config.pageId] Reserved for a time when multiple portal pages are allowed per container.
388              * If not provided, main portal page for the container will be modified.
389              * @param {String} [config.containerPath] Specifies the container in which the web part modification should be performed.
390              * If not provided, the method will operate on the current container.
391              * @param {String} config.webPartId The unique integer ID of the web part to be moved.
392              * @param {Boolean} [config.updateDOM] Indicates whether the current page's DOM should be updated to reflect changes to web part layout.
393              * Defaults to false.
394              * @param {Function} config.success
395              Function called when the this function completes successfully.
396              This function will be called with the following arguments:
397              <ul>
398              <li>webparts: an object with one property for each page region, generally 'body' and 'right'.  The value
399              of each property is an ordered array of objects indicating the current web part configuration
400              on the page.  Each object has the following properties:
401              <ul>
402              <li>name: the name of the web part</li>
403              <li>index: the index of the web part</li>
404              <li>webPartId: the unique integer ID of this web part.</li>
405              </ul>
406              </li>
407              <li>responseObj: the XMLHttpResponseObject instance used to make the AJAX request</li>
408              <li>options: the options used for the AJAX request</li>
409              </ul>
410              * @param {Function} [config.failure] Function called when execution fails.
411              *       This function will be called with the following arguments:
412              <ul>
413              <li>exceptionObj: A JavaScript Error object caught by the calling code.</li>
414              <li>responseObj: The XMLHttpRequest object containing the response data.</li>
415              <li>options: the options used for the AJAX request</li>
416              </ul>
417              */
418             moveWebPartUp : function(config)
419             {
420                 var callConfig = mapIndexConfigParameters(config, MOVE_ACTION, MOVE_UP);
421                 LABKEY.Ajax.request({
422                     url: LABKEY.ActionURL.buildURL('project', 'moveWebPartAsync', config.containerPath),
423                     method : 'GET',
424                     success: LABKEY.Utils.getOnSuccess(callConfig),
425                     failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(callConfig), callConfig.scope, true),
426                     params: callConfig.params
427                 });
428             },
429 
430 
431             /**
432              * Move an existing web part down within its portal page, identifying the web part by the unique ID of the containing span.
433              * This span will have name 'webpart'.
434              * @param config An object which contains the following configuration properties.
435              * @param {String} [config.pageId] Reserved for a time when multiple portal pages are allowed per container.
436              * If not provided, main portal page for the container will be modified.
437              * @param {String} [config.containerPath] Specifies the container in which the web part modification should be performed.
438              * If not provided, the method will operate on the current container.
439              * @param {String} config.webPartId The unique integer ID of the web part to be moved.
440              * @param {Boolean} [config.updateDOM] Indicates whether the current page's DOM should be updated to reflect changes to web part layout.
441              * Defaults to false.
442              * @param {Function} config.success
443              Function called when the this function completes successfully.
444              This function will be called with the following arguments:
445              <ul>
446              <li>webparts: an object with one property for each page region, generally 'body' and 'right'.  The value
447              of each property is an ordered array of objects indicating the current web part configuration
448              on the page.  Each object has the following properties:
449              <ul>
450              <li>name: the name of the web part</li>
451              <li>index: the index of the web part</li>
452              <li>webPartId: the unique integer ID of this web part.</li>
453              </ul>
454              </li>
455              <li>responseObj: the XMLHttpResponseObject instance used to make the AJAX request</li>
456              <li>options: the options used for the AJAX request</li>
457              </ul>
458              * @param {Function} [config.failure] Function called when execution fails.
459              *       This function will be called with the following arguments:
460              <ul>
461              <li>exceptionObj: A JavaScript Error object caught by the calling code.</li>
462              <li>responseObj: The XMLHttpRequest object containing the response data.</li>
463              <li>options: the options used for the AJAX request</li>
464              </ul>
465              */
466             moveWebPartDown : function(config)
467             {
468                 var callConfig = mapIndexConfigParameters(config, MOVE_ACTION, MOVE_DOWN);
469                 LABKEY.Ajax.request({
470                     url: LABKEY.ActionURL.buildURL('project', 'moveWebPartAsync', config.containerPath),
471                     method : 'GET',
472                     success: LABKEY.Utils.getOnSuccess(callConfig),
473                     failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(callConfig), callConfig.scope, true),
474                     params: callConfig.params
475                 });
476             },
477             /**
478              * Remove an existing web part within its portal page.
479              * @param config An object which contains the following configuration properties.
480              * @param {String} [config.pageId] Reserved for a time when multiple portal pages are allowed per container.
481              * If not provided, main portal page for the container will be modified.
482              * @param {String} [config.containerPath] Specifies the container in which the web part modification should be performed.
483              * If not provided, the method will operate on the current container.
484              * @param {String} config.webPartId The unique integer ID of the web part to be moved.
485              * @param {Boolean} [config.updateDOM] Indicates whether the current page's DOM should be updated to reflect changes to web part layout.
486              * Defaults to false.
487              * @param {Function} config.success
488              Function called when the this function completes successfully.
489              This function will be called with the following arguments:
490              <ul>
491              <li>webparts: an object with one property for each page region, generally 'body' and 'right'.  The value
492              of each property is an ordered array of objects indicating the current web part configuration
493              on the page.  Each object has the following properties:
494              <ul>
495              <li>name: the name of the web part</li>
496              <li>index: the index of the web part</li>
497              <li>webPartId: the unique integer ID of this web part.</li>
498              </ul>
499              </li>
500              <li>responseObj: the XMLHttpResponseObject instance used to make the AJAX request</li>
501              <li>options: the options used for the AJAX request</li>
502              </ul>
503              * @param {Function} [config.failure] Function called when execution fails.
504              *       This function will be called with the following arguments:
505              <ul>
506              <li>exceptionObj: A JavaScript Error object caught by the calling code.</li>
507              <li>responseObj: The XMLHttpRequest object containing the response data.</li>
508              <li>options: the options used for the AJAX request</li>
509              </ul>
510              */
511             removeWebPart : function(config)
512             {
513                 var callConfig = mapIndexConfigParameters(config, REMOVE_ACTION, undefined);
514                 LABKEY.Ajax.request({
515                     url: LABKEY.ActionURL.buildURL('project', 'deleteWebPartAsync', config.containerPath),
516                     method : 'GET',
517                     success: LABKEY.Utils.getOnSuccess(callConfig),
518                     failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(callConfig), callConfig.scope, true),
519                     params: callConfig.params
520                 });
521             },
522 
523             /**
524              * Move a folder tab to the left.
525              * @param config An object which contains the following configuration properties.
526              * @param {String} [config.pageId] The pageId of the tab to be moved.
527              * @param {String} [config.folderTabCaption] The caption of the tab to be moved.
528              */
529             moveTabLeft : function(config)
530             {
531                 LABKEY.Ajax.request({
532                     url: LABKEY.ActionURL.buildURL('admin', 'moveTab', LABKEY.container.path),
533                     method: 'GET',
534                     params: {
535                         pageId: config.pageId,
536                         direction: MOVE_LEFT
537                     },
538                     success: LABKEY.Utils.getCallbackWrapper(function(response, options) {
539                         if(config.domId && response.pageIdToSwap && response.pageIdToSwap !== response.pageId) {
540                             var tabAnchor = $('#' + config.domId)[0];
541                             if (tabAnchor) {
542                                 $(tabAnchor.parentElement).insertBefore(tabAnchor.parentNode.previousElementSibling);
543                             }
544                         }
545                     }, this, false),
546                     failure: function(response){
547                         // Currently no-op when failure occurs.
548                     }
549                 });
550             },
551 
552             /**
553              * Move a folder tab to the right.
554              * @param config An object which contains the following configuration properties.
555              * @param {String} [config.pageId] Reserved for a time when multiple portal pages are allowed per container.
556              */
557             moveTabRight : function(config)
558             {
559                 LABKEY.Ajax.request({
560                     url: LABKEY.ActionURL.buildURL('admin', 'moveTab', LABKEY.container.path),
561                     method: 'GET',
562                     params: {
563                         pageId: config.pageId,
564                         direction: MOVE_RIGHT
565                     },
566                     success: LABKEY.Utils.getCallbackWrapper(function(response, options) {
567                         if(config.domId && response.pageIdToSwap && response.pageIdToSwap !== response.pageId) {
568                             var tabAnchor = $('#' + config.domId)[0];
569                             if (tabAnchor) {
570                                 $(tabAnchor.parentElement).insertAfter(tabAnchor.parentNode.nextElementSibling);
571                             }
572                         }
573                     }, this, false),
574                     failure: function(response, options){
575                         // Currently no-op when failure occurs.
576                     }
577                 });
578             },
579 
580             /**
581              * Toggle tab edit mode. Enables or disables tab edit mode. When in tab edit mode an administrator
582              * can manage tabs (i.e. change order, add, remove, etc.)
583              */
584             toggleTabEditMode : function()
585             {
586                 LABKEY.Ajax.request({
587                     url: LABKEY.ActionURL.buildURL('admin', 'toggleTabEditMode', LABKEY.container.path),
588                     method: 'GET',
589                     success: LABKEY.Utils.getCallbackWrapper(function(response, options){
590                         var classToSearchFor = response.tabEditMode ? 'tab-edit-mode-disabled' : 'tab-edit-mode-enabled';
591                         var classToReplaceWith = response.tabEditMode ? 'tab-edit-mode-enabled' : 'tab-edit-mode-disabled';
592                         var tabDiv = document.getElementsByClassName(classToSearchFor)[0];
593 
594                         if (tabDiv) {
595                             // Navigate to the start URL if the current active tab is also hidden.
596                             if (response.startURL && tabDiv.querySelector('li.tab-nav-active.tab-nav-hidden'))
597                                 window.location = response.startURL;
598                             else
599                                 tabDiv.setAttribute('class', tabDiv.getAttribute('class').replace(classToSearchFor, classToReplaceWith));
600                         }
601                     })
602                 });
603             },
604 
605             /**
606              * Allows an administrator to add a new portal page tab.
607              */
608             addTab : function()
609             {
610                 var addTabHandler = function(name, editWindow)
611                 {
612                     LABKEY.Ajax.request({
613                         url: LABKEY.ActionURL.buildURL('admin', 'addTab'),
614                         method: 'POST',
615                         jsonData: {tabName: name},
616                         success: function(response)
617                         {
618                             var jsonResp = LABKEY.Utils.decode(response.responseText);
619                             if (jsonResp && jsonResp.success)
620                             {
621                                 if (jsonResp.url)
622                                     window.location = jsonResp.url;
623                             }
624                         },
625                         failure: function(response)
626                         {
627                             var jsonResp = LABKEY.Utils.decode(response.responseText);
628                             var errorMsg;
629                             if (jsonResp && jsonResp.errors)
630                                 errorMsg = jsonResp.errors[0].message;
631                             else
632                                 errorMsg = 'An unknown error occured. Please contact your administrator.';
633                             alert(errorMsg);
634                         }
635                     });
636                 };
637 
638                 showEditTabWindow("Add Tab", addTabHandler, null);
639             },
640 
641             /**
642              * Shows a hidden tab.
643              * @param pageId the pageId of the tab.
644              */
645             showTab : function(pageId)
646             {
647                 LABKEY.Ajax.request({
648                     url: LABKEY.ActionURL.buildURL('admin', 'showTab'),
649                     method: 'POST',
650                     jsonData: {tabPageId: pageId},
651                     success: function(response)
652                     {
653                         var jsonResp = LABKEY.Utils.decode(response.responseText);
654                         if (jsonResp && jsonResp.success)
655                         {
656                             if (jsonResp.url)
657                                 window.location = jsonResp.url;
658                         }
659                     },
660                     failure: function(response)
661                     {
662                         var jsonResp = LABKEY.Utils.decode(response.responseText);
663                         if (jsonResp && jsonResp.errors)
664                         {
665                             alert(jsonResp.errors[0].message);
666                         }
667                     }
668                 });
669             },
670 
671             /**
672              * Allows an administrator to rename a tab.
673              * @param pageId the pageId of the tab to rename
674              * @param urlId the id of the anchor tag of the tab to be renamed.
675              */
676             renameTab : function(pageId, urlId)
677             {
678                 var tabLinkEl = document.getElementById(urlId);
679 
680                 if (tabLinkEl)
681                 {
682                     var currentName = tabLinkEl.textContent;
683 
684                     var renameHandler = function(name, editWindow)
685                     {
686                         LABKEY.Ajax.request({
687                             url: LABKEY.ActionURL.buildURL('admin', 'renameTab'),
688                             method: 'POST',
689                             jsonData: {
690                                 tabPageId: pageId,
691                                 tabName: name
692                             },
693                             success: function(response)
694                             {
695                                 var jsonResp = LABKEY.Utils.decode(response.responseText);
696                                 if (jsonResp.success)
697                                     tabLinkEl.textContent = name;
698                                 editWindow.close();
699                             },
700                             failure: function(response)
701                             {
702                                 var jsonResp = LABKEY.Utils.decode(response.responseText);
703                                 var errorMsg;
704                                 if (jsonResp.errors)
705                                     errorMsg = jsonResp.errors[0].message;
706                                 else
707                                     errorMsg = 'An unknown error occured. Please contact your administrator.';
708                                 LABKEY.Utils.alert('Oops', errorMsg);
709                             }
710                         });
711                     };
712 
713                     showEditTabWindow("Rename Tab", renameHandler, currentName);
714                 }
715             },
716 
717             _showPermissions : showPermissions
718         };
719     };
720 
721 })(jQuery);
722 
723