PageSourceSearch

http://crazymama.ru/jscripts/ns/main_Blinds.js

js crazymama.ru collected 2026-10-02 10:18:41 UTC 19,586 bytes, 496 lines download raw bytes

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.