UNPKG

clicks

Version:

Browser click streams

271 lines (232 loc) 6.39 kB
/* * Copyright (c) 2012 VMware, Inc. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ define(function (require) { "use strict"; var dom3, transforms, arrayBuffer, listeners, stream, timeStampMin, timeStampMax, undef; dom3 = require('./events/dom3'); transforms = require('./transforms/_registry'); arrayBuffer = require('./streams/arrayBuffer'); function get() { if (!('get' in stream)) { return undef; } var events = stream.get(); if ('flush' in stream) { stream.flush(); } return events; } /** * Obtains the contents of the stream. Flushing the stream if possible. */ function clicks() { return get.apply(undef, arguments); } function findTransform(property) { if (typeof property === 'function') { return property; } return transforms.lookup(property); } function eventCallback(config, type) { var properties = config.properties; return function (e) { var safe, prop, transform; safe = {}; for (prop in e) { if (prop in properties) { // transform properties whose values are likely to mutate // or not serialization friendly transform = findTransform(properties[prop]); if (transform) { safe[prop] = transform.call(undef, e[prop]); } } } if ('_postTransform' in properties) { properties._postTransform(safe, e); } if (!('type' in safe)) { // include type if missing safe.type = type; } if (!('timeStamp' in safe) || safe.timeStamp > timeStampMax || safe.timeStamp < timeStampMin) { // timeStamp is such a disaster in Firefox, the only thing we can do it set it ourselves safe.timeStamp = new Date().getTime(); } stream(safe); }; } function on(node, event, callback) { var off; if (node.addEventListener) { node.addEventListener(event, callback, true); } else if (node.attachEvent) { node.attachEvent('on' + event, callback); } else { throw new Error("Unable to attach to node: " + node); } off = function () { if (off) { if (node.removeEventListener) { node.removeEventListener(event, callback, true); } else if (node.detachEvent) { node.detachEvent('on' + event, callback); } off = undef; } }; return off; } function resolveNode(node) { // TODO use a query selector? if (typeof node === 'object' && 'nodeName' in node && 'nodeType' in node) { // already a DOM node return node; } else if (node === 'window') { return window; } else if (node === 'document') { return window.document; } else if (node === 'html') { return window.document.documentElement; } else if (node === 'body') { return window.document.body; } else if (node === 'head') { return window.document.head; } return window.document.documentElement; } function listen(type, config) { unlisten(type); config = config || {}; listeners[type] = on(resolveNode(config.attachPoint), type, eventCallback(config, type)); } function unlisten(type) { if (type in listeners) { listeners[type].call(); delete listeners[type]; } } /** * Start listening for the provided events. Defaults to dom3 events if * specific types are not provided. * * @param types the events to listen for * @returns clicks for api chaining */ function attach(types) { var type; // default to dom3 types = types || dom3.types; if (arguments.length > 1) { listen.apply(undef, arguments); } else { for (type in types) { listen(type, types[type]); } } return clicks; } /** * Remove listeners for the provided event types. Defaults to all listeners * if specific types are not provided. * * @param types the events to stop listening for * @returns clicks for api chaining */ function detach(types) { var type; // default to all listeners types = types || listeners; if (Object.prototype.toString.call(types) === '[object String]') { unlisten(types); } else { for (type in types) { unlisten(type); } } return clicks; } /** * Provides a transformer to use for the property type * * @param name the property type * @param transform the transform function * @returns clicks for api chaining */ function transformer(name, transform) { if (typeof transform !== 'function') { throw new Error('Function expected for transform'); } transforms.register(name, transform); return clicks; } /** * Specifies the stream to publish stream events * * @param {Function} func the stream receiver * @returns clicks for api chaining */ function setStream(func) { stream = func; return clicks; } /** * Reverts to a pristine state. Removes all event listeners, transforms and * restores the default buffer, flushing any buffered events. * * @returns clicks for api chaining */ function reset() { detach(); if ('flush' in stream) { stream.flush(); } if (stream !== arrayBuffer) { stream = arrayBuffer; stream.flush(); } transforms.reset(); return clicks; } listeners = {}; stream = arrayBuffer; timeStampMin = new Date().getTime() * 0.9; timeStampMax = new Date().getTime() * 1.5; clicks.attach = attach; clicks.detach = detach; clicks.transformer = transformer; clicks.stream = setStream; clicks.reset = reset; return clicks; });