1/** 2 * The YACS AJAX shared library. 3 * 4 * This file extends prototype, etc., to enhance interactions with the end-user 5 * 6 * @author Bernard Paques [email][email protected][/email] 7 * @reference 8 * @license http://www.gnu.org/copyleft/lesser.txt GNU Lesser General Public License 9 */ 10 11var startWatch = new Date(); 12 13var Yacs = { 14 15 /** 16 * alert the surfer 17 * 18 * @param message to display 19 * @param function to be called on click, if any 20 */ 21 alert: function(message, callBack) { 22 23 Yacs.displayModalBox({ body: '<div style="text-align: left">'+message.replace(/\n/gi, '<br />')+'</div>', 24 button_TRUE: "OK" }, callBack); 25 26 }, 27 28 /** 29 * reflect last sort in table 30 * 31 * @param target table 32 * @param sorted column 33 */ 34 beautifyTable: function(tblEl, col) { 35 36 // look for class names in styles 37 var rowTest = new RegExp("odd", "gi"); 38 var colTest = new RegExp("sorted", "gi"); 39 40 // alternate row appearance 41 var i, j; 42 var rowEl, cellEl; 43 44 for (i = 0; i < tblEl.rows.length; i++) { 45 rowEl = tblEl.rows[i]; 46 Element.removeClassName(rowEl, 'odd'); 47 if((i % 2) !== 0) { 48 Element.addClassName(rowEl, 'odd'); 49 } 50 51 // set style classes on each column 52 for (j = 2; j < tblEl.rows[i].cells.length; j++) { 53 cellEl = rowEl.cells[j]; 54 Element.removeClassName(cellEl, 'sorted'); 55 // highlight the one that was sorted 56 if (j == col) { 57 Element.addClassName(cellEl, 'sorted'); 58 } 59 } 60 } 61 62 // find the table header and highlight the column that was sorted 63 var el = tblEl.parentNode.tHead; 64 rowEl = el.rows[el.rows.length - 1]; 65 66 // set style classes for each column 67 for (i = 0; i < rowEl.cells.length; i++) { 68 cellEl = rowEl.cells[i]; 69 Element.removeClassName(cellEl, 'sorted'); 70 71 // highlight the header of the sorted column 72 if (i == col) { 73 Element.addClassName(cellEl, 'sorted'); 74 } 75 } 76 }, 77 78 /** 79 * remote procedure call based on JSON-RPC 80 * 81 */ 82 call: function(parameters, callBack) { 83 84 // hash encoding to JSON 85 parameters = Object.toJSON(parameters); 86 87 // start an ajax transaction 88 var handle = new Ajax.Request(url_to_root + 'services/json_rpc.php', { 89 method: 'post', 90 parameters: parameters, 91 requestHeaders: {Accept: 'application/json'}, 92 onSuccess: function(transport) { 93 var response = transport.responseText.evalJSON(true); 94 if(response.error) { 95 callBack(FALSE); 96 } else if(response.result) { 97 callBack(response.result); 98 } else { 99 callBack(FALSE); 100 } 101 response = null; // no memory leak 102 }, 103 onFailure: function(transport) { 104 callBack(FALSE); 105 } 106 }); 107 }, 108 109 /** 110 * clear one cookie 111 * 112 * @param cookie name 113 */ 114 clearCookie: function(name) { 115 if(Yacs.getCookie(name)) { 116 document.cookie = name + "=; expires=Thu, 01-Jan-70 00:00:01 GMT"; 117 } 118 }, 119 120 /** 121 * trigger the show 122 */ 123 clickImage: function() { 124 Yacs.showImage(this); 125 return false; 126 }, 127 128 /** 129 * close the modal box 130 */ 131 closeModalBox: function() { 132 133 var handle = new Effect.Opacity('modal_content', {duration:0.3, from:1.0, to:0.3, queue: 'end', 134 afterFinish: function(target) { 135 136 // clear the content 137 Element.update('modal_content', ''); 138 139 // mask the modal box 140 Element.setStyle('modal_panel', { display: 'none' }); 141 142 } }); 143 }, 144 145 /** 146 * ask for confirmation 147 * 148 * @param message to display 149 * @param callBack the function to call when the OK button is cliqued 150 */ 151 confirm: function(message, callBack) { 152 153 Yacs.displayModalBox({ body: '<div style="text-align: left">
153'+message.replace(/\n/gi, '<br />')+'</div>', 154 button_TRUE: "OK", button_FALSE: "Cancel" }, callBack); 155 156 }, 157 158 /** 159 * compare two values 160 * 161 * @param first value 162 * @param second value 163 * @return comparison value 164 */ 165 compareValues: function(v1, v2) { 166 167 // if the values are numeric, convert them to floats 168 var f1 = parseFloat(v1); 169 var f2 = parseFloat(v2); 170 if(!isNaN(f1) && !isNaN(f2)) { 171 v1 = f1; 172 v2 = f2; 173 } 174 175 // compare the two values 176 if(v1 == v2) { 177 return 0; 178 } 179 if(v1 > v2) { 180 return 1; 181 } 182 return -1; 183 }, 184 185 /** 186 * detect Flash on client-side 187 * 188 * The script used to detect Flash under Firefox, etc. This is complemented 189 * by server-side detection for IE. 190 * 191 * This function also save findings in a cookie for transmission to server. 192 * 193 * @return 'yes' or 'no' 194 */ 195 detectFlash: function () { 196 197 var detected = Yacs.getCookie("FlashIsAvailable"); 198 199 // cookie has already been set 200 if(detected) { 201 return detected; 202 } 203 204 // detect in plugins 205 if((navigator.plugins !== null) && (navigator.plugins.length > 0)) { 206 if(navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) { 207 detected = 'yes'; 208 } 209 } else if(navigator.userAgent.toLowerCase().indexOf("webtv") != -1) { 210 detected = 'yes'; 211 } 212 213 // remember this in a cookie 214 Yacs.setCookie("FlashIsAvailable", detected); 215 216 // return the result, if useful 217 return detected; 218 219 }, 220 221 /** 222 * compute and record surfer time zone 223 * 224 * This function computes the surfer time zone, and save it in a cookie to 225 * share this with the back-end 226 * 227 * @return the time zone 228 */ 229 detectTimeZone: function () { 230 231 var timeZone = Yacs.getCookie("TimeZone"); 232 233 // cookie has already been set 234 if(timeZone) { 235 return timeZone; 236 } 237 238 // compute time shift 239 var now = new Date(); 240 timeZone = (-now.getTimezoneOffset() /60); 241 242 // remember this in a cookie 243 Yacs.setCookie("TimeZone", timeZone); 244 245 // return the result, if useful 246 return timeZone; 247 248 }, 249 250 /** 251 * display a modal box 252 * 253 * @param string XHTML content of the box 254 * @param function to be called on OK or Cancel 255 * 256 * @see tools/ajax.php 257 */ 258 displayModalBox: function(content, callBack) { 259 260 // prepare box actual content 261 var boxContent = ''; 262 263 if(content.title) { 264 boxContent += '<h2 class="boxTitle">'+content.title+'</h2>'; 265 } 266 if(content.body) { 267 boxContent += '<div class="boxBody">'+content.body+'</div>'; 268 } 269 270 // handle key press 271 var keyCodes = []; 272 273 // all buttons 274 boxContent += '<div class="boxButtons">'; 275 if(content.button_PREVIOUS) { 276 if(typeof Yacs.modalCallPrevious == "function") { 277 boxContent += '<span class="button"><button type="button" onclick="(Yacs.modalCallPrevious)();"><div style="padding: 0 1em">'+content.button_PREVIOUS+'</div></button></span> '; 278 } else { 279 boxContent += '<span class="button"><button type="button" disabled="disabled"><div style="padding: 0 1em">'+content.button_PREVIOUS+'</div></button></span> '; 280 } 281 } 282 if(content.button_TRUE) { 283 boxContent += '<span class="button"><button type="button" onclick="if(typeof Yacs.modalCallBack == "function") { (Yacs.modalCallBack)(true) }; Yacs.closeModalBox();"><div style="padding: 0 1em">'+content.button_TRUE+'</div></button></span> '; 284 } 285 if(content.button_FALSE) { 286 boxContent += ' <span class="button"><button type="button" onclick="if(typeof Yacs.modalCallBack == "function") { (Yacs.modalCallBack)(false) }; Yacs.closeModalBox();">'+content.button_FALSE+'</button></span> '; 287 } 288 if(content.button_NEXT) { 289 if(typeof Yacs.modalCallNext == "function") { 290 boxContent += '<span class="button"><button type="button" onclick="(Yacs.modalCallNext)();"><div style="padding: 0 1em">'+content.button_NEXT+'</div></button></span>'; 291 } else { 292 boxContent += '<span class="button"><button type="button" disabled="disabled"><div style="padding: 0 1em">'+content.button_NEXT+'</div></button></span>'; 293 } 294 } 295 boxContent += '</div>'; 296 297 // wait for the update 298 if(typeof callBack == "function") { 299 Yacs.modalCallBack = callBack; 300 } else { 301 Yacs.modalCallBack = null; 302 } 303 304 // insert some html at the bottom of the page that looks similar to this: 305 // 306 // <div id="modal_panel"> 307 // <div id="modal_centered"> 308 // <div id="modal_content"> 309 // content, passed as parameter, goes here 310 // </div> 311 // </div> 312 // </div> 313 314 // extend the DOM tree 315 if(!Yacs.modalOverlay) { 316 317 var objContent = document.createElement("div"); 318 objContent.setAttribute('id','modal_content'); 319 Element.update(objContent, '<img src="'+Yacs.spinningImage.src+'" />'); 320 321 var objCentered = document.createElement("div"); 322 objCentered.setAttribute('id','modal_centered'); 323// objCentered.setStyle({ visibility: 'hidden' }); 324 objCentered.appendChild(objContent); 325 326 Yacs.modalOverlay = document.createElement("div"); 327 Yacs.modalOverlay.setAttribute('id','modal_panel'); 328 Yacs.modalOverlay.onclick = function() {}; // you have to click in the box to close the modal box 329 Yacs.modalOverlay.appendChild(objCentered); 330
331 var objBody = document.getElementsByTagName("body").item(0); 332 objBody.appendChild(Yacs.modalOverlay); 333 334 // just to fix a bug on first image rendering for Internet Explorer 7... 335 var fix = new Effect.MoveBy('modal_centered', 0, 0, {duration: 0.0}); 336 337 // ensure containers are visible to compute box size 338 } else { 339 Element.setStyle('modal_panel', { display: 'block' }); 340 } 341 342 // paint or repaint box content 343 var handle = new Effect.Opacity('modal_content', {duration:0.1, from:1.0, to:0.3, queue: 'end', 344 afterFinish: function(target) { 345 346 // update the content 347 Yacs.updateModalBox(boxContent); 348 349 // display the updated box 350 Element.setStyle('modal_content', { visibility: 'visible' }); 351 var opacity = new Effect.Opacity('modal_content', {duration:0.3, from:0.5, to:1.0, queue: 'end'}); 352 353 } }); 354 355 356 357 }, 358 359 /** 360 * get the value of one cookie 361 * 362 * @param cookie name 363 * @return cookie value 364 */ 365 getCookie: function(name) { 366 367 var prefix = name + "="; 368 var begin = document.cookie.indexOf(prefix); 369 if(begin == -1) { return null; } 370 var end = document.cookie.indexOf(";", begin); 371 if(end == -1) { end = document.cookie.length; } 372 return unescape(document.cookie.substring(begin + prefix.length, end)); 373 374 }, 375 376 /** 377 * get a sortable string 378 * 379 * This function finds and concatenate the values of all text nodes 380 * 381 * @param the target DOM element 382 * @return a sortable string 383 */ 384 getTextValue: function(el) { 385 386 // the returned string 387 var s = ""; 388 389 // look at every child node 390 var i; 391 for(i = 0; i < el.childNodes.length; i++) { 392 393 // a text node 394 if(el.childNodes[i].nodeType == 3) { 395 s += el.childNodes[i].nodeValue; 396 397 // an element node which is a break 398 } else if((el.childNodes[i].nodeType == 1) && (el.childNodes[i].tagName == "br")) { 399 s += " "; 400 401 // use recursion to get text within sub-elements 402 } else { 403 s += Yacs.getTextValue(el.childNodes[i]); 404 } 405 406 } 407 408 return s; 409 }, 410 411 /** 412 * we have received an 'alert' notification 413 * 414 * @see users/notifications.php 415 */ 416 handleAlertNotification: function(response) { 417 418 // build a message in English if no localized message has been provided by the server 419 if(typeof response.dialog_text != 'string') { 420 response.dialog_text = 'New at ' + response.title + "\nby: " + response.nick_name + "\n" + 'Would you like to browse this page?'; 421 } 422 423 // switch to the offered address, if accepted by surfer 424 if(typeof response.address == 'string') { 425 Yacs.confirm(response.dialog_text, function(choice) { if(choice) {window.open(response.address);} }); 426 } 427 }, 428 429 /** 430 * we have received a 'browse' notification 431 * 432 * @see users/notifications.php 433 */ 434 handleBrowseNotification: function(response) { 435 436 // build a message in English if no localized message has been provided by the server 437 if(typeof response.dialog_text != 'string') { 438 response.dialog_text = 'From ' + response.nick_name + ":\n" + response.message + "\n" + 'Would you like to browse the provided link?'; 439 } 440 441 // switch to the offered address, if already in tracker, or if accepted by surfer 442 if(typeof response.address == 'string') { 443 if((window.name == 'yacs_tracker')) { 444 window.open(response.address, 'yacs_tracker'); 445 } else { 446 Yacs.confirm(response.dialog_text, function(choice) { if(choice) {window.open(response.address, 'yacs_tracker');} }); 447 } 448 } 449 }, 450 451 /** 452 * we have received a 'hello' notification 453 * 454 * @see users/notifications.php 455 */ 456 handleHelloNotification: function(response) { 457 458 // build a message in English if no localized message has been provided by the server 459 if(typeof response.dialog_text != 'string') { 460 response.dialog_text = 'From ' + response.nick_name + ":\n" + response.message; 461 462 if(typeof response.address == 'string') { 463 response.dialog_text += "\n" + 'Would you like to chat with this person?'; 464 } 465 } 466 467 // only show the message to the end-user 468 if(typeof response.address != 'string') { 469 Yacs.alert(response.dialog_text); 470 471 // else switch to the offered address, if accepted by surfer 472 } else { 473 Yacs.confirm(response.dialog_text, function(choice) { if(choice) {window.open(response.address);} }); 474 } 475 }, 476 477 /** 478 * prepare for mouse hovering 479 */ 480 addOnDemandTools: function(handle, option) { 481 handle = $(handle); 482 483 var prefix = ''; 484 if(handle.hasClassName('sortable')) { 485 prefix += '<span class="onHoverLeft drag_handle"><img src="'+url_to_root+'skins/_reference/on_demand_handle.png" width="16" height="16" alt="Drag" /></span>'; 486 } 487 var suffix = '<span class="onHoverRight">'; 488 if(handle.hasClassName('mutable')) { 489 suffix += '<a href="#" onclick="Yacs.toggleProperties(\''+handle.identify()+'\'); return false;"><img src="'+url_to_root+'skins/_reference/on_demand_properties.png" width="16" height="16" alt="Properties" /></a>'; 490 } 491 suffix += '<a href="#" onclick="Element.remove(\''+handle.identify()+'\'); return false;"><img src="'+url_to_root+'skins/_reference/on_demand_delete.png" width="16" height="16" alt="Delete" /></a></span>'; 492 var here = new Element.insert(handle, { top: suffix + prefix }); 493 494 handle.onmouseout = function () { Yacs.mouseOut(this); return false; }; 495 handle.onmouseover = function () { Yacs.mouseOver(this); return false; }; 496 497 handle = null; // no memory leak 498 499 }, 500 501 /** 502 * mouse is moving elsewhere 503 */ 504 mouseOut: function(handle) {
505 var nodes = handle.select('span.onHoverLeft', 'span.onHoverRight'); 506 nodes.each(function (node) { Element.setStyle(node, { visibility: 'hidden' }); }); 507 508 nodes = null; // no memory leak 509 }, 510 511 /** 512 * mouse is coming on top of some element 513 */ 514 mouseOver: function(handle) { 515 var nodes = handle.select('span.onHoverLeft', 'span.onHoverRight'); 516 nodes.each(function (node) { Element.setStyle(node, { visibility: 'visible' }); }); 517 518 nodes = null; // no memory leak 519 }, 520 521 toggleProperties: function(handle) { 522 var nodes = $(handle).select('.properties'); 523 nodes.each(function (node) { var handle = new Effect.toggle(node, 'slide'); }); 524 525 nodes = null; // no memory leak 526 }, 527 528 /** 529 * general initialization 530 */ 531 onWindowLoad: function() { 532 533 // resize according to surfer preferences 534 Yacs.textSize(); 535 536 // this window has the focus 537 Yacs.hasFocus = true; 538 539 // compute and record surfer time zone 540 Yacs.detectTimeZone(); 541 542 // detect Flash on client side 543 Yacs.detectFlash(); 544 545 // pre-load the spinning image used during ajax updates 546 Yacs.spinningImage = new Image(); 547 Yacs.spinningImage.src = url_to_root + 'skins/_reference/ajax_spinner.gif'; 548 549 // pre-load the image used at the working overlay 550 Yacs.workingImage = new Image(); 551 Yacs.workingImage.src = url_to_root + 'skins/_reference/ajax_working.gif'; 552 553 // change the behavior of buttons used for data submission, except those with style 'no_spin_on_click' 554 var buttons = $$('button'); 555 for(var index = 0; index < buttons.length; index++) { 556 var button = buttons[index]; 557 var buttonType = String(button.getAttribute('type')); 558 if(buttonType.toLowerCase().match('submit') && !Element.hasClassName(button, 'no_spin_on_click')) { 559 button.onclick = Yacs.startWorking; 560 } 561 562 button = null; // no memory leak 563 } 564 565 // on-demand headers 566 var nodes = $$('.onDemandTools'); 567 for(index = 0; index < nodes.length; index++) { 568 var node = nodes[index]; 569 Yacs.addOnDemandTools(node, { }); 570 } 571 572 // prepare for a nice slideshow 573 var anchors = $$('a.image_show'); 574 for(index = 0; index < anchors.length; index++) { 575 var anchor = anchors[index]; 576 anchor.onclick = Yacs.clickImage; 577 if(index > 0) { 578 anchor.previousAnchor = anchors[index - 1]; 579 } 580 if(index+1 < anchors.length) { 581 anchor.nextAnchor = anchors[index + 1]; 582 } 583 584 anchor = null; // no memory leak 585 } 586 587 // slow down notifications on window blur 588 Event.observe(window, 'blur', Yacs.looseFocus); 589 590 // back to normal rate on focus 591 Event.observe(window, 'focus', Yacs.getFocus); 592 593 // check for asynchronous notifications 594 setTimeout(Yacs.subscribe, 40000); 595 }, 596 597 getFocus: function() { 598 Yacs.hasFocus = true; 599 }, 600 601 looseFocus: function() { 602 Yacs.hasFocus = false; 603 }, 604 605 /** 606 * open a popup window 607 */ 608 popup: function(options) { 609 610 // default options 611 this.options = { 612 url: '#', 613 width: 600, 614 height: 500, 615 name:"_blank", 616 location:"no", 617 menubar:"no", 618 toolbar:"no", 619 status:"yes", 620 scrollbars:"yes", 621 resizable:"yes", 622 left:"", 623 top:"", 624 normal:false 625 }; 626 627 // use provided options, if any 628 Object.extend(this.options, options || {}); 629 630 // sanity check 631 if(this.options.normal) { 632 this.options.menubar = "yes"; 633 this.options.status = "yes"; 634 this.options.toolbar = "yes"; 635 this.options.location = "yes"; 636 } 637 638 // some computations 639 this.options.width = (this.options.width < screen.availWidth?this.options.width:screen.availWidth); 640 this.options.height= (this.options.height < screen.availHeight?this.options.height:screen.availHeight); 641 var openoptions = 'width='+this.options.width+',height='+this.options.height+',location='+this.options.location+',menubar='+this.options.menubar+',toolbar='+this.options.toolbar+',scrollbars='+this.options.scrollbars+',resizable='+this.options.resizable+',status='+this.options.status; 642 if(this.options.top !== '') { openoptions+=",top="+this.options.top; } 643 if(this.options.left !== '') { openoptions+=",left="+this.options.left; } 644 645 // open the popup 646 var window_handle; 647 window_handle = window.open(this.options.url, this.options.name, openoptions); 648 649 // add content, if option allows to do so 650 if(window_handle && this.options.content && (this.options.url == '#')) { 651 window_handle.document.write(this.options.content); 652 window_handle.document.close(); 653 } 654 655 // focus on the new window 656 if(window_handle && window_handle.focus) { 657 window_handle.focus(); 658 } 659 660 // allow for additional handling of this window 661 return window_handle; 662 }, 663 664 /** 665 * set a new cookie 666 * 667 * @param cookie name 668 * @param cookie value 669 * @param days before expiration (optional) 670 */
671 setCookie: function(name, value) { 672 673 var argv = arguments; 674 var argc = arguments.length; 675 var expires = (argc > 2) ? argv[2] : null; 676 if(expires > 0) { 677 var today = new Date(); 678 var future = today.getTime() + 3600000*24*expires; 679 expires = new Date(); 680 expires.setTime(future); 681 } 682 var path = (argc > 3) ? argv[3] : '/'; 683 var domain = (argc > 4) ? argv[4] : null; 684 var secure = (argc > 5) ? argv[5] : false; 685 document.cookie = name + "=" + escape (value) + 686 ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + 687 ((path === null) ? "" : ("; path=" + path)) + 688 ((domain === null) ? "" : ("; domain=" + domain)) + 689 ((secure === true) ? "; secure" : ""); 690 691 }, 692 693 /** 694 * similar to lightbox, integrated into yacs 695 */ 696 showImage: function(anchor) { 697 698 // load the image in the background 699 var loader = new Image(); 700 loader.onload=function(){ 701 702 // adjust image size to viewport dimensions 703 var scale = 1.0; 704 if((loader.width > 1) && (loader.width + 30 > document.viewport.getWidth())) { 705 scale = (document.viewport.getWidth() - 30) / loader.width; 706 loader.height*= scale; 707 loader.width *= scale; 708 } 709 if((loader.height > 1) && (loader.height + 110 > document.viewport.getHeight())) { // take title and buttons into account 710 scale = (document.viewport.getHeight() - 110) / loader.height; 711 loader.height*= scale; 712 loader.width *= scale; 713 } 714 715 // difference with previous image 716 var yDelta = 0; 717 if(typeof Yacs.previousImageHeight == 'number') { 718 yDelta = loader.height - Yacs.previousImageHeight; 719 } 720 Yacs.previousImageHeight = loader.height; 721 var xDelta = 0; 722 if(typeof Yacs.previousImageWidth == 'number') { 723 xDelta = loader.width - Yacs.previousImageWidth; 724 } 725 Yacs.previousImageWidth = loader.width; 726 727 // rescale on size change 728 if((yDelta !== 0) && (xDelta !== 0) && $('modal_image_panel')) { 729 730 // previous image -- <div id="modal_image_panel"><img ... 731 var previousImage = $('modal_image_panel').down(); 732 733 // current height and width 734 var currentHeight = Element.getHeight(previousImage); 735 var currentWidth = Element.getWidth(previousImage); 736 737 // compute scaling factors 738 var yScale = ((currentHeight + yDelta) / currentHeight) * 100; 739 var xScale = ((currentWidth + xDelta) / currentWidth) * 100; 740 741 // scaling previous image makes ugly things 742 var opacity = new Effect.Opacity('modal_content', {duration:0.1, from:1.0, to:0.0}); 743 Element.setStyle('modal_content', { visibility: 'hidden' }); 744 745 // adjust the overall size 746 if(yDelta !== 0) { 747 var effect = new Effect.Scale(previousImage, yScale, {scaleX: false, duration: 0.4, queue: 'end'}); 748 } 749 if(xDelta !== 0) { 750 var effect2 = new Effect.Scale(previousImage, xScale, {scaleY: false, duration: 0.4, queue: 'end'}); 751 } 752 } 753 754 // image title -- <a><span><img title="" ... 755 var imageTitle = Element.firstDescendant(Element.firstDescendant(anchor)).getAttribute('title'); 756// if(!imageTitle) 757// imageTitle = Element.firstDescendant(Element.firstDescendant(anchor)).getAttribute('alt'); 758 759 // image href 760 var imageReference = '<div id="modal_image_panel"><img src="'+anchor.getAttribute('href')+'" width="'+loader.width+'" height="'+loader.height+'" /></div>'; 761 762 // a neighbour on the left 763 if(anchor.previousAnchor) { 764 Yacs.modalCallPrevious = function() { Yacs.showImage(anchor.previousAnchor); }; 765 } else { 766 Yacs.modalCallPrevious = null; 767 } 768 769 // a neighbour on the right 770 if(anchor.nextAnchor) { 771 Yacs.modalCallNext = function() { Yacs.showImage(anchor.nextAnchor); }; 772 773 // do not wait for user click to load the image 774 var nextLoader = new Image(); 775 nextLoader.src = anchor.nextAnchor.getAttribute('href'); 776 777 } else { 778 Yacs.modalCallNext = null; 779 } 780 781 // display the image on screen 782 Yacs.displayModalBox({ title: imageTitle, 783 body: imageReference, 784 button_PREVIOUS: '<<', 785 button_TRUE: 'X', 786 button_NEXT: '>>' }); 787 788 // clear onLoad, IE behaves erratically with animated gifs otherwise 789 loader.onload = function(){}; 790 }; 791 792 // actual pre-load 793 loader.src = anchor.getAttribute('href'); 794 795 }, 796 797 /** 798 * load some opaque overlay during back-end processing 799 */ 800 startWorking: function() { 801 802 if(Yacs.workingOverlay) { 803 Element.setStyle(Yacs.workingOverlay, { display: 'block' }); 804 return true; 805 } 806 807 // insert some html at the bottom of the page that looks similar to this: 808 // 809 // <div id="yacsWorkingOverlay"> 810 // <div> 811 // <img src="/yacs/skins/_reference/ajax_working.gif"/> 812 // </div> 813 // </div> 814 815 var objWorkingImage = document.createElement("img"); 816 objWorkingImage.setAttribute('src', url_to_root + 'skins/_reference/ajax_working.gif'); 817 818 var objCentered = document.createElement("div"); 819 Element.setStyle(objCentered, { position: 'absolute', top: '30%', left: '0%', height: '25%', width: '100%', textAlign: 'center', lineHeight: '0' }); 820 objCentered.appendChild(objWorkingImage); 821 822 Yacs.workingOverlay = document.createElement("div"); 823 Yacs.workingOverlay.setAttribute('id','yacsWorkingOverlay'); 824 Element.setStyle(Yacs.workingOverlay, { position: 'fixed', top: '0', left: '0', zIndex: '1000', width: '100%', height: '100%', minHeight: '100%', backgroundColor: '#000', filter: 'alpha(opacity=20)', opacity: '0.2', display: 'block' }); 825 Yacs.workingOverlay.onclick = function() { Element.setStyle(Yacs.workingOverlay, { display: 'none' });}; 826 Yacs.workingOverlay.appendChild(objCentered); 827
828 var objBody = document.getElementsByTagName("body").item(0); 829 objBody.appendChild(Yacs.workingOverlay); 830 831 return true; 832 }, 833 834 /** 835 * hide the working overlay 836 */ 837 stopWorking: function() { 838 839 var handle = $('yacsWorkingOverlay'); 840 if(handle) { 841 Element.setStyle(handle, { display: 'none' }); 842 } 843 844 }, 845 846 /** 847 * subscribe to notifications sent by the back-end asynchronously 848 * 849 * @see users/heartbit.php 850 */ 851 subscribe: function() { 852 853 // not less than 3 seconds between two successive calls 854 var now = new Date(); 855 if((typeof Yacs.subscribeStamp == "object") && (now.getTime() < (Yacs.subscribeStamp.getTime() + 60000))) { 856 return; 857 } 858 Yacs.subscribeStamp = now; 859 860 // a transaction is taking place 861 if(Yacs.subscribeAjax) { 862 return; 863 } 864 865 // clear on-going action, if any 866 if((typeof Yacs.subscribeTimer == "number") && (Yacs.subscribeTimer > 0)) { 867 clearTimeout(Yacs.subscribeTimer); 868 Yacs.subscribeTimer = 0; 869 } 870 871 // start an ajax transaction 872 Yacs.subscribeAjax = new Ajax.Request(url_to_root + 'users/heartbit.php', { 873 method: 'get', 874 parameters: { }, 875 requestHeaders: {Accept: 'application/json'}, 876 onSuccess: Yacs.subscribeSuccess, 877 onFailure: Yacs.subscribeFailure 878 }); 879 }, 880 881 subscribeFailure: function(transport) { 882 883 if(Yacs.hasFocus) { // regular idle cycle 884 Yacs.subscribeTimer = setTimeout(Yacs.subscribe, 40000); 885 } else { // don't stress the server when we don't have the focus 886 Yacs.subscribeTimer = setTimeout(Yacs.subscribe, 120000); 887 } 888 Yacs.subscribeAjax = null; 889 890 }, 891 892 subscribeSuccess: function(transport) { 893 894 // dispatch received notification 895 var response = transport.responseText.evalJSON(true); 896 switch(response.type) { 897 case 'alert': 898 Yacs.handleAlertNotification(response); 899 break; 900 case 'browse': 901 Yacs.handleBrowseNotification(response); 902 break; 903 case 'hello': 904 Yacs.handleHelloNotification(response); 905 break; 906 } 907 908 // minimum time between two successive notifications 909 Yacs.subscribeTimer = setTimeout(Yacs.subscribe, 20000); 910 Yacs.subscribeAjax = null; 911 912 }, 913 914 // remember polling rate 915 subscribeRate: "fast", 916 917 // on-going timer, if any 918 subscribeTimer: 0, 919 920 /** 921 * sort a table 922 * 923 * @param id of the table, tbody or tfoot element to be sorted 924 * @param index of the column to sort, starting at 0 925 * @param if true, sort in reverse order 926 */ 927 sortTable: function(id, column, rev) { 928 929 // get the table or table section to sort 930 var handle = document.getElementById(id); 931 932 // on first sort set up an array of reverse sort flags 933 if(!handle.reverseSort || (handle.reverseSort === null)) { 934 handle.reverseSort = []; 935 } 936 937 // set the initial sort direction 938 if(!handle.reverseSort[column] || (handle.reverseSort[column] === null)) { 939 handle.reverseSort[column] = rev; 940 } 941 942 // if this column was the last one sorted, reverse its sort direction 943 if(column == handle.lastColumn) { 944 handle.reverseSort[column] = !handle.reverseSort[column]; 945 } 946 947 // remember this column as the last one sorted 948 handle.lastColumn = column; 949 950 // hide the table during operations 951 var oldDsply = handle.style.display; 952 handle.style.display = "none"; 953 954 // use a selection sort algorithm 955 var tmpEl; 956 var i, j; 957 var minVal, minIdx; 958 var testVal; 959 var cmp; 960 for(i = 0; i < handle.rows.length - 1; i++) { 961 962 // assume the current row has the minimum value 963 minIdx = i; 964 minVal = Yacs.getTextValue(handle.rows[i].cells[column]); 965 966 // look for a smaller values in following rows 967 for(j = i + 1; j < handle.rows.length; j++) { 968 testVal = Yacs.getTextValue(handle.rows[j].cells[column]); 969 cmp = Yacs.compareValues(minVal, testVal); 970 971 // the case of reverse ordering 972 if(handle.reverseSort[column]) { 973 cmp = -cmp; 974 } 975 976 // sort by the first column if those values are equal 977 if(cmp === 0 && column !== 0) { 978 cmp = Yacs.compareValues(Yacs.getTextValue(handle.rows[minIdx].cells[0]), Yacs.getTextValue(handle.rows[j].cells[0])); 979 } 980 981 // this is the new minimum 982 if (cmp > 0) { 983 minIdx = j; 984 minVal = testVal; 985 } 986 } 987 988 // move the minimum row just below the current row 989 if (minIdx > i) { 990 tmpEl = handle.removeChild(handle.rows[minIdx]); 991 handle.insertBefore(tmpEl, handle.rows[i]); 992 } 993 } 994 995 // beautify the table 996 Yacs.beautifyTable(handle, column); 997 998 // Set team rankings. 999 // setRanks(handle, column, rev); 1000 1001 // show the table again 1002 handle.style.display = oldDsply; 1003 1004 return false;
1005 }, 1006 1007 /** 1008 * @link http://ajaxcookbook.org/ 1009 */ 1010 syslog: function(message) { 1011 1012 if (!Yacs.window_ || Yacs.window_.closed) { 1013 var win = window.open("", null, "width=400,height=200," + 1014 "scrollbars=yes,resizable=yes,status=no," + 1015 "location=no,menubar=no,toolbar=no"); 1016 if (!win) { return; } 1017 var doc = win.document; 1018 doc.write("<html><head><title>Debug Log</title></head>" + 1019 "<body></body></html>"); 1020 doc.close(); 1021 Yacs.window_ = win; 1022 } 1023 var logLine = Yacs.window_.document.createElement("div"); 1024 logLine.appendChild(Yacs.window_.document.createTextNode('=> ' + message)); 1025 Yacs.window_.document.body.appendChild(logLine); 1026 1027 }, 1028 1029 /** 1030 * This handler adds code to tabbing items, to process further clicks. 1031 * 1032 * It has to be called once, after the construction of proper DOM elements. 1033 * You can either build the DOM manually, then place a call to this function, 1034 * or, alternatively, just call Skin::build_tabs() from within you PHP code 1035 * to have everything done automatically. 1036 * 1037 * @param tabs A list of tabs related to panels and URLs 1038 * @param args This corresponds to the Ajax options supported in prototype.js 1039 * 1040 * @see users/view.php 1041 * @see skins/skin_skeleton.php 1042 * 1043 * @link http://actsasflinn.com/Ajax_Tabs/index.html AJAX Tabs (Rails redux) 1044 * @link http://20bits.com/2007/05/23/dynamic-ajax-tabs-in-20-lines/ 1045 */ 1046 tabs: function(tabs, args) { 1047 1048 Yacs.tabs_list = tabs; 1049 Yacs.tabs_args = args; 1050 1051 // react to clicks 1052 var id; 1053 for(id in tabs) { 1054 if(tabs.hasOwnProperty(id)) { 1055 Event.observe($(id), 'click', Yacs.tabs_event); 1056 } 1057 } 1058 }, 1059 1060 /** 1061 * click on a tab 1062 */ 1063 tabs_event: function(e) { 1064 1065 // target the clicked tab 1066 var clicked = Event.element(e); 1067 1068 // if we click on a link, move upwards to list item -- 'a' is for XHTML strict, 'A' for other cases 1069 if((clicked.tagName == 'a') || (clicked.tagName == 'A')) { 1070 clicked = clicked.parentNode; 1071 } 1072 1073 // trigger custom behavior, if any 1074 if(typeof Yacs.tabs_args.onClick == 'function') { 1075 Yacs.tabs_args.onClick(clicked); 1076 } 1077 1078 // activate the clicked tab -- see skins/_reference/ajax.css 1079 var iterator; 1080 for(iterator in Yacs.tabs_list) { 1081 if(clicked.id == $(iterator).id) { 1082 $(iterator).className = 'tab-foreground'; 1083 } else { 1084 $(iterator).className = 'tab-background'; 1085 } 1086 } 1087 1088 // activate the related panel -- see skins/_reference/ajax.css 1089 for(iterator in Yacs.tabs_list) { 1090 if(clicked.id == $(iterator).id) { 1091 $(Yacs.tabs_list[iterator][0]).className = 'panel-foreground'; 1092 1093 // load panel content, if any 1094 if(Yacs.tabs_list[iterator].length > 1) { 1095 Yacs.updateOnce(Yacs.tabs_list[iterator][0], Yacs.tabs_list[iterator][1], Yacs.tabs_args); 1096 } 1097 1098 } else { 1099 $(Yacs.tabs_list[iterator][0]).className = 'panel-background'; 1100 } 1101 } 1102 1103 // do not propagate event 1104 Event.stop(e); 1105 }, 1106 1107 /** 1108 * change text size 1109 * 1110 * Use this function in links, to allow surfer to adjust text size. 1111 * 1112 * @param target container in page 1113 * @param size increment or decrement 1114 */ 1115 textResize: function(handle, increment) { 1116 1117 // get current size 1118 var current = Yacs.getCookie('TextSize'); 1119 var currentSize = 2; 1120 if(current !== null) { 1121 eval("current = "+current); 1122 currentSize = current.size; 1123 } 1124 1125 // change it 1126 currentSize += increment; 1127 if(currentSize < 0 ) { currentSize = 0; } 1128 if(currentSize > 6 ) { currentSize = 6; } 1129 1130 // save it for 6 months = 6 * 30 = 180 days 1131 Yacs.setCookie('TextSize', '{ handle: "' + handle + '", size: ' + currentSize + ' }', 180); 1132 1133 // actual style update 1134 Yacs.textSize(); 1135 }, 1136 1137 /** 1138 * enforce current text settings 1139 * 1140 * This is invoked each time a page is loaded 1141 */ 1142 textSize: function() { 1143 1144 // use data from cookie 1145 var current = Yacs.getCookie('TextSize'); 1146 if(current === null) { 1147 return; 1148 } 1149 eval('current = ' + current); 1150 1151 // do nothing on standard size 1152 if(current == 2) { 1153 return; 1154 } 1155 1156 // get actual style 1157 var allSizes = [ 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large' ]; 1158 var currentSize = allSizes[ current.size ]; 1159 1160 // resize the target container 1161 $(current.handle).style.fontsize = currentSize; 1162 1163 // also resize poorly inherited items 1164 allTags = $(current.handle).getElementsByTagName('div'); 1165 for(var index = 0; index < allTags.length; index++ ) { allTags[index].style.fontSize = currentSize; } 1166 allTags = $(current.handle).getElementsByTagName('td'); 1167 for(index = 0; index < allTags.length; index++ ) { allTags[index].style.fontSize = currentSize; } 1168 allTags = $(current.handle).getElementsByTagName('tr'); 1169 for(index = 0; index < allTags.length; index++ ) { allTags[index].style.fontSize = currentSize; } 1170 }, 1171 1172 /** 1173 * toggle a folded box 1174 * 1175 * @param the box 1176 * @param string URL of the extending icon 1177 * @param string URL of the collapsing icon 1178 */ 1179 toggle_folder: function(node, plus_href, minus_href) { 1180 1181 // unfold the branch if it is not visible 1182 if(node.nextSibling.style.display == 'none') { 1183 node.nextSibling.style.display = 'block'; 1184 1185 // change the image (if there is an image) 1186 if(node.childNodes.length > 0) { 1187 if(node.childNodes.item(0).nodeName == "IMG") { 1188 node.childNodes.item(0).src = minus_href; 1189 } 1190 } 1191 1192 // collapse the branch if it is visible 1193 } else { 1194 node.nextSibling.style.display = 'none'; 1195 1196 // change the image (if there is an image) 1197 if(node.childNodes.length > 0) { 1198 if(node.childNodes.item(0).nodeName == "IMG") { 1199 node.childNodes.item(0).src = plus_href; 1200 } 1201 } 1202 1203 } 1204 1205 }, 1206 1207 /** 1208 * update content asynchronously 1209 * 1210 * This function displays a nice spinning image while loading the page. 1211 * 1212 * @param string id of the target CSS container 1213 * @param string web address to fetch new snippet 1214 * @param mixed additional parameters to transmit to Ajax 1215 * 1216 */ 1217 update: function(panel, address, args) { 1218 1219 $(panel).innerHTML = '<img alt="*" src="' + Yacs.spinningImage.src + '" style="vertical-align:-3px" />'; 1220 1221 var updater = new Ajax.Updater(panel, address, $H({ 1222 asynchronous: true, 1223 method: 'get', 1224 evalScripts: true 1225 }).merge(args) 1226 ); 1227 1228 }, 1229 1230 /** 1231 * update a modal box 1232 *
1233 * This is called internally by Yacs.displayModalBox() 1234 * 1235 * @param string XHTML content of the box 1236 */ 1237 updateModalBox: function(content) { 1238 1239 // update box content 1240 Element.update('modal_content', content); 1241 1242 // adjust box size 1243 Element.setStyle('modal_content', { width: 'auto', height: 'auto' }); 1244 1245 // center the box 1246 var yShift, xShift; 1247 yShift = Math.floor(((document.viewport.getHeight() - $('modal_centered').offsetHeight) / 2) - $('modal_centered').offsetTop); 1248 xShift = Math.floor(((document.viewport.getWidth() - $('modal_centered').offsetWidth) / 2) - $('modal_centered').offsetLeft); 1249 1250 // update box position 1251 if((Math.abs(yShift) > 1) || (Math.abs(xShift) > 1)) { 1252 var effect = new Effect.MoveBy('modal_centered', yShift, xShift, {duration: 0.2, queue: 'end'}); 1253 } 1254 1255 }, 1256 1257 /** 1258 * set content only once 1259 * 1260 * This function is similar to Yacs.update(), except it does nothing if the target 1261 * item already contains something. 1262 * 1263 * Use this function for example to populate tabbed panels of a complex page. 1264 * Look at Yacs.tabs() above for a practical example of use 1265 * 1266 * @param string id of the target CSS container 1267 * @param string web address to fetch new snippet 1268 * @param mixed additional parameters to transmit to Ajax 1269 * 1270 */ 1271 updateOnce: function(panel, address, args) { 1272 1273 // do nothing if the panel contains something 1274 if(!$(panel).innerHTML || ($(panel).innerHTML === '') || ($(panel).innerHTML == '<img alt="*" src="' + Yacs.spinningImage.src + '" style="vertical-align:-3px" />')) { 1275 Yacs.update(panel, address, args); 1276 } 1277 1278 } 1279 1280}; 1281 1282// initialize yacs 1283Event.observe(window, "load", Yacs.onWindowLoad);
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.