/** * jQiery scrollBar Plugin * @author Falk Müller (www-falk-m.de) * Thankts to https://codepen.io/IliaSky/pen/obowmv */ ;( function( $, window, document ) { "use strict"; var pluginName = "scrollBox", defaults = { containerClass: "sb-container", containerNoScrollClass: "sb-container-noscroll", contentClass: "sb-content", scrollbarContainerClass: "sb-scrollbar-container", scrollBarClass: "sb-scrollbar" }; // plugin constructor function Plugin ( element, options ) { this.element = element; this.settings = $.extend( {}, defaults, options ); this._defaults = defaults; this._name = pluginName; this.init(); } // Avoid Plugin.prototype conflicts $.extend( Plugin.prototype, { init: function() { this.addScrollbar(); this.addEvents(); this.onResize(); }, addScrollbar: function() { $( this.element ).addClass(this.settings.containerClass); this.wrapper = $("
"); this.wrapper.append($( this.element ).contents()); $( this.element ).append( this.wrapper ); this.scollbarContainer = $("
"); this.scrollBar = $("
"); this.scollbarContainer.append(this.scrollBar); $( this.element ).prepend(this.scollbarContainer); }, addEvents: function(){ this.wrapper.on("scroll." + pluginName, $.proxy(this.onScroll, this) ); $(window).on("resize." + pluginName, $.proxy(this.onResize, this) ); this.scrollBar.on('mousedown.' + pluginName, $.proxy(this.onMousedown, this)); this.scrollBar.on('touchstart.' + pluginName, $.proxy(this.onTouchstart, this)); }, onTouchstart: function(ev){ var me = this; ev.preventDefault(); var y = me.scrollBar[0].offsetTop; var onMove = function(end){ var delta = end.touches[0].pageY - ev.touches[0].pageY; me.scrollBar[0].style.top = Math.min(me.scollbarContainer[0].clientHeight - me.scrollBar[0].clientHeight, Math.max(0, y + delta)) + 'px'; me.wrapper[0].scrollTop = (me.wrapper[0].scrollHeight * me.scrollBar[0].offsetTop / me.scollbarContainer[0].clientHeight); }; $(document).on("touchmove." + pluginName, onMove); $(document).on("touchend." + pluginName, function(){ $(document).off("touchmove." + pluginName); $(document).off("touchend." + pluginName); }); }, onMousedown: function(ev){ var me = this; ev.preventDefault(); var y = me.scrollBar[0].offsetTop; var onMove = function(end){ var delta = end.pageY - ev.pageY; me.scrollBar[0].style.top = Math.min(me.scollbarContainer[0].clientHeight - me.scrollBar[0].clientHeight, Math.max(0, y + delta)) + 'px'; me.wrapper[0].scrollTop = (me.wrapper[0].scrollHeight * me.scrollBar[0].offsetTop / me.scollbarContainer[0].clientHeight); }; $(document).on("mousemove." + pluginName, onMove); $(document).on("mouseup." + pluginName, function(){ $(document).off("mousemove." + pluginName); $(document).off("mouseup." + pluginName); }); }, onResize: function(){ this.wrapper.css("max-height", $(this.element).height()); var wrapper_client_height = this.wrapper[0].clientHeight; this.scrollBar.css("height", this.scollbarContainer[0].clientHeight * wrapper_client_height / this.wrapper[0].scrollHeight + "px"); if(this.scollbarContainer[0].clientHeight <= this.scrollBar[0].clientHeight){ $( this.element ).addClass(this.settings.containerNoScrollClass); } else { $( this.element ).removeClass(this.settings.containerNoScrollClass); } this.onScroll(); }, onScroll: function(){ this.scrollBar.css("top", Math.min(this.scollbarContainer[0].clientHeight - this.scrollBar[0].clientHeight, this.scollbarContainer[0].clientHeight * this.wrapper[0].scrollTop / this.wrapper[0].scrollHeight) + "px"); } } ); // A really lightweight plugin wrapper around the constructor, // preventing against multiple instantiations $.fn[pluginName] = function( options ) { return this.each( function() { if ( !$.data( this, "plugin_" + pluginName ) ) { $.data( this, "plugin_" + pluginName, new Plugin( this, options ) ); } } ); }; } )( jQuery, window, document ); (function( $, document ) { "use strict"; var orderable_drawer = { /** * On doc ready. */ on_ready: function() { orderable_drawer.cache(); orderable_drawer.watch(); }, /** * Cache. */ cache: function() { orderable_drawer.vars = { classes: { overlay: 'orderable-drawer-overlay', drawer: 'orderable-drawer', drawer_cart: 'orderable-drawer__cart', drawer_html: 'orderable-drawer__html', overlay_open: 'orderable-drawer-overlay--open', drawer_open: 'orderable-drawer--open', drawer_open_body: 'orderable-drawer-open' } }; orderable_drawer.elements = { body: $( 'body' ), overlay: $( '.' + orderable_drawer.vars.classes.overlay ), drawer: $( '.' + orderable_drawer.vars.classes.drawer ), drawer_cart: $( '.' + orderable_drawer.vars.classes.drawer_cart ), drawer_html: $( '.' + orderable_drawer.vars.classes.drawer_html ), floating_cart_button_class: '.orderable-floating-cart__button' }; }, /** * Watch for trigger events. */ watch: function() { if ( typeof orderable_drawer.elements.drawer === 'undefined' ) { return; } $( document.body ).on( 'orderable-drawer.open', orderable_drawer.open ); $( document.body ).on( 'orderable-drawer.close', orderable_drawer.close ); $( document.body ).on( 'click', orderable_drawer.elements.floating_cart_button_class, function() { $( document.body ).trigger( 'orderable-drawer.open', { show_cart: true } ); } ); $( document.body ).on( 'orderable-increase-quantity', orderable_drawer.cart.click_increase_decrease_quantity ); $( document.body ).on( 'orderable-decrease-quantity', orderable_drawer.cart.click_increase_decrease_quantity ); }, /** * Open the drawer. */ open: function( event, args ) { args.html = args.html || false; args.show_cart = args.show_cart || false; orderable_drawer.elements.drawer_html.hide(); orderable_drawer.elements.drawer_cart.hide(); if ( args.html ) { orderable_drawer.elements.drawer_html.html( args.html ); orderable_drawer.elements.drawer_html.show(); } if ( args.show_cart ) { // Empty drawer HTML before showing cart. Prevents options // interfering with subsequent cart additions. orderable_drawer.elements.drawer_html.html( '' ); orderable_drawer.elements.drawer_cart.show(); } orderable_drawer.elements.overlay.addClass( orderable_drawer.vars.classes.overlay_open ); orderable_drawer.elements.drawer.addClass( orderable_drawer.vars.classes.drawer_open ); orderable_drawer.elements.body.addClass( orderable_drawer.vars.classes.drawer_open_body ); $( document.body ).trigger( 'orderable-drawer.opened', args ); }, /** * Close the drawer. */ close: function() { orderable_drawer.elements.overlay.removeClass( orderable_drawer.vars.classes.overlay_open ); orderable_drawer.elements.drawer.removeClass( orderable_drawer.vars.classes.drawer_open ); orderable_drawer.elements.body.removeClass( orderable_drawer.vars.classes.drawer_open_body ); orderable_drawer.elements.drawer_html.html( '' ); $( document.body ).trigger( 'orderable-drawer.closed' ); }, /** * Mini cart related functions. */ cart: { /** * When increase qty is clicked. * * @param e * @param $button */ click_increase_decrease_quantity: function( e, $button ) { var product_id = $button.data( 'orderable-product-id' ), cart_item_key = $button.data( 'orderable-cart-item-key' ), direction = $button.data( 'orderable-trigger' ), quantity = parseInt( $button.data( 'orderable-quantity' ) ); var data = { 'action': 'orderable_cart_quantity', cart_item_key, product_id, 'quantity': 'increase-quantity' === direction ? quantity + 1 : quantity - 1 }; jQuery.post( wc_add_to_cart_params.ajax_url, data, function( response ) { if ( ! response ) { return; } $( document.body ).trigger( 'added_to_cart', [ response.fragments, response.cart_hash, $button ] ); $( document.body ).trigger( 'orderable-drawer.quantity-updated' ); } ); } } }; $( document ).ready( orderable_drawer.on_ready ); }( jQuery, document )); (function( $, document ) { "use strict"; var orderable_layouts = { /** * On doc ready. */ on_ready: function() { orderable_layouts.cache(); orderable_layouts.watch(); }, /** * Cache. */ cache: function() { orderable_layouts.vars = { classes: { main: 'orderable-main', tabs: 'orderable-tabs', tab_items: 'orderable-tabs__item', tab_item_active: 'orderable-tabs__item--active', tab_links: 'orderable-tabs__link', sections: 'orderable-main__group', }, }; orderable_layouts.elements = {}; }, /** * Watch. */ watch: function() { $( document.body ).on( 'click mouseup touchend', '.' + orderable_layouts.vars.classes.tab_links, function( e ) { e.preventDefault(); var $link = $( this ), section_id = $link.attr( 'href' ), $tab = $link.closest( '.' + orderable_layouts.vars.classes.tab_items ), $tabs = $link.closest( '.' + orderable_layouts.vars.classes.tabs ), $tab_items = $tabs.find( '.' + orderable_layouts.vars.classes.tab_items ), $main = $link.closest( '.' + orderable_layouts.vars.classes.main ), $sections = $main.find( '.' + orderable_layouts.vars.classes.sections ), $section = $main.find( section_id ); $sections.hide(); $section.show(); $tab_items.removeClass( orderable_layouts.vars.classes.tab_item_active ); $tab.addClass( orderable_layouts.vars.classes.tab_item_active ); } ); }, }; $( document ).ready( orderable_layouts.on_ready ); }( jQuery, document )); (function( $, document ) { "use strict"; var orderable_products = { /** * On doc ready. */ on_ready: function() { orderable_products.cache(); orderable_products.watch(); }, /** * Cache. */ cache: function() { orderable_products.vars = { classes: { clickable_product: 'orderable-product--clickable', add_to_order_button: 'orderable-product__add-to-order', product_messages: 'orderable-product__messages', product_price: 'orderable-product__actions-price', invalid_field: 'orderable-field--invalid', option_select_td: 'orderable-product__option-select', button_loading: 'orderable-button--loading', out_of_stock: 'orderable-button--out-of-stock' }, parent_price: null }; orderable_products.elements = {}; }, /** * Watch for trigger events. */ watch: function() { $( document.body ).on( 'orderable-drawer.opened', orderable_products.init_product_options ); $( document.body ).on( 'orderable-add-to-cart', orderable_products.click_add_to_order ); $( document.body ).on( 'orderable-product-options', orderable_products.click_add_to_order ); $( document.body ).on( 'mouseenter mouseleave', '.' + orderable_products.vars.classes.clickable_product, orderable_products.simulate_add_to_order_hover ); $( document.body ).on( 'click', '.' + orderable_products.vars.classes.clickable_product, orderable_products.click_add_to_order ); }, /** * Simulate hover on add to order button. * * @param event */ simulate_add_to_order_hover: function( event ) { var $element = $( this ), $button = $element.find( '.' + orderable_products.vars.classes.add_to_order_button ); $button.toggleClass( 'orderable-button--hover', 'mouseenter' === event.type ); }, /** * Add to order click event. * * This event accounts for button clicks or card clicks. */ click_add_to_order: function( event, $element ) { // If undefined, it means it was triggered by a click // event and not the `orderable-add-to-cart` trigger. $element = typeof $element !== 'undefined' ? $element : $( this ); // The button is either the clicked element, or the // add to order button within the clicked element. var $button = $element.is( 'button' ) ? $element : $element.find( '.' + orderable_products.vars.classes.add_to_order_button ), action = $button.data( 'orderable-trigger' ), product_id = $button.data( 'orderable-product-id' ), variation_id = $button.data( 'orderable-variation-id' ), attributes = $button.data( 'orderable-variation-attributes' ), args = { action: action }; if ( $button.hasClass( orderable_products.vars.classes.button_loading ) || $button.hasClass( orderable_products.vars.classes.out_of_stock ) ) { return; } $button.addClass( orderable_products.vars.classes.button_loading ); if ( 'add-to-cart' === action ) { orderable_products.add_to_cart( { 'product_id': product_id, 'variation_id': variation_id, 'attributes': attributes }, function( response ) { args.show_cart = true; args.response = response; $( document.body ).trigger( 'orderable-drawer.open', args ); $button.removeClass( orderable_products.vars.classes.button_loading ); } ); } else if ( 'product-options' === action ) { orderable_products.get_product_options( { 'product_id': product_id }, function( response ) { args.html = response.html; $( document.body ).trigger( 'orderable-drawer.open', args ); $button.removeClass( orderable_products.vars.classes.button_loading ); } ); } }, /** * Ajax add to cart. */ add_to_cart: function( args, callback ) { if ( typeof args.product_id === 'undefined' ) { return; } var data = { 'action': 'orderable_add_to_cart', 'product_id': args.product_id, 'variation_id': args.variation_id || false, 'attributes': args.attributes || false }; // Prepare addons data. if ( $( '.orderable-product-fields-group' ).length ) { var inputs = jQuery( '.orderable-product-fields-group :input' ).serializeArray(); var addons_data = orderable_products.convert_to_flat_object( inputs ); if ( ! jQuery.isEmptyObject( addons_data ) ) { data = Object.assign( data, addons_data ); // Merge objects. } } jQuery.post( wc_add_to_cart_params.ajax_url, data, function( response ) { if ( !response ) { return; } // Trigger event so themes can refresh other areas. $( document.body ).trigger( 'added_to_cart', [ response.fragments, response.cart_hash ] ); if ( typeof callback === 'function' ) { callback( response ); } } ); }, /** * Convert [{name:x, value:y }] to {x:y} format. */ convert_to_flat_object: function ( inputs ) { var data = {}; inputs.forEach( function ( input ) { var is_array = '[]' === input.name.substr( -2 ) || Array.isArray( input.name ); // If last 2 chars are '[]', remove them. var key = is_array ? input.name.substr( 0, input.name.length - 2 ) : input.name; if ( is_array ) { data[ key ] = typeof data[ key ] === 'undefined' ? [] : data[ key ]; data[ key ].push( input.value ); } else { data[ key ] = input.value; } } ); return data; }, /** * Get variable product options. * * @param args * @param callback */ get_product_options: function( args, callback ) { if ( typeof args.product_id === 'undefined' ) { return; } args.action = 'orderable_get_product_options'; jQuery.post( wc_add_to_cart_params.ajax_url, args, function( response ) { if ( !response.success ) { return; } if ( typeof callback === 'function' ) { callback( response.data ); } } ); }, /** * Init drawer product options. * * @param event * @param args */ init_product_options: function( event, args ) { if ( typeof args.action === 'undefined' || 'product-options' !== args.action ) { return; } var $options = $( '.orderable-drawer .orderable-product__options input, .orderable-drawer .orderable-product__options select' ); orderable_products.vars.parent_price = $( '.orderable-drawer .orderable-product__actions-price' ).html(); orderable_products.product_options_change( $options ); orderable_products.update_button_state(); $options.on( 'change', function() { orderable_products.product_options_change( $options ); orderable_products.update_button_state(); } ); }, /** * On product options change. * * @param $options */ product_options_change: function( $options ) { var $add_to_order_button = $( '.orderable-drawer .orderable-product__add-to-order' ), options_set = orderable_products.check_options( $options ), product_type = $add_to_order_button.data( 'orderable-product-type' ); $add_to_order_button.attr( 'data-orderable-trigger', 'add-to-cart' ); $( '.' + orderable_products.vars.classes.product_messages ).html( '' ); if ( 'variable' !== product_type ) { return; } if ( !options_set ) { orderable_products.clear_variation( $add_to_order_button ); return; } var variation = orderable_products.check_variation( $options ); orderable_products.set_variation( $add_to_order_button, variation ); }, /** * Check if all product options are set. * * @param $options * @returns {boolean} */ check_options: function( $options ) { if ( $options.length <= 0 ) { return false; } var all_set = true; $options.each( function( index, option ) { if ( '' === $( option ).val() ) { $( option ).addClass( orderable_products.vars.classes.invalid_field ); all_set = false; } else { $( option ).removeClass( orderable_products.vars.classes.invalid_field ); } } ); return all_set; }, /** * Check if variation has been selected. */ check_variation: function( $options ) { var $product = $options.closest( '.orderable-drawer' ), variations = $.parseJSON( $product.find( '.orderable-product__variations' ).text() ), selected_options = orderable_products.serialize_object( $options ), matching_variations = orderable_products.find_matching_variations( variations, selected_options ); if ( orderable_products.is_empty( matching_variations ) ) { return false; } var variation = matching_variations.shift(); variation.attributes = selected_options; variation.attributes_json = JSON.stringify( selected_options ); return typeof variation !== 'undefined' ? variation : false; }, /** * Set variation for add to cart button. */ set_variation: function( $button, variation ) { var variation_id = variation.variation_id || '', attributes = variation.attributes_json || '', price = variation.price_html || orderable_products.vars.parent_price, message = ''; if ( variation && '' !== variation.availability_html ) { message = variation.availability_html; } if ( variation && !variation.is_in_stock ) { message = '

' + orderable_vars.i18n.out_of_stock + '

'; } if ( variation && !variation.is_purchasable ) { message = '

' + orderable_vars.i18n.unavailable + '

'; } if ( false === variation ) { message = '

' + orderable_vars.i18n.no_exist + '

'; } if ( variation && (!variation.is_purchasable || !variation.is_in_stock) ) { variation_id = ''; attributes = ''; } if ( '' !== message ) { $( '.' + orderable_products.vars.classes.product_messages ).html( message ); } $button.data( 'orderable-variation-id', variation_id ); $button.data( 'orderable-variation-attributes', attributes ); $( '.orderable-drawer .orderable-product__actions-price' ).html( price ); $button.trigger( 'orderable_variation_set', { variation: variation, variation_id: variation_id, attributes: attributes, price: price } ); }, /** * Clear variation and disable add to order. * * @param $button */ clear_variation: function( $button ) { orderable_products.set_variation( $button, '' ); if ( orderable_products.vars.parent_price ) { $( '.orderable-drawer .orderable-product__actions-price' ).html( orderable_products.vars.parent_price ); } }, /** * Find matching variations for attributes. */ find_matching_variations: function( variations, attributes ) { var matching = []; for ( var i = 0; i < variations.length; i ++ ) { var variation = variations[ i ]; if ( orderable_products.is_matching_variation( variation.attributes, attributes ) ) { matching.push( variation ); } } return matching; }, /** * See if attributes match. * @return {Boolean} */ is_matching_variation: function( variation_attributes, attributes ) { var match = true; for ( var attr_name in variation_attributes ) { if ( variation_attributes.hasOwnProperty( attr_name ) ) { var val1 = variation_attributes[ attr_name ]; var val2 = attributes[ attr_name ]; if ( val1 !== undefined && val2 !== undefined && val1.length !== 0 && val2.length !== 0 && val1 !== val2 ) { match = false; } } } return match; }, /** * Is value empty? * * @param value * @returns {boolean} */ is_empty: function( value ) { return typeof value === 'undefined' || false === value || value.length <= 0 || !value; }, /** * Serialize into a key/value object. * * @param $elements * @returns {{}} */ serialize_object: function objectifyForm( $elements ) { var serialized = $elements.serializeArray(), return_object = {}; for ( var i = 0; i < serialized.length; i ++ ) { return_object[ serialized[ i ].name ] = serialized[ i ].value; } return return_object; }, /** * Disable/Enable the 'Add to cart' button based on the presence of orderable-field--invalid class. */ update_button_state: function () { // Add delay to ensure invalid class has been assigned to inputs. setTimeout( function () { var $button = $( '.orderable-drawer .orderable-product__add-to-order' ), invalid_fields_count = $( '.orderable-drawer__html .' + orderable_products.vars.classes.invalid_field ).length, product_type = $button.data( 'orderable-product-type' ), has_variation_id = true; if ( 'variable' === product_type ) { has_variation_id = '' !== $button.data( 'orderable-variation-id' ); } $button.prop( 'disabled', invalid_fields_count || ! has_variation_id ); }, 50 ); } }; $( document ).ready( orderable_products.on_ready ); }( jQuery, document )); (function( $, document ) { "use strict"; var orderable_scrollbar = { /** * On doc ready. */ on_ready: function() { orderable_scrollbar.cache(); orderable_scrollbar.watch(); }, /** * Cache. */ cache: function() { orderable_scrollbar.vars = { top: {}, }; orderable_scrollbar.elements = {}; }, /** * Watch. */ watch: function() { $( document.body ).on( 'orderable-drawer.opened', orderable_scrollbar.trigger ); $( document.body ).on( 'wc_fragments_loaded', orderable_scrollbar.trigger ); }, /** * Init or retrigger scrollbars. */ trigger: function() { $( '.orderable-sb-container' ).each( function( index, element ) { var $element = $( element ), scroll_id = $element.data( 'orderable-scroll-id' ); if ( ! orderable_scrollbar.has_scrollbar( $element ) ) { $element.scrollBox( { containerClass: "orderable-sb-container", containerNoScrollClass: "orderable-sb-container-noscroll", contentClass: "orderable-sb-content", scrollbarContainerClass: "orderable-sb-scrollbar-container", scrollBarClass: "orderable-sb-scrollbar" } ); var $content = $element.find( '.orderable-sb-content' ); if ( $content.length > 0 ) { $content.on( 'scroll.scrollBox', orderable_scrollbar.log_top_position ); // Set scroll position. if ( typeof orderable_scrollbar.vars.top[ scroll_id ] !== 'undefined' ) { $content.scrollTop( orderable_scrollbar.vars.top[ scroll_id ] ); } } } } ); $( window ).trigger( 'resize.scrollBox' ); }, /** * Has scrollbar already? * * @param $element * @return {boolean} */ has_scrollbar: function( $element ) { return $element.find( '.orderable-sb-content' ).length > 0; }, /** * Set scrolltop position. * * @param e */ log_top_position: function( e ) { var $element = $( e.currentTarget ), $container = $element.closest( '.orderable-sb-container' ), scroll_id = $container.data( 'orderable-scroll-id' ); orderable_scrollbar.vars.top[ scroll_id ] = $( e.currentTarget ).scrollTop(); } }; $( document ).ready( orderable_scrollbar.on_ready ); }( jQuery, document )); (function( $, document ) { "use strict"; var orderable_services = { /** * On doc ready. */ on_ready: function() { orderable_services.watch(); }, /** * Watch for trigger events. */ watch: function() { $( document.body ).on( 'orderable-show-lookup-services', function( event, $button ) { var $parent = $button.closest( '.orderable-services-selector' ), $lookup = $parent.find( '.orderable-services-selector__lookup' ); $button.hide(); $lookup.show(); } ); $( document.body ).on( 'orderable-lookup-services', function( event, $button ) { var $parent = $button.closest( '.orderable-services-selector' ), $message = $parent.find( '.orderable-services-selector__lookup-message' ), $change = $parent.find( '.orderable-services-selector__selected-change' ), city = $parent.find( '.orderable-services-selector__lookup-city' ).val(), postcode = $parent.find( '.orderable-services-selector__lookup-postcode' ).val(), service = $button.data( 'orderable-service' ); var data = { 'action': 'orderable_lookup_service', 'postcode': postcode, 'city': city, 'service': service }; jQuery.post( wc_add_to_cart_params.ajax_url, data, function( response ) { if ( !response.success ) { $message.html( '

' + response.data.message + '

' ); return; } $message.html( '' ); $change.show(); if ( typeof response.data.fragments !== 'undefined' ) { $.each( response.data.fragments, function( fragment_selector, fragment_html ) { $( fragment_selector ).html( fragment_html ); } ); } console.log( response ); } ); } ); } }; $( document ).ready( orderable_services.on_ready ); }( jQuery, document )); (function( $, document ) { "use strict"; var orderable_timings = { /** * On doc ready. */ on_ready: function() { orderable_timings.watch(); }, /** * Watch for trigger events. */ watch: function() { $( document.body ).on( 'change', '.orderable-order-timings__date', function( event ) { var $date_field = $( this ), $selected = $date_field.find( 'option:selected' ), slots = $selected.data( 'orderable-slots' ), $time_field_wrap = $( '.orderable-order-timings--time' ), $time_field = $( '.orderable-order-timings__time' ), $first_option = $time_field.find( 'option' ).first(), $asap_option = $time_field.find( 'option[value="asap"]' ); $time_field.html( $first_option ); if ( $asap_option ) { $time_field.append( $asap_option ); } if ( ! slots ) { $time_field.prop( 'disabled', true ); $time_field_wrap.hide(); return; } if ( 'all-day' === slots[ 0 ].value ) { $time_field_wrap.hide(); $time_field.prop( 'disabled', true ); } else { $time_field.prop( 'disabled', false ); $time_field_wrap.show(); $.each( slots, function( index, slot ) { $time_field.append( $( '