1

I have a visualforce page that we use for product selection on our opportunities. I've updated it recently with a couple of Jquery functions that allow users to edit the sales price and auto-calculate the discount and vice versa. It works great if there is only one product added, but once I add more than a single product the calculations goes wonky -- I assume because the code does not knwo which row to update. Can anyone help me sort it out?

Screen Shots:

1 Product 1 Product 2 Products 2 Products

    <script type='text/javascript'>

        function calculateDiscount(){
            var discount= 100*($('[id$=dp]').text() - $('[id$=sp]').val())/$('[id$=dp]').text();
            var discountRounded = discount.toFixed(1);
            $('[id$=disc]').val(discountRounded);
        }        

        function calculateSalesPrice(){
            var salesPrice= $('[id$=dp]').text() - ($('[id$=dp]').text() * $('[id$=disc]').val())/100;
            var salesPriceRounded = salesPrice.toFixed(2);
            $('[id$=sp]').val(salesPriceRounded);
        }             
</script>


<apex:form >
        <apex:pageBlock title="Selected {!$ObjectType.Product2.LabelPlural}" id="selected">

            <apex:pageblockTable value="{!shoppingCart}" var="s">

                <apex:column >
                    <apex:commandLink value="Remove" action="{!removeFromShoppingCart}" reRender="selected,searchResults" immediate="true">
                        <!-- this param is how we send an argument to the controller, so it knows which row we clicked 'remove' on -->
                        <apex:param value="{!s.PriceBookEntryId}" assignTo="{!toUnselect}" name="toUnselect"/>
                    </apex:commandLink>
                </apex:column>

                <!--********** Product Name **********-->
                <apex:column headerValue="{!$ObjectType.Product2.LabelPlural}" value="{!s.PriceBookEntry.Product2.Name}"/>

                <!--********** Product SKU **********-->
                <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.SKU__c.Label}" value="{!s.PriceBookEntry.Product2.SKU__c}"/> 

                <!--********** Annual Product Term **********-->
                <apex:column headerValue="Annual Term" id="pt" rendered="false" value="{!s.PriceBookEntry.Product2.Product_Term_Years__c}"/>                         

                <!--********** Quantity **********-->
                <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Quantity.Label}">
                    <apex:inputField value="{!s.Quantity}" id="quant" style="width:70px" required="true">
                        <apex:actionSupport event="onchange" reRender="tot"/>
                    </apex:inputField>
                </apex:column>

                <!--********** Service Term in Months **********-->
                <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Service_Term_in_Months__c.Label}">
                    <apex:inputField value="{!s.Service_Term_in_Months__c}" id="svc" style="width:70px" required="true">
                        <apex:actionSupport event="onchange" reRender="tot"/>
                    </apex:inputField>
                </apex:column>                                        

                <!--********** Discount (Manual) **********-->
                <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Discount_off_list_manual__c.Label}">
                    <apex:inputField value="{!s.Discount_off_list_manual__c}" id="disc" style="width:70px" required="True" onkeyup="calculateSalesPrice();">
                        <apex:actionSupport event="onkeyup" reRender="spproxy"/> 

                         <!--<apex:actionSupport event="onkeyup" action="{!calculateSalesPrice}"/>-->
                    </apex:inputField>                                            
                </apex:column>                    

                <!--********** Sales Price **********-->
                <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.UnitPrice.Label}">
                    <apex:inputField value="{!s.UnitPrice}" id="sp" style="width:70px" required="true" onkeyup="calculateDiscount();">
                        <apex:actionSupport event="onchange" reRender="tot"/>
                    </apex:inputField>                                          
                </apex:column>

                <!--********** Disti Transfer Price (Standard) Hidden **********-->
                <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Disti_Transfer_Price__c.Label}" rendered="True">
                    <apex:outputText id="dp" value="{!s.PriceBookEntry.Disti_Transfer_Price__c}"/>
                </apex:column>

                <!--********** List Price (Standard) Hidden **********-->
                <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.ListPrice.Label}" id="lp" value="{!s.PriceBookEntry.UnitPrice}" rendered="True"/>

                <!--********** Total Price (display only field) **********-->
                <apex:column headerValue="Total Price">
                    <apex:outputText id="tot" value="${0, number, ###,###,###,##0.00}">
                        <apex:param value="{!IF(s.PriceBookEntry.Product2.Product_Term_Years__c != null,(s.Quantity * s.Service_Term_in_Months__c * s.UnitPrice)/(s.PriceBookEntry.Product2.Product_Term_Years__c*12),(s.Quantity * s.UnitPrice)) }"/>
                    </apex:outputText> 
                </apex:column>                                   

            </apex:pageblockTable>


            <apex:pageBlockButtons >
                <apex:commandButton action="{!onSave}" value="Save"/>
                <apex:commandButton action="{!onCancel}" value="Cancel" immediate="true"/>
            </apex:pageBlockButtons>

        </apex:pageBlock>

