/**
* @module "montage/ui/succession.reel"
*/
var Component = require("ui/component").Component;
/**
* Subclasses Component for its `domContent` behavior.
*
* If passage properties are defined on the Succession, they will override children's.
* See {@link Succession#_prepareForBuild}.
*
* @class Succession
* @augments Component
*/
exports.Succession = Component.specialize(/** @lends Succession.prototype */{
contentBuildInAnimation: {
value: undefined
},
contentBuildOutAnimation: {
value: undefined
},
/**
* Setting content to a component will add the component to `history`.
* Setting content to null will clear `history`.
*/
content: {
get: function () {
return this.history.length ? this.history[this.history.length - 1] : undefined;
},
set: function (component) {
if (component) {
this.history.push(component);
} else {
this.history.clear();
}
}
},
_firstComponent: {
value: undefined
},
_history: {
value: undefined
},
/**
* A stack consisted of {@link Component}s.
*
* @property {Array}
*/
history: {
get: function () {
if (!this._history) {
this._history = [];
this._history.addBeforeRangeChangeListener(this);
this._history.addRangeChangeListener(this);
}
return this._history;
},
set: function (history) {
history = Array.isArray(history) ? history : [];
if (this.history !== history) {
if (!this.history.length && history.length) {
this._firstComponent = history[0];
}
this.history.splice.apply(this.history, [0, this.history.length].concat(history));
}
}
},
/**
* @property {boolean}
* @default false
*/
hasTemplate: {
enumerable: false,
value: false
},
/**
* Override build-in / out animation; checks for whether properties are undefined,
* as null is used to disable passage animation.
*
* Priority from most important: Succession -> Passage -> Component
*
* @private
* @function
*/
_prepareForBuild: {
value: function (content) {
if (content) {
if (this.contentBuildInAnimation) {
content.buildInAnimationOverride = this.contentBuildInAnimation;
}
if (this.contentBuildOutAnimation) {
content.buildOutAnimationOverride = this.contentBuildOutAnimation;
}
}
}
},
/**
* Ensure components generated by instantiating in JavaScript instead of
* declaring in template serialization has an element.
*
* @private
* @function
* @param {Component} content
*/
_updateDomContentWith: {
value: function (content) {
if (content) {
var element;
if (!content.element) {
element = document.createElement("div");
element.id = content.identifier || "appendDiv";
content.element = element;
} else {
element = content.element;
}
this.domContent = element;
content.needsDraw = true;
} else {
this.domContent = null;
}
}
},
// =============================================================================================
// Event Handlers
// =============================================================================================
/**
* Prepare outgoing content; need to prepare before range actually changes because we need to
* prepare on outgoing content and handleRangeChange happens after outgoing content is gone
*/
handleRangeWillChange: {
value: function (plus, minus, index) {
this._prepareForBuild(this.content);
}
},
/**
* Sets classes on Succession depending on how history was changed
* Prepare incoming content
*/
handleRangeChange: {
value: function (plus, minus, index) {
//console.log(this.content && this.content.title);
//console.log(plus[0] && plus[0].title);
//console.log(minus[0] && minus[0].title);
var length = this.history ? this.history.length : 0,
isChanged = plus.length || minus.length,
isChangeVisible = isChanged && index + plus.length === length,
isPush = isChangeVisible && !minus.length && index,
isPop = isChangeVisible && !plus.length && length,
isReplace = isChangeVisible && !isPush && !isPop && length,
isClear = isChangeVisible && !length;
// Set appropriate classes and update the succession if necessary.
if (isChangeVisible) {
this.classList[isPush ? "add" : "remove"]("montage-Succession--push");
this.classList[isPop ? "add" : "remove"]("montage-Succession--pop");
this.classList[isReplace ? "add" : "remove"]("montage-Succession--replace");
this.classList[isClear ? "add" : "remove"]("montage-Succession--clear");
this._prepareForBuild(this.content);
this.dispatchBeforeOwnPropertyChange("content", this.content);
this._updateDomContentWith(this.content);
this.dispatchOwnPropertyChange("content", this.content);
}
}
},
handleBuildInEnd: {
value: function (event) {
this.needsCssClassCleanup = true;
this.needsDraw = true;
}
},
handleBuildOutEnd: {
value: function (event) {
this.needsCssClassCleanup = true;
this.needsDraw = true;
}
},
// =============================================================================================
// Life Cycle Hooks
// =============================================================================================
/**
* The first time, extract the argument component (if any) and insert it into the succession
* without animation. Must wait for argument component's template to be expanded as
* parent replaces template with component's element.
*
* TODO: Component's extractDomArgument("*") does not seem to be working so the
* content argument must explicitly be named "content".
*/
enterDocument: {
value: function (isFirstTime) {
var contentElement = isFirstTime && this.extractDomArgument("content"),
contentComponent = contentElement && contentElement.component,
self = this;
if (contentComponent) {
contentComponent.expandComponent().then(function () {
self.history.push(contentComponent);
});
}
if (isFirstTime) {
this.addEventListener("buildInEnd", this);
this.addEventListener("buildOutEnd", this);
}
}
},
draw: {
value: function () {
if (this.needsCssClassCleanup) {
this.needsCssClassCleanup = false;
this.classList.deleteEach([
"montage-Succession--push",
"montage-Succession--pop",
"montage-Succession--replace",
"montage-Succession--clear"
]);
}
}
}
});