Handle WooCommerce selected variation custom field conditional display - javascript

I have this code where I need to insert a value based on a condition in: **///////// HERE THE MY CODE /////////**
Here I have overridden single-product/add-to-cart/variation.php Woocommerce template file via my active theme:
<script type="text/template" id="tmpl-variation-template">
<div class="woocommerce-variation-description">
{{{ data.variation.variation_description }}}
</div>
<div class="woocommerce-variation-price">
{{{ data.variation.price_html }}}
</div>
<div class="woocommerce-variation-custom_field">
{{{ data.variation.custom_field}}}
///////// HERE MY CODE /////////
</div>
<div class="woocommerce-variation-availability">
{{{ data.variation.availability_html }}}
</div>
</script>
The condition should check the value of the variable `{{{ data.variation.custom_field}}}` and if this data is greater than 10 then the code should print "Yes".
**Something like**:
if( $data.variation.custom_field > 10 ){
echo "yes";
}
But it's not working. I guess, this should be done using Javascript instead of php but I don't know how grab the variable value.

There is no need to use additional javascript (or jQuery) code for that.
The following will handle a product variation custom field displaying "YES" if the custom field value is bigger than 10 (otherwise nothing).
You will need to replace your exiting hooked function that use woocommerce_available_variation hook, with one of the following ways.
There are mainly 2 ways:
1). The simplest way, without overriding variation.php template:
// Frontend: Handle Conditional display and include custom field value on product variation
add_filter( 'woocommerce_available_variation', 'variation_data_custom_field_conditional_display', 10, 3 );
function variation_data_custom_field_conditional_display( $data, $product, $variation ) {
// Get custom field value and set it in the variation data array (not for display)
$data['custom_field'] = $variation->get_meta('custom_field');
// Defined custom field conditional display
$displayed_value = $data['custom_field'] > 10 ? 'YES' : '';
// Frontend variation: Display value below formatted price
$data['price_html'] .= '</div>' . $displayed_value . '
<div class="woocommerce-variation-custom_field_html">';
return $data;
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
2). Another simple way (overriding variation.php template):
// Frontend: Handle Conditional display and include custom field value on product variation
add_filter( 'woocommerce_available_variation', 'variation_data_custom_field_conditional_display', 10, 3 );
function variation_data_custom_field_conditional_display( $data, $product, $variation ) {
// Get custom field value and set it in the variation data array (not for display)
$data['custom_field'] = $variation->get_meta('custom_field');
// Frontend display: Define custom field conditional display
$data['custom_field_html'] = $data['custom_field'] > 10 ? "YES" : "";
return $data;
}
Code goes in functions.php file of the active child theme (or active theme).
Then in your custom template single-product/add-to-cart/variation.php you will replace:
{{{ data.variation.custom_field}}}
with:
{{{ data.variation.custom_field_html }}}
It will work nicely without any additional requirements.
Here is a complete code example for the community, based on the 2nd Way:
1). Admin variations: Display a custom field and save it's value
// Admin: Add a custom field in product variations options pricing
add_action( 'woocommerce_variation_options_pricing', 'add_admin_variation_custom_field', 10, 3 );
function add_admin_variation_custom_field( $loop, $variation_data, $variation ){
woocommerce_wp_text_input( array(
'id' => 'custom_field['.$loop.']',
'label' => __('Custom Field', 'woocommerce' ),
'placeholder' => __('Enter Custom Field value here', 'woocommerce' ),
'desc_tip' => true,
'description' => __('This field is for … (explanation / description).', 'woocommerce' ),
'value' => get_post_meta( $variation->ID, 'custom_field', true )
) );
}
// Admin: Save custom field value from product variations options pricing
add_action( 'woocommerce_save_product_variation', 'save_admin_variation_custom_field', 10, 2 );
function save_admin_variation_custom_field( $variation_id, $i ){
if( isset($_POST['custom_field'][$i]) ){
update_post_meta( $variation_id, 'custom_field', sanitize_text_field($_POST['custom_field'][$i]) );
}
}
Code goes in functions.php file of the active child theme (or active theme).
2). Frontend variations: Conditional display based on selected variation and custom field value
// Frontend: Handle Conditional display and include custom field value on product variation
add_filter( 'woocommerce_available_variation', 'variation_data_custom_field_conditional_display', 10, 3 );
function variation_data_custom_field_conditional_display( $data, $product, $variation ) {
// Get custom field value and set it in the variation data array (not for display)
$data['custom_field'] = $variation->get_meta('custom_field');
// Frontend display: Define custom field conditional display
$data['custom_field_html'] = $data['custom_field'] > 10 ? __("YES", "woocommerce") : "";
return $data;
}
Code goes in functions.php file of the active child theme (or active theme).
3). Template override: single-product/add-to-cart/variation.php file to your active theme's:
<?php
/**
* Single variation display
*
* This is a javascript-based template for single variations (see https://codex.wordpress.org/Javascript_Reference/wp.template).
* The values will be dynamically replaced after selecting attributes.
*
* #see https://docs.woocommerce.com/document/template-structure/
* #package WooCommerce/Templates
* #version 2.5.0
*/
defined( 'ABSPATH' ) || exit;
?>
<script type="text/template" id="tmpl-variation-template">
<div class="woocommerce-variation-description">{{{ data.variation.variation_description }}}</div>
<div class="woocommerce-variation-price">{{{ data.variation.price_html }}}</div>
<div class="woocommerce-variation-custom_field">{{{ data.variation.custom_field_html}}}</div>
<div class="woocommerce-variation-availability">{{{ data.variation.availability_html }}}</div>
</script>
<script type="text/template" id="tmpl-unavailable-variation-template">
<p><?php esc_html_e( 'Sorry, this product is unavailable. Please choose a different combination.', 'woocommerce' ); ?></p>
</script>
Tested and works.
Related: WooCommerce: Add/display Product or Variation custom field everywhere

