HOW TO : Add products with custom fields and variations from within an article.

Started by WebStuff, November 14, 2012, 19:41:57 PM

Previous topic - Next topic

WebStuff

Firstly, there may be a way to do this that is better, faster or more technically correct but it works for me.
I have only tested this on J2.5.8 and VM2.0.12f, VM2.0.14, VM2.0.18 so your files or results may vary.
This is a simple example of a, variable size, sheet material that can be one of three materials, displayed in a dropdown, and two values, one for width and the other for length. It could be expanded further for other custom fields
It requires a reasonable knowledge of php and javascript and also one core file alteration. As with all changes MAKE A BACKUP before changing anything.


This example uses a simple html form layout to which you can add your own images, description, etc. You may add more than one product per article just use a different form for each one. With this approach you can set up radio buttons or dropdowns or text boxes or anything else you would normally put in a form. Then just do a write to the hidden VM2 inputs of the values of the components in your form. The disadvantage is that you must make sure the options entered are acceptable ranges for numbers and are one of the options on dropdowns to prevent errors/code being added.

There is an extension called JUMI which can be used to embed php files in articles, this allows more control over what's loading. This may prove useful to you if you require database lookups in your code. You include code in your article like so:{jumi [customitem.php]}
It can be downloaded from http://2glux.com/projects/jumi
I am in no way affiliated to JUMI.  :)

