I am using Select2 to enhance a element in my web application. I want to display tooltips when users hover over the options in the Select2 dropdown. However, the tooltips are not appearing as expected when hovering over the options. The tooltip container is created, but it doesn’t show up on hover.
Below is js file code
$(document).ready(function() {
function formatProjectOption(option) {
if (!option.id) {
return option.text;
}
return $('<span>' + option.text + '</span>');
}
$("#projectName").select2({
placeholder: "Select Projects...",
templateResult: formatProjectOption,
templateSelection: formatProjectOption,
}).on('select2:open', function() {
var $container = $('#projectName').next('.select2-container');
var $tooltipContainer = $('<div class="select2-tooltip"></div>').appendTo($container);
$('.select2-results__option').hover(
function() {
var tooltipText = $(this).data('tooltip');
if (tooltipText) {
$tooltipContainer.text(tooltipText).show();
}
},
function() {
$tooltipContainer.hide();
}
);
// Adjust tooltip position on hover
$('.select2-results__option').mousemove(function(e) {
$('.select2-tooltip').css({
top: e.pageY + 10,
left: e.pageX + 10
});
});
});
const multiselect = $("select[multiple]");
multiselect.each(function() {
const savedOptions = JSON.parse(localStorage.getItem(this.id));
if (savedOptions) {
const currentOptions = $(this).val() || [];
const mergedOptions = [...currentOptions, ...savedOptions];
$(this).val(mergedOptions).trigger("change");
}
});
const savedStartDate = localStorage.getItem('startDate');
if (savedStartDate) {
$('#startDate').val(savedStartDate);
}
const savedEndDate = localStorage.getItem('endDate');
if (savedEndDate) {
$('#endDate').val(savedEndDate);
}
$("#label").select2();
$("#defect").select2();
$("#projectName").select2({
placeholder: "Select Projects...",
});
$("#label").select2({
placeholder: "Select Label...",
});
$("#defect").select2({
placeholder: "Select Defect Type...",
});
// Show/hide form and chart sections based on user interaction
$("#form-section").show();
$("#nike-image").show();
$("#chart-section").hide();
$("#backToFormBtn").hide();
// Handle form submission
$("#metrics-form").submit(function(event) {
event.preventDefault();
multiselect.each(function() {
const selectedOptions = $(this).val();
localStorage.setItem(this.id, JSON.stringify(selectedOptions));
});
var projectName = $("#projectName").val();
/*var squad = $("#squad").val();*/
var label = $("#label").val();
var startDate = $("#startDate").val();
var defectType = $("#defect").val();
var endDate = $("#endDate").val();
if (projectName.length === 0 && label.length === 0) {
$("#modal-body").html("Please Select Project Name Or Label.");
$("#errorModal").modal("show");
} else if (defectType.length == 0) {
$("#modal-body").html("Please Select Defect Type.");
$("#errorModal").modal("show");
} else {
localStorage.setItem('startDate', startDate);
localStorage.setItem('endDate', endDate);
$("#errorModal").modal("hide");
$("#errorDateModal").modal("hide");
$("#spinner").show();
$.ajax({
type: $(this).attr("method"),
url: $(this).attr("action"),
data: $(this).serialize(),
success: function(response) {
$("#spinner").hide();
// Hide the form and show the chart section
$("#form-section").hide();
$("#chart-section").show();
$("#backToFormBtn").show();
var chartData = JSON.parse(response);
google.charts.load("current", {
packages: ["corechart"],
});
google.charts.setOnLoadCallback(function() {
drawCharts(chartData);
});
},
});
}
});
$("#add-project-form").submit(function(event) {
event.preventDefault();
var form = $(this);
var actionUrl = form.attr("action");
var method = form.attr("method");
// Show the spinner
$("#spinner").show();
$.ajax({
type: method,
url: actionUrl,
data: form.serialize(),
success: function(response) {
$("#spinner").hide();
// Assuming response contains the message and/or HTML snippet for displaying errors or success messages
// Insert response into the designated element for messages
$("#response-message")
.removeClass("alert-danger")
.addClass("alert-success")
.find("#response-text")
.text(response) // Set the response text
.end()
.fadeIn(500);
// Optionally, you might want to update specific parts of the page
},
error: function(jqXHR, textStatus, errorThrown) {
$("#spinner").hide();
console.error("Error occurred:", textStatus, errorThrown);
$("#response-container").html("<div class='alert alert-danger'>An error occurred while processing your request. Please try again.</div>");
}
});
});
// Handle the "OK" button click to hide the response message
$(document).on('click', '#response-ok', function() {
$("#response-message").fadeOut(500);
});
$('#addProjectButton').click(function() {
$("#add-project").show();
$('#add-project').slideDown();
$('html, body').animate({
scrollTop: $('#add-project').offset().top
}, 500);
});
// Handle "Back to Form" button click
$("#backToFormBtn").click(function() {
$("#chart-section").hide();
});
function drawCharts(chartData) {
drawBarChart("defectTotalCount", chartData.jiraStats, "Defect Type Count");
drawBarGroupChart("defectGroupChart", chartData.numberOfDaysToResolveDefect, "Test", chartData.jiraStats);
drawBarChartForPriorityCount("priorityCountChart", chartData.defectPriorityMatrices, "Priority Count");
drawBarChartForOpenStatusDefect("openDefectCount", chartData.numberOfOpenDefectsByType, "Open Defect Count")
populateTable(chartData);
}
function drawBarChart(containerId, chartData, title) {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Defect Type');
data.addColumn('number', 'Defect(s) Count');
data.addColumn({
type: 'string',
role: 'style'
});
data.addColumn({
type: 'string',
role: 'link'
});
chartData.forEach((element, index) => {
var value = element.defectType;
var count = element.count;
var color = element.color;
var link = element.jqlQuery;
data.addRow([value, Number(count), 'color:' + color, link]);
});
var options = {
title: title,
vAxis: {
title: 'Defect Count',
format: '0',
minValue: 0
},
hAxis: {
title: 'Defect Type'
},
legend: {
position: 'none'
}
};
var chart = new google.visualization.ColumnChart(document.getElementById(containerId));
chart.draw(data, options);
google.visualization.events.addListener(chart, 'select', selectHandler);
function selectHandler() {
var selectedItem = chart.getSelection()[0];
if (selectedItem) {
var link = data.getValue(selectedItem.row, 3);
if (link) {
window.open(link, '_link');
}
}
}
}
function drawBarGroupChart(containerId, chartData, title, colorData) {
if (Object.keys(chartData).length === 0) {
// Draw an empty chart
drawEmptyChart(containerId, title);
return;
}
// Convert javaData object into an array of arrays
var data = [];
var types = Object.keys(chartData);
var defects = {};
var jiraData = chartData;
// Collect all unique defect keys
types.forEach(function(type) {
Object.assign(defects, chartData[type]);
});
// Create the header row
var headerRow = ['Type'];
Object.keys(defects).forEach(function(defect) {
headerRow.push(defect);
});
data.push(headerRow);
// Populate data rows
types.forEach(function(type) {
var row = [type];
Object.keys(defects).forEach(function(defect) {
row.push(chartData[type][defect] || 0);
});
data.push(row);
});
console.log(data);
var chartData = google.visualization.arrayToDataTable(data);
var options = {
title: 'Total Days to resolve defect',
vAxis: {
title: 'Days'
},
hAxis: {
title: 'Resolved Defect Type'
},
seriesType: 'bars',
series: {} // You can adjust this to customize the chart
};
var index = 0;
colorData.forEach(function(defect) {
var groupSize = Object.keys(jiraData[defect.defectType]).length;
for (var i = 0; i < groupSize; i++) {
options.series[index++] = {
color: defect.color
};
}
});
options.bar = {
groupWidth: '200%'
};
var chart = new google.visualization.ComboChart(document.getElementById(containerId));
chart.draw(chartData, options);
}
function drawEmptyChart(containerId, title) {
var data = google.visualization.arrayToDataTable([
['Type', 'No Data'],
['No Data', 0]
]);
var options = {
title: title,
vAxis: {
title: 'Days'
},
hAxis: {
title: 'Resolved Defect Type'
},
seriesType: 'bars',
series: {} // You can adjust this to customize the chart
};
var chart = new google.visualization.ComboChart(document.getElementById(containerId));
chart.draw(data, options);
}
function drawBarChartForPriorityCount(containerId, chartData, title) {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Type');
data.addColumn('number', 'Count');
data.addColumn({
type: 'string',
role: 'style'
});
chartData.forEach((element, index) => {
var value = element.priorityName;
var count = element.totalCount;
var color = element.color;
data.addRow([value, Number(count), 'color:' + color]);
});
var options = {
title: title,
vAxis: {
title: 'Priority Total Count',
format: '0',
minValue: 0
},
hAxis: {
title: 'Priority'
},
legend: {
position: 'none'
}
};
var chart = new google.visualization.ColumnChart(document.getElementById(containerId));
chart.draw(data, options);
}
function drawBarChartForOpenStatusDefect(containerId, chartData, title) {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Defect Type');
data.addColumn('number', 'Open Defect Count');
data.addColumn({
type: 'string',
role: 'style'
});
chartData.forEach((element, index) => {
var value = element.defectType;
var count = element.count;
var color = element.color;
data.addRow([value, Number(count), 'color:' + color]);
});
var options = {
title: title,
vAxis: {
title: 'Open Defect Count',
format: '0',
minValue: 0
},
hAxis: {
title: 'Open Defect Type'
},
legend: {
position: 'none'
}
};
var chart = new google.visualization.ColumnChart(document.getElementById(containerId));
chart.draw(data, options);
}
function populateTable(chartData) {
const tableBody = document.getElementById("defectTable");
// Clear existing table rows
tableBody.innerHTML = '';
// Populate table with single object data
const row = `
<tr>
<td>${chartData.totalDefectCount}</td>
<td>${chartData.totalResolvedCount}</td>
<td>${chartData.totalOpenCount}</td>
</tr>
`;
tableBody.insertAdjacentHTML('beforeend', row);
}
$("#clearSelection").click(function() {
multiselect.val(null).trigger("change");
multiselect.each(function() {
localStorage.removeItem(this.id);
});
localStorage.removeItem('startDate');
localStorage.removeItem('endDate');
$('#startDate').val('');
$('#endDate').val('');
});
$("#label").on("select2:select select2:unselect", function(e) {
if (e.params.data.id === "ALL") {
if (e.type === "select2:select") {
// If "ALL" is selected, select all other options except "ALL"
$(this).find('option[value!="ALL"]').prop("selected", true);
$(this).trigger("change");
} else if (e.type === "select2:unselect") {
// If "ALL" is deselected, unselect all options
$(this).val(null).trigger("change");
}
}
});
$("#defect").on("select2:select select2:unselect", function(e) {
if (e.params.data.id === "ALL") {
if (e.type === "select2:select") {
// If "ALL" is selected, select all other options except "ALL"
$(this).find('option[value!="ALL"]').prop("selected", true);
$(this).trigger("change");
} else if (e.type === "select2:unselect") {
// If "ALL" is deselected, unselect all options
$(this).val(null).trigger("change");
}
}
});
$('#startDate, #endDate').datepicker({
container: 'body',
format: 'yyyy-mm-dd',
autoclose: true,
orientation: 'bottom'
}).on('show', function() {
$('html, body').animate({
scrollTop: $('.form-group.text-center').offset().top
}, 500);
$('.form-group.text-center').css('margin-top', '19%');
}).on('hide', function() {
$('.form-group.text-center').css('margin-top', '0');
});
});
Html code
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Defect Dashboard</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.min.css">
<link rel="stylesheet" href="defectdashboard.css">
<link href="custom_styles.css" rel="stylesheet" />
</head>
<body>
<!-- Form Section -->
<section class="container mt-3" id="form-section">
<div class="spinner" id="spinner" style="display: none;">
<i class="fas fa-spinner fa-spin fa-3x"></i>
</div>
<form id="metrics-form" action="submit" method="POST" th:object="${defectModel}">
<!-- Project Name Dropdown -->
<div class="form-group">
<label for="projectName">Project Name</label>
<select id="projectName" name="projectName" class="form-control" th:field="*{projectName}" multiple>
<option th:each="project : ${projectNames}" th:value="${project.name}" th:text="${project.name}"
th:data-tooltip="${project.labels}">
></option>
</select>
</div>
<!-- Squad Dropdown -->
<!-- <div class="form-group">
<label for="squad">Squad Name</label>
<select id="squad" name="squad" class="form-control" th:field="*{squad}" multiple>
<option th:each="squad : ${squads}" th:value="${squad}" th:text="${squad}"></option>
</select>
</div>-->
<div class="form-group">
<label for="label">Domain/Platform/System</label>
<select id="label" name="label" class="form-control" th:field="*{label}" multiple>
<option value="ALL">ALL</option>
<option th:each="label : ${labels}" th:value="${label}" th:text="${label}"></option>
</select>
</div>
<div class="form-group">
<label for="defect">Defect Type<span style="color:red">*</span></label>
<select id="defect" name="defect" class="form-control" th:field="*{defect}" multiple>
<option value="ALL">ALL</option>
<option th:each="defect : ${defectTypes}" th:value="${defect}" th:text="${defect}"></option>
</select>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="startDate">Start Date</label>
<input type="text" id="startDate" name="startDate" class="form-control" th:field="*{startDate}" autocomplete="off" placeholder="Enter Start Date">
</div>
<div class="form-group col-md-6">
<label for="endDate">End Date</label>
<input type="text" id="endDate" name="endDate" class="form-control" th:field="*{endDate}" autocomplete="off" placeholder="Enter End Date">
</div>
</div>
<!-- Multiselect Option -->
<!-- Submit and Cancel Buttons -->
<div class="form-group text-center">
<button type="submit" class="btn btn-primary">Submit</button>
<button type="button" id="clearSelection" class="btn btn-primary">Clear</button>
<div class="tooltip-container">
<button type="button" class="btn btn-secondary" id="addProjectButton" >Add New Project</button>
<div class="tooltip-content">Only project co-ordinator can add a New Project</div>
</div>
</div>
</form>
</section>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/js/bootstrap-datepicker.min.js"></script>
<script src="dashboard.js"></script>
</body>
</html>
Css
.header-bar {
background-color: black;
color: white;
padding: 5px;
display: flex;
justify-content: space-between;
align-items: center;
}
.dashboard-defect{
text-align: center;
flex: 1;
}
/* Custom styles for bold labels */
.form-group label {
font-weight: bold;
}
.chart-container {
margin-bottom: 20px;
}
.spinner {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
/* Semi-transparent white background */
z-index: 9999;
/* Higher z-index to ensure it's displayed on top of other elements */
}
/* CSS for the spinner icon */
.spinner i {
font-size: 3em;
/* Adjust the size of the spinner icon */
color: #007bff;
/* Spinner color, you can change this to match your design */
}
.tooltip-container {
position: relative;
/* Ensure tooltip is positioned relative to this container */
display: inline-block;
/* Ensure it only takes up space for its content */
}
.tooltip-content {
display: none;
/* Initially hidden */
position: absolute;
/* Positioning it relative to the container */
background-color: #fff;
color: #333;
padding: 10px;
border: 1px solid #ccc;
/* Thin black border */
border-radius: 4px;
max-width: 300px;
/* Adjust width as needed */
white-space: nowrap;
/* Keep text in a single line */
overflow: hidden;
/* Hide overflowed text */
text-overflow: ellipsis;
/* Add ellipsis for overflowed text */
font-size: 12px;
text-align: center;
z-index: 1060;
/* Ensure it is above other content */
top: 100%;
/* Position below the button */
left: 50%;
/* Center horizontally */
transform: translateX(-50%);
/* Adjust for centering */
opacity: 0;
/* Start as invisible */
transition: opacity 0.3s, transform 0.3s;
/* Fade in/out effect and slight move */
}
.tooltip-container:hover .tooltip-content {
display: block;
/* Show tooltip on hover */
opacity: 1;
/* Make it visible */
transform: translateX(-50%) translateY(5px);
/* Slightly move it down */
}
#response-message {
display: none;
/* Hide by default */
max-width: 400px;
/* Set a maximum width for the alert */
width: 100%;
/* Allow the width to adjust but not exceed max-width */
margin: 20px auto;
/* Center the alert horizontally with auto margins */
padding: 10px;
/* Padding inside the alert box */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
/* Optional: Add shadow for better visibility */
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 4px;
/* Rounded corners */
}
#response-text {
flex: 1;
/* Allow text to take up remaining space */
}
#response-ok {
margin-left: 10px;
/* Space between the message and the button */
}
.select2-tooltip {
display: block;
position: absolute;
background-color: #333;
color: #fff;
padding: 5px;
border-radius: 3px;
font-size: 12px;
z-index: 1000;
}
.select2-container--open .select2-tooltip {
display: block;
}