22 * SouJava 30-Year Celebration Week - Event Application
33 * Optimized JavaScript with Sessionize API integration
44 * FIXES: Proper session scheduling, timezone handling, and data validation
5+ * FIXED: Analytics tracking to prevent "(not set)" values
56 */
67
78// Application State
@@ -10,6 +11,7 @@ const AppState = {
1011 currentTimezone : 'America/Sao_Paulo' ,
1112 isLoading : true ,
1213 error : null ,
14+ isDataReady : false , // New flag to prevent race conditions
1315 filters : {
1416 day : '' ,
1517 language : '' ,
@@ -29,7 +31,8 @@ const CONFIG = {
2931 speakers : [ ] ,
3032 categories : [ ] ,
3133 rooms : [ ]
32- }
34+ } ,
35+ DEBUG_ANALYTICS : false // Disable debug logging for production
3336} ;
3437
3538// Utility Functions
@@ -104,6 +107,14 @@ const Utils = {
104107 return div . innerHTML ;
105108 } ,
106109
110+ // New utility to ensure non-empty strings for analytics
111+ ensureNonEmptyString ( value , fallback = 'Unknown' ) {
112+ if ( ! value || typeof value !== 'string' || value . trim ( ) === '' ) {
113+ return fallback ;
114+ }
115+ return value . trim ( ) ;
116+ } ,
117+
107118 debounce ( func , wait ) {
108119 let timeout ;
109120 return function executedFunction ( ...args ) {
@@ -198,14 +209,18 @@ class DataLoader {
198209 static transformSessionizeData ( sessionizeData ) {
199210 const transformed = {
200211 speakers : { } ,
201- schedule : [ ]
212+ schedule : [ ] ,
213+ sessionsById : { } // New: Store sessions by ID for quick lookup
202214 } ;
203215
204- // Transform speakers
216+ // Transform speakers with validation
205217 if ( sessionizeData . speakers ) {
206218 sessionizeData . speakers . forEach ( speaker => {
219+ // Validate speaker data
220+ const speakerName = Utils . ensureNonEmptyString ( speaker . fullName , 'Unknown Speaker' ) ;
221+
207222 transformed . speakers [ speaker . id ] = {
208- name : speaker . fullName ,
223+ name : speakerName ,
209224 title : speaker . tagLine || '' ,
210225 bio : speaker . bio || '' ,
211226 image : speaker . profilePicture || '' ,
@@ -239,10 +254,13 @@ class DataLoader {
239254 const timeInfo = Utils . parseSessionizeTime ( session . startsAt ) ;
240255 if ( ! timeInfo ) return ;
241256
257+ // Validate session title
258+ const sessionTitle = Utils . ensureNonEmptyString ( session . title , 'Untitled Session' ) ;
259+
242260 const transformedSession = {
243261 id : session . id . toString ( ) ,
244262 time : timeInfo . time ,
245- title : session . title ,
263+ title : sessionTitle ,
246264 description : session . description || '' ,
247265 language : this . getLanguageFromCategories ( session . categoryItems , categoryLookup ) ,
248266 type : this . getTypeFromCategories ( session . categoryItems , categoryLookup ) ,
@@ -255,6 +273,9 @@ class DataLoader {
255273 room : sessionizeData . rooms ?. find ( r => r . id === session . roomId ) ?. name || ''
256274 } ;
257275
276+ // Store in sessionsById for quick lookup
277+ transformed . sessionsById [ transformedSession . id ] = transformedSession ;
278+
258279 // Find or create day in schedule
259280 let daySchedule = transformed . schedule . find ( day => day . date === timeInfo . date ) ;
260281 if ( ! daySchedule ) {
@@ -455,16 +476,37 @@ class ModalHandler {
455476 }
456477
457478 showSpeaker ( speakerId ) {
479+ // Check if data is ready
480+ if ( ! AppState . isDataReady ) {
481+ console . warn ( '[Analytics] Attempted to show speaker before data loaded' ) ;
482+ return ;
483+ }
484+
485+ if ( CONFIG . DEBUG_ANALYTICS ) {
486+ console . log ( '[Analytics Debug] showSpeaker called with speakerId:' , speakerId ) ;
487+ }
488+
458489 const speaker = AppState . eventData ?. speakers [ speakerId ] ;
459490
460491 if ( ! speaker ) {
492+ console . warn ( '[Analytics] No speaker found for ID:' , speakerId ) ;
493+ // Track the failed lookup
494+ Analytics . trackSpeakerProfileView ( 'Speaker Not Found' , false ) ;
461495 return ;
462496 }
463497
464- // Track speaker view
498+ // Ensure speaker name is valid
499+ const speakerName = Utils . ensureNonEmptyString ( speaker . name , 'Unknown Speaker' ) ;
500+
501+ // Track speaker view with validated data
465502 const isJavaChampion = speaker . title ?. toLowerCase ( ) . includes ( 'java champion' ) ||
466503 speaker . bio ?. toLowerCase ( ) . includes ( 'java champion' ) ;
467- Analytics . trackSpeakerProfileView ( speaker . name , isJavaChampion ) ;
504+
505+ if ( CONFIG . DEBUG_ANALYTICS ) {
506+ console . log ( '[Analytics Debug] Tracking speaker:' , speakerName , 'isJavaChampion:' , isJavaChampion ) ;
507+ }
508+
509+ Analytics . trackSpeakerProfileView ( speakerName , isJavaChampion ) ;
468510
469511 this . populateSpeakerContent ( speaker ) ;
470512 this . showModal ( this . speakerModal ) ;
@@ -912,6 +954,12 @@ class ScheduleRenderer {
912954 }
913955
914956 bindEvents ( ) {
957+ // Only bind events if data is ready
958+ if ( ! AppState . isDataReady ) {
959+ console . warn ( '[Events] Attempted to bind events before data loaded' ) ;
960+ return ;
961+ }
962+
915963 // Session toggle events
916964 this . container . addEventListener ( 'click' , ( e ) => {
917965 const toggleElement = e . target . closest ( '[data-session-toggle]' ) ;
@@ -938,7 +986,7 @@ class ScheduleRenderer {
938986 this . container . addEventListener ( 'click' , ( e ) => {
939987 if ( e . target . classList . contains ( 'speaker-avatar' ) || e . target . classList . contains ( 'speaker-fallback' ) ) {
940988 const speakerId = e . target . dataset . speakerId ;
941- if ( speakerId ) {
989+ if ( speakerId && AppState . isDataReady ) {
942990 this . modalHandler . showSpeaker ( speakerId ) ;
943991 }
944992 }
@@ -949,7 +997,7 @@ class ScheduleRenderer {
949997 const speakerDetail = e . target . closest ( '.session-speaker-detail' ) ;
950998 if ( speakerDetail ) {
951999 const speakerId = speakerDetail . dataset . speakerId ;
952- if ( speakerId ) {
1000+ if ( speakerId && AppState . isDataReady ) {
9531001 this . modalHandler . showSpeaker ( speakerId ) ;
9541002 }
9551003 }
@@ -966,12 +1014,26 @@ class ScheduleRenderer {
9661014
9671015 const isExpanded = sessionElement . classList . contains ( 'expanded' ) ;
9681016
969- // Track session view when expanding
1017+ // Track session view when expanding - USE ACTUAL SESSION DATA
9701018 if ( ! isExpanded ) {
971- const sessionTitle = sessionElement . querySelector ( '.session-title' ) ?. textContent || 'Unknown Session' ;
972- const topicsElements = sessionElement . querySelectorAll ( '.badge-topic' ) ;
973- const topics = Array . from ( topicsElements ) . map ( el => el . textContent . trim ( ) ) ;
974- Analytics . trackSessionDetailsView ( sessionTitle , topics ) ;
1019+ // Get session from AppState instead of DOM
1020+ const session = AppState . eventData ?. sessionsById [ sessionId ] ;
1021+
1022+ if ( session ) {
1023+ // Validate session title
1024+ const sessionTitle = Utils . ensureNonEmptyString ( session . title , 'Unknown Session' ) ;
1025+ const topics = session . topics || [ ] ;
1026+
1027+ if ( CONFIG . DEBUG_ANALYTICS ) {
1028+ console . log ( '[Analytics Debug] Tracking session:' , sessionTitle , 'topics:' , topics ) ;
1029+ }
1030+
1031+ Analytics . trackSessionDetailsView ( sessionTitle , topics ) ;
1032+ } else {
1033+ console . warn ( '[Analytics] Session not found in AppState:' , sessionId ) ;
1034+ // Fallback tracking
1035+ Analytics . trackSessionDetailsView ( 'Session Not Found' , [ ] ) ;
1036+ }
9751037 }
9761038
9771039 if ( isExpanded ) {
@@ -1050,10 +1112,14 @@ class ScheduleRenderer {
10501112 }
10511113
10521114 findSessionById ( sessionId ) {
1053- for ( const day of AppState . eventData . schedule ) {
1054- const session = day . sessions . find ( s => s . id === sessionId ) ;
1055- if ( session ) {
1056- return { ...session , date : day . date , dayName : day . dayName } ;
1115+ // Use the new sessionsById lookup for better performance
1116+ const session = AppState . eventData ?. sessionsById [ sessionId ] ;
1117+ if ( session ) {
1118+ // Find the date from schedule
1119+ for ( const day of AppState . eventData . schedule ) {
1120+ if ( day . sessions . some ( s => s . id === sessionId ) ) {
1121+ return { ...session , date : day . date , dayName : day . dayName } ;
1122+ }
10571123 }
10581124 }
10591125 return null ;
@@ -1256,6 +1322,12 @@ class SpeakersRenderer {
12561322 }
12571323
12581324 bindEvents ( ) {
1325+ // Only bind if data is ready
1326+ if ( ! AppState . isDataReady ) {
1327+ console . warn ( '[Events] Attempted to bind speaker events before data loaded' ) ;
1328+ return ;
1329+ }
1330+
12591331 this . container . addEventListener ( 'click' , ( e ) => {
12601332 const speakerCard = e . target . closest ( '.speaker-card' ) ;
12611333 if ( speakerCard && ! e . target . closest ( '.speaker-social' ) ) {
@@ -1351,8 +1423,21 @@ class AnimationController {
13511423// Analytics Helper with Specific Event Names
13521424const Analytics = {
13531425 track ( eventName , parameters = { } ) {
1426+ // Validate all parameters before sending
1427+ const validatedParams = { } ;
1428+
1429+ Object . entries ( parameters ) . forEach ( ( [ key , value ] ) => {
1430+ // Ensure all values are non-empty strings
1431+ validatedParams [ key ] = Utils . ensureNonEmptyString ( value , 'Not Specified' ) ;
1432+ } ) ;
1433+
13541434 if ( typeof gtag !== 'undefined' ) {
1355- gtag ( 'event' , eventName , parameters ) ;
1435+ gtag ( 'event' , eventName , validatedParams ) ;
1436+ }
1437+
1438+ // Debug logging
1439+ if ( CONFIG . DEBUG_ANALYTICS ) {
1440+ console . log ( '[Analytics] Event:' , eventName , 'Params:' , validatedParams ) ;
13561441 }
13571442 } ,
13581443
@@ -1372,35 +1457,47 @@ const Analytics = {
13721457 } ) ;
13731458 } ,
13741459
1375- // Track session detail views
1460+ // Track session detail views - FIXED
13761461 trackSessionDetailsView ( sessionTitle , sessionTopics = [ ] ) {
1462+ // Ensure session title is valid
1463+ const validTitle = Utils . ensureNonEmptyString ( sessionTitle , 'Unknown Session' ) ;
1464+ const validTopics = sessionTopics . filter ( t => t && t . trim ( ) ) . join ( ', ' ) || 'No Topics' ;
1465+
13771466 this . track ( 'view_session_details' , {
1378- session_title : sessionTitle ,
1379- session_topics : sessionTopics . join ( ', ' ) ,
1467+ session_title : validTitle ,
1468+ session_topics : validTopics ,
13801469 topics_count : sessionTopics . length
13811470 } ) ;
13821471 } ,
13831472
1384- // Track speaker profile views
1473+ // Track speaker profile views - ENHANCED
13851474 trackSpeakerProfileView ( speakerName , isJavaChampion = false ) {
1475+ // Ensure speaker name is valid
1476+ const validName = Utils . ensureNonEmptyString ( speakerName , 'Unknown Speaker' ) ;
1477+
13861478 this . track ( 'view_speaker_profile' , {
1387- speaker_name : speakerName ,
1479+ speaker_name : validName ,
13881480 is_java_champion : isJavaChampion
13891481 } ) ;
13901482 } ,
13911483
13921484 // Track filter usage
13931485 trackFilterUse ( filterType , filterValue ) {
1486+ const validType = Utils . ensureNonEmptyString ( filterType , 'Unknown Filter' ) ;
1487+ const validValue = Utils . ensureNonEmptyString ( filterValue , 'No Value' ) ;
1488+
13941489 this . track ( 'filter_used' , {
1395- filter_type : filterType ,
1396- filter_value : filterValue
1490+ filter_type : validType ,
1491+ filter_value : validValue
13971492 } ) ;
13981493 } ,
13991494
14001495 // Track timezone changes
14011496 trackTimezoneChange ( timezone ) {
1497+ const validTimezone = Utils . ensureNonEmptyString ( timezone , 'Unknown Timezone' ) ;
1498+
14021499 this . track ( 'change_timezone' , {
1403- timezone : timezone
1500+ timezone : validTimezone
14041501 } ) ;
14051502 }
14061503} ;
@@ -1506,12 +1603,14 @@ class App {
15061603 async init ( ) {
15071604 try {
15081605 AppState . isLoading = true ;
1606+ AppState . isDataReady = false ; // Ensure data ready flag is false
15091607 this . scheduleRenderer . renderLoading ( ) ;
15101608 this . speakersRenderer . renderLoading ( ) ;
15111609
15121610 AppState . eventData = await DataLoader . loadEventData ( ) ;
15131611 AppState . isLoading = false ;
15141612 AppState . error = null ;
1613+ AppState . isDataReady = true ; // Set data ready flag
15151614
15161615 // Preload speaker images
15171616 if ( AppState . eventData . speakers ) {
@@ -1527,6 +1626,7 @@ class App {
15271626
15281627 } catch ( error ) {
15291628 AppState . isLoading = false ;
1629+ AppState . isDataReady = false ;
15301630 AppState . error = error . message ;
15311631
15321632 this . scheduleRenderer . renderError ( error . message ) ;
0 commit comments