From a39bad832722a10f6556f91e94c3301a41f59bd5 Mon Sep 17 00:00:00 2001 From: Jonathan Duran Date: Mon, 6 Feb 2012 13:30:49 -0800 Subject: merge new timeline Signed-off-by: Jonathan Duran --- js/panels/Timeline/Collapser.js | 319 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 js/panels/Timeline/Collapser.js (limited to 'js/panels/Timeline/Collapser.js') diff --git a/js/panels/Timeline/Collapser.js b/js/panels/Timeline/Collapser.js new file mode 100644 index 00000000..2fbe8e83 --- /dev/null +++ b/js/panels/Timeline/Collapser.js @@ -0,0 +1,319 @@ +/* + * Collapser: Takes two elements and creates a visual "expando:" clicking on one element expands/collapses the other. + * Required properties: + * clicker: The element that will be clicked on. + * content: The element that will expand or collapse as the clicker is clicked on. + * Optional properties: + * isCollapsed: Is the content collapsed. Set to true on serialization (or initialization) to start content in collapsed state. + * Can be manually set as well. + * collapsibleClass: The CSS class to apply to the content and the clicker when collapsed. Defaults to "collapsible-collapsed". + * isAnimated: Set to true to apply a transition to expand/collapse (defaults to false). + * transitionClass: If isAnimated is set to true, the component will apply transitionClass to the content during the + * collapse process. You can then define transitionClass in your style sheet with the desired CSS transitions. + * Defaults to "collapsible-transition". + * contentHeight: If both isAnimated and isCollapsedAtStart are set to true, set contentHeight to the height of the content + * (in pixels, but without the "px") when not collapsed. If this value is not set, the first time the content is expanded + * the transition will not work. Subsequent collapses (and expansions) will transition as expected. + * isLabelClickable: Boolean that indicates whether or not the clicker should have listener events. Defaults to true; set to + * false for collapsers that will only be operated remotely. + * toggle(): Manually toggle the expand/collapse of the content. + * + */ +var Montage = require("montage/core/core").Montage, + Component = require("montage/ui/component").Component, + Collapser = exports.Collapser = Montage.create(Component, { + + // This component has no template. + hasTemplate:{ + value: false + }, + + /* === BEGIN: Models === */ + + // contentHeight: Stores the height of the content just before collapse. + _contentHeight: { + value: 0 + }, + contentHeight: { + get: function() { + return this._contentHeight; + }, + set: function(newVal) { + this._contentHeight = newVal; + } + }, + + // isCollapsing: true if the collapser is collapsing (or expanding); used in the draw cycle. + _isCollapsing: { + value: false + }, + + // isAnimated: boolean to apply transition to expand/collapse + _isAnimated : { + value: false + }, + isAnimated: { + get: function() { + return this._isAnimated; + }, + set: function(newVal) { + this._isAnimated = newVal; + } + }, + + _bypassAnimation : { + value: false + }, + bypassAnimation: { + get: function() { + return this._bypassAnimation; + }, + set: function(newVal) { + this._bypassAnimation= newVal; + } + }, + + // transitionClass: The CSS class to apply to the content during collapse to provide CSS transition. + // Note that this CSS class must be defined in your style sheet with the desired transitions. + _transitionClass : { + value: "collapsible-transition" + }, + transitionClass: { + get: function() { + return this._transitionClass; + }, + set: function(newVal) { + this._transitionClass = newVal; + } + }, + + // isCollapsed: is the content actually collapsed at this moment + _isCollapsed: { + value: "" + }, + isCollapsed : { + get: function() { + return this._isCollapsed; + }, + set: function(newVal) { + if (newVal !== this._isCollapsed) { + this._isCollapsed = newVal; + this.needsDraw = true; + } + + } + }, + + // collapsedClass: the class to apply to the clicker and content when the content is collapsed. + _collapsedClass : { + value: "collapsible-collapsed" + }, + collapsedClass: { + get: function() { + return this._collapsedClass; + }, + set: function(newVal) { + this._collapsedClass = newVal; + } + }, + + // _origOverflowValue: Stores the original overflow value of the collapsible element. + // Why store the value? While the collapsible element is collapsed, obviously we will need overflow: hidden. + // But when the collapsible element is open, we will need overflow to return to its original value. + _origOverflowValue : { + value: false + }, + + // isLabelClickable: Boolean for whether or not the label is clickable. If set to false, + // the label click listener is never applied. For collapsibles that will only be operated remotely. + // Defaults to true. + _isLabelClickable : { + value: true + }, + isLabelClickable : { + get: function() { + return this._isLabelClickable; + }, + set: function(newVal) { + this._isLabelClickable = newVal; + } + }, + + // labelClickEvent: an event to fire when the label is clicked. + _labelClickEvent: { + value: false + }, + labelClickEvent: { + get: function() { + return this._labelClickEvent; + }, + set: function(newVal) { + this._labelClickEvent = newVal; + } + }, + + // toggle: manually toggle the collapser. + toggle: { + value: function() { + if (this.bypassAnimation) { + this.isAnimated = false; + } + this.myContent.classList.remove(this.transitionClass); + this.handleCollapserLabelClick(); + } + }, + + /* === END: Models === */ + + /* === BEGIN: Draw cycle === */ + + prepareForDraw: { + value: function() { + // Add a click listener to the label for expand/collapse + if (this.isLabelClickable) { + this.clicker.identifier = "collapserLabel"; + this.clicker.addEventListener("click", this, false); + } + + // Get the original value of the overflow property: + this._origOverflowValue = window.getComputedStyle(this.myContent, null).getPropertyValue("overflow"); + + // If the content area is supposed to start out collapsed: + if (this.isCollapsed) { + this.myContent.style.height = "0px"; + // Set the overflow to hidden if it's not already + if (this._origOverflowValue !== "hidden") { + this.myContent.style.overflow = "hidden"; + } + this.myContent.classList.add(this.collapsedClass); + this.clicker.classList.add(this.collapsedClass); + } else { + this.myContent.style.height = "auto"; + this.myContent.classList.remove(this.collapsedClass); + this.clicker.classList.remove(this.collapsedClass); + } + } + }, + draw: { + value: function() { + // Is the content area expanding/collapsing? + this.myContent.classList.remove(this.transitionClass); + if (this._isCollapsing) { + + if (this.isAnimated) { + // Apply the transition class to the content. + this.myContent.classList.add(this.transitionClass); + + // Add a handler for the end of the transition, so we can tidy things up after + // the transition completes + this.myContent.addEventListener("webkitTransitionEnd", this, false); + + this.myContent.style.overflow = "hidden"; + } + + // Next, are we expanding or collapsing? + if (this.myContent.classList.contains(this.collapsedClass)) { + // It's already collapsed so we are expanding + this.myContent.style.height = this.contentHeight + "px"; + this.isCollapsed = false; + + } else { + // It's expanded so we are collapsing + this.myContent.style.height = "0px"; + this.isCollapsed = true; + + // Set the overflow to hidden if it isn't already + if (this._origOverflowValue !== "hidden") { + this.myContent.style.overflow = "hidden"; + } + } + + // Toggle the CSS class and deactivate the collapsing flag because we are now done. + this.myContent.classList.toggle(this.collapsedClass); + this.clicker.classList.toggle(this.collapsedClass); + this._isCollapsing = false; + + // Special cases: If transition does not happen (in the case of a contentHeight of 0 + // or isAnimated = false) we need to manually fire it here to do the cleanup. + if ((this.contentHeight < 3) || (!this.isAnimated)) { + this.handleWebkitTransitionEnd(); + } + } + } + }, + + /* === END: Draw cycle === */ + + /* === BEGIN: Event handlers === */ + + // Handle a click on the label + handleCollapserLabelClick: { + value: function() { + + // The user has clicked on one of the expandos. What should we do? + // First, are we expanding or collapsing? + if (!this.myContent.classList.contains(this.collapsedClass)) { + // We are collapsing! + // Save the current height of the content. + this.contentHeight = this.myContent.offsetHeight; + // Set the current height of the content to a pixel height instead of "auto" + // so that the transition can happen. + // (This doesn't actually change the appearance of the element, + // so it's okay to do here, outside the draw cycle.) + this.myContent.style.height = this.contentHeight + "px"; + + this.isCollapsed = true; + } else { + this.isCollapsed = false; + } + + // Set the collapsing flag so when the draw cycle begins + // it will know to expand/collapse. + this._isCollapsing = true; + + // Set the component to run its draw cycle. + this.needsDraw = true; + + // Dispatch my labelClick event + if (this.labelClickEvent) { + this.labelClickEvent(this.bypassAnimation); + } + + } + }, + + // This handler is bound to the transitionEnd event. If transitions + // are disabled, it is called manually. + handleWebkitTransitionEnd: { + value: function(event) { + + // Are we animating the transitions? + if (this.isAnimated) { + // Yes, transitions are animated. + // Remove the event listener so it won't fire again. + this.myContent.removeEventListener("webkitTransitionEnd", this, false); + + // remove the CSS class that supplies the transition + this.myContent.classList.remove(this.transitionClass); + } + + // Set the height of the content area to auto; this way it can expand/collapse as interactions + // happen within. + if (!this.myContent.classList.contains(this.collapsedClass)) { + this.myContent.style.height = "auto"; + // Return the overflow to its original value if it wasn't hidden + if (this._origOverflowValue !== "hidden") { + this.myContent.style.overflow = this._origOverflowValue; + } + + } + + if (this.bypassAnimation) { + this.bypassAnimation = false; + this.isAnimated = true; + } + } + } + + /* === END: Event handlers === */ +}); \ No newline at end of file -- cgit v1.2.3