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-2017 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/Documentation/wiki-page.view?name=projects">Project and Folder Administration</a></li>
 27      *                  <li><a href= "https://www.labkey.org/Documentation/wiki-page.view?name=addModule">Add Web Parts</a></li>
 28      *                  <li><a href= "https://www.labkey.org/Documentation/wiki-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 TOGGLE_FRAME_ACTION = 'toggle_frame';
 38         var MOVE_UP = 0;
 39         var MOVE_DOWN = 1;
 40         var MOVE_LEFT = 0;
 41         var MOVE_RIGHT = 1;
 42 
 43         function wrapSuccessCallback(userSuccessCallback, action, webPartId, direction)
 44         {
 45             return function(webparts, responseObj, options)
 46             {
 47                 updateDOM(webparts, action, webPartId, direction);
 48                 // after update, call the user's success function:
 49                 if (userSuccessCallback)
 50                     userSuccessCallback(webparts, responseObj, options);
 51             }
 52         }
 53 
 54         function updateDOM(webparts, action, webPartId, direction)
 55         {
 56             var targetTable = document.getElementById('webpart_' + webPartId);
 57 
 58             if (targetTable)
 59             {
 60                 if (action === MOVE_ACTION)
 61                 {
 62                     var swapTable;
 63                     swapTable = direction === MOVE_UP ?
 64                             getAdjacentWebparts(targetTable).above :
 65                             getAdjacentWebparts(targetTable).below;
 66 
 67                     if (swapTable)
 68                     {
 69                         var parentEl = targetTable.parentNode;
 70                         var insertPoint = swapTable.nextSibling;
 71                         var swapPoint = targetTable.nextSibling;
 72 
 73                         // Need to make sure the element is actually a child before trying to remove
 74                         for (var node = 0; node < parentEl.childNodes.length; node++) {
 75                             if (parentEl.childNodes[node] === swapTable) {
 76                                 parentEl.removeChild(targetTable);
 77                                 parentEl.removeChild(swapTable);
 78                                 parentEl.insertBefore(targetTable, insertPoint);
 79                                 parentEl.insertBefore(swapTable, swapPoint);
 80                                 break;
 81                             }
 82                         }
 83 
 84                         var targetUpButtonClass = getUpDownButtons(targetTable).upButton.className;
 85                         var targetDownButtonClass= getUpDownButtons(targetTable).downButton.className;
 86                         updateUpDownButtons(targetTable, getUpDownButtons(swapTable).upButton.className, getUpDownButtons(swapTable).downButton.className);
 87                         updateUpDownButtons(swapTable, targetUpButtonClass, targetDownButtonClass);
 88                     }
 89                 }
 90                 else if (action === REMOVE_ACTION)
 91                 {
 92                     var adjacentWebparts = getAdjacentWebparts(targetTable);
 93 
 94                     if (adjacentWebparts.below)
 95                     {
 96                         updateUpDownButtons(adjacentWebparts.below, getUpDownButtons(targetTable).upButton.className, undefined);
 97                     }
 98 
 99                     if (adjacentWebparts.above)
100                     {
101                         updateUpDownButtons(adjacentWebparts.above, undefined, getUpDownButtons(targetTable).downButton.className);
102                     }
103 
104                     targetTable.parentNode.removeChild(targetTable);
105                 }
106             }
107         }
108 
109         function getAdjacentWebparts(webpart)
110         {
111             var above = webpart.previousElementSibling;
112             var below = webpart.nextElementSibling;
113 
114             return {
115                 above: above && above.getAttribute("name") === "webpart" ? above : undefined,
116                 below: below && below.getAttribute("name") === "webpart" ? below : undefined
117             }
118         }
119 
120         function getUpDownButtons(webpart)
121         {
122             var moveUpImage = 'fa fa-caret-square-o-up labkey-fa-portal-nav';
123             var moveUpDisabledImage = 'fa fa-caret-square-o-up labkey-btn-default-toolbar-small-disabled labkey-fa-portal-nav';
124             var moveDownImage = 'fa fa-caret-square-o-down labkey-fa-portal-nav';
125             var moveDownDisabledImage = 'fa fa-caret-square-o-down labkey-btn-default-toolbar-small-disabled labkey-fa-portal-nav';
126 
127             var getImageEl = function(webpart, imgClass)
128             {
129                 if (webpart){
130                     var imgChildren = webpart.getElementsByClassName('labkey-fa-portal-nav');
131                     for (var imageIndex = 0; imageIndex < imgChildren.length; imageIndex++)
132                     {
133                         if (imgChildren[imageIndex].className.indexOf(imgClass) >= 0)
134                         {
135                             return imgChildren[imageIndex];
136                         }
137                     }
138                 }
139             };
140 
141             return {
142                 upButton: getImageEl(webpart, moveUpImage) || getImageEl(webpart, moveUpDisabledImage),
143                 downButton: getImageEl(webpart, moveDownImage) || getImageEl(webpart, moveDownDisabledImage)
144             }
145         }
146 
147 
148         function updateUpDownButtons(webpart, upButtonClass, downButtonClass)
149         {
150             if (upButtonClass)
151                 getUpDownButtons(webpart).upButton.className = upButtonClass;
152             if (downButtonClass)
153                 getUpDownButtons(webpart).downButton.className = downButtonClass;
154         }
155 
156         function wrapErrorCallback(userErrorCallback)
157         {
158             return function(exceptionObj, responseObj, options)
159             {
160                 // after update, call the user's success function:
161                 return userErrorCallback(exceptionObj, responseObj, options);
162             }
163         }
164 
165         function defaultErrorHandler(exceptionObj, responseObj, options)
166         {
167             LABKEY.Utils.displayAjaxErrorResponse(responseObj, exceptionObj);
168         }
169 
170         function mapIndexConfigParameters(config, action, direction)
171         {
172             var params = {};
173 
174             LABKEY.Utils.applyTranslated(params, config, {
175                 success: false,
176                 failure: false,
177                 scope: false
178             });
179 
180             if (direction == MOVE_UP || direction == MOVE_DOWN)
181                 params.direction = direction;
182 
183             // These layered callbacks are confusing.  The outermost (second wrapper, below) de-JSONs the response, passing
184             // native javascript objects to the success wrapper function defined by wrapErrorCallback (wrapSuccessCallback
185             // below).  The wrapErrorCallback/wrapSuccessCallback function is responsible for updating the DOM, if necessary,
186             // closing the wait dialog, and then calling the API developer's success callback function, if one exists.  If
187             // no DOM update is requested, we skip the middle callback layer.
188             var errorCallback = LABKEY.Utils.getOnFailure(config) || defaultErrorHandler;
189 
190             if (config.updateDOM)
191                 errorCallback = wrapErrorCallback(errorCallback);
192             errorCallback = LABKEY.Utils.getCallbackWrapper(errorCallback, config.scope, true);
193 
194             // do the same double-wrap with the success callback as with the error callback:
195             var successCallback = config.success;
196             if (config.updateDOM)
197                 successCallback = wrapSuccessCallback(LABKEY.Utils.getOnSuccess(config), action, config.webPartId, direction);
198             successCallback = LABKEY.Utils.getCallbackWrapper(successCallback, config.scope);
199 
200             return {
201                 params: params,
202                 success: successCallback,
203                 error: errorCallback
204             };
205         }
206 
207         // TODO: This should be considered 'Native UI' and be migrated away from ExtJS
208         var showEditTabWindow = function(title, handler, name)
209         {
210             LABKEY.requiresExt4Sandbox(function() {
211                 Ext4.onReady(function() {
212                     var nameTextField = Ext4.create('Ext.form.field.Text', {
213                         xtype: 'textfield',
214                         fieldLabel: 'Name',
215                         labelWidth: 50,
216                         width: 250,
217                         name: 'tabName',
218                         value: name ? name : '',
219                         maxLength: 64,
220                         enforceMaxLength: true,
221                         enableKeyEvents: true,
222                         labelSeparator: '',
223                         selectOnFocus: true,
224                         listeners: {
225                             scope: this,
226                             keypress: function(field, event){
227                                 if (event.getKey() == event.ENTER) {
228                                     handler(nameTextField.getValue(), editTabWindow);
229                                 }
230                             }
231                         }
232                     });
233 
234                     var editTabWindow = Ext4.create('Ext.window.Window', {
235                         title: title,
236                         closeAction: 'destroy',
237                         modal: true,
238                         border: false,
239                         items: [{
240                             xtype: 'panel',
241                             border: false,
242                             frame: false,
243                             bodyPadding: 5,
244                             items: [nameTextField]
245                         }],
246                         buttons: [{
247                             text: 'Ok',
248                             scope: this,
249                             handler: function(){handler(nameTextField.getValue(), editTabWindow);}
250                         },{
251                             text: 'Cancel',
252                             scope: this,
253                             handler: function(){
254                                 editTabWindow.close();
255                             }
256                         }]
257                     });
258 
259                     // TODO: Until async CSS load blocking is complete give style a moment to load
260                     setTimeout(function() {
261                         editTabWindow.show(false, function(){nameTextField.focus();}, this);
262                     }, 100);
263                 });
264             });
265         };
266 
267         var showPermissions = function(webpartID, permission, containerPath) {
268 
269             var display = function() {
270                 Ext4.onReady(function() {
271                     Ext4.create('LABKEY.Portal.WebPartPermissionsPanel', {
272                         webPartId: webpartID,
273                         permission: permission,
274                         containerPath: containerPath,
275                         autoShow: true
276                     });
277                 });
278             };
279 
280             var loader = function() {
281                 LABKEY.requiresExt4Sandbox(function() {
282                     LABKEY.requiresScript('WebPartPermissionsPanel.js', display, this);
283                 }, this);
284             };
285 
286             // Require a webpartID for any action
287             if (webpartID) {
288                 if (LABKEY.Portal.WebPartPermissionsPanel) {
289                     display();
290                 }
291                 else {
292                     loader();
293                 }
294             }
295         };
296 
297         // public methods:
298         /** @scope LABKEY.Portal.prototype */
299         return {
300 
301             /**
302              * Move an existing web part up within its portal page, identifying the web part by its unique web part ID.
303              * @param config An object which contains the following configuration properties.
304              * @param {String} [config.pageId] Reserved for a time when multiple portal pages are allowed per container.
305              * If not provided, main portal page for the container will be queried.
306              * @param {String} [config.containerPath] Specifies the container in which the web part query should be performed.
307              * If not provided, the method will operate on the current container.
308              * @param {Function} config.success
309              Function called when the this function completes successfully.
310              This function will be called with the following arguments:
311              <ul>
312              <li>webparts: an object with one property for each page region, generally 'body' and 'right'.  The value
313              of each property is an ordered array of objects indicating the current web part configuration
314              on the page.  Each object has the following properties:
315              <ul>
316              <li>name: the name of the web part</li>
317              <li>index: the index of the web part</li>
318              <li>webPartId: the unique integer ID of this web part.</li>
319              </ul>
320              </li>
321              <li>responseObj: the XMLHttpResponseObject instance used to make the AJAX request</li>
322              <li>options: the options used for the AJAX request</li>
323              </ul>
324              * @param {Function} [config.failure] Function called when execution fails.
325              *       This function will be called with the following arguments:
326              <ul>
327              <li>exceptionObj: A JavaScript Error object caught by the calling code.</li>
328              <li>responseObj: The XMLHttpRequest object containing the response data.</li>
329              <li>options: the options used for the AJAX request</li>
330              </ul>
331              */
332             getWebParts : function(config)
333             {
334                 LABKEY.Ajax.request({
335                     url: LABKEY.ActionURL.buildURL('project', 'getWebParts.api', config.containerPath),
336                     method : 'GET',
337                     success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
338                     failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
339                     params: config
340                 });
341             },
342 
343             /**
344              * Move an existing web part up within its portal page, identifying the web part by index.
345              * @param config An object which contains the following configuration properties.
346              * @param {String} [config.pageId] Reserved for a time when multiple portal pages are allowed per container.
347              * If not provided, main portal page for the container will be modified.
348              * @param {String} [config.containerPath] Specifies the container in which the web part modification should be performed.
349              * If not provided, the method will operate on the current container.
350              * @param {String} config.webPartId The unique integer ID of the web part to be moved.
351              * @param {Boolean} [config.updateDOM] Indicates whether the current page's DOM should be updated to reflect changes to web part layout.
352              * Defaults to false.
353              * @param {Function} config.success
354              Function called when the this function completes successfully.
355              This function will be called with the following arguments:
356              <ul>
357              <li>webparts: an object with one property for each page region, generally 'body' and 'right'.  The value
358              of each property is an ordered array of objects indicating the current web part configuration
359              on the page.  Each object has the following properties:
360              <ul>
361              <li>name: the name of the web part</li>
362              <li>index: the index of the web part</li>
363              <li>webPartId: the unique integer ID of this web part.</li>
364              </ul>
365              </li>
366              <li>responseObj: the XMLHttpResponseObject instance used to make the AJAX request</li>
367              <li>options: the options used for the AJAX request</li>
368              </ul>
369              * @param {Function} [config.failure] Function called when execution fails.
370              *       This function will be called with the following arguments:
371              <ul>
372              <li>exceptionObj: A JavaScript Error object caught by the calling code.</li>
373              <li>responseObj: The XMLHttpRequest object containing the response data.</li>
374              <li>options: the options used for the AJAX request</li>
375              </ul>
376              */
377             moveWebPartUp : function(config)
378             {
379                 var callConfig = mapIndexConfigParameters(config, MOVE_ACTION, MOVE_UP);
380                 LABKEY.Ajax.request({
381                     url: LABKEY.ActionURL.buildURL('project', 'moveWebPartAsync.api', config.containerPath),
382                     method : 'GET',
383                     success: LABKEY.Utils.getOnSuccess(callConfig),
384                     failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(callConfig), callConfig.scope, true),
385                     params: callConfig.params
386                 });
387             },
388 
389 
390             /**
391              * Move an existing web part down within its portal page, identifying the web part by the unique ID of the containing span.
392              * This span will have name 'webpart'.
393              * @param config An object which contains the following configuration properties.
394              * @param {String} [config.pageId] Reserved for a time when multiple portal pages are allowed per container.
395              * If not provided, main portal page for the container will be modified.
396              * @param {String} [config.containerPath] Specifies the container in which the web part modification should be performed.
397              * If not provided, the method will operate on the current container.
398              * @param {String} config.webPartId The unique integer ID of the web part to be moved.
399              * @param {Boolean} [config.updateDOM] Indicates whether the current page's DOM should be updated to reflect changes to web part layout.
400              * Defaults to false.
401              * @param {Function} config.success
402              Function called when the this function completes successfully.
403              This function will be called with the following arguments:
404              <ul>
405              <li>webparts: an object with one property for each page region, generally 'body' and 'right'.  The value
406              of each property is an ordered array of objects indicating the current web part configuration
407              on the page.  Each object has the following properties:
408              <ul>
409              <li>name: the name of the web part</li>
410              <li>index: the index of the web part</li>
411              <li>webPartId: the unique integer ID of this web part.</li>
412              </ul>
413              </li>
414              <li>responseObj: the XMLHttpResponseObject instance used to make the AJAX request</li>
415              <li>options: the options used for the AJAX request</li>
416              </ul>
417              * @param {Function} [config.failure] Function called when execution fails.
418              *       This function will be called with the following arguments:
419              <ul>
420              <li>exceptionObj: A JavaScript Error object caught by the calling code.</li>
421              <li>responseObj: The XMLHttpRequest object containing the response data.</li>
422              <li>options: the options used for the AJAX request</li>
423              </ul>
424              */
425             moveWebPartDown : function(config)
426             {
427                 var callConfig = mapIndexConfigParameters(config, MOVE_ACTION, MOVE_DOWN);
428                 LABKEY.Ajax.request({
429                     url: LABKEY.ActionURL.buildURL('project', 'moveWebPartAsync.api', config.containerPath),
430                     method : 'GET',
431                     success: LABKEY.Utils.getOnSuccess(callConfig),
432                     failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(callConfig), callConfig.scope, true),
433                     params: callConfig.params
434                 });
435             },
436             /**
437              * Remove an existing web part within its portal page.
438              * @param config An object which contains the following configuration properties.
439              * @param {String} [config.pageId] Reserved for a time when multiple portal pages are allowed per container.
440              * If not provided, main portal page for the container will be modified.
441              * @param {String} [config.containerPath] Specifies the container in which the web part modification should be performed.
442              * If not provided, the method will operate on the current container.
443              * @param {String} config.webPartId The unique integer ID of the web part to be moved.
444              * @param {Boolean} [config.updateDOM] Indicates whether the current page's DOM should be updated to reflect changes to web part layout.
445              * Defaults to false.
446              * @param {Function} config.success
447              Function called when the this function completes successfully.
448              This function will be called with the following arguments:
449              <ul>
450              <li>webparts: an object with one property for each page region, generally 'body' and 'right'.  The value
451              of each property is an ordered array of objects indicating the current web part configuration
452              on the page.  Each object has the following properties:
453              <ul>
454              <li>name: the name of the web part</li>
455              <li>index: the index of the web part</li>
456              <li>webPartId: the unique integer ID of this web part.</li>
457              </ul>
458              </li>
459              <li>responseObj: the XMLHttpResponseObject instance used to make the AJAX request</li>
460              <li>options: the options used for the AJAX request</li>
461              </ul>
462              * @param {Function} [config.failure] Function called when execution fails.
463              *       This function will be called with the following arguments:
464              <ul>
465              <li>exceptionObj: A JavaScript Error object caught by the calling code.</li>
466              <li>responseObj: The XMLHttpRequest object containing the response data.</li>
467              <li>options: the options used for the AJAX request</li>
468              </ul>
469              */
470             removeWebPart : function(config)
471             {
472                 var callConfig = mapIndexConfigParameters(config, REMOVE_ACTION, undefined);
473                 LABKEY.Ajax.request({
474                     url: LABKEY.ActionURL.buildURL('project', 'deleteWebPartAsync.api', config.containerPath),
475                     method : 'GET',
476                     success: LABKEY.Utils.getOnSuccess(callConfig),
477                     failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(callConfig), callConfig.scope, true),
478                     params: callConfig.params
479                 });
480             },
481 
482             toggleWebPartFrame : function(config)
483             {
484                 var callConfig = mapIndexConfigParameters(config, TOGGLE_FRAME_ACTION, undefined);
485                 LABKEY.Ajax.request({
486                     url: LABKEY.ActionURL.buildURL('project', 'toggleWebPartFrameAsync.api', config.containerPath),
487                     method : 'GET',
488                     success: LABKEY.Utils.getOnSuccess(callConfig),
489                     failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(callConfig), callConfig.scope, true),
490                     params: callConfig.params
491                 });
492             },
493 
494             /**
495              * Move a folder tab to the left.
496              * @param pageId the pageId of the tab.
497              * @param domId the id of the anchor tag of the tab.
498              */
499             moveTabLeft : function(pageId, domId)
500             {
501                 LABKEY.Ajax.request({
502                     url: LABKEY.ActionURL.buildURL('admin', 'moveTab.api', LABKEY.container.path),
503                     method: 'GET',
504                     params: {
505                         pageId: pageId,
506                         direction: MOVE_LEFT
507                     },
508                     success: LABKEY.Utils.getCallbackWrapper(function(response, options) {
509                         if(domId && response.pageIdToSwap && response.pageIdToSwap !== response.pageId) {
510                             var tabAnchor = $('#' + domId)[0];
511                             if (tabAnchor) {
512                                 $(tabAnchor.parentElement).insertBefore(tabAnchor.parentNode.previousElementSibling);
513                             }
514                         }
515                     }, this, false),
516                     failure: function(response){
517                         // Currently no-op when failure occurs.
518                     }
519                 });
520             },
521 
522             /**
523              * Move a folder tab to the right.
524              * @param pageId the pageId of the tab.
525              * @param domId the id of the anchor tag of the tab.
526              */
527             moveTabRight : function(pageId, domId)
528             {
529                 LABKEY.Ajax.request({
530                     url: LABKEY.ActionURL.buildURL('admin', 'moveTab.api', LABKEY.container.path),
531                     method: 'GET',
532                     params: {
533                         pageId: pageId,
534                         direction: MOVE_RIGHT
535                     },
536                     success: LABKEY.Utils.getCallbackWrapper(function(response, options) {
537                         if(domId && response.pageIdToSwap && response.pageIdToSwap !== response.pageId) {
538                             var tabAnchor = $('#' + domId)[0];
539                             if (tabAnchor) {
540                                 $(tabAnchor.parentElement).insertAfter(tabAnchor.parentNode.nextElementSibling);
541                             }
542                         }
543                     }, this, false),
544                     failure: function(response, options){
545                         // Currently no-op when failure occurs.
546                     }
547                 });
548             },
549 
550             /**
551              * Allows an administrator to add a new portal page tab.
552              */
553             addTab : function()
554             {
555                 var addTabHandler = function(name, editWindow)
556                 {
557                     LABKEY.Ajax.request({
558                         url: LABKEY.ActionURL.buildURL('admin', 'addTab.api'),
559                         method: 'POST',
560                         jsonData: {tabName: name},
561                         success: function(response)
562                         {
563                             var jsonResp = LABKEY.Utils.decode(response.responseText);
564                             if (jsonResp && jsonResp.success)
565                             {
566                                 if (jsonResp.url)
567                                     window.location = jsonResp.url;
568                             }
569                         },
570                         failure: function(response)
571                         {
572                             var jsonResp = LABKEY.Utils.decode(response.responseText);
573                             var errorMsg;
574                             if (jsonResp && jsonResp.errors)
575                                 errorMsg = jsonResp.errors[0].message;
576                             else
577                                 errorMsg = 'An unknown error occurred. Please contact your administrator.';
578                             Ext4.Msg.alert(errorMsg);
579                         }
580                     });
581                 };
582 
583                 if (LABKEY.pageAdminMode) {
584                     showEditTabWindow("Add Tab", addTabHandler, null);
585                 }
586             },
587 
588             /**
589              * Shows a hidden tab.
590              * @param pageId the pageId of the tab.
591              */
592             showTab : function(pageId)
593             {
594                 LABKEY.Ajax.request({
595                     url: LABKEY.ActionURL.buildURL('admin', 'showTab.api'),
596                     method: 'POST',
597                     jsonData: {tabPageId: pageId},
598                     success: function(response)
599                     {
600                         var jsonResp = LABKEY.Utils.decode(response.responseText);
601                         if (jsonResp && jsonResp.success)
602                         {
603                             if (jsonResp.url)
604                                 window.location = jsonResp.url;
605                         }
606                     },
607                     failure: function(response)
608                     {
609                         var jsonResp = LABKEY.Utils.decode(response.responseText);
610                         if (jsonResp && jsonResp.errors)
611                         {
612                             alert(jsonResp.errors[0].message);
613                         }
614                     }
615                 });
616             },
617 
618             /**
619              * Allows an administrator to rename a tab.
620              * @param pageId the pageId of the tab.
621              * @param domId the id of the anchor tag of the tab.
622              * @param currentLabel the current label of the tab.
623              */
624             renameTab : function(pageId, domId, currentLabel)
625             {
626                 var tabLinkEl = document.getElementById(domId);
627 
628                 if (tabLinkEl)
629                 {
630                     var renameHandler = function(name, editWindow)
631                     {
632                         LABKEY.Ajax.request({
633                             url: LABKEY.ActionURL.buildURL('admin', 'renameTab.api'),
634                             method: 'POST',
635                             jsonData: {
636                                 tabPageId: pageId,
637                                 tabName: name
638                             },
639                             success: function(response)
640                             {
641                                 var jsonResp = LABKEY.Utils.decode(response.responseText);
642                                 if (jsonResp.success)
643                                     tabLinkEl.textContent = name;
644                                 editWindow.close();
645                             },
646                             failure: function(response)
647                             {
648                                 var jsonResp = LABKEY.Utils.decode(response.responseText);
649                                 var errorMsg;
650                                 if (jsonResp.errors)
651                                     errorMsg = jsonResp.errors[0].message;
652                                 else
653                                     errorMsg = 'An unknown error occured. Please contact your administrator.';
654                                 Ext4.Msg.alert('Oops', errorMsg);
655                             }
656                         });
657                     };
658 
659                     if (LABKEY.pageAdminMode) {
660                         showEditTabWindow("Rename Tab", renameHandler, currentLabel);
661                     }
662                 }
663             },
664 
665             _showPermissions : showPermissions
666         };
667     };
668 
669 })(jQuery);
670 
671