First thing to do is to create your Custom Fields.
Virtuemart->Custom Fields->New:
Custom Field Type:  Select Plug-ins from the dropdown
Title: Enter "ITEM" (without the quotes this one is important, it must be this word and capitalised).
Published: set to "Yes"
Parent: leave as "Please Select"
Cart attribute: set to "Yes"
Description: set to "Item" (without the quotes)
Default: set this to some text it doesn't matter what. I use "sd" but any couple of letters will be fine
Tooltip: leave blank
Layout position: leave blank
Admin only: leave set to "No"
Is a list: leave set to "No"
Hidden: set to "No"
Select a plug-in: In the dropdown select "Customer text input"
Title(in Select a plug-in frame): Enter "ITEM" (without the quotes this one is important, it must be this word and capitalised).
Size of input(in Select a plug-in frame): set to maximum length your field will need (some posts say more than 50 doesn't work but I have not tested this)
Price per letter or per input(in Select a plug-in frame): leave at "Price per input"
It should look like picture custom-field-ITEM.jpg below.
Hit Save & Close
Repeat the process for Width, Length and Material substituting "Width" for "ITEM" and "Item" on the first one and "Length" for "ITEM" and "Item" on the second and "Material" for "ITEM" on the third.(Capitalisation does not matter for these two custom fields.)
Hit Save & Close for each of these.

Next thing to do is to create your custom product category.
Virtuemart->Product Categories->New : (See pic custom-items-setup.jpg)
Set Published to "Yes"
Hit Save & Close

Next create your custom item.
Virtuemart->Product->New :(See pic custom-item-products.jpg)
Set Published to "Yes". Set name of product and place it in the custom-items category and select Shopper Groups, set price to "1.00" and set any tax options from the "Pricing rules overrides" dropdown "Tax" (I won't be dealing with discounts at present.)

Next add your custom fields to the item.
Choose "ITEM" from the dropdown
Then choose "Width"
Then choose "Length"
Then choose "Materials"
Click Save & Close and you're done with this section.
Next we take a look at our item.
Go to your Categories Menu and choose your newly created category.
Then choose your Custom Item. You should see a page similar to custom-item-product-details.jpg
right click on this page and choose view source or use Firebug or similar to view the source.
In the source for your page search for this bold text: class="addtocart-area"
This should take you to a div similar to:


<div class="addtocart-area">

    <form method="post" class="product js-recalculate" action="index.php?option=com_virtuemart">
         <div class="product-fields">
              <div class="product-field product-field-type-E">
                  <span class="product-fields-title-wrapper"><span class="product-fields-title"><strong>ITEM</strong></span>
                  </span>
                  <span class="product-field-display">
                      <input class="vmcustom-textinput" type="text" value="" size="49" name="customPlugin[85][textinput][comment]"><br />
<input type="hidden" value="85" name="customPrice[0][3]" /> </span>

                   <span class="product-field-desc">Custom Item</span>
            </div><br/>
            <div class="product-field product-field-type-E">
                <span class="product-fields-title-wrapper"><span class="product-fields-title"><strong>Width</strong></span>
                </span>
                <span class="product-field-display">
                    <input class="vmcustom-textinput" type="text" value="" size="10" name="customPlugin[86][textinput][comment]"><br />
<input type="hidden" value="86" name="customPrice[1][16]" /> </span>

                <span class="product-field-desc">Width</span>
            </div><br/>
            <div class="product-field product-field-type-E">
                <span class="product-fields-title-wrapper"><span class="product-fields-title"><strong>Length</strong></span>
               </span>
              <span class="product-field-display">
                  <input class="vmcustom-textinput" type="text" value="" size="10" name="customPlugin[87][textinput][comment]"><br />
<input type="hidden" value="87" name="customPrice[2][17]" /> </span>

              <span class="product-field-desc">Length</span>
          </div><br/>
      </div>
                       
      <div class="addtocart-bar">

            <!-- <label for="quantity229" class="quantity_box">Quantity: </label> -->
            <span class="quantity-box">
                <input type="text" class="quantity-input js-recalculate" name="quantity[]" value="1"/>
            </span>
            <span class="quantity-controls js-recalculate">
                <input type="button" class="quantity-controls quantity-plus"/>
                <input type="button" class="quantity-controls quantity-minus"/>
            </span>
                               
            <span class="addtocart-button">
                <input type="submit" name="addtocart" class="addtocart-button" value="Add to Cart" title="Add to Cart" />               </span>
                               
            <div class="clear"></div>
        </div>

        <input type="hidden" class="pname" value="Custom Item"/>
        <input type="hidden" name="option" value="com_virtuemart"/>
        <input type="hidden" name="view" value="cart"/>
        <noscript><input type="hidden" name="task" value="add"/></noscript>
        <input type="hidden" name="virtuemart_product_id[]" value="229"/>
    </form>

    <div class="clear"></div>
</div>

Copy everything from the <form method="post" class="product js-recalculate" action="index.php?option=com_virtuemart"> to the closing </form>(including these tags) from your source not the one here and paste it to a file for use later.
Now you MUST unpublish your category or people will be able to get to the 1.00 priced version.
Now create your Article
Content->Article Manager->New Article
Create your article as you would normally, entering title and so on.
And for the article text cut and paste the following code to get you going.
We need to add id="quantity" to the quantity[] field, two onclick methods to handle the plus and minus buttons and a onclick method to the add to cart button.

// Simple Custom Fields with calculation code example.
// This style sheet is for the Add to Cart button styling.
<link rel="stylesheet" href="components/com_virtuemart/assets/css/vmsite-ltr.css" type="text/css" />
<script type="text/javascript">
// This is just a function to check if the input is a number. :)
function IsNumeric(sText)
{
sText = sText.trim();
if ( sText === "" ) {
return true;
}
var ValidChars = "0123456789.";
var IsNumber=true;
var Char;

for (i = 0; i < sText.length && IsNumber == true; i++) {
Char = sText.charAt(i);
if (ValidChars.indexOf(Char) == -1) {
IsNumber = false;
}
}
return IsNumber;
}

// We need to add functions to handle the plus and minus quantity buttons
function plus_click() {
var quantity = document.getElementById("quantity");
var Qtt = parseInt(quantity.value);
if (!isNaN(Qtt)) {
quantity.value=(Qtt + 1);
}
}
function minus_click() {
var quantity = document.getElementById("quantity");
var Qtt = parseInt(quantity.value);
if (!isNaN(Qtt) && Qtt>1) {
quantity.value=(Qtt - 1);
} else quantity.value=(1);
}

// This next section is for pushing the input values to the VM2 hidden fields.
function material_change() {
document.getElementById("material_send").value = document.getElementById("material_select").value;
}
function width_change() {
document.getElementById("width_send").value = document.getElementById("width_input").value;
}
function length_change() {
document.getElementById("length_send").value = document.getElementById("length_input").value;
}

// This is called by all functions to make sure price and attributes are updated
// Some validation is also done in this function. It is up to you to check your input values are the correct format and within valid ranges.
function item_change() {
var area = 0;
var endPrice = 0.00;
var materialCost = 0.00;
var materialType = document.getElementById("material_select").value;
if ( IsNumeric( document.getElementById("width_input").value ) && IsNumeric( document.getElementById("length_input").value ) ) {
area = (document.getElementById("width_input").value * document.getElementById("length_input").value) / 1000.00;
if ( materialType == "Wood" ) {
materialCost = 0.10 * area;
}
else if ( materialType == "Aluminium" ) {
materialCost = 0.45 * area;
}
else if ( materialType == "Steel" ) {
materialCost = 0.20 * area;
}
material_change();
width_change();
length_change();
document.getElementById("ITEM_send").value = materialCost.toFixed(2);
document.getElementById("final_price").innerHTML = materialCost.toFixed(2).toString();
}
else {
alert("Please input a number");
document.getElementById("width_input").value = 1000;
document.getElementById("length_input").value = 1000;

}
}

</script>
<!--// This is the visible section to the user for entering their values //-->
Material:<br />
<select id="material_select" onchange="item_change();">
<option value="Wood">Wood</option>
<option value="Aluminium">Aluminium</option>
<option value="Steel">Steel</option>
</select>
<br />
Width (in mm):<br />
<input type="text" value="1000" id="width_input" onchange="item_change()" />
<br />
Length (in mm):<br />
<input type="text" value="1000" id="length_input" onchange="item_change()" />
<br />

Calculated Price : &pound;<span class="null" id="final_price">100.00</span>

<!--// This is the VM2 add to cart section with the hidden custom fields //-->
<form method="post" class="product js-recalculate" action="index.php?option=com_virtuemart">

<!--// These are the actual custom fields stripped down and converted to <input>'s and hidden. Anything with a class of "vmcustom-textinput" MUST have it's value set here to default values so the initial displayed fields above match the values here and in case some one clicks the add to cart straight away.  //-->
<input class="vmcustom-textinput" type="hidden" value="100.00" size="49" name="customPlugin[AA][textinput][comment]" id="ITEM_send"><br />
<input type="hidden" value="AA" name="customPrice[X][[Y]" />
<input class="vmcustom-textinput" type="hidden" value="1000" size="10" name="customPlugin[BB][textinput][comment]" id="width_send"><br />
<input type="hidden" value="BB" name="customPrice[X][Y]" />
<input class="vmcustom-textinput" type="hidden" value="1000" size="10" name="customPlugin[DD][textinput][comment]" id="length_send"><br />
<input type="hidden" value="CC" name="customPrice[X][Y]" />
<input class="vmcustom-textinput" type="hidden" value="Wood" size="20" name="customPlugin[DD][textinput][comment]" id="material_send"><br />
<input type="hidden" value="DD" name="customPrice[X][Y]" />

<!--// This is the add to cart button //-->
<div class="addtocart-bar">
<span class="quantity-box">
<input type="text" class="quantity-input js-recalculate" name="quantity[]" value="1" [color=green][b]id="quantity"[/b][/color]/>
</span>
<span class="quantity-controls js-recalculate">
<input type="button" class="quantity-controls quantity-plus" onclick="plus_click();"/>
<input type="button" class="quantity-controls quantity-minus" onclick="minus_click();"/>
</span>

<span class="addtocart-button">
<input type="submit" name="addtocart" class="addtocart-button" value="Add to Cart" title="Add to Cart" onclick="item_change();"/>
</span>
<div class="clear"></div>
</div>

<input type="hidden" class="pname" value="Custom Item"/>
<input type="hidden" name="option" value="com_virtuemart"/>
<input type="hidden" name="view" value="cart"/>
<input type="hidden" name="task" value="add"/><!--// This is normally in a <noscript> tag for the popup to work but I haven't done this yet //-->
<input type="hidden" name="virtuemart_product_id[]" value="2000"/>
</form>


You will need to change the customPlugin[AA], customPlugin[BB], customPlugin[CC], customPlugin[DD] and all customPrice[X][[Y] to match the numbers from the text you cut and pasted from the source view of the page earlier.
You will also need to change the value of <input> virtuemart_product_id from 2000 to the id of your own custom item.

i.e. If ITEM in the saved source code is customPlugin[84] you would change the ITEM_send's customPlugin[AA] to that and the <input> below to have a value of 84 and the customPrice[X][Y] to the corresponding value in your source e.g. customPrice[0][2]. The first (X) number is generally the position of the field in the list and the second (Y) number is the custom fields id.

Once this is done you are nearly ready to go, now we just need the core file change.
Please see next Post for final instructions.

Malac.


[attachment cleanup by admin]

WebStuff

Part 2:
Navigate to administrator/components/com_virtuemart/helpers/calculationh.php
and open it for editing.
Search for : public function getProductPrices($product
Follow the function down until you finde :// vmdebug('getProductPrices',$this->productPrices);
return $this->productPrices;
}


Add this:
// ADDED CODE FOR Custom Item CALCULATIONS
include( 'customitem.php' );
// END ADDED CODE

You need  to comment out this when you add further custom items as it will not display them in the standard product view with this code in place. So you won't be able to get the field definitions to cut and paste. Just re-instate it once you are done.
That's it this should minimise upgrade shock meaning you only need to replace that tiny bit of code each update.

Next in the same directory create an empty file called customitem.php and open it for editing.
Paste the following code into it.:<?php
defined
('_JEXEC') or die();
//
// include file for calculationh.php to allow for custom items in articles
//

// DEBUGGING
// Uncomment the "print_r" line under this comment to print out the $product object in full.
// This will allow you to see the variable names that you may need to call for more complex calculations.
// You may access properties like so: $my_variable_name = $product->virtuemart_calc_id.
//print_r($product->customPrices[0]); echo "<br />";
//var_dump($product->customPlugin); echo "<br />";
// END DEBUGGING

$this_category_id $product->virtuemart_category_id;
$this_product_id $product->virtuemart_product_id;
//print_r($this);
// DEBUGGING
// Depending on how you work out tax you may need these values as well. Uncomment the following lines to display the various tax arrays.
//print_r($this->rules['Tax']);
//print_r($this->rules['VatTax']);
//print_r($this->rules['DBTax']);
//print_r($this->rules['DATax']);
// END DEBUGGING

// Change the 'this_category_id' number to the category id of your custom items so this code is only executed for those products.
// Alternatively you can execute the code for products using the $this_product_id variable instead.
// e.g. change the 'if' statement on the next line to :"    if ($this_product_id == 28) {  "
// You may use multiple numbers using php || (or) in the if statement. e.g.:  if ($this_product_id == 28 || $this_product_id == 29 || $this_product_id == 30) {
if ($this_category_id == 6) {
// DEBUGGING
// Uncomment this next line to see an array of your custom attributes for debug purposes.
// print_r( json_decode($product->customPlugin) );
//echo  "<br />";
// END DEBUGGING
// Change the numbers 0 and 3 to match your ITEM customPrices[x][y] values
$index $product->customPrices[0][3];
$custom_attributes json_decode($product->customPlugin);
$custom_priceBeforeTax $custom_attributes->$index->textinput->comment;
$this->productPrices['priceBeforeTax'] = $custom_priceBeforeTax;
// Change the 'VatTax' to 'Tax' in the next line if you use those rules.
$tax_rate = (float)$this->rules['VatTax'][0]['calc_value'];
//$discountBeforeTax = (float)$this->rules['DBTax'][0]['calc_value'];
//$discountAfterTax = (float)$this->rules['DATax'][0]['calc_value'];
// DEBUGGING
//echo "tax_rate : ".($tax_rate). "<br />";
//echo "discountBeforeTax : ".($discountBeforeTax). "<br />";
//echo "discountAfterTax : ".($discountAfterTax). "<br />";
//echo $product->product_discount_id."<br />";
//echo $product->virtuemart_shoppergroup_id."<br />";
// END DEBUGGING

$db JFactory::getDbo();
$user JFactory::getUser();
$user1 = ($user->get('id'));
$query 'SELECT `virtuemart_shoppergroup_id` FROM `#__virtuemart_vmuser_shoppergroups` WHERE `virtuemart_user_id` =' .$user1 ;
$db->setQuery($query010);
$result1 $db->loadResult();
$query 'SELECT `virtuemart_calc_id` FROM `#__virtuemart_calc_shoppergroups` AS `sg` WHERE `sg`.`virtuemart_shoppergroup_id` = "'.$result1.'"';
$db->setQuery($query);
$result2 $db->loadResult();
$query 'SELECT `calc_value` FROM `#__virtuemart_calcs` AS `calcs` WHERE `calcs`.`virtuemart_calc_id` = "'.$result2.'"';
$db->setQuery($query);
$discount $db->loadResult();
// There is a Shopper Group Id in the '$product->virtuemart_shoppergroup_id' variable
// and a Discount Id in the '$product->product_discount_id' variable.
$this->productPrices['basePrice'] = $this->productPrices['priceBeforeTax'];
$this->productPrices['taxAmount'] = $this->productPrices['priceBeforeTax']*($tax_rate/100.0000);
$this->productPrices['basePriceWithTax'] = $this->productPrices['basePrice']+$this->productPrices['taxAmount'];
$discountAmount $this->productPrices['basePriceWithTax'] * ($discount/100);
$this->productPrices['salesPriceWithDiscount'] = $this->productPrices['basePriceWithTax']-$discountAmount;
if ($discountAmount 0) {
$this->productPrices['taxAmount'] = $this->productPrices['taxAmount']-($this->productPrices['taxAmount']*($tax_rate/100.0000));
}
$this->productPrices['salesPrice'] = $this->productPrices['salesPriceWithDiscount'];
// DEBUGGING
// Uncomment the next line to show the productPrices array. So you can check values such as discount, etc.
//echo "HERE :"; foreach( $this->productPrices as $key => $item ){ echo $key ." ". $item; echo "<br />";}
// END DEBUGGING
}
// end tag as it's an included file
?>

Makes sure you change the category_id to yours.
I have tried to comment the code to explain what each section does. If something doesn't work please re-read the commented sections and use the //DEBUGGING options before posting.
Save the File and make sure it is readable by the web user.
Now create a link to your Article and access it.

Change some values and then add it to cart.

That's it.
I hope all is explained reasonably clearly, I have tried to comment all the code examples where I could and there are some sections with print_r or echo statements under "DEBUGGING" sections for troubleshooting.
Hope this helps someone any problems or feedback please feel free to post.
Thanks, Malac.

[attachment cleanup by admin]