PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.86
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.86
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 51.1.45 All 40 releases
← All changes | includes/widgets/Pricing_Slider/script.js +869 -0 51.1.2 → 51.1.86 View file →
@@ -1,0 +1,869 @@
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 +*/