1/* Enumerator for common keys that we may want to perform actions against. 2** NOTE: The keycodes here are not valid for the keypress function. Use keydown or keyup instead. */ 3 4 /* Use this function to detect & add new keys to the enumerator 5 $(document.body).keydown(function(event) { 6 var keyCode = event.which ? event.which : event.keyCode; 7 // console.log(event.originalEvent); 8 $(document.body).append("<p>" + String.fromCharCode(keyCode) + ":" + keyCode + "</p>"); 9 }); 10 */ 11 12 var KEYENUM = { 13 // Alphabet 14 A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, 15 N: 78, O: 79, P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90, 16 17 // Numbers 18 NUMROW0: 48, NUMROW1: 49, NUMROW2: 50, NUMROW3: 51, NUMROW4: 52, NUMROW5: 53, NUMROW6: 54, NUMROW7: 55, NUMROW8: 56, NUMROW9: 57, 19 NUMPAD0: 96, NUMPAD1: 97, NUMPAD2: 98, NUMPAD3: 99, NUMPAD4: 100, NUMPAD5: 101, NUMPAD6: 102, NUMPAD7: 103, NUMPAD8: 104, NUMPAD9: 105, 20 21 // Arrows 22 LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40, 23 24 // Special Characters 25 BSLASH: 220, COMMA: 188, EQUALSIGN: 187, FSLASH: 191, GRAVE: 192, HYPHEN: 189, LBRACKET: 219, MINUS: 109, PERIOD: 190, PLUS: 107, RBRACKET: 221, SEMICOLON: 186, TICK: 222, FORWARDSLASH: 191, NUMPADFORWARDSLASH: 111, 26 27 // Modifiers 28 ALT: 18, CONTROL: 17, SHIFT: 16, 29 30 // Operators 31 BACKSPACE: 8, DEL: 46, END: 35, ESC: 27, HOME: 36, INSERT: 45, PAGEDOWN: 34, PAGEUP: 33, RETURN: 13, TAB: 9 32 }; 33 34/* Cookie funcions from: http://www.w3schools.com/js/js_cookies.asp 35 Set these up before the jQuery binding so we can ensure the visbility logic below has them available to run. */ 36 function setCookie(cname, cvalue, exdays, path) { 37 var d = new Date(); 38 d.setTime(d.getTime() + (exdays*24*60*60*1000)); 39 document.cookie = cname + "=" + cvalue + "; expires=" + d.toUTCString() + "; path=" + (path || "/"); 40 } 41 42 function getCookie(cname) { 43 var name = cname + "="; 44 var ca = document.cookie.split(';'); 45 for(var i=0; i<ca.length; i++) { 46 var c = ca[i]; 47 while (c.charAt(0)==' ') c = c.substring(1); 48 if (c.indexOf(name) != -1) return c.substring(name.length,c.length); 49 } 50 return ""; 51 } 52 53 function deleteCookie(cname, path) { 54 document.cookie = cname + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=" + (path || "/"); 55 } 56 57/* Generate GUID 58 Script from: http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript */ 59 generateGUID = function() { 60 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {var r = Math.random()*16|0,v=c=='x'?r:r&0x3|0x8;return v.toString(16);}); 61 }; 62 63// Name the browser tab if it does not have one 64 if (window.name == '') 65 window.name = generateGUID(); 66 67 /* Set up the cookie for maintaining the active tab 68 TEMPORARY: Clean up any non root activeTab cookies */ 69 var activeTabMatches = document.cookie.match(/activeTab/g); 70 if (activeTabMatches != null && typeof activeTabMatches != "undefined" && activeTabMatches.length > 1) { 71 var path = window.location.pathname; 72 while (path != "") { 73 document.cookie = "activeTab=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=" + path; 74 path = path.replace(/(\/[^\/]*?)$/, ''); 75 } 76 } 77 78 // Now set the activeTab if we do not have one 79 if (getCookie("activeTab") == "") 80 setCookie("activeTab", window.name, 365); 81 82/* Setup tab focusing / leaving 83 NOTE! Always make sure this script runs first so following logic can use the visibility values as needed */ 84 var visibilityKey_hidden, visibilityKey_changeEvent; 85 86 // Detect which version of the API is available 87 if (typeof document.hidden !== "undefined") { 88 visibilityKey_hidden = "hidden"; 89 visibilityKey_changeEvent = "visibilitychange"; 90 91 } else if (typeof document.mozHidden !== "undefined") { 92 visibilityKey_hidden = "mozHidden"; 93 visibilityKey_changeEvent = "mozvisibilitychange"; 94 95 } else if (typeof document.msHidden !== "undefined") { 96 visibilityKey_hidden = "msHidden"; 97 visibilityKey_changeEvent = "msvisibilitychange"; 98 99 } else if (typeof document.webkitHidden !== "undefined") { 100 visibilityKey_hidden = "webkitHidden"; 101 visibilityKey_changeEvent = "webkitvisibilitychange"; 102 } 103 104 // If the browser supports the visibility API then set everything up 105 if (visibilityKey_hidden !== "undefined") { 106 $(document).bind(visibilityKey_changeEvent, function(event) { 107 if (document[visibilityKey_hidden]) { 108 } else { 109 } 110 }); 111 } 112 113/* Unfocus / focus functions for the window. 114 These are different than the visibility functions above. The visibility ones will still function even if the user is using a different application but that tab is still visible. 115 The onblur below will still fire even if the tab is still open in the browser but the user is using a different application. */ 116 window.onblur = function() { 117 }; 118 119 window.onfocus = function() { 120 setCookie("activeTab", window.name, 365); 121 }; 122 123/* jQuery document ready 124** Bind all custom tpWheels logic inside here*/ 125$(document).ready(function() { 126 /* jQuery plugins settings 127 ** Set defaults for jQuery plugins here. */ 128 129 // $.datepicker.setDefaults({ 130 // changeMonth: true, 131 // changeYear: true, 132 // showOn: "button", 133 // buttonImage: "/tpWheels/images/icons/calendar.png", 134 // buttonImageOnly: true, 135 // buttonText: "", 136 // onSelect: function(dateText, inst) { 137 // $(inst.input).change(); 138 // } 139 // }); 140 141 // Setup the context menu on click 142 $("div.context-menu").livequery(function() { 143 var contextMenu = this, 144 openLinksInNewTab = $(contextMenu).data("open-links-in-new-tab") ? new Boolean($(contextMenu).data("open-links-in-new-tab")) : false; 145 146 // Create a span element and and bind the click event to show / hide the context menu and append it to the contextMenu 147 $('<span class="context-menu">▼</span>') 148 .click(function() { 149 // Set the positon 150 if (!$(contextMenu).is(":visible")) { 151 var inTopNav = $(contextMenu).closest("ul#main-nav").length > 0;
152 if (!inTopNav) 153 $(contextMenu).css("left", $(this).position().left); 154 else 155 $(contextMenu).parent().css("position", "relative"); 156 $(contextMenu).css("top", $(this).position().top + $(this).height()); 157 } 158 // Show/Hide the context menu 159 $(contextMenu).toggle(); 160 161 // If context menu is visible, add few classes, load data if nexessary 162 if ($(contextMenu).is(":visible")) { 163 // Add necessary classes 164 $(this).html('▲').addClass("on").removeClass("off"); 165 166 // Calculate the width for top-border, subtracting 2 to compensate for 2px margin 167 widthForTopBorder = $(this).outerWidth() - 2; 168 169 // If this is a dynamic context menu, request the server for data 170 if ($(contextMenu).attr("rel") != undefined) { 171 var contextMenuOptions = $('<div class="context-menu-options" />').appendTo(contextMenu); 172 appendLoadingMask(contextMenuOptions); 173 $.get($(contextMenu).attr("rel"), {}, function(data) { 174 $(contextMenuOptions).html(data); 175 176 // Set all links as target="_blank" if requested and the link is not remote or a facebox link 177 if (openLinksInNewTab) { 178 $(contextMenu).find("a").filter(":not(.facebox)").each(function() { 179 if (!$(this).data("remote")) 180 $(this).attr("target", "_blank"); 181 });
182 } 183 184 // Append div to render context menu's top-border 185 $('<div class="context-menu-top" />').css("left", widthForTopBorder).css("width", $(contextMenu).outerWidth() - widthForTopBorder - 1).prependTo(contextMenu); 186 }); 187 188 // Append div to render context menu's top-border 189 } else { 190 $('<div class="context-menu-top" />').css("left", widthForTopBorder).css("width", $(contextMenu).outerWidth() - widthForTopBorder - 1).prependTo(contextMenu); 191 } 192 193 // Remove elements from context-menu 194 } else { 195 $(contextMenu).find("div.context-menu-top").remove().end().find("div.context-menu-options").remove(); 196 $(this).html('▼').addClass("off").removeClass("on"); 197 } 198 }) 199 .insertAfter(contextMenu); 200 }); 201 202 // handle file fields in ajax requests 203 $("form").on("ajax:aborted:file", function(e, fields) { 204 var form = $(this); 205 $(form).one("ajax:beforeSend.multipart", function(e, xhr, settings) { 206 delete settings.data; 207 form.ajaxSubmit(settings); 208 return false; 209 }); 210 $.rails.handleRemote(form); 211 return false; 212 }); 213 214 // Any link with delete row, inside a table row, needs to delete that row if its post is successful (empty data) 215 $("tr a.delete-row").on("ajax:success", function(event, data) { 216 if (data.length == 0) 217 $(this).closest("tr").remove(); 218 else 219 alert(data); 220 }); 221 222 // Select the contents of an input field on focus if requested 223 $(":input.select-on-focus").focus(function() { 224 $(this).select(); 225 }); 226 227 // pop open a Facebox 228 $("a.facebox:not([data-remote])").livequery(function() { 229 var e = this; 230 231 // Before loading facebox, set width determined by data-facebox-width attribute 232 $(document).unbind("beforeReveal.facebox").bind("beforeReveal.facebox", function() { 233 // read data-facebox-width or default to value in jquery.facebox.css 234 var width = $(e).data("facebox-width"); 235 if (width != null && width.toString().indexOf("%") != -1) 236 width = $(document).width() * parseInt(width) / 100; 237 if (width != null) 238 $("#facebox div.content").css("width", width); 239 }); 240 241 $(this).facebox(); 242 }); 243 244 // Use .facebox to open remote links in faceboxes 245 $(document).on("ajax:beforeSend", "a.facebox[data-remote],a.facebox[remote],form.facebox[data-remote]", function(event,settings) { 246 var faceboxOpener = this; 247 248 $("#facebox .body div.content").is(":visible") ? appendLoadingMask($("#facebox .body div.content")) : $.facebox.loading(); 249 var ajaxUrl = $(this)[0].tagName == 'A' ? $(this).attr("href") : ''; 250 if (ajaxUrl.length == 0) 251 $("#facebox .reload").hide().unbind("click"); 252 else 253 $("#facebox .reload").show().unbind("click").bind("click", function() {$.facebox({ajax:ajaxUrl});}); 254 255 // If this link has a facebox-close callback on it remove any current ones and set this one up 256 if ($(this).data("facebox-close") != null) 257 $(document).unbind("afterClose.facebox").bind("afterClose.facebox", function() {eval($(faceboxOpener).data("facebox-close"));}); 258 259 }).on("ajax:success", "a.facebox[data-remote],a.facebox[remote],form.facebox[data-remote]", function(event, data, status, xhr) { 260 261 // If we get some data go ahead and show it in the facebox 262 if (data.length > 0 && (xhr.getAllResponseHeaders() || '').indexOf("text/javascript") == -1) { 263 var e = this; 264 265 // Before loading facebox, set width determined by data-facebox-width attribute 266 $(document).unbind("beforeReveal.facebox").bind("beforeReveal.facebox", function() { 267 // read data-facebox-width or default to value in jquery.facebox.css 268 var width = $(e).data("facebox-width"); 269 if (width != null && width.toString().indexOf("%") != -1) 270 width = $(document).width() * parseInt(width) / 100; 271 if (width != null) 272 $("#facebox div.content").css("width", width); 273 }); 274 275 // Display facebox 276 $.facebox(data); 277 278 // Close facebox if empty data is returned 279 } else if (data.length == 0) { 280 $(document).trigger("close.facebox"); 281 } 282 }); 283 284 // set page reload callback for ajax forms 285 $("form.callback-reload[data-remote],a.callback-reload[data-remote]").on("ajax:success", function(event, data) { 286 if (data.length == 0) { 287 $(document).trigger('close.facebox'); 288 window.onbeforeunload = null; 289 window.location.reload(); 290 } 291 }); 292 293 // make page focus on flash errors 294 $(".flash-err").livequery(function() {
295 var viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0), 296 errorPoint = $(this).offset().top, 297 flashArea = $(this).closest("flash-area"); 298 299 if (errorPoint < window.pageYOffset) { 300 $(window).scrollTop(errorPoint - 75); 301 } else if (errorPoint > window.pageYOffset + viewportHeight) { 302 var numScreensDown = Math.floor(errorPoint / viewportHeight); 303 var remainingPixels = errorPoint % viewportHeight; 304 $(window).scrollTop((numScreensDown * viewportHeight) + (remainingPixels / 2)); 305 } 306 }); 307 308 // Make flash messages be modals when they're generated by ajax calls. 309 // $(".table-listing td a").livequery(function() { 310 // $(this).click(function() { 311 // $showFlashMessage($(this), "link"); 312 // }); 313 // }); 314 // $(".table-listing td input").livequery(function() { 315 // $(this).change(function() { 316 // $showFlashMessage($(this), "input"); 317 // }); 318 // }); 319 // function $showFlashMessage(thisArg, type) { 320 // $(".flash-area").appendTo('html') 321 // if (type == "link") { 322 // if (!$(thisArg).hasClass('linked-icon-delete')) 323 // listingRecordNumber = $(thisArg).parent().siblings('td.numbered').html(); 324 // } else if (type == "input") { 325 // listingRecordNumber = $(thisArg).closest("form").parent().siblings('td.numbered').html(); 326 // } 327 // $(".listing-flash-highlight").each(function() { 328 // $(".listing-flash-highlight").removeClass('listing-flash-highlight').removeClass('err').removeClass('success'); 329 // }); 330 // } 331 // $(document).ajaxStop(function() { 332 // $(".flash-area.modal-flash").each(function() { 333 // if ($(this).closest('.table-listing').length == 0) { 334 // var flashMessage = $(".flash-area"); 335 // if (flashMessage.html() != "") { 336 // var htmlToAppend = `<div class="temp-flash"><div class="flash-area">${flashMessage.html()}</div></div>`; 337 // $("body").after(htmlToAppend); 338 // flashMessage.removeClass().addClass('flash-area').html(""); 339 // } 340 // $("td.numbered").each(function() { 341 // if ($('.table-listing').length > 0 && typeof listingRecordNumber !== 'undefined' && listingRecordNumber != "" && $(this).html() == listingRecordNumber) { 342 // var currentRow = $(this).parent(); 343 // currentRow.addClass('listing-flash-highlight'); 344 // currentRow.addClass(flashMessage.children('div').hasClass('flash-err') ? 'err' : 'success'); 345 // } 346 // }); 347 // } 348 // }); 349 // }); 350 // $(".temp-flash").livequery(function() { 351 // $(this).fadeIn(300, function() { 352 // var durationMultiplier = Math.ceil($(this).find(".flash-area").text().length / 150); 353 // if (durationMultiplier < 1) 354 // durationMultiplier = 1; 355 // $(this).delay(2500 * durationMultiplier).fadeOut(300, function() { 356 // $(this).remove(); 357 // }); 358 // }); 359 // delete listingRecordNumber; 360 // }); 361 362 // use data-load-target to specify destination for remote content 363 $("a[data-load-target], form[data-load-target]").livequery("ajax:beforeSend", function(event, settings) { 364 var theTarget = $($(this).data("load-target")); 365 if (!$(theTarget).data("pre-load")) 366 $(theTarget).data("pre-load", $(theTarget).html()); 367 appendLoadingMask(theTarget); 368 }).livequery("ajax:success", function(event, data, status, xhr) { 369 if (data.length > 0 && (xhr.getAllResponseHeaders() || '').indexOf("text/javascript") == -1) { 370 if ($(this).data("replace-target") != null && $(this).data("replace-target") == true) 371 $($(this).data("load-target")).replaceWith(data); 372 else 373 $($(this).data("load-target")).html(data); 374 } 375 }); 376 377 // use data-revert-load-target on links or buttons to revert to the previous load target content on click. 378 // the element does not have to specify data-load-target if it is contained in an element that does. 379 $(document).on("click", "a[data-revert-load-target], input[data-revert-load-target]", function(event) { 380 var targetSelector = $(this).data("load-target") || $(this).closest("[data-load-target]").data("load-target"); 381 var target = $(targetSelector); 382 if (target.length > 0) 383 target.html(target.data("pre-load") || ""); 384 return stopEvent(event); 385 }); 386 387 // use data-empty-target on links or buttons to clear a targets contents on click 388 $(document).on("click", "a[data-empty-target], input[data-empty-target]", function(event) { 389 var target = $($(this).data("empty-target")); 390 if (target.length > 0) 391 target.html(""); 392 return stopEvent(event); 393 }); 394 395 // pop open a Facebox with a button 396 $(":button.facebox[rel], :submit.facebox[rel]").livequery(function() { 397 $(this).click(function() { 398 $.facebox({ajax: $(this).attr("rel")}); 399 }); 400 }); 401 402 // close a facebox on click 403 $(".close-facebox").livequery("click", function(event) { 404 if (!$(this).attr("disabled")) { 405 $(document).trigger('close.facebox'); 406 if (!$(this).hasClass("input-submit")) 407 return stopEvent(event); 408 else 409 return true; 410 } 411 }); 412 413 // cause an item to disappear in 1.5 seconds 414 $(".fade-out").livequery(function() { 415 setTimeout(function(e) {$(e).remove();}, 1500, [this]); 416 }); 417 418 // cause a form field to be all caps on blur 419 $(".make-caps").livequery("blur", function() { 420 $(this).val($(this).val().toUpperCase()); 421 422 // make-caps isn't firing the blur or change if enter is hit, manually bind it and submit the form 423 }).livequery("keydown", function(event) { 424 var key = event.which ? event.which : event.keyCode; 425 if (key == KEYENUM.RETURN) { 426 if (!$(this).hasClass("stop-enter-key")) { 427 $(this).blur().change(); 428 if ($(this.form) !== undefined) 429 $(this.form).submit(); 430 } 431 return stopEvent(event); 432 } 433 }); 434 435 // Bind sortables 436 $("ul.sortable, .sortable[data-sortableelementsselector]").livequery(function() { 437 var connectToAny = $(this).hasClass("connect-to-any"); 438 var sortableElementsSelector = $(this).data("sortableelementsselector") | "li"; 439 $(this).sortable({
440 connectWith: !connectToAny ? $(this).siblings('.sortable:not(.do-not-connect)') : ".sortable:not(.do-not-connect)", 441 items: "> *:not(.locked)" 442 }); 443 444 // Update any sort elements with a span of .option-index 445 $(this).bind("sortupdate", function() { 446 $(".sortable > " + sortableElementsSelector).each(function() { 447 if ($(this).has("span.option-index")) { 448 var spanContent = trim($(this).find("span.option-index").html()), 449 isAlphaContent = /^[a-zA-Z]$/.test(spanContent), 450 siblingLis = $(this).closest(".sortable").siblings(".sortable:not(.do-not-connect)").find("> " + sortableElementsSelector), 451 452 // If this list is only connecting to siblings we need to index against all sibling sort element tags only, otherwise, all of them. 453 index = !connectToAny ? siblingLis.add($(this).closest(".sortable").find("> " + sortableElementsSelector)).index(this) : $(this).index(".sortable > " + sortableElementsSelector); 454 455 // Specifying the original selector again on the index call returns the index relative to the whole set, not the containing element. 456 if (isAlphaContent) 457 $("span.option-index", this).html(spanContent.replace(/^[a-zA-Z]+/, String.fromCharCode(String(index + 1).charCodeAt(0) + 16))); 458 else 459 $("span.option-index", this).html(spanContent.replace(/^[0-9]+/, index + 1)); 460 } 461 }); 462 }) 463 .bind("sortstart",function() { 464 $(".sortable > " + sortableElementsSelector).each(function() { 465 if ($(this).has("span.context-menu") && $("div.context-menu",this).is(":visible")) 466 $("span.context-menu", this).click(); 467 }); 468 }); 469 470 // Bind the column balancing logic if requested 471 if ($(this).data("balance-columns")) { 472 473 // Re-balance columns on sortstop 474 $(this).bind("sortstop", function() { 475 476 // Find all sortables on the page that are connected 477 var sortablesToBalance = !connectToAny ? $(this).siblings('.sortable:not(.do-not-connect)').add(this) : $(".sortable:not(.do-not-connect)"); 478 $(sortablesToBalance).each(function() { 479 // Variables we need 480 var nextSortable = $(this).next(".sortable:not(.do-not-connect)"), 481 balanceAt = $(this).data("balance-columns"), 482 excessiveOptions = $(this).find("> " + sortableElementsSelector + ":gt(" + (balanceAt - 1) + ")"), 483 numOptions = $(this).find("> " + sortableElementsSelector).length; 484 485 // This column has too many options. Move them to the next column. 486 if (numOptions > balanceAt) { 487 if ($(excessiveOptions).length > 0) 488 $(excessiveOptions).detach().prependTo(nextSortable); 489 490 // This column has too few options. Grab X amount from the next column. 491 } else if (nextSortable != null) { 492 var neededElements = $(nextSortable).find("> " + sortableElementsSelector + ":lt(" + (balanceAt - numOptions) + ")"); 493 494 if ($(neededElements).length > 0) 495 $(neededElements).detach().appendTo(this); 496 } 497 }); 498 }); 499 } 500 }); 501 502 // Stop submission on the enter key 503 $("input.stop-enter-key").livequery('keydown', function(e) { 504 // stop the event 505 if ((e.keyCode ? e.keyCode : e.which) == KEYENUM.RETURN) 506 return stopEvent(e); 507 }); 508 509 // Add special functionality to the enter key 510 $("input.move-on-enter-key").livequery('keydown', function(e) { 511 var key = e.keyCode ? e.keyCode : e.which; 512 513 // find the next input field 514 var inputs = $(":input:visible"); 515 var next = $(inputs).get(inputs.index(this) + 1); 516 517 // as long as we hit the enter key, are not on a button, and have a next field 518 if (key == 13 && !$(this).is(":button, :submit, :reset") && !(next === undefined)) { 519 520 // focus 521 // NOTE: this must be in a timeout block. alerts in click() will break stopEvent 522 setTimeout(function() { 523 $(next).focus(); 524 }, 0); 525 } 526 527 // stop the event 528 if (key == 13) 529 return stopEvent(e); 530 }); 531 532 // Add special functionality to the backspace key 533 $(":input:not(:text,:password,textarea)").not("[type='number']").livequery('keydown', function(e) { 534 if ((e.keyCode ? e.keyCode : e.which) == 8) 535 return stopEvent(e); 536 }); 537 538 // Activate date selection boxes 539 $("input.input-date:not(:disabled):not([readonly])").livequery(function() { 540 var options = {}; 541 // Look for possible options coming in 542 if ($(this).data("min-date") != null) 543 options.minDate = $(this).data("min-date"); 544 if ($(this).data("max-date") != null) 545 options.maxDate = $(this).data("max-date"); 546 $(this).data("dateSettings", options); 547 bindDatePicker(this); 548 }); 549 550 // Activate datetime selection boxes 551 $("input.input-datetime:not(:disabled):not([readonly]), input.input-timepicker:not(:disabled):not([readonly])").livequery(function() { 552 var options = { 553 timeFormat: "hh:mm TT", 554 addSliderAccess: true, 555 sliderAccessArgs: { touchonly: false } 556 }; 557 558 // If this is timepicker, only show time 559 if ($(this).is(".input-timepicker")) 560 options.timeOnly = true; 561 562 // Look for possible options coming in 563 if ($(this).data("min-date") != null) 564 options.minDate = $(this).data("min-date"); 565 if ($(this).data("max-date") != null) 566 options.maxDate = $(this).data("max-date"); 567 if ($(this).data("min-hour") != null) 568 options.hourMin = $(this).data("min-hour"); 569 if ($(this).data("max-hour") != null) 570 options.hourMax = $(this).data("max-hour"); 571 if ($(this).data("min-minute") != null) 572 options.minuteMin = $(this).data("min-minute"); 573 if ($(this).data("max-minute") != null) 574 options.minuteMax = $(this).data("max-minute"); 575 if ($(this).data("step-hour") != null) 576 options.stepHour = $(this).data("step-hour"); 577 if ($(this).data("step-minute") != null) 578 options.stepMinute = $(this).data("step-minute"); 579 580 $(this).data("datetimeSettings", options); 581 bindDatetimePicker(this); 582 }); 583 584 // For date and datetime fields automatically insert the / characters as needed 585 $("input.input-date,input.input-datetime").livequery(function() { 586 // If the last character is a / and they try to enter one, stop it.
587 $(this).keydown(function(event) { 588 var key = event.which ? event.which : event.keyCode; 589 var currVal = $(this).val(); 590 if (currVal[currVal.length - 1] == '/' && (key == KEYENUM.FORWARDSLASH || key == KEYENUM.NUMPADFORWARDSLASH)) 591 return stopEvent(event); 592 593 // Setup some logic to automatically insert the / characters as necessary while they type 594 }).keyup(function(event) { 595 var key = event.which ? event.which : event.keyCode; 596 var currVal = $(this).val(); 597 598 // After the 2nd and 5th characters insert a / 599 if (key != KEYENUM.FORWARDSLASH && key != KEYENUM.NUMPADFORWARDSLASH) { 600 if (currVal.length == 2 || currVal.length == 5) 601 $(this).val(currVal + '/'); 602 } 603 }); 604 }); 605 606 // Format Percentage fields 607 $(":input.percent").livequery(function() { 608 $(this).val($.formatNumber($(this).val(), {format:numberFormats.decimal, locale:"us"})); 609 $(this).blur(function() { 610 $(this).val($.formatNumber($(this).val(), {format:numberFormats.decimal, locale:"us"})); 611 }); 612 }); 613 614 // enable inline labels for text fields 615 // this allows labels to appear in text boxes, not outside them 616 $("textarea.inline-label, input.input-text.inline-label").livequery(function() { 617 var meta = $(this).metadata(); 618 if (!(meta.LABEL === undefined)) { 619 if ($(this).origVal() == '') 620 $(this).addClass("empty-field").origVal(meta.LABEL); 621 $(this).focus(function() { 622 if ($(this).hasClass("empty-field")) 623 $(this).removeClass("empty-field").origVal(''); 624 }); 625 $(this).blur(function() { 626 if ($(this).origVal() == '') 627 $(this).addClass("empty-field").origVal(meta.LABEL); 628 }); 629 } 630 }); 631 632 // enable editable-data input toggling 633 $(":input.facade-field").livequery(function() { 634 var field = this; 635 var data = $(this).next("span.facade-data"); 636 var oldVal = $(this).val(); 637 var origVal = oldVal; 638 if ($(data).length > 0) { 639 $(data) 640 .click(function() { 641 $(data).addClass("hidden"); 642 $(field).removeClass("hidden").focus().select(); 643 return false; 644 }) 645 .focus(function() { 646 $(this).click(); 647 }); 648 $(field) 649 .change(function() { 650 var val = $(field).val(); 651 $(data).html(val); 652 if (val != "") 653 oldVal = val; 654 }) 655 .blur(function() { 656 if ($(field).val() == "") { 657 $(field).val(oldVal); 658 $(data).html(oldVal); 659 } 660 if ($(field).val() != "") { 661 $(field).addClass("hidden"); 662 $(data).removeClass("hidden"); 663 } 664 }) 665 .keydown(function(e) { 666 var key = e.which | e.keyCode; 667 if (key == KEYENUM.ESC) { 668 $(field).val(origVal); 669 $(data).html(origVal); 670 oldVal = origVal; 671 $(field).blur(); 672 } 673 }); 674 } 675 }); 676 677 $(":input.facade-field").livequery(function() { 678 $(this).blur(function() { 679 // update the contents 680 }); 681 }); 682 683 // focus on the focus field 684 $(":input.focus,a.focus").livequery(function() { 685 // Make sure we are not stealing focus from a visible facebox element and 686 // Make sure we are focusing on the element if it is in a visible facebox 687 if (($(this).closest('#facebox').length == 0 && !$("#facebox").is(":visible")) || ($(this).closest('#facebox').length > 0 && $("#facebox").is(":visible"))) 688 $(this).focus(); 689 }); 690 691 // If there is no visible focus field, set focus on the first visible input - Do it in a 1ms time out so that DOM gets loaded 692 setTimeout(function() { 693 if ($(":input:visible.focus,a:visible.focus").length == 0) 694 $("div#body :input:visible:first").focus(); 695 }, 1); 696 697 // If there is no visible focus field on the facebox, set focus on the first visible input in the facebox - Do it in a 1ms time out so that DOM gets loaded 698 $(document).bind('reveal.facebox', function() { 699 setTimeout(function() { 700 if ($("#facebox :input:visible.focus,a:visible.focus").length == 0) 701 $("#facebox :input:visible:first").focus(); 702 }, 1); 703 return true; 704 }); 705 706 $("p span[class*='toggle-text-length-'],div.table-listing tr td span[class*='toggle-text-length-']").livequery(function() { 707 var maxLen = $(this).attr('class').replace(/^.*toggle-text-length-(\d+).*$/, '$1'); 708 var text = $(this).html().replace(/<(.|\n|\r)*?>/g,''); 709 710 // if the length of html exceeds our maximum length... 711 if (text.length > maxLen) { 712 var regex = new RegExp("^(.|\\n|\\r){" + maxLen + "}\\w{0,15}"); 713 maxLen = regex.exec(text)[0].length; 714 715 $(this).html(text.substr(0, maxLen) + ' <span style="display: none">' + text.substr(maxLen) + '</span> <a class="show-more">... [more]</a>'); 716 $("a.show-more", this).click(function() { 717 var span = $(this).prev("span"); 718 $(this).html($(span).is(":visible") ? "... [more]" : "[less]"); 719 $(span).slideToggle(400); 720 return false; 721 }); 722 } 723 }); 724 725 $("a.toggle-all-text-length").on("click", function(event) { 726 $("a.show-more").click(); 727 $(this).html($(this).html().trim() == "More" ? "Less" : "More"); 728 return stopEvent(event); 729 }); 730 731 // Active html editor boxes 732 $("textarea.input-html").livequery(function() { 733 if ($(this).parent("div.rte-zone").length == 0) { 734 $(this).rte({ 735 controls_rte: { 736 s1: { 737 separator: true 738 }, 739 bold: { 740 command: "bold", 741 tags: ["b", "strong"] 742 }, 743 italic: { 744 command: "italic", 745 tags: ["i", "em"] 746 }, 747 underline: { 748 command: "underline", 749 tags: ["u"] 750 }, 751 s2: { 752 separator: true 753 }, 754 justifyLeft: { 755 command: "justifyleft" 756 }, 757 justifyCenter: { 758 command: "justifycenter" 759 }, 760 justifyRight: { 761 command: "justifyright" 762 }, 763 justifyFull: { 764 command: "justifyfull" 765 }, 766 s3: { 767 separator: true 768 }, 769 subscript: { 770 command: "subscript", 771 tags: ["sub"] 772 }, 773 superscript: { 774 command: "superscript", 775 tags: ["sup"] 776 }, 777 s5: { 778 separator: true 779 }, 780 orderedList: { 781 command: "insertorderedlist", 782 tags: ["ol"] 783 }, 784 unorderedList: { 785 command: "insertunorderedlist", 786 tags: ["ul"] 787 }, 788 s6: { 789 separator: true 790 }, 791 block: { 792 command: "formatblock", 793 select: '<select> <option value="">- format -</option> <option value="<p>">Paragraph</option> <option value="<h1>">Header 1</option> <option value="<h2>">Header 2</options> <option value="<h3>">Header 3</option> <option value="<h4>">Header 4</options> <option value="<h5>">Header 5</option> <option value="<h6>">Header 6</options></select> ', 794 tag_cmp: lwrte_block_compare, 795 tags: ["p", "h1", "h2", "h3", "h4", "h5", "h6"] 796 }, 797 link: { 798 exec: lwrte_link, 799 tags: ["a"] 800 }, 801 unlink: { 802 command: "unlink" 803 }, 804 s8: { 805 separator: true 806 }, 807 removeFormat: { 808 exec: lwrte_unformat 809 }, 810 clear: { 811 exec: lwrte_clear 812 } 813 }, 814 controls_html: {} 815 }); 816 } 817 }); 818 819 // Automatically post on change 820 $(":input.submit-on-change").livequery(function() { 821 $(this).change(function() { 822 $(this).blur(); 823 $(this.form).submit(); 824 }); 825 }); 826 827 $("form a.submit-on-click").on("click", function(event) { 828 $(this).closest("form").submit(); 829 return stopEvent(event); 830 }); 831 832 $(":input.highlight-row-on-change").livequery(function() { 833 $(this).change(function() { 834 $(this).closest("tr").addClass("unsaved-row"); 835 }); 836 }); 837 838 // Turn on autocomplete features 839 $(":input.autocomplete").livequery(function() { 840 var name = $(this).attr("name"); 841 842 // if item is a selectbox, use ui-combobox 843 if ($(this).is("select")) { 844 $(this).combobox(); 845 846 // otherwise, use ui-autocomplete with some tweaking 847 } else { 848 849 // remove any hidden fields after the element 850 $(this).nextAll("input.autocomplete-hidden").remove(); 851 852 // change name of original field 853 if (/^autocomplete\b/.test(name)) 854 name = name.replace(/^autocomplete\[([^\]]+)\]/, "$1"); 855 else 856 $(this).attr("name", name.replace(/^([^\[]+)/, "autocomplete[$1]")); 857 858 // create hidden field for value 859 var hidden = $("<input type='hidden' />") 860 .attr("name", name) 861 .attr("data-field-name", $(this).data("field-name") + "-hidden") 862 .addClass("autocomplete-hidden") 863 // add any classes set up on data-autocomplete-class attribute to this hidden field 864 .addClass($(this).data("autocomplete-class") != null ? $(this).data("autocomplete-class") : '') 865 .insertAfter(this); 866 867 // grab variables for processing 868 var url = $(this).data("autocomplete-url"), 869 field = this; 870 871 // function to look up a value and set it if valid 872 var lookup = function(val) { 873 if (val == "") { 874 $(hidden).add(field).val(''); 875 return; 876 } 877 $.ajax({
878 url: url, 879 data: "id=" + val, 880 dataType: "json", 881 type: "get", 882 success: function(data) { 883 if (data.length > 0) { 884 $(hidden).val(data[0].id); 885 // The blur below causes us to lose focus on the current focused input. 886 var currentFocus = $(":input:focus"); 887 888 // Hack to correctly change value 889 $(field) 890 .autocomplete("disable") 891 .val(data[0].value) 892 .blur() 893 .autocomplete("enable"); 894 $(field).add(hidden).trigger("autocomplete:lookup"); 895 896 // If we had a focused input return the focus and select its contents 897 if ($(currentFocus).length > 0) 898 $(currentFocus).focus().select(); 899 } else if (!$(field).hasClass("allow-invalid-selection")) { 900 $(hidden).add(field).val(''); 901 } 902 } 903 }); 904 }; 905 906 // TODO: don't just store this method in the element data 907 $(this).data("lookup", lookup); 908 909 // add autocomplete functionality 910 // TODO: multiple selection for textarea 911 $(this).autocomplete({ 912 source: url, 913 autoFocus: false, 914 delay: 500, 915 select: function(event, ui) { 916 // If we are selecting a "more" option, do not actually change anything. Still fire the selected function 917 if (ui.item.id != "autocomplete-more") { 918 $(hidden).val(ui.item.id).change(); 919 } else { 920 $(this).trigger("moreSelected"); 921 } 922 }, 923 change: function(event, ui) { 924 // If this autocomplete allows invalid selections go ahead and copy the value down to the hidden field 925 if ($(field).hasClass("allow-invalid-selection")) 926 $(hidden).val($(field).val()).change(); 927 // Otherwise, empty the two fields out 928 else if (!ui.item) $(field).add(hidden).val('').change(); 929 } 930 }); 931 932 // When the field can accept invalid selections we need the enter key to pass the value down to the hidden field before the submit triggers 933 if ($(field).hasClass("allow-invalid-selection")) { 934 935 $(field).blur(function() { 936 $(hidden).val($(field).val()); 937 }); 938 939 $(field).keypress(function(event) { 940 var key = event.which ? event.which : event.keyCode; 941 if (key == KEYENUM.RETURN) 942 $(field).blur(); 943 }); 944 } 945 946 // perform reverse lookup to correctly fill value 947 lookup($(this).val()); 948 } 949 }); 950 951 // Place the populatedFrom code after autocomplete code to bind the autocomplete fields too 952 // Turn on populatedFrom features 953 $(":input.populated-from").livequery(function() { 954 // store this field 955 var populateField = this; 956 var withinAssociation = $(this).closest("div.ui-association").length; 957 var closestRow = $(this).closest("div.row"); 958 959 // look up hidden (value) field 960 var hidden = $(this).parent().find(":input.populated-from-hidden")[0]; 961 962 // store the different populated-from values in an array to loop over 963 var hiddenNames = $(hidden).metadata().POPULATEDFROM.split(","); 964 965 $.each(hiddenNames, function(i) { 966 // look up the field that triggers the population 967 if (withinAssociation) 968 var controlField = (/^#/.test(hiddenNames[i]) ? $(hiddenNames[i]) : $(":input[name*='" + hiddenNames[i] + "']:not([name*='populated-from'])", closestRow).not(this)); 969 else 970 var controlField = (/^#/.test(hiddenNames[i]) ? $(hiddenNames[i]) : $(":input[name='" + hiddenNames[i] + "']:not([name*='populated-from'])").not(this)); 971 972 // bind the change event on the controlFields 973 $(controlField).change(function(event) { 974 975 // Grab the current value for this field 976 var val = $(populateField).val(); 977 var url = $(hidden).metadata().POPULATEBINDING; 978 979 // for each field/value, build our dataObject 980 var dataObject = {}; 981 982 $.each(hiddenNames, function(j) { 983 if (hiddenNames.length > 1) 984 bindValue = "bindValue" + (j+1); 985 else 986 bindValue = "bindValue"; 987 988 if (withinAssociation) 989 dataObject[bindValue] = (/^#/.test(hiddenNames[j]) ? $(hiddenNames[j]).val() : $(":input[name*='" + hiddenNames[j] + "']:not([name*='populated-from'])", closestRow).not(this).val()); 990 else 991 dataObject[bindValue] = (/^#/.test(hiddenNames[j]) ? $(hiddenNames[j]).val() : $(":input[name='" + hiddenNames[j] + "']:not([name*='populated-from'])").not(this).val()); 992 }); 993 994 // Sometimes we may want to programatically prevent the populate-from execution 995 $(populateField).data("abortPopulate", false).data("populated", false); 996 997 // Run the populate:before action if it is present 998 $(populateField).trigger("populate:before"); 999 1000 // Check if we need to abort the execution on this run 1001 if ($(populateField).data("abortPopulate")) { 1002 $(populateField).data("populated", true); 1003 return stopEvent(event); 1004 } 1005 1006 // pass the controlField value up to the populateBinding and populate our select field 1007 $.ajax({
1008 type: $(hidden).metadata().POPULATEMETHOD, 1009 url: url, 1010 data: dataObject, 1011 success: function(data) { 1012 // If we have a select field drop the options and append the new ones 1013 if ($(populateField).prop("tagName") == 'SELECT') { 1014 $("option:gt(0)", populateField).remove(); 1015 if ($(populateField).hasClass("uses-generated-value")) 1016 $(populateField).append(data); 1017 else 1018 $(populateField).append(data).val(val); 1019 1020 // Otherwise just expect a single value back and drop it into the field 1021 } else { 1022 $(populateField).val(data); 1023 } 1024 1025 // In some cases we need to call .change() on the populated field because other fields populate off of it 1026 if ($(populateField).hasClass("change-populate-field-on-populate")) { 1027 $(populateField).change(); 1028 } 1029 1030 $(populateField).data("populated", true).trigger("populate:after"); 1031 } 1032 }); 1033 }); 1034 1035 // In some cases, we don't want .change() to be called on control field. In these cases, pass in the do-not-change class below. 1036 if (!$(controlField).hasClass("do-not-change-populate-from-on-load")) { 1037 $(controlField).change(); 1038 } 1039 }); 1040 }); 1041 1042 // Duplicate values from one field to another 1043 $(":input[data-copy-value-from]").livequery(function() { 1044 var copyFromValue = $(this).attr("data-copy-value-from"); 1045 var theForm = $(this).closest("form"); 1046 var targetField = $(this); 1047 var sourceField = $(theForm).find(copyFromValue); 1048 1049 $(sourceField).change(function() { 1050 $(targetField).val($(sourceField).val()); 1051 }).change(); 1052 }); 1053 1054 // enable fields when passed in checkbox is checked 1055 $(":input[data-enabled-from]").livequery(function() { 1056 var enabledFromValue = $(this).attr("data-enabled-from"); 1057 var theForm = $(this).closest("form"); 1058 var targetField = $(this); 1059 var sourceField = $(theForm).find(enabledFromValue); 1060 1061 $(sourceField).change(function() { 1062 if ($(sourceField).is(":checked") || !$(sourceField).is(":checkbox") && $(sourceField).val() != "") 1063 $(targetField).attr("disabled", false); 1064 else 1065 $(targetField).attr("disabled", true); 1066 }).change(); 1067 }); 1068 1069 // disable fields when passed in checkbox is unchecked 1070 $(":input[data-disabled-from]").livequery(function() { 1071 var disabledFromValue = $(this).attr("data-disabled-from"); 1072 var theForm = $(this).closest("form"); 1073 var targetField = $(this); 1074 var sourceField = $(theForm).find(disabledFromValue); 1075 1076 $(sourceField).change(function() { 1077 if ($(sourceField).is(":checked") || !$(sourceField).is(":checkbox") && $(sourceField).val() != "") 1078 $(targetField).attr("disabled", true); 1079 else 1080 $(targetField).attr("disabled", false); 1081 }).change(); 1082 }); 1083 1084 // disable fields when passed in checkbox is unchecked 1085 $(":checkbox[data-checked-from]").livequery(function() { 1086 var checkedFromValue = $(this).attr("data-checked-from"); 1087 var theForm = $(this).closest("form"); 1088 var targetField = $(this); 1089 var sourceField = $(theForm).find(checkedFromValue); 1090 1091 $(sourceField).change(function() { 1092 if ($(sourceField).is(":checked")) 1093 $(targetField).attr("checked", true); 1094 else 1095 $(targetField).attr("checked", false); 1096 }); 1097 }); 1098 1099 // Animate panel menus 1100 $("li:has(> ul.sub-menu)").livequery(function() { 1101 $(this).hover(function() { 1102 $("> ul.sub-menu", this).show(); 1103 }, function() { 1104 $("> ul.sub-menu", this).hide(); 1105 }); 1106 }); 1107 1108 // Force confirmation on delete boxes 1109 $("a.confirm").livequery(function() { 1110 var titleText = $(this).attr("title"); 1111 $(this).click(function() { 1112 var message = titleText; 1113 if (message.length < 5) 1114 message = "Are you sure you want to perform this action?"; 1115 return confirm(message); 1116 }); 1117 }); 1118 1119 // Throw the person back one page 1120 $("a.return-link").livequery(function() { 1121 $(this).click(function() { 1122 history.back(); 1123 return false; 1124 }); 1125 }); 1126 1127 // Add tool-tips to jMonthCalendar events 1128 $("#jMonthCalendar div.Event a").livequery(function() { 1129 $(this).attr('title', $(this).html()); 1130 }); 1131 1132 // Add confirmation for mailto addresses 1133 $("a.mailto").livequery(function() { 1134 $(this).click(function() { 1135 var email = this.href.replace(/^mailto:\/*/, ""); 1136 return confirm("Send email to " + email + "?"); 1137 }); 1138 }); 1139 1140 // Put tool-tips on links with titles 1141 $("a[title]:not(.no-tooltip)").livequery(function () { 1142 // HACK: Since 'tooltip' removes the title from this element, save it on the 'saved-title' attribute for future reference 1143 $(this).attr("saved-title", $(this).attr("title")); 1144 $(this).tooltip({track:true}); 1145 }); 1146 1147 // put a tooltip on any element we specify 1148 $(".show-tooltip[title]").livequery(function() { 1149 // HACK: Since 'tooltip' removes the title from this element, save it on the 'saved-title' attribute for future reference 1150 $(this).attr("saved-title", $(this).attr("title")); 1151 $(this).tooltip({track:true}); 1152 }); 1153 1154 // Assign ajax tooltip to every element that is matched 1155 $(".ajax-tooltip[rel!='']").livequery(function() { 1156 var e = this; 1157 // HACK: Since 'tooltip' removes the title from this element, save it on the 'saved-title' attribute for future reference 1158 $(this).attr("saved-title", $(this).attr("title")); 1159 $(this).tooltip({ 1160 track: true,
1161 content: function() { 1162 var d = $(e).data('tooltip-cache'); 1163 if (!(d === undefined) && d.length > 0) 1164 return d; 1165 $.ajax({ 1166 async: false, 1167 url: $(e).attr('rel'), 1168 success: function(data){ 1169 d = data; 1170 } 1171 }); 1172 $(e).data('tooltip-cache', d); 1173 return d; 1174 } 1175 }); 1176 }); 1177 1178 // Load contents into a facebox on hover 1179 $(".hover-facebox[rel!='']").livequery(function() { 1180 var timeout = null; 1181 $(this).hover( 1182 function(){ 1183 timeout = setTimeout(function(e){$.facebox({ajax: $(e).attr("rel")})}, 1000, [this]); 1184 }, 1185 function(){clearTimeout(timeout)}); 1186 }); 1187 1188 // Setup ajax-load-targets 1189 $("a.ajax-load-target[rel]").livequery(function() { 1190 $(this).click(function(event) { 1191 1192 // find the object we're loading into 1193 var loadTarget = $("#" + $(this).attr("rel")); 1194 1195 // if we found one, load it. 1196 if ($(loadTarget).length > 0) { 1197 $(loadTarget).load($(this).attr("href")); 1198 } 1199 1200 // kill the click event 1201 return stopEvent(event); 1202 }); 1203 }); 1204 1205 // Run left-right assigners 1206 $("div.assigner-container").livequery(function() { 1207 $(this).assigner(); 1208 }); 1209 1210 // handle regular forms 1211 // IMPORTANT: this must be ran before all other .submit() binds! 1212 $("form").livequery(function() { 1213 1214 // turn on form validation 1215 $(this).validate(); 1216 1217 // handle form submission AFTER VALIDATION! 1218 $(this).submit(function() { 1219 1220 // clean up fields with inline-labels 1221 $(":input.empty-field", this).origVal(""); 1222 }); 1223 }); 1224 1225 // handle ajax-forms 1226 $("form.ajax-form").livequery(function() { 1227 if ($(this).attr('data-load-target') != null) 1228 var div = $("#" + $(this).attr('data-load-target')); 1229 else 1230 var div = $(this).closest('div')[0]; 1231 var theForm = this; 1232 $(this).submit(function(event) { 1233 1234 // create and append the loading mask 1235 appendLoadingMask(div); 1236 1237 // post! 1238 $(this).ajaxSubmit({ 1239 success: function(data){ 1240 if ($(theForm).data("callback")) { 1241 $(theForm).data("callback")(data); 1242 } 1243 1244 if (div) 1245 $(div).html(data); 1246 } 1247 }); 1248 return false; 1249 }); 1250 }); 1251 1252 // button link to cancel ajax form 1253 $("form.ajax-form a.cancel").livequery(function() { 1254 var p = $(this).closest('form'); 1255 $(this).click(function() { 1256 $(p).remove(); 1257 return false; 1258 }); 1259 }); 1260 1261 // post form via ajax and load into a facebox 1262 $("form.facebox-form").livequery(function() { 1263 $(this).submit(function() { 1264 1265 // perform submission 1266 $(this).ajaxSubmit({ 1267 success: function(data){ 1268 $.facebox(data); 1269 } 1270 }); 1271 return false; 1272 }); 1273 }); 1274 1275 // set page reload callback for ajax forms 1276 $("form.callback-reload").livequery(function() { 1277 $(this).data("callback", function(data) { 1278 if (data.length == 0) { 1279 $(document).trigger('close.facebox'); 1280 window.location.reload(); 1281 } 1282 }); 1283 }); 1284 1285 // Run tabbed windows 1286 $("div.tab-container.dynamic").livequery(function() { 1287 $(this).tabset(); 1288 }); 1289 1290 // Set up tab forms 1291 $("div.tab-container.dynamic div.tab-content-item:not('.static') form").livequery(function() { 1292 $(this).tabsetForm(); 1293 }); 1294 1295 /* ajax load any div that comes in with a rel attribute and the correct class 1296 Only one object is loading at a time to reduce the amount of hits on the server and to provide the user with a less abrasive UI */ 1297 var ajaxLoadRunning = false; 1298 $("div.ajax-on-load[rel],section.ajax-on-load[rel]").livequery(function() { 1299 var theDiv = this; 1300 1301 var load = function() { 1302 var ajaxLoadTimer = setInterval(function() { 1303 if (!ajaxLoadRunning) { 1304 ajaxLoadRunning = true; 1305 clearInterval(ajaxLoadTimer); 1306 ajaxLoadTimer = null; 1307 1308 appendLoadingMask(theDiv); 1309 $(theDiv).load($(theDiv).attr("rel"), function() { 1310 ajaxLoadRunning = false; 1311 }); 1312 } 1313 }, 100); 1314 } 1315 1316 // Store a pointer to the load function so it can be refreshed as necessary 1317 $(this).data("reload", load); 1318 load(); 1319 }); 1320 1321 $("div.ajax-on-load-async[rel]").livequery(function() { 1322 appendLoadingMask(this); 1323 $(this).load($(this).attr("rel")); 1324 }); 1325 1326 // table listings 1327 $("div.table-listing.dynamic").livequery(function() { 1328 1329 // elements and data 1330 var div = this; 1331 var meta = $(this).metadata(); 1332 var filters = $('> div.table-header form.filter-fields', this)[0]; 1333 var filterInputs = $('> div.table-header form.filter-inputs', this)[1]; 1334 var queryField = $("#field-filter-query", div)[0] || null; 1335 var queryElement = $("li.query", filters); 1336 if ($(this).hasClass("has-filter-form")) { 1337 var additionalFieldsForm = $("form.additional-fields", div); 1338 } else { 1339 var additionalFieldsForm = $("form.additional-fields").not(function() { 1340 1341 // make sure that the fields form is adjacent to the table listing div 1342 var elements = $("form.additional-fields,div.table-listing"); 1343 for (var i = 0; i < elements.length; i++) 1344 if (elements.eq(i).is(this) && elements.eq(i+1).is(div)) 1345 return false; 1346 return true; 1347 }); 1348 } 1349 var additionalFields = $(":input.listing-field", additionalFieldsForm); 1350 var sidebarForm = $("form.sidebar-fields"); 1351 var sortFields = $("> div.table-header form.sort-fields", div); 1352 var element_sortQuery = $(this).find("#field-sort-query"); 1353 if ($(sortFields).length > 0) 1354 var sortQuery = $("#field-sort-query", sortFields)[0]; 1355 var bulkActions = $("> div.table-header a.bulk-action", div); 1356 var head = $("> table > thead", this); 1357 var headOffset = $(head).offset(); 1358 var numberedCells = $("td.numbered, th.numbered", div); 1359 var sidebar = null; 1360 var sidebarOffset = 0; 1361 var fauxHead = null;
1362 var floatWithFauxHead = null; 1363 1364 // Modify listing id and, thereby, listingName for sessionParamsAndSavedReports call to be unique when listing is in a drill-down 1365 var parentDrillDownRow = $(div).closest("tr.edit-form-row").prev("tr"); 1366 if ($(parentDrillDownRow).length == 1) { 1367 1368 // Disable the listing-default div if it exists. Functionality not supported on drill-down listings 1369 $("a.set-listing-defaults > img", div).attr("title", "Functionality not supported on drill-down listings"); 1370 $("a.set-listing-defaults", div).replaceWith(function() { 1371 return $("img", this); 1372 }); 1373 1374 1375 if ($(additionalFieldsForm).length == 0) { 1376 1377 // Find the rel string to parse through 1378 var relString = $("a.row-is-open", parentDrillDownRow).attr("rel").replace(/.*\?/, ""); 1379 var paramsArray = relString.split("&"); 1380 1381 if (paramsArray.length > 0) { 1382 $(div).before('<form class="additional-fields" action="#" method="post"></form>'); 1383 } 1384 1385 // Create the additional fields form 1386 for (var i = paramsArray.length - 1; i >= 0; i--) { 1387 if (paramsArray[i].search("=") !== -1) { 1388 paramsArray[i] = decodeURIComponent(paramsArray[i]); 1389 $("form.additional-fields", $(div).closest("td")).append('<input type="hidden" class="input-hidden" id="field-additional-field-' + i + '" name="' + paramsArray[i].replace(/=.*/, "") + '" value=' + paramsArray[i].replace(/.*=/, "") + '>'); 1390 } 1391 }; 1392 1393 // We just created our additionalFieldsForm. Set it to the variable so everything else can use it. 1394 additionalFieldsForm = $("form.additional-fields", $(div).closest("td")); 1395 } 1396 } 1397 1398 // rebuild the value of the query from the spans 1399 var rebuildQuery = function() { 1400 var struct = $(queryElement).clone(); 1401 1402 // fix IN operations 1403 $("> span", struct).each(function() { 1404 if ($("span.op", this).attr("rel") == "IN") 1405 $("span.val", this).html("(" + $("span.val", this).html().replace(/<[^>]+>/g, "") + ")"); 1406 }); 1407 1408 // remove links 1409 $("a", struct).remove(); 1410 1411 // remove top spans, and replace inner spans with their contents or rels 1412 $("span.label", struct).unwrap(); 1413 $("span", struct).replaceWith(function() { 1414 var rel = $(this).attr("rel"); 1415 return (rel === undefined) ? $(this).html() : rel; 1416 }); 1417 1418 // set new query value without redundant spaces 1419 if (queryField != null) 1420 $(queryField).val(trim($(struct).html().replace(/\s+/g, " ").replace(/\[|\]/g, ""))); 1421 }; 1422 1423 // format filter query string 1424 var formatQuery = function() { 1425 if (queryField != null) { 1426 var q = $(queryField).val(); 1427 // wrap spans around: column names, operations, values, and logical operators 1428 q = q.replace( 1429 /(\w+)\s+([<>^*$=!]=|<|>|IN)\s+\(?((?:'[^']+'|\d+)\.?(?:,? *(?:'[^']+'|\d+))*|EMPTY)\)?(\W*)/g, 1430 '<span>[<span class="label" rel="$1">$1</span> <span class="op" rel="$2">$2</span> <span class="val">$3</span>]$4</span>' 1431 ); 1432 q = q.replace(/((&){2}|&&|\|{2})/g, '<span class="conn" rel="$1">$1</span>'); 1433 1434 // set query html up 1435 $(queryElement).html(q); 1436 1437 // if the operator is an IN 1438 $("> span", queryElement).each(function() { 1439 if ($("span.op", this).attr('rel') == "IN") { 1440 var span = this; 1441 var val = $("span.val", this); 1442 var values = $(val).html().split(','); 1443 $(val).html(""); 1444 for (var i = 0; i < values.length; i++) { 1445 $("<a href='#' />").html(trim(values[i])).click(function() { 1446 var prev = $(this).prev(); 1447 if ($(this).siblings().length == 0) { 1448 $("span.label a", span).click(); 1449 } else { 1450 if ($(this).is(":last-child")) 1451 $("span.comma", prev).remove(); 1452 $(this).remove(); 1453 rebuildQuery(); 1454 refreshView(1); 1455 } 1456 return false; 1457 }).appendTo(val); 1458 } 1459 $("> a:not(:last-child)", val).append($("<span class='comma'>,</span>")); 1460 } 1461 }); 1462 1463 // replace column names with appropriate labels 1464 $("span.label", queryElement).each(function(){ 1465 var label = $("span[rel='" + $(this).attr('rel') + "'] label", filterInputs); 1466 if (label.length > 0) 1467 $(this).html(label.html()); 1468 }); 1469 1470 // replace operation symbols with appropriate words 1471 $("span.op,span.conn", queryElement).each(function() { 1472 var key = $(this).html(); 1473 key = key.replace("==", "is"); 1474 key = key.replace("!=", "is not"); 1475 key = key.replace(/(<|<)=/, "less than / equal"); 1476 key = key.replace(/(<|<)/, "less than"); 1477 key = key.replace(/(>|>)=/, "greater than / equal"); 1478 key = key.replace(/(>|>)/, "greater than"); 1479 key = key.replace("*=", "contains"); 1480 key = key.replace("^=", "starts with"); 1481 key = key.replace("$=", "ends with"); 1482 key = key.replace(/\bIN\b/, "in"); 1483 key = key.replace("&&", "and"); 1484 key = key.replace("&&", "and"); 1485 key = key.replace("||", "or");
1486 $(this).html(key); 1487 }); 1488 1489 // add links to remove filters 1490 $("> span", queryElement).each(function(){ 1491 var span = this; 1492 var label = $("span.label", this).find("a").remove().end(); 1493 var link = $("<a href='#' title='Click to remove' />").click(function(){ 1494 1495 // remove previous logical operator if needed 1496 if ($("span.conn", span).length == 0) 1497 $(span).prev().find("span.conn").remove(); 1498 1499 // call mouse-out to make hint bubble disappear 1500 $(this).mouseout(); 1501 1502 // remove current span 1503 $(span).remove(); 1504 1505 // rebuild query and refresh 1506 rebuildQuery(); 1507 refreshView(1); 1508 1509 return false; 1510 }); 1511 1512 $(label).html($(link).html($(label).html().trim())); 1513 }); 1514 } 1515 }; 1516 1517 // rebuild the sorting query from list items 1518 var rebuildSort = function() { 1519 if (sortQuery !== undefined) { 1520 var q = ""; 1521 $("li.sort", sortFields).each(function() { 1522 var meta = $(this).metadata(); 1523 var val = meta.SORTNAME; 1524 val += ($(this).hasClass("sort-desc")) ? " DESC:" : " ASC:"; 1525 val += meta.SORTBY; 1526 if (q.length > 0) 1527 q += "|"; 1528 q += val; 1529 }); 1530 $(sortQuery).val(q); 1531 } 1532 }; 1533 1534 // helper to send ajax requests 1535 var refreshView = function (page, afterRefresh, rowKey) { 1536 1537 // If we have additional fields form, validate everything in it 1538 if ($(additionalFieldsForm).length > 0) { 1539 1540 // Normal form validation 1541 if (!validate_form($(additionalFieldsForm))) 1542 return; 1543 1544 // Do not allow empty dates on additional fields when not permitted or dates exceeding the max date range 1545 if ($(additionalFieldsForm[0].dateSpan).length > 0) { 1546 if ($(additionalFieldsForm[0].dateSpan).data("allow-empty-dates") != null) { 1547 if (!$(additionalFieldsForm[0].dateSpan).data("allow-empty-dates") && (additionalFieldsForm[0].startDate.value == '' || additionalFieldsForm[0].endDate.value == '')) { 1548 alert("Both start and end dates must be defined for this listing."); 1549 return; 1550 } 1551 } 1552 if ($(additionalFieldsForm[0].dateSpan).data("max-date-range") != '') { 1553 if ((new Date(new Date(additionalFieldsForm[0].endDate.value) - new Date(additionalFieldsForm[0].startDate.value)))/(1000 * 3600 * 24) >= $(additionalFieldsForm[0].dateSpan).data("max-date-range")) { 1554 alert("Date range cannot be larger than " + $(additionalFieldsForm[0].dateSpan).data("max-date-range") + " days"); 1555 return; 1556 } 1557 } 1558 } 1559 } 1560 1561 // Remove the per-page alert if present 1562 removePageAlertBox(); 1563 //$("#alert-box-per-page").remove(); 1564 1565 // Lookup the current page they are on, if they didn't pass anything 1566 if (page===undefined || page == null) { 1567 page = 1; 1568 if ($("ul.pagination li a.current", div).length > 0) 1569 page = $("ul.pagination li a.current", div).attr("rel"); 1570 else if ($("ul.pagination li :input.page-selector", div).length > 0) 1571 page = $("ul.pagination li :input.page-selector", div).val(); 1572 } 1573 1574 // grab page size (possibly from faux-thead, it also may not exist if paging is off) 1575 var perPage = $("#faux-thead input.per-page"); 1576 if ($(perPage).length == 0) 1577 perPage = $("input.per-page", div).length > 0 ? $("input.per-page", div) : null; 1578 1579 // store the value of the perPage field 1580 var perPageVal = perPage == null ? 0 : $(perPage).val(); 1581 1582 // grab page numbering (possibly from faux-thead, it also may not exist if paging is off) 1583 var numbered = $("#faux-thead input.numbered-rows"); 1584 if ($(numbered).length == 0) 1585 numbered = $("input.numbered-rows", div).length > 0 ? $("input.numbered-rows", div) : null; 1586 1587 // store whether the numbered checkbox is checked 1588 var numberedChecked = numbered == null ? false : $(numbered).is(":checked"); 1589 1590 // Handle invalid per-page values 1591 if (perPage !== null && (isNaN(perPageVal) || perPageVal < 1)) { 1592 $(perPage).focus().select(); 1593 createPageAlertBox(perPage, "Per page value must be a positive number."); 1594 } else if ($("ul.pagination li :input.page-selector", div).length > 0 && (isNaN(page) || page < 1)) { 1595 $("ul.pagination li :input.page-selector", div).focus().select(); 1596 createPageAlertBox($("ul.pagination li :input.page-selector", div), "Page value must be a positive number."); 1597 // Page and per-page values are ok..continue 1598 } else if (!isNaN(page) && !isNaN(perPageVal)) { 1599 1600 // create and append the loading mask 1601 if (rowKey === undefined || rowKey == null) 1602 appendLoadingMask(div); 1603 1604 // add page/perPage data 1605 var data = "page=" + parseInt(page) + "&perPage=" + perPageVal; 1606 1607 // add numbered rows choice 1608 if (numberedChecked) 1609 data += "&numberedRows=true"; 1610 1611 // add sorting data 1612 if (sortQuery !== undefined && $(sortQuery).val().length > 0) { 1613 data += "&sortQuery=" + $(sortQuery).val(); 1614 } else { 1615 var sortCol = $("th.sort-asc, th.sort-desc", head); 1616 if (sortCol.length > 0) { 1617 var aMeta = sortCol.metadata(); 1618 var sortAsc = (sortCol.is('.sort-asc')) ? true : false;
1619 data += "&sortColumn=" + aMeta.SORTNAME + "&sortBy=" + aMeta.SORTBY + "&sortAsc=" + sortAsc; 1620 } 1621 } 1622 1623 // grab filter query string 1624 if ((queryField != null) && $(queryField).val().length > 0) 1625 data += "&" + $(queryField).serialize(); 1626 1627 // grab additional filters 1628 if ($(":input", additionalFieldsForm).length > 0) { 1629 var extraData = $(additionalFieldsForm).serialize(); 1630 if (extraData.length > 0) 1631 data += "&" + extraData; 1632 } 1633 1634 // grab sidebar filters 1635 if ($(":input", sidebarForm).length > 0) { 1636 var sidebarData = $(sidebarForm).serialize(); 1637 if (sidebarData.length > 0) 1638 data += "&" + sidebarData; 1639 } 1640 1641 // Pass the rowKey up if requested 1642 if (rowKey !== undefined && rowKey != null) 1643 data += "&rowKey=" + rowKey; 1644 1645 // Add _method=GET to the data if the _method key does not exist yet - this ensures Coldroute can still find the properly formed URLs via the GET route search table 1646 if (!/_method=/i.test(data)) 1647 data += "&_method=GET"; 1648 1649 // post the data and refresh 1650 $.ajax({
1651 url: meta.FETCH, 1652 data: data + "&listingCall=true", 1653 type: meta.METHOD, 1654 success: function(data, status, xhr) { 1655 if (fauxHead != null) 1656 $(fauxHead).remove(); 1657 if (xhr.getResponseHeader("Content-Type") != 'text/javascript') { 1658 // We are updating the whole listing 1659 if (rowKey === undefined || rowKey == null) { 1660 $(div).replaceWith(data); 1661 1662 // We are only updating one row 1663 } else { 1664 // If we got data back just update the row's contents 1665 if (trim(data).length > 0) 1666 $("tr[data-row-key='" + rowKey + "']").html($(data).html()); 1667 // Otherwise remove the row because the query is no longer returning it 1668 else 1669 $("tr[data-row-key='" + rowKey + "']").remove(); 1670 } 1671 } 1672 } 1673 }); 1674 } 1675 1676 if (!(afterRefresh === undefined) && afterRefresh != null) 1677 afterRefresh(); 1678 }; 1679 1680 // Store refreshView function on this element, so that we can refresh the listing from anywhere 1681 $(this).data("refreshView", refreshView); 1682 1683 // helper to bind pagination actions 1684 var bindPagination = function(e) { 1685 1686 var focusOnBody = function () {window.location.href="#body";} 1687 1688 // paging links 1689 $('ul.pagination a:not(.view-all)', e).click(function() { 1690 refreshView($(this).attr('rel'), focusOnBody); 1691 return false; 1692 }); 1693 1694 // view all link 1695 $('ul.pagination a.view-all', e).click(function() { 1696 $("input.per-page",$(this).closest(".table-listing.dynamic")).val($(this).attr('rel')); 1697 refreshView(1, focusOnBody); 1698 return false; 1699 }); 1700 1701 // paging selectbox 1702 $(':input.page-selector', e).change(function() { 1703 refreshView($(this).val(), focusOnBody); 1704 return false; 1705 }); 1706 1707 // Bind Keys for < and > links 1708 $("ul.pagination", e).livequery(function() { 1709 1710 // Bind a key to the next item entry tab 1711 bindKey("ctrl+period", "Next Item Entry Tab", function(event, keys, combo) { 1712 var theUl = $("ul.pagination"); 1713 if ($("ul.pagination").closest(".table-listing.primary-listing").length > 0) { 1714 theUl = $("ul.pagination", ".table-listing.primary-listing").eq(0); 1715 } 1716 $("li.arrow", theUl).eq(2).find("a").click(); 1717 }, null); 1718 1719 // Bind a key to the previous item entry tab 1720 bindKey("ctrl+comma", "Previous Item Entry Tab", function(event, keys, combo) { 1721 var theUl = $("ul.pagination"); 1722 if ($("ul.pagination").closest(".table-listing.primary-listing").length > 0) { 1723 theUl = $("ul.pagination", ".table-listing.primary-listing").eq(0); 1724 } 1725 $("li.arrow", theUl).eq(1).find("a").click(); 1726 }, null); 1727 }); 1728 1729 // paging selectbox 1730 $('input.per-page', e).change(function() { 1731 refreshView(1, focusOnBody); 1732 }); 1733 1734 // numbered rows checkbox 1735 $('input.numbered-rows', e).change(function() { 1736 $(this).is(":checked") ? $(numberedCells).show() : $(numberedCells).hide(); 1737 $("input.numbered-rows").not(this).attr("checked", $(this).attr("checked") || false); 1738 $(fauxHead).remove(); 1739 fauxHead = null; 1740 $(window).scroll(); 1741 }); 1742 1743 return e; 1744 }; 1745 1746 // sort links in table headers 1747 var bindSortingLinks = function(e) { 1748 $('a.sort', e).click(function() { 1749 1750 // create and append the loading mask 1751 appendLoadingMask(div); 1752 1753 // decide on sorting direction to apply 1754 var th = $(this).closest("th"); 1755 var aMeta = $(th).metadata(); 1756 var sortAsc = (aMeta.ASC != "NO"); 1757 if ($(th).is(".sort-asc")) 1758 sortAsc = false; 1759 else if ($(th).is(".sort-desc")) 1760 sortAsc = true; 1761 1762 // create querystring with sort params 1763 var data = "sortColumn=" + aMeta.SORTNAME + "&sortBy=" + aMeta.SORTBY + "&sortAsc=" + sortAsc + "&perPage=" + parseInt($("input.per-page", div).val()); 1764 1765 // append all filters 1766 if (filters) { 1767 var filterData = $(filters).serialize(); 1768 if (filterData.length > 0) 1769 data += "&" + filterData; 1770 } 1771 if ($(":input", additionalFieldsForm).length > 0) { 1772 var extraData = $(additionalFieldsForm).serialize(); 1773 if (extraData.length > 0) 1774 data += "&" + extraData; 1775 } 1776 1777 // Add _method=GET to the data if the _method key does not exist yet - this ensures Coldroute can still find the properly formed URLs via the GET route search table 1778 if (!/_method=/i.test(data)) 1779 data += "&_method=GET"; 1780 1781 // send ajax post 1782 $.ajax({
1783 url: meta.FETCH, 1784 data: data + "&listingCall=true", 1785 type: meta.METHOD, 1786 success: function(data) { 1787 if (fauxHead != null) 1788 $(fauxHead).remove(); 1789 $(div).replaceWith(data); 1790 } 1791 }); 1792 return false; 1793 }); 1794 1795 return e; 1796 } 1797 1798 // bind click on links in multi-sort 1799 var bindMultiSortLinks = function(e) { 1800 $("a.remove", e).unbind("click").click(function() { 1801 if ($(e).siblings("li.sort").length == 0) 1802 $(e).siblings("li.placeholder").show() 1803 $(this).mouseout(); 1804 $(e).remove(); 1805 rebuildSort(); 1806 return false; 1807 }); 1808 $("a.flip", e).unbind("click").click(function() { 1809 var img = $("img", this); 1810 if ($(e).hasClass("sort-desc")) 1811 $(img).attr('src', img.attr('src').replace(/-desc\b/, "-asc")); 1812 else 1813 $(img).attr('src', img.attr('src').replace(/-asc\b/, "-desc")); 1814 $(e).toggleClass("sort-desc"); 1815 rebuildSort(); 1816 return false; 1817 }); 1818 return e; 1819 }; 1820 1821 // format query/filter spans when table loads 1822 formatQuery(); 1823 1824 // show numbered cells if needed 1825 $("input.numbered-rows", this).is(":checked") ? $(numberedCells).show() : $(numberedCells).hide(); 1826 1827 // bind click on existing multi-sort links 1828 $("li.sort", sortFields).each(function() { 1829 bindMultiSortLinks(this); 1830 }); 1831 1832 // make sort fields area droppable 1833 if ($(element_sortQuery).length > 0) { 1834 $(sortFields).droppable({ 1835 accept: "th.sortable", 1836 tolerance: "touch", 1837 activate: function(event, ui) { 1838 $(this).addClass("highlight-drop") 1839 }, 1840 deactivate: function(event, ui) { 1841 $(this).removeClass("highlight-drop") 1842 }, 1843 drop: function(event, ui) { 1844 1845 // grab sort metadata 1846 var meta = $(ui.draggable).metadata(); 1847 1848 // only allow three sorts, and no repeat items, then append the new item 1849 if ($("li[rel*='" + meta.SORTNAME + "']", this).length > 0) { 1850 alert("You are already sorting by this item."); 1851 } else if ($("li.sort", this).length > 4) { 1852 alert("You may only sort by five items."); 1853 } else { 1854 var item = $("<li class='sort' />") 1855 .attr("rel", meta.SORTNAME) 1856 .addClass("{SORTNAME: '" + meta.SORTNAME + "',SORTBY: '" + meta.SORTBY + "'}"); 1857 if ($(ui.draggable).hasClass("sort-desc")) 1858 $(item).addClass("sort-desc"); 1859 $("<a href='#' class='remove' title='Click to remove' />") 1860 .html($("span.header-text", ui.draggable).html()) 1861 .appendTo(item); 1862 $("<a href='#' class='flip' title='Click to flip sorting' />").append( 1863 $("img", ui.draggable) 1864 .clone() 1865 .attr('src', $("img", ui.draggable).attr('src').replace(/not-sorted/, "dark-asc")) 1866 ).appendTo(item); 1867 bindMultiSortLinks(item); 1868 $("ul li.refresh", this).before(item); 1869 $("li.placeholder", this).hide(); 1870 var label = $("> ul > li:eq(0)", this); 1871 if ($("span.important", label).length == 0) 1872 $(label).html($(label).html() + '<span class="important">*</span>'); 1873 rebuildSort(); 1874 } 1875 } 1876 }); 1877 1878 // make sortable headers draggable 1879 $("th.sortable", head).draggable({ 1880 helper: function() { 1881 var clone = $(this).clone()[0]; 1882 $(clone).css("background-color", $(this).closest("tr").css("background-color")); 1883 return clone; 1884 }, 1885 containment: div 1886 }); 1887 1888 // make sort area refresh button work 1889 $("li.refresh a", sortFields).click(function() { 1890 refreshView(1); 1891 return false; 1892 }); 1893 } 1894 1895 // Setup window scroll to execute its event stack 1896 $(window).scroll(function(event) { 1897 executeEventStack(this, "scroll", "after"); 1898 }); 1899 1900 // activate scrolling for thead 1901 if (!$(this).hasClass("no-floating-t-head")) { 1902 eventStackPush(window, "scroll", "after", "afterScroll:floatingThead"); 1903 $(window).unbind("afterScroll:floatingThead").bind("afterScroll:floatingThead", function() { 1904 if ($(window).scrollTop() > headOffset.top) { 1905 if (fauxHead == null && $(head).is(':visible')) { 1906 var tableOffset = (navigator.appName == "Netscape") ? 2 : (navigator.appName == "Microsoft Internet Explorer") ? 1 : 0; 1907 fauxHead = $("<div id='faux-thead' class='table-listing'><table cellspacing='0' cellpadding='0' border='0' /></div>") 1908 .find("table").append($(head).clone()).css("width", $(div).find("> table").width() + tableOffset).end() 1909 .find("a").click(function() { return false; }).end() 1910 .width($(div).width()) 1911 .css("font-size", $(div).css("font-size")) 1912 .prepend(bindPagination( 1913 $("div.pagination-fields", div).clone().css('background-color', $('> div.table-header', div).css('background-color')) 1914 )) 1915 .appendTo(document.body); 1916 bindSortingLinks($("thead tr", fauxHead)); 1917 $("th:visible", head).each(function() { 1918 $("th:eq(" + $(this).index() + ")", fauxHead).css("width", $(this).width() + 1); 1919 });
1920 if (floatWithFauxHead == null) { 1921 floatWithFauxHead = $(".float-with-floating-t-head").clone().addClass("float-with-floating-t-head-clone"); 1922 1923 $(floatWithFauxHead).width($(div).width()).appendTo(document.body); 1924 } 1925 $(fauxHead).css({ 1926 top: 0+$(floatWithFauxHead).outerHeight(), 1927 left: $(div).offset().left 1928 }); 1929 $(floatWithFauxHead).css({ 1930 top: 0, 1931 left: $(div).offset().left 1932 }); 1933 } 1934 1935 1936 } else if(fauxHead != null) { 1937 $(fauxHead).remove(); 1938 fauxHead = null; 1939 $(floatWithFauxHead).remove(); 1940 floatWithFauxHead = null; 1941 } 1942 if (sidebarOffset && $(window).scrollTop() > sidebarOffset.top && $(sidebar).height() < $(window).height()) { 1943 $(sidebar).css("top", $(window).scrollTop()); 1944 } else { 1945 $(sidebar).css("top", null); 1946 } 1947 }); 1948 } 1949 1950 // Chrome doesn't like .scroll(); So, don't do it. 1951 if (!/Chrome[\/\s](\d+\.\d+)/.test(navigator.userAgent)) 1952 $(window).scroll(); 1953 1954 // enable sliders on range fields 1955 $("div.range-slider", "form.filter-inputs").livequery(function() { 1956 1957 // store some values for later 1958 var sliderDiv = this; 1959 var meta = $(sliderDiv).metadata(); 1960 var rangeFields = $('#' + $(sliderDiv).attr('id') + ' ~ input'); 1961 var rangeValues = eval(meta.RANGEVALUES); 1962 1963 // save the values of the range fields so we can conditionally include them in the filters 1964 $(rangeFields).each(function () {$(this).data("loadVal", $(this).val());}); 1965 1966 // define the function that will update the spans so the user sees the change 1967 var updateRangeDisplay = function(from,to) { 1968 var spans = $(sliderDiv).next("span.range-values").find("span"); 1969 $(spans[0]).html($.formatNumber(from, {format:eval('numberFormats.' + meta.RANGEFORMAT), locale:"us"})); 1970 $(spans[1]).html($.formatNumber(to, {format:eval('numberFormats.' + meta.RANGEFORMAT), locale:"us"})); 1971 } 1972 1973 // call the update display function once to format the numbers initially 1974 updateRangeDisplay(rangeValues[0], rangeValues[1]); 1975 1976 // create the slider object 1977 $(sliderDiv).slider({ 1978 range: true, 1979 min: meta.RANGEMIN, 1980 max: meta.RANGEMAX, 1981 step: (meta.RANGEMAX - meta.RANGEMIN) / meta.RANGESTEPS, 1982 values: rangeValues, 1983 slide: function(event, ui) { 1984 $(rangeFields[0]).val(ui.values[0]); 1985 $(rangeFields[1]).val(ui.values[1]); 1986 updateRangeDisplay(ui.values[0], ui.values[1]); 1987 } 1988 }); 1989 }); 1990 1991 // add filter form 1992 $("form.filter-inputs", this).submit(function() { 1993 1994 // remove range filters if they haven't been changed (prevents undesired filters) 1995 $("div.range-slider", this).each(function() { 1996 // get the inputs under this slider, if any of them have their default values we're not changing them 1997 $('#' + $(this).attr('id') + ' ~ input').each(function() { 1998 if ($(this).val() == $(this).data("loadVal")) 1999 $(this).remove(); 2000 }); 2001 }); 2002 2003 // grab new filter input values 2004 $(":input:not(:button,:submit)", this).each(function() { 2005 var tagType = $(this).prop("type"); 2006 if ($(this).val().length > 0 && $(this).val() != "EMPTY" && (tagType.toLowerCase() != 'checkbox' || $(this).is(':checked'))) { 2007 var meta = $(this).metadata(); 2008 var name = $(this).attr("name").replace(/searchItem\[([^\]]+)\]/, "$1"); 2009 var value = $(this).val(); 2010 var query = $(queryField).val(); 2011 var exp = ""; 2012 2013 // simple function to add quotes to list 2014 var quote_value = function(str, list) { 2015 if (meta.TYPE != "string" && meta.TYPE != "char") 2016 return str; 2017 if (list === undefined || list == true) 2018 str = str.replace(/,/g, "','"); 2019 return "'" + str + "'"; 2020 }; 2021 2022 // escape operation (and consider IN operations if stackable) 2023 var op = meta.OPERATION.replace(/(\*|\$|\^)/, "\\$1"); 2024 if (meta.STACKABLE) 2025 op = "(?:IN|" + op + ")"; 2026 2027 // use regex to decide if value is already set 2028 var regex = new RegExp(name + "\\s+" + op + "\\s+\\(?((?:'[^']+'|\\d+)\\.?(?:,? *(?:'[^']+'|\\d+))*|EMPTY)\\)?"); 2029 var existing = regex.exec(query); 2030 2031 // build appropriate comparison string 2032 if (meta.STACKABLE) { 2033 2034 // die if value is not a valid list 2035 if (/^[^,]+(,[^,]+)*$/.test(value) == false) 2036 return false; 2037 2038 // if existing values exist 2039 if (existing != null) { 2040 2041 // replace existing values
2042 var existingValue = existing[1].replace(/'/g, ""); 2043 var existingArray = existingValue.split(","); 2044 for (var i = 0; i < existingArray.length; i++) 2045 value = listDelete(value, existingArray[i]); 2046 2047 // append new values to existing 2048 if (existingValue.length > 0 && value.length > 0) 2049 value = existingValue + "," + value; 2050 else if (existingValue.length > 0) 2051 value = existingValue; 2052 } 2053 2054 // create expression 2055 if (/,/.test(value)) 2056 exp = name + " IN (" + quote_value(value) + ")"; 2057 else 2058 exp = name + " " + meta.OPERATION + " " + quote_value(value, false); 2059 2060 } else { 2061 exp = name + " " + meta.OPERATION + " " + quote_value(value, false); 2062 } 2063 2064 // prevent duplicates 2065 if (existing != null) { 2066 2067 // replace to prevent duplicate operations 2068 query = query.replace(regex, exp); 2069 2070 } else { 2071 2072 // add value to query 2073 if (query.length > 0) 2074 query += " && "; 2075 query += exp; 2076 } 2077 2078 // update and show the query in the bar 2079 $(queryField).val(query); 2080 formatQuery(); 2081 } 2082 }); 2083 refreshView(1); 2084 return false; 2085 }); 2086 2087 // close button on filter form 2088 $("form.filter-inputs :input[name='closeFilters']", this).click(function() { 2089 $(this.form).hide(); 2090 return false; 2091 }); 2092 2093 // add filter link 2094 $('a.add-filter', this).click(function() { 2095 var div2 = $('form.filter-inputs', div); 2096 $(div2).toggle().find(":input:visible:first").focus(); 2097 return false; 2098 }); 2099 2100 // call add/remove from listing select 2101 $(":input.input-checkbox.listing-select:not(.toggle-checkboxes-in-column)").livequery(function() { 2102 2103 $(this).unbind("change"); 2104 2105 $(this).change(function() { 2106 var listingName = $(this).closest("div.table-listing").attr("id").replace(/^table-listing-/, '').replace(/-(\w)/g, function(str, match) {return match.toUpperCase();}); 2107 var columnName = $(this).data("column-name"); 2108 var controller = $(this).closest("div.table-listing").data("controller"); 2109 // var url = $(this).closest("div.table-listing").metadata().FETCH; 2110 // url = url.substring(url.lastIndexOf("/")).toString().replace(/,/g, "/"); 2111 var parent = $(this).parent(); 2112 2113 appendLoadingMask($(parent)); 2114 if ($(this).is(":checked")) { 2115 $.ajax({
2116 url: "/" + controller + "/add-to-listing-select?listingName=" + listingName + "&columnName=" + columnName + "&listingSelectData=" + $(this).val(), 2117 method: "get", 2118 success: function() {$destroy($(parent).find(".loading-mask"));} 2119 }); 2120 } else { 2121 $.ajax({ 2122 url: "/" + controller + "/remove-from-listing-select?listingName=" + listingName + "&columnName=" + columnName + "&listingSelectData=" + $(this).val(), 2123 method: "get", 2124 success: function() {$destroy($(parent).find(".loading-mask"));} 2125 }); 2126 } 2127 2128 }); 2129 }); 2130 2131 // on click, do bulk processing using check box values 2132 $(bulkActions).click(function(event) { 2133 var href = $(this).attr("href"), 2134 colIndex = $("> table > thead > tr > th:has(:checkbox)", div).index(), 2135 dataElements = $("> table > tbody > tr > td:nth-child(" + (colIndex + 1) + ") > input:checkbox:checked", div), 2136 method = $(this).data("method"), 2137 remote = $(this).data("remote"), 2138 confirmMessage = $(this).data("confirm") || "Are you sure you want to perform this action on the selected records?"; 2139 2140 // Can't do anything with no data. 2141 if ($(dataElements).length == 0) { 2142 alert("You must select at least one row to perform this action"); 2143 return stopEvent(event); 2144 2145 // Do it! 2146 } else { 2147 // Only confirm if we aren't already using a facebox to confirm the bulk action 2148 if ($(this).hasClass("facebox") || confirm(confirmMessage)) { 2149 // Handle the data attribute manually (modeled after jQuery-rails script), so we can do bulk actions with posts and such 2150 if (method != null) { 2151 var form = $('<form method="post" action="' + href + '"></form>'); 2152 if (remote != null) 2153 $(form).attr("data-remote", remote); 2154 $(form).hide().append('<input type="hidden" name="_method" value="' + method + '" />').appendTo("body"); 2155 $(dataElements).appendTo(form); 2156 appendLoadingMask($(bulkActions).closest("div")); 2157 $(form).bind("ajax:complete", function() {$(bulkActions).closest("div").find("div.loading-mask").remove();}); 2158 $(form).submit(); 2159 } else { 2160 if ($(this).hasClass("facebox")) 2161 $.facebox({ajax: href + (href.indexOf('?') != -1 ? '&' : '?') + $(dataElements).serialize()}); 2162 else 2163 window.location = href + (href.indexOf('?') != -1 ? '&' : '?') + $(dataElements).serialize(); 2164 } 2165 } 2166 return stopEvent(event); 2167 } 2168 }); 2169 2170 // on submit for additional fields and sidebar forms 2171 $(additionalFieldsForm).add(sidebarForm).unbind("submit.listing").bind("submit.listing", function() { 2172 refreshView(1); 2173 return false; 2174 }); 2175 2176 // allow sidebar content to be cleared 2177 $(":button.clear-sidebar", sidebarForm).unbind("click").click(function() { 2178 $("input[type='text'],textarea", sidebarForm).val(""); 2179 $("select", sidebarForm).val("EMPTY").change(); 2180 $("div#sidebar.expanded").click(); 2181 $(sidebarForm).submit(); 2182 }); 2183 2184 // on change for extra fields 2185 $(additionalFields) 2186 .filter(':checkbox,select').unbind("change.listingField").bind("change.listingField", function() { 2187 $(this.form).submit(); 2188 }).end() 2189 .filter(':button').unbind("click.listingField").bind("click.listingField", function() { 2190 $(this.form).submit(); 2191 }).end() 2192 .not(':checkbox,:button,select').unbind("keydown.listingField").bind("keydown.listingField", function(e) { 2193 var k = e.keyCode ? e.keyCode : e.which; 2194 if (k == 13) $(this.form).submit(); 2195 }); 2196 2197 // make sidebar operational 2198 if ($(sidebarForm).closest("div#body").length == 1) { 2199 $("body").addClass("has-sidebar"); 2200 var sidebarFormClick = false; 2201 $("<div />") 2202 .attr("id", "sidebar") 2203 .append($(sidebarForm).show()) // The sidebar starts out as hidden so it does not impact page loading performance 2204 .insertAfter("div#header") 2205 .click(function(e) { 2206 if (sidebarFormClick == false) { 2207 if ($(this).hasClass("expanded")) 2208 $(this).removeClass("expanded").css("left", $(this).width() * -1).next().css("margin-left", "16px"); 2209 else 2210 $(this).addClass("expanded").css("left", 0).next().css("margin-left", $(this).width() + 16); 2211 } 2212 sidebarFormClick = false; 2213 }) 2214 .each(function() { 2215 $(this).css("width", $(this).width() + 20); 2216 $(this).css({ 2217 left: $(this).width() * -1,
2218 backgroundPosition: ($(this).width() + 3) + "px center" 2219 }); 2220 if ($(sidebarForm).hasClass("expanded")) 2221 $(this).click(); 2222 }) 2223 .find("form").click(function(e) { 2224 sidebarFormClick = true; 2225 }); 2226 } 2227 sidebar = $("div#sidebar"); 2228 sidebarOffset = sidebar.offset(); 2229 2230 // links to [x] out an existing filter 2231 $('a.remove-filter', this).click(function() { 2232 $(this).closest("li").remove(); 2233 refreshView(1); 2234 return false; 2235 }); 2236 2237 // bind pagination to this div 2238 bindPagination(this); 2239 2240 // bind the sorting links to this div 2241 bindSortingLinks(this); 2242 }); 2243 2244 // Truly disable disabled buttons in listing facebox if it is disabled. 2245 $("div.user-default-options a.disabled").livequery(function() { 2246 $(this).click(function(event) { 2247 event.preventDefault(); 2248 }); 2249 }); 2250 2251 // enabled highlighting of rows on hover and click 2252 $("div.table-listing.highlight > table > tbody > tr:not(.edit-form-row)").livequery(function() { 2253 $(this).click(function() { 2254 $(this).hasClass("highlight") ? $(this).removeClass("highlight") : $(this).addClass("highlight"); 2255 }); 2256 $(this).hover(function() { 2257 $(this).addClass("hover"); 2258 }, function() { 2259 $(this).removeClass("hover"); 2260 }); 2261 }); 2262 2263 // enabled highlighting of rows on focus of input field 2264 $("div.table-listing.highlight-on-input-focus > table > tbody > tr:not(.edit-form-row) > td > :input").livequery(function() { 2265 $(this).focusin(function() { 2266 $(this).closest("tr").removeClass("highlight-on-input-focus").addClass("highlight-on-input-focus"); 2267 }); 2268 $(this).focusout(function() { 2269 $(this).closest("tr").removeClass("highlight-on-input-focus"); 2270 }); 2271 }); 2272 2273 // if a table has a column that contains "collapsable" text we want to enforce column widths to prevent jumpy listings 2274 $("div.table-listing table").livequery(function() { 2275 if ($("span[class*='toggle-text-']", this).length > 0) { 2276 2277 // loop the th cells and set their css width to their current width 2278 $("th:visible", this).each(function() { 2279 $(this).css("width", $(this).width() + 'px'); 2280 }); 2281 } 2282 }); 2283 2284 // If a table has aggregates we want to prepare our data and send it all up to CF 2285 $("div.table-listing.aggregated-listing").livequery(function() { 2286 var aggregates = $("div[data-aggregate-column]", this), 2287 columnData = {columns : {}}; 2288 2289 // Build the list of columns and types for CF 2290 $(aggregates).each(function() { 2291 columnData.columns[$(this).data("aggregate-column")] = { 2292 type: $(this).data("aggregate-type"), 2293 format: $(this).data("aggregate-format") != null ? $(this).data("aggregate-format") : '' 2294 }; 2295 appendLoadingMask(this); 2296 }); 2297 2298 // Prepare the listing name and url for sending the request 2299 var listingName = $(this).attr("id").replace(/^table-listing-/, '').replace(/-(\w)/g, function(str, match) { 2300 return match.toUpperCase(); 2301 }), 2302 url = $(this).metadata().FETCH.split("/"); 2303 2304 // Regex failed above, so we used a split method. This finishes cleaning up the url for maintaining the controller. 2305 url.pop(); 2306 url = url.toString().replace(/,/g, "/"); 2307 2308 // Send the data up to CF 2309 $.get("/" + $(this).data("controller") + "/get-aggregates-for-listing?listingName=" + listingName, columnData, function(data) { 2310 // Set the return value in the column headers based on column name 2311 $(aggregates).each(function() { 2312 var currColumnName = $(this).data("aggregate-column"), 2313 currTypes = $(this).data("aggregate-type").split(","), 2314 iEnd = currTypes.length; 2315 2316 // Remove the loading mask 2317 $destroy($(this).find(".loading-mask")); 2318 2319 // If this column has data we need to append it to the div 2320 for (var i=0; i < iEnd; i++) { 2321 2322 // Loop each type of aggregate requested (sum,avg,min,max,etc) and create a span for each value returned 2323 var currType = currTypes[i]; 2324 if (data[currColumnName][currType] !== undefined) { 2325 // Create a span to hold the new value 2326 var newSpan = $('<span class="help-bubble" title="Aggregate Type: ' + currType.toUpperCase() + '" />').html(data[currColumnName][currType]); 2327 $(this).append(newSpan); 2328 } 2329 } 2330 }); 2331 }, "json"); 2332 }); 2333 2334 // Set this on the trigger element inside the row 2335 $('.row-edit-opener,.facebox-opener').livequery(function() { 2336 2337 // Pass a guid as a listing name suffix to ensure unique session params for any listings within the drill-down 2338 $(this).attr("rel", $(this).attr("rel").replace(/\?/, "?listingNameSuffix=" + generateGUID().replace(/-/g, "") + "&"));
2339 2340 $(this).click(function(event) { 2341 2342 // Add the row-is-open class. row-is-open class should be removed before the edit-form-row is removed. 2343 $(this).removeClass("row-is-open").addClass("row-is-open"); 2344 2345 // function to bind form actions to facebox or inline form 2346 var bindFormActions = function (e, editorRow) { 2347 2348 // will only exist when we're coming from a row. Otherwise it will be a facebox 2349 var editor = (editorRow === undefined) ? $('div#facebox') : editorRow; 2350 2351 $('form:not(.ajax-form)', editor).attr("data-remote", true).bind("ajax:success", function(event, data, status, xhr) { 2352 if (event.target.tagName == 'FORM') { 2353 data = trim(data); 2354 if (data.length > 0 && (xhr.getAllResponseHeaders() || '').indexOf("text/javascript") == -1) { 2355 if (!$(data).is("tr")) { 2356 $(this).replaceWith(data); 2357 bindFormActions(e, editorRow); 2358 } else { 2359 updateRowAfterRowEditForm(data, editor); 2360 2361 //Now close the form container 2362 if(editorRow === undefined) { 2363 $(document).trigger('close.facebox'); 2364 } else { 2365 $($(editorRow).data('opener')).removeClass("editing-row"); 2366 $("a.row-edit-opener.row-is-open", $(editorRow).prev("tr")).removeClass("row-is-open"); 2367 $(editorRow).remove(); 2368 } 2369 } 2370 } 2371 } 2372 }); 2373 2374 $('.cancel', editor).click(function() { 2375 if(editorRow === undefined) { 2376 $(document).trigger('close.facebox'); 2377 } else { 2378 $($(editorRow).data('opener')).removeClass("editing-row"); 2379 $("a.row-edit-opener.row-is-open", $(editorRow).prev("tr")).removeClass("row-is-open"); 2380 $(editorRow).remove(); 2381 } 2382 }); 2383 }; 2384 2385 // function to update form fields in response to json data 2386 var updateRowAfterRowEditForm = function (data, formContainer) { 2387 2388 // Retrieve the row we modified 2389 var theRow = $(formContainer).data('opener'); 2390 2391 // get the classes 2392 var classList = $(theRow).attr('class'); 2393 2394 // replace the row and add old classes 2395 theRow = $(data).replaceAll(theRow).attr('class', classList); 2396 2397 // put new variable back in opener storage 2398 $(formContainer).data('opener', theRow); 2399 }; 2400 2401 // store the current TR element 2402 var theTR = $(this).closest('tr'), 2403 theTable = $(this).closest("table"); 2404 2405 // Store the current width of this table in its data 2406 if ($(theTable).data("currentWidth") == null) 2407 $(theTable).data("currentWidth", $(theTable).width()); 2408 2409 // close inline edit form if it is open 2410 if ($(theTR).next("tr.edit-form-row").length > 0) { 2411 $(theTR).removeClass("editing-row"); 2412 $("a.row-edit-opener.row-is-open", $(theTR)).removeClass("row-is-open"); 2413 $(theTR).next("tr.edit-form-row").remove(); 2414 return false; 2415 } 2416 2417 // We're loading via facebox 2418 if($(this).hasClass('facebox-opener')) { 2419 2420 // bind into facebox reveal event 2421 $(document).bind('afterReveal.facebox', bindFormActions); 2422 2423 // perform asynchronous facebox load 2424 $.ajaxSetup({async: false}); 2425 $.facebox({ajax: $(this).attr('rel')}); 2426 $.ajaxSetup({async: true}); 2427 2428 // store the TR for the facebox form to use 2429 $('div#facebox').data('opener', theTR); 2430 2431 // We're loading via a row 2432 } else { 2433 2434 // Find the row we're editing 2435 var editRowNumber = ($("tr[id*='edit-form-row']").length + 1); 2436 2437 // Add a new row after it with a unique id 2438 var editorRow = $("<tr id='edit-form-row-" + editRowNumber + "' class='edit-form-row " + $(theTR).attr('class') + "'><td colspan='" + $('td', theTR).length + "'></td></tr>") 2439 .removeClass("hover").removeClass("highlight") 2440 .insertAfter($(theTR).addClass("editing-row")); 2441 2442 // Load into the new row storing the TR that opened this one so we can reopen it when necessary 2443 $(editorRow).data('opener', theTR).find('td').load($(this).attr('rel'), '', function() { 2444 bindFormActions(event, editorRow); 2445 $(editorRow).show(0, function() { 2446 var newTableWidth = $(theTable).width(); 2447 if (newTableWidth > $(theTable).data("currentWidth")) { 2448 $(theTable).css("width", newTableWidth);
2449 $(theTable).data("currentWidth", newTableWidth); 2450 } 2451 }); 2452 }).css('padding', '1em'); 2453 } 2454 2455 return false; 2456 }); 2457 }); 2458 2459 $("div.ui-association").livequery(function() { 2460 $(this).association(); 2461 }); 2462 2463 $("div.ui-association").find('input').livequery(function() { 2464 $(this).keyup(function() { 2465 if ($(this).attr('name').indexOf('[undefined]') >= 0 && $(this).val() != "") 2466 $(this).change(); 2467 }); 2468 }); 2469 2470 $("div.edit-section > h3 > span.link.collapse a").livequery(function() { 2471 // store handlers for faster access 2472 var div = $(this).closest("div.edit-section"); 2473 var content = $("> div.content", div); 2474 var image = $("img", this); 2475 var theLink = this; 2476 2477 $(this).click(function(event) { 2478 if ($(div).hasClass("collapsed")) { 2479 // hide other contents 2480 $("div.edit-section").not(".collapsed").find("h3 > span.link.collapse a").each(function() { 2481 $(this).click(); 2482 }); 2483 // expand the contents 2484 $(image).attr("src", "/tpWheels/images/icons/collapse.png"); 2485 $(theLink).attr("title","Collapse"); 2486 $(div).removeClass("collapsed"); 2487 $(content).slideDown(); 2488 } else { 2489 // hide the contents 2490 $(content).slideUp(function() { 2491 $(image).attr("src", "/tpWheels/images/icons/expand.png"); 2492 $(theLink).attr("title","Expand"); 2493 $(div).addClass("collapsed"); 2494 }); 2495 } 2496 event.preventDefault(); 2497 }); 2498 // start all steps in collapsed mode except for the first one by default, unless there's only one 2499 if ($("div.edit-section > h3 > span.link.collapse a").length == 1) 2500 $(this).click(); 2501 }); 2502 2503 $(":input.numeric-input-only").livequery(function() { 2504 // Blocks most invalid keystrokes from getting into numerc only fields. 2505 // [ ] { } " - These strokes are not prevent due to complications with regex formatting. However, to input one of these, you have to try hard. 2506 // _ and % are not included, and should not be. They are handy for regexing in SQL. 2507 var invalidKeys = new RegExp("[a-zA-Z,./;'<>?:]"); 2508 $(this).keypress(function(event) { 2509 var key = String.fromCharCode(event.which ? event.which : event.keyCode); 2510 if (invalidKeys.test(key)) 2511 return stopEvent(event); 2512 }); 2513 }); 2514 2515 // Append spinner to ajax forms and ajax links which have spinner class 2516 $(document).on("ajax:before", "form[data-remote]:not(.no-spinner),a[data-remote].spinner", function() { 2517 appendLoadingMask(this); 2518 }).on("ajax:complete ajax:error", "form[data-remote]:not(.no-spinner),a[data-remote].spinner", function() { 2519 $destroy($(this).find("> div.loading-mask")); 2520 }); 2521 2522 // Stop requested events for elements that specify them 2523 $("*[data-prevent-bubbling]").livequery(function() { 2524 var eventsToStop = $(this).attr("data-prevent-bubbling").split(","); 2525 for (var key in eventsToStop) 2526 $(this).bind(eventsToStop[key], function(event) {return event.stopPropagation();}); 2527 }); 2528 2529 $(":input.spinner").livequery(function() { 2530 $(this).click(function() { 2531 appendLoadingMask($(this).parent()); 2532 }); 2533 }); 2534 2535 // Add loading mask for non-ajax spinner links; Remove it when user clicks cancel on a confirm link 2536 $("a.spinner:not([data-remote])").livequery(function() { 2537 $(this).click(function() { 2538 appendLoadingMask($(this).parent()); 2539 }); 2540 }).on("confirm:complete", function(e,answer) { 2541 if (!answer) $destroy($(this).parent().find("> div.loading-mask")); 2542 }); 2543 2544 $("div#column-selection-prompt").livequery(function() { 2545 var element_columnSelectionPrompt = this; 2546 // Select/Deselect all columns 2547 $('#field-toggle-select-all-0', element_columnSelectionPrompt).change(function() { 2548 if (!$(this).is(":checked")) 2549 $('input[name="selectedColumns"]', element_columnSelectionPrompt).attr('checked', false); 2550 else 2551 $('input[name="selectedColumns"]', element_columnSelectionPrompt).attr('checked', true); 2552 }); 2553 }); 2554 2555 // Before exporting attempt to pass up the filters for exporting to html / pdf 2556 $("div#export-prompt").livequery(function() { 2557 var theExportPrompt = this, 2558 theForm = $(this).find("form"), 2559 cookieId = "export-download-" + generateGUID(); 2560 2561 // Mark this export's cookie as preparing and add it to the form for the server to use 2562 setCookie(cookieId, "preparing", 1); 2563 $(theForm).append($('<input type="hidden" name="cookieId" value="' + co
2563okieId + '"/>"')); 2564 $(this).attr("cookieId", cookieId); 2565 2566 // Default the print title if we have an h2 on the page 2567 if ($(document.body).find("h2").length > 0) { 2568 var printTitle = $(document.body).find("h2:first").clone().find("img,a").remove().end().text(); 2569 $("input#field-print-title", this).val(printTitle); 2570 } 2571 2572 // If the user chooses html or pdf, display the printer controls (title & print filters checkbox) 2573 $(":input[name='exportFormat']", this).change(function() { 2574 var thisVal = $(this).val(); 2575 if (thisVal == "html" || thisVal == "pdf") 2576 $("ul#export-print-controls").removeClass("hidden"); 2577 else 2578 $("ul#export-print-controls").removeClass("hidden").addClass("hidden"); 2579 }); 2580 2581 // Append the additional-fields form data from the listing to the facebox's form 2582 var filters = []; 2583 $(":input:visible:not(:button,:submit)","form.additional-fields").each(function() { 2584 var thisVal = $(this).val(); 2585 2586 // Include only non-empty inputs 2587 if (!(thisVal == "" || thisVal == 'EMPTY' || ($(this).is(":checkbox") && !$(this).is(":checked")))) { 2588 var parent = $(this).parent(), 2589 label = $(parent).is("label") ? parent : $(parent).find("label[for='" + $(this).attr("id") +"']"); 2590 2591 // If we didn't find a label be lazy about it. Grab the first visible one under the parent if possible. 2592 if (label == null || $(label).length == 0) 2593 label = $(parent).find("label:visible").first(); 2594 2595 // Remove unwanted content from the label 2596 label = $(label).find("img,a").remove().end().html(); 2597 2598 // Match the value against the label 2599 filters[filters.length] = { 2600 label: label, 2601 value: $(this).is("select") ? $(this).find("option:selected").html() : $(this).val() 2602 }; 2603 } 2604 }); 2605 2606 // Append the filter values as a hidden input on the form 2607 var filterInput = $('<input type="text" name="additionalFieldsData" id="additional-fields-data" />').hide(); 2608 $(filterInput).val(JSON.stringify(filters)).appendTo($(this).find("form")); 2609 2610 // Select/Deselect all columns 2611 $('#field-toggle-select-all-0', theExportPrompt).change(function() { 2612 if (!$(this).is(":checked")) 2613 $('input[name="exportColumns"]').attr('checked', false); 2614 else 2615 $('input[name="exportColumns"]').attr('checked', true); 2616 }); 2617 2618 $(theForm).submit(function() { 2619 setCookie(cookieId, "running", 1); 2620 var intervalHandle = setInterval(function() { 2621 if (getCookie(cookieId) == "complete") { 2622 $(document).trigger('close.facebox'); 2623 deleteCookie(cookieId); 2624 $(theExportPrompt).attr("cookieId", ""); 2625 clearInterval(intervalHandle); 2626 } 2627 }, 1000); 2628 }); 2629 }, function() { 2630 if ($(this).attr("cookieId") !== undefined && $(this).attr("cookieId") != "") 2631 deleteCookie($(this).attr("cookieId")); 2632 }); 2633 2634 // Make force-no-wrap elements auto-size to the necessary width to accomodate the direct elements underneath it 2635 $(".force-no-wrap").livequery(function() { 2636 var container = this, 2637 directElements = $(this).find("> *:visible"), 2638 resizeTo = 0; 2639 2640 // Sum up the outerWidth (includes padding, margin, and borders) for the direct descendants 2641 $(directElements).each(function() { 2642 if (/\b(x-small|small|medium|large|x-large|full)\b/.test($(this).attr("class"))) 2643 $(this).width(parseInt($(this).width())); 2644 resizeTo += $(this).outerWidth(true); 2645 }); 2646 2647 // Fix the size. 2648 $(container).width(resizeTo); 2649 }); 2650 2651 // Enable CKEditors on textareas (Ensure CKEDITOR.config.replaceClass is set to '' in ckeditor/config.js. replace can be called manually to allow for specific options per instance.) 2652 $("textarea.ckeditor").livequery(function() { 2653 var config = {}; 2654 if ($(this).attr("data-ckeditor")) 2655 $.extend(config, $.parseJSON(urldecode($(this).attr("data-ckeditor")))); 2656 else if ($(this).data("ckeditor")) 2657 $.extend(config, $.parseJSON(urldecode($(this).data("ckeditor")))); 2658 if ($(this).attr("data-width")) 2659 $.extend(config, {width: $(this).attr("data-width")}); 2660 CKEDITOR.replace(this, config); 2661 }); 2662 2663 // Enable refresh every 2664 $("select#refresh-window-after-x-seconds").livequery(function() { 2665 2666 // I'm going on break 2667 var refresher = this; 2668 var refresherImage = $("img#refresh-window-after-image"); 2669 2670 // Function that will submit the form and refresh the page 2671 var refresh = function() { 2672 $($(refresher)[0].form).submit(); 2673 } 2674 2675 // Default the timer 2676 if ($(refresher).val() == 0) 2677 $(refresher).data("timer", null); 2678 else 2679 $(refresher).data("timer", setInterval(refresh, $(refresher).val() * 1000)); 2680 2681 // Setup the click on the image 2682 $(refresherImage).css("cursor", "pointer").click(function(event) {refresh(); return stopEvent(event);}); 2683 2684 // On change reset the timer
2685 $(refresher).change(function(event) { 2686 2687 // Clear out the interval that's currently there 2688 if ($(refresher).data("timer") != null) { 2689 clearInterval($(refresher).data("timer")); 2690 $(refresher).data("timer", null); 2691 } 2692 2693 // Setup the new interval 2694 if ($(refresher).val() > 0) 2695 $(refresher).data("timer", setInterval(refresh, $(refresher).val() * 1000)); 2696 2697 return stopEvent(event); 2698 }); 2699 }); 2700 2701 // allow remote form data to come from other sources than just the from (such as ui:listings) 2702 $("form[data-remote][data-include-fields-from]").livequery("ajax:beforeSend", function(event, xhr, settings) { 2703 settings.data += "&" + $(":input", $(this).data("include-fields-from")).filter(function() { 2704 return this.form == null && !$(this).is(":checkbox:not(:checked)"); 2705 }).serialize(); 2706 }); 2707 2708 // allow very general polling behavior; expects javascript partial to be returned over ajax 2709 $("[data-poll]").livequery(function() { 2710 var poll_element = this, 2711 poll_url = $(this).attr("data-poll"), 2712 poll_time = $(this).attr("data-poll-interval") != null ? $(this).attr("data-poll-interval") : 5000, 2713 poll_interval = null, 2714 poll_last_runat = null, 2715 poll_on_load = $(this).attr("data-poll-on-load") != null ? $(this).attr("data-poll-on-load") == 'true' : true, 2716 2717 pollIntervalFunction = function() { 2718 // Only let the poll run if this is the active tab 2719 if (window.name == getCookie("activeTab")) { 2720 $.get(poll_url, {poll: true}, function(data) { 2721 if (data.length > 0) 2722 eval(data); 2723 }, 'text'); 2724 poll_last_runat = new Date(); 2725 } 2726 }, 2727 2728 startPolling = function() { 2729 stopPolling(); 2730 poll_interval = setInterval(pollIntervalFunction, poll_time); 2731 $(poll_element).attr("data-poll-interval-id", poll_interval); 2732 2733 /* Handle when the element loads for the first time and when the user is switching between tabs. 2734 If this is the element's creation and we want to poll on load, run the polling function. 2735 If this is not the element's creation but the startPolling function is called for this element (the user switched back to the tab) \ 2736 check if the tab was idle long enough for at least one run to have occured. If so, run the polling function. */ 2737 if ((poll_last_runat == null && poll_on_load) || (poll_last_runat != null && new Date() - poll_last_runat >= poll_time)) 2738 pollIntervalFunction(); 2739 }, 2740 2741 stopPolling = function() { 2742 if (poll_interval != null) { 2743 clearInterval(poll_interval); 2744 poll_interval = null; 2745 $(poll_element).attr("data-poll-interval-id", poll_interval); 2746 } 2747 }; 2748 2749 // Bind the functions to this element's data so the window blur/focus can use them 2750 $(this).data("stopPolling", stopPolling).data("startPolling", startPolling); 2751 2752 // Kick off the polling no matter what this element loads. The pollIntervalFunction will handle if it is the active tab or not. 2753 startPolling(); 2754 }, function() { 2755 if ($(this).attr("data-poll-interval-id") != null) 2756 clearInterval($(this).attr("data-poll-interval-id")); 2757 }); 2758 2759 // Automatically post on enter 2760 $("form.submit-on-enter :input").livequery("keypress", function(event) { 2761 var key = event.keyCode ? event.keyCode : event.which; 2762 if (key == KEYENUM.RETURN) { 2763 $(this.form).submit(); 2764 return stopEvent(event); 2765 } 2766 }); 2767 2768 // Bind elements to toggle the display of a specified element based on a value 2769 $(":input[data-toggle-show]").livequery(function() { 2770 var matcher = $(this).attr("data-toggle-show"); 2771 var exactVal = $(this).attr("data-toggle-value"); 2772 var emptyVal = $(this).is("select") ? "EMPTY" : ""; 2773 $(this).change(function() { 2774 var val = $(this).val(); 2775 if ($(this).is(":checkbox:checked") || exactVal != null && val == exactVal || exactVal == null && val != emptyVal) 2776 $(matcher).show(); 2777 else 2778 $(matcher).hide(); 2779 }).change(); 2780 }); 2781 2782 // Do the oposite of the above function. 2783 $(":input[data-toggle-hide]").livequery(function() { 2784 var matcher = $(this).attr("data-toggle-hide"); 2785 var exactVal = $(this).attr("data-toggle-value"); 2786 var emptyVal = $(this).is("select") ? "EMPTY" : ""; 2787 $(this).change(function() { 2788 var val = $(this).val(); 2789 if ($(this).is(":checkbox:checked") || exactVal != null && val == exactVal || exactVal == null && val != emptyVal) 2790 $(matcher).hide(); 2791 else 2792 $(matcher).show(); 2793 }).change(); 2794 }); 2795 2796 // allow checkboxes to act like radio buttons 2797 $("input[type='checkbox'].radio").on("change", function(e) { 2798 if ($(this).is(':checked')) 2799 $("input[type='checkbox'][name='" + $(this).attr("name") + "'].radio").not(this).attr("checked", false).change(); 2800 }); 2801 2802 // enable a red X to reset certain field values 2803 $("form.additional-fields :input:visible:not(:checkbox, :radio), form.additional-fields :input.autocomplete, :input:visible.clearable").livequery(function() { 2804 var $input = $(this), 2805 value = $input.is("select") ? $input.find("option:first-child").attr("value") : '', 2806 $label = $(this).closest("label").add("label[for='" + $(this).attr("id") + "']"); 2807 if ($label.length == 1) { 2808 var link = $("<a href='#' class='clear-field' tabindex='-1'>[x]</a>").click(function(e) { 2809 $input.not(":disabled").val(value).change().focus(); 2810 if ($input.is(".autocomplete")) { 2811 $label.next("input.ui-autocomplete-input", $(this)).val(""); 2812 $input.next("input.autocomplete-hidden").val('').change(); 2813 } 2814 return stopEvent(e); 2815 }); 2816 $label.append(link.hide()); 2817 var show_or_hide = function(e) { 2818 $input.val() == value ? link.hide() : link.show(); 2819 }; 2820 show_or_hide(); 2821 $input.bind("change.clearable", show_or_hide); 2822 $input.bind("change.autocomplete", show_or_hide); 2823 } 2824 }); 2825 2826 // do the Christopher Lambert thing 2827 $(".there-can-be-only-one :input").livequery('change', function() { 2828 $(this).closest(".there-can-be-only-one").find(":input").not(this).not(".there-can-be-only-one-exception").val(''); 2829 }); 2830 2831 // do the Christopher Lambert thing again..again 2832 // necessary for all inputs to be affected to have the data attribute on them 2833 $(":input[data-there-can-be-only-one-within]").livequery('change', function() { 2834 var theDataValue = $(this).attr("data-there-can-be-only-one-within"); 2835 $(this).closest(theDataValue).find(":input[data-there-can-be-only-one-within]").not(this).not(":input[type='checkbox']").filter(function() { 2836 return $(this).attr("data-there-can-be-only-one-within") == theDataValue; 2837 }).val(''); 2838 $(this).closest(theDataValue).find(":input[data-there-can-be-only-one-within,type='checkbox']").not(this).filter(function() { 2839 return $(this).attr("data-there-can-be-only-one-within") == theDataValue; 2840 }).attr("checked", false); 2841 }); 2842 2843 // Don't bind crop images with livequery because the plugin clones the source img object, causing a loop 2844 $("img.crop-image").each(function() { 2845 2846 // Setup the minimum width and height values for the selection box 2847 var minWidth = $(this).attr("data-min-width") == null ? 500 : parseInt($(this).attr("data-min-width")); 2848 var minHeight = $(this).attr("data-min-height") == null ? 500 : parseInt($(this).attr("data-min-height")); 2849 2850 // Bind the crop functionality to the image 2851 var img = this; 2852 $.Jcrop(this, { 2853 allowSelect: false, 2854 minSize: [minWidth,minHeight], 2855 setSelect: [25,25,minWidth-25,minHeight-25], 2856 onChange: setCoords, 2857 onSelect: setCoords 2858 }); 2859 2860 // Function to set the coordinates in the surrounding form, if present 2861 function setCoords(coords) { 2862 if ($(img).closest("form").length > 0) { 2863 var theForm = $(img).closest("form"); 2864 $("input#field-crop-coords-x", theForm).val(coords.x); 2865 $("input#field-crop-coords-y", theForm).val(coords.y); 2866 $("input#field-crop-coords-width", theForm).val(coords.w); 2867 $("input#field-crop-coords-height", theForm).val(coords.h);
2868 } 2869 } 2870 }); 2871 2872 // Load a specified image file into the facebox 2873 $("a.facebox-image-preview").livequery(function() { 2874 $(this).click(function(event) { 2875 $.facebox({image: $(this).attr("rel")}); 2876 return stopEvent(event); 2877 }); 2878 }); 2879 2880 // Collapse divs in a tree structure 2881 $("div.collapsible").livequery(function() { 2882 var div = this; 2883 $("a.collapse-all", this).click(function(e) { 2884 $("li > ul", div).hide(); 2885 $("a.expand-list", div).html("+"); 2886 return stopEvent(e); 2887 }); 2888 $("a.expand-all", this).click(function(e) { 2889 $("li > ul", div).show(); 2890 $("a.expand-list", div).html("-"); 2891 return stopEvent(e); 2892 }); 2893 $("li > ul", div).hide(); 2894 $("li", this).css("list-style", "none").each(function() { 2895 var li = this; 2896 if ($("> ul", li).length > 0) { 2897 $(this).prepend($("<a href='#' class='expand-list'>+</a>").click(function(e){ 2898 var ul = $("> ul", li); 2899 if (ul.is(":visible")) { 2900 $(this).html("+"); 2901 $(ul).hide(); 2902 } else { 2903 $(this).html("-"); 2904 $(ul).show(); 2905 } 2906 return stopEvent(e); 2907 })); 2908 } else { 2909 if ($(li).siblings().find("ul").length > 0) 2910 $(li).prepend("<span class='expand-list'> </span>"); 2911 } 2912 }); 2913 }); 2914 2915 // Bind date-span inputs to request the new dates from the server and change the fields in the form 2916 $("form select.date-span").livequery(function() { 2917 2918 // store the select object so the callback from the post can use it 2919 var timeSpanSel = this; 2920 2921 $(timeSpanSel).change(function(event) { 2922 if ($(timeSpanSel).val() != "EMPTY") { 2923 2924 // use Coldfusion to calculate our date range since Javascript doesn't have a built in function 2925 $.post("/home/ajaxCalculateDateRange", {dateSpan: $(timeSpanSel).val()}, function(data){ 2926 2927 // set the values that Coldfusion returned 2928 $(timeSpanSel.form.startDate).val(data.STARTDATE); 2929 $(timeSpanSel.form.endDate).val(data.ENDDATE); 2930 }, "json"); 2931 } 2932 }); 2933 2934 // add events to startDate / endDate forms 2935 $(timeSpanSel.form.startDate).add(timeSpanSel.form.endDate) 2936 2937 // if they hit enter on the date fields to submit we want to blur the field 2938 .keypress(function(event) { 2939 var code = event.keyCode ? event.keyCode : event.which; 2940 if (code == 13) { 2941 $(this).blur(); 2942 $(this.form).submit(); 2943 } 2944 }) 2945 2946 // on focus store the value 2947 .focus(function(event) { 2948 $(this).data('focusValue', $(this).val()); 2949 }) 2950 2951 // on change see if it changed 2952 .change(function(event){ 2953 2954 // Validate the dates are in order. If not show an alert and set the one that was just changed to the other. 2955 var startDate = new Date(timeSpanSel.form.startDate.value), endDate = new Date(timeSpanSel.form.endDate.value); 2956 if (startDate > endDate) { 2957 createPageAlertBox(this, 'The start date must be before the end date.'); 2958 $(this).val($(this).prop("name") == 'startDate' ? timeSpanSel.form.endDate.value : timeSpanSel.form.startDate.value).focus(); 2959 } 2960 2961 if (timeSpanSel.form.startDate.value == '' && timeSpanSel.form.endDate.value == '' && $(timeSpanSel).find("option[val='all']").length > 0) 2962 $(timeSpanSel).val("all"); 2963 else if ($(this).val() != $(this).data('focusValue')) 2964 $(timeSpanSel).val("EMPTY"); 2965 }) 2966 2967 // On blur, if they are the same, remove the page alert box. 2968 .blur(function() { 2969 var startDate = new Date(timeSpanSel.form.startDate.value), endDate = new Date(timeSpanSel.form.endDate.value); 2970 if (startDate <= endDate) 2971 removePageAlertBox(); 2972 }); 2973 }); 2974 2975 // Enable master toggling of checkboxes in a listing column 2976 $("table th :input.toggle-checkboxes-in-column").livequery(function() { 2977 $(this).click(function(event) { 2978 var cellIndex = $(this).closest("th").index(); 2979 var listingTable = $(this).closest("table"); 2980 2981 if ($(this).attr("data-confirm") && !confirm($(this).data("confirm"))) 2982 return stopEvent(event); 2983 2984 // Find the cells that correspond 2985 $("td:nth-child(" + (cellIndex + 1) + ") :input[type='checkbox']", listingTable).attr("checked", $(this).is(":checked")).change(); 2986 }); 2987 }); 2988 2989 // Enable master toggling of checkboxes in a table 2990 $("table th :input.toggle-checkboxes-in-table").livequery(function() { 2991 $(this).click(function() { 2992 var listingTable = $(this).closest("table"); 2993 // Find the cells that correspond 2994 $("td :input[type='checkbox'][checked='" + !$(this).is(":checked") + "']", listingTable).attr("checked", $(this).is(":checked")).change(); 2995 }); 2996 }); 2997 2998 // Enable master toggling of checkboxes in a form 2999 $(":input.toggle-checkboxes-in-form").livequery(function() { 3000 $(this).click(function() { 3001 var form = $(this).closest("form"); 3002 // Find the cells that correspond 3003 $(":input[type='checkbox'][checked='" + !$(this).is(":checked") + "']", form).attr("checked", $(this).is(":checked")).change(); 3004 }); 3005 }); 3006 3007 // Handle preventing a user from leaving a page, unless we want them to (see form.clear-protect-user-data below), so they do not lose their work 3008 $("body.protect-user-data").each(function() { 3009 window.onbeforeunload = function() {return "Caution: Leaving this page may result in a loss of your work.";}; 3010 }); 3011 3012 $("form.clear-protect-user-data").on("submit", function() { 3013 window.onbeforeunload = null; 3014 }); 3015 3016 // Bind post-to-on-change fields
3017 $("tr input[id^='field-post-to-on-change']").livequery(function() { 3018 3019 // If we told to update/submit only the column, then look for closest td else look for entire row (tr) 3020 var theRow = $(this).is('[id^="field-post-to-on-change-column-only"]') ? $(this).closest("td") : $(this).closest("tr"); 3021 3022 // Shorthand some variables 3023 var postField = this; 3024 var postAction = $(this).val(); 3025 3026 // Decide on the ajax request type 3027 var postMethod = $(":input[id^='field-method']", theRow).length > 0 ? $(":input[id^='field-method']", theRow).val() : 'post'; 3028 if (postMethod == 'put' || postMethod == 'delete') 3029 postMethod = "post"; 3030 3031 // Bind any buttons or submits under the form that do not have .do-not-post to trigger change on click 3032 // We're doing this so it triggers the code down below. 3033 $(":input[type='button'],:input[type='submit'],a.save-row", theRow).click(function(event) { 3034 3035 // Show confirm dialog box if present 3036 if ($(this).attr("data-confirm") === undefined || confirm($(this).attr("data-confirm"))) 3037 $(this).change(); 3038 3039 return stopEvent(event); 3040 }); 3041 3042 // Bind all inputs under the row to ajax submit the data as requested 3043 $(":input:not(.do-not-post),a.save-row", theRow).change(function() { 3044 var theField = this; 3045 var allowPost = true; 3046 3047 // Check for a prepost hook, and run it if present. 3048 if ($(theField).data("hooks.prepost") != null) { 3049 try { 3050 allowPost = eval($(theField).data("hooks.prepost"))(theRow, theField); 3051 } catch (e) {allowPost = false;} 3052 } 3053 3054 // Post if ok and form is validated? 3055 if (allowPost && validate_form(theRow)) { 3056 appendLoadingMask($(this).closest("td")); 3057 $.ajax({
3058 type: postMethod, 3059 url: postAction, 3060 data: $(":input", theRow).serialize(), 3061 success: function(){ 3062 // Check for a post-post hook, and run it if present. 3063 if ($(theField).data("hooks.postpost") != null) { 3064 try { 3065 eval($(theField).data("hooks.postpost"))(theRow, theField); 3066 } catch (e) {} 3067 } 3068 $destroy($(theRow).find("div.loading-mask")); 3069 if ($(postField).data("clear-field") != null && $(postField).data("clear-field") == true) 3070 $(theField).val($(theField)[0].tagName == 'SELECT' ? 'EMPTY' : ''); 3071 } 3072 }); 3073 } 3074 }); 3075 3076 }); 3077 3078 /*Requires keyboard.js to function*/ 3079 $("a[data-trigger-with]").livequery(function() { 3080 var theLink = this; 3081 bindKey($(this).data("trigger-with"), $(this).html(), null, function(event, keys, combo) { 3082 $(theLink).click(); 3083 }); 3084 }); 3085 3086 // Fill-up/down links for inputs within listing columns need to fill up in the same column until they reach a non-empty value 3087 $("table tr td input.fill-up,table tr td input.fill-down,table tr td input.fill-both").livequery(function() { 3088 var input = this, 3089 tr = $(input).closest("tr"), 3090 prevTr = $(tr).prev(), 3091 nextTr = $(tr).next(), 3092 colIndex = $(input).closest("td").index(), 3093 3094 fillUp = $(input).is(".fill-up,.fill-both"), 3095 fillDown = $(input).is(".fill-down,.fill-both"); 3096 3097 // Function to call on click of the images that triggers the fill in the appropriate direction 3098 var fill = function(direction) { 3099 var listing = $(input).closest("tbody"); 3100 if (direction == 'up') { 3101 var endIndex = $("tr:lt(" + $(tr).index() + ")", listing).has("td:nth-child(" + (colIndex+1) + ") input:visible[value!='']").last().index(); 3102 endIndex = endIndex == -1 ? 0 : endIndex; 3103 $("tr", listing).slice(endIndex, $(tr).index()).find("td:nth-child(" + (colIndex+1) + ") input:visible[value='']").val($(input).val()).change(); 3104 } else { 3105 var endIndex = $("tr:gt(" + ($(tr).index() + 1) + ")", listing).has("td:nth-child(" + (colIndex+1) + ") input:visible[value!='']").first().index(); 3106 endIndex = endIndex == -1 ? listing[0].children.length : endIndex; 3107 $("tr", listing).slice($(tr).index(), endIndex).find("td:nth-child(" + (colIndex+1) + ") input:visible[value='']").val($(input).val()).change(); 3108 } 3109 }; 3110 3111 // Function to add or remove the fill image links from the input 3112 var addOrRemoveFillLinks = function() { 3113 var val = $(input).val(), 3114 emptyBefore = val != '' && $($("td", prevTr)[colIndex]).find("input:visible[value='']").length == 1, 3115 emptyAfter = val != '' && $($("td", nextTr)[colIndex]).find("input:visible[value='']").length == 1, 3116 3117 // If this input isn't empty, and depending on whether it is fill up or down and there is an empty field in that direction, decide if the arrow should appear 3118 show = val != '' && ((fillUp && emptyBefore) || (fillDown && emptyAfter)); 3119 3120 if (show) { 3121 // Float this input, and then add the fill div with links beside it. 3122 if (!$(input).hasClass("float-right")) 3123 $(input).addClass("float-right"); 3124 3125 var div = $('<div class="fill-links"/>'); 3126 if (fillUp && emptyBefore) 3127 $('<img src="/tpWheels/images/arrows/up.png" class="fill-up" />').click(function() { 3128 fill("up"); 3129 $(this).remove(); 3130 }).appendTo(div); 3131 if (fillDown && emptyAfter) 3132 $('<img src="/tpWheels/images/arrows/down.png" class="fill-down" />').click(function() { 3133 fill("down"); 3134 $(this).remove(); 3135 }).appendTo(div); 3136 3137 $(div).insertAfter(input); 3138 } else { 3139 $(input).closest("td").find("div.fill-links").remove(); 3140 } 3141 }; 3142 3143 // We may want to see the fill links on load 3144 addOrRemoveFillLinks(); 3145 3146 // On change we need to decide on the fill links, as well. 3147 $(input).change(function() { 3148 addOrRemoveFillLinks(); 3149 }); 3150 }); 3151 3152 // Moves the cursor to the next available visible input on the screen when the source input is filled up 3153 $(".move-when-full[maxlength]").livequery(function() { 3154 var maxLength = parseInt($(this).attr("maxlength")); 3155 var inputIndex = $(this).index(":input:visible"); 3156 var moved = false; 3157 3158 // On key up, find the next visible input, ensuring that it's not full. 3159 $(this).keyup(function(event) { 3160 if ($(this).val().length == maxLength) { 3161 $(":input:visible:not(:disabled)").slice(inputIndex + 1).each(function() { 3162 if (!moved && ($(this).val() == '' || ($(this).attr("maxlength") != null && $(this).val().length < parseInt($(this).attr("maxlength"))))) { 3163 $(this).focus(); 3164 moved = true; 3165 } 3166 }); 3167 } 3168 moved = false; 3169 }); 3170 }); 3171 3172 // Strip any leading zeros on input 3173 $(":input.strip-leading-zeros").livequery(function() { 3174 $(this).change(function() { 3175 $(this).val($(this).val().replace(/^(0+)/g, "")); 3176 }); 3177 }); 3178 3179 // Enable copy inputs from a specified source to a specifed target 3180 $("[data-copy-from] :input.trigger-copy").on("change", function() { 3181 var target = $(this).closest("[data-copy-from]"),
3182 source = $($(target).data("copy-from")), 3183 isChecked = $(this).is(":checked"); 3184 3185 // Loop over targetFields and copy data from sourceField 3186 $(":input:not('.do-not-copy')", target).each(function() { 3187 var isRadioOrCheckbox = $(this).is(":checkbox,:radio"), 3188 sourceFieldName = $(this).data("field-name") || $(this).attr('name'), 3189 sourceField = $(":input[data-field-name = '" + sourceFieldName + "']", source); // Look up sourceFeild by 'data-field-name'; 3190 3191 // If we didn't find sourceField by 'data-field-name', try to find it by 'name' 3192 if (sourceField.length == 0) 3193 sourceField = $(":input[name = '" + sourceFieldName + "']", source); 3194 3195 // If we have more than one sourceFields, filter by value 3196 if (sourceField.length > 1) 3197 sourceField = $(sourceField).filter("[value='" + $(this).val() + "']"); 3198 3199 if (sourceField.length > 0) { 3200 if (!$(this).hasClass("do-not-change")) { 3201 if (isChecked) 3202 isRadioOrCheckbox && $(sourceField).is(":checked") ? $(this).attr("checked", true).change() : $(this).val($(sourceField).val()).change(); 3203 else 3204 isRadioOrCheckbox ? $(this).attr("checked", false).change() : $(this).val('').change(); 3205 } else { 3206 if (isChecked) 3207 isRadioOrCheckbox && $(sourceField).is(":checked") ? $(this).attr("checked", true) : $(this).val($(sourceField).val()); 3208 else 3209 isRadioOrCheckbox ? $(this).attr("checked", false) : $(this).val(''); 3210 } 3211 3212 // If this is a autocomplete field trigger the lookup 3213 if ($(this).hasClass("autocomplete-hidden")) 3214 $(this).parent().find("input.autocomplete").data("lookup")($(this).val()); 3215 } 3216 $(this).trigger("dataCopyFrom:after"); 3217 }); 3218 }); 3219 3220 // Save and reset buttons should be enabled / disabled as necessary 3221 $("input.save-form-changes,input.reset-form-changes").livequery(function() { 3222 var theForm = this.form, 3223 saveFormChanges = $(this).hasClass("save-form-changes") ? this : $(theForm).find("input.save-form-changes"), 3224 resetFormChanges = $(this).hasClass("reset-form-changes") ? this : $(theForm).find("input.reset-form-changes"); 3225 3226 $(theForm).change(function() { 3227 $(saveFormChanges).add(resetFormChanges).attr("disabled", false); 3228 }); 3229 3230 $(resetFormChanges).click(function() { 3231 // If this is an additional fields form click all of the 'x's 3232 if ($(theForm).hasClass("additional-fields")) { 3233 // (the confirm is hard coded because we don't have a way to specify confirm on reset buttons.... not great) 3234 if (confirm("Are you sure you'd like to clear all filters?")) { 3235 3236 // Find the nearest dynamic listing and wipe out its query string 3237 $("#field-filter-query", "div.table-listing.dynamic:first").val(''); 3238 $("form.filter-fields li.query", "div.table-listing.dynamic:first").remove(); 3239 3240 // Force a clear on applicable fields 3241 $(".clear-field:visible", theForm).click(); 3242 $(saveFormChanges).add(resetFormChanges).attr("disabled", true); 3243 } 3244 } else { 3245 theForm.reset(); 3246 3247 // Force any autocomplete fields to update themselves after a reset 3248 $("input.autocomplete", theForm).each(function() { 3249 $(this).data("lookup")($(this).val()); 3250 }); 3251 $(saveFormChanges).add(resetFormChanges).attr("disabled", true); 3252 } 3253 }); 3254 }); 3255 3256 // Make entities with data-match-width-of do exactly what it sounds like 3257 $("[data-match-width-of]").livequery(function() { 3258 var theElement = this, 3259 elementToMatch = $($(this).data("match-width-of")); 3260 3261 if ($(elementToMatch).length > 0) 3262 $(theElement).css("min-width", $(elementToMatch).innerWidth()); 3263 }); 3264 3265 // Setup manual paging for listings when it is necessary 3266 $("div#query-paging").each(function() { 3267 var pageInput = $(this).find("input"), 3268 numPages = $(pageInput).data("numpages"); 3269 3270 // Changing the page number field needs to refresh the page 3271 $(pageInput).change(function() { 3272 3273 // Get the new value and ensure it doesn't go beyond the max number of pages 3274 var val = $(this).val(); 3275 if (val > numPages) 3276 val = numPages; 3277 3278 // Reload the page to the entered page number 3279 window.location.href = window.location.href.replace(/\?.*/, '') + '?page=' + val; 3280 3281 // On enter we want to trigger the change event 3282 }
3282).keydown(function(e) { 3283 var key = e.which ? e.which : e.keyCode; 3284 if (key == KEYENUM.RETURN) { 3285 $(this).change(); 3286 return stopEvent(e); 3287 } 3288 }); 3289 }); 3290 3291 // On blur of trim fields... trim the data. 3292 $("input.trim").on("blur", function() { 3293 $(this).val(trim($(this).val())); 3294 }); 3295 3296 // Show/Hide manual fax fields 3297 $("form.fax-form :input[name='fax[faxOption]']").livequery(function() { 3298 var theForm = $(this).closest("form"), 3299 manualFaxFields = $("ul.manual-fax-fields"); 3300 $(this).change(function() { 3301 if ($(this).filter(":checked").val() == 'manual') 3302 $(manualFaxFields).show().find(":input").addClass("required-field"); 3303 else 3304 $(manualFaxFields).hide().find(":input").removeClass("required-field"); 3305 }).change(); 3306 }); 3307 3308 // Setup requireWhenElementIs fields 3309 $(":input[data-requiredwhenelementis]").livequery(function() { 3310 var theField = this, 3311 3312 // Find the label for this input 3313 theLabel = $(theField).closest("label"), 3314 theLabel = $(theLabel).length ? theLabel : $("label[for='" + $(theField).attr("id") + "']"), 3315 3316 // Separate different element fields by | 3317 bindData = $(theField).data("requiredwhenelementis").split("|"); 3318 3319 // Loop over all elements 3320 for (i = 0; i < bindData.length; i++) { 3321 $(bindData[i].split(",")[0]).change(function() { 3322 var required = true; 3323 // Loop over all elements again to check what the value of each element is when any one changes. 3324 for (j = 0; j < bindData.length; j++) { 3325 var whenData = bindData[j].split(","), 3326 // The shift function removes the first element of an array and returns it. This allows the following line to work properly 3327 whenField = whenData.length > 0 ? $(whenData.shift()) : null, 3328 whenValues = whenData.length > 0 ? whenData.toString().replace(/\(|\)/g, '').split(",") : null, 3329 // If an ! is the first value then the rest are negated 3330 negateWhenValues = whenValues.length > 0 && whenValues[0] == '!' ? true : false; 3331 3332 // If we have an element and values setup requirement toggling. If a requirement has already failed then we don't need to go through this condition 3333 if (whenField != null && whenValues != null && required) { 3334 var val = $(whenField).val(); 3335 3336 if (negateWhenValues) { 3337 whenValues.shift(); 3338 required = whenValues.indexOf($(whenField).val()) == -1; 3339 } else 3340 required = whenValues.indexOf($(whenField).val()) >= 0; 3341 3342 // If the controlling field is a checkbox and if 'whenValue' is 1 it is required on check and if 'whenValue' is 0 it is required on un-check 3343 if ($(whenField).prop("type").toLowerCase() == "checkbox") 3344 required = ((whenValues == 1 && $(whenField).is(":checked")) || (whenValues == 0 && !$(whenField).is(":checked"))); 3345 3346 // If the controlling field is a radio button, find the checked field and compare that to the when values 3347 if ($(whenField).prop("type").toLowerCase() == "radio") { 3348 if (negateWhenValues) { 3349 required = whenValues.indexOf($(whenField).filter(":checked").val()) == -1; 3350 } else 3351 required = whenValues.indexOf($(whenField).filter(":checked").val()) >= 0; 3352 } 3353 } 3354 } 3355 $(theField).removeClass("required-field"); 3356 if (required) { 3357 $(theField).addClass("required-field"); 3358 if (theLabel != null && $(theLabel).find("em").length == 0) 3359 $(theLabel).append("<em>*</em>"); 3360 } else { 3361 if (theLabel != null) 3362 $(theLabel).find("em").remove(); 3363 } 3364 }).change(); 3365 } 3366 }); 3367 3368 // Setup disabledWhenElementIs fields 3369 $(":input[data-disabledwhenelementis]").livequery(function() { 3370 var theField = this, 3371 3372 // Shift removes the first element and returns it. The array is modified which allows the next line to work. 3373 bindData = $(theField).data("disabledwhenelementis").split(","), 3374 whenField = bindData.length > 0 ? $(bindData.shift()) : null, 3375 whenValues = bindData.length > 0 ? bindData.toString().replace(/\(|\)/g, '').split(",") : null; 3376 3377 // If we have an element and values setup requirement toggling 3378 if (whenField != null && whenValues != null) { 3379 $(whenField).change(function() { 3380 var val = $(this).val(), 3381 setDisabled = whenValues.indexOf($(this).val()) >= 0; 3382 3383 // If the controlling field is a checkbox and if 'whenValue' is 1 it is disabled on check and if 'whenValue' is 0 it is disabled on un-check 3384 if ($(whenField).prop("type").toLowerCase() == "checkbox") 3385 setDisabled = ((whenValues == 1 && $(whenField).is(":checked")) || (whenValues == 0 && !$(whenField).is(":checked"))); 3386 3387 if (setDisabled) { 3388 $(theField).prop("disabled", true); 3389 } else { 3390 $(theField).prop("disabled", false); 3391 } 3392 }).change(); 3393 } 3394 }); 3395 3396 // Setup hideWhenElementIs fields 3397 $("*[data-hiddenwhenelementis]").livequery(function() { 3398 3399 var theElement = this, 3400 theInputField = this; 3401 3402 if (!$(theInputField).is(":input")) 3403 theInputField = $(":input", theElement); 3404 3405 if (/\|\||&&/gi.test($(theElement).data("hiddenwhenelementis"))) { 3406 var bindData = $(theElement).data("hiddenwhenelementis").match(/.{1,}?(\|\||&&|$)/gi); 3407 } else { 3408 var bindData = [$(theElement).data("hiddenwhenelementis")]; 3409 } 3410 3411 var multipleControlsOperation = ""; 3412 if (bindData.length > 1) { 3413 multipleControlsOperation = bindData[0].substring(bindData[0].length - 2); 3414 } 3415
3416 var validWhenFields = []; 3417 for (var i = 0; i < bindData.length; i++) { 3418 bindData[i] = bindData[i].replace(/\|\||&&/gi, ""); 3419 3420 var currentBindData = bindData[i].split(","), 3421 // Shift removes the first element and returns it. The array is modified which allows the next line to work. 3422 whenField = currentBindData.length > 0 ? $(currentBindData.shift()) : null, 3423 whenValues = currentBindData.length > 0 ? currentBindData.shift().toString().replace(/\(|\)/g, '').split("|") : null, 3424 clearFlag = currentBindData.length > 0 ? currentBindData.shift() : false, 3425 triggerChangesFlag = currentBindData.length > 0 ? (currentBindData == "true" ? true : false) : false; 3426 3427 // If we have an element and values, setup requirement toggling 3428 if (whenField != null && whenValues != null) { 3429 validWhenFields.push({field:whenField, values:whenValues, clearFlag:clearFlag, triggerChangesFlag:triggerChangesFlag}); 3430 } 3431 } 3432 3433 for (var i = 0; i < validWhenFields.length; i++) { 3434 validWhenFields[i].field.on("change", function() { 3435 var setHidden = false; 3436 for (var j = 0; j < validWhenFields.length; j++) { 3437 var whenField = validWhenFields[j], 3438 val = whenField.field.val(), 3439 currentFieldEvaluation = whenField.values.indexOf(whenField.field.val()) >= 0, 3440 checkboxOrRadioFlag = whenField.field.prop("type").toLowerCase() == "checkbox" || whenField.field.prop("type").toLowerCase() == "radio"; 3441 3442 // If the controlling field is a checkbox and if 'whenValue' is 1 it is disabled on check and if 'whenValue' is 0 it is disabled on un-check 3443 if (checkboxOrRadioFlag) { 3444 currentFieldEvaluation = ((whenField.values == 1 && whenField.field.is(":checked")) || (whenField.values == 0 && !whenField.field.is(":checked"))); 3445 } 3446 3447 // This field is a positive test 3448 if (currentFieldEvaluation) { 3449 // If we do not have multiple fields to test or the operation is an OR, mark the element to hide and break the loop. 3450 if (multipleControlsOperation == "" || multipleControlsOperation == "||") { 3451 setHidden = true; 3452 break; 3453 // If we do have multiple fields to test and we keep getting here, all is good. If we are on the last element, mark it to hide and break the loop. 3454 } else if (multipleControlsOperation == "&&" && j == validWhenFields.length - 1) { 3455 setHidden = true; 3456 break; 3457 } 3458 3459 // Negative test and we are trying to test multiple fields. Mark the element to NOT hide and immediately break the loop. 3460 } else if (multipleControlsOperation == "&&") { 3461 setHidden = false; 3462 break; 3463 } 3464 } 3465 3466 if (setHidden) { 3467 if (validWhenFields[0].clearFlag && $(theInputField).is(":input")) { 3468 validWhenFields[0].theFieldCheckboxOrRadioFlag = $(theInputField).prop("type").toLowerCase() == "checkbox" || $(theInputField).prop("type").toLowerCase() == "radio"; 3469 if (validWhenFields[0].theFieldCheckboxOrRadioFlag) 3470 $(theInputField).prop("checked", false); 3471 else 3472 $(theInputField).val(""); 3473 if (validWhenFields[0].triggerChangesFlag) 3474 $(theInputField).trigger("change"); 3475 } 3476 3477 $(theElement).removeClass("hidden").addClass("hidden"); 3478 3479 } else { 3480 $(theElement).removeClass("hidden"); 3481 } 3482 }).trigger("change"); 3483 } 3484 }); 3485 3486 // Validate form on button click 3487 $("input.input-button.validate-form").livequery(function() { 3488 $(this).click(function(event) { 3489 if (!validate_form($(this).closest("form"))) 3490 return stopEvent(event); 3491 }); 3492 }); 3493 3494 // Add masked input plugin's .mask to input fields with data-mask specified 3495 $("input[data-mask]").livequery(function() { 3496 // Default definitions 3497 // '9': "[0-9]" 3498 // 'a': "[A-Za-z]" 3499 // '*': "[A-Za-z0-9]" 3500 3501 // Add extra definitions here 3502 $.mask.definitions['A']='[A-Z]'; 3503 $.mask.definitions['P']='[a|A|p|P]'; 3504 3505 // Settings for .mask 3506 if ($(this).attr("data-mask-placeholder")) 3507 $.mask.placeholder = $(this).data("mask-placeholder"); 3508 3509 $(this).mask($(this).data("mask")); 3510 }); 3511 3512}); 3513 3514/* Global Functions */ 3515 function appendLoadingMask(div) { 3516 3517 // calculate properties of div 3518 var height = $(div).height() + parseInt($(div).css('padding-top')) + parseInt($(div).css('padding-bottom')); 3519 3520 // ensure that the loading mask is positioned properly 3521 var containerPosition = $(div).css("position"); 3522 if (containerPosition != 'relative') 3523 $(div).css('position', 'relative'); 3524 3525 // create the loading mask, apply properties, and append it to the div 3526 var div2 = $("<div class='loading-mask'><img src='/tpWheels/images/spinners/default.gif' /></div>"); 3527 div2.css({ 3528 height: height + 'px', 3529 width: $(div).width() + parseInt($(div).css('padding-left')) + parseInt($(div).css('padding-right')) + 'px', 3530 lineHeight: height + 'px' 3531 }) 3532 .bind("before:remove", function() { 3533 if (containerPosition != 'relative') 3534 $(div).css('position', containerPosition); 3535 }) 3536 .appendTo(div); 3537 } 3538 3539 // Enable 'stacking' custom events on a specified element's DOM event 3540 // Example: Stack afterScroll:tableHeader and afterScroll:siteFooterBar to $(window).scroll() 3541 // eventStackPush(window, 'scroll', 'after', 'afterScroll:tableHeader') and eventStackPush(window, 'scroll', 'after', 'afterScroll:siteFooterBar') 3542 // TODO: Make before useful in some way shape or form 3543 function eventStackPush(selector, eventName, stepInChain, customEventName) { 3544 var element = $(selector); 3545 if (element != null) { 3546 // Load the current event stacks on this element 3547 var eventStacks = $(element).data("eventStacks"); 3548 3549 // Ensure the element has an event stack structure setup on it 3550 if (eventStacks == null) 3551 eventStacks = {}; 3552 3553 // Ensure this event has an array stack prepared 3554 if (eventStacks[eventName] == null) 3555 eventStacks[eventName] = {before:[], after:[]}; 3556 3557 // Grab the current stack 3558 var currentEventStack = eventStacks[eventName][stepInChain]; 3559 3560 // Push the new event into the stack if it's not yet there 3561 if ($.inArray(customEventName, currentEventStack) == -1) 3562 eventStacks[eventName][stepInChain][currentEventStack.length] = customEventName; 3563 3564 // Update the eventStacks on the element 3565 $(element).data("eventStacks", eventStacks);
3566 } 3567 } 3568 3569 // Execute the given event stack created using eventStackPush 3570 function executeEventStack(selector, eventName, stepInChain) { 3571 var element = $(selector), 3572 eventStacks = $(element).data("eventStacks"), 3573 stackedEvents = eventStacks != null ? eventStacks[eventName][stepInChain] : null; 3574 if (stackedEvents != null) { 3575 for (var i = 0; i < stackedEvents.length; i++) { 3576 $(element).trigger(stackedEvents[i]); 3577 } 3578 } 3579 } 3580 3581 // Fix the ids, names and labels of given elements using the provided index 3582 function fixDistinctElements(idElements, objectElements, index) { 3583 3584 // HACK: Destroy the datepicker before modifying the ids and names 3585 $("input.input-date", idElements).datepicker("destroy"); 3586 $("input.input-date", objectElements).datepicker("destroy"); 3587 $("input.input-datetime", idElements).datetimepicker("destroy"); 3588 $("input.input-datetime", objectElements).datetimepicker("destroy"); 3589 3590 // Append a number to elements with an id already 3591 $(idElements).each(function() { 3592 var id = $(this).attr("id"); 3593 $(this).attr("id", id + '-' + index); 3594 $("label[for='" + id + "']").attr("for", id + '-' + index); 3595 }); 3596 3597 // Make the objects on inputs (with an object) uniquely named (requires extra processing on server side to find them...) 3598 $(objectElements).filter("[name*='[']").each(function() { 3599 var origName = $(this).attr("name"), 3600 name = $(this).attr("name").split("[")[0], 3601 regex = new RegExp("^" + name); 3602 $(this).attr("name", origName.replace(regex, name + index)); 3603 }); 3604 3605 // HACK: Rebind datepicker on 1ms delay to not cross up DOM references 3606 setTimeout(function() { 3607 bindDatePicker($("input.input-date", idElements)); 3608 bindDatePicker($("input.input-date", objectElements)); 3609 bindDatetimePicker($("input.input-datetime", idElements)); 3610 bindDatetimePicker($("input.input-datetime", objectElements)); 3611 }, 1); 3612 } 3613 3614 // Create a way to register a keyboard shortcut, name, and action for the application 3615 var key_bindings = new Array(); 3616 function bindKey(combo, name, down, up) { 3617 var return_false = function(event, keys, combo) { return false; } 3618 3619 // default the key up/down callbacks to return alse 3620 down = down || return_false; 3621 up = up || return_false; 3622 3623 // simply call KeyboardJS to register the key(s) 3624 KeyboardJS.on(combo, down, up); 3625 3626 // format the keyboard combination for the legend 3627 combo = combo.toUpperCase() 3628 .replace(/(ctrl|alt|shift)/gi, "<$1>") 3629 .replace(/\+/g, " + ") 3630 .replace(/\s{2,}/g, " "); 3631 3632 // add the combination and name to the global key binding list 3633 key_bindings.push({combo: combo, name: name}); 3634 } 3635 3636 // Creates an absolutely positioned box next to a specific element 3637 createPageAlertBox = function(element, message) { 3638 removePageAlertBox(); 3639 $('<div id="alert-box-per-page" class="alert-box"><p>' + message + '</p>') 3640 .css({position: "absolute", top: $(element).offset().top - $(element).outerHeight(), left: $(element).offset().left + $(element).outerWidth()}).appendTo(document.body); 3641 }; 3642 3643 // Removes the box created above. Called within the create just in case. 3644 removePageAlertBox = function() { 3645 $("#alert-box-per-page").remove(); 3646 };
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.