├── .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