|
jQuery( document ).ready( function($) { |
|
|
|
/** |
|
* woocommerce_nyp_update function |
|
* Wraps all important nyp callbacks for plugins that maybe don't have elements available on load |
|
* ie: quickview, bundles, etc |
|
*/ |
|
$.fn.woocommerce_nyp_update = function() { |
|
|
|
/** |
|
* Name Your Price Handler for individual items |
|
*/ |
|
$( this ).on( 'woocommerce-nyp-update', function() { |
|
|
|
// Some important objects. |
|
var $cart = $( this ); |
|
var $nyp = $cart.find( '.nyp' ); |
|
var $nyp_input = $cart.find( '.nyp-input' ); |
|
var $submit = $cart.find( ':submit' ); |
|
var $ajax_cart_button = $cart.find( '.ajax_add_to_cart' ); |
|
|
|
// Add a div to hold the error message. |
|
var $error = $cart.find( '.woocommerce-nyp-message' ); |
|
|
|
if ( ! $error.length ){ |
|
$('<div class="woocommerce-nyp-message woocommerce-error"></div>').hide().prependTo($nyp); |
|
} |
|
|
|
// Clear errors. |
|
$nyp.removeClass( 'nyp-error' ); |
|
|
|
// Skip validation for optional products, ex: grouped/bundled. |
|
if( $nyp.data( 'optional' ) === 'yes' && $nyp.data( 'optional_status' ) === false ) { |
|
$error.empty().slideUp(); |
|
return; |
|
} |
|
|
|
// The default error message. |
|
var error_message = $nyp.data( 'hide-minimum' ) ? $nyp.data( 'hide-minimum-error' ) : $nyp.data( 'minimum-error' ); |
|
var error_tag = "%%MINIMUM%%"; |
|
var error = false; |
|
var error_price = ''; // This will hold the formatted price for the error message. |
|
|
|
// The current price. |
|
var form_price = $nyp_input.val(); |
|
// Convert price to default decimal setting for calculations. |
|
var form_price_num = woocommerce_nyp_unformat_price( form_price ); |
|
|
|
var min_price = parseFloat( $nyp.data( 'min-price' ) ); |
|
var max_price = parseFloat( $nyp.data( 'max-price' ) ); |
|
var annual_minimum = parseFloat( $nyp.data( 'annual-minimum' ) ); |
|
|
|
// Get variable billing period data. |
|
var $nyp_period = $cart.find( '.nyp-period' ); |
|
var form_period = $nyp_period.val(); |
|
|
|
// If has variable billing period AND a minimum then we need to annulalize min price for comparison. |
|
if ( annual_minimum > 0 ){ |
|
|
|
// Calculate the price over the course of a year for comparison. |
|
form_annulualized_price = form_price_num * woocommerce_nyp_params.annual_price_factors[form_period]; |
|
|
|
// If the calculated annual price is less than the annual minimum. |
|
if( form_annulualized_price < annual_minimum ){ |
|
|
|
error = annual_minimum / woocommerce_nyp_params.annual_price_factors[form_period]; |
|
|
|
// In the case of variable period we need to adjust the error message a bit. |
|
error_price = woocommerce_nyp_format_price( error, woocommerce_nyp_params.currency_format_symbol, true ) + ' / ' + $nyp_period.find('option[value="' + form_period + '"]').text(); |
|
|
|
} |
|
|
|
// Otherwise a regular product or subscription with non-variable periods, compare price directly. |
|
} else if ( form_price_num < min_price ) { |
|
error = min_price; |
|
error_price = woocommerce_nyp_format_price( min_price, woocommerce_nyp_params.currency_format_symbol, true ); |
|
|
|
// Check maximum price. |
|
} else if ( form_price_num > max_price ) { |
|
error = max_price; |
|
error_message = $nyp.data( 'maximum-error' ); |
|
error_tag = "%%MAXIMUM%%"; |
|
error_price = woocommerce_nyp_format_price( max_price, woocommerce_nyp_params.currency_format_symbol, true ); |
|
} |
|
|
|
// Maybe auto-format the input. |
|
if( $.trim( form_price ) != '' ){ |
|
$nyp_input.val( woocommerce_nyp_format_price( form_price_num ) ); |
|
} |
|
|
|
// Always add the price to the button as data for AJAX add to cart. |
|
$submit.data( $nyp_input.attr( 'name' ), woocommerce_nyp_format_price( form_price_num ) ); |
|
|
|
// For grouped products and product bundles, make sure to show error message that has quantity. |
|
$nyp_closest_form = $nyp.closest( 'form' ); |
|
if( $nyp_closest_form.hasClass( 'grouped_form' ) || $nyp_closest_form.hasClass( 'bundle_form' ) ){ |
|
// Also change the btn value, its different for grouped products. |
|
$submit = $nyp_closest_form.find( ':submit' ); |
|
} |
|
|
|
// If we've set an error, show message and prevent submit. |
|
if ( error ){ |
|
|
|
// Disable submit. |
|
$nyp.addClass( 'nyp-error' ); |
|
|
|
// Disable core AJAX support. |
|
if( $ajax_cart_button.length ) { |
|
$ajax_cart_button.removeAttr( 'data-product_id' ); |
|
} |
|
|
|
// Show error, but not on page load. |
|
if( $nyp.data( 'initialized' ) === 1 ) { |
|
error_message = error_message.replace( error_tag, error_price ); |
|
|
|
$error.html( error_message ).slideDown( function() { |
|
$nyp_input.focus(); |
|
}); |
|
|
|
// Add disabled btn class. |
|
$submit.addClass( 'nyp-disabled' ); |
|
|
|
} |
|
|
|
// Otherwise allow submit and update. |
|
} else { |
|
|
|
// Remove error. |
|
$error.slideUp(); |
|
|
|
// Remove disabled btn class. |
|
$submit.removeClass( 'nyp-disabled' ); |
|
|
|
// Restore core AJAX support. |
|
if( $ajax_cart_button.length ) { |
|
$ajax_cart_button.attr( 'data-product_id', $nyp.data( 'product_id' ) ); |
|
} |
|
|
|
// Product add ons compatibility. |
|
$(this).find( '#product-addons-total' ).data( 'price', form_price_num ); |
|
$cart.trigger( 'woocommerce-product-addons-update' ); |
|
|
|
// Bundles compatibility. |
|
$nyp.data( 'price', form_price_num ); |
|
$cart.trigger( 'woocommerce-nyp-updated-item' ); |
|
$( 'body' ).trigger( 'woocommerce-nyp-updated' ); |
|
|
|
} |
|
|
|
// For NYP Products in OPC Template. |
|
|
|
// If opc is currently around, and nyp is also found. |
|
if( $nyp.closest( '.wcopc' ).length > 0 ){ |
|
|
|
if( error && resetQty === true ){ |
|
// Reset quantity value of current NYP element. |
|
$cart.find( '.quantity .qty' ).val(0); |
|
|
|
// Set value to true. |
|
nypAllowTrigger = true; |
|
|
|
// Reset back |
|
resetQty = false; |
|
} |
|
|
|
} |
|
|
|
// Set to initialized. |
|
$nyp.data( 'initialized', 1 ); |
|
|
|
} ); // End woocommerce-nyp-update handler. |
|
|
|
// NYP update on change to any nyp input. |
|
$( this ).on( 'change', '.nyp-input, .nyp-period', function() { |
|
var $cart = $(this).closest( '.cart, .nyp-product' ); |
|
$cart.trigger( 'woocommerce-nyp-update' ); |
|
} ); |
|
|
|
// Trigger right away. |
|
$( this ).find( '.nyp-input' ).trigger( 'change' ); |
|
|
|
/** |
|
* Handle NYP Variations |
|
*/ |
|
|
|
if ( $( this ).hasClass( 'variations_form' ) ) { |
|
|
|
// Some important objects. |
|
var $variation_form = $(this); |
|
var $add_to_cart = $(this).find( 'button.single_add_to_cart_button' ); |
|
var $nyp = $(this).find( '.nyp' ); |
|
var $nyp_input = $nyp.find( '.nyp-input' ); |
|
var $minimum = $nyp.find( '.minimum-price' ); |
|
var $subscription_terms = $nyp.find( '.subscription-details' ); |
|
var $error = $variation_form.find( '.woocommerce-nyp-message' ); |
|
|
|
// The add to cart text. |
|
var default_add_to_cart_text = $add_to_cart.html(); |
|
|
|
// Hide the nyp form by default. |
|
$nyp.hide(); |
|
$minimum.hide(); |
|
|
|
// Listeners |
|
|
|
// When variation is found, decide if it is NYP or not. |
|
$variation_form |
|
|
|
.on( 'found_variation', function( event, variation ) { |
|
|
|
// Hide any existing error message. |
|
$error.slideUp(); |
|
|
|
// If NYP show the price input and tweak the data attributes. |
|
if ( typeof variation.is_nyp != undefined && variation.is_nyp == true ) { |
|
|
|
// Switch add to cart button text if variation is NYP. |
|
$add_to_cart.html( variation.add_to_cart_text ); |
|
|
|
// Get the prices out of data attributes. |
|
var display_price = typeof variation.display_price != 'undefined' ? variation.display_price : ''; |
|
var minimum_price = typeof variation.minimum_price != 'undefined' ? variation.minimum_price : ''; |
|
var maximum_price = typeof variation.maximum_price != 'undefined' ? variation.maximum_price : ''; |
|
|
|
// Maybe auto-format the input. |
|
if( $.trim( display_price ) != '' ){ |
|
$nyp_input.val( woocommerce_nyp_format_price( display_price ) ); |
|
} else { |
|
$nyp_input.val( '' ); |
|
} |
|
|
|
// Maybe show subscription terms. |
|
if( $subscription_terms.length && variation.subscription_terms ){ |
|
$subscription_terms.html( variation.subscription_terms ); |
|
} |
|
|
|
// Maybe show minimum price html. |
|
if( variation.minimum_price_html ){ |
|
$minimum.html( variation.minimum_price_html ).show(); |
|
} else { |
|
$minimum.hide(); |
|
} |
|
|
|
// Set the NYP data attributes for JS validation on submit. |
|
$nyp.data( 'min-price', minimum_price ).slideDown(); |
|
$nyp.data( 'max-price', maximum_price ); |
|
|
|
// Toggle minimum error message between explicit and obscure. |
|
$nyp.data( 'hide-minimum', variation.hide_minimum ); |
|
|
|
// Product add ons compatibility. |
|
var form_price_num = woocommerce_nyp_unformat_price( $nyp_input.val() ); |
|
$(this).find( '#product-addons-total' ).data( 'price', form_price_num ); |
|
$(this).trigger( 'woocommerce-product-addons-update' ); |
|
|
|
// If not NYP, hide the price input. |
|
} else { |
|
|
|
// Use default add to cart button text if variation is not NYP. |
|
$add_to_cart.html( default_add_to_cart_text ); |
|
|
|
// Hide. |
|
$nyp.slideUp(); |
|
|
|
} |
|
|
|
} ) |
|
|
|
// Hide the price input when reset is clicked. |
|
.on( 'reset_image', function( event ) { |
|
|
|
$add_to_cart.html( default_add_to_cart_text ); |
|
$nyp.slideUp(); |
|
$error.hide(); |
|
|
|
} ) |
|
|
|
// Hide the price input when reset is clicked. |
|
.on( 'click', '.reset_variations', function( event ) { |
|
|
|
$add_to_cart.html( default_add_to_cart_text ); |
|
$nyp.slideUp(); |
|
$error.hide(); |
|
|
|
} ); |
|
|
|
// Need to re-trigger some things on load since Woo unbinds the found_variation event. |
|
$( this ).find( '.variations select, .variations input[type=radio]' ).trigger( 'change' ); |
|
|
|
} |
|
|
|
|
|
} // End fn.woocommerce_nyp_update(). |
|
|
|
/** |
|
* Run when Quick view item is launched. |
|
*/ |
|
$( 'body' ).on( 'quick-view-displayed', function() { |
|
$( 'body' ).find( '.cart:not(.cart_group)' ).each( function() { |
|
$( this ).woocommerce_nyp_update(); |
|
} ); |
|
} ); |
|
|
|
/** |
|
* Run when a Composite component is re-loaded. |
|
*/ |
|
$( 'body .component' ).on( 'wc-composite-component-loaded', function() { |
|
$( this ).find( '.cart:not(.cart_group)' ).each( function() { |
|
$( this ).woocommerce_nyp_update(); |
|
} ); |
|
} ); |
|
|
|
/** |
|
* Run on load. |
|
*/ |
|
$( 'body' ).find( '.cart:not(.cart_group, .grouped_form)' ).each( function() { |
|
$( this ).woocommerce_nyp_update(); |
|
} ); |
|
|
|
/** |
|
* Run on load for grouped products. |
|
*/ |
|
$( 'body .grouped_form' ).find( '.nyp-product' ).each( function() { |
|
$( this ).woocommerce_nyp_update(); |
|
} ); |
|
|
|
/** |
|
* Compatibility with optional grouped products. |
|
*/ |
|
$( 'body .grouped_form .qty' ).on( 'change', function() { |
|
|
|
var $nyp = $(this).closest( 'tr' ).find( '.nyp' ); |
|
|
|
if( $nyp.length ) { |
|
if( $(this).val() > 0 ) { |
|
$nyp.data( 'optional_status', true ); |
|
} else { |
|
$nyp.data( 'optional_status', false ); |
|
} |
|
} |
|
|
|
} ).change(); |
|
|
|
|
|
/** |
|
* Beginning of OPC compatibilty. |
|
* If opc is currently around, and nyp is also found, to preserve resource :) |
|
*/ |
|
if( $( 'body .wcopc' ).length > 0 && $( 'body .wcopc .nyp' ).length > 0 ){ |
|
/** |
|
* Variables to be used within this scope |
|
*/ |
|
// Store the destroyed bind here, access anywhere |
|
var bindStorer = null; |
|
|
|
// Determines if we are to reset qty value in 'woocommerce-nyp-update' function. |
|
var resetQty = false; |
|
|
|
/** |
|
* Should we allow .trigger( 'woocommerce-nyp-update' ); in event opc_validate_add_remove_product |
|
* This is useful when the 2 events "input and change" are running, and along the line, qty is already set to 0 |
|
* before the second event runs, cause if qty is already zero before second event runs, opc will go allowed to validate. |
|
*/ |
|
var nypAllowTrigger = false; |
|
|
|
/** |
|
* Validate One Page Checkout ajax add to cart. |
|
*/ |
|
$( 'body' ).on( 'opc_validate_add_remove_product', function( e, $triggeredBy ) { |
|
var valid = true; |
|
var $cart = $triggeredBy.closest( '.cart' ); |
|
var $nyp = $cart.find( '.nyp' ); |
|
var $qty = $cart.find( '.quantity .qty' ); |
|
|
|
if( $qty.val() > 0 || nypAllowTrigger === true ) { |
|
$cart.trigger( 'woocommerce-nyp-update' ); |
|
if( $nyp.hasClass( 'nyp-error' ) ) { |
|
valid = false; |
|
} |
|
} |
|
return valid; |
|
}); |
|
|
|
/** |
|
* PreBind Function |
|
* |
|
* Helps to trigger an event before its ancestors :) |
|
* This helps to make our validation trigger before that of opc |
|
* helps to solve the issue of not having to add a snippet to the one-page-checkout.js file ;) |
|
* @reference https://stackoverflow.com/a/10413124/5510038 |
|
*/ |
|
$.fn.preBind = function( type, data, fn ) { |
|
this.each( function(){ |
|
var $this = $( this ); |
|
|
|
// Add nyp namespace to our event for easy recognition. |
|
$this.bind( type + '.nyp', data, fn ); |
|
|
|
var currentBindings = $this.data( 'events' )[type]; |
|
|
|
if ( $.isArray( currentBindings ) ){ |
|
currentBindings.unshift( currentBindings.pop() ); |
|
} |
|
}); |
|
return this; |
|
}; |
|
|
|
|
|
/** |
|
* Extract Handler Function |
|
* |
|
* Helps to remove the callback function attached to the element event |
|
* @return Array |
|
*/ |
|
$.fn.nypExtractEventBinding = function( type, data, fn ) { |
|
var newBindings = {}; |
|
var i = 0; |
|
this.each( function(){ |
|
var $this = $( this ); |
|
|
|
// If a bind element is to be destroyed |
|
if ( undefined !== data && data.bindDestroy === true ){ |
|
// Delete only from element without event namespace nyp |
|
$this.data( 'events' )[type].forEach( function( item, index, object ){ |
|
|
|
if( item.namespace !== "nyp" ){ |
|
// store and remove from element. |
|
newBindings[i] = item; |
|
object.splice( index, 1 ); |
|
} |
|
}); |
|
} |
|
++i; |
|
}); |
|
return newBindings; |
|
}; |
|
|
|
/** |
|
* Run by our own ocp quantity input to change default behaviour of the opc version |
|
* @param e - event |
|
* @param that - this object |
|
*/ |
|
var wcNypOpcValidate = function( e, that ) { |
|
var $input = $( that ); |
|
|
|
// Get the element opc uses to trigger validation |
|
var $opcSelector = $( '#opc-product-selection input[type="number"][data-add_to_cart]' ); |
|
var nypOpcValidTrigger = $( 'body' ).triggerHandler( 'opc_validate_add_remove_product', [ $input ] ); |
|
|
|
// Set nyp Allowed trigger to false now. |
|
nypAllowTrigger = false; |
|
|
|
if( nypOpcValidTrigger === false ) { |
|
// Destroy bind. |
|
// You can get the function handler of an event and store. :) |
|
// Storing the event function on opc js file. |
|
var storeBindChange = $input.nypExtractEventBinding( 'change', { "bindDestroy": true } ); |
|
var storeBindInput = $input.nypExtractEventBinding( 'input', { "bindDestroy": true } ); |
|
|
|
// Store anyone of the above only when there's a value. |
|
bindStorer = ( JSON.stringify(storeBindInput) !== "{}" ? storeBindInput : bindStorer ); |
|
|
|
// Allow quantity reset. |
|
resetQty = true; |
|
|
|
// Prevent opc trigger when running first time. |
|
e.stopImmediatePropagation(); |
|
} |
|
else{ |
|
// On and re-add the handler to retrigger for next time. :) |
|
if(bindStorer != null){ |
|
// Disable qauntity reset. |
|
resetQty = false; |
|
|
|
// Turn back on. |
|
$opcSelector.on( 'input change', bindStorer[0]['handler'] ); |
|
} |
|
|
|
} |
|
}; |
|
|
|
/** |
|
* Compatibility with Validation One Page Checkout ajax add to cart. |
|
* Get the selector that opc uses to trigger on change,input and prebind our function to run before it |
|
* I guess you can call this technique being the flash :) |
|
*/ |
|
var $wcNypOpcSelector = $( '#opc-product-selection input[type="number"][data-add_to_cart]' ); |
|
|
|
// Prebind for change. |
|
$wcNypOpcSelector.preBind( 'change', function( e ){ |
|
wcNypOpcValidate( e, this ); |
|
}); |
|
// Prebind for input. |
|
$wcNypOpcSelector.preBind( 'input', function( e ){ |
|
wcNypOpcValidate( e, this ); |
|
}); |
|
} |
|
// End of OPC Compatibility. |
|
|
|
/** |
|
* Validate on submit. |
|
*/ |
|
$( document.body ).on( 'click', '.single_add_to_cart_button', function(e) { |
|
var valid = true; |
|
$( '.nyp' ).each( function() { |
|
$( this ).woocommerce_nyp_update(); |
|
if( $( this ).hasClass( 'nyp-error' ) ) { |
|
valid = false; |
|
} |
|
}); |
|
|
|
if( ! valid ) { |
|
e.preventDefault(); |
|
} |
|
|
|
}); |
|
|
|
/** |
|
* Helper functions |
|
*/ |
|
// Format the price with accounting.js. |
|
function woocommerce_nyp_format_price( price, currency_symbol, format ){ |
|
|
|
if ( typeof currency_symbol === 'undefined' ) { |
|
currency_symbol = ''; |
|
} |
|
|
|
if ( typeof format === 'undefined' ) { |
|
format = false; |
|
} |
|
|
|
var currency_format = format ? woocommerce_nyp_params.currency_format : '%v'; |
|
|
|
return accounting.formatMoney( price, { |
|
symbol : currency_symbol, |
|
decimal : woocommerce_nyp_params.currency_format_decimal_sep, |
|
thousand: woocommerce_nyp_params.currency_format_thousand_sep, |
|
precision : woocommerce_nyp_params.currency_format_num_decimals, |
|
format: currency_format |
|
}).trim(); |
|
|
|
} |
|
|
|
// Get absolute value of price and turn price into float decimal. |
|
function woocommerce_nyp_unformat_price( price ){ |
|
return Math.abs( parseFloat( accounting.unformat( price, woocommerce_nyp_params.currency_format_decimal_sep ) ) ); |
|
} |
|
|
|
} ); |