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
← All changes | includes/widgets/Pricing_Slider/script.js +897 -0 51.1.2 → 51.1.87 View file →
@@ -1,0 +1,897 @@
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 +*/