├── .gitignore ├── .npm2gem.yml ├── .rspec ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib └── material_design_lite │ ├── rails.rb │ └── rails │ └── version.rb ├── material_design_lite-rails.gemspec └── vendor └── assets ├── javascripts └── material.js └── stylesheets └── material.css /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | /node_modules 11 | -------------------------------------------------------------------------------- /.npm2gem.yml: -------------------------------------------------------------------------------- 1 | # See: https://github.com/cllns/npm2gem 2 | material-design-lite: 3 | - material.js 4 | - material.css 5 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, 4 | we pledge to respect all people who contribute through reporting issues, 5 | posting feature requests, updating documentation, 6 | submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project 9 | a harassment-free experience for everyone, 10 | regardless of 11 | level of experience, 12 | gender, 13 | gender identity and expression, 14 | sexual orientation, 15 | disability, 16 | personal appearance, 17 | body size, 18 | race, 19 | age, 20 | or religion. 21 | 22 | Examples of unacceptable behavior by participants include 23 | the use of sexual language or imagery, 24 | derogatory comments or personal attacks, 25 | trolling, 26 | public or private harassment, 27 | insults, 28 | or other unprofessional conduct. 29 | 30 | Project maintainers have the right and responsibility 31 | to remove, edit, or reject comments, 32 | commits, 33 | code, 34 | wiki edits, 35 | issues, 36 | and other contributions that are not aligned to this Code of Conduct. 37 | Project maintainers who do not follow the Code of Conduct 38 | may be removed from the project team. 39 | 40 | Instances of abusive, harassing, or otherwise unacceptable behavior 41 | may be reported by 42 | opening an issue or 43 | contacting one or more of the project maintainers. 44 | 45 | This Code of Conduct is adapted from the 46 | [Contributor Covenant](http:contributor-covenant.org), 47 | version 1.0.0, available at 48 | [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 49 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in material_design_lite-rails.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sean Collins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Material Design Lite, for Rails! 2 | 3 | A gemified version of [Google's Material Design Lite](http://www.getmdl.io/) library. 4 | 5 | ## Installation 6 | 7 | To your Rails application's Gemfile, add 8 | 9 | ```ruby 10 | gem 'material_design_lite-rails', '~> 1.3' 11 | ``` 12 | 13 | And then run 14 | 15 | $ bundle 16 | 17 | #### Javascripts 18 | 19 | To your `application.js` file, add: 20 | 21 | ``` 22 | //= require material 23 | ``` 24 | 25 | #### Stylesheets 26 | 27 | Do one of the following: 28 | 29 | To your `application.css` , add 30 | ``` 31 | *= require material 32 | ``` 33 | 34 | **OR** 35 | 36 | If you're using sass, use sass's 37 | [`@import`](https://github.com/rails/sass-rails#important-note) 38 | in your `application.scss`. 39 | 40 | ``` 41 | @import "material"; 42 | ``` 43 | 44 | This gem only provides the compiled CSS file from 45 | [`google/material-design-lite`](https://github.com/google/material-design-lite). 46 | 47 | If you're looking for the individual Sass files, 48 | so you can take only parts of Material Design Lite, 49 | you should use 50 | the [`rubysamurai/material_design_lite-sass`](https://github.com/rubysamurai/material_design_lite-sass) 51 | gem instead. 52 | 53 | #### Icons 54 | Material Design Lite uses a font called 'Material Icons'. 55 | You can either load this font from google, or host it yourself. 56 | 57 | ##### Load font from google 58 | Add the following line to your `application.html.erb` view layout file, 59 | in the `` section: 60 | 61 | ``` 62 | <%= stylesheet_link_tag "https://fonts.googleapis.com/icon?family=Material+Icons" %> 63 | ``` 64 | 65 | **OR** 66 | 67 | ##### Host font locally 68 | Use the `material_icons` gem to [host the font locally](https://github.com/Angelmmiguel/material_icons). 69 | 70 | ## Versioning 71 | 72 | This gem is versioned semantically, 73 | in line with 74 | [`google/material-design-lite`](https://github.com/google/material-design-lite) 75 | 76 | If there needs to be a release of this gem without a corresponding release to 77 | `google/material-design-lite'` to the repo, an additional digit will be added 78 | (so if this gem's version is `1.0.0.1`, google's version would still be `1.0.0`). 79 | 80 | The first three digits will always be the same as `google/material-design-lite`. 81 | 82 | ## TODO: 83 | 84 | - [ ] Add tests (make sure CSS/JS loads, and check version) 85 | - [ ] Add view helpers, to ease burden of manually adding all the classes. 86 | - [ ] Fix issue where you need to manually create `node_modules` directory 87 | 88 | ## Contributing 89 | 90 | 1. Fork it ( https://github.com/cllns/material_design_lite-rails/fork ) 91 | 2. Create your feature branch (`git checkout -b my-new-feature`) 92 | 3. Commit your changes (`git commit -am 'Add some feature'`) 93 | 4. Push to the branch (`git push origin my-new-feature`) 94 | 5. Create a new Pull Request 95 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /lib/material_design_lite/rails.rb: -------------------------------------------------------------------------------- 1 | require "material_design_lite/rails/version" 2 | 3 | module MaterialDesignLite 4 | module Rails 5 | class Engine < ::Rails::Engine 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/material_design_lite/rails/version.rb: -------------------------------------------------------------------------------- 1 | module MaterialDesignLite 2 | module Rails 3 | VERSION = "1.3.0" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /material_design_lite-rails.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'material_design_lite/rails/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "material_design_lite-rails" 8 | spec.version = MaterialDesignLite::Rails::VERSION 9 | spec.authors = ["Sean Collins"] 10 | spec.email = ["sean@cllns.com"] 11 | 12 | spec.summary = %q{Material Design Lite, for Rails} 13 | spec.homepage = "https://github.com/cllns/material_design_lite-rails" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 17 | spec.require_paths = ["lib"] 18 | 19 | spec.add_dependency "railties", ">= 3.1" 20 | 21 | spec.add_development_dependency "bundler", "~> 1.9" 22 | spec.add_development_dependency "rake", "~> 10.0" 23 | end 24 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/material.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | "use strict"; 3 | 4 | /** 5 | * @license 6 | * Copyright 2015 Google Inc. All Rights Reserved. 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | /** 22 | * A component handler interface using the revealing module design pattern. 23 | * More details on this design pattern here: 24 | * https://github.com/jasonmayes/mdl-component-design-pattern 25 | * 26 | * @author Jason Mayes. 27 | */ 28 | /* exported componentHandler */ 29 | 30 | // Pre-defining the componentHandler interface, for closure documentation and 31 | // static verification. 32 | var componentHandler = { 33 | /** 34 | * Searches existing DOM for elements of our component type and upgrades them 35 | * if they have not already been upgraded. 36 | * 37 | * @param {string=} optJsClass the programatic name of the element class we 38 | * need to create a new instance of. 39 | * @param {string=} optCssClass the name of the CSS class elements of this 40 | * type will have. 41 | */ 42 | upgradeDom: function(optJsClass, optCssClass) {}, 43 | /** 44 | * Upgrades a specific element rather than all in the DOM. 45 | * 46 | * @param {!Element} element The element we wish to upgrade. 47 | * @param {string=} optJsClass Optional name of the class we want to upgrade 48 | * the element to. 49 | */ 50 | upgradeElement: function(element, optJsClass) {}, 51 | /** 52 | * Upgrades a specific list of elements rather than all in the DOM. 53 | * 54 | * @param {!Element|!Array|!NodeList|!HTMLCollection} elements 55 | * The elements we wish to upgrade. 56 | */ 57 | upgradeElements: function(elements) {}, 58 | /** 59 | * Upgrades all registered components found in the current DOM. This is 60 | * automatically called on window load. 61 | */ 62 | upgradeAllRegistered: function() {}, 63 | /** 64 | * Allows user to be alerted to any upgrades that are performed for a given 65 | * component type 66 | * 67 | * @param {string} jsClass The class name of the MDL component we wish 68 | * to hook into for any upgrades performed. 69 | * @param {function(!HTMLElement)} callback The function to call upon an 70 | * upgrade. This function should expect 1 parameter - the HTMLElement which 71 | * got upgraded. 72 | */ 73 | registerUpgradedCallback: function(jsClass, callback) {}, 74 | /** 75 | * Registers a class for future use and attempts to upgrade existing DOM. 76 | * 77 | * @param {componentHandler.ComponentConfigPublic} config the registration configuration 78 | */ 79 | register: function(config) {}, 80 | /** 81 | * Downgrade either a given node, an array of nodes, or a NodeList. 82 | * 83 | * @param {!Node|!Array|!NodeList} nodes 84 | */ 85 | downgradeElements: function(nodes) {} 86 | }; 87 | 88 | componentHandler = (function() { 89 | 'use strict'; 90 | 91 | /** @type {!Array} */ 92 | var registeredComponents_ = []; 93 | 94 | /** @type {!Array} */ 95 | var createdComponents_ = []; 96 | 97 | var componentConfigProperty_ = 'mdlComponentConfigInternal_'; 98 | 99 | /** 100 | * Searches registered components for a class we are interested in using. 101 | * Optionally replaces a match with passed object if specified. 102 | * 103 | * @param {string} name The name of a class we want to use. 104 | * @param {componentHandler.ComponentConfig=} optReplace Optional object to replace match with. 105 | * @return {!Object|boolean} 106 | * @private 107 | */ 108 | function findRegisteredClass_(name, optReplace) { 109 | for (var i = 0; i < registeredComponents_.length; i++) { 110 | if (registeredComponents_[i].className === name) { 111 | if (typeof optReplace !== 'undefined') { 112 | registeredComponents_[i] = optReplace; 113 | } 114 | return registeredComponents_[i]; 115 | } 116 | } 117 | return false; 118 | } 119 | 120 | /** 121 | * Returns an array of the classNames of the upgraded classes on the element. 122 | * 123 | * @param {!Element} element The element to fetch data from. 124 | * @return {!Array} 125 | * @private 126 | */ 127 | function getUpgradedListOfElement_(element) { 128 | var dataUpgraded = element.getAttribute('data-upgraded'); 129 | // Use `['']` as default value to conform the `,name,name...` style. 130 | return dataUpgraded === null ? [''] : dataUpgraded.split(','); 131 | } 132 | 133 | /** 134 | * Returns true if the given element has already been upgraded for the given 135 | * class. 136 | * 137 | * @param {!Element} element The element we want to check. 138 | * @param {string} jsClass The class to check for. 139 | * @returns {boolean} 140 | * @private 141 | */ 142 | function isElementUpgraded_(element, jsClass) { 143 | var upgradedList = getUpgradedListOfElement_(element); 144 | return upgradedList.indexOf(jsClass) !== -1; 145 | } 146 | 147 | /** 148 | * Create an event object. 149 | * 150 | * @param {string} eventType The type name of the event. 151 | * @param {boolean} bubbles Whether the event should bubble up the DOM. 152 | * @param {boolean} cancelable Whether the event can be canceled. 153 | * @returns {!Event} 154 | */ 155 | function createEvent_(eventType, bubbles, cancelable) { 156 | if ('CustomEvent' in window && typeof window.CustomEvent === 'function') { 157 | return new CustomEvent(eventType, { 158 | bubbles: bubbles, 159 | cancelable: cancelable 160 | }); 161 | } else { 162 | var ev = document.createEvent('Events'); 163 | ev.initEvent(eventType, bubbles, cancelable); 164 | return ev; 165 | } 166 | } 167 | 168 | /** 169 | * Searches existing DOM for elements of our component type and upgrades them 170 | * if they have not already been upgraded. 171 | * 172 | * @param {string=} optJsClass the programatic name of the element class we 173 | * need to create a new instance of. 174 | * @param {string=} optCssClass the name of the CSS class elements of this 175 | * type will have. 176 | */ 177 | function upgradeDomInternal(optJsClass, optCssClass) { 178 | if (typeof optJsClass === 'undefined' && 179 | typeof optCssClass === 'undefined') { 180 | for (var i = 0; i < registeredComponents_.length; i++) { 181 | upgradeDomInternal(registeredComponents_[i].className, 182 | registeredComponents_[i].cssClass); 183 | } 184 | } else { 185 | var jsClass = /** @type {string} */ (optJsClass); 186 | if (typeof optCssClass === 'undefined') { 187 | var registeredClass = findRegisteredClass_(jsClass); 188 | if (registeredClass) { 189 | optCssClass = registeredClass.cssClass; 190 | } 191 | } 192 | 193 | var elements = document.querySelectorAll('.' + optCssClass); 194 | for (var n = 0; n < elements.length; n++) { 195 | upgradeElementInternal(elements[n], jsClass); 196 | } 197 | } 198 | } 199 | 200 | /** 201 | * Upgrades a specific element rather than all in the DOM. 202 | * 203 | * @param {!Element} element The element we wish to upgrade. 204 | * @param {string=} optJsClass Optional name of the class we want to upgrade 205 | * the element to. 206 | */ 207 | function upgradeElementInternal(element, optJsClass) { 208 | // Verify argument type. 209 | if (!(typeof element === 'object' && element instanceof Element)) { 210 | throw new Error('Invalid argument provided to upgrade MDL element.'); 211 | } 212 | // Allow upgrade to be canceled by canceling emitted event. 213 | var upgradingEv = createEvent_('mdl-componentupgrading', true, true); 214 | element.dispatchEvent(upgradingEv); 215 | if (upgradingEv.defaultPrevented) { 216 | return; 217 | } 218 | 219 | var upgradedList = getUpgradedListOfElement_(element); 220 | var classesToUpgrade = []; 221 | // If jsClass is not provided scan the registered components to find the 222 | // ones matching the element's CSS classList. 223 | if (!optJsClass) { 224 | var classList = element.classList; 225 | registeredComponents_.forEach(function(component) { 226 | // Match CSS & Not to be upgraded & Not upgraded. 227 | if (classList.contains(component.cssClass) && 228 | classesToUpgrade.indexOf(component) === -1 && 229 | !isElementUpgraded_(element, component.className)) { 230 | classesToUpgrade.push(component); 231 | } 232 | }); 233 | } else if (!isElementUpgraded_(element, optJsClass)) { 234 | classesToUpgrade.push(findRegisteredClass_(optJsClass)); 235 | } 236 | 237 | // Upgrade the element for each classes. 238 | for (var i = 0, n = classesToUpgrade.length, registeredClass; i < n; i++) { 239 | registeredClass = classesToUpgrade[i]; 240 | if (registeredClass) { 241 | // Mark element as upgraded. 242 | upgradedList.push(registeredClass.className); 243 | element.setAttribute('data-upgraded', upgradedList.join(',')); 244 | var instance = new registeredClass.classConstructor(element); 245 | instance[componentConfigProperty_] = registeredClass; 246 | createdComponents_.push(instance); 247 | // Call any callbacks the user has registered with this component type. 248 | for (var j = 0, m = registeredClass.callbacks.length; j < m; j++) { 249 | registeredClass.callbacks[j](element); 250 | } 251 | 252 | if (registeredClass.widget) { 253 | // Assign per element instance for control over API 254 | element[registeredClass.className] = instance; 255 | } 256 | } else { 257 | throw new Error( 258 | 'Unable to find a registered component for the given class.'); 259 | } 260 | 261 | var upgradedEv = createEvent_('mdl-componentupgraded', true, false); 262 | element.dispatchEvent(upgradedEv); 263 | } 264 | } 265 | 266 | /** 267 | * Upgrades a specific list of elements rather than all in the DOM. 268 | * 269 | * @param {!Element|!Array|!NodeList|!HTMLCollection} elements 270 | * The elements we wish to upgrade. 271 | */ 272 | function upgradeElementsInternal(elements) { 273 | if (!Array.isArray(elements)) { 274 | if (elements instanceof Element) { 275 | elements = [elements]; 276 | } else { 277 | elements = Array.prototype.slice.call(elements); 278 | } 279 | } 280 | for (var i = 0, n = elements.length, element; i < n; i++) { 281 | element = elements[i]; 282 | if (element instanceof HTMLElement) { 283 | upgradeElementInternal(element); 284 | if (element.children.length > 0) { 285 | upgradeElementsInternal(element.children); 286 | } 287 | } 288 | } 289 | } 290 | 291 | /** 292 | * Registers a class for future use and attempts to upgrade existing DOM. 293 | * 294 | * @param {componentHandler.ComponentConfigPublic} config 295 | */ 296 | function registerInternal(config) { 297 | // In order to support both Closure-compiled and uncompiled code accessing 298 | // this method, we need to allow for both the dot and array syntax for 299 | // property access. You'll therefore see the `foo.bar || foo['bar']` 300 | // pattern repeated across this method. 301 | var widgetMissing = (typeof config.widget === 'undefined' && 302 | typeof config['widget'] === 'undefined'); 303 | var widget = true; 304 | 305 | if (!widgetMissing) { 306 | widget = config.widget || config['widget']; 307 | } 308 | 309 | var newConfig = /** @type {componentHandler.ComponentConfig} */ ({ 310 | classConstructor: config.constructor || config['constructor'], 311 | className: config.classAsString || config['classAsString'], 312 | cssClass: config.cssClass || config['cssClass'], 313 | widget: widget, 314 | callbacks: [] 315 | }); 316 | 317 | registeredComponents_.forEach(function(item) { 318 | if (item.cssClass === newConfig.cssClass) { 319 | throw new Error('The provided cssClass has already been registered: ' + item.cssClass); 320 | } 321 | if (item.className === newConfig.className) { 322 | throw new Error('The provided className has already been registered'); 323 | } 324 | }); 325 | 326 | if (config.constructor.prototype 327 | .hasOwnProperty(componentConfigProperty_)) { 328 | throw new Error( 329 | 'MDL component classes must not have ' + componentConfigProperty_ + 330 | ' defined as a property.'); 331 | } 332 | 333 | var found = findRegisteredClass_(config.classAsString, newConfig); 334 | 335 | if (!found) { 336 | registeredComponents_.push(newConfig); 337 | } 338 | } 339 | 340 | /** 341 | * Allows user to be alerted to any upgrades that are performed for a given 342 | * component type 343 | * 344 | * @param {string} jsClass The class name of the MDL component we wish 345 | * to hook into for any upgrades performed. 346 | * @param {function(!HTMLElement)} callback The function to call upon an 347 | * upgrade. This function should expect 1 parameter - the HTMLElement which 348 | * got upgraded. 349 | */ 350 | function registerUpgradedCallbackInternal(jsClass, callback) { 351 | var regClass = findRegisteredClass_(jsClass); 352 | if (regClass) { 353 | regClass.callbacks.push(callback); 354 | } 355 | } 356 | 357 | /** 358 | * Upgrades all registered components found in the current DOM. This is 359 | * automatically called on window load. 360 | */ 361 | function upgradeAllRegisteredInternal() { 362 | for (var n = 0; n < registeredComponents_.length; n++) { 363 | upgradeDomInternal(registeredComponents_[n].className); 364 | } 365 | } 366 | 367 | /** 368 | * Check the component for the downgrade method. 369 | * Execute if found. 370 | * Remove component from createdComponents list. 371 | * 372 | * @param {?componentHandler.Component} component 373 | */ 374 | function deconstructComponentInternal(component) { 375 | if (component) { 376 | var componentIndex = createdComponents_.indexOf(component); 377 | createdComponents_.splice(componentIndex, 1); 378 | 379 | var upgrades = component.element_.getAttribute('data-upgraded').split(','); 380 | var componentPlace = upgrades.indexOf(component[componentConfigProperty_].classAsString); 381 | upgrades.splice(componentPlace, 1); 382 | component.element_.setAttribute('data-upgraded', upgrades.join(',')); 383 | 384 | var ev = createEvent_('mdl-componentdowngraded', true, false); 385 | component.element_.dispatchEvent(ev); 386 | } 387 | } 388 | 389 | /** 390 | * Downgrade either a given node, an array of nodes, or a NodeList. 391 | * 392 | * @param {!Node|!Array|!NodeList} nodes 393 | */ 394 | function downgradeNodesInternal(nodes) { 395 | /** 396 | * Auxiliary function to downgrade a single node. 397 | * @param {!Node} node the node to be downgraded 398 | */ 399 | var downgradeNode = function(node) { 400 | createdComponents_.filter(function(item) { 401 | return item.element_ === node; 402 | }).forEach(deconstructComponentInternal); 403 | }; 404 | if (nodes instanceof Array || nodes instanceof NodeList) { 405 | for (var n = 0; n < nodes.length; n++) { 406 | downgradeNode(nodes[n]); 407 | } 408 | } else if (nodes instanceof Node) { 409 | downgradeNode(nodes); 410 | } else { 411 | throw new Error('Invalid argument provided to downgrade MDL nodes.'); 412 | } 413 | } 414 | 415 | // Now return the functions that should be made public with their publicly 416 | // facing names... 417 | return { 418 | upgradeDom: upgradeDomInternal, 419 | upgradeElement: upgradeElementInternal, 420 | upgradeElements: upgradeElementsInternal, 421 | upgradeAllRegistered: upgradeAllRegisteredInternal, 422 | registerUpgradedCallback: registerUpgradedCallbackInternal, 423 | register: registerInternal, 424 | downgradeElements: downgradeNodesInternal 425 | }; 426 | })(); 427 | 428 | /** 429 | * Describes the type of a registered component type managed by 430 | * componentHandler. Provided for benefit of the Closure compiler. 431 | * 432 | * @typedef {{ 433 | * constructor: Function, 434 | * classAsString: string, 435 | * cssClass: string, 436 | * widget: (string|boolean|undefined) 437 | * }} 438 | */ 439 | componentHandler.ComponentConfigPublic; // jshint ignore:line 440 | 441 | /** 442 | * Describes the type of a registered component type managed by 443 | * componentHandler. Provided for benefit of the Closure compiler. 444 | * 445 | * @typedef {{ 446 | * constructor: !Function, 447 | * className: string, 448 | * cssClass: string, 449 | * widget: (string|boolean), 450 | * callbacks: !Array 451 | * }} 452 | */ 453 | componentHandler.ComponentConfig; // jshint ignore:line 454 | 455 | /** 456 | * Created component (i.e., upgraded element) type as managed by 457 | * componentHandler. Provided for benefit of the Closure compiler. 458 | * 459 | * @typedef {{ 460 | * element_: !HTMLElement, 461 | * className: string, 462 | * classAsString: string, 463 | * cssClass: string, 464 | * widget: string 465 | * }} 466 | */ 467 | componentHandler.Component; // jshint ignore:line 468 | 469 | // Export all symbols, for the benefit of Closure compiler. 470 | // No effect on uncompiled code. 471 | componentHandler['upgradeDom'] = componentHandler.upgradeDom; 472 | componentHandler['upgradeElement'] = componentHandler.upgradeElement; 473 | componentHandler['upgradeElements'] = componentHandler.upgradeElements; 474 | componentHandler['upgradeAllRegistered'] = 475 | componentHandler.upgradeAllRegistered; 476 | componentHandler['registerUpgradedCallback'] = 477 | componentHandler.registerUpgradedCallback; 478 | componentHandler['register'] = componentHandler.register; 479 | componentHandler['downgradeElements'] = componentHandler.downgradeElements; 480 | window.componentHandler = componentHandler; 481 | window['componentHandler'] = componentHandler; 482 | 483 | window.addEventListener('load', function() { 484 | 'use strict'; 485 | 486 | /** 487 | * Performs a "Cutting the mustard" test. If the browser supports the features 488 | * tested, adds a mdl-js class to the element. It then upgrades all MDL 489 | * components requiring JavaScript. 490 | */ 491 | if ('classList' in document.createElement('div') && 492 | 'querySelector' in document && 493 | 'addEventListener' in window && Array.prototype.forEach) { 494 | document.documentElement.classList.add('mdl-js'); 495 | componentHandler.upgradeAllRegistered(); 496 | } else { 497 | /** 498 | * Dummy function to avoid JS errors. 499 | */ 500 | componentHandler.upgradeElement = function() {}; 501 | /** 502 | * Dummy function to avoid JS errors. 503 | */ 504 | componentHandler.register = function() {}; 505 | } 506 | }); 507 | 508 | // Source: https://github.com/darius/requestAnimationFrame/blob/master/requestAnimationFrame.js 509 | // Adapted from https://gist.github.com/paulirish/1579671 which derived from 510 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 511 | // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating 512 | // requestAnimationFrame polyfill by Erik Möller. 513 | // Fixes from Paul Irish, Tino Zijdel, Andrew Mao, Klemen Slavič, Darius Bacon 514 | // MIT license 515 | if (!Date.now) { 516 | /** 517 | * Date.now polyfill. 518 | * @return {number} the current Date 519 | */ 520 | Date.now = function () { 521 | return new Date().getTime(); 522 | }; 523 | Date['now'] = Date.now; 524 | } 525 | var vendors = [ 526 | 'webkit', 527 | 'moz' 528 | ]; 529 | for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { 530 | var vp = vendors[i]; 531 | window.requestAnimationFrame = window[vp + 'RequestAnimationFrame']; 532 | window.cancelAnimationFrame = window[vp + 'CancelAnimationFrame'] || window[vp + 'CancelRequestAnimationFrame']; 533 | window['requestAnimationFrame'] = window.requestAnimationFrame; 534 | window['cancelAnimationFrame'] = window.cancelAnimationFrame; 535 | } 536 | if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) || !window.requestAnimationFrame || !window.cancelAnimationFrame) { 537 | var lastTime = 0; 538 | /** 539 | * requestAnimationFrame polyfill. 540 | * @param {!Function} callback the callback function. 541 | */ 542 | window.requestAnimationFrame = function (callback) { 543 | var now = Date.now(); 544 | var nextTime = Math.max(lastTime + 16, now); 545 | return setTimeout(function () { 546 | callback(lastTime = nextTime); 547 | }, nextTime - now); 548 | }; 549 | window.cancelAnimationFrame = clearTimeout; 550 | window['requestAnimationFrame'] = window.requestAnimationFrame; 551 | window['cancelAnimationFrame'] = window.cancelAnimationFrame; 552 | } 553 | /** 554 | * @license 555 | * Copyright 2015 Google Inc. All Rights Reserved. 556 | * 557 | * Licensed under the Apache License, Version 2.0 (the "License"); 558 | * you may not use this file except in compliance with the License. 559 | * You may obtain a copy of the License at 560 | * 561 | * http://www.apache.org/licenses/LICENSE-2.0 562 | * 563 | * Unless required by applicable law or agreed to in writing, software 564 | * distributed under the License is distributed on an "AS IS" BASIS, 565 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 566 | * See the License for the specific language governing permissions and 567 | * limitations under the License. 568 | */ 569 | /** 570 | * Class constructor for Button MDL component. 571 | * Implements MDL component design pattern defined at: 572 | * https://github.com/jasonmayes/mdl-component-design-pattern 573 | * 574 | * @param {HTMLElement} element The element that will be upgraded. 575 | */ 576 | var MaterialButton = function MaterialButton(element) { 577 | this.element_ = element; 578 | // Initialize instance. 579 | this.init(); 580 | }; 581 | window['MaterialButton'] = MaterialButton; 582 | /** 583 | * Store constants in one place so they can be updated easily. 584 | * 585 | * @enum {string | number} 586 | * @private 587 | */ 588 | MaterialButton.prototype.Constant_ = {}; 589 | /** 590 | * Store strings for class names defined by this component that are used in 591 | * JavaScript. This allows us to simply change it in one place should we 592 | * decide to modify at a later date. 593 | * 594 | * @enum {string} 595 | * @private 596 | */ 597 | MaterialButton.prototype.CssClasses_ = { 598 | RIPPLE_EFFECT: 'mdl-js-ripple-effect', 599 | RIPPLE_CONTAINER: 'mdl-button__ripple-container', 600 | RIPPLE: 'mdl-ripple' 601 | }; 602 | /** 603 | * Handle blur of element. 604 | * 605 | * @param {Event} event The event that fired. 606 | * @private 607 | */ 608 | MaterialButton.prototype.blurHandler_ = function (event) { 609 | if (event) { 610 | this.element_.blur(); 611 | } 612 | }; 613 | // Public methods. 614 | /** 615 | * Disable button. 616 | * 617 | * @public 618 | */ 619 | MaterialButton.prototype.disable = function () { 620 | this.element_.disabled = true; 621 | }; 622 | MaterialButton.prototype['disable'] = MaterialButton.prototype.disable; 623 | /** 624 | * Enable button. 625 | * 626 | * @public 627 | */ 628 | MaterialButton.prototype.enable = function () { 629 | this.element_.disabled = false; 630 | }; 631 | MaterialButton.prototype['enable'] = MaterialButton.prototype.enable; 632 | /** 633 | * Initialize element. 634 | */ 635 | MaterialButton.prototype.init = function () { 636 | if (this.element_) { 637 | if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { 638 | var rippleContainer = document.createElement('span'); 639 | rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER); 640 | this.rippleElement_ = document.createElement('span'); 641 | this.rippleElement_.classList.add(this.CssClasses_.RIPPLE); 642 | rippleContainer.appendChild(this.rippleElement_); 643 | this.boundRippleBlurHandler = this.blurHandler_.bind(this); 644 | this.rippleElement_.addEventListener('mouseup', this.boundRippleBlurHandler); 645 | this.element_.appendChild(rippleContainer); 646 | } 647 | this.boundButtonBlurHandler = this.blurHandler_.bind(this); 648 | this.element_.addEventListener('mouseup', this.boundButtonBlurHandler); 649 | this.element_.addEventListener('mouseleave', this.boundButtonBlurHandler); 650 | } 651 | }; 652 | // The component registers itself. It can assume componentHandler is available 653 | // in the global scope. 654 | componentHandler.register({ 655 | constructor: MaterialButton, 656 | classAsString: 'MaterialButton', 657 | cssClass: 'mdl-js-button', 658 | widget: true 659 | }); 660 | /** 661 | * @license 662 | * Copyright 2015 Google Inc. All Rights Reserved. 663 | * 664 | * Licensed under the Apache License, Version 2.0 (the "License"); 665 | * you may not use this file except in compliance with the License. 666 | * You may obtain a copy of the License at 667 | * 668 | * http://www.apache.org/licenses/LICENSE-2.0 669 | * 670 | * Unless required by applicable law or agreed to in writing, software 671 | * distributed under the License is distributed on an "AS IS" BASIS, 672 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 673 | * See the License for the specific language governing permissions and 674 | * limitations under the License. 675 | */ 676 | /** 677 | * Class constructor for Checkbox MDL component. 678 | * Implements MDL component design pattern defined at: 679 | * https://github.com/jasonmayes/mdl-component-design-pattern 680 | * 681 | * @constructor 682 | * @param {HTMLElement} element The element that will be upgraded. 683 | */ 684 | var MaterialCheckbox = function MaterialCheckbox(element) { 685 | this.element_ = element; 686 | // Initialize instance. 687 | this.init(); 688 | }; 689 | window['MaterialCheckbox'] = MaterialCheckbox; 690 | /** 691 | * Store constants in one place so they can be updated easily. 692 | * 693 | * @enum {string | number} 694 | * @private 695 | */ 696 | MaterialCheckbox.prototype.Constant_ = { TINY_TIMEOUT: 0.001 }; 697 | /** 698 | * Store strings for class names defined by this component that are used in 699 | * JavaScript. This allows us to simply change it in one place should we 700 | * decide to modify at a later date. 701 | * 702 | * @enum {string} 703 | * @private 704 | */ 705 | MaterialCheckbox.prototype.CssClasses_ = { 706 | INPUT: 'mdl-checkbox__input', 707 | BOX_OUTLINE: 'mdl-checkbox__box-outline', 708 | FOCUS_HELPER: 'mdl-checkbox__focus-helper', 709 | TICK_OUTLINE: 'mdl-checkbox__tick-outline', 710 | RIPPLE_EFFECT: 'mdl-js-ripple-effect', 711 | RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 712 | RIPPLE_CONTAINER: 'mdl-checkbox__ripple-container', 713 | RIPPLE_CENTER: 'mdl-ripple--center', 714 | RIPPLE: 'mdl-ripple', 715 | IS_FOCUSED: 'is-focused', 716 | IS_DISABLED: 'is-disabled', 717 | IS_CHECKED: 'is-checked', 718 | IS_UPGRADED: 'is-upgraded' 719 | }; 720 | /** 721 | * Handle change of state. 722 | * 723 | * @param {Event} event The event that fired. 724 | * @private 725 | */ 726 | MaterialCheckbox.prototype.onChange_ = function (event) { 727 | this.updateClasses_(); 728 | }; 729 | /** 730 | * Handle focus of element. 731 | * 732 | * @param {Event} event The event that fired. 733 | * @private 734 | */ 735 | MaterialCheckbox.prototype.onFocus_ = function (event) { 736 | this.element_.classList.add(this.CssClasses_.IS_FOCUSED); 737 | }; 738 | /** 739 | * Handle lost focus of element. 740 | * 741 | * @param {Event} event The event that fired. 742 | * @private 743 | */ 744 | MaterialCheckbox.prototype.onBlur_ = function (event) { 745 | this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); 746 | }; 747 | /** 748 | * Handle mouseup. 749 | * 750 | * @param {Event} event The event that fired. 751 | * @private 752 | */ 753 | MaterialCheckbox.prototype.onMouseUp_ = function (event) { 754 | this.blur_(); 755 | }; 756 | /** 757 | * Handle class updates. 758 | * 759 | * @private 760 | */ 761 | MaterialCheckbox.prototype.updateClasses_ = function () { 762 | this.checkDisabled(); 763 | this.checkToggleState(); 764 | }; 765 | /** 766 | * Add blur. 767 | * 768 | * @private 769 | */ 770 | MaterialCheckbox.prototype.blur_ = function () { 771 | // TODO: figure out why there's a focus event being fired after our blur, 772 | // so that we can avoid this hack. 773 | window.setTimeout(function () { 774 | this.inputElement_.blur(); 775 | }.bind(this), this.Constant_.TINY_TIMEOUT); 776 | }; 777 | // Public methods. 778 | /** 779 | * Check the inputs toggle state and update display. 780 | * 781 | * @public 782 | */ 783 | MaterialCheckbox.prototype.checkToggleState = function () { 784 | if (this.inputElement_.checked) { 785 | this.element_.classList.add(this.CssClasses_.IS_CHECKED); 786 | } else { 787 | this.element_.classList.remove(this.CssClasses_.IS_CHECKED); 788 | } 789 | }; 790 | MaterialCheckbox.prototype['checkToggleState'] = MaterialCheckbox.prototype.checkToggleState; 791 | /** 792 | * Check the inputs disabled state and update display. 793 | * 794 | * @public 795 | */ 796 | MaterialCheckbox.prototype.checkDisabled = function () { 797 | if (this.inputElement_.disabled) { 798 | this.element_.classList.add(this.CssClasses_.IS_DISABLED); 799 | } else { 800 | this.element_.classList.remove(this.CssClasses_.IS_DISABLED); 801 | } 802 | }; 803 | MaterialCheckbox.prototype['checkDisabled'] = MaterialCheckbox.prototype.checkDisabled; 804 | /** 805 | * Disable checkbox. 806 | * 807 | * @public 808 | */ 809 | MaterialCheckbox.prototype.disable = function () { 810 | this.inputElement_.disabled = true; 811 | this.updateClasses_(); 812 | }; 813 | MaterialCheckbox.prototype['disable'] = MaterialCheckbox.prototype.disable; 814 | /** 815 | * Enable checkbox. 816 | * 817 | * @public 818 | */ 819 | MaterialCheckbox.prototype.enable = function () { 820 | this.inputElement_.disabled = false; 821 | this.updateClasses_(); 822 | }; 823 | MaterialCheckbox.prototype['enable'] = MaterialCheckbox.prototype.enable; 824 | /** 825 | * Check checkbox. 826 | * 827 | * @public 828 | */ 829 | MaterialCheckbox.prototype.check = function () { 830 | this.inputElement_.checked = true; 831 | this.updateClasses_(); 832 | }; 833 | MaterialCheckbox.prototype['check'] = MaterialCheckbox.prototype.check; 834 | /** 835 | * Uncheck checkbox. 836 | * 837 | * @public 838 | */ 839 | MaterialCheckbox.prototype.uncheck = function () { 840 | this.inputElement_.checked = false; 841 | this.updateClasses_(); 842 | }; 843 | MaterialCheckbox.prototype['uncheck'] = MaterialCheckbox.prototype.uncheck; 844 | /** 845 | * Initialize element. 846 | */ 847 | MaterialCheckbox.prototype.init = function () { 848 | if (this.element_) { 849 | this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT); 850 | var boxOutline = document.createElement('span'); 851 | boxOutline.classList.add(this.CssClasses_.BOX_OUTLINE); 852 | var tickContainer = document.createElement('span'); 853 | tickContainer.classList.add(this.CssClasses_.FOCUS_HELPER); 854 | var tickOutline = document.createElement('span'); 855 | tickOutline.classList.add(this.CssClasses_.TICK_OUTLINE); 856 | boxOutline.appendChild(tickOutline); 857 | this.element_.appendChild(tickContainer); 858 | this.element_.appendChild(boxOutline); 859 | if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { 860 | this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); 861 | this.rippleContainerElement_ = document.createElement('span'); 862 | this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER); 863 | this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT); 864 | this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER); 865 | this.boundRippleMouseUp = this.onMouseUp_.bind(this); 866 | this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp); 867 | var ripple = document.createElement('span'); 868 | ripple.classList.add(this.CssClasses_.RIPPLE); 869 | this.rippleContainerElement_.appendChild(ripple); 870 | this.element_.appendChild(this.rippleContainerElement_); 871 | } 872 | this.boundInputOnChange = this.onChange_.bind(this); 873 | this.boundInputOnFocus = this.onFocus_.bind(this); 874 | this.boundInputOnBlur = this.onBlur_.bind(this); 875 | this.boundElementMouseUp = this.onMouseUp_.bind(this); 876 | this.inputElement_.addEventListener('change', this.boundInputOnChange); 877 | this.inputElement_.addEventListener('focus', this.boundInputOnFocus); 878 | this.inputElement_.addEventListener('blur', this.boundInputOnBlur); 879 | this.element_.addEventListener('mouseup', this.boundElementMouseUp); 880 | this.updateClasses_(); 881 | this.element_.classList.add(this.CssClasses_.IS_UPGRADED); 882 | } 883 | }; 884 | // The component registers itself. It can assume componentHandler is available 885 | // in the global scope. 886 | componentHandler.register({ 887 | constructor: MaterialCheckbox, 888 | classAsString: 'MaterialCheckbox', 889 | cssClass: 'mdl-js-checkbox', 890 | widget: true 891 | }); 892 | /** 893 | * @license 894 | * Copyright 2015 Google Inc. All Rights Reserved. 895 | * 896 | * Licensed under the Apache License, Version 2.0 (the "License"); 897 | * you may not use this file except in compliance with the License. 898 | * You may obtain a copy of the License at 899 | * 900 | * http://www.apache.org/licenses/LICENSE-2.0 901 | * 902 | * Unless required by applicable law or agreed to in writing, software 903 | * distributed under the License is distributed on an "AS IS" BASIS, 904 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 905 | * See the License for the specific language governing permissions and 906 | * limitations under the License. 907 | */ 908 | /** 909 | * Class constructor for icon toggle MDL component. 910 | * Implements MDL component design pattern defined at: 911 | * https://github.com/jasonmayes/mdl-component-design-pattern 912 | * 913 | * @constructor 914 | * @param {HTMLElement} element The element that will be upgraded. 915 | */ 916 | var MaterialIconToggle = function MaterialIconToggle(element) { 917 | this.element_ = element; 918 | // Initialize instance. 919 | this.init(); 920 | }; 921 | window['MaterialIconToggle'] = MaterialIconToggle; 922 | /** 923 | * Store constants in one place so they can be updated easily. 924 | * 925 | * @enum {string | number} 926 | * @private 927 | */ 928 | MaterialIconToggle.prototype.Constant_ = { TINY_TIMEOUT: 0.001 }; 929 | /** 930 | * Store strings for class names defined by this component that are used in 931 | * JavaScript. This allows us to simply change it in one place should we 932 | * decide to modify at a later date. 933 | * 934 | * @enum {string} 935 | * @private 936 | */ 937 | MaterialIconToggle.prototype.CssClasses_ = { 938 | INPUT: 'mdl-icon-toggle__input', 939 | JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect', 940 | RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 941 | RIPPLE_CONTAINER: 'mdl-icon-toggle__ripple-container', 942 | RIPPLE_CENTER: 'mdl-ripple--center', 943 | RIPPLE: 'mdl-ripple', 944 | IS_FOCUSED: 'is-focused', 945 | IS_DISABLED: 'is-disabled', 946 | IS_CHECKED: 'is-checked' 947 | }; 948 | /** 949 | * Handle change of state. 950 | * 951 | * @param {Event} event The event that fired. 952 | * @private 953 | */ 954 | MaterialIconToggle.prototype.onChange_ = function (event) { 955 | this.updateClasses_(); 956 | }; 957 | /** 958 | * Handle focus of element. 959 | * 960 | * @param {Event} event The event that fired. 961 | * @private 962 | */ 963 | MaterialIconToggle.prototype.onFocus_ = function (event) { 964 | this.element_.classList.add(this.CssClasses_.IS_FOCUSED); 965 | }; 966 | /** 967 | * Handle lost focus of element. 968 | * 969 | * @param {Event} event The event that fired. 970 | * @private 971 | */ 972 | MaterialIconToggle.prototype.onBlur_ = function (event) { 973 | this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); 974 | }; 975 | /** 976 | * Handle mouseup. 977 | * 978 | * @param {Event} event The event that fired. 979 | * @private 980 | */ 981 | MaterialIconToggle.prototype.onMouseUp_ = function (event) { 982 | this.blur_(); 983 | }; 984 | /** 985 | * Handle class updates. 986 | * 987 | * @private 988 | */ 989 | MaterialIconToggle.prototype.updateClasses_ = function () { 990 | this.checkDisabled(); 991 | this.checkToggleState(); 992 | }; 993 | /** 994 | * Add blur. 995 | * 996 | * @private 997 | */ 998 | MaterialIconToggle.prototype.blur_ = function () { 999 | // TODO: figure out why there's a focus event being fired after our blur, 1000 | // so that we can avoid this hack. 1001 | window.setTimeout(function () { 1002 | this.inputElement_.blur(); 1003 | }.bind(this), this.Constant_.TINY_TIMEOUT); 1004 | }; 1005 | // Public methods. 1006 | /** 1007 | * Check the inputs toggle state and update display. 1008 | * 1009 | * @public 1010 | */ 1011 | MaterialIconToggle.prototype.checkToggleState = function () { 1012 | if (this.inputElement_.checked) { 1013 | this.element_.classList.add(this.CssClasses_.IS_CHECKED); 1014 | } else { 1015 | this.element_.classList.remove(this.CssClasses_.IS_CHECKED); 1016 | } 1017 | }; 1018 | MaterialIconToggle.prototype['checkToggleState'] = MaterialIconToggle.prototype.checkToggleState; 1019 | /** 1020 | * Check the inputs disabled state and update display. 1021 | * 1022 | * @public 1023 | */ 1024 | MaterialIconToggle.prototype.checkDisabled = function () { 1025 | if (this.inputElement_.disabled) { 1026 | this.element_.classList.add(this.CssClasses_.IS_DISABLED); 1027 | } else { 1028 | this.element_.classList.remove(this.CssClasses_.IS_DISABLED); 1029 | } 1030 | }; 1031 | MaterialIconToggle.prototype['checkDisabled'] = MaterialIconToggle.prototype.checkDisabled; 1032 | /** 1033 | * Disable icon toggle. 1034 | * 1035 | * @public 1036 | */ 1037 | MaterialIconToggle.prototype.disable = function () { 1038 | this.inputElement_.disabled = true; 1039 | this.updateClasses_(); 1040 | }; 1041 | MaterialIconToggle.prototype['disable'] = MaterialIconToggle.prototype.disable; 1042 | /** 1043 | * Enable icon toggle. 1044 | * 1045 | * @public 1046 | */ 1047 | MaterialIconToggle.prototype.enable = function () { 1048 | this.inputElement_.disabled = false; 1049 | this.updateClasses_(); 1050 | }; 1051 | MaterialIconToggle.prototype['enable'] = MaterialIconToggle.prototype.enable; 1052 | /** 1053 | * Check icon toggle. 1054 | * 1055 | * @public 1056 | */ 1057 | MaterialIconToggle.prototype.check = function () { 1058 | this.inputElement_.checked = true; 1059 | this.updateClasses_(); 1060 | }; 1061 | MaterialIconToggle.prototype['check'] = MaterialIconToggle.prototype.check; 1062 | /** 1063 | * Uncheck icon toggle. 1064 | * 1065 | * @public 1066 | */ 1067 | MaterialIconToggle.prototype.uncheck = function () { 1068 | this.inputElement_.checked = false; 1069 | this.updateClasses_(); 1070 | }; 1071 | MaterialIconToggle.prototype['uncheck'] = MaterialIconToggle.prototype.uncheck; 1072 | /** 1073 | * Initialize element. 1074 | */ 1075 | MaterialIconToggle.prototype.init = function () { 1076 | if (this.element_) { 1077 | this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT); 1078 | if (this.element_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) { 1079 | this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); 1080 | this.rippleContainerElement_ = document.createElement('span'); 1081 | this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER); 1082 | this.rippleContainerElement_.classList.add(this.CssClasses_.JS_RIPPLE_EFFECT); 1083 | this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER); 1084 | this.boundRippleMouseUp = this.onMouseUp_.bind(this); 1085 | this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp); 1086 | var ripple = document.createElement('span'); 1087 | ripple.classList.add(this.CssClasses_.RIPPLE); 1088 | this.rippleContainerElement_.appendChild(ripple); 1089 | this.element_.appendChild(this.rippleContainerElement_); 1090 | } 1091 | this.boundInputOnChange = this.onChange_.bind(this); 1092 | this.boundInputOnFocus = this.onFocus_.bind(this); 1093 | this.boundInputOnBlur = this.onBlur_.bind(this); 1094 | this.boundElementOnMouseUp = this.onMouseUp_.bind(this); 1095 | this.inputElement_.addEventListener('change', this.boundInputOnChange); 1096 | this.inputElement_.addEventListener('focus', this.boundInputOnFocus); 1097 | this.inputElement_.addEventListener('blur', this.boundInputOnBlur); 1098 | this.element_.addEventListener('mouseup', this.boundElementOnMouseUp); 1099 | this.updateClasses_(); 1100 | this.element_.classList.add('is-upgraded'); 1101 | } 1102 | }; 1103 | // The component registers itself. It can assume componentHandler is available 1104 | // in the global scope. 1105 | componentHandler.register({ 1106 | constructor: MaterialIconToggle, 1107 | classAsString: 'MaterialIconToggle', 1108 | cssClass: 'mdl-js-icon-toggle', 1109 | widget: true 1110 | }); 1111 | /** 1112 | * @license 1113 | * Copyright 2015 Google Inc. All Rights Reserved. 1114 | * 1115 | * Licensed under the Apache License, Version 2.0 (the "License"); 1116 | * you may not use this file except in compliance with the License. 1117 | * You may obtain a copy of the License at 1118 | * 1119 | * http://www.apache.org/licenses/LICENSE-2.0 1120 | * 1121 | * Unless required by applicable law or agreed to in writing, software 1122 | * distributed under the License is distributed on an "AS IS" BASIS, 1123 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1124 | * See the License for the specific language governing permissions and 1125 | * limitations under the License. 1126 | */ 1127 | /** 1128 | * Class constructor for dropdown MDL component. 1129 | * Implements MDL component design pattern defined at: 1130 | * https://github.com/jasonmayes/mdl-component-design-pattern 1131 | * 1132 | * @constructor 1133 | * @param {HTMLElement} element The element that will be upgraded. 1134 | */ 1135 | var MaterialMenu = function MaterialMenu(element) { 1136 | this.element_ = element; 1137 | // Initialize instance. 1138 | this.init(); 1139 | }; 1140 | window['MaterialMenu'] = MaterialMenu; 1141 | /** 1142 | * Store constants in one place so they can be updated easily. 1143 | * 1144 | * @enum {string | number} 1145 | * @private 1146 | */ 1147 | MaterialMenu.prototype.Constant_ = { 1148 | // Total duration of the menu animation. 1149 | TRANSITION_DURATION_SECONDS: 0.3, 1150 | // The fraction of the total duration we want to use for menu item animations. 1151 | TRANSITION_DURATION_FRACTION: 0.8, 1152 | // How long the menu stays open after choosing an option (so the user can see 1153 | // the ripple). 1154 | CLOSE_TIMEOUT: 150 1155 | }; 1156 | /** 1157 | * Keycodes, for code readability. 1158 | * 1159 | * @enum {number} 1160 | * @private 1161 | */ 1162 | MaterialMenu.prototype.Keycodes_ = { 1163 | ENTER: 13, 1164 | ESCAPE: 27, 1165 | SPACE: 32, 1166 | UP_ARROW: 38, 1167 | DOWN_ARROW: 40 1168 | }; 1169 | /** 1170 | * Store strings for class names defined by this component that are used in 1171 | * JavaScript. This allows us to simply change it in one place should we 1172 | * decide to modify at a later date. 1173 | * 1174 | * @enum {string} 1175 | * @private 1176 | */ 1177 | MaterialMenu.prototype.CssClasses_ = { 1178 | CONTAINER: 'mdl-menu__container', 1179 | OUTLINE: 'mdl-menu__outline', 1180 | ITEM: 'mdl-menu__item', 1181 | ITEM_RIPPLE_CONTAINER: 'mdl-menu__item-ripple-container', 1182 | RIPPLE_EFFECT: 'mdl-js-ripple-effect', 1183 | RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 1184 | RIPPLE: 'mdl-ripple', 1185 | // Statuses 1186 | IS_UPGRADED: 'is-upgraded', 1187 | IS_VISIBLE: 'is-visible', 1188 | IS_ANIMATING: 'is-animating', 1189 | // Alignment options 1190 | BOTTOM_LEFT: 'mdl-menu--bottom-left', 1191 | // This is the default. 1192 | BOTTOM_RIGHT: 'mdl-menu--bottom-right', 1193 | TOP_LEFT: 'mdl-menu--top-left', 1194 | TOP_RIGHT: 'mdl-menu--top-right', 1195 | UNALIGNED: 'mdl-menu--unaligned' 1196 | }; 1197 | /** 1198 | * Initialize element. 1199 | */ 1200 | MaterialMenu.prototype.init = function () { 1201 | if (this.element_) { 1202 | // Create container for the menu. 1203 | var container = document.createElement('div'); 1204 | container.classList.add(this.CssClasses_.CONTAINER); 1205 | this.element_.parentElement.insertBefore(container, this.element_); 1206 | this.element_.parentElement.removeChild(this.element_); 1207 | container.appendChild(this.element_); 1208 | this.container_ = container; 1209 | // Create outline for the menu (shadow and background). 1210 | var outline = document.createElement('div'); 1211 | outline.classList.add(this.CssClasses_.OUTLINE); 1212 | this.outline_ = outline; 1213 | container.insertBefore(outline, this.element_); 1214 | // Find the "for" element and bind events to it. 1215 | var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for'); 1216 | var forEl = null; 1217 | if (forElId) { 1218 | forEl = document.getElementById(forElId); 1219 | if (forEl) { 1220 | this.forElement_ = forEl; 1221 | forEl.addEventListener('click', this.handleForClick_.bind(this)); 1222 | forEl.addEventListener('keydown', this.handleForKeyboardEvent_.bind(this)); 1223 | } 1224 | } 1225 | var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM); 1226 | this.boundItemKeydown_ = this.handleItemKeyboardEvent_.bind(this); 1227 | this.boundItemClick_ = this.handleItemClick_.bind(this); 1228 | for (var i = 0; i < items.length; i++) { 1229 | // Add a listener to each menu item. 1230 | items[i].addEventListener('click', this.boundItemClick_); 1231 | // Add a tab index to each menu item. 1232 | items[i].tabIndex = '-1'; 1233 | // Add a keyboard listener to each menu item. 1234 | items[i].addEventListener('keydown', this.boundItemKeydown_); 1235 | } 1236 | // Add ripple classes to each item, if the user has enabled ripples. 1237 | if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { 1238 | this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); 1239 | for (i = 0; i < items.length; i++) { 1240 | var item = items[i]; 1241 | var rippleContainer = document.createElement('span'); 1242 | rippleContainer.classList.add(this.CssClasses_.ITEM_RIPPLE_CONTAINER); 1243 | var ripple = document.createElement('span'); 1244 | ripple.classList.add(this.CssClasses_.RIPPLE); 1245 | rippleContainer.appendChild(ripple); 1246 | item.appendChild(rippleContainer); 1247 | item.classList.add(this.CssClasses_.RIPPLE_EFFECT); 1248 | } 1249 | } 1250 | // Copy alignment classes to the container, so the outline can use them. 1251 | if (this.element_.classList.contains(this.CssClasses_.BOTTOM_LEFT)) { 1252 | this.outline_.classList.add(this.CssClasses_.BOTTOM_LEFT); 1253 | } 1254 | if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) { 1255 | this.outline_.classList.add(this.CssClasses_.BOTTOM_RIGHT); 1256 | } 1257 | if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) { 1258 | this.outline_.classList.add(this.CssClasses_.TOP_LEFT); 1259 | } 1260 | if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) { 1261 | this.outline_.classList.add(this.CssClasses_.TOP_RIGHT); 1262 | } 1263 | if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) { 1264 | this.outline_.classList.add(this.CssClasses_.UNALIGNED); 1265 | } 1266 | container.classList.add(this.CssClasses_.IS_UPGRADED); 1267 | } 1268 | }; 1269 | /** 1270 | * Handles a click on the "for" element, by positioning the menu and then 1271 | * toggling it. 1272 | * 1273 | * @param {Event} evt The event that fired. 1274 | * @private 1275 | */ 1276 | MaterialMenu.prototype.handleForClick_ = function (evt) { 1277 | if (this.element_ && this.forElement_) { 1278 | var rect = this.forElement_.getBoundingClientRect(); 1279 | var forRect = this.forElement_.parentElement.getBoundingClientRect(); 1280 | if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) { 1281 | } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) { 1282 | // Position below the "for" element, aligned to its right. 1283 | this.container_.style.right = forRect.right - rect.right + 'px'; 1284 | this.container_.style.top = this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px'; 1285 | } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) { 1286 | // Position above the "for" element, aligned to its left. 1287 | this.container_.style.left = this.forElement_.offsetLeft + 'px'; 1288 | this.container_.style.bottom = forRect.bottom - rect.top + 'px'; 1289 | } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) { 1290 | // Position above the "for" element, aligned to its right. 1291 | this.container_.style.right = forRect.right - rect.right + 'px'; 1292 | this.container_.style.bottom = forRect.bottom - rect.top + 'px'; 1293 | } else { 1294 | // Default: position below the "for" element, aligned to its left. 1295 | this.container_.style.left = this.forElement_.offsetLeft + 'px'; 1296 | this.container_.style.top = this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px'; 1297 | } 1298 | } 1299 | this.toggle(evt); 1300 | }; 1301 | /** 1302 | * Handles a keyboard event on the "for" element. 1303 | * 1304 | * @param {Event} evt The event that fired. 1305 | * @private 1306 | */ 1307 | MaterialMenu.prototype.handleForKeyboardEvent_ = function (evt) { 1308 | if (this.element_ && this.container_ && this.forElement_) { 1309 | var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM + ':not([disabled])'); 1310 | if (items && items.length > 0 && this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) { 1311 | if (evt.keyCode === this.Keycodes_.UP_ARROW) { 1312 | evt.preventDefault(); 1313 | items[items.length - 1].focus(); 1314 | } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) { 1315 | evt.preventDefault(); 1316 | items[0].focus(); 1317 | } 1318 | } 1319 | } 1320 | }; 1321 | /** 1322 | * Handles a keyboard event on an item. 1323 | * 1324 | * @param {Event} evt The event that fired. 1325 | * @private 1326 | */ 1327 | MaterialMenu.prototype.handleItemKeyboardEvent_ = function (evt) { 1328 | if (this.element_ && this.container_) { 1329 | var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM + ':not([disabled])'); 1330 | if (items && items.length > 0 && this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) { 1331 | var currentIndex = Array.prototype.slice.call(items).indexOf(evt.target); 1332 | if (evt.keyCode === this.Keycodes_.UP_ARROW) { 1333 | evt.preventDefault(); 1334 | if (currentIndex > 0) { 1335 | items[currentIndex - 1].focus(); 1336 | } else { 1337 | items[items.length - 1].focus(); 1338 | } 1339 | } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) { 1340 | evt.preventDefault(); 1341 | if (items.length > currentIndex + 1) { 1342 | items[currentIndex + 1].focus(); 1343 | } else { 1344 | items[0].focus(); 1345 | } 1346 | } else if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) { 1347 | evt.preventDefault(); 1348 | // Send mousedown and mouseup to trigger ripple. 1349 | var e = new MouseEvent('mousedown'); 1350 | evt.target.dispatchEvent(e); 1351 | e = new MouseEvent('mouseup'); 1352 | evt.target.dispatchEvent(e); 1353 | // Send click. 1354 | evt.target.click(); 1355 | } else if (evt.keyCode === this.Keycodes_.ESCAPE) { 1356 | evt.preventDefault(); 1357 | this.hide(); 1358 | } 1359 | } 1360 | } 1361 | }; 1362 | /** 1363 | * Handles a click event on an item. 1364 | * 1365 | * @param {Event} evt The event that fired. 1366 | * @private 1367 | */ 1368 | MaterialMenu.prototype.handleItemClick_ = function (evt) { 1369 | if (evt.target.hasAttribute('disabled')) { 1370 | evt.stopPropagation(); 1371 | } else { 1372 | // Wait some time before closing menu, so the user can see the ripple. 1373 | this.closing_ = true; 1374 | window.setTimeout(function (evt) { 1375 | this.hide(); 1376 | this.closing_ = false; 1377 | }.bind(this), this.Constant_.CLOSE_TIMEOUT); 1378 | } 1379 | }; 1380 | /** 1381 | * Calculates the initial clip (for opening the menu) or final clip (for closing 1382 | * it), and applies it. This allows us to animate from or to the correct point, 1383 | * that is, the point it's aligned to in the "for" element. 1384 | * 1385 | * @param {number} height Height of the clip rectangle 1386 | * @param {number} width Width of the clip rectangle 1387 | * @private 1388 | */ 1389 | MaterialMenu.prototype.applyClip_ = function (height, width) { 1390 | if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) { 1391 | // Do not clip. 1392 | this.element_.style.clip = ''; 1393 | } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) { 1394 | // Clip to the top right corner of the menu. 1395 | this.element_.style.clip = 'rect(0 ' + width + 'px ' + '0 ' + width + 'px)'; 1396 | } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) { 1397 | // Clip to the bottom left corner of the menu. 1398 | this.element_.style.clip = 'rect(' + height + 'px 0 ' + height + 'px 0)'; 1399 | } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) { 1400 | // Clip to the bottom right corner of the menu. 1401 | this.element_.style.clip = 'rect(' + height + 'px ' + width + 'px ' + height + 'px ' + width + 'px)'; 1402 | } else { 1403 | // Default: do not clip (same as clipping to the top left corner). 1404 | this.element_.style.clip = ''; 1405 | } 1406 | }; 1407 | /** 1408 | * Cleanup function to remove animation listeners. 1409 | * 1410 | * @param {Event} evt 1411 | * @private 1412 | */ 1413 | MaterialMenu.prototype.removeAnimationEndListener_ = function (evt) { 1414 | evt.target.classList.remove(MaterialMenu.prototype.CssClasses_.IS_ANIMATING); 1415 | }; 1416 | /** 1417 | * Adds an event listener to clean up after the animation ends. 1418 | * 1419 | * @private 1420 | */ 1421 | MaterialMenu.prototype.addAnimationEndListener_ = function () { 1422 | this.element_.addEventListener('transitionend', this.removeAnimationEndListener_); 1423 | this.element_.addEventListener('webkitTransitionEnd', this.removeAnimationEndListener_); 1424 | }; 1425 | /** 1426 | * Displays the menu. 1427 | * 1428 | * @public 1429 | */ 1430 | MaterialMenu.prototype.show = function (evt) { 1431 | if (this.element_ && this.container_ && this.outline_) { 1432 | // Measure the inner element. 1433 | var height = this.element_.getBoundingClientRect().height; 1434 | var width = this.element_.getBoundingClientRect().width; 1435 | // Apply the inner element's size to the container and outline. 1436 | this.container_.style.width = width + 'px'; 1437 | this.container_.style.height = height + 'px'; 1438 | this.outline_.style.width = width + 'px'; 1439 | this.outline_.style.height = height + 'px'; 1440 | var transitionDuration = this.Constant_.TRANSITION_DURATION_SECONDS * this.Constant_.TRANSITION_DURATION_FRACTION; 1441 | // Calculate transition delays for individual menu items, so that they fade 1442 | // in one at a time. 1443 | var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM); 1444 | for (var i = 0; i < items.length; i++) { 1445 | var itemDelay = null; 1446 | if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT) || this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) { 1447 | itemDelay = (height - items[i].offsetTop - items[i].offsetHeight) / height * transitionDuration + 's'; 1448 | } else { 1449 | itemDelay = items[i].offsetTop / height * transitionDuration + 's'; 1450 | } 1451 | items[i].style.transitionDelay = itemDelay; 1452 | } 1453 | // Apply the initial clip to the text before we start animating. 1454 | this.applyClip_(height, width); 1455 | // Wait for the next frame, turn on animation, and apply the final clip. 1456 | // Also make it visible. This triggers the transitions. 1457 | window.requestAnimationFrame(function () { 1458 | this.element_.classList.add(this.CssClasses_.IS_ANIMATING); 1459 | this.element_.style.clip = 'rect(0 ' + width + 'px ' + height + 'px 0)'; 1460 | this.container_.classList.add(this.CssClasses_.IS_VISIBLE); 1461 | }.bind(this)); 1462 | // Clean up after the animation is complete. 1463 | this.addAnimationEndListener_(); 1464 | // Add a click listener to the document, to close the menu. 1465 | var callback = function (e) { 1466 | // Check to see if the document is processing the same event that 1467 | // displayed the menu in the first place. If so, do nothing. 1468 | // Also check to see if the menu is in the process of closing itself, and 1469 | // do nothing in that case. 1470 | // Also check if the clicked element is a menu item 1471 | // if so, do nothing. 1472 | if (e !== evt && !this.closing_ && e.target.parentNode !== this.element_) { 1473 | document.removeEventListener('click', callback); 1474 | this.hide(); 1475 | } 1476 | }.bind(this); 1477 | document.addEventListener('click', callback); 1478 | } 1479 | }; 1480 | MaterialMenu.prototype['show'] = MaterialMenu.prototype.show; 1481 | /** 1482 | * Hides the menu. 1483 | * 1484 | * @public 1485 | */ 1486 | MaterialMenu.prototype.hide = function () { 1487 | if (this.element_ && this.container_ && this.outline_) { 1488 | var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM); 1489 | // Remove all transition delays; menu items fade out concurrently. 1490 | for (var i = 0; i < items.length; i++) { 1491 | items[i].style.removeProperty('transition-delay'); 1492 | } 1493 | // Measure the inner element. 1494 | var rect = this.element_.getBoundingClientRect(); 1495 | var height = rect.height; 1496 | var width = rect.width; 1497 | // Turn on animation, and apply the final clip. Also make invisible. 1498 | // This triggers the transitions. 1499 | this.element_.classList.add(this.CssClasses_.IS_ANIMATING); 1500 | this.applyClip_(height, width); 1501 | this.container_.classList.remove(this.CssClasses_.IS_VISIBLE); 1502 | // Clean up after the animation is complete. 1503 | this.addAnimationEndListener_(); 1504 | } 1505 | }; 1506 | MaterialMenu.prototype['hide'] = MaterialMenu.prototype.hide; 1507 | /** 1508 | * Displays or hides the menu, depending on current state. 1509 | * 1510 | * @public 1511 | */ 1512 | MaterialMenu.prototype.toggle = function (evt) { 1513 | if (this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) { 1514 | this.hide(); 1515 | } else { 1516 | this.show(evt); 1517 | } 1518 | }; 1519 | MaterialMenu.prototype['toggle'] = MaterialMenu.prototype.toggle; 1520 | // The component registers itself. It can assume componentHandler is available 1521 | // in the global scope. 1522 | componentHandler.register({ 1523 | constructor: MaterialMenu, 1524 | classAsString: 'MaterialMenu', 1525 | cssClass: 'mdl-js-menu', 1526 | widget: true 1527 | }); 1528 | /** 1529 | * @license 1530 | * Copyright 2015 Google Inc. All Rights Reserved. 1531 | * 1532 | * Licensed under the Apache License, Version 2.0 (the "License"); 1533 | * you may not use this file except in compliance with the License. 1534 | * You may obtain a copy of the License at 1535 | * 1536 | * http://www.apache.org/licenses/LICENSE-2.0 1537 | * 1538 | * Unless required by applicable law or agreed to in writing, software 1539 | * distributed under the License is distributed on an "AS IS" BASIS, 1540 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1541 | * See the License for the specific language governing permissions and 1542 | * limitations under the License. 1543 | */ 1544 | /** 1545 | * Class constructor for Progress MDL component. 1546 | * Implements MDL component design pattern defined at: 1547 | * https://github.com/jasonmayes/mdl-component-design-pattern 1548 | * 1549 | * @constructor 1550 | * @param {HTMLElement} element The element that will be upgraded. 1551 | */ 1552 | var MaterialProgress = function MaterialProgress(element) { 1553 | this.element_ = element; 1554 | // Initialize instance. 1555 | this.init(); 1556 | }; 1557 | window['MaterialProgress'] = MaterialProgress; 1558 | /** 1559 | * Store constants in one place so they can be updated easily. 1560 | * 1561 | * @enum {string | number} 1562 | * @private 1563 | */ 1564 | MaterialProgress.prototype.Constant_ = {}; 1565 | /** 1566 | * Store strings for class names defined by this component that are used in 1567 | * JavaScript. This allows us to simply change it in one place should we 1568 | * decide to modify at a later date. 1569 | * 1570 | * @enum {string} 1571 | * @private 1572 | */ 1573 | MaterialProgress.prototype.CssClasses_ = { INDETERMINATE_CLASS: 'mdl-progress__indeterminate' }; 1574 | /** 1575 | * Set the current progress of the progressbar. 1576 | * 1577 | * @param {number} p Percentage of the progress (0-100) 1578 | * @public 1579 | */ 1580 | MaterialProgress.prototype.setProgress = function (p) { 1581 | if (this.element_.classList.contains(this.CssClasses_.INDETERMINATE_CLASS)) { 1582 | return; 1583 | } 1584 | this.progressbar_.style.width = p + '%'; 1585 | }; 1586 | MaterialProgress.prototype['setProgress'] = MaterialProgress.prototype.setProgress; 1587 | /** 1588 | * Set the current progress of the buffer. 1589 | * 1590 | * @param {number} p Percentage of the buffer (0-100) 1591 | * @public 1592 | */ 1593 | MaterialProgress.prototype.setBuffer = function (p) { 1594 | this.bufferbar_.style.width = p + '%'; 1595 | this.auxbar_.style.width = 100 - p + '%'; 1596 | }; 1597 | MaterialProgress.prototype['setBuffer'] = MaterialProgress.prototype.setBuffer; 1598 | /** 1599 | * Initialize element. 1600 | */ 1601 | MaterialProgress.prototype.init = function () { 1602 | if (this.element_) { 1603 | var el = document.createElement('div'); 1604 | el.className = 'progressbar bar bar1'; 1605 | this.element_.appendChild(el); 1606 | this.progressbar_ = el; 1607 | el = document.createElement('div'); 1608 | el.className = 'bufferbar bar bar2'; 1609 | this.element_.appendChild(el); 1610 | this.bufferbar_ = el; 1611 | el = document.createElement('div'); 1612 | el.className = 'auxbar bar bar3'; 1613 | this.element_.appendChild(el); 1614 | this.auxbar_ = el; 1615 | this.progressbar_.style.width = '0%'; 1616 | this.bufferbar_.style.width = '100%'; 1617 | this.auxbar_.style.width = '0%'; 1618 | this.element_.classList.add('is-upgraded'); 1619 | } 1620 | }; 1621 | // The component registers itself. It can assume componentHandler is available 1622 | // in the global scope. 1623 | componentHandler.register({ 1624 | constructor: MaterialProgress, 1625 | classAsString: 'MaterialProgress', 1626 | cssClass: 'mdl-js-progress', 1627 | widget: true 1628 | }); 1629 | /** 1630 | * @license 1631 | * Copyright 2015 Google Inc. All Rights Reserved. 1632 | * 1633 | * Licensed under the Apache License, Version 2.0 (the "License"); 1634 | * you may not use this file except in compliance with the License. 1635 | * You may obtain a copy of the License at 1636 | * 1637 | * http://www.apache.org/licenses/LICENSE-2.0 1638 | * 1639 | * Unless required by applicable law or agreed to in writing, software 1640 | * distributed under the License is distributed on an "AS IS" BASIS, 1641 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1642 | * See the License for the specific language governing permissions and 1643 | * limitations under the License. 1644 | */ 1645 | /** 1646 | * Class constructor for Radio MDL component. 1647 | * Implements MDL component design pattern defined at: 1648 | * https://github.com/jasonmayes/mdl-component-design-pattern 1649 | * 1650 | * @constructor 1651 | * @param {HTMLElement} element The element that will be upgraded. 1652 | */ 1653 | var MaterialRadio = function MaterialRadio(element) { 1654 | this.element_ = element; 1655 | // Initialize instance. 1656 | this.init(); 1657 | }; 1658 | window['MaterialRadio'] = MaterialRadio; 1659 | /** 1660 | * Store constants in one place so they can be updated easily. 1661 | * 1662 | * @enum {string | number} 1663 | * @private 1664 | */ 1665 | MaterialRadio.prototype.Constant_ = { TINY_TIMEOUT: 0.001 }; 1666 | /** 1667 | * Store strings for class names defined by this component that are used in 1668 | * JavaScript. This allows us to simply change it in one place should we 1669 | * decide to modify at a later date. 1670 | * 1671 | * @enum {string} 1672 | * @private 1673 | */ 1674 | MaterialRadio.prototype.CssClasses_ = { 1675 | IS_FOCUSED: 'is-focused', 1676 | IS_DISABLED: 'is-disabled', 1677 | IS_CHECKED: 'is-checked', 1678 | IS_UPGRADED: 'is-upgraded', 1679 | JS_RADIO: 'mdl-js-radio', 1680 | RADIO_BTN: 'mdl-radio__button', 1681 | RADIO_OUTER_CIRCLE: 'mdl-radio__outer-circle', 1682 | RADIO_INNER_CIRCLE: 'mdl-radio__inner-circle', 1683 | RIPPLE_EFFECT: 'mdl-js-ripple-effect', 1684 | RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 1685 | RIPPLE_CONTAINER: 'mdl-radio__ripple-container', 1686 | RIPPLE_CENTER: 'mdl-ripple--center', 1687 | RIPPLE: 'mdl-ripple' 1688 | }; 1689 | /** 1690 | * Handle change of state. 1691 | * 1692 | * @param {Event} event The event that fired. 1693 | * @private 1694 | */ 1695 | MaterialRadio.prototype.onChange_ = function (event) { 1696 | // Since other radio buttons don't get change events, we need to look for 1697 | // them to update their classes. 1698 | var radios = document.getElementsByClassName(this.CssClasses_.JS_RADIO); 1699 | for (var i = 0; i < radios.length; i++) { 1700 | var button = radios[i].querySelector('.' + this.CssClasses_.RADIO_BTN); 1701 | // Different name == different group, so no point updating those. 1702 | if (button.getAttribute('name') === this.btnElement_.getAttribute('name')) { 1703 | if (typeof radios[i]['MaterialRadio'] !== 'undefined') { 1704 | radios[i]['MaterialRadio'].updateClasses_(); 1705 | } 1706 | } 1707 | } 1708 | }; 1709 | /** 1710 | * Handle focus. 1711 | * 1712 | * @param {Event} event The event that fired. 1713 | * @private 1714 | */ 1715 | MaterialRadio.prototype.onFocus_ = function (event) { 1716 | this.element_.classList.add(this.CssClasses_.IS_FOCUSED); 1717 | }; 1718 | /** 1719 | * Handle lost focus. 1720 | * 1721 | * @param {Event} event The event that fired. 1722 | * @private 1723 | */ 1724 | MaterialRadio.prototype.onBlur_ = function (event) { 1725 | this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); 1726 | }; 1727 | /** 1728 | * Handle mouseup. 1729 | * 1730 | * @param {Event} event The event that fired. 1731 | * @private 1732 | */ 1733 | MaterialRadio.prototype.onMouseup_ = function (event) { 1734 | this.blur_(); 1735 | }; 1736 | /** 1737 | * Update classes. 1738 | * 1739 | * @private 1740 | */ 1741 | MaterialRadio.prototype.updateClasses_ = function () { 1742 | this.checkDisabled(); 1743 | this.checkToggleState(); 1744 | }; 1745 | /** 1746 | * Add blur. 1747 | * 1748 | * @private 1749 | */ 1750 | MaterialRadio.prototype.blur_ = function () { 1751 | // TODO: figure out why there's a focus event being fired after our blur, 1752 | // so that we can avoid this hack. 1753 | window.setTimeout(function () { 1754 | this.btnElement_.blur(); 1755 | }.bind(this), this.Constant_.TINY_TIMEOUT); 1756 | }; 1757 | // Public methods. 1758 | /** 1759 | * Check the components disabled state. 1760 | * 1761 | * @public 1762 | */ 1763 | MaterialRadio.prototype.checkDisabled = function () { 1764 | if (this.btnElement_.disabled) { 1765 | this.element_.classList.add(this.CssClasses_.IS_DISABLED); 1766 | } else { 1767 | this.element_.classList.remove(this.CssClasses_.IS_DISABLED); 1768 | } 1769 | }; 1770 | MaterialRadio.prototype['checkDisabled'] = MaterialRadio.prototype.checkDisabled; 1771 | /** 1772 | * Check the components toggled state. 1773 | * 1774 | * @public 1775 | */ 1776 | MaterialRadio.prototype.checkToggleState = function () { 1777 | if (this.btnElement_.checked) { 1778 | this.element_.classList.add(this.CssClasses_.IS_CHECKED); 1779 | } else { 1780 | this.element_.classList.remove(this.CssClasses_.IS_CHECKED); 1781 | } 1782 | }; 1783 | MaterialRadio.prototype['checkToggleState'] = MaterialRadio.prototype.checkToggleState; 1784 | /** 1785 | * Disable radio. 1786 | * 1787 | * @public 1788 | */ 1789 | MaterialRadio.prototype.disable = function () { 1790 | this.btnElement_.disabled = true; 1791 | this.updateClasses_(); 1792 | }; 1793 | MaterialRadio.prototype['disable'] = MaterialRadio.prototype.disable; 1794 | /** 1795 | * Enable radio. 1796 | * 1797 | * @public 1798 | */ 1799 | MaterialRadio.prototype.enable = function () { 1800 | this.btnElement_.disabled = false; 1801 | this.updateClasses_(); 1802 | }; 1803 | MaterialRadio.prototype['enable'] = MaterialRadio.prototype.enable; 1804 | /** 1805 | * Check radio. 1806 | * 1807 | * @public 1808 | */ 1809 | MaterialRadio.prototype.check = function () { 1810 | this.btnElement_.checked = true; 1811 | this.onChange_(null); 1812 | }; 1813 | MaterialRadio.prototype['check'] = MaterialRadio.prototype.check; 1814 | /** 1815 | * Uncheck radio. 1816 | * 1817 | * @public 1818 | */ 1819 | MaterialRadio.prototype.uncheck = function () { 1820 | this.btnElement_.checked = false; 1821 | this.onChange_(null); 1822 | }; 1823 | MaterialRadio.prototype['uncheck'] = MaterialRadio.prototype.uncheck; 1824 | /** 1825 | * Initialize element. 1826 | */ 1827 | MaterialRadio.prototype.init = function () { 1828 | if (this.element_) { 1829 | this.btnElement_ = this.element_.querySelector('.' + this.CssClasses_.RADIO_BTN); 1830 | this.boundChangeHandler_ = this.onChange_.bind(this); 1831 | this.boundFocusHandler_ = this.onChange_.bind(this); 1832 | this.boundBlurHandler_ = this.onBlur_.bind(this); 1833 | this.boundMouseUpHandler_ = this.onMouseup_.bind(this); 1834 | var outerCircle = document.createElement('span'); 1835 | outerCircle.classList.add(this.CssClasses_.RADIO_OUTER_CIRCLE); 1836 | var innerCircle = document.createElement('span'); 1837 | innerCircle.classList.add(this.CssClasses_.RADIO_INNER_CIRCLE); 1838 | this.element_.appendChild(outerCircle); 1839 | this.element_.appendChild(innerCircle); 1840 | var rippleContainer; 1841 | if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { 1842 | this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); 1843 | rippleContainer = document.createElement('span'); 1844 | rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER); 1845 | rippleContainer.classList.add(this.CssClasses_.RIPPLE_EFFECT); 1846 | rippleContainer.classList.add(this.CssClasses_.RIPPLE_CENTER); 1847 | rippleContainer.addEventListener('mouseup', this.boundMouseUpHandler_); 1848 | var ripple = document.createElement('span'); 1849 | ripple.classList.add(this.CssClasses_.RIPPLE); 1850 | rippleContainer.appendChild(ripple); 1851 | this.element_.appendChild(rippleContainer); 1852 | } 1853 | this.btnElement_.addEventListener('change', this.boundChangeHandler_); 1854 | this.btnElement_.addEventListener('focus', this.boundFocusHandler_); 1855 | this.btnElement_.addEventListener('blur', this.boundBlurHandler_); 1856 | this.element_.addEventListener('mouseup', this.boundMouseUpHandler_); 1857 | this.updateClasses_(); 1858 | this.element_.classList.add(this.CssClasses_.IS_UPGRADED); 1859 | } 1860 | }; 1861 | // The component registers itself. It can assume componentHandler is available 1862 | // in the global scope. 1863 | componentHandler.register({ 1864 | constructor: MaterialRadio, 1865 | classAsString: 'MaterialRadio', 1866 | cssClass: 'mdl-js-radio', 1867 | widget: true 1868 | }); 1869 | /** 1870 | * @license 1871 | * Copyright 2015 Google Inc. All Rights Reserved. 1872 | * 1873 | * Licensed under the Apache License, Version 2.0 (the "License"); 1874 | * you may not use this file except in compliance with the License. 1875 | * You may obtain a copy of the License at 1876 | * 1877 | * http://www.apache.org/licenses/LICENSE-2.0 1878 | * 1879 | * Unless required by applicable law or agreed to in writing, software 1880 | * distributed under the License is distributed on an "AS IS" BASIS, 1881 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1882 | * See the License for the specific language governing permissions and 1883 | * limitations under the License. 1884 | */ 1885 | /** 1886 | * Class constructor for Slider MDL component. 1887 | * Implements MDL component design pattern defined at: 1888 | * https://github.com/jasonmayes/mdl-component-design-pattern 1889 | * 1890 | * @constructor 1891 | * @param {HTMLElement} element The element that will be upgraded. 1892 | */ 1893 | var MaterialSlider = function MaterialSlider(element) { 1894 | this.element_ = element; 1895 | // Browser feature detection. 1896 | this.isIE_ = window.navigator.msPointerEnabled; 1897 | // Initialize instance. 1898 | this.init(); 1899 | }; 1900 | window['MaterialSlider'] = MaterialSlider; 1901 | /** 1902 | * Store constants in one place so they can be updated easily. 1903 | * 1904 | * @enum {string | number} 1905 | * @private 1906 | */ 1907 | MaterialSlider.prototype.Constant_ = {}; 1908 | /** 1909 | * Store strings for class names defined by this component that are used in 1910 | * JavaScript. This allows us to simply change it in one place should we 1911 | * decide to modify at a later date. 1912 | * 1913 | * @enum {string} 1914 | * @private 1915 | */ 1916 | MaterialSlider.prototype.CssClasses_ = { 1917 | IE_CONTAINER: 'mdl-slider__ie-container', 1918 | SLIDER_CONTAINER: 'mdl-slider__container', 1919 | BACKGROUND_FLEX: 'mdl-slider__background-flex', 1920 | BACKGROUND_LOWER: 'mdl-slider__background-lower', 1921 | BACKGROUND_UPPER: 'mdl-slider__background-upper', 1922 | IS_LOWEST_VALUE: 'is-lowest-value', 1923 | IS_UPGRADED: 'is-upgraded' 1924 | }; 1925 | /** 1926 | * Handle input on element. 1927 | * 1928 | * @param {Event} event The event that fired. 1929 | * @private 1930 | */ 1931 | MaterialSlider.prototype.onInput_ = function (event) { 1932 | this.updateValueStyles_(); 1933 | }; 1934 | /** 1935 | * Handle change on element. 1936 | * 1937 | * @param {Event} event The event that fired. 1938 | * @private 1939 | */ 1940 | MaterialSlider.prototype.onChange_ = function (event) { 1941 | this.updateValueStyles_(); 1942 | }; 1943 | /** 1944 | * Handle mouseup on element. 1945 | * 1946 | * @param {Event} event The event that fired. 1947 | * @private 1948 | */ 1949 | MaterialSlider.prototype.onMouseUp_ = function (event) { 1950 | event.target.blur(); 1951 | }; 1952 | /** 1953 | * Handle mousedown on container element. 1954 | * This handler is purpose is to not require the use to click 1955 | * exactly on the 2px slider element, as FireFox seems to be very 1956 | * strict about this. 1957 | * 1958 | * @param {Event} event The event that fired. 1959 | * @private 1960 | * @suppress {missingProperties} 1961 | */ 1962 | MaterialSlider.prototype.onContainerMouseDown_ = function (event) { 1963 | // If this click is not on the parent element (but rather some child) 1964 | // ignore. It may still bubble up. 1965 | if (event.target !== this.element_.parentElement) { 1966 | return; 1967 | } 1968 | // Discard the original event and create a new event that 1969 | // is on the slider element. 1970 | event.preventDefault(); 1971 | var newEvent = new MouseEvent('mousedown', { 1972 | target: event.target, 1973 | buttons: event.buttons, 1974 | clientX: event.clientX, 1975 | clientY: this.element_.getBoundingClientRect().y 1976 | }); 1977 | this.element_.dispatchEvent(newEvent); 1978 | }; 1979 | /** 1980 | * Handle updating of values. 1981 | * 1982 | * @private 1983 | */ 1984 | MaterialSlider.prototype.updateValueStyles_ = function () { 1985 | // Calculate and apply percentages to div structure behind slider. 1986 | var fraction = (this.element_.value - this.element_.min) / (this.element_.max - this.element_.min); 1987 | if (fraction === 0) { 1988 | this.element_.classList.add(this.CssClasses_.IS_LOWEST_VALUE); 1989 | } else { 1990 | this.element_.classList.remove(this.CssClasses_.IS_LOWEST_VALUE); 1991 | } 1992 | if (!this.isIE_) { 1993 | this.backgroundLower_.style.flex = fraction; 1994 | this.backgroundLower_.style.webkitFlex = fraction; 1995 | this.backgroundUpper_.style.flex = 1 - fraction; 1996 | this.backgroundUpper_.style.webkitFlex = 1 - fraction; 1997 | } 1998 | }; 1999 | // Public methods. 2000 | /** 2001 | * Disable slider. 2002 | * 2003 | * @public 2004 | */ 2005 | MaterialSlider.prototype.disable = function () { 2006 | this.element_.disabled = true; 2007 | }; 2008 | MaterialSlider.prototype['disable'] = MaterialSlider.prototype.disable; 2009 | /** 2010 | * Enable slider. 2011 | * 2012 | * @public 2013 | */ 2014 | MaterialSlider.prototype.enable = function () { 2015 | this.element_.disabled = false; 2016 | }; 2017 | MaterialSlider.prototype['enable'] = MaterialSlider.prototype.enable; 2018 | /** 2019 | * Update slider value. 2020 | * 2021 | * @param {number} value The value to which to set the control (optional). 2022 | * @public 2023 | */ 2024 | MaterialSlider.prototype.change = function (value) { 2025 | if (typeof value !== 'undefined') { 2026 | this.element_.value = value; 2027 | } 2028 | this.updateValueStyles_(); 2029 | }; 2030 | MaterialSlider.prototype['change'] = MaterialSlider.prototype.change; 2031 | /** 2032 | * Initialize element. 2033 | */ 2034 | MaterialSlider.prototype.init = function () { 2035 | if (this.element_) { 2036 | if (this.isIE_) { 2037 | // Since we need to specify a very large height in IE due to 2038 | // implementation limitations, we add a parent here that trims it down to 2039 | // a reasonable size. 2040 | var containerIE = document.createElement('div'); 2041 | containerIE.classList.add(this.CssClasses_.IE_CONTAINER); 2042 | this.element_.parentElement.insertBefore(containerIE, this.element_); 2043 | this.element_.parentElement.removeChild(this.element_); 2044 | containerIE.appendChild(this.element_); 2045 | } else { 2046 | // For non-IE browsers, we need a div structure that sits behind the 2047 | // slider and allows us to style the left and right sides of it with 2048 | // different colors. 2049 | var container = document.createElement('div'); 2050 | container.classList.add(this.CssClasses_.SLIDER_CONTAINER); 2051 | this.element_.parentElement.insertBefore(container, this.element_); 2052 | this.element_.parentElement.removeChild(this.element_); 2053 | container.appendChild(this.element_); 2054 | var backgroundFlex = document.createElement('div'); 2055 | backgroundFlex.classList.add(this.CssClasses_.BACKGROUND_FLEX); 2056 | container.appendChild(backgroundFlex); 2057 | this.backgroundLower_ = document.createElement('div'); 2058 | this.backgroundLower_.classList.add(this.CssClasses_.BACKGROUND_LOWER); 2059 | backgroundFlex.appendChild(this.backgroundLower_); 2060 | this.backgroundUpper_ = document.createElement('div'); 2061 | this.backgroundUpper_.classList.add(this.CssClasses_.BACKGROUND_UPPER); 2062 | backgroundFlex.appendChild(this.backgroundUpper_); 2063 | } 2064 | this.boundInputHandler = this.onInput_.bind(this); 2065 | this.boundChangeHandler = this.onChange_.bind(this); 2066 | this.boundMouseUpHandler = this.onMouseUp_.bind(this); 2067 | this.boundContainerMouseDownHandler = this.onContainerMouseDown_.bind(this); 2068 | this.element_.addEventListener('input', this.boundInputHandler); 2069 | this.element_.addEventListener('change', this.boundChangeHandler); 2070 | this.element_.addEventListener('mouseup', this.boundMouseUpHandler); 2071 | this.element_.parentElement.addEventListener('mousedown', this.boundContainerMouseDownHandler); 2072 | this.updateValueStyles_(); 2073 | this.element_.classList.add(this.CssClasses_.IS_UPGRADED); 2074 | } 2075 | }; 2076 | // The component registers itself. It can assume componentHandler is available 2077 | // in the global scope. 2078 | componentHandler.register({ 2079 | constructor: MaterialSlider, 2080 | classAsString: 'MaterialSlider', 2081 | cssClass: 'mdl-js-slider', 2082 | widget: true 2083 | }); 2084 | /** 2085 | * Copyright 2015 Google Inc. All Rights Reserved. 2086 | * 2087 | * Licensed under the Apache License, Version 2.0 (the "License"); 2088 | * you may not use this file except in compliance with the License. 2089 | * You may obtain a copy of the License at 2090 | * 2091 | * http://www.apache.org/licenses/LICENSE-2.0 2092 | * 2093 | * Unless required by applicable law or agreed to in writing, software 2094 | * distributed under the License is distributed on an "AS IS" BASIS, 2095 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2096 | * See the License for the specific language governing permissions and 2097 | * limitations under the License. 2098 | */ 2099 | /** 2100 | * Class constructor for Snackbar MDL component. 2101 | * Implements MDL component design pattern defined at: 2102 | * https://github.com/jasonmayes/mdl-component-design-pattern 2103 | * 2104 | * @constructor 2105 | * @param {HTMLElement} element The element that will be upgraded. 2106 | */ 2107 | var MaterialSnackbar = function MaterialSnackbar(element) { 2108 | this.element_ = element; 2109 | this.textElement_ = this.element_.querySelector('.' + this.cssClasses_.MESSAGE); 2110 | this.actionElement_ = this.element_.querySelector('.' + this.cssClasses_.ACTION); 2111 | if (!this.textElement_) { 2112 | throw new Error('There must be a message element for a snackbar.'); 2113 | } 2114 | if (!this.actionElement_) { 2115 | throw new Error('There must be an action element for a snackbar.'); 2116 | } 2117 | this.active = false; 2118 | this.actionHandler_ = undefined; 2119 | this.message_ = undefined; 2120 | this.actionText_ = undefined; 2121 | this.queuedNotifications_ = []; 2122 | this.setActionHidden_(true); 2123 | }; 2124 | window['MaterialSnackbar'] = MaterialSnackbar; 2125 | /** 2126 | * Store constants in one place so they can be updated easily. 2127 | * 2128 | * @enum {string | number} 2129 | * @private 2130 | */ 2131 | MaterialSnackbar.prototype.Constant_ = { 2132 | // The duration of the snackbar show/hide animation, in ms. 2133 | ANIMATION_LENGTH: 250 2134 | }; 2135 | /** 2136 | * Store strings for class names defined by this component that are used in 2137 | * JavaScript. This allows us to simply change it in one place should we 2138 | * decide to modify at a later date. 2139 | * 2140 | * @enum {string} 2141 | * @private 2142 | */ 2143 | MaterialSnackbar.prototype.cssClasses_ = { 2144 | SNACKBAR: 'mdl-snackbar', 2145 | MESSAGE: 'mdl-snackbar__text', 2146 | ACTION: 'mdl-snackbar__action', 2147 | ACTIVE: 'mdl-snackbar--active' 2148 | }; 2149 | /** 2150 | * Display the snackbar. 2151 | * 2152 | * @private 2153 | */ 2154 | MaterialSnackbar.prototype.displaySnackbar_ = function () { 2155 | this.element_.setAttribute('aria-hidden', 'true'); 2156 | if (this.actionHandler_) { 2157 | this.actionElement_.textContent = this.actionText_; 2158 | this.actionElement_.addEventListener('click', this.actionHandler_); 2159 | this.setActionHidden_(false); 2160 | } 2161 | this.textElement_.textContent = this.message_; 2162 | this.element_.classList.add(this.cssClasses_.ACTIVE); 2163 | this.element_.setAttribute('aria-hidden', 'false'); 2164 | setTimeout(this.cleanup_.bind(this), this.timeout_); 2165 | }; 2166 | /** 2167 | * Show the snackbar. 2168 | * 2169 | * @param {Object} data The data for the notification. 2170 | * @public 2171 | */ 2172 | MaterialSnackbar.prototype.showSnackbar = function (data) { 2173 | if (data === undefined) { 2174 | throw new Error('Please provide a data object with at least a message to display.'); 2175 | } 2176 | if (data['message'] === undefined) { 2177 | throw new Error('Please provide a message to be displayed.'); 2178 | } 2179 | if (data['actionHandler'] && !data['actionText']) { 2180 | throw new Error('Please provide action text with the handler.'); 2181 | } 2182 | if (this.active) { 2183 | this.queuedNotifications_.push(data); 2184 | } else { 2185 | this.active = true; 2186 | this.message_ = data['message']; 2187 | if (data['timeout']) { 2188 | this.timeout_ = data['timeout']; 2189 | } else { 2190 | this.timeout_ = 2750; 2191 | } 2192 | if (data['actionHandler']) { 2193 | this.actionHandler_ = data['actionHandler']; 2194 | } 2195 | if (data['actionText']) { 2196 | this.actionText_ = data['actionText']; 2197 | } 2198 | this.displaySnackbar_(); 2199 | } 2200 | }; 2201 | MaterialSnackbar.prototype['showSnackbar'] = MaterialSnackbar.prototype.showSnackbar; 2202 | /** 2203 | * Check if the queue has items within it. 2204 | * If it does, display the next entry. 2205 | * 2206 | * @private 2207 | */ 2208 | MaterialSnackbar.prototype.checkQueue_ = function () { 2209 | if (this.queuedNotifications_.length > 0) { 2210 | this.showSnackbar(this.queuedNotifications_.shift()); 2211 | } 2212 | }; 2213 | /** 2214 | * Cleanup the snackbar event listeners and accessiblity attributes. 2215 | * 2216 | * @private 2217 | */ 2218 | MaterialSnackbar.prototype.cleanup_ = function () { 2219 | this.element_.classList.remove(this.cssClasses_.ACTIVE); 2220 | setTimeout(function () { 2221 | this.element_.setAttribute('aria-hidden', 'true'); 2222 | this.textElement_.textContent = ''; 2223 | if (!Boolean(this.actionElement_.getAttribute('aria-hidden'))) { 2224 | this.setActionHidden_(true); 2225 | this.actionElement_.textContent = ''; 2226 | this.actionElement_.removeEventListener('click', this.actionHandler_); 2227 | } 2228 | this.actionHandler_ = undefined; 2229 | this.message_ = undefined; 2230 | this.actionText_ = undefined; 2231 | this.active = false; 2232 | this.checkQueue_(); 2233 | }.bind(this), this.Constant_.ANIMATION_LENGTH); 2234 | }; 2235 | /** 2236 | * Set the action handler hidden state. 2237 | * 2238 | * @param {boolean} value 2239 | * @private 2240 | */ 2241 | MaterialSnackbar.prototype.setActionHidden_ = function (value) { 2242 | if (value) { 2243 | this.actionElement_.setAttribute('aria-hidden', 'true'); 2244 | } else { 2245 | this.actionElement_.removeAttribute('aria-hidden'); 2246 | } 2247 | }; 2248 | // The component registers itself. It can assume componentHandler is available 2249 | // in the global scope. 2250 | componentHandler.register({ 2251 | constructor: MaterialSnackbar, 2252 | classAsString: 'MaterialSnackbar', 2253 | cssClass: 'mdl-js-snackbar', 2254 | widget: true 2255 | }); 2256 | /** 2257 | * @license 2258 | * Copyright 2015 Google Inc. All Rights Reserved. 2259 | * 2260 | * Licensed under the Apache License, Version 2.0 (the "License"); 2261 | * you may not use this file except in compliance with the License. 2262 | * You may obtain a copy of the License at 2263 | * 2264 | * http://www.apache.org/licenses/LICENSE-2.0 2265 | * 2266 | * Unless required by applicable law or agreed to in writing, software 2267 | * distributed under the License is distributed on an "AS IS" BASIS, 2268 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2269 | * See the License for the specific language governing permissions and 2270 | * limitations under the License. 2271 | */ 2272 | /** 2273 | * Class constructor for Spinner MDL component. 2274 | * Implements MDL component design pattern defined at: 2275 | * https://github.com/jasonmayes/mdl-component-design-pattern 2276 | * 2277 | * @param {HTMLElement} element The element that will be upgraded. 2278 | * @constructor 2279 | */ 2280 | var MaterialSpinner = function MaterialSpinner(element) { 2281 | this.element_ = element; 2282 | // Initialize instance. 2283 | this.init(); 2284 | }; 2285 | window['MaterialSpinner'] = MaterialSpinner; 2286 | /** 2287 | * Store constants in one place so they can be updated easily. 2288 | * 2289 | * @enum {string | number} 2290 | * @private 2291 | */ 2292 | MaterialSpinner.prototype.Constant_ = { MDL_SPINNER_LAYER_COUNT: 4 }; 2293 | /** 2294 | * Store strings for class names defined by this component that are used in 2295 | * JavaScript. This allows us to simply change it in one place should we 2296 | * decide to modify at a later date. 2297 | * 2298 | * @enum {string} 2299 | * @private 2300 | */ 2301 | MaterialSpinner.prototype.CssClasses_ = { 2302 | MDL_SPINNER_LAYER: 'mdl-spinner__layer', 2303 | MDL_SPINNER_CIRCLE_CLIPPER: 'mdl-spinner__circle-clipper', 2304 | MDL_SPINNER_CIRCLE: 'mdl-spinner__circle', 2305 | MDL_SPINNER_GAP_PATCH: 'mdl-spinner__gap-patch', 2306 | MDL_SPINNER_LEFT: 'mdl-spinner__left', 2307 | MDL_SPINNER_RIGHT: 'mdl-spinner__right' 2308 | }; 2309 | /** 2310 | * Auxiliary method to create a spinner layer. 2311 | * 2312 | * @param {number} index Index of the layer to be created. 2313 | * @public 2314 | */ 2315 | MaterialSpinner.prototype.createLayer = function (index) { 2316 | var layer = document.createElement('div'); 2317 | layer.classList.add(this.CssClasses_.MDL_SPINNER_LAYER); 2318 | layer.classList.add(this.CssClasses_.MDL_SPINNER_LAYER + '-' + index); 2319 | var leftClipper = document.createElement('div'); 2320 | leftClipper.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER); 2321 | leftClipper.classList.add(this.CssClasses_.MDL_SPINNER_LEFT); 2322 | var gapPatch = document.createElement('div'); 2323 | gapPatch.classList.add(this.CssClasses_.MDL_SPINNER_GAP_PATCH); 2324 | var rightClipper = document.createElement('div'); 2325 | rightClipper.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER); 2326 | rightClipper.classList.add(this.CssClasses_.MDL_SPINNER_RIGHT); 2327 | var circleOwners = [ 2328 | leftClipper, 2329 | gapPatch, 2330 | rightClipper 2331 | ]; 2332 | for (var i = 0; i < circleOwners.length; i++) { 2333 | var circle = document.createElement('div'); 2334 | circle.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE); 2335 | circleOwners[i].appendChild(circle); 2336 | } 2337 | layer.appendChild(leftClipper); 2338 | layer.appendChild(gapPatch); 2339 | layer.appendChild(rightClipper); 2340 | this.element_.appendChild(layer); 2341 | }; 2342 | MaterialSpinner.prototype['createLayer'] = MaterialSpinner.prototype.createLayer; 2343 | /** 2344 | * Stops the spinner animation. 2345 | * Public method for users who need to stop the spinner for any reason. 2346 | * 2347 | * @public 2348 | */ 2349 | MaterialSpinner.prototype.stop = function () { 2350 | this.element_.classList.remove('is-active'); 2351 | }; 2352 | MaterialSpinner.prototype['stop'] = MaterialSpinner.prototype.stop; 2353 | /** 2354 | * Starts the spinner animation. 2355 | * Public method for users who need to manually start the spinner for any reason 2356 | * (instead of just adding the 'is-active' class to their markup). 2357 | * 2358 | * @public 2359 | */ 2360 | MaterialSpinner.prototype.start = function () { 2361 | this.element_.classList.add('is-active'); 2362 | }; 2363 | MaterialSpinner.prototype['start'] = MaterialSpinner.prototype.start; 2364 | /** 2365 | * Initialize element. 2366 | */ 2367 | MaterialSpinner.prototype.init = function () { 2368 | if (this.element_) { 2369 | for (var i = 1; i <= this.Constant_.MDL_SPINNER_LAYER_COUNT; i++) { 2370 | this.createLayer(i); 2371 | } 2372 | this.element_.classList.add('is-upgraded'); 2373 | } 2374 | }; 2375 | // The component registers itself. It can assume componentHandler is available 2376 | // in the global scope. 2377 | componentHandler.register({ 2378 | constructor: MaterialSpinner, 2379 | classAsString: 'MaterialSpinner', 2380 | cssClass: 'mdl-js-spinner', 2381 | widget: true 2382 | }); 2383 | /** 2384 | * @license 2385 | * Copyright 2015 Google Inc. All Rights Reserved. 2386 | * 2387 | * Licensed under the Apache License, Version 2.0 (the "License"); 2388 | * you may not use this file except in compliance with the License. 2389 | * You may obtain a copy of the License at 2390 | * 2391 | * http://www.apache.org/licenses/LICENSE-2.0 2392 | * 2393 | * Unless required by applicable law or agreed to in writing, software 2394 | * distributed under the License is distributed on an "AS IS" BASIS, 2395 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2396 | * See the License for the specific language governing permissions and 2397 | * limitations under the License. 2398 | */ 2399 | /** 2400 | * Class constructor for Checkbox MDL component. 2401 | * Implements MDL component design pattern defined at: 2402 | * https://github.com/jasonmayes/mdl-component-design-pattern 2403 | * 2404 | * @constructor 2405 | * @param {HTMLElement} element The element that will be upgraded. 2406 | */ 2407 | var MaterialSwitch = function MaterialSwitch(element) { 2408 | this.element_ = element; 2409 | // Initialize instance. 2410 | this.init(); 2411 | }; 2412 | window['MaterialSwitch'] = MaterialSwitch; 2413 | /** 2414 | * Store constants in one place so they can be updated easily. 2415 | * 2416 | * @enum {string | number} 2417 | * @private 2418 | */ 2419 | MaterialSwitch.prototype.Constant_ = { TINY_TIMEOUT: 0.001 }; 2420 | /** 2421 | * Store strings for class names defined by this component that are used in 2422 | * JavaScript. This allows us to simply change it in one place should we 2423 | * decide to modify at a later date. 2424 | * 2425 | * @enum {string} 2426 | * @private 2427 | */ 2428 | MaterialSwitch.prototype.CssClasses_ = { 2429 | INPUT: 'mdl-switch__input', 2430 | TRACK: 'mdl-switch__track', 2431 | THUMB: 'mdl-switch__thumb', 2432 | FOCUS_HELPER: 'mdl-switch__focus-helper', 2433 | RIPPLE_EFFECT: 'mdl-js-ripple-effect', 2434 | RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 2435 | RIPPLE_CONTAINER: 'mdl-switch__ripple-container', 2436 | RIPPLE_CENTER: 'mdl-ripple--center', 2437 | RIPPLE: 'mdl-ripple', 2438 | IS_FOCUSED: 'is-focused', 2439 | IS_DISABLED: 'is-disabled', 2440 | IS_CHECKED: 'is-checked' 2441 | }; 2442 | /** 2443 | * Handle change of state. 2444 | * 2445 | * @param {Event} event The event that fired. 2446 | * @private 2447 | */ 2448 | MaterialSwitch.prototype.onChange_ = function (event) { 2449 | this.updateClasses_(); 2450 | }; 2451 | /** 2452 | * Handle focus of element. 2453 | * 2454 | * @param {Event} event The event that fired. 2455 | * @private 2456 | */ 2457 | MaterialSwitch.prototype.onFocus_ = function (event) { 2458 | this.element_.classList.add(this.CssClasses_.IS_FOCUSED); 2459 | }; 2460 | /** 2461 | * Handle lost focus of element. 2462 | * 2463 | * @param {Event} event The event that fired. 2464 | * @private 2465 | */ 2466 | MaterialSwitch.prototype.onBlur_ = function (event) { 2467 | this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); 2468 | }; 2469 | /** 2470 | * Handle mouseup. 2471 | * 2472 | * @param {Event} event The event that fired. 2473 | * @private 2474 | */ 2475 | MaterialSwitch.prototype.onMouseUp_ = function (event) { 2476 | this.blur_(); 2477 | }; 2478 | /** 2479 | * Handle class updates. 2480 | * 2481 | * @private 2482 | */ 2483 | MaterialSwitch.prototype.updateClasses_ = function () { 2484 | this.checkDisabled(); 2485 | this.checkToggleState(); 2486 | }; 2487 | /** 2488 | * Add blur. 2489 | * 2490 | * @private 2491 | */ 2492 | MaterialSwitch.prototype.blur_ = function () { 2493 | // TODO: figure out why there's a focus event being fired after our blur, 2494 | // so that we can avoid this hack. 2495 | window.setTimeout(function () { 2496 | this.inputElement_.blur(); 2497 | }.bind(this), this.Constant_.TINY_TIMEOUT); 2498 | }; 2499 | // Public methods. 2500 | /** 2501 | * Check the components disabled state. 2502 | * 2503 | * @public 2504 | */ 2505 | MaterialSwitch.prototype.checkDisabled = function () { 2506 | if (this.inputElement_.disabled) { 2507 | this.element_.classList.add(this.CssClasses_.IS_DISABLED); 2508 | } else { 2509 | this.element_.classList.remove(this.CssClasses_.IS_DISABLED); 2510 | } 2511 | }; 2512 | MaterialSwitch.prototype['checkDisabled'] = MaterialSwitch.prototype.checkDisabled; 2513 | /** 2514 | * Check the components toggled state. 2515 | * 2516 | * @public 2517 | */ 2518 | MaterialSwitch.prototype.checkToggleState = function () { 2519 | if (this.inputElement_.checked) { 2520 | this.element_.classList.add(this.CssClasses_.IS_CHECKED); 2521 | } else { 2522 | this.element_.classList.remove(this.CssClasses_.IS_CHECKED); 2523 | } 2524 | }; 2525 | MaterialSwitch.prototype['checkToggleState'] = MaterialSwitch.prototype.checkToggleState; 2526 | /** 2527 | * Disable switch. 2528 | * 2529 | * @public 2530 | */ 2531 | MaterialSwitch.prototype.disable = function () { 2532 | this.inputElement_.disabled = true; 2533 | this.updateClasses_(); 2534 | }; 2535 | MaterialSwitch.prototype['disable'] = MaterialSwitch.prototype.disable; 2536 | /** 2537 | * Enable switch. 2538 | * 2539 | * @public 2540 | */ 2541 | MaterialSwitch.prototype.enable = function () { 2542 | this.inputElement_.disabled = false; 2543 | this.updateClasses_(); 2544 | }; 2545 | MaterialSwitch.prototype['enable'] = MaterialSwitch.prototype.enable; 2546 | /** 2547 | * Activate switch. 2548 | * 2549 | * @public 2550 | */ 2551 | MaterialSwitch.prototype.on = function () { 2552 | this.inputElement_.checked = true; 2553 | this.updateClasses_(); 2554 | }; 2555 | MaterialSwitch.prototype['on'] = MaterialSwitch.prototype.on; 2556 | /** 2557 | * Deactivate switch. 2558 | * 2559 | * @public 2560 | */ 2561 | MaterialSwitch.prototype.off = function () { 2562 | this.inputElement_.checked = false; 2563 | this.updateClasses_(); 2564 | }; 2565 | MaterialSwitch.prototype['off'] = MaterialSwitch.prototype.off; 2566 | /** 2567 | * Initialize element. 2568 | */ 2569 | MaterialSwitch.prototype.init = function () { 2570 | if (this.element_) { 2571 | this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT); 2572 | var track = document.createElement('div'); 2573 | track.classList.add(this.CssClasses_.TRACK); 2574 | var thumb = document.createElement('div'); 2575 | thumb.classList.add(this.CssClasses_.THUMB); 2576 | var focusHelper = document.createElement('span'); 2577 | focusHelper.classList.add(this.CssClasses_.FOCUS_HELPER); 2578 | thumb.appendChild(focusHelper); 2579 | this.element_.appendChild(track); 2580 | this.element_.appendChild(thumb); 2581 | this.boundMouseUpHandler = this.onMouseUp_.bind(this); 2582 | if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { 2583 | this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); 2584 | this.rippleContainerElement_ = document.createElement('span'); 2585 | this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER); 2586 | this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT); 2587 | this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER); 2588 | this.rippleContainerElement_.addEventListener('mouseup', this.boundMouseUpHandler); 2589 | var ripple = document.createElement('span'); 2590 | ripple.classList.add(this.CssClasses_.RIPPLE); 2591 | this.rippleContainerElement_.appendChild(ripple); 2592 | this.element_.appendChild(this.rippleContainerElement_); 2593 | } 2594 | this.boundChangeHandler = this.onChange_.bind(this); 2595 | this.boundFocusHandler = this.onFocus_.bind(this); 2596 | this.boundBlurHandler = this.onBlur_.bind(this); 2597 | this.inputElement_.addEventListener('change', this.boundChangeHandler); 2598 | this.inputElement_.addEventListener('focus', this.boundFocusHandler); 2599 | this.inputElement_.addEventListener('blur', this.boundBlurHandler); 2600 | this.element_.addEventListener('mouseup', this.boundMouseUpHandler); 2601 | this.updateClasses_(); 2602 | this.element_.classList.add('is-upgraded'); 2603 | } 2604 | }; 2605 | // The component registers itself. It can assume componentHandler is available 2606 | // in the global scope. 2607 | componentHandler.register({ 2608 | constructor: MaterialSwitch, 2609 | classAsString: 'MaterialSwitch', 2610 | cssClass: 'mdl-js-switch', 2611 | widget: true 2612 | }); 2613 | /** 2614 | * @license 2615 | * Copyright 2015 Google Inc. All Rights Reserved. 2616 | * 2617 | * Licensed under the Apache License, Version 2.0 (the "License"); 2618 | * you may not use this file except in compliance with the License. 2619 | * You may obtain a copy of the License at 2620 | * 2621 | * http://www.apache.org/licenses/LICENSE-2.0 2622 | * 2623 | * Unless required by applicable law or agreed to in writing, software 2624 | * distributed under the License is distributed on an "AS IS" BASIS, 2625 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2626 | * See the License for the specific language governing permissions and 2627 | * limitations under the License. 2628 | */ 2629 | /** 2630 | * Class constructor for Tabs MDL component. 2631 | * Implements MDL component design pattern defined at: 2632 | * https://github.com/jasonmayes/mdl-component-design-pattern 2633 | * 2634 | * @constructor 2635 | * @param {Element} element The element that will be upgraded. 2636 | */ 2637 | var MaterialTabs = function MaterialTabs(element) { 2638 | // Stores the HTML element. 2639 | this.element_ = element; 2640 | // Initialize instance. 2641 | this.init(); 2642 | }; 2643 | window['MaterialTabs'] = MaterialTabs; 2644 | /** 2645 | * Store constants in one place so they can be updated easily. 2646 | * 2647 | * @enum {string} 2648 | * @private 2649 | */ 2650 | MaterialTabs.prototype.Constant_ = {}; 2651 | /** 2652 | * Store strings for class names defined by this component that are used in 2653 | * JavaScript. This allows us to simply change it in one place should we 2654 | * decide to modify at a later date. 2655 | * 2656 | * @enum {string} 2657 | * @private 2658 | */ 2659 | MaterialTabs.prototype.CssClasses_ = { 2660 | TAB_CLASS: 'mdl-tabs__tab', 2661 | PANEL_CLASS: 'mdl-tabs__panel', 2662 | ACTIVE_CLASS: 'is-active', 2663 | UPGRADED_CLASS: 'is-upgraded', 2664 | MDL_JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect', 2665 | MDL_RIPPLE_CONTAINER: 'mdl-tabs__ripple-container', 2666 | MDL_RIPPLE: 'mdl-ripple', 2667 | MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events' 2668 | }; 2669 | /** 2670 | * Handle clicks to a tabs component 2671 | * 2672 | * @private 2673 | */ 2674 | MaterialTabs.prototype.initTabs_ = function () { 2675 | if (this.element_.classList.contains(this.CssClasses_.MDL_JS_RIPPLE_EFFECT)) { 2676 | this.element_.classList.add(this.CssClasses_.MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS); 2677 | } 2678 | // Select element tabs, document panels 2679 | this.tabs_ = this.element_.querySelectorAll('.' + this.CssClasses_.TAB_CLASS); 2680 | this.panels_ = this.element_.querySelectorAll('.' + this.CssClasses_.PANEL_CLASS); 2681 | // Create new tabs for each tab element 2682 | for (var i = 0; i < this.tabs_.length; i++) { 2683 | new MaterialTab(this.tabs_[i], this); 2684 | } 2685 | this.element_.classList.add(this.CssClasses_.UPGRADED_CLASS); 2686 | }; 2687 | /** 2688 | * Reset tab state, dropping active classes 2689 | * 2690 | * @private 2691 | */ 2692 | MaterialTabs.prototype.resetTabState_ = function () { 2693 | for (var k = 0; k < this.tabs_.length; k++) { 2694 | this.tabs_[k].classList.remove(this.CssClasses_.ACTIVE_CLASS); 2695 | } 2696 | }; 2697 | /** 2698 | * Reset panel state, droping active classes 2699 | * 2700 | * @private 2701 | */ 2702 | MaterialTabs.prototype.resetPanelState_ = function () { 2703 | for (var j = 0; j < this.panels_.length; j++) { 2704 | this.panels_[j].classList.remove(this.CssClasses_.ACTIVE_CLASS); 2705 | } 2706 | }; 2707 | /** 2708 | * Initialize element. 2709 | */ 2710 | MaterialTabs.prototype.init = function () { 2711 | if (this.element_) { 2712 | this.initTabs_(); 2713 | } 2714 | }; 2715 | /** 2716 | * Constructor for an individual tab. 2717 | * 2718 | * @constructor 2719 | * @param {Element} tab The HTML element for the tab. 2720 | * @param {MaterialTabs} ctx The MaterialTabs object that owns the tab. 2721 | */ 2722 | function MaterialTab(tab, ctx) { 2723 | if (tab) { 2724 | if (ctx.element_.classList.contains(ctx.CssClasses_.MDL_JS_RIPPLE_EFFECT)) { 2725 | var rippleContainer = document.createElement('span'); 2726 | rippleContainer.classList.add(ctx.CssClasses_.MDL_RIPPLE_CONTAINER); 2727 | rippleContainer.classList.add(ctx.CssClasses_.MDL_JS_RIPPLE_EFFECT); 2728 | var ripple = document.createElement('span'); 2729 | ripple.classList.add(ctx.CssClasses_.MDL_RIPPLE); 2730 | rippleContainer.appendChild(ripple); 2731 | tab.appendChild(rippleContainer); 2732 | } 2733 | tab.addEventListener('click', function (e) { 2734 | if (tab.getAttribute('href').charAt(0) === '#') { 2735 | e.preventDefault(); 2736 | var href = tab.href.split('#')[1]; 2737 | var panel = ctx.element_.querySelector('#' + href); 2738 | ctx.resetTabState_(); 2739 | ctx.resetPanelState_(); 2740 | tab.classList.add(ctx.CssClasses_.ACTIVE_CLASS); 2741 | panel.classList.add(ctx.CssClasses_.ACTIVE_CLASS); 2742 | } 2743 | }); 2744 | } 2745 | } 2746 | // The component registers itself. It can assume componentHandler is available 2747 | // in the global scope. 2748 | componentHandler.register({ 2749 | constructor: MaterialTabs, 2750 | classAsString: 'MaterialTabs', 2751 | cssClass: 'mdl-js-tabs' 2752 | }); 2753 | /** 2754 | * @license 2755 | * Copyright 2015 Google Inc. All Rights Reserved. 2756 | * 2757 | * Licensed under the Apache License, Version 2.0 (the "License"); 2758 | * you may not use this file except in compliance with the License. 2759 | * You may obtain a copy of the License at 2760 | * 2761 | * http://www.apache.org/licenses/LICENSE-2.0 2762 | * 2763 | * Unless required by applicable law or agreed to in writing, software 2764 | * distributed under the License is distributed on an "AS IS" BASIS, 2765 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2766 | * See the License for the specific language governing permissions and 2767 | * limitations under the License. 2768 | */ 2769 | /** 2770 | * Class constructor for Textfield MDL component. 2771 | * Implements MDL component design pattern defined at: 2772 | * https://github.com/jasonmayes/mdl-component-design-pattern 2773 | * 2774 | * @constructor 2775 | * @param {HTMLElement} element The element that will be upgraded. 2776 | */ 2777 | var MaterialTextfield = function MaterialTextfield(element) { 2778 | this.element_ = element; 2779 | this.maxRows = this.Constant_.NO_MAX_ROWS; 2780 | // Initialize instance. 2781 | this.init(); 2782 | }; 2783 | window['MaterialTextfield'] = MaterialTextfield; 2784 | /** 2785 | * Store constants in one place so they can be updated easily. 2786 | * 2787 | * @enum {string | number} 2788 | * @private 2789 | */ 2790 | MaterialTextfield.prototype.Constant_ = { 2791 | NO_MAX_ROWS: -1, 2792 | MAX_ROWS_ATTRIBUTE: 'maxrows' 2793 | }; 2794 | /** 2795 | * Store strings for class names defined by this component that are used in 2796 | * JavaScript. This allows us to simply change it in one place should we 2797 | * decide to modify at a later date. 2798 | * 2799 | * @enum {string} 2800 | * @private 2801 | */ 2802 | MaterialTextfield.prototype.CssClasses_ = { 2803 | LABEL: 'mdl-textfield__label', 2804 | INPUT: 'mdl-textfield__input', 2805 | IS_DIRTY: 'is-dirty', 2806 | IS_FOCUSED: 'is-focused', 2807 | IS_DISABLED: 'is-disabled', 2808 | IS_INVALID: 'is-invalid', 2809 | IS_UPGRADED: 'is-upgraded', 2810 | HAS_PLACEHOLDER: 'has-placeholder' 2811 | }; 2812 | /** 2813 | * Handle input being entered. 2814 | * 2815 | * @param {Event} event The event that fired. 2816 | * @private 2817 | */ 2818 | MaterialTextfield.prototype.onKeyDown_ = function (event) { 2819 | var currentRowCount = event.target.value.split('\n').length; 2820 | if (event.keyCode === 13) { 2821 | if (currentRowCount >= this.maxRows) { 2822 | event.preventDefault(); 2823 | } 2824 | } 2825 | }; 2826 | /** 2827 | * Handle focus. 2828 | * 2829 | * @param {Event} event The event that fired. 2830 | * @private 2831 | */ 2832 | MaterialTextfield.prototype.onFocus_ = function (event) { 2833 | this.element_.classList.add(this.CssClasses_.IS_FOCUSED); 2834 | }; 2835 | /** 2836 | * Handle lost focus. 2837 | * 2838 | * @param {Event} event The event that fired. 2839 | * @private 2840 | */ 2841 | MaterialTextfield.prototype.onBlur_ = function (event) { 2842 | this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); 2843 | }; 2844 | /** 2845 | * Handle reset event from out side. 2846 | * 2847 | * @param {Event} event The event that fired. 2848 | * @private 2849 | */ 2850 | MaterialTextfield.prototype.onReset_ = function (event) { 2851 | this.updateClasses_(); 2852 | }; 2853 | /** 2854 | * Handle class updates. 2855 | * 2856 | * @private 2857 | */ 2858 | MaterialTextfield.prototype.updateClasses_ = function () { 2859 | this.checkDisabled(); 2860 | this.checkValidity(); 2861 | this.checkDirty(); 2862 | this.checkFocus(); 2863 | }; 2864 | // Public methods. 2865 | /** 2866 | * Check the disabled state and update field accordingly. 2867 | * 2868 | * @public 2869 | */ 2870 | MaterialTextfield.prototype.checkDisabled = function () { 2871 | if (this.input_.disabled) { 2872 | this.element_.classList.add(this.CssClasses_.IS_DISABLED); 2873 | } else { 2874 | this.element_.classList.remove(this.CssClasses_.IS_DISABLED); 2875 | } 2876 | }; 2877 | MaterialTextfield.prototype['checkDisabled'] = MaterialTextfield.prototype.checkDisabled; 2878 | /** 2879 | * Check the focus state and update field accordingly. 2880 | * 2881 | * @public 2882 | */ 2883 | MaterialTextfield.prototype.checkFocus = function () { 2884 | if (Boolean(this.element_.querySelector(':focus'))) { 2885 | this.element_.classList.add(this.CssClasses_.IS_FOCUSED); 2886 | } else { 2887 | this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); 2888 | } 2889 | }; 2890 | MaterialTextfield.prototype['checkFocus'] = MaterialTextfield.prototype.checkFocus; 2891 | /** 2892 | * Check the validity state and update field accordingly. 2893 | * 2894 | * @public 2895 | */ 2896 | MaterialTextfield.prototype.checkValidity = function () { 2897 | if (this.input_.validity) { 2898 | if (this.input_.validity.valid) { 2899 | this.element_.classList.remove(this.CssClasses_.IS_INVALID); 2900 | } else { 2901 | this.element_.classList.add(this.CssClasses_.IS_INVALID); 2902 | } 2903 | } 2904 | }; 2905 | MaterialTextfield.prototype['checkValidity'] = MaterialTextfield.prototype.checkValidity; 2906 | /** 2907 | * Check the dirty state and update field accordingly. 2908 | * 2909 | * @public 2910 | */ 2911 | MaterialTextfield.prototype.checkDirty = function () { 2912 | if (this.input_.value && this.input_.value.length > 0) { 2913 | this.element_.classList.add(this.CssClasses_.IS_DIRTY); 2914 | } else { 2915 | this.element_.classList.remove(this.CssClasses_.IS_DIRTY); 2916 | } 2917 | }; 2918 | MaterialTextfield.prototype['checkDirty'] = MaterialTextfield.prototype.checkDirty; 2919 | /** 2920 | * Disable text field. 2921 | * 2922 | * @public 2923 | */ 2924 | MaterialTextfield.prototype.disable = function () { 2925 | this.input_.disabled = true; 2926 | this.updateClasses_(); 2927 | }; 2928 | MaterialTextfield.prototype['disable'] = MaterialTextfield.prototype.disable; 2929 | /** 2930 | * Enable text field. 2931 | * 2932 | * @public 2933 | */ 2934 | MaterialTextfield.prototype.enable = function () { 2935 | this.input_.disabled = false; 2936 | this.updateClasses_(); 2937 | }; 2938 | MaterialTextfield.prototype['enable'] = MaterialTextfield.prototype.enable; 2939 | /** 2940 | * Update text field value. 2941 | * 2942 | * @param {string} value The value to which to set the control (optional). 2943 | * @public 2944 | */ 2945 | MaterialTextfield.prototype.change = function (value) { 2946 | this.input_.value = value || ''; 2947 | this.updateClasses_(); 2948 | }; 2949 | MaterialTextfield.prototype['change'] = MaterialTextfield.prototype.change; 2950 | /** 2951 | * Initialize element. 2952 | */ 2953 | MaterialTextfield.prototype.init = function () { 2954 | if (this.element_) { 2955 | this.label_ = this.element_.querySelector('.' + this.CssClasses_.LABEL); 2956 | this.input_ = this.element_.querySelector('.' + this.CssClasses_.INPUT); 2957 | if (this.input_) { 2958 | if (this.input_.hasAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE)) { 2959 | this.maxRows = parseInt(this.input_.getAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE), 10); 2960 | if (isNaN(this.maxRows)) { 2961 | this.maxRows = this.Constant_.NO_MAX_ROWS; 2962 | } 2963 | } 2964 | if (this.input_.hasAttribute('placeholder')) { 2965 | this.element_.classList.add(this.CssClasses_.HAS_PLACEHOLDER); 2966 | } 2967 | this.boundUpdateClassesHandler = this.updateClasses_.bind(this); 2968 | this.boundFocusHandler = this.onFocus_.bind(this); 2969 | this.boundBlurHandler = this.onBlur_.bind(this); 2970 | this.boundResetHandler = this.onReset_.bind(this); 2971 | this.input_.addEventListener('input', this.boundUpdateClassesHandler); 2972 | this.input_.addEventListener('focus', this.boundFocusHandler); 2973 | this.input_.addEventListener('blur', this.boundBlurHandler); 2974 | this.input_.addEventListener('reset', this.boundResetHandler); 2975 | if (this.maxRows !== this.Constant_.NO_MAX_ROWS) { 2976 | // TODO: This should handle pasting multi line text. 2977 | // Currently doesn't. 2978 | this.boundKeyDownHandler = this.onKeyDown_.bind(this); 2979 | this.input_.addEventListener('keydown', this.boundKeyDownHandler); 2980 | } 2981 | var invalid = this.element_.classList.contains(this.CssClasses_.IS_INVALID); 2982 | this.updateClasses_(); 2983 | this.element_.classList.add(this.CssClasses_.IS_UPGRADED); 2984 | if (invalid) { 2985 | this.element_.classList.add(this.CssClasses_.IS_INVALID); 2986 | } 2987 | if (this.input_.hasAttribute('autofocus')) { 2988 | this.element_.focus(); 2989 | this.checkFocus(); 2990 | } 2991 | } 2992 | } 2993 | }; 2994 | // The component registers itself. It can assume componentHandler is available 2995 | // in the global scope. 2996 | componentHandler.register({ 2997 | constructor: MaterialTextfield, 2998 | classAsString: 'MaterialTextfield', 2999 | cssClass: 'mdl-js-textfield', 3000 | widget: true 3001 | }); 3002 | /** 3003 | * @license 3004 | * Copyright 2015 Google Inc. All Rights Reserved. 3005 | * 3006 | * Licensed under the Apache License, Version 2.0 (the "License"); 3007 | * you may not use this file except in compliance with the License. 3008 | * You may obtain a copy of the License at 3009 | * 3010 | * http://www.apache.org/licenses/LICENSE-2.0 3011 | * 3012 | * Unless required by applicable law or agreed to in writing, software 3013 | * distributed under the License is distributed on an "AS IS" BASIS, 3014 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3015 | * See the License for the specific language governing permissions and 3016 | * limitations under the License. 3017 | */ 3018 | /** 3019 | * Class constructor for Tooltip MDL component. 3020 | * Implements MDL component design pattern defined at: 3021 | * https://github.com/jasonmayes/mdl-component-design-pattern 3022 | * 3023 | * @constructor 3024 | * @param {HTMLElement} element The element that will be upgraded. 3025 | */ 3026 | var MaterialTooltip = function MaterialTooltip(element) { 3027 | this.element_ = element; 3028 | // Initialize instance. 3029 | this.init(); 3030 | }; 3031 | window['MaterialTooltip'] = MaterialTooltip; 3032 | /** 3033 | * Store constants in one place so they can be updated easily. 3034 | * 3035 | * @enum {string | number} 3036 | * @private 3037 | */ 3038 | MaterialTooltip.prototype.Constant_ = {}; 3039 | /** 3040 | * Store strings for class names defined by this component that are used in 3041 | * JavaScript. This allows us to simply change it in one place should we 3042 | * decide to modify at a later date. 3043 | * 3044 | * @enum {string} 3045 | * @private 3046 | */ 3047 | MaterialTooltip.prototype.CssClasses_ = { 3048 | IS_ACTIVE: 'is-active', 3049 | BOTTOM: 'mdl-tooltip--bottom', 3050 | LEFT: 'mdl-tooltip--left', 3051 | RIGHT: 'mdl-tooltip--right', 3052 | TOP: 'mdl-tooltip--top' 3053 | }; 3054 | /** 3055 | * Handle mouseenter for tooltip. 3056 | * 3057 | * @param {Event} event The event that fired. 3058 | * @private 3059 | */ 3060 | MaterialTooltip.prototype.handleMouseEnter_ = function (event) { 3061 | var props = event.target.getBoundingClientRect(); 3062 | var left = props.left + props.width / 2; 3063 | var top = props.top + props.height / 2; 3064 | var marginLeft = -1 * (this.element_.offsetWidth / 2); 3065 | var marginTop = -1 * (this.element_.offsetHeight / 2); 3066 | if (this.element_.classList.contains(this.CssClasses_.LEFT) || this.element_.classList.contains(this.CssClasses_.RIGHT)) { 3067 | left = props.width / 2; 3068 | if (top + marginTop < 0) { 3069 | this.element_.style.top = '0'; 3070 | this.element_.style.marginTop = '0'; 3071 | } else { 3072 | this.element_.style.top = top + 'px'; 3073 | this.element_.style.marginTop = marginTop + 'px'; 3074 | } 3075 | } else { 3076 | if (left + marginLeft < 0) { 3077 | this.element_.style.left = '0'; 3078 | this.element_.style.marginLeft = '0'; 3079 | } else { 3080 | this.element_.style.left = left + 'px'; 3081 | this.element_.style.marginLeft = marginLeft + 'px'; 3082 | } 3083 | } 3084 | if (this.element_.classList.contains(this.CssClasses_.TOP)) { 3085 | this.element_.style.top = props.top - this.element_.offsetHeight - 10 + 'px'; 3086 | } else if (this.element_.classList.contains(this.CssClasses_.RIGHT)) { 3087 | this.element_.style.left = props.left + props.width + 10 + 'px'; 3088 | } else if (this.element_.classList.contains(this.CssClasses_.LEFT)) { 3089 | this.element_.style.left = props.left - this.element_.offsetWidth - 10 + 'px'; 3090 | } else { 3091 | this.element_.style.top = props.top + props.height + 10 + 'px'; 3092 | } 3093 | this.element_.classList.add(this.CssClasses_.IS_ACTIVE); 3094 | }; 3095 | /** 3096 | * Hide tooltip on mouseleave or scroll 3097 | * 3098 | * @private 3099 | */ 3100 | MaterialTooltip.prototype.hideTooltip_ = function () { 3101 | this.element_.classList.remove(this.CssClasses_.IS_ACTIVE); 3102 | }; 3103 | /** 3104 | * Initialize element. 3105 | */ 3106 | MaterialTooltip.prototype.init = function () { 3107 | if (this.element_) { 3108 | var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for'); 3109 | if (forElId) { 3110 | this.forElement_ = document.getElementById(forElId); 3111 | } 3112 | if (this.forElement_) { 3113 | // It's left here because it prevents accidental text selection on Android 3114 | if (!this.forElement_.hasAttribute('tabindex')) { 3115 | this.forElement_.setAttribute('tabindex', '0'); 3116 | } 3117 | this.boundMouseEnterHandler = this.handleMouseEnter_.bind(this); 3118 | this.boundMouseLeaveAndScrollHandler = this.hideTooltip_.bind(this); 3119 | this.forElement_.addEventListener('mouseenter', this.boundMouseEnterHandler, false); 3120 | this.forElement_.addEventListener('touchend', this.boundMouseEnterHandler, false); 3121 | this.forElement_.addEventListener('mouseleave', this.boundMouseLeaveAndScrollHandler, false); 3122 | window.addEventListener('scroll', this.boundMouseLeaveAndScrollHandler, true); 3123 | window.addEventListener('touchstart', this.boundMouseLeaveAndScrollHandler); 3124 | } 3125 | } 3126 | }; 3127 | // The component registers itself. It can assume componentHandler is available 3128 | // in the global scope. 3129 | componentHandler.register({ 3130 | constructor: MaterialTooltip, 3131 | classAsString: 'MaterialTooltip', 3132 | cssClass: 'mdl-tooltip' 3133 | }); 3134 | /** 3135 | * @license 3136 | * Copyright 2015 Google Inc. All Rights Reserved. 3137 | * 3138 | * Licensed under the Apache License, Version 2.0 (the "License"); 3139 | * you may not use this file except in compliance with the License. 3140 | * You may obtain a copy of the License at 3141 | * 3142 | * http://www.apache.org/licenses/LICENSE-2.0 3143 | * 3144 | * Unless required by applicable law or agreed to in writing, software 3145 | * distributed under the License is distributed on an "AS IS" BASIS, 3146 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3147 | * See the License for the specific language governing permissions and 3148 | * limitations under the License. 3149 | */ 3150 | /** 3151 | * Class constructor for Layout MDL component. 3152 | * Implements MDL component design pattern defined at: 3153 | * https://github.com/jasonmayes/mdl-component-design-pattern 3154 | * 3155 | * @constructor 3156 | * @param {HTMLElement} element The element that will be upgraded. 3157 | */ 3158 | var MaterialLayout = function MaterialLayout(element) { 3159 | this.element_ = element; 3160 | // Initialize instance. 3161 | this.init(); 3162 | }; 3163 | window['MaterialLayout'] = MaterialLayout; 3164 | /** 3165 | * Store constants in one place so they can be updated easily. 3166 | * 3167 | * @enum {string | number} 3168 | * @private 3169 | */ 3170 | MaterialLayout.prototype.Constant_ = { 3171 | MAX_WIDTH: '(max-width: 1024px)', 3172 | TAB_SCROLL_PIXELS: 100, 3173 | RESIZE_TIMEOUT: 100, 3174 | MENU_ICON: '', 3175 | CHEVRON_LEFT: 'chevron_left', 3176 | CHEVRON_RIGHT: 'chevron_right' 3177 | }; 3178 | /** 3179 | * Keycodes, for code readability. 3180 | * 3181 | * @enum {number} 3182 | * @private 3183 | */ 3184 | MaterialLayout.prototype.Keycodes_ = { 3185 | ENTER: 13, 3186 | ESCAPE: 27, 3187 | SPACE: 32 3188 | }; 3189 | /** 3190 | * Modes. 3191 | * 3192 | * @enum {number} 3193 | * @private 3194 | */ 3195 | MaterialLayout.prototype.Mode_ = { 3196 | STANDARD: 0, 3197 | SEAMED: 1, 3198 | WATERFALL: 2, 3199 | SCROLL: 3 3200 | }; 3201 | /** 3202 | * Store strings for class names defined by this component that are used in 3203 | * JavaScript. This allows us to simply change it in one place should we 3204 | * decide to modify at a later date. 3205 | * 3206 | * @enum {string} 3207 | * @private 3208 | */ 3209 | MaterialLayout.prototype.CssClasses_ = { 3210 | CONTAINER: 'mdl-layout__container', 3211 | HEADER: 'mdl-layout__header', 3212 | DRAWER: 'mdl-layout__drawer', 3213 | CONTENT: 'mdl-layout__content', 3214 | DRAWER_BTN: 'mdl-layout__drawer-button', 3215 | ICON: 'material-icons', 3216 | JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect', 3217 | RIPPLE_CONTAINER: 'mdl-layout__tab-ripple-container', 3218 | RIPPLE: 'mdl-ripple', 3219 | RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 3220 | HEADER_SEAMED: 'mdl-layout__header--seamed', 3221 | HEADER_WATERFALL: 'mdl-layout__header--waterfall', 3222 | HEADER_SCROLL: 'mdl-layout__header--scroll', 3223 | FIXED_HEADER: 'mdl-layout--fixed-header', 3224 | OBFUSCATOR: 'mdl-layout__obfuscator', 3225 | TAB_BAR: 'mdl-layout__tab-bar', 3226 | TAB_CONTAINER: 'mdl-layout__tab-bar-container', 3227 | TAB: 'mdl-layout__tab', 3228 | TAB_BAR_BUTTON: 'mdl-layout__tab-bar-button', 3229 | TAB_BAR_LEFT_BUTTON: 'mdl-layout__tab-bar-left-button', 3230 | TAB_BAR_RIGHT_BUTTON: 'mdl-layout__tab-bar-right-button', 3231 | TAB_MANUAL_SWITCH: 'mdl-layout__tab-manual-switch', 3232 | PANEL: 'mdl-layout__tab-panel', 3233 | HAS_DRAWER: 'has-drawer', 3234 | HAS_TABS: 'has-tabs', 3235 | HAS_SCROLLING_HEADER: 'has-scrolling-header', 3236 | CASTING_SHADOW: 'is-casting-shadow', 3237 | IS_COMPACT: 'is-compact', 3238 | IS_SMALL_SCREEN: 'is-small-screen', 3239 | IS_DRAWER_OPEN: 'is-visible', 3240 | IS_ACTIVE: 'is-active', 3241 | IS_UPGRADED: 'is-upgraded', 3242 | IS_ANIMATING: 'is-animating', 3243 | ON_LARGE_SCREEN: 'mdl-layout--large-screen-only', 3244 | ON_SMALL_SCREEN: 'mdl-layout--small-screen-only' 3245 | }; 3246 | /** 3247 | * Handles scrolling on the content. 3248 | * 3249 | * @private 3250 | */ 3251 | MaterialLayout.prototype.contentScrollHandler_ = function () { 3252 | if (this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)) { 3253 | return; 3254 | } 3255 | var headerVisible = !this.element_.classList.contains(this.CssClasses_.IS_SMALL_SCREEN) || this.element_.classList.contains(this.CssClasses_.FIXED_HEADER); 3256 | if (this.content_.scrollTop > 0 && !this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) { 3257 | this.header_.classList.add(this.CssClasses_.CASTING_SHADOW); 3258 | this.header_.classList.add(this.CssClasses_.IS_COMPACT); 3259 | if (headerVisible) { 3260 | this.header_.classList.add(this.CssClasses_.IS_ANIMATING); 3261 | } 3262 | } else if (this.content_.scrollTop <= 0 && this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) { 3263 | this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW); 3264 | this.header_.classList.remove(this.CssClasses_.IS_COMPACT); 3265 | if (headerVisible) { 3266 | this.header_.classList.add(this.CssClasses_.IS_ANIMATING); 3267 | } 3268 | } 3269 | }; 3270 | /** 3271 | * Handles a keyboard event on the drawer. 3272 | * 3273 | * @param {Event} evt The event that fired. 3274 | * @private 3275 | */ 3276 | MaterialLayout.prototype.keyboardEventHandler_ = function (evt) { 3277 | // Only react when the drawer is open. 3278 | if (evt.keyCode === this.Keycodes_.ESCAPE && this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) { 3279 | this.toggleDrawer(); 3280 | } 3281 | }; 3282 | /** 3283 | * Handles changes in screen size. 3284 | * 3285 | * @private 3286 | */ 3287 | MaterialLayout.prototype.screenSizeHandler_ = function () { 3288 | if (this.screenSizeMediaQuery_.matches) { 3289 | this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN); 3290 | } else { 3291 | this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN); 3292 | // Collapse drawer (if any) when moving to a large screen size. 3293 | if (this.drawer_) { 3294 | this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN); 3295 | this.obfuscator_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN); 3296 | } 3297 | } 3298 | }; 3299 | /** 3300 | * Handles events of drawer button. 3301 | * 3302 | * @param {Event} evt The event that fired. 3303 | * @private 3304 | */ 3305 | MaterialLayout.prototype.drawerToggleHandler_ = function (evt) { 3306 | if (evt && evt.type === 'keydown') { 3307 | if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) { 3308 | // prevent scrolling in drawer nav 3309 | evt.preventDefault(); 3310 | } else { 3311 | // prevent other keys 3312 | return; 3313 | } 3314 | } 3315 | this.toggleDrawer(); 3316 | }; 3317 | /** 3318 | * Handles (un)setting the `is-animating` class 3319 | * 3320 | * @private 3321 | */ 3322 | MaterialLayout.prototype.headerTransitionEndHandler_ = function () { 3323 | this.header_.classList.remove(this.CssClasses_.IS_ANIMATING); 3324 | }; 3325 | /** 3326 | * Handles expanding the header on click 3327 | * 3328 | * @private 3329 | */ 3330 | MaterialLayout.prototype.headerClickHandler_ = function () { 3331 | if (this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) { 3332 | this.header_.classList.remove(this.CssClasses_.IS_COMPACT); 3333 | this.header_.classList.add(this.CssClasses_.IS_ANIMATING); 3334 | } 3335 | }; 3336 | /** 3337 | * Reset tab state, dropping active classes 3338 | * 3339 | * @private 3340 | */ 3341 | MaterialLayout.prototype.resetTabState_ = function (tabBar) { 3342 | for (var k = 0; k < tabBar.length; k++) { 3343 | tabBar[k].classList.remove(this.CssClasses_.IS_ACTIVE); 3344 | } 3345 | }; 3346 | /** 3347 | * Reset panel state, droping active classes 3348 | * 3349 | * @private 3350 | */ 3351 | MaterialLayout.prototype.resetPanelState_ = function (panels) { 3352 | for (var j = 0; j < panels.length; j++) { 3353 | panels[j].classList.remove(this.CssClasses_.IS_ACTIVE); 3354 | } 3355 | }; 3356 | /** 3357 | * Toggle drawer state 3358 | * 3359 | * @public 3360 | */ 3361 | MaterialLayout.prototype.toggleDrawer = function () { 3362 | var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN); 3363 | this.drawer_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN); 3364 | this.obfuscator_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN); 3365 | // Set accessibility properties. 3366 | if (this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) { 3367 | this.drawer_.setAttribute('aria-hidden', 'false'); 3368 | drawerButton.setAttribute('aria-expanded', 'true'); 3369 | } else { 3370 | this.drawer_.setAttribute('aria-hidden', 'true'); 3371 | drawerButton.setAttribute('aria-expanded', 'false'); 3372 | } 3373 | }; 3374 | MaterialLayout.prototype['toggleDrawer'] = MaterialLayout.prototype.toggleDrawer; 3375 | /** 3376 | * Initialize element. 3377 | */ 3378 | MaterialLayout.prototype.init = function () { 3379 | if (this.element_) { 3380 | var container = document.createElement('div'); 3381 | container.classList.add(this.CssClasses_.CONTAINER); 3382 | var focusedElement = this.element_.querySelector(':focus'); 3383 | this.element_.parentElement.insertBefore(container, this.element_); 3384 | this.element_.parentElement.removeChild(this.element_); 3385 | container.appendChild(this.element_); 3386 | if (focusedElement) { 3387 | focusedElement.focus(); 3388 | } 3389 | var directChildren = this.element_.childNodes; 3390 | var numChildren = directChildren.length; 3391 | for (var c = 0; c < numChildren; c++) { 3392 | var child = directChildren[c]; 3393 | if (child.classList && child.classList.contains(this.CssClasses_.HEADER)) { 3394 | this.header_ = child; 3395 | } 3396 | if (child.classList && child.classList.contains(this.CssClasses_.DRAWER)) { 3397 | this.drawer_ = child; 3398 | } 3399 | if (child.classList && child.classList.contains(this.CssClasses_.CONTENT)) { 3400 | this.content_ = child; 3401 | } 3402 | } 3403 | window.addEventListener('pageshow', function (e) { 3404 | if (e.persisted) { 3405 | // when page is loaded from back/forward cache 3406 | // trigger repaint to let layout scroll in safari 3407 | this.element_.style.overflowY = 'hidden'; 3408 | requestAnimationFrame(function () { 3409 | this.element_.style.overflowY = ''; 3410 | }.bind(this)); 3411 | } 3412 | }.bind(this), false); 3413 | if (this.header_) { 3414 | this.tabBar_ = this.header_.querySelector('.' + this.CssClasses_.TAB_BAR); 3415 | } 3416 | var mode = this.Mode_.STANDARD; 3417 | if (this.header_) { 3418 | if (this.header_.classList.contains(this.CssClasses_.HEADER_SEAMED)) { 3419 | mode = this.Mode_.SEAMED; 3420 | } else if (this.header_.classList.contains(this.CssClasses_.HEADER_WATERFALL)) { 3421 | mode = this.Mode_.WATERFALL; 3422 | this.header_.addEventListener('transitionend', this.headerTransitionEndHandler_.bind(this)); 3423 | this.header_.addEventListener('click', this.headerClickHandler_.bind(this)); 3424 | } else if (this.header_.classList.contains(this.CssClasses_.HEADER_SCROLL)) { 3425 | mode = this.Mode_.SCROLL; 3426 | container.classList.add(this.CssClasses_.HAS_SCROLLING_HEADER); 3427 | } 3428 | if (mode === this.Mode_.STANDARD) { 3429 | this.header_.classList.add(this.CssClasses_.CASTING_SHADOW); 3430 | if (this.tabBar_) { 3431 | this.tabBar_.classList.add(this.CssClasses_.CASTING_SHADOW); 3432 | } 3433 | } else if (mode === this.Mode_.SEAMED || mode === this.Mode_.SCROLL) { 3434 | this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW); 3435 | if (this.tabBar_) { 3436 | this.tabBar_.classList.remove(this.CssClasses_.CASTING_SHADOW); 3437 | } 3438 | } else if (mode === this.Mode_.WATERFALL) { 3439 | // Add and remove shadows depending on scroll position. 3440 | // Also add/remove auxiliary class for styling of the compact version of 3441 | // the header. 3442 | this.content_.addEventListener('scroll', this.contentScrollHandler_.bind(this)); 3443 | this.contentScrollHandler_(); 3444 | } 3445 | } 3446 | // Add drawer toggling button to our layout, if we have an openable drawer. 3447 | if (this.drawer_) { 3448 | var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN); 3449 | if (!drawerButton) { 3450 | drawerButton = document.createElement('div'); 3451 | drawerButton.setAttribute('aria-expanded', 'false'); 3452 | drawerButton.setAttribute('role', 'button'); 3453 | drawerButton.setAttribute('tabindex', '0'); 3454 | drawerButton.classList.add(this.CssClasses_.DRAWER_BTN); 3455 | var drawerButtonIcon = document.createElement('i'); 3456 | drawerButtonIcon.classList.add(this.CssClasses_.ICON); 3457 | drawerButtonIcon.innerHTML = this.Constant_.MENU_ICON; 3458 | drawerButton.appendChild(drawerButtonIcon); 3459 | } 3460 | if (this.drawer_.classList.contains(this.CssClasses_.ON_LARGE_SCREEN)) { 3461 | //If drawer has ON_LARGE_SCREEN class then add it to the drawer toggle button as well. 3462 | drawerButton.classList.add(this.CssClasses_.ON_LARGE_SCREEN); 3463 | } else if (this.drawer_.classList.contains(this.CssClasses_.ON_SMALL_SCREEN)) { 3464 | //If drawer has ON_SMALL_SCREEN class then add it to the drawer toggle button as well. 3465 | drawerButton.classList.add(this.CssClasses_.ON_SMALL_SCREEN); 3466 | } 3467 | drawerButton.addEventListener('click', this.drawerToggleHandler_.bind(this)); 3468 | drawerButton.addEventListener('keydown', this.drawerToggleHandler_.bind(this)); 3469 | // Add a class if the layout has a drawer, for altering the left padding. 3470 | // Adds the HAS_DRAWER to the elements since this.header_ may or may 3471 | // not be present. 3472 | this.element_.classList.add(this.CssClasses_.HAS_DRAWER); 3473 | // If we have a fixed header, add the button to the header rather than 3474 | // the layout. 3475 | if (this.element_.classList.contains(this.CssClasses_.FIXED_HEADER)) { 3476 | this.header_.insertBefore(drawerButton, this.header_.firstChild); 3477 | } else { 3478 | this.element_.insertBefore(drawerButton, this.content_); 3479 | } 3480 | var obfuscator = document.createElement('div'); 3481 | obfuscator.classList.add(this.CssClasses_.OBFUSCATOR); 3482 | this.element_.appendChild(obfuscator); 3483 | obfuscator.addEventListener('click', this.drawerToggleHandler_.bind(this)); 3484 | this.obfuscator_ = obfuscator; 3485 | this.drawer_.addEventListener('keydown', this.keyboardEventHandler_.bind(this)); 3486 | this.drawer_.setAttribute('aria-hidden', 'true'); 3487 | } 3488 | // Keep an eye on screen size, and add/remove auxiliary class for styling 3489 | // of small screens. 3490 | this.screenSizeMediaQuery_ = window.matchMedia(this.Constant_.MAX_WIDTH); 3491 | this.screenSizeMediaQuery_.addListener(this.screenSizeHandler_.bind(this)); 3492 | this.screenSizeHandler_(); 3493 | // Initialize tabs, if any. 3494 | if (this.header_ && this.tabBar_) { 3495 | this.element_.classList.add(this.CssClasses_.HAS_TABS); 3496 | var tabContainer = document.createElement('div'); 3497 | tabContainer.classList.add(this.CssClasses_.TAB_CONTAINER); 3498 | this.header_.insertBefore(tabContainer, this.tabBar_); 3499 | this.header_.removeChild(this.tabBar_); 3500 | var leftButton = document.createElement('div'); 3501 | leftButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON); 3502 | leftButton.classList.add(this.CssClasses_.TAB_BAR_LEFT_BUTTON); 3503 | var leftButtonIcon = document.createElement('i'); 3504 | leftButtonIcon.classList.add(this.CssClasses_.ICON); 3505 | leftButtonIcon.textContent = this.Constant_.CHEVRON_LEFT; 3506 | leftButton.appendChild(leftButtonIcon); 3507 | leftButton.addEventListener('click', function () { 3508 | this.tabBar_.scrollLeft -= this.Constant_.TAB_SCROLL_PIXELS; 3509 | }.bind(this)); 3510 | var rightButton = document.createElement('div'); 3511 | rightButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON); 3512 | rightButton.classList.add(this.CssClasses_.TAB_BAR_RIGHT_BUTTON); 3513 | var rightButtonIcon = document.createElement('i'); 3514 | rightButtonIcon.classList.add(this.CssClasses_.ICON); 3515 | rightButtonIcon.textContent = this.Constant_.CHEVRON_RIGHT; 3516 | rightButton.appendChild(rightButtonIcon); 3517 | rightButton.addEventListener('click', function () { 3518 | this.tabBar_.scrollLeft += this.Constant_.TAB_SCROLL_PIXELS; 3519 | }.bind(this)); 3520 | tabContainer.appendChild(leftButton); 3521 | tabContainer.appendChild(this.tabBar_); 3522 | tabContainer.appendChild(rightButton); 3523 | // Add and remove tab buttons depending on scroll position and total 3524 | // window size. 3525 | var tabUpdateHandler = function () { 3526 | if (this.tabBar_.scrollLeft > 0) { 3527 | leftButton.classList.add(this.CssClasses_.IS_ACTIVE); 3528 | } else { 3529 | leftButton.classList.remove(this.CssClasses_.IS_ACTIVE); 3530 | } 3531 | if (this.tabBar_.scrollLeft < this.tabBar_.scrollWidth - this.tabBar_.offsetWidth) { 3532 | rightButton.classList.add(this.CssClasses_.IS_ACTIVE); 3533 | } else { 3534 | rightButton.classList.remove(this.CssClasses_.IS_ACTIVE); 3535 | } 3536 | }.bind(this); 3537 | this.tabBar_.addEventListener('scroll', tabUpdateHandler); 3538 | tabUpdateHandler(); 3539 | // Update tabs when the window resizes. 3540 | var windowResizeHandler = function () { 3541 | // Use timeouts to make sure it doesn't happen too often. 3542 | if (this.resizeTimeoutId_) { 3543 | clearTimeout(this.resizeTimeoutId_); 3544 | } 3545 | this.resizeTimeoutId_ = setTimeout(function () { 3546 | tabUpdateHandler(); 3547 | this.resizeTimeoutId_ = null; 3548 | }.bind(this), this.Constant_.RESIZE_TIMEOUT); 3549 | }.bind(this); 3550 | window.addEventListener('resize', windowResizeHandler); 3551 | if (this.tabBar_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) { 3552 | this.tabBar_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); 3553 | } 3554 | // Select element tabs, document panels 3555 | var tabs = this.tabBar_.querySelectorAll('.' + this.CssClasses_.TAB); 3556 | var panels = this.content_.querySelectorAll('.' + this.CssClasses_.PANEL); 3557 | // Create new tabs for each tab element 3558 | for (var i = 0; i < tabs.length; i++) { 3559 | new MaterialLayoutTab(tabs[i], tabs, panels, this); 3560 | } 3561 | } 3562 | this.element_.classList.add(this.CssClasses_.IS_UPGRADED); 3563 | } 3564 | }; 3565 | /** 3566 | * Constructor for an individual tab. 3567 | * 3568 | * @constructor 3569 | * @param {HTMLElement} tab The HTML element for the tab. 3570 | * @param {!Array} tabs Array with HTML elements for all tabs. 3571 | * @param {!Array} panels Array with HTML elements for all panels. 3572 | * @param {MaterialLayout} layout The MaterialLayout object that owns the tab. 3573 | */ 3574 | function MaterialLayoutTab(tab, tabs, panels, layout) { 3575 | /** 3576 | * Auxiliary method to programmatically select a tab in the UI. 3577 | */ 3578 | function selectTab() { 3579 | var href = tab.href.split('#')[1]; 3580 | var panel = layout.content_.querySelector('#' + href); 3581 | layout.resetTabState_(tabs); 3582 | layout.resetPanelState_(panels); 3583 | tab.classList.add(layout.CssClasses_.IS_ACTIVE); 3584 | panel.classList.add(layout.CssClasses_.IS_ACTIVE); 3585 | } 3586 | if (layout.tabBar_.classList.contains(layout.CssClasses_.JS_RIPPLE_EFFECT)) { 3587 | var rippleContainer = document.createElement('span'); 3588 | rippleContainer.classList.add(layout.CssClasses_.RIPPLE_CONTAINER); 3589 | rippleContainer.classList.add(layout.CssClasses_.JS_RIPPLE_EFFECT); 3590 | var ripple = document.createElement('span'); 3591 | ripple.classList.add(layout.CssClasses_.RIPPLE); 3592 | rippleContainer.appendChild(ripple); 3593 | tab.appendChild(rippleContainer); 3594 | } 3595 | if (!layout.tabBar_.classList.contains(layout.CssClasses_.TAB_MANUAL_SWITCH)) { 3596 | tab.addEventListener('click', function (e) { 3597 | if (tab.getAttribute('href').charAt(0) === '#') { 3598 | e.preventDefault(); 3599 | selectTab(); 3600 | } 3601 | }); 3602 | } 3603 | tab.show = selectTab; 3604 | } 3605 | window['MaterialLayoutTab'] = MaterialLayoutTab; 3606 | // The component registers itself. It can assume componentHandler is available 3607 | // in the global scope. 3608 | componentHandler.register({ 3609 | constructor: MaterialLayout, 3610 | classAsString: 'MaterialLayout', 3611 | cssClass: 'mdl-js-layout' 3612 | }); 3613 | /** 3614 | * @license 3615 | * Copyright 2015 Google Inc. All Rights Reserved. 3616 | * 3617 | * Licensed under the Apache License, Version 2.0 (the "License"); 3618 | * you may not use this file except in compliance with the License. 3619 | * You may obtain a copy of the License at 3620 | * 3621 | * http://www.apache.org/licenses/LICENSE-2.0 3622 | * 3623 | * Unless required by applicable law or agreed to in writing, software 3624 | * distributed under the License is distributed on an "AS IS" BASIS, 3625 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3626 | * See the License for the specific language governing permissions and 3627 | * limitations under the License. 3628 | */ 3629 | /** 3630 | * Class constructor for Data Table Card MDL component. 3631 | * Implements MDL component design pattern defined at: 3632 | * https://github.com/jasonmayes/mdl-component-design-pattern 3633 | * 3634 | * @constructor 3635 | * @param {Element} element The element that will be upgraded. 3636 | */ 3637 | var MaterialDataTable = function MaterialDataTable(element) { 3638 | this.element_ = element; 3639 | // Initialize instance. 3640 | this.init(); 3641 | }; 3642 | window['MaterialDataTable'] = MaterialDataTable; 3643 | /** 3644 | * Store constants in one place so they can be updated easily. 3645 | * 3646 | * @enum {string | number} 3647 | * @private 3648 | */ 3649 | MaterialDataTable.prototype.Constant_ = {}; 3650 | /** 3651 | * Store strings for class names defined by this component that are used in 3652 | * JavaScript. This allows us to simply change it in one place should we 3653 | * decide to modify at a later date. 3654 | * 3655 | * @enum {string} 3656 | * @private 3657 | */ 3658 | MaterialDataTable.prototype.CssClasses_ = { 3659 | DATA_TABLE: 'mdl-data-table', 3660 | SELECTABLE: 'mdl-data-table--selectable', 3661 | SELECT_ELEMENT: 'mdl-data-table__select', 3662 | IS_SELECTED: 'is-selected', 3663 | IS_UPGRADED: 'is-upgraded' 3664 | }; 3665 | /** 3666 | * Generates and returns a function that toggles the selection state of a 3667 | * single row (or multiple rows). 3668 | * 3669 | * @param {Element} checkbox Checkbox that toggles the selection state. 3670 | * @param {Element} row Row to toggle when checkbox changes. 3671 | * @param {(Array|NodeList)=} opt_rows Rows to toggle when checkbox changes. 3672 | * @private 3673 | */ 3674 | MaterialDataTable.prototype.selectRow_ = function (checkbox, row, opt_rows) { 3675 | if (row) { 3676 | return function () { 3677 | if (checkbox.checked) { 3678 | row.classList.add(this.CssClasses_.IS_SELECTED); 3679 | } else { 3680 | row.classList.remove(this.CssClasses_.IS_SELECTED); 3681 | } 3682 | }.bind(this); 3683 | } 3684 | if (opt_rows) { 3685 | return function () { 3686 | var i; 3687 | var el; 3688 | if (checkbox.checked) { 3689 | for (i = 0; i < opt_rows.length; i++) { 3690 | el = opt_rows[i].querySelector('td').querySelector('.mdl-checkbox'); 3691 | el['MaterialCheckbox'].check(); 3692 | opt_rows[i].classList.add(this.CssClasses_.IS_SELECTED); 3693 | } 3694 | } else { 3695 | for (i = 0; i < opt_rows.length; i++) { 3696 | el = opt_rows[i].querySelector('td').querySelector('.mdl-checkbox'); 3697 | el['MaterialCheckbox'].uncheck(); 3698 | opt_rows[i].classList.remove(this.CssClasses_.IS_SELECTED); 3699 | } 3700 | } 3701 | }.bind(this); 3702 | } 3703 | }; 3704 | /** 3705 | * Creates a checkbox for a single or or multiple rows and hooks up the 3706 | * event handling. 3707 | * 3708 | * @param {Element} row Row to toggle when checkbox changes. 3709 | * @param {(Array|NodeList)=} opt_rows Rows to toggle when checkbox changes. 3710 | * @private 3711 | */ 3712 | MaterialDataTable.prototype.createCheckbox_ = function (row, opt_rows) { 3713 | var label = document.createElement('label'); 3714 | var labelClasses = [ 3715 | 'mdl-checkbox', 3716 | 'mdl-js-checkbox', 3717 | 'mdl-js-ripple-effect', 3718 | this.CssClasses_.SELECT_ELEMENT 3719 | ]; 3720 | label.className = labelClasses.join(' '); 3721 | var checkbox = document.createElement('input'); 3722 | checkbox.type = 'checkbox'; 3723 | checkbox.classList.add('mdl-checkbox__input'); 3724 | if (row) { 3725 | checkbox.checked = row.classList.contains(this.CssClasses_.IS_SELECTED); 3726 | checkbox.addEventListener('change', this.selectRow_(checkbox, row)); 3727 | } else if (opt_rows) { 3728 | checkbox.addEventListener('change', this.selectRow_(checkbox, null, opt_rows)); 3729 | } 3730 | label.appendChild(checkbox); 3731 | componentHandler.upgradeElement(label, 'MaterialCheckbox'); 3732 | return label; 3733 | }; 3734 | /** 3735 | * Initialize element. 3736 | */ 3737 | MaterialDataTable.prototype.init = function () { 3738 | if (this.element_) { 3739 | var firstHeader = this.element_.querySelector('th'); 3740 | var bodyRows = Array.prototype.slice.call(this.element_.querySelectorAll('tbody tr')); 3741 | var footRows = Array.prototype.slice.call(this.element_.querySelectorAll('tfoot tr')); 3742 | var rows = bodyRows.concat(footRows); 3743 | if (this.element_.classList.contains(this.CssClasses_.SELECTABLE)) { 3744 | var th = document.createElement('th'); 3745 | var headerCheckbox = this.createCheckbox_(null, rows); 3746 | th.appendChild(headerCheckbox); 3747 | firstHeader.parentElement.insertBefore(th, firstHeader); 3748 | for (var i = 0; i < rows.length; i++) { 3749 | var firstCell = rows[i].querySelector('td'); 3750 | if (firstCell) { 3751 | var td = document.createElement('td'); 3752 | if (rows[i].parentNode.nodeName.toUpperCase() === 'TBODY') { 3753 | var rowCheckbox = this.createCheckbox_(rows[i]); 3754 | td.appendChild(rowCheckbox); 3755 | } 3756 | rows[i].insertBefore(td, firstCell); 3757 | } 3758 | } 3759 | this.element_.classList.add(this.CssClasses_.IS_UPGRADED); 3760 | } 3761 | } 3762 | }; 3763 | // The component registers itself. It can assume componentHandler is available 3764 | // in the global scope. 3765 | componentHandler.register({ 3766 | constructor: MaterialDataTable, 3767 | classAsString: 'MaterialDataTable', 3768 | cssClass: 'mdl-js-data-table' 3769 | }); 3770 | /** 3771 | * @license 3772 | * Copyright 2015 Google Inc. All Rights Reserved. 3773 | * 3774 | * Licensed under the Apache License, Version 2.0 (the "License"); 3775 | * you may not use this file except in compliance with the License. 3776 | * You may obtain a copy of the License at 3777 | * 3778 | * http://www.apache.org/licenses/LICENSE-2.0 3779 | * 3780 | * Unless required by applicable law or agreed to in writing, software 3781 | * distributed under the License is distributed on an "AS IS" BASIS, 3782 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3783 | * See the License for the specific language governing permissions and 3784 | * limitations under the License. 3785 | */ 3786 | /** 3787 | * Class constructor for Ripple MDL component. 3788 | * Implements MDL component design pattern defined at: 3789 | * https://github.com/jasonmayes/mdl-component-design-pattern 3790 | * 3791 | * @constructor 3792 | * @param {HTMLElement} element The element that will be upgraded. 3793 | */ 3794 | var MaterialRipple = function MaterialRipple(element) { 3795 | this.element_ = element; 3796 | // Initialize instance. 3797 | this.init(); 3798 | }; 3799 | window['MaterialRipple'] = MaterialRipple; 3800 | /** 3801 | * Store constants in one place so they can be updated easily. 3802 | * 3803 | * @enum {string | number} 3804 | * @private 3805 | */ 3806 | MaterialRipple.prototype.Constant_ = { 3807 | INITIAL_SCALE: 'scale(0.0001, 0.0001)', 3808 | INITIAL_SIZE: '1px', 3809 | INITIAL_OPACITY: '0.4', 3810 | FINAL_OPACITY: '0', 3811 | FINAL_SCALE: '' 3812 | }; 3813 | /** 3814 | * Store strings for class names defined by this component that are used in 3815 | * JavaScript. This allows us to simply change it in one place should we 3816 | * decide to modify at a later date. 3817 | * 3818 | * @enum {string} 3819 | * @private 3820 | */ 3821 | MaterialRipple.prototype.CssClasses_ = { 3822 | RIPPLE_CENTER: 'mdl-ripple--center', 3823 | RIPPLE_EFFECT_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 3824 | RIPPLE: 'mdl-ripple', 3825 | IS_ANIMATING: 'is-animating', 3826 | IS_VISIBLE: 'is-visible' 3827 | }; 3828 | /** 3829 | * Handle mouse / finger down on element. 3830 | * 3831 | * @param {Event} event The event that fired. 3832 | * @private 3833 | */ 3834 | MaterialRipple.prototype.downHandler_ = function (event) { 3835 | if (!this.rippleElement_.style.width && !this.rippleElement_.style.height) { 3836 | var rect = this.element_.getBoundingClientRect(); 3837 | this.boundHeight = rect.height; 3838 | this.boundWidth = rect.width; 3839 | this.rippleSize_ = Math.sqrt(rect.width * rect.width + rect.height * rect.height) * 2 + 2; 3840 | this.rippleElement_.style.width = this.rippleSize_ + 'px'; 3841 | this.rippleElement_.style.height = this.rippleSize_ + 'px'; 3842 | } 3843 | this.rippleElement_.classList.add(this.CssClasses_.IS_VISIBLE); 3844 | if (event.type === 'mousedown' && this.ignoringMouseDown_) { 3845 | this.ignoringMouseDown_ = false; 3846 | } else { 3847 | if (event.type === 'touchstart') { 3848 | this.ignoringMouseDown_ = true; 3849 | } 3850 | var frameCount = this.getFrameCount(); 3851 | if (frameCount > 0) { 3852 | return; 3853 | } 3854 | this.setFrameCount(1); 3855 | var bound = event.currentTarget.getBoundingClientRect(); 3856 | var x; 3857 | var y; 3858 | // Check if we are handling a keyboard click. 3859 | if (event.clientX === 0 && event.clientY === 0) { 3860 | x = Math.round(bound.width / 2); 3861 | y = Math.round(bound.height / 2); 3862 | } else { 3863 | var clientX = event.clientX !== undefined ? event.clientX : event.touches[0].clientX; 3864 | var clientY = event.clientY !== undefined ? event.clientY : event.touches[0].clientY; 3865 | x = Math.round(clientX - bound.left); 3866 | y = Math.round(clientY - bound.top); 3867 | } 3868 | this.setRippleXY(x, y); 3869 | this.setRippleStyles(true); 3870 | window.requestAnimationFrame(this.animFrameHandler.bind(this)); 3871 | } 3872 | }; 3873 | /** 3874 | * Handle mouse / finger up on element. 3875 | * 3876 | * @param {Event} event The event that fired. 3877 | * @private 3878 | */ 3879 | MaterialRipple.prototype.upHandler_ = function (event) { 3880 | // Don't fire for the artificial "mouseup" generated by a double-click. 3881 | if (event && event.detail !== 2) { 3882 | // Allow a repaint to occur before removing this class, so the animation 3883 | // shows for tap events, which seem to trigger a mouseup too soon after 3884 | // mousedown. 3885 | window.setTimeout(function () { 3886 | this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE); 3887 | }.bind(this), 0); 3888 | } 3889 | }; 3890 | /** 3891 | * Initialize element. 3892 | */ 3893 | MaterialRipple.prototype.init = function () { 3894 | if (this.element_) { 3895 | var recentering = this.element_.classList.contains(this.CssClasses_.RIPPLE_CENTER); 3896 | if (!this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT_IGNORE_EVENTS)) { 3897 | this.rippleElement_ = this.element_.querySelector('.' + this.CssClasses_.RIPPLE); 3898 | this.frameCount_ = 0; 3899 | this.rippleSize_ = 0; 3900 | this.x_ = 0; 3901 | this.y_ = 0; 3902 | // Touch start produces a compat mouse down event, which would cause a 3903 | // second ripples. To avoid that, we use this property to ignore the first 3904 | // mouse down after a touch start. 3905 | this.ignoringMouseDown_ = false; 3906 | this.boundDownHandler = this.downHandler_.bind(this); 3907 | this.element_.addEventListener('mousedown', this.boundDownHandler); 3908 | this.element_.addEventListener('touchstart', this.boundDownHandler); 3909 | this.boundUpHandler = this.upHandler_.bind(this); 3910 | this.element_.addEventListener('mouseup', this.boundUpHandler); 3911 | this.element_.addEventListener('mouseleave', this.boundUpHandler); 3912 | this.element_.addEventListener('touchend', this.boundUpHandler); 3913 | this.element_.addEventListener('blur', this.boundUpHandler); 3914 | /** 3915 | * Getter for frameCount_. 3916 | * @return {number} the frame count. 3917 | */ 3918 | this.getFrameCount = function () { 3919 | return this.frameCount_; 3920 | }; 3921 | /** 3922 | * Setter for frameCount_. 3923 | * @param {number} fC the frame count. 3924 | */ 3925 | this.setFrameCount = function (fC) { 3926 | this.frameCount_ = fC; 3927 | }; 3928 | /** 3929 | * Getter for rippleElement_. 3930 | * @return {Element} the ripple element. 3931 | */ 3932 | this.getRippleElement = function () { 3933 | return this.rippleElement_; 3934 | }; 3935 | /** 3936 | * Sets the ripple X and Y coordinates. 3937 | * @param {number} newX the new X coordinate 3938 | * @param {number} newY the new Y coordinate 3939 | */ 3940 | this.setRippleXY = function (newX, newY) { 3941 | this.x_ = newX; 3942 | this.y_ = newY; 3943 | }; 3944 | /** 3945 | * Sets the ripple styles. 3946 | * @param {boolean} start whether or not this is the start frame. 3947 | */ 3948 | this.setRippleStyles = function (start) { 3949 | if (this.rippleElement_ !== null) { 3950 | var transformString; 3951 | var scale; 3952 | var size; 3953 | var offset = 'translate(' + this.x_ + 'px, ' + this.y_ + 'px)'; 3954 | if (start) { 3955 | scale = this.Constant_.INITIAL_SCALE; 3956 | size = this.Constant_.INITIAL_SIZE; 3957 | } else { 3958 | scale = this.Constant_.FINAL_SCALE; 3959 | size = this.rippleSize_ + 'px'; 3960 | if (recentering) { 3961 | offset = 'translate(' + this.boundWidth / 2 + 'px, ' + this.boundHeight / 2 + 'px)'; 3962 | } 3963 | } 3964 | transformString = 'translate(-50%, -50%) ' + offset + scale; 3965 | this.rippleElement_.style.webkitTransform = transformString; 3966 | this.rippleElement_.style.msTransform = transformString; 3967 | this.rippleElement_.style.transform = transformString; 3968 | if (start) { 3969 | this.rippleElement_.classList.remove(this.CssClasses_.IS_ANIMATING); 3970 | } else { 3971 | this.rippleElement_.classList.add(this.CssClasses_.IS_ANIMATING); 3972 | } 3973 | } 3974 | }; 3975 | /** 3976 | * Handles an animation frame. 3977 | */ 3978 | this.animFrameHandler = function () { 3979 | if (this.frameCount_-- > 0) { 3980 | window.requestAnimationFrame(this.animFrameHandler.bind(this)); 3981 | } else { 3982 | this.setRippleStyles(false); 3983 | } 3984 | }; 3985 | } 3986 | } 3987 | }; 3988 | // The component registers itself. It can assume componentHandler is available 3989 | // in the global scope. 3990 | componentHandler.register({ 3991 | constructor: MaterialRipple, 3992 | classAsString: 'MaterialRipple', 3993 | cssClass: 'mdl-js-ripple-effect', 3994 | widget: false 3995 | }); 3996 | }()); 3997 | --------------------------------------------------------------------------------