Implement comprehensive mobile experience framework for entire application

This commit is contained in:
Eric Gullickson
2025-07-27 21:03:06 -05:00
parent f46d471453
commit ea055f1c38
20 changed files with 713 additions and 174 deletions

View File

@@ -1,10 +1,26 @@
// Initialize collision record modal for mobile (using shared mobile framework)
function initializeCollisionRecordMobile() {
initMobileModal({
modalId: '#collisionRecordModal',
dateInputId: '#collisionRecordDate',
tagSelectorId: '#collisionRecordTag'
});
// Handle desktop initialization
if (!isMobileDevice()) {
initDatePicker($('#collisionRecordDate'));
initTagSelector($("#collisionRecordTag"));
}
}
function showAddCollisionRecordModal() {
$.get('/Vehicle/GetAddCollisionRecordPartialView', function (data) {
if (data) {
$("#collisionRecordModalContent").html(data);
//initiate datepicker
initDatePicker($('#collisionRecordDate'));
initTagSelector($("#collisionRecordTag"));
// Initialize mobile experience using shared framework
initializeCollisionRecordMobile();
$('#collisionRecordModal').modal('show');
}
});
@@ -25,9 +41,8 @@ function showEditCollisionRecordModal(collisionRecordId, nocache) {
$.get(`/Vehicle/GetCollisionRecordForEditById?collisionRecordId=${collisionRecordId}`, function (data) {
if (data) {
$("#collisionRecordModalContent").html(data);
//initiate datepicker
initDatePicker($('#collisionRecordDate'));
initTagSelector($("#collisionRecordTag"));
// Initialize mobile experience using shared framework
initializeCollisionRecordMobile();
$('#collisionRecordModal').modal('show');
bindModalInputChanges('collisionRecordModal');
$('#collisionRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {

View File

@@ -1,11 +1,33 @@
// Initialize vehicle modal for mobile (using shared mobile framework)
function initializeVehicleMobile() {
// Vehicle modal has multiple date inputs, handle them individually
if (isMobileDevice()) {
// Convert date inputs to native HTML5 on mobile
$('#inputPurchaseDate').attr('type', 'date').removeClass('datepicker');
$('#inputSoldDate').attr('type', 'date').removeClass('datepicker');
// Initialize mobile tag selector
initMobileTagSelector($("#inputTag"));
// Initialize swipe to dismiss
initSwipeToDismiss('#addVehicleModal');
} else {
// Desktop initialization
initTagSelector($("#inputTag"));
initDatePicker($('#inputPurchaseDate'));
initDatePicker($('#inputSoldDate'));
}
}
function showAddVehicleModal() {
uploadedFile = "";
$.get('/Vehicle/AddVehiclePartialView', function (data) {
if (data) {
$("#addVehicleModalContent").html(data);
initTagSelector($("#inputTag"));
initDatePicker($('#inputPurchaseDate'));
initDatePicker($('#inputSoldDate'));
// Initialize mobile experience using shared framework
initializeVehicleMobile();
$('#addVehicleModal').modal('show');
}
})

View File

@@ -1,133 +1,29 @@
// Mobile detection utility
function isMobileDevice() {
return window.matchMedia("(max-width: 768px)").matches ||
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
// Initialize form inputs based on device type
function initializeFormInputs() {
if (isMobileDevice()) {
// Convert date input to native HTML5 on mobile
var dateInput = $('#gasRecordDate');
if (dateInput.length) {
dateInput.attr('type', 'date');
dateInput.removeClass('datepicker'); // Remove Bootstrap datepicker class
}
// Default to simple mode on mobile
var modeToggle = $('#fuelEntryModeToggle');
if (modeToggle.length && !modeToggle.is(':checked')) {
modeToggle.prop('checked', true);
toggleFuelEntryMode(true);
}
// Initialize mobile-friendly tag selector
initMobileTagSelector($("#gasRecordTag"));
} else {
// Use Bootstrap datepicker on desktop
// Initialize gas record modal for mobile (using shared mobile framework)
function initializeGasRecordMobile() {
initMobileModal({
modalId: '#gasRecordModal',
dateInputId: '#gasRecordDate',
tagSelectorId: '#gasRecordTag',
modeToggleId: '#fuelEntryModeToggle',
simpleModeDefault: true
});
// Handle desktop initialization
if (!isMobileDevice()) {
initDatePicker($('#gasRecordDate'));
// Use standard tag selector on desktop
initTagSelector($("#gasRecordTag"));
}
}
// Mobile-optimized tag selector
function initMobileTagSelector(input) {
if (input.length) {
// Initialize with mobile-friendly options
input.tagsinput({
maxTags: 5, // Limit tags on mobile
trimValue: true,
confirmKeys: [13, 44, 32], // Enter, comma, space
focusClass: 'focus',
freeInput: true
});
// Add mobile-specific styling
input.parent().addClass('mobile-tag-input');
// Increase touch target size for mobile
input.parent().find('.bootstrap-tagsinput').css({
'min-height': '44px',
'padding': '8px',
'font-size': '16px' // Prevent zoom on iOS
});
// Style the input within tags
input.parent().find('.bootstrap-tagsinput input').css({
'font-size': '16px',
'min-height': '30px'
});
}
}
// Swipe to dismiss functionality for mobile modals
function initSwipeToDismiss() {
if (!isMobileDevice()) return;
var modal = $('#gasRecordModal');
var modalContent = modal.find('.modal-content');
var startY = 0;
var currentY = 0;
var isDragging = false;
var threshold = 100; // Minimum swipe distance to dismiss
// Touch start
modalContent.on('touchstart', function(e) {
startY = e.originalEvent.touches[0].clientY;
isDragging = true;
modalContent.css('transition', 'none');
});
// Touch move
modalContent.on('touchmove', function(e) {
if (!isDragging) return;
currentY = e.originalEvent.touches[0].clientY;
var deltaY = currentY - startY;
// Only allow downward swipes
if (deltaY > 0) {
modalContent.css('transform', `translateY(${deltaY}px)`);
}
});
// Touch end
modalContent.on('touchend', function(e) {
if (!isDragging) return;
isDragging = false;
var deltaY = currentY - startY;
modalContent.css('transition', 'transform 0.3s ease-out');
if (deltaY > threshold) {
// Dismiss modal
modalContent.css('transform', 'translateY(100%)');
setTimeout(function() {
hideAddGasRecordModal();
modalContent.css('transform', '');
}, 300);
} else {
// Snap back
modalContent.css('transform', '');
}
});
}
function showAddGasRecordModal() {
$.get(`/Vehicle/GetAddGasRecordPartialView?vehicleId=${GetVehicleId().vehicleId}`, function (data) {
if (data) {
$("#gasRecordModalContent").html(data);
// Initialize inputs based on device type
initializeFormInputs();
// Initialize mobile experience using shared framework
initializeGasRecordMobile();
$('#gasRecordModal').modal('show');
// Initialize swipe to dismiss for mobile
initSwipeToDismiss();
}
});
}
@@ -148,15 +44,12 @@ function showEditGasRecordModal(gasRecordId, nocache) {
if (data) {
$("#gasRecordModalContent").html(data);
// Initialize inputs based on device type
initializeFormInputs();
// Initialize mobile experience using shared framework
initializeGasRecordMobile();
$('#gasRecordModal').modal('show');
bindModalInputChanges('gasRecordModal');
// Initialize swipe to dismiss for mobile
initSwipeToDismiss();
$('#gasRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {
if (getGlobalConfig().useMarkDown) {
toggleMarkDownOverlay("gasRecordNotes");
@@ -569,13 +462,10 @@ function editMultipleGasRecords(ids) {
if (data) {
$("#gasRecordModalContent").html(data);
// Initialize inputs based on device type
initializeFormInputs();
// Initialize mobile experience using shared framework
initializeGasRecordMobile();
$('#gasRecordModal').modal('show');
// Initialize swipe to dismiss for mobile
initSwipeToDismiss();
}
});
}

View File

@@ -1,9 +1,26 @@
// Initialize reminder record modal for mobile (using shared mobile framework)
function initializeReminderRecordMobile() {
initMobileModal({
modalId: '#reminderRecordModal',
dateInputId: '#reminderDate',
tagSelectorId: '#reminderRecordTag'
});
// Handle desktop initialization
if (!isMobileDevice()) {
initDatePicker($('#reminderDate'), true);
initTagSelector($("#reminderRecordTag"));
}
}
function showEditReminderRecordModal(reminderId) {
$.get(`/Vehicle/GetReminderRecordForEditById?reminderRecordId=${reminderId}`, function (data) {
if (data) {
$("#reminderRecordModalContent").html(data);
initDatePicker($('#reminderDate'), true);
initTagSelector($("#reminderRecordTag"));
// Initialize mobile experience using shared framework
initializeReminderRecordMobile();
$("#reminderRecordModal").modal("show");
$('#reminderRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {
if (getGlobalConfig().useMarkDown) {

View File

@@ -1,10 +1,26 @@
// Initialize service record modal for mobile (using shared mobile framework)
function initializeServiceRecordMobile() {
initMobileModal({
modalId: '#serviceRecordModal',
dateInputId: '#serviceRecordDate',
tagSelectorId: '#serviceRecordTag'
});
// Handle desktop initialization
if (!isMobileDevice()) {
initDatePicker($('#serviceRecordDate'));
initTagSelector($("#serviceRecordTag"));
}
}
function showAddServiceRecordModal() {
$.get('/Vehicle/GetAddServiceRecordPartialView', function (data) {
if (data) {
$("#serviceRecordModalContent").html(data);
//initiate datepicker
initDatePicker($('#serviceRecordDate'));
initTagSelector($("#serviceRecordTag"));
// Initialize mobile experience using shared framework
initializeServiceRecordMobile();
$('#serviceRecordModal').modal('show');
}
});
@@ -25,9 +41,10 @@ function showEditServiceRecordModal(serviceRecordId, nocache) {
$.get(`/Vehicle/GetServiceRecordForEditById?serviceRecordId=${serviceRecordId}`, function (data) {
if (data) {
$("#serviceRecordModalContent").html(data);
//initiate datepicker
initDatePicker($('#serviceRecordDate'));
initTagSelector($("#serviceRecordTag"));
// Initialize mobile experience using shared framework
initializeServiceRecordMobile();
$('#serviceRecordModal').modal('show');
bindModalInputChanges('serviceRecordModal');
$('#serviceRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {

View File

@@ -375,6 +375,149 @@ function initTagSelector(input, noDataList) {
input.tagsinput();
}
}
// ===== MOBILE EXPERIENCE FRAMEWORK =====
// Mobile detection utility
function isMobileDevice() {
return window.matchMedia("(max-width: 768px)").matches ||
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
// Mobile-optimized tag selector
function initMobileTagSelector(input) {
if (input.length) {
// Initialize with mobile-friendly options
input.tagsinput({
maxTags: 5, // Limit tags on mobile
trimValue: true,
confirmKeys: [13, 44, 32], // Enter, comma, space
focusClass: 'focus',
freeInput: true
});
// Add mobile-specific styling
input.parent().addClass('mobile-tag-input');
// Increase touch target size for mobile
input.parent().find('.bootstrap-tagsinput').css({
'min-height': '44px',
'padding': '8px',
'font-size': '16px' // Prevent zoom on iOS
});
// Style the input within tags
input.parent().find('.bootstrap-tagsinput input').css({
'font-size': '16px',
'min-height': '30px'
});
}
}
// Universal mobile modal initializer
function initMobileModal(config) {
if (!isMobileDevice()) return;
var modalId = config.modalId || '#modal';
var dateInputId = config.dateInputId;
var tagSelectorId = config.tagSelectorId;
var modeToggleId = config.modeToggleId;
var simpleModeDefault = config.simpleModeDefault || false;
// Convert date input to native HTML5 on mobile
if (dateInputId) {
var dateInput = $(dateInputId);
if (dateInput.length) {
dateInput.attr('type', 'date');
dateInput.removeClass('datepicker'); // Remove Bootstrap datepicker class
}
}
// Default to simple mode on mobile if toggle exists
if (modeToggleId && simpleModeDefault) {
var modeToggle = $(modeToggleId);
if (modeToggle.length && !modeToggle.is(':checked')) {
modeToggle.prop('checked', true);
// Try to call the mode toggle function if it exists
if (typeof toggleFuelEntryMode === 'function') {
toggleFuelEntryMode(true);
}
}
}
// Initialize mobile-friendly tag selector
if (tagSelectorId) {
initMobileTagSelector($(tagSelectorId));
}
// Initialize swipe to dismiss
initSwipeToDismiss(modalId);
}
// Swipe to dismiss functionality for mobile modals
function initSwipeToDismiss(modalSelector) {
if (!isMobileDevice()) return;
modalSelector = modalSelector || '#modal';
var modal = $(modalSelector);
var modalContent = modal.find('.modal-content');
var startY = 0;
var currentY = 0;
var isDragging = false;
var threshold = 100; // Minimum swipe distance to dismiss
// Remove any existing touch handlers to avoid conflicts
modalContent.off('touchstart touchmove touchend');
// Touch start
modalContent.on('touchstart', function(e) {
startY = e.originalEvent.touches[0].clientY;
isDragging = true;
modalContent.css('transition', 'none');
});
// Touch move
modalContent.on('touchmove', function(e) {
if (!isDragging) return;
currentY = e.originalEvent.touches[0].clientY;
var deltaY = currentY - startY;
// Only allow downward swipes
if (deltaY > 0) {
modalContent.css('transform', `translateY(${deltaY}px)`);
}
});
// Touch end
modalContent.on('touchend', function(e) {
if (!isDragging) return;
isDragging = false;
var deltaY = currentY - startY;
modalContent.css('transition', 'transform 0.3s ease-out');
if (deltaY > threshold) {
// Dismiss modal
modalContent.css('transform', 'translateY(100%)');
setTimeout(function() {
modal.modal('hide');
modalContent.css('transform', '');
}, 300);
} else {
// Snap back
modalContent.css('transform', '');
}
});
}
// Initialize form inputs based on device type (legacy compatibility)
function initializeFormInputs() {
// This function is maintained for backward compatibility with gasrecord.js
// New modals should use initMobileModal() instead
console.warn('initializeFormInputs() is deprecated. Use initMobileModal() instead.');
}
function getAndValidateSelectedVehicle() {
var selectedVehiclesArray = [];
$("#vehicleSelector :checked").map(function () {

View File

@@ -1,10 +1,26 @@
// Initialize tax record modal for mobile (using shared mobile framework)
function initializeTaxRecordMobile() {
initMobileModal({
modalId: '#taxRecordModal',
dateInputId: '#taxRecordDate',
tagSelectorId: '#taxRecordTag'
});
// Handle desktop initialization
if (!isMobileDevice()) {
initDatePicker($('#taxRecordDate'));
initTagSelector($("#taxRecordTag"));
}
}
function showAddTaxRecordModal() {
$.get('/Vehicle/GetAddTaxRecordPartialView', function (data) {
if (data) {
$("#taxRecordModalContent").html(data);
//initiate datepicker
initDatePicker($('#taxRecordDate'));
initTagSelector($("#taxRecordTag"));
// Initialize mobile experience using shared framework
initializeTaxRecordMobile();
$('#taxRecordModal').modal('show');
}
});
@@ -25,9 +41,10 @@ function showEditTaxRecordModal(taxRecordId, nocache) {
$.get(`/Vehicle/GetTaxRecordForEditById?taxRecordId=${taxRecordId}`, function (data) {
if (data) {
$("#taxRecordModalContent").html(data);
//initiate datepicker
initDatePicker($('#taxRecordDate'));
initTagSelector($("#taxRecordTag"));
// Initialize mobile experience using shared framework
initializeTaxRecordMobile();
$('#taxRecordModal').modal('show');
bindModalInputChanges('taxRecordModal');
$('#taxRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {

View File

@@ -1,10 +1,26 @@
// Initialize upgrade record modal for mobile (using shared mobile framework)
function initializeUpgradeRecordMobile() {
initMobileModal({
modalId: '#upgradeRecordModal',
dateInputId: '#upgradeRecordDate',
tagSelectorId: '#upgradeRecordTag'
});
// Handle desktop initialization
if (!isMobileDevice()) {
initDatePicker($('#upgradeRecordDate'));
initTagSelector($("#upgradeRecordTag"));
}
}
function showAddUpgradeRecordModal() {
$.get('/Vehicle/GetAddUpgradeRecordPartialView', function (data) {
if (data) {
$("#upgradeRecordModalContent").html(data);
//initiate datepicker
initDatePicker($('#upgradeRecordDate'));
initTagSelector($("#upgradeRecordTag"));
// Initialize mobile experience using shared framework
initializeUpgradeRecordMobile();
$('#upgradeRecordModal').modal('show');
}
});
@@ -25,9 +41,8 @@ function showEditUpgradeRecordModal(upgradeRecordId, nocache) {
$.get(`/Vehicle/GetUpgradeRecordForEditById?upgradeRecordId=${upgradeRecordId}`, function (data) {
if (data) {
$("#upgradeRecordModalContent").html(data);
//initiate datepicker
initDatePicker($('#upgradeRecordDate'));
initTagSelector($("#upgradeRecordTag"));
// Initialize mobile experience using shared framework
initializeUpgradeRecordMobile();
$('#upgradeRecordModal').modal('show');
bindModalInputChanges('upgradeRecordModal');
$('#upgradeRecordModal').off('shown.bs.modal').on('shown.bs.modal', function () {