diff options
author | Thomas Steur <thomas.steur@googlemail.com> | 2014-08-27 21:10:20 +0400 |
---|---|---|
committer | Thomas Steur <thomas.steur@googlemail.com> | 2014-08-27 21:10:20 +0400 |
commit | 10ecff50c3e25cab6501dbc73a1c64131e21db7e (patch) | |
tree | b5c29fb038563d0ec724e8c1f95f07c8350b7f31 /js/piwik.js | |
parent | ce8d27ce90be5be96ae540cc5805c34601590260 (diff) |
refs #4996 worked on detecting content name, piece and target. Next: Sending the impressions to Piwik via bulk tracking
Diffstat (limited to 'js/piwik.js')
-rw-r--r-- | js/piwik.js | 799 |
1 files changed, 423 insertions, 376 deletions
diff --git a/js/piwik.js b/js/piwik.js index f265180c75..1e4b3e4825 100644 --- a/js/piwik.js +++ b/js/piwik.js @@ -976,6 +976,417 @@ if (typeof Piwik !== 'object') { } /************************************************************ + * Query + ************************************************************/ + + var query = { + find: function(selector) + { + if (!document.querySelectorAll) { + return []; // TODO + } + + return document.querySelectorAll(selector); + }, + findMultiple: function(selectors) + { + var index, j,foundNodes; + var nodes = []; + for (index = 0; index < selectors.length; index++) { + foundNodes = this.find(selectors[index]); + if (!foundNodes || !foundNodes.length) { + continue; + } + for (j = 0; j < foundNodes.length; j++) { + nodes.push(foundNodes[j]); + } + } + + nodes = this.makeNodesUnique(nodes); + + return nodes; + }, + makeNodesUnique: function (nodes) + { + nodes.sort(function (n1, n2) { + if (n1 === n2) { + return 0; + } + + return n1.innerHTML > n2.innerHTML ? 1 : -1; + }); + + var index = 0; + var numDuplicates = 0; + var duplicates = []; + var node; + + while ((node = nodes[index++])) { + if (node === nodes[index]) { + numDuplicates = duplicates.push(index); + } + } + + while (numDuplicates--) { + nodes.splice(duplicates[numDuplicates], 1); + } + + return nodes; + }, + getAttributeValueFromNode: function (node, attributeName) { + if (node && node.getAttribute) { + return node.getAttribute(attributeName) + } + + if (!node || !node.attributes) { + return; + } + + if ('undefined' === (typeof node.attributes[attributeName])) { + return; + } + + if (node.attributes[attributeName].value) { + return node.attributes[attributeName].value; // nodeValue is deprecated ie Chrome + } + + if (node.attributes[attributeName].nodeValue) { + return node.attributes[attributeName].nodeValue; + } + + var index; + var attrs = node.attributes; + + if (!attrs) { + return; + } + + for (index = 0; index < attrs.length; index++) { + if (attrs[index].nodeName === attributeName) { + return attrs[index].nodeValue; + } + } + + return null; + }, + hasNodeAttributeWithValue: function (node, attributeName) { + if (this.hasNodeAttribute(node, attributeName)) { + var value = this.getAttributeValueFromNode(node, attributeName); + + if (value) { + return value; + } + } + }, + hasNodeAttribute: function (node, attributeName) { + if (node && node.hasAttribute) { + return node.hasAttribute(attributeName) + } + + if (node && node.attributes) { + return 'undefined' !== (typeof node.attributes[attributeName]) + } + + return false; + }, + hasNodeCssClass: function (node, className) { + if (node && node.className) { + var classes = node.className.split(' '); + if (-1 !== classes.indexOf(className)) { + return true; + } + } + + return false; + }, + findNodesHavingAttribute: function (nodeToSearch, attributeName, nodes) { + if (!nodes) { + nodes = [] + } + + if (!nodeToSearch || !nodeToSearch.children) { + return nodes; + } + + var index, child; + for (index = 0; index < nodeToSearch.children.length; index++) { + child = nodeToSearch.children[index]; + if (this.hasNodeAttribute(child, attributeName)) { + nodes.push(child); + } + + nodes = this.findNodesHavingAttribute(child, attributeName, nodes); + } + + return nodes; + }, + findFirstNodeHavingAttribute: function (node, attributeName) { + if (this.hasNodeAttribute(node, attributeName)) { + return node; + } + + var nodes = this.findNodesHavingAttribute(node, attributeName); + + if (nodes && nodes.length) { + return nodes[0]; // TODO here we should check whether this node is also present within another nested piece of content and if there are multiple other nodes maybe use the next one + } + }, + findFirstNodeHavingAttributeWithValue: function (node, attributeName) { + if (this.hasNodeAttributeWithValue(node, attributeName)) { + return node; + } + + var nodes = this.findNodesHavingAttribute(node, attributeName); + + if (!nodes || !nodes.length) { + return; + } + + var index; + for (index = 0; index < nodes.length; index++) { + if (this.getAttributeValueFromNode(nodes[index], attributeName)) { + return nodes[index]; + } + } + }, + findNodesHavingCssClass: function (nodeToSearch, className, nodes) { + if (!nodes) { + nodes = [] + } + + if (!nodeToSearch) { + return nodes; + } + + if (nodeToSearch.getElementsByClassName) { + return nodeToSearch.getElementsByClassName(className); + } + + if (!nodeToSearch.children) { + return; + } + + var index, child; + for (index = 0; index < nodeToSearch.children.length; index++) { + child = nodeToSearch.children[index]; + if (this.hasNodeCssClass(child, className)) { + nodes.push(child); + } + + nodes = this.findNodesHavingCssClass(child, className, nodes); + } + + return nodes; + }, + findFirstNodeHavingClass: function (node, className) { + if (this.hasNodeCssClass(node, className)) { + return node; + } + + var nodes = this.findNodesHavingCssClass(node, className); + + if (nodes && nodes.length) { + return nodes[0]; // TODO here we should check whether this node is also present within another nested piece of content and if there are multiple other nodes maybe use the next one + } + }, + findFirstLink: function (node) { + var linkNodes = node.getElementsByTagName('a'); + + if (linkNodes && linkNodes.length) { + return linkNodes[0]; + } + } + } + + /************************************************************ + * Content Tracking + ************************************************************/ + + var content = { + CONTENT_ATTR: 'data-track-content', + CONTENT_CLASS: 'piwikTrackContent', + CONTENT_NAME_ATTR: 'data-content-name', + CONTENT_PIECE_ATTR: 'data-content-piece', + CONTENT_PIECE_CLASS: 'piwikContentPiece', + CONTENT_INTERACTION_ATTR: 'data-content-interaction', + CONTENT_INTERACTION_CLASS: 'piwikContentInteraction', + + findPieceNode: function (node) { + var contentPiece; + + contentPiece = query.findFirstNodeHavingAttribute(this.CONTENT_NAME_ATTR); + + if (!contentPiece) { + contentPiece = query.findFirstNodeHavingClass(this.CONTENT_PIECE_CLASS); + } + + if (!contentPiece) { + contentPiece = query.findFirstNodeHavingClass(this.CONTENT_INTERACTION_CLASS); + } + + if (contentPiece) { + return contentPiece; + } + + return node; + }, + findTargetNode: function (node) + { + var target = query.findFirstNodeHavingAttributeWithValue(node, this.CONTENT_INTERACTION_ATTR); + if (target) { + return target; + } + + target = query.findFirstNodeHavingAttribute(node, this.CONTENT_INTERACTION_ATTR); + if (target) { + return target; + } + + target = query.findFirstNodeHavingClass(node, this.CONTENT_INTERACTION_CLASS); + if (target) { + return target; + } + + return node; + }, + findContentName: function (node) + { + var nameNode = query.findFirstNodeHavingAttributeWithValue(node, this.CONTENT_NAME_ATTR); + + if (nameNode) { + return query.getAttributeValueFromNode(nameNode, this.CONTENT_NAME_ATTR); + } + + var contentPiece = this.findContentPiece(node); + if (contentPiece) { + return this.removeDomainIfIsUrl(contentPiece); + } + + if (query.hasNodeAttributeWithValue(node, 'title')) { + return query.getAttributeValueFromNode(node, 'title'); + } + + var clickUrlNode = this.findPieceNode(node); + + if (query.hasNodeAttributeWithValue(clickUrlNode, 'title')) { + return query.getAttributeValueFromNode(clickUrlNode, 'title'); + } + + var targetNode = this.findTargetNode(node); + + if (query.hasNodeAttributeWithValue(targetNode, 'title')) { + return query.getAttributeValueFromNode(targetNode, 'title'); + } + }, + findContentPiece: function (node) + { + var nameNode = query.findFirstNodeHavingAttributeWithValue(node, this.CONTENT_PIECE_ATTR); + + if (nameNode) { + return query.getAttributeValueFromNode(nameNode, this.CONTENT_NAME_ATTR); + } + + var contentNode = this.findPieceNode(node); + + var media = this.findMediaUrlInNode(contentNode); + if (media) { + return media; + } + + var targetNode = this.findTargetNode(node); + + if (contentNode !== targetNode) { + var media = this.findMediaUrlInNode(targetNode); + if (media) { + return media; + } + } + + // contentNode.innerText + + return ''; + }, + findContentTarget: function (node) + { + var targetNode = this.findTargetNode(node); + + if (query.hasNodeAttributeWithValue(targetNode, this.CONTENT_INTERACTION_ATTR)) { + return query.getAttributeValueFromNode(targetNode, this.CONTENT_INTERACTION_ATTR); + } + + if (query.hasNodeAttributeWithValue(targetNode, 'href')) { + return query.getAttributeValueFromNode(targetNode, 'href'); + } + + var contentNode = this.findPieceNode(node); + + if (query.hasNodeAttributeWithValue(contentNode, 'href')) { + return query.getAttributeValueFromNode(contentNode, 'href'); + } + }, + removeDomainIfIsUrl: function (text) { + if (text && -1 !== text.search(/^https?:\/\/[^\/]+/)) { + text = text.replace(/^.*\/\/[^\/]+/, ''); // TODO only remove if !== location.origin + } + + return text; + }, + findMediaUrlInNode: function (node) + { + if (!node) { + return; + } + + var mediaElements = ['img', 'embed', 'video', 'audio']; + var elementName = node.nodeName.toLowerCase(); + + if (-1 !== mediaElements.indexOf(elementName) && query.findFirstNodeHavingAttributeWithValue(node, 'src')) { + + var sourceNode = query.findFirstNodeHavingAttributeWithValue(node, 'src'); + + return query.getAttributeValueFromNode(sourceNode, 'src'); + } + + if (elementName === 'object' && query.hasNodeAttributeWithValue(node, 'data')) { + + return query.getAttributeValueFromNode(node, 'data'); + } + + }, + buildContentPiece: function (node) + { + var name = this.findContentName(node); + var piece = this.findContentPiece(node); + var target = this.findContentTarget(node); + + return { + c_n: name ? name : 'Unknown', + c_p: piece ? piece : 'Unknown', + c_t: target ? target : '' + }; + }, + collectContent: function () + { + var cssSelector = '.' + this.CONTENT_CLASS; + var attrSelector = '[' + this.CONTENT_ATTR + ']'; + var contentNodes = query.findMultiple([cssSelector, attrSelector]); + + if (!contentNodes || !contentNodes.length) { + return; + } + + var contents = []; + + var index; + for (index = 0; index < contentNodes.length; index++) { + contents.push(this.buildContentPiece(contentNodes[index])); + } + + return contents; + } + }; + + /************************************************************ * Page Overlay ************************************************************/ @@ -1973,386 +2384,22 @@ if (typeof Piwik !== 'object') { } } - function makeNodesUnique(nodes) - { - nodes.sort(function (n1, n2) { - if (n1 === n2) { - return 0; - } - - return n1.innerHTML > n2.innerHTML ? 1 : -1; - }); - - var index = 0; - var numDuplicates = 0; - var duplicates = []; - var node; - - while ((node = nodes[index++])) { - if (node === nodes[index]) { - numDuplicates = duplicates.push(index); - } - } - - while (numDuplicates--) { - nodes.splice(duplicates[numDuplicates], 1); - } - - return nodes; - } - - function queryDom(selector) - { - if (!document.querySelectorAll) { - return []; // TODO - } - - return document.querySelectorAll(selector); - } - - function queryDomMultiple(selectors) - { - var index; - var nodes = []; - for (index = 0; index < selectors.length; index++) { - nodes = nodes.concat(queryDom(selectors[index])); - } - - nodes = makeNodesUnique(nodes); - - return nodes; - } - - function findContentName(node) - { - /** - * the Content Name must be memorable and distinguishable from others. Piwik will detect the content name by: - o TODO what is actually meant by main content element, it is described three times differently here - o reading the 'data-name' attribute of the main content element: <div data-name="Content name 1" ...> - o reading the title element of the main content element: <a href="{CLICK_URL}" data-trackclick title="This is used as content name"> - o TODO image hard coded makes no sense, reading the title element of the Image found within this link element and used as content image <a href="{CLICK_URL}" data-trackclick> <img title="This is used as content name" src="{BANNER_IMAGE}"> </a> - o note: if data-name=" is not found in the data-trackclick element or any children, piwik will also read the title="" and alt="" attributes - o TODO not possible as well, if no title or alt is found, the Content Image name is used as Content name, eg. "SHOP-new_at_shopi_tile" - + TOOD everything here is very vague and it is way too complicated for users to understand which value will be used!!! - */ - - /** - * It should be actually - * Search for data-name attribute where piwik-trackcontent is set // why not let users directly specify `piwik-trackcontent="data name"`??? - * Search for data-name attribute where piwik-clickurl is set - * Search for any data-name attribute within piwik-trackcontent's children - * Search for title attribute where piwik-trackcontent is set - * Search for title attribute where piwik-clickurl is set - * Search for any title within piwik-clickurl's children - * Search for any alt within piwik-clickurl's children - * TODO use 'Unknown'? or ignore content? - * - * TODO Actually I think we should read ONLY!!! data-name and maybe title attribute to keep it simple for users! It would end in random content names that change often!!! - */ - - /** - * So better: - * Search for any data-name attribute within the content (children of piwik-trackcontent) - * Search for any title attribute within click-url (children of piwik-clickurl) - * Search for any alt attribute within click-url (children of piwik-clickurl) - * - * Maybe we should prefix data-name since it might be used in other components as well! - */ - - var name; - var target = ''; - var clickUrlNode = null; - - if (hasNodeAttributeWithValue(node, 'data-name')) { - return getAttributeValueFromNode(node, 'data-name'); - } - - if (hasNodeAttributeWithValue(clickUrlNode, 'data-name')) { - return getAttributeValueFromNode(clickUrlNode, 'data-name'); - } - - name = findFirstNodeHavingAttributeWithValue(node, 'data-name'); - - if (name) { - return name; - } - - if (hasNodeAttributeWithValue(node, 'title')) { - return getAttributeValueFromNode(node, 'title'); - } - - if (hasNodeAttributeWithValue(clickUrlNode, 'title')) { - return getAttributeValueFromNode(clickUrlNode, 'title'); - } - - name = findFirstNodeHavingAttributeWithValue(clickUrlNode, 'title'); - - if (name) { - return name; - } - - name = findFirstNodeHavingAttributeWithValue(clickUrlNode, 'alt'); - - if (name) { - return name; - } - - return 'Unknown'; - } - - function findContentPiece(node) - { - /** - * the Content Image is what all visitors see before clicking on the content. - o by default, Piwik will automatically detect the first image found within the content: - o alternatively, you may add a CSS class to the image element to use as banner image. <img class="piwik-banner" src="{BANNER_IMAGE}"> - o or add a tag <img data-banner src="{BANNER_IMAGE}"> - */ - return ''; - } - - function findContentTarget(node) - { - /** - * the Click URL can be a landing page or a product page. Help Piwik detect this Click URL by tagging your content links elements: - o by default, Piwik will automatically detect the first link found within the content: - o you may add a CSS class to the link element to track, <a class="piwik-trackclick" href="{CLICK_URL}"> - o you may add a tag "data-click" to the link element <a data-trackclick href="{CLICK_URL}"> - */ - - var link; - var target = ''; - - link = findFirstNodeHavingAttribute(node, 'data-trackclick'); - // TODO it might be not always 'href' or it may contain javascript so we allow data-trackclick="http://..."? - - if (link) { - target = getAttributeValueFromNode(node, 'data-trackclick'); - - if (target) { - return target; - } - } - - - if (!link) { - link = findFirstNodeHavingClass(node, 'piwik-trackclick'); - } - - if (!link) { - link = findFirstLink(node) - } - - if (!link) { - return; - } - - return getAttributeValueFromNode(node, 'href'); - } - - function getAttributeValueFromNode(node, attributeName) - { - if (node && node.getAttribute) { - return node.getAttribute(attributeName) - } - - if (!node || !node.attributes) { - return; - } - - if ('undefined' === (typeof node.attributes[attributeName])) { - return; - } - - if (node.attributes[attributeName].value) { - return node.attributes[attributeName].value; // nodeValue is deprecated ie Chrome - } - - if (node.attributes[attributeName].nodeValue) { - return node.attributes[attributeName].nodeValue; - } - - var index; - var attrs = node.attributes; - - for (index = 0; index < attrs.length; index++) { - if (attrs[index].nodeName === attributeName) { - return attrs[index].nodeValue; - } - } - - return null; - } - - function hasNodeAttributeWithValue(node, attributeName) - { - if (hasNodeAttribute(node, attributeName)) { - var value = getAttributeValueFromNode(node, attributeName); - - if (value) { - return value; - } - } - } - - function hasNodeAttribute(node, attributeName) - { - if (node && node.hasAttribute) { - return node.hasAttribute(attributeName) - } - - if (node && node.attributes) { - return 'undefined' !== (typeof node.attributes[attributeName]) - } - - return false; - } - - function hasNodeCssClass(node, className) - { - if (node && node.className) { - var classes = node.className.split(' '); - if (-1 !== classes.indexOf(className)) { - return true; - } - } - - return false; - } - - function findNodesHavingAttribute(nodeToSearch, attributeName, nodes) - { - if (!nodes) { - nodes = [] - } - - if (!nodeToSearch) { - return nodes; - } - - var index, child; - for (index = 0; index < nodeToSearch.children.length; index++) { - child = nodeToSearch.children[index]; - if (hasNodeAttribute(child, attributeName)) { - nodes.push(child); - } - - nodes = findNodesHavingAttribute(child, attributeName, nodes); - } - - return nodes; - } - - function findNodesHavingCssClass(nodeToSearch, className, nodes) - { - if (!nodes) { - nodes = [] - } - - if (!nodeToSearch) { - return nodes; - } - - if (nodeToSearch.getElementsByClassName) { - return nodeToSearch.getElementsByClassName(className); - } - - var index, child; - for (index = 0; index < nodeToSearch.children.length; index++) { - child = nodeToSearch.children[index]; - if (hasNodeCssClass(child, className)) { - nodes.push(child); - } - - nodes = findNodesHavingCssClass(child, className, nodes); - } - - return nodes; - } - - function findFirstNodeHavingClass(node, className) - { - if (hasNodeCssClass(node, className)) { - return node; - } - - var nodes = findNodesHavingCssClass(node, className); - - if (nodes && nodes.length) { - return nodes[0]; // TODO here we should check whether this node is also present within another nested piece of content and if there are multiple other nodes maybe use the next one - } - } - - function findFirstNodeHavingAttributeWithValue(node, attributeName) - { - if (hasNodeAttribute(node, attributeName) && getAttributeValueFromNode(node, attributeName)) { - return node; - } - - var nodes = findNodesHavingAttribute(node, attributeName); - - var index; - for (index = 0; index < nodes.length; index++) { - if (getAttributeValueFromNode(nodes[index], attributeName)) { - return nodes[index]; - } - } - } - - function findFirstNodeHavingAttribute(node, attributeName) - { - if (hasNodeAttribute(node, attributeName)) { - return node; - } - - var nodes = findNodesHavingAttribute(node, attributeName); - - if (nodes && nodes.length) { - return nodes[0]; // TODO here we should check whether this node is also present within another nested piece of content and if there are multiple other nodes maybe use the next one - } - } - - function findFirstLink(node) - { - var linkNodes = node.getElementsByTagName('a'); - - if (linkNodes && linkNodes.length) { - return linkNodes[0]; - } - } - - function buildContentPiece(node) - { - return { - c_n: findContentName(node), - c_p: findContentPiece(node), - c_t: findContentTarget(node) - }; - } - - function collectContentPieces() - { - var contentNodes = queryDomMultiple(['.piwik-trackcontent', '[data-trackcontent]']); - - if (!contentNodes.length) { - return; - } - - var contents = []; - - var index; - for (index = 0; index < contentNodes.length; index++) { - contents.push(buildContentPiece(contents[index])); - } - - return contents; - } - /* * Log all content pieces */ function logContentPieces() { - var contents = collectContentPieces(); + var contents = content.collectContent(); + + // sendRequest(request, configTrackerPause); + /** + * { + "requests": [ + "?idsite=1&url=http://example.org&action_name=Test bulk log Pageview&rec=1", + "?idsite=1&url=http://example.net/test.htm&action_name=Another bul k page view&rec=1" + ], + "token_auth": "33dc3f2536d3025974cccb4b4d2d98f4" +} + */ // send bulk tracking request? } |