Show:

File: platform/plugins/Q/web/js/tools/expandable.js

(function (Q, $) {
/**
 * @module Q-tools
 */
	
/**
 * Implements expandable containers that work on most modern browsers,
 * including ones on touchscreens.
 * @class Q expandable
 * @constructor
 * @param {Object} [options] Override various options for this tool
 *  @param {String} [options.title] Required. The title for the expandable.
 *  @param {String} [options.content] Required. The content.
 *  @param {Number} [options.count] A number, if any, to display when collapsed.
 *  @param {Number} [options.spaceAbove] How many pixels of space to leave above at the end of the scrolling animation
 *  @param {Boolean} [options.expanded] Whether it should start out expanded
 *  @param {Boolean} [options.autoCollapseSiblings] Whether, when expanding an expandable, its siblings should be automatically collapsed.
 *  @param {Boolean} [options.scrollContainer] Whether to scroll a parent container when necessary
 * @return {Q.Tool}
 */
Q.Tool.define('Q/expandable', function (options) {
	var tool = this;
	var state = tool.state;
	var $te = $(tool.element);
	
	Q.addStylesheet('{{Q}}/css/expandable.css');
	
	if (!$te.children().length) {
		// set it up with javascript
		var count = options.count || '';
		var h2 = "<h2 class='Q_clearfix'>"
			+"<span class='Q_expandable_count '>"+count+"</span>"
			+options.title
			+"</h2>";
		var div = "<div class='Q_expandable_container'><div class='Q_expandable_content'>"
			+options.content+"</div></div>";
		this.element.innerHTML = h2 + div;
	}
	
	var $h2 = $('h2', $te);
	if (state.expanded == null) {
		state.expanded = $h2.next().is(':visible');
	} else if (state.expanded) {
		$h2.addClass('Q_expanded')
		.next().addClass('Q_expanded')
		.css('display', 'block');
	}
	
	this.element.preventSelections(true);
	var $h2 = $('h2', $te)
	.on(Q.Pointer.fastclick, function () {
		var $h2 = $('h2', $te);
		if ($h2.hasClass('Q_expanded')) {
			tool.collapse();
		} else {
			tool.expand();
		}
	}).on(Q.Pointer.start, function () {
		var $this = $(this);
		$this.addClass('Q_pressed');
		function f() {
			$this.removeClass('Q_pressed');
			$(window).off(Q.Pointer.end, f);
		}
		$(window).on(Q.Pointer.end, f);
	});
	if (!Q.info.isTouchscreen) {
		$h2.on('mouseenter', function () {
			$(this).addClass('Q_hover');
		}).on('mouseleave', function () {
			$(this).removeClass('Q_hover');
		});
	}
	tool.Q.onStateChanged('count').set(function () {
		$h2.find('.Q_expandable_count').html(state.count);
	});
}, {
	count: 0,
	spaceAbove: null,
	expanded: null,
	autoCollapseSiblings: true,
	scrollContainer: true,
	duration: 300,
	beforeExpand: new Q.Event(),
	onExpand: new Q.Event(),
	beforeCollapse: new Q.Event()
}, {
	/**
	 * @method expand
	 * @param {Object} [options]
	 *  @param {Boolean} [options.autoCollapseSiblings] Whether, when expanding an expandable,
	 *  @param {Boolean} [options.scrollContainer] Whether to scroll a parent container
	 *  @param {Boolean} [options.scrollToElement] Can be used to specify another element to scroll to when expanding. Defaults to the title element of the expandable.
 *  @param {Number} [options.spaceAbove] How many pixels of space to leave above at the end of the scrolling animation
	 * @param {Function} [callback] the function to call once the expanding has completed
	 */
	expand: function (options, callback) {
		var tool = this;
		var state = tool.state;
		if (false === Q.handle(state.beforeExpand, this, [])) {
			return false;
		}
		var o = Q.extend({}, tool.state, options);
		var $te = $(this.element);
		var $h2 = $('h2', $te);
		var $parent = $te.parent();
		if (o.autoCollapseSiblings) {
			$('.Q_expandable_tool h2', $parent).not(this)
			.removeClass('Q_expanded')
			.next().removeClass('Q_expanded')
			.slideUp(state.duration).each(function () {
				var t = this.parentNode.Q("Q/expandable");
				Q.handle(t.state.beforeCollapse, t, [tool]);
			});
		}
		var $expandable = $h2.next().slideDown(state.duration, 'linear');
		state.expanded = true;
		if (!o.scrollContainer) {
			return;
		}
		Q.Animation.play(function (x, y) {
			var $scrollable = (o.scrollContainer instanceof Element)
				? $(o.scrollContainer) : tool.scrollable();
			var offset = $scrollable
				? $scrollable.offset()
				: {left: 0, top: 0};
			var $element = o.scrollToElement ? $(o.scrollToElement) : $h2;
			var t1 = $element.offset().top - offset.top;
			var spaceAbove;
			var defaultSpaceAbove = $element.height() / 2;
			var moreSpaceAbove = 0;
			var $ts = $expandable.closest('.Q_columns_column').find('.Q_columns_title');
			if ($ts.length && $ts.css('position') === 'fixed') {
				moreSpaceAbove = $ts.outerHeight();
			} else {
				$('body').children().each(function () {
					var $this = $(this);
					if ($this.css('position') === 'fixed') {
						var top = $this.offset().top - Q.Pointer.scrollTop();
						if (top < 100) {
							moreSpaceAbove = top + $this.outerHeight();
							return false;
						}
					}
				});
			}
			defaultSpaceAbove += moreSpaceAbove;
			var spaceAbove = (state.spaceAbove == null)
				? defaultSpaceAbove
				: state.spaceAbove;
			var isBody = $scrollable &&
				['BODY', 'HTML'].indexOf($scrollable[0].tagName.toUpperCase()) >= 0;
			if (isBody) {
				t1 -= Q.Pointer.scrollTop();
			}
			if ($scrollable) {
				var t = $element.offset().top - offset.top;
				if (isBody) {
					t -= Q.Pointer.scrollTop();
				}
				var scrollTop = $scrollable.scrollTop() + t - t1 * (1-y) - spaceAbove * y;
				$scrollable.scrollTop(scrollTop);
			}
		}, state.duration).onComplete.set(function () {
			Q.handle(callback, tool, []);
			Q.handle(state.onExpand, tool, []);
			$h2.add($expandable).addClass('Q_expanded');
		});
	},
	
	collapse: function () {
		var tool = this;
		var state = this.state;
		var $h2 = $('h2', this.element);
		$h2.removeClass('Q_expanded')
		.next().removeClass('Q_expanded')
		.slideUp(state.duration).each(function () {
			var t = this.parentNode.Q("Q/expandable");
			Q.handle(t.state.beforeCollapse, t, [tool]);
		});
		state.expanded = false;
	},
	
	scrollable: function () {
		return $(this.element.scrollingParent(true, 'vertical'));
	}
});

})(Q, jQuery);