Skip to content

Instantly share code, notes, and snippets.

@Preciousomonze
Last active September 29, 2020 03:23
Show Gist options
  • Save Preciousomonze/291248f4537be7697812231e97b228bb to your computer and use it in GitHub Desktop.
Save Preciousomonze/291248f4537be7697812231e97b228bb to your computer and use it in GitHub Desktop.
"if you've got a dump file for lost and abandoned snippets it can go there" @helgatheviking Said. I think it makes sense, so it's what i'll do 🤾🏽‍♂️

THIS Is the README Section

For proper documentation.

Format

  • DD/MM/YYYY - Repo Name/Issue branch - PR NAME - {File_location}: {Brief Note}

19/09/2020 - Woocommerce-free-gift-coupons/issues/33-force-gift-selection - #34 - {woocommerce-free-gift-coupons/assets/js/free-gift-coupons-meta-box.js}

The Js validation wasn't needed 🙂💔

06/03/2020 - woocommerce-name-your-price/fix-opc-script-validation-compatibility - #114 - {woocommerce-name-your-price/assets/js/name-your-price.js}

This was an honourable battle, took me around 4 days to fix, was cool, manipulating dom element and rearranging order of event being executed. dope!! But OPC fixed it on their end, so "wasted" effort 🥺 cruise and vanity.

28/09/2020 - woocommerce-free-gift-coupons/issues/16-sync-gifts-num-specific-products - #35 - {woocommerce-free-gift-coupons/includes/admin/class-wc-free-gift-coupons-admin.php}

This feature allowed the store owner to decide to leave a previously synced product in the required product list, by checking a check box.I have a feeling it'll come in handy, so i'm dropping this here.

