SHARE
TWEET

Untitled

a guest Aug 20th, 2019 151 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /* eslint-disable import/no-cycle */
  2. import {
  3.   mergeObjects,
  4. } from './utils';
  5.  
  6. /**
  7.  * @desc Create a countdown component
  8.  */
  9. export const countdown = (userOptions) => {
  10.   let options = {
  11.     cutoff: null,
  12.     element: null,
  13.     labels: {
  14.       d: 'days',
  15.       h: 'hours',
  16.       m: 'minutes',
  17.       s: 'seconds',
  18.     },
  19.     zeroPrefixHours: false,
  20.     zeroPrefixMinutes: false,
  21.     delivery: {
  22.       deliveryDays: null,
  23.       excludeDays: null,
  24.       deliveryDayElement: null,
  25.       tomorrowLabel: false,
  26.     },
  27.   };
  28.  
  29.   // Overwrite any default options with user supplied options
  30.   if (userOptions) {
  31.     options = mergeObjects(options, userOptions);
  32.   }
  33.  
  34.   const now = new Date();
  35.   let cutoff = new Date(options.cutoff);
  36.   let countdownTimer;
  37.   const dayLabels = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  38.   const countdownData = {};
  39.   const {
  40.     delivery,
  41.   } = options;
  42.   const {
  43.     deliveryDays,
  44.     excludeDays,
  45.     deliveryDayElement,
  46.     tomorrowLabel,
  47.   } = delivery;
  48.  
  49.   /**
  50.    * Increments the date to the earliest day that isn't in the excludeDays array
  51.    * e.g. If the date is a Friday and excludeDays is ['Saturday', 'Sunday'], the function will
  52.    * return the following Monday
  53.    * @param {object} date Date object to use as a starting point
  54.    * @returns {object} date object for the next available date
  55.    */
  56.   const skipToNextAvailableDate = (date) => {
  57.     const getDeliveryDay = () => dayLabels[date.getDay()];
  58.     const isExcludedDay = () => excludeDays && excludeDays.indexOf(getDeliveryDay()) > -1;
  59.  
  60.     while (isExcludedDay()) {
  61.       date.setDate(date.getDate() + 1);
  62.     }
  63.  
  64.     return date;
  65.   };
  66.  
  67.   /**
  68.    * Calculates the delivery date based on the order cutoff time and the number of days it
  69.    * takes for delivery (as provided in options.delivery.deliveryDays)
  70.    * @returns {object} date object for the delivery date
  71.    */
  72.   const getDeliveryDate = () => {
  73.     let deliveryDate = new Date();
  74.  
  75.     // Add the number of days it takes to deliver to the cutoff date
  76.     deliveryDate.setDate(cutoff.getDate() + deliveryDays);
  77.  
  78.     /*
  79.      * If there is no delivery availble on the current delivery date, increment the date
  80.      * by one until it's no longer a day in the excludeDays array
  81.      */
  82.     deliveryDate = skipToNextAvailableDate(deliveryDate);
  83.  
  84.     return deliveryDate;
  85.   };
  86.  
  87.   /**
  88.    * @param {object} one First date object
  89.    * @param {object} two Second date object
  90.    * @returns {number} The number of days between two date objects
  91.    */
  92.   const getDaysBetween = (one, two) => Math.round(Math.abs((+one) - (+two)) / 8.64e7);
  93.  
  94.   // If now is an excluded day, change cutoff to next working day
  95.   if (excludeDays && excludeDays.length && excludeDays.indexOf(dayLabels[now.getDay()] > -1)) {
  96.     const nextProcessingDay = skipToNextAvailableDate(new Date(now));
  97.     const daysBetween = getDaysBetween(now, nextProcessingDay);
  98.     cutoff.setDate(cutoff.getDate() + daysBetween);
  99.   }
  100.  
  101.   // If the cutoff time has passed for today, change it to tomorrow
  102.   if (now > cutoff) {
  103.     cutoff.setDate(cutoff.getDate() + 1);
  104.     cutoff = skipToNextAvailableDate(cutoff);
  105.   }
  106.  
  107.   // Store the cutoff time as a unix timestamp
  108.   countdownData.cutoff = cutoff.getTime();
  109.  
  110.   // Create countdown timer
  111.   // Call timer in an interval to refresh the time remaining each second
  112.   let secondsUntilCutoff = Math.floor((cutoff.getTime() - now.getTime()) / 1000);
  113.   const countdownElements = document.querySelectorAll(options.element);
  114.  
  115.   /**
  116.    * Calculates the current time remaining and updates the html of all countdown elements
  117.    */
  118.   const timer = () => {
  119.     // Time remaining calculations
  120.     const days = Math.floor(secondsUntilCutoff / 24 / 60 / 60);
  121.     const hoursLeftInSeconds = Math.floor((secondsUntilCutoff) - (days * 86400));
  122.     let hours = Math.floor(hoursLeftInSeconds / 3600);
  123.     const minutesLeftInSeconds = Math.floor((hoursLeftInSeconds) - (hours * 3600));
  124.     let minutes = Math.floor(minutesLeftInSeconds / 60);
  125.     let seconds = secondsUntilCutoff % 60;
  126.  
  127.     // Prefix numbers with 0 to occupy space of two digits
  128.     if (seconds < 10) seconds = `${0}remainingSeconds`;
  129.     if (options.zeroPrefixHours && hours < 10) hours = `${0}hours`;
  130.     if (options.zeroPrefixMinutes && minutes < 10) minutes = `${0}minutes`;
  131.  
  132.     let countdownEl;
  133.     for (let i = 0, ii = countdownElements.length; i < ii; i += 1) {
  134.       countdownEl = countdownElements[i];
  135.       countdownEl.innerHTML = `
  136.         ${days > 0 ? `<span class="UC_cd-days">${days}</span> ${options.labels.d} ` : ''}
  137.         <span class="UC_cd-hours">${hours}</span> ${options.labels.h}
  138.         <span class="UC_cd-minutes">${minutes}</span> ${options.labels.m}
  139.         <span class="UC_cd-seconds">${seconds}</span> ${options.labels.s}
  140.       `;
  141.     }
  142.  
  143.     if (secondsUntilCutoff === 0) {
  144.       clearInterval(countdownTimer);
  145.     } else {
  146.       secondsUntilCutoff -= 1;
  147.     }
  148.   };
  149.  
  150.   // Begin countdown
  151.   countdownTimer = setInterval(timer, 1000);
  152.  
  153.   // If countdown is for delivery, create delivery day string
  154.   if (deliveryDays) {
  155.     const deliveryDate = getDeliveryDate();
  156.     const deliveryEl = document.querySelectorAll(deliveryDayElement);
  157.     let deliveryDay = dayLabels[deliveryDate.getDay()];
  158.  
  159.     // If delivery date is tomorrow and tomorrowLabel is true, change deliveryDay label
  160.     if (tomorrowLabel) {
  161.       const tomorrow = new Date(now);
  162.       tomorrow.setDate(tomorrow.getDate() + 1);
  163.  
  164.       /**
  165.        * Check if the delivery date is tomorrow
  166.        * @returns {boolean}
  167.        */
  168.       const deliveryDateIsTomorrow = () => {
  169.         const yearMatches = () => tomorrow.getFullYear() === deliveryDate.getFullYear();
  170.         const monthMatches = () => tomorrow.getMonth() === deliveryDate.getMonth();
  171.         const dateMatches = () => tomorrow.getDate() === deliveryDate.getDate();
  172.  
  173.         return yearMatches() && monthMatches() && dateMatches();
  174.       };
  175.  
  176.       if (deliveryDateIsTomorrow()) {
  177.         deliveryDay = 'tomorrow';
  178.       }
  179.     }
  180.  
  181.     // Set content for delivery day elements to delivery day label
  182.     for (let i = 0, ii = deliveryEl.length; i < ii; i += 1) {
  183.       deliveryEl[i].innerHTML = deliveryDay;
  184.     }
  185.  
  186.     // Note: Delivery time will not be accurate but the date will be correct
  187.     countdownData.deliveryDate = deliveryDate.getTime();
  188.     countdownData.deliveryDay = deliveryDay;
  189.   }
  190.  
  191.   // Expose public data
  192.   return countdownData;
  193. };
  194.  
  195. /**
  196.  * @desc Create a feedback tab component
  197.  */
  198. export const feedbackTab = () => {
  199.   var $ = window.jQuery;
  200.   var settings, component, background, css, animations, dimensions, dimensionProperty;
  201.  
  202.   var getSettings = function (options) {
  203.     var newSettings = settings || {
  204.       label: false,
  205.       content: false,
  206.       position: 'left',
  207.       customClass: false,
  208.       sessionClose: true,
  209.       tabDimensions: {
  210.         height: 'auto',
  211.         width: '350px'
  212.       },
  213.       contentDimensions: {
  214.         height: '350px',
  215.         width: '600px'
  216.       },
  217.       mobileBreakpoint: 768,
  218.       animationSpeed: 600,
  219.       dimBackground: false,
  220.       zIndex: 99999,
  221.     };
  222.  
  223.     if (options) {
  224.       // Overwrite defaults with values from options
  225.       for (var option in options) {
  226.         newSettings[option] = options[option];
  227.       }
  228.     } else {
  229.       options = newSettings;
  230.     }
  231.  
  232.     return newSettings;
  233.   };
  234.  
  235.   var createComponent = function () {
  236.     var $template = $([
  237.         '<div class="UC_fb-tab-container">',
  238.         '<div class="UC_fb-tab">',
  239.         '<span class="UC_fb-tab__inner"></span>',
  240.         '<span class="UC_fb-tab__close">&#215;</span>',
  241.         '</div>',
  242.         '<div class="UC_fb-content">',
  243.         '<div class="UC_fb-content__inner"></div>',
  244.         '</div>',
  245.         '</div>'
  246.       ].join('')),
  247.       $tab = $template.find('.UC_fb-tab'),
  248.       $content = $template.find('.UC_fb-content');
  249.     // Optional
  250.     if (settings.label) $tab.find('.UC_fb-tab__inner').html(settings.label);
  251.     if (settings.content) $content.find('.UC_fb-content__inner').html(settings.content);
  252.     if (settings.customClass) $template.addClass(settings.customClass);
  253.     if (settings.dimBackground) background = $('<div class="UC_fb-tab-bg"></div>');
  254.  
  255.     // Set user defined styles
  256.     $tab.css({
  257.       'height': settings.tabDimensions.height,
  258.       'width': settings.tabDimensions.width
  259.     });
  260.     $content.css({
  261.       'height': settings.contentDimensions.height,
  262.       'width': settings.contentDimensions.width
  263.     });
  264.  
  265.     return $template;
  266.   };
  267.  
  268.   var destroyComponent = function () {
  269.     if (component) component.remove();
  270.     if (background) background.remove();
  271.   };
  272.  
  273.   var createCSS = function () {
  274.     // Define positioning variables
  275.     var tabPosStyle, contentPosStyle, containerPosStyle;
  276.     switch (settings.position) {
  277.       case 'left':
  278.         tabPosStyle = '-webkit-transform:rotate(-90deg) translateX(-50%);-moz-transform:rotate(-90deg) translateX(-50%);-ms-transform:rotate(-90deg) translateX(-50%);-o-transform:rotate(-90deg) translateX(-50%);transform:rotate(-90deg) translateX(-50%);transform-origin:top left;top:50%;left:100%;';
  279.         containerPosStyle = 'top:50%;-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-ms-transform:translateY(-50%);-o-transform:translateY(-50%);transform:translateY(-50%);left:-100%;';
  280.         dimensionProperty = 'width';
  281.         break;
  282.  
  283.       case 'right':
  284.         tabPosStyle = '-webkit-transform:rotate(-90deg) translateY(-100%);-moz-transform:rotate(-90deg) translateY(-100%);-ms-transform:rotate(-90deg) translateY(-100%);-o-transform:rotate(-90deg) translateY(-100%);transform:rotate(-90deg) translateY(-100%);transform-origin:top right;right:100%;';
  285.         containerPosStyle = 'top:50%;-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-ms-transform:translateY(-50%);-o-transform:translateY(-50%);transform:translateY(-50%);right:-100%;';
  286.         dimensionProperty = 'width';
  287.         break;
  288.  
  289.       case 'bottom':
  290.         tabPosStyle = '-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);-ms-transform:translateX(-50%);-o-transform:translateX(-50%);transform:translateX(-50%);bottom:100%;left:50%;';
  291.         containerPosStyle = 'left:50%;-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);-ms-transform:translateX(-50%);-o-transform:translateX(-50%);transform:translateX(-50%);bottom:-100%;';
  292.         dimensionProperty = 'height';
  293.         break;
  294.  
  295.       case 'top':
  296.         tabPosStyle = '-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);-ms-transform:translateX(-50%);-o-transform:translateX(-50%);transform:translateX(-50%);top:100%;left:50%;';
  297.         containerPosStyle = 'left:50%;-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);-ms-transform:translateX(-50%);-o-transform:translateX(-50%);transform:translateX(-50%);top:-100%;';
  298.         dimensionProperty = 'height';
  299.         break;
  300.  
  301.       default:
  302.         tabPosStyle = '';
  303.         containerPosStyle = '';
  304.         dimensionProperty = 'width';
  305.         break;
  306.     }
  307.  
  308.     // Create style node
  309.     var style = document.createElement('style');
  310.     style.type = 'text/css';
  311.  
  312.     var css = '.UC_fb-tab,.UC_fb-tab__close{display:inline-block;cursor:pointer}.UC_fb-content,.UC_fb-tab{max-width:100%;max-height:100%;box-sizing:border-box;background:#fff}.UC_fb-tab-container{position:fixed;z-index:' + settings.zIndex + ';' + containerPosStyle + '}.UC_fb-tab{position:absolute;margin:0 auto;text-align:center;z-index:' + settings.zIndex + ';color:#333;font-size:15px;padding:10px 10px 10px 20px;' + tabPosStyle + '}.UC_fb-tab__inner{display:inline-block;margin:0 auto}.UC_fb-tab__close{position:absolute;right:10px;font-family:sans-serif}.UC_fb-content{padding:20px;text-align:left;position:relative;}.UC_fb-tab-bg{display:none;background:#000;opacity:0.7;position:fixed;top:0;right:0;bottom:0;left:0;z-index:' + (settings.zIndex - 1) + ';}';
  313.  
  314.     if (style.styleSheet) {
  315.       style.styleSheet.cssText = css;
  316.     } else {
  317.       style.appendChild(document.createTextNode(css));
  318.     }
  319.  
  320.     return style;
  321.   };
  322.  
  323.   var destroyCSS = function () {
  324.     if (css) css.parentElement.removeChild(css);
  325.   };
  326.  
  327.   // Return height and width of each element
  328.   var getDimensions = function () {
  329.     var $container = $('.UC_fb-tab-container');
  330.     var $tab = $container.children('.UC_fb-tab');
  331.     var $content = $container.children('.UC_fb-content');
  332.     var $window = $(window);
  333.  
  334.     var dimensions = {
  335.       window: {
  336.         width: $window.innerWidth(),
  337.         height: $window.innerHeight()
  338.       },
  339.       tab: {
  340.         width: $tab.outerWidth(),
  341.         height: $tab.outerHeight()
  342.       },
  343.       content: {
  344.         width: $content.outerWidth(),
  345.         height: $content.outerHeight()
  346.       }
  347.     };
  348.  
  349.     return dimensions;
  350.   };
  351.  
  352.   // Generate animations based on dimensions
  353.   var getAnimations = function (dimensions) {
  354.     if (!dimensions) dimensions = getDimensions();
  355.     if (!settings) settings = getSettings();
  356.  
  357.     var updatedAnimations = {
  358.       remove: {},
  359.       open: {},
  360.       close: {}
  361.     };
  362.  
  363.     updatedAnimations.remove[settings.position] = '-100%';
  364.     updatedAnimations.open[settings.position] = '0';
  365.     updatedAnimations.close[settings.position] = '-' + dimensions.content[dimensionProperty] + 'px';
  366.  
  367.     return updatedAnimations;
  368.   };
  369.  
  370.   // Event Handlers
  371.   var attachEventHandlers = function (component) {
  372.     if (!component) return false;
  373.     var tab = component.find('.UC_fb-tab');
  374.     var content = component.find('.UC_fb-content');
  375.     var tabStatus = 'closed';
  376.     var pos = settings.position;
  377.     var orientation = (pos === 'left' || pos === 'right') ? 'horizontal' : 'vertical';
  378.  
  379.     tab.click(function () {
  380.       var maxWidth, maxHeight, animationToPerform;
  381.  
  382.       // Refresh dimensions and animations
  383.       dimensions = getDimensions();
  384.       animations = getAnimations(dimensions);
  385.  
  386.       // Define max dimensions to make sure close tab is always visible
  387.       if (orientation === 'horizontal') {
  388.         maxWidth = dimensions.window.width - dimensions.tab.height - 5;
  389.         maxHeight = dimensions.window.height;
  390.       } else {
  391.         maxWidth = dimensions.window.width;
  392.         maxHeight = dimensions.window.height - dimensions.tab.height - 5;
  393.       }
  394.       content.css({
  395.         'max-width': maxWidth,
  396.         'max-height': maxHeight
  397.       });
  398.  
  399.       // If dimensions.content values exeed new max dimensions, update values to reflect current render state
  400.       if (dimensions.content.width > maxWidth) dimensions.content.width = maxWidth;
  401.       if (dimensions.content.height > maxHeight) dimensions.content.height = maxHeight;
  402.  
  403.  
  404.       // Get next animation based on whether tab is open or closed
  405.       if (tabStatus === 'open') {
  406.         animationToPerform = animations.close
  407.         if (background) background.fadeOut();
  408.       } else {
  409.         animationToPerform = animations.open;
  410.         if (background) background.fadeIn();
  411.       }
  412.  
  413.       // Perform animation
  414.       component.animate(animationToPerform, settings.animationSpeed, function () {
  415.         tabStatus = tabStatus === 'open' ? 'closed' : 'open';
  416.       });
  417.     });
  418.  
  419.     tab.find('.UC_fb-tab__close').click(function (e) {
  420.       e.stopPropagation();
  421.       if (background) background.fadeOut();
  422.       component.animate(animations.remove, settings.animationSpeed);
  423.  
  424.       if (settings.sessionClose) {
  425.         // Prevent showing again using session storage
  426.         window.sessionStorage.setItem('ucfbtab-closed', 1);
  427.       }
  428.     });
  429.   };
  430.  
  431.   /*
  432.    * Public Methods
  433.    */
  434.   var methods = {
  435.     init: function (options) {
  436.       var newSettings = getSettings(options);
  437.       if (settings !== newSettings) settings = newSettings;
  438.  
  439.       if (settings.sessionClose && window.sessionStorage.getItem('ucfbtab-closed')) {
  440.         return;
  441.       }
  442.  
  443.       component = createComponent();
  444.       css = createCSS();
  445.  
  446.       // Inject in DOM
  447.       component.prependTo('body'); // HTML
  448.       document.body.insertBefore(css, component[0]); // Style tag
  449.       if (settings.dimBackground) component.before(background); // Background
  450.  
  451.  
  452.       dimensions = getDimensions();
  453.       animations = getAnimations(dimensions);
  454.       attachEventHandlers(component);
  455.  
  456.       // Set initial position
  457.       component.css(settings.position, '-' + dimensions.content[dimensionProperty] + 'px');
  458.     },
  459.  
  460.     destroy: {
  461.       component: destroyComponent,
  462.       css: destroyCSS,
  463.       all: function () {
  464.         destroyComponent();
  465.         destroyCSS();
  466.       }
  467.     },
  468.  
  469.     refresh: function (options) {
  470.       this.destroy.all();
  471.       this.init(options);
  472.     }
  473.   };
  474.  
  475.   return methods;
  476. };
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top