PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.87
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.87
51.1.87 51.1.86 51.1.84 51.1.85 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 All 41 releases
king-addons / includes / widgets / Pricing_Slider / script.js

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

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