├── .gitignore ├── Gruntfile.js ├── Readme.md ├── dist ├── BootstrapClosure.js └── BootstrapClosure.min.js ├── js ├── .jscsrc ├── .jshintrc ├── alert.js ├── button.js ├── carousel.js ├── collapse.js ├── dropdown.js ├── externs │ ├── bootstrap.js │ └── jQuery.js ├── modal.js ├── popover.js ├── scrollspy.js ├── tab.js ├── tests │ ├── README.md │ ├── closure.html │ ├── index.html │ ├── unit │ │ ├── .jshintrc │ │ ├── alert.js │ │ ├── button.js │ │ ├── carousel.js │ │ ├── collapse.js │ │ ├── dropdown.js │ │ ├── modal.js │ │ ├── phantom.js │ │ ├── popover.js │ │ ├── scrollspy.js │ │ ├── tab.js │ │ └── tooltip.js │ ├── vendor │ │ ├── jquery.min.js │ │ ├── qunit.css │ │ └── qunit.js │ └── visual │ │ ├── alert.html │ │ ├── button.html │ │ ├── carousel.html │ │ ├── collapse.html │ │ ├── dropdown.html │ │ ├── modal.html │ │ ├── popover.html │ │ ├── scrollspy.html │ │ ├── tab.html │ │ └── tooltip.html ├── tooltip.js ├── transition.js └── util.js ├── license └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore docs files 2 | _gh_pages 3 | _site 4 | 5 | # Ignore ruby files 6 | .ruby-version 7 | .bundle 8 | vendor/cache 9 | 10 | # Numerous always-ignore extensions 11 | *.diff 12 | *.err 13 | *.log 14 | *.orig 15 | *.rej 16 | *.swo 17 | *.swp 18 | *.vi 19 | *.zip 20 | *~ 21 | 22 | # OS or Editor folders 23 | ._* 24 | .cache 25 | .DS_Store 26 | .idea 27 | .project 28 | .settings 29 | .tmproj 30 | *.esproj 31 | *.sublime-project 32 | *.sublime-workspace 33 | nbproject 34 | Thumbs.db 35 | 36 | # Komodo 37 | .komodotools 38 | *.komodoproject 39 | 40 | # grunt-html-validation 41 | validation-report.json 42 | validation-status.json 43 | 44 | # SCSS-Lint 45 | scss-lint-report.xml 46 | 47 | # grunt-contrib-sass cache 48 | .sass-cache 49 | 50 | # Folders to ignore 51 | bower_components 52 | node_modules 53 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap's Gruntfile 3 | * http://getbootstrap.com 4 | * Copyright 2013-2014 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | */ 7 | 8 | module.exports = function (grunt) { 9 | 'use strict' 10 | 11 | // Force use of Unix newlines 12 | grunt.util.linefeed = '\n' 13 | 14 | // Project configuration. 15 | grunt.initConfig({ 16 | 17 | // Metadata. 18 | pkg: grunt.file.readJSON('package.json'), 19 | banner: '/*!\n' + 20 | ' * Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' + 21 | ' * Copyright 2011-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' + 22 | ' * Licensed under <%= pkg.license.type %> (<%= pkg.license.url %>)\n' + 23 | ' */\n', 24 | jqueryCheck: 'if (typeof jQuery === \'undefined\') {\n' + 25 | ' throw new Error(\'Bootstrap\\\'s JavaScript requires jQuery\')\n' + 26 | '}\n', 27 | jqueryVersionCheck: '+function ($) {\n' + 28 | ' var version = $.fn.jquery.split(\' \')[0].split(\'.\')\n' + 29 | ' if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {\n' + 30 | ' throw new Error(\'Bootstrap\\\'s JavaScript requires jQuery version 1.9.1 or higher\')\n' + 31 | ' }\n' + 32 | '}(jQuery);\n\n', 33 | 34 | // Task configuration. 35 | clean: { 36 | dist: 'dist' 37 | }, 38 | 39 | jshint: { 40 | options: { 41 | jshintrc: 'js/.jshintrc' 42 | }, 43 | core: { 44 | src: 'js/*.js' 45 | }, 46 | test: { 47 | options: { 48 | jshintrc: 'js/tests/unit/.jshintrc' 49 | }, 50 | src: 'js/tests/unit/*.js' 51 | } 52 | }, 53 | 54 | jscs: { 55 | options: { 56 | config: 'js/.jscsrc' 57 | }, 58 | core: { 59 | src: '<%= jshint.core.src %>' 60 | }, 61 | test: { 62 | src: '<%= jshint.test.src %>' 63 | }, 64 | assets: { 65 | options: { 66 | requireCamelCaseOrUpperCaseIdentifiers: null 67 | }, 68 | src: '<%= jshint.assets.src %>' 69 | } 70 | }, 71 | 72 | concat: { 73 | options: { 74 | banner: '<%= banner %>\n<%= jqueryCheck %>\n<%= jqueryVersionCheck %>', 75 | stripBanners: false 76 | }, 77 | bootstrap: { 78 | src: [ 79 | 'js/util.js', 80 | 'js/alert.js', 81 | 'js/button.js', 82 | 'js/carousel.js', 83 | 'js/collapse.js', 84 | 'js/dropdown.js', 85 | 'js/modal.js', 86 | 'js/scrollspy.js', 87 | 'js/tooltip.js', 88 | 'js/popover.js', 89 | 'js/tab.js' 90 | ], 91 | dest: 'dist/<%= pkg.name %>.js' 92 | } 93 | }, 94 | 95 | closureCompiler: { 96 | 97 | options: { 98 | compilerFile: require('superstartup-closure-compiler').getPath(), 99 | checkModified: false, 100 | 101 | compilerOpts: { 102 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers 103 | // jscomp_warning: 'reportUnknownTypes', someday - maybe we will get to 100% typed, this helps track those down 104 | compilation_level: 'ADVANCED_OPTIMIZATIONS', 105 | warning_level: 'verbose', 106 | summary_detail_level: 3, 107 | output_wrapper: 108 | '"<%= banner %><%= jqueryCheck %><%= jqueryVersionCheck %>' 109 | + '(function($){%output%})(jQuery);"', 110 | externs: 'js/externs/*.js' 111 | // jscs:enable requireCamelCaseOrUpperCaseIdentifiers 112 | }, 113 | 114 | execOpts: { 115 | maxBuffer: 999999 * 1024 116 | }, 117 | 118 | // [OPTIONAL] Java VM optimization options 119 | // see https://code.google.com/p/closure-compiler/wiki/FAQ#What_are_the_recommended_Java_VM_command-line_options? 120 | // Setting one of these to 'true' is strongly recommended, 121 | // and can reduce compile times by 50-80% depending on compilation size 122 | // and hardware. 123 | // On server-class hardware, such as with Github's Travis hook, 124 | // TieredCompilation should be used; on standard developer hardware, 125 | // d32 may be better. Set as appropriate for your environment. 126 | // Default for both is 'false'; do not set both to 'true'. 127 | d32: false, // will use 'java -client -d32 -jar compiler.jar' 128 | TieredCompilation: false // will use 'java -server -XX:+TieredCompilation -jar compiler.jar' 129 | }, 130 | 131 | targetName: { 132 | src: [ 133 | 'js/util.js', 134 | 'js/alert.js', 135 | 'js/button.js', 136 | 'js/carousel.js', 137 | 'js/collapse.js', 138 | 'js/dropdown.js', 139 | 'js/modal.js', 140 | 'js/scrollspy.js', 141 | 'js/tooltip.js', 142 | 'js/popover.js', 143 | 'js/tab.js' 144 | ], 145 | dest: 'dist/<%= pkg.name %>.min.js' 146 | } 147 | 148 | }, 149 | 150 | qunit: { 151 | options: { 152 | inject: 'js/tests/unit/phantom.js' 153 | }, 154 | files: 'js/tests/index.html' 155 | } 156 | 157 | }) 158 | 159 | grunt.loadNpmTasks('grunt-contrib-clean') 160 | grunt.loadNpmTasks('grunt-contrib-jshint') 161 | grunt.loadNpmTasks('grunt-contrib-qunit') 162 | grunt.loadNpmTasks('grunt-contrib-concat') 163 | grunt.loadNpmTasks('grunt-closure-tools') 164 | grunt.loadNpmTasks('grunt-jscs') 165 | 166 | grunt.registerTask('test-js', ['jshint:core', 'jshint:test', 'jscs:core', 'jscs:test', 'qunit']) 167 | grunt.registerTask('dist-js', ['concat', 'closureCompiler']) 168 | 169 | // Full distribution task. 170 | grunt.registerTask('dist', ['clean:dist', 'dist-js']) 171 | 172 | // Default task. 173 | grunt.registerTask('default', ['clean:dist', 'test-js', 'dist']) 174 | 175 | } -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Bootstrap JS + Closure Compiler 2 | ------------------------------- 3 | 4 | This was a prototype largely written during Winter Vacation 2014. 5 | 6 | Goals: 7 | 8 | + Statically Type Bootstrap's JavaScript Source 9 | + Introduce Global Util Method 10 | + Refactor JS to be more "closure-y" 11 | 12 | --- 13 | 14 | Gulp Tasks 15 | 16 | + `$ Gulp` - runs jshint, jscs, unit tests, and copmiles source 17 | + `$ Gulp Dist` just runs closure dist 18 | + `$ Gulp Test` just runs tests 19 | 20 | All visual tests running + unit tests running against compiled and uncompiled code. 21 | 22 | --- 23 | 24 | Note: this was just an experiement, this will not be actively maintained. -------------------------------------------------------------------------------- /js/.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowEmptyBlocks": true, 3 | "disallowKeywords": ["with"], 4 | "disallowMixedSpacesAndTabs": true, 5 | "disallowMultipleLineStrings": true, 6 | "disallowMultipleVarDecl": true, 7 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], 8 | "disallowSpaceBeforeBinaryOperators": [","], 9 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], 10 | "disallowSpacesInFunctionDeclaration": { "beforeOpeningRoundBrace": true }, 11 | "disallowSpacesInNamedFunctionExpression": { "beforeOpeningRoundBrace": true }, 12 | "disallowSpacesInsideArrayBrackets": true, 13 | "disallowSpacesInsideParentheses": true, 14 | "disallowTrailingComma": true, 15 | "disallowTrailingWhitespace": true, 16 | "requireCapitalizedConstructors": true, 17 | "requireCommaBeforeLineBreak": true, 18 | "requireLineFeedAtFileEnd": true, 19 | "requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="], 20 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], 21 | "requireSpaceAfterLineComment": true, 22 | "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="], 23 | "requireSpaceBetweenArguments": true, 24 | "requireSpacesInAnonymousFunctionExpression": { "beforeOpeningCurlyBrace": true, "beforeOpeningRoundBrace": true }, 25 | "requireSpacesInForStatement": true, 26 | "requireSpacesInFunctionDeclaration": { "beforeOpeningCurlyBrace": true }, 27 | "requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true }, 28 | "requireSpacesInNamedFunctionExpression": { "beforeOpeningCurlyBrace": true }, 29 | "validateIndentation": 2, 30 | "validateLineBreaks": "LF", 31 | "validateQuoteMarks": "'" 32 | } 33 | -------------------------------------------------------------------------------- /js/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node" : true, 3 | "asi" : true, 4 | "browser" : true, 5 | "eqeqeq" : false, 6 | "eqnull" : true, 7 | "es3" : true, 8 | "expr" : true, 9 | "evil" : true, 10 | "jquery" : true, 11 | "latedef" : true, 12 | "laxbreak" : true, 13 | "nonbsp" : true, 14 | "strict" : true, 15 | "undef" : true, 16 | "sub" : true, 17 | "unused" : true, 18 | "globals": { 19 | "Bootstrap": true, 20 | "Tooltip": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /js/alert.js: -------------------------------------------------------------------------------- 1 | /** ======================================================================= 2 | * Bootstrap: alert.js v4.0.0 3 | * http://getbootstrap.com/javascript/#alerts 4 | * ======================================================================== 5 | * Copyright 2011-2015 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== 8 | * @fileoverview - Bootstrap's generic alert component. Add dismiss 9 | * functionality to all alert messages with this plugin. 10 | * 11 | * Public Methods & Properties: 12 | * 13 | * + $.alert 14 | * + $.alert.noConflict 15 | * + $.alert.Constructor 16 | * + $.alert.Constructor.VERSION 17 | * + $.alert.Constructor.prototype.close 18 | * 19 | * ======================================================================== 20 | */ 21 | 22 | 'use strict'; 23 | 24 | 25 | /** 26 | * Our Alert class. 27 | * @param {Element=} opt_element 28 | * @constructor 29 | */ 30 | var Alert = function (opt_element) { 31 | if (opt_element) { 32 | $(opt_element).on('click', Alert._DISMISS_SELECTOR, Alert._handleDismiss(this)) 33 | } 34 | } 35 | 36 | 37 | /** 38 | * @const 39 | * @type {string} 40 | */ 41 | Alert['VERSION'] = '4.0.0' 42 | 43 | 44 | /** 45 | * @const 46 | * @type {string} 47 | * @private 48 | */ 49 | Alert._NAME = 'alert' 50 | 51 | 52 | /** 53 | * @const 54 | * @type {string} 55 | * @private 56 | */ 57 | Alert._DATA_KEY = 'bs.alert' 58 | 59 | 60 | /** 61 | * @const 62 | * @type {string} 63 | * @private 64 | */ 65 | Alert._DISMISS_SELECTOR = '[data-dismiss="alert"]' 66 | 67 | 68 | /** 69 | * @const 70 | * @type {number} 71 | * @private 72 | */ 73 | Alert._TRANSITION_DURATION = 150 74 | 75 | 76 | /** 77 | * @const 78 | * @type {Function} 79 | * @private 80 | */ 81 | Alert._JQUERY_NO_CONFLICT = $.fn[Alert._NAME] 82 | 83 | 84 | /** 85 | * @const 86 | * @enum {string} 87 | * @private 88 | */ 89 | Alert._Event = { 90 | CLOSE : 'close.bs.alert', 91 | CLOSED : 'closed.bs.alert' 92 | } 93 | 94 | 95 | /** 96 | * @const 97 | * @enum {string} 98 | * @private 99 | */ 100 | Alert._ClassName = { 101 | ALERT : 'alert', 102 | FADE : 'fade', 103 | IN : 'in' 104 | } 105 | 106 | 107 | /** 108 | * Provides the jQuery Interface for the alert component. 109 | * @param {string=} opt_config 110 | * @this {jQuery} 111 | * @return {jQuery} 112 | * @private 113 | */ 114 | Alert._jQueryInterface = function (opt_config) { 115 | return this.each(function () { 116 | var $this = $(this) 117 | var data = $this.data(Alert._DATA_KEY) 118 | 119 | if (!data) { 120 | data = new Alert(this) 121 | $this.data(Alert._DATA_KEY, data) 122 | } 123 | 124 | if (opt_config === 'close') { 125 | data[opt_config](this) 126 | } 127 | }) 128 | } 129 | 130 | 131 | /** 132 | * Close the alert component 133 | * @param {Alert} alertInstance 134 | * @return {Function} 135 | * @private 136 | */ 137 | Alert._handleDismiss = function (alertInstance) { 138 | return function (event) { 139 | if (event) { 140 | event.preventDefault() 141 | } 142 | 143 | alertInstance['close'](this) 144 | } 145 | } 146 | 147 | 148 | /** 149 | * Close the alert component 150 | * @param {Element} element 151 | */ 152 | Alert.prototype['close'] = function (element) { 153 | var rootElement = this._getRootElement(element) 154 | var customEvent = this._triggerCloseEvent(rootElement) 155 | 156 | if (customEvent.isDefaultPrevented()) return 157 | 158 | this._removeElement(rootElement) 159 | } 160 | 161 | 162 | /** 163 | * Tries to get the alert's root element 164 | * @return {Element} 165 | * @private 166 | */ 167 | Alert.prototype._getRootElement = function (element) { 168 | var parent = false 169 | var selector = Bootstrap.getSelectorFromElement(element) 170 | 171 | if (selector) { 172 | parent = $(selector)[0] 173 | } 174 | 175 | if (!parent) { 176 | parent = $(element).closest('.' + Alert._ClassName.ALERT)[0] 177 | } 178 | 179 | return parent 180 | } 181 | 182 | 183 | /** 184 | * Trigger close event on element 185 | * @return {$.Event} 186 | * @private 187 | */ 188 | Alert.prototype._triggerCloseEvent = function (element) { 189 | var closeEvent = $.Event(Alert._Event.CLOSE) 190 | $(element).trigger(closeEvent) 191 | return closeEvent 192 | } 193 | 194 | 195 | /** 196 | * Trigger closed event and remove element from dom 197 | * @private 198 | */ 199 | Alert.prototype._removeElement = function (element) { 200 | $(element).removeClass(Alert._ClassName.IN) 201 | 202 | if (!Bootstrap.transition || !$(element).hasClass(Alert._ClassName.FADE)) { 203 | this._destroyElement(element) 204 | return 205 | } 206 | 207 | $(element) 208 | .one(Bootstrap.TRANSITION_END, this._destroyElement.bind(this, element)) 209 | .emulateTransitionEnd(Alert._TRANSITION_DURATION) 210 | } 211 | 212 | 213 | /** 214 | * clean up any lingering jquery data and kill element 215 | * @private 216 | */ 217 | Alert.prototype._destroyElement = function (element) { 218 | $(element) 219 | .detach() 220 | .trigger(Alert._Event.CLOSED) 221 | .remove() 222 | } 223 | 224 | 225 | /** 226 | * ------------------------------------------------------------------------ 227 | * jQuery Interface + noConflict implementaiton 228 | * ------------------------------------------------------------------------ 229 | */ 230 | 231 | /** 232 | * @const 233 | * @type {Function} 234 | */ 235 | $.fn[Alert._NAME] = Alert._jQueryInterface 236 | 237 | 238 | /** 239 | * @const 240 | * @type {Function} 241 | */ 242 | $.fn[Alert._NAME]['Constructor'] = Alert 243 | 244 | 245 | /** 246 | * @return {Function} 247 | */ 248 | $.fn[Alert._NAME]['noConflict'] = function () { 249 | $.fn[Alert._NAME] = Alert._JQUERY_NO_CONFLICT 250 | return Alert._jQueryInterface 251 | } 252 | 253 | 254 | /** 255 | * ------------------------------------------------------------------------ 256 | * Data Api implementation 257 | * ------------------------------------------------------------------------ 258 | */ 259 | 260 | $(document).on('click.bs.alert.data-api', Alert._DISMISS_SELECTOR, Alert._handleDismiss(new Alert())) 261 | -------------------------------------------------------------------------------- /js/button.js: -------------------------------------------------------------------------------- 1 | /** ======================================================================= 2 | * Bootstrap: button.js v4.0.0 3 | * http://getbootstrap.com/javascript/#buttons 4 | * ======================================================================== 5 | * Copyright 2011-2015 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== 8 | * @fileoverview - Bootstrap's generic button component. 9 | * 10 | * Note (@fat): Deprecated "setState" – imo, better solutions for managing a 11 | * buttons state should exist outside this plugin. 12 | * 13 | * Public Methods & Properties: 14 | * 15 | * + $.button 16 | * + $.button.noConflict 17 | * + $.button.Constructor 18 | * + $.button.Constructor.VERSION 19 | * + $.button.Constructor.prototype.toggle 20 | * 21 | * ======================================================================== 22 | */ 23 | 24 | 'use strict'; 25 | 26 | 27 | /** 28 | * Our Button class. 29 | * @param {Element!} element 30 | * @constructor 31 | */ 32 | var Button = function (element) { 33 | 34 | /** @private {Element} */ 35 | this._element = element 36 | 37 | } 38 | 39 | 40 | /** 41 | * @const 42 | * @type {string} 43 | */ 44 | Button['VERSION'] = '4.0.0' 45 | 46 | 47 | /** 48 | * @const 49 | * @type {string} 50 | * @private 51 | */ 52 | Button._NAME = 'button' 53 | 54 | 55 | /** 56 | * @const 57 | * @type {string} 58 | * @private 59 | */ 60 | Button._DATA_KEY = 'bs.button' 61 | 62 | 63 | /** 64 | * @const 65 | * @type {Function} 66 | * @private 67 | */ 68 | Button._JQUERY_NO_CONFLICT = $.fn[Button._NAME] 69 | 70 | 71 | /** 72 | * @const 73 | * @enum {string} 74 | * @private 75 | */ 76 | Button._ClassName = { 77 | ACTIVE : 'active', 78 | BUTTON : 'btn', 79 | FOCUS : 'focus' 80 | } 81 | 82 | 83 | /** 84 | * @const 85 | * @enum {string} 86 | * @private 87 | */ 88 | Button._Selector = { 89 | DATA_TOGGLE_CARROT : '[data-toggle^="button"]', 90 | DATA_TOGGLE : '[data-toggle="buttons"]', 91 | INPUT : 'input', 92 | ACTIVE : '.active', 93 | BUTTON : '.btn' 94 | } 95 | 96 | 97 | /** 98 | * Provides the jQuery Interface for the Button component. 99 | * @param {string=} opt_config 100 | * @this {jQuery} 101 | * @return {jQuery} 102 | * @private 103 | */ 104 | Button._jQueryInterface = function (opt_config) { 105 | return this.each(function () { 106 | var data = $(this).data(Button._DATA_KEY) 107 | 108 | if (!data) { 109 | data = new Button(this) 110 | $(this).data(Button._DATA_KEY, data) 111 | } 112 | 113 | if (opt_config === 'toggle') { 114 | data[opt_config]() 115 | } 116 | }) 117 | } 118 | 119 | 120 | /** 121 | * Toggle's the button active state 122 | */ 123 | Button.prototype['toggle'] = function () { 124 | var triggerChangeEvent = true 125 | var rootElement = $(this._element).closest(Button._Selector.DATA_TOGGLE)[0] 126 | 127 | if (rootElement) { 128 | var input = $(this._element).find(Button._Selector.INPUT)[0] 129 | if (input) { 130 | if (input.type == 'radio') { 131 | if (input.checked && $(this._element).hasClass(Button._ClassName.ACTIVE)) { 132 | triggerChangeEvent = false 133 | } else { 134 | var activeElement = $(rootElement).find(Button._Selector.ACTIVE)[0] 135 | if (activeElement) { 136 | $(activeElement).removeClass(Button._ClassName.ACTIVE) 137 | } 138 | } 139 | } 140 | 141 | if (triggerChangeEvent) { 142 | input.checked = !$(this._element).hasClass(Button._ClassName.ACTIVE) 143 | $(this._element).trigger('change') 144 | } 145 | } 146 | } else { 147 | this._element.setAttribute('aria-pressed', !$(this._element).hasClass(Button._ClassName.ACTIVE)) 148 | } 149 | 150 | if (triggerChangeEvent) { 151 | $(this._element).toggleClass(Button._ClassName.ACTIVE) 152 | } 153 | } 154 | 155 | 156 | /** 157 | * ------------------------------------------------------------------------ 158 | * jQuery Interface + noConflict implementaiton 159 | * ------------------------------------------------------------------------ 160 | */ 161 | 162 | /** 163 | * @const 164 | * @type {Function} 165 | */ 166 | $.fn[Button._NAME] = Button._jQueryInterface 167 | 168 | 169 | /** 170 | * @const 171 | * @type {Function} 172 | */ 173 | $.fn[Button._NAME]['Constructor'] = Button 174 | 175 | 176 | /** 177 | * @const 178 | * @type {Function} 179 | */ 180 | $.fn[Button._NAME]['noConflict'] = function () { 181 | $.fn[Button._NAME] = Button._JQUERY_NO_CONFLICT 182 | return this 183 | } 184 | 185 | 186 | /** 187 | * ------------------------------------------------------------------------ 188 | * Data Api implementation 189 | * ------------------------------------------------------------------------ 190 | */ 191 | 192 | $(document) 193 | .on('click.bs.button.data-api', Button._Selector.DATA_TOGGLE_CARROT, function (event) { 194 | event.preventDefault() 195 | 196 | var button = event.target 197 | 198 | if (!$(button).hasClass(Button._ClassName.BUTTON)) { 199 | button = $(button).closest(Button._Selector.BUTTON) 200 | } 201 | 202 | Button._jQueryInterface.call($(button), 'toggle') 203 | }) 204 | .on('focus.bs.button.data-api blur.bs.button.data-api', Button._Selector.DATA_TOGGLE_CARROT, function (event) { 205 | var button = $(event.target).closest(Button._Selector.BUTTON)[0] 206 | $(button).toggleClass(Button._ClassName.FOCUS, /^focus(in)?$/.test(event.type)) 207 | }) 208 | -------------------------------------------------------------------------------- /js/carousel.js: -------------------------------------------------------------------------------- 1 | /** ======================================================================= 2 | * Bootstrap: carousel.js v4.0.0 3 | * http://getbootstrap.com/javascript/#carousel 4 | * ======================================================================== 5 | * Copyright 2011-2015 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== 8 | * @fileoverview - Bootstrap's carousel. A slideshow component for cycling 9 | * through elements, like a carousel. Nested carousels are not supported. 10 | * 11 | * Public Methods & Properties: 12 | * 13 | * + $.carousel 14 | * + $.carousel.noConflict 15 | * + $.carousel.Constructor 16 | * + $.carousel.Constructor.VERSION 17 | * + $.carousel.Constructor.Defaults 18 | * + $.carousel.Constructor.Defaults.interval 19 | * + $.carousel.Constructor.Defaults.pause 20 | * + $.carousel.Constructor.Defaults.wrap 21 | * + $.carousel.Constructor.Defaults.keyboard 22 | * + $.carousel.Constructor.Defaults.slide 23 | * + $.carousel.Constructor.prototype.next 24 | * + $.carousel.Constructor.prototype.prev 25 | * + $.carousel.Constructor.prototype.pause 26 | * + $.carousel.Constructor.prototype.cycle 27 | * 28 | * ======================================================================== 29 | */ 30 | 31 | 'use strict'; 32 | 33 | 34 | /** 35 | * Our carousel class. 36 | * @param {Element!} element 37 | * @param {Object=} opt_config 38 | * @constructor 39 | */ 40 | var Carousel = function (element, opt_config) { 41 | 42 | /** @private {Element} */ 43 | this._element = $(element)[0] 44 | 45 | /** @private {Element} */ 46 | this._indicatorsElement = $(this._element).find(Carousel._Selector.INDICATORS)[0] 47 | 48 | /** @private {?Object} */ 49 | this._config = opt_config || null 50 | 51 | /** @private {boolean} */ 52 | this._isPaused = false 53 | 54 | /** @private {boolean} */ 55 | this._isSliding = false 56 | 57 | /** @private {?number} */ 58 | this._interval = null 59 | 60 | /** @private {?Element} */ 61 | this._activeElement = null 62 | 63 | /** @private {?Array} */ 64 | this._items = null 65 | 66 | this._addEventListeners() 67 | 68 | } 69 | 70 | 71 | /** 72 | * @const 73 | * @type {string} 74 | */ 75 | Carousel['VERSION'] = '4.0.0' 76 | 77 | 78 | /** 79 | * @const 80 | * @type {Object} 81 | */ 82 | Carousel['Defaults'] = { 83 | 'interval' : 5000, 84 | 'pause' : 'hover', 85 | 'wrap' : true, 86 | 'keyboard' : true, 87 | 'slide' : false 88 | } 89 | 90 | 91 | /** 92 | * @const 93 | * @type {string} 94 | * @private 95 | */ 96 | Carousel._NAME = 'carousel' 97 | 98 | 99 | /** 100 | * @const 101 | * @type {string} 102 | * @private 103 | */ 104 | Carousel._DATA_KEY = 'bs.carousel' 105 | 106 | 107 | /** 108 | * @const 109 | * @type {number} 110 | * @private 111 | */ 112 | Carousel._TRANSITION_DURATION = 600 113 | 114 | 115 | /** 116 | * @const 117 | * @enum {string} 118 | * @private 119 | */ 120 | Carousel._Direction = { 121 | NEXT : 'next', 122 | PREVIOUS : 'prev' 123 | } 124 | 125 | 126 | /** 127 | * @const 128 | * @enum {string} 129 | * @private 130 | */ 131 | Carousel._Event = { 132 | SLIDE : 'slide.bs.carousel', 133 | SLID : 'slid.bs.carousel' 134 | } 135 | 136 | 137 | /** 138 | * @const 139 | * @enum {string} 140 | * @private 141 | */ 142 | Carousel._ClassName = { 143 | CAROUSEL : 'carousel', 144 | ACTIVE : 'active', 145 | SLIDE : 'slide', 146 | RIGHT : 'right', 147 | LEFT : 'left', 148 | ITEM : 'carousel-item' 149 | } 150 | 151 | 152 | /** 153 | * @const 154 | * @enum {string} 155 | * @private 156 | */ 157 | Carousel._Selector = { 158 | ACTIVE : '.active', 159 | ACTIVE_ITEM : '.active.carousel-item', 160 | ITEM : '.carousel-item', 161 | NEXT_PREV : '.next, .prev', 162 | INDICATORS : '.carousel-indicators' 163 | } 164 | 165 | 166 | /** 167 | * @const 168 | * @type {Function} 169 | * @private 170 | */ 171 | Carousel._JQUERY_NO_CONFLICT = $.fn[Carousel._NAME] 172 | 173 | 174 | /** 175 | * @param {Object=} opt_config 176 | * @this {jQuery} 177 | * @return {jQuery} 178 | * @private 179 | */ 180 | Carousel._jQueryInterface = function (opt_config) { 181 | return this.each(function () { 182 | var data = $(this).data(Carousel._DATA_KEY) 183 | var config = $.extend({}, Carousel['Defaults'], $(this).data(), typeof opt_config == 'object' && opt_config) 184 | var action = typeof opt_config == 'string' ? opt_config : config.slide 185 | 186 | if (!data) { 187 | data = new Carousel(this, config) 188 | $(this).data(Carousel._DATA_KEY, data) 189 | } 190 | 191 | if (typeof opt_config == 'number') { 192 | data.to(opt_config) 193 | 194 | } else if (action) { 195 | data[action]() 196 | 197 | } else if (config.interval) { 198 | data['pause']() 199 | data['cycle']() 200 | } 201 | }) 202 | } 203 | 204 | 205 | /** 206 | * Click handler for data api 207 | * @param {Event} event 208 | * @this {Element} 209 | * @private 210 | */ 211 | Carousel._dataApiClickHandler = function (event) { 212 | var selector = Bootstrap.getSelectorFromElement(this) 213 | 214 | if (!selector) { 215 | return 216 | } 217 | 218 | var target = $(selector)[0] 219 | 220 | if (!target || !$(target).hasClass(Carousel._ClassName.CAROUSEL)) { 221 | return 222 | } 223 | 224 | var config = $.extend({}, $(target).data(), $(this).data()) 225 | 226 | var slideIndex = this.getAttribute('data-slide-to') 227 | if (slideIndex) { 228 | config.interval = false 229 | } 230 | 231 | Carousel._jQueryInterface.call($(target), config) 232 | 233 | if (slideIndex) { 234 | $(target).data(Carousel._DATA_KEY).to(slideIndex) 235 | } 236 | 237 | event.preventDefault() 238 | } 239 | 240 | 241 | /** 242 | * Advance the carousel to the next slide 243 | */ 244 | Carousel.prototype['next'] = function () { 245 | if (!this._isSliding) { 246 | this._slide(Carousel._Direction.NEXT) 247 | } 248 | } 249 | 250 | 251 | /** 252 | * Return the carousel to the previous slide 253 | */ 254 | Carousel.prototype['prev'] = function () { 255 | if (!this._isSliding) { 256 | this._slide(Carousel._Direction.PREVIOUS) 257 | } 258 | } 259 | 260 | 261 | /** 262 | * Pause the carousel cycle 263 | * @param {Event=} opt_event 264 | */ 265 | Carousel.prototype['pause'] = function (opt_event) { 266 | if (!opt_event) { 267 | this._isPaused = true 268 | } 269 | 270 | if ($(this._element).find(Carousel._Selector.NEXT_PREV)[0] && Bootstrap.transition) { 271 | $(this._element).trigger(Bootstrap.transition.end) 272 | this['cycle'](true) 273 | } 274 | 275 | clearInterval(this._interval) 276 | this._interval = null 277 | } 278 | 279 | 280 | /** 281 | * Cycle to the next carousel item 282 | * @param {Event|boolean=} opt_event 283 | */ 284 | Carousel.prototype['cycle'] = function (opt_event) { 285 | if (!opt_event) { 286 | this._isPaused = false 287 | } 288 | 289 | if (this._interval) { 290 | clearInterval(this._interval) 291 | this._interval = null 292 | } 293 | 294 | if (this._config['interval'] && !this._isPaused) { 295 | this._interval = setInterval(this['next'].bind(this), this._config['interval']) 296 | } 297 | } 298 | 299 | 300 | /** 301 | * @return {Object} 302 | */ 303 | Carousel.prototype['getConfig'] = function () { 304 | return this._config 305 | } 306 | 307 | 308 | /** 309 | * Move active carousel item to specified index 310 | * @param {number} index 311 | */ 312 | Carousel.prototype.to = function (index) { 313 | this._activeElement = $(this._element).find(Carousel._Selector.ACTIVE_ITEM)[0] 314 | 315 | var activeIndex = this._getItemIndex(this._activeElement) 316 | 317 | if (index > (this._items.length - 1) || index < 0) { 318 | return 319 | } 320 | 321 | if (this._isSliding) { 322 | $(this._element).one(Carousel._Event.SLID, function () { this.to(index) }.bind(this)) 323 | return 324 | } 325 | 326 | if (activeIndex == index) { 327 | this['pause']() 328 | this['cycle']() 329 | return 330 | } 331 | 332 | var direction = index > activeIndex ? 333 | Carousel._Direction.NEXT : 334 | Carousel._Direction.PREVIOUS 335 | 336 | this._slide(direction, this._items[index]) 337 | } 338 | 339 | 340 | /** 341 | * Add event listeners to root element 342 | * @private 343 | */ 344 | Carousel.prototype._addEventListeners = function () { 345 | if (this._config['keyboard']) { 346 | $(this._element).on('keydown.bs.carousel', this._keydown.bind(this)) 347 | } 348 | 349 | if (this._config['pause'] == 'hover' && !('ontouchstart' in document.documentElement)) { 350 | $(this._element) 351 | .on('mouseenter.bs.carousel', this['pause'].bind(this)) 352 | .on('mouseleave.bs.carousel', this['cycle'].bind(this)) 353 | } 354 | } 355 | 356 | 357 | /** 358 | * Keydown handler 359 | * @param {Event} event 360 | * @private 361 | */ 362 | Carousel.prototype._keydown = function (event) { 363 | event.preventDefault() 364 | 365 | if (/input|textarea/i.test(event.target.tagName)) return 366 | 367 | switch (event.which) { 368 | case 37: this['prev'](); break 369 | case 39: this['next'](); break 370 | default: return 371 | } 372 | } 373 | 374 | 375 | /** 376 | * Get item index 377 | * @param {Element} element 378 | * @return {number} 379 | * @private 380 | */ 381 | Carousel.prototype._getItemIndex = function (element) { 382 | this._items = $.makeArray($(element).parent().find(Carousel._Selector.ITEM)) 383 | 384 | return this._items.indexOf(element) 385 | } 386 | 387 | 388 | /** 389 | * Get next displayed item based on direction 390 | * @param {Carousel._Direction} direction 391 | * @param {Element} activeElement 392 | * @return {Element} 393 | * @private 394 | */ 395 | Carousel.prototype._getItemByDirection = function (direction, activeElement) { 396 | var activeIndex = this._getItemIndex(activeElement) 397 | var isGoingToWrap = (direction === Carousel._Direction.PREVIOUS && activeIndex === 0) || 398 | (direction === Carousel._Direction.NEXT && activeIndex == (this._items.length - 1)) 399 | 400 | if (isGoingToWrap && !this._config['wrap']) { 401 | return activeElement 402 | } 403 | 404 | var delta = direction == Carousel._Direction.PREVIOUS ? -1 : 1 405 | var itemIndex = (activeIndex + delta) % this._items.length 406 | 407 | return itemIndex === -1 ? this._items[this._items.length - 1] : this._items[itemIndex] 408 | } 409 | 410 | 411 | /** 412 | * Trigger slide event on element 413 | * @param {Element} relatedTarget 414 | * @param {Carousel._ClassName} directionalClassname 415 | * @return {$.Event} 416 | * @private 417 | */ 418 | Carousel.prototype._triggerSlideEvent = function (relatedTarget, directionalClassname) { 419 | var slideEvent = $.Event(Carousel._Event.SLIDE, { 420 | relatedTarget: relatedTarget, 421 | direction: directionalClassname 422 | }) 423 | 424 | $(this._element).trigger(slideEvent) 425 | 426 | return slideEvent 427 | } 428 | 429 | 430 | /** 431 | * Set the active indicator if available 432 | * @param {Element} element 433 | * @private 434 | */ 435 | Carousel.prototype._setActiveIndicatorElement = function (element) { 436 | if (this._indicatorsElement) { 437 | $(this._indicatorsElement) 438 | .find(Carousel._Selector.ACTIVE) 439 | .removeClass(Carousel._ClassName.ACTIVE) 440 | 441 | var nextIndicator = this._indicatorsElement.children[this._getItemIndex(element)] 442 | if (nextIndicator) { 443 | $(nextIndicator).addClass(Carousel._ClassName.ACTIVE) 444 | } 445 | } 446 | } 447 | 448 | 449 | /** 450 | * Slide the carousel element in a direction 451 | * @param {Carousel._Direction} direction 452 | * @param {Element=} opt_nextElement 453 | */ 454 | Carousel.prototype._slide = function (direction, opt_nextElement) { 455 | var activeElement = $(this._element).find(Carousel._Selector.ACTIVE_ITEM)[0] 456 | var nextElement = opt_nextElement || activeElement && this._getItemByDirection(direction, activeElement) 457 | 458 | var isCycling = !!this._interval 459 | 460 | var directionalClassName = direction == Carousel._Direction.NEXT ? 461 | Carousel._ClassName.LEFT : 462 | Carousel._ClassName.RIGHT 463 | 464 | if (nextElement && $(nextElement).hasClass(Carousel._ClassName.ACTIVE)) { 465 | this._isSliding = false 466 | return 467 | } 468 | 469 | var slideEvent = this._triggerSlideEvent(nextElement, directionalClassName) 470 | if (slideEvent.isDefaultPrevented()) { 471 | return 472 | } 473 | 474 | if (!activeElement || !nextElement) { 475 | // some weirdness is happening, so we bail (maybe throw exception here alerting user that they're dom is off 476 | return 477 | } 478 | 479 | this._isSliding = true 480 | 481 | if (isCycling) { 482 | this['pause']() 483 | } 484 | 485 | this._setActiveIndicatorElement(nextElement) 486 | 487 | var slidEvent = $.Event(Carousel._Event.SLID, { relatedTarget: nextElement, direction: directionalClassName }) 488 | 489 | if (Bootstrap.transition && $(this._element).hasClass(Carousel._ClassName.SLIDE)) { 490 | $(nextElement).addClass(direction) 491 | 492 | Bootstrap.reflow(nextElement) 493 | 494 | $(activeElement).addClass(directionalClassName) 495 | $(nextElement).addClass(directionalClassName) 496 | 497 | $(activeElement) 498 | .one(Bootstrap.TRANSITION_END, function () { 499 | $(nextElement) 500 | .removeClass(directionalClassName) 501 | .removeClass(direction) 502 | 503 | $(nextElement).addClass(Carousel._ClassName.ACTIVE) 504 | 505 | $(activeElement) 506 | .removeClass(Carousel._ClassName.ACTIVE) 507 | .removeClass(direction) 508 | .removeClass(directionalClassName) 509 | 510 | this._isSliding = false 511 | 512 | setTimeout(function () { 513 | $(this._element).trigger(slidEvent) 514 | }.bind(this), 0) 515 | }.bind(this)) 516 | .emulateTransitionEnd(Carousel._TRANSITION_DURATION) 517 | 518 | } else { 519 | $(activeElement).removeClass(Carousel._ClassName.ACTIVE) 520 | $(nextElement).addClass(Carousel._ClassName.ACTIVE) 521 | 522 | this._isSliding = false 523 | $(this._element).trigger(slidEvent) 524 | } 525 | 526 | if (isCycling) { 527 | this['cycle']() 528 | } 529 | } 530 | 531 | 532 | /** 533 | * ------------------------------------------------------------------------ 534 | * jQuery Interface + noConflict implementaiton 535 | * ------------------------------------------------------------------------ 536 | */ 537 | 538 | /** 539 | * @const 540 | * @type {Function} 541 | */ 542 | $.fn[Carousel._NAME] = Carousel._jQueryInterface 543 | 544 | 545 | /** 546 | * @const 547 | * @type {Function} 548 | */ 549 | $.fn[Carousel._NAME]['Constructor'] = Carousel 550 | 551 | 552 | /** 553 | * @const 554 | * @type {Function} 555 | */ 556 | $.fn[Carousel._NAME]['noConflict'] = function () { 557 | $.fn[Carousel._NAME] = Carousel._JQUERY_NO_CONFLICT 558 | return this 559 | } 560 | 561 | 562 | /** 563 | * ------------------------------------------------------------------------ 564 | * Data Api implementation 565 | * ------------------------------------------------------------------------ 566 | */ 567 | 568 | $(document) 569 | .on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', Carousel._dataApiClickHandler) 570 | 571 | $(window).on('load', function () { 572 | $('[data-ride="carousel"]').each(function () { 573 | var $carousel = $(this) 574 | Carousel._jQueryInterface.call($carousel, /** @type {Object} */ ($carousel.data())) 575 | }) 576 | }) 577 | -------------------------------------------------------------------------------- /js/collapse.js: -------------------------------------------------------------------------------- 1 | /** ======================================================================= 2 | * Bootstrap: collapse.js v4.0.0 3 | * http://getbootstrap.com/javascript/#collapse 4 | * ======================================================================== 5 | * Copyright 2011-2015 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== 8 | * @fileoverview - Bootstrap's collapse plugin. Flexible support for 9 | * collapsible components like accordions and navigation. 10 | * 11 | * Public Methods & Properties: 12 | * 13 | * + $.carousel 14 | * + $.carousel.noConflict 15 | * + $.carousel.Constructor 16 | * + $.carousel.Constructor.VERSION 17 | * + $.carousel.Constructor.Defaults 18 | * + $.carousel.Constructor.Defaults.toggle 19 | * + $.carousel.Constructor.Defaults.trigger 20 | * + $.carousel.Constructor.Defaults.parent 21 | * + $.carousel.Constructor.prototype.toggle 22 | * + $.carousel.Constructor.prototype.show 23 | * + $.carousel.Constructor.prototype.hide 24 | * 25 | * ======================================================================== 26 | */ 27 | 28 | 'use strict'; 29 | 30 | 31 | /** 32 | * Our collapse class. 33 | * @param {Element!} element 34 | * @param {Object=} opt_config 35 | * @constructor 36 | */ 37 | var Collapse = function (element, opt_config) { 38 | 39 | /** @private {Element} */ 40 | this._element = element 41 | 42 | /** @private {Object} */ 43 | this._config = $.extend({}, Collapse['Defaults'], opt_config) 44 | 45 | /** @private {Element} */ 46 | this._trigger = typeof this._config['trigger'] == 'string' ? 47 | $(this._config['trigger'])[0] : this._config['trigger'] 48 | 49 | /** @private {boolean} */ 50 | this._isTransitioning = false 51 | 52 | /** @private {?Element} */ 53 | this._parent = this._config['parent'] ? this._getParent() : null 54 | 55 | if (!this._config['parent']) { 56 | this._addAriaAndCollapsedClass(this._element, this._trigger) 57 | } 58 | 59 | if (this._config['toggle']) { 60 | this['toggle']() 61 | } 62 | 63 | } 64 | 65 | 66 | /** 67 | * @const 68 | * @type {string} 69 | */ 70 | Collapse['VERSION'] = '4.0.0' 71 | 72 | 73 | /** 74 | * @const 75 | * @type {Object} 76 | */ 77 | Collapse['Defaults'] = { 78 | 'toggle' : true, 79 | 'trigger' : '[data-toggle="collapse"]', 80 | 'parent' : null 81 | } 82 | 83 | 84 | /** 85 | * @const 86 | * @type {string} 87 | * @private 88 | */ 89 | Collapse._NAME = 'collapse' 90 | 91 | 92 | /** 93 | * @const 94 | * @type {string} 95 | * @private 96 | */ 97 | Collapse._DATA_KEY = 'bs.collapse' 98 | 99 | 100 | /** 101 | * @const 102 | * @type {number} 103 | * @private 104 | */ 105 | Collapse._TRANSITION_DURATION = 600 106 | 107 | 108 | /** 109 | * @const 110 | * @type {Function} 111 | * @private 112 | */ 113 | Collapse._JQUERY_NO_CONFLICT = $.fn[Collapse._NAME] 114 | 115 | 116 | /** 117 | * @const 118 | * @enum {string} 119 | * @private 120 | */ 121 | Collapse._Event = { 122 | SHOW : 'show.bs.collapse', 123 | SHOWN : 'shown.bs.collapse', 124 | HIDE : 'hide.bs.collapse', 125 | HIDDEN : 'hidden.bs.collapse' 126 | } 127 | 128 | 129 | /** 130 | * @const 131 | * @enum {string} 132 | * @private 133 | */ 134 | Collapse._ClassName = { 135 | IN : 'in', 136 | COLLAPSE : 'collapse', 137 | COLLAPSING : 'collapsing', 138 | COLLAPSED : 'collapsed' 139 | } 140 | 141 | 142 | /** 143 | * @const 144 | * @enum {string} 145 | * @private 146 | */ 147 | Collapse._Dimension = { 148 | WIDTH : 'width', 149 | HEIGHT : 'height' 150 | } 151 | 152 | 153 | /** 154 | * @const 155 | * @enum {string} 156 | * @private 157 | */ 158 | Collapse._Selector = { 159 | ACTIVES : '.panel > .in, .panel > .collapsing' 160 | } 161 | 162 | 163 | /** 164 | * Provides the jQuery Interface for the alert component. 165 | * @param {Object|string=} opt_config 166 | * @this {jQuery} 167 | * @return {jQuery} 168 | * @private 169 | */ 170 | Collapse._jQueryInterface = function (opt_config) { 171 | return this.each(function () { 172 | var $this = $(this) 173 | var data = $this.data(Collapse._DATA_KEY) 174 | var config = $.extend({}, Collapse['Defaults'], $this.data(), typeof opt_config == 'object' && opt_config) 175 | 176 | if (!data && config['toggle'] && opt_config == 'show') { 177 | config['toggle'] = false 178 | } 179 | 180 | if (!data) { 181 | data = new Collapse(this, config) 182 | $this.data(Collapse._DATA_KEY, data) 183 | } 184 | 185 | if (typeof opt_config == 'string') { 186 | data[opt_config]() 187 | } 188 | }) 189 | } 190 | 191 | 192 | /** 193 | * Function for getting target element from element 194 | * @return {Element} 195 | * @private 196 | */ 197 | Collapse._getTargetFromElement = function (element) { 198 | var selector = Bootstrap.getSelectorFromElement(element) 199 | 200 | return selector ? $(selector)[0] : null 201 | } 202 | 203 | 204 | /** 205 | * Toggles the collapse element based on the presence of the 'in' class 206 | */ 207 | Collapse.prototype['toggle'] = function () { 208 | if ($(this._element).hasClass(Collapse._ClassName.IN)) { 209 | this['hide']() 210 | } else { 211 | this['show']() 212 | } 213 | } 214 | 215 | 216 | /** 217 | * Show's the collapsing element 218 | */ 219 | Collapse.prototype['show'] = function () { 220 | if (this._isTransitioning || $(this._element).hasClass(Collapse._ClassName.IN)) { 221 | return 222 | } 223 | 224 | var activesData 225 | var actives 226 | 227 | if (this._parent) { 228 | actives = $.makeArray($(Collapse._Selector.ACTIVES)) 229 | if (!actives.length) { 230 | actives = null 231 | } 232 | } 233 | 234 | if (actives) { 235 | activesData = $(actives).data(Collapse._DATA_KEY) 236 | if (activesData && activesData._isTransitioning) { 237 | return 238 | } 239 | } 240 | 241 | var startEvent = $.Event(Collapse._Event.SHOW) 242 | $(this._element).trigger(startEvent) 243 | if (startEvent.isDefaultPrevented()) { 244 | return 245 | } 246 | 247 | if (actives) { 248 | Collapse._jQueryInterface.call($(actives), 'hide') 249 | if (!activesData) { 250 | $(actives).data(Collapse._DATA_KEY, null) 251 | } 252 | } 253 | 254 | var dimension = this._getDimension() 255 | 256 | $(this._element) 257 | .removeClass(Collapse._ClassName.COLLAPSE) 258 | .addClass(Collapse._ClassName.COLLAPSING) 259 | 260 | this._element.style[dimension] = 0 261 | this._element.setAttribute('aria-expanded', true) 262 | 263 | if (this._trigger) { 264 | $(this._trigger).removeClass(Collapse._ClassName.COLLAPSED) 265 | this._trigger.setAttribute('aria-expanded', true) 266 | } 267 | 268 | this['setTransitioning'](true) 269 | 270 | var complete = function () { 271 | $(this._element) 272 | .removeClass(Collapse._ClassName.COLLAPSING) 273 | .addClass(Collapse._ClassName.COLLAPSE) 274 | .addClass(Collapse._ClassName.IN) 275 | 276 | this._element.style[dimension] = '' 277 | 278 | this['setTransitioning'](false) 279 | 280 | $(this._element).trigger(Collapse._Event.SHOWN) 281 | }.bind(this) 282 | 283 | if (!Bootstrap.transition) { 284 | complete() 285 | return 286 | } 287 | 288 | var scrollSize = 'scroll' + (dimension[0].toUpperCase() + dimension.slice(1)) 289 | 290 | $(this._element) 291 | .one(Bootstrap.TRANSITION_END, complete) 292 | .emulateTransitionEnd(Collapse._TRANSITION_DURATION) 293 | 294 | this._element.style[dimension] = this._element[scrollSize] + 'px' 295 | } 296 | 297 | 298 | /** 299 | * Hides's the collapsing element 300 | */ 301 | Collapse.prototype['hide'] = function () { 302 | if (this._isTransitioning || !$(this._element).hasClass(Collapse._ClassName.IN)) { 303 | return 304 | } 305 | 306 | var startEvent = $.Event(Collapse._Event.HIDE) 307 | $(this._element).trigger(startEvent) 308 | if (startEvent.isDefaultPrevented()) return 309 | 310 | var dimension = this._getDimension() 311 | var offsetDimension = dimension === Collapse._Dimension.WIDTH ? 312 | 'offsetWidth' : 'offsetHeight' 313 | 314 | this._element.style[dimension] = this._element[offsetDimension] + 'px' 315 | 316 | Bootstrap.reflow(this._element) 317 | 318 | $(this._element) 319 | .addClass(Collapse._ClassName.COLLAPSING) 320 | .removeClass(Collapse._ClassName.COLLAPSE) 321 | .removeClass(Collapse._ClassName.IN) 322 | 323 | this._element.setAttribute('aria-expanded', false) 324 | 325 | if (this._trigger) { 326 | $(this._trigger).addClass(Collapse._ClassName.COLLAPSED) 327 | this._trigger.setAttribute('aria-expanded', false) 328 | } 329 | 330 | this['setTransitioning'](true) 331 | 332 | var complete = function () { 333 | this['setTransitioning'](false) 334 | $(this._element) 335 | .removeClass(Collapse._ClassName.COLLAPSING) 336 | .addClass(Collapse._ClassName.COLLAPSE) 337 | .trigger(Collapse._Event.HIDDEN) 338 | 339 | }.bind(this) 340 | 341 | this._element.style[dimension] = 0 342 | 343 | if (!Bootstrap.transition) { 344 | return complete() 345 | } 346 | 347 | $(this._element) 348 | .one(Bootstrap.TRANSITION_END, complete) 349 | .emulateTransitionEnd(Collapse._TRANSITION_DURATION) 350 | } 351 | 352 | 353 | 354 | /** 355 | * @param {boolean} isTransitioning 356 | */ 357 | Collapse.prototype['setTransitioning'] = function (isTransitioning) { 358 | this._isTransitioning = isTransitioning 359 | } 360 | 361 | 362 | /** 363 | * Returns the collapsing dimension 364 | * @return {string} 365 | * @private 366 | */ 367 | Collapse.prototype._getDimension = function () { 368 | var hasWidth = $(this._element).hasClass(Collapse._Dimension.WIDTH) 369 | return hasWidth ? Collapse._Dimension.WIDTH : Collapse._Dimension.HEIGHT 370 | } 371 | 372 | 373 | /** 374 | * Returns the parent element 375 | * @return {Element} 376 | * @private 377 | */ 378 | Collapse.prototype._getParent = function () { 379 | var selector = '[data-toggle="collapse"][data-parent="' + this._config['parent'] + '"]' 380 | var parent = $(this._config['parent'])[0] 381 | var elements = /** @type {Array.} */ ($.makeArray($(parent).find(selector))) 382 | 383 | for (var i = 0; i < elements.length; i++) { 384 | this._addAriaAndCollapsedClass(Collapse._getTargetFromElement(elements[i]), elements[i]) 385 | } 386 | 387 | return parent 388 | } 389 | 390 | 391 | /** 392 | * Returns the parent element 393 | * @param {Element} element 394 | * @param {Element} trigger 395 | * @private 396 | */ 397 | Collapse.prototype._addAriaAndCollapsedClass = function (element, trigger) { 398 | if (element) { 399 | var isOpen = $(element).hasClass(Collapse._ClassName.IN) 400 | element.setAttribute('aria-expanded', isOpen) 401 | 402 | if (trigger) { 403 | trigger.setAttribute('aria-expanded', isOpen) 404 | $(trigger).toggleClass(Collapse._ClassName.COLLAPSED, !isOpen) 405 | } 406 | } 407 | } 408 | 409 | 410 | 411 | /** 412 | * ------------------------------------------------------------------------ 413 | * jQuery Interface + noConflict implementaiton 414 | * ------------------------------------------------------------------------ 415 | */ 416 | 417 | /** 418 | * @const 419 | * @type {Function} 420 | */ 421 | $.fn[Collapse._NAME] = Collapse._jQueryInterface 422 | 423 | 424 | /** 425 | * @const 426 | * @type {Function} 427 | */ 428 | $.fn[Collapse._NAME]['Constructor'] = Collapse 429 | 430 | 431 | /** 432 | * @const 433 | * @type {Function} 434 | */ 435 | $.fn[Collapse._NAME]['noConflict'] = function () { 436 | $.fn[Collapse._NAME] = Collapse._JQUERY_NO_CONFLICT 437 | return this 438 | } 439 | 440 | 441 | /** 442 | * ------------------------------------------------------------------------ 443 | * Data Api implementation 444 | * ------------------------------------------------------------------------ 445 | */ 446 | 447 | $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (event) { 448 | event.preventDefault() 449 | 450 | var target = Collapse._getTargetFromElement(this) 451 | 452 | var data = $(target).data(Collapse._DATA_KEY) 453 | var config = data ? 'toggle' : $.extend({}, $(this).data(), { trigger: this }) 454 | 455 | Collapse._jQueryInterface.call($(target), config) 456 | }) 457 | -------------------------------------------------------------------------------- /js/dropdown.js: -------------------------------------------------------------------------------- 1 | /** ======================================================================= 2 | * Bootstrap: dropdown.js v4.0.0 3 | * http://getbootstrap.com/javascript/#dropdown 4 | * ======================================================================== 5 | * Copyright 2011-2015 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== 8 | * @fileoverview - Add dropdown menus to nearly anything with this simple 9 | * plugin, including the navbar, tabs, and pills. 10 | * 11 | * Public Methods & Properties: 12 | * 13 | * + $.dropdown 14 | * + $.dropdown.noConflict 15 | * + $.dropdown.Constructor 16 | * + $.dropdown.Constructor.VERSION 17 | * + $.dropdown.Constructor.prototype.toggle 18 | * 19 | * ======================================================================== 20 | */ 21 | 22 | 'use strict'; 23 | 24 | 25 | /** 26 | * Our dropdown class. 27 | * @param {Element!} element 28 | * @constructor 29 | */ 30 | var Dropdown = function (element) { 31 | $(element).on('click.bs.dropdown', this['toggle']) 32 | } 33 | 34 | 35 | /** 36 | * @const 37 | * @type {string} 38 | */ 39 | Dropdown['VERSION'] = '4.0.0' 40 | 41 | 42 | /** 43 | * @const 44 | * @type {string} 45 | * @private 46 | */ 47 | Dropdown._NAME = 'dropdown' 48 | 49 | 50 | /** 51 | * @const 52 | * @type {string} 53 | * @private 54 | */ 55 | Dropdown._DATA_KEY = 'bs.dropdown' 56 | 57 | 58 | /** 59 | * @const 60 | * @type {Function} 61 | * @private 62 | */ 63 | Dropdown._JQUERY_NO_CONFLICT = $.fn[Dropdown._NAME] 64 | 65 | 66 | /** 67 | * @const 68 | * @enum {string} 69 | * @private 70 | */ 71 | Dropdown._Event = { 72 | HIDE : 'hide.bs.dropdown', 73 | HIDDEN : 'hidden.bs.dropdown', 74 | SHOW : 'show.bs.dropdown', 75 | SHOWN : 'shown.bs.dropdown' 76 | } 77 | 78 | 79 | /** 80 | * @const 81 | * @enum {string} 82 | * @private 83 | */ 84 | Dropdown._ClassName = { 85 | BACKDROP : 'dropdown-backdrop', 86 | DISABLED : 'disabled', 87 | OPEN : 'open' 88 | } 89 | 90 | 91 | /** 92 | * @const 93 | * @enum {string} 94 | * @private 95 | */ 96 | Dropdown._Selector = { 97 | BACKDROP : '.dropdown-backdrop', 98 | DATA_TOGGLE : '[data-toggle="dropdown"]', 99 | FORM_CHILD : '.dropdown form', 100 | ROLE_MENU : '[role="menu"]', 101 | ROLE_LISTBOX : '[role="listbox"]', 102 | NAVBAR_NAV : '.navbar-nav', 103 | VISIBLE_ITEMS : '[role="menu"] li:not(.divider) a, [role="listbox"] li:not(.divider) a' 104 | } 105 | 106 | 107 | /** 108 | * Provides the jQuery Interface for the alert component. 109 | * @param {string=} opt_config 110 | * @this {jQuery} 111 | * @return {jQuery} 112 | * @private 113 | */ 114 | Dropdown._jQueryInterface = function (opt_config) { 115 | return this.each(function () { 116 | var data = $(this).data(Dropdown._DATA_KEY) 117 | 118 | if (!data) { 119 | $(this).data(Dropdown._DATA_KEY, (data = new Dropdown(this))) 120 | } 121 | 122 | if (typeof opt_config === 'string') { 123 | data[opt_config].call(this) 124 | } 125 | }) 126 | } 127 | 128 | 129 | /** 130 | * @param {Event=} opt_event 131 | * @private 132 | */ 133 | Dropdown._clearMenus = function (opt_event) { 134 | if (opt_event && opt_event.which == 3) { 135 | return 136 | } 137 | 138 | var backdrop = $(Dropdown._Selector.BACKDROP)[0] 139 | if (backdrop) { 140 | backdrop.parentNode.removeChild(backdrop) 141 | } 142 | 143 | var toggles = /** @type {Array.} */ ($.makeArray($(Dropdown._Selector.DATA_TOGGLE))) 144 | 145 | for (var i = 0; i < toggles.length; i++) { 146 | var parent = Dropdown._getParentFromElement(toggles[i]) 147 | var relatedTarget = { 'relatedTarget': toggles[i] } 148 | 149 | if (!$(parent).hasClass(Dropdown._ClassName.OPEN)) { 150 | continue 151 | } 152 | 153 | var hideEvent = $.Event(Dropdown._Event.HIDE, relatedTarget) 154 | $(parent).trigger(hideEvent) 155 | if (hideEvent.isDefaultPrevented()) { 156 | continue 157 | } 158 | 159 | toggles[i].setAttribute('aria-expanded', 'false') 160 | 161 | $(parent) 162 | .removeClass(Dropdown._ClassName.OPEN) 163 | .trigger(Dropdown._Event.HIDDEN, relatedTarget) 164 | } 165 | } 166 | 167 | 168 | /** 169 | * @param {Element} element 170 | * @return {Element} 171 | * @private 172 | */ 173 | Dropdown._getParentFromElement = function (element) { 174 | var parent 175 | var selector = Bootstrap.getSelectorFromElement(element) 176 | 177 | if (selector) { 178 | parent = $(selector)[0] 179 | } 180 | 181 | return /** @type {Element} */ (parent || element.parentNode) 182 | } 183 | 184 | 185 | /** 186 | * @param {Event} event 187 | * @this {Element} 188 | * @private 189 | */ 190 | Dropdown._dataApiKeydownHandler = function (event) { 191 | if (!/(38|40|27|32)/.test(event.which) || /input|textarea/i.test(event.target.tagName)) { 192 | return 193 | } 194 | 195 | event.preventDefault() 196 | event.stopPropagation() 197 | 198 | if (this.disabled || $(this).hasClass(Dropdown._ClassName.DISABLED)) { 199 | return 200 | } 201 | 202 | var parent = Dropdown._getParentFromElement(this) 203 | var isActive = $(parent).hasClass(Dropdown._ClassName.OPEN) 204 | 205 | if ((!isActive && event.which != 27) || (isActive && event.which == 27)) { 206 | if (event.which == 27) { 207 | var toggle = $(parent).find(Dropdown._Selector.DATA_TOGGLE)[0] 208 | $(toggle).trigger('focus') 209 | } 210 | $(this).trigger('click') 211 | return 212 | } 213 | 214 | var items = $.makeArray($(Dropdown._Selector.VISIBLE_ITEMS)) 215 | 216 | items = items.filter(function (item) { 217 | return item.offsetWidth || item.offsetHeight 218 | }) 219 | 220 | if (!items.length) { 221 | return 222 | } 223 | 224 | var index = items.indexOf(event.target) 225 | 226 | if (event.which == 38 && index > 0) index-- // up 227 | if (event.which == 40 && index < items.length - 1) index++ // down 228 | if (!~index) index = 0 229 | 230 | items[index].focus() 231 | } 232 | 233 | 234 | /** 235 | * Toggles the dropdown 236 | * @this {Element} 237 | * @return {boolean|undefined} 238 | */ 239 | Dropdown.prototype['toggle'] = function () { 240 | if (this.disabled || $(this).hasClass(Dropdown._ClassName.DISABLED)) { 241 | return 242 | } 243 | 244 | var parent = Dropdown._getParentFromElement(this) 245 | var isActive = $(parent).hasClass(Dropdown._ClassName.OPEN) 246 | 247 | Dropdown._clearMenus() 248 | 249 | if (isActive) { 250 | return false 251 | } 252 | 253 | if ('ontouchstart' in document.documentElement && !$(parent).closest(Dropdown._Selector.NAVBAR_NAV).length) { 254 | // if mobile we use a backdrop because click events don't delegate 255 | var dropdown = document.createElement('div') 256 | dropdown.className = Dropdown._ClassName.BACKDROP 257 | $(dropdown).insertBefore(this) 258 | $(dropdown).on('click', Dropdown._clearMenus) 259 | } 260 | 261 | var relatedTarget = { 'relatedTarget': this } 262 | var showEvent = $.Event(Dropdown._Event.SHOW, relatedTarget) 263 | 264 | $(parent).trigger(showEvent) 265 | 266 | if (showEvent.isDefaultPrevented()) { 267 | return 268 | } 269 | 270 | this.focus() 271 | this.setAttribute('aria-expanded', 'true') 272 | 273 | $(parent).toggleClass(Dropdown._ClassName.OPEN) 274 | 275 | $(parent).trigger(Dropdown._Event.SHOWN, relatedTarget) 276 | 277 | return false 278 | } 279 | 280 | 281 | /** 282 | * ------------------------------------------------------------------------ 283 | * jQuery Interface + noConflict implementaiton 284 | * ------------------------------------------------------------------------ 285 | */ 286 | 287 | /** 288 | * @const 289 | * @type {Function} 290 | */ 291 | $.fn[Dropdown._NAME] = Dropdown._jQueryInterface 292 | 293 | 294 | /** 295 | * @const 296 | * @type {Function} 297 | */ 298 | $.fn[Dropdown._NAME]['Constructor'] = Dropdown 299 | 300 | 301 | /** 302 | * @const 303 | * @type {Function} 304 | */ 305 | $.fn[Dropdown._NAME]['noConflict'] = function () { 306 | $.fn[Dropdown._NAME] = Dropdown._JQUERY_NO_CONFLICT 307 | return this 308 | } 309 | 310 | 311 | /** 312 | * ------------------------------------------------------------------------ 313 | * Data Api implementation 314 | * ------------------------------------------------------------------------ 315 | */ 316 | 317 | $(document) 318 | .on('click.bs.dropdown.data-api', Dropdown._clearMenus) 319 | .on('click.bs.dropdown.data-api', Dropdown._Selector.FORM_CHILD, function (e) { e.stopPropagation() }) 320 | .on('click.bs.dropdown.data-api', Dropdown._Selector.DATA_TOGGLE, Dropdown.prototype['toggle']) 321 | .on('keydown.bs.dropdown.data-api', Dropdown._Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler) 322 | .on('keydown.bs.dropdown.data-api', Dropdown._Selector.ROLE_MENU, Dropdown._dataApiKeydownHandler) 323 | .on('keydown.bs.dropdown.data-api', Dropdown._Selector.ROLE_LISTBOX, Dropdown._dataApiKeydownHandler) 324 | -------------------------------------------------------------------------------- /js/externs/bootstrap.js: -------------------------------------------------------------------------------- 1 | jQuery.event.prototype.handleObj = function () {} 2 | jQuery.event.prototype.handleObj.handler = function () {} 3 | $.event.special = function () {} 4 | $.event.trueHover = true 5 | $.offset = {} 6 | $.offset.setOffset = function (a,b,c) {} 7 | -------------------------------------------------------------------------------- /js/modal.js: -------------------------------------------------------------------------------- 1 | /** ======================================================================= 2 | * Bootstrap: modal.js v4.0.0 3 | * http://getbootstrap.com/javascript/#modal 4 | * ======================================================================== 5 | * Copyright 2011-2015 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== 8 | * @fileoverview - Bootstrap's modal plugin. Modals are streamlined, but 9 | * flexible, dialog prompts with the minimum required functionality and 10 | * smart defaults. 11 | * 12 | * Public Methods & Properties: 13 | * 14 | * + $.modal 15 | * + $.modal.noConflict 16 | * + $.modal.Constructor 17 | * + $.modal.Constructor.VERSION 18 | * + $.modal.Constructor.Defaults 19 | * + $.modal.Constructor.Defaults.backdrop 20 | * + $.modal.Constructor.Defaults.keyboard 21 | * + $.modal.Constructor.Defaults.show 22 | * + $.modal.Constructor.prototype.toggle 23 | * + $.modal.Constructor.prototype.show 24 | * + $.modal.Constructor.prototype.hide 25 | * 26 | * ======================================================================== 27 | */ 28 | 29 | 'use strict'; 30 | 31 | 32 | /** 33 | * Our modal class. 34 | * @param {Element} element 35 | * @param {Object} config 36 | * @constructor 37 | */ 38 | var Modal = function (element, config) { 39 | 40 | /** @private {Object} */ 41 | this._config = config 42 | 43 | /** @private {Element} */ 44 | this._element = element 45 | 46 | /** @private {Element} */ 47 | this._backdrop = null 48 | 49 | /** @private {boolean} */ 50 | this._isShown = false 51 | 52 | /** @private {boolean} */ 53 | this._isBodyOverflowing = false 54 | 55 | /** @private {number} */ 56 | this._scrollbarWidth = 0 57 | 58 | } 59 | 60 | 61 | /** 62 | * @const 63 | * @type {string} 64 | */ 65 | Modal['VERSION'] = '4.0.0' 66 | 67 | 68 | /** 69 | * @const 70 | * @type {Object} 71 | */ 72 | Modal['Defaults'] = { 73 | 'backdrop' : true, 74 | 'keyboard' : true, 75 | 'show' : true 76 | } 77 | 78 | 79 | /** 80 | * @const 81 | * @type {string} 82 | * @private 83 | */ 84 | Modal._NAME = 'modal' 85 | 86 | 87 | /** 88 | * @const 89 | * @type {string} 90 | * @private 91 | */ 92 | Modal._DATA_KEY = 'bs.modal' 93 | 94 | 95 | /** 96 | * @const 97 | * @type {number} 98 | * @private 99 | */ 100 | Modal._TRANSITION_DURATION = 300 101 | 102 | 103 | /** 104 | * @const 105 | * @type {number} 106 | * @private 107 | */ 108 | Modal._BACKDROP_TRANSITION_DURATION = 150 109 | 110 | 111 | /** 112 | * @const 113 | * @type {Function} 114 | * @private 115 | */ 116 | Modal._JQUERY_NO_CONFLICT = $.fn[Modal._NAME] 117 | 118 | 119 | /** 120 | * @const 121 | * @enum {string} 122 | * @private 123 | */ 124 | Modal._Event = { 125 | HIDE : 'hide.bs.modal', 126 | HIDDEN : 'hidden.bs.modal', 127 | SHOW : 'show.bs.modal', 128 | SHOWN : 'shown.bs.modal' 129 | } 130 | 131 | 132 | /** 133 | * @const 134 | * @enum {string} 135 | * @private 136 | */ 137 | Modal._ClassName = { 138 | BACKDROP : 'modal-backdrop', 139 | OPEN : 'modal-open', 140 | FADE : 'fade', 141 | IN : 'in' 142 | } 143 | 144 | 145 | /** 146 | * @const 147 | * @enum {string} 148 | * @private 149 | */ 150 | Modal._Selector = { 151 | DIALOG : '.modal-dialog', 152 | DATA_TOGGLE : '[data-toggle="modal"]', 153 | DATA_DISMISS : '[data-dismiss="modal"]', 154 | SCROLLBAR_MEASURER : 'modal-scrollbar-measure' 155 | } 156 | 157 | 158 | 159 | /** 160 | * Provides the jQuery Interface for the alert component. 161 | * @param {Object|string=} opt_config 162 | * @param {Element=} opt_relatedTarget 163 | * @this {jQuery} 164 | * @return {jQuery} 165 | * @private 166 | */ 167 | Modal._jQueryInterface = function Plugin(opt_config, opt_relatedTarget) { 168 | return this.each(function () { 169 | var data = $(this).data(Modal._DATA_KEY) 170 | var config = $.extend({}, Modal['Defaults'], $(this).data(), typeof opt_config == 'object' && opt_config) 171 | 172 | if (!data) { 173 | data = new Modal(this, config) 174 | $(this).data(Modal._DATA_KEY, data) 175 | } 176 | 177 | if (typeof opt_config == 'string') { 178 | data[opt_config](opt_relatedTarget) 179 | 180 | } else if (config['show']) { 181 | data['show'](opt_relatedTarget) 182 | } 183 | }) 184 | } 185 | 186 | 187 | /** 188 | * @param {Element} relatedTarget 189 | */ 190 | Modal.prototype['toggle'] = function (relatedTarget) { 191 | return this._isShown ? this['hide']() : this['show'](relatedTarget) 192 | } 193 | 194 | 195 | /** 196 | * @param {Element} relatedTarget 197 | */ 198 | Modal.prototype['show'] = function (relatedTarget) { 199 | var showEvent = $.Event(Modal._Event.SHOW, { relatedTarget: relatedTarget }) 200 | 201 | $(this._element).trigger(showEvent) 202 | 203 | if (this._isShown || showEvent.isDefaultPrevented()) { 204 | return 205 | } 206 | 207 | this._isShown = true 208 | 209 | this._checkScrollbar() 210 | this._setScrollbar() 211 | 212 | $(document.body).addClass(Modal._ClassName.OPEN) 213 | 214 | this._escape() 215 | this._resize() 216 | 217 | $(this._element).on('click.dismiss.bs.modal', Modal._Selector.DATA_DISMISS, this['hide'].bind(this)) 218 | 219 | this._showBackdrop(this._showElement.bind(this, relatedTarget)) 220 | } 221 | 222 | 223 | /** 224 | * @param {Event} event 225 | */ 226 | Modal.prototype['hide'] = function (event) { 227 | if (event) { 228 | event.preventDefault() 229 | } 230 | 231 | var hideEvent = $.Event(Modal._Event.HIDE) 232 | 233 | $(this._element).trigger(hideEvent) 234 | 235 | if (!this._isShown || hideEvent.isDefaultPrevented()) { 236 | return 237 | } 238 | 239 | this._isShown = false 240 | 241 | this._escape() 242 | this._resize() 243 | 244 | $(document).off('focusin.bs.modal') 245 | 246 | $(this._element).removeClass(Modal._ClassName.IN) 247 | this._element.setAttribute('aria-hidden', true) 248 | 249 | $(this._element).off('click.dismiss.bs.modal') 250 | 251 | if (Bootstrap.transition && $(this._element).hasClass(Modal._ClassName.FADE)) { 252 | $(this._element) 253 | .one(Bootstrap.TRANSITION_END, this._hideModal.bind(this)) 254 | .emulateTransitionEnd(Modal._TRANSITION_DURATION) 255 | } else { 256 | this._hideModal() 257 | } 258 | } 259 | 260 | 261 | /** 262 | * @param {Element} relatedTarget 263 | * @private 264 | */ 265 | Modal.prototype._showElement = function (relatedTarget) { 266 | var transition = Bootstrap.transition && $(this._element).hasClass(Modal._ClassName.FADE) 267 | 268 | if (!this._element.parentNode || this._element.parentNode.nodeType != Node.ELEMENT_NODE) { 269 | document.body.appendChild(this._element) // don't move modals dom position 270 | } 271 | 272 | this._element.style.display = 'block' 273 | this._element.scrollTop = 0 274 | 275 | if (this._config['backdrop']) { 276 | this._adjustBackdrop() 277 | } 278 | 279 | if (transition) { 280 | Bootstrap.reflow(this._element) 281 | } 282 | 283 | $(this._element).addClass(Modal._ClassName.IN) 284 | this._element.setAttribute('aria-hidden', false) 285 | 286 | this._enforceFocus() 287 | 288 | var shownEvent = $.Event(Modal._Event.SHOWN, { relatedTarget: relatedTarget }) 289 | 290 | var transitionComplete = function () { 291 | this._element.focus() 292 | $(this._element).trigger(shownEvent) 293 | }.bind(this) 294 | 295 | if (transition) { 296 | var dialog = $(this._element).find(Modal._Selector.DIALOG)[0] 297 | $(dialog) 298 | .one(Bootstrap.TRANSITION_END, transitionComplete) 299 | .emulateTransitionEnd(Modal._TRANSITION_DURATION) 300 | } else { 301 | transitionComplete() 302 | } 303 | } 304 | 305 | 306 | 307 | /** 308 | * @private 309 | */ 310 | Modal.prototype._enforceFocus = function () { 311 | $(document) 312 | .off('focusin.bs.modal') // guard against infinite focus loop 313 | .on('focusin.bs.modal', function (e) { 314 | if (this._element !== e.target && !$(this._element).has(e.target).length) { 315 | this._element.focus() 316 | } 317 | }.bind(this)) 318 | } 319 | 320 | 321 | /** 322 | * @private 323 | */ 324 | Modal.prototype._escape = function () { 325 | if (this._isShown && this._config['keyboard']) { 326 | $(this._element).on('keydown.dismiss.bs.modal', function (event) { 327 | if (event.which === 27) { 328 | this['hide']() 329 | } 330 | }.bind(this)) 331 | 332 | } else if (!this._isShown) { 333 | $(this._element).off('keydown.dismiss.bs.modal') 334 | } 335 | } 336 | 337 | 338 | /** 339 | * @private 340 | */ 341 | Modal.prototype._resize = function () { 342 | if (this._isShown) { 343 | $(window).on('resize.bs.modal', this._handleUpdate.bind(this)) 344 | } else { 345 | $(window).off('resize.bs.modal') 346 | } 347 | } 348 | 349 | 350 | /** 351 | * @private 352 | */ 353 | Modal.prototype._hideModal = function () { 354 | this._element.style.display = 'none' 355 | this._showBackdrop(function () { 356 | $(document.body).removeClass(Modal._ClassName.OPEN) 357 | this._resetAdjustments() 358 | this._resetScrollbar() 359 | $(this._element).trigger(Modal._Event.HIDDEN) 360 | }.bind(this)) 361 | } 362 | 363 | 364 | /** 365 | * @private 366 | */ 367 | Modal.prototype._removeBackdrop = function () { 368 | if (this._backdrop) { 369 | this._backdrop.parentNode.removeChild(this._backdrop) 370 | this._backdrop = null 371 | } 372 | } 373 | 374 | 375 | /** 376 | * @param {Function} callback 377 | * @private 378 | */ 379 | Modal.prototype._showBackdrop = function (callback) { 380 | var animate = $(this._element).hasClass(Modal._ClassName.FADE) ? Modal._ClassName.FADE : '' 381 | 382 | if (this._isShown && this._config['backdrop']) { 383 | var doAnimate = Bootstrap.transition && animate 384 | 385 | this._backdrop = document.createElement('div') 386 | this._backdrop.className = Modal._ClassName.BACKDROP 387 | 388 | if (animate) { 389 | $(this._backdrop).addClass(animate) 390 | } 391 | 392 | $(this._element).prepend(this._backdrop) 393 | 394 | $(this._backdrop).on('click.dismiss.bs.modal', function (event) { 395 | if (event.target !== event.currentTarget) return 396 | this._config['backdrop'] === 'static' 397 | ? this._element.focus() 398 | : this['hide']() 399 | }.bind(this)) 400 | 401 | if (doAnimate) { 402 | Bootstrap.reflow(this._backdrop) 403 | } 404 | 405 | $(this._backdrop).addClass(Modal._ClassName.IN) 406 | 407 | if (!callback) { 408 | return 409 | } 410 | 411 | if (!doAnimate) { 412 | callback() 413 | return 414 | } 415 | 416 | $(this._backdrop) 417 | .one(Bootstrap.TRANSITION_END, callback) 418 | .emulateTransitionEnd(Modal._BACKDROP_TRANSITION_DURATION) 419 | 420 | } else if (!this._isShown && this._backdrop) { 421 | $(this._backdrop).removeClass(Modal._ClassName.IN) 422 | 423 | var callbackRemove = function () { 424 | this._removeBackdrop() 425 | if (callback) { 426 | callback() 427 | } 428 | }.bind(this) 429 | 430 | if (Bootstrap.transition && $(this._element).hasClass(Modal._ClassName.FADE)) { 431 | $(this._backdrop) 432 | .one(Bootstrap.TRANSITION_END, callbackRemove) 433 | .emulateTransitionEnd(Modal._BACKDROP_TRANSITION_DURATION) 434 | } else { 435 | callbackRemove() 436 | } 437 | 438 | } else if (callback) { 439 | callback() 440 | } 441 | } 442 | 443 | 444 | /** 445 | * ------------------------------------------------------------------------ 446 | * the following methods are used to handle overflowing modals 447 | * todo (fat): these should probably be refactored into a 448 | * ------------------------------------------------------------------------ 449 | */ 450 | 451 | 452 | /** 453 | * @private 454 | */ 455 | Modal.prototype._handleUpdate = function () { 456 | if (this._config['backdrop']) this._adjustBackdrop() 457 | this._adjustDialog() 458 | } 459 | 460 | /** 461 | * @private 462 | */ 463 | Modal.prototype._adjustBackdrop = function () { 464 | this._backdrop.style.height = 0 // todo (fat): no clue why we do this 465 | this._backdrop.style.height = this._element.scrollHeight + 'px' 466 | } 467 | 468 | 469 | /** 470 | * @private 471 | */ 472 | Modal.prototype._adjustDialog = function () { 473 | var isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight 474 | 475 | if (!this._isBodyOverflowing && isModalOverflowing) { 476 | this._element.style.paddingLeft = this._scrollbarWidth + 'px' 477 | } 478 | 479 | if (this._isBodyOverflowing && !isModalOverflowing) { 480 | this._element.style.paddingRight = this._scrollbarWidth + 'px' 481 | } 482 | } 483 | 484 | 485 | /** 486 | * @private 487 | */ 488 | Modal.prototype._resetAdjustments = function () { 489 | this._element.style.paddingLeft = '' 490 | this._element.style.paddingRight = '' 491 | } 492 | 493 | 494 | /** 495 | * @private 496 | */ 497 | Modal.prototype._checkScrollbar = function () { 498 | this._isBodyOverflowing = document.body.scrollHeight > document.documentElement.clientHeight 499 | this._scrollbarWidth = this._getScrollbarWidth() 500 | } 501 | 502 | 503 | /** 504 | * @private 505 | */ 506 | Modal.prototype._setScrollbar = function () { 507 | var bodyPadding = parseInt(($(document.body).css('padding-right') || 0), 10) 508 | 509 | if (this._isBodyOverflowing) { 510 | document.body.style.paddingRight = bodyPadding + this._scrollbarWidth + 'px' 511 | } 512 | } 513 | 514 | 515 | /** 516 | * @private 517 | */ 518 | Modal.prototype._resetScrollbar = function () { 519 | document.body.style.paddingRight = '' 520 | } 521 | 522 | 523 | /** 524 | * @private 525 | */ 526 | Modal.prototype._getScrollbarWidth = function () { // thx walsh 527 | var scrollDiv = document.createElement('div') 528 | scrollDiv.className = Modal._Selector.SCROLLBAR_MEASURER 529 | document.body.appendChild(scrollDiv) 530 | var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth 531 | document.body.removeChild(scrollDiv) 532 | return scrollbarWidth 533 | } 534 | 535 | 536 | /** 537 | * ------------------------------------------------------------------------ 538 | * jQuery Interface + noConflict implementaiton 539 | * ------------------------------------------------------------------------ 540 | */ 541 | 542 | /** 543 | * @const 544 | * @type {Function} 545 | */ 546 | $.fn[Modal._NAME] = Modal._jQueryInterface 547 | 548 | 549 | /** 550 | * @const 551 | * @type {Function} 552 | */ 553 | $.fn[Modal._NAME]['Constructor'] = Modal 554 | 555 | 556 | /** 557 | * @const 558 | * @type {Function} 559 | */ 560 | $.fn[Modal._NAME]['noConflict'] = function () { 561 | $.fn[Modal._NAME] = Modal._JQUERY_NO_CONFLICT 562 | return this 563 | } 564 | 565 | 566 | /** 567 | * ------------------------------------------------------------------------ 568 | * Data Api implementation 569 | * ------------------------------------------------------------------------ 570 | */ 571 | 572 | $(document).on('click.bs.modal.data-api', Modal._Selector.DATA_TOGGLE, function (event) { 573 | var target 574 | var selector = Bootstrap.getSelectorFromElement(this) 575 | 576 | if (selector) { 577 | target = $(selector)[0] 578 | } 579 | 580 | var config = $(target).data(Modal._DATA_KEY) ? 'toggle' : $.extend({}, $(target).data(), $(this).data()) 581 | 582 | if (this.tagName == 'A') { 583 | event.preventDefault() 584 | } 585 | 586 | var $target = $(target).one(Modal._Event.SHOW, function (showEvent) { 587 | if (showEvent.isDefaultPrevented()) { 588 | return // only register focus restorer if modal will actually get shown 589 | } 590 | 591 | $target.one(Modal._Event.HIDDEN, function () { 592 | if ($(this).is(':visible')) { 593 | this.focus() 594 | } 595 | }.bind(this)) 596 | }.bind(this)) 597 | 598 | Modal._jQueryInterface.call($(target), config, this) 599 | }) 600 | -------------------------------------------------------------------------------- /js/popover.js: -------------------------------------------------------------------------------- 1 | /** ======================================================================= 2 | * Bootstrap: popover.js v4.0.0 3 | * http://getbootstrap.com/javascript/#popovers 4 | * ======================================================================== 5 | * Copyright 2011-2015 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== 8 | * @fileoverview - Bootstrap's popover plugin - extends tooltip. 9 | * 10 | * Public Methods & Properties: 11 | * 12 | * + $.popover 13 | * + $.popover.noConflict 14 | * + $.popover.Constructor 15 | * + $.popover.Constructor.VERSION 16 | * + $.popover.Constructor.Defaults 17 | * + $.popover.Constructor.Defaults.container 18 | * + $.popover.Constructor.Defaults.animation 19 | * + $.popover.Constructor.Defaults.placement 20 | * + $.popover.Constructor.Defaults.selector 21 | * + $.popover.Constructor.Defaults.template 22 | * + $.popover.Constructor.Defaults.trigger 23 | * + $.popover.Constructor.Defaults.title 24 | * + $.popover.Constructor.Defaults.content 25 | * + $.popover.Constructor.Defaults.delay 26 | * + $.popover.Constructor.Defaults.html 27 | * + $.popover.Constructor.Defaults.viewport 28 | * + $.popover.Constructor.Defaults.viewport.selector 29 | * + $.popover.Constructor.Defaults.viewport.padding 30 | * + $.popover.Constructor.prototype.enable 31 | * + $.popover.Constructor.prototype.disable 32 | * + $.popover.Constructor.prototype.destroy 33 | * + $.popover.Constructor.prototype.toggleEnabled 34 | * + $.popover.Constructor.prototype.toggle 35 | * + $.popover.Constructor.prototype.show 36 | * + $.popover.Constructor.prototype.hide 37 | * 38 | * ======================================================================== 39 | */ 40 | 41 | 42 | 'use strict'; 43 | 44 | 45 | if (!Tooltip) throw new Error('Popover requires tooltip.js') 46 | 47 | 48 | /** 49 | * Our tooltip class. 50 | * @param {Element!} element 51 | * @param {Object=} opt_config 52 | * @constructor 53 | * @extends {Tooltip} 54 | */ 55 | var Popover = function (element, opt_config) { 56 | Tooltip.call(this, element, opt_config) 57 | } 58 | Bootstrap.inherits(Popover, Tooltip) 59 | 60 | 61 | /** 62 | * @const 63 | * @type {string} 64 | */ 65 | Popover['VERSION'] = '4.0.0' 66 | 67 | 68 | /** 69 | * @const 70 | * @type {Object} 71 | */ 72 | Popover['Defaults'] = $.extend({}, $.fn['tooltip']['Constructor']['Defaults'], { 73 | 'placement': 'right', 74 | 'trigger': 'click', 75 | 'content': '', 76 | 'template': '' 77 | }) 78 | 79 | 80 | /** 81 | * @const 82 | * @type {string} 83 | * @private 84 | */ 85 | Popover._NAME = 'popover' 86 | 87 | 88 | /** 89 | * @const 90 | * @type {string} 91 | * @private 92 | */ 93 | Popover._DATA_KEY = 'bs.popover' 94 | 95 | 96 | /** 97 | * @const 98 | * @enum {string} 99 | * @private 100 | */ 101 | Popover._Event = { 102 | HIDE : 'hide.bs.popover', 103 | HIDDEN : 'hidden.bs.popover', 104 | SHOW : 'show.bs.popover', 105 | SHOWN : 'shown.bs.popover' 106 | } 107 | 108 | 109 | /** 110 | * @const 111 | * @enum {string} 112 | * @private 113 | */ 114 | Popover._ClassName = { 115 | FADE : 'fade', 116 | IN : 'in' 117 | } 118 | 119 | 120 | /** 121 | * @const 122 | * @enum {string} 123 | * @private 124 | */ 125 | Popover._Selector = { 126 | TITLE : '.popover-title', 127 | CONTENT : '.popover-content', 128 | ARROW : '.popover-arrow' 129 | } 130 | 131 | 132 | /** 133 | * @const 134 | * @type {Function} 135 | * @private 136 | */ 137 | Popover._JQUERY_NO_CONFLICT = $.fn[Popover._NAME] 138 | 139 | 140 | /** 141 | * @param {Object|string=} opt_config 142 | * @this {jQuery} 143 | * @return {jQuery} 144 | * @private 145 | */ 146 | Popover._jQueryInterface = function (opt_config) { 147 | return this.each(function () { 148 | var data = $(this).data(Popover._DATA_KEY) 149 | var config = typeof opt_config === 'object' ? opt_config : null 150 | 151 | if (!data && opt_config === 'destroy') { 152 | return 153 | } 154 | 155 | if (!data) { 156 | data = new Popover(this, config) 157 | $(this).data(Popover._DATA_KEY, data) 158 | } 159 | 160 | if (typeof opt_config === 'string') { 161 | data[opt_config]() 162 | } 163 | }) 164 | } 165 | 166 | 167 | /** 168 | * @return {string} 169 | * @protected 170 | */ 171 | Popover.prototype.getName = function () { 172 | return Popover._NAME 173 | } 174 | 175 | 176 | /** 177 | * @override 178 | */ 179 | Popover.prototype.getDataKey = function () { 180 | return Popover._DATA_KEY 181 | } 182 | 183 | 184 | /** 185 | * @override 186 | */ 187 | Popover.prototype.getEventObject = function () { 188 | return Popover._Event 189 | } 190 | 191 | 192 | /** 193 | * @override 194 | */ 195 | Popover.prototype.getArrowElement = function () { 196 | return (this.arrow = this.arrow || $(this.getTipElement()).find(Popover._Selector.ARROW)[0]) 197 | } 198 | 199 | 200 | /** 201 | * @override 202 | */ 203 | Popover.prototype.setContent = function () { 204 | var tip = this.getTipElement() 205 | var title = this.getTitle() 206 | var content = this._getContent() 207 | var titleElement = $(tip).find(Popover._Selector.TITLE)[0] 208 | 209 | if (titleElement) { 210 | titleElement[this.config['html'] ? 'innerHTML' : 'innerText'] = title 211 | } 212 | 213 | // we use append for html objects to maintain js events 214 | $(tip).find(Popover._Selector.CONTENT).children().detach().end()[ 215 | this.config['html'] ? (typeof content == 'string' ? 'html' : 'append') : 'text' 216 | ](content) 217 | 218 | $(tip) 219 | .removeClass(Popover._ClassName.FADE) 220 | .removeClass(Popover._ClassName.IN) 221 | 222 | for (var direction in Tooltip.Direction) { 223 | $(tip).removeClass(Popover._NAME + '-' + Tooltip.Direction[direction]) 224 | } 225 | } 226 | 227 | 228 | /** 229 | * @override 230 | */ 231 | Popover.prototype.isWithContent = function () { 232 | return this.getTitle() || this._getContent() 233 | } 234 | 235 | 236 | /** 237 | * @override 238 | */ 239 | Popover.prototype.getTipElement = function () { 240 | return (this.tip = this.tip || $(this.config['template'])[0]) 241 | } 242 | 243 | 244 | /** 245 | * @private 246 | */ 247 | Popover.prototype._getContent = function () { 248 | return this.element.getAttribute('data-content') 249 | || (typeof this.config['content'] == 'function' ? 250 | this.config['content'].call(this.element) : 251 | this.config['content']) 252 | } 253 | 254 | 255 | 256 | /** 257 | * ------------------------------------------------------------------------ 258 | * jQuery Interface + noConflict implementaiton 259 | * ------------------------------------------------------------------------ 260 | */ 261 | 262 | /** 263 | * @const 264 | * @type {Function} 265 | */ 266 | $.fn[Popover._NAME] = Popover._jQueryInterface 267 | 268 | 269 | /** 270 | * @const 271 | * @type {Function} 272 | */ 273 | $.fn[Popover._NAME]['Constructor'] = Popover 274 | 275 | 276 | /** 277 | * @const 278 | * @type {Function} 279 | */ 280 | $.fn[Popover._NAME]['noConflict'] = function () { 281 | $.fn[Popover._NAME] = Popover._JQUERY_NO_CONFLICT 282 | return this 283 | } 284 | -------------------------------------------------------------------------------- /js/scrollspy.js: -------------------------------------------------------------------------------- 1 | /** ======================================================================= 2 | * Bootstrap: scrollspy.js v4.0.0 3 | * http://getbootstrap.com/javascript/#scrollspy 4 | * ======================================================================== 5 | * Copyright 2011-2015 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== 8 | * @fileoverview - Bootstrap's scrollspy plugin. 9 | * 10 | * Public Methods & Properties: 11 | * 12 | * + $.scrollspy 13 | * + $.scrollspy.noConflict 14 | * + $.scrollspy.Constructor 15 | * + $.scrollspy.Constructor.VERSION 16 | * + $.scrollspy.Constructor.Defaults 17 | * + $.scrollspy.Constructor.Defaults.offset 18 | * + $.scrollspy.Constructor.prototype.refresh 19 | * 20 | * ======================================================================== 21 | */ 22 | 23 | 'use strict'; 24 | 25 | 26 | /** 27 | * Our scrollspy class. 28 | * @param {Element!} element 29 | * @param {Object=} opt_config 30 | * @constructor 31 | */ 32 | function ScrollSpy(element, opt_config) { 33 | 34 | /** @private {Element|Window} */ 35 | this._scrollElement = element.tagName == 'BODY' ? window : element 36 | 37 | /** @private {Object} */ 38 | this._config = $.extend({}, ScrollSpy['Defaults'], opt_config) 39 | 40 | /** @private {string} */ 41 | this._selector = (this._config.target || '') + ' .nav li > a' 42 | 43 | /** @private {Array} */ 44 | this._offsets = [] 45 | 46 | /** @private {Array} */ 47 | this._targets = [] 48 | 49 | /** @private {Element} */ 50 | this._activeTarget = null 51 | 52 | /** @private {number} */ 53 | this._scrollHeight = 0 54 | 55 | $(this._scrollElement).on('scroll.bs.scrollspy', this._process.bind(this)) 56 | 57 | this['refresh']() 58 | 59 | this._process() 60 | } 61 | 62 | 63 | /** 64 | * @const 65 | * @type {string} 66 | */ 67 | ScrollSpy['VERSION'] = '4.0.0' 68 | 69 | 70 | /** 71 | * @const 72 | * @type {Object} 73 | */ 74 | ScrollSpy['Defaults'] = { 75 | 'offset': 10 76 | } 77 | 78 | 79 | /** 80 | * @const 81 | * @type {string} 82 | * @private 83 | */ 84 | ScrollSpy._NAME = 'scrollspy' 85 | 86 | 87 | /** 88 | * @const 89 | * @type {string} 90 | * @private 91 | */ 92 | ScrollSpy._DATA_KEY = 'bs.scrollspy' 93 | 94 | 95 | /** 96 | * @const 97 | * @type {Function} 98 | * @private 99 | */ 100 | ScrollSpy._JQUERY_NO_CONFLICT = $.fn[ScrollSpy._NAME] 101 | 102 | 103 | /** 104 | * @const 105 | * @enum {string} 106 | * @private 107 | */ 108 | ScrollSpy._Event = { 109 | ACTIVATE: 'activate.bs.scrollspy' 110 | } 111 | 112 | 113 | /** 114 | * @const 115 | * @enum {string} 116 | * @private 117 | */ 118 | ScrollSpy._ClassName = { 119 | DROPDOWN_MENU : 'dropdown-menu', 120 | ACTIVE : 'active' 121 | } 122 | 123 | 124 | /** 125 | * @const 126 | * @enum {string} 127 | * @private 128 | */ 129 | ScrollSpy._Selector = { 130 | DATA_SPY : '[data-spy="scroll"]', 131 | ACTIVE : '.active', 132 | LI_DROPDOWN : 'li.dropdown', 133 | LI : 'li' 134 | } 135 | 136 | 137 | /** 138 | * @param {Object=} opt_config 139 | * @this {jQuery} 140 | * @return {jQuery} 141 | * @private 142 | */ 143 | ScrollSpy._jQueryInterface = function (opt_config) { 144 | return this.each(function () { 145 | var data = $(this).data(ScrollSpy._DATA_KEY) 146 | var config = typeof opt_config === 'object' && opt_config || null 147 | 148 | if (!data) { 149 | data = new ScrollSpy(this, config) 150 | $(this).data(ScrollSpy._DATA_KEY, data) 151 | } 152 | 153 | if (typeof opt_config === 'string') { 154 | data[opt_config]() 155 | } 156 | }) 157 | } 158 | 159 | 160 | /** 161 | * Refresh the scrollspy target cache 162 | */ 163 | ScrollSpy.prototype['refresh'] = function () { 164 | var offsetMethod = 'offset' 165 | var offsetBase = 0 166 | 167 | if (this._scrollElement !== this._scrollElement.window) { 168 | offsetMethod = 'position' 169 | offsetBase = this._getScrollTop() 170 | } 171 | 172 | this._offsets = [] 173 | this._targets = [] 174 | 175 | this._scrollHeight = this._getScrollHeight() 176 | 177 | var targets = /** @type {Array.} */ ($.makeArray($(this._selector))) 178 | 179 | targets 180 | .map(function (element) { 181 | var target 182 | var targetSelector = Bootstrap.getSelectorFromElement(element) 183 | 184 | if (targetSelector) { 185 | target = $(targetSelector)[0] 186 | } 187 | 188 | if (target && (target.offsetWidth || target.offsetHeight)) { 189 | // todo (fat): remove sketch reliance on jQuery position/offset 190 | return [$(target)[offsetMethod]().top + offsetBase, targetSelector] 191 | } 192 | }) 193 | .filter(function (item) { return item }) 194 | .sort(function (a, b) { return a[0] - b[0] }) 195 | .forEach(function (item) { 196 | this._offsets.push(item[0]) 197 | this._targets.push(item[1]) 198 | }.bind(this)) 199 | } 200 | 201 | 202 | /** 203 | * @private 204 | */ 205 | ScrollSpy.prototype._getScrollTop = function () { 206 | return this._scrollElement === window ? 207 | this._scrollElement.scrollY : this._scrollElement.scrollTop 208 | } 209 | 210 | 211 | /** 212 | * @private 213 | */ 214 | ScrollSpy.prototype._getScrollHeight = function () { 215 | return this._scrollElement.scrollHeight 216 | || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) 217 | } 218 | 219 | 220 | /** 221 | * @private 222 | */ 223 | ScrollSpy.prototype._process = function () { 224 | var scrollTop = this._getScrollTop() + this._config.offset 225 | var scrollHeight = this._getScrollHeight() 226 | var maxScroll = this._config.offset + scrollHeight - this._scrollElement.offsetHeight 227 | 228 | if (this._scrollHeight != scrollHeight) { 229 | this['refresh']() 230 | } 231 | 232 | if (scrollTop >= maxScroll) { 233 | var target = this._targets[this._targets.length - 1] 234 | 235 | if (this._activeTarget != target) { 236 | this._activate(target) 237 | } 238 | } 239 | 240 | if (this._activeTarget && scrollTop < this._offsets[0]) { 241 | this._activeTarget = null 242 | this._clear() 243 | return 244 | } 245 | 246 | for (var i = this._offsets.length; i--;) { 247 | var isActiveTarget = this._activeTarget != this._targets[i] 248 | && scrollTop >= this._offsets[i] 249 | && (!this._offsets[i + 1] || scrollTop < this._offsets[i + 1]) 250 | 251 | if (isActiveTarget) { 252 | this._activate(this._targets[i]) 253 | } 254 | } 255 | } 256 | 257 | 258 | /** 259 | * @param {Element} target 260 | * @private 261 | */ 262 | ScrollSpy.prototype._activate = function (target) { 263 | this._activeTarget = target 264 | 265 | this._clear() 266 | 267 | var selector = this._selector 268 | + '[data-target="' + target + '"],' 269 | + this._selector + '[href="' + target + '"]' 270 | 271 | // todo (fat): this seems horribly wrong… getting all raw li elements up the tree ,_, 272 | var parentListItems = $(selector).parents(ScrollSpy._Selector.LI) 273 | 274 | for (var i = parentListItems.length; i--;) { 275 | $(parentListItems[i]).addClass(ScrollSpy._ClassName.ACTIVE) 276 | 277 | var itemParent = parentListItems[i].parentNode 278 | 279 | if (itemParent && $(itemParent).hasClass(ScrollSpy._ClassName.DROPDOWN_MENU)) { 280 | var closestDropdown = $(itemParent).closest(ScrollSpy._Selector.LI_DROPDOWN)[0] 281 | $(closestDropdown).addClass(ScrollSpy._ClassName.ACTIVE) 282 | } 283 | } 284 | 285 | $(this._scrollElement).trigger(ScrollSpy._Event.ACTIVATE, { 286 | relatedTarget: target 287 | }) 288 | } 289 | 290 | 291 | /** 292 | * @private 293 | */ 294 | ScrollSpy.prototype._clear = function () { 295 | var activeParents = $(this._selector).parentsUntil(this._config.target, ScrollSpy._Selector.ACTIVE) 296 | 297 | for (var i = activeParents.length; i--;) { 298 | $(activeParents[i]).removeClass(ScrollSpy._ClassName.ACTIVE) 299 | } 300 | } 301 | 302 | 303 | /** 304 | * ------------------------------------------------------------------------ 305 | * jQuery Interface + noConflict implementaiton 306 | * ------------------------------------------------------------------------ 307 | */ 308 | 309 | /** 310 | * @const 311 | * @type {Function} 312 | */ 313 | $.fn[ScrollSpy._NAME] = ScrollSpy._jQueryInterface 314 | 315 | 316 | /** 317 | * @const 318 | * @type {Function} 319 | */ 320 | $.fn[ScrollSpy._NAME]['Constructor'] = ScrollSpy 321 | 322 | 323 | /** 324 | * @const 325 | * @type {Function} 326 | */ 327 | $.fn[ScrollSpy._NAME]['noConflict'] = function () { 328 | $.fn[ScrollSpy._NAME] = ScrollSpy._JQUERY_NO_CONFLICT 329 | return this 330 | } 331 | 332 | 333 | /** 334 | * ------------------------------------------------------------------------ 335 | * Data Api implementation 336 | * ------------------------------------------------------------------------ 337 | */ 338 | 339 | $(window).on('load.bs.scrollspy.data-api', function () { 340 | var scrollSpys = /** @type {Array.} */ ($.makeArray($(ScrollSpy._Selector.DATA_SPY))) 341 | 342 | for (var i = scrollSpys.length; i--;) { 343 | var $spy = $(scrollSpys[i]) 344 | ScrollSpy._jQueryInterface.call($spy, /** @type {Object|null} */ ($spy.data())) 345 | } 346 | }) 347 | -------------------------------------------------------------------------------- /js/tab.js: -------------------------------------------------------------------------------- 1 | /** ======================================================================= 2 | * Bootstrap: tab.js v4.0.0 3 | * http://getbootstrap.com/javascript/#tabs 4 | * ======================================================================== 5 | * Copyright 2011-2015 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== 8 | * @fileoverview - Bootstrap's tab plugin. Tab O_O 9 | * 10 | * Public Methods & Properties: 11 | * 12 | * + $.tab 13 | * + $.tab.noConflict 14 | * + $.tab.Constructor 15 | * + $.tab.Constructor.VERSION 16 | * + $.tab.Constructor.prototype.show 17 | * 18 | * ======================================================================== 19 | */ 20 | 21 | 22 | 'use strict'; 23 | 24 | /** 25 | * Our Tab class. 26 | * @param {Element!} element 27 | * @constructor 28 | */ 29 | var Tab = function (element) { 30 | 31 | /** @type {Element} */ 32 | this._element = element 33 | 34 | } 35 | 36 | 37 | /** 38 | * @const 39 | * @type {string} 40 | */ 41 | Tab['VERSION'] = '4.0.0' 42 | 43 | 44 | /** 45 | * @const 46 | * @type {string} 47 | * @private 48 | */ 49 | Tab._NAME = 'tab' 50 | 51 | 52 | /** 53 | * @const 54 | * @type {string} 55 | * @private 56 | */ 57 | Tab._DATA_KEY = 'bs.tab' 58 | 59 | 60 | /** 61 | * @const 62 | * @type {number} 63 | * @private 64 | */ 65 | Tab._TRANSITION_DURATION = 150 66 | 67 | 68 | /** 69 | * @const 70 | * @enum {string} 71 | * @private 72 | */ 73 | Tab._Event = { 74 | HIDE : 'hide.bs.tab', 75 | HIDDEN : 'hidden.bs.tab', 76 | SHOW : 'show.bs.tab', 77 | SHOWN : 'shown.bs.tab' 78 | } 79 | 80 | 81 | /** 82 | * @const 83 | * @enum {string} 84 | * @private 85 | */ 86 | Tab._ClassName = { 87 | DROPDOWN_MENU : 'dropdown-menu', 88 | ACTIVE : 'active', 89 | FADE : 'fade', 90 | IN : 'in' 91 | } 92 | 93 | 94 | /** 95 | * @const 96 | * @enum {string} 97 | * @private 98 | */ 99 | Tab._Selector = { 100 | A : 'a', 101 | LI : 'li', 102 | LI_DROPDOWN : 'li.dropdown', 103 | UL : 'ul:not(.dropdown-menu)', 104 | FADE_CHILD : '> .fade', 105 | ACTIVE : '.active', 106 | ACTIVE_CHILD : '> .active', 107 | DATA_TOGGLE : '[data-toggle="tab"], [data-toggle="pill"]', 108 | DROPDOWN_ACTIVE_CHILD : '> .dropdown-menu > .active' 109 | } 110 | 111 | 112 | /** 113 | * @param {Object|string=} opt_config 114 | * @this {jQuery} 115 | * @return {jQuery} 116 | * @private 117 | */ 118 | Tab._jQueryInterface = function (opt_config) { 119 | return this.each(function () { 120 | var $this = $(this) 121 | var data = $this.data(Tab._DATA_KEY) 122 | 123 | if (!data) { 124 | data = data = new Tab(this) 125 | $this.data(Tab._DATA_KEY, data) 126 | } 127 | 128 | if (typeof opt_config === 'string') { 129 | data[opt_config]() 130 | } 131 | }) 132 | } 133 | 134 | 135 | /** 136 | * Show the tab 137 | */ 138 | Tab.prototype['show'] = function () { 139 | if (this._element.parentNode 140 | && this._element.parentNode.nodeType == Node.ELEMENT_NODE 141 | && $(this._element).parent().hasClass(Tab._ClassName.ACTIVE)) { 142 | return 143 | } 144 | 145 | var target 146 | var previous 147 | var ulElement = $(this._element).closest(Tab._Selector.UL)[0] 148 | var selector = Bootstrap.getSelectorFromElement(this._element) 149 | 150 | if (ulElement) { 151 | previous = /** @type {Array.} */ ($.makeArray($(ulElement).find(Tab._Selector.ACTIVE))) 152 | previous = previous[previous.length - 1] 153 | 154 | if (previous) { 155 | previous = $(previous).find('a')[0] 156 | } 157 | } 158 | 159 | var hideEvent = $.Event(Tab._Event.HIDE, { 160 | relatedTarget: this._element 161 | }) 162 | 163 | var showEvent = $.Event(Tab._Event.SHOW, { 164 | relatedTarget: previous 165 | }) 166 | 167 | if (previous) { 168 | $(previous).trigger(hideEvent) 169 | } 170 | 171 | $(this._element).trigger(showEvent) 172 | 173 | if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return 174 | 175 | if (selector) { 176 | target = $(selector)[0] 177 | } 178 | 179 | this._activate($(this._element).closest(Tab._Selector.LI)[0], ulElement) 180 | 181 | var complete = function () { 182 | var hiddenEvent = $.Event(Tab._Event.HIDDEN, { 183 | relatedTarget: this._element 184 | }) 185 | 186 | var shownEvent = $.Event(Tab._Event.SHOWN, { 187 | relatedTarget: previous 188 | }) 189 | 190 | $(previous).trigger(hiddenEvent) 191 | $(this._element).trigger(shownEvent) 192 | }.bind(this) 193 | 194 | if (target) { 195 | this._activate(target, /** @type {Element} */ (target.parentNode), complete) 196 | } else { 197 | complete() 198 | } 199 | } 200 | 201 | 202 | /** 203 | * @param {Element} element 204 | * @param {Element} container 205 | * @param {Function=} opt_callback 206 | * @private 207 | */ 208 | Tab.prototype._activate = function (element, container, opt_callback) { 209 | var active = $(container).find(Tab._Selector.ACTIVE_CHILD)[0] 210 | var isTransitioning = opt_callback 211 | && Bootstrap.transition 212 | && ((active && $(active).hasClass(Tab._ClassName.FADE)) 213 | || !!$(container).find(Tab._Selector.FADE_CHILD)[0]) 214 | 215 | var complete = this._transitionComplete.bind(this, element, active, isTransitioning, opt_callback) 216 | 217 | if (active && isTransitioning) { 218 | $(active) 219 | .one(Bootstrap.TRANSITION_END, complete) 220 | .emulateTransitionEnd(Tab._TRANSITION_DURATION) 221 | 222 | } else { 223 | complete() 224 | } 225 | 226 | if (active) { 227 | $(active).removeClass(Tab._ClassName.IN) 228 | } 229 | } 230 | 231 | 232 | /** 233 | * @param {Element} element 234 | * @param {Element} active 235 | * @param {boolean} isTransitioning 236 | * @param {Function=} opt_callback 237 | * @private 238 | */ 239 | Tab.prototype._transitionComplete = function (element, active, isTransitioning, opt_callback) { 240 | if (active) { 241 | $(active).removeClass(Tab._ClassName.ACTIVE) 242 | 243 | var dropdownChild = $(active).find(Tab._Selector.DROPDOWN_ACTIVE_CHILD)[0] 244 | if (dropdownChild) { 245 | $(dropdownChild).removeClass(Tab._ClassName.ACTIVE) 246 | } 247 | 248 | var activeToggle = $(active).find(Tab._Selector.DATA_TOGGLE)[0] 249 | if (activeToggle) { 250 | activeToggle.setAttribute('aria-expanded', false) 251 | } 252 | } 253 | 254 | $(element).addClass(Tab._ClassName.ACTIVE) 255 | 256 | var elementToggle = $(element).find(Tab._Selector.DATA_TOGGLE)[0] 257 | if (elementToggle) { 258 | elementToggle.setAttribute('aria-expanded', true) 259 | } 260 | 261 | if (isTransitioning) { 262 | Bootstrap.reflow(element) 263 | $(element).addClass(Tab._ClassName.IN) 264 | } else { 265 | $(element).removeClass(Tab._ClassName.FADE) 266 | } 267 | 268 | if (element.parentNode && $(element.parentNode).hasClass(Tab._ClassName.DROPDOWN_MENU)) { 269 | var dropdownElement = $(element).closest(Tab._Selector.LI_DROPDOWN)[0] 270 | if (dropdownElement) { 271 | $(dropdownElement).addClass(Tab._ClassName.ACTIVE) 272 | } 273 | 274 | elementToggle = $(element).find(Tab._Selector.DATA_TOGGLE)[0] 275 | if (elementToggle) { 276 | elementToggle.setAttribute('aria-expanded', true) 277 | } 278 | } 279 | 280 | if (opt_callback) { 281 | opt_callback() 282 | } 283 | } 284 | 285 | 286 | /** 287 | * ------------------------------------------------------------------------ 288 | * jQuery Interface + noConflict implementaiton 289 | * ------------------------------------------------------------------------ 290 | */ 291 | 292 | /** 293 | * @const 294 | * @type {Function} 295 | */ 296 | $.fn[Tab._NAME] = Tab._jQueryInterface 297 | 298 | 299 | /** 300 | * @const 301 | * @type {Function} 302 | */ 303 | $.fn[Tab._NAME]['Constructor'] = Tab 304 | 305 | 306 | /** 307 | * @const 308 | * @type {Function} 309 | */ 310 | $.fn[Tab._NAME]['noConflict'] = function () { 311 | $.fn[Tab._NAME] = Tab._JQUERY_NO_CONFLICT 312 | return this 313 | } 314 | 315 | 316 | 317 | // TAB DATA-API 318 | // ============ 319 | 320 | var clickHandler = function (e) { 321 | e.preventDefault() 322 | Tab._jQueryInterface.call($(this), 'show') 323 | } 324 | 325 | $(document) 326 | .on('click.bs.tab.data-api', Tab._Selector.DATA_TOGGLE, clickHandler) 327 | -------------------------------------------------------------------------------- /js/tests/README.md: -------------------------------------------------------------------------------- 1 | ## How does Bootstrap's test suite work? 2 | 3 | Bootstrap uses [QUnit](http://api.qunitjs.com/), a powerful, easy-to-use JavaScript unit test framework. Each plugin has a file dedicated to its tests in `unit/.js`. 4 | 5 | * `unit/` contains the unit test files for each Bootstrap plugin. 6 | * `vendor/` contains third-party testing-related code (QUnit and jQuery). 7 | * `visual/` contains "visual" tests which are run interactively in real browsers and require manual verification by humans. 8 | 9 | To run the unit test suite via [PhantomJS](http://phantomjs.org/), run `grunt test-js`. 10 | 11 | To run the unit test suite via a real web browser, open `index.html` in the browser. 12 | 13 | 14 | ## How do I add a new unit test? 15 | 16 | 1. Locate and open the file dedicated to the plugin which you need to add tests to (`unit/.js`). 17 | 2. Review the [QUnit API Documentation](http://api.qunitjs.com/) and use the existing tests as references for how to structure your new tests. 18 | 3. Write the necessary unit test(s) for the new or revised functionality. 19 | 4. Run `grunt test-js` to see the results of your newly-added test(s). 20 | 21 | **Note:** Your new unit tests should fail before your changes are applied to the plugin, and should pass after your changes are applied to the plugin. 22 | 23 | ## What should a unit test look like? 24 | 25 | * Each test should have a unique name clearly stating what unit is being tested. 26 | * Each test should test only one unit per test, although one test can include several assertions. Create multiple tests for multiple units of functionality. 27 | * Each test should begin with [`assert.expect`](http://api.qunitjs.com/expect/) to ensure that the expected assertions are run. 28 | * Each test should follow the project's [JavaScript Code Guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md#js) 29 | 30 | ### Example tests 31 | 32 | ```javascript 33 | // Synchronous test 34 | QUnit.test('should describe the unit being tested', function (assert) { 35 | assert.expect(1) 36 | var templateHTML = '
' 37 | + '×' 38 | + '

