Hey guys

Attached is probably the final version of my Effect.Accordion. I don't
know how it compares to the previous Effect.Accoridon submitted.

It still flickers a bit, however there is a hack which fixes the
container's height, so it doesn't flicker the rest of the elements on
the page. It's also barely noticable in IE6. Any suggestions on how to
fix this would be great. I think it's a bug related to
Effect.BlindUp/Down more than my code - the flicker only happens at the
start and end of the effect, and it's a flicker I see a lot.

As before:

<div id="accordion">
    <div>
       <h3>Title</h3>
       <div>
          <p>Content</p>
       </div>
    </div>
    <div>
       <h3>Title</h3>
       <div>
          <p>Content</p>
       </div>
    </div>
    <div>
       <h3>Title</h3>
       <div>
          <p>Content</p>
       </div>
    </div>
    <div>
       <h3>Title</h3>
       <div>
          <p>Content</p>
       </div>
    </div>
</div>
<script>
<!--
    new Effect.Accordion("accordion");
-->
</script>

It fixes the height of all the panels to match the largest panel + 20px.
If you have extremely large panels I suggest using overflow: auto and
fixing a height on the div - that is "#accordion div div" - the script
should pick up the changes just fine. I haven't tried a scrollable
panel, you should see the same results with using Effect.BlindUp on an
element with overflow:auto and a fixed height.

It's also pretty resilient to applying the effect more than once.

There are still some outstanding bugs - the smaller flicker, and you
can't use <div> within your <h3>. The h3 can be modifier by using the
headingtag option:

new Effect.Accoridon("accordion", { headingtag: "h2" });

There are also similar paneltag and bodytag options - I would leave them
alone though.

One more option: "initdisplay" decides the panel to initially display,
starting from 0.

My application required that all panels should be collapsable - clicking
on the heading of a panel that was displayed would hide it, leaving all
panels hidden. If this is a problem for someone I can "optionise" that
behaviour.

FYI my application required that panel headings and contents be edited
with InPlaceEditor, that panels could be added and removed "Live", and
that panel headings should be re-orderable, so my code was written so
that it shouldn't interfere with this. The only caveat is that dragging
a panel headings would activate it when it was dropped. I couldn't be
arsed with patching s.a.u and keeping YET ANOTHER custom change, so I'm
just living with it. The static function "Effect.Accordion.Reset" should
be used when replacing the contents of the accordion, for example with
Ajax.Updater. I don't think my code supports "new Insertion.Bottom".

Enjoy

-Rob
Effect.Accordion = Class.create();
Effect.Accordion.prototype = {
	initialize: function(element, options) {
		if (Element.hasClassName(element, "is_accordion"))
			return false;
		else
			Element.addClassName(element, "is_accordion");
		this.container = $(element);
		this.container.style.height = "auto";
		this.options = Object.extend({
			paneltag: "div", // The tag of the panel containers
			headingtag: "h3", // The tag of the headings
			bodytag: "div", // The body
			initdisplay: 0 // Index of the panel to show first
		}, options || {});
		this.options.paneltag = this.options.paneltag.toUpperCase();
		this.options.headingtag = this.options.headingtag.toUpperCase();
		this.options.bodytag = this.options.bodytag.toUpperCase();
		this.panels = Array();
		this.headings = Array();
		this.bodies = Array();
		this.maxheight = 0;
		panels = this.container.childNodes;
		pl = panels.length;
		n = 0;
		for (i = 0; i < pl; i++) {
			if (panels[i].nodeName != this.options.paneltag)
				continue;
			this.panels[n] = panels[i];
			this.headings[n] = this.getPanelHeading(this.panels[n]);
			this.bodies[n] = this.getPanelBody(this.panels[n]);
			height = Element.getHeight(this.bodies[n]);
			if (height > this.maxheight)
				this.maxheight = height;
			n++;
		}
		// Set some heights
		bl = this.bodies.length;
		this.maxheight += 20;
		for (i = 0; i < bl; i++) {
			this.bodies[i].style.height = this.maxheight+"px";
		}
		// We should probably sort out some event listeners
		this.clickHandler = this.onClickHeading.bindAsEventListener(this);
		pl = this.panels.length;
		for (i = 0; i < pl; i++) {
			heading = this.headings[i];
			if (this.options.initdisplay != i) {
				Element.hide(this.bodies[i]);
			} else {
				this.curvisible = i;
			}
			Event.observe(heading, "click", this.clickHandler);
			heading.style.cursor = "pointer";
		}
		this.container.style.height = Element.getHeight(this.container)+"px";
	},
	onClickHeading: function(ev) {
		el = Event.element(ev);
		clickedid = this.getHeadingNumber(el);
		panel = this.panels[clickedid];
		if (panel == this.panels[this.curvisible]) {
			Effect.toggle(this.bodies[this.curvisible], "blind", { duration: 0.5 });
			return;
		}
		newborn = this.bodies[clickedid];
		victim = this.bodies[this.curvisible];
		this.curvisible = clickedid;
		new Effect.Parallel (
		[
			Effect.BlindUp(victim, { sync: true }),
			Effect.BlindDown(newborn, { sync: true })
		], {
			duration: 0.5
		});
	},
	getPanelHeading: function(el) {
		return el.getElementsByTagName(this.options.headingtag)[0];
	},
	getPanelBody: function(el) {
//		n = 0;
//		if (this.options.bodytag == this.options.headingtag)
//			n = 1;
		return el.getElementsByTagName(this.options.bodytag)[0];
	},
	/*
	getPanelBody: function(el) { // FIXME: Longer version to compensate for the body tag appearing in the heading tag. This sliently halts execution however. Fix if you need it.
		n = 0;
		if (this.options.bodytag == this.options.headingtag)
			n = 1;
		children = el.childNodes;
		cl = children.length;
		for (i = 0; i < cl; i++) {
			if (children[i].nodeName == this.options.bodytag) {
				if (n == 0)
					return children[i];
				else
					n = n-1;
			}
		}
	}
	*/
	getPanelNumber: function(el) {
		pl = this.panels.length;
		for (i = 0; i < pl; i++) {
			if (this.panels[i] == el)
				return i;
		}
		return "Not found";
	},
	getHeadingNumber: function(el) {
		while (el.nodeName != this.options.headingtag && el) {
			el = el.parentNode;
		}
		pl = this.headings.length;
		for (i = 0; i < pl; i++) {
			if (this.headings[i] == el)
				return i;
		}
		alert(el);
		return "Not found";
	}
};
Effect.Accordion.Reset = function(el) {
	if (Element.hasClassName(el, "is_accordion"))
		Element.removeClassName(el, "is_accordion");
}
_______________________________________________
Rails-spinoffs mailing list
Rails-spinoffs@lists.rubyonrails.org
http://lists.rubyonrails.org/mailman/listinfo/rails-spinoffs

Reply via email to