/*! * UI development toolkit for HTML5 (OpenUI5) * (c) Copyright 2009-2018 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides control sap.m.Carousel. sap.ui.define([ './library', 'sap/ui/core/Control', 'sap/ui/Device', 'sap/ui/core/ResizeHandler', 'sap/ui/core/library', './CarouselRenderer', "sap/ui/events/KeyCodes", "sap/base/Log", "sap/ui/events/F6Navigation", "sap/ui/thirdparty/jquery", 'sap/ui/thirdparty/mobify-carousel', 'sap/ui/core/IconPool' ], function( library, Control, Device, ResizeHandler, coreLibrary, CarouselRenderer, KeyCodes, Log, F6Navigation, jQuery /*, mobifycarousel, IconPool (indirect dependency, kept for compatibility with tests, to be fixed in ImageHelper) */ ) { "use strict"; //shortcut for sap.ui.core.BusyIndicatorSize var BusyIndicatorSize = coreLibrary.BusyIndicatorSize; // shortcut for sap.m.ImageHelper var ImageHelper = library.ImageHelper; // shortcut for sap.m.CarouselArrowsPlacement var CarouselArrowsPlacement = library.CarouselArrowsPlacement; // shortcut for sap.m.PlacementType var PlacementType = library.PlacementType; /** * Constructor for a new Carousel. * * @param {string} [sId] ID for the new control, generated automatically if no ID is given * @param {object} [mSettings] Initial settings for the new control * * @class * The carousel allows the user to browse through a set of items by swiping right or left. *
showPageIndicator
- determines if the indicator is displayed.pageIndicatorPlacement
- determines where the indicator is located. Default (sap.m.PlacementType.Bottom
) - below the content.arrowsPlacement
to sap.m.CarouselArrowsPlacement.PageIndicator
, the arrows will be located at the bottom by the paging indicator.
* sap.m.CarouselArrowsPlacement.Content
used to
* place the arrows on the sides of the carousel. Alternatively sap.m.CarouselArrowsPlacement.PageIndicator
can
* be used to place the arrows on the sides of the page indicator.
*/
arrowsPlacement : {type : "sap.m.CarouselArrowsPlacement", group : "Appearance", defaultValue : CarouselArrowsPlacement.Content}
},
defaultAggregation : "pages",
aggregations : {
/**
* The content which the carousel displays.
*/
pages : {type : "sap.ui.core.Control", multiple : true, singularName : "page"}
},
associations : {
/**
* Provides getter and setter for the currently displayed page. For the setter, argument may be the control itself, which must be member of the carousel's page list, or the control's id.
* The getter will return the control id
*/
activePage : {type : "sap.ui.core.Control", multiple : false}
},
events : {
/**
* Carousel requires a new page to be loaded. This event may be used to fill the content of that page
* @deprecated Since version 1.18.7.
* Since 1.18.7 pages are no longer loaded or unloaded
*/
loadPage : {deprecated: true,
parameters : {
/**
* Id of the page which will be loaded
*/
pageId : {type : "string"}
}
},
/**
* Carousel does not display a page any longer and unloads it. This event may be used to clean up the content of that page.
* @deprecated Since version 1.18.7.
* Since 1.18.7 pages are no longer loaded or unloaded
*/
unloadPage : {deprecated: true,
parameters : {
/**
* Id of the page which will be unloaded
*/
pageId : {type : "string"}
}
},
/**
* This event is fired after a carousel swipe has been completed. It is triggered both by physical swipe events and through API carousel manipulations such as calling 'next', 'previous' or 'setActivePageId' functions.
*/
pageChanged : {
parameters : {
/**
* Id of the page which was active before the page change.
*/
oldActivePageId : {type : "string"},
/**
* Id of the page which is active after the page change.
*/
newActivePageId : {type : "string"}
}
}
}
}});
//Constants convenient class selections
Carousel._INNER_SELECTOR = ".sapMCrslInner";
Carousel._PAGE_INDICATOR_SELECTOR = ".sapMCrslBulleted";
Carousel._PAGE_INDICATOR_ARROWS_SELECTOR = ".sapMCrslIndicatorArrow";
Carousel._CONTROLS = ".sapMCrslControls";
Carousel._ITEM_SELECTOR = ".sapMCrslItem";
Carousel._LEFTMOST_CLASS = "sapMCrslLeftmost";
Carousel._RIGHTMOST_CLASS = "sapMCrslRightmost";
Carousel._LATERAL_CLASSES = "sapMCrslLeftmost sapMCrslRightmost";
Carousel._MODIFIERNUMBERFORKEYBOARDHANDLING = 10; // The number 10 is by keyboard specification
Carousel._BULLETS_TO_NUMBERS_THRESHOLD = 9; //The number 9 is by visual specification. Less than 9 pages - bullets for page indicator. 9 or more pages - numeric page indicator.
Carousel._PREVIOUS_CLASS_ARROW = "sapMCrslPrev";
Carousel._NEXT_CLASS_ARROW = "sapMCrslNext";
/**
* Initialize member variables which are needed later on.
*
* @private
*/
Carousel.prototype.init = function() {
//Scroll container list for clean- up
this._aScrollContainers = [];
//Initialize '_fnAdjustAfterResize' to be used by window
//'resize' event
this._fnAdjustAfterResize = jQuery.proxy(function() {
var $carouselInner = this.$().find(Carousel._INNER_SELECTOR);
this._oMobifyCarousel.resize($carouselInner);
}, this);
this.data("sap-ui-fastnavgroup", "true", true); // Define group for F6 handling
};
/**
* Called when the control is destroyed.
*
* @private
*/
Carousel.prototype.exit = function() {
if (this._oMobifyCarousel) {
this._oMobifyCarousel.destroy();
delete this._oMobifyCarousel;
}
if (this._oArrowLeft) {
this._oArrowLeft.destroy();
delete this._oArrowLeft;
}
if (this._oArrowRight) {
this._oArrowRight.destroy();
delete this._oArrowRight;
}
if (this._sResizeListenerId) {
ResizeHandler.deregister(this._sResizeListenerId);
this._sResizeListenerId = null;
}
this.$().off('afterSlide');
this._cleanUpScrollContainer();
this._fnAdjustAfterResize = null;
this._aScrollContainers = null;
this._$InnerDiv = null;
};
/**
* Housekeeping for scroll containers: Removes content for each container,
* destroys the contianer and clears the local container list.
*
* @private
*/
Carousel.prototype._cleanUpScrollContainer = function() {
var oScrollCont;
while (this._aScrollContainers && this._aScrollContainers.length > 0) {
oScrollCont = this._aScrollContainers.pop();
oScrollCont.destroyContent();
if (oScrollCont && typeof oScrollCont.destroy === 'function') {
oScrollCont.destroy();
}
}
};
/**
* Delegates 'touchstart' event to mobify carousel
*
* @param oEvent
*/
Carousel.prototype.ontouchstart = function(oEvent) {
if (this._oMobifyCarousel) {
this._oMobifyCarousel.touchstart(oEvent);
}
};
/**
* Delegates 'touchmove' event to mobify carousel
*
* @param oEvent
*/
Carousel.prototype.ontouchmove = function(oEvent) {
if (this._oMobifyCarousel) {
this._oMobifyCarousel.touchmove(oEvent);
}
};
/**
* Delegates 'touchend' event to mobify carousel
*
* @param oEvent
*/
Carousel.prototype.ontouchend = function(oEvent) {
if (this._oMobifyCarousel) {
this._oMobifyCarousel.touchend(oEvent);
}
};
/**
* Cleans up bindings
*
* @private
*/
Carousel.prototype.onBeforeRendering = function() {
//make sure, active page has an initial value
var sActivePage = this.getActivePage();
if (!sActivePage && this.getPages().length > 0) {
//if no active page is specified, set first page.
this.setAssociation("activePage", this.getPages()[0].getId(), true);
}
if (this._sResizeListenerId) {
ResizeHandler.deregister(this._sResizeListenerId);
this._sResizeListenerId = null;
}
return this;
};
/**
* When this method is called for the first time, a swipe-view instance is created which is renders
* itself into its dedicated spot within the DOM tree. This instance is used throughout the
* Carousel instance's lifecycle.
*
* @private
*/
Carousel.prototype.onAfterRendering = function() {
//Check if carousel has been initialized
if (this._oMobifyCarousel) {
//Clean up existing mobify carousel
this._oMobifyCarousel.unbind();
}
//Create and initialize new carousel
this.$().carousel();
this._oMobifyCarousel = this.getDomRef()._carousel;
this._oMobifyCarousel.setLoop(this.getLoop());
this._oMobifyCarousel.setRTL(sap.ui.getCore().getConfiguration().getRTL());
//Go to active page: this may be necessary after adding or
//removing pages
var sActivePage = this.getActivePage();
if (sActivePage) {
var iIndex = this._getPageNumber(sActivePage);
if (isNaN(iIndex) || iIndex == 0) {
if (this.getPages().length > 0) {
//First page is always shown as default
//Do not fire page changed event, though
this.setAssociation("activePage", this.getPages()[0].getId(), true);
this._adjustHUDVisibility(1);
}
} else {
var oCore = sap.ui.getCore();
if (oCore.isThemeApplied()) {
// mobify carousel is 1-based
this._moveToPage(iIndex + 1);
} else {
oCore.attachThemeChanged(this._handleThemeLoad, this);
}
// BCP: 1580078315
if (sap.zen && sap.zen.commons && this.getParent() instanceof sap.zen.commons.layout.PositionContainer) {
if (this._isCarouselUsedWithCommonsLayout === undefined){
setTimeout(this["invalidate"].bind(this), 0);
this._isCarouselUsedWithCommonsLayout = true;
}
}
}
}
//attach delegate for firing 'PageChanged' events to mobify carousel's
//'afterSlide'
this.$().on('afterSlide', jQuery.proxy(function(e, iPreviousSlide, iNextSlide) {
//the event might bubble up from another carousel inside of this one.
//in this case we ignore the event
if (e.target !== this.getDomRef()) {
return;
}
if (iNextSlide > 0) {
this._changePage(iPreviousSlide, iNextSlide);
}
}, this));
this._$InnerDiv = this.$().find(Carousel._INNER_SELECTOR)[0];
this._sResizeListenerId = ResizeHandler.register(this._$InnerDiv, this._fnAdjustAfterResize);
// Fixes wrong focusing in IE// TODO remove after 1.62 version
// BCP: 1670008915
this.$().find('.sapMCrslItemTableCell').focus(function(e) {
e.preventDefault();
jQuery(e.target).parents('.sapMCrsl').focus();
return false;
});
// Fixes displaying correct page after carousel become visible in an IconTabBar
// BCP: 1680019792
var sClassName = 'sap.m.IconTabBar';
var oParent = this.getParent();
while (oParent) {
if (oParent.getMetadata().getName() == sClassName) {
var that = this;
/*eslint-disable no-loop-func */
oParent.attachExpand(function (oEvt) {
var bExpand = oEvt.getParameter('expand');
if (bExpand && iIndex > 0) {
// mobify carousel is 1-based
that._moveToPage(iIndex + 1);
}
});
break;
}
oParent = oParent.getParent();
}
};
/**
* Fired when the theme is loaded
*
* @private
*/
Carousel.prototype._handleThemeLoad = function() {
var oCore,
sActivePage = this.getActivePage();
if (sActivePage) {
var iIndex = this._getPageNumber(sActivePage);
if (iIndex > 0) {
// mobify carousel is 1-based
this._moveToPage(iIndex + 1);
}
}
oCore = sap.ui.getCore();
oCore.detachThemeChanged(this._handleThemeLoad, this);
};
/**
* Moves carousel and mobify carousel to specific page
*
* @private
*/
Carousel.prototype._moveToPage = function(iIndex) {
this._oMobifyCarousel.changeAnimation('sapMCrslNoTransition');
this._oMobifyCarousel.move(iIndex);
this._changePage(undefined, iIndex);
};
/**
* Private method which adjusts the Hud visibility and fires a page change
* event when the active page changes
*
* @param {int} [iOldPageIndex] Optional index of the old page. If not specified, the current active page index will be taken.
* @param {int} iNewPageIndex index of new page in 'pages' aggregation.
* @private
*/
Carousel.prototype._changePage = function(iOldPageIndex, iNewPageIndex) {
this._adjustHUDVisibility(iNewPageIndex);
var sOldActivePageId = this.getActivePage();
// If setActivePage is called through API, getActivePage will return the wrong page.
// In this case iOldPageIndex must be passed.
if (iOldPageIndex) {
sOldActivePageId = this.getPages()[iOldPageIndex - 1].getId();
}
var sNewActivePageId = this.getPages()[iNewPageIndex - 1].getId();
this.setAssociation("activePage", sNewActivePageId, true);
var sTextBetweenNumbers = sap.ui.getCore().getLibraryResourceBundle("sap.m").getText("CAROUSEL_PAGE_INDICATOR_TEXT", [iNewPageIndex, this.getPages().length]);
Log.debug("sap.m.Carousel: firing pageChanged event: old page: " + sOldActivePageId
+ ", new page: " + sNewActivePageId);
// close the soft keyboard
if (!Device.system.desktop) {
jQuery(document.activeElement).blur();
}
this.firePageChanged( { oldActivePageId: sOldActivePageId,
newActivePageId: sNewActivePageId});
// change the number in the page indicator
this.$('slide-number').text(sTextBetweenNumbers);
};
/**
* Sets HUD control's visibility after page has changed
*
* @param {int} iNextSlide index of the next active page
* @private
*
*/
Carousel.prototype._adjustHUDVisibility = function(iNextSlide) {
if (Device.system.desktop && !this.getLoop() && this.getPages().length > 1) {
//update HUD arrow visibility for left- and
//rightmost pages
var $HUDContainer = this.$('hud');
//clear marker classes first
$HUDContainer.removeClass(Carousel._LATERAL_CLASSES);
if (iNextSlide === 1) {
$HUDContainer.addClass(Carousel._LEFTMOST_CLASS);
this._focusCarouselContainer($HUDContainer, Carousel._PREVIOUS_CLASS_ARROW);
} else if (iNextSlide === this.getPages().length) {
$HUDContainer.addClass(Carousel._RIGHTMOST_CLASS);
this._focusCarouselContainer($HUDContainer, Carousel._NEXT_CLASS_ARROW);
}
}
};
/*
* Focus Carousel container.
* Focus is moved to carousel container if clicked arrow is first or last from carousel
* @param {object} $HUDContainer Arrow container inside Carousel
* @param {string} sArrowClassName Arrow class name
* @private
*
*/
Carousel.prototype._focusCarouselContainer = function($HUDContainer, sArrowClassName) {
if ($HUDContainer.find('.' + sArrowClassName)[0] === document.activeElement) {
this.focus();
}
};
/*
* API method to set carousel's active page during runtime.
*
* @param vPage Id of the page or page which shall become active
* @override
*
*/
Carousel.prototype.setActivePage = function (vPage) {
var sPageId = null;
if (typeof (vPage) == 'string') {
sPageId = vPage;
} else if (vPage instanceof Control) {
sPageId = vPage.getId();
}
if (sPageId) {
if (sPageId === this.getActivePage()) {
//page has not changed, nothing to do, return
return this;
}
var iPageNr = this._getPageNumber(sPageId);
if (!isNaN(iPageNr)) {
if (this._oMobifyCarousel) {
//mobify carousel's move function is '1' based
this._oMobifyCarousel.move(iPageNr + 1);
}
// if oMobifyCarousel is not present yet, move takes place
// 'onAfterRendering', when oMobifyCarousel is created
}
}
this.setAssociation("activePage", sPageId, true);
return this;
};
/*
* API method to set the carousel's height
*
* @param {sap.ui.core.CSSSize} oHeight the new height as CSSSize
* @public
* @override
*/
Carousel.prototype.setHeight = function(oHeight) {
//do suppress rerendering
this.setProperty("height", oHeight, true);
this.$().css("height", oHeight);
return this;
};
/*
* API method to set the carousel's width
*
* @param {sap.ui.core.CSSSize} oWidth the new width as CSSSize
* @public
* @override
*/
Carousel.prototype.setWidth = function(oWidth) {
//do suppress rerendering
this.setProperty("width", oWidth, true);
this.$().css("width", oWidth);
return this;
};
/*
* API method to set whether the carousel should loop, i.e
* show the first page after the last page is reached and vice
* versa.
*
* @param {boolean} bLoop the new loop property
* @public
* @override
*/
Carousel.prototype.setLoop = function(bLoop) {
//do suppress rerendering
this.setProperty("loop", bLoop, true);
if (this._oMobifyCarousel) {
this._oMobifyCarousel.setLoop(bLoop);
}
return this;
};
/**
* Gets the icon of the requested arrow (left/right).
* @private
* @param {string} sName left or right
* @returns icon of the requested arrow
*/
Carousel.prototype._getNavigationArrow = function(sName) {
var mProperties = {
src: "sap-icon://slim-arrow-" + sName,
useIconTooltip : false
};
if (sName === "left") {
if (!this._oArrowLeft) {
this._oArrowLeft = ImageHelper.getImageControl(this.getId() + "-arrowScrollLeft", this._oArrowLeft, this, mProperties);
}
return this._oArrowLeft;
} else if (sName === "right") {
if (!this._oArrowRight) {
this._oArrowRight = ImageHelper.getImageControl(this.getId() + "-arrowScrollRight", this._oArrowRight, this, mProperties);
}
return this._oArrowRight;
}
};
/**
* Private method that places a given page control into
* a scroll container which does not scroll. That container does
* not scroll itself. This is necessary to achieve the 100% height
* effect with an offset for the page indicator.
*
* @param oPage the page to check
* @private
*/
Carousel.prototype._createScrollContainer = function (oPage) {
var imgClass;
var bShowIndicatorArrows = Device.system.desktop && this.getArrowsPlacement() === CarouselArrowsPlacement.PageIndicator;
if (bShowIndicatorArrows) {
imgClass = "sapMCrslImg";
} else {
imgClass = "sapMCrslImgNoArrows";
}
var cellClasses = oPage instanceof sap.m.Image ? "sapMCrslItemTableCell " + imgClass : "sapMCrslItemTableCell",
oContent = new sap.ui.core.HTML({
content : "