Template necessary for the test.

' 39 | + '
' 40 | var $alert = $(templateHTML).appendTo('#qunit-fixture').bootstrapAlert() 41 | 42 | $alert.find('.close').click() 43 | 44 | // Make assertion 45 | assert.strictEqual($alert.hasClass('in'), false, 'remove .in class on .close click') 46 | }) 47 | 48 | // Asynchronous test 49 | QUnit.test('should describe the unit being tested', function (assert) { 50 | assert.expect(1) 51 | var done = assert.async() 52 | 53 | $('
') 54 | .appendTo('#qunit-fixture') 55 | .on('shown.bs.tooltip', function () { 56 | assert.ok(true, '"shown" event was fired after calling "show"') 57 | done() 58 | }) 59 | .bootstrapTooltip('show') 60 | }) 61 | ``` 62 | -------------------------------------------------------------------------------- /js/tests/closure.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bootstrap Plugin Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 24 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
79 |
80 |
81 |
82 | 83 | 84 | -------------------------------------------------------------------------------- /js/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bootstrap Plugin Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 23 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
85 |
86 |
87 |
88 | 89 | -------------------------------------------------------------------------------- /js/tests/unit/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends" : "../../.jshintrc", 3 | "devel" : true, 4 | "es3" : false, 5 | "qunit" : true 6 | } 7 | -------------------------------------------------------------------------------- /js/tests/unit/alert.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 'use strict'; 3 | 4 | module('alert plugin') 5 | 6 | test('should be defined on jquery object', function () { 7 | ok($(document.body).alert, 'alert method is defined') 8 | }) 9 | 10 | module('alert', { 11 | setup: function () { 12 | // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode 13 | $.fn.bootstrapAlert = $.fn.alert.noConflict() 14 | }, 15 | teardown: function () { 16 | $.fn.alert = $.fn.bootstrapAlert 17 | delete $.fn.bootstrapAlert 18 | } 19 | }) 20 | 21 | test('should provide no conflict', function () { 22 | strictEqual($.fn.alert, undefined, 'alert was set back to undefined (org value)') 23 | }) 24 | 25 | test('should return jquery collection containing the element', function () { 26 | var $el = $('
') 27 | var $alert = $el.bootstrapAlert() 28 | ok($alert instanceof $, 'returns jquery collection') 29 | strictEqual($alert[0], $el[0], 'collection contains element') 30 | }) 31 | 32 | test('should fade element out on clicking .close', function () { 33 | var alertHTML = '
' 34 | + '×' 35 | + '

