/** * @fileoverview Provides layout specific functionality for HTML5 layout. * This includes layout specific directives, that are responsible for * interaction with the user, alignment of the blocks and texts in them. * Also includes layout specification and initialization. */ /** * Utilities object with specific functionality for the layout. * @param {!angular.Object} ng AngularJS object. * @return {!Object.} Available functions. */ var layout = (function(ng) { /** * Minimal aspect ratio to consider an image to be vertical. * @const {number} */ var ASPECT_RATIO = 1.2; /** * Uniform text font size attributes. * @enum {string} */ var Font = { FONT_SIZE: 'fontSize', MIN_FONT_SIZE: 'minfontsize', UNIFORM_FONT_SIZE_SELECTOR: '[uniform-text-size]' }; /** * Maximum font size for product name. * @const {number} */ var MAX_FONT_SIZE_PRODUCT_NAME = 26; /** * Maximum font size for subtitle. * @const {number} */ var MAX_FONT_SIZE_SUBTITLE = 16; /** * Maximum font size for subtitle in vertical layouts type. * @const {number} */ var MAX_FONT_SIZE_SUBTITLE_VERTICAL_LAYOUT = 20; /** * Minimum font size for product name. * @const {number} */ var MIN_FONT_SIZE_PRODUCT_NAME = 12; /** * Minimum font size for subtitle. * @const {number} */ var MIN_FONT_SIZE_SUBTITLE = 9; /** * Minimum font size for subtitle in vertical layouts type. * @const {number} */ var MIN_FONT_SIZE_SUBTITLE_VERTICAL_LAYOUT = 10; /** * Delay for the uniform font size directive and the initial font size. * @enum {number} * @private */ var UniformFontSize_ = { DELAY: 400, SMALLEST_FONT_SIZE: 1000 }; var module = ng.module('custom', []); var loadedRes = {}; window.onAdData = function(data, util) { var preloader = window.initPreloading(data); preloader.addCompletionListener(function() { loadedRes = preloader.getLoadedImages(); utils.onAdData(data, util); }); preloader.start(); }; /** * Creates the list of the CSS classes to apply to the layout content * depending on parameters from DAB. * @param {!angular.Scope} scope AngularJS layout scope. * @return {!Object.} All available CSS classes. */ function getClasses(scope) { var design = scope.design; var btn = [design['btnStyle']]; if (!scope.toBoolean(design['btnShad'])) { btn.push('no-shadow'); } return { layout: design['cornerStyle'], button: btn.join(' ') }; } /** * Controller for using data binding in layout. * @param {!angular.Scope} $scope AngularJS layout scope. * @param {!Object} dynamicData Dynamic data from DAB. * @constructor */ function LayoutController($scope, dynamicData) { helpers.LayoutController($scope, dynamicData); var design = $scope.design; $scope.classes = getClasses($scope); $scope.stopAnimation = false; $scope.frameWidth = dynamicData.google_width; $scope.isThin = function(url) { var imgContainer = loadedRes[url]; var img = imgContainer && imgContainer[0]; return img && (img.width / img.height < ASPECT_RATIO); }; design.nameSize = Math.max(MIN_FONT_SIZE_PRODUCT_NAME, design.nameSize); design.nameSize = Math.min(MAX_FONT_SIZE_PRODUCT_NAME, design.nameSize); if ($scope.layoutType != LayoutTypes.VERTICAL) { design.subTitleSize = Math.max(MIN_FONT_SIZE_SUBTITLE, design.subTitleSize); design.subTitleSize = Math.min(MAX_FONT_SIZE_SUBTITLE, design.subTitleSize); } else { design.subTitleSize = Math.max(MIN_FONT_SIZE_SUBTITLE_VERTICAL_LAYOUT, design.subTitleSize); design.subTitleSize = Math.min(MAX_FONT_SIZE_SUBTITLE_VERTICAL_LAYOUT, design.subTitleSize); } angular.forEach($scope.products, function(product) { product.price = product.salePrice || product.price; }); } /** * Applies animation to the elements when all DOM has loaded. * @return {!angular.Directive} Directive definition object. */ module.directive('animation', function() { return { restrict: 'A', link: function(scope) { scope.$evalAsync(function() { var prodImage = document.querySelector('.product-image-container'); var info = document.querySelector('.info-holder'); var actions = document.querySelector('.actions'); var logoHolder = document.querySelector('.logo-holder'); var disclaimer = document.querySelector('.disclaimer'); switch (scope.layoutType) { case LayoutTypes.SQUARE: case LayoutTypes.MIN: case LayoutTypes.VERTICAL: if (scope.frameHeight == LayoutHeight.AD_320x50) { TweenLite.from(prodImage, 1.5, { top: 70, rotation: -4, delay: .5, ease: Quint.easeInOut }); } else if (scope.layoutType != LayoutTypes.VERTICAL) { TweenLite.from(prodImage, 1.5, { right: -168, rotation: -4, delay: .5, ease: Quint.easeInOut }); } else if (scope.frameWidth == 300) { TweenLite.from(prodImage, 1.6, { right: 310, rotation: -4, delay: .5, ease: Quint.easeInOut }); } else { TweenLite.from(prodImage, 1.6, { right: 220, rotation: -4, delay: .5, ease: Quint.easeInOut }); } TweenLite.from(info, 1.2, { opacity: 0, delay: .6, ease: Quint.easeInOut }); if (scope.layoutType != LayoutTypes.VERTICAL) { TweenLite.from(actions, 0.9, { opacity: 0, marginTop: 10, delay: 1.15, ease: Quint.easeInOut }); TweenLite.from(logoHolder, 0.9, { opacity: 0, marginTop: 10, delay: 1, ease: Quint.easeInOut }); } else { TweenLite.from(actions, 1, { opacity: 0, marginTop: 10, delay: 1.3, ease: Quint.easeInOut }); TweenLite.from(logoHolder, 1, { opacity: 0, marginTop: 10, delay: 1.6, ease: Quint.easeInOut }); } break; case LayoutTypes.HORIZONTAL: TweenLite.from(prodImage, 1.5, { top: 120, rotation: -4, delay: .4, ease: Quint.easeInOut }); TweenLite.from(info, 1.2, { opacity: 0, delay: .6, ease: Quint.easeInOut }); TweenLite.from(actions, 0.9, { opacity: 0, marginTop: 10, delay: 1.3, ease: Quint.easeInOut }); TweenLite.from(logoHolder, 0.9, { opacity: 0, marginTop: 10, delay: 1.5, ease: Quint.easeInOut }); } TweenLite.from(disclaimer, 0.5, { opacity: 0, delay: 2.1, ease: Quint.easeInOut }); }); } }; }); /** * After dynamic text sizing, this makes item font sizes uniform based * on the smallest font size. * @return {!angular.Directive} Directive definition object. */ module.directive('uniformTextSize', ['$timeout', function($timeout) { return { restrict: 'A', link: function() { $timeout(function() { var selector = Font.UNIFORM_FONT_SIZE_SELECTOR; var smallestFontSize = UniformFontSize_.SMALLEST_FONT_SIZE; // Find smallest font size. ng.forEach(getElementsList(selector), function(textFitElement) { var elementMinimumFontSize = textFitElement.getAttribute(Font.MIN_FONT_SIZE); var elementFontSize = parseInt( helpers.getStyleProperty(textFitElement, Font.FONT_SIZE)); if (elementFontSize < elementMinimumFontSize) { elementFontSize = elementMinimumFontSize; } if (elementFontSize < smallestFontSize) { smallestFontSize = elementFontSize; } }); // Make uniform. ng.forEach(getElementsList(selector), function(listElement) { var ngSpan = ng.element(listElement.querySelector('span')); ng.element(listElement) .css(Font.FONT_SIZE, smallestFontSize + 'px'); ngSpan.css(Font.FONT_SIZE, smallestFontSize + 'px'); }); }, UniformFontSize_.DELAY); } }; }]); /** * Convenience alias for querySelectorAll that returns results as Array * (instead of querySelectorAll's native nodeList.) * @param {string} selector A CSS-style selector. ex: "#foo .bar>img" * @param {Element=} opt_element Root element to query (document is default). * @return {Array.} */ function getElementsList(selector, opt_element) { if (!opt_element) { opt_element = document; } return Array.prototype.slice.call(opt_element.querySelectorAll(selector)); } /** * Extends utils directives with custom layout specific directives. All * directives will be stored in new container "layout". * This code allows to use directives from utils inside the layout. */ ng.module('layout', ['utils', module.name], function($controllerProvider) { $controllerProvider.allowGlobals(); }); return { controller: LayoutController }; })(angular); (function() { // Hotel vertical utils.defineMeta('version', '3.0'); // REQUIRED utils.defineAttribute('Headline', 'productClickOnly', true); utils.defineAttribute('Product', 'description', true); utils.defineAttribute('Product', 'name', true); utils.defineAttribute('Product', 'price', true); utils.defineAttribute('Product', 'imageUrl', true); utils.defineAttribute('Product', 'url', true); // OPTIONAL utils.defineAttribute('Design', 'logoImageUrl', false); utils.defineAttribute('Design', 'borderColor', false); utils.defineAttribute('Design', 'bgColor', false); utils.defineAttribute('Design', 'bgColorAlt', false); utils.defineAttribute('Design', 'btnColor', false); utils.defineAttribute('Design', 'btnShad', false); utils.defineAttribute('Design', 'btnStyle', false); utils.defineAttribute('Design', 'txtColorPrice', false); utils.defineAttribute('Design', 'txtColorProduct', false); utils.defineAttribute('Design', 'txtColorCta', false); utils.defineAttribute('Design', 'txtColorPricePrefix', false); utils.defineAttribute('Design', 'txtColorSubTitle', false); utils.defineAttribute('Design', 'cornerStyle', false); utils.defineAttribute('Headline', 'showPrice', false); utils.defineAttribute('Headline', 'cta', false); utils.defineAttribute('Headline', 'pricePrefix', false); utils.defineAttribute('Headline', 'disclaimer', false); utils.defineAttribute('Product', 'subTitle', false); utils.defineAttribute('Design', 'nameSize', false); utils.defineAttribute('Design', 'subTitleSize', false); // OCCURRENCES utils.defineOccurrences('Headline', 1, 1); utils.defineOccurrences('Design', 1, 1); utils.defineOccurrences('Product', 1, 1); window.setup = function() { var adContainer = document.getElementById('ad-container'); if (adContainer) { adContainer.addEventListener('click', utils.clickHandler, false); } }; window.initPreloading = function(dynamicData) { var data = dynamicData.google_template_data.adData[0]; var design = utils.parse(data, 'Design')[0]; var prods = utils.parse(data, 'Product').slice(0); var preloader = utils.preloader; for (var i = 0; i < prods.length; i++) { preloader.addImage(prods[i].imageUrl); } preloader.addImage(design.logoImageUrl); preloader.addImage(design.bgImageUrl); return preloader; }; })();