|
|
- /**
- The enyo.Panels kind is designed to satisfy a variety of common use cases for
- application layout. Using enyo.Panels, controls may be arranged as (among other
- things) a carousel, a set of collapsing panels, a card stack that fades between
- panels, or a grid.
-
- Any Enyo control may be placed inside an enyo.Panels, but by convention we refer
- to each of these controls as a "panel." From the set of panels in an enyo.Panels,
- one is considered active. The active panel is set by index using the *setIndex*
- method. The actual layout of the panels typically changes each time the active
- panel is set, such that the new active panel has the most prominent position.
-
- For more information, see the [Panels documentation](https://github.com/enyojs/enyo/wiki/Panels)
- in the Enyo Developer Guide.
- */
- enyo.kind({
- name: "enyo.Panels",
- classes: "enyo-panels",
- published: {
- /**
- The index of the active panel. The layout of panels is controlled by
- the layoutKind, but as a rule, the active panel is displayed in the
- most prominent position. For example, in the (default) CardArranger
- layout, the active panel is shown and the other panels are hidden.
- */
- index: 0,
- //* Controls whether the user can drag between panels.
- draggable: true,
- //* Controls whether the panels animate when transitioning; for example,
- //* when _setIndex_ is called.
- animate: true,
- //* Controls whether panels "wrap around" when moving past the end. Actual effect depends upon the arranger in use.
- wrap: false,
- //* Sets the arranger kind to be used for dynamic layout.
- arrangerKind: "CardArranger",
- //* By default, each panel will be sized to fit the Panels' width when
- //* the screen size is narrow enough (less than ~800px). Set to false
- //* to avoid this behavior.
- narrowFit: true
- },
- events: {
- /**
- Fires at the start of a panel transition.
- This event fires when _setIndex_ is called and also during dragging.
- */
- onTransitionStart: "",
- /**
- Fires at the end of a panel transition.
- This event fires when _setIndex_ is called and also during dragging.
- */
- onTransitionFinish: ""
- },
- //* @protected
- handlers: {
- ondragstart: "dragstart",
- ondrag: "drag",
- ondragfinish: "dragfinish"
- },
- tools: [
- {kind: "Animator", onStep: "step", onEnd: "completed"}
- ],
- fraction: 0,
- create: function() {
- this.transitionPoints = [];
- this.inherited(arguments);
- this.arrangerKindChanged();
- this.avoidFitChanged();
- this.indexChanged();
- },
- initComponents: function() {
- this.createChrome(this.tools);
- this.inherited(arguments);
- },
- arrangerKindChanged: function() {
- this.setLayoutKind(this.arrangerKind);
- },
- avoidFitChanged: function() {
- this.addRemoveClass("enyo-panels-fit-narrow", this.narrowFit);
- },
- removeControl: function(inControl) {
- this.inherited(arguments);
- if (this.controls.length > 1 && this.isPanel(inControl)) {
- this.setIndex(Math.max(this.index - 1, 0));
- this.flow();
- this.reflow();
- }
- },
- isPanel: function() {
- // designed to be overridden in kinds derived from Panels that have
- // non-panel client controls
- return true;
- },
- flow: function() {
- this.arrangements = [];
- this.inherited(arguments);
- },
- reflow: function() {
- this.arrangements = [];
- this.inherited(arguments);
- this.refresh();
- },
- //* @public
- /**
- Returns an array of contained panels.
- Subclasses can override this if they don't want the arranger to layout all of their children
- */
- getPanels: function() {
- var p = this.controlParent || this;
- return p.children;
- },
- //* Returns a reference to the active panel--i.e., the panel at the specified index.
- getActive: function() {
- var p$ = this.getPanels();
- return p$[this.index];
- },
- /**
- Returns a reference to the <a href="#enyo.Animator">enyo.Animator</a>
- instance used to animate panel transitions. The Panels' animator can be used
- to set the duration of panel transitions, e.g.:
-
- this.getAnimator().setDuration(1000);
- */
- getAnimator: function() {
- return this.$.animator;
- },
- /**
- Sets the active panel to the panel specified by the given index.
- Note that if the _animate_ property is set to true, the active panel
- will animate into view.
- */
- setIndex: function(inIndex) {
- // override setIndex so that indexChanged is called
- // whether this.index has actually changed or not
- this.setPropertyValue("index", inIndex, "indexChanged");
- },
- /**
- Sets the active panel to the panel specified by the given index.
- Regardless of the value of the _animate_ property, the transition to the
- next panel will not animate and will be immediate.
- */
- setIndexDirect: function(inIndex) {
- this.setIndex(inIndex);
- this.completed();
- },
- //* Transitions to the previous panel--i.e., the panel whose index value is
- //* one less than that of the current active panel.
- previous: function() {
- this.setIndex(this.index-1);
- },
- //* Transitions to the next panel--i.e., the panel whose index value is one
- //* greater than that of the current active panel.
- next: function() {
- this.setIndex(this.index+1);
- },
- //* @protected
- clamp: function(inValue) {
- var l = this.getPanels().length-1;
- if (this.wrap) {
- // FIXME: dragging makes assumptions about direction and from->start indexes.
- //return inValue < 0 ? l : (inValue > l ? 0 : inValue);
- return inValue;
- } else {
- return Math.max(0, Math.min(inValue, l));
- }
- },
- indexChanged: function(inOld) {
- this.lastIndex = inOld;
- this.index = this.clamp(this.index);
- if (!this.dragging) {
- if (this.$.animator.isAnimating()) {
- this.completed();
- }
- this.$.animator.stop();
- if (this.hasNode()) {
- if (this.animate) {
- this.startTransition();
- this.$.animator.play({
- startValue: this.fraction
- });
- } else {
- this.refresh();
- }
- }
- }
- },
- step: function(inSender) {
- this.fraction = inSender.value;
- this.stepTransition();
- },
- completed: function() {
- if (this.$.animator.isAnimating()) {
- this.$.animator.stop();
- }
- this.fraction = 1;
- this.stepTransition();
- this.finishTransition();
- },
- dragstart: function(inSender, inEvent) {
- if (this.draggable && this.layout && this.layout.canDragEvent(inEvent)) {
- inEvent.preventDefault();
- this.dragstartTransition(inEvent);
- this.dragging = true;
- this.$.animator.stop();
- return true;
- }
- },
- drag: function(inSender, inEvent) {
- if (this.dragging) {
- inEvent.preventDefault();
- this.dragTransition(inEvent);
- }
- },
- dragfinish: function(inSender, inEvent) {
- if (this.dragging) {
- this.dragging = false;
- inEvent.preventTap();
- this.dragfinishTransition(inEvent);
- }
- },
- dragstartTransition: function(inEvent) {
- if (!this.$.animator.isAnimating()) {
- var f = this.fromIndex = this.index;
- this.toIndex = f - (this.layout ? this.layout.calcDragDirection(inEvent) : 0);
- } else {
- this.verifyDragTransition(inEvent);
- }
- this.fromIndex = this.clamp(this.fromIndex);
- this.toIndex = this.clamp(this.toIndex);
- //this.log(this.fromIndex, this.toIndex);
- this.fireTransitionStart();
- if (this.layout) {
- this.layout.start();
- }
- },
- dragTransition: function(inEvent) {
- // note: for simplicity we choose to calculate the distance directly between
- // the first and last transition point.
- var d = this.layout ? this.layout.calcDrag(inEvent) : 0;
- var t$ = this.transitionPoints, s = t$[0], f = t$[t$.length-1];
- var as = this.fetchArrangement(s);
- var af = this.fetchArrangement(f);
- var dx = this.layout ? this.layout.drag(d, s, as, f, af) : 0;
- var dragFail = d && !dx;
- if (dragFail) {
- //this.log(dx, s, as, f, af);
- }
- this.fraction += dx;
- var fr = this.fraction;
- if (fr > 1 || fr < 0 || dragFail) {
- if (fr > 0 || dragFail) {
- this.dragfinishTransition(inEvent);
- }
- this.dragstartTransition(inEvent);
- this.fraction = 0;
- // FIXME: account for lost fraction
- //this.dragTransition(inEvent);
- }
- this.stepTransition();
- },
- dragfinishTransition: function(inEvent) {
- this.verifyDragTransition(inEvent);
- this.setIndex(this.toIndex);
- // note: if we're still dragging, then we're at a transition boundary
- // and should fire the finish event
- if (this.dragging) {
- this.fireTransitionFinish();
- }
- },
- verifyDragTransition: function(inEvent) {
- var d = this.layout ? this.layout.calcDragDirection(inEvent) : 0;
- var f = Math.min(this.fromIndex, this.toIndex);
- var t = Math.max(this.fromIndex, this.toIndex);
- if (d > 0) {
- var s = f;
- f = t;
- t = s;
- }
- if (f != this.fromIndex) {
- this.fraction = 1 - this.fraction;
- }
- //this.log("old", this.fromIndex, this.toIndex, "new", f, t);
- this.fromIndex = f;
- this.toIndex = t;
- },
- refresh: function() {
- if (this.$.animator.isAnimating()) {
- this.$.animator.stop();
- }
- this.startTransition();
- this.fraction = 1;
- this.stepTransition();
- this.finishTransition();
- },
- startTransition: function() {
- this.fromIndex = this.fromIndex != null ? this.fromIndex : this.lastIndex || 0;
- this.toIndex = this.toIndex != null ? this.toIndex : this.index;
- //this.log(this.id, this.fromIndex, this.toIndex);
- if (this.layout) {
- this.layout.start();
- }
- this.fireTransitionStart();
- },
- finishTransition: function() {
- if (this.layout) {
- this.layout.finish();
- }
- this.transitionPoints = [];
- this.fraction = 0;
- this.fromIndex = this.toIndex = null;
- this.fireTransitionFinish();
- },
- fireTransitionStart: function() {
- var t = this.startTransitionInfo;
- if (this.hasNode() && (!t || (t.fromIndex != this.fromIndex || t.toIndex != this.toIndex))) {
- this.startTransitionInfo = {fromIndex: this.fromIndex, toIndex: this.toIndex};
- this.doTransitionStart(enyo.clone(this.startTransitionInfo));
- }
- },
- fireTransitionFinish: function() {
- var t = this.finishTransitionInfo;
- if (this.hasNode() && (!t || (t.fromIndex != this.lastIndex || t.toIndex != this.index))) {
- this.finishTransitionInfo = {fromIndex: this.lastIndex, toIndex: this.index};
- this.doTransitionFinish(enyo.clone(this.finishTransitionInfo));
- }
- this.lastIndex=this.index;
- },
- // gambit: we interpolate between arrangements as needed.
- stepTransition: function() {
- if (this.hasNode()) {
- // select correct transition points and normalize fraction.
- var t$ = this.transitionPoints;
- var r = (this.fraction || 0) * (t$.length-1);
- var i = Math.floor(r);
- r = r - i;
- var s = t$[i], f = t$[i+1];
- // get arrangements and lerp between them
- var s0 = this.fetchArrangement(s);
- var s1 = this.fetchArrangement(f);
- this.arrangement = s0 && s1 ? enyo.Panels.lerp(s0, s1, r) : (s0 || s1);
- if (this.arrangement && this.layout) {
- this.layout.flowArrangement();
- }
- }
- },
- fetchArrangement: function(inName) {
- if ((inName != null) && !this.arrangements[inName] && this.layout) {
- this.layout._arrange(inName);
- this.arrangements[inName] = this.readArrangement(this.getPanels());
- }
- return this.arrangements[inName];
- },
- readArrangement: function(inC) {
- var r = [];
- for (var i=0, c$=inC, c; (c=c$[i]); i++) {
- r.push(enyo.clone(c._arranger));
- }
- return r;
- },
- statics: {
- isScreenNarrow: function() {
- return enyo.dom.getWindowWidth() <= 800;
- },
- lerp: function(inA0, inA1, inFrac) {
- var r = [];
- for (var i=0, k$=enyo.keys(inA0), k; (k=k$[i]); i++) {
- r.push(this.lerpObject(inA0[k], inA1[k], inFrac));
- }
- return r;
- },
- lerpObject: function(inNew, inOld, inFrac) {
- var b = enyo.clone(inNew), n, o;
- // inOld might be undefined when deleting panels
- if (inOld) {
- for (var i in inNew) {
- n = inNew[i];
- o = inOld[i];
- if (n != o) {
- b[i] = n - (n - o) * inFrac;
- }
- }
- }
- return b;
- }
- }
- });
-
|