@@ -48,6 +48,15 @@ object.getFormTrackingManager = function (core, trackerId) {
4848 // Tag names of mutable elements inside a form
4949 var innerElementTags = [ 'textarea' , 'input' , 'select' ] ;
5050
51+ // Used to mark elements with event listeners
52+ var trackingMarker = trackerId + 'form' ;
53+
54+ // Filter to determine which forms should be tracked
55+ var formFilter = function ( e ) { return true ; }
56+
57+ // Filter to determine which form fields should be tracked
58+ var fieldFilter = function ( e ) { return true ; }
59+
5160 /*
5261 * Get an identifier for a form, input, textarea, or select element
5362 */
@@ -77,8 +86,13 @@ object.getFormTrackingManager = function (core, trackerId) {
7786 */
7887 function getInnerFormElements ( elt ) {
7988 var innerElements = [ ] ;
80- lodash . map ( innerElementTags , function ( tagname ) {
81- lodash . map ( elt . getElementsByTagName ( tagname ) , function ( child ) {
89+ lodash . forEach ( innerElementTags , function ( tagname ) {
90+
91+ var trackedChildren = lodash . filter ( elt . getElementsByTagName ( tagname ) , function ( child ) {
92+ return child . hasOwnProperty ( trackingMarker ) ;
93+ } ) ;
94+
95+ lodash . forEach ( trackedChildren , function ( child ) {
8296 if ( child . type === 'submit' ) {
8397 return ;
8498 }
@@ -124,26 +138,95 @@ object.getFormTrackingManager = function (core, trackerId) {
124138 } ;
125139 }
126140
141+ /**
142+ * Check whether an element has at least one class from a given list
143+ */
144+ function checkClass ( elt , classList ) {
145+ var classes = lodash . map ( elt . classList ) ,
146+ i ;
147+
148+ for ( i = 0 ; i < classes . length ; i ++ ) {
149+ if ( classList [ classes [ i ] ] ) {
150+ return true ;
151+ }
152+ }
153+ return false ;
154+ }
155+
156+ /*
157+ * Convert a criterion object to a filter function
158+ *
159+ * @param object criterion Either {whitelist: [array of allowable strings]}
160+ * or {blacklist: [array of allowable strings]}
161+ * or {filter: function (elt) {return whether to track the element}}
162+ * @param boolean byClass Whether to whitelist/blacklist based on an element's classes (for forms)
163+ * or name attribute (for fields)
164+ */
165+ function getFilter ( criterion , byClass ) {
166+
167+ // If the criterion argument is not an object, add listeners to all elements
168+ if ( lodash . isArray ( criterion ) || ! lodash . isObject ( criterion ) ) {
169+ return function ( elt ) {
170+ return true ;
171+ } ;
172+ }
173+
174+ if ( criterion . hasOwnProperty ( 'filter' ) ) {
175+ return criterion . filter ;
176+ } else {
177+ var inclusive = criterion . hasOwnProperty ( 'whitelist' ) ;
178+ var specifiedClasses = criterion . whitelist || criterion . blacklist ;
179+ if ( ! lodash . isArray ( specifiedClasses ) ) {
180+ specifiedClasses = [ specifiedClasses ] ;
181+ }
182+
183+ // Convert the array of classes to an object of the form {class1: true, class2: true, ...}
184+ var specifiedClassesSet = { } ;
185+ for ( var i = 0 ; i < specifiedClasses . length ; i ++ ) {
186+ specifiedClassesSet [ specifiedClasses [ i ] ] = true ;
187+ }
188+
189+ if ( byClass ) {
190+ return function ( elt ) {
191+ return checkClass ( elt , specifiedClassesSet ) === inclusive ;
192+ } ;
193+ } else {
194+ return function ( elt ) {
195+ return elt . name in specifiedClassesSet === inclusive ;
196+ } ;
197+ }
198+ }
199+ }
200+
127201 return {
128202
203+ /*
204+ * Configures form tracking: which forms and fields will be tracked, and the context to attach
205+ */
206+ configureFormTracking : function ( config , context ) {
207+ if ( config ) {
208+ formFilter = getFilter ( config . forms , true ) ;
209+ fieldFilter = getFilter ( config . fields , false ) ;
210+ }
211+ } ,
212+
129213 /*
130214 * Add submission event listeners to all form elements
131215 * Add value change event listeners to all mutable inner form elements
132216 */
133217 addFormListeners : function ( context ) {
134- var trackingMarker = trackerId + 'form' ;
135-
136- lodash . map ( innerElementTags , function ( tagname ) {
137- lodash . map ( document . getElementsByTagName ( tagname ) , function ( innerElement ) {
138- if ( ! innerElement [ trackingMarker ] ) {
139- helpers . addEventListener ( innerElement , 'change' , getFormChangeListener ( context ) , false ) ;
140- innerElement [ trackingMarker ] = true ;
141- }
142- } ) ;
143- } ) ;
218+ lodash . forEach ( document . getElementsByTagName ( 'form' ) , function ( form ) {
219+ if ( formFilter ( form ) && ! form [ trackingMarker ] ) {
220+
221+ lodash . forEach ( innerElementTags , function ( tagname ) {
222+ lodash . forEach ( form . getElementsByTagName ( tagname ) , function ( innerElement ) {
223+ if ( fieldFilter ( innerElement ) && ! innerElement [ trackingMarker ] ) {
224+ helpers . addEventListener ( innerElement , 'change' , getFormChangeListener ( context ) , false ) ;
225+ innerElement [ trackingMarker ] = true ;
226+ }
227+ } ) ;
228+ } ) ;
144229
145- lodash . map ( document . getElementsByTagName ( 'form' ) , function ( form ) {
146- if ( ! form [ trackingMarker ] ) {
147230 helpers . addEventListener ( form , 'submit' , getFormSubmissionListener ( context ) ) ;
148231 form [ trackingMarker ] = true ;
149232 }
0 commit comments