/** * This component is used in {@link Ext.navigation.View} to control animations in the toolbar. You should never need to * interact with the component directly, unless you are subclassing it. * @private */Ext.define('Ext.navigation.Bar', { extend: 'Ext.TitleBar', requires: [ 'Ext.Button', 'Ext.Spacer' ], /** * @private */ isToolbar: true, config: { /** * @cfg cls * @inheritdoc */ cls: Ext.baseCSSPrefix + 'navigation-bar', /** * @cfg {String} ui * Style options for Toolbar. Either 'light' or 'dark'. * @accessor */ ui: 'dark', /** * @cfg {String} title * The title of the toolbar. You should NEVER set this, it is used internally. * You set the title of the navigation bar by giving a navigation views children * a title configuration. * @private * @accessor */ title: null, /** * @cfg defaultType * @hide * @accessor */ defaultType: 'button', /** * @cfg layout * @ignore * @accessor */ layout: { type: 'hbox' }, /** * @cfg {Array/Object} items * The child items to add to this NavigationBar. The {@link #cfg-defaultType} of * a NavigationBar is {@link Ext.Button}, so you do not need to specify an * `xtype` if you are adding buttons. * * You can also give items a `align` configuration which will align the item to * the `left` or `right` of the NavigationBar. * @hide * @accessor */ /** * @cfg {String} defaultBackButtonText * The text to be displayed on the back button if: * a) The previous view does not have a title * b) The {@link #useTitleForBackButtonText} configuration is true. * @private * @accessor */ defaultBackButtonText: 'Back', /** * @cfg {Object} animation * @private * @accessor */ animation: { duration: 300 }, /** * @cfg {Boolean} useTitleForBackButtonText * Set to false if you always want to display the {@link #defaultBackButtonText} * as the text on the back button. True if you want to use the previous views * title. * @private * @accessor */ useTitleForBackButtonText: null, /** * @cfg {Ext.navigation.View} view * A reference to the navigation view this bar is linked to. * @private * @accessor */ view: null, /** * @cfg {Boolean} android2Transforms * Optionally enable CSS transforms on Android 2 * for NavigationBar animations. Note that this may cause flickering if the * NavigationBar is hidden. * @accessor */ android2Transforms: false, /** * @cfg {Ext.Button/Object} backButton * The configuration for the back button * @private * @accessor */ backButton: { align: 'left', ui: 'back', hidden: true } }, /** * @property baseCls * @inheritdoc */ baseCls: Ext.baseCSSPrefix + 'toolbar', /** * @event back * Fires when the back button was tapped. * @param {Ext.navigation.Bar} this This bar */ constructor: function (config) { config = config || {}; if (!config.items) { config.items = []; } this.backButtonStack = []; this.activeAnimations = []; this.callParent([config]); }, /** * @private */ applyBackButton: function (config) { return Ext.factory(config, Ext.Button, this.getBackButton()); }, /** * @private */ updateBackButton: function (newBackButton, oldBackButton) { if (oldBackButton) { this.remove(oldBackButton); } if (newBackButton) { this.add(newBackButton); newBackButton.on({ scope: this, tap: this.onBackButtonTap }); } }, onBackButtonTap: function () { this.fireEvent('back', this); }, /** * @private */ updateView: function (newView) { var me = this, backButton, innerItems, i, backButtonText, item, title, titleText; // Need to have items initialized before getting the backButton me.getItems(); backButton = me.getBackButton(); if (newView) { //update the back button stack with the current inner items of the view innerItems = newView.getInnerItems(); for (i = 0; i < innerItems.length; i++) { item = innerItems[i]; title = (item.getTitle) ? item.getTitle() : item.config.title; me.backButtonStack.push(title || ' '); } titleText = me.getTitleText(); if (titleText === undefined) { titleText = ''; } me.setTitle(titleText); backButtonText = me.getBackButtonText(); if (backButtonText) { backButton.setText(backButtonText); backButton.show(); } } }, /** * @private */ onViewAdd: function (view, item) { var me = this, backButtonStack = me.backButtonStack, hasPrevious, title; me.endAnimation(); title = (item.getTitle) ? item.getTitle() : item.config.title; backButtonStack.push(title || ' '); hasPrevious = backButtonStack.length > 1; me.doChangeView(view, hasPrevious, false); }, /** * @private */ onViewRemove: function (view) { var me = this, backButtonStack = me.backButtonStack, hasPrevious; me.endAnimation(); backButtonStack.pop(); hasPrevious = backButtonStack.length > 1; me.doChangeView(view, hasPrevious, true); }, /** * @private */ doChangeView: function (view, hasPrevious, reverse) { var me = this, leftBox = me.leftBox, leftBoxElement = leftBox.element, titleComponent = me.titleComponent, titleElement = titleComponent.element, backButton = me.getBackButton(), titleText = me.getTitleText(), backButtonText = me.getBackButtonText(), animation = me.getAnimation() && view.getLayout().getAnimation(), animated = animation && animation.isAnimation && view.isPainted(), properties, leftGhost, titleGhost, leftProps, titleProps; if (animated) { leftGhost = me.createProxy(leftBox.element); leftBoxElement.setStyle('opacity', '0'); backButton.setText(backButtonText); backButton[hasPrevious ? 'show' : 'hide'](); titleGhost = me.createProxy(titleComponent.element.getParent()); titleElement.setStyle('opacity', '0'); me.setTitle(titleText); properties = me.measureView(leftGhost, titleGhost, reverse); leftProps = properties.left; titleProps = properties.title; me.isAnimating = true; me.animate(leftBoxElement, leftProps.element); me.animate(titleElement, titleProps.element, function () { titleElement.setLeft(properties.titleLeft); me.isAnimating = false; me.refreshTitlePosition(); }); me.animate(leftGhost.ghost, leftProps.ghost); me.animate(titleGhost.ghost, titleProps.ghost, function () { leftGhost.ghost.destroy(); titleGhost.ghost.destroy(); }); } else { if (hasPrevious) { backButton.setText(backButtonText); backButton.show(); } else { backButton.hide(); } me.setTitle(titleText); } }, /** * Calculates and returns the position values needed for the back button when you are pushing a title. * @private */ measureView: function (oldLeft, oldTitle, reverse) { var me = this, barElement = me.element, newLeftElement = me.leftBox.element, titleElement = me.titleComponent.element, minOffset = Math.min(barElement.getWidth() / 3, 200), newLeftWidth = newLeftElement.getWidth(), barX = barElement.getX(), barWidth = barElement.getWidth(), titleX = titleElement.getX(), titleLeft = titleElement.getLeft(true), titleWidth = titleElement.getWidth(), oldLeftX = oldLeft.x, oldLeftWidth = oldLeft.width, oldLeftLeft = oldLeft.left, newOffset, oldOffset, leftAnims, titleAnims, omega, theta; theta = barX - oldLeftX - oldLeftWidth; if (reverse) { newOffset = theta; oldOffset = Math.min(titleX - oldLeftWidth, minOffset); } else { oldOffset = theta; newOffset = Math.min(titleX - barX, minOffset); } leftAnims = { element: { from: { transform: { translateX: newOffset }, opacity: 0 }, to: { transform: { translateX: 0 }, opacity: 1 } }, ghost: { to: { transform: { translateX: oldOffset }, opacity: 0 } } }; theta = barX - titleX + newLeftWidth; if ((oldLeftLeft + titleWidth) > titleX) { omega = barX - titleX - titleWidth; } if (reverse) { titleElement.setLeft(0); oldOffset = barX + barWidth - titleX - titleWidth; if (omega !== undefined) { newOffset = omega; } else { newOffset = theta; } } else { newOffset = barX + barWidth - titleX - titleWidth; if (omega !== undefined) { oldOffset = omega; } else { oldOffset = theta; } newOffset = Math.max(titleLeft, newOffset); } titleAnims = { element: { from: { transform: { translateX: newOffset }, opacity: 0 }, to: { transform: { translateX: titleLeft }, opacity: 1 } }, ghost: { to: { transform: { translateX: oldOffset }, opacity: 0 } } }; return { left: leftAnims, title: titleAnims, titleLeft: titleLeft }; }, /** * Helper method used to animate elements. * You pass it an element, objects for the from and to positions an option onEnd callback called when the animation is over. * Normally this method is passed configurations returned from the methods such as #measureTitle(true) etc. * It is called from the #pushLeftBoxAnimated, #pushTitleAnimated, #popBackButtonAnimated and #popTitleAnimated * methods. * * If the current device is Android, it will use top/left to animate. * If it is anything else, it will use transform. * @private */ animate: function (element, config, callback) { var me = this, animation; //reset the left of the element element.setLeft(0); config = Ext.apply(config, { element: element, easing: 'ease-in-out', duration: me.getAnimation().duration || 250, preserveEndState: true }); animation = new Ext.fx.Animation(config); animation.on('animationend', function () { if (callback) { callback.call(me); } }, me); Ext.Animator.run(animation); me.activeAnimations.push(animation); }, endAnimation: function () { var activeAnimations = this.activeAnimations, animation, i, ln; if (activeAnimations) { ln = activeAnimations.length; for (i = 0; i < ln; i++) { animation = activeAnimations[i]; if (animation.isAnimating) { animation.stopAnimation(); } else { animation.destroy(); } } this.activeAnimations = []; } }, refreshTitlePosition: function () { if (!this.isAnimating) { this.callParent(); } }, /** * Returns the text needed for the current back button at anytime. * @private */ getBackButtonText: function () { var text = this.backButtonStack[this.backButtonStack.length - 2], useTitleForBackButtonText = this.getUseTitleForBackButtonText(); if (!useTitleForBackButtonText) { if (text) { text = this.getDefaultBackButtonText(); } } return text; }, /** * Returns the text needed for the current title at anytime. * @private */ getTitleText: function () { return this.backButtonStack[this.backButtonStack.length - 1]; }, /** * Handles removing back button stacks from this bar * @private */ beforePop: function (count) { count--; for (var i = 0; i < count; i++) { this.backButtonStack.pop(); } }, /** * We override the hidden method because we don't want to remove it from the view using display:none. Instead we just position it off * the screen, much like the navigation bar proxy. This means that all animations, pushing, popping etc. all still work when if you hide/show * this bar at any time. * @private */ updateHidden: function (hidden) { if (!hidden) { this.element.setStyle({ position: 'relative', top: 'auto', left: 'auto', width: 'auto' }); } else { this.element.setStyle({ position: 'absolute', top: '-1000px', left: '-1000px', width: this.element.getWidth() + 'px' }); } }, /** * Creates a proxy element of the passed element, and positions it in the same position, using absolute positioning. * The createNavigationBarProxy method uses this to create proxies of the backButton and the title elements. * @private */ createProxy: function (element) { var ghost, x, y, left, width; ghost = element.dom.cloneNode(true); ghost.id = element.id + '-proxy'; //insert it into the toolbar element.getParent().dom.appendChild(ghost); //set the x/y ghost = Ext.get(ghost); x = element.getX(); y = element.getY(); left = element.getLeft(true); width = element.getWidth(); ghost.setStyle('position', 'absolute'); ghost.setX(x); ghost.setY(y); ghost.setHeight(element.getHeight()); ghost.setWidth(width); return { x: x, y: y, left: left, width: width, ghost: ghost }; }});