Advertisement
Guest User

Untitled

a guest
Aug 20th, 2019
342
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.17 KB | None | 0 0
  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. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement