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