├── .gitignore ├── .travis.yml ├── .zuul.yml ├── LICENCE ├── css-path.js ├── example.js ├── package.json ├── readme.md └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | env: 5 | global: 6 | - secure: wz8y8vfVyZlyBFM6dgyYF3NJt6vg9GnT/BN3e9zYFqWF9Moi3FYXH956L9z58f0+Wk/gxPvprMURGFsKET0ztwirlhMWinrQdWWM6GsclXcCzMCJ+GYADmkYLI6W8jrcvSpfpKr61VV27EHZj4zEM8DdL94BpDR0E8IAjJ6sCiY= 7 | - secure: lQAQLD0KV3WmEY5nm1DfubeDKmUSWClBEP9D9OuEw/psHNhSvI3eG8ylzghSAMWf5uxzX0gq7h36YKvPtCA4zTAWnK0wSiGHXchthk+PABL2ioR+Q0XpzfQ3jEokY0wWil1Xj+DJZyIEX2wY7Zbxpyd6F68VaCWcBmtoubOU8PY= 8 | -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: tape 2 | 3 | browsers: 4 | - name: chrome 5 | version: latest 6 | - name: safari 7 | version: latest 8 | - name: firefox 9 | version: latest 10 | - name: internet explorer 11 | version: 8..latest 12 | - name: opera 13 | version: latest 14 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Mic Network, Inc 2 | 3 | This software is released under the MIT license: 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /css-path.js: -------------------------------------------------------------------------------- 1 | var trim = require('trim') 2 | 3 | , classSelector = function (className) { 4 | var selectors = className.split(/\s/g) 5 | , array = [] 6 | 7 | for (var i = 0; i < selectors.length; ++i) { 8 | if (selectors[i].length > 0) { 9 | array.push('.' + selectors[i]) 10 | } 11 | } 12 | 13 | return array.join('') 14 | } 15 | 16 | , nthChild = function (elm) { 17 | var childNumber = 0 18 | , childNodes = elm.parentNode.childNodes 19 | , index = 0 20 | 21 | for(; index < childNodes.length; ++index) { 22 | if (childNodes[index].nodeType === 1) 23 | ++childNumber 24 | 25 | if (childNodes[index] === elm) 26 | return childNumber 27 | } 28 | } 29 | 30 | , path = function (elm, rootNode, list) { 31 | 32 | var tag = elm.tagName.toLowerCase() 33 | , selector = [ tag ] 34 | , className = elm.getAttribute('class') 35 | , id = elm.getAttribute('id') 36 | 37 | if (id) { 38 | list.unshift(tag + '#' + trim(id)) 39 | return list 40 | } 41 | 42 | if (className) 43 | selector.push( classSelector(className) ) 44 | 45 | if (tag !== 'html' && tag !== 'body' && elm.parentNode) { 46 | selector.push(':nth-child(' + nthChild(elm) + ')') 47 | } 48 | 49 | list.unshift(selector.join('')) 50 | 51 | if (elm.parentNode && elm.parentNode !== rootNode && elm.parentNode.tagName) { 52 | path(elm.parentNode, rootNode, list) 53 | } 54 | 55 | return list 56 | } 57 | 58 | module.exports = function (elm, rootNode) { 59 | return path(elm, rootNode, []).join(' > ') 60 | } -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var document = require('global/document') 2 | 3 | , cssPath = require('./css-path') 4 | 5 | , elm = document.createElement('div') 6 | , elm2 = document.createElement('h1') 7 | , elm3 = document.createElement('span') 8 | 9 | elm.setAttribute('class', 'beep boop') 10 | elm2.setAttribute('class', 'foo') 11 | elm3.setAttribute('class', 'hello') 12 | 13 | elm.appendChild(elm2) 14 | elm2.appendChild(document.createElement('span')) 15 | elm2.appendChild(elm3) 16 | 17 | console.log('You can get the css-selector for an element not attached to the document') 18 | console.log(cssPath(elm3)) 19 | 20 | document.body.appendChild(elm) 21 | 22 | console.log() 23 | console.log('Getting the same element now shows a different path') 24 | console.log(cssPath(elm3)) 25 | 26 | console.log() 27 | console.log('You can also choose a base element') 28 | console.log(cssPath(elm3, elm)) 29 | console.log('or perhaps just skip the body-element') 30 | console.log(cssPath(elm3, document.body)) 31 | 32 | elm2.setAttribute('id', 'epic') 33 | 34 | console.log() 35 | console.log('Setting the id attribute make the path shorter') 36 | console.log(cssPath(elm3)) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-path", 3 | "version": "1.0.3", 4 | "description": "Get the unique css-path to a DOM-element", 5 | "main": "css-path.js", 6 | "dependencies": { 7 | "trim": "0.0.1" 8 | }, 9 | "devDependencies": { 10 | "global": "^4.2.1", 11 | "tape": "^2.13.4", 12 | "zuul": "^1.10.1" 13 | }, 14 | "scripts": { 15 | "test": "zuul -- test.js" 16 | }, 17 | "testling": { 18 | "files": "test.js", 19 | "browsers": [ 20 | "ie/9..latest", 21 | "chrome/latest", 22 | "firefox/latest", 23 | "safari/latest", 24 | "opera/11.0..latest", 25 | "iphone/6", 26 | "ipad/6", 27 | "android-browser/latest" 28 | ] 29 | }, 30 | "author": "David Björklund (http://davidbjorklund.com/)", 31 | "license": "MIT", 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/micnews/css-path.git" 35 | }, 36 | "bugs": { 37 | "url": "https://github.com/micnews/css-path/issues" 38 | }, 39 | "homepage": "https://github.com/micnews/css-path" 40 | } 41 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # css-path 2 | 3 | Get the unique css-path to a DOM-element 4 | 5 | [![NPM](https://nodei.co/npm/css-path.png?downloads&stars)](https://nodei.co/npm/css-path/) 6 | 7 | [![NPM](https://nodei.co/npm-dl/css-path.png)](https://nodei.co/npm/css-path/) 8 | 9 | [![Sauce Test Status](https://saucelabs.com/browser-matrix/micnews-css-path.svg)](https://saucelabs.com/u/micnews-css-path) 10 | 11 | 12 | ## Installation 13 | 14 | ``` 15 | npm install css-path 16 | ``` 17 | 18 | ## Example 19 | 20 | ### Input 21 | 22 | ```javascript 23 | var document = require('global/document') 24 | 25 | , cssPath = require('./css-path') 26 | 27 | , elm = document.createElement('div') 28 | , elm2 = document.createElement('h1') 29 | , elm3 = document.createElement('span') 30 | 31 | elm.setAttribute('class', 'beep boop') 32 | elm2.setAttribute('class', 'foo') 33 | elm3.setAttribute('class', 'hello') 34 | 35 | elm.appendChild(elm2) 36 | elm2.appendChild(document.createElement('span')) 37 | elm2.appendChild(elm3) 38 | 39 | console.log('You can get the css-selector for an element not attached to the document') 40 | console.log(cssPath(elm3)) 41 | 42 | document.body.appendChild(elm) 43 | 44 | console.log() 45 | console.log('Getting the same element now shows a different path') 46 | console.log(cssPath(elm3)) 47 | 48 | console.log() 49 | console.log('You can also choose a base element') 50 | console.log(cssPath(elm3, elm)) 51 | console.log('or perhaps just skip the body-element') 52 | console.log(cssPath(elm3, document.body)) 53 | 54 | elm2.setAttribute('id', 'epic') 55 | 56 | console.log() 57 | console.log('Setting the id attribute make the path shorter') 58 | console.log(cssPath(elm3)) 59 | ``` 60 | 61 | ### Output 62 | 63 | ``` 64 | You can get the css-selector for an element not attached to the document 65 | div.beep.boop > h1.foo:nth-child(1) > span.hello:nth-child(2) 66 | 67 | Getting the same element now shows a different path 68 | html > body > div.beep.boop:nth-child(1) > h1.foo:nth-child(1) > span.hello:nth-child(2) 69 | 70 | You can also choose a base element 71 | h1.foo:nth-child(1) > span.hello:nth-child(2) 72 | or perhaps just skip the body-element 73 | div.beep.boop:nth-child(1) > h1.foo:nth-child(1) > span.hello:nth-child(2) 74 | 75 | Setting the id attribute make the path shorter 76 | h1#epic > span.hello:nth-child(2) 77 | ``` 78 | 79 | ## Licence 80 | 81 | Copyright (c) 2014 Mic Network, Inc 82 | 83 | This software is released under the MIT license: 84 | 85 | Permission is hereby granted, free of charge, to any person obtaining a copy 86 | of this software and associated documentation files (the "Software"), to deal 87 | in the Software without restriction, including without limitation the rights 88 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 89 | copies of the Software, and to permit persons to whom the Software is 90 | furnished to do so, subject to the following conditions: 91 | 92 | The above copyright notice and this permission notice shall be included in 93 | all copies or substantial portions of the Software. 94 | 95 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 96 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 97 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 98 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 99 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 100 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 101 | THE SOFTWARE. 102 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | 3 | , document = require('global/document') 4 | , body = document.body 5 | 6 | , cssPath = require('./css-path') 7 | 8 | test('the body element', function (t) { 9 | t.equal(cssPath(document.body), 'html > body') 10 | t.end() 11 | }) 12 | 13 | test('elements not attached to the body', function (t) { 14 | var div = document.createElement('div') 15 | , span = document.createElement('span') 16 | 17 | t.equal(cssPath(div), 'div') 18 | 19 | div.setAttribute('class', 'beep boop') 20 | 21 | t.equal(cssPath(div), 'div.beep.boop') 22 | 23 | div.setAttribute('id', 'hello') 24 | 25 | t.equal(cssPath(div), 'div#hello') 26 | 27 | div.appendChild(span) 28 | 29 | t.equal(cssPath(span), 'div#hello > span:nth-child(1)') 30 | t.equal(cssPath(span, div), 'span:nth-child(1)') 31 | 32 | t.end() 33 | }) 34 | 35 | test('attributes with some whitespace', function (t) { 36 | var elm = document.createElement('div') 37 | 38 | elm.setAttribute('class', ' foo\tbar\n') 39 | t.equal(cssPath(elm), 'div.foo.bar') 40 | 41 | elm.setAttribute('class', '') 42 | t.equal(cssPath(elm), 'div') 43 | 44 | elm.setAttribute('id', ' bong ') 45 | t.equal(cssPath(elm), 'div#bong') 46 | t.end() 47 | }) 48 | 49 | test('elements attached to the body', function (t) { 50 | var div = document.createElement('div') 51 | , div2 = document.createElement('div') 52 | , text = document.createTextNode('hello, world!') 53 | 54 | // use insertBefore so that it works nice with testling 55 | body.insertBefore(div, body.childNodes[0]) 56 | 57 | div.setAttribute('class', 'foo bar') 58 | 59 | t.equal(cssPath(div), 'html > body > div.foo.bar:nth-child(1)') 60 | 61 | body.insertBefore(text, body.childNodes[1]) 62 | body.insertBefore(div2, body.childNodes[2]) 63 | 64 | t.equal(cssPath(div2), 'html > body > div:nth-child(2)') 65 | t.equal(cssPath(div2, document.documentElement), 'body > div:nth-child(2)') 66 | t.equal(cssPath(div2, body), 'div:nth-child(2)') 67 | 68 | div.setAttribute('id', 'identifier') 69 | 70 | t.equal(cssPath(div), 'div#identifier') 71 | 72 | t.end() 73 | }) --------------------------------------------------------------------------------