1 Answer 1

2

This is really just a jQuery question - not specifically related to VF and is likely to be closed because of that. There are quite a few ways you can solve this.

The reason it's not working as you've got it written is because you're using the "ends with" selector when retrieving elements by their ids.

For instance, [id$=dp] will find every single element in the page that has an id which ends with dp. The result might be a collection of some inputs, images, divs, paragraphs, or anything else rendered in the page whose id ends with the two characters, dp. Your selector needs to be much more granular. Every single row in your table has an element with an id that ends in dp.

One jQuery Solution:

You might consider changing your function call on the element like this:

onkeyup="calculateSalesPrice(this);"

and then updating your javascript function to use the reference to this to traverse up the DOM to the closest parent tr and only calculate using the input:text fields that are within that particular tr.

function calculateDiscount(element) {
    var $discountField = jQuery(element); // use jQuery to select just this node
    // console.log($discountField); // output some debug info to see what you selected

    // traverse to the closest (parent) table row
    var $parentTR = $discountField.closest('tr'); // probably want to debug out this too

    // within the context of this table row, find the input:text elements to do math on them
    var $inputTextFields = $parentTR.find('input:text'); // debug this to see what you got

    // identify the appropriate input fields using ids or class attributes and do the math
    // var discount= 100*($('[id$=dp]').text() - $('[id$=sp]').val())/$('[id$=dp]').text();
    // var discountRounded = discount.toFixed(1);

    // this is too broad, will contain every discount field in the page
    // use the context of the $parentTR to limit the selector search
    // $('[id$=disc]').val(discountRounded); 
}

Alternate (and probably easier) Solution not using jQuery:

You could skip jQuery and table row context altogether and use the $Component syntax to render the ids of those fields directly into the page where you need them. Best Practices for Accessing Component IDs

Your VF markup might then look like this:

onkeyup="calculateDiscount('{!$Component.yourPageBlock.yourPageBlockTable.dp}', '{!$Component.yourPageBlock.yourPageBlockTable.sp}', '{!$Component.yourPageBlock.yourPageBlockTable.disc}');"

Which would render all of those id values from those fields in the row directly into the function call itself and you wouldn't need to use jQuery to traverse the DOM at all. document.getElementById(blah) would suffice to select the element(s) for you to get the values and to do the math.

Your calculateDiscount function would take three params and you could use those provided ids to just do the math with the first two and update the third one with the result.

 function calculateDiscount(dpElementId, spElementId, discElementId) {
    var dpElement = document.getElementById(dpElementId); // get the input field
    var spElement = document.getElementById(spElementId); // get the input field
    var discElement = document.getElementById(discElementId); // get the input field

     // do the math then update the value of discElement
}
2
  • Thanks so much mark! You're a life saver. Great point regarding using $Component syntax. I'm basically self-taught with respect to Apex and VF coding and am a total noob when it comes to JS so I'm just kind of hacking my way through it at this point. I ultimately decided to go with the $Component syntax as it is definitely much simpler. Commented Jun 8, 2016 at 0:09
  • One thing to note about that best practices doc; when it says "it won't work" and "it won't match", the actual result is an empty string output, not an error. So, in the event that $Component can't find the element you specified, your page markup will just have nothing there. i.e. alert('{!$Component.abc123'); would just be rendered as alert('');
    – Mark Pond
    Commented Jun 8, 2016 at 0:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .