/** * @typedef YqEcommerceResultsList * @type {object} */ class YouneeqEcommerceHandler { public box: HTMLElement; public ecom: object; public is_loading: boolean; public currency: string; public currency_format: string; public static instances: YouneeqEcommerceHandler[] = []; public static user_id: string = ''; public static is_waiting_for_id: boolean = false; private static readonly ECOM_PATH = `https://apitest.youneeq.ca/api/ecom`; private static readonly SESSION_ID_FILE = `https://api.youneeq.ca/app/sessionid`; private static readonly ADD_TO_CART_PATH = `https://apitest.youneeq.ca/api/addtocart/get`; public constructor( box: HTMLElement, auto: boolean = true ) { if ( !this || !this.init_all_data ) { throw `YouneeqEcommerceHandler() must be called with "new"`; } this.box = box; this.ecom = {}; this.is_loading = false; this.currency = ''; this.currency_format = ''; YouneeqEcommerceHandler.instances.push( auto ? this.init_all_data().request( [ `first`, `observe` ] ) : this.init_all_data() ); } /** * Generates YouneeqEcommerceHandler instances for each valid HTML element on the page. * * @since 3.1.0 */ public static generate(): void { let ecom_boxes = jQuery( `#youneeq-ecommerce, .youneeq-ecommerce, youneeq-ecommerce` ) .get().sort( ( a, b ) => { let prio_a = jQuery( a ).attr( `data-yq-priority` ), prio_b = jQuery( b ).attr( `data-yq-priority` ); prio_a = prio_a ? parseInt( prio_a ) : 0; prio_b = prio_b ? parseInt( prio_b ) : 0; if ( prio_a > prio_b ) { return 1; } else if ( prio_a < prio_b ) { return -1; } return 0; }); ecom_boxes.forEach( e => { let box = jQuery( e ), handler = new YouneeqEcommerceHandler( e, !box.hasClass( `yq-no-auto` ) ); box.data( `youneeqEcommerceHandler`, handler ); }); } public static init_user_id( user_id: string|null = null ): void { if ( user_id !== null && user_id.length >= 8 ) { YouneeqEcommerceHandler.user_id = user_id; } else { let has_storage = true; try { user_id = localStorage.getItem( `yq_session` ); } catch ( e ) { has_storage = false; } if ( user_id && user_id.length >= 8 ) { YouneeqEcommerceHandler.user_id = user_id; } else { YouneeqEcommerceHandler.is_waiting_for_id = true; jQuery( `` ).load( YouneeqEcommerceHandler.SESSION_ID_FILE, id => { if ( id.length >= 8 ) { YouneeqEcommerceHandler.user_id = id; if ( has_storage ) { localStorage.setItem( `yq_session`, id ); } } YouneeqEcommerceHandler.is_waiting_for_id = false; }); } } } public static add_to_cart( product_data: object ): void { if ( product_data.product_id ) { product_data.domain = product_data.domain ? product_data.domain : window.location.hostname; product_data.variant_id = product_data.variant_id ? product_data.variant_id : 0; product_data.tags = product_data.tags ? product_data.tags : []; product_data.user_id = YouneeqEcommerceHandler.user_id; product_data.url = product_data.url ? product_data.url : window.location.toString(); try { product_data.published_at = new Date( product_data.published_at ).toISOString(); } catch ( e ) { product_data.published_at = new Date().toISOString(); } jQuery.ajax({ url: YouneeqEcommerceHandler.ADD_TO_CART_PATH, crossDomain: true, dataType: `jsonp`, data: { json: JSON.stringify( product_data ) } }); } } public static place_order( order_data: object ): void { order_data.user_id = YouneeqEcommerceHandler.user_id; try { order_data.created_at = new Date( order_data.created_at ).toISOString(); } catch ( e ) { order_data.created_at = new Date().toISOString(); } console.info( order_data ); } /** * Send a Youneeq request. * * @since 3.1.0 * * @return {YouneeqEcommerceHandler} * @param {string[]} tags List of tags specifying request context. */ public request( tags: string[] = [] ): this { if ( !this.is_loading ) { this.is_loading = true; let data_get = this.get_request_data( false, tags ), data_post = this.get_request_data( true, tags ); jQuery.ajax({ url: YouneeqEcommerceHandler.ECOM_PATH, crossDomain: true, dataType: `jsonp`, method: `GET`, data: { json: JSON.stringify( data_get ) } }) .done( this._populate( tags, `ajax_display` in this ) ); jQuery.ajax({ url: YouneeqEcommerceHandler.ECOM_PATH, crossDomain: true, dataType: `jsonp`, method: `POST`, data: { json: JSON.stringify( data_post ) }, beforeSend: console.info }); } return this; } private get_request_data( is_post: boolean, tags: string[] ): object { let data = {}; data.currency = this.ecom.currency.name; data.domain = this.ecom.domain; data.product_id = this.ecom.product && this.ecom.product.id ? this.ecom.product.id : null; data.url = this.ecom.url; data.user_id = YouneeqEcommerceHandler.user_id; if ( is_post ) { data.product = this.ecom.product; } else { // Send page hit data with first request if ( tags.indexOf( `first` ) > -1 ) { data.page_hit = YouneeqEcommerceHandler.get_page_hit_data(); } if ( 'suggest' in this.ecom ) { data.suggest = [ this.ecom.suggest ]; } data.track_user = true; data.index_product = false; } return data; } private static get_page_hit_data(): object { let data = { href: document.location.href, referrer: document.referrer }; try { let tz_info = jzTimezoneDetector.determine_timezone(); data.tzoff = tz_info.timezone.utc_offset; data.tzname = tz_info.timezone.olson_tz; } catch ( e ) {} return data; } /** * Formats a number into a price according to the formatting rules supplied to the ecommerce handler. * * Prices should be positive integers with no decimal places. * * @since 3.1.0 * * @return {string} * @param {number} price A positive integer representing the price. */ public format_price( price: number ): string { let out_num = '', num_format = null, over_1000 = false; num_format = this.ecom.currency.format.match( /#{(\d+)(\D)(\D)}/ ); if ( !num_format || num_format.length != 4 ) { throw new Error( 'YouneeqEcommerceHandler: Invalid currency format' ); } price = Math.floor( Math.abs( price ) ); if ( num_format[1] > 0 ) { out_num = num_format[3] + ( price % Math.pow( 10, num_format[1] ) ) .toString().padStart( num_format[1], '0'); price = Math.floor( price / Math.pow( 10, num_format[1] ) ); } if ( !price ) { out_num = '0' + out_num; } else { while ( price >= 1000 ) { out_num = ( price % 1000 ).toString() .padStart( 3, '0' ) + ( over_1000 ? num_format[2] : '' ) + out_num; price = Math.floor( price / 1000 ); over_1000 = true; } out_num = price + ( over_1000 ? num_format[2] : '' ) + out_num; } return this.ecom.currency.format.replace( /#{\d+\D\D}/, out_num ); } /** * Generate recommended product HTML and display it on the page. * * @since 3.1.0 * * @param {YqEcommerceResultsList} response Returned data from Youneeq request. * @param {string[]} tags List of tags specifying request context. */ private display( response: YqEcommerceResultsList, tags: string[] ): void { if ( response && `results` in response && response.results.length ) { let products = response.results, $box = jQuery( this.box ); for ( let i = 0, max = products.length; i < max; i++ ) { let id = products[ i ].id ? products[ i ].id : ``, title = products[ i ].title ? products [ i ].title : ``, url = products[ i ].url ? products[ i ].url : ``, img = products[ i ].featured_image ? products[ i ].featured_image : ``, price = products[ i ].price ? products[ i ].price : ``; $box.append( `
${ this.format_price( price ) }