Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
v:2.0:snippets:updating_total_price_with_option_modifers [2018/12/21 05:11] – [Step 2: Set up forms] adamv:2.0:snippets:updating_total_price_with_option_modifers [2023/07/12 10:38] (current) – [Dynamically triggering recalculation] adam
Line 10: Line 10:
 <code javascript> <code javascript>
 <script> <script>
-// Dynamic Price Calculation v3.1 +// Dynamic Price Calculation v3.3 
-var pricemod_regex=/[{\|]p([+\-:])([\d\.]+)(?:\D{3})?(?=[\|}])/,id_regex=/^(\d+):/,FC=FC||{};FC.onLoad=function(){FC.client.on("ready.done",initDynamicPrice)}; +var pricemod_regex=/[{\|]p([+\-:])([\d\.]+)(?:\D{3})?(?=[\|}])/,id_regex=/^(\d+):/,FC=FC||{};function initDynamicPrice(){ADJUST={},$("input,select").off("change.foxy-dynamic-price"),$('form[action*="'+FC.settings.storedomain+'"]').each((function(){var t=$(this),a="",e={products:{}};$(this).find("[name='name'],[name^='name||'],[name$=':name'],[name*=':name||']").each((function(){var r=getId(this.name),i=r?r+":":"",s=parseFloat(t.find("[name='"+i+"price'],[name^='"+i+"price||']").first().val()),c={id:r,code:"",base_price:isNaN(s)?0:s,quantity:1,attributes:{},has_quantity:!1},n=t.find("[name='"+i+"quantity'],[name^='"+i+"quantity||']"),o=t.find("[name='"+i+"code'],[name^='"+i+"code||']");if(o.length>0&&(c.code=clearHash(o.first().val()),""===a&&(a=c.code)),n.length>0){var d=0,h=getElementType(n);["select","text"].indexOf(h)>-1?(c.has_quantity=!0,d=parseFloat(clearHash(n.val()))):["radio","checkbox"].indexOf(h)>-1&&(c.has_quantity=!0,1==n.filter(":checked").length&&(d=parseFloat(clearHash(n.filter(":checked").val())))),isNaN(d)&&(d=0),c.quantity=d}e.products[r]=c}));t.attr("data-fc-form-code")&&(a=t.attr("data-fc-form-code")),""!==a&&($(this).find("input,select").each((function(){if(!this.disabled){var t=getId(this.name),r=getName(this.name),i=getElementType($(this));if("quantity"!=r)if("price"!=r||"hidden"==i){if("SELECT"==this.tagName){var s=!1;$(this).children("option").each((function(){this.value.search(pricemod_regex)>-1&&(s=!0)})),s&&($(this).data("fc-adjust-for",a),e.products[t].attributes[clearHash(this.name)]=clearHash(this.value),$(this).on("change.foxy-dynamic-price",(function(){ADJUST[$(this).data("fc-adjust-for")].products[t].attributes[clearHash(this.name)]=clearHash(this.value),recalcTotal()})))}else if(this.value.search(pricemod_regex)>-1)switch($(this).data("fc-adjust-for",a),$(this).attr("type")){case"checkbox":$(this).is(":checked")?e.products[t].attributes[clearHash(this.name)]=clearHash(this.value):e.products[t].attributes[clearHash(this.name)]="",$(this).on("change.foxy-dynamic-price",(function(){$(this).is(":checked")?ADJUST[$(this).data("fc-adjust-for")].products[t].attributes[clearHash(this.name)]=clearHash(this.value):ADJUST[$(this).data("fc-adjust-for")].products[t].attributes[clearHash(this.name)]="",recalcTotal()}));break;case"radio":e.products[t].attributes.hasOwnProperty(clearHash(this.name))||(e.products[t].attributes[clearHash(this.name)]=""),$(this).is(":checked")&&(e.products[t].attributes[clearHash(this.name)]=clearHash(this.value)),$("[name='"+this.name+"']").data("fc-adjust-for",a).on("change.foxy-dynamic-price",(function(){ADJUST[$(this).data("fc-adjust-for")].products[t].attributes[clearHash(this.name)]=clearHash(this.value),recalcTotal()}))}}else $(this).data("fc-adjust-for",a).on("change.foxy-dynamic-price",(function(){var a=0;(["select","text"].indexOf(i)>-1||["radio","checkbox"].indexOf(i)>-1&&$(this).is(":checked"))&&(a=parseFloat(clearHash(this.value))),isNaN(a)&&(a=0),ADJUST[$(this).data("fc-adjust-for")].products[t].base_price=a,recalcTotal()}));else $(this).data("fc-adjust-for",a).on("change.foxy-dynamic-price",(function(){var a=0;(["select","text"].indexOf(i)>-1||["radio","checkbox"].indexOf(i)>-1&&$(this).is(":checked"))&&(a=parseFloat(clearHash(this.value))),isNaN(a)&&(a=0),ADJUST[$(this).data("fc-adjust-for")].products[t].quantity=a,recalcTotal()}))}})),ADJUST[a]=e)})),recalcTotal()}function clearHash(t){return t.replace(/\|\|[\d\w]+(?:\|\|open)?$/,"")}function getNameParts(t){return(t=clearHash(t)).match(/(?:(\d+):)?(.*)/)}function getId(t){var a=getNameParts(t);return id_regex.test(this.name)&&(prefix=parseInt(this.name.match(id_regex)[0])),void 0===a[1]?0:parseInt(a[1])}function getName(t){return getNameParts(t)[2]}function getElementType(t){if("SELECT"==t[0].tagName)return"select";if("INPUT"==t[0].tagName)switch(t.attr("type").toLowerCase()){case"text":case"number":case"tel":return"text";default:return t.attr("type").toLowerCase()}}function recalcTotal(){for(f in ADJUST){var t=0,e=0;for(p in ADJUST[f].products){var r=ADJUST[f].products[p].base_price,i=0;for(a in ADJUST[f].products[p].attributes){var s=ADJUST[f].products[p].attributes[a].match(pricemod_regex);if(s)switch(s[1]){case":":r=parseFloat(s[2]);break;case"+":i+=parseFloat(s[2]);break;case"-":i-=parseFloat(s[2])}}r+=i,t+=r*=ADJUST[f].products[p].quantity,e+=ADJUST[f].products[p].quantity}"function"==typeof fcFormatPrice&&(t=fcFormatPrice(t,f)),"function"==typeof fcFormatQuantity&&(e=fcFormatQuantity(e,f)),t=adjustDisplayPrice(t);var c="object"==typeof FC&&FC.hasOwnProperty("json")&&FC.json.config.hasOwnProperty("currency_format")?jQuery.trim(FC.util.money_format(FC.json.config.currency_format,t)):t.formatMoney(2);$("."+f+"_total").html(c),$("."+f+"_total_quantity").html(e)}}function adjustDisplayPrice(t){return"function"!=typeof fc_adjust_price_display||(adjusted_price=fc_adjust_price_display(t),void 0===adjusted_price||isNaN(parseFloat(adjusted_price)))?t:parseFloat(adjusted_price)}FC.onLoad=function(){FC.client.on("ready.done",initDynamicPrice)},Number.prototype.formatMoney=function(t,a,e){var r=this,i=(t=isNaN(t=Math.abs(t))?2:t,a=null==a?".":a,e=null==e?",":e,r<0?"-":""),s=parseInt(r=Math.abs(+r||0).toFixed(t))+"",c=(c=s.length)>3?c%3:0;return i+(c?s.substr(0,c)+e:"")+s.substr(c).replace(/(\d{3})(?=\d)/g,"$1"+e)+(t?a+Math.abs(r-s).toFixed(t).slice(2):"")};
-function initDynamicPrice(){ADJUST={};$("input,select").off("change.foxy-dynamic-price");$('form[action*="'+FC.settings.storedomain+'"]').each(function(){var b=$(this),d="",g={products:{}};$(this).find("[name='name'],[name^='name||'],[name$=':name'],[name*=':name||']").each(function(){var k=getId(this.name),c=k?k+":":"",e=parseFloat(b.find("[name='"+c+"price'],[name^='"+c+"price||']").first().val());e={id:k,code:"",base_price:isNaN(e)?0:e,quantity:1,attributes:{},has_quantity:!1};var h=b.find("[name='"+ +
-c+"quantity'],[name^='"+c+"quantity||']");c=b.find("[name='"+c+"code'],[name^='"+c+"code||']");0<c.length&&(e.code=clearHash(c.first().val()),""===d&&(d=e.code));if(0<h.length){c=0;var l=getElementType(h);-1<["select","text"].indexOf(l)?(e.has_quantity=!0,c=parseFloat(clearHash(h.val()))):-1<["radio","checkbox"].indexOf(l)&&(e.has_quantity=!0,1==h.filter(":checked").length&&(c=parseFloat(clearHash(h.filter(":checked").val()))));isNaN(c)&&(c=0);e.quantity=c}g.products[k]=e});b.attr("data-fc-form-code")&& +
-(d=b.attr("data-fc-form-code"));""!==d&&($(this).find("input,select").each(function(){var b=getId(this.name),c=getName(this.name),e=getElementType($(this));if("quantity"==c)$(this).data("fc-adjust-for",d).on("change.foxy-dynamic-price",function(){var c=0;if(-1<["select","text"].indexOf(e)||-1<["radio","checkbox"].indexOf(e)&&$(this).is(":checked"))c=parseFloat(clearHash(this.value));isNaN(c)&&(c=0);ADJUST[$(this).data("fc-adjust-for")].products[b].quantity=c;recalcTotal()});else if("price"==c&&"hidden"!= +
-e)$(this).data("fc-adjust-for",d).on("change.foxy-dynamic-price",function(){var c=0;if(-1<["select","text"].indexOf(e)||-1<["radio","checkbox"].indexOf(e)&&$(this).is(":checked"))c=parseFloat(clearHash(this.value));isNaN(c)&&(c=0);ADJUST[$(this).data("fc-adjust-for")].products[b].base_price=c;recalcTotal()});else if("SELECT"==this.tagName){var h=!1;$(this).children("option").each(function(){-1<this.value.search(pricemod_regex)&&(h=!0)});h&&($(this).data("fc-adjust-for",d),g.products[b].attributes[clearHash(this.name)]= +
-clearHash(this.value),$(this).on("change.foxy-dynamic-price",function(){ADJUST[$(this).data("fc-adjust-for")].products[b].attributes[clearHash(this.name)]=clearHash(this.value);recalcTotal()}))}else if(-1<this.value.search(pricemod_regex))switch($(this).data("fc-adjust-for",d),$(this).attr("type")){case "checkbox":$(this).is(":checked")?g.products[b].attributes[clearHash(this.name)]=clearHash(this.value):g.products[b].attributes[clearHash(this.name)]="";$(this).on("change.foxy-dynamic-price",function(){$(this).is(":checked")? +
-ADJUST[$(this).data("fc-adjust-for")].products[b].attributes[clearHash(this.name)]=clearHash(this.value):ADJUST[$(this).data("fc-adjust-for")].products[b].attributes[clearHash(this.name)]="";recalcTotal()});break;case "radio":g.products[b].attributes.hasOwnProperty(clearHash(this.name))||(g.products[b].attributes[clearHash(this.name)]=""),$(this).is(":checked")&&(g.products[b].attributes[clearHash(this.name)]=clearHash(this.value)),$("[name='"+this.name+"']").data("fc-adjust-for",d).on("change.foxy-dynamic-price", +
-function(){ADJUST[$(this).data("fc-adjust-for")].products[b].attributes[clearHash(this.name)]=clearHash(this.value);recalcTotal()})}}),ADJUST[d]=g)});recalcTotal()}function clearHash(b){return b.replace(/\|\|[\d\w]+(?:\|\|open)?$/,"")}function getNameParts(b){b=clearHash(b);return b.match(/(?:(\d+):)?(.*)/)}function getId(b){b=getNameParts(b);id_regex.test(this.name)&&(prefix=parseInt(this.name.match(id_regex)[0]));return void 0===b[1]?0:parseInt(b[1])} +
-function getName(b){return getNameParts(b)[2]}function getElementType(b){if("SELECT"==b[0].tagName)return"select";if("INPUT"==b[0].tagName)switch(b.attr("type").toLowerCase()){case "text":case "number":case "tel":return"text";default:return b.attr("type").toLowerCase()}} +
-function recalcTotal(){for(f in ADJUST){var b=0,d=0;for(p in ADJUST[f].products){var g=ADJUST[f].products[p].base_price,k=0;for(a in ADJUST[f].products[p].attributes){var c=ADJUST[f].products[p].attributes[a].match(pricemod_regex);if(c)switch(c[1]){case ":":g=parseFloat(c[2]);break;case "+":k+=parseFloat(c[2]);break;case "-":k-=parseFloat(c[2])}}g+=k;g*=ADJUST[f].products[p].quantity;b+=g;d+=ADJUST[f].products[p].quantity}"function"===typeof fcFormatPrice&&(b=fcFormatPrice(b,f));"function"===typeof fcFormatQuantity&& +
-(d=fcFormatQuantity(d,f));b="object"==typeof FC&&FC.hasOwnProperty("json")&&FC.json.config.hasOwnProperty("currency_format")?jQuery.trim(FC.util.money_format(FC.json.config.currency_format,b)):b.formatMoney(2);$("."+f+"_total").html(b);$("."+f+"_total_quantity").html(d)}} +
-Number.prototype.formatMoney=function(b,d,g){var k=this;b=isNaN(b=Math.abs(b))?2:b;d=void 0==d?".":d;g=void 0==g?",":g;var c=0>k?"-":"",e=parseInt(k=Math.abs(+k||0).toFixed(b))+"",h=3<(h=e.length)?h%3:0;return c+(h?e.substr(0,h)+g:"")+e.substr(h).replace(/(\d{3})(?=\d)/g,"$1"+g)+(b?d+Math.abs(k-e).toFixed(b).slice(2):"")};+
 </script> </script>
 </code> </code>
