├── .gitignore ├── docs ├── images │ └── vanilla-js-logo.png ├── styles │ ├── vanilla-js-tabs.css │ ├── docs-page.css │ ├── prism.css │ └── docs-page.less ├── javascript │ ├── vanilla-js-tabs.min.js │ └── prism.js └── index.html ├── tsconfig.json ├── dist ├── vanilla-js-tabs.css ├── vanilla-js-tabs.min.js ├── index.html └── vanilla-js-tabs.js ├── src ├── styles │ └── vanilla-js-tabs.less ├── index.pug └── javascript │ └── vanilla-js-tabs.ts ├── .jshintrc ├── LICENSE ├── package.json ├── test └── spec │ ├── fixtures │ └── tabs.fixture.html │ └── tabs.spec.ts ├── gulpfile.js ├── karma.conf.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /coverage -------------------------------------------------------------------------------- /docs/images/vanilla-js-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoltantothcom/vanilla-js-tabs/HEAD/docs/images/vanilla-js-logo.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "lib": ["es5", "dom"], 6 | "sourceMap": true, 7 | "declaration": false, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "types": ["jasmine", "jasmine-jquery"] 11 | }, 12 | "include": ["src/**/*.ts", "test/**/*.ts"], 13 | "exclude": ["node_modules"] 14 | } 15 | -------------------------------------------------------------------------------- /dist/vanilla-js-tabs.css: -------------------------------------------------------------------------------- 1 | .js-tabs { 2 | margin: 2em; 3 | max-width: 100%; 4 | } 5 | .js-tabs__header { 6 | display: block; 7 | margin: 0; 8 | padding: 0; 9 | overflow: hidden; 10 | } 11 | .js-tabs__header li { 12 | display: inline-block; 13 | float: left; 14 | } 15 | .js-tabs__title { 16 | background: #f5f5f5; 17 | border: 1px solid #ccc; 18 | cursor: pointer; 19 | display: block; 20 | margin-right: 0.5em; 21 | padding: 1em 1.5em; 22 | transition: all 0.25s; 23 | } 24 | .js-tabs__title:hover { 25 | text-decoration: none; 26 | } 27 | .js-tabs__title-active { 28 | background: #fff; 29 | border-bottom-color: #fff; 30 | border-top-left-radius: 0.75em; 31 | } 32 | .js-tabs__content { 33 | border: 1px solid #ccc; 34 | line-height: 1.5; 35 | margin-top: -1px; 36 | padding: 1em 2em 3em; 37 | } 38 | -------------------------------------------------------------------------------- /docs/styles/vanilla-js-tabs.css: -------------------------------------------------------------------------------- 1 | .js-tabs { 2 | margin: 2em; 3 | max-width: 100%; 4 | } 5 | .js-tabs__header { 6 | display: block; 7 | margin: 0; 8 | padding: 0; 9 | overflow: hidden; 10 | } 11 | .js-tabs__header li { 12 | display: inline-block; 13 | float: left; 14 | } 15 | .js-tabs__title { 16 | background: #f5f5f5; 17 | border: 1px solid #ccc; 18 | cursor: pointer; 19 | display: block; 20 | margin-right: 0.5em; 21 | padding: 1em 1.5em; 22 | transition: all 0.25s; 23 | } 24 | .js-tabs__title:hover { 25 | text-decoration: none; 26 | } 27 | .js-tabs__title-active { 28 | background: #fff; 29 | border-bottom-color: #fff; 30 | border-top-left-radius: 0.75em; 31 | } 32 | .js-tabs__content { 33 | border: 1px solid #ccc; 34 | line-height: 1.5; 35 | margin-top: -1px; 36 | padding: 1em 2em 3em; 37 | } 38 | -------------------------------------------------------------------------------- /src/styles/vanilla-js-tabs.less: -------------------------------------------------------------------------------- 1 | .js-tabs { 2 | margin: 2em; 3 | max-width: 100%; 4 | } 5 | 6 | .js-tabs__header { 7 | display: block; 8 | margin: 0; 9 | padding: 0; 10 | overflow: hidden; 11 | 12 | li { 13 | display: inline-block; 14 | float: left; 15 | } 16 | } 17 | 18 | .js-tabs__title { 19 | background: #f5f5f5; 20 | border: 1px solid #ccc; 21 | cursor: pointer; 22 | display: block; 23 | margin-right: .5em; 24 | padding: 1em 1.5em; 25 | transition: all .25s; 26 | 27 | &:hover { 28 | text-decoration: none; 29 | } 30 | } 31 | 32 | .js-tabs__title-active { 33 | background: #fff; 34 | border-bottom-color: #fff; 35 | border-top-left-radius: .75em; 36 | } 37 | 38 | .js-tabs__content { 39 | border: 1px solid #ccc; 40 | line-height: 1.5; 41 | margin-top: -1px; 42 | padding: 1em 2em 3em; 43 | } 44 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Define globals exposed by modern browsers. 3 | "browser": true, 4 | 5 | // Define globals exposed by Node.js. 6 | "node": true, 7 | 8 | // Force all variable names to use either camelCase style or UPPER_CASE. 9 | "camelcase": true, 10 | 11 | // Prohibit use of == and != in favor of === and !==. 12 | "eqeqeq": true, 13 | 14 | // Enforce tab width of 2 spaces. 15 | "indent": 2, 16 | 17 | // Prohibit use of a variable before it is defined. 18 | "latedef": false, 19 | 20 | // Enforce line length to 100 characters 21 | "maxlen": 100, 22 | 23 | // Require capitalized names for constructor functions. 24 | "newcap": true, 25 | 26 | // Enforce use of single quotation marks for strings. 27 | "quotmark": "single", 28 | 29 | // Prohibit use of explicitly undeclared variables. 30 | "undef": true, 31 | 32 | // Warn when variables are defined but never used. 33 | "unused": true, 34 | 35 | // Suppress warnings about == null comparisons. 36 | "eqnull": true 37 | } -------------------------------------------------------------------------------- /dist/vanilla-js-tabs.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vanilla JavaScript Tabs v2.0.1 3 | * https://zoltantothcom.github.io/vanilla-js-tabs 4 | */ 5 | const Tabs=function(e){var t=document.getElementById(e.elem);if(!t)throw new Error(`Element with ID "${e.elem}" not found`);const n=t;let l=e.open||0;const r="js-tabs__title",c="js-tabs__title-active",o="js-tabs__content",a=n.querySelectorAll("."+r).length;function s(e){n.addEventListener("click",i);var t=d(null==e?l:e);for(let e=0;e{e.style.display="none"}),[].forEach.call(n.querySelectorAll("."+r),e=>{e.className=function(e,t){t=new RegExp(`(\\s|^)${t}(\\s|$)`,"g");return e.replace(t,"")}(e.className,c)})}function d(e){return e<0||isNaN(e)||e>=a?0:e}function f(e){u();e=d(e);n.querySelectorAll("."+r)[e].classList.add(c),n.querySelectorAll("."+o)[e].style.display=""}function y(){n.removeEventListener("click",i)}return s(),{open:f,update:function(e){y(),u(),s(e)},destroy:y}}; -------------------------------------------------------------------------------- /docs/javascript/vanilla-js-tabs.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vanilla JavaScript Tabs v2.0.1 3 | * https://zoltantothcom.github.io/vanilla-js-tabs 4 | */ 5 | const Tabs=function(e){var t=document.getElementById(e.elem);if(!t)throw new Error(`Element with ID "${e.elem}" not found`);const n=t;let l=e.open||0;const r="js-tabs__title",c="js-tabs__title-active",o="js-tabs__content",a=n.querySelectorAll("."+r).length;function s(e){n.addEventListener("click",i);var t=d(null==e?l:e);for(let e=0;e{e.style.display="none"}),[].forEach.call(n.querySelectorAll("."+r),e=>{e.className=function(e,t){t=new RegExp(`(\\s|^)${t}(\\s|$)`,"g");return e.replace(t,"")}(e.className,c)})}function d(e){return e<0||isNaN(e)||e>=a?0:e}function f(e){u();e=d(e);n.querySelectorAll("."+r)[e].classList.add(c),n.querySelectorAll("."+o)[e].style.display=""}function y(){n.removeEventListener("click",i)}return s(),{open:f,update:function(e){y(),u(),s(e)},destroy:y}}; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /docs/styles/docs-page.css: -------------------------------------------------------------------------------- 1 | body{counter-reset:item;font-family:sans-serif;font-size:15px;margin:0;padding:0}img{display:inline-block}header{width:800px;margin:16px auto;text-align:center}h1{margin:48px 0 0}h3{font-size:18px;font-style:italic;font-weight:400;margin:32px 0 48px}h4{font-size:16px;font-weight:400;margin:32px 0 48px}section{border:1px solid #f0db4f;border-radius:3px;line-height:1.75;margin:0 auto 32px;width:800px}section h2{background:#fefac9;border-bottom:1px solid #f0db4f;font-size:15px;margin:0 0 30px;padding:10px}section ol,section p,section ul{margin:0 45px 30px}section ol{list-style:none;margin-left:25px}section ol li{counter-increment:item;margin-bottom:3em}section ol li:before{margin-right:10px;border-radius:4px;content:counter(item);background:#272822;color:#fff;width:2em;text-align:center;display:inline-block;height:2em;line-height:2em}section a{color:#55acee;text-decoration:none}section a:hover{text-decoration:underline}section table{border:1px solid #eee;border-collapse:collapse;font-size:14px;margin:16px 32px 32px;width:92%}section table th{background:#272822;color:#fafafa;font-size:14px;font-weight:400}section table th.subhead{background:#fffeee;color:#e09e41}section table td{font-family:monospace}section table tr:nth-child(2n){background:#f5f5f5}section table td,section table th{border:1px solid #eee;padding:10px;text-align:left}section section code{font-size:16px}.smaller{font-size:16px;font-style:italic}#custom-color-select{display:block;margin:0 auto;width:24em}input{display:block;height:3em;margin:2em auto;width:8em} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vanilla-js-tabs", 3 | "version": "2.0.1", 4 | "description": "Vanilla JavaScript tabs - extremely tiny, but gets the job done.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "gulp", 8 | "test": "karma start karma.conf.js" 9 | }, 10 | "devDependencies": { 11 | "@types/jasmine": "^5.1.4", 12 | "@types/jasmine-jquery": "^1.5.37", 13 | "@types/jquery": "^3.5.29", 14 | "coveralls": "^3.1.1", 15 | "gulp": "^4.0.2", 16 | "gulp-clean-css": "^4.3.0", 17 | "gulp-cli": "^2.3.0", 18 | "gulp-header": "^2.0.9", 19 | "gulp-jshint": "^2.1.0", 20 | "gulp-less": "^4.0.1", 21 | "gulp-pug": "^4.0.1", 22 | "gulp-rename": "^1.4.0", 23 | "gulp-strip-code": "^0.1.4", 24 | "gulp-typescript": "^6.0.0-alpha.1", 25 | "gulp-uglify": "^3.0.2", 26 | "jasmine": "^5.1.0", 27 | "jasmine-core": "^3.99.1", 28 | "jasmine-jquery": "^2.1.1", 29 | "jquery": "^3.7.1", 30 | "jshint": "^2.13.6", 31 | "jshint-stylish": "^2.2.1", 32 | "karma": "^4.4.1", 33 | "karma-chrome-launcher": "^2.2.0", 34 | "karma-cli": "^2.0.0", 35 | "karma-coverage": "^1.1.2", 36 | "karma-jasmine": "^2.0.1", 37 | "karma-jasmine-jquery": "^0.1.1", 38 | "karma-phantomjs-launcher": "^1.0.4", 39 | "karma-spec-reporter": "0.0.32", 40 | "karma-typescript": "^5.5.4", 41 | "phantom": "^6.3.0", 42 | "pug": "^2.0.4", 43 | "typescript": "^5.4.5" 44 | }, 45 | "repository": { 46 | "type": "git", 47 | "url": "https://github.com/zoltantothcom/vanilla-js-tabs.git" 48 | }, 49 | "author": "Zoltan Toth", 50 | "license": "Unlicense", 51 | "bugs": { 52 | "url": "https://github.com/zoltantothcom/vanilla-js-tabs/issues" 53 | }, 54 | "homepage": "https://zoltantothcom.github.io/vanilla-js-tabs" 55 | } 56 | -------------------------------------------------------------------------------- /test/spec/fixtures/tabs.fixture.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 9 | 10 |
11 |

ONE

12 |

Shabby chic ennui cred godard, forage roof party scenester health goth typewriter pitchfork. Stumptown whatever fap, austin heirloom asymmetrical lo-fi ethical seitan. Post-ironic hella listicle brunch meggings artisan. YOLO tattooed blue bottle, fanny pack gluten-free put a bird on it migas forage trust fund.

13 |
14 | 15 |
16 |

TWO

17 | 18 | 19 |

Shabby chic ennui cred godard, forage roof party scenester health goth typewriter pitchfork. Stumptown whatever fap, austin heirloom asymmetrical lo-fi ethical seitan. Post-ironic hella listicle brunch meggings artisan. YOLO tattooed blue bottle, fanny pack gluten-free put a bird on it migas forage trust fund.

20 |
21 | 22 |
23 |

THREE

24 | 25 |
26 | 27 |
28 |

FOUR

29 |

Meggings distillery pop-up artisan, hashtag 90's echo park kickstarter gluten-free. Pinterest gentrify squid vinyl chicharrones meh venmo. Beard aesthetic whatever bicycle rights artisan gastropub. Fingerstache bicycle rights you probably haven't heard of them, schlitz franzen semiotics microdosing.

30 |

Shabby chic ennui cred godard, forage roof party scenester health goth typewriter pitchfork. Stumptown whatever fap, austin heirloom asymmetrical lo-fi ethical seitan. Post-ironic hella listicle brunch meggings artisan. YOLO tattooed blue bottle, fanny pack gluten-free put a bird on it migas forage trust fund.

31 |
32 | 33 |
-------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var ts = require("gulp-typescript"), 2 | pkg = require("./package.json"), 3 | pug = require("gulp-pug"), 4 | gulp = require("gulp"), 5 | less = require("gulp-less"), 6 | clean = require("gulp-clean-css"), 7 | uglify = require("gulp-uglify"), 8 | rename = require("gulp-rename"), 9 | header = require("gulp-header"), 10 | jshint = require("gulp-jshint"), 11 | stylish = require("jshint-stylish"); 12 | 13 | var banner = [ 14 | "/**", 15 | " * Vanilla JavaScript Tabs v<%= pkg.version %>", 16 | " * <%= pkg.homepage %>", 17 | " */", 18 | "", 19 | ].join("\n"); 20 | 21 | gulp.task("ts", function () { 22 | return gulp 23 | .src("./src/javascript/vanilla-js-tabs.ts") 24 | .pipe( 25 | ts({ 26 | target: "es2015", 27 | lib: ["es2015", "dom"], 28 | noImplicitAny: true, 29 | outFile: "vanilla-js-tabs.js", 30 | }) 31 | ) 32 | .pipe(gulp.dest("./dist")); 33 | }); 34 | 35 | gulp.task("script", function (done) { 36 | gulp 37 | .src(["./dist/vanilla-js-tabs.js"]) 38 | .pipe(uglify()) 39 | .pipe( 40 | header(banner, { 41 | pkg: pkg, 42 | }) 43 | ) 44 | .pipe( 45 | rename({ 46 | suffix: ".min", 47 | }) 48 | ) 49 | .pipe(gulp.dest("./dist")) 50 | .pipe(gulp.dest("./docs/javascript")); 51 | 52 | done(); 53 | }); 54 | 55 | gulp.task("markup", function (done) { 56 | gulp 57 | .src("./src/index.pug") 58 | .pipe( 59 | pug({ 60 | pretty: true, 61 | }) 62 | ) 63 | .pipe(gulp.dest("./dist")); 64 | 65 | done(); 66 | }); 67 | 68 | gulp.task("styles", function (done) { 69 | gulp 70 | .src("./src/styles/*.less") 71 | .pipe(less()) 72 | .pipe(gulp.dest("./dist")) 73 | .pipe(gulp.dest("./docs/styles")); 74 | 75 | done(); 76 | }); 77 | 78 | gulp.task("docs-styles", function (done) { 79 | gulp 80 | .src("./docs/styles/*.less") 81 | .pipe(less()) 82 | .pipe( 83 | clean({ 84 | compatibility: "ie9", 85 | }) 86 | ) 87 | .pipe(gulp.dest("./docs/styles")); 88 | 89 | done(); 90 | }); 91 | 92 | gulp.task("lint", function () { 93 | return gulp 94 | .src("./src/javascript/*.js") 95 | .pipe(jshint(".jshintrc")) 96 | .pipe(jshint.reporter(stylish)); 97 | }); 98 | 99 | gulp.task( 100 | "default", 101 | gulp.series("ts", "script", "markup", "styles", "docs-styles", "lint") 102 | ); 103 | -------------------------------------------------------------------------------- /docs/styles/prism.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+scss */ 2 | /** 3 | * okaidia theme for JavaScript, CSS and HTML 4 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 5 | * @author ocodia 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: #f8f8f2; 11 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | direction: ltr; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | /* Code blocks */ 32 | pre[class*="language-"] { 33 | padding: 1em; 34 | margin: .5em 0; 35 | overflow: auto; 36 | border-radius: 0.3em; 37 | } 38 | 39 | :not(pre) > code[class*="language-"], 40 | pre[class*="language-"] { 41 | background: #272822; 42 | } 43 | 44 | /* Inline code */ 45 | :not(pre) > code[class*="language-"] { 46 | padding: .1em; 47 | border-radius: .3em; 48 | white-space: normal; 49 | } 50 | 51 | .token.comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: slategray; 56 | } 57 | 58 | .token.punctuation { 59 | color: #f8f8f2; 60 | } 61 | 62 | .namespace { 63 | opacity: .7; 64 | } 65 | 66 | .token.property, 67 | .token.tag, 68 | .token.constant, 69 | .token.symbol, 70 | .token.deleted { 71 | color: #f92672; 72 | } 73 | 74 | .token.boolean, 75 | .token.number { 76 | color: #ae81ff; 77 | } 78 | 79 | .token.selector, 80 | .token.attr-name, 81 | .token.string, 82 | .token.char, 83 | .token.builtin, 84 | .token.inserted { 85 | color: #a6e22e; 86 | } 87 | 88 | .token.operator, 89 | .token.entity, 90 | .token.url, 91 | .language-css .token.string, 92 | .style .token.string, 93 | .token.variable { 94 | color: #f8f8f2; 95 | } 96 | 97 | .token.atrule, 98 | .token.attr-value, 99 | .token.function { 100 | color: #e6db74; 101 | } 102 | 103 | .token.keyword { 104 | color: #66d9ef; 105 | } 106 | 107 | .token.regex, 108 | .token.important { 109 | color: #fd971f; 110 | } 111 | 112 | .token.important, 113 | .token.bold { 114 | font-weight: bold; 115 | } 116 | .token.italic { 117 | font-style: italic; 118 | } 119 | 120 | .token.entity { 121 | cursor: help; 122 | } 123 | 124 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vanilla JavaScript Dropdown 6 | 7 | 8 | 9 |
10 | 16 |
17 |

ONE

18 |

Shabby chic ennui cred godard, forage roof party scenester health goth typewriter pitchfork. Stumptown whatever fap, austin heirloom asymmetrical lo-fi ethical seitan. Post-ironic hella listicle brunch meggings artisan. YOLO tattooed blue bottle, fanny pack gluten-free put a bird on it migas forage trust fund.

19 |
20 |
21 |

TWO

22 |

23 | Shabby chic cred godard, forage roof party scenester health goth typewriter pitchfork. Stumptown whatever fap, austin heirloom asymmetrical lo-fi ethical seitan. Post-ironic hella listicle brunch meggings artisan. YOLO tattooed blue bottle, fanny pack gluten-free put a bird on it migas forage trust fund.

24 | 25 |

26 |
27 |
28 |

THREE

29 |
30 |
31 |

FOUR

32 |

Meggings distillery pop-up artisan, hashtag 90's echo park kickstarter gluten-free. Pinterest gentrify squid vinyl chicharrones meh venmo. Beard aesthetic whatever bicycle rights artisan gastropub. Fingerstache bicycle rights you probably haven't heard of them, schlitz franzen semiotics microdosing.

33 |

Shabby chic ennui cred godard, forage roof party scenester health goth typewriter pitchfork. Stumptown whatever fap, austin heirloom asymmetrical lo-fi ethical seitan. Post-ironic hella listicle brunch meggings artisan. YOLO tattooed blue bottle, fanny pack gluten-free put a bird on it migas forage trust fund.

34 |
35 |
36 | 37 | 38 | 44 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Tue Feb 21 2017 14:28:05 GMT-0500 (Eastern Standard Time) 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | // base path that will be used to resolve all patterns (eg. files, exclude) 7 | basePath: "", 8 | 9 | // frameworks to use 10 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 11 | frameworks: ["jasmine-jquery", "jasmine", "karma-typescript"], 12 | 13 | // list of files / patterns to load in the browser 14 | files: [ 15 | "src/*/*.ts", 16 | "test/*js", 17 | "dist/vanilla-js-tabs.js", 18 | "test/spec/*.ts", 19 | "node_modules/jquery/dist/jquery.min.js", 20 | "test/spec/fixtures/*.html", 21 | { 22 | pattern: "img/*.jpg", 23 | watched: false, 24 | included: false, 25 | served: true, 26 | nocache: false, 27 | }, 28 | ], 29 | 30 | karmaTypescriptConfig: { 31 | transformPath: function (filepath) { 32 | return filepath.replace(/\.(ts|tsx)$/, ".js"); 33 | }, 34 | }, 35 | 36 | // list of files to exclude 37 | exclude: [], 38 | 39 | // preprocess matching files before serving them to the browser 40 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 41 | preprocessors: { 42 | "src/*/*.js": "coverage", 43 | "src/**/*.ts": ["karma-typescript"], 44 | "test/**/*.spec.ts": ["karma-typescript"], 45 | }, 46 | 47 | // test results reporter to use 48 | // possible values: 'dots', 'progress' 49 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 50 | reporters: ["spec", "coverage", "karma-typescript"], 51 | coverageReporter: { 52 | type: "lcov", 53 | dir: "coverage", 54 | }, 55 | 56 | // web server port 57 | port: 9876, 58 | 59 | // enable / disable colors in the output (reporters and logs) 60 | colors: true, 61 | 62 | // level of logging 63 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 64 | logLevel: config.LOG_INFO, 65 | 66 | // enable / disable watching file and executing tests whenever any file changes 67 | autoWatch: true, 68 | 69 | // start these browsers 70 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 71 | browsers: ["Chrome"], 72 | 73 | // Continuous Integration mode 74 | // if true, Karma captures browsers, runs the tests and exits 75 | singleRun: true, 76 | }); 77 | }; 78 | -------------------------------------------------------------------------------- /src/index.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | meta(charset="utf-8") 5 | 6 | title="Vanilla JavaScript Dropdown" 7 | link(rel="stylesheet" href="vanilla-js-dropdown.css") 8 | 9 | body 10 | 11 | div.js-tabs#tabs 12 | 13 | ul.js-tabs__header 14 | li 15 | a.js-tabs__title(href="#") Title 1 16 | li 17 | a.js-tabs__title(href="#") Title 2 18 | li 19 | a.js-tabs__title(href="#") Title 3 20 | li 21 | a.js-tabs__title(href="#") Title 4 22 | 23 | 24 | div.js-tabs__content 25 | h1 ONE 26 | p Shabby chic ennui cred godard, forage roof party scenester health goth typewriter pitchfork. Stumptown whatever fap, austin heirloom asymmetrical lo-fi ethical seitan. Post-ironic hella listicle brunch meggings artisan. YOLO tattooed blue bottle, fanny pack gluten-free put a bird on it migas forage trust fund.

27 | 28 | div.js-tabs__content 29 | h1 TWO 30 | img(src="http://lorempixel.com/600/100" alt="") 31 | 32 | p. 33 | Shabby chic cred godard, forage roof party scenester health goth typewriter pitchfork. Stumptown whatever fap, austin heirloom asymmetrical lo-fi ethical seitan. Post-ironic hella listicle brunch meggings artisan. YOLO tattooed blue bottle, fanny pack gluten-free put a bird on it migas forage trust fund.

34 | 35 | div.js-tabs__content 36 | h1 THREE 37 | img(src="http://lorempixel.com/500/300" alt="") 38 | 39 | div.js-tabs__content 40 | h1 FOUR 41 | p Meggings distillery pop-up artisan, hashtag 90's echo park kickstarter gluten-free. Pinterest gentrify squid vinyl chicharrones meh venmo. Beard aesthetic whatever bicycle rights artisan gastropub. Fingerstache bicycle rights you probably haven't heard of them, schlitz franzen semiotics microdosing.

42 | p Shabby chic ennui cred godard, forage roof party scenester health goth typewriter pitchfork. Stumptown whatever fap, austin heirloom asymmetrical lo-fi ethical seitan. Post-ironic hella listicle brunch meggings artisan. YOLO tattooed blue bottle, fanny pack gluten-free put a bird on it migas forage trust fund.

43 | 44 | script(src="vanilla-js-tabs.min.js") 45 | script. 46 | var tabs = Tabs({ 47 | elem: 'tabs', 48 | open: 1 49 | }); 50 | -------------------------------------------------------------------------------- /docs/styles/docs-page.less: -------------------------------------------------------------------------------- 1 | body { 2 | counter-reset: item; 3 | font-family: sans-serif; 4 | font-size: 15px; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | img { 10 | display: inline-block; 11 | } 12 | 13 | header { 14 | width: 800px; 15 | margin: 16px auto; 16 | text-align: center; 17 | } 18 | 19 | h1 { 20 | margin: 48px 0 0; 21 | } 22 | 23 | h3 { 24 | font-size: 18px; 25 | font-style: italic; 26 | font-weight: 400; 27 | margin: 32px 0 48px; 28 | } 29 | 30 | h4 { 31 | font-size: 16px; 32 | font-weight: 400; 33 | margin: 32px 0 48px; 34 | } 35 | 36 | section { 37 | border: 1px solid #f0db4f; 38 | border-radius: 3px; 39 | line-height: 1.75; 40 | margin: 0 auto 32px; 41 | width: 800px; 42 | 43 | h2 { 44 | background: #fefac9; 45 | border-bottom: 1px solid #f0db4f; 46 | font-size: 15px; 47 | margin: 0 0 30px; 48 | padding: 10px; 49 | } 50 | 51 | p, 52 | ul, 53 | ol { 54 | margin: 0 45px 30px; 55 | } 56 | 57 | ol { 58 | list-style: none; 59 | margin-left: 25px; 60 | 61 | li { 62 | counter-increment: item; 63 | margin-bottom: 3em; 64 | 65 | &:before { 66 | margin-right: 10px; 67 | border-radius: 4px; 68 | content: counter(item); 69 | background: #272822; 70 | color: #fff; 71 | width: 2em; 72 | text-align: center; 73 | display: inline-block; 74 | height: 2em; 75 | line-height: 2em; 76 | } 77 | } 78 | } 79 | 80 | a { 81 | color: #55acee; 82 | text-decoration: none; 83 | 84 | &:hover { 85 | text-decoration: underline; 86 | } 87 | } 88 | 89 | table { 90 | border: 1px solid #eee; 91 | border-collapse: collapse; 92 | font-size: 14px; 93 | margin: 16px 32px 32px; 94 | width: 92%; 95 | 96 | th { 97 | background: #272822; 98 | color: #fafafa; 99 | font-size: 14px; 100 | font-weight: 400; 101 | 102 | &.subhead { 103 | background: #fffeee; 104 | color: #e09e41; 105 | } 106 | } 107 | 108 | td { 109 | font-family: monospace; 110 | } 111 | 112 | tr:nth-child(2n) { 113 | background: #f5f5f5; 114 | } 115 | 116 | th, 117 | td { 118 | border: 1px solid #eee; 119 | padding: 10px; 120 | text-align: left; 121 | } 122 | } 123 | 124 | section code { 125 | font-size: 16px; 126 | } 127 | } 128 | 129 | .smaller { 130 | font-size: 16px; 131 | font-style: italic; 132 | } 133 | 134 | #custom-color-select { 135 | display: block; 136 | margin: 0 auto; 137 | width: 24em; 138 | } 139 | 140 | input { 141 | display: block; 142 | height: 3em; 143 | margin: 2em auto; 144 | width: 8em; 145 | } 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vanilla JavaScript Tabs 2 | 3 | [![Coverage Status](https://coveralls.io/repos/github/zoltantothcom/vanilla-js-tabs/badge.svg?branch=master)](https://coveralls.io/github/zoltantothcom/vanilla-js-tabs?branch=master) ![Dependencies](https://img.shields.io/badge/dependencies-none-brightgreen.svg) 4 | 5 | Vanilla JavaScript Tabs - simple and awesome. 6 | 7 | _— Inspired by the blazing fast, lightweight, cross-platform and crazy popular [Vanilla JS](http://vanilla-js.com/) framework._ 8 | 9 | ## Demo 10 | 11 | [TABS](http://zoltantothcom.github.io/vanilla-js-tabs) 12 | 13 | ## Options 14 | 15 | | Option | Type | Default | Description | 16 | | ------ | ------ | ------- | -------------------------------------------------- | 17 | | elem | string | | HTML _id_ of the tab container in the HTML markup. | 18 | | open | number | 0 | Opens this tab initially. | 19 | 20 | ## Methods 21 | 22 | | Method | Type | Description | 23 | | --------- | ------ | ---------------------------------------------------------------------------------- | 24 | | open(n) | number | Opens a tab by index | 25 | | update(n) | number | Updates the tabs with _n_-th tab open
_(useful when dynamically adding tabs)_ | 26 | | destroy() | | Removes the listeners | 27 | 28 | ## Usage example 29 | 30 | ```javascript 31 | var tabs = Tabs({ 32 | elem: "tabs", 33 | open: 1, 34 | }); 35 | ``` 36 | 37 | ```javascript 38 | // Open any other tab 39 | tabs.open(3); 40 | ``` 41 | 42 | ## Running the tests 43 | 44 | ``` 45 | npm test 46 | ``` 47 | 48 | ## Browser support and dependencies 49 | 50 | | Browser | Support | Dependencies | 51 | | ---------- | ------- | ------------ | 52 | | Chrome | yes | - | 53 | | Firefox | yes | - | 54 | | Safari | yes | - | 55 | | Opera | yes | - | 56 | | IE9 and up | yes | - | 57 | 58 | ## License 59 | 60 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. 61 | 62 | See [Unlicense](http://unlicense.org) for full details. 63 | 64 | ## Related 65 | 66 | - [Vanilla JavaScript **Carousel**](https://github.com/zoltantothcom/vanilla-js-carousel) 67 | - [Vanilla JavaScript **Dropdown**](https://github.com/zoltantothcom/vanilla-js-dropdown) 68 | - [Vanilla JavaScript **Tooltip**](https://github.com/zoltantothcom/vanilla-js-tooltip) 69 | - [Vanilla JavaScript **Accordion**](https://github.com/zoltantothcom/vanilla-js-accordion) 70 | -------------------------------------------------------------------------------- /dist/vanilla-js-tabs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Zoltan Toth 4 | * @version 2.0.1 5 | */ 6 | const Tabs = function (options) { 7 | const el = document.getElementById(options.elem); 8 | if (!el) 9 | throw new Error(`Element with ID "${options.elem}" not found`); 10 | const elem = el; 11 | let open = options.open || 0; 12 | const titleClass = "js-tabs__title"; 13 | const activeClass = "js-tabs__title-active"; 14 | const contentClass = "js-tabs__content"; 15 | const tabsNum = elem.querySelectorAll(`.${titleClass}`).length; 16 | render(); 17 | /** 18 | * Initial rendering of the tabs. 19 | */ 20 | function render(n) { 21 | elem.addEventListener("click", onClick); 22 | const init = n == null ? checkTab(open) : checkTab(n); 23 | for (let i = 0; i < tabsNum; i++) { 24 | elem.querySelectorAll(`.${titleClass}`)[i].setAttribute("data-index", i.toString()); 25 | if (i === init) 26 | openTab(i); 27 | } 28 | } 29 | /** 30 | * Handle clicks on the tabs. 31 | * 32 | * @param {object} e - Element the click occured on. 33 | */ 34 | function onClick(e) { 35 | var _a; 36 | const target = e.target.closest(`.${titleClass}`); 37 | if (!target) 38 | return; 39 | e.preventDefault(); 40 | openTab(parseInt((_a = target.getAttribute("data-index")) !== null && _a !== void 0 ? _a : "0")); 41 | } 42 | /** 43 | * Hide all tabs and re-set tab titles. 44 | */ 45 | function reset() { 46 | [].forEach.call(elem.querySelectorAll(`.${contentClass}`), (item) => { 47 | item.style.display = "none"; 48 | }); 49 | [].forEach.call(elem.querySelectorAll(`.${titleClass}`), (item) => { 50 | item.className = removeClass(item.className, activeClass); 51 | }); 52 | } 53 | /** 54 | * Utility function to remove the open class from tab titles. 55 | * 56 | * @param {string} str - Current class. 57 | * @param {string} cls - The class to remove. 58 | */ 59 | function removeClass(str, cls) { 60 | const reg = new RegExp(`(\\s|^)${cls}(\\s|$)`, "g"); 61 | return str.replace(reg, ""); 62 | } 63 | /** 64 | * Utility function to remove the open class from tab titles. 65 | * 66 | * @param n - Tab to open. 67 | */ 68 | function checkTab(n) { 69 | return n < 0 || isNaN(n) || n >= tabsNum ? 0 : n; 70 | } 71 | /** 72 | * Opens a tab by index. 73 | * 74 | * @param {number} n - Index of tab to open. Starts at 0. 75 | * 76 | * @public 77 | */ 78 | function openTab(n) { 79 | reset(); 80 | const i = checkTab(n); 81 | elem.querySelectorAll(`.${titleClass}`)[i].classList.add(activeClass); 82 | elem.querySelectorAll(`.${contentClass}`)[i].style.display = ""; 83 | } 84 | /** 85 | * Updates the tabs. 86 | * 87 | * @param {number} n - Index of tab to open. Starts at 0. 88 | * 89 | * @public 90 | */ 91 | function update(n) { 92 | destroy(); 93 | reset(); 94 | render(n); 95 | } 96 | /** 97 | * Removes the listeners from the tabs. 98 | * 99 | * @public 100 | */ 101 | function destroy() { 102 | elem.removeEventListener("click", onClick); 103 | } 104 | return { 105 | open: openTab, 106 | update, 107 | destroy, 108 | }; 109 | }; 110 | -------------------------------------------------------------------------------- /src/javascript/vanilla-js-tabs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * @author Zoltan Toth 4 | * @version 2.0.1 5 | */ 6 | 7 | /** 8 | * @description 9 | * Vanilla Javascript Tabs 10 | * 11 | * @class 12 | * @param {string} options.elem - HTML id of the tabs container 13 | * @param {number} [options.open = 0] - Render the tabs with this item open 14 | */ 15 | 16 | interface TabsOptions { 17 | elem: string; 18 | open?: number; 19 | } 20 | 21 | interface Tabs { 22 | open: (n: number) => void; 23 | update: (n: number) => void; 24 | destroy: () => void; 25 | } 26 | 27 | const Tabs = function (options: TabsOptions): Tabs { 28 | const el: HTMLElement | null = document.getElementById(options.elem); 29 | if (!el) throw new Error(`Element with ID "${options.elem}" not found`); 30 | 31 | const elem = el; 32 | 33 | let open: number = options.open || 0; 34 | const titleClass: string = "js-tabs__title"; 35 | const activeClass: string = "js-tabs__title-active"; 36 | const contentClass: string = "js-tabs__content"; 37 | const tabsNum: number = elem.querySelectorAll(`.${titleClass}`).length; 38 | 39 | render(); 40 | 41 | /** 42 | * Initial rendering of the tabs. 43 | */ 44 | function render(n?: number): void { 45 | elem.addEventListener("click", onClick); 46 | 47 | const init = n == null ? checkTab(open) : checkTab(n); 48 | 49 | for (let i = 0; i < tabsNum; i++) { 50 | (elem.querySelectorAll(`.${titleClass}`)[i] as HTMLElement).setAttribute( 51 | "data-index", 52 | i.toString() 53 | ); 54 | if (i === init) openTab(i); 55 | } 56 | } 57 | 58 | /** 59 | * Handle clicks on the tabs. 60 | * 61 | * @param {object} e - Element the click occured on. 62 | */ 63 | function onClick(e: MouseEvent): void { 64 | const target = (e.target as HTMLElement).closest(`.${titleClass}`); 65 | if (!target) return; 66 | 67 | e.preventDefault(); 68 | 69 | openTab(parseInt(target.getAttribute("data-index") ?? "0")); 70 | } 71 | 72 | /** 73 | * Hide all tabs and re-set tab titles. 74 | */ 75 | function reset(): void { 76 | [].forEach.call( 77 | elem.querySelectorAll(`.${contentClass}`), 78 | (item: HTMLElement) => { 79 | item.style.display = "none"; 80 | } 81 | ); 82 | 83 | [].forEach.call( 84 | elem.querySelectorAll(`.${titleClass}`), 85 | (item: HTMLElement) => { 86 | item.className = removeClass(item.className, activeClass); 87 | } 88 | ); 89 | } 90 | 91 | /** 92 | * Utility function to remove the open class from tab titles. 93 | * 94 | * @param {string} str - Current class. 95 | * @param {string} cls - The class to remove. 96 | */ 97 | function removeClass(str: string, cls: string): string { 98 | const reg = new RegExp(`(\\s|^)${cls}(\\s|$)`, "g"); 99 | return str.replace(reg, ""); 100 | } 101 | 102 | /** 103 | * Utility function to remove the open class from tab titles. 104 | * 105 | * @param n - Tab to open. 106 | */ 107 | function checkTab(n: number): number { 108 | return n < 0 || isNaN(n) || n >= tabsNum ? 0 : n; 109 | } 110 | 111 | /** 112 | * Opens a tab by index. 113 | * 114 | * @param {number} n - Index of tab to open. Starts at 0. 115 | * 116 | * @public 117 | */ 118 | function openTab(n: number): void { 119 | reset(); 120 | 121 | const i = checkTab(n); 122 | 123 | elem.querySelectorAll(`.${titleClass}`)[i].classList.add(activeClass); 124 | ( 125 | elem.querySelectorAll(`.${contentClass}`)[i] as HTMLElement 126 | ).style.display = ""; 127 | } 128 | 129 | /** 130 | * Updates the tabs. 131 | * 132 | * @param {number} n - Index of tab to open. Starts at 0. 133 | * 134 | * @public 135 | */ 136 | function update(n: number): void { 137 | destroy(); 138 | reset(); 139 | render(n); 140 | } 141 | 142 | /** 143 | * Removes the listeners from the tabs. 144 | * 145 | * @public 146 | */ 147 | function destroy(): void { 148 | elem.removeEventListener("click", onClick); 149 | } 150 | 151 | return { 152 | open: openTab, 153 | update, 154 | destroy, 155 | }; 156 | }; 157 | -------------------------------------------------------------------------------- /test/spec/tabs.spec.ts: -------------------------------------------------------------------------------- 1 | const fixturePath: string = "base/test/spec/fixtures"; 2 | const tabsFixture: string = "tabs.fixture.html"; 3 | 4 | interface Tabs { 5 | open: (n: number) => void; 6 | update: (n: number) => void; 7 | destroy: () => void; 8 | } 9 | 10 | describe("TABS", function () { 11 | beforeEach(function () { 12 | jasmine.getFixtures().fixturesPath = fixturePath; 13 | loadFixtures(tabsFixture); 14 | 15 | const tabsInstance = Tabs({ 16 | elem: "tabs", 17 | open: -123, 18 | }) as Tabs; 19 | 20 | this.tabs = tabsInstance; 21 | }); 22 | 23 | afterEach(function () { 24 | this.tabs.destroy(); 25 | }); 26 | 27 | describe("original tabs", function () { 28 | it("markup should be present", function () { 29 | expect($("#tabs")).toBeDefined(); 30 | }); 31 | 32 | it("should have more than 0 tabs", function () { 33 | expect($(".js-tabs__title").length).toBeGreaterThan(0); 34 | expect($(".js-tabs__content").length).toBeGreaterThan(0); 35 | }); 36 | 37 | it("should have the same number of titles and content blocks", function () { 38 | const titles: number = $(".js-tabs__title").length; 39 | const contents: number = $(".js-tabs__content").length; 40 | 41 | expect(titles).toBe(contents); 42 | }); 43 | 44 | it("should default to 1st tab when open property is invalid", function () { 45 | expect($(".js-tabs__title")[0]).toHaveClass("js-tabs__title-active"); 46 | }); 47 | }); 48 | 49 | describe("methods", function () { 50 | it("should have .open() method", function () { 51 | expect(typeof this.tabs.open).toBe("function"); 52 | }); 53 | 54 | it("should have .update() method", function () { 55 | expect(typeof this.tabs.update).toBe("function"); 56 | }); 57 | 58 | it("should have .destroy() method", function () { 59 | expect(typeof this.tabs.destroy).toBe("function"); 60 | }); 61 | }); 62 | 63 | describe("method calls", function () { 64 | beforeEach(function () { 65 | jasmine.getFixtures().fixturesPath = fixturePath; 66 | loadFixtures(tabsFixture); 67 | 68 | const tabsInstance: Tabs = Tabs({ 69 | elem: "tabs", 70 | }); 71 | 72 | this.tabs = tabsInstance; 73 | }); 74 | 75 | it("should default to 1st tab when .open() argument is invalid", function () { 76 | this.tabs.open(-123); 77 | expect($(".js-tabs__title")[0]).toHaveClass("js-tabs__title-active"); 78 | }); 79 | 80 | it(".open(2) should open the 3rd tab", function () { 81 | expect($(".js-tabs__title")[2]).not.toHaveClass("js-tabs__title-active"); 82 | this.tabs.open(2); 83 | expect($(".js-tabs__title")[2]).toHaveClass("js-tabs__title-active"); 84 | }); 85 | 86 | it(".update(2) should reset the tabs with 3rd tab open", function () { 87 | expect($(".js-tabs__title")[0]).toHaveClass("js-tabs__title-active"); 88 | expect($(".js-tabs__title")[2]).not.toHaveClass("js-tabs__title-active"); 89 | this.tabs.update(2); 90 | expect($(".js-tabs__title")[2]).toHaveClass("js-tabs__title-active"); 91 | }); 92 | 93 | it("should not react to clicks after destroy()", function () { 94 | expect($(".js-tabs__title")[0]).toHaveClass("js-tabs__title-active"); 95 | 96 | this.tabs.destroy(); 97 | 98 | const spyEvent = spyOnEvent(".js-tabs__title", "click"); 99 | $(".js-tabs__title")[1].click(); 100 | 101 | expect("click").toHaveBeenTriggeredOn(".js-tabs__title"); 102 | expect(spyEvent).toHaveBeenTriggered(); 103 | 104 | expect($(".js-tabs__title")[1]).not.toHaveClass("js-tabs__title-active"); 105 | }); 106 | }); 107 | 108 | describe("behavior", function () { 109 | it("should open the 2nd tab on title click", function () { 110 | expect($(".js-tabs__title")[1]).not.toHaveClass("js-tabs__title-active"); 111 | 112 | const spyEvent = spyOnEvent(".js-tabs__title", "click"); 113 | $(".js-tabs__title")[1].click(); 114 | 115 | expect("click").toHaveBeenTriggeredOn(".js-tabs__title"); 116 | expect(spyEvent).toHaveBeenTriggered(); 117 | 118 | expect($(".js-tabs__title")[1]).toHaveClass("js-tabs__title-active"); 119 | }); 120 | 121 | it("should ignore any clicks in the content blocks", function () { 122 | this.tabs.open(2); 123 | expect($(".js-tabs__title")[2]).toHaveClass("js-tabs__title-active"); 124 | 125 | const spyEvent = spyOnEvent(".js-tabs__content", "click"); 126 | $(".js-tabs__content")[2].click(); 127 | 128 | expect("click").toHaveBeenTriggeredOn(".js-tabs__content"); 129 | expect(spyEvent).toHaveBeenTriggered(); 130 | 131 | expect($(".js-tabs__title")[2]).toHaveClass("js-tabs__title-active"); 132 | }); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vanilla JavaScript tabs - a tiny select tag replacement. 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 |
17 | Vanilla JavaScript 18 | 19 |

Vanilla JavaScript Tabs

20 | 21 |

Tiny and awesome

22 |
23 | 24 |
25 |

Demo

26 | 27 |
28 | 34 | 35 |
36 |

TAB 1

37 |

38 | Shabby chic ennui cred godard, forage roof party scenester health 39 | goth typewriter pitchfork. 40 |

41 |
42 | 43 |
44 |

TAB 2

45 |

46 | Shabby chic 47 | github cred godard, 48 | forage roof party scenester health goth typewriter pitchfork. 49 |

50 |
51 | 52 |
53 |

TAB 3

54 |

55 | Yolo tattooed blue bottle, fanny pack gluten-free put a bird on it 56 | migas forage trust fund. 57 |

58 |
59 | 60 |
61 |

TAB 4

62 |

63 | Fingerstache bicycle rights you probably haven't heard of them, 64 | schlitz franzen semiotics. 65 |

66 |
67 |
68 |
69 | 70 |
71 |

Download

72 | 73 |

74 | Available on 75 | GitHub 80 |

81 |
82 | 83 |
84 |

Installation

85 | 86 |
    87 |
  1. 88 | Include the script 89 |
    <script src="path/to/vanilla-js-tabs.min.js"></script>
    90 |
  2. 91 |
  3. 92 | Include the CSS (feel free to edit it or write your own) 93 |
    <link rel="stylesheet" href="path/to/vanilla-js-tabs.css">
    94 |
  4. 95 |
  5. 96 | Write your tabs markup 97 | 98 |
    <div class="js-tabs" id="tabs">
     99 |                         
    100 |     <ul class="js-tabs__header">
    101 |         <li><a href="#" class="js-tabs__title">Title 1</a></li>
    102 |         <li><a href="#" class="js-tabs__title">Title 2</a></li>
    103 |         <li><a href="#" class="js-tabs__title">Title 3</a></li>
    104 |         <li><a href="#" class="js-tabs__title">Title 4</a></li>
    105 |     </ul>
    106 |     
    107 |     <div class="js-tabs__content">
    108 |         <h1>ONE</h1>
    109 |     </div>
    110 | 
    111 |     <div class="js-tabs__content">
    112 |         <h1>TWO</h1>
    113 |     </div>
    114 | 
    115 |     <div class="js-tabs__content">
    116 |         <h1>THREE</h1>
    117 |     </div>
    118 | 
    119 |     <div class="js-tabs__content">
    120 |         <h1>FOUR</h1>
    121 |     </div>
    122 |     
    123 | </div>
    124 | 
    125 |
  6. 126 |
  7. 127 | Initialize the tabs 128 |
    var tabs = Tabs({
    129 |     elem: "tabs",
    130 |     open: 2
    131 | });
    132 | 
    133 | // Open any other tab
    134 | tabs.open(4);
    135 |
  8. 136 |
137 |
138 | 139 |
140 |

Options

141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 |
OptionTypeDefaultDescription
elemstringHTML id of the tab container
opennumber0Starts with this tab oipen
162 |
163 | 164 |
165 |

Methods

166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 182 | 183 | 184 | 185 | 186 | 187 |
MethodDescription
.open(n)Opens a tab by index
.update(n) 179 | Updates tabs with n-th tab open
180 | (useful when dynamically adding tabs) 181 |
.destroy()Removes the listeners
188 |
189 | 190 |
191 |

Licence

192 | 193 |
194 |

195 | This is free and unencumbered software released into the public 196 | domain. 197 |

198 | 199 |

200 | Anyone is free to copy, modify, publish, use, compile, sell, or 201 | distribute this software, either in source code form or as a compiled 202 | binary, for any purpose, commercial or non-commercial, and by any 203 | means. 204 |

205 | 206 |

207 | In jurisdictions that recognize copyright laws, the author or authors 208 | of this software dedicate any and all copyright interest in the 209 | software to the public domain. We make this dedication for the benefit 210 | of the public at large and to the detriment of our heirs and 211 | successors. We intend this dedication to be an overt act of 212 | relinquishment in perpetuity of all present and future rights to this 213 | software under copyright law. 214 |

215 | 216 |

217 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 218 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 219 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 220 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 221 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 222 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 223 | OTHER DEALINGS IN THE SOFTWARE. 224 |

225 | 226 |

227 | For more information, please refer to 228 | http://unlicense.org 231 |

232 |
233 |
234 | 235 | Fork me on GitHub 247 | 248 | 249 | 250 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /docs/javascript/prism.js: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+scss */ 2 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=_self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),P=[p,1];b&&P.push(b);var A=new a(i,g?t.tokenize(m,g):m,h);P.push(A),w&&P.push(w),Array.prototype.splice.apply(r,P)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var l={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==l.type&&(l.attributes.spellcheck="true"),e.alias){var i="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}t.hooks.run("wrap",l);var o="";for(var s in l.attributes)o+=(o?" ":"")+s+'="'+(l.attributes[s]||"")+'"';return"<"+l.tag+' class="'+l.classes.join(" ")+'" '+o+">"+l.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code,l=n.immediateClose;_self.postMessage(t.highlight(r,t.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 3 | Prism.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=.$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; 4 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/()[\w\W]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag)); 5 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; 6 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}}),Prism.languages.insertBefore("javascript","class-name",{"template-string":{pattern:/`(?:\\`|\\?[^`])*`/,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/()[\w\W]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript; 7 | Prism.languages.scss=Prism.languages.extend("css",{comment:{pattern:/(^|[^\\])(?:\/\*[\w\W]*?\*\/|\/\/.*)/,lookbehind:!0},atrule:{pattern:/@[\w-]+(?:\([^()]+\)|[^(])*?(?=\s+[{;])/,inside:{rule:/@[\w-]+/}},url:/(?:[-a-z]+-)*url(?=\()/i,selector:{pattern:/(?=\S)[^@;\{\}\(\)]?([^@;\{\}\(\)]|&|#\{\$[-_\w]+\})+(?=\s*\{(\}|\s|[^\}]+(:|\{)[^\}]+))/m,inside:{placeholder:/%[-_\w]+/}}}),Prism.languages.insertBefore("scss","atrule",{keyword:[/@(?:if|else(?: if)?|for|each|while|import|extend|debug|warn|mixin|include|function|return|content)/i,{pattern:/( +)(?:from|through)(?= )/,lookbehind:!0}]}),Prism.languages.insertBefore("scss","property",{variable:/\$[-_\w]+|#\{\$[-_\w]+\}/}),Prism.languages.insertBefore("scss","function",{placeholder:{pattern:/%[-_\w]+/,alias:"selector"},statement:/\B!(?:default|optional)\b/i,"boolean":/\b(?:true|false)\b/,"null":/\bnull\b/,operator:{pattern:/(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|or|not)(?=\s)/,lookbehind:!0}}),Prism.languages.scss.atrule.inside.rest=Prism.util.clone(Prism.languages.scss); 8 | --------------------------------------------------------------------------------