<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
if ( class_exists( 'WC_Free_Gift_Coupons_Admin' ) ) {
WC_Free_Gift_Coupons_Admin::init(); // Run if class exists.
}
/**
* Main Admin Class
*
* @class WC_Free_Gift_Coupons_Admin
* @version 2.0.0
*/
class WC_Free_Gift_Coupons_Admin {
/**
* The plugin version
*
* @var string
*/
public static $version = '2.5.2';
/**
* Coupon Product ids list
*
* @var array
* @since 3.0.0
*/
protected static $coupon_product_ids = array();
/**
* Initialize
*
* @return WC_Free_Gift_Coupons_Admin
* @since 1.0
*/
public static function init() {
// Admin scripts.
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_scripts' ) );
// Add and save coupon meta.
add_action( 'woocommerce_coupon_options', array( __CLASS__, 'coupon_options' ), 10, 2 );
add_action( 'woocommerce_coupon_options_save', array( __CLASS__, 'process_shop_coupon_meta' ), 20, 2 );
// Show row meta on the plugin screen.
add_filter( 'plugin_row_meta', array( __CLASS__, 'plugin_row_meta' ), 10, 2 );
}
/**
* Load admin script
*
* @return void
* @since 1.0
*/
public static function admin_scripts() {
// Coupon styles.
wp_register_style( 'woocommerce_free_gift_coupon_meta', plugins_url( '../assets/css/free-gift-coupons-meta-box.css' , __DIR__ ), array(), WC_Free_Gift_Coupons::$version );
// Coupon script.
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
wp_register_script( 'woocommerce_free_gift_coupon_meta', plugins_url( '../assets/js/free-gift-coupons-meta-box' . $suffix . '.js' , __DIR__ ), array( 'jquery', 'backbone', 'underscore', 'wp-util', 'jquery-ui-sortable', 'wc-enhanced-select' ), WC_Free_Gift_Coupons::$version, true );
}
/**
* Load admin script
*
* @param int $coupon_id
* @return void
* @since 1.0
*/
public static function load_scripts( $coupon_id = '' ) {
// Coupon styles.
wp_enqueue_style( 'woocommerce_free_gift_coupon_meta' );
// Coupon script.
wp_enqueue_script( 'woocommerce_free_gift_coupon_meta' );
$translation_array = array(
'coupon_types' => wp_json_encode( WC_Free_Gift_Coupons::get_gift_coupon_types() ),
'free_gifts' => array_values( WC_Free_Gift_Coupons::get_gift_data( intval( $coupon_id ) ) )
);
wp_localize_script( 'woocommerce_free_gift_coupon_meta', 'woocommerce_free_gift_coupon_meta_i18n', $translation_array );
// Backbone template.
add_action( 'admin_print_footer_scripts', array( __CLASS__, 'print_templates' ) );
}
/**
* Output the new Coupon metabox fields
*
* @return HTML
* @since 1.0
*/
public static function coupon_options( $coupon_id, $coupon ) {
self::load_scripts( $coupon_id );
?>
<p class="form-field show_if_free_gift">
<label for="free_gift_ids"><?php esc_html_e( 'Free Gifts', 'wc_free_gift_coupons' ) ?></label>
<span class="description"><?php esc_html_e( 'These are the products you are giving away with this coupon. They will automatically be added to the cart.', 'wc_free_gift_coupons' ); ?></span>
</p>
<p class="form-field show_if_free_gift">
<span class="add_prompt dashicons-before dashicons-plus"></span>
<select id="free_gift_ids" style="width:90%;" class="wc-product-search" name="free_gift_ids" multiple="multiple" data-sortable="sortable" data-placeholder="<?php esc_attr_e( 'Search for a product&hellip;', 'wc_free_gift_coupons' ); ?>" data-action="woocommerce_json_search_products_and_variations">
<option></option>
</select>
</p>
<div id="wc-free-gift-container" class="show_if_free_gift">
<table id="wc-free-gift-table"></table>
<noscript><?php esc_html_e( 'Javascript is required for product selection to work.', 'wc_free_gift_coupons' );?></noscript>
</div>
<!-- Sync Quantities -->
<p class="form-field show_if_free_gift">
<label for="_wc_fgc_product_sync_ids"><?php esc_html_e( 'Sync Quantities', 'wc_free_gift_coupons' ) ?></label>
<span class="description"><?php esc_html_e( 'Sync the gift products\' quantities to the qauntity of a product in the cart.', 'wc_free_gift_coupons' ); ?></span>
</p>
<p class="form-field show_if_free_gift">
<span class="add_prompt dashicons-before dashicons-plus"></span>
<select id="_wc_fgc_product_sync_ids" style="width:80%;" class="wc-product-search" name="_wc_fgc_product_sync_ids" data-sortable="sortable" data-allow-clear="tru" data-placeholder="<?php esc_attr_e( 'Search for a product&hellip;', 'wc_free_gift_coupons' ); ?>" data-action="woocommerce_json_search_products_and_variations">
<option></option>
<?php
$product_ids = $coupon->get_meta( '_wc_fgc_product_sync_ids', true, 'edit' );
foreach ( $product_ids as $row => $product_id ) {
$product = wc_get_product( $product_id );
if ( is_object( $product ) ) {
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . htmlspecialchars( wp_kses_post( $product->get_formatted_name() ) ) . '</option>';
}
}
?>
</select>
<?php
echo wc_help_tip(
__( 'This adds the Synced Product to the required Product List, if it is not already added.', 'wc_free_gift_coupons' )
);
?>
</p>
<?php
woocommerce_wp_checkbox( array(
'id' => 'wc_fgc_leave_old_synced_products',
'value' => $coupon->get_meta( '_wc_fgc_leave_old_synced_products', true, 'edit' ),
'label' => __( 'Leave synced products in required product list.', 'wc_free_gift_coupons' ),
'description' => __( 'Check this box if you want to keep the previous synced product in the required product list.', 'wc_free_gift_coupons' ),
'wrapper_class' => 'show_if_free_gift'
) );
// Free shipping for free gift.
if ( function_exists( 'wc_shipping_enabled' ) && wc_shipping_enabled() ) {
woocommerce_wp_checkbox( array(
'id' => 'wc_free_gift_coupon_free_shipping',
'value' => $coupon->get_meta( '_wc_free_gift_coupon_free_shipping', true, 'edit' ),
'label' => __( 'Free shipping for gift(s)', 'wc_free_gift_coupons' ),
'description' => __( 'Check this box if the free gift(s) should not incur a shipping cost. A free shipping method must be enabled.', 'wc_free_gift_coupons' ),
'wrapper_class' => 'show_if_free_gift'
) );
}
}
/**
* Prints the templates used in the coupon options metabox
*
* @since 2.0.0
*/
public static function print_templates() {
/**
* Backbone Templates
* This file contains all of the HTML used in our application
*
* Each template is wrapped in a script block ( note the type is set to "text/html" ) and given an ID prefixed with
* 'tmpl'. The wp.template method retrieves the contents of the script block and converts these blocks into compiled
* templates to be used and reused in your application.
*/
/**
* The Table Header View
*/
?>
<script type="text/template" id="tmpl-wc-free-gift-products-table-header">
<thead>
<tr>
<th><?php esc_html_e( 'Product', 'wc_free_gift_coupons' );?></th>
<th><?php esc_html_e( 'Quantity', 'wc_free_gift_coupons' );?></th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody></tbody>
</script>
<?php
/**
* The Singular List View
*/
?>
<script type="text/template" id="tmpl-wc-free-gift-product">
<td class="product-title">{{{ data.title }}}</td>
<td class="product-quantity">
<input type="number" name="wc_free_gift_coupons_data[{{{ data.gift_id }}}][quantity]" value="{{{ data.quantity }}}" />
</td>
<td class="product-remove">
<button class="delete-product dashicons-before dashicons-no" href="#" title="<?php esc_attr_e( 'Click to remove', 'wc_free_gift_coupons' );?>"></button>
</td>
</script>
<?php
}
/**
* Save the new coupon metabox field data
*
* @param integer $post_id
* @param object WC_Coupon $coupon
* @return void
* @since 1.0
*/
public static function process_shop_coupon_meta( $post_id, $coupon ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce check handled by WooCommerce core.
if ( isset( $_POST['discount_type'] ) && in_array( $_POST['discount_type'], WC_Free_Gift_Coupons::get_gift_coupon_types(), true ) ) {
// Sanitize gift products.
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing -- Nonce check handled by WooCommerce core.
$gift_data = isset( $_POST['wc_free_gift_coupons_data'] ) ? self::sanitize_free_gift_meta( $_POST['wc_free_gift_coupons_data'] ) : array();
// If discount type is free gift, free gift is important.
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce check handled by WooCommerce core.
if ( 'free_gift' === $_POST['discount_type'] ) {
// Do not allow, a free gift is a must.
if ( empty( $gift_data ) ) {
$notice = __( 'Please select at least one product to give away with this coupon.', 'wc_free_gift_coupons' );
WC_Free_Gift_Coupons_Admin_Notices::add_notice( $notice, 'error', true );
return;
}
}
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing -- Nonce check handled by WooCommerce core.
$synced_products = isset( $_POST['_wc_fgc_product_sync_ids'] ) ? array_filter( array_map( 'intval', (array) $_POST['_wc_fgc_product_sync_ids'] ) ) : array();
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce check handled by WooCommerce core.
$leave_old_synced_products = isset( $_POST['wc_fgc_leave_old_synced_products'] ) ? 'yes' : 'no';
if ( 'yes' === $leave_old_synced_products ) {
$clear_old_synced_prod = false;
} else {
$clear_old_synced_prod = true;
}
// Add the synced product to list of required products. Stealth way of making sure the sync doesn't malfunction!
self::sort_synced_products_for_coupon( $coupon, $synced_products, $clear_old_synced_prod, true );
// Sanitize free shipping option.
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce check handled by WooCommerce core.
$free_gift_shipping = isset( $_POST['wc_free_gift_coupon_free_shipping'] ) ? 'yes' : 'no';
// Save.
$coupon->update_meta_data( '_wc_free_gift_coupon_data', $gift_data );
$coupon->update_meta_data( '_wc_free_gift_coupon_free_shipping', $free_gift_shipping );
$coupon->update_meta_data( '_wc_fgc_product_sync_ids', $synced_products );
$coupon->update_meta_data( '_wc_fgc_leave_old_synced_products', $leave_old_synced_products );
$coupon->save_meta_data();
}
}
/**
* Sorts/Clears old Synced products from Coupon's product id list.
*
* Clears the old synced data is $clear_old_synced_products is true,
* And adds the new set.
*
* @param WC_Coupon $coupon The coupon object.
* @param array $new_synced_products New synced product to be added to product_ids list.
* @param bool $clear_old_synced_products Removes old synced products if true, default is true.
* @param bool $save If true, runs the save method, default is false.
* @return array The flushed array of product_ids
*/
public static function sort_synced_products_for_coupon( $coupon, $new_synced_products, $clear_old_synced_products = true, $save = false ) {
// First get list of products already added, if any.
self::$coupon_product_ids = $coupon->get_product_ids();
// Get old synced product data.
$old_synced_products = $coupon->get_meta( '_wc_fgc_product_sync_ids' );
// Should we clear old data? if no, let's save stress.
if ( true === $clear_old_synced_products ) {
// Check if old synced products are in product ids data.
foreach ( $old_synced_products as $old_data ) {
if ( in_array( $old_data, self::$coupon_product_ids, true ) ) {
// Remove old synced products from product_ids, incase there's a change.
unset( self::$coupon_product_ids[ array_search( $old_data, self::$coupon_product_ids, true ) ] );
}
}
} else {
// Show notice only when new synced product is different from old.
if ( $old_synced_products !== $new_synced_products ) {
// Show notice
$notice = __( 'The previously synced product is still in your required product list.', 'wc_free_gift_coupons' );
WC_Free_Gift_Coupons_Admin_Notices::add_notice(
$notice,
array(
'type' => 'info',
'dismiss_class' => 'yes',
),
true
);
}
}
// Set new, and no need to check for duplicates, WC does that by default :).
$merged = array_merge( self::$coupon_product_ids, $new_synced_products );
$coupon->set_product_ids( $merged );
//exit;
if ( true === $save ) {
$coupon->save();
}
return $coupon->get_product_ids();
}
/**
* Sanitize separately so we can re-use this method for compatibility reasons.
*
* @param array $wc_free_gift_coupons_data - The posted data.
* @return array
* @since 2.1.0
*/
public static function sanitize_free_gift_meta( $wc_free_gift_coupons_data ) {
$gift_data = array();
if ( is_array( $wc_free_gift_coupons_data ) ) {
foreach ( $wc_free_gift_coupons_data as $gift_id => $data ) {
$gift_id = intval( $gift_id );
$_product = wc_get_product( $gift_id );
if ( $_product ) {
$gift_data[$gift_id] =
array(
'product_id' => $_product->get_parent_id() > 0 ? $_product->get_parent_id() : $gift_id,
'variation_id' => $_product->get_parent_id() > 0 ? $gift_id : 0,
'quantity' => isset( $data['quantity'] ) ? intval( $data['quantity'] ) : 1
);
}
}
}
return $gift_data;
}
/**
* Show row meta on the plugin screen.
*
* @param mixed $links
* @param mixed $file
* @return array
*/
public static function plugin_row_meta( $links, $file ) {
if ( $file === WC_FGC_PLUGIN_NAME ) {
$row_meta = array(
'docs' => '<a target="_blank" href="https://docs.woocommerce.com/document/free-gift-coupons/">' . __( 'Documentation', 'wc_free_gift_coupons' ) . '</a>',
'support' => '<a target="_blank" href="' . esc_url( 'https://woocommerce.com/my-account/marketplace-ticket-form/' ) . '">' . __( 'Support', 'wc_free_gift_coupons' ) . '</a>',
);
return array_merge( $links, $row_meta );
}
return $links;
}
} // End class.
WC_Free_Gift_Coupons_Admin::init();
/* global woocommerce_free_gift_coupon_meta_i18n */
/**
* Script for handling the coupon metabox UI.
*
* @type {Object} JavaScript namespace for our application.
*/
var WC_Free_Gift_Coupons = {};
(function($, WC_Free_Gift_Coupons) {
// Model.
WC_Free_Gift_Coupons.Product = Backbone.Model.extend({
defaults: {
"quantity": 1,
"title": "",
"product_id" : "",
"variation_id" : "",
"gift_id" : ""
},
initialize: function() {
if ( ! this.get( "gift_id" ).length ) {
this.set( "gift_id", this.get( "variation_id" ) > 0 ? this.get( "variation_id" ) : this.get( "product_id" ) );
}
},
});
// Collection.
WC_Free_Gift_Coupons.ProductsCollection = Backbone.Collection.extend({
model: WC_Free_Gift_Coupons.Product,
el: "#wc-free-gift-container"
});
// Singular Row View.
WC_Free_Gift_Coupons.productView = Backbone.View.extend({
model: WC_Free_Gift_Coupons.Product,
tagName: 'tr',
events: {
'click .delete-product': 'removeProduct',
'change .product-quantity :input': 'setQuantity'
},
// Get the template from the DOM.
template: wp.template("wc-free-gift-product"),
// Render the single model.
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
// Reorder after sort.
reorder: function(event, index) {
this.$el.trigger('update-sort', [this.model, index]);
},
// Remove/destroy a model.
removeProduct: function(e) {
e.preventDefault();
this.model.destroy();
WC_Free_Gift_Coupons.productList.render(); // Manually calling instead of listening since listening interferes with sorting.
// Disable button or not.
WC_Free_Gift_Coupons.toggleButtonActivity();
},
// Persist the quantity in the model.
setQuantity: function(e) {
value = this.$el.find('.product-quantity :input').val();
this.model.set('quantity', value);
}
});
// List View.
WC_Free_Gift_Coupons.productListTable = Backbone.View.extend({
el: "#wc-free-gift-table",
template: wp.template( 'wc-free-gift-products-table-header'),
addSingle: function(model) {
var view = new WC_Free_Gift_Coupons.productView({
model: model
});
this.$el.find('tbody').prepend(view.render().el);
},
// Callback for SelectWoo section.
addProduct: function( event, attributes) {
Product = new WC_Free_Gift_Coupons.Product(attributes);
this.collection.add(Product, {at: 0} );
this.render();
},
events: {
'click .delete-product': 'removeProduct',
'addFreeGiftProduct': 'addProduct',
},
render: function() {
this.$el.children().remove();
if ( this.collection.length ) {
this.$el.html(this.template());
this.collection.each( function(data) {
this.$el.find('tbody').append(new WC_Free_Gift_Coupons.productView({model : data}).render().el);
}, this);
}
return this;
}
});
// Init.
WC_Free_Gift_Coupons.initApplication = function() {
// Create Collection From Existing Meta.
WC_Free_Gift_Coupons.productCollection = new WC_Free_Gift_Coupons.ProductsCollection(woocommerce_free_gift_coupon_meta_i18n.free_gifts);
// Create the List View.
WC_Free_Gift_Coupons.productList = new WC_Free_Gift_Coupons.productListTable({
collection: WC_Free_Gift_Coupons.productCollection
});
// Render the List View.
WC_Free_Gift_Coupons.productList.render();
};
/**
* Toggle Publish Button enable/disable.
*
* It also checks if free gift products have been added.
*/
WC_Free_Gift_Coupons.toggleButtonActivity = function() {
let $ = jQuery;
let freeGiftsQty = $( '#wc-free-gift-table' ).find( 'tr' ).length;
// Has any product been added?
if ( 'free_gift' === $( 'select#discount_type' ).val() && freeGiftsQty == 0 ) {
$( '#publish' ).attr( 'disabled', 'true' );
}
else {
$( '#publish' ).removeAttr( 'disabled' );
}
};
/*-----------------------------------------------------------------------------------*/
/* Execute the above methods in the WC_Free_Gift_Coupons object.
/*-----------------------------------------------------------------------------------*/
jQuery(document).ready(function($) {
WC_Free_Gift_Coupons.initApplication();
$( '#wc-free-gift-table' ).sortable({
items: "tbody > tr",
handle: ".product-title",
axis: "y",
opacity: 0.5,
grid: [20, 10],
tolerance: "pointer"
});
$( document.body ).trigger( 'wc-enhanced-select-init' );
if ( $( '#free_gift_ids' ).hasClass( 'select2-hidden-accessible' ) ) {
$( '#free_gift_ids' ).on( 'select2:select', function ( e ) {
var data = e.params.data;
if ( data.id.length ) {
var new_gift = {
gift_id: data.id,
title: data.text
}
$( '#wc-free-gift-table' ).trigger( 'addFreeGiftProduct', new_gift );
// Keep values out of enhanced select container.
$( this ).val([]).change();
$('#wc-free-gift-table').sortable('refresh');
// Enable Publish Button.
WC_Free_Gift_Coupons.toggleButtonActivity();
}
});
}
// Toggle coupon type options.
$( 'select#discount_type' ).change(function(){
// Get value.
var select_val = $(this).val();
$toggle_fields = $( '.coupon_amount_field' );
coupon_types = jQuery.parseJSON( woocommerce_free_gift_coupon_meta_i18n.coupon_types );
// Check if coupon type is in supported type list.
if ( $.inArray( select_val, coupon_types ) !== -1 ) {
$( '.show_if_free_gift' ).show();
// Only hide the price field for Free Gift type
if ( 'free_gift' == select_val ) {
$toggle_fields.hide();
} else {
$toggle_fields.show();
}
// Enable/Disable Publish Button.
WC_Free_Gift_Coupons.toggleButtonActivity();
} else {
$( '.show_if_free_gift' ).hide();
$toggle_fields.show();
}
}).change();
});
})(jQuery, WC_Free_Gift_Coupons);
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 ) ) );
}
} );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment