'use strict' const choiceTypeParsers2 = { 'string': v => v, 'integer': v => parseInt(v), }; function getChoiceValue(choiceElement) { const parser = choiceTypeParsers2[choiceElement.dataset.type] || choiceTypeParsers2['string']; return parser(choiceElement.value); } function getChoiceHiddenWhen(choiceElement) { // Get the data-hide_when attribute const hideWhenAttr = choiceElement.dataset.hide_when || choiceElement.getAttribute('data-hide_when'); if (!hideWhenAttr || hideWhenAttr === '') { return null; } // Parse the JSON string try { return JSON.parse(hideWhenAttr); } catch (e) { console.warn('Failed to parse data-hide_when for element:', choiceElement, e); return null; } } function getQuestionID (choiceElement) { const question_id = choiceElement.getAttribute('question_id'); if (!question_id) { return null; } return question_id; } function getAllChoices(source) { const choices = {}; const choiceElements = {}; // If source is provided, use it; otherwise find the currently visible entry let searchRoot = source; if (!searchRoot) { // Find the currently visible/selected product entry const selectedTypeElement = document.querySelector(".product-selection-option.selected"); if (selectedTypeElement) { document.querySelectorAll(".detail-inputs .entry").forEach((elem) => { if (elem.dataset.for == selectedTypeElement.dataset.for && !elem.classList.contains("hidden")) { searchRoot = elem; } }); } } // If we still don't have a search root, return empty if (!searchRoot) { return [choices, choiceElements]; } // Search within the visible entry, INCLUDING hidden questions searchRoot.querySelectorAll('.question [data-choice]').forEach(el => { const name = el.dataset.choice; if (!name) return; // safety // Store the actual DOM element choiceElements[name] = el; // live getter — so choices.randtype is ALWAYS up-to-date Object.defineProperty(choices, name, { get: () => getChoiceValue(el), enumerable: true, configurable: true }); }); return [choices, choiceElements]; } function getChoices(source) { const choices = {}; const choiceElements = {}; source.querySelectorAll('[data-choice]').forEach(el => { const name = el.dataset.choice; // Define a getter for the choice Object.defineProperty(choices, name, { get: () => getChoiceValue(el), enumerable: true }); // Store the element as well choiceElements[name] = el; }); return [choices, choiceElements]; } function getVarToUse(self) { const useVarWhenData = self.dataset.useVarWhen ? JSON.parse(self.dataset.useVarWhen) : {}; var VarToUse = null; outerLoop: for (let [key, value] of Object.entries(useVarWhenData)) { for (let [key2, value2] of Object.entries(value)) { for(let [key3, value3] of Object.entries(value2)) { const choiceElement = self.closest(".entry").querySelector(`select[data-choice="${key3}"]`); const typeValue = choiceElement.value; if (!choiceElement) { continue; } if (typeValue === value3) { VarToUse = key; break outerLoop; } } } } return VarToUse; } function assignHeight(choiceElements) { // Find the active nomH element - the one who is NOT fully hidden const heightVar = document.querySelector('[data-choice="_height"]'); if(heightVar) { let _heightElement = null; for (const element of Object.values(choiceElements)) { if (element.dataset.choice && element.dataset.choice.includes('_height')) { const question = element.closest('.question'); const isFullyHidden = question && question.classList.contains('fully_hidden_question'); const isHidden = question && question.classList.contains('hidden_question'); const isVisible = question && !isFullyHidden && !isHidden && question.offsetParent !== null; if (isVisible) { _heightElement = element; break; } } } if(_heightElement && heightVar) { heightVar.value = _heightElement.value; } } } function updateQuestions(event) { const source = event.target; const direction = source.dataset.direction; const [ choices, choiceElements ] = getChoices(source); const [ allChoices, allChoiceElements ] = getAllChoices(source); // Create a single object with all current choice values for easy lookup const settings = {}; for(let [key, entry] of Object.entries(allChoiceElements)) { //console.log(key, entry); const value = getChoiceValue(entry); settings[key] = value; } // Hide direction question based on product.show_direction_choice const entryElement = source.closest('.entry'); if (entryElement) { const showDirectionChoice = entryElement.dataset.showDirectionChoice; const directionSelect = entryElement.querySelector('select[data-choice="direction"]'); if (directionSelect && showDirectionChoice !== undefined) { const directionQuestion = directionSelect.closest('.question'); if (directionQuestion) { // Hide if show_direction_choice is false, show if true if (showDirectionChoice === 'false') { directionQuestion.classList.add("fully_hidden_question"); } else { directionQuestion.classList.remove("fully_hidden_question"); } } } } // Hide questions based on the data-hide_when attribute // Enable / disable add product button based on min/max values for(let [choiceKey, entry] of Object.entries(allChoiceElements)) { const hidden_when = getChoiceHiddenWhen(entry); const question_id = getQuestionID(entry); const question_elem = document.getElementById(question_id); // Skip if question element not found if (!question_elem) { //console.warn('Question element not found for question_id:', question_id); continue; } // Select active height value and set every height opbject as current height value var heightInput = question_elem.querySelector('input[data-choice*="_height"]'); if (heightInput && !question_elem.classList.contains('fully_hidden_question')) { let activeHeightValue = heightInput.value; for (const heightOption of Object.keys(allChoiceElements)) { if(heightOption.includes('_height')) { allChoiceElements[heightOption].value = activeHeightValue; } } } if (hidden_when !== null) { let shouldHide = false; // Track if ANY condition matches for(let condition of hidden_when) { let conditionMatches = false; // Check every key-value pair inside this condition OR-logic for (let [condKey, condValue] of Object.entries(condition)) { // Check if this key-value pair matches the current settings if (settings[condKey] === condValue) { conditionMatches = true; break; } } // If this condition matches, mark that we should hide if (conditionMatches) { shouldHide = true; break; } } // Apply the hide/show based on whether any condition matched if (shouldHide) { question_elem.classList.add("fully_hidden_question"); } else { question_elem.classList.remove("fully_hidden_question"); } } // Hide for all option choices if(entry.tagName == "SELECT") { // Skip processing dropdowns in hidden questions for performance const questionElem = entry.closest('.question'); if(questionElem && (questionElem.classList.contains('fully_hidden_question') || questionElem.classList.contains('hidden_question'))) { continue; // Skip this dropdown entirely } var hidden_options = 0; var total_options = 0; // list each option for (const option of entry.querySelectorAll("option")) { total_options++; // if the option has a hide_when if(option.dataset.hide_when && option.dataset.hide_when != "") { var hide = false; // Cache parsed JSON for performance (parse once, reuse on subsequent calls) if(!option._parsedHideWhen) { option._parsedHideWhen = JSON.parse(option.dataset.hide_when); } const hidden_json = option._parsedHideWhen; for(const entry_json of hidden_json) { // check the if the condition of the hide_when applies // direction is a special case if(direction) { if('direction' in entry_json) { if(entry_json['direction'] == direction) { hide = true; } delete entry_json.direction; } } else { if('direction' in entry_json) { if(choiceElements['direction'].value.endsWith(entry_json['direction'])) { hide = true; } delete entry_json.direction; } } var matched = 0; for(const key in entry_json) { const value = entry_json[key]; const choiceEl = choiceElements[key]; if(!choiceEl) continue; if(choiceEl.tagName == "SELECT") { if(choiceEl.value == value) { matched++; } } else if(choiceEl.tagName == "INPUT") { const test_val = choiceEl.value; if(value.startsWith("<=")) { if(test_val <= parseFloat(value.substring(2))) { matched++; } } else if(value.startsWith("<")) { if(test_val < parseFloat(value.substring(1))) { matched++; } } else if(value.startsWith(">=")) { if(test_val >= parseFloat(value.substring(2))) { matched++; } } else if(value.startsWith(">")) { if(test_val > parseFloat(value.substring(1))) { matched++; } } } } if(matched > 0 && matched == Object.keys(entry_json).length) { hide = true; } } // check if the item needs to be hidden if(hide) { option.classList.add("hidden_option"); hidden_options++; } else { option.classList.remove("hidden_option"); } } else { option.classList.remove("hidden_option"); } } if(total_options - hidden_options <= 1) { // TODO: all but one options are hidden, so hide the entire question // TODO: unhide the question if options are available again, but don't unhide if it needs to be hidden for other reasons } // check if the option that is selected is still visible and if not, select the first visible one. const options = [...entry.querySelectorAll("option")]; // Find all visible options const visibleOptions = options.filter( option => !option.classList.contains('hidden_option') ); if (visibleOptions.length === 0) { // No visible options: disable the select entry.closest(".question").classList.add("hidden_question") entry.disabled = true; } else if ( visibleOptions.length === 1 && entry.closest(".question").dataset.hide_with_one_option) { // One visible options and listed it should be hidden: disable the select entry.closest(".question").classList.add("hidden_question") entry.disabled = true; } else { // Ensure entry is enabled in case it was disabled previously entry.closest(".question").classList.remove("hidden_question") entry.disabled = false; const selectedOption = options[entry.selectedIndex]; // If selected option is hidden, change to the first visible one if (selectedOption.classList.contains('hidden_option')) { entry.value = visibleOptions[0].value; entry.dispatchEvent(new Event("change")) } } } } // Enable / disable add product button based on min/max values // Validate all number choices when anything changes if (typeof validateNumberChoice === 'function') { const entryElement = source ? source.closest('.entry') : null; const searchRoot = entryElement || document.querySelector(".product-selection-container"); if (searchRoot) { // Find all number inputs with data-choice attribute and validate only visible ones // Break out of loop if an error message is displayed const numberInputs = searchRoot.querySelectorAll('input[type="number"][data-choice]'); for (const input of numberInputs) { // Check if the input is visible (not in a hidden question or entry) const question = input.closest(".question"); const entry = input.closest(".entry"); // Skip if question or entry is hidden if (question && (question.classList.contains('fully_hidden_question') || question.classList.contains('hidden_question'))) { continue; } if (entry && entry.classList.contains('hidden')) { continue; } validateNumberChoice(input); // Check if an error message was displayed for this input const errorDiv = question?.querySelector(".error"); if (errorDiv && !errorDiv.classList.contains("hidden") && errorDiv.innerHTML.trim() !== "") { break; } } } } // Assign height after all visibility updates are complete assignHeight(allChoiceElements); } // // 315 = aansluitdiameter 250, 400 = aansluitdiameter 315 // const allowVorm = ((choices.diameter == 315 || choices.diameter == 400) && choices.plenum_type != ':none'); // if(choiceElements.vorm.closest('.question')) { // choiceElements.vorm.closest('.question').classList.toggle('hidden', !allowVorm); // }