PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.78
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.78
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / widgets / Pricing_Slider / script.js

script.js in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.78, at includes/widgets/Pricing_Slider/script.js

869 lines 50.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Pricing Slider JavaScript
3 * Handles interactive functionality for both single and multiple sliders.
4 */
5 (function($) {
6 'use strict';
7
8 // Add basic CSS for notifications that will be created by JS
9 function addBasicNotificationCSS() {
10 if ($('#king-addons-pricing-slider-notification-styles').length) {
11 return; // Already added
12 }
13
14 var css = `
15 .king-addons-slider-notice {
16 display: block;
17 padding: 10px 15px;
18 margin: 10px 0;
19 border-radius: 4px;
20 font-size: 14px;
21 line-height: 1.5;
22 }
23 .king-addons-slider-notice.success {
24 color: #3c763d;
25 background-color: #dff0d8;
26 border: 1px solid #d6e9c6;
27 }
28 .king-addons-slider-notice.error {
29 color: #a94442;
30 background-color: #f2dede;
31 border: 1px solid #ebccd1;
32 }
33 .king-addons-slider-view-cart {
34 display: block;
35 margin: 10px 0;
36 padding: 8px 12px;
37 text-align: center;
38 text-decoration: none;
39 color: #4e4e4e;
40 background-color: #f7f7f7;
41 border: 1px solid #ddd;
42 border-radius: 4px;
43 transition: all 0.3s ease;
44 }
45 .king-addons-slider-view-cart:hover {
46 color: #23527c;
47 background-color: #e6e6e6;
48 border-color: #adadad;
49 }
50 `;
51
52 $('<style id="king-addons-pricing-slider-notification-styles">' + css + '</style>').appendTo('head');
53 }
54
55 // Execute once when script loads
56 addBasicNotificationCSS();
57
58 // --- Shared Helper Functions --- (Moved here)
59
60 /**
61 * Calculates the price based on the value and formula settings.
62 * @param {number} value The current slider value.
63 * @param {object} formulaSettings Settings object containing formula type and parameters.
64 * @returns {number} Calculated price.
65 */
66 function calculatePrice(value, formulaSettings) {
67 if (!formulaSettings) return value; // Return raw value if no settings
68
69 var formulaType = formulaSettings.advanced_formula_types || formulaSettings.formula || 'linear';
70
71 // Linear formula (Default in free version)
72 if (formulaType === 'linear') {
73 var a = typeof formulaSettings.a !== 'undefined' ? parseFloat(formulaSettings.a) : 1;
74 var b = typeof formulaSettings.b !== 'undefined' ? parseFloat(formulaSettings.b) : 0;
75 return a * value + b;
76 }
77 // Pro version formulas...
78 else if (formulaType === 'exponential') {
79 var base = typeof formulaSettings.formula_exp_base !== 'undefined' ? parseFloat(formulaSettings.formula_exp_base) : 1.1;
80 var multiplier = typeof formulaSettings.formula_exp_multiplier !== 'undefined' ? parseFloat(formulaSettings.formula_exp_multiplier) : 1;
81 return multiplier * Math.pow(base, value);
82 }
83 else if (formulaType === 'logarithmic') {
84 var baseLog = typeof formulaSettings.formula_log_base !== 'undefined' ? parseFloat(formulaSettings.formula_log_base) : 10;
85 var multiplierLog = typeof formulaSettings.formula_log_multiplier !== 'undefined' ? parseFloat(formulaSettings.formula_log_multiplier) : 10;
86 value = Math.max(1, value); // Logarithm is undefined for 0 or negative
87 return multiplierLog * (Math.log(value) / Math.log(baseLog));
88 }
89 else if (formulaType === 'power') {
90 var exponent = typeof formulaSettings.formula_power_exponent !== 'undefined' ? parseFloat(formulaSettings.formula_power_exponent) : 2;
91 var multiplierPower = typeof formulaSettings.formula_power_multiplier !== 'undefined' ? parseFloat(formulaSettings.formula_power_multiplier) : 1;
92 return multiplierPower * Math.pow(value, exponent);
93 }
94 else if (formulaType === 'custom') {
95 // Find the closest custom price point <= current value
96 var customPrices = formulaSettings.custom_prices || [];
97 var bestMatchPrice = value; // Fallback
98 var closestLowerVal = -Infinity;
99
100 customPrices.forEach(function(point) {
101 var pointValue = parseFloat(point.value);
102 var pointPrice = parseFloat(point.price);
103 if (!isNaN(pointValue) && !isNaN(pointPrice) && pointValue <= value && pointValue > closestLowerVal) {
104 closestLowerVal = pointValue;
105 bestMatchPrice = pointPrice;
106 }
107 });
108 return bestMatchPrice;
109 }
110
111 // Fallback to linear if formula type is unknown or default linear params if only 'linear' is specified
112 var defaultA = typeof formulaSettings.a !== 'undefined' ? parseFloat(formulaSettings.a) : 1;
113 var defaultB = typeof formulaSettings.b !== 'undefined' ? parseFloat(formulaSettings.b) : 0;
114 return defaultA * value + defaultB;
115 }
116
117 /**
118 * Formats a price number into an HTML string with currency, prefix, suffix, etc.
119 * @param {number} price The numerical price.
120 * @param {object} priceFormatData Settings for formatting (currency_symbol, etc.).
121 * @returns {string} HTML string for the price display.
122 */
123 function formatPriceHTML(price, priceFormatData) {
124 var decimals = typeof priceFormatData.decimals !== 'undefined' ? parseInt(priceFormatData.decimals) : 2;
125 var formattedPrice = parseFloat(price).toFixed(decimals);
126 var currencySymbol = priceFormatData.currency_symbol || '';
127 var pricePrefix = priceFormatData.price_prefix || '';
128 var priceSuffix = priceFormatData.price_suffix || '';
129 var period = priceFormatData.period || '';
130 var currencyPosition = priceFormatData.currency_position || 'before';
131
132 var price_html = '';
133 if (currencyPosition === 'before') {
134 price_html += '<span class="king-addons-pricing-slider__currency">' + currencySymbol + '</span>';
135 }
136 if (pricePrefix) {
137 price_html += '<span class="king-addons-pricing-slider__prefix">' + pricePrefix + '</span>';
138 }
139 // Ensure the value itself has the specific class for targeting if needed later
140 price_html += '<span class="king-addons-pricing-slider__price-value">' + formattedPrice + '</span>';
141 if (priceSuffix) {
142 price_html += '<span class="king-addons-pricing-slider__suffix">' + priceSuffix + '</span>';
143 }
144 if (currencyPosition === 'after') {
145 price_html += '<span class="king-addons-pricing-slider__currency">' + currencySymbol + '</span>';
146 }
147 if (period) {
148 price_html += '<span class="king-addons-pricing-slider__period">' + period + '</span>';
149 }
150 return price_html;
151 }
152
153 /**
154 * Updates the visual state (icon, class) of feature items based on the current value.
155 * @param {number} value The current slider value.
156 * @param {jQuery} featureItems jQuery object of all feature list items.
157 * @param {Array} featuresDefinition Array of feature definition objects from settings.
158 */
159 function updateFeatures(value, featureItems, featuresDefinition) {
160 featureItems.each(function() {
161 var feature = $(this);
162 // Use min-value from data attribute if present, otherwise assume 0
163 var minValueFeature = parseInt(feature.data('min-value')) || 0;
164 // Determine if the feature should be 'included' based on the slider value
165 var isIncluded = value >= minValueFeature;
166
167 var featureTextElement = feature.find('.king-addons-pricing-slider__feature-text');
168 var featureText = featureTextElement.length ? featureTextElement.text().trim() : ''; // Get text content for matching
169 var featureData = null;
170
171 // Find the matching feature definition from the settings array
172 if (Array.isArray(featuresDefinition) && featureText) {
173 for (var i = 0; i < featuresDefinition.length; i++) {
174 // Match based on the feature text content
175 if (featuresDefinition[i] && featuresDefinition[i].text && featuresDefinition[i].text.trim() === featureText) {
176 featureData = featuresDefinition[i];
177 break;
178 }
179 }
180 }
181
182 // Update icon and class if we found matching data
183 if (featureData) {
184 var iconSpan = feature.find('span:first-child'); // Assuming icon is in the first span
185 var iconIncluded = featureData.icon_included || 'fas fa-check'; // Default icons
186 var iconExcluded = featureData.icon_excluded || 'fas fa-times';
187
188 iconSpan.removeClass('king-addons-pricing-slider__feature-included king-addons-pricing-slider__feature-excluded');
189
190 if (isIncluded) {
191 iconSpan.addClass('king-addons-pricing-slider__feature-included');
192 iconSpan.html('<i class="' + iconIncluded + '" aria-hidden="true"></i>');
193 } else {
194 iconSpan.addClass('king-addons-pricing-slider__feature-excluded');
195 iconSpan.html('<i class="' + iconExcluded + '" aria-hidden="true"></i>');
196 }
197 } else {
198 // Optional: Handle case where feature item in HTML doesn't match any definition
199 // console.warn('[KA Slider] No feature definition found for:', featureText);
200 }
201 });
202 }
203
204 /**
205 * Updates the UI elements of a single slider instance (progress bar, thumb, value display).
206 * Moved here to be a shared helper.
207 * @param {jQuery} singleSliderWrapper The wrapper (.king-addons-single-slider or .king-addons-pricing-slider).
208 * @param {number} value The current value.
209 */
210 function sharedUpdateSliderUI(singleSliderWrapper, value) {
211 var rangeInput = singleSliderWrapper.find('.king-addons-pricing-slider__range');
212 var progressBar = singleSliderWrapper.find('.king-addons-pricing-slider__progress');
213 var customThumb = singleSliderWrapper.find('.king-addons-pricing-slider__custom-thumb');
214 var currentValDisplay = singleSliderWrapper.find('.king-addons-pricing-slider__current-value');
215
216 if (!rangeInput.length) {
217 // console.error('[KA Slider] Range input not found within:', singleSliderWrapper[0]);
218 return;
219 }
220 if (!progressBar.length) {
221 // console.warn('[KA Slider] Progress bar not found within:', singleSliderWrapper[0]);
222 // Continue without progress bar update
223 }
224 if (!customThumb.length) {
225 // console.warn('[KA Slider] Custom thumb not found within:', singleSliderWrapper[0]); // Log raw DOM element
226 // Continue without thumb update
227 }
228
229 var min = parseFloat(rangeInput.attr('min')) || 0;
230 var max = parseFloat(rangeInput.attr('max')) || 100;
231 var percentage = 0;
232
233 if (max > min) {
234 percentage = ((value - min) / (max - min)) * 100;
235 } else {
236 // If max <= min, slider is effectively fixed. Percentage is 0 if value <= min, 100 if value >= max.
237 // Or just treat as 0 to be safe.
238 percentage = (value >= max) ? 100 : 0;
239 // console.warn('[KA Slider] Max <= Min for slider:', rangeInput[0]);
240 }
241
242 percentage = Math.max(0, Math.min(100, percentage)); // Clamp percentage
243
244 // console.log('[KA Slider] sharedUpdateSliderUI called:', { // Keep logs for now
245 // wrapper: singleSliderWrapper[0],
246 // value: value,
247 // percentage: percentage,
248 // thumbElement: customThumb.length ? customThumb[0] : 'Not Found'
249 // });
250
251 // Apply styles
252 if (progressBar.length) {
253 progressBar.css('width', percentage + '%');
254 }
255 if (customThumb.length) {
256 customThumb.css('left', percentage + '%');
257 // console.log('[KA Slider] Applied left: ' + percentage + '% to thumb:', customThumb[0]);
258 }
259
260 // Update current value indicator (if exists)
261 if (currentValDisplay.length) {
262 currentValDisplay.text(value);
263 currentValDisplay.css('left', percentage + '%');
264 // Recalculate margin based on actual thumb height at the time of update
265 var thumbHeight = customThumb.length ? customThumb.outerHeight() : 0;
266 if (thumbHeight) {
267 var indicatorMarginTop = (thumbHeight / 2) + 10; // 10px gap above thumb center
268 currentValDisplay.css('margin-top', indicatorMarginTop + 'px');
269 } else {
270 // Fallback margin if thumb isn't found or has no height
271 currentValDisplay.css('margin-top', '22px');
272 }
273 }
274 }
275
276 // --- Initialization Functions (Using Shared Helpers) ---
277
278 /**
279 * Initialize a specific SINGLE pricing slider. (Revisited Version)
280 * @param {jQuery} sliderWrapper The main wrapper element (.king-addons-pricing-slider).
281 */
282 function initSinglePricingSliderRevisited(sliderWrapper) {
283 // Get elements
284 var rangeInput = sliderWrapper.find('.king-addons-pricing-slider__range');
285 var priceWrapper = sliderWrapper.find('.king-addons-pricing-slider__price');
286 var button = sliderWrapper.find('.king-addons-pricing-slider__button'); // Could be <a> or <button>
287 var features = sliderWrapper.find('.king-addons-pricing-slider__feature-item');
288
289 if (!rangeInput.length) {
290 // console.error("[KA Slider] Single slider init failed: Range input not found in", sliderWrapper[0]);
291 return;
292 }
293
294 // Get data (use attributes as fallback for robustness)
295 var defaultValue = parseFloat(rangeInput.val()) || parseFloat(sliderWrapper.data('default-value')) || parseFloat(rangeInput.attr('min')) || 0;
296 var priceData = sliderWrapper.data('price-data') || {}; // Get combined price settings
297 var featuresData = sliderWrapper.data('features') || []; // Get features definitions
298
299 // Correct initial value if it's outside min/max bounds
300 var minValue = parseFloat(rangeInput.attr('min')) || 0;
301 var maxValue = parseFloat(rangeInput.attr('max')) || 100;
302 defaultValue = Math.max(minValue, Math.min(maxValue, defaultValue));
303 if (parseFloat(rangeInput.val()) !== defaultValue) {
304 rangeInput.val(defaultValue); // Ensure input value matches calculated default
305 }
306
307 /**
308 * Updates the price display, button link (if applicable), and features for the single slider.
309 */
310 function updateDisplay() {
311 var value = parseFloat(rangeInput.val()); // Get current value from input
312 var price = calculatePrice(value, priceData); // Use shared function
313 var new_price_html = formatPriceHTML(price, priceData); // Use shared function
314
315 if (priceWrapper.length) {
316 priceWrapper.html(new_price_html);
317 }
318
319 // Update button URL only if it's a link (<a> tag)
320 if (button.length && button.is('a')) {
321 var url = button.attr('href');
322 // Check if href exists and is not just '#' or empty
323 if (url && url !== '#') {
324 try {
325 var urlParts = url.split('?');
326 var baseUrl = urlParts[0];
327 var params = new URLSearchParams(urlParts.length > 1 ? urlParts[1] : '');
328 params.set('price', parseFloat(price).toFixed(priceData.decimals || 2)); // Use decimals from data
329 button.attr('href', baseUrl + '?' + params.toString());
330 } catch (e) {
331 // console.error("[KA Slider] Error updating button URL:", e);
332 // Potentially invalid URL, leave it as is
333 }
334 }
335 }
336
337 updateFeatures(value, features, featuresData); // Update icons
338
339 // --- Add dynamic class update for feature list items --- START
340 if (features.length) {
341 features.each(function() {
342 var item = $(this);
343 var minValue = parseFloat(item.data('min-value')); // Get minimum value from data-attribute
344 var isIncluded = value >= minValue;
345
346 // Remove both classes before adding the correct one
347 item.removeClass('king-addons-feature-included king-addons-feature-excluded');
348
349 if (isIncluded) {
350 item.addClass('king-addons-feature-included');
351 } else {
352 item.addClass('king-addons-feature-excluded');
353 }
354 });
355 }
356 // --- Add dynamic class update for feature list items --- END
357 }
358
359 // --- Initialization ---
360 sharedUpdateSliderUI(sliderWrapper, defaultValue); // Initial UI positioning
361 updateDisplay(); // Initial price, features, button link update
362
363 // --- Event Listener ---
364 // Ensure previous listeners are removed before adding new ones (important for editor)
365 rangeInput.off('.pricingSlider').on('input.pricingSlider change.pricingSlider', function() {
366 var value = parseFloat($(this).val());
367 sharedUpdateSliderUI(sliderWrapper, value); // Update slider UI visuals
368 updateDisplay(); // Update price, button, features based on new value
369 });
370 }
371
372 /**
373 * Initialize MULTIPLE pricing sliders within a container. (Revisited Version)
374 * @param {jQuery} slidersContainer The main wrapper element (.king-addons-pricing-sliders).
375 */
376 function initMultiplePricingSlidersRevisited(slidersContainer) {
377 // Get common elements and data for the whole group
378 var priceData = slidersContainer.data('price-data') || {}; // Combined price display settings
379 var featuresData = slidersContainer.data('features') || []; // Combined features definitions
380 var priceWrapper = slidersContainer.find('.king-addons-pricing-slider__price'); // The single price display for the group
381 var featuresList = slidersContainer.find('.king-addons-pricing-slider__feature-list'); // Container for features
382 var button = slidersContainer.find('.king-addons-pricing-slider__button'); // Combined button (link or WC)
383 var addToCartButton = slidersContainer.find('.king-addons-pricing-slider__add-to-cart'); // Specific WC button
384 var individualSliders = slidersContainer.find('.king-addons-single-slider'); // Wrappers for each slider
385 var allSlidersData = slidersContainer.data('sliders') || []; // Array of settings for each slider (from repeater)
386
387 if (!individualSliders.length) {
388 // console.error("[KA Slider] Multi-slider init failed: No individual sliders found in", slidersContainer[0]);
389 return;
390 }
391 if (allSlidersData.length !== individualSliders.length) {
392 // console.warn("[KA Slider] Mismatch between slider data count and slider element count.");
393 // Attempt to continue, but calculations might be off
394 }
395
396 /**
397 * Calculates the combined weighted value/price for all sliders in the group.
398 * @returns {number} The final calculated price.
399 */
400 function calculateCombinedPriceForMulti() {
401 var combinedValue = 0;
402 var totalWeight = 0;
403
404 individualSliders.each(function(index) {
405 var singleSliderWrapper = $(this);
406 var rangeInput = singleSliderWrapper.find('.king-addons-pricing-slider__range');
407 if (!rangeInput.length) return; // Skip if input not found
408
409 var value = parseFloat(rangeInput.val());
410 // Get weight from the corresponding data object, default to 1 if missing or invalid
411 var weight = (allSlidersData[index] && typeof allSlidersData[index].weight !== 'undefined' && !isNaN(parseFloat(allSlidersData[index].weight)))
412 ? parseFloat(allSlidersData[index].weight)
413 : 1;
414
415 combinedValue += value * weight;
416 totalWeight += weight;
417 });
418
419 // Avoid division by zero if totalWeight is 0
420 var averageValue = (totalWeight > 0) ? (combinedValue / totalWeight) : 0;
421
422 // Now calculate the final price based on the combined/averaged value using the group's price settings
423 return calculatePrice(averageValue, priceData); // Use shared function
424 }
425
426 /**
427 * Updates the combined price display, features list, and button(s) for the group.
428 */
429 function updateCombinedDisplayForMulti() {
430 var finalPrice = calculateCombinedPriceForMulti(); // Use the final calculated price/value for feature check
431 var new_price_html = formatPriceHTML(finalPrice, priceData); // Use shared function
432
433 if (priceWrapper.length) {
434 priceWrapper.html(new_price_html);
435 }
436
437 // Update features based on the final calculated price/value
438 if (featuresList.length) {
439 var featureItems = featuresList.find('.king-addons-pricing-slider__feature-item');
440 updateFeatures(finalPrice, featureItems, featuresData); // Update icons based on final price
441
442 // --- Add dynamic class update for feature list items (Multi-Slider) --- START
443 featureItems.each(function() {
444 var item = $(this);
445 var minValue = parseFloat(item.data('min-value'));
446 // Use the final calculated price/value to determine inclusion for the combined list
447 var isIncluded = finalPrice >= minValue;
448
449 item.removeClass('king-addons-feature-included king-addons-feature-excluded');
450
451 if (isIncluded) {
452 item.addClass('king-addons-feature-included');
453 } else {
454 item.addClass('king-addons-feature-excluded');
455 }
456 });
457 // --- Add dynamic class update for feature list items (Multi-Slider) --- END
458 }
459
460 // Update standard button link (if it's an <a> tag)
461 if (button.length && button.is('a')) {
462 var url = button.attr('href');
463 if (url && url !== '#') {
464 try {
465 var urlParts = url.split('?');
466 var baseUrl = urlParts[0];
467 var params = new URLSearchParams(urlParts.length > 1 ? urlParts[1] : '');
468 params.set('price', parseFloat(finalPrice).toFixed(priceData.decimals || 2));
469 button.attr('href', baseUrl + '?' + params.toString());
470 } catch(e) {
471 // console.error("[KA Slider] Error updating multi-slider button URL:", e);
472 }
473 }
474 }
475 // Update WooCommerce button data attribute (if exists)
476 if (addToCartButton.length) {
477 addToCartButton.data('price', finalPrice); // Store calculated price for potential AJAX use
478 addToCartButton.attr('data-price', finalPrice); // Also set attribute for easier debugging/selection
479 }
480 }
481
482 // --- Initialization ---
483 // Initialize UI for each individual slider
484 individualSliders.each(function(index) {
485 var singleSliderWrapper = $(this);
486 var rangeInput = singleSliderWrapper.find('.king-addons-pricing-slider__range');
487 if (!rangeInput.length) return; // Skip if no input
488
489 var minValue = parseFloat(rangeInput.attr('min')) || 0;
490 var maxValue = parseFloat(rangeInput.attr('max')) || 100;
491 // Use default value from data if available, otherwise from input, clamped
492 var defaultValue = (allSlidersData[index] && typeof allSlidersData[index].default_value !== 'undefined')
493 ? parseFloat(allSlidersData[index].default_value)
494 : parseFloat(rangeInput.val());
495 defaultValue = Math.max(minValue, Math.min(maxValue, defaultValue || minValue));
496
497 if (parseFloat(rangeInput.val()) !== defaultValue) {
498 rangeInput.val(defaultValue); // Set initial value correctly
499 }
500 sharedUpdateSliderUI(singleSliderWrapper, defaultValue); // Use shared function
501 });
502 // Update the combined display based on initial values
503 updateCombinedDisplayForMulti();
504
505 // --- Event Listener (Delegated) ---
506 // Remove previous listeners first
507 slidersContainer.off('.pricingSlider', '.king-addons-pricing-slider__range');
508 // Add new delegated listener
509 slidersContainer.on('input.pricingSlider change.pricingSlider', '.king-addons-pricing-slider__range', function() {
510 var rangeInput = $(this); // The input that triggered the event
511 var singleSliderWrapper = rangeInput.closest('.king-addons-single-slider');
512 var value = parseFloat(rangeInput.val());
513
514 // Log for debugging multi-slider events
515 // console.log('[KA Slider] Multi-Slider Input Event Fired:', {
516 // value: value,
517 // targetSliderWrapper: singleSliderWrapper.length ? singleSliderWrapper[0] : 'Not Found',
518 // eventTarget: rangeInput[0]
519 // });
520
521 if (!singleSliderWrapper.length) {
522 // console.error('[KA Slider] Could not find .king-addons-single-slider parent for', rangeInput[0]);
523 return;
524 }
525
526 sharedUpdateSliderUI(singleSliderWrapper, value); // Update the UI of the slider that changed
527 updateCombinedDisplayForMulti(); // Recalculate and update the combined price/features
528 });
529
530 // --- WooCommerce Handler Placeholder ---
531 if (addToCartButton.length) {
532 addToCartButton.off('click.wcAddToCart').on('click.wcAddToCart', function(e) {
533 e.preventDefault();
534 var $button = $(this);
535
536 // --- DEBUGGING START ---
537 // console.log('[KA Slider] Add to Cart clicked. Localized vars:', typeof king_addons_slider_vars !== 'undefined' ? king_addons_slider_vars : 'Not Defined');
538 // --- DEBUGGING END ---
539
540 var price = $button.data('price') || calculateCombinedPriceForMulti(); // Get price
541 var productId = $button.data('product-id');
542 var productType = $button.data('product-type') || 'dynamic'; // Get product type
543 var autoQuantity = $button.data('auto-quantity') === 'yes'; // Check if auto quantity is enabled
544 var useAsBudget = $button.data('use-budget') === 'yes'; // Check if using slider as budget
545
546 var quantity = 1; // Default quantity
547
548 // If auto quantity is enabled, try to get the value from the relevant slider
549 // NOTE: This assumes auto_quantity makes sense primarily with a single slider scenario.
550 // If used with multiple, logic might need refinement (e.g., use combined value?).
551 if (autoQuantity) {
552 // Find the slider input within the same container as the button
553 // This works for single slider and might need adjustment for complex multi-slider quantity logic
554 var $sliderInput = $button.closest('.king-addons-pricing-slider, .king-addons-pricing-sliders').find('.king-addons-pricing-slider__range').first(); // Get the first slider in the context
555 if ($sliderInput.length) {
556 quantity = parseInt($sliderInput.val()) || 1;
557 } else {
558 // console.warn('[KA Slider] Could not find slider input for auto quantity.');
559 }
560 quantity = Math.max(1, quantity); // Ensure quantity is at least 1
561 }
562
563 if (!productId && productType === 'specific') { // Only require product ID for specific type
564 // Maybe show a user-facing error?
565 // Clear any existing notices or links
566 $button.siblings('.king-addons-slider-notice, .king-addons-slider-view-cart').remove();
567 var errorMsg = $('<span class="king-addons-slider-notice error">Missing Product ID.</span>').insertAfter($button);
568 setTimeout(function(){ errorMsg.fadeOut(function(){ $(this).remove(); }); }, 5000);
569 return;
570 }
571
572 // Basic AJAX add-to-cart implementation (needs refinement)
573 // Consider adding loading state to button
574 $button.addClass('loading');
575 // Clear any existing notices or links before making the request
576 $button.siblings('.king-addons-slider-notice, .king-addons-slider-view-cart').remove();
577
578 $.ajax({
579 type: 'POST',
580 url: king_addons_slider_vars.ajax_url, // Make sure this variable is localized
581 data: {
582 action: 'king_addons_add_to_cart', // PHP action hook
583 nonce: $button.data('nonce'), // Get nonce from button data attribute
584 product_id: productId,
585 price: price, // Send the calculated price
586 product_type: productType,
587 quantity: quantity,
588 use_as_budget: useAsBudget ? 'true' : 'false', // Send as string 'true'/'false'
589 auto_quantity: autoQuantity ? 'true' : 'false' // Send as string 'true'/'false'
590 },
591 success: function(response) {
592 $button.removeClass('loading');
593
594 // Find the target container for notifications based on settings
595 var notificationsContainer;
596 var notificationPosition = (priceData && priceData.notification_position) ? priceData.notification_position : 'after_button';
597
598 // Remove existing notifications
599 $button.closest('.king-addons-pricing-slider, .king-addons-pricing-sliders')
600 .find('.king-addons-slider-notice, .king-addons-slider-view-cart').remove();
601
602 switch(notificationPosition) {
603 case 'before_button':
604 notificationsContainer = $button.parent();
605 // Insert before button
606 if (response.success) {
607 // console.log('[KA Slider] Product added to cart:', response.data);
608 // Trigger WooCommerce added_to_cart event for themes/plugins to hook into
609 $(document.body).trigger('added_to_cart', [response.fragments, response.cart_hash, $button]);
610
611 var successMsg = $('<span class="king-addons-slider-notice success">' + (response.data.message || 'Added to cart!') + '</span>');
612 successMsg.insertBefore($button);
613
614 if (response.data.cart_url) {
615 var viewCartText = (typeof king_addons_slider_vars !== 'undefined' && king_addons_slider_vars.view_cart_text)
616 ? king_addons_slider_vars.view_cart_text : 'View Cart';
617 var viewCartLink = $('<a href="' + response.data.cart_url + '" class="king-addons-slider-view-cart">' + viewCartText + '</a>');
618 viewCartLink.insertBefore($button);
619
620 setTimeout(function(){
621 successMsg.fadeOut(function(){ $(this).remove(); });
622 viewCartLink.fadeOut(function(){ $(this).remove(); });
623 }, 5000);
624 } else {
625 setTimeout(function(){ successMsg.fadeOut(function(){ $(this).remove(); }); }, 3000);
626 }
627 } else {
628 var errorMessage = 'Could not add product to cart. Please try again.';
629 if (response.data && response.data.error_code === 'budget_too_low') {
630 var templateMessage = priceData.budget_too_low_message || 'Your selected budget is too low. Please increase the budget to at least {product_price}.';
631 var productPrice = response.data.product_price || '';
632 errorMessage = templateMessage.replace('{product_price}', productPrice);
633 } else if (response.data && response.data.message) {
634 errorMessage = response.data.message;
635 }
636
637 var errorMsg = $('<span class="king-addons-slider-notice error">' + errorMessage + '</span>');
638 errorMsg.insertBefore($button);
639 setTimeout(function(){ errorMsg.fadeOut(function(){ $(this).remove(); }); }, 5000);
640 }
641 break;
642
643 case 'after_price':
644 var priceElement = $button.closest('.king-addons-pricing-slider, .king-addons-pricing-sliders')
645 .find('.king-addons-pricing-slider__price');
646 if (priceElement.length) {
647 if (response.success) {
648 // console.log('[KA Slider] Product added to cart:', response.data);
649 // Trigger WooCommerce added_to_cart event for themes/plugins to hook into
650 $(document.body).trigger('added_to_cart', [response.fragments, response.cart_hash, $button]);
651
652 var successMsg = $('<span class="king-addons-slider-notice success">' + (response.data.message || 'Added to cart!') + '</span>');
653 successMsg.insertAfter(priceElement);
654
655 if (response.data.cart_url) {
656 var viewCartText = (typeof king_addons_slider_vars !== 'undefined' && king_addons_slider_vars.view_cart_text)
657 ? king_addons_slider_vars.view_cart_text : 'View Cart';
658 var viewCartLink = $('<a href="' + response.data.cart_url + '" class="king-addons-slider-view-cart">' + viewCartText + '</a>');
659 viewCartLink.insertAfter(successMsg);
660
661 setTimeout(function(){
662 successMsg.fadeOut(function(){ $(this).remove(); });
663 viewCartLink.fadeOut(function(){ $(this).remove(); });
664 }, 5000);
665 } else {
666 setTimeout(function(){ successMsg.fadeOut(function(){ $(this).remove(); }); }, 3000);
667 }
668 } else {
669 var errorMessage = 'Could not add product to cart. Please try again.';
670 if (response.data && response.data.error_code === 'budget_too_low') {
671 var templateMessage = priceData.budget_too_low_message || 'Your selected budget is too low. Please increase the budget to at least {product_price}.';
672 var productPrice = response.data.product_price || '';
673 errorMessage = templateMessage.replace('{product_price}', productPrice);
674 } else if (response.data && response.data.message) {
675 errorMessage = response.data.message;
676 }
677
678 var errorMsg = $('<span class="king-addons-slider-notice error">' + errorMessage + '</span>');
679 errorMsg.insertAfter(priceElement);
680 setTimeout(function(){ errorMsg.fadeOut(function(){ $(this).remove(); }); }, 5000);
681 }
682 } else {
683 // Fallback to after button if price element not found
684 notificationPosition = 'after_button';
685 }
686 break;
687
688 case 'in_container':
689 var customContainer = $button.closest('.king-addons-pricing-slider, .king-addons-pricing-sliders')
690 .find('.king-addons-notification-container');
691 if (customContainer.length) {
692 customContainer.empty(); // Clear previous notifications
693
694 if (response.success) {
695 // console.log('[KA Slider] Product added to cart:', response.data);
696 // Trigger WooCommerce added_to_cart event for themes/plugins to hook into
697 $(document.body).trigger('added_to_cart', [response.fragments, response.cart_hash, $button]);
698
699 var successMsg = $('<span class="king-addons-slider-notice success">' + (response.data.message || 'Added to cart!') + '</span>');
700 customContainer.append(successMsg);
701
702 if (response.data.cart_url) {
703 var viewCartText = (typeof king_addons_slider_vars !== 'undefined' && king_addons_slider_vars.view_cart_text)
704 ? king_addons_slider_vars.view_cart_text : 'View Cart';
705 var viewCartLink = $('<a href="' + response.data.cart_url + '" class="king-addons-slider-view-cart">' + viewCartText + '</a>');
706 customContainer.append(viewCartLink);
707
708 setTimeout(function(){
709 successMsg.fadeOut(function(){ $(this).remove(); });
710 viewCartLink.fadeOut(function(){ $(this).remove(); });
711 }, 5000);
712 } else {
713 setTimeout(function(){ successMsg.fadeOut(function(){ $(this).remove(); }); }, 3000);
714 }
715 } else {
716 var errorMessage = 'Could not add product to cart. Please try again.';
717 if (response.data && response.data.error_code === 'budget_too_low') {
718 var templateMessage = priceData.budget_too_low_message || 'Your selected budget is too low. Please increase the budget to at least {product_price}.';
719 var productPrice = response.data.product_price || '';
720 errorMessage = templateMessage.replace('{product_price}', productPrice);
721 } else if (response.data && response.data.message) {
722 errorMessage = response.data.message;
723 }
724
725 var errorMsg = $('<span class="king-addons-slider-notice error">' + errorMessage + '</span>');
726 customContainer.append(errorMsg);
727 setTimeout(function(){ errorMsg.fadeOut(function(){ $(this).remove(); }); }, 5000);
728 }
729 } else {
730 // Fallback to after button if custom container not found
731 notificationPosition = 'after_button';
732 }
733 break;
734
735 case 'after_button':
736 default:
737 // This is the original behavior - place notifications after the button
738 if (response.success) {
739 // console.log('[KA Slider] Product added to cart:', response.data);
740 // Trigger WooCommerce added_to_cart event for themes/plugins to hook into
741 $(document.body).trigger('added_to_cart', [response.fragments, response.cart_hash, $button]);
742
743 var successMsg = $('<span class="king-addons-slider-notice success">' + (response.data.message || 'Added to cart!') + '</span>');
744 successMsg.insertAfter($button);
745
746 if (response.data.cart_url) {
747 var viewCartText = (typeof king_addons_slider_vars !== 'undefined' && king_addons_slider_vars.view_cart_text)
748 ? king_addons_slider_vars.view_cart_text : 'View Cart';
749 var viewCartLink = $('<a href="' + response.data.cart_url + '" class="king-addons-slider-view-cart">' + viewCartText + '</a>');
750 viewCartLink.insertAfter(successMsg);
751
752 setTimeout(function(){
753 successMsg.fadeOut(function(){ $(this).remove(); });
754 viewCartLink.fadeOut(function(){ $(this).remove(); });
755 }, 5000);
756 } else {
757 setTimeout(function(){ successMsg.fadeOut(function(){ $(this).remove(); }); }, 3000);
758 }
759 } else {
760 var errorMessage = 'Could not add product to cart. Please try again.';
761 // console.error('[KA Slider] Error adding to cart:', response.data);
762
763 if (response.data && response.data.error_code === 'budget_too_low') {
764 var templateMessage = priceData.budget_too_low_message || 'Your selected budget is too low. Please increase the budget to at least {product_price}.';
765 var productPrice = response.data.product_price || '';
766 errorMessage = templateMessage.replace('{product_price}', productPrice);
767 } else if (response.data && response.data.message) {
768 errorMessage = response.data.message;
769 }
770
771 var errorMsg = $('<span class="king-addons-slider-notice error">' + errorMessage + '</span>');
772 errorMsg.insertAfter($button);
773 setTimeout(function(){ errorMsg.fadeOut(function(){ $(this).remove(); }); }, 5000);
774 }
775 break;
776 }
777 },
778 error: function(jqXHR, textStatus, errorThrown) {
779 $button.removeClass('loading');
780 // console.error('[KA Slider] AJAX Error:', textStatus, errorThrown);
781 // Show generic user-friendly error for AJAX failures
782 var errorMsg = $('<span class="king-addons-slider-notice error">An error occurred. Please try again later.</span>').insertAfter($button);
783 setTimeout(function(){ errorMsg.fadeOut(function(){ $(this).remove(); }); }, 5000);
784 }
785 });
786 });
787 }
788 }
789
790 // --- Elementor Hook --- (Handles both Editor and Frontend)
791 $(window).on('elementor/frontend/init', function() {
792 var initAction = function($scope) {
793 // Use $scope if provided (in editor), otherwise use document context (frontend)
794 var context = $scope ? $scope : $(document);
795
796 // Initialize Single Sliders
797 // Find only direct .king-addons-pricing-slider that are NOT .king-addons-pricing-sliders containers
798 context.find('.king-addons-pricing-slider:not(.king-addons-pricing-sliders)').each(function() {
799 var sliderInstance = $(this);
800 // Check if already initialized (e.g., by multiple init calls in editor)
801 if (!sliderInstance.data('ka-slider-initialized')) {
802 initSinglePricingSliderRevisited(sliderInstance);
803 sliderInstance.data('ka-slider-initialized', true);
804 } else {
805 // console.log('[KA Slider] Single slider already initialized, skipping:', sliderInstance[0]);
806 // Optionally re-trigger UI update if needed in editor on changes
807 var rangeInput = sliderInstance.find('.king-addons-pricing-slider__range');
808 if(rangeInput.length) {
809 sharedUpdateSliderUI(sliderInstance, parseFloat(rangeInput.val()));
810 }
811 }
812 });
813
814 // Initialize Multiple Sliders Containers
815 context.find('.king-addons-pricing-sliders').each(function() {
816 var slidersContainer = $(this);
817 if (!slidersContainer.data('ka-slider-initialized')) {
818 initMultiplePricingSlidersRevisited(slidersContainer);
819 slidersContainer.data('ka-slider-initialized', true);
820 } else {
821 // console.log('[KA Slider] Multi-slider container already initialized, skipping:', slidersContainer[0]);
822 // Optionally re-trigger UI update for all children
823 slidersContainer.find('.king-addons-single-slider').each(function() {
824 var singleSliderWrapper = $(this);
825 var rangeInput = singleSliderWrapper.find('.king-addons-pricing-slider__range');
826 if (rangeInput.length) {
827 sharedUpdateSliderUI(singleSliderWrapper, parseFloat(rangeInput.val()));
828 }
829 });
830 // And update combined display
831 // Need a way to call updateCombinedDisplayForMulti() here if instance already exists
832 // For now, just re-init might be simpler, or store the update function on the element data
833 }
834 });
835 };
836
837 // --- Elementor Editor Integration ---
838 if (typeof elementorFrontend !== 'undefined' && elementorFrontend.isEditMode()) {
839 elementorFrontend.hooks.addAction('frontend/element_ready/king-addons-pricing-slider.default', function($scope) {
840 // Use timeout to allow elements and data attributes to be fully rendered by Elementor
841 // console.log('[KA Slider] Elementor element_ready hook fired for scope:', $scope[0]);
842 setTimeout(function() {
843 // Clear initialized flag before re-init to handle widget updates
844 $scope.find('.king-addons-pricing-slider, .king-addons-pricing-sliders').removeData('ka-slider-initialized');
845 initAction($scope);
846 }, 150); // Slightly longer timeout for editor stability
847 });
848 } else {
849 // --- Frontend Initialization ---
850 // Run on document ready for frontend
851 $(document).ready(function() {
852 initAction();
853 });
854 }
855 });
856
857 })(jQuery);
858 // --- REMOVE OLD FUNCTIONS ---
859 /*
860 // Old function, replaced by initSinglePricingSliderRevisited
861 function initSinglePricingSlider(sliderWrapper) {
862 // ... implementation removed ...
863 }
864
865 // Old function, replaced by initMultiplePricingSlidersRevisited
866 function initMultiplePricingSliders(slidersContainer) {
867 // ... implementation removed ...
868 }
869 */