1"use strict"; 2/* All shared functionality to go in libtoaster object. 3 * This object really just helps readability since we can then have 4 * a traceable namespace. 5 */ 6var libtoaster = (function () { 7 // prevent conflicts with Bootstrap 2's typeahead (required during 8 // transition from v2 to v3) 9 var typeahead = jQuery.fn.typeahead.noConflict(); 10 jQuery.fn._typeahead = typeahead; 11 12 /* Make a typeahead from an input element 13 * 14 * _makeTypeahead parameters 15 * jQElement: input element as selected by $('selector') 16 * xhrUrl: the url to get the JSON from; this URL should return JSON in the 17 * format: 18 * { "results": [ { "name": "test", "detail" : "a test thing" }, ... ] } 19 * xhrParams: the data/parameters to pass to the getJSON url e.g. 20 * { 'type' : 'projects' }; the text typed will be passed as 'search'. 21 * selectedCB: function to call once an item has been selected; has 22 * signature selectedCB(item), where item is an item in the format shown 23 * in the JSON list above, i.e. 24 * { "name": "name", "detail": "detail" }. 25 */ 26 function _makeTypeahead(jQElement, xhrUrl, xhrParams, selectedCB) { 27 if (!xhrUrl || xhrUrl.length === 0) { 28 throw("No url supplied for typeahead"); 29 } 30 31 var xhrReq; 32 33 jQElement._typeahead( 34 { 35 highlight: true, 36 classNames: { 37 open: "dropdown-menu", 38 cursor: "active" 39 } 40 }, 41 { 42 source: function (query, syncResults, asyncResults) { 43 xhrParams.search = query; 44 45 // if we have a request in progress, cancel it and start another 46 if (xhrReq) { 47 xhrReq.abort(); 48 } 49 50 xhrReq = $.getJSON(xhrUrl, xhrParams, function (data) { 51 if (data.error !== "ok") { 52 console.error("Error getting data from server: " + data.error); 53 return; 54 } 55 56 xhrReq = null; 57 58 asyncResults(data.results); 59 }); 60 }, 61 62 // how the selected item is shown in the input 63 display: function (item) { 64 return item.name; 65 }, 66 67 templates: { 68 // how the item is displayed in the dropdown 69 suggestion: function (item) { 70 var elt = document.createElement("div"); 71 elt.innerHTML = item.name + " " + item.detail; 72 return elt; 73 } 74 } 75 } 76 ); 77 78 // when an item is selected using the typeahead, invoke the callback 79 jQElement.on("typeahead:select", function (event, item) { 80 selectedCB(item); 81 }); 82 } 83 84 /* startABuild: 85 * url: xhr_buildrequest or null for current project 86 * targets: an array or space separated list of targets to build 87 * onsuccess: callback for successful execution 88 * onfail: callback for failed execution 89 */ 90 function _startABuild (url, targets, onsuccess, onfail) { 91 92 if (!url) 93 url = libtoaster.ctx.xhrBuildRequestUrl; 94 95 /* Flatten the array of targets into a space spearated list */ 96 if (targets instanceof Array){ 97 targets = targets.reduce(function(prevV, nextV){ 98 return prev + ' ' + next; 99 }); 100 } 101 102 $.ajax( { 103 type: "POST", 104 url: url, 105 data: { 'targets' : targets }, 106 headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, 107 success: function (_data) { 108 if (_data.error !== "ok") { 109 console.warn(_data.error); 110 } else { 111 if (onsuccess !== undefined) onsuccess(_data); 112 } 113 }, 114 error: function (_data) { 115 console.warn("Call failed"); 116 console.warn(_data); 117 if (onfail) onfail(data); 118 } }); 119 } 120 121 /* cancelABuild: 122 * url: xhr_buildrequest url or null for current project 123 * buildRequestIds: space separated list of build request ids 124 * onsuccess: callback for successful execution 125 * onfail: callback for failed execution 126 */ 127 function _cancelABuild(url, buildRequestIds, onsuccess, onfail){ 128 if (!url) 129 url = libtoaster.ctx.xhrBuildRequestUrl; 130 131 $.ajax( { 132 type: "POST", 133 url: url, 134 data: { 'buildCancel': buildRequestIds }, 135 headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, 136 success: function (_data) { 137 if (_data.error !== "ok") { 138 console.warn(_data.error); 139 } else { 140 if (onsuccess) onsuccess(_data); 141 } 142 }, 143 error: function (_data) { 144 console.warn("Call failed"); 145 console.warn(_data); 146 if (onfail) onfail(_data); 147 } 148 }); 149 } 150 151 function _getMostRecentBuilds(url, onsuccess, onfail) { 152 $.ajax({ 153 url: url, 154 type: 'GET', 155 data : {format: 'json'}, 156 headers: {'X-CSRFToken': $.cookie('csrftoken')}, 157 success: function (data) { 158 onsuccess ? onsuccess(data) : console.log(data); 159 }, 160 error: function (data) { 161 onfail ? onfail(data) : console.error(data); 162 } 163 }); 164 } 165 166 /* Get a project's configuration info */ 167 function _getProjectInfo(url, onsuccess, onfail){ 168 $.ajax({ 169 type: "GET", 170 url: url, 171 headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, 172 success: function (_data) { 173 if (_data.error !== "ok") { 174 console.warn(_data.error); 175 } else { 176 if (onsuccess !== undefined) onsuccess(_data); 177 } 178 }, 179 error: function (_data) { 180 console.warn(_data); 181 if (onfail) onfail(_data); 182 } 183 }); 184 } 185 186 /* Properties for data can be: 187 * layerDel (csv) 188 * layerAdd (csv) 189 * projectName 190 * projectVersion 191 * machineName 192 */ 193 function _editCurrentProject(data, onSuccess, onFail){ 194 $.ajax({ 195 type: "POST", 196 url: libtoaster.ctx.xhrProjectUrl, 197 data: data, 198 headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, 199 success: function (data) { 200 if (data.error != "ok") { 201 console.log(data.error); 202 if (onFail !== undefined) 203 onFail(data); 204 } else { 205 if (onSuccess !== undefined) 206 onSuccess(data); 207 } 208 }, 209 error: function (data) { 210 console.log("Call failed"); 211 console.log(data); 212 } 213 }); 214 } 215 216 function _getLayerDepsForProject(url, onSuccess, onFail){ 217 /* Check for dependencies not in the current project */ 218 $.getJSON(url, 219 { format: 'json' }, 220 function(data) { 221 if (data.error != "ok") { 222 console.log(data.error); 223 if (onFail !== undefined) 224 onFail(data); 225 } else { 226 var deps = {}; 227 /* Filter out layer dep ids which are in the 228 * project already. 229 */ 230 deps.list = data.layerdeps.list.filter(function(layerObj){ 231 return (data.projectlayers.lastIndexOf(layerObj.id) < 0); 232 }); 233 234 onSuccess(deps); 235 } 236 }, function() { 237 console.log("E: Failed to make request"); 238 }); 239 } 240 241 /* parses the query string of the current window.location to an object */ 242 function _parseUrlParams() { 243 var string = window.location.search; 244 string = string.substr(1); 245 var stringArray = string.split ("&"); 246 var obj = {}; 247 248 for (var i in stringArray) { 249 var keyVal = stringArray[i].split ("="); 250 obj[keyVal[0]] = keyVal[1]; 251 } 252 253 return obj; 254 } 255 256 /* takes a flat object and outputs it as a query string 257 * e.g. the output of dumpsUrlParams 258 */ 259 function _dumpsUrlParams(obj) { 260 var str = "?"; 261 262 for (var key in obj){ 263 if (!obj[key]) 264 continue; 265 266 str += key+ "="+obj[key].toString(); 267 str += "&"; 268 } 269 270 /* Maintain the current hash */ 271 str += window.location.hash; 272 273 return str; 274 } 275 276 function _addRmLayer(layerObj, add, doneCb){ 277 if (layerObj.xhrLayerUrl === undefined){ 278 alert("ERROR: missing xhrLayerUrl object. Please file a bug."); 279 return; 280 } 281 282 if (add === true) { 283 /* If adding get the deps for this layer */ 284 libtoaster.getLayerDepsForProject(layerObj.xhrLayerUrl, 285 function (layers) { 286 287 /* got result for dependencies */ 288 if (layers.list.length === 0){ 289 var editData = { layerAdd : layerObj.id }; 290 libtoaster.editCurrentProject(editData, function() { 291 doneCb([]); 292 }); 293 return; 294 } else { 295 try { 296 showLayerDepsModal(layerObj, layers.list, null, null, true, doneCb); 297 } catch (e) { 298 $.getScript(libtoaster.ctx.jsUrl + "layerDepsModal.js", function(){ 299 showLayerDepsModal(layerObj, layers.list, null, null, true, doneCb); 300 }, function(){ 301 console.warn("Failed to load layerDepsModal"); 302 }); 303 } 304 } 305 }, null); 306 } else if (add === false) { 307 var editData = { layerDel : layerObj.id }; 308 309 libtoaster.editCurrentProject(editData, function () { 310 doneCb([]); 311 }, function () { 312 console.warn ("Removing layer from project failed"); 313 doneCb(null); 314 }); 315 } 316 } 317 318 function _makeLayerAddRmAlertMsg(layer, layerDepsList, add) { 319 var alertMsg; 320 321 if (layerDepsList.length > 0 && add === true) { 322 alertMsg = $("<span>You have added <strong>"+(layerDepsList.length+1)+"</strong> layers to your project: <a class=\"alert-link\" id=\"layer-affected-name\"></a> and its dependencies </span>"); 323 324 /* Build the layer deps list */ 325 layerDepsList.map(function(layer, i){ 326 var link = $("<a class=\"alert-link\"></a>"); 327 328 link.attr("href", layer.layerdetailurl); 329 link.text(layer.name); 330 link.tooltip({title: layer.tooltip}); 331 332 if (i !== 0) 333 alertMsg.append(", "); 334 335 alertMsg.append(link); 336 }); 337 } else if (layerDepsList.length === 0 && add === true) { 338 alertMsg = $("<span>You have added <strong>1</strong> layer to your project: <a class=\"alert-link\" id=\"layer-affected-name\"></a></span></span>"); 339 } else if (add === false) { 340 alertMsg = $("<span>You have removed <strong>1</strong> layer from your project: <a class=\"alert-link\" id=\"layer-affected-name\"></a></span>"); 341 } 342 343 alertMsg.children("#layer-affected-name").text(layer.name); 344 alertMsg.children("#layer-affected-name").attr("href", layer.layerdetailurl); 345 346 return alertMsg.html(); 347 } 348 349 function _showChangeNotification(message){ 350 $(".alert-dismissible").fadeOut().promise().done(function(){ 351 var alertMsg = $("#change-notification-msg"); 352 353 alertMsg.html(message); 354 $("#change-notification, #change-notification *").fadeIn(); 355 }); 356 } 357 358 function _createCustomRecipe(name, baseRecipeId, doneCb){ 359 var data = { 360 'name' : name, 361 'project' : libtoaster.ctx.projectId, 362 'base' : baseRecipeId, 363 }; 364 365 $.ajax({ 366 type: "POST", 367 url: libtoaster.ctx.xhrCustomRecipeUrl, 368 data: data, 369 headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, 370 success: function (ret) { 371 if (doneCb){ 372 doneCb(ret); 373 } else if (ret.error !== "ok") { 374 console.warn(ret.error); 375 } 376 }, 377 error: function (ret) { 378 console.warn("Call failed"); 379 console.warn(ret); 380 } 381 }); 382 } 383 384 /* Validate project names. Use unique project names 385 386 All arguments accepted by this function are JQeury objects. 387 388 For example if the HTML element has "hint-error-project-name", then 389 it is passed to this function as $("#hint-error-project-name"). 390 391 Arg1 - projectName : This is a string object. In the HTML, project name will be entered here. 392 Arg2 - hintEerror : This is a jquery object which will accept span which throws error for 393 duplicate project 394 Arg3 - ctrlGrpValidateProjectName : This object holds the div with class "control-group" 395 Arg4 - enableOrDisableBtn : This object will help the API to enable or disable the form. 396 For example in the new project the create project button will be hidden if the 397 duplicate project exist. Similarly in the projecttopbar the save button will be 398 disabled if the project name already exist. 399 400 Return - This function doesn't return anything. It sets/unsets the behavior of the elements. 401 */ 402 403 function _makeProjectNameValidation(projectName, hintError, 404 ctrlGrpValidateProjectName, enableOrDisableBtn ) { 405 406 function checkProjectName(projectName){ 407 $.ajax({ 408 type: "GET", 409 url: libtoaster.ctx.projectsTypeAheadUrl, 410 data: { 'search' : projectName }, 411 headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, 412 success: function(data){ 413 if (data.results.length > 0 && 414 data.results[0].name === projectName) { 415 // This project name exists hence show the error and disable 416 // the save button 417 ctrlGrpValidateProjectName.addClass('has-error'); 418 hintError.show(); 419 enableOrDisableBtn.attr('disabled', 'disabled'); 420 } else { 421 ctrlGrpValidateProjectName.removeClass('has-error'); 422 hintError.hide(); 423 enableOrDisableBtn.removeAttr('disabled'); 424 } 425 }, 426 error: function (data) { 427 console.log(data); 428 }, 429 }); 430 } 431 432 /* The moment user types project name remove the error */ 433 projectName.on("input", function() { 434 var projectName = $(this).val(); 435 checkProjectName(projectName) 436 }); 437 438 /* Validate new project name */ 439 projectName.on("blur", function(){ 440 var projectName = $(this).val(); 441 checkProjectName(projectName) 442 }); 443 } 444 445 // if true, the loading spinner for Ajax requests will be displayed 446 // if requests take more than 1200ms 447 var ajaxLoadingTimerEnabled = true; 448 449 // turn on the page-level loading spinner for Ajax requests 450 function _enableAjaxLoadingTimer() { 451 ajaxLoadingTimerEnabled = true; 452 } 453 454 // turn off the page-level loading spinner for Ajax requests 455 function _disableAjaxLoadingTimer() { 456 ajaxLoadingTimerEnabled = false; 457 } 458 459 /* Utility function to set a notification for the next page load */ 460 function _setNotification(name, message){ 461 var data = { 462 name: name, 463 message: message 464 }; 465 466 $.cookie('toaster-notification', JSON.stringify(data), { path: '/'}); 467 } 468 469 /* _updateProject: 470 * url: xhrProjectUpdateUrl or null for current project 471 * onsuccess: callback for successful execution 472 * onfail: callback for failed execution 473 */ 474 function _updateProject (url, targets, default_image, onsuccess, onfail) { 475 476 if (!url) 477 url = libtoaster.ctx.xhrProjectUpdateUrl; 478 479 /* Flatten the array of targets into a space spearated list */ 480 if (targets instanceof Array){ 481 targets = targets.reduce(function(prevV, nextV){ 482 return prev + ' ' + next; 483 }); 484 } 485 486 $.ajax( { 487 type: "POST", 488 url: url, 489 data: { 'do_update' : 'True' , 'targets' : targets , 'default_image' : default_image , }, 490 headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, 491 success: function (_data) { 492 if (_data.error !== "ok") { 493 console.warn(_data.error); 494 } else { 495 if (onsuccess !== undefined) onsuccess(_data); 496 } 497 }, 498 error: function (_data) { 499 console.warn("Call failed"); 500 console.warn(_data); 501 if (onfail) onfail(data); 502 } }); 503 } 504 505 /* _cancelProject: 506 * url: xhrProjectUpdateUrl or null for current project 507 * onsuccess: callback for successful execution 508 * onfail: callback for failed execution 509 */ 510 function _cancelProject (url, onsuccess, onfail) { 511 512 if (!url) 513 url = libtoaster.ctx.xhrProjectCancelUrl; 514 515 $.ajax( { 516 type: "POST", 517 url: url, 518 data: { 'do_cancel' : 'True' }, 519 headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, 520 success: function (_data) { 521 if (_data.error !== "ok") { 522 console.warn(_data.error); 523 } else { 524 if (onsuccess !== undefined) onsuccess(_data); 525 } 526 }, 527 error: function (_data) { 528 console.warn("Call failed"); 529 console.warn(_data); 530 if (onfail) onfail(data); 531 } }); 532 } 533 534 /* _setDefaultImage: 535 * url: xhrSetDefaultImageUrl or null for current project 536 * targets: an array or space separated list of targets to set as default 537 * onsuccess: callback for successful execution 538 * onfail: callback for failed execution 539 */ 540 function _setDefaultImage (url, targets, onsuccess, onfail) { 541 542 if (!url) 543 url = libtoaster.ctx.xhrSetDefaultImageUrl; 544 545 /* Flatten the array of targets into a space spearated list */ 546 if (targets instanceof Array){ 547 targets = targets.reduce(function(prevV, nextV){ 548 return prev + ' ' + next; 549 }); 550 } 551 552 $.ajax( { 553 type: "POST", 554 url: url, 555 data: { 'targets' : targets }, 556 headers: { 'X-CSRFToken' : $.cookie('csrftoken')}, 557 success: function (_data) { 558 if (_data.error !== "ok") { 559 console.warn(_data.error); 560 } else { 561 if (onsuccess !== undefined) onsuccess(_data); 562 } 563 }, 564 error: function (_data) { 565 console.warn("Call failed"); 566 console.warn(_data); 567 if (onfail) onfail(data); 568 } }); 569 } 570 571 return { 572 enableAjaxLoadingTimer: _enableAjaxLoadingTimer, 573 disableAjaxLoadingTimer: _disableAjaxLoadingTimer, 574 reload_params : reload_params, 575 startABuild : _startABuild, 576 cancelABuild : _cancelABuild, 577 getMostRecentBuilds: _getMostRecentBuilds, 578 makeTypeahead : _makeTypeahead, 579 getProjectInfo: _getProjectInfo, 580 getLayerDepsForProject : _getLayerDepsForProject, 581 editCurrentProject : _editCurrentProject, 582 debug: false, 583 parseUrlParams : _parseUrlParams, 584 dumpsUrlParams : _dumpsUrlParams, 585 addRmLayer : _addRmLayer, 586 makeLayerAddRmAlertMsg : _makeLayerAddRmAlertMsg, 587 showChangeNotification : _showChangeNotification, 588 createCustomRecipe: _createCustomRecipe, 589 makeProjectNameValidation: _makeProjectNameValidation, 590 setNotification: _setNotification, 591 updateProject : _updateProject, 592 cancelProject : _cancelProject, 593 setDefaultImage : _setDefaultImage, 594 }; 595})(); 596 597/* keep this in the global scope for compatability */ 598function reload_params(params) { 599 var uri = window.location.href; 600 var splitlist = uri.split("?"); 601 var url = splitlist[0]; 602 var parameters = splitlist[1]; 603 // deserialize the call parameters 604 var cparams = []; 605 if(parameters) 606 cparams = parameters.split("&"); 607 608 var nparams = {}; 609 for (var i = 0; i < cparams.length; i++) { 610 var temp = cparams[i].split("="); 611 nparams[temp[0]] = temp[1]; 612 } 613 // update parameter values 614 for (i in params) { 615 nparams[encodeURIComponent(i)] = encodeURIComponent(params[i]); 616 } 617 // serialize the structure 618 var callparams = []; 619 for (i in nparams) { 620 callparams.push(i+"="+nparams[i]); 621 } 622 window.location.href = url+"?"+callparams.join('&'); 623} 624 625/* Things that happen for all pages */ 626$(document).ready(function() { 627 628 (function showNotificationRequest(){ 629 var cookie = $.cookie('toaster-notification'); 630 631 if (!cookie) 632 return; 633 634 var notificationData = JSON.parse(cookie); 635 636 libtoaster.showChangeNotification(notificationData.message); 637 638 $.removeCookie('toaster-notification', { path: "/"}); 639 })(); 640 641 642 643 var ajaxLoadingTimer; 644 645 /* If we don't have a console object which might be the case in some 646 * browsers, no-op it to avoid undefined errors. 647 */ 648 if (!window.console) { 649 window.console = {}; 650 window.console.warn = function() {}; 651 window.console.error = function() {}; 652 } 653 654 /* 655 * highlight plugin. 656 */ 657 hljs.initHighlightingOnLoad(); 658 659 // Prevent invalid links from jumping page scroll 660 $('a[href="#"]').click(function() { 661 return false; 662 }); 663 664 665 /* START TODO Delete this section now redundant */ 666 /* Belen's additions */ 667 668 // turn Edit columns dropdown into a multiselect menu 669 $('.dropdown-menu input, .dropdown-menu label').click(function(e) { 670 e.stopPropagation(); 671 }); 672 673 // enable popovers in any table cells that contain an anchor with the 674 // .btn class applied, and make sure popovers work on click, are mutually 675 // exclusive and they close when your click outside their area 676 677 $('html').click(function(){ 678 $('td > a.btn').popover('hide'); 679 }); 680 681 $('td > a.btn').popover({ 682 html:true, 683 placement:'left', 684 container:'body', 685 trigger:'manual' 686 }).click(function(e){ 687 $('td > a.btn').not(this).popover('hide'); 688 // ideally we would use 'toggle' here 689 // but it seems buggy in our Bootstrap version 690 $(this).popover('show'); 691 e.stopPropagation(); 692 }); 693 694 // enable tooltips for applied filters 695 $('th a.btn-primary').tooltip({container:'body', html:true, placement:'bottom', delay:{hide:1500}}); 696 697 // hide applied filter tooltip when you click on the filter button 698 $('th a.btn-primary').click(function () { 699 $('.tooltip').hide(); 700 }); 701 702 /* Initialise bootstrap tooltips */ 703 $(".get-help, [data-toggle=tooltip]").tooltip({ 704 container : 'body', 705 html : true, 706 delay: { show : 300 } 707 }); 708 709 // show help bubble on hover inside tables 710 $("table").on("mouseover", "th, td", function () { 711 $(this).find(".hover-help").css("visibility","visible"); 712 }); 713 714 $("table").on("mouseleave", "th, td", function () { 715 $(this).find(".hover-help").css("visibility","hidden"); 716 }); 717 718 /* END TODO Delete this section now redundant */ 719 720 // show task type and outcome in task details pages 721 $(".task-info").tooltip({ container: 'body', html: true, delay: {show: 200}, placement: 'right' }); 722 723 // initialise the tooltips for the edit icons 724 $(".glyphicon-edit").tooltip({ container: 'body', html: true, delay: {show: 400}, title: "Change" }); 725 726 // initialise the tooltips for the download icons 727 $(".icon-download-alt").tooltip({ container: 'body', html: true, delay: { show: 200 } }); 728 729 // initialise popover for debug information 730 $(".glyphicon-info-sign").popover( { placement: 'bottom', html: true, container: 'body' }); 731 732 // linking directly to tabs 733 $(function(){ 734 var hash = window.location.hash; 735 $('ul.nav a[href="' + hash + '"]').tab('show'); 736 737 $('.nav-tabs a').click(function () { 738 $(this).tab('show'); 739 $('body').scrollTop(); 740 }); 741 }); 742 743 // toggle for long content (variables, python stack trace, etc) 744 $('.full, .full-hide').hide(); 745 $('.full-show').click(function(){ 746 $('.full').slideDown(function(){ 747 $('.full-hide').show(); 748 }); 749 $(this).hide(); 750 }); 751 $('.full-hide').click(function(){ 752 $(this).hide(); 753 $('.full').slideUp(function(){ 754 $('.full-show').show(); 755 }); 756 }); 757 758 //toggle the errors and warnings sections 759 $('.show-errors').click(function() { 760 $('#collapse-errors').addClass('in'); 761 }); 762 $('.toggle-errors').click(function() { 763 $('#collapse-errors').toggleClass('in'); 764 }); 765 $('.show-warnings').click(function() { 766 $('#collapse-warnings').addClass('in'); 767 }); 768 $('.toggle-warnings').click(function() { 769 $('#collapse-warnings').toggleClass('in'); 770 }); 771 $('.show-exceptions').click(function() { 772 $('#collapse-exceptions').addClass('in'); 773 }); 774 $('.toggle-exceptions').click(function() { 775 $('#collapse-exceptions').toggleClass('in'); 776 }); 777 778 779 $("#hide-alert").click(function(){ 780 $(this).parent().fadeOut(); 781 }); 782 783 //show warnings section when requested from the previous page 784 if (location.href.search('#warnings') > -1) { 785 $('#collapse-warnings').addClass('in'); 786 } 787 788 /* Show the loading notification if nothing has happend after 1.5 789 * seconds 790 */ 791 $(document).bind("ajaxStart", function(){ 792 if (ajaxLoadingTimer) 793 window.clearTimeout(ajaxLoadingTimer); 794 795 ajaxLoadingTimer = window.setTimeout(function() { 796 if (libtoaster.ajaxLoadingTimerEnabled) { 797 $("#loading-notification").fadeIn(); 798 } 799 }, 1200); 800 }); 801 802 $(document).bind("ajaxStop", function(){ 803 if (ajaxLoadingTimer) 804 window.clearTimeout(ajaxLoadingTimer); 805 806 $("#loading-notification").fadeOut(); 807 }); 808 809 $(document).ajaxError(function(event, jqxhr, settings, errMsg){ 810 if (errMsg === 'abort') 811 return; 812 813 console.warn("Problem with xhr call"); 814 console.warn(errMsg); 815 console.warn(jqxhr.responseText); 816 }); 817 818 function check_for_duplicate_ids () { 819 /* warn about duplicate element ids */ 820 var ids = {}; 821 $("[id]").each(function() { 822 if (this.id && ids[this.id]) { 823 console.warn('Duplicate element id #'+this.id); 824 } 825 ids[this.id] = true; 826 }); 827 } 828 829 /* Make sure we don't have a notification overlay a modal */ 830 $(".modal").on('show.bs.modal', function(){ 831 $(".alert-dismissible").fadeOut(); 832 }); 833 834 if (libtoaster.debug) { 835 check_for_duplicate_ids(); 836 } else { 837 /* Debug is false so supress warnings by overriding the functions */ 838 window.console.warn = function () {}; 839 window.console.error = function () {}; 840 } 841}); 842