├── .gitignore ├── .npmignore ├── History.md ├── Makefile ├── Readme.md ├── component.json ├── index.js ├── package.json └── test └── classes.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | components 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | examples 4 | *.sock 5 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.2.6 / 2016-04-01 3 | ================== 4 | 5 | * fix: properly fix `component-indexof` require (#29, @ockham) 6 | 7 | 1.2.5 / 2016-02-28 8 | ================== 9 | 10 | * package: don't use browser alias for component-indexof dependency (#27, @ockham) 11 | 12 | 1.2.4 / 2015-03-17 13 | ================== 14 | 15 | * fix: classes(el).array() when el is an svg element. 16 | 17 | 1.2.3 / 2014-11-19 18 | ================== 19 | 20 | * index: Improve HTMLElement duck typing 21 | * Readme: Display new test command 22 | * Makefile: Install and use local component binaries 23 | * package: Add component dev deps 24 | * test: Use component/assert 25 | 26 | 1.2.2 / 2014-10-27 27 | ================== 28 | 29 | * bump version to 1.2.2 30 | * pin component-indexof to 0.0.3 in package.json 31 | * package: add "license" field 32 | 33 | 1.2.1 / 2014-02-10 34 | ================== 35 | 36 | * package: use "component-indexof" 37 | 38 | 1.2.0 / 2014-01-16 39 | ================== 40 | 41 | * add `.toggle()` force parameter 42 | * change npm package name to `component-classes` 43 | 44 | 1.1.4 / 2013-11-14 45 | ================== 46 | 47 | * package: allow any "indexof-component" version 48 | * add repository field to `package.json` 49 | 50 | 1.1.3 / 2013-08-02 51 | ================== 52 | 53 | * add browser field to map indexof -> indexof-component 54 | * throw an Error if no DOM element is given 55 | 56 | 1.1.2 / 2013-06-10 57 | ================== 58 | 59 | * fix leading / trailing whitespace leading to empty string in .array() retval 60 | 61 | 1.1.0 / 2013-01-30 62 | ================== 63 | 64 | * add .remove(regexp) support 65 | 66 | 0.0.2 / 2012-08-01 67 | ================== 68 | 69 | * add support for tags. Closes #1 [domenic] 70 | 71 | 0.0.1 / 2010-01-03 72 | ================== 73 | 74 | * Initial release 75 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | BIN := node_modules/.bin 3 | 4 | build: node_modules build/build.js 5 | build/build.js: index.js components 6 | @mkdir -p $(dir $@) 7 | @$(BIN)/component-build --dev 8 | 9 | components: node_modules component.json 10 | @$(BIN)/component-install --dev 11 | 12 | test: build/build.js 13 | $(BIN)/component-test browser 14 | 15 | clean: 16 | rm -fr build components 17 | 18 | node_modules: package.json 19 | @npm install 20 | @touch $@ 21 | 22 | .PHONY: clean test 23 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # classes 3 | 4 | Cross-browser element class manipulation, utilizing the native `.classList` when possible. This is not designed to be a `.classList` polyfill. 5 | 6 | ## Installation 7 | 8 | ``` 9 | $ component install component/classes 10 | ``` 11 | 12 | ## Example 13 | 14 | ```js 15 | var classes = require('classes'); 16 | classes(el) 17 | .add('foo') 18 | .toggle('bar') 19 | .remove(/^item-\d+/); 20 | ``` 21 | 22 | ## API 23 | 24 | ### .add(class) 25 | 26 | Add `class`. 27 | 28 | ### .remove(class) 29 | 30 | Remove `class` name or all classes matching the given regular expression. 31 | 32 | ### .toggle(class) 33 | 34 | Toggle `class`. 35 | 36 | ### .has(class) 37 | 38 | Check if `class` is present. 39 | 40 | ### .array() 41 | 42 | Return an array of classes. 43 | 44 | ## Test 45 | 46 | ```sh 47 | $ make test 48 | ``` 49 | 50 | ## License 51 | 52 | MIT 53 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "classes", 3 | "version": "1.2.6", 4 | "description": "Cross-browser element class list", 5 | "keywords": [ 6 | "dom", 7 | "html", 8 | "classList", 9 | "class", 10 | "ui" 11 | ], 12 | "scripts": [ 13 | "index.js" 14 | ], 15 | "dependencies": { 16 | "component/indexof": "*" 17 | }, 18 | "development": { 19 | "component/assert": "*" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | try { 6 | var index = require('indexof'); 7 | } catch (err) { 8 | var index = require('component-indexof'); 9 | } 10 | 11 | /** 12 | * Whitespace regexp. 13 | */ 14 | 15 | var re = /\s+/; 16 | 17 | /** 18 | * toString reference. 19 | */ 20 | 21 | var toString = Object.prototype.toString; 22 | 23 | /** 24 | * Wrap `el` in a `ClassList`. 25 | * 26 | * @param {Element} el 27 | * @return {ClassList} 28 | * @api public 29 | */ 30 | 31 | module.exports = function(el){ 32 | return new ClassList(el); 33 | }; 34 | 35 | /** 36 | * Initialize a new ClassList for `el`. 37 | * 38 | * @param {Element} el 39 | * @api private 40 | */ 41 | 42 | function ClassList(el) { 43 | if (!el || !el.nodeType) { 44 | throw new Error('A DOM element reference is required'); 45 | } 46 | this.el = el; 47 | this.list = el.classList; 48 | } 49 | 50 | /** 51 | * Add class `name` if not already present. 52 | * 53 | * @param {String} name 54 | * @return {ClassList} 55 | * @api public 56 | */ 57 | 58 | ClassList.prototype.add = function(name){ 59 | // classList 60 | if (this.list) { 61 | this.list.add(name); 62 | return this; 63 | } 64 | 65 | // fallback 66 | var arr = this.array(); 67 | var i = index(arr, name); 68 | if (!~i) arr.push(name); 69 | this.el.className = arr.join(' '); 70 | return this; 71 | }; 72 | 73 | /** 74 | * Remove class `name` when present, or 75 | * pass a regular expression to remove 76 | * any which match. 77 | * 78 | * @param {String|RegExp} name 79 | * @return {ClassList} 80 | * @api public 81 | */ 82 | 83 | ClassList.prototype.remove = function(name){ 84 | if ('[object RegExp]' == toString.call(name)) { 85 | return this.removeMatching(name); 86 | } 87 | 88 | // classList 89 | if (this.list) { 90 | this.list.remove(name); 91 | return this; 92 | } 93 | 94 | // fallback 95 | var arr = this.array(); 96 | var i = index(arr, name); 97 | if (~i) arr.splice(i, 1); 98 | this.el.className = arr.join(' '); 99 | return this; 100 | }; 101 | 102 | /** 103 | * Remove all classes matching `re`. 104 | * 105 | * @param {RegExp} re 106 | * @return {ClassList} 107 | * @api private 108 | */ 109 | 110 | ClassList.prototype.removeMatching = function(re){ 111 | var arr = this.array(); 112 | for (var i = 0; i < arr.length; i++) { 113 | if (re.test(arr[i])) { 114 | this.remove(arr[i]); 115 | } 116 | } 117 | return this; 118 | }; 119 | 120 | /** 121 | * Toggle class `name`, can force state via `force`. 122 | * 123 | * For browsers that support classList, but do not support `force` yet, 124 | * the mistake will be detected and corrected. 125 | * 126 | * @param {String} name 127 | * @param {Boolean} force 128 | * @return {ClassList} 129 | * @api public 130 | */ 131 | 132 | ClassList.prototype.toggle = function(name, force){ 133 | // classList 134 | if (this.list) { 135 | if ("undefined" !== typeof force) { 136 | if (force !== this.list.toggle(name, force)) { 137 | this.list.toggle(name); // toggle again to correct 138 | } 139 | } else { 140 | this.list.toggle(name); 141 | } 142 | return this; 143 | } 144 | 145 | // fallback 146 | if ("undefined" !== typeof force) { 147 | if (!force) { 148 | this.remove(name); 149 | } else { 150 | this.add(name); 151 | } 152 | } else { 153 | if (this.has(name)) { 154 | this.remove(name); 155 | } else { 156 | this.add(name); 157 | } 158 | } 159 | 160 | return this; 161 | }; 162 | 163 | /** 164 | * Return an array of classes. 165 | * 166 | * @return {Array} 167 | * @api public 168 | */ 169 | 170 | ClassList.prototype.array = function(){ 171 | var className = this.el.getAttribute('class') || ''; 172 | var str = className.replace(/^\s+|\s+$/g, ''); 173 | var arr = str.split(re); 174 | if ('' === arr[0]) arr.shift(); 175 | return arr; 176 | }; 177 | 178 | /** 179 | * Check if class `name` is present. 180 | * 181 | * @param {String} name 182 | * @return {ClassList} 183 | * @api public 184 | */ 185 | 186 | ClassList.prototype.has = 187 | ClassList.prototype.contains = function(name){ 188 | return this.list 189 | ? this.list.contains(name) 190 | : !! ~index(this.array(), name); 191 | }; 192 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "component-classes", 3 | "version": "1.2.6", 4 | "description": "Cross-browser element class list", 5 | "keywords": [ 6 | "dom", 7 | "html", 8 | "classList", 9 | "class", 10 | "ui" 11 | ], 12 | "dependencies": { 13 | "component-indexof": "0.0.3" 14 | }, 15 | "browser": { 16 | "indexof": "component-indexof" 17 | }, 18 | "component": { 19 | "scripts": { 20 | "classes/index.js": "index.js" 21 | } 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/component/classes.git" 26 | }, 27 | "license": "MIT", 28 | "devDependencies": { 29 | "component": "^0.19.9", 30 | "component-test": "^0.1.4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/classes.js: -------------------------------------------------------------------------------- 1 | 2 | var classes = require('classes'); 3 | var assert = require('assert'); 4 | 5 | describe('classes(el)', function(){ 6 | var el; 7 | beforeEach(function(){ 8 | el = document.createElement('div'); 9 | }) 10 | 11 | describe('.add(class)', function(){ 12 | it('should add a class', function(){ 13 | classes(el).add('foo'); 14 | assert('foo' == el.className); 15 | }) 16 | 17 | it('should not add the same class twice', function(){ 18 | var list = classes(el); 19 | list.add('foo'); 20 | list.add('foo'); 21 | list.add('foo'); 22 | list.add('bar'); 23 | assert('foo bar' == el.className); 24 | }) 25 | }) 26 | 27 | describe('.remove(class)', function(){ 28 | it('should remove a class from the beginning', function(){ 29 | el.className = 'foo bar baz'; 30 | classes(el).remove('foo'); 31 | assert('bar baz' == el.className); 32 | }) 33 | 34 | it('should remove a class from the middle', function(){ 35 | el.className = 'foo bar baz'; 36 | classes(el).remove('bar'); 37 | assert('foo baz' == el.className); 38 | }) 39 | 40 | it('should remove a class from the end', function(){ 41 | el.className = 'foo bar baz'; 42 | classes(el).remove('baz'); 43 | assert('foo bar' == el.className); 44 | }) 45 | }) 46 | 47 | describe('.remove(regexp)', function(){ 48 | it('should remove matching classes', function(){ 49 | el.className = 'foo item-1 item-2 bar'; 50 | classes(el).remove(/^item-/); 51 | assert('foo bar' == el.className); 52 | }) 53 | }) 54 | 55 | describe('.toggle(class, force)', function(){ 56 | describe('when present', function(){ 57 | it('should remove the class', function(){ 58 | el.className = 'foo bar hidden'; 59 | classes(el).toggle('hidden'); 60 | assert('foo bar' == el.className); 61 | }) 62 | }) 63 | 64 | describe('when not present', function(){ 65 | it('should add the class', function(){ 66 | el.className = 'foo bar'; 67 | classes(el).toggle('hidden'); 68 | assert('foo bar hidden' == el.className); 69 | }) 70 | }) 71 | 72 | describe('when force is true', function(){ 73 | it('should add the class', function(){ 74 | el.className = 'foo bar'; 75 | classes(el).toggle('hidden', true); 76 | assert('foo bar hidden' == el.className); 77 | }) 78 | 79 | it('should not remove the class', function(){ 80 | el.className = 'foo bar hidden'; 81 | classes(el).toggle('hidden', true); 82 | assert('foo bar hidden' == el.className); 83 | }) 84 | }) 85 | 86 | describe('when force is false', function(){ 87 | it('should remove the class', function(){ 88 | el.className = 'foo bar hidden'; 89 | classes(el).toggle('hidden', false); 90 | assert('foo bar' == el.className); 91 | }) 92 | 93 | it('should not add the class', function(){ 94 | el.className = 'foo bar'; 95 | classes(el).toggle('hidden', false); 96 | assert('foo bar' == el.className); 97 | }) 98 | }) 99 | }) 100 | 101 | describe('.array()', function(){ 102 | it('should return an array of classes', function(){ 103 | el.className = 'foo bar baz'; 104 | var ret = classes(el).array(); 105 | assert('foo' == ret[0]); 106 | assert('bar' == ret[1]); 107 | assert('baz' == ret[2]); 108 | }) 109 | 110 | it('should return an empty array when no className is defined', function(){ 111 | var ret = classes(el).array(); 112 | assert(0 == ret.length); 113 | }) 114 | 115 | it('should ignore leading whitespace', function(){ 116 | el.className = ' foo bar baz'; 117 | var ret = classes(el).array(); 118 | assert('foo' == ret[0]); 119 | assert('bar' == ret[1]); 120 | assert('baz' == ret[2]); 121 | assert(3 == ret.length); 122 | }) 123 | 124 | it('should ignore trailing whitespace', function(){ 125 | el.className = 'foo bar baz '; 126 | var ret = classes(el).array(); 127 | assert('foo' == ret[0]); 128 | assert('bar' == ret[1]); 129 | assert('baz' == ret[2]); 130 | assert(3 == ret.length); 131 | }) 132 | }) 133 | 134 | describe('.has(class)', function(){ 135 | it('should check if the class is present', function(){ 136 | el.className = 'hey there'; 137 | assert(false === classes(el).has('foo')); 138 | assert(true === classes(el).has('hey')); 139 | assert(true === classes(el).has('there')); 140 | }) 141 | }) 142 | }) 143 | --------------------------------------------------------------------------------