Based on https://codex.wordpress.org/Javascript_Reference/wp.template and similar template engine like https://github.com/blueimp/JavaScript-Templates#evaluation, you need to build a template with evaluation.
In your case, it should be something like:
<div class="woocommerce-variation-custom_field">
<# if (data.variation.custom_field > 10) { #>
yes
<# } #>
</div>
Also, here https://lkwdwrd.com/wp-template-js-templates-wp you can find an example with if statement itself.

Related

Dynamic Block - How to create dynamic stylesheet on post save / load

I've created a working Gutenberg Block with Create Guten Block (https://github.com/ahmadawais/create-guten-block).
Currently it's only working with inline-styles, but as a requirement I have to avoid them.
Therefore I want to create a post/page stylesheet when the post is saved including the style settings for my blocks (for example background-color, color, font-size...)
My block's current save function (block.js)
save: function( props ) {
const { attributes: { typetext, infotext, linktext, background_color, background_button_color, text_color, text_color_button }} = props;
return (
<div id="cgb-infoblock" className="cgb-infoblock">
<div className="cgb-infoblock-body" style={{
backgroundColor: background_color,
color: text_color,
}}>
<div className="cgb-infoblock-type">
<p>
<span className="cgb-infoblock-icon"><i>i</i></span>
{ typetext && !! typetext.length && (
<RichText.Content
tagName="span"
className={ classnames(
'cgb-infoblock-type-text'
) }
style={ {
color: text_color
} }
value={ typetext }
/>
)}
</p>
</div>
<div className="cgb-infoblock-text">
{ infotext && !! infotext.length && (
<RichText.Content
tagName="p"
style={ {
color: text_color
} }
value={ infotext }
/>
)}
</div>
</div>
<div className="cgb-infoblock-button" style={{
backgroundColor: background_button_color,
color: text_color_button,
}}>
{ linktext && !! linktext.length && (
<RichText.Content
tagName="p"
style={ {
color: text_color_button
} }
value={ linktext }
/>
)}
</div>
</div>
);
},
The best solution would be some sort of stylesheet generation for a whole page/post with all settings from all blocks.
Best way would be if the stylesheet generation is happening on page save, but it would be also ok if it is happening on page-load. Since those posts are not going to be large, the performance shouldn't be that much of a problem.
So after digging around I've figured it out myself.
Just in case someone else got this problem here's the solution:
First of all, attributes have to be defined in registerBlockTypefunction
registerBlockType( 'cgb/your-block-type', {
title: __( 'Your Block Name' ),
icon: 'shield',
category: 'maybe-a-category',
keywords: [
__( 'some keywords' ),
],
attributes: {
background_color: {
type: 'string',
default: 'default' //we will use the "default"-value later
},
},
So now Wordpress knows which attributes you wanna save. Problem now, as long as the "default" value is not overwritten, Wordpress won't save the value into the block object's attributes.
To solve this we'll use the save function from registerBlockType.
(Quick note on this: This will not trigger the default value for the editor widget, so you allways have to change your background_color's value to see it the first time you insert your widget into gutenberg editor. To fix this, use saveDefaultValue(this.props) right at the beginning of your render() function.)
save: function( props ) {
saveDefaultValues(props);
const { attributes: {background_color}} = props;
return (
//... here's your html that's beeing saved
);
},
function saveDefaultValues(props) {
if(props.attributes.background_color === 'default'){
props.attributes.background_color = '#f1f6fb';
}
}
With this we are forcing wordpress to save our default value. Pretty sure there's a better solution for this, but since I just started with react / Gutenberg, this is the only thing that got it working for me.
Ok, now we can save our Attributes into the Block-object.
Now we wanna create our dynamic stylesheet.
For this we are creating a new .php file in the following directory /plugin-dir/src/since we are using create-guten-block. The name doesn't matter, but I named it the same way like my stylesheet. `gutenberg-styles.css.php``
The gutenberg-styles.css.php will later create a gutenberg-styles.cssfile, everytime someone is visiting the post. But first we are looking into the plugin.phpfile.
Add the following code:
function create_dynamic_gutenberg_stylesheet() {
global $post;
require_once plugin_dir_path( __FILE__ ) . 'src/gutenberg-styles.css.php';
wp_enqueue_style('cgb/gutenberg-styles', plugins_url( 'src/gutenberg-styles.css', __FILE__ ));
}
add_action('wp_head', 'create_dynamic_gutenberg_stylesheet', 5, 0);
This code accesses the global $post variable, we need it to get all the gutenberg-blocks from the current visited post.
After that we require our own gutenberg-styles.css.php which will automatically create our stylesheet, which will be enqueued in the next line.
Now hook it up to wp_head(you could probably hook it up to wordpress save action as well, but you will have to do more work for enqueuing the stylesheet)
Finally a look into our gutenberg-styles.css.php:
$styleSheetPath = plugin_dir_path( __FILE__ ) . 'gutenberg-styles.css';
$styleSheet = '';
$blocks = parse_blocks($post->post_content);
//loop over all blocks and create styles
foreach($blocks as $block) {
$blockType = $block['blockName'];
$blockAttributes = $block['attrs']; //these are the attributes we've forced to saved in our block's save function
//switch case so you can target different blocks
switch ($blockType) {
case 'cgb/your-block-type':
$styleSheet .= '.your-block-class {'.PHP_EOL
$styleSheet .= 'background-color: '.$blockAttributes['background_color'].';'.PHP_EOL
$styleSheet .= '}'.PHP_EOL
break;
}
}
file_put_contents($styleSheetPath, $styleSheet); //write css styles to stylesheet (creates file if it not exists)
I've added PHP_EOL at each line to generate line breaks, you don't have to do this.
But now you can visit a page with your custom block and will see the gutenberg-styles.css is loaded and applied to your blocks.

How to auto-populate sub-field selects from within a parent field

I'm using the ACF tutorial here to build from.
What I'd like to do is use the values in a text sub-field to populate other select sub-fields within the same repeater field. I know it sounds recursive and maybe that's prohibitive. The field admin will not be to ajax-y or update on the fly, it's more of an admin field for other site functionality.
Anyway, here's what I have so far.
ACF Repeater field = core_values
Page the field is on = valuesadmin
Source text sub-field within core_values = value_name
Target sub-fields (
each needing dynamically propagated selects from value_name) =
constructor1_name
constructor2_name
constructor3_name
destructor1_name
destructor2_name
I've tried to modify the code at the tutorial linked above and put it in the theme's functions.php and in the main file of a plugin I'm building other custom functions.
/**
* ACF population functions
*/
function acf_load_core_values_field_choices( $field ) {
// reset choices
$field['choices'] = array();
// if has rows
if( have_rows('core_values', 'valuesadmin') ) {
// while has rows
while( have_rows('core_values', 'valuesadmin') ) {
// instantiate row
the_row();
// vars
$value = get_sub_field('value_name');
$label = get_sub_field('value_name');
// append to choices
$field['constructor1_name'][ $value ] = $label;
$field['constructor2_name'][ $value ] = $label;
$field['constructor3_name'][ $value ] = $label;
$field['destructor1_name'][ $value ] = $label;
$field['destructor2_name'][ $value ] = $label;
}
}
// return the field
return $field;
}
add_filter('acf/load_field/name=constructor1_name', 'acf_load_core_values_field_choices');
add_filter('acf/load_field/name=constructor2_name', 'acf_load_core_values_field_choices');
add_filter('acf/load_field/name=constructor3_name', 'acf_load_core_values_field_choices');
add_filter('acf/load_field/name=destructor1_name', 'acf_load_core_values_field_choices');
add_filter('acf/load_field/name=destructor2_name', 'acf_load_core_values_field_choices');
Obviously this isn't propagating the select sub-fields as I'd like.
Questions:
- Is this even possible ( the value_name fields are all filled with values already )
- Where should the function code go?
- Maybe I've mangled the code somehow
Thanks in advance!
Well, I achieved the functionality I was looking for by first moving this all to an ACF options page and then creating another ACF field ( values_master) with which I could populate the values dynamically in a second field on the options page. So I'm not sure if this was not working because of some recursively but it IS working.
function acf_load_value_field_choices( $field ) {
// reset choices
$field['choices'] = array();
// if has rows
if( have_rows('values_master', 'option') ) {
// while has rows
while( have_rows('values_master', 'option') ) {
// instantiate row
the_row();
// vars
$value = get_sub_field('value_name');
$label = get_sub_field('value_name');
// append to choices
$field['choices'][ $value ] = $label;
}
}
// return the field
return $field;
}
add_filter('acf/load_field/name=constructor1_name', 'acf_load_value_field_choices');
add_filter('acf/load_field/name=constructor2_name', 'acf_load_value_field_choices');
add_filter('acf/load_field/name=constructor3_name', 'acf_load_value_field_choices');
add_filter('acf/load_field/name=destructor1_name', 'acf_load_value_field_choices');
add_filter('acf/load_field/name=destructor2_name', 'acf_load_value_field_choices');
add_filter('acf/load_field/name=value_mstr_name', 'acf_load_value_field_choices');

Woocommerce Mini Cart Widget product price override

is it possible to change product price in Woocommerce Mini Cart Widget? I've overrided price in cart using tips from WooCommerce: Add product to cart with price override? but it only works for cart page. Prices in widget are not changed.
Instead of using "woocommerce_before_calculate_totals" you can use "woocommerce_cart_item_price" filter, some thing like
add_filter('woocommerce_cart_item_price','modify_cart_product_price',10,3);
function modify_cart_product_price( $price, $cart_item, $cart_item_key){
$price = wc_price($custom_price, 4);
return $price;
}
This is how I set price to 0 for free products:
function set_free_items_price( $price, $cart_item, $cart_item_key ) {
$returned_item_price = $cart_item['data']->get_price();
if (isset($cart_item['free_item']['value'])
&& $cart_item['free_item']['value'] == true) {
$returned_item_price = 0;
}
return get_woocommerce_currency_symbol() . $returned_item_price;
}
I hooked inside class, so it hook looks like this:
add_filter( 'set_free_items_price', array(__CLASS__, 'woocommerce_cart_item_price_filter'), 10, 3 );
But if you are using it as procedural function, your hook should look like this:
add_filter( 'set_free_items_price', 'woocommerce_cart_item_price_filter', 10, 3 );
Keep in mind this is also affecting price row in regular cart.
And, if you are not seeing changes in mini cart, try to update cart on regular cart page, and the go back and check again if mini-cart values has been changed.
To change the price on the WooCommerce Mini Cart Widget you have to use this filter hook: woocommerce_widget_cart_item_quantity
You can check the line 63 of the file: woocommerce/templates/cart/mini-cart.php to see how the filter hook is created.
apply_filters( 'woocommerce_widget_cart_item_quantity', '<span class="quantity">' . sprintf( '%s × %s', $cart_item['quantity'], $product_price ) . '</span>', $cart_item, $cart_item_key );
As the name of the filter hook indicates it can be used not only for the price but also to show the quantity.
For example you can use it to apply a discount to certain products based on a condition. In this case I used a value stored in the meta data of the product.
add_filter('woocommerce_widget_cart_item_quantity', 'custom_wc_widget_cart_item_quantity', 10, 3 );
function custom_wc_widget_cart_item_quantity( $output, $cart_item, $cart_item_key ) {
$product_id = $cart_item['product_id'];
// Use your own way to decide which product's price should be changed
// In this case I used a custom meta field to apply a discount
$special_discount = get_post_meta( $product_id, 'special_discount', true );
if ($special_discount) {
$price = $cart_item['data']->get_price();
$final_price = $price - ( $price * $special_discount );
// The final string with the quantity and price with the discount applied
return sprintf( '<span class="quantity">%s × <span class="woocommerce-Price-amount amount">%s <span class="woocommerce-Price-currencySymbol">%s</span></span></span>', $cart_item['quantity'], $final_price, get_woocommerce_currency_symbol() );
} else {
// For the products without discount nothing is done and the initial string remains unchanged
return $output;
}
}
Note that this will only change how price is displayed, if you have to change it internally use also this action hook: woocommerce_before_calculate_totals

Yii2: Change Gridviews' DataProvider on button click

I have 3 seperate dataProviders for my Gridview, one with Saved data, one with Unsaved data and one with both.
Now this is what I'm trying to accomplish:
If you click on saved, the dataProvider changes to the one with saved data.
I'm trying it like this:
<?php
if($i == 1){
$dataProvider = $dataProviderSaved;
} elseif($i == 2) {
$dataProvider = $dataProviderNotsaved;
} else {
$dataProvider = $dataProviderBoth;
};
\yii\widgets\Pjax::begin(['id' => 'gridview', 'timeout' => false,
'enablePushState' => false, 'clientOptions' => ['method' => 'POST']]) ?>
<?= GridView::widget([
'dataProvider' => $dataProvider,
//regular gridview..
\yii\widgets\Pjax::end(); ?>
Javascript:
var i = $i;
$("#saved").click(function(){
i=1;
$.pjax.defaults.timeout = false;//IMPORTANT
$.pjax.reload({container:"#gridview"});
});
', \yii\web\View::POS_READY);
So, I've just read that changing PHP variables inside JS is 'impossible'.
How would I accomplish this?
Is there a better way?
Do I need 3
DataProviders? (This means 3 find()'s inside of the controller)
If I understood properly you don't need 3 dataProviders. You should use GridView's FilterSelector option to treat that external element as part of GridView's filter.
For example
echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'filterSelector' => "input[name='ModelSearch[_selection]'],",
...
Then, in your Filter Model you filter depending on that value
switch($this->_selection) {
case 'Saved':
$query->andFilterWhere([...]);
break;
case 'Unsaved':
$query->andFilterWhere([...]);
break;
case 'Both':
$query->andFilterWhere([...]);
break;
Don't forget to add the _selection attribute to your Model class and to rules() as 'safe' in the Search Model class.
You can try in two ways:
The first the simpler you assign to each button (saved, unsaved, both) the call of three separate cation of your controller
that invoke each a respective view connected to a single gridview each of these latter with the appropriate dataprovider
The second consists of controller you have the three dataprovider different as this example
return $this->render('viewYourView', [
'/modelContribuente' =>$modelContribuente,
'dataProviderOne' => $providerOne,
'dataProviderTwo' => $providerTwo,
'dataProviderThree' => $providerThree,
]);(
In a single View you can create your three gridview everyone who uses the dataprovider appropriate and then visalizzare or hide gridviewn with JQuery functions controlled by buttons

give two function in one button in Yii framework

I have a question about Yii framework, i have problem with submit button, i want to given two fungsi save and update in one submit button, can anyone tell me how to set that function on form ?
<div class="row buttons">
<?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>
</div>
i change 'Save' with 'Update' it's still have error Primary key added, how i can create two function update and save in one push button ?
public function actionCreate()
{
$model=new TblUasUts;
// Uncomment the following line if AJAX validation is needed
// $this->performAjaxValidation($model);
if(isset($_POST['TblUasUts']))
{
$model->attributes=$_POST['TblUasUts'];
if($model->save())
$this->redirect(array('view','id'=>$model->nim_mhs));
}
if(isset($_POST['TblUasUts'])
{
$model->attributes=$_POST['TblUasUts'];
if($model->update())
$this->redirect(array('view','id'=>$model->nim_mhs));
}
$this->render('update',array(
'model'=>$model,
));
}
In your form, you can use something like :
<div class="row buttons">
<?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Update'); ?>
</div>
As far as processing different actions on the backend code, there are a few options, for example, you could :-
Direct your form to different URLs
Set a (hidden) field (for example ID) and parse for that.
Use the default action from the activeForm, which directs back to the invoking action, for example actionCreate(), or actionUpdate()
In light of your updates, please extend your controller as per my initial suggestion to have another action actionUpdate()
The main difference between the actionCreate(), or actionUpdate() actions is that the Create action create a new (empty) TblUasUts object, while the Update action populates the TblUasUts object from the database.
public function actionCreate()
{
$model=new TblUasUts;
...
... Do things with $model ...
...
$model->save();
}
public function actionUpdate
{
// The id of the existing entry is passed in the url. for example
// ...http:// .... /update/id/10
//
$model = TblUasUts::model()->findByPK($_GET['id']);
...
... Do things with $model ...
...
$model->save();
}

Categories