Line 33: Line 22:
 <code html><input type="hidden" name="code" value="p1" /></code> <code html><input type="hidden" name="code" value="p1" /></code>
  
-The code for the product will be used as a key for using in the element that dynamically shows the total product price.+The code from the first product in your form will be used as a key for using in the element that dynamically shows the total product price.
  
-If you're not able to know what product code is set, or if there are multiple products in your form, or you just want to set a specific code to use for the price modifier, you can also add a data attribute to your form like this: +If you're not able to know what product code is first, or you just want to set a specific code to use for the price modifier, you can also add a data attribute to your form like this: 
  
 <code html><form ... data-fc-form-code="p1"></code> <code html><form ... data-fc-form-code="p1"></code>
Line 47: Line 36:
 <code html><p class="p1_total_quantity">0</p></code> <code html><p class="p1_total_quantity">0</p></code>
  
 +==== Dynamically triggering recalculation ====
  
 +If for whatever reason you need to trigger the dynamic price calculations to be recreated - for example if you dynamically add in a new input that includes a price modifier, you can execute this function in your javascript:
 +
 +<code javascript>initDynamicPrice();</code>
 +
 +==== Modify dynamically calculated total price ====
 +
 +If you need to dynamically modify the calculated price, for example if your product prices are tax exclusive, but you want to show the calculated price as tax inclusive, you can define a function on your page to achieve that result. The function has a name of ''fc_adjust_price_display'', and is passed a single argument which is the dynamically calculated total price. The function should return your modified price.
 +
 +As an example, let's say you want to account for a 25% inclusive tax on your product prices, that would look like this:
 +
 +<code javascript><script>
 +  function fc_adjust_price_display(price) {
 +    return price * 1.25;
 +  }
 +</script></code>
 ===== That's it! ===== ===== That's it! =====
  
-That should be it. The script basically runs through all the inputs within FoxyCart add to cart forms on the page, and grabs what the active value is. If that value has a price modifier (p:X, p+X, p-X), it chucks it into an array. After looping over all the form elements, it loops through all the selected price modifiers, adjusts the base price, multiplies it by the quantity and sets the total element.+That should be it. The script basically runs through all the inputs within FoxyCart add to cart forms on the page, and grabs what the active value is. If that value has a price modifier (''p:X''''p+X''''p-X''), it chucks it into an array. After looping over all the form elements, it loops through all the selected price modifiers, adjusts the base price, multiplies it by the quantity and sets the total element.
  
 ===== Notes and Limitations ===== ===== Notes and Limitations =====
  
   * The snippet is run after the FoxyCart javascript has been initialized. As such - there may be a small time while the page is loading before the dynamic total is calculated.   * The snippet is run after the FoxyCart javascript has been initialized. As such - there may be a small time while the page is loading before the dynamic total is calculated.

Site Tools