1717
1818goog . module ( 'googlecodelabs.CodelabAnalytics' ) ;
1919
20+ const Const = goog . require ( 'goog.string.Const' ) ;
2021const EventHandler = goog . require ( 'goog.events.EventHandler' ) ;
22+ const TrustedResourceUrl = goog . require ( 'goog.html.TrustedResourceUrl' ) ;
23+ const { safeScriptEl} = goog . require ( 'safevalues.dom' ) ;
2124
2225/**
2326 * The general codelab action event fired for trackable interactions.
@@ -38,6 +41,24 @@ const PAGEVIEW_EVENT = 'google-codelab-pageview';
3841 */
3942const GAID_ATTR = 'gaid' ;
4043
44+ /**
45+ * The Google Analytics GA4 ID.
46+ * @const {string}
47+ */
48+ const GA4ID_ATTR = 'ga4id' ;
49+
50+ /** @const {string} */
51+ const GTAG = 'gtag' ;
52+
53+ /**
54+ * Namespaced data layer for use with GA4 properties. Allows for independent
55+ * data layers so that other data layers, like that for GTM, don't receive data
56+ * they don't need.
57+ *
58+ * @const {string}
59+ */
60+ const CODELAB_DATA_LAYER = 'codelabDataLayer' ;
61+
4162/** @const {string} */
4263const CODELAB_ID_ATTR = 'codelab-id' ;
4364
@@ -47,6 +68,12 @@ const CODELAB_ID_ATTR = 'codelab-id';
4768 */
4869const CODELAB_GAID_ATTR = 'codelab-gaid' ;
4970
71+ /**
72+ * The GA4ID defined by the current codelab.
73+ * @const {string}
74+ */
75+ const CODELAB_GA4ID_ATTR = 'codelab-ga4id' ;
76+
5077/** @const {string} */
5178const CODELAB_ENV_ATTR = 'environment' ;
5279
@@ -103,6 +130,9 @@ class CodelabAnalytics extends HTMLElement {
103130 /** @private {?string} */
104131 this . gaid_ ;
105132
133+ /** @private {?string} */
134+ this . ga4Id_ ;
135+
106136 /** @private {?string} */
107137 this . codelabId_ ;
108138
@@ -125,8 +155,9 @@ class CodelabAnalytics extends HTMLElement {
125155 */
126156 connectedCallback ( ) {
127157 this . gaid_ = this . getAttribute ( GAID_ATTR ) || '' ;
158+ this . ga4Id_ = this . getAttribute ( GA4ID_ATTR ) || '' ;
128159
129- if ( this . hasSetup_ || ! this . gaid_ ) {
160+ if ( this . hasSetup_ || ( ! this . gaid_ && ! this . ga4Id_ ) ) {
130161 return ;
131162 }
132163
@@ -139,6 +170,14 @@ class CodelabAnalytics extends HTMLElement {
139170 } else {
140171 this . init_ ( ) ;
141172 }
173+
174+ if ( this . ga4Id_ ) {
175+ this . initializeGa4_ ( ) ;
176+ }
177+
178+ if ( this . ga4Id_ && ! this . gaid_ ) {
179+ this . addEventListeners_ ( ) ;
180+ }
142181 }
143182
144183 /** @private */
@@ -153,7 +192,7 @@ class CodelabAnalytics extends HTMLElement {
153192 addEventListeners_ ( ) {
154193 this . eventHandler_ . listen ( document . body , ACTION_EVENT ,
155194 ( e ) => {
156- const detail = /** @type {AnalyticsTrackingEvent } */ (
195+ const detail = /** @type {! AnalyticsTrackingEvent } */ (
157196 e . getBrowserEvent ( ) . detail ) ;
158197 // Add tracking...
159198 this . trackEvent_ (
@@ -162,7 +201,7 @@ class CodelabAnalytics extends HTMLElement {
162201
163202 this . eventHandler_ . listen ( document . body , PAGEVIEW_EVENT ,
164203 ( e ) => {
165- const detail = /** @type {AnalyticsPageview } */ (
204+ const detail = /** @type {! AnalyticsPageview } */ (
166205 e . getBrowserEvent ( ) . detail ) ;
167206 this . trackPageview_ ( detail [ 'page' ] , detail [ 'title' ] ) ;
168207 } ) ;
@@ -216,6 +255,7 @@ class CodelabAnalytics extends HTMLElement {
216255 * @private
217256 */
218257 trackEvent_ ( category , opt_action , opt_label ) {
258+ // UA related section.
219259 const params = {
220260 // Always event for trackEvent_ method
221261 'hitType' : 'event' ,
@@ -227,6 +267,30 @@ class CodelabAnalytics extends HTMLElement {
227267 'eventLabel' : opt_label || '' ,
228268 } ;
229269 this . gaSend_ ( params ) ;
270+
271+ // GA4 related section.
272+ if ( ! this . getGa4Ids_ ( ) . length ) {
273+ return ;
274+ }
275+
276+ window [ CODELAB_DATA_LAYER ] = window [ CODELAB_DATA_LAYER ] || [ ] ;
277+ window [ GTAG ] = window [ GTAG ] || function ( ) {
278+ window [ CODELAB_DATA_LAYER ] . push ( arguments ) ;
279+ } ;
280+
281+ for ( const ga4Id of this . getGa4Ids_ ( ) ) {
282+ window [ GTAG ] ( 'event' , category , {
283+ // Snakecase naming convention is followed for all built-in GA4 event
284+ // properties.
285+ 'send_to' : ga4Id ,
286+ // Camelcase naming convention is followed for all custom dimensions
287+ // constructed in the custom element.
288+ 'eventAction' : opt_action || '' ,
289+ 'eventLabel' : opt_label || '' ,
290+ 'codelabEnv' : this . codelabEnv_ || '' ,
291+ 'codelabId' : this . codelabId_ || '' ,
292+ } ) ;
293+ }
230294 }
231295
232296 /**
@@ -235,6 +299,7 @@ class CodelabAnalytics extends HTMLElement {
235299 * @private
236300 */
237301 trackPageview_ ( opt_page , opt_title ) {
302+ // UA related section.
238303 const params = {
239304 'hitType' : 'pageview' ,
240305 'dimension1' : this . codelabEnv_ ,
@@ -244,6 +309,33 @@ class CodelabAnalytics extends HTMLElement {
244309 'title' : opt_title || ''
245310 } ;
246311 this . gaSend_ ( params ) ;
312+
313+ // GA4 related section.
314+ if ( ! this . getGa4Ids_ ( ) . length ) {
315+ return ;
316+ }
317+
318+ window [ CODELAB_DATA_LAYER ] = window [ CODELAB_DATA_LAYER ] || [ ] ;
319+ window [ GTAG ] = window [ GTAG ] || function ( ) {
320+ window [ CODELAB_DATA_LAYER ] . push ( arguments ) ;
321+ } ;
322+
323+ for ( const ga4Id of this . getGa4Ids_ ( ) ) {
324+ window [ GTAG ] ( 'event' , 'page_view' , {
325+ // Snakecase naming convention is followed for all built-in GA4 event
326+ // properties.
327+ 'send_to' : ga4Id ,
328+ 'page_location' :
329+ `${ document . location . origin } ${ document . location . pathname } ` ,
330+ 'page_path' : opt_page || '' ,
331+ 'page_title' : opt_title || '' ,
332+ // Camelcase naming convention is followed for all custom dimensions
333+ // constructed in the custom element.
334+ 'codelabCategory' : this . codelabCategory_ || '' ,
335+ 'codelabEnv' : this . codelabEnv_ || '' ,
336+ 'codelabId' : this . codelabId_ || '' ,
337+ } ) ;
338+ }
247339 }
248340
249341 /**
@@ -385,6 +477,68 @@ class CodelabAnalytics extends HTMLElement {
385477 }
386478 return isCreated ;
387479 }
480+
481+ /**
482+ * Gets all GA4 IDs for the current page.
483+ * @return {!Array<string> }
484+ * @private
485+ */
486+ getGa4Ids_ ( ) {
487+ if ( ! this . ga4Id_ ) {
488+ return [ ] ;
489+ }
490+ const ga4Ids = [ ] ;
491+ ga4Ids . push ( this . ga4Id_ ) ;
492+ const codelabGa4Id = this . getAttribute ( CODELAB_GA4ID_ATTR ) ;
493+ if ( codelabGa4Id ) {
494+ ga4Ids . push ( codelabGa4Id ) ;
495+ }
496+ if ( ga4Ids . length ) {
497+ return ga4Ids ;
498+ }
499+ return [ ] ;
500+ }
501+
502+ /**
503+ * Initialize the gtag script element and namespaced data layer based on the
504+ * codelabs primary GA4 ID.
505+ * @private
506+ */
507+ initializeGa4_ ( ) {
508+ if ( ! this . ga4Id_ ) {
509+ return ;
510+ }
511+
512+ // First, set the GTAG data layer before pushing anything to it.
513+ window [ CODELAB_DATA_LAYER ] = window [ CODELAB_DATA_LAYER ] || [ ] ;
514+
515+ const firstScriptElement = document . querySelector ( 'script' ) ;
516+ const gtagScriptElement = /** @type {!HTMLScriptElement } */ (
517+ document . createElement ( 'script' ) ) ;
518+ gtagScriptElement . async = true ;
519+ // Key for the formatted params below:
520+ // 'id': the stream id for the GA4 analytics property. The gtag script
521+ // element must only be created once, and only the ID of the primary
522+ // stream is appended when creating the src for that element.
523+ // Additional streams are initialized via the function call
524+ // `window[GTAG]('config', ga4Id...`
525+ // 'l': the namespaced dataLayer used to separate codelabs related GA4
526+ // data from other data layers that may exist on a site or page.
527+ safeScriptEl . setSrc (
528+ gtagScriptElement , TrustedResourceUrl . formatWithParams (
529+ Const . from ( '//www.googletagmanager.com/gtag/js' ) ,
530+ { } , { 'id' : this . ga4Id_ , 'l' : CODELAB_DATA_LAYER } ) ) ;
531+ firstScriptElement . parentNode . insertBefore (
532+ gtagScriptElement , firstScriptElement ) ;
533+
534+ window [ GTAG ] = function ( ) {
535+ window [ CODELAB_DATA_LAYER ] . push ( arguments ) ;
536+ } ;
537+ window [ GTAG ] ( 'js' , new Date ( Date . now ( ) ) ) ;
538+
539+ // Set send_page_view to false. We send pageviews manually.
540+ window [ GTAG ] ( 'config' , this . ga4Id_ , { send_page_view : false } ) ;
541+ }
388542}
389543
390544exports = CodelabAnalytics ;
0 commit comments