Holy guacamole! Best check yo self, you\'re not looking too good.

' 36 | + '
' 37 | var $alert = $(alertHTML).bootstrapAlert() 38 | 39 | $alert.find('.close').click() 40 | 41 | equal($alert.hasClass('in'), false, 'remove .in class on .close click') 42 | }) 43 | 44 | test('should remove element when clicking .close', function () { 45 | var alertHTML = '
' 46 | + '×' 47 | + '

Holy guacamole! Best check yo self, you\'re not looking too good.

' 48 | + '
' 49 | var $alert = $(alertHTML).appendTo('#qunit-fixture').bootstrapAlert() 50 | 51 | notEqual($('#qunit-fixture').find('.alert').length, 0, 'element added to dom') 52 | 53 | $alert.find('.close').click() 54 | 55 | equal($('#qunit-fixture').find('.alert').length, 0, 'element removed from dom') 56 | }) 57 | 58 | test('should not fire closed when close is prevented', function (assert) { 59 | var done = assert.async() 60 | $('
') 61 | .on('close.bs.alert', function (e) { 62 | e.preventDefault() 63 | ok(true, 'close event fired') 64 | done() 65 | }) 66 | .on('closed.bs.alert', function () { 67 | ok(false, 'closed event fired') 68 | }) 69 | .bootstrapAlert('close') 70 | }) 71 | 72 | }) 73 | -------------------------------------------------------------------------------- /js/tests/unit/button.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 'use strict'; 3 | 4 | module('button plugin') 5 | 6 | test('should be defined on jquery object', function () { 7 | ok($(document.body).button, 'button method is defined') 8 | }) 9 | 10 | module('button', { 11 | setup: function () { 12 | // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode 13 | $.fn.bootstrapButton = $.fn.button.noConflict() 14 | }, 15 | teardown: function () { 16 | $.fn.button = $.fn.bootstrapButton 17 | delete $.fn.bootstrapButton 18 | } 19 | }) 20 | 21 | test('should provide no conflict', function () { 22 | strictEqual($.fn.button, undefined, 'button was set back to undefined (org value)') 23 | }) 24 | 25 | test('should return jquery collection containing the element', function () { 26 | var $el = $('
') 27 | var $button = $el.bootstrapButton() 28 | ok($button instanceof $, 'returns jquery collection') 29 | strictEqual($button[0], $el[0], 'collection contains element') 30 | }) 31 | 32 | test('should toggle active', function () { 33 | var $btn = $('') 34 | ok(!$btn.hasClass('active'), 'btn does not have active class') 35 | $btn.bootstrapButton('toggle') 36 | ok($btn.hasClass('active'), 'btn has class active') 37 | }) 38 | 39 | test('should toggle active when btn children are clicked', function () { 40 | var $btn = $('') 41 | var $inner = $('') 42 | $btn 43 | .append($inner) 44 | .appendTo('#qunit-fixture') 45 | ok(!$btn.hasClass('active'), 'btn does not have active class') 46 | $inner.click() 47 | ok($btn.hasClass('active'), 'btn has class active') 48 | }) 49 | 50 | test('should toggle aria-pressed', function () { 51 | var $btn = $('') 52 | equal($btn.attr('aria-pressed'), 'false', 'btn aria-pressed state is false') 53 | $btn.bootstrapButton('toggle') 54 | equal($btn.attr('aria-pressed'), 'true', 'btn aria-pressed state is true') 55 | }) 56 | 57 | test('should toggle aria-pressed when btn children are clicked', function () { 58 | var $btn = $('') 59 | var $inner = $('') 60 | $btn 61 | .append($inner) 62 | .appendTo('#qunit-fixture') 63 | equal($btn.attr('aria-pressed'), 'false', 'btn aria-pressed state is false') 64 | $inner.click() 65 | equal($btn.attr('aria-pressed'), 'true', 'btn aria-pressed state is true') 66 | }) 67 | 68 | test('should toggle active when btn children are clicked within btn-group', function () { 69 | var $btngroup = $('
') 70 | var $btn = $('') 71 | var $inner = $('') 72 | $btngroup 73 | .append($btn.append($inner)) 74 | .appendTo('#qunit-fixture') 75 | ok(!$btn.hasClass('active'), 'btn does not have active class') 76 | $inner.click() 77 | ok($btn.hasClass('active'), 'btn has class active') 78 | }) 79 | 80 | test('should check for closest matching toggle', function () { 81 | var groupHTML = '
' 82 | + '' 85 | + '' 88 | + '' 91 | + '
' 92 | var $group = $(groupHTML).appendTo('#qunit-fixture') 93 | 94 | var $btn1 = $group.children().eq(0) 95 | var $btn2 = $group.children().eq(1) 96 | 97 | ok($btn1.hasClass('active'), 'btn1 has active class') 98 | ok($btn1.find('input').prop('checked'), 'btn1 is checked') 99 | ok(!$btn2.hasClass('active'), 'btn2 does not have active class') 100 | ok(!$btn2.find('input').prop('checked'), 'btn2 is not checked') 101 | $btn2.find('input').click() 102 | ok(!$btn1.hasClass('active'), 'btn1 does not have active class') 103 | ok(!$btn1.find('input').prop('checked'), 'btn1 is checked') 104 | ok($btn2.hasClass('active'), 'btn2 has active class') 105 | ok($btn2.find('input').prop('checked'), 'btn2 is checked') 106 | 107 | $btn2.find('input').click() // clicking an already checked radio should not un-check it 108 | ok(!$btn1.hasClass('active'), 'btn1 does not have active class') 109 | ok(!$btn1.find('input').prop('checked'), 'btn1 is checked') 110 | ok($btn2.hasClass('active'), 'btn2 has active class') 111 | ok($btn2.find('input').prop('checked'), 'btn2 is checked') 112 | }) 113 | 114 | }) 115 | -------------------------------------------------------------------------------- /js/tests/unit/collapse.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 'use strict'; 3 | 4 | module('collapse plugin') 5 | 6 | test('should be defined on jquery object', function () { 7 | ok($(document.body).collapse, 'collapse method is defined') 8 | }) 9 | 10 | module('collapse', { 11 | setup: function () { 12 | // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode 13 | $.fn.bootstrapCollapse = $.fn.collapse.noConflict() 14 | }, 15 | teardown: function () { 16 | $.fn.collapse = $.fn.bootstrapCollapse 17 | delete $.fn.bootstrapCollapse 18 | } 19 | }) 20 | 21 | test('should provide no conflict', function () { 22 | strictEqual($.fn.collapse, undefined, 'collapse was set back to undefined (org value)') 23 | }) 24 | 25 | test('should return jquery collection containing the element', function () { 26 | var $el = $('
') 27 | var $collapse = $el.bootstrapCollapse() 28 | ok($collapse instanceof $, 'returns jquery collection') 29 | strictEqual($collapse[0], $el[0], 'collection contains element') 30 | }) 31 | 32 | test('should show a collapsed element', function () { 33 | var $el = $('
').bootstrapCollapse('show') 34 | 35 | ok($el.hasClass('in'), 'has class "in"') 36 | ok(!/height/i.test($el.attr('style')), 'has height reset') 37 | }) 38 | 39 | test('should hide a collapsed element', function () { 40 | var $el = $('
').bootstrapCollapse('hide') 41 | 42 | ok(!$el.hasClass('in'), 'does not have class "in"') 43 | ok(/height/i.test($el.attr('style')), 'has height set') 44 | }) 45 | 46 | test('should not fire shown when show is prevented', function (assert) { 47 | var done = assert.async() 48 | 49 | $('
') 50 | .on('show.bs.collapse', function (e) { 51 | e.preventDefault() 52 | ok(true, 'show event fired') 53 | done() 54 | }) 55 | .on('shown.bs.collapse', function () { 56 | ok(false, 'shown event fired') 57 | }) 58 | .bootstrapCollapse('show') 59 | }) 60 | 61 | test('should reset style to auto after finishing opening collapse', function (assert) { 62 | var done = assert.async() 63 | 64 | $('
') 65 | .on('show.bs.collapse', function () { 66 | equal(this.style.height, '0px', 'height is 0px') 67 | }) 68 | .on('shown.bs.collapse', function () { 69 | strictEqual(this.style.height, '', 'height is auto') 70 | done() 71 | }) 72 | .bootstrapCollapse('show') 73 | }) 74 | 75 | test('should remove "collapsed" class from target when collapse is shown', function (assert) { 76 | var done = assert.async() 77 | 78 | var $target = $('
').appendTo('#qunit-fixture') 94 | 95 | $('
') 96 | .appendTo('#qunit-fixture') 97 | .on('hidden.bs.collapse', function () { 98 | ok($target.hasClass('collapsed')) 99 | done() 100 | }) 101 | 102 | $target.click() 103 | }) 104 | 105 | test('should not close a collapse when initialized with "show" if already shown', function (assert) { 106 | var done = assert.async() 107 | 108 | expect(0) 109 | 110 | var $test = $('
') 111 | .appendTo('#qunit-fixture') 112 | .on('hide.bs.collapse', function () { 113 | ok(false) 114 | }) 115 | 116 | $test.bootstrapCollapse('show') 117 | 118 | setTimeout(done, 0) 119 | }) 120 | 121 | test('should open a collapse when initialized with "show" if not already shown', function (assert) { 122 | var done = assert.async() 123 | 124 | expect(1) 125 | 126 | var $test = $('
') 127 | .appendTo('#qunit-fixture') 128 | .on('show.bs.collapse', function () { 129 | ok(true) 130 | }) 131 | 132 | $test.bootstrapCollapse('show') 133 | 134 | setTimeout(done, 0) 135 | }) 136 | 137 | test('should remove "collapsed" class from active accordion target', function (assert) { 138 | var done = assert.async() 139 | 140 | var accordionHTML = '
' 141 | + '
' 142 | + '
' 143 | + '
' 144 | + '
' 145 | var $groups = $(accordionHTML).appendTo('#qunit-fixture').find('.panel') 146 | 147 | var $target1 = $('
').appendTo($groups.eq(0)) 148 | 149 | $('
').appendTo($groups.eq(0)) 150 | 151 | var $target2 = $('
').appendTo($groups.eq(0)) 181 | 182 | $('
').appendTo($groups.eq(0)) 183 | 184 | var $target2 = $('
').appendTo('#qunit-fixture') 222 | 223 | $('
') 224 | .appendTo('#qunit-fixture') 225 | .on('hidden.bs.collapse', function () { 226 | equal($target.attr('aria-expanded'), 'false', 'aria-expanded on target is "false"') 227 | done() 228 | }) 229 | 230 | $target.click() 231 | }) 232 | 233 | test('should change aria-expanded from active accordion target to "false" and set the newly active one to "true"', function (assert) { 234 | var done = assert.async() 235 | 236 | var accordionHTML = '
' 237 | + '
' 238 | + '
' 239 | + '
' 240 | + '
' 241 | var $groups = $(accordionHTML).appendTo('#qunit-fixture').find('.panel') 242 | 243 | var $target1 = $('
').appendTo($groups.eq(0)) 244 | 245 | $('
').appendTo($groups.eq(0)) 246 | 247 | var $target2 = $('