ExamplesPlaygroundReference Source

coral-spectrum/coral-utils/src/scripts/Tracking.js

  1. /**
  2. * Copyright 2019 Adobe. All rights reserved.
  3. * This file is licensed to you under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License. You may obtain a copy
  5. * of the License at http://www.apache.org/licenses/LICENSE-2.0
  6. *
  7. * Unless required by applicable law or agreed to in writing, software distributed under
  8. * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
  9. * OF ANY KIND, either express or implied. See the License for the specific language
  10. * governing permissions and limitations under the License.
  11. */
  12.  
  13. /**
  14. Signature function used to track the usage of Coral components. By default, there is no out of the box
  15. implementation as tracking is agnostic of the underlying technology.
  16.  
  17. You need to implement a new tracker and add it with: <code>Coral.tracking.addListener(fn(){ });</code>
  18.  
  19. The <code>fn()</code> callback will receive multiple arguments:
  20. * <code>trackData</code> - an object with fixed structure e.g. <code>{type: "button", eventType: "click", element: "save settings", feature: "sites"}</code>
  21. * <code>event</code> - the CustomEvent or MouseEvent details object.
  22. * <code>component</code> - the component reference object.
  23.  
  24. Using the above data you can map it to your own analytics tracker.
  25. */
  26. class Tracking {
  27. /* @ignore */
  28. constructor() {
  29. /**
  30. All registered trackers.
  31.  
  32. @type {Array<Function>}
  33. */
  34. this._trackers = [];
  35. }
  36.  
  37. /**
  38. Returns <code>true</code> if the tracking is disabled for the given component, otherwise false.
  39.  
  40. @param {HTMLElement} component
  41. @returns {Boolean}
  42. */
  43. _isTrackingDisabledForComponent(component) {
  44. return component &&
  45. typeof component.tracking !== 'undefined' &&
  46. component.tracking === component.constructor.tracking.OFF;
  47. }
  48.  
  49. /**
  50. Get tracking annotations from the parent Component to which the event was bound.
  51.  
  52. @param {BaseComponent} component
  53. @returns {{trackingElement: String, trackingElement: String}}
  54. */
  55. _getTrackingDataFromComponentAttr(component) {
  56. if (this._isTrackingDisabledForComponent(component)) {
  57. return {
  58. trackingElement: '',
  59. trackingFeature: ''
  60. };
  61. }
  62.  
  63. // Eg. from DOM trackingfeature="sites"
  64. const trackingFeature = component.trackingFeature || '';
  65.  
  66. // Eg. from DOM trackingelement="rail toggle"
  67. const trackingElement = component.trackingElement || '';
  68.  
  69. return {trackingFeature, trackingElement};
  70. }
  71.  
  72. /**
  73. Returns a tracking data object that can be used to compile data to send to an actual Analytics tracker.
  74.  
  75. @param {String} eventType
  76. @param {String} targetType
  77. @param {CustomEvent} event
  78. @param {BaseComponent} component
  79. @param {BaseComponent} childComponent
  80. @returns {Object} An object with the tracking data.
  81. */
  82. _createTrackingData(eventType, targetType, event, component, childComponent) {
  83. const parentComponentType = (component.getAttribute('is') || component.tagName).toLowerCase();
  84.  
  85. // Gather data into the Coral Tracking structure.
  86. const trackDataFromAttr = this._getTrackingDataFromComponentAttr(component);
  87.  
  88. // Compile data
  89. /**
  90. The default Coral tracking data object
  91. filled with values from root Component and child Component (if exists).
  92. @type {{targetType: string, targetElement: string, eventType: string, rootElement: string, rootFeature: string, rootType: string}}
  93. */
  94. return {
  95. targetType: targetType || parentComponentType || '',
  96. targetElement: childComponent && childComponent.trackingElement ? childComponent.trackingElement : component.trackingElement,
  97. eventType: eventType || event.type,
  98. rootElement: trackDataFromAttr.trackingElement,
  99. rootFeature: trackDataFromAttr.trackingFeature,
  100. rootType: parentComponentType
  101. };
  102. }
  103.  
  104. /**
  105. Add a tracking callback. This will be invoked every time a tracking event is emitted.
  106.  
  107. @param {TrackingCallback} trackingCallback
  108. The callback to execute.
  109. */
  110. addListener(trackingCallback) {
  111. if (typeof trackingCallback !== 'function') {
  112. throw new Error('Coral.Tracking: Tracker must be a function callback.');
  113. }
  114.  
  115. if (this._trackers.indexOf(trackingCallback) !== -1) {
  116. throw new Error('Coral.Tracking: Tracker callback cannot be added twice.');
  117. }
  118.  
  119. this._trackers.push(trackingCallback);
  120. }
  121.  
  122. /**
  123. Removes a tracker.
  124.  
  125. @param {TrackingCallback} trackingCallback
  126. */
  127. removeListener(trackingCallback) {
  128. this._trackers = this._trackers.filter(trackerFn => trackerFn !== trackingCallback);
  129. }
  130.  
  131. /**
  132. Notify all trackers subscribed.
  133.  
  134. @param {String} eventType
  135. Eg. click, select, etc.
  136. @param {String} targetType
  137. Eg. cycle button, cycle button item, etc.
  138. @param {CustomEvent} event
  139. @param {BaseComponent} component
  140. @param {BaseComponent} childComponent
  141. Optional, in case the event occurred on a child component.
  142. @returns {Boolean} if the event was dispatch to at least 1 tracker.
  143. */
  144. track(eventType, targetType, event, component, childComponent) {
  145. if (
  146. this._trackers.length === 0 ||
  147. this._isTrackingDisabledForComponent(component) ||
  148. this._isTrackingDisabledForComponent(childComponent)
  149. ) {
  150. return false;
  151. }
  152.  
  153. const args = Array.prototype.slice.call(arguments, [2]);
  154.  
  155. const trackingData = this._createTrackingData(eventType, targetType, event, component, childComponent);
  156. this._trackers.forEach((trackerFn) => {
  157. trackerFn.apply(null, [trackingData].concat(args));
  158. });
  159.  
  160. return true;
  161. }
  162. }
  163.  
  164. /**
  165. Executes the callback when ever there is an interaction inside the component that needs to be tracked. This can be used
  166. to get insight on how users interact with the page and the features that available.
  167.  
  168. @typedef {function} TrackingCallback
  169.  
  170. @param {Object} trackData
  171. Object containing the data to be tracked. It contains the properties <code>type</code>, <code>eventType</code>,
  172. <code>element</code> and <code>feature</code>.
  173. @param {CustomEvent} event
  174. Underlying event that was generated by the user
  175. @param {HTMLElement} component
  176. Component that triggered the tracking event.
  177. */
  178. /**
  179. Tracking API to get insight on component usage.
  180.  
  181. @type {Tracking}
  182. */
  183. const tracking = new Tracking();
  184. export default tracking;