if (!window.ISVTurboflowResults) { window.ISVTurboflowResults = class ISVTurboflowResults { constructor(opts) { const localStorageUtil = new LocalStorageUtil({ storageKey: "isv-turboflow-result" }); const SELECTORS = { cardTmpl: "#taskCardStlyeTmpl", allTasksTmpl: "#allTasksTmpl", completedTasksTmpl: "#completedTasksTmpl", zeroTaskStateTmpl: "#zeroTaskStateTmpl", cardsContainer: ".isv-cards-container", stepContainer: ".step-container", allTasksContainer: ".allTasks-container", completedContainer: ".completed-container", btn_nextStage: ".stage-navigator .next-btn a", btn_prevStage: ".stage-navigator .prev-btn a", categoryDropdownContainer: ".dropdown", categoryDropdownListContainer: ".dropdown-menu", stepper: ".m365-stepper", clickGroup: "[data-mount='click-group']", tab: "[data-mount='tab']", cardSwitch: ".custom-switch-input", card: ".task-card", activeCard: ".active .card", tabGroup: ".task-tab-group", zeroTasksStateContainer: ".zero-task-state-container", stageNavigator: ".stage-navigator", solutionWizard: "[data-wizard-id]", srElement: ".sr-task-completion-feedback", mainHeading: ".main-heading", featureBadge: ".feature .badge", stepNavLinks: ".step-nav-links", collapse: "[data-identifier='collapse']", collapseControls: "[data-identifier='collapse-controls']", individualStepNavLink: ".step-nav-links .cta", accordion: ".accordion", dropdownItems: ".dropdown .dropdown-item", solutionWizardContainer: ".oc-solution-wizard-container", dropdownToggle: ".dropdown .dropdown-toggle", stepperNavHeader: ".stepnav-header", stepNav: ".stepnav", stepperContainer: ".stepper-container" }; const EVENTS = { ON_NEXT: "onNext", ON_PREV: "onPrev", ON_STARTOVER: "onStartOver", ON_CLICK: "click", ON_CHANGE: "change", ON_TAB_SHOWN: "onShown", MWF_INITIALIZED: "mwfInitialized", M365_INITIALIZED: "m365ExtInitialized", CHANGE: "change", RESULTS_SHOWN: "resultsShown" }; let CONFIG = { renderAttributes: [ { renderKey: "id", apiKey: "id" }, { renderKey: "appIcon", apiKey: "imageUrl" }, { renderKey: "imageAltText", apiKey: "imageAltText" }, { renderKey: "appTitle", apiKey: "title" }, { renderKey: "appDesc", apiKey: "description" }, { renderKey: "url", apiKey: "url" }, { renderKey: "ctaLabel", apiKey: "ctaLabel" }, { renderKey: "ctaAriaLabel", apiKey: "ctaAriaLabel" }, { renderKey: "displayLabel", apiKey: "displayLabel" }, { renderKey: "helpText", apiKey: "helpText" }, ], locale: document.documentElement.lang, completedTabActive: false, activeTab: "allTasks", isPageLoad: true, analytics: window.telemetry && window.telemetry.webAnalyticsPlugin, }; const STRINGS = { card: "CARD", completed: "COMPLETED", allTasks: "ALL_TASKS", zeroTaskState: "ZERO_STATE", STAGE_DISCOVER: "DISCOVER", NEXT_STAGE: "NEXT_STAGE", PREV_STAGE: "PREV_STAGE", localStorageKeys: { currentStage: "currentStage", ISV_data: "ISV_data", completed: "completed", history: "history", solutionwizardresult: "solutionwizardresult", evaluatedStep: "evaluatedStep", evaluatedStage: "evaluatedStage", history: "history", }, ARIA_DISABLED: "aria-disabled", TAB_INDEX: "tabindex", EMPTY: "", SOLUTION_WIZARD_RESULT: "solutionwizardresult", IMAGE_ALT_TEXT: "imageAltText", ARIA_HIDDEN: "aria-hidden", TRANSITION_END: "transitionend" }; let MAPS = { jsonStore: opts.jsonStore, allTasksMap: [], currentStage: { id: STRINGS.EMPTY, stage: STRINGS.EMPTY }, selectedProducts: ["General"], productQuestions: opts.productMapping, stepIdentifier: opts.stepIdentifier, completedTasksStore: localStorageUtil.getItem(STRINGS.localStorageKeys.completed) || [], defaultBehaviorId: 0, cardTitleBehaviorId: 14, resultsetContainerBehaviourId: 22, stepToShowId: "", cardCompletedBehaviorId: 4, cardIncompleteBehaviorId: 5 }; const CSS_CLASSES = { displayNone: "d-none", stepCompleted: "step-completed", completed: "completed", taskComplete: "task-complete", taskInComplete: "task-complete", disabled: "disabled", allTasks: "allTasks", displayXlBlock: "d-xl-block", stickyStepper: "sticky-stepper" }; window.location.hash = STRINGS.EMPTY; let ELEMENTS = {}; /** * setup Elements and Config on page load */ const _setupElements = (wrapper) => { ELEMENTS = { ...ELEMENTS, wrapper: wrapper, cardsContainer: wrapper.querySelector(SELECTORS.cardsContainer), categoryDropdownContainer: wrapper.querySelector(SELECTORS.categoryDropdownContainer), categoryDropdownListContainer: wrapper.querySelector(SELECTORS.categoryDropdownListContainer), nextButton: wrapper.querySelector(SELECTORS.btn_nextStage), prevButton: wrapper.querySelector(SELECTORS.btn_prevStage), completedContainer: wrapper.querySelector(SELECTORS.completedContainer), allTasksContainer: wrapper.querySelector(SELECTORS.allTasksContainer), wizardContainer: wrapper.closest(SELECTORS.solutionWizardContainer), dropdownToggleElement: wrapper.querySelector(SELECTORS.dropdownToggle), stepperNavLinkHeader: wrapper.querySelector(SELECTORS.stepperNavHeader), stepNav: wrapper.querySelector(SELECTORS.stepNav), stepNavLinks: wrapper.querySelector(SELECTORS.stepNavLinks) }; ELEMENTS.stepperContainer = ELEMENTS.wrapper.querySelector(SELECTORS.stepperContainer); ELEMENTS.featureTitleElement = ELEMENTS.wizardContainer?.querySelector(SELECTORS.featureBadge); ELEMENTS.featureTitleText = ELEMENTS.featureTitleElement?.textContent; ELEMENTS.dropdownToggleElementText = ELEMENTS.dropdownToggleElement?.textContent; CONFIG.wizardId = ELEMENTS.wizardContainer?.firstElementChild.dataset.wizardId; CONFIG.compId = wrapper.dataset.compid; CONFIG.cardTmpl = _getTmplFromDom(STRINGS.card); CONFIG.allTasksTmpl = _getTmplFromDom(STRINGS.allTasks); CONFIG.completedTasksTmpl = _getTmplFromDom(STRINGS.completed) CONFIG.zeroTaskStateTmpl = _getTmplFromDom(STRINGS.zeroTaskState); CONFIG.match_lg = window.matchMedia("(min-width: 1400px)"); this.stepper = new window.m365.ProgressStepperExtension({ el: wrapper.querySelector(SELECTORS.stepper), }); this.stepper.el.addEventListener(EVENTS.ON_NEXT, _stageSelectionHandler.bind(this, null)); this.stepper.el.addEventListener(EVENTS.ON_PREV, _stageSelectionHandler.bind(this, null)); this.stepper.el.addEventListener(EVENTS.ON_STARTOVER, _startOverHandler); ELEMENTS.nextButton.addEventListener(EVENTS.ON_CLICK, _stageSelectionHandler.bind(this, STRINGS.NEXT_STAGE)); ELEMENTS.prevButton.addEventListener(EVENTS.ON_CLICK, _stageSelectionHandler.bind(this, STRINGS.PREV_STAGE)); _enableDisableButtons.call(this); _generateTaskArray(); }; /** * Filter the tasks based on the selected product if it is a product question */ const _filterProductCategories = (history) => { MAPS.productQuestions.forEach((productQuestion) => { const filteredQuestion = history.find( (historyValue) => historyValue.questionIndex === productQuestion.questionIndex ); if (filteredQuestion) { filteredQuestion.answers.forEach((filteredAnswerIndex) => { let productName = productQuestion.answers[filteredAnswerIndex]?.value; if (productName && !MAPS.selectedProducts.includes(productName)) { const allproducts = productName.split(",").map(product => product.trim()) MAPS.selectedProducts.push(...allproducts); } }); } }); }; /** * check if wizard stored in localstorage is different from current wizard */ const _isWizardLocalStorageChanged = () => { const resultSetWizardValue = localStorageUtil.getItem(CONFIG.wizardId); const wizardValue = localStorage.getItem(CONFIG.wizardId).toString(); return resultSetWizardValue !== wizardValue; }; /** * on component load get step from wizard localstorage and render cards */ const _onComponentLoad = (checkHistory) => { this.wizardLocalStorageChanged = _isWizardLocalStorageChanged(); if (this.wizardLocalStorageChanged || checkHistory) { localStorageUtil.setItem(CONFIG.wizardId, localStorage.getItem(CONFIG.wizardId).toString()); const history = wizardLocalStorage.getItem(STRINGS.localStorageKeys.history); if (history) { let lastQuestion = history[history.length - 1]; let filteredQuestion = MAPS.stepIdentifier.find( (ques) => Number(ques.questionIndex) === lastQuestion.questionIndex ); if (filteredQuestion?.chooseStepFromQuestion) { lastQuestion = history.find(value => value.questionIndex === Number(filteredQuestion.chooseStepFromQuestion)); filteredQuestion = MAPS.stepIdentifier.find((ques) => Number(ques.questionIndex) === lastQuestion.questionIndex); } const step = filteredQuestion?.answers?.find((ans) => Number(ans.id) === lastQuestion.answers[0])?.step; _setEvaluatedStepAndStage(step, _filterStageForStep(step)?.key); this.wizardLocalStorageChanged && localStorageUtil.setItem(STRINGS.localStorageKeys.currentStage, _filterStageForStep(step)?.key); _filterProductCategories(history); } } _fireTelemetryEvent(MAPS.resultsetContainerBehaviourId, { issuccess: true }); _updateStageInFeatureComponent(); _beginCardRender(); }; /** * update feature heading on stage change */ const _updateStageInFeatureComponent = () => { if (ELEMENTS.featureTitleElement) { const titleContent = ELEMENTS.featureTitleText.replace("{{$stage}}", _getStageDisplayLabel()); ELEMENTS.featureTitleElement.textContent = titleContent; ELEMENTS.featureTitleElement.style.setProperty("visibility", "visible"); } }; /** * Update stepper to indicate the current/active stage */ const _updateStepperOnStageChange = () => { localStorageUtil.setItem(STRINGS.localStorageKeys.currentStage, MAPS.currentStage.stage || STRINGS.EMPTY); this.stepper.el.dataset.activeStep = MAPS.currentStage.id; this.stepper.update(); _updateStageInFeatureComponent(); }; /** * Handles the stage selection based on buttonType and event * Update the stepper stage and render cards * @param {string} buttonType * @param {Event} e */ const _stageSelectionHandler = (buttonType, e) => { CONFIG.isPageLoad = false; if ((buttonType && buttonType === STRINGS.NEXT_STAGE) || (e.type === EVENTS.ON_NEXT)) { MAPS.currentStage.id += 1; } else { MAPS.currentStage.id -= 1; } this.stepper.el.dataset.activeStep = MAPS.currentStage.id; this.stepper.update(); const stage = MAPS.jsonStore.find((stage) => stage.id === MAPS.currentStage.id); if (stage) { MAPS.currentStage.stage = stage.key; _updateStepperOnStageChange(); _beginCardRender(); } _enableDisableButtons.call(this); this.tabInstances.find((tab) => tab.el === this.allTaskTab)?.show(); if (buttonType == STRINGS.NEXT_STAGE || buttonType == STRINGS.PREV_STAGE) { _scrollToElement(SELECTORS.tabGroup); } }; /** * start over button functionality */ const _startOverHandler = () => { wizardLocalStorage.clear(); localStorageUtil.clear(); localStorage.removeItem(`${STRINGS.SOLUTION_WIZARD_RESULT}-${CONFIG.wizardId}`); window.location.reload(); }; /** * Generate an array of all tasks by iterating through stage, steps and tasks * Adding tasks to the MAPS.allTasksMap array */ const _generateTaskArray = () => { MAPS.jsonStore.forEach((stage) => { stage.steps.forEach((step) => { step.tasks.forEach((task) => { MAPS.allTasksMap.push(task); }); }); }); }; /** * Enables or disables the navigation buttons based on the current state of the stepper. */ function _enableDisableButtons() { ELEMENTS.nextButton.classList.remove(CSS_CLASSES.disabled); ELEMENTS.nextButton.removeAttribute(STRINGS.ARIA_DISABLED); ELEMENTS.nextButton.removeAttribute(STRINGS.TAB_INDEX); ELEMENTS.prevButton.classList.remove(CSS_CLASSES.disabled); ELEMENTS.prevButton.removeAttribute(STRINGS.ARIA_DISABLED); ELEMENTS.prevButton.removeAttribute(STRINGS.TAB_INDEX); if (this.stepper.previousButton.classList.contains(CSS_CLASSES.disabled)) { ELEMENTS.prevButton.classList.add(CSS_CLASSES.disabled); ELEMENTS.prevButton.setAttribute(STRINGS.ARIA_DISABLED, "true"); ELEMENTS.prevButton.setAttribute(STRINGS.TAB_INDEX, "-1"); } else if (this.stepper.nextButton.classList.contains(CSS_CLASSES.disabled)) { ELEMENTS.nextButton.classList.add(CSS_CLASSES.disabled); ELEMENTS.nextButton.setAttribute(STRINGS.ARIA_DISABLED, "true"); ELEMENTS.nextButton.setAttribute(STRINGS.TAB_INDEX, "-1"); } }; /** * Returns the html markup in string format for the selected template * @param {string} whichTmpl */ const _getTmplFromDom = (whichTmpl) => { let tmplDOMSelector = STRINGS.EMPTY; switch (whichTmpl) { case STRINGS.card: tmplDOMSelector = `${SELECTORS.cardTmpl}_${CONFIG.compId}`; break; case STRINGS.allTasks: tmplDOMSelector = `${SELECTORS.allTasksTmpl}_${CONFIG.compId}`; break; case STRINGS.zeroTaskState: tmplDOMSelector = `${SELECTORS.zeroTaskStateTmpl}_${CONFIG.compId}`; break; case STRINGS.completed: tmplDOMSelector = `${SELECTORS.completedTasksTmpl}_${CONFIG.compId}`; break; } return document.querySelector(tmplDOMSelector).innerHTML.toString().trim(); }; /** * Parses the template against the data object and returns the markup with the substituted values. * It also takes care of removing the DOM elements where the data doesn't exist */ const _evaluateTmpl = (tmpl, cardObj) => { const keys = Object.keys(cardObj); let $evaluatedTmpl = $(tmpl); // modify the template by removing the elements for which the card object does not have a valid value. keys.forEach((key) => { const keyValue = cardObj[key]; if (!keyValue || keyValue == STRINGS.EMPTY || keyValue.length == 0) { $evaluatedTmpl.find(`[data-elementmarker="${key}"]`).remove(); } else if (key == "appIcon") { $evaluatedTmpl.find("img").prop("src", keyValue); } else if (key == "url") { $evaluatedTmpl.find("a[data-elementmarker=url]").attr("href", keyValue); } }); // populate the modified template with the values from card object. let evaluatedTmpl = $evaluatedTmpl.prop("outerHTML"); keys.forEach((key) => { evaluatedTmpl = replaceTemplateValues(evaluatedTmpl, key, cardObj); }); evaluatedTmpl = evaluatedTmpl.replace("(#=cardCheckboxBehaviorId#)", CONFIG.completedTabActive ? MAPS.cardCompletedBehaviorId : MAPS.cardIncompleteBehaviorId); if (cardObj.appTitle) { _fireTelemetryEvent(MAPS.cardTitleBehaviorId, { hn: cardObj.appTitle }); } return evaluatedTmpl; }; /** * fires telemetry */ const _fireTelemetryEvent = (behavior = MAPS.defaultBehaviorId, contentTags = {}) => { let overrides = { actionType: "A", behavior: behavior, contentTags: contentTags } try { CONFIG.analytics?.capturePageAction(null, overrides); } catch (error) { console.error(error); } }; /** * replaces template keys with values */ const replaceTemplateValues = (evaluatedTmpl, key, cardObj) => { return evaluatedTmpl.replaceAll(`(#=${key}#)`, cardObj[key] || STRINGS.EMPTY); }; /** * initialize click group */ const _initializeMWFClickGroup = (wrapper) => { ELEMENTS.cgElements = []; wrapper.querySelectorAll(SELECTORS.clickGroup).forEach((cgEl) => { ELEMENTS.cgElements.push( new mwf.ClickGroup({ el: cgEl, }) ); }); }; /** * Iterates through the array of objects and returns the HTML markup based on the provided template & data array */ const _renderTmpl = (tmpl, rendererObject) => { return rendererObject .map((item) => { return _evaluateTmpl(tmpl, item); }) .join(" "); }; /** * Displays the zero task state by rendering the template into render container. * @param {HTMLElement} renderContainer */ const _showZeroTaskState = (renderContainer) => { renderContainer.html($(CONFIG.zeroTaskStateTmpl)); }; /** * Render the task cards inside the renderContainer * It calls _showZeroTaskState function if there are no tasks in rendererObject * @param {HTMLElement} wrapper * @param {Array} rendererObject */ const _renderTaskTemplate = (wrapper, rendererObject) => { const renderContainer = $( CONFIG.completedTabActive ? ELEMENTS.completedContainer : ELEMENTS.allTasksContainer ); if (rendererObject.length == 0 || (rendererObject.length == 1 && rendererObject[0].tasks.length == 0)) { _showZeroTaskState(renderContainer); return; } let completedTaksTemplate; const allTasksTemplate = $(CONFIG.allTasksTmpl); if (!CONFIG.completedTabActive) { let accordionTmpl = allTasksTemplate.find(SELECTORS.stepContainer).prop("outerHTML").toString(); allTasksTemplate.find(SELECTORS.accordion).html(_renderTmpl(accordionTmpl, rendererObject)); } else { completedTaksTemplate = _renderTmpl(CONFIG.completedTasksTmpl, rendererObject); } $(wrapper).find(`${SELECTORS.completedContainer}, ${SELECTORS.allTasksContainer}`).html(STRINGS.EMPTY); let $finaltasksMarkup = CONFIG.completedTabActive ? $(completedTaksTemplate) : allTasksTemplate; rendererObject.forEach((obj, index) => { $finaltasksMarkup.find(SELECTORS.cardsContainer).eq(index).html(_renderTmpl(CONFIG.cardTmpl, obj.tasks)); }); renderContainer.html($finaltasksMarkup); _initializeMWFClickGroup(wrapper); if (!CONFIG.completedTabActive) { _initializeAccordionAndControls.call(this); } }; /** * Initialize accordion collapses and collapse controls */ const _initializeAccordionAndControls = () => { const container = ELEMENTS.wrapper.querySelector(SELECTORS.allTasksContainer); const collapseControls = container.querySelectorAll(SELECTORS.collapseControls); const collapses = container.querySelectorAll(SELECTORS.collapse); collapses.forEach(collapse => { const opts = {}; opts.el = collapse; new mwf.Collapse(opts); }); MAPS.collapseInstances = window.mwf.Collapse.getInstances(); const filteredCollapses = MAPS.collapseInstances.filter(collapseInst => Array.from(collapses).some(collapse => collapse === collapseInst.triggerElement)); filteredCollapses.forEach(collapse => { collapse.triggerElement.addEventListener(EVENTS.ON_CLICK, () => { const hideEl = MAPS.collapseInstances.filter(inst => inst.triggerElement !== collapse.triggerElement); hideEl.forEach(el => { el.hide(); }); }); }); collapseControls.forEach(controls => { const opts = {}; opts.el = controls; opts.collapses = filteredCollapses; new mwf.CollapseControls(opts); }); }; /** * Scroll the page to the target element. */ const _scrollToElement = (scrollTarget) => { var element = document.querySelector(scrollTarget); if (element) { element.scrollIntoView({ behavior: 'smooth' }); } }; /** * get stage from local storage */ const _readStageFromLocalStorage = () => { return localStorageUtil.getItem(STRINGS.localStorageKeys.currentStage) || STRINGS.STAGE_DISCOVER; }; /** * get step from local storage */ const _readStepFromLocalStorage = () => { return localStorageUtil.getItem(STRINGS.localStorageKeys.evaluatedStep) || "step_1"; }; /** * generate unique category ID based on label */ const _buildUniqueId = (displayLabel, id) => { return `${displayLabel}_${id}`; }; /** * set evaluated stage and step to local storage */ const _setEvaluatedStepAndStage = (step, stage) => { localStorageUtil.setItem(STRINGS.localStorageKeys.evaluatedStep, step); localStorageUtil.setItem(STRINGS.localStorageKeys.evaluatedStage, stage); }; /** * filter stage based on selected step */ const _filterStageForStep = (selectedStep) => { if (selectedStep) { const stage = MAPS.jsonStore.find((stage) => stage.steps.find((step) => step.id === Number(selectedStep.split("_").pop())) ); return stage; } return null; }; /** * Generates an array of tasks to be rendered based on the current stage and selected step * Retrieves the list of completed tasks from local storage * If completed tab is not active - build the taskToRender array by including required tasks and not completed tasks * If completed tab is active - build the taskToRender array by including completed tasks * @param {HTMLElement} stage * @param {HTMLElement} selectedStep * @returns {Array} taskToRender */ const generateTasksToRender = (stage, selectedStep) => { let taskToRender = []; const completedTasks = localStorageUtil.getItem(STRINGS.localStorageKeys.completed); if (!CONFIG.completedTabActive) { stage.steps.forEach((step) => { const jsonStr = { id: step.id, tasks: _buildAllTasksMap(step, completedTasks), }; if (jsonStr.tasks.length !== 0) { jsonStr.displayLabel = step.displayLabel; jsonStr.displayLabelShort = step.displayLabelShort; jsonStr.cardCategoryID = _buildUniqueId("cardCategory", step.id); jsonStr.shortCardCategoryID = _buildUniqueId("cardCategoryBtnId", step.id); jsonStr.helpText = step.helpText; taskToRender.push(jsonStr); } if (selectedStep && step.id === Number(selectedStep.split("_").pop())) { MAPS.stepToShowId = `${jsonStr.shortCardCategoryID}_nav`; } }); } else if (completedTasks) { const jsonStr = { id: null, displayLabel: STRINGS.EMPTY, tasks: _buildCompletedTasksMap(completedTasks), }; if (jsonStr.tasks.length !== 0) { taskToRender.push(jsonStr); } } return taskToRender; }; /** * Filters the tasks array to return the tasks which are not completed and not required * @param {Object} step * @param {Array} completedTasks */ const _buildAllTasksMap = (step, completedTasks) => { return step.tasks .filter( (task) => !completedTasks?.includes(task.id.toString()) && MAPS.selectedProducts.includes(task.product) ) .map((task) => _buildTaskMapForRendering(task)); }; /** * Builds an array of completed tasks. * @param {Array} completedTasks */ const _buildCompletedTasksMap = (completedTasks) => { return completedTasks?.reverse().map((task) => { const filteredTask = MAPS.allTasksMap.find((eachTask) => eachTask.id.toString() === task); return _buildTaskMapForRendering(filteredTask); }); }; /** * Builds the array which will be used for rendering. The object keys will be updated to match the template keys. */ const _buildTaskMapForRendering = (sourceObj) => { const taskMap = []; CONFIG.renderAttributes.forEach((attr) => { if (sourceObj[attr.apiKey] || attr.apiKey === STRINGS.IMAGE_ALT_TEXT) { taskMap[attr.renderKey] = sourceObj[attr.apiKey]; } }); return taskMap; }; /** * Builds a render object containing the tasks * Based on selectedstep and stage calls generateTasksToRender function. */ const _buildRenderObj = () => { const selectedStep = CONFIG.isPageLoad && this.wizardLocalStorageChanged ? _readStepFromLocalStorage() : null; const selectedStage = selectedStep ? _filterStageForStep(selectedStep) : _readStageFromLocalStorage(); const stage = selectedStep ? selectedStage : MAPS.jsonStore.filter((stage) => stage.key === selectedStage)[0]; MAPS.currentStage.id = stage.id; MAPS.currentStage.stage = stage.key; return generateTasksToRender(stage, selectedStep); }; /** * get current stage display label */ const _getStageDisplayLabel = () => { const selectedStage = _readStageFromLocalStorage(); return MAPS.jsonStore.find(stage => stage.key === selectedStage)?.displayLabel; }; /** * Populates the category dropdown list using the step labels from the rendererObject. */ const _populateDropdownAndStepperNav = (rendererObject) => { const updatedLabel = ELEMENTS.dropdownToggleElementText?.replace("{{$stage}}", _getStageDisplayLabel()); ELEMENTS.dropdownToggleElement.textContent = updatedLabel; ELEMENTS.stepperNavLinkHeader.textContent = updatedLabel; ELEMENTS.categoryDropdownListContainer.innerHTML = rendererObject .map((eachStep) => { const cardCategoryID = _buildUniqueId("cardCategoryBtnId", eachStep.id); return `
  • ${eachStep.displayLabelShort}
  • `; }) .join(" "); ELEMENTS.stepNavLinks.innerHTML = rendererObject .map((eachStep) => { const cardCategoryID = _buildUniqueId("cardCategoryBtnId", eachStep.id); return `
  • ${eachStep.displayLabelShort}
  • `; }) .join(" "); }; /** * Takes care of displaying the category dropdown only when the active tab is "All tasks". The category dropdown is hidden for the completed task tab. * @param {event} e */ const _tabShowHandler = (e) => { if ( e.target.classList.contains(CSS_CLASSES.completed) || e.target.parentElement.classList.contains(CSS_CLASSES.completed) ) { CONFIG.completedTabActive = true; ELEMENTS.categoryDropdownContainer.classList.add(CSS_CLASSES.displayNone); } else { CONFIG.completedTabActive = false; ELEMENTS.categoryDropdownContainer.classList.remove(CSS_CLASSES.displayNone); } _beginCardRender(); }; /** * Attaches event handlers to the tabs * It attaches an event listener for 'on_shown' event using the _tabShowHandler function. */ const _attachTabEventHandlers = (wrapper) => { this.tabInstances = window.mwf.Tab.getInstances(); this.allTabs = wrapper.querySelectorAll(SELECTORS.tab); this.allTaskTab = Array.from(this.allTabs).find((tab) => tab.classList.contains(CSS_CLASSES.allTasks)); this.allTabs.forEach((tab) => { const filteredInstance = this.tabInstances.find((instance) => instance.el === tab); if (filteredInstance) { filteredInstance.el.addEventListener(EVENTS.ON_TAB_SHOWN, _tabShowHandler.bind(this)); } }); }; /** * Updates the local storage object to store the completed tasks on toggle of the "Mark as Complete" checkbox */ const _toggleTaskCompletion = (targetEl, isChecked = false) => { const cardId = targetEl.id; if (isChecked && !MAPS.completedTasksStore.includes(cardId)) { MAPS.completedTasksStore.push(cardId); } else { MAPS.completedTasksStore = MAPS.completedTasksStore.filter((id) => id != cardId); } localStorageUtil.setItem(STRINGS.localStorageKeys.completed, MAPS.completedTasksStore); }; /** * Modifies telemetry behaviour id of card checkbox on toggle */ const _toggleTelemetryBehavior = (targetEl, isChecked) => { targetEl.dataset.biBhvr = isChecked ? MAPS.cardCompletedBehaviorId : MAPS.cardIncompleteBehaviorId; } /** * Event handler for handling task completion events * Updates the screen reader element to provide feedback on task completion status. * Handles the animation end event * Calls the _showZeroTaskState function to display a feedback message if no tasks are visible in the container. */ const _taskCompletionHandler = (e) => { const targetEl = e.target; const isChecked = targetEl.checked; const cardEl = targetEl.closest(SELECTORS.card); const cardsContainer = cardEl.closest(SELECTORS.cardsContainer); const stepContainer = cardsContainer.closest(SELECTORS.stepContainer); const renderContainer = $( CONFIG.completedTabActive ? ELEMENTS.completedContainer : ELEMENTS.allTasksContainer ); const srElement = cardEl.querySelector(SELECTORS.srElement); if (srElement && isChecked) { srElement.textContent = srElement.dataset.taskCompletedFeedback; } else if (srElement) { srElement.textContent = srElement.dataset.taskIncompletedFeedback; } cardEl.classList.toggle(CSS_CLASSES.taskComplete); _toggleTaskCompletion(targetEl, isChecked); _toggleTelemetryBehavior(targetEl, isChecked); const hasVisibleChild = Array.from(cardsContainer.children).some( (child) => !child.classList.contains(CSS_CLASSES.taskComplete) ); stepContainer.classList.toggle(CSS_CLASSES.stepCompleted, !hasVisibleChild); if (!hasVisibleChild) { stepContainer.querySelector(SELECTORS.mainHeading)?.setAttribute(STRINGS.ARIA_HIDDEN, "true"); const stepNavLinks = Array.from(ELEMENTS.wrapper.querySelectorAll(SELECTORS.individualStepNavLink))?.filter(link => !link.classList.contains(CSS_CLASSES.displayNone)); const triggerElement = stepContainer.querySelector(SELECTORS.collapse); const collapseInst = MAPS.collapseInstances?.find(inst => inst.triggerElement === triggerElement) collapseInst?.hide(); stepNavLinks.forEach(link => { if (triggerElement && link.dataset.targetId === triggerElement.id) { link.classList.add(CSS_CLASSES.displayNone); } }) const dropdowns = ELEMENTS.wrapper.querySelectorAll(SELECTORS.dropdownItems); dropdowns.forEach(dropdown => { if (triggerElement && dropdown.dataset.targetId === triggerElement.id) { dropdown.classList.add(CSS_CLASSES.displayNone); } }); if (stepNavLinks && stepNavLinks.length === 1) { _hideDropdown(); _hideStepNavLinks(); } } const stepContainers = Array.from(renderContainer.find(SELECTORS.stepContainer)); const value = stepContainers.some((container) => !container.classList.contains(CSS_CLASSES.stepCompleted)); const transitionEndHandler = (value) => { if (!value) { _showZeroTaskState(renderContainer); } _updateCardForA11y(cardEl, targetEl); cardEl.removeEventListener(STRINGS.TRANSITION_END, transitionEndHandler); } cardEl.addEventListener(STRINGS.TRANSITION_END, transitionEndHandler.bind(this, value, cardEl)); }; /** * update card details for ally support */ const _updateCardForA11y = (cardEl, targetEl) => { cardEl.setAttribute(STRINGS.ARIA_HIDDEN, "true"); targetEl.setAttribute(STRINGS.TAB_INDEX, "-1"); cardEl.querySelector("a")?.setAttribute(STRINGS.TAB_INDEX, "-1"); }; /** * Binds event handlers to the task completion checkboxes * Attaches an event listener for the ON_CHANGE event using the _taskCompletionHandler function. */ const _bindCardSwitchEvents = (wrapper) => { const allCards = wrapper.querySelectorAll(SELECTORS.activeCard); allCards.forEach((card) => { const cardInput = card.querySelectorAll(SELECTORS.cardSwitch); cardInput.forEach((input) => { input.addEventListener(EVENTS.ON_CHANGE, _taskCompletionHandler.bind(this)); }); }); }; /** * Initiates the card rendering process. * Render the cards based on the renderedObject and wrapper by using _renderTaskTemplate function * Update the stepper to indicate current/active step * Based on the switch checkbox status it calls _populateDropdownAndStepperNav function. */ const _beginCardRender = () => { const rendererObject = _buildRenderObj(); _renderTaskTemplate(ELEMENTS.wrapper, rendererObject); const firstCollapse = ELEMENTS.wrapper.querySelector(SELECTORS.collapse); const firstCollapseInst = MAPS.collapseInstances?.find(inst => inst.triggerElement === firstCollapse); if (CONFIG.isPageLoad) { _updateStepperOnStageChange(); _enableDisableButtons.call(this); } if (!CONFIG.completedTabActive && CONFIG.isPageLoad && this.wizardLocalStorageChanged) { const collapseInst = MAPS.collapseInstances?.find(inst => inst.triggerElement.id === MAPS.stepToShowId); collapseInst?.show(); CONFIG.isPageLoad = false; } else if (!CONFIG.completedTabActive && CONFIG.isPageLoad) { const stepToShowOnLoad = _readStepFromLocalStorage(); const stepIdToShow = `cardCategoryBtnId_${stepToShowOnLoad}_nav`; const collapseInst = MAPS.collapseInstances?.find(inst => inst.triggerElement.id === stepIdToShow); collapseInst ? collapseInst.show() : firstCollapseInst.show(); } else { if (firstCollapse) { firstCollapseInst?.show(); } } const completedContainer = ELEMENTS.wrapper.querySelector(SELECTORS.completedContainer); const cardInput = completedContainer.querySelectorAll(SELECTORS.cardSwitch); cardInput.forEach((input) => { input.checked = true; }); !CONFIG.completedTabActive && _populateDropdownAndStepperNav(rendererObject); _bindCardSwitchEvents.call(this, ELEMENTS.wrapper); ELEMENTS.nextButton.closest(SELECTORS.stageNavigator)?.removeAttribute("style"); if (!CONFIG.completedTabActive) { _addDropdownAndStepNavEvents.call(this); } _showHideNavLinks(rendererObject); }; /** * show/hide nav and dropdown */ const _showHideNavLinks = (rendererObject) => { if (CONFIG.completedTabActive || rendererObject.length == 0 || (rendererObject.length == 1 && rendererObject[0].tasks.length == 0)) { _hideStepNavLinks(); _hideDropdown(); } else { _showStepNavLinks(); _showDropdown(); } }; /** * hides step nav */ const _hideStepNavLinks = () => { ELEMENTS.stepNav.classList.remove(CSS_CLASSES.displayXlBlock); }; /** * shows step nav */ const _showStepNavLinks = () => { ELEMENTS.stepNav.classList.add(CSS_CLASSES.displayXlBlock); }; /** * hides dropdown */ const _hideDropdown = () => { ELEMENTS.categoryDropdownContainer.classList.add(CSS_CLASSES.displayNone); }; /** * shows dropdown */ const _showDropdown = () => { ELEMENTS.categoryDropdownContainer.classList.remove(CSS_CLASSES.displayNone); }; /** * add click events to dropdown links and step nav links */ const _addDropdownAndStepNavEvents = () => { const stepNavLinks = ELEMENTS.wrapper.querySelectorAll(SELECTORS.individualStepNavLink); stepNavLinks.forEach(nav => { nav.addEventListener(EVENTS.ON_CLICK, () => { MAPS.collapseInstances.forEach(collapse => { if (collapse.triggerElement.id === nav.dataset.targetId) { collapse.show(); } else { collapse.hide(); } }); }); }); const dropdowns = ELEMENTS.wrapper.querySelectorAll(SELECTORS.dropdownItems); dropdowns.forEach(dropdown => { dropdown.addEventListener(EVENTS.ON_CLICK, () => { MAPS.collapseInstances.forEach(collapse => { if (collapse.triggerElement.id === dropdown.dataset.targetId) { collapse.show(); } else { collapse.hide(); } }); }); }); }; this._beginCardRender = () => { _beginCardRender(); }; const _onResultsShown = (checkHistory = false) => { if (localStorage.getItem(`${STRINGS.SOLUTION_WIZARD_RESULT}-${CONFIG.wizardId}`)) { _onComponentLoad(checkHistory); }; }; const _m365InitHandler = () => { const wizardInstances = window.m365.SolutionWizard.getInstances(); const wizardInst = wizardInstances.find(inst => inst.states.wizardId === CONFIG.wizardId); wizardInst?.el.addEventListener(EVENTS.RESULTS_SHOWN, () => { _onResultsShown(false); }); }; this._init = (wrapper) => { _setupElements.call(this, wrapper); document.addEventListener(EVENTS.MWF_INITIALIZED, _attachTabEventHandlers.bind(this, wrapper)); document.addEventListener(EVENTS.M365_INITIALIZED, _m365InitHandler.bind(this)) }; this._init(opts.element); const wizardLocalStorage = new LocalStorageUtil({ storageKey: CONFIG.wizardId }); if(!ELEMENTS.wizardContainer) { _beginCardRender(); } else { _onResultsShown(true); } /** * handles viewport change */ const handleMediaQueryChange = (event) => { if (event.matches) { ELEMENTS.stepperContainer.classList.add(CSS_CLASSES.stickyStepper); } else { ELEMENTS.stepperContainer.classList.remove(CSS_CLASSES.stickyStepper); } }; CONFIG.match_lg.addEventListener(EVENTS.CHANGE, handleMediaQueryChange); handleMediaQueryChange(CONFIG.match_lg); } init() { this._init(opts.element); } }; } // Stores all the Card carousel instances ISVTurboflowResultsInstances = [];