mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
88e90d5601
Swaps out usage of `AbstractEvent` with `SyntheticEvent` (and subclasses).
231 lines
8.5 KiB
JavaScript
231 lines
8.5 KiB
JavaScript
/**
|
|
* Copyright 2013 Facebook, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* @providesModule AnalyticsEventPluginFactory
|
|
*
|
|
* This module provides a factory method to create the AnalyticsEventPlugin that
|
|
* can be used to track the usage of React components that are of interest to
|
|
* the user.
|
|
*
|
|
* In order to enable a component for analytics tracking, you need to specify
|
|
* two additional attributes to the component when you describe the structure of
|
|
* your component in the render() method:
|
|
*
|
|
* 1. 'data-analytics-id': This represents a unique ID that the analytics module
|
|
* will use to identify this component in all the analytics data. Note that
|
|
* this is independent of the ref or the DOM id of the element. Over the
|
|
* lifetime of the product, even if the component id or ref needs to be
|
|
* changed, as long as you can ensure the analytics ID doesnt change, the
|
|
* historical data can be correlated. Also note that React does NOT do
|
|
* anything to guarantee or enforce uniqueness of this ID. If its not unique
|
|
* the analytics data reported will be incorrect.
|
|
*
|
|
* 2. 'data-analytics-events': This is a comma separated list of DOM events that
|
|
* you want analytics on. React currently supports tracking only on a
|
|
* distinct set of events (See topLevelTypesToAnalyticsEvent).
|
|
* If the list contains an event that React does not recognize for analytics
|
|
* tracking, in __DEV__, an error will be thrown. Note that it is case
|
|
* sensitive and space sensitive.
|
|
*
|
|
* By default the AnalyticsEventPlugin is NOT enabled in React. To use it, you
|
|
* need to create the plugin using the factory method and add it to the list of
|
|
* Plugins maintained in the EventPluginHub before your component is rendered.
|
|
* As creation parameters you can specify two arguments:
|
|
*
|
|
* 1. callback: This is a required parameter. In __DEV__, an error will be
|
|
* thrown if this param is missing. The callback will be called with the
|
|
* analyticsData as an argument. The analyticsData will contain one property
|
|
* per every React component, identified by its data-analytics-id. The value
|
|
* of this property will be an object containing properties corresponding to
|
|
* each of the comma separated events specified in data-analytics-events.
|
|
*
|
|
* For example, if you have:
|
|
* <Button ...
|
|
* data-analytics-id="createButton"
|
|
* data-analytics-events="click"
|
|
* />
|
|
* and
|
|
* <TextBox ...
|
|
* data-analytics-id="disclaimerBox"
|
|
* data-analytics-events="focus,scroll"
|
|
* />
|
|
* analyticsData will be something like:
|
|
* '{"createButton":{"click":50}, "disclaimerBox":{"focus":15, "scroll":5}}'
|
|
*
|
|
* DO NOT mutate the data that you get in the callback. Mutating it will lead
|
|
* to errors and unstable behavior.
|
|
*
|
|
* The React component will be included for analytics as long as some user
|
|
* interaction has happened with that component. If no user interaction has
|
|
* happened with any of the components tracked for analytics, the callback
|
|
* will not be called.
|
|
*
|
|
* 2. interval (in milliseconds): This is an optional parameter to specify the
|
|
* interval at which the callback needs to be called. It needs to be greater
|
|
* than the 2 minutes (which is the default value if this parameter is not
|
|
* specified or a value less than 2 minutes is specified)
|
|
*
|
|
* Please refer to the unit tests AnalyticsEventPlugin-test.js for details on
|
|
* usage.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
var ExecutionEnvironment = require('ExecutionEnvironment');
|
|
|
|
var emptyFunction = require('emptyFunction');
|
|
var invariant = require('invariant');
|
|
var topLevelTypes = require('EventConstants').topLevelTypes;
|
|
|
|
var ANALYTICS_ID = 'data-analytics-id';
|
|
var ANALYTICS_EVENTS = 'data-analytics-events';
|
|
var DEFAULT_INTERVAL_MS = 2 * 60 * 1000; // 2 minutes
|
|
|
|
var analyticsData = {};
|
|
|
|
// List of topLevel event types that React supports for analytics tracking
|
|
var topLevelTypesToAnalyticsEvent = {
|
|
topClick: 'click',
|
|
topDoubleClick: 'doubleClick',
|
|
wheel: 'wheel',
|
|
topTouchStart: 'touchStart',
|
|
topTouchEnd: 'touchEnd',
|
|
topTouchMove: 'touchMove',
|
|
topTouchCancel: 'touchCancel',
|
|
topKeyUp: 'keyUp',
|
|
topKeyPress: 'keyPress',
|
|
topKeyDown: 'keyDown',
|
|
topFocus: 'focus',
|
|
topBlur: 'blur',
|
|
topScroll: 'scroll',
|
|
topChange: 'change'
|
|
};
|
|
|
|
if (__DEV__) {
|
|
var analyticsEventNameToTopLevelType = {
|
|
'click': topLevelTypes.topClick,
|
|
'doubleClick': topLevelTypes.topDoubleClick,
|
|
'wheel': topLevelTypes.wheel,
|
|
'touchStart': topLevelTypes.topTouchStart,
|
|
'touchEnd': topLevelTypes.topTouchEnd,
|
|
'touchMove': topLevelTypes.topTouchMove,
|
|
'touchCancel': topLevelTypes.topTouchCancel,
|
|
'keyUp': topLevelTypes.topKeyUp,
|
|
'keyPress': topLevelTypes.topKeyPress,
|
|
'keyDown': topLevelTypes.topKeyDown,
|
|
'focus': topLevelTypes.topFocus,
|
|
'blur': topLevelTypes.topBlur,
|
|
'scroll': topLevelTypes.topScroll,
|
|
'change': topLevelTypes.topChange
|
|
};
|
|
}
|
|
|
|
/**
|
|
* This plugin does not really extract any synthetic events. Rather it just
|
|
* looks at the top-level event and bumps up counters as appropriate
|
|
*
|
|
* @param {string} topLevelType Record from `EventConstants`.
|
|
* @param {DOMEventTarget} topLevelTarget The listening component root node.
|
|
* @param {string} topLevelTargetID ID of `topLevelTarget`.
|
|
* @param {object} nativeEvent Native browser event.
|
|
* @return {*} An accumulation of synthetic events.
|
|
* @see {EventPluginHub.extractEvents}
|
|
*/
|
|
function extractEvents(
|
|
topLevelType,
|
|
topLevelTarget,
|
|
topLevelTargetID,
|
|
nativeEvent) {
|
|
var currentEvent = topLevelTypesToAnalyticsEvent[topLevelType];
|
|
if (!currentEvent || !topLevelTarget || !topLevelTarget.attributes) {
|
|
return null;
|
|
}
|
|
|
|
var analyticsIDAttribute = topLevelTarget.attributes[ANALYTICS_ID];
|
|
var analyticsEventsAttribute = topLevelTarget.attributes[ANALYTICS_EVENTS];
|
|
if(!analyticsIDAttribute || !analyticsEventsAttribute) {
|
|
return null;
|
|
}
|
|
|
|
var analyticsEventsArr = analyticsEventsAttribute.value.split(",");
|
|
var analyticsID = analyticsIDAttribute.value;
|
|
if (!analyticsData[analyticsID]) {
|
|
initAnalyticsDataForID(analyticsID, analyticsEventsArr);
|
|
}
|
|
|
|
if (analyticsEventsArr.indexOf(currentEvent) !== -1) {
|
|
analyticsData[analyticsID][currentEvent]++;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Initialize the analytics data for a specific element identified by the
|
|
* analyticsID - Create an entry in the analyticsData object for the element and
|
|
* initialize all counters for that element to 0.
|
|
*/
|
|
function initAnalyticsDataForID(analyticsID, analyticsEventsArr) {
|
|
analyticsData[analyticsID] = {};
|
|
analyticsEventsArr.forEach(function(analyticsEvent) {
|
|
if (__DEV__) {
|
|
invariant(
|
|
analyticsEventNameToTopLevelType[analyticsEvent],
|
|
'Invalid analyticsEvent:%s for analyticsID:%s',
|
|
analyticsEvent,
|
|
analyticsID
|
|
);
|
|
}
|
|
analyticsData[analyticsID][analyticsEvent] = 0;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns the analytics event plugin given the callback that needs to be
|
|
* invoked for reporting analytics and the interval at which the callback needs
|
|
* to be invoked. This interval has to be atleast DEFAULT_INTERVAL_MS.
|
|
*/
|
|
var createAnalyticsPlugin = function(cb, interval) {
|
|
invariant(
|
|
ExecutionEnvironment.canUseDOM,
|
|
'createAnalyticsPlugin(...): The DOM is not supported in the execution ' +
|
|
'environment.'
|
|
);
|
|
|
|
if (__DEV__) {
|
|
invariant(cb, 'createAnalyticsPlugin(...): You must provide a callback.');
|
|
}
|
|
cb = cb || emptyFunction;
|
|
|
|
setInterval(
|
|
function() {
|
|
if (Object.keys(analyticsData).length) {
|
|
// Invoke the callback with a clone of analyticsData, otherwise our
|
|
// analyticsData will be dirtied by user changes
|
|
cb(analyticsData);
|
|
}
|
|
},
|
|
interval > DEFAULT_INTERVAL_MS ? interval : DEFAULT_INTERVAL_MS
|
|
);
|
|
|
|
return {extractEvents: extractEvents};
|
|
};
|
|
|
|
var AnalyticsEventPluginFactory = {
|
|
createAnalyticsPlugin: createAnalyticsPlugin
|
|
};
|
|
|
|
module.exports = AnalyticsEventPluginFactory;
|