1// Add an URL parser to JQuery that returns an object 2// This function is meant to be used with an URL like the window.location 3// Simple variable: ?var=abc returns {var: "abc"} 4// Simple object: ?var.length=2&var.scope=123 returns {var: {length: "2", scope: "123"}} 5// Simple array: ?var[]=0&var[]=9 returns {var: ["0", "9"]} 6// Array with index: ?var[0]=0&var[1]=9 returns {var: ["0", "9"]} 7// Nested objects: ?my.var.is.here=5 returns {my: {var: {is: {here: "5"}}}} 8// All together: ?var=a&my.var[]=b&my.cookie=no returns {var: "a", my: {var: ["b"], cookie: "no"}} 9// You just cant have an object in an array, e.g. ?var[1].test=abc DOES NOT WORK 10 11function alParseParams(query) { 12 var re = /([^&=]+)=?([^&]*)/g; 13 var decode = function (str) { 14 return decodeURIComponent(str.replace(/\+/g, ' ')); 15 }; 16 17 // recursive function to construct the result object 18 function createElement(params, key, value, shouldPush) { 19 key = key + ''; 20 21 // if the key is a property 22 if (key.indexOf('.') !== -1 && (key.indexOf('[') == -1 || (key.indexOf('[') > key.indexOf('.')))) { 23 // extract the first part with the name of the object 24 var list = key.split('.'); 25 26 // the rest of the key 27 var new_key = key.split(/\.(.+)?/)[1]; 28 29 // create the object if it doesnt exist 30 if (!params[list[0]]) params[list[0]] = {}; 31 32 // if the key is not empty, create it in the object 33 if (new_key !== '') { 34 createElement(params[list[0]], new_key, value); 35 } else console.warn('parseParams :: empty property in key "' + key + '"', false); 36 } else 37 // if the key is an array 38 if (key.indexOf('[') !== -1) { 39 // extract the array name 40 var list = key.split('['); 41 key = list[0]; 42 43 // extract the index of the array 44 var list = list[1].split(']'); 45 var index = list[0] 46 47 // if index is empty, just push the value at the end of the array 48 if (index == '') { 49 if (!params) params = {}; 50 if (!params[key] || !$.isArray(params[key])) params[key] = []; 51 params[key].push(value); 52 } else 53 // add the value at the index (must be an integer) 54 { 55 if (!params) params = {}; 56 if (!params[key] || !$.isArray(params[key])) params[key] = []; 57 58 if (list[1].indexOf('.') !== -1) { 59 var new_key = list[1].split(/\.(.+)?/)[1]; 60 // if the key is not empty, create it in the object 61 if (new_key !== '') { 62 if (!params[key][parseInt(index)] || (!$.isArray(params[key][parseInt(index)]) && !$.isPlainObject(params[key][parseInt(index)]))) { 63 if (new_key.indexOf('.') !== -1 || new_key.indexOf('[') !== -1) { 64 params[key][parseInt(index)] = []; 65 } else { 66 params[key][parseInt(index)] = {}; 67 } 68 } 69 70 if (new_key.indexOf('.') !== -1 || new_key.indexOf('[') !== -1) 71 createElement(params[key][parseInt(index)], new_key, value, true); 72 else { 73 if (params[key][parseInt(index)][new_key]) 74 params[key][parseInt(index)][new_key] += ';' + value; 75 else 76 params[key][parseInt(index)][new_key] = value; 77 } 78 } else console.warn('parseParams :: empty property in key "' + list[1] + '"'); 79 } else params[key][parseInt(index)] = value; 80 } 81 } else 82 // just normal key 83 { 84 if (!params) params = {}; 85 if (!shouldPush) { 86 params[key] = value; 87 } else { 88 var nestedParam = {}; 89 params.push(nestedParam); 90 createElement(nestedParam, key, value, false); 91 } 92 } 93 } 94 95 // be sure the query is a string 96 query = query + ''; 97 98 if (query === '') query = window.location + ''; 99 100 var params = {}, e; 101 if (query) { 102 // remove # from end of query 103 if (query.indexOf('#') !== -1) { 104 query = query.substr(0, query.indexOf('#')); 105 } 106 107 // remove ? at the begining of the query 108 if (query.indexOf('?') !== -1) { 109 query = query.substr(query.indexOf('?') + 1, query.length); 110 } 111 112 // empty parameters 113 if (query == '') return {}; 114 115 // execute a createElement on every key and value 116 while (e = re.exec(query)) { 117 var key = decode(e[1]); 118 var value = decode(e[2]); 119 createElement(params, key, value, false); 120 } 121 } 122 return params; 123} 124 125//to be run on engine request start 126function requestStart(sender, eventArgs) { 127 console.log("requestStart"); 128 kendo.ui.progress($('#pricingArea'), true); 129 if (window.calcStart) { 130 calcStart(sender, eventArgs); 131 } 132 if (window.intCalcStart) { 133 intCalcStart(sender, eventArgs); 134 } 135 $("#detailPage_shippingCalculatorResults").html(""); 136} 137//to be run on engine request end 138function responseEnd(sender, eventArgs) { 139 console.log("responseEnd"); 140 kendo.ui.progress($('#pricingArea'), false); 141 if (window.calcFinish) { 142 calcFinish(sender, eventArgs); 143 } 144 if (window.intCalcFinish) { 145 intCalcFinish(sender, eventArgs); 146 } 147} 148 149function ResetCalculatorDropdownSelection(pricingParams) { 150 if (pricingParams && pricingParams.Options) { 151 $(pricingParams.Options).each(function (index) { 152 if(pricingParams.Options[index]) 153 { 154 $("#Options\\[" + index + "\\]\\.Value").val(pricingParams.Options[index].Value); 155 } 156 }); 157 } 158} 159 160function extractNumber(obj, decimalPlaces, allowNegative, separator) { 161 "use strict"; 162 163 //Assuming '.' as the default separator 164 if (!separator) 165 separator = '.'; 166
167 //will prevent anything other than a number entered withing min and max payment value text boxes 168 var temp = obj.value; 169 // avoid changing things if already formatted correctly 170 var reg0Str = '[0-9]*'; 171 if (decimalPlaces > 0) { 172 reg0Str += '\\' + separator + '?[0-9]{0,' + decimalPlaces + '}'; 173 } else if (decimalPlaces < 0) { 174 reg0Str += '\\' + separator + '?[0-9]*'; 175 } 176 reg0Str = allowNegative ? '^-?' + reg0Str : '^' + reg0Str; 177 reg0Str = reg0Str + '$'; 178 var reg0 = new RegExp(reg0Str); 179 if (reg0.test(temp)) return true; 180 181 // first replace all non numbers 182 var reg1Str = '[^0-9' + (decimalPlaces != 0 ? separator : '') + (allowNegative ? '-' : '') + ']'; 183 var reg1 = new RegExp(reg1Str, 'g'); 184 temp = temp.replace(reg1, ''); 185 186 if (allowNegative) { 187 // replace extra negative 188 var hasNegative = temp.length > 0 && temp.charAt(0) == '-'; 189 var reg2 = /-/g; 190 temp = temp.replace(reg2, ''); 191 if (hasNegative) temp = '-' + temp; 192 } 193 194 if (decimalPlaces != 0) { 195 var reg3 = new RegExp('\\' + separator, 'g'); 196 var reg3Array = reg3.exec(temp); 197 if (reg3Array != null) { 198 // keep only first occurrence of . 199 // and the number of places specified by decimalPlaces or the entire string if decimalPlaces < 0 200 var reg3Right = temp.substring(reg3Array.index + reg3Array[0].length); 201 reg3Right = reg3Right.replace(reg3, ''); 202 reg3Right = decimalPlaces > 0 ? reg3Right.substring(0, decimalPlaces) : reg3Right; 203 temp = temp.substring(0, reg3Array.index) + separator + reg3Right; 204 } 205 } 206 obj.value = temp; 207} 208 209var pricingAreaQ1 = 0; 210var pricingAreaQ2 = 0; 211var pricingAreaQ3 = 0; 212var pricingAreaQ4 = 0; 213var pricingAreaQ5 = 0; 214var pricingParameterOld = null; 215 216function getPricingParameters() { 217 var isKit = $('#kitArea').length > 0; 218 var kitAreaData = isKit ? alParseParams($('#kitArea :input').serialize()) : {}; 219 220 var pricingParameters = $('#pricingArea').length > 0 ? alParseParams($('#pricingArea :input').serialize()) : isKit ? { Q1: kitAreaData.txtKitQuantity } : null; 221 222 if (pricingParameters && pricingParameters.SelectedUom && pricingParameters.SelectedUom != '' && $('#Q1').val() == '0') 223 $('#Q1').val(1); 224 225 if (pricingParameters) 226 pricingParameters.KitParameters = isKit ? kitAreaData.kitParameters : null; 227 228 return pricingParameters; 229} 230 231function onEngineLoaded(pricingParametersUsedToLoadEngine) { 232 // Bug 93397: the pricing call fired on load can fail by design (e.g. calculators whose 233 // default option is priced out of range to force a real selection). The error branch 234 // reverts Q1-Q5 to the last known-good values, which before any successful calculation 235 // were still the initial 0s - so the first failure wiped the server-rendered quantities 236 // and every later attempt (like just picking a valid option) kept failing with qty 0. 237 // Seed the known-good values from the freshly rendered inputs instead. 238 if (!pricingParameterOld) { 239 pricingAreaQ1 = $('#Q1').val() || pricingAreaQ1; 240 pricingAreaQ2 = $('#Q2').val() || pricingAreaQ2; 241 pricingAreaQ3 = $('#Q3').val() || pricingAreaQ3; 242 pricingAreaQ4 = $('#Q4').val() || pricingAreaQ4; 243 pricingAreaQ5 = $('#Q5').val() || pricingAreaQ5; 244 } 245 246 //This is reponsible for formatting the quantity fields on the pricing engine 247 $('.pricingGridQuantity').on('keyup',function (event) { 248 var decimalPlaces = $(this).data('decimalPlaces'); 249 var decimalSeparator = $(this).data('decimalSeparator'); 250 extractNumber($(this)[0], decimalPlaces, false, decimalSeparator); 251 }); 252 253 $('.pricingGridQuantity, #pricingEngineArea input[type=text]').on('keypress',function (event) { 254 if (event.keyCode === 13) { 255 calculatePrice(); 256 event.preventDefault(); 257 } 258 }); 259 260 var isKit = $('#kitArea').length > 0; 261 var calculatePrice = function () { 262 //disable add to cart and customize buttons to prevent pricing error on click before pricing calculated 263 // we add data-updating-price flag, so that we can avoid enabled the button when still uploading files 264 $('body').attr('data-updating-price', 'true'); 265 $("#btnAddToCartButton, #btnCustomizeButton").attr("disabled", "disabled"); 266 267 //Code to handle field suppressions 268 var inputsForSuppression = $('#pricingArea :input'); 269 270 for (var input_index = 0; input_index < inputsForSuppression.length; input_index++) { 271 var element = inputsForSuppression[input_index]; 272 var elementType = $(element).attr('type'); 273 if (elementType && elementType.toLowerCase() == 'hidden') 274 continue; 275 276 var suppressionRule = $(element).data('suppression'); 277 if (suppressionRule && suppressionRule.length > 0) { 278 var rules = suppressionRule.split(';'); 279 for (var rule_index = 0; rule_index < rules.length; rule_index++) { 280 var isRuleMatched = false; 281 var rule = rules[rule_index]; 282 283 var ruleParts = rule.split('='); 284 if (ruleParts.length > 1) { 285 var source = $('#pricingArea').find("[data-key='" + ruleParts[0] + "']").find(':input'); 286 287 for (var src_index = 0; src_index < source.length; src_index++) { 288 var src_element = source[src_index]; 289 290 var src_elementType = $(src_element).attr('type'); 291 if (src_elementType && src_elementType.toLowerCase() == 'hidden') 292 continue; 293 294 var parentNode = $(element).parent('li'); 295 if ($(src_element).val() == ruleParts[1]) { 296 var defaultValue = $(element).data('default'); 297 298 if (defaultValue) { 299 $(element).val(defaultValue); 300 } 301 302 parentNode.hide(); 303 isRuleMatched = true; 304 break; 305 } else { 306 parentNode.show(); 307 } 308 } 309 } 310 if (isRuleMatched) 311 break; 312 } 313 } 314 } 315 316 //Here we run reqiest start, this will eventually run other functions that custom skin developers hook into. 317 requestStart(null, null); 318 var pricingParameters = getPricingParameters(); 319 //console.log('get pricing start'); 320 $.ajax({
321 url: "/product/" + $("#urlName").val() + "/pricing", 322 type: "POST", 323 data: JSON.stringify({ pricingParameters: pricingParameters }), 324 dataType: "json", 325 contentType: "application/json; charset=utf-8;" 326 }) 327 .done(function (data) { 328 //console.log('get pricing end', data); 329 if (isKit) { 330 if ($("#kitSetTotal").length > 0) //#kitSetTotal is rendered for KitOptionPricingType.EachSelectedKit only 331 $("#kitSetTotal").html(data["FormattedCost"]); 332 else if ($("#pricingEngine_lblFinalPrice").length > 0) //KitOptionPricingType.SingleProductEngine 333 $("#pricingEngine_lblFinalPrice").html(data["FormattedCost"]); 334 335 if (data["ErrorMessage"] && data["ErrorMessage"] != "") { 336 $("#KitSetError").html(data["ErrorMessage"]); 337 $("#KitSetError").show(); 338 } 339 else { 340 $("#KitSetError").html(); 341 $("#KitSetError").hide(); 342 } 343 } else { 344 if (data["ErrorMessage"] && data["ErrorMessage"] != "") { 345 $("#priceErrorMessage").html(data["ErrorMessage"]); 346 $("#priceErrorMessage").removeClass('hidden'); 347 $('#Q1').val(pricingAreaQ1); 348 $('#Q2').val(pricingAreaQ2); 349 $('#Q3').val(pricingAreaQ3); 350 $('#Q4').val(pricingAreaQ4); 351 $('#Q5').val(pricingAreaQ5); 352 //ResetCalculatorDropdownSelection(pricingParameterOld); 353 // Revert to previous value will cause issue, as use can not change two option at the same time. 354 // So not all combination is available to choose. 355 $("#pricingEngine_lblFinalPrice").html('#.##'); 356 $("#pricingEngine_lblPerPiecePrice").html('#.##'); 357 window.hasPricingEngineError = true; 358 } 359 else { 360 $("#priceErrorMessage").empty(); 361 $("#priceErrorMessage").addClass('hidden'); 362 pricingAreaQ1 = $('#Q1').val(); 363 pricingAreaQ2 = $('#Q2').val(); 364 pricingAreaQ3 = $('#Q3').val(); 365 pricingAreaQ4 = $('#Q4').val(); 366 pricingAreaQ5 = $('#Q5').val(); 367 pricingParameterOld = pricingParameters; 368 $("#pricingEngine_lblFinalPrice").html(data["FormattedCost"]); 369 $("#pricingEngine_lblPerPiecePrice").html(data["FormattedPerUnitCost"]); 370 window.hasPricingEngineError = false; 371 } 372 } 373 $("#hdnTotalCost").val(data["Cost"]); 374 $("#hdnTotalWeight").val(data["Weight"]); 375 376 //Here we run response end, this will eventually run other functions that custom skin developers hook into. 377 378 // We enable the button if not uploading files 379 $('body').attr('data-updating-price', 'false'); 380 if (parseInt($('body').attr("data-uploading-files") || "0", 10) <= 0) { 381 $("#btnAddToCartButton, #btnCustomizeButton").removeAttr("disabled"); 382 } 383 384 responseEnd(null, data); 385 }) 386 .fail(function (error) { 387 console.error("Not able to retrieve price. Request failed.", error); 388 $("#priceErrorMessage").html("Not able to retrieve price. Request failed.").removeClass('hidden'); 389 responseEnd(null, error); 390 391 }); 392 } 393 394 var newPricingParameters = getPricingParameters(); 395 var usedSamePricingParameters = pricingParametersUsedToLoadEngine && pricingParametersUsedToLoadEngine.length == newPricingParameters.length; 396 for (var i = 0; i < newPricingParameters.length; i++) { 397 usedSamePricingParameters = usedSamePricingParameters && pricingParametersUsedToLoadEngine && pricingParametersUsedToLoadEngine[i].Key == newPricingParameters[i].Key; 398 } 399 400 var supportSuppressions = $('#calcParmInputs').hasClass('supportSuppressions'); 401 if (!supportSuppressions || usedSamePricingParameters || isKit) { 402 var changeCallback = supportSuppressions ? function (e) { 403 loadPricingEngine(getPricingParameters()); 404 405 //get current active input 406 window.calcActiveInputID = e.currentTarget.id; 407 //console.log("set active element id: ", window.calcActiveInputID); 408 } : function () { 409 calculatePrice(); 410 }; 411 412 //if there are control other than textbox and select then need to add event for that 413 if ($("#pricingArea :input[type=number]").length) { 414 $("#pricingArea :input[type=number]").change(changeCallback); 415 } 416 417 if ($("#pricingArea :input[type=text]").length) { 418 $("#pricingArea :input[type=text]").on('change',changeCallback); 419 } 420 421 //remove keypress events added before and make 'enter' key to trigger callback 422 $('.pricingGridQuantity, #pricingEngineArea input[type=text]').off("keypress").on("keypress", function (event) { 423 if (event.keyCode === 13) { 424 $(this).trigger('change'); 425 event.preventDefault(); 426 } 427 });
428 429 if ($("#pricingArea select").length) { 430 $("#pricingArea select").on('change',changeCallback); 431 } 432 433 if ($("#kitArea :input").length) { 434 $("#kitArea :input").on('change',changeCallback); 435 } 436 437 $("#pricingArea :input[type=number], #pricingArea :input[type=text], #pricingArea select").on('focus', function (e) { 438 window.calcActiveInputID = e.currentTarget.id; 439 }); 440 441 calculatePrice(); //fire on load 442 } else { 443 //console.log('used', pricingParametersUsedToLoadEngine ? pricingParametersUsedToLoadEngine.Options : null); 444 //console.log('new', newPricingParameters.Options); 445 446 loadPricingEngine(newPricingParameters); 447 } 448} 449 450function isInViewport(element) { 451 const rect = element.getBoundingClientRect(); 452 return ( 453 rect.top >= 0 && 454 rect.left >= 0 && 455 rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && 456 rect.right <= (window.innerWidth || document.documentElement.clientWidth) 457 ); 458} 459 460function loadPricingEngine(pricingParameters) { 461 462 kendo.ui.progress($('#pricingArea'), true); 463 $.ajax({ 464 url: "/product/" + $("#urlName").val() + "/options/" + $("#orderItemId").val(), 465 type: "POST", 466 data: JSON.stringify({ pricingParameters: pricingParameters }), 467 dataType: "html", 468 contentType: "application/json; charset=utf-8;", 469 }) 470 .done(function (result) { 471 $("#pricingEngineArea").html(result); 472 kendo.ui.progress($('#pricingArea'), false); 473 onEngineLoaded(pricingParameters); 474 475 if (!window.calcActiveInputID || window.calcActiveInputID == "undefined") { 476 if ($("#pricingArea :input").length) { 477 var $firstInput = $('#pricingArea :input').first(); 478 if(isInViewport($firstInput.get(0))){ 479 $firstInput.focus(); 480 } 481 } 482 //console.log("set focus on first input"); 483 } else { 484 document.getElementById(window.calcActiveInputID).focus(); 485 //console.log("set focus on active element"); 486 } 487 488 //console.log("loadPricingEngine done"); 489 }) 490 .fail(function (e) { 491 console.error("Product/Options AJAX ERROR: ", e); 492 }); 493} 494 495$(document).ready(function () { 496 //console.log("document read"); 497 loadPricingEngine(null); 498 499 $(window).on('unload',function () { 500 console.log("unload here"); 501 window.kendo.ui.progress($('.addToCartButton'), false); 502 window.kendo.ui.progress($('#pricingArea'), false); 503 }); 504});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.