debian-mirror-gitlab/snowplow-javascript-tracker/src/js/forms.js

190 lines
7.0 KiB
JavaScript
Executable File

/*
* JavaScript tracker for Snowplow: forms.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var forEach = require('lodash/forEach'),
filter = require('lodash/filter'),
find = require('lodash/find'),
helpers = require('./lib/helpers'),
object = typeof exports !== 'undefined' ? exports : this;
/**
* Object for handling automatic form tracking
*
* @param object core The tracker core
* @param string trackerId Unique identifier for the tracker instance, used to mark tracked elements
* @param function contextAdder Function to add common contexts like PerformanceTiming to all events
* @return object formTrackingManager instance
*/
object.getFormTrackingManager = function (core, trackerId, contextAdder) {
// Tag names of mutable elements inside a form
var innerElementTags = ['textarea', 'input', 'select'];
// Used to mark elements with event listeners
var trackingMarker = trackerId + 'form';
// Filter to determine which forms should be tracked
var formFilter = function () { return true };
// Filter to determine which form fields should be tracked
var fieldFilter = function () { return true };
// Default function applied to all elements, optionally overridden by transform field
var fieldTransform = function (x) { return x };
/*
* Get an identifier for a form, input, textarea, or select element
*/
function getFormElementName(elt) {
return elt[find(['name', 'id', 'type', 'nodeName'], function (propName) {
// If elt has a child whose name is "id", that element will be returned
// instead of the actual id of elt unless we ensure that a string is returned
return elt[propName] && typeof elt[propName] === 'string';
})];
}
/*
* Identifies the parent form in which an element is contained
*/
function getParentFormName(elt) {
while (elt && elt.nodeName && elt.nodeName.toUpperCase() !== 'HTML' && elt.nodeName.toUpperCase() !== 'FORM') {
elt = elt.parentNode;
}
if (elt && elt.nodeName && elt.nodeName.toUpperCase() === 'FORM') {
return getFormElementName(elt);
}
}
/*
* Returns a list of the input, textarea, and select elements inside a form along with their values
*/
function getInnerFormElements(elt) {
var innerElements = [];
forEach(innerElementTags, function (tagname) {
var trackedChildren = filter(elt.getElementsByTagName(tagname), function (child) {
return child.hasOwnProperty(trackingMarker);
});
forEach(trackedChildren, function (child) {
if (child.type === 'submit') {
return;
}
var elementJson = {
name: getFormElementName(child),
value: child.value,
nodeName: child.nodeName
};
if (child.type && child.nodeName.toUpperCase() === 'INPUT') {
elementJson.type = child.type;
}
if ((child.type === 'checkbox' || child.type === 'radio') && !child.checked) {
elementJson.value = null;
}
innerElements.push(elementJson);
});
});
return innerElements;
}
/*
* Return function to handle form field change event
*/
function getFormChangeListener(event_type, context) {
return function (e) {
var elt = e.target;
var type = (elt.nodeName && elt.nodeName.toUpperCase() === 'INPUT') ? elt.type : null;
var value = (elt.type === 'checkbox' && !elt.checked) ? null : fieldTransform(elt.value);
if (event_type === 'change_form' || (type !== 'checkbox' && type !== 'radio')) {
core.trackFormFocusOrChange(event_type, getParentFormName(elt), getFormElementName(elt), elt.nodeName, type, helpers.getCssClasses(elt), value, contextAdder(helpers.resolveDynamicContexts(context, elt, type, value)));
}
};
}
/*
* Return function to handle form submission event
*/
function getFormSubmissionListener(context) {
return function (e) {
var elt = e.target;
var innerElements = getInnerFormElements(elt);
forEach(innerElements, function (innerElement) {
innerElement.value = fieldTransform(innerElement.value);
});
core.trackFormSubmission(getFormElementName(elt), helpers.getCssClasses(elt), innerElements, contextAdder(helpers.resolveDynamicContexts(context, elt, innerElements)));
};
}
return {
/*
* Configures form tracking: which forms and fields will be tracked, and the context to attach
*/
configureFormTracking: function (config) {
if (config) {
formFilter = helpers.getFilter(config.forms, true);
fieldFilter = helpers.getFilter(config.fields, false);
fieldTransform = helpers.getTransform(config.fields);
}
},
/*
* Add submission event listeners to all form elements
* Add value change event listeners to all mutable inner form elements
*/
addFormListeners: function (context) {
forEach(document.getElementsByTagName('form'), function (form) {
if (formFilter(form) && !form[trackingMarker]) {
forEach(innerElementTags, function (tagname) {
forEach(form.getElementsByTagName(tagname), function (innerElement) {
if (fieldFilter(innerElement) && !innerElement[trackingMarker] && innerElement.type.toLowerCase() !== 'password') {
helpers.addEventListener(innerElement, 'focus', getFormChangeListener('focus_form', context), false);
helpers.addEventListener(innerElement, 'change', getFormChangeListener('change_form', context), false);
innerElement[trackingMarker] = true;
}
});
});
helpers.addEventListener(form, 'submit', getFormSubmissionListener(context));
form[trackingMarker] = true;
}
});
}
};
};