1/* 2 * @Class: Blinds 3 * 4 * @Description: A class which can turn a set of elements into overlapping 'blinds' which open and close with animation 5 * 6 * @Usage: 7 * 8 * var s3 = new Blinds(collection contentElements,element containerElement, capElement, options hash); 9 * 10 * <div id="" class="blinds 89 auto click closeable closed"> 11 * 12 * blinds - makes the div contents into blinds 13 * 89 - height of content by default (omitting this parameter makes the blinds 14 * automatically-sized by content height of each blind. to avoid this, make 15 * sure to explicitly specify the height in case you want statically-sized heights) 16 * auto - make height automatically detected 17 * click - make blinds click-activated instead of mouseover-activated 18 * closeable - make blinds able to close 19 * closed - make all the sections of the blinds closed initially 20 * 21 * options hash details: 22 * 23 * { 24 * 'current_id':'test123', the DOM id of the link or heading which is the "current" item 25 * 'current_item_class':'someclassname' the CSS classname to apply to an element which is "current" 26 * } 27 * 28 * @Methods: no public methods 29 * 30 */ 31 32var Blinds = (!detectBrowser.modernBrowser())?function(){}:Class.create(); 33Blinds.prototype = (!detectBrowser.modernBrowser())?{}:{ 34 initialize: function(elements,containerElement,capElement,options,id){ //extra id param 35 if(!elements){ 36 return false; 37 } 38 if(!containerElement){ 39 return false; 40 } 41 this.contentHeight = 80; 42 var classNames = $A(containerElement.classNames()); 43 44 this.containerElement = containerElement; 45 // Some elements look bizarre during initial load until javascript gets to them. 46 // For those elements, we apply a class called "prejs" so that once the code is 47 // running it can remove that CSS class. This way, a "pre-rendered" state can be 48 // seen by the user while the Javascript is still loading, and unsettled layout 49 // can be avoided. 50 if(this.containerElement.className.indexOf("prejs_")>-1){ 51 for(var i=0;i<classNames.length;i++){ 52 if(classNames[i].indexOf("prejs_")>-1){ 53 this.containerElement.removeClassName(classNames[i]); 54 } 55 } 56 } 57 58 this.contentHeight = parseInt(classNames.find(function(cname){ return parseInt(cname)>0; })); 59 this.eventType = (classNames.include("click")?"click":"mouseover"); 60 this.autoHeight = classNames.include("auto") || (typeof(this.contentHeight)!='number'); 61 this.isAnimating = false; 62 this.nextEffect = null; 63 this.initiallyClosed = classNames.include("closed"); 64 this.closeable = this.initiallyClosed || classNames.include("closeable"); 65 if(options!=null && typeof(options)=='object' && typeof(options['current_id'])=='string' && options['current_id'].strip()!=""){ 66 var currentItemClass = "current_item"; 67 if(typeof(options['current_item_class'])=='string'){ 68 options['current_item_class'] = currentItemClass; 69 } 70 var currentItem = $(options['current_id'].strip()); 71 if(currentItem){ 72 currentItem.addClassName(currentItemClass); 73 // climb up to .content 74 // prev() yields header 75 // give header current style too 76 77 // if this is NOT the content node, we have to climb up to it 78 if(currentItem.hasClassName("content")!=true){ 79 var p = currentItem; 80 var done = false; 81 while(!done && p!=document.body){ 82 p = p.up(); 83 if(p.hasClassName("content")){ 84 p.addClassName(currentItemClass); 85 this.contentElementToOpen = p; 86 if(p.previous()){ 87 p.previous().addClassName(currentItemClass); 88 } 89 done = true; 90 } 91 } 92 } else { 93 this.contentElementToOpen = currentItem; 94 } 95 } 96 97 } 98 if(this.contentElementToOpen){ 99 this.previousElement = this.contentElementToOpen; 100 } else { 101 this.previousElement = elements[0]; 102 } 103 this.elements = elements; 104 this.capElement = capElement; 105 this.currentlyHoveredElement = -1; // index of currently hovered element. Used to track if we have "committed" to hovering over a blind 106 this.measuredSizes = []; 107 this.transition = (classNames.include("click")?Effect.Transitions.EaseTo:Effect.Transitions.EaseFromTo); 108 this.makeSliders(); 109 // id check 110 if (id != null) { 111 this.activateBlind (id); 112 } 113 }, 114 115 /*eTouch - Parag*/ 116 activateBlind: function (id) { 117 var element = null; 118 var tokens = id.split ('_'); 119 this.elements.each (function(item){ 120 if (item.getAttribute ("blindsindex") == $(tokens[0]).parentNode.parentNode.getAttribute ("blindsindex")) { 121 element = item; 122 } 123 }) 124 if (element != null) { 125 var func = this.makeSlideFunc (this.elements, element, 126 $(id).parentNode.parentNode.getAttribute ("blindsindex")); 127 func (); 128 } 129 $(id).setStyle ('font-weight: bold'); 130 }, 131 /*eTouch -Parag*/ 132 133 // iterate through the content blocks and make assign event listeners which trigger the sliding animation 134 makeSliders: function(elements){ 135 this.elements.each(function(item,i){ 136 item = $(item); 137 138 item.setAttribute("blindsindex",i); 139 if(this.contentElementToOpen){ 140 if(this.contentElementToOpen==item){ 141 // found the "current" item 142 item.setAttribute("blindstatus","open"); 143 } else { 144 item.setAttribute("blindstatus","closed"); 145 } 146 } else { 147 /* this is added,not to open the first item of left nav if nothing is opened. */ 148 //item.setAttribute("blindstatus",((i==0)?((this.initiallyClosed==true)?"closed":"open"):("closed"))); 149 if(this.eventType == 'click') { 150 item.setAttribute("blindstatus","closed"); 151 } else { 152 item.setAttribute("blindstatus",((i==0)?((this.initiallyClosed==true)?"closed":"open"):("closed"))); 153 } 154 /* End of changes for,not to open the first item of left nav if nothing is opened. */ 155 } 156 157 158 // if a particular element is to be opened because it's "current", we make it open 159 // and make all others closed 160 if(this.contentElementToOpen){ 161 if(this.contentElementToOpen==item && this.initiallyClosed==false){ 162 item.previous().addClassName('active_header'); 163 item.addClassName('active_content'); 164 item.next().addClassName('active_footer'); 165 } else { 166 item.previous().addClassName('inactive_header'); 167 item.addClassName('inactive_content'); 168 item.next().addClassName('inactive_footer'); 169 } 170 } else { 171 // If no particular element is to be open, set the 0th element as open (unless initiallyClosed is true) 172 if(i==0 && this.initiallyClosed==false) { 173 174 /* this is added,not to open the first item of left nav if nothing is opened. */ 175 if(this.eventType == 'click') { 176 item.previous().addClassName('inactive_header'); 177 item.addClassName('inactive_content'); 178 item.next().addClassName('inactive_footer'); 179 } else { 180 item.previous().addClassName('active_header'); 181 item.addClassName('active_content'); 182 item.next().addClassName('active_footer'); 183 } 184 /* End of changes for,not to open the first item of left nav if nothing is opened. */ 185 186 } else { 187 item.previous().addClassName('inactive_header'); 188 item.addClassName('inactive_content'); 189 item.next().addClassName('inactive_footer'); 190 } 191 } 192 193 // measure size of the content area 194 if(this.autoHeight) { 195 this.measuredSizes[i] = item.getHeight(); 196 } 197 198 if(this.initiallyClosed==true){ 199 item.setStyle({'height':'0px'}); 200 } else { 201 if(this.contentElementToOpen){ 202 if(this.contentElementToOpen==item){ 203 item.setStyle({'height':(this.autoHeight?this.measuredSizes[i]:this.contentHeight)+'px'}); 204 } else { 205 item.setStyle({'height':'0px'}); 206 } 207 } else { 208 //item.setStyle({'height':((i==0)?(this.autoHeight?this.measuredSizes[i]:this.contentHeight):(0))+'px'}); 209 /* this is added,not to open the first item of left nav if nothing is opened. */ 210 if(this.eventType == 'click') { 211 item.setStyle({'height':'0px'}); 212 } else { 213 item.setStyle({'height':((i==0)?(this.autoHeight?this.measuredSizes[i]:this.contentHeight):(0))+'px'}); 214 } 215 /* End of changes for,not to open the first item of left nav if nothing is opened. */ 216 } 217 } 218 219 // slightly different classes get applied to the last item in the blinds so they can be styled as a cap or base. 220 if(i==this.elements.length - 1){ 221 item.previous().addClassName('last_header'); 222 item.addClassName("last_content"); 223 item.next().addClassName('last_footer'); 224 } 225 item.previous().isHeader=true; 226 item.isContent=true; 227 // observe the header, content, and footer of each blind panel with mouse events (click or hover depending on config) 228 [item.previous(),item,item.next()].each(function(subItem){ 229 var slideFunc = this.makeSlideFunc(this.elements,item,i); 230 231 // Allow the keyboard focus to actually activate this blind section 232 subItem.getElementsBySelector("a").each(function(focusableItem){ 233 if(focusableItem.hasClassName('deadmouse')){ 234 // we require some placeholder / dummy links that don't go anywhere 235 // kill all links that have class "deadmouse" 236 237 // both of these events fire when a click happens so we don't want the side to happen twice 238 var focusHasFired = false;
239 Event.observe(focusableItem,'click',function(ev){ 240 if(focusHasFired){ 241 focusHasFired = false; 242 } else { 243 slideFunc(); 244 } 245 ev.stop(); 246 focusHasFired = false; 247 return false; 248 }.bind(this)); 249 Event.observe(focusableItem,'focus',function(ev){ 250 focusHasFired = true; 251 slideFunc(); 252 ev.stop(); 253 return false; 254 }.bind(this)); 255 } else { 256 if(subItem.isHeader==true){ 257 // all links inside of headers can be focus-triggers-open 258 Event.observe(focusableItem,'focus',slideFunc); 259 } else if (subItem.isContent==true){ 260 // for content we have to proxy slideFunc through a check to see if 261 // the item is currently open before triggering slideFunc 262 Event.observe(focusableItem,'focus',function(){ 263 if(subItem.getAttribute("blindstatus")!="open"){ 264 // if this item is already opening, we need to tell slideFunc not to queue this attempt to 265 // open after the current open. Thus, we pass in the current item plus a flag to tell it 266 // to check that we aren't trying to double-open a given content area 267 if(this.isAnimating!=true){ 268 slideFunc(true,subItem); 269 } 270 } 271 }.bind(this)); 272 } 273 } 274 }.bind(this)); 275 276 if(subItem) { 277 Event.observe(subItem,this.eventType,slideFunc); 278 } 279 if(subItem && this.eventType=='mouseover') { 280 Event.observe(subItem,'mouseout',this.makeMouseOutFunc(this.elements,item,i)); 281 } 282 }.bind(this)); 283 284 Event.observe(item.previous(),"mouseover",function(){item.previous().addClassName('header_hover')}); 285 Event.observe(item.previous(),"mouseout",function(){item.previous().removeClassName('header_hover')}); 286 }.bind(this)); 287 this.setCapClasses(this.elements[0]); 288 this.setFooterBlendingClasses(this.elements[0]); 289 }, 290 291 // check for a queued effect. If one is present cancel the current one and start the new one 292 checkForNextEffect: function(effect){ 293 this.isAnimating = false; 294 if(typeof(this.nextEffect)=='function'){ 295 var f = this.nextEffect; 296 this.nextEffect = null; 297 f(); 298 } 299 }, 300 301 // if there is a cap (element immediately above the top header), style it according to whether the header is active 302 setCapClasses: function(currentElement){ 303 if(this.capElement) { 304 // is the capping element active ? 305 if(parseInt(currentElement.getAttribute("blindsindex"))==0){ 306 this.capElement.removeClassName("cap_inactive"); 307 this.capElement.addClassName("cap_active"); 308 } else { 309 this.capElement.removeClassName("cap_active"); 310 this.capElement.addClassName("cap_inactive"); 311 } 312 } 313 }, 314 315 // a footer can either be above an inactive header, or above an active header, or not above any header at all. 316 // Append appropriate CSS classes 317 setFooterBlendingClasses: function(currentElement,activeIndex){ 318 if(typeof(activeIndex)!='number') activeIndex = parseInt(currentElement.getAttribute("blindsindex")); 319 for(var i=0;i<this.elements.length;i++){ 320 if(i==activeIndex - 1){ 321 this.elements[i].next().removeClassName("above_inactive"); 322 this.elements[i].next().addClassName("above_active"); 323 } else { 324 if(i!=this.elements.length - 1){ 325 this.elements[i].next().removeClassName("above_active"); 326 this.elements[i].next().addClassName("above_inactive"); 327 } 328 } 329 } 330 }, 331 332 makeMouseOutFunc:function(elements,currentElement,i){ 333 return function(){ 334 if(this.currentlyHoveredElement==i){ // clear commitment to this hover 335 this.currentlyHoveredElement = -1; 336 } 337 }.bind(this); 338 }, 339 340 // return a function that makes element, the i'th element, slide to its open location. All other 341 // elements should slide to their locations too 342 makeSlideFunc: function(elements, currentElement, i){ 343 var newEffect = function(dontQueueIfAnimating, dontQueueElementObject){ 344 // drop self into basket. This slide gets queued up to be the next slide to happen (unless another one comes along) 345 if(this.isAnimating==true){ 346 // this effect can be called with a flag that indicates not to queue after the current transition is finished, 347 // but rather just let this attempt go ignored. This is used for keyboard focus control for links inside of the 348 // content region of the blind 349 if(dontQueueIfAnimating!=true){ 350 this.nextEffect = newEffect; 351 } else { 352 if(dontQueueElementObject!=currentElement){ 353 this.nextEffect = newEffect; 354 } 355 } 356 return; 357 } 358 this.currentlyHoveredElement = i; 359 // if closing elements is not permitted and the to-slide element matches 360 // the previously-slid element, don't allow a close 361 if(this.closeable!=true && this.previousElement==currentElement) { 362 return; 363 } 364 setTimeout(function(){ 365 if(this.currentlyHoveredElement==i){ 366 367 var swapBlindStyles = function(){ 368 // apply active + inactive classes 369 370 // if previous==current, that means the to-be-slid element matches the previously-slid element 371 if(this.previousElement==currentElement){ 372 if(currentElement.getAttribute("blindstatus")=="closed"){ 373 // currently closed - going to be open 374 currentElement.previous().removeClassName('inactive_header'); 375 currentElement.removeClassName('inactive_content'); 376 currentElement.next().removeClassName('inactive_footer'); 377 this.setCapClasses(currentElement); 378 this.setFooterBlendingClasses(currentElement); 379 currentElement.previous().addClassName('active_header'); 380 currentElement.addClassName('active_content'); 381 currentElement.next().addClassName('active_footer'); 382 } else { 383 // currently open - going to be closed 384 currentElement.previous().addClassName('inactive_header'); 385 currentElement.addClassName('inactive_content'); 386 currentElement.next().addClassName('inactive_footer'); 387 currentElement.previous().removeClassName('active_header'); 388 currentElement.removeClassName('active_content'); 389 currentElement.next().removeClassName('active_footer'); 390 } 391 } else { 392 // Normal slide - some element needs to be open, some *other* element needs to be closed. 393 this.previousElement.previous().addClassName('inactive_header'); 394 this.previousElement.addClassName('inactive_content'); 395 this.previousElement.next().addClassName('inactive_footer'); 396 currentElement.previous().addClassName('active_header'); 397 currentElement.addClassName('active_content'); 398 currentElement.next().addClassName('active_footer'); 399 // remove active + inactive classes 400 this.previousElement.previous().removeClassName('active_header'); 401 this.previousElement.removeClassName('active_content'); 402 this.previousElement.next().removeClassName('active_footer'); 403 currentElement.previous().removeClassName('inactive_header'); 404 currentElement.removeClassName('inactive_content'); 405 currentElement.next().removeClassName('inactive_footer'); 406 this.setCapClasses(currentElement); 407 this.setFooterBlendingClasses(currentElement); 408 } 409 }.bind(this); 410 411 // The target height for the content region is either the automatically-measured height or 412 // a strictly-defined height in the CSS class of the blinds 413 var targetHeight = (this.autoHeight?this.measuredSizes[i]:this.contentHeight)+'px'; 414 var fx; 415 if(this.previousElement==currentElement && this.closeable==true){ 416 // next element is also current 417 if(currentElement.getAttribute("blindstatus")=="closed"){ 418 // open it 419 fx = [new Effect.RoundedMorph(currentElement,{style:{'height':targetHeight},sync:true,transition:this.transition})]; 420 } else { 421 // close it 422 fx = [new Effect.RoundedMorph(currentElement,{style:{'height':'0px'},sync:true,transition:this.transition})]; 423 } 424 } else { 425 fx = [ 426 new Effect.RoundedMorph(currentElement,{style:{'height':targetHeight},sync:true,transition:this.transition}), 427 new Effect.RoundedMorph(this.previousElement,{style:{'height':'0px'},sync:true,transition:this.transition}) 428 ]; 429 } 430 431 // 432 // Note: 433 // if closed , remove hide_blind before opening 434 // if open, add hide_blind after closing 435 // 436 new Effect.Parallel(fx,{ 437 fps:50,wait:true,duration:500/1000, 438 beforeStart:function(effect){ 439 // note: WHEN the blindstyles are swapped is order-of-operations sensitive, it must happen 440 // before setClosed() or setOpen() run. 441 swapBlindStyles(); 442 this.isAnimating = true; 443 // about to open, so remove the content hiding for Safari2 444 if(this.previousElement==currentElement && this.closeable){ 445 if(currentElement.getAttribute("blindstatus")=="closed"){ 446 currentElement.removeClassName('hide_blind'); 447 } 448 } else { 449 currentElement.removeClassName('hide_blind'); 450 } 451 452 }.bind(this), 453 afterFinish:function(effect){ 454 if(this.previousElement==currentElement && this.closeable){ 455 if(currentElement.getAttribute("blindstatus")=="closed"){ 456 this.setOpen(currentElement); 457 } else { 458 this.setClosed(currentElement); 459 // has just finished closing, add content hiding for Safari2 460 currentElement.addClassName('hide_blind'); 461 } 462 } else { 463 this.setOpen(currentElement); 464 this.setClosed(this.previousElement); 465 // has just finished closing, add content hiding for Safari2 466 this.previousElement.addClassName('hide_blind'); 467 } 468 this.previousElement = currentElement; 469 this.checkForNextEffect(); 470 }.bind(this) 471 } 472 ); 473 this.currentlyHoveredElement = -1; 474 } 475 }.bind(this),(this.eventType=='click')?15:225); 476 // setTimeout 477 }.bind(this); 478 return newEffect; 479 }, 480 481 // given elem the CSS classes which indicate it is an open blind 482 setOpen:function(elem){ 483 elem = $(elem); 484 elem.setAttribute("blindstatus","open"); 485 elem.addClassName("blind_open"); 486 elem.removeClassName("blind_closed"); 487 }, 488 489 // given elem the CSS classes which indicate it is a closed blind 490 setClosed:function(elem){ 491 elem = $(elem); 492 elem.setAttribute("blindstatus","closed"); 493 elem.addClassName("blind_closed"); 494 elem.removeClassName("blind_open"); 495 } 496};
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.