";
}
}).on('show.bs.popover', function (e) {
loadFilterObjBoxintoPopover();
}).on('shown.bs.popover', function (e) {
var popoverID = $(e.target).attr("aria-describedby");
$('.clearFilter').append(getClearSvg()); // Added this change to append Delete SVG Icon in popup
if ($('.popover-arrow').is(":visible")) {
$('.popover-arrow').hide(); // Added this to hide collapsible icon coming from bootstrap before Public Preview
}
$("#" + popoverID).find(".clearFilter").first().focus();
$("#" + popoverID).attr("data-popper-placement", "bottom");
if ($("#closepopoverdiv").length === 0)
$("#" + popoverID).prepend("");
// Code for Release Quarter dropdown in filter - Release Quarter Year Code
// Get the current year
const currentYear = new Date().getFullYear();
// Define the range of years you want to display
const startYear = 2024;
const endYear = currentYear + 2; // For example, 4 years into the future
// Get the dropdown element and add default value as Select Year
let $yearDropdown = $("#yearDropdown");
let $defaultYearOption = $("").val("").text("Select Year").prop("selected", true);
$yearDropdown.append($defaultYearOption);
// Populate the dropdown with years
for (let year = startYear; year ").val(year).text(year);
$yearDropdown.append(option);
}
// Set updated value if present in filterBoxState
var $yearSelectDropdown = $("[name='ReleaseYear']");
let releaseYear = filterBoxState.FilterBy[0].ReleaseQuarterDetails.ReleaseYear;
if(releaseYear != '') { $yearSelectDropdown.prop("value", releaseYear) };
// Release Quarter Dropdown Code
let quarters = ["Q1", "Q2", "Q3", "Q4"];
// Get the dropdown element
let $quarterDropdown = $("#quarterDropdown");
let $defaultQuarterOption = $("").val("").text("Select Quarter").prop("selected", true);
$quarterDropdown.append($defaultQuarterOption);
// Populate the dropdown with quarters
quarters.forEach(quarter => {
let option = $("").val(quarter).text(quarter);
$quarterDropdown.append(option);
});
var $quarterSelectDropdown = $("[name='ReleaseQuarter']");
let releaseQuarter = filterBoxState.FilterBy[0].ReleaseQuarterDetails.ReleaseQuarter;
if(releaseQuarter != '') { $quarterSelectDropdown.prop("value", releaseQuarter); }
}).on('hidden.bs.popover', function (e) {
$(e.target).focus();
}).on('inserted.bs.popover', function (e) {
var popoverID = $(e.target).attr("aria-describedby");
$("#" + popoverID).css("position", "relative");
});
});
$(".filterPopover").on("keydown", function (event) {
if (event.key === "Enter") {
$(this).popover("toggle");
event.preventDefault(); // Prevent default behavior
}
});
jQuery('.filterPopover').on('inserted.bs.popover', function (e) {
let isMobile = $(e.target).hasClass('mobile-filter');
if (isMobile) $('.clearFilter').append(getClearSvg());
loadFilterObjBoxintoPopover();
});
renderProducts();
function loadFilterObjBoxintoPopover() {
let filterBox = filterBoxState.FilterBy[0];
let sortBox = filterBoxState.SortBy[0];
let FilterReleaseStatus = filterBoxState.FilterBy[0].ReleaseStatus;
let FilterReleaseType = filterBoxState.FilterBy[0].ReleaseType;
let FilterReleaseDate = filterBoxState.FilterBy[0].ReleaseDate;
let SortOrder = filterBoxState.SortBy[0].order;
let FilterReleaseQaurter = filterBoxState.FilterBy[0].ReleaseQuarterDetails;
$.each(FilterReleaseStatus, function (key, value) {
$("[name='" + key + "']").prop("checked", value);
});
$.each(FilterReleaseType, function (key, value) {
$("[name='" + key + "']").prop("checked", value);
});
$.each(SortOrder, function (key, value) {
$("[name='" + key + "']").prop("checked", value);
});
$.each(FilterReleaseQaurter, function (key, value) {
$("[name='" + key + "']").prop("value", value);
});
}
hideLoader();
});
//ready end
$('.navbar-collapse').on('keydown', function (event) {
if (event.key === 'Escape' || event.keyCode === 27) {
const hamburgerBtn = $('#hamburger');
const menuOpen = hamburgerBtn.attr('aria-expanded') === 'true';
if (menuOpen) {
hamburgerBtn.click(); // Close the popup
}
}
});
function renderProducts() {
renderProductAccordion(AppState.productList);
}
function renderProductAccordion(products) {
// [DIAG] Trace what reaches the accordion renderer. If length=0 here, the FetchXML
// block returned nothing -> investigate Dataverse data / Table Permissions / cache.
console.log('[ProductList][DIAG] renderProductAccordion called. products.length =',
(products || []).length, 'urlProductParam =', getQueryParams());
if (!products || products.length === 0) {
console.error('[ProductList][DIAG] No products to render. Aborting to avoid TypeError on productList[0].id.');
return;
}
const product=getQueryParams();
let isProductPresent=products.filter((p)=>p.queryString===product);
let row = null;
for (var i = 0; i ${products[index].name}`);
$(`#collapse1`).children().children('ul').append(row);
if(index === 0) {
return;
}
}
function onProductClick(element, e) {
e.preventDefault();
$('#collapse1 ul li').attr('aria-selected', 'false');
$(element).attr('aria-selected', 'true');
//reset on product click
filterBoxState = {
"FilterBy": [
{
"ReleaseStatus": { 'Planned': false, 'Shipped': false },
"ReleaseType": { 'PublicPreview': false, 'GeneralAvailability': false },
"ReleaseDate": { 'StartDate': '', 'EndDate': '' },
"ReleaseQuarterDetails": { 'ReleaseYear': '', 'ReleaseQuarter': '' }
}
],
"SortBy": [
{
"order": { 'New_to_old_sort': false, 'Old_t0_new_sort': false }
}
]
}
closePopover(); // Added this to close popup when clicked on product
$('.filter-filled').addClass('hidden-item');
$('.filter-empty').removeClass('hidden-item');
$('#accordion-group').html('');
AppState.productResults = [];
AppState.selectedProduct = $(element).attr('id');
$('li.active').removeClass('active');
if (!$(element).hasClass('navigationProduct')) $('div.active').removeClass('active');
$(element).addClass('active');
var filterValue = $('.filter--dropdown');
filterValue.val('3');
// Passing below the value 3(Next in 6 months), which will make the Current Semester as default tab whenever product is changed
renderReleasePlanData(3, element);
const params = new URLSearchParams(window.location.search);
// [DIAG] Guard against missing product (empty productList or stale selection)
const matchedProduct = AppState.productList.filter((p)=>p.id===AppState.selectedProduct)[0];
if (!matchedProduct) {
console.warn('[ProductList][DIAG] onProductClick: selectedProduct not found in productList', AppState.selectedProduct);
return;
}
const productName = matchedProduct.queryString;
params.set("product", productName);
const newUrl = `${window.location.origin}${window.location.pathname}?${params.toString()}`;
window.history.replaceState({}, '', newUrl);
}
async function renderReleasePlanData(filterValue = 3, element = '') {
$('.active-filter.active').removeClass('active');
$(`.status--filters-${filterValue}`).find('.active-filter').addClass('active');
$(".tablist--features").find('[role="tab"]').attr('aria-selected', 'false');
$(`.status--filters-${filterValue}`).find('[role="tab"]').attr('aria-selected', 'true');
$('#accordion-group').html('');
if (!AppState.selectedProduct) {
return;
}
if (AppState.productResults.length === 0) {
showLoader();
let url = `${document.location.origin}/fabric-json/?productId=${AppState.selectedProduct}`;
try {
var data = await getResponse(url);
hideLoader();
if (data.results.length > 0) {
data.results.forEach(element => {
let converter = new showdown.Converter();
var str = element.FeatureDescription;
var newStr = str.replace(/([^*])\*(?!\*)/g, "$1\n*");
let convertedStr = converter.makeHtml(newStr);
var HTMLFDescription = convertedStr.replaceAll('ApplyTheBackwardSlashandUnderscoreChangesAgain', '\\_').replaceAll('ApplyTheBulletChangesAgain', '• ');
if(/]*href="[^"]+"[^>]*>.*?/i.test(HTMLFDescription)) //To check if string contains any Hyperlink with tag
{
const anchorWithHyperLinks = HTMLFDescription.match(/]*>.*?/gi);
anchorWithHyperLinks.forEach(anchorWithHyperLink => {
HTMLFDescription = HTMLFDescription.replace(anchorWithHyperLink, anchorWithHyperLink.slice(0,3) + " class='descriptionHyperlink' " + anchorWithHyperLink.slice(3) + " ");
});
}
// Check if string contains any hyperlink as https://.....
const hyperLinks = HTMLFDescription.match(/https:\/\/[^\s"]+/g) || [];
if(hyperLinks.length != 0)
{
const hrefLinks = [...HTMLFDescription.matchAll(/]*href="(https:\/\/[^"]+)"[^>]*>/gi)].map(m => m[1]);
const onlyRawLinks = hyperLinks.filter(link => !hrefLinks.includes(link));
if(onlyRawLinks.length != 0) {
if(onlyRawLinks.length !== 0)
{
onlyRawLinks.forEach(hyperLink => {
HTMLFDescription = HTMLFDescription.replace(hyperLink, "" + hyperLink + "");
});
}
}
}
let containsLineBreak = HTMLFDescription.includes("<br/>") || HTMLFDescription.includes("<br>");
if(containsLineBreak)
{
HTMLFDescription = HTMLFDescription.replaceAll("<br/>", " ").replaceAll("<br>", " ");
}
let releaseDateParts = (element.ReleaseDate || '').split(' ');
let featureObj = {
ReleaseItemID: element.ReleaseItemID, FeatureName: element.FeatureName, ReleaseDate: element.ReleaseDate, ReleaseQuarter: releaseDateParts[0] || '', ReleaseYear: releaseDateParts[1] || '', ReleaseType: element.ReleaseType, ReleaseTypeValue: element.ReleaseTypeValue, ReleaseStatus: element.ReleaseStatus, ReleaseStatusValue: element.ReleaseStatusValue, ReleaseSemester: element.ReleaseSemester,
ProductID: element.ProductID, ProductName: element.ProductName, isPublishExternally: element.isPublishExternally, FeatureDescription: HTMLFDescription, markDownFeatureDescription: element.FeatureDescription
}
AppState.productResults.push(featureObj);
});
verifyAndRenderSnapshots(AppState.productResults, filterValue, element);
}
else {
}
} catch (e) {
console.log("getResponse fail. " + e.message);
}
} else {
verifyAndRenderSnapshots(AppState.productResults, filterValue, element);
}
}
async function getResponse(webTemplateURL) {
try {
var response = await fetch(webTemplateURL)
var rawText = await response.text();
var replacedValues = rawText.replaceAll('\\_', 'ApplyTheBackwardSlashandUnderscoreChangesAgain');
replacedValues = replacedValues.replaceAll('• ', 'ApplyTheBulletChangesAgain');
return JSON.parse(replacedValues);
}
catch (e) {
console.log("getResponse :: " + e.message);
}
}
function verifyAndRenderSnapshots(results, filterValue, element) {
let filteredSnapshots = [];
results.forEach(item => {
filteredSnapshots = filterCheck(item, filteredSnapshots, filterValue);
});
showSnapshots(filteredSnapshots, element, filterValue);
}
function showSnapshots(snapshots, element, filterValue) {
if (snapshots.length == 0) {
setNoResultMessage($('#accordion-group'));
return;
}
let sortedSnapshots;
if (filterBoxState.SortBy[0].order.Old_t0_new_sort || filterBoxState.SortBy[0].order.New_to_old_sort) {
sortedSnapshots = sortSnapshots(snapshots, filterValue);
}
else {
sortedSnapshots = sortSnapshots(snapshots, filterValue)
}
if (sortedSnapshots.length == 0) {
setNoResultMessage($('#accordion-group'));
return;
}
renderDataSets(sortedSnapshots, element, sortedSnapshots.length);
}
function setNoResultMessage($element) {
$element.html('');
$element.append($(``));
$element.find('.no-result').append(getNoResultSvg());
$element.find('.no-result').append($('
No Results
'));
const selectedTab = $('#filterDropdown select').val();
let selectedProductObj = AppState.productList.find(x => x.id == AppState.selectedProduct);
// [DIAG] Guard: avoid crash when productList is empty or no match
let selectedProductName = selectedProductObj ? (selectedProductObj.shortname || selectedProductObj.name) : '';
let tip = `
Tip: Click on SelectedOptionPlaceHolder to view all new features recently released or planned to market or select a different application.
`;
let noRecordMessageHtml = `
We didn't find any results for: ${$('#filterDropdown option:selected').text()} in ${selectedProductName}.
`;
if (selectedTab === '3' || selectedTab === '2') {
tip = tip.replace('SelectedOptionPlaceHolder', $('#filterDropdown option[value="1"]').text());
}
else if (selectedTab === '1') {
tip = '
Tip: Click on SelectedOptionPlaceHolder to view planned and released features or select a different application.
';
tip = tip.replace('SelectedOptionPlaceHolder', $('#filterDropdown option[value="3"]').text() + " and " + $('#filterDropdown option[value="2"]').text());
}
$element.find('.no-result').append(noRecordMessageHtml);
$element.find('.no-result').append(tip);
}
function sortSnapshots(snapshots, filterValue) {
if (!snapshots) return snapshots;
snapshots = snapshots.sort((a, b) => {
if (filterBoxState.SortBy[0].order.Old_t0_new_sort || filterBoxState.SortBy[0].order.New_to_old_sort)
return interactiveFilterSortBy(getTargetReleaseDate(filterValue, a), getTargetReleaseDate(filterValue, b), filterValue);
else {
let plan1DateValue = quarterToSortValue(a);
let plan2DateValue = quarterToSortValue(b);
if (filterValue === 1 || filterValue === 2) //Default Released
return plan2DateValue - plan1DateValue; //Dsc
else if (filterValue === 3) //Default Current Semester
{
return plan1DateValue - plan2DateValue; // Asc
}
}
});
return snapshots;
}
function quarterToSortValue(item) {
let qMap = { 'Q1': 1, 'Q2': 2, 'Q3': 3, 'Q4': 4 };
let year = parseInt(item.ReleaseYear) || 0;
let q = qMap[item.ReleaseQuarter] || 0;
return year * 10 + q;
}
function getTargetReleaseDate(filterValue, plan) {
return quarterToSortValue(plan);
}
function interactiveFilterSortBy(plan1, plan2, filterValue) {
if (filterBoxState.SortBy[0].order.Old_t0_new_sort) //Asc
return plan1 - plan2;
else if (filterBoxState.SortBy[0].order.New_to_old_sort) //Dsc
return plan2 - plan1;
}
function filterCheck(featureObj, tabledata, filterValue) {
let ReleaseDate = featureObj.ReleaseDate;
let ReleaseStatus = featureObj.ReleaseStatusValue;
if (ReleaseDate && ReleaseDate !== '' && shouldAddInCurrentFilter(`${filterValue}`, featureObj, ReleaseStatus) && verifyInteractiveFilters(featureObj) && releaseQuarterFilter(filterValue, featureObj, ReleaseStatus)) {
tabledata.push(featureObj);
}
return tabledata;
}
function releaseQuarterFilter(filterValue, featureObj, releaseStatus) {
if (!featureObj.ReleaseDate || featureObj.ReleaseDate === '') {
return false;
}
let includeInFilter = false;
let appliedYear = filterBoxState.FilterBy[0].ReleaseQuarterDetails.ReleaseYear;
let appliedQuarter = filterBoxState.FilterBy[0].ReleaseQuarterDetails.ReleaseQuarter;
if (appliedYear == '' && appliedQuarter == '') {
if (filterValue == '3' && releaseStatus == '457530001') { // Current Semester - Planned - Only Planned
includeInFilter = true;
}
else if (filterValue == '2' && releaseStatus == '457530002') { // Try Now - Only Released
includeInFilter = true;
} else if (filterValue == '1') { // All - Both Planned and Released
includeInFilter = true;
}
}
else {
let quarter = featureObj.ReleaseQuarter || '';
let year = featureObj.ReleaseYear || '';
if (appliedYear != '' && String(appliedYear) === String(year) && appliedQuarter != '' && appliedQuarter === quarter) {
if (filterValue == '3' && releaseStatus == '457530001'){ // Planned
includeInFilter = true;
}
else if (filterValue == '2' && releaseStatus == '457530002') { // Try Now
includeInFilter = true;
} else if (filterValue == '1') { // All
includeInFilter = true;
}
}
}
return includeInFilter;
}
function dateRangeFilter(filterValue, dateValue, releaseStatus) {
if (!dateValue || dateValue === '') {
return false;
}
let includeInFilter = false;
let givenDate = moment(dateValue, 'MM/DD/YYYY');
let givenMonth = Number(givenDate.format('M'));
let givenYear = Number(givenDate.format('YYYY'));
let currentDateOnly = $.datepicker.formatDate('mm/dd/yy', new Date());
let currentDate = moment(currentDateOnly, 'MM/DD/YYYY');
let currentMonth = Number(currentDate.format('M'));
let currentYear = Number(currentDate.format('YYYY'));
var dayDiff = givenDate.diff(currentDate, 'days');
//addforDate from object only, not UI
let appliedStart = filterBoxState.FilterBy[0].ReleaseDate.StartDate;
let appliedEnd = filterBoxState.FilterBy[0].ReleaseDate.EndDate;
if ($(".popover-body input[type='date']").length == 0 && appliedStart == '' && appliedEnd == '') {
if (filterValue == '3' && releaseStatus == '457530001') { // Current Semester - Planned - Only Planned
includeInFilter = true;
}
else if (filterValue == '2' && releaseStatus == '457530002') { // Try Now - Only Released
if(dayDiff >= -365) includeInFilter = true;
} else if (filterValue == '1') { // All - Both Planned and Released
includeInFilter = true;
}
}
else {
let appliedStartDate = appliedStart != '' ? new Date(appliedStart) : '';
let appliedEndDate = appliedEnd != '' ? new Date(appliedEnd) : '';
let dateFormattedValue = new Date(dateValue);
if (appliedStartDate == '' && appliedEndDate == '') {
includeInFilter = true;
}
else if ((appliedStart != '' && dateFormattedValue >= appliedStartDate && appliedEnd != '' && dateFormattedValue = appliedStartDate) ||
(appliedStart == '' && appliedEndDate != '' && dateFormattedValue = -365) includeInFilter = true;
} else if (filterValue == '1') { // Planned
includeInFilter = true; // All - Both Planned and Released
}
}
}
return includeInFilter;
}
function shouldAddInCurrentFilter(filterValue, featureObj, releaseStatus) {
if (!featureObj.ReleaseDate || featureObj.ReleaseDate === '') {
return false;
}
let includeInFilter = false;
if (filterValue === '3' && releaseStatus == '457530001'){ // Planned => (Current Semester)
includeInFilter = true;
}
else if (filterValue === '2' && releaseStatus == '457530002') { // Coming Soon => (Released) - Try Now
includeInFilter = true;
} else if (filterValue === '1') { // All - Both Planned and Released
includeInFilter = true;
}
return includeInFilter;
}
function verifyInteractiveFilters(featureObj) {
return StatusInteractiveFilter(featureObj) && AvailabilityCheck(featureObj);
}
function StatusInteractiveFilter(featureObj) {
var filterStatus = filterBoxState.FilterBy[0].ReleaseStatus;
var statusCheck = false;
$.each(filterStatus, function (key, value) {
if (value) statusCheck = true;
});
if (statusCheck) {
var plannedStatus = false;
var shippedStatus = false;
var planned = filterBoxState.FilterBy[0].ReleaseStatus.Planned;
var shipped = filterBoxState.FilterBy[0].ReleaseStatus.Shipped;
if (planned && featureObj.ReleaseStatus.contains("Planned")) plannedStatus = true;
if (shipped && featureObj.ReleaseStatus.contains("Shipped")) shippedStatus = true;
if (plannedStatus && shippedStatus) return true;
else if (plannedStatus) return true;
else if (shippedStatus) return true;
else return false;
}
else return true;
}
function AvailabilityCheck(featureObj) {
let ReleaseTypeStatus = featureObj.ReleaseType;
var filterAvailability = filterBoxState.FilterBy[0].ReleaseType;
var availaibilityCheck = false;
$.each(filterAvailability, function (key, value) {
if (value) availaibilityCheck = true;
});
if (availaibilityCheck) {
var GAStatus = false;
var PPStatus = false;
var filtercheck_GA = filterBoxState.FilterBy[0].ReleaseType.GeneralAvailability;
var filtercheck_PP = filterBoxState.FilterBy[0].ReleaseType.PublicPreview;
if (filtercheck_GA && ReleaseTypeStatus.contains('General availability')) GAStatus = true; else GAStatus = false;
if (filtercheck_PP && ReleaseTypeStatus.contains('Public preview')) PPStatus = true; else PPStatus = false;
if (GAStatus && PPStatus) return true;
else if (GAStatus) return true;
else if (PPStatus) return true;
else if (GAStatus && PPStatus) return true;
else if (GAStatus) return true;
else if (PPStatus) return true;
else return false;
}
else return true;
}
function renderDataSets(tabledata, element, count) {
for (var i = 0; i •');
if (tabledata[i].FeatureDescription.contains(':')) {
var featureDescription1 = tabledata[i].FeatureDescription.split(':')[0];
var featureDesc = featureDescription1.contains('.') ? featureDescription1.split('.') : featureDescription1;
var lastElement = ' ' + featureDesc[featureDesc.length - 1];
tabledata[i].FeatureDescription = tabledata[i].FeatureDescription.replace(featureDesc[featureDesc.length - 1], lastElement);
}
}
// Adding this change to display bullet or unordered list properly provided in feature description.
if (tabledata[i].FeatureName.contains("•")) {
tabledata[i].FeatureName = tabledata[i].FeatureName.replaceAll('•', ' •');
if (tabledata[i].FeatureName.contains(':')) {
var featureFeatureName1 = tabledata[i].FeatureName.split(':')[0];
var featureName = featureFeatureName1.contains('.') ? featureFeatureName1.split('.') : featureFeatureName1;
var lastElement = ' ' + featureName[featureName.length - 1];
tabledata[i].FeatureName = tabledata[i].FeatureName.replace(featureName[featureName.length - 1], lastElement);
}
}
let row = $(`