| OLD | NEW |
| 1 <!DOCTYPE html><html><head><!-- | 1 <!DOCTYPE html><html><head><!-- |
| 2 @license | 2 @license |
| 3 Copyright (c) 2016 The Polymer Project Authors. All rights reserved. | 3 Copyright (c) 2016 The Polymer Project Authors. All rights reserved. |
| 4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt | 4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt |
| 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt | 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt | 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt |
| 7 Code distributed by Google as part of the polymer project is also | 7 Code distributed by Google as part of the polymer project is also |
| 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt | 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt |
| 9 --><!-- | 9 --><!-- |
| 10 @license | 10 @license |
| (...skipping 15620 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 15631 // added before "os", for example. Additionally, this makes sure that | 15631 // added before "os", for example. Additionally, this makes sure that |
| 15632 // only one sort-toggle is active at a given time. | 15632 // only one sort-toggle is active at a given time. |
| 15633 if (this.current && this.current.name === this.name) { | 15633 if (this.current && this.current.name === this.name) { |
| 15634 this.set("direction", this.current.direction); | 15634 this.set("direction", this.current.direction); |
| 15635 } else { | 15635 } else { |
| 15636 this.set("direction", ""); | 15636 this.set("direction", ""); |
| 15637 } | 15637 } |
| 15638 }, | 15638 }, |
| 15639 }); | 15639 }); |
| 15640 </script> | 15640 </script> |
| 15641 </dom-module><script> | 15641 </dom-module> |
| 15642 |
| 15643 <dom-module id="iron-a11y-announcer" assetpath="/res/imp/bower_components/iron-a
11y-announcer/"> |
| 15644 <template> |
| 15645 <style> |
| 15646 :host { |
| 15647 display: inline-block; |
| 15648 position: fixed; |
| 15649 clip: rect(0px,0px,0px,0px); |
| 15650 } |
| 15651 </style> |
| 15652 <div aria-live$="[[mode]]">[[_text]]</div> |
| 15653 </template> |
| 15654 |
| 15655 <script> |
| 15656 |
| 15657 (function() { |
| 15658 'use strict'; |
| 15659 |
| 15660 Polymer.IronA11yAnnouncer = Polymer({ |
| 15661 is: 'iron-a11y-announcer', |
| 15662 |
| 15663 properties: { |
| 15664 |
| 15665 /** |
| 15666 * The value of mode is used to set the `aria-live` attribute |
| 15667 * for the element that will be announced. Valid values are: `off`, |
| 15668 * `polite` and `assertive`. |
| 15669 */ |
| 15670 mode: { |
| 15671 type: String, |
| 15672 value: 'polite' |
| 15673 }, |
| 15674 |
| 15675 _text: { |
| 15676 type: String, |
| 15677 value: '' |
| 15678 } |
| 15679 }, |
| 15680 |
| 15681 created: function() { |
| 15682 if (!Polymer.IronA11yAnnouncer.instance) { |
| 15683 Polymer.IronA11yAnnouncer.instance = this; |
| 15684 } |
| 15685 |
| 15686 document.body.addEventListener('iron-announce', this._onIronAnnounce.b
ind(this)); |
| 15687 }, |
| 15688 |
| 15689 /** |
| 15690 * Cause a text string to be announced by screen readers. |
| 15691 * |
| 15692 * @param {string} text The text that should be announced. |
| 15693 */ |
| 15694 announce: function(text) { |
| 15695 this._text = ''; |
| 15696 this.async(function() { |
| 15697 this._text = text; |
| 15698 }, 100); |
| 15699 }, |
| 15700 |
| 15701 _onIronAnnounce: function(event) { |
| 15702 if (event.detail && event.detail.text) { |
| 15703 this.announce(event.detail.text); |
| 15704 } |
| 15705 } |
| 15706 }); |
| 15707 |
| 15708 Polymer.IronA11yAnnouncer.instance = null; |
| 15709 |
| 15710 Polymer.IronA11yAnnouncer.requestAvailability = function() { |
| 15711 if (!Polymer.IronA11yAnnouncer.instance) { |
| 15712 Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y
-announcer'); |
| 15713 } |
| 15714 |
| 15715 document.body.appendChild(Polymer.IronA11yAnnouncer.instance); |
| 15716 }; |
| 15717 })(); |
| 15718 |
| 15719 </script> |
| 15720 </dom-module> |
| 15721 <script> |
| 15722 /** |
| 15723 `Polymer.IronFitBehavior` fits an element in another element using `max-height`
and `max-width`, and |
| 15724 optionally centers it in the window or another element. |
| 15725 |
| 15726 The element will only be sized and/or positioned if it has not already been size
d and/or positioned |
| 15727 by CSS. |
| 15728 |
| 15729 CSS properties | Action |
| 15730 -----------------------------|------------------------------------------- |
| 15731 `position` set | Element is not centered horizontally or verticall
y |
| 15732 `top` or `bottom` set | Element is not vertically centered |
| 15733 `left` or `right` set | Element is not horizontally centered |
| 15734 `max-height` set | Element respects `max-height` |
| 15735 `max-width` set | Element respects `max-width` |
| 15736 |
| 15737 `Polymer.IronFitBehavior` can position an element into another element using |
| 15738 `verticalAlign` and `horizontalAlign`. This will override the element's css posi
tion. |
| 15739 |
| 15740 <div class="container"> |
| 15741 <iron-fit-impl vertical-align="top" horizontal-align="auto"> |
| 15742 Positioned into the container |
| 15743 </iron-fit-impl> |
| 15744 </div> |
| 15745 |
| 15746 Use `noOverlap` to position the element around another element without overlappi
ng it. |
| 15747 |
| 15748 <div class="container"> |
| 15749 <iron-fit-impl no-overlap vertical-align="auto" horizontal-align="auto"> |
| 15750 Positioned around the container |
| 15751 </iron-fit-impl> |
| 15752 </div> |
| 15753 |
| 15754 @demo demo/index.html |
| 15755 @polymerBehavior |
| 15756 */ |
| 15757 |
| 15758 Polymer.IronFitBehavior = { |
| 15759 |
| 15760 properties: { |
| 15761 |
| 15762 /** |
| 15763 * The element that will receive a `max-height`/`width`. By default it is
the same as `this`, |
| 15764 * but it can be set to a child element. This is useful, for example, for
implementing a |
| 15765 * scrolling region inside the element. |
| 15766 * @type {!Element} |
| 15767 */ |
| 15768 sizingTarget: { |
| 15769 type: Object, |
| 15770 value: function() { |
| 15771 return this; |
| 15772 } |
| 15773 }, |
| 15774 |
| 15775 /** |
| 15776 * The element to fit `this` into. |
| 15777 */ |
| 15778 fitInto: { |
| 15779 type: Object, |
| 15780 value: window |
| 15781 }, |
| 15782 |
| 15783 /** |
| 15784 * Will position the element around the positionTarget without overlapping
it. |
| 15785 */ |
| 15786 noOverlap: { |
| 15787 type: Boolean |
| 15788 }, |
| 15789 |
| 15790 /** |
| 15791 * The element that should be used to position the element. If not set, it
will |
| 15792 * default to the parent node. |
| 15793 * @type {!Element} |
| 15794 */ |
| 15795 positionTarget: { |
| 15796 type: Element |
| 15797 }, |
| 15798 |
| 15799 /** |
| 15800 * The orientation against which to align the element horizontally |
| 15801 * relative to the `positionTarget`. Possible values are "left", "right",
"auto". |
| 15802 */ |
| 15803 horizontalAlign: { |
| 15804 type: String |
| 15805 }, |
| 15806 |
| 15807 /** |
| 15808 * The orientation against which to align the element vertically |
| 15809 * relative to the `positionTarget`. Possible values are "top", "bottom",
"auto". |
| 15810 */ |
| 15811 verticalAlign: { |
| 15812 type: String |
| 15813 }, |
| 15814 |
| 15815 /** |
| 15816 * If true, it will use `horizontalAlign` and `verticalAlign` values as pr
eferred alignment |
| 15817 * and if there's not enough space, it will pick the values which minimize
the cropping. |
| 15818 */ |
| 15819 dynamicAlign: { |
| 15820 type: Boolean |
| 15821 }, |
| 15822 |
| 15823 /** |
| 15824 * The same as setting margin-left and margin-right css properties. |
| 15825 * @deprecated |
| 15826 */ |
| 15827 horizontalOffset: { |
| 15828 type: Number, |
| 15829 value: 0, |
| 15830 notify: true |
| 15831 }, |
| 15832 |
| 15833 /** |
| 15834 * The same as setting margin-top and margin-bottom css properties. |
| 15835 * @deprecated |
| 15836 */ |
| 15837 verticalOffset: { |
| 15838 type: Number, |
| 15839 value: 0, |
| 15840 notify: true |
| 15841 }, |
| 15842 |
| 15843 /** |
| 15844 * Set to true to auto-fit on attach. |
| 15845 */ |
| 15846 autoFitOnAttach: { |
| 15847 type: Boolean, |
| 15848 value: false |
| 15849 }, |
| 15850 |
| 15851 /** @type {?Object} */ |
| 15852 _fitInfo: { |
| 15853 type: Object |
| 15854 } |
| 15855 }, |
| 15856 |
| 15857 get _fitWidth() { |
| 15858 var fitWidth; |
| 15859 if (this.fitInto === window) { |
| 15860 fitWidth = this.fitInto.innerWidth; |
| 15861 } else { |
| 15862 fitWidth = this.fitInto.getBoundingClientRect().width; |
| 15863 } |
| 15864 return fitWidth; |
| 15865 }, |
| 15866 |
| 15867 get _fitHeight() { |
| 15868 var fitHeight; |
| 15869 if (this.fitInto === window) { |
| 15870 fitHeight = this.fitInto.innerHeight; |
| 15871 } else { |
| 15872 fitHeight = this.fitInto.getBoundingClientRect().height; |
| 15873 } |
| 15874 return fitHeight; |
| 15875 }, |
| 15876 |
| 15877 get _fitLeft() { |
| 15878 var fitLeft; |
| 15879 if (this.fitInto === window) { |
| 15880 fitLeft = 0; |
| 15881 } else { |
| 15882 fitLeft = this.fitInto.getBoundingClientRect().left; |
| 15883 } |
| 15884 return fitLeft; |
| 15885 }, |
| 15886 |
| 15887 get _fitTop() { |
| 15888 var fitTop; |
| 15889 if (this.fitInto === window) { |
| 15890 fitTop = 0; |
| 15891 } else { |
| 15892 fitTop = this.fitInto.getBoundingClientRect().top; |
| 15893 } |
| 15894 return fitTop; |
| 15895 }, |
| 15896 |
| 15897 /** |
| 15898 * The element that should be used to position the element, |
| 15899 * if no position target is configured. |
| 15900 */ |
| 15901 get _defaultPositionTarget() { |
| 15902 var parent = Polymer.dom(this).parentNode; |
| 15903 |
| 15904 if (parent && parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
| 15905 parent = parent.host; |
| 15906 } |
| 15907 |
| 15908 return parent; |
| 15909 }, |
| 15910 |
| 15911 /** |
| 15912 * The horizontal align value, accounting for the RTL/LTR text direction. |
| 15913 */ |
| 15914 get _localeHorizontalAlign() { |
| 15915 if (this._isRTL) { |
| 15916 // In RTL, "left" becomes "right". |
| 15917 if (this.horizontalAlign === 'right') { |
| 15918 return 'left'; |
| 15919 } |
| 15920 if (this.horizontalAlign === 'left') { |
| 15921 return 'right'; |
| 15922 } |
| 15923 } |
| 15924 return this.horizontalAlign; |
| 15925 }, |
| 15926 |
| 15927 attached: function() { |
| 15928 // Memoize this to avoid expensive calculations & relayouts. |
| 15929 this._isRTL = window.getComputedStyle(this).direction == 'rtl'; |
| 15930 this.positionTarget = this.positionTarget || this._defaultPositionTarget; |
| 15931 if (this.autoFitOnAttach) { |
| 15932 if (window.getComputedStyle(this).display === 'none') { |
| 15933 setTimeout(function() { |
| 15934 this.fit(); |
| 15935 }.bind(this)); |
| 15936 } else { |
| 15937 this.fit(); |
| 15938 } |
| 15939 } |
| 15940 }, |
| 15941 |
| 15942 /** |
| 15943 * Positions and fits the element into the `fitInto` element. |
| 15944 */ |
| 15945 fit: function() { |
| 15946 this.position(); |
| 15947 this.constrain(); |
| 15948 this.center(); |
| 15949 }, |
| 15950 |
| 15951 /** |
| 15952 * Memoize information needed to position and size the target element. |
| 15953 * @suppress {deprecated} |
| 15954 */ |
| 15955 _discoverInfo: function() { |
| 15956 if (this._fitInfo) { |
| 15957 return; |
| 15958 } |
| 15959 var target = window.getComputedStyle(this); |
| 15960 var sizer = window.getComputedStyle(this.sizingTarget); |
| 15961 |
| 15962 this._fitInfo = { |
| 15963 inlineStyle: { |
| 15964 top: this.style.top || '', |
| 15965 left: this.style.left || '', |
| 15966 position: this.style.position || '' |
| 15967 }, |
| 15968 sizerInlineStyle: { |
| 15969 maxWidth: this.sizingTarget.style.maxWidth || '', |
| 15970 maxHeight: this.sizingTarget.style.maxHeight || '', |
| 15971 boxSizing: this.sizingTarget.style.boxSizing || '' |
| 15972 }, |
| 15973 positionedBy: { |
| 15974 vertically: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto'
? |
| 15975 'bottom' : null), |
| 15976 horizontally: target.left !== 'auto' ? 'left' : (target.right !== 'aut
o' ? |
| 15977 'right' : null) |
| 15978 }, |
| 15979 sizedBy: { |
| 15980 height: sizer.maxHeight !== 'none', |
| 15981 width: sizer.maxWidth !== 'none', |
| 15982 minWidth: parseInt(sizer.minWidth, 10) || 0, |
| 15983 minHeight: parseInt(sizer.minHeight, 10) || 0 |
| 15984 }, |
| 15985 margin: { |
| 15986 top: parseInt(target.marginTop, 10) || 0, |
| 15987 right: parseInt(target.marginRight, 10) || 0, |
| 15988 bottom: parseInt(target.marginBottom, 10) || 0, |
| 15989 left: parseInt(target.marginLeft, 10) || 0 |
| 15990 } |
| 15991 }; |
| 15992 |
| 15993 // Support these properties until they are removed. |
| 15994 if (this.verticalOffset) { |
| 15995 this._fitInfo.margin.top = this._fitInfo.margin.bottom = this.verticalOf
fset; |
| 15996 this._fitInfo.inlineStyle.marginTop = this.style.marginTop || ''; |
| 15997 this._fitInfo.inlineStyle.marginBottom = this.style.marginBottom || ''; |
| 15998 this.style.marginTop = this.style.marginBottom = this.verticalOffset + '
px'; |
| 15999 } |
| 16000 if (this.horizontalOffset) { |
| 16001 this._fitInfo.margin.left = this._fitInfo.margin.right = this.horizontal
Offset; |
| 16002 this._fitInfo.inlineStyle.marginLeft = this.style.marginLeft || ''; |
| 16003 this._fitInfo.inlineStyle.marginRight = this.style.marginRight || ''; |
| 16004 this.style.marginLeft = this.style.marginRight = this.horizontalOffset +
'px'; |
| 16005 } |
| 16006 }, |
| 16007 |
| 16008 /** |
| 16009 * Resets the target element's position and size constraints, and clear |
| 16010 * the memoized data. |
| 16011 */ |
| 16012 resetFit: function() { |
| 16013 var info = this._fitInfo || {}; |
| 16014 for (var property in info.sizerInlineStyle) { |
| 16015 this.sizingTarget.style[property] = info.sizerInlineStyle[property]; |
| 16016 } |
| 16017 for (var property in info.inlineStyle) { |
| 16018 this.style[property] = info.inlineStyle[property]; |
| 16019 } |
| 16020 |
| 16021 this._fitInfo = null; |
| 16022 }, |
| 16023 |
| 16024 /** |
| 16025 * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after |
| 16026 * the element or the `fitInto` element has been resized, or if any of the |
| 16027 * positioning properties (e.g. `horizontalAlign, verticalAlign`) is updated
. |
| 16028 * It preserves the scroll position of the sizingTarget. |
| 16029 */ |
| 16030 refit: function() { |
| 16031 var scrollLeft = this.sizingTarget.scrollLeft; |
| 16032 var scrollTop = this.sizingTarget.scrollTop; |
| 16033 this.resetFit(); |
| 16034 this.fit(); |
| 16035 this.sizingTarget.scrollLeft = scrollLeft; |
| 16036 this.sizingTarget.scrollTop = scrollTop; |
| 16037 }, |
| 16038 |
| 16039 /** |
| 16040 * Positions the element according to `horizontalAlign, verticalAlign`. |
| 16041 */ |
| 16042 position: function() { |
| 16043 if (!this.horizontalAlign && !this.verticalAlign) { |
| 16044 // needs to be centered, and it is done after constrain. |
| 16045 return; |
| 16046 } |
| 16047 this._discoverInfo(); |
| 16048 |
| 16049 this.style.position = 'fixed'; |
| 16050 // Need border-box for margin/padding. |
| 16051 this.sizingTarget.style.boxSizing = 'border-box'; |
| 16052 // Set to 0, 0 in order to discover any offset caused by parent stacking c
ontexts. |
| 16053 this.style.left = '0px'; |
| 16054 this.style.top = '0px'; |
| 16055 |
| 16056 var rect = this.getBoundingClientRect(); |
| 16057 var positionRect = this.__getNormalizedRect(this.positionTarget); |
| 16058 var fitRect = this.__getNormalizedRect(this.fitInto); |
| 16059 |
| 16060 var margin = this._fitInfo.margin; |
| 16061 |
| 16062 // Consider the margin as part of the size for position calculations. |
| 16063 var size = { |
| 16064 width: rect.width + margin.left + margin.right, |
| 16065 height: rect.height + margin.top + margin.bottom |
| 16066 }; |
| 16067 |
| 16068 var position = this.__getPosition(this._localeHorizontalAlign, this.vertic
alAlign, size, positionRect, fitRect); |
| 16069 |
| 16070 var left = position.left + margin.left; |
| 16071 var top = position.top + margin.top; |
| 16072 |
| 16073 // Use original size (without margin). |
| 16074 var right = Math.min(fitRect.right - margin.right, left + rect.width); |
| 16075 var bottom = Math.min(fitRect.bottom - margin.bottom, top + rect.height); |
| 16076 |
| 16077 var minWidth = this._fitInfo.sizedBy.minWidth; |
| 16078 var minHeight = this._fitInfo.sizedBy.minHeight; |
| 16079 if (left < margin.left) { |
| 16080 left = margin.left; |
| 16081 if (right - left < minWidth) { |
| 16082 left = right - minWidth; |
| 16083 } |
| 16084 } |
| 16085 if (top < margin.top) { |
| 16086 top = margin.top; |
| 16087 if (bottom - top < minHeight) { |
| 16088 top = bottom - minHeight; |
| 16089 } |
| 16090 } |
| 16091 |
| 16092 this.sizingTarget.style.maxWidth = (right - left) + 'px'; |
| 16093 this.sizingTarget.style.maxHeight = (bottom - top) + 'px'; |
| 16094 |
| 16095 // Remove the offset caused by any stacking context. |
| 16096 this.style.left = (left - rect.left) + 'px'; |
| 16097 this.style.top = (top - rect.top) + 'px'; |
| 16098 }, |
| 16099 |
| 16100 /** |
| 16101 * Constrains the size of the element to `fitInto` by setting `max-height` |
| 16102 * and/or `max-width`. |
| 16103 */ |
| 16104 constrain: function() { |
| 16105 if (this.horizontalAlign || this.verticalAlign) { |
| 16106 return; |
| 16107 } |
| 16108 this._discoverInfo(); |
| 16109 |
| 16110 var info = this._fitInfo; |
| 16111 // position at (0px, 0px) if not already positioned, so we can measure the
natural size. |
| 16112 if (!info.positionedBy.vertically) { |
| 16113 this.style.position = 'fixed'; |
| 16114 this.style.top = '0px'; |
| 16115 } |
| 16116 if (!info.positionedBy.horizontally) { |
| 16117 this.style.position = 'fixed'; |
| 16118 this.style.left = '0px'; |
| 16119 } |
| 16120 |
| 16121 // need border-box for margin/padding |
| 16122 this.sizingTarget.style.boxSizing = 'border-box'; |
| 16123 // constrain the width and height if not already set |
| 16124 var rect = this.getBoundingClientRect(); |
| 16125 if (!info.sizedBy.height) { |
| 16126 this.__sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom'
, 'Height'); |
| 16127 } |
| 16128 if (!info.sizedBy.width) { |
| 16129 this.__sizeDimension(rect, info.positionedBy.horizontally, 'left', 'righ
t', 'Width'); |
| 16130 } |
| 16131 }, |
| 16132 |
| 16133 /** |
| 16134 * @protected |
| 16135 * @deprecated |
| 16136 */ |
| 16137 _sizeDimension: function(rect, positionedBy, start, end, extent) { |
| 16138 this.__sizeDimension(rect, positionedBy, start, end, extent); |
| 16139 }, |
| 16140 |
| 16141 /** |
| 16142 * @private |
| 16143 */ |
| 16144 __sizeDimension: function(rect, positionedBy, start, end, extent) { |
| 16145 var info = this._fitInfo; |
| 16146 var fitRect = this.__getNormalizedRect(this.fitInto); |
| 16147 var max = extent === 'Width' ? fitRect.width : fitRect.height; |
| 16148 var flip = (positionedBy === end); |
| 16149 var offset = flip ? max - rect[end] : rect[start]; |
| 16150 var margin = info.margin[flip ? start : end]; |
| 16151 var offsetExtent = 'offset' + extent; |
| 16152 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent]; |
| 16153 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO
ffset) + 'px'; |
| 16154 }, |
| 16155 |
| 16156 /** |
| 16157 * Centers horizontally and vertically if not already positioned. This also
sets |
| 16158 * `position:fixed`. |
| 16159 */ |
| 16160 center: function() { |
| 16161 if (this.horizontalAlign || this.verticalAlign) { |
| 16162 return; |
| 16163 } |
| 16164 this._discoverInfo(); |
| 16165 |
| 16166 var positionedBy = this._fitInfo.positionedBy; |
| 16167 if (positionedBy.vertically && positionedBy.horizontally) { |
| 16168 // Already positioned. |
| 16169 return; |
| 16170 } |
| 16171 // Need position:fixed to center |
| 16172 this.style.position = 'fixed'; |
| 16173 // Take into account the offset caused by parents that create stacking |
| 16174 // contexts (e.g. with transform: translate3d). Translate to 0,0 and |
| 16175 // measure the bounding rect. |
| 16176 if (!positionedBy.vertically) { |
| 16177 this.style.top = '0px'; |
| 16178 } |
| 16179 if (!positionedBy.horizontally) { |
| 16180 this.style.left = '0px'; |
| 16181 } |
| 16182 // It will take in consideration margins and transforms |
| 16183 var rect = this.getBoundingClientRect(); |
| 16184 var fitRect = this.__getNormalizedRect(this.fitInto); |
| 16185 if (!positionedBy.vertically) { |
| 16186 var top = fitRect.top - rect.top + (fitRect.height - rect.height) / 2; |
| 16187 this.style.top = top + 'px'; |
| 16188 } |
| 16189 if (!positionedBy.horizontally) { |
| 16190 var left = fitRect.left - rect.left + (fitRect.width - rect.width) / 2; |
| 16191 this.style.left = left + 'px'; |
| 16192 } |
| 16193 }, |
| 16194 |
| 16195 __getNormalizedRect: function(target) { |
| 16196 if (target === document.documentElement || target === window) { |
| 16197 return { |
| 16198 top: 0, |
| 16199 left: 0, |
| 16200 width: window.innerWidth, |
| 16201 height: window.innerHeight, |
| 16202 right: window.innerWidth, |
| 16203 bottom: window.innerHeight |
| 16204 }; |
| 16205 } |
| 16206 return target.getBoundingClientRect(); |
| 16207 }, |
| 16208 |
| 16209 __getCroppedArea: function(position, size, fitRect) { |
| 16210 var verticalCrop = Math.min(0, position.top) + Math.min(0, fitRect.bottom
- (position.top + size.height)); |
| 16211 var horizontalCrop = Math.min(0, position.left) + Math.min(0, fitRect.righ
t - (position.left + size.width)); |
| 16212 return Math.abs(verticalCrop) * size.width + Math.abs(horizontalCrop) * si
ze.height; |
| 16213 }, |
| 16214 |
| 16215 |
| 16216 __getPosition: function(hAlign, vAlign, size, positionRect, fitRect) { |
| 16217 // All the possible configurations. |
| 16218 // Ordered as top-left, top-right, bottom-left, bottom-right. |
| 16219 var positions = [{ |
| 16220 verticalAlign: 'top', |
| 16221 horizontalAlign: 'left', |
| 16222 top: positionRect.top, |
| 16223 left: positionRect.left |
| 16224 }, { |
| 16225 verticalAlign: 'top', |
| 16226 horizontalAlign: 'right', |
| 16227 top: positionRect.top, |
| 16228 left: positionRect.right - size.width |
| 16229 }, { |
| 16230 verticalAlign: 'bottom', |
| 16231 horizontalAlign: 'left', |
| 16232 top: positionRect.bottom - size.height, |
| 16233 left: positionRect.left |
| 16234 }, { |
| 16235 verticalAlign: 'bottom', |
| 16236 horizontalAlign: 'right', |
| 16237 top: positionRect.bottom - size.height, |
| 16238 left: positionRect.right - size.width |
| 16239 }]; |
| 16240 |
| 16241 if (this.noOverlap) { |
| 16242 // Duplicate. |
| 16243 for (var i = 0, l = positions.length; i < l; i++) { |
| 16244 var copy = {}; |
| 16245 for (var key in positions[i]) { |
| 16246 copy[key] = positions[i][key]; |
| 16247 } |
| 16248 positions.push(copy); |
| 16249 } |
| 16250 // Horizontal overlap only. |
| 16251 positions[0].top = positions[1].top += positionRect.height; |
| 16252 positions[2].top = positions[3].top -= positionRect.height; |
| 16253 // Vertical overlap only. |
| 16254 positions[4].left = positions[6].left += positionRect.width; |
| 16255 positions[5].left = positions[7].left -= positionRect.width; |
| 16256 } |
| 16257 |
| 16258 // Consider auto as null for coding convenience. |
| 16259 vAlign = vAlign === 'auto' ? null : vAlign; |
| 16260 hAlign = hAlign === 'auto' ? null : hAlign; |
| 16261 |
| 16262 var position; |
| 16263 for (var i = 0; i < positions.length; i++) { |
| 16264 var pos = positions[i]; |
| 16265 |
| 16266 // If both vAlign and hAlign are defined, return exact match. |
| 16267 // For dynamicAlign and noOverlap we'll have more than one candidate, so |
| 16268 // we'll have to check the croppedArea to make the best choice. |
| 16269 if (!this.dynamicAlign && !this.noOverlap && |
| 16270 pos.verticalAlign === vAlign && pos.horizontalAlign === hAlign) { |
| 16271 position = pos; |
| 16272 break; |
| 16273 } |
| 16274 |
| 16275 // Align is ok if alignment preferences are respected. If no preferences
, |
| 16276 // it is considered ok. |
| 16277 var alignOk = (!vAlign || pos.verticalAlign === vAlign) && |
| 16278 (!hAlign || pos.horizontalAlign === hAlign); |
| 16279 |
| 16280 // Filter out elements that don't match the alignment (if defined). |
| 16281 // With dynamicAlign, we need to consider all the positions to find the |
| 16282 // one that minimizes the cropped area. |
| 16283 if (!this.dynamicAlign && !alignOk) { |
| 16284 continue; |
| 16285 } |
| 16286 |
| 16287 position = position || pos; |
| 16288 pos.croppedArea = this.__getCroppedArea(pos, size, fitRect); |
| 16289 var diff = pos.croppedArea - position.croppedArea; |
| 16290 // Check which crops less. If it crops equally, check if align is ok. |
| 16291 if (diff < 0 || (diff === 0 && alignOk)) { |
| 16292 position = pos; |
| 16293 } |
| 16294 // If not cropped and respects the align requirements, keep it. |
| 16295 // This allows to prefer positions overlapping horizontally over the |
| 16296 // ones overlapping vertically. |
| 16297 if (position.croppedArea === 0 && alignOk) { |
| 16298 break; |
| 16299 } |
| 16300 } |
| 16301 |
| 16302 return position; |
| 16303 } |
| 16304 |
| 16305 }; |
| 16306 </script> |
| 16307 <script> |
| 16308 (function() { |
| 16309 'use strict'; |
| 16310 |
| 16311 /** |
| 16312 * Chrome uses an older version of DOM Level 3 Keyboard Events |
| 16313 * |
| 16314 * Most keys are labeled as text, but some are Unicode codepoints. |
| 16315 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712
21/keyset.html#KeySet-Set |
| 16316 */ |
| 16317 var KEY_IDENTIFIER = { |
| 16318 'U+0008': 'backspace', |
| 16319 'U+0009': 'tab', |
| 16320 'U+001B': 'esc', |
| 16321 'U+0020': 'space', |
| 16322 'U+007F': 'del' |
| 16323 }; |
| 16324 |
| 16325 /** |
| 16326 * Special table for KeyboardEvent.keyCode. |
| 16327 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett
er |
| 16328 * than that. |
| 16329 * |
| 16330 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve
nt.keyCode#Value_of_keyCode |
| 16331 */ |
| 16332 var KEY_CODE = { |
| 16333 8: 'backspace', |
| 16334 9: 'tab', |
| 16335 13: 'enter', |
| 16336 27: 'esc', |
| 16337 33: 'pageup', |
| 16338 34: 'pagedown', |
| 16339 35: 'end', |
| 16340 36: 'home', |
| 16341 32: 'space', |
| 16342 37: 'left', |
| 16343 38: 'up', |
| 16344 39: 'right', |
| 16345 40: 'down', |
| 16346 46: 'del', |
| 16347 106: '*' |
| 16348 }; |
| 16349 |
| 16350 /** |
| 16351 * MODIFIER_KEYS maps the short name for modifier keys used in a key |
| 16352 * combo string to the property name that references those same keys |
| 16353 * in a KeyboardEvent instance. |
| 16354 */ |
| 16355 var MODIFIER_KEYS = { |
| 16356 'shift': 'shiftKey', |
| 16357 'ctrl': 'ctrlKey', |
| 16358 'alt': 'altKey', |
| 16359 'meta': 'metaKey' |
| 16360 }; |
| 16361 |
| 16362 /** |
| 16363 * KeyboardEvent.key is mostly represented by printable character made by |
| 16364 * the keyboard, with unprintable keys labeled nicely. |
| 16365 * |
| 16366 * However, on OS X, Alt+char can make a Unicode character that follows an |
| 16367 * Apple-specific mapping. In this case, we fall back to .keyCode. |
| 16368 */ |
| 16369 var KEY_CHAR = /[a-z0-9*]/; |
| 16370 |
| 16371 /** |
| 16372 * Matches a keyIdentifier string. |
| 16373 */ |
| 16374 var IDENT_CHAR = /U\+/; |
| 16375 |
| 16376 /** |
| 16377 * Matches arrow keys in Gecko 27.0+ |
| 16378 */ |
| 16379 var ARROW_KEY = /^arrow/; |
| 16380 |
| 16381 /** |
| 16382 * Matches space keys everywhere (notably including IE10's exceptional name |
| 16383 * `spacebar`). |
| 16384 */ |
| 16385 var SPACE_KEY = /^space(bar)?/; |
| 16386 |
| 16387 /** |
| 16388 * Matches ESC key. |
| 16389 * |
| 16390 * Value from: http://w3c.github.io/uievents-key/#key-Escape |
| 16391 */ |
| 16392 var ESC_KEY = /^escape$/; |
| 16393 |
| 16394 /** |
| 16395 * Transforms the key. |
| 16396 * @param {string} key The KeyBoardEvent.key |
| 16397 * @param {Boolean} [noSpecialChars] Limits the transformation to |
| 16398 * alpha-numeric characters. |
| 16399 */ |
| 16400 function transformKey(key, noSpecialChars) { |
| 16401 var validKey = ''; |
| 16402 if (key) { |
| 16403 var lKey = key.toLowerCase(); |
| 16404 if (lKey === ' ' || SPACE_KEY.test(lKey)) { |
| 16405 validKey = 'space'; |
| 16406 } else if (ESC_KEY.test(lKey)) { |
| 16407 validKey = 'esc'; |
| 16408 } else if (lKey.length == 1) { |
| 16409 if (!noSpecialChars || KEY_CHAR.test(lKey)) { |
| 16410 validKey = lKey; |
| 16411 } |
| 16412 } else if (ARROW_KEY.test(lKey)) { |
| 16413 validKey = lKey.replace('arrow', ''); |
| 16414 } else if (lKey == 'multiply') { |
| 16415 // numpad '*' can map to Multiply on IE/Windows |
| 16416 validKey = '*'; |
| 16417 } else { |
| 16418 validKey = lKey; |
| 16419 } |
| 16420 } |
| 16421 return validKey; |
| 16422 } |
| 16423 |
| 16424 function transformKeyIdentifier(keyIdent) { |
| 16425 var validKey = ''; |
| 16426 if (keyIdent) { |
| 16427 if (keyIdent in KEY_IDENTIFIER) { |
| 16428 validKey = KEY_IDENTIFIER[keyIdent]; |
| 16429 } else if (IDENT_CHAR.test(keyIdent)) { |
| 16430 keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16); |
| 16431 validKey = String.fromCharCode(keyIdent).toLowerCase(); |
| 16432 } else { |
| 16433 validKey = keyIdent.toLowerCase(); |
| 16434 } |
| 16435 } |
| 16436 return validKey; |
| 16437 } |
| 16438 |
| 16439 function transformKeyCode(keyCode) { |
| 16440 var validKey = ''; |
| 16441 if (Number(keyCode)) { |
| 16442 if (keyCode >= 65 && keyCode <= 90) { |
| 16443 // ascii a-z |
| 16444 // lowercase is 32 offset from uppercase |
| 16445 validKey = String.fromCharCode(32 + keyCode); |
| 16446 } else if (keyCode >= 112 && keyCode <= 123) { |
| 16447 // function keys f1-f12 |
| 16448 validKey = 'f' + (keyCode - 112); |
| 16449 } else if (keyCode >= 48 && keyCode <= 57) { |
| 16450 // top 0-9 keys |
| 16451 validKey = String(keyCode - 48); |
| 16452 } else if (keyCode >= 96 && keyCode <= 105) { |
| 16453 // num pad 0-9 |
| 16454 validKey = String(keyCode - 96); |
| 16455 } else { |
| 16456 validKey = KEY_CODE[keyCode]; |
| 16457 } |
| 16458 } |
| 16459 return validKey; |
| 16460 } |
| 16461 |
| 16462 /** |
| 16463 * Calculates the normalized key for a KeyboardEvent. |
| 16464 * @param {KeyboardEvent} keyEvent |
| 16465 * @param {Boolean} [noSpecialChars] Set to true to limit keyEvent.key |
| 16466 * transformation to alpha-numeric chars. This is useful with key |
| 16467 * combinations like shift + 2, which on FF for MacOS produces |
| 16468 * keyEvent.key = @ |
| 16469 * To get 2 returned, set noSpecialChars = true |
| 16470 * To get @ returned, set noSpecialChars = false |
| 16471 */ |
| 16472 function normalizedKeyForEvent(keyEvent, noSpecialChars) { |
| 16473 // Fall back from .key, to .keyIdentifier, to .keyCode, and then to |
| 16474 // .detail.key to support artificial keyboard events. |
| 16475 return transformKey(keyEvent.key, noSpecialChars) || |
| 16476 transformKeyIdentifier(keyEvent.keyIdentifier) || |
| 16477 transformKeyCode(keyEvent.keyCode) || |
| 16478 transformKey(keyEvent.detail ? keyEvent.detail.key : keyEvent.detail, no
SpecialChars) || ''; |
| 16479 } |
| 16480 |
| 16481 function keyComboMatchesEvent(keyCombo, event) { |
| 16482 // For combos with modifiers we support only alpha-numeric keys |
| 16483 var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers); |
| 16484 return keyEvent === keyCombo.key && |
| 16485 (!keyCombo.hasModifiers || ( |
| 16486 !!event.shiftKey === !!keyCombo.shiftKey && |
| 16487 !!event.ctrlKey === !!keyCombo.ctrlKey && |
| 16488 !!event.altKey === !!keyCombo.altKey && |
| 16489 !!event.metaKey === !!keyCombo.metaKey) |
| 16490 ); |
| 16491 } |
| 16492 |
| 16493 function parseKeyComboString(keyComboString) { |
| 16494 if (keyComboString.length === 1) { |
| 16495 return { |
| 16496 combo: keyComboString, |
| 16497 key: keyComboString, |
| 16498 event: 'keydown' |
| 16499 }; |
| 16500 } |
| 16501 return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboP
art) { |
| 16502 var eventParts = keyComboPart.split(':'); |
| 16503 var keyName = eventParts[0]; |
| 16504 var event = eventParts[1]; |
| 16505 |
| 16506 if (keyName in MODIFIER_KEYS) { |
| 16507 parsedKeyCombo[MODIFIER_KEYS[keyName]] = true; |
| 16508 parsedKeyCombo.hasModifiers = true; |
| 16509 } else { |
| 16510 parsedKeyCombo.key = keyName; |
| 16511 parsedKeyCombo.event = event || 'keydown'; |
| 16512 } |
| 16513 |
| 16514 return parsedKeyCombo; |
| 16515 }, { |
| 16516 combo: keyComboString.split(':').shift() |
| 16517 }); |
| 16518 } |
| 16519 |
| 16520 function parseEventString(eventString) { |
| 16521 return eventString.trim().split(' ').map(function(keyComboString) { |
| 16522 return parseKeyComboString(keyComboString); |
| 16523 }); |
| 16524 } |
| 16525 |
| 16526 /** |
| 16527 * `Polymer.IronA11yKeysBehavior` provides a normalized interface for proces
sing |
| 16528 * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3
.org/TR/wai-aria-practices/#kbd_general_binding). |
| 16529 * The element takes care of browser differences with respect to Keyboard ev
ents |
| 16530 * and uses an expressive syntax to filter key presses. |
| 16531 * |
| 16532 * Use the `keyBindings` prototype property to express what combination of k
eys |
| 16533 * will trigger the callback. A key binding has the format |
| 16534 * `"KEY+MODIFIER:EVENT": "callback"` (`"KEY": "callback"` or |
| 16535 * `"KEY:EVENT": "callback"` are valid as well). Some examples: |
| 16536 * |
| 16537 * keyBindings: { |
| 16538 * 'space': '_onKeydown', // same as 'space:keydown' |
| 16539 * 'shift+tab': '_onKeydown', |
| 16540 * 'enter:keypress': '_onKeypress', |
| 16541 * 'esc:keyup': '_onKeyup' |
| 16542 * } |
| 16543 * |
| 16544 * The callback will receive with an event containing the following informat
ion in `event.detail`: |
| 16545 * |
| 16546 * _onKeydown: function(event) { |
| 16547 * console.log(event.detail.combo); // KEY+MODIFIER, e.g. "shift+tab" |
| 16548 * console.log(event.detail.key); // KEY only, e.g. "tab" |
| 16549 * console.log(event.detail.event); // EVENT, e.g. "keydown" |
| 16550 * console.log(event.detail.keyboardEvent); // the original KeyboardE
vent |
| 16551 * } |
| 16552 * |
| 16553 * Use the `keyEventTarget` attribute to set up event handlers on a specific |
| 16554 * node. |
| 16555 * |
| 16556 * See the [demo source code](https://github.com/PolymerElements/iron-a11y-k
eys-behavior/blob/master/demo/x-key-aware.html) |
| 16557 * for an example. |
| 16558 * |
| 16559 * @demo demo/index.html |
| 16560 * @polymerBehavior |
| 16561 */ |
| 16562 Polymer.IronA11yKeysBehavior = { |
| 16563 properties: { |
| 16564 /** |
| 16565 * The EventTarget that will be firing relevant KeyboardEvents. Set it t
o |
| 16566 * `null` to disable the listeners. |
| 16567 * @type {?EventTarget} |
| 16568 */ |
| 16569 keyEventTarget: { |
| 16570 type: Object, |
| 16571 value: function() { |
| 16572 return this; |
| 16573 } |
| 16574 }, |
| 16575 |
| 16576 /** |
| 16577 * If true, this property will cause the implementing element to |
| 16578 * automatically stop propagation on any handled KeyboardEvents. |
| 16579 */ |
| 16580 stopKeyboardEventPropagation: { |
| 16581 type: Boolean, |
| 16582 value: false |
| 16583 }, |
| 16584 |
| 16585 _boundKeyHandlers: { |
| 16586 type: Array, |
| 16587 value: function() { |
| 16588 return []; |
| 16589 } |
| 16590 }, |
| 16591 |
| 16592 // We use this due to a limitation in IE10 where instances will have |
| 16593 // own properties of everything on the "prototype". |
| 16594 _imperativeKeyBindings: { |
| 16595 type: Object, |
| 16596 value: function() { |
| 16597 return {}; |
| 16598 } |
| 16599 } |
| 16600 }, |
| 16601 |
| 16602 observers: [ |
| 16603 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)' |
| 16604 ], |
| 16605 |
| 16606 |
| 16607 /** |
| 16608 * To be used to express what combination of keys will trigger the relati
ve |
| 16609 * callback. e.g. `keyBindings: { 'esc': '_onEscPressed'}` |
| 16610 * @type {Object} |
| 16611 */ |
| 16612 keyBindings: {}, |
| 16613 |
| 16614 registered: function() { |
| 16615 this._prepKeyBindings(); |
| 16616 }, |
| 16617 |
| 16618 attached: function() { |
| 16619 this._listenKeyEventListeners(); |
| 16620 }, |
| 16621 |
| 16622 detached: function() { |
| 16623 this._unlistenKeyEventListeners(); |
| 16624 }, |
| 16625 |
| 16626 /** |
| 16627 * Can be used to imperatively add a key binding to the implementing |
| 16628 * element. This is the imperative equivalent of declaring a keybinding |
| 16629 * in the `keyBindings` prototype property. |
| 16630 */ |
| 16631 addOwnKeyBinding: function(eventString, handlerName) { |
| 16632 this._imperativeKeyBindings[eventString] = handlerName; |
| 16633 this._prepKeyBindings(); |
| 16634 this._resetKeyEventListeners(); |
| 16635 }, |
| 16636 |
| 16637 /** |
| 16638 * When called, will remove all imperatively-added key bindings. |
| 16639 */ |
| 16640 removeOwnKeyBindings: function() { |
| 16641 this._imperativeKeyBindings = {}; |
| 16642 this._prepKeyBindings(); |
| 16643 this._resetKeyEventListeners(); |
| 16644 }, |
| 16645 |
| 16646 /** |
| 16647 * Returns true if a keyboard event matches `eventString`. |
| 16648 * |
| 16649 * @param {KeyboardEvent} event |
| 16650 * @param {string} eventString |
| 16651 * @return {boolean} |
| 16652 */ |
| 16653 keyboardEventMatchesKeys: function(event, eventString) { |
| 16654 var keyCombos = parseEventString(eventString); |
| 16655 for (var i = 0; i < keyCombos.length; ++i) { |
| 16656 if (keyComboMatchesEvent(keyCombos[i], event)) { |
| 16657 return true; |
| 16658 } |
| 16659 } |
| 16660 return false; |
| 16661 }, |
| 16662 |
| 16663 _collectKeyBindings: function() { |
| 16664 var keyBindings = this.behaviors.map(function(behavior) { |
| 16665 return behavior.keyBindings; |
| 16666 }); |
| 16667 |
| 16668 if (keyBindings.indexOf(this.keyBindings) === -1) { |
| 16669 keyBindings.push(this.keyBindings); |
| 16670 } |
| 16671 |
| 16672 return keyBindings; |
| 16673 }, |
| 16674 |
| 16675 _prepKeyBindings: function() { |
| 16676 this._keyBindings = {}; |
| 16677 |
| 16678 this._collectKeyBindings().forEach(function(keyBindings) { |
| 16679 for (var eventString in keyBindings) { |
| 16680 this._addKeyBinding(eventString, keyBindings[eventString]); |
| 16681 } |
| 16682 }, this); |
| 16683 |
| 16684 for (var eventString in this._imperativeKeyBindings) { |
| 16685 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri
ng]); |
| 16686 } |
| 16687 |
| 16688 // Give precedence to combos with modifiers to be checked first. |
| 16689 for (var eventName in this._keyBindings) { |
| 16690 this._keyBindings[eventName].sort(function (kb1, kb2) { |
| 16691 var b1 = kb1[0].hasModifiers; |
| 16692 var b2 = kb2[0].hasModifiers; |
| 16693 return (b1 === b2) ? 0 : b1 ? -1 : 1; |
| 16694 }) |
| 16695 } |
| 16696 }, |
| 16697 |
| 16698 _addKeyBinding: function(eventString, handlerName) { |
| 16699 parseEventString(eventString).forEach(function(keyCombo) { |
| 16700 this._keyBindings[keyCombo.event] = |
| 16701 this._keyBindings[keyCombo.event] || []; |
| 16702 |
| 16703 this._keyBindings[keyCombo.event].push([ |
| 16704 keyCombo, |
| 16705 handlerName |
| 16706 ]); |
| 16707 }, this); |
| 16708 }, |
| 16709 |
| 16710 _resetKeyEventListeners: function() { |
| 16711 this._unlistenKeyEventListeners(); |
| 16712 |
| 16713 if (this.isAttached) { |
| 16714 this._listenKeyEventListeners(); |
| 16715 } |
| 16716 }, |
| 16717 |
| 16718 _listenKeyEventListeners: function() { |
| 16719 if (!this.keyEventTarget) { |
| 16720 return; |
| 16721 } |
| 16722 Object.keys(this._keyBindings).forEach(function(eventName) { |
| 16723 var keyBindings = this._keyBindings[eventName]; |
| 16724 var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings); |
| 16725 |
| 16726 this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyH
andler]); |
| 16727 |
| 16728 this.keyEventTarget.addEventListener(eventName, boundKeyHandler); |
| 16729 }, this); |
| 16730 }, |
| 16731 |
| 16732 _unlistenKeyEventListeners: function() { |
| 16733 var keyHandlerTuple; |
| 16734 var keyEventTarget; |
| 16735 var eventName; |
| 16736 var boundKeyHandler; |
| 16737 |
| 16738 while (this._boundKeyHandlers.length) { |
| 16739 // My kingdom for block-scope binding and destructuring assignment.. |
| 16740 keyHandlerTuple = this._boundKeyHandlers.pop(); |
| 16741 keyEventTarget = keyHandlerTuple[0]; |
| 16742 eventName = keyHandlerTuple[1]; |
| 16743 boundKeyHandler = keyHandlerTuple[2]; |
| 16744 |
| 16745 keyEventTarget.removeEventListener(eventName, boundKeyHandler); |
| 16746 } |
| 16747 }, |
| 16748 |
| 16749 _onKeyBindingEvent: function(keyBindings, event) { |
| 16750 if (this.stopKeyboardEventPropagation) { |
| 16751 event.stopPropagation(); |
| 16752 } |
| 16753 |
| 16754 // if event has been already prevented, don't do anything |
| 16755 if (event.defaultPrevented) { |
| 16756 return; |
| 16757 } |
| 16758 |
| 16759 for (var i = 0; i < keyBindings.length; i++) { |
| 16760 var keyCombo = keyBindings[i][0]; |
| 16761 var handlerName = keyBindings[i][1]; |
| 16762 if (keyComboMatchesEvent(keyCombo, event)) { |
| 16763 this._triggerKeyHandler(keyCombo, handlerName, event); |
| 16764 // exit the loop if eventDefault was prevented |
| 16765 if (event.defaultPrevented) { |
| 16766 return; |
| 16767 } |
| 16768 } |
| 16769 } |
| 16770 }, |
| 16771 |
| 16772 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) { |
| 16773 var detail = Object.create(keyCombo); |
| 16774 detail.keyboardEvent = keyboardEvent; |
| 16775 var event = new CustomEvent(keyCombo.event, { |
| 16776 detail: detail, |
| 16777 cancelable: true |
| 16778 }); |
| 16779 this[handlerName].call(this, event); |
| 16780 if (event.defaultPrevented) { |
| 16781 keyboardEvent.preventDefault(); |
| 16782 } |
| 16783 } |
| 16784 }; |
| 16785 })(); |
| 16786 </script> |
| 16787 |
| 16788 |
| 16789 <dom-module id="iron-overlay-backdrop" assetpath="/res/imp/bower_components/iron
-overlay-behavior/"> |
| 16790 |
| 16791 <template> |
| 16792 <style> |
| 16793 :host { |
| 16794 position: fixed; |
| 16795 top: 0; |
| 16796 left: 0; |
| 16797 width: 100%; |
| 16798 height: 100%; |
| 16799 background-color: var(--iron-overlay-backdrop-background-color, #000); |
| 16800 opacity: 0; |
| 16801 transition: opacity 0.2s; |
| 16802 pointer-events: none; |
| 16803 @apply(--iron-overlay-backdrop); |
| 16804 } |
| 16805 |
| 16806 :host(.opened) { |
| 16807 opacity: var(--iron-overlay-backdrop-opacity, 0.6); |
| 16808 pointer-events: auto; |
| 16809 @apply(--iron-overlay-backdrop-opened); |
| 16810 } |
| 16811 </style> |
| 16812 |
| 16813 <content></content> |
| 16814 </template> |
| 16815 |
| 16816 </dom-module> |
| 16817 |
| 16818 <script> |
| 16819 (function() { |
| 16820 'use strict'; |
| 16821 |
| 16822 Polymer({ |
| 16823 |
| 16824 is: 'iron-overlay-backdrop', |
| 16825 |
| 16826 properties: { |
| 16827 |
| 16828 /** |
| 16829 * Returns true if the backdrop is opened. |
| 16830 */ |
| 16831 opened: { |
| 16832 reflectToAttribute: true, |
| 16833 type: Boolean, |
| 16834 value: false, |
| 16835 observer: '_openedChanged' |
| 16836 } |
| 16837 |
| 16838 }, |
| 16839 |
| 16840 listeners: { |
| 16841 'transitionend': '_onTransitionend' |
| 16842 }, |
| 16843 |
| 16844 created: function() { |
| 16845 // Used to cancel previous requestAnimationFrame calls when opened changes
. |
| 16846 this.__openedRaf = null; |
| 16847 }, |
| 16848 |
| 16849 attached: function() { |
| 16850 this.opened && this._openedChanged(this.opened); |
| 16851 }, |
| 16852 |
| 16853 /** |
| 16854 * Appends the backdrop to document body if needed. |
| 16855 */ |
| 16856 prepare: function() { |
| 16857 if (this.opened && !this.parentNode) { |
| 16858 Polymer.dom(document.body).appendChild(this); |
| 16859 } |
| 16860 }, |
| 16861 |
| 16862 /** |
| 16863 * Shows the backdrop. |
| 16864 */ |
| 16865 open: function() { |
| 16866 this.opened = true; |
| 16867 }, |
| 16868 |
| 16869 /** |
| 16870 * Hides the backdrop. |
| 16871 */ |
| 16872 close: function() { |
| 16873 this.opened = false; |
| 16874 }, |
| 16875 |
| 16876 /** |
| 16877 * Removes the backdrop from document body if needed. |
| 16878 */ |
| 16879 complete: function() { |
| 16880 if (!this.opened && this.parentNode === document.body) { |
| 16881 Polymer.dom(this.parentNode).removeChild(this); |
| 16882 } |
| 16883 }, |
| 16884 |
| 16885 _onTransitionend: function(event) { |
| 16886 if (event && event.target === this) { |
| 16887 this.complete(); |
| 16888 } |
| 16889 }, |
| 16890 |
| 16891 /** |
| 16892 * @param {boolean} opened |
| 16893 * @private |
| 16894 */ |
| 16895 _openedChanged: function(opened) { |
| 16896 if (opened) { |
| 16897 // Auto-attach. |
| 16898 this.prepare(); |
| 16899 } else { |
| 16900 // Animation might be disabled via the mixin or opacity custom property. |
| 16901 // If it is disabled in other ways, it's up to the user to call complete
. |
| 16902 var cs = window.getComputedStyle(this); |
| 16903 if (cs.transitionDuration === '0s' || cs.opacity == 0) { |
| 16904 this.complete(); |
| 16905 } |
| 16906 } |
| 16907 |
| 16908 if (!this.isAttached) { |
| 16909 return; |
| 16910 } |
| 16911 |
| 16912 // Always cancel previous requestAnimationFrame. |
| 16913 if (this.__openedRaf) { |
| 16914 window.cancelAnimationFrame(this.__openedRaf); |
| 16915 this.__openedRaf = null; |
| 16916 } |
| 16917 // Force relayout to ensure proper transitions. |
| 16918 this.scrollTop = this.scrollTop; |
| 16919 this.__openedRaf = window.requestAnimationFrame(function() { |
| 16920 this.__openedRaf = null; |
| 16921 this.toggleClass('opened', this.opened); |
| 16922 }.bind(this)); |
| 16923 } |
| 16924 }); |
| 16925 |
| 16926 })(); |
| 16927 |
| 16928 </script> |
| 16929 <script> |
| 16930 |
| 16931 /** |
| 16932 * @struct |
| 16933 * @constructor |
| 16934 * @private |
| 16935 */ |
| 16936 Polymer.IronOverlayManagerClass = function() { |
| 16937 /** |
| 16938 * Used to keep track of the opened overlays. |
| 16939 * @private {Array<Element>} |
| 16940 */ |
| 16941 this._overlays = []; |
| 16942 |
| 16943 /** |
| 16944 * iframes have a default z-index of 100, |
| 16945 * so this default should be at least that. |
| 16946 * @private {number} |
| 16947 */ |
| 16948 this._minimumZ = 101; |
| 16949 |
| 16950 /** |
| 16951 * Memoized backdrop element. |
| 16952 * @private {Element|null} |
| 16953 */ |
| 16954 this._backdropElement = null; |
| 16955 |
| 16956 // Enable document-wide tap recognizer. |
| 16957 Polymer.Gestures.add(document, 'tap', null); |
| 16958 // Need to have useCapture=true, Polymer.Gestures doesn't offer that. |
| 16959 document.addEventListener('tap', this._onCaptureClick.bind(this), true); |
| 16960 document.addEventListener('focus', this._onCaptureFocus.bind(this), true); |
| 16961 document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true
); |
| 16962 }; |
| 16963 |
| 16964 Polymer.IronOverlayManagerClass.prototype = { |
| 16965 |
| 16966 constructor: Polymer.IronOverlayManagerClass, |
| 16967 |
| 16968 /** |
| 16969 * The shared backdrop element. |
| 16970 * @type {!Element} backdropElement |
| 16971 */ |
| 16972 get backdropElement() { |
| 16973 if (!this._backdropElement) { |
| 16974 this._backdropElement = document.createElement('iron-overlay-backdrop'); |
| 16975 } |
| 16976 return this._backdropElement; |
| 16977 }, |
| 16978 |
| 16979 /** |
| 16980 * The deepest active element. |
| 16981 * @type {!Element} activeElement the active element |
| 16982 */ |
| 16983 get deepActiveElement() { |
| 16984 // document.activeElement can be null |
| 16985 // https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement |
| 16986 // In case of null, default it to document.body. |
| 16987 var active = document.activeElement || document.body; |
| 16988 while (active.root && Polymer.dom(active.root).activeElement) { |
| 16989 active = Polymer.dom(active.root).activeElement; |
| 16990 } |
| 16991 return active; |
| 16992 }, |
| 16993 |
| 16994 /** |
| 16995 * Brings the overlay at the specified index to the front. |
| 16996 * @param {number} i |
| 16997 * @private |
| 16998 */ |
| 16999 _bringOverlayAtIndexToFront: function(i) { |
| 17000 var overlay = this._overlays[i]; |
| 17001 if (!overlay) { |
| 17002 return; |
| 17003 } |
| 17004 var lastI = this._overlays.length - 1; |
| 17005 var currentOverlay = this._overlays[lastI]; |
| 17006 // Ensure always-on-top overlay stays on top. |
| 17007 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)
) { |
| 17008 lastI--; |
| 17009 } |
| 17010 // If already the top element, return. |
| 17011 if (i >= lastI) { |
| 17012 return; |
| 17013 } |
| 17014 // Update z-index to be on top. |
| 17015 var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ); |
| 17016 if (this._getZ(overlay) <= minimumZ) { |
| 17017 this._applyOverlayZ(overlay, minimumZ); |
| 17018 } |
| 17019 |
| 17020 // Shift other overlays behind the new on top. |
| 17021 while (i < lastI) { |
| 17022 this._overlays[i] = this._overlays[i + 1]; |
| 17023 i++; |
| 17024 } |
| 17025 this._overlays[lastI] = overlay; |
| 17026 }, |
| 17027 |
| 17028 /** |
| 17029 * Adds the overlay and updates its z-index if it's opened, or removes it if
it's closed. |
| 17030 * Also updates the backdrop z-index. |
| 17031 * @param {!Element} overlay |
| 17032 */ |
| 17033 addOrRemoveOverlay: function(overlay) { |
| 17034 if (overlay.opened) { |
| 17035 this.addOverlay(overlay); |
| 17036 } else { |
| 17037 this.removeOverlay(overlay); |
| 17038 } |
| 17039 }, |
| 17040 |
| 17041 /** |
| 17042 * Tracks overlays for z-index and focus management. |
| 17043 * Ensures the last added overlay with always-on-top remains on top. |
| 17044 * @param {!Element} overlay |
| 17045 */ |
| 17046 addOverlay: function(overlay) { |
| 17047 var i = this._overlays.indexOf(overlay); |
| 17048 if (i >= 0) { |
| 17049 this._bringOverlayAtIndexToFront(i); |
| 17050 this.trackBackdrop(); |
| 17051 return; |
| 17052 } |
| 17053 var insertionIndex = this._overlays.length; |
| 17054 var currentOverlay = this._overlays[insertionIndex - 1]; |
| 17055 var minimumZ = Math.max(this._getZ(currentOverlay), this._minimumZ); |
| 17056 var newZ = this._getZ(overlay); |
| 17057 |
| 17058 // Ensure always-on-top overlay stays on top. |
| 17059 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)
) { |
| 17060 // This bumps the z-index of +2. |
| 17061 this._applyOverlayZ(currentOverlay, minimumZ); |
| 17062 insertionIndex--; |
| 17063 // Update minimumZ to match previous overlay's z-index. |
| 17064 var previousOverlay = this._overlays[insertionIndex - 1]; |
| 17065 minimumZ = Math.max(this._getZ(previousOverlay), this._minimumZ); |
| 17066 } |
| 17067 |
| 17068 // Update z-index and insert overlay. |
| 17069 if (newZ <= minimumZ) { |
| 17070 this._applyOverlayZ(overlay, minimumZ); |
| 17071 } |
| 17072 this._overlays.splice(insertionIndex, 0, overlay); |
| 17073 |
| 17074 // Get focused node. |
| 17075 var element = this.deepActiveElement; |
| 17076 overlay.restoreFocusNode = this._overlayParent(element) ? null : element; |
| 17077 this.trackBackdrop(); |
| 17078 }, |
| 17079 |
| 17080 /** |
| 17081 * @param {!Element} overlay |
| 17082 */ |
| 17083 removeOverlay: function(overlay) { |
| 17084 var i = this._overlays.indexOf(overlay); |
| 17085 if (i === -1) { |
| 17086 return; |
| 17087 } |
| 17088 this._overlays.splice(i, 1); |
| 17089 |
| 17090 var node = overlay.restoreFocusOnClose ? overlay.restoreFocusNode : null; |
| 17091 overlay.restoreFocusNode = null; |
| 17092 // Focus back only if still contained in document.body |
| 17093 if (node && Polymer.dom(document.body).deepContains(node)) { |
| 17094 node.focus(); |
| 17095 } |
| 17096 this.trackBackdrop(); |
| 17097 }, |
| 17098 |
| 17099 /** |
| 17100 * Returns the current overlay. |
| 17101 * @return {Element|undefined} |
| 17102 */ |
| 17103 currentOverlay: function() { |
| 17104 var i = this._overlays.length - 1; |
| 17105 return this._overlays[i]; |
| 17106 }, |
| 17107 |
| 17108 /** |
| 17109 * Returns the current overlay z-index. |
| 17110 * @return {number} |
| 17111 */ |
| 17112 currentOverlayZ: function() { |
| 17113 return this._getZ(this.currentOverlay()); |
| 17114 }, |
| 17115 |
| 17116 /** |
| 17117 * Ensures that the minimum z-index of new overlays is at least `minimumZ`. |
| 17118 * This does not effect the z-index of any existing overlays. |
| 17119 * @param {number} minimumZ |
| 17120 */ |
| 17121 ensureMinimumZ: function(minimumZ) { |
| 17122 this._minimumZ = Math.max(this._minimumZ, minimumZ); |
| 17123 }, |
| 17124 |
| 17125 focusOverlay: function() { |
| 17126 var current = /** @type {?} */ (this.currentOverlay()); |
| 17127 // We have to be careful to focus the next overlay _after_ any current |
| 17128 // transitions are complete (due to the state being toggled prior to the |
| 17129 // transition). Otherwise, we risk infinite recursion when a transitioning |
| 17130 // (closed) overlay becomes the current overlay. |
| 17131 // |
| 17132 // NOTE: We make the assumption that any overlay that completes a transiti
on |
| 17133 // will call into focusOverlay to kick the process back off. Currently: |
| 17134 // transitionend -> _applyFocus -> focusOverlay. |
| 17135 if (current && !current.transitioning) { |
| 17136 current._applyFocus(); |
| 17137 } |
| 17138 }, |
| 17139 |
| 17140 /** |
| 17141 * Updates the backdrop z-index. |
| 17142 */ |
| 17143 trackBackdrop: function() { |
| 17144 var overlay = this._overlayWithBackdrop(); |
| 17145 // Avoid creating the backdrop if there is no overlay with backdrop. |
| 17146 if (!overlay && !this._backdropElement) { |
| 17147 return; |
| 17148 } |
| 17149 this.backdropElement.style.zIndex = this._getZ(overlay) - 1; |
| 17150 this.backdropElement.opened = !!overlay; |
| 17151 }, |
| 17152 |
| 17153 /** |
| 17154 * @return {Array<Element>} |
| 17155 */ |
| 17156 getBackdrops: function() { |
| 17157 var backdrops = []; |
| 17158 for (var i = 0; i < this._overlays.length; i++) { |
| 17159 if (this._overlays[i].withBackdrop) { |
| 17160 backdrops.push(this._overlays[i]); |
| 17161 } |
| 17162 } |
| 17163 return backdrops; |
| 17164 }, |
| 17165 |
| 17166 /** |
| 17167 * Returns the z-index for the backdrop. |
| 17168 * @return {number} |
| 17169 */ |
| 17170 backdropZ: function() { |
| 17171 return this._getZ(this._overlayWithBackdrop()) - 1; |
| 17172 }, |
| 17173 |
| 17174 /** |
| 17175 * Returns the first opened overlay that has a backdrop. |
| 17176 * @return {Element|undefined} |
| 17177 * @private |
| 17178 */ |
| 17179 _overlayWithBackdrop: function() { |
| 17180 for (var i = 0; i < this._overlays.length; i++) { |
| 17181 if (this._overlays[i].withBackdrop) { |
| 17182 return this._overlays[i]; |
| 17183 } |
| 17184 } |
| 17185 }, |
| 17186 |
| 17187 /** |
| 17188 * Calculates the minimum z-index for the overlay. |
| 17189 * @param {Element=} overlay |
| 17190 * @private |
| 17191 */ |
| 17192 _getZ: function(overlay) { |
| 17193 var z = this._minimumZ; |
| 17194 if (overlay) { |
| 17195 var z1 = Number(overlay.style.zIndex || window.getComputedStyle(overlay)
.zIndex); |
| 17196 // Check if is a number |
| 17197 // Number.isNaN not supported in IE 10+ |
| 17198 if (z1 === z1) { |
| 17199 z = z1; |
| 17200 } |
| 17201 } |
| 17202 return z; |
| 17203 }, |
| 17204 |
| 17205 /** |
| 17206 * @param {!Element} element |
| 17207 * @param {number|string} z |
| 17208 * @private |
| 17209 */ |
| 17210 _setZ: function(element, z) { |
| 17211 element.style.zIndex = z; |
| 17212 }, |
| 17213 |
| 17214 /** |
| 17215 * @param {!Element} overlay |
| 17216 * @param {number} aboveZ |
| 17217 * @private |
| 17218 */ |
| 17219 _applyOverlayZ: function(overlay, aboveZ) { |
| 17220 this._setZ(overlay, aboveZ + 2); |
| 17221 }, |
| 17222 |
| 17223 /** |
| 17224 * Returns the overlay containing the provided node. If the node is an overl
ay, |
| 17225 * it returns the node. |
| 17226 * @param {Element=} node |
| 17227 * @return {Element|undefined} |
| 17228 * @private |
| 17229 */ |
| 17230 _overlayParent: function(node) { |
| 17231 while (node && node !== document.body) { |
| 17232 // Check if it is an overlay. |
| 17233 if (node._manager === this) { |
| 17234 return node; |
| 17235 } |
| 17236 // Use logical parentNode, or native ShadowRoot host. |
| 17237 node = Polymer.dom(node).parentNode || node.host; |
| 17238 } |
| 17239 }, |
| 17240 |
| 17241 /** |
| 17242 * Returns the deepest overlay in the path. |
| 17243 * @param {Array<Element>=} path |
| 17244 * @return {Element|undefined} |
| 17245 * @suppress {missingProperties} |
| 17246 * @private |
| 17247 */ |
| 17248 _overlayInPath: function(path) { |
| 17249 path = path || []; |
| 17250 for (var i = 0; i < path.length; i++) { |
| 17251 if (path[i]._manager === this) { |
| 17252 return path[i]; |
| 17253 } |
| 17254 } |
| 17255 }, |
| 17256 |
| 17257 /** |
| 17258 * Ensures the click event is delegated to the right overlay. |
| 17259 * @param {!Event} event |
| 17260 * @private |
| 17261 */ |
| 17262 _onCaptureClick: function(event) { |
| 17263 var overlay = /** @type {?} */ (this.currentOverlay()); |
| 17264 // Check if clicked outside of top overlay. |
| 17265 if (overlay && this._overlayInPath(Polymer.dom(event).path) !== overlay) { |
| 17266 overlay._onCaptureClick(event); |
| 17267 } |
| 17268 }, |
| 17269 |
| 17270 /** |
| 17271 * Ensures the focus event is delegated to the right overlay. |
| 17272 * @param {!Event} event |
| 17273 * @private |
| 17274 */ |
| 17275 _onCaptureFocus: function(event) { |
| 17276 var overlay = /** @type {?} */ (this.currentOverlay()); |
| 17277 if (overlay) { |
| 17278 overlay._onCaptureFocus(event); |
| 17279 } |
| 17280 }, |
| 17281 |
| 17282 /** |
| 17283 * Ensures TAB and ESC keyboard events are delegated to the right overlay. |
| 17284 * @param {!Event} event |
| 17285 * @private |
| 17286 */ |
| 17287 _onCaptureKeyDown: function(event) { |
| 17288 var overlay = /** @type {?} */ (this.currentOverlay()); |
| 17289 if (overlay) { |
| 17290 if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc'))
{ |
| 17291 overlay._onCaptureEsc(event); |
| 17292 } else if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event,
'tab')) { |
| 17293 overlay._onCaptureTab(event); |
| 17294 } |
| 17295 } |
| 17296 }, |
| 17297 |
| 17298 /** |
| 17299 * Returns if the overlay1 should be behind overlay2. |
| 17300 * @param {!Element} overlay1 |
| 17301 * @param {!Element} overlay2 |
| 17302 * @return {boolean} |
| 17303 * @suppress {missingProperties} |
| 17304 * @private |
| 17305 */ |
| 17306 _shouldBeBehindOverlay: function(overlay1, overlay2) { |
| 17307 return !overlay1.alwaysOnTop && overlay2.alwaysOnTop; |
| 17308 } |
| 17309 }; |
| 17310 |
| 17311 Polymer.IronOverlayManager = new Polymer.IronOverlayManagerClass(); |
| 17312 </script> |
| 17313 <script> |
| 17314 (function() { |
| 17315 'use strict'; |
| 17316 |
| 17317 /** |
| 17318 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or
shown, and displays |
| 17319 on top of other content. It includes an optional backdrop, and can be used to im
plement a variety |
| 17320 of UI controls including dialogs and drop downs. Multiple overlays may be displa
yed at once. |
| 17321 |
| 17322 See the [demo source code](https://github.com/PolymerElements/iron-overlay-behav
ior/blob/master/demo/simple-overlay.html) |
| 17323 for an example. |
| 17324 |
| 17325 ### Closing and canceling |
| 17326 |
| 17327 An overlay may be hidden by closing or canceling. The difference between close a
nd cancel is user |
| 17328 intent. Closing generally implies that the user acknowledged the content on the
overlay. By default, |
| 17329 it will cancel whenever the user taps outside it or presses the escape key. This
behavior is |
| 17330 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click
` properties. |
| 17331 `close()` should be called explicitly by the implementer when the user interacts
with a control |
| 17332 in the overlay element. When the dialog is canceled, the overlay fires an 'iron-
overlay-canceled' |
| 17333 event. Call `preventDefault` on this event to prevent the overlay from closing. |
| 17334 |
| 17335 ### Positioning |
| 17336 |
| 17337 By default the element is sized and positioned to fit and centered inside the wi
ndow. You can |
| 17338 position and size it manually using CSS. See `Polymer.IronFitBehavior`. |
| 17339 |
| 17340 ### Backdrop |
| 17341 |
| 17342 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The
backdrop is |
| 17343 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page
for styling |
| 17344 options. |
| 17345 |
| 17346 In addition, `with-backdrop` will wrap the focus within the content in the light
DOM. |
| 17347 Override the [`_focusableNodes` getter](#Polymer.IronOverlayBehavior:property-_f
ocusableNodes) |
| 17348 to achieve a different behavior. |
| 17349 |
| 17350 ### Limitations |
| 17351 |
| 17352 The element is styled to appear on top of other content by setting its `z-index`
property. You |
| 17353 must ensure no element has a stacking context with a higher `z-index` than its p
arent stacking |
| 17354 context. You should place this element as a child of `<body>` whenever possible. |
| 17355 |
| 17356 @demo demo/index.html |
| 17357 @polymerBehavior Polymer.IronOverlayBehavior |
| 17358 */ |
| 17359 |
| 17360 Polymer.IronOverlayBehaviorImpl = { |
| 17361 |
| 17362 properties: { |
| 17363 |
| 17364 /** |
| 17365 * True if the overlay is currently displayed. |
| 17366 */ |
| 17367 opened: { |
| 17368 observer: '_openedChanged', |
| 17369 type: Boolean, |
| 17370 value: false, |
| 17371 notify: true |
| 17372 }, |
| 17373 |
| 17374 /** |
| 17375 * True if the overlay was canceled when it was last closed. |
| 17376 */ |
| 17377 canceled: { |
| 17378 observer: '_canceledChanged', |
| 17379 readOnly: true, |
| 17380 type: Boolean, |
| 17381 value: false |
| 17382 }, |
| 17383 |
| 17384 /** |
| 17385 * Set to true to display a backdrop behind the overlay. It traps the focu
s |
| 17386 * within the light DOM of the overlay. |
| 17387 */ |
| 17388 withBackdrop: { |
| 17389 observer: '_withBackdropChanged', |
| 17390 type: Boolean |
| 17391 }, |
| 17392 |
| 17393 /** |
| 17394 * Set to true to disable auto-focusing the overlay or child nodes with |
| 17395 * the `autofocus` attribute` when the overlay is opened. |
| 17396 */ |
| 17397 noAutoFocus: { |
| 17398 type: Boolean, |
| 17399 value: false |
| 17400 }, |
| 17401 |
| 17402 /** |
| 17403 * Set to true to disable canceling the overlay with the ESC key. |
| 17404 */ |
| 17405 noCancelOnEscKey: { |
| 17406 type: Boolean, |
| 17407 value: false |
| 17408 }, |
| 17409 |
| 17410 /** |
| 17411 * Set to true to disable canceling the overlay by clicking outside it. |
| 17412 */ |
| 17413 noCancelOnOutsideClick: { |
| 17414 type: Boolean, |
| 17415 value: false |
| 17416 }, |
| 17417 |
| 17418 /** |
| 17419 * Contains the reason(s) this overlay was last closed (see `iron-overlay-
closed`). |
| 17420 * `IronOverlayBehavior` provides the `canceled` reason; implementers of t
he |
| 17421 * behavior can provide other reasons in addition to `canceled`. |
| 17422 */ |
| 17423 closingReason: { |
| 17424 // was a getter before, but needs to be a property so other |
| 17425 // behaviors can override this. |
| 17426 type: Object |
| 17427 }, |
| 17428 |
| 17429 /** |
| 17430 * Set to true to enable restoring of focus when overlay is closed. |
| 17431 */ |
| 17432 restoreFocusOnClose: { |
| 17433 type: Boolean, |
| 17434 value: false |
| 17435 }, |
| 17436 |
| 17437 /** |
| 17438 * Set to true to keep overlay always on top. |
| 17439 */ |
| 17440 alwaysOnTop: { |
| 17441 type: Boolean |
| 17442 }, |
| 17443 |
| 17444 /** |
| 17445 * Shortcut to access to the overlay manager. |
| 17446 * @private |
| 17447 * @type {Polymer.IronOverlayManagerClass} |
| 17448 */ |
| 17449 _manager: { |
| 17450 type: Object, |
| 17451 value: Polymer.IronOverlayManager |
| 17452 }, |
| 17453 |
| 17454 /** |
| 17455 * The node being focused. |
| 17456 * @type {?Node} |
| 17457 */ |
| 17458 _focusedChild: { |
| 17459 type: Object |
| 17460 } |
| 17461 |
| 17462 }, |
| 17463 |
| 17464 listeners: { |
| 17465 'iron-resize': '_onIronResize' |
| 17466 }, |
| 17467 |
| 17468 /** |
| 17469 * The backdrop element. |
| 17470 * @type {Element} |
| 17471 */ |
| 17472 get backdropElement() { |
| 17473 return this._manager.backdropElement; |
| 17474 }, |
| 17475 |
| 17476 /** |
| 17477 * Returns the node to give focus to. |
| 17478 * @type {Node} |
| 17479 */ |
| 17480 get _focusNode() { |
| 17481 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]'
) || this; |
| 17482 }, |
| 17483 |
| 17484 /** |
| 17485 * Array of nodes that can receive focus (overlay included), ordered by `tab
index`. |
| 17486 * This is used to retrieve which is the first and last focusable nodes in o
rder |
| 17487 * to wrap the focus for overlays `with-backdrop`. |
| 17488 * |
| 17489 * If you know what is your content (specifically the first and last focusab
le children), |
| 17490 * you can override this method to return only `[firstFocusable, lastFocusab
le];` |
| 17491 * @type {Array<Node>} |
| 17492 * @protected |
| 17493 */ |
| 17494 get _focusableNodes() { |
| 17495 // Elements that can be focused even if they have [disabled] attribute. |
| 17496 var FOCUSABLE_WITH_DISABLED = [ |
| 17497 'a[href]', |
| 17498 'area[href]', |
| 17499 'iframe', |
| 17500 '[tabindex]', |
| 17501 '[contentEditable=true]' |
| 17502 ]; |
| 17503 |
| 17504 // Elements that cannot be focused if they have [disabled] attribute. |
| 17505 var FOCUSABLE_WITHOUT_DISABLED = [ |
| 17506 'input', |
| 17507 'select', |
| 17508 'textarea', |
| 17509 'button' |
| 17510 ]; |
| 17511 |
| 17512 // Discard elements with tabindex=-1 (makes them not focusable). |
| 17513 var selector = FOCUSABLE_WITH_DISABLED.join(':not([tabindex="-1"]),') + |
| 17514 ':not([tabindex="-1"]),' + |
| 17515 FOCUSABLE_WITHOUT_DISABLED.join(':not([disabled]):not([tabindex="-1"]),'
) + |
| 17516 ':not([disabled]):not([tabindex="-1"])'; |
| 17517 |
| 17518 var focusables = Polymer.dom(this).querySelectorAll(selector); |
| 17519 if (this.tabIndex >= 0) { |
| 17520 // Insert at the beginning because we might have all elements with tabIn
dex = 0, |
| 17521 // and the overlay should be the first of the list. |
| 17522 focusables.splice(0, 0, this); |
| 17523 } |
| 17524 // Sort by tabindex. |
| 17525 return focusables.sort(function (a, b) { |
| 17526 if (a.tabIndex === b.tabIndex) { |
| 17527 return 0; |
| 17528 } |
| 17529 if (a.tabIndex === 0 || a.tabIndex > b.tabIndex) { |
| 17530 return 1; |
| 17531 } |
| 17532 return -1; |
| 17533 }); |
| 17534 }, |
| 17535 |
| 17536 ready: function() { |
| 17537 // Used to skip calls to notifyResize and refit while the overlay is anima
ting. |
| 17538 this.__isAnimating = false; |
| 17539 // with-backdrop needs tabindex to be set in order to trap the focus. |
| 17540 // If it is not set, IronOverlayBehavior will set it, and remove it if wit
h-backdrop = false. |
| 17541 this.__shouldRemoveTabIndex = false; |
| 17542 // Used for wrapping the focus on TAB / Shift+TAB. |
| 17543 this.__firstFocusableNode = this.__lastFocusableNode = null; |
| 17544 // Used for requestAnimationFrame when opened changes. |
| 17545 this.__openChangedAsync = null; |
| 17546 // Used for requestAnimationFrame when iron-resize is fired. |
| 17547 this.__onIronResizeAsync = null; |
| 17548 this._ensureSetup(); |
| 17549 }, |
| 17550 |
| 17551 attached: function() { |
| 17552 // Call _openedChanged here so that position can be computed correctly. |
| 17553 if (this.opened) { |
| 17554 this._openedChanged(); |
| 17555 } |
| 17556 this._observer = Polymer.dom(this).observeNodes(this._onNodesChange); |
| 17557 }, |
| 17558 |
| 17559 detached: function() { |
| 17560 Polymer.dom(this).unobserveNodes(this._observer); |
| 17561 this._observer = null; |
| 17562 this.opened = false; |
| 17563 }, |
| 17564 |
| 17565 /** |
| 17566 * Toggle the opened state of the overlay. |
| 17567 */ |
| 17568 toggle: function() { |
| 17569 this._setCanceled(false); |
| 17570 this.opened = !this.opened; |
| 17571 }, |
| 17572 |
| 17573 /** |
| 17574 * Open the overlay. |
| 17575 */ |
| 17576 open: function() { |
| 17577 this._setCanceled(false); |
| 17578 this.opened = true; |
| 17579 }, |
| 17580 |
| 17581 /** |
| 17582 * Close the overlay. |
| 17583 */ |
| 17584 close: function() { |
| 17585 this._setCanceled(false); |
| 17586 this.opened = false; |
| 17587 }, |
| 17588 |
| 17589 /** |
| 17590 * Cancels the overlay. |
| 17591 * @param {Event=} event The original event |
| 17592 */ |
| 17593 cancel: function(event) { |
| 17594 var cancelEvent = this.fire('iron-overlay-canceled', event, {cancelable: t
rue}); |
| 17595 if (cancelEvent.defaultPrevented) { |
| 17596 return; |
| 17597 } |
| 17598 |
| 17599 this._setCanceled(true); |
| 17600 this.opened = false; |
| 17601 }, |
| 17602 |
| 17603 _ensureSetup: function() { |
| 17604 if (this._overlaySetup) { |
| 17605 return; |
| 17606 } |
| 17607 this._overlaySetup = true; |
| 17608 this.style.outline = 'none'; |
| 17609 this.style.display = 'none'; |
| 17610 }, |
| 17611 |
| 17612 _openedChanged: function() { |
| 17613 if (this.opened) { |
| 17614 this.removeAttribute('aria-hidden'); |
| 17615 } else { |
| 17616 this.setAttribute('aria-hidden', 'true'); |
| 17617 } |
| 17618 |
| 17619 // wait to call after ready only if we're initially open |
| 17620 if (!this._overlaySetup) { |
| 17621 return; |
| 17622 } |
| 17623 |
| 17624 if (this.__openChangedAsync) { |
| 17625 window.cancelAnimationFrame(this.__openChangedAsync); |
| 17626 } |
| 17627 |
| 17628 // Synchronously remove the overlay. |
| 17629 // The adding is done asynchronously to go out of the scope of the event |
| 17630 // which might have generated the opening. |
| 17631 if (!this.opened) { |
| 17632 this._manager.removeOverlay(this); |
| 17633 } |
| 17634 |
| 17635 // Defer any animation-related code on attached |
| 17636 // (_openedChanged gets called again on attached). |
| 17637 if (!this.isAttached) { |
| 17638 return; |
| 17639 } |
| 17640 |
| 17641 this.__isAnimating = true; |
| 17642 |
| 17643 // requestAnimationFrame for non-blocking rendering |
| 17644 this.__openChangedAsync = window.requestAnimationFrame(function() { |
| 17645 this.__openChangedAsync = null; |
| 17646 if (this.opened) { |
| 17647 this._manager.addOverlay(this); |
| 17648 this._prepareRenderOpened(); |
| 17649 this._renderOpened(); |
| 17650 } else { |
| 17651 // Move the focus before actually closing. |
| 17652 this._applyFocus(); |
| 17653 this._renderClosed(); |
| 17654 } |
| 17655 }.bind(this)); |
| 17656 }, |
| 17657 |
| 17658 _canceledChanged: function() { |
| 17659 this.closingReason = this.closingReason || {}; |
| 17660 this.closingReason.canceled = this.canceled; |
| 17661 }, |
| 17662 |
| 17663 _withBackdropChanged: function() { |
| 17664 // If tabindex is already set, no need to override it. |
| 17665 if (this.withBackdrop && !this.hasAttribute('tabindex')) { |
| 17666 this.setAttribute('tabindex', '-1'); |
| 17667 this.__shouldRemoveTabIndex = true; |
| 17668 } else if (this.__shouldRemoveTabIndex) { |
| 17669 this.removeAttribute('tabindex'); |
| 17670 this.__shouldRemoveTabIndex = false; |
| 17671 } |
| 17672 if (this.opened && this.isAttached) { |
| 17673 this._manager.trackBackdrop(); |
| 17674 } |
| 17675 }, |
| 17676 |
| 17677 /** |
| 17678 * tasks which must occur before opening; e.g. making the element visible. |
| 17679 * @protected |
| 17680 */ |
| 17681 _prepareRenderOpened: function() { |
| 17682 |
| 17683 // Needed to calculate the size of the overlay so that transitions on its
size |
| 17684 // will have the correct starting points. |
| 17685 this._preparePositioning(); |
| 17686 this.refit(); |
| 17687 this._finishPositioning(); |
| 17688 |
| 17689 // Move the focus to the child node with [autofocus]. |
| 17690 this._applyFocus(); |
| 17691 |
| 17692 // Safari will apply the focus to the autofocus element when displayed for
the first time, |
| 17693 // so we blur it. Later, _applyFocus will set the focus if necessary. |
| 17694 if (this.noAutoFocus && document.activeElement === this._focusNode) { |
| 17695 this._focusNode.blur(); |
| 17696 } |
| 17697 }, |
| 17698 |
| 17699 /** |
| 17700 * Tasks which cause the overlay to actually open; typically play an animati
on. |
| 17701 * @protected |
| 17702 */ |
| 17703 _renderOpened: function() { |
| 17704 this._finishRenderOpened(); |
| 17705 }, |
| 17706 |
| 17707 /** |
| 17708 * Tasks which cause the overlay to actually close; typically play an animat
ion. |
| 17709 * @protected |
| 17710 */ |
| 17711 _renderClosed: function() { |
| 17712 this._finishRenderClosed(); |
| 17713 }, |
| 17714 |
| 17715 /** |
| 17716 * Tasks to be performed at the end of open action. Will fire `iron-overlay-
opened`. |
| 17717 * @protected |
| 17718 */ |
| 17719 _finishRenderOpened: function() { |
| 17720 |
| 17721 this.notifyResize(); |
| 17722 this.__isAnimating = false; |
| 17723 |
| 17724 // Store it so we don't query too much. |
| 17725 var focusableNodes = this._focusableNodes; |
| 17726 this.__firstFocusableNode = focusableNodes[0]; |
| 17727 this.__lastFocusableNode = focusableNodes[focusableNodes.length - 1]; |
| 17728 |
| 17729 this.fire('iron-overlay-opened'); |
| 17730 }, |
| 17731 |
| 17732 /** |
| 17733 * Tasks to be performed at the end of close action. Will fire `iron-overlay
-closed`. |
| 17734 * @protected |
| 17735 */ |
| 17736 _finishRenderClosed: function() { |
| 17737 // Hide the overlay and remove the backdrop. |
| 17738 this.style.display = 'none'; |
| 17739 // Reset z-index only at the end of the animation. |
| 17740 this.style.zIndex = ''; |
| 17741 |
| 17742 this.notifyResize(); |
| 17743 this.__isAnimating = false; |
| 17744 this.fire('iron-overlay-closed', this.closingReason); |
| 17745 }, |
| 17746 |
| 17747 _preparePositioning: function() { |
| 17748 this.style.transition = this.style.webkitTransition = 'none'; |
| 17749 this.style.transform = this.style.webkitTransform = 'none'; |
| 17750 this.style.display = ''; |
| 17751 }, |
| 17752 |
| 17753 _finishPositioning: function() { |
| 17754 // First, make it invisible & reactivate animations. |
| 17755 this.style.display = 'none'; |
| 17756 // Force reflow before re-enabling animations so that they don't start. |
| 17757 // Set scrollTop to itself so that Closure Compiler doesn't remove this. |
| 17758 this.scrollTop = this.scrollTop; |
| 17759 this.style.transition = this.style.webkitTransition = ''; |
| 17760 this.style.transform = this.style.webkitTransform = ''; |
| 17761 // Now that animations are enabled, make it visible again |
| 17762 this.style.display = ''; |
| 17763 // Force reflow, so that following animations are properly started. |
| 17764 // Set scrollTop to itself so that Closure Compiler doesn't remove this. |
| 17765 this.scrollTop = this.scrollTop; |
| 17766 }, |
| 17767 |
| 17768 /** |
| 17769 * Applies focus according to the opened state. |
| 17770 * @protected |
| 17771 */ |
| 17772 _applyFocus: function() { |
| 17773 if (this.opened) { |
| 17774 if (!this.noAutoFocus) { |
| 17775 this._focusNode.focus(); |
| 17776 } |
| 17777 } else { |
| 17778 this._focusNode.blur(); |
| 17779 this._focusedChild = null; |
| 17780 this._manager.focusOverlay(); |
| 17781 } |
| 17782 }, |
| 17783 |
| 17784 /** |
| 17785 * Cancels (closes) the overlay. Call when click happens outside the overlay
. |
| 17786 * @param {!Event} event |
| 17787 * @protected |
| 17788 */ |
| 17789 _onCaptureClick: function(event) { |
| 17790 if (!this.noCancelOnOutsideClick) { |
| 17791 this.cancel(event); |
| 17792 } |
| 17793 }, |
| 17794 |
| 17795 /** |
| 17796 * Keeps track of the focused child. If withBackdrop, traps focus within ove
rlay. |
| 17797 * @param {!Event} event |
| 17798 * @protected |
| 17799 */ |
| 17800 _onCaptureFocus: function (event) { |
| 17801 if (!this.withBackdrop) { |
| 17802 return; |
| 17803 } |
| 17804 var path = Polymer.dom(event).path; |
| 17805 if (path.indexOf(this) === -1) { |
| 17806 event.stopPropagation(); |
| 17807 this._applyFocus(); |
| 17808 } else { |
| 17809 this._focusedChild = path[0]; |
| 17810 } |
| 17811 }, |
| 17812 |
| 17813 /** |
| 17814 * Handles the ESC key event and cancels (closes) the overlay. |
| 17815 * @param {!Event} event |
| 17816 * @protected |
| 17817 */ |
| 17818 _onCaptureEsc: function(event) { |
| 17819 if (!this.noCancelOnEscKey) { |
| 17820 this.cancel(event); |
| 17821 } |
| 17822 }, |
| 17823 |
| 17824 /** |
| 17825 * Handles TAB key events to track focus changes. |
| 17826 * Will wrap focus for overlays withBackdrop. |
| 17827 * @param {!Event} event |
| 17828 * @protected |
| 17829 */ |
| 17830 _onCaptureTab: function(event) { |
| 17831 if (!this.withBackdrop) { |
| 17832 return; |
| 17833 } |
| 17834 // TAB wraps from last to first focusable. |
| 17835 // Shift + TAB wraps from first to last focusable. |
| 17836 var shift = event.shiftKey; |
| 17837 var nodeToCheck = shift ? this.__firstFocusableNode : this.__lastFocusable
Node; |
| 17838 var nodeToSet = shift ? this.__lastFocusableNode : this.__firstFocusableNo
de; |
| 17839 var shouldWrap = false; |
| 17840 if (nodeToCheck === nodeToSet) { |
| 17841 // If nodeToCheck is the same as nodeToSet, it means we have an overlay |
| 17842 // with 0 or 1 focusables; in either case we still need to trap the |
| 17843 // focus within the overlay. |
| 17844 shouldWrap = true; |
| 17845 } else { |
| 17846 // In dom=shadow, the manager will receive focus changes on the main |
| 17847 // root but not the ones within other shadow roots, so we can't rely on |
| 17848 // _focusedChild, but we should check the deepest active element. |
| 17849 var focusedNode = this._manager.deepActiveElement; |
| 17850 // If the active element is not the nodeToCheck but the overlay itself, |
| 17851 // it means the focus is about to go outside the overlay, hence we |
| 17852 // should prevent that (e.g. user opens the overlay and hit Shift+TAB). |
| 17853 shouldWrap = (focusedNode === nodeToCheck || focusedNode === this); |
| 17854 } |
| 17855 |
| 17856 if (shouldWrap) { |
| 17857 // When the overlay contains the last focusable element of the document |
| 17858 // and it's already focused, pressing TAB would move the focus outside |
| 17859 // the document (e.g. to the browser search bar). Similarly, when the |
| 17860 // overlay contains the first focusable element of the document and it's |
| 17861 // already focused, pressing Shift+TAB would move the focus outside the |
| 17862 // document (e.g. to the browser search bar). |
| 17863 // In both cases, we would not receive a focus event, but only a blur. |
| 17864 // In order to achieve focus wrapping, we prevent this TAB event and |
| 17865 // force the focus. This will also prevent the focus to temporarily move |
| 17866 // outside the overlay, which might cause scrolling. |
| 17867 event.preventDefault(); |
| 17868 this._focusedChild = nodeToSet; |
| 17869 this._applyFocus(); |
| 17870 } |
| 17871 }, |
| 17872 |
| 17873 /** |
| 17874 * Refits if the overlay is opened and not animating. |
| 17875 * @protected |
| 17876 */ |
| 17877 _onIronResize: function() { |
| 17878 if (this.__onIronResizeAsync) { |
| 17879 window.cancelAnimationFrame(this.__onIronResizeAsync); |
| 17880 this.__onIronResizeAsync = null; |
| 17881 } |
| 17882 if (this.opened && !this.__isAnimating) { |
| 17883 this.__onIronResizeAsync = window.requestAnimationFrame(function() { |
| 17884 this.__onIronResizeAsync = null; |
| 17885 this.refit(); |
| 17886 }.bind(this)); |
| 17887 } |
| 17888 }, |
| 17889 |
| 17890 /** |
| 17891 * Will call notifyResize if overlay is opened. |
| 17892 * Can be overridden in order to avoid multiple observers on the same node. |
| 17893 * @protected |
| 17894 */ |
| 17895 _onNodesChange: function() { |
| 17896 if (this.opened && !this.__isAnimating) { |
| 17897 this.notifyResize(); |
| 17898 } |
| 17899 } |
| 17900 }; |
| 17901 |
| 17902 /** @polymerBehavior */ |
| 17903 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB
ehavior, Polymer.IronOverlayBehaviorImpl]; |
| 17904 |
| 17905 /** |
| 17906 * Fired after the overlay opens. |
| 17907 * @event iron-overlay-opened |
| 17908 */ |
| 17909 |
| 17910 /** |
| 17911 * Fired when the overlay is canceled, but before it is closed. |
| 17912 * @event iron-overlay-canceled |
| 17913 * @param {Event} event The closing of the overlay can be prevented |
| 17914 * by calling `event.preventDefault()`. The `event.detail` is the original eve
nt that |
| 17915 * originated the canceling (e.g. ESC keyboard event or click event outside th
e overlay). |
| 17916 */ |
| 17917 |
| 17918 /** |
| 17919 * Fired after the overlay closes. |
| 17920 * @event iron-overlay-closed |
| 17921 * @param {Event} event The `event.detail` is the `closingReason` property |
| 17922 * (contains `canceled`, whether the overlay was canceled). |
| 17923 */ |
| 17924 |
| 17925 })(); |
| 17926 </script> |
| 17927 |
| 17928 |
| 17929 <dom-module id="paper-toast" assetpath="/res/imp/bower_components/paper-toast/"> |
| 17930 <template> |
| 17931 <style> |
| 17932 :host { |
| 17933 display: block; |
| 17934 position: fixed; |
| 17935 background-color: var(--paper-toast-background-color, #323232); |
| 17936 color: var(--paper-toast-color, #f1f1f1); |
| 17937 min-height: 48px; |
| 17938 min-width: 288px; |
| 17939 padding: 16px 24px; |
| 17940 box-sizing: border-box; |
| 17941 box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); |
| 17942 border-radius: 2px; |
| 17943 margin: 12px; |
| 17944 font-size: 14px; |
| 17945 cursor: default; |
| 17946 -webkit-transition: -webkit-transform 0.3s, opacity 0.3s; |
| 17947 transition: transform 0.3s, opacity 0.3s; |
| 17948 opacity: 0; |
| 17949 -webkit-transform: translateY(100px); |
| 17950 transform: translateY(100px); |
| 17951 @apply(--paper-font-common-base); |
| 17952 } |
| 17953 |
| 17954 :host(.capsule) { |
| 17955 border-radius: 24px; |
| 17956 } |
| 17957 |
| 17958 :host(.fit-bottom) { |
| 17959 width: 100%; |
| 17960 min-width: 0; |
| 17961 border-radius: 0; |
| 17962 margin: 0; |
| 17963 } |
| 17964 |
| 17965 :host(.paper-toast-open) { |
| 17966 opacity: 1; |
| 17967 -webkit-transform: translateY(0px); |
| 17968 transform: translateY(0px); |
| 17969 } |
| 17970 </style> |
| 17971 |
| 17972 <span id="label">{{text}}</span> |
| 17973 <content></content> |
| 17974 </template> |
| 17975 |
| 17976 <script> |
| 17977 (function() { |
| 17978 // Keeps track of the toast currently opened. |
| 17979 var currentToast = null; |
| 17980 |
| 17981 Polymer({ |
| 17982 is: 'paper-toast', |
| 17983 |
| 17984 behaviors: [ |
| 17985 Polymer.IronOverlayBehavior |
| 17986 ], |
| 17987 |
| 17988 properties: { |
| 17989 /** |
| 17990 * The element to fit `this` into. |
| 17991 * Overridden from `Polymer.IronFitBehavior`. |
| 17992 */ |
| 17993 fitInto: { |
| 17994 type: Object, |
| 17995 value: window, |
| 17996 observer: '_onFitIntoChanged' |
| 17997 }, |
| 17998 |
| 17999 /** |
| 18000 * The orientation against which to align the dropdown content |
| 18001 * horizontally relative to `positionTarget`. |
| 18002 * Overridden from `Polymer.IronFitBehavior`. |
| 18003 */ |
| 18004 horizontalAlign: { |
| 18005 type: String, |
| 18006 value: 'left' |
| 18007 }, |
| 18008 |
| 18009 /** |
| 18010 * The orientation against which to align the dropdown content |
| 18011 * vertically relative to `positionTarget`. |
| 18012 * Overridden from `Polymer.IronFitBehavior`. |
| 18013 */ |
| 18014 verticalAlign: { |
| 18015 type: String, |
| 18016 value: 'bottom' |
| 18017 }, |
| 18018 |
| 18019 /** |
| 18020 * The duration in milliseconds to show the toast. |
| 18021 * Set to `0`, a negative number, or `Infinity`, to disable the |
| 18022 * toast auto-closing. |
| 18023 */ |
| 18024 duration: { |
| 18025 type: Number, |
| 18026 value: 3000 |
| 18027 }, |
| 18028 |
| 18029 /** |
| 18030 * The text to display in the toast. |
| 18031 */ |
| 18032 text: { |
| 18033 type: String, |
| 18034 value: '' |
| 18035 }, |
| 18036 |
| 18037 /** |
| 18038 * Overridden from `IronOverlayBehavior`. |
| 18039 * Set to false to enable closing of the toast by clicking outside it. |
| 18040 */ |
| 18041 noCancelOnOutsideClick: { |
| 18042 type: Boolean, |
| 18043 value: true |
| 18044 }, |
| 18045 |
| 18046 /** |
| 18047 * Overridden from `IronOverlayBehavior`. |
| 18048 * Set to true to disable auto-focusing the toast or child nodes with |
| 18049 * the `autofocus` attribute` when the overlay is opened. |
| 18050 */ |
| 18051 noAutoFocus: { |
| 18052 type: Boolean, |
| 18053 value: true |
| 18054 } |
| 18055 }, |
| 18056 |
| 18057 listeners: { |
| 18058 'transitionend': '__onTransitionEnd' |
| 18059 }, |
| 18060 |
| 18061 /** |
| 18062 * Read-only. Deprecated. Use `opened` from `IronOverlayBehavior`. |
| 18063 * @property visible |
| 18064 * @deprecated |
| 18065 */ |
| 18066 get visible() { |
| 18067 Polymer.Base._warn('`visible` is deprecated, use `opened` instead'); |
| 18068 return this.opened; |
| 18069 }, |
| 18070 |
| 18071 /** |
| 18072 * Read-only. Can auto-close if duration is a positive finite number. |
| 18073 * @property _canAutoClose |
| 18074 */ |
| 18075 get _canAutoClose() { |
| 18076 return this.duration > 0 && this.duration !== Infinity; |
| 18077 }, |
| 18078 |
| 18079 created: function() { |
| 18080 this._autoClose = null; |
| 18081 Polymer.IronA11yAnnouncer.requestAvailability(); |
| 18082 }, |
| 18083 |
| 18084 /** |
| 18085 * Show the toast. Without arguments, this is the same as `open()` from
`IronOverlayBehavior`. |
| 18086 * @param {(Object|string)=} properties Properties to be set before open
ing the toast. |
| 18087 * e.g. `toast.show('hello')` or `toast.show({text: 'hello', duration: 3
000})` |
| 18088 */ |
| 18089 show: function(properties) { |
| 18090 if (typeof properties == 'string') { |
| 18091 properties = { text: properties }; |
| 18092 } |
| 18093 for (var property in properties) { |
| 18094 if (property.indexOf('_') === 0) { |
| 18095 Polymer.Base._warn('The property "' + property + '" is private and
was not set.'); |
| 18096 } else if (property in this) { |
| 18097 this[property] = properties[property]; |
| 18098 } else { |
| 18099 Polymer.Base._warn('The property "' + property + '" is not valid.'
); |
| 18100 } |
| 18101 } |
| 18102 this.open(); |
| 18103 }, |
| 18104 |
| 18105 /** |
| 18106 * Hide the toast. Same as `close()` from `IronOverlayBehavior`. |
| 18107 */ |
| 18108 hide: function() { |
| 18109 this.close(); |
| 18110 }, |
| 18111 |
| 18112 /** |
| 18113 * Called on transitions of the toast, indicating a finished animation |
| 18114 * @private |
| 18115 */ |
| 18116 __onTransitionEnd: function(e) { |
| 18117 // there are different transitions that are happening when opening and |
| 18118 // closing the toast. The last one so far is for `opacity`. |
| 18119 // This marks the end of the transition, so we check for this to deter
mine if this |
| 18120 // is the correct event. |
| 18121 if (e && e.target === this && e.propertyName === 'opacity') { |
| 18122 if (this.opened) { |
| 18123 this._finishRenderOpened(); |
| 18124 } else { |
| 18125 this._finishRenderClosed(); |
| 18126 } |
| 18127 } |
| 18128 }, |
| 18129 |
| 18130 /** |
| 18131 * Overridden from `IronOverlayBehavior`. |
| 18132 * Called when the value of `opened` changes. |
| 18133 */ |
| 18134 _openedChanged: function() { |
| 18135 if (this._autoClose !== null) { |
| 18136 this.cancelAsync(this._autoClose); |
| 18137 this._autoClose = null; |
| 18138 } |
| 18139 if (this.opened) { |
| 18140 if (currentToast && currentToast !== this) { |
| 18141 currentToast.close(); |
| 18142 } |
| 18143 currentToast = this; |
| 18144 this.fire('iron-announce', { |
| 18145 text: this.text |
| 18146 }); |
| 18147 if (this._canAutoClose) { |
| 18148 this._autoClose = this.async(this.close, this.duration); |
| 18149 } |
| 18150 } else if (currentToast === this) { |
| 18151 currentToast = null; |
| 18152 } |
| 18153 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments); |
| 18154 }, |
| 18155 |
| 18156 /** |
| 18157 * Overridden from `IronOverlayBehavior`. |
| 18158 */ |
| 18159 _renderOpened: function() { |
| 18160 this.classList.add('paper-toast-open'); |
| 18161 }, |
| 18162 |
| 18163 /** |
| 18164 * Overridden from `IronOverlayBehavior`. |
| 18165 */ |
| 18166 _renderClosed: function() { |
| 18167 this.classList.remove('paper-toast-open'); |
| 18168 }, |
| 18169 |
| 18170 /** |
| 18171 * @private |
| 18172 */ |
| 18173 _onFitIntoChanged: function(fitInto) { |
| 18174 this.positionTarget = fitInto; |
| 18175 } |
| 18176 |
| 18177 /** |
| 18178 * Fired when `paper-toast` is opened. |
| 18179 * |
| 18180 * @event 'iron-announce' |
| 18181 * @param {{text: string}} detail Contains text that will be announced. |
| 18182 */ |
| 18183 }); |
| 18184 })(); |
| 18185 </script> |
| 18186 </dom-module> |
| 18187 <dom-module id="url-param" assetpath="/res/imp/common/"> |
| 18188 <template> |
| 18189 <paper-toast id="toast"></paper-toast> |
| 18190 </template> |
| 18191 <script> |
| 18192 (function(){ |
| 18193 Polymer({ |
| 18194 is: 'url-param', |
| 18195 properties: { |
| 18196 default_value: { |
| 18197 type: String, |
| 18198 }, |
| 18199 default_values: { |
| 18200 type: Array, |
| 18201 }, |
| 18202 multi: { |
| 18203 type: Boolean, |
| 18204 value: false, |
| 18205 }, |
| 18206 name: { |
| 18207 type: String, |
| 18208 }, |
| 18209 valid: { |
| 18210 type: Array, |
| 18211 }, |
| 18212 value: { |
| 18213 type: String, |
| 18214 value: '', |
| 18215 notify: true, |
| 18216 observer: 'valueChanged', |
| 18217 }, |
| 18218 |
| 18219 _loaded: { |
| 18220 type: Boolean, |
| 18221 value: false, |
| 18222 } |
| 18223 }, |
| 18224 // Listens to array changes for multi urls |
| 18225 observers: ["valueChanged(value.splices)"], |
| 18226 |
| 18227 ready: function () { |
| 18228 this._loaded = true; |
| 18229 |
| 18230 // Read the URL parameters. If our variable is set, save its value. |
| 18231 // Otherwise, place our value in the URL. |
| 18232 var val = this.getURL(); |
| 18233 if (val && this.isValid(val)) { |
| 18234 this.set('value', val); |
| 18235 } else if (this.default_value && this.isValid(this.default_value)) { |
| 18236 this.set('value', this.default_value); |
| 18237 } |
| 18238 else if (this.multi && this.default_values && this.isValid(this.default_
values)) { |
| 18239 this.set('value', this.default_values); |
| 18240 } |
| 18241 else { |
| 18242 this.putURL(); |
| 18243 } |
| 18244 }, |
| 18245 // Retrieve the value for our variable from the URL. |
| 18246 getURL: function () { |
| 18247 var vals = sk.query.toParamSet(window.location.search.substring(1))[this
.name]; |
| 18248 if (!vals) { |
| 18249 return null; |
| 18250 } |
| 18251 if (this.multi) { |
| 18252 return vals; |
| 18253 } |
| 18254 if (vals.length > 1) { |
| 18255 this.error('Multiple values provided for ' + this.name + ' but only on
e accepted: ' + vals); |
| 18256 return null; |
| 18257 } |
| 18258 return vals[0]; |
| 18259 }, |
| 18260 // Store the value for our variable in the URL. |
| 18261 putURL: function () { |
| 18262 var params = sk.query.toParamSet(window.location.search.substring(1)); |
| 18263 delete params[this.name]; |
| 18264 if (!this.value || Array.isArray(this.value) && this.value.length == 0)
{ |
| 18265 } else |
| 18266 // Don't insert undefined/empty values. |
| 18267 { |
| 18268 if (this.multi) { |
| 18269 params[this.name] = this.value; |
| 18270 } else { |
| 18271 params[this.name] = [this.value]; |
| 18272 } |
| 18273 } |
| 18274 var newUrl = window.location.href.split('?')[0] + '?' + sk.query.fromPar
amSet(params); |
| 18275 window.history.replaceState('', '', newUrl); |
| 18276 }, |
| 18277 // Check to see whether the given value is valid. |
| 18278 isValid: function (val) { |
| 18279 var checkValid = function (val) { |
| 18280 if (this.valid) { |
| 18281 for (var i = 0; i < this.valid.length; i++) { |
| 18282 if (val == this.valid[i]) { |
| 18283 return true; |
| 18284 } |
| 18285 } |
| 18286 this.error('Invalid value for ' + this.name + ': "' + val + '". Must
be one of: ' + this.valid); |
| 18287 return false; |
| 18288 } |
| 18289 return true; |
| 18290 }.bind(this); |
| 18291 if (this.multi) { |
| 18292 // Verify that it's an array and that all elements are valid. |
| 18293 if (!Array.isArray(val)) { |
| 18294 this.error('url-param-sk: Value is not an array: ' + val); |
| 18295 return false; |
| 18296 } |
| 18297 for (var i = 0; i < val.length; i++) { |
| 18298 if (!checkValid(val[i])) { |
| 18299 return false; |
| 18300 } |
| 18301 } |
| 18302 } else { |
| 18303 if (Array.isArray(val)) { |
| 18304 this.error('Multiple values provided for ' + this.name + ' but only
one accepted: ' + val); |
| 18305 } |
| 18306 return checkValid(val); |
| 18307 } |
| 18308 return true; |
| 18309 }, |
| 18310 valueChanged: function () { |
| 18311 if (this._loaded) { |
| 18312 // Save our value to the URL. |
| 18313 this.putURL(); |
| 18314 } |
| 18315 }, |
| 18316 error: function (msg) { |
| 18317 console.log('[ERROR] '+msg); |
| 18318 this.set('$.toast.text', msg); |
| 18319 this.$.toast.show(); |
| 18320 } |
| 18321 }); |
| 18322 })() |
| 18323 </script> |
| 18324 </dom-module> |
| 18325 <script> |
| 15642 | 18326 |
| 15643 /** | 18327 /** |
| 15644 * @param {!Function} selectCallback | 18328 * @param {!Function} selectCallback |
| 15645 * @constructor | 18329 * @constructor |
| 15646 */ | 18330 */ |
| 15647 Polymer.IronSelection = function(selectCallback) { | 18331 Polymer.IronSelection = function(selectCallback) { |
| 15648 this.selection = []; | 18332 this.selection = []; |
| 15649 this.selectCallback = selectCallback; | 18333 this.selectCallback = selectCallback; |
| 15650 }; | 18334 }; |
| 15651 | 18335 |
| (...skipping 1050 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 16702 | 19386 |
| 16703 /** @polymerBehavior Polymer.IronCheckedElementBehavior */ | 19387 /** @polymerBehavior Polymer.IronCheckedElementBehavior */ |
| 16704 Polymer.IronCheckedElementBehavior = [ | 19388 Polymer.IronCheckedElementBehavior = [ |
| 16705 Polymer.IronFormElementBehavior, | 19389 Polymer.IronFormElementBehavior, |
| 16706 Polymer.IronValidatableBehavior, | 19390 Polymer.IronValidatableBehavior, |
| 16707 Polymer.IronCheckedElementBehaviorImpl | 19391 Polymer.IronCheckedElementBehaviorImpl |
| 16708 ]; | 19392 ]; |
| 16709 | 19393 |
| 16710 </script> | 19394 </script> |
| 16711 <script> | 19395 <script> |
| 16712 (function() { | |
| 16713 'use strict'; | |
| 16714 | |
| 16715 /** | |
| 16716 * Chrome uses an older version of DOM Level 3 Keyboard Events | |
| 16717 * | |
| 16718 * Most keys are labeled as text, but some are Unicode codepoints. | |
| 16719 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712
21/keyset.html#KeySet-Set | |
| 16720 */ | |
| 16721 var KEY_IDENTIFIER = { | |
| 16722 'U+0008': 'backspace', | |
| 16723 'U+0009': 'tab', | |
| 16724 'U+001B': 'esc', | |
| 16725 'U+0020': 'space', | |
| 16726 'U+007F': 'del' | |
| 16727 }; | |
| 16728 | |
| 16729 /** | |
| 16730 * Special table for KeyboardEvent.keyCode. | |
| 16731 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett
er | |
| 16732 * than that. | |
| 16733 * | |
| 16734 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve
nt.keyCode#Value_of_keyCode | |
| 16735 */ | |
| 16736 var KEY_CODE = { | |
| 16737 8: 'backspace', | |
| 16738 9: 'tab', | |
| 16739 13: 'enter', | |
| 16740 27: 'esc', | |
| 16741 33: 'pageup', | |
| 16742 34: 'pagedown', | |
| 16743 35: 'end', | |
| 16744 36: 'home', | |
| 16745 32: 'space', | |
| 16746 37: 'left', | |
| 16747 38: 'up', | |
| 16748 39: 'right', | |
| 16749 40: 'down', | |
| 16750 46: 'del', | |
| 16751 106: '*' | |
| 16752 }; | |
| 16753 | |
| 16754 /** | |
| 16755 * MODIFIER_KEYS maps the short name for modifier keys used in a key | |
| 16756 * combo string to the property name that references those same keys | |
| 16757 * in a KeyboardEvent instance. | |
| 16758 */ | |
| 16759 var MODIFIER_KEYS = { | |
| 16760 'shift': 'shiftKey', | |
| 16761 'ctrl': 'ctrlKey', | |
| 16762 'alt': 'altKey', | |
| 16763 'meta': 'metaKey' | |
| 16764 }; | |
| 16765 | |
| 16766 /** | |
| 16767 * KeyboardEvent.key is mostly represented by printable character made by | |
| 16768 * the keyboard, with unprintable keys labeled nicely. | |
| 16769 * | |
| 16770 * However, on OS X, Alt+char can make a Unicode character that follows an | |
| 16771 * Apple-specific mapping. In this case, we fall back to .keyCode. | |
| 16772 */ | |
| 16773 var KEY_CHAR = /[a-z0-9*]/; | |
| 16774 | |
| 16775 /** | |
| 16776 * Matches a keyIdentifier string. | |
| 16777 */ | |
| 16778 var IDENT_CHAR = /U\+/; | |
| 16779 | |
| 16780 /** | |
| 16781 * Matches arrow keys in Gecko 27.0+ | |
| 16782 */ | |
| 16783 var ARROW_KEY = /^arrow/; | |
| 16784 | |
| 16785 /** | |
| 16786 * Matches space keys everywhere (notably including IE10's exceptional name | |
| 16787 * `spacebar`). | |
| 16788 */ | |
| 16789 var SPACE_KEY = /^space(bar)?/; | |
| 16790 | |
| 16791 /** | |
| 16792 * Matches ESC key. | |
| 16793 * | |
| 16794 * Value from: http://w3c.github.io/uievents-key/#key-Escape | |
| 16795 */ | |
| 16796 var ESC_KEY = /^escape$/; | |
| 16797 | |
| 16798 /** | |
| 16799 * Transforms the key. | |
| 16800 * @param {string} key The KeyBoardEvent.key | |
| 16801 * @param {Boolean} [noSpecialChars] Limits the transformation to | |
| 16802 * alpha-numeric characters. | |
| 16803 */ | |
| 16804 function transformKey(key, noSpecialChars) { | |
| 16805 var validKey = ''; | |
| 16806 if (key) { | |
| 16807 var lKey = key.toLowerCase(); | |
| 16808 if (lKey === ' ' || SPACE_KEY.test(lKey)) { | |
| 16809 validKey = 'space'; | |
| 16810 } else if (ESC_KEY.test(lKey)) { | |
| 16811 validKey = 'esc'; | |
| 16812 } else if (lKey.length == 1) { | |
| 16813 if (!noSpecialChars || KEY_CHAR.test(lKey)) { | |
| 16814 validKey = lKey; | |
| 16815 } | |
| 16816 } else if (ARROW_KEY.test(lKey)) { | |
| 16817 validKey = lKey.replace('arrow', ''); | |
| 16818 } else if (lKey == 'multiply') { | |
| 16819 // numpad '*' can map to Multiply on IE/Windows | |
| 16820 validKey = '*'; | |
| 16821 } else { | |
| 16822 validKey = lKey; | |
| 16823 } | |
| 16824 } | |
| 16825 return validKey; | |
| 16826 } | |
| 16827 | |
| 16828 function transformKeyIdentifier(keyIdent) { | |
| 16829 var validKey = ''; | |
| 16830 if (keyIdent) { | |
| 16831 if (keyIdent in KEY_IDENTIFIER) { | |
| 16832 validKey = KEY_IDENTIFIER[keyIdent]; | |
| 16833 } else if (IDENT_CHAR.test(keyIdent)) { | |
| 16834 keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16); | |
| 16835 validKey = String.fromCharCode(keyIdent).toLowerCase(); | |
| 16836 } else { | |
| 16837 validKey = keyIdent.toLowerCase(); | |
| 16838 } | |
| 16839 } | |
| 16840 return validKey; | |
| 16841 } | |
| 16842 | |
| 16843 function transformKeyCode(keyCode) { | |
| 16844 var validKey = ''; | |
| 16845 if (Number(keyCode)) { | |
| 16846 if (keyCode >= 65 && keyCode <= 90) { | |
| 16847 // ascii a-z | |
| 16848 // lowercase is 32 offset from uppercase | |
| 16849 validKey = String.fromCharCode(32 + keyCode); | |
| 16850 } else if (keyCode >= 112 && keyCode <= 123) { | |
| 16851 // function keys f1-f12 | |
| 16852 validKey = 'f' + (keyCode - 112); | |
| 16853 } else if (keyCode >= 48 && keyCode <= 57) { | |
| 16854 // top 0-9 keys | |
| 16855 validKey = String(keyCode - 48); | |
| 16856 } else if (keyCode >= 96 && keyCode <= 105) { | |
| 16857 // num pad 0-9 | |
| 16858 validKey = String(keyCode - 96); | |
| 16859 } else { | |
| 16860 validKey = KEY_CODE[keyCode]; | |
| 16861 } | |
| 16862 } | |
| 16863 return validKey; | |
| 16864 } | |
| 16865 | |
| 16866 /** | |
| 16867 * Calculates the normalized key for a KeyboardEvent. | |
| 16868 * @param {KeyboardEvent} keyEvent | |
| 16869 * @param {Boolean} [noSpecialChars] Set to true to limit keyEvent.key | |
| 16870 * transformation to alpha-numeric chars. This is useful with key | |
| 16871 * combinations like shift + 2, which on FF for MacOS produces | |
| 16872 * keyEvent.key = @ | |
| 16873 * To get 2 returned, set noSpecialChars = true | |
| 16874 * To get @ returned, set noSpecialChars = false | |
| 16875 */ | |
| 16876 function normalizedKeyForEvent(keyEvent, noSpecialChars) { | |
| 16877 // Fall back from .key, to .keyIdentifier, to .keyCode, and then to | |
| 16878 // .detail.key to support artificial keyboard events. | |
| 16879 return transformKey(keyEvent.key, noSpecialChars) || | |
| 16880 transformKeyIdentifier(keyEvent.keyIdentifier) || | |
| 16881 transformKeyCode(keyEvent.keyCode) || | |
| 16882 transformKey(keyEvent.detail ? keyEvent.detail.key : keyEvent.detail, no
SpecialChars) || ''; | |
| 16883 } | |
| 16884 | |
| 16885 function keyComboMatchesEvent(keyCombo, event) { | |
| 16886 // For combos with modifiers we support only alpha-numeric keys | |
| 16887 var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers); | |
| 16888 return keyEvent === keyCombo.key && | |
| 16889 (!keyCombo.hasModifiers || ( | |
| 16890 !!event.shiftKey === !!keyCombo.shiftKey && | |
| 16891 !!event.ctrlKey === !!keyCombo.ctrlKey && | |
| 16892 !!event.altKey === !!keyCombo.altKey && | |
| 16893 !!event.metaKey === !!keyCombo.metaKey) | |
| 16894 ); | |
| 16895 } | |
| 16896 | |
| 16897 function parseKeyComboString(keyComboString) { | |
| 16898 if (keyComboString.length === 1) { | |
| 16899 return { | |
| 16900 combo: keyComboString, | |
| 16901 key: keyComboString, | |
| 16902 event: 'keydown' | |
| 16903 }; | |
| 16904 } | |
| 16905 return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboP
art) { | |
| 16906 var eventParts = keyComboPart.split(':'); | |
| 16907 var keyName = eventParts[0]; | |
| 16908 var event = eventParts[1]; | |
| 16909 | |
| 16910 if (keyName in MODIFIER_KEYS) { | |
| 16911 parsedKeyCombo[MODIFIER_KEYS[keyName]] = true; | |
| 16912 parsedKeyCombo.hasModifiers = true; | |
| 16913 } else { | |
| 16914 parsedKeyCombo.key = keyName; | |
| 16915 parsedKeyCombo.event = event || 'keydown'; | |
| 16916 } | |
| 16917 | |
| 16918 return parsedKeyCombo; | |
| 16919 }, { | |
| 16920 combo: keyComboString.split(':').shift() | |
| 16921 }); | |
| 16922 } | |
| 16923 | |
| 16924 function parseEventString(eventString) { | |
| 16925 return eventString.trim().split(' ').map(function(keyComboString) { | |
| 16926 return parseKeyComboString(keyComboString); | |
| 16927 }); | |
| 16928 } | |
| 16929 | |
| 16930 /** | |
| 16931 * `Polymer.IronA11yKeysBehavior` provides a normalized interface for proces
sing | |
| 16932 * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3
.org/TR/wai-aria-practices/#kbd_general_binding). | |
| 16933 * The element takes care of browser differences with respect to Keyboard ev
ents | |
| 16934 * and uses an expressive syntax to filter key presses. | |
| 16935 * | |
| 16936 * Use the `keyBindings` prototype property to express what combination of k
eys | |
| 16937 * will trigger the callback. A key binding has the format | |
| 16938 * `"KEY+MODIFIER:EVENT": "callback"` (`"KEY": "callback"` or | |
| 16939 * `"KEY:EVENT": "callback"` are valid as well). Some examples: | |
| 16940 * | |
| 16941 * keyBindings: { | |
| 16942 * 'space': '_onKeydown', // same as 'space:keydown' | |
| 16943 * 'shift+tab': '_onKeydown', | |
| 16944 * 'enter:keypress': '_onKeypress', | |
| 16945 * 'esc:keyup': '_onKeyup' | |
| 16946 * } | |
| 16947 * | |
| 16948 * The callback will receive with an event containing the following informat
ion in `event.detail`: | |
| 16949 * | |
| 16950 * _onKeydown: function(event) { | |
| 16951 * console.log(event.detail.combo); // KEY+MODIFIER, e.g. "shift+tab" | |
| 16952 * console.log(event.detail.key); // KEY only, e.g. "tab" | |
| 16953 * console.log(event.detail.event); // EVENT, e.g. "keydown" | |
| 16954 * console.log(event.detail.keyboardEvent); // the original KeyboardE
vent | |
| 16955 * } | |
| 16956 * | |
| 16957 * Use the `keyEventTarget` attribute to set up event handlers on a specific | |
| 16958 * node. | |
| 16959 * | |
| 16960 * See the [demo source code](https://github.com/PolymerElements/iron-a11y-k
eys-behavior/blob/master/demo/x-key-aware.html) | |
| 16961 * for an example. | |
| 16962 * | |
| 16963 * @demo demo/index.html | |
| 16964 * @polymerBehavior | |
| 16965 */ | |
| 16966 Polymer.IronA11yKeysBehavior = { | |
| 16967 properties: { | |
| 16968 /** | |
| 16969 * The EventTarget that will be firing relevant KeyboardEvents. Set it t
o | |
| 16970 * `null` to disable the listeners. | |
| 16971 * @type {?EventTarget} | |
| 16972 */ | |
| 16973 keyEventTarget: { | |
| 16974 type: Object, | |
| 16975 value: function() { | |
| 16976 return this; | |
| 16977 } | |
| 16978 }, | |
| 16979 | |
| 16980 /** | |
| 16981 * If true, this property will cause the implementing element to | |
| 16982 * automatically stop propagation on any handled KeyboardEvents. | |
| 16983 */ | |
| 16984 stopKeyboardEventPropagation: { | |
| 16985 type: Boolean, | |
| 16986 value: false | |
| 16987 }, | |
| 16988 | |
| 16989 _boundKeyHandlers: { | |
| 16990 type: Array, | |
| 16991 value: function() { | |
| 16992 return []; | |
| 16993 } | |
| 16994 }, | |
| 16995 | |
| 16996 // We use this due to a limitation in IE10 where instances will have | |
| 16997 // own properties of everything on the "prototype". | |
| 16998 _imperativeKeyBindings: { | |
| 16999 type: Object, | |
| 17000 value: function() { | |
| 17001 return {}; | |
| 17002 } | |
| 17003 } | |
| 17004 }, | |
| 17005 | |
| 17006 observers: [ | |
| 17007 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)' | |
| 17008 ], | |
| 17009 | |
| 17010 | |
| 17011 /** | |
| 17012 * To be used to express what combination of keys will trigger the relati
ve | |
| 17013 * callback. e.g. `keyBindings: { 'esc': '_onEscPressed'}` | |
| 17014 * @type {Object} | |
| 17015 */ | |
| 17016 keyBindings: {}, | |
| 17017 | |
| 17018 registered: function() { | |
| 17019 this._prepKeyBindings(); | |
| 17020 }, | |
| 17021 | |
| 17022 attached: function() { | |
| 17023 this._listenKeyEventListeners(); | |
| 17024 }, | |
| 17025 | |
| 17026 detached: function() { | |
| 17027 this._unlistenKeyEventListeners(); | |
| 17028 }, | |
| 17029 | |
| 17030 /** | |
| 17031 * Can be used to imperatively add a key binding to the implementing | |
| 17032 * element. This is the imperative equivalent of declaring a keybinding | |
| 17033 * in the `keyBindings` prototype property. | |
| 17034 */ | |
| 17035 addOwnKeyBinding: function(eventString, handlerName) { | |
| 17036 this._imperativeKeyBindings[eventString] = handlerName; | |
| 17037 this._prepKeyBindings(); | |
| 17038 this._resetKeyEventListeners(); | |
| 17039 }, | |
| 17040 | |
| 17041 /** | |
| 17042 * When called, will remove all imperatively-added key bindings. | |
| 17043 */ | |
| 17044 removeOwnKeyBindings: function() { | |
| 17045 this._imperativeKeyBindings = {}; | |
| 17046 this._prepKeyBindings(); | |
| 17047 this._resetKeyEventListeners(); | |
| 17048 }, | |
| 17049 | |
| 17050 /** | |
| 17051 * Returns true if a keyboard event matches `eventString`. | |
| 17052 * | |
| 17053 * @param {KeyboardEvent} event | |
| 17054 * @param {string} eventString | |
| 17055 * @return {boolean} | |
| 17056 */ | |
| 17057 keyboardEventMatchesKeys: function(event, eventString) { | |
| 17058 var keyCombos = parseEventString(eventString); | |
| 17059 for (var i = 0; i < keyCombos.length; ++i) { | |
| 17060 if (keyComboMatchesEvent(keyCombos[i], event)) { | |
| 17061 return true; | |
| 17062 } | |
| 17063 } | |
| 17064 return false; | |
| 17065 }, | |
| 17066 | |
| 17067 _collectKeyBindings: function() { | |
| 17068 var keyBindings = this.behaviors.map(function(behavior) { | |
| 17069 return behavior.keyBindings; | |
| 17070 }); | |
| 17071 | |
| 17072 if (keyBindings.indexOf(this.keyBindings) === -1) { | |
| 17073 keyBindings.push(this.keyBindings); | |
| 17074 } | |
| 17075 | |
| 17076 return keyBindings; | |
| 17077 }, | |
| 17078 | |
| 17079 _prepKeyBindings: function() { | |
| 17080 this._keyBindings = {}; | |
| 17081 | |
| 17082 this._collectKeyBindings().forEach(function(keyBindings) { | |
| 17083 for (var eventString in keyBindings) { | |
| 17084 this._addKeyBinding(eventString, keyBindings[eventString]); | |
| 17085 } | |
| 17086 }, this); | |
| 17087 | |
| 17088 for (var eventString in this._imperativeKeyBindings) { | |
| 17089 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri
ng]); | |
| 17090 } | |
| 17091 | |
| 17092 // Give precedence to combos with modifiers to be checked first. | |
| 17093 for (var eventName in this._keyBindings) { | |
| 17094 this._keyBindings[eventName].sort(function (kb1, kb2) { | |
| 17095 var b1 = kb1[0].hasModifiers; | |
| 17096 var b2 = kb2[0].hasModifiers; | |
| 17097 return (b1 === b2) ? 0 : b1 ? -1 : 1; | |
| 17098 }) | |
| 17099 } | |
| 17100 }, | |
| 17101 | |
| 17102 _addKeyBinding: function(eventString, handlerName) { | |
| 17103 parseEventString(eventString).forEach(function(keyCombo) { | |
| 17104 this._keyBindings[keyCombo.event] = | |
| 17105 this._keyBindings[keyCombo.event] || []; | |
| 17106 | |
| 17107 this._keyBindings[keyCombo.event].push([ | |
| 17108 keyCombo, | |
| 17109 handlerName | |
| 17110 ]); | |
| 17111 }, this); | |
| 17112 }, | |
| 17113 | |
| 17114 _resetKeyEventListeners: function() { | |
| 17115 this._unlistenKeyEventListeners(); | |
| 17116 | |
| 17117 if (this.isAttached) { | |
| 17118 this._listenKeyEventListeners(); | |
| 17119 } | |
| 17120 }, | |
| 17121 | |
| 17122 _listenKeyEventListeners: function() { | |
| 17123 if (!this.keyEventTarget) { | |
| 17124 return; | |
| 17125 } | |
| 17126 Object.keys(this._keyBindings).forEach(function(eventName) { | |
| 17127 var keyBindings = this._keyBindings[eventName]; | |
| 17128 var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings); | |
| 17129 | |
| 17130 this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyH
andler]); | |
| 17131 | |
| 17132 this.keyEventTarget.addEventListener(eventName, boundKeyHandler); | |
| 17133 }, this); | |
| 17134 }, | |
| 17135 | |
| 17136 _unlistenKeyEventListeners: function() { | |
| 17137 var keyHandlerTuple; | |
| 17138 var keyEventTarget; | |
| 17139 var eventName; | |
| 17140 var boundKeyHandler; | |
| 17141 | |
| 17142 while (this._boundKeyHandlers.length) { | |
| 17143 // My kingdom for block-scope binding and destructuring assignment.. | |
| 17144 keyHandlerTuple = this._boundKeyHandlers.pop(); | |
| 17145 keyEventTarget = keyHandlerTuple[0]; | |
| 17146 eventName = keyHandlerTuple[1]; | |
| 17147 boundKeyHandler = keyHandlerTuple[2]; | |
| 17148 | |
| 17149 keyEventTarget.removeEventListener(eventName, boundKeyHandler); | |
| 17150 } | |
| 17151 }, | |
| 17152 | |
| 17153 _onKeyBindingEvent: function(keyBindings, event) { | |
| 17154 if (this.stopKeyboardEventPropagation) { | |
| 17155 event.stopPropagation(); | |
| 17156 } | |
| 17157 | |
| 17158 // if event has been already prevented, don't do anything | |
| 17159 if (event.defaultPrevented) { | |
| 17160 return; | |
| 17161 } | |
| 17162 | |
| 17163 for (var i = 0; i < keyBindings.length; i++) { | |
| 17164 var keyCombo = keyBindings[i][0]; | |
| 17165 var handlerName = keyBindings[i][1]; | |
| 17166 if (keyComboMatchesEvent(keyCombo, event)) { | |
| 17167 this._triggerKeyHandler(keyCombo, handlerName, event); | |
| 17168 // exit the loop if eventDefault was prevented | |
| 17169 if (event.defaultPrevented) { | |
| 17170 return; | |
| 17171 } | |
| 17172 } | |
| 17173 } | |
| 17174 }, | |
| 17175 | |
| 17176 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) { | |
| 17177 var detail = Object.create(keyCombo); | |
| 17178 detail.keyboardEvent = keyboardEvent; | |
| 17179 var event = new CustomEvent(keyCombo.event, { | |
| 17180 detail: detail, | |
| 17181 cancelable: true | |
| 17182 }); | |
| 17183 this[handlerName].call(this, event); | |
| 17184 if (event.defaultPrevented) { | |
| 17185 keyboardEvent.preventDefault(); | |
| 17186 } | |
| 17187 } | |
| 17188 }; | |
| 17189 })(); | |
| 17190 </script> | |
| 17191 <script> | |
| 17192 | 19396 |
| 17193 /** | 19397 /** |
| 17194 * @demo demo/index.html | 19398 * @demo demo/index.html |
| 17195 * @polymerBehavior | 19399 * @polymerBehavior |
| 17196 */ | 19400 */ |
| 17197 Polymer.IronControlState = { | 19401 Polymer.IronControlState = { |
| 17198 | 19402 |
| 17199 properties: { | 19403 properties: { |
| 17200 | 19404 |
| 17201 /** | 19405 /** |
| (...skipping 1511 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 18713 var label = this.getAttribute('aria-label'); | 20917 var label = this.getAttribute('aria-label'); |
| 18714 | 20918 |
| 18715 // Don't stomp over a user-set aria-label. | 20919 // Don't stomp over a user-set aria-label. |
| 18716 if (!label || oldValue == label) { | 20920 if (!label || oldValue == label) { |
| 18717 this.setAttribute('aria-label', newValue); | 20921 this.setAttribute('aria-label', newValue); |
| 18718 } | 20922 } |
| 18719 } | 20923 } |
| 18720 }); | 20924 }); |
| 18721 </script> | 20925 </script> |
| 18722 </dom-module> | 20926 </dom-module> |
| 18723 | |
| 18724 | |
| 18725 <dom-module id="iron-a11y-announcer" assetpath="/res/imp/bower_components/iron-a
11y-announcer/"> | |
| 18726 <template> | |
| 18727 <style> | |
| 18728 :host { | |
| 18729 display: inline-block; | |
| 18730 position: fixed; | |
| 18731 clip: rect(0px,0px,0px,0px); | |
| 18732 } | |
| 18733 </style> | |
| 18734 <div aria-live$="[[mode]]">[[_text]]</div> | |
| 18735 </template> | |
| 18736 | |
| 18737 <script> | |
| 18738 | |
| 18739 (function() { | |
| 18740 'use strict'; | |
| 18741 | |
| 18742 Polymer.IronA11yAnnouncer = Polymer({ | |
| 18743 is: 'iron-a11y-announcer', | |
| 18744 | |
| 18745 properties: { | |
| 18746 | |
| 18747 /** | |
| 18748 * The value of mode is used to set the `aria-live` attribute | |
| 18749 * for the element that will be announced. Valid values are: `off`, | |
| 18750 * `polite` and `assertive`. | |
| 18751 */ | |
| 18752 mode: { | |
| 18753 type: String, | |
| 18754 value: 'polite' | |
| 18755 }, | |
| 18756 | |
| 18757 _text: { | |
| 18758 type: String, | |
| 18759 value: '' | |
| 18760 } | |
| 18761 }, | |
| 18762 | |
| 18763 created: function() { | |
| 18764 if (!Polymer.IronA11yAnnouncer.instance) { | |
| 18765 Polymer.IronA11yAnnouncer.instance = this; | |
| 18766 } | |
| 18767 | |
| 18768 document.body.addEventListener('iron-announce', this._onIronAnnounce.b
ind(this)); | |
| 18769 }, | |
| 18770 | |
| 18771 /** | |
| 18772 * Cause a text string to be announced by screen readers. | |
| 18773 * | |
| 18774 * @param {string} text The text that should be announced. | |
| 18775 */ | |
| 18776 announce: function(text) { | |
| 18777 this._text = ''; | |
| 18778 this.async(function() { | |
| 18779 this._text = text; | |
| 18780 }, 100); | |
| 18781 }, | |
| 18782 | |
| 18783 _onIronAnnounce: function(event) { | |
| 18784 if (event.detail && event.detail.text) { | |
| 18785 this.announce(event.detail.text); | |
| 18786 } | |
| 18787 } | |
| 18788 }); | |
| 18789 | |
| 18790 Polymer.IronA11yAnnouncer.instance = null; | |
| 18791 | |
| 18792 Polymer.IronA11yAnnouncer.requestAvailability = function() { | |
| 18793 if (!Polymer.IronA11yAnnouncer.instance) { | |
| 18794 Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y
-announcer'); | |
| 18795 } | |
| 18796 | |
| 18797 document.body.appendChild(Polymer.IronA11yAnnouncer.instance); | |
| 18798 }; | |
| 18799 })(); | |
| 18800 | |
| 18801 </script> | |
| 18802 </dom-module> | |
| 18803 <script> | 20927 <script> |
| 18804 | 20928 |
| 18805 /* | 20929 /* |
| 18806 `<iron-input>` adds two-way binding and custom validators using `Polymer.IronVal
idatorBehavior` | 20930 `<iron-input>` adds two-way binding and custom validators using `Polymer.IronVal
idatorBehavior` |
| 18807 to `<input>`. | 20931 to `<input>`. |
| 18808 | 20932 |
| 18809 ### Two-way binding | 20933 ### Two-way binding |
| 18810 | 20934 |
| 18811 By default you can only get notified of changes to an `input`'s `value` due to u
ser input: | 20935 By default you can only get notified of changes to an `input`'s `value` due to u
ser input: |
| 18812 | 20936 |
| (...skipping 1998 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 20811 margin: 2px; | 22935 margin: 2px; |
| 20812 /* See https://sites.google.com/a/google.com/skia-infrastructure/design-
docs/general-design-guidance */ | 22936 /* See https://sites.google.com/a/google.com/skia-infrastructure/design-
docs/general-design-guidance */ |
| 20813 --paper-checkbox-checked-color: black; | 22937 --paper-checkbox-checked-color: black; |
| 20814 --paper-checkbox-checked-ink-color: black; | 22938 --paper-checkbox-checked-ink-color: black; |
| 20815 --paper-checkbox-unchecked-color: black; | 22939 --paper-checkbox-unchecked-color: black; |
| 20816 --paper-checkbox-unchecked-ink-color: black; | 22940 --paper-checkbox-unchecked-ink-color: black; |
| 20817 --paper-checkbox-label-color: black; | 22941 --paper-checkbox-label-color: black; |
| 20818 } | 22942 } |
| 20819 </style> | 22943 </style> |
| 20820 | 22944 |
| 22945 <url-param name="filters" value="{{_filters}}" default_values="[]" multi=""> |
| 22946 </url-param> |
| 22947 <url-param name="columns" value="{{columns}}" default_values="["id"
;,"os","task","status"]" multi=""> |
| 22948 </url-param> |
| 22949 <url-param name="query" value="{{_query}}"> |
| 22950 </url-param> |
| 22951 <url-param name="verbose" value="{{verbose}}"> |
| 22952 </url-param> |
| 22953 <url-param name="limit" default_value="200" value="{{limit}}"> |
| 22954 </url-param> |
| 22955 |
| 20821 <div class="container horizontal layout"> | 22956 <div class="container horizontal layout"> |
| 20822 | 22957 |
| 20823 | 22958 |
| 20824 <div class="narrow-down-selector"> | 22959 <div class="narrow-down-selector"> |
| 20825 <div> | 22960 <div> |
| 20826 <paper-input id="filter" label="Search columns and filters" placeholde
r="gpu nvidia" value="{{_query}}"></paper-input> | 22961 <paper-input id="filter" label="Search columns and filters" placeholde
r="gpu nvidia" value="{{_query}}"> |
| 22962 </paper-input> |
| 20827 </div> | 22963 </div> |
| 20828 | 22964 |
| 20829 <div class="selector side-by-side"> | 22965 <div class="selector side-by-side" title="This shows all bot dimension n
ames and other interesting bot properties. Mark the check box to add as a column
. Select the row to see filter options."> |
| 20830 <iron-selector attr-for-selected="label" selected="{{_primarySelected}
}"> | 22966 <iron-selector attr-for-selected="label" selected="{{_primarySelected}
}"> |
| 20831 <template is="dom-repeat" items="[[_primaryItems]]" as="item"> | 22967 <template is="dom-repeat" items="[[_primaryItems]]" as="item"> |
| 20832 <div class="selectable item horizontal layout" label="[[item]]"> | 22968 <div class="selectable item horizontal layout" label="[[item]]"> |
| 20833 | 22969 |
| 20834 <span>[[_beforeBold(item,_query)]]<span class="bold">[[_bold(ite
m,_query)]]</span>[[_afterBold(item,_query)]]</span> | 22970 <span>[[_beforeBold(item,_query)]]<span class="bold">[[_bold(ite
m,_query)]]</span>[[_afterBold(item,_query)]]</span> |
| 20835 <span class="flex"></span> | 22971 <span class="flex"></span> |
| 20836 <paper-checkbox noink="" disabled$="[[_cantToggleColumn(item)]]"
checked="[[_columnState(item,columns.*)]]" on-change="_toggleColumn"> | 22972 <paper-checkbox noink="" disabled$="[[_cantToggleColumn(item)]]"
checked="[[_columnState(item,columns.*)]]" on-change="_toggleColumn"> |
| 20837 </paper-checkbox> | 22973 </paper-checkbox> |
| 20838 </div> | 22974 </div> |
| 20839 </template> | 22975 </template> |
| 20840 </iron-selector> | 22976 </iron-selector> |
| 20841 </div> | 22977 </div> |
| 20842 | 22978 |
| 20843 <div class="selector side-by-side"> | 22979 <div class="selector side-by-side" title="These are all options (if any)
that the bot list can be filtered on."> |
| 20844 <template is="dom-repeat" id="secondaryList" items="[[_secondaryItems]
]" as="item"> | 22980 <template is="dom-repeat" id="secondaryList" items="[[_secondaryItems]
]" as="item"> |
| 20845 <div class="item horizontal layout" label="[[item]]"> | 22981 <div class="item horizontal layout" label="[[item]]"> |
| 20846 | 22982 |
| 20847 <span>[[_beforeBold(item,_query)]]<span class="bold">[[_bold(item,
_query)]]</span>[[_afterBold(item,_query)]]</span> | 22983 <span>[[_beforeBold(item,_query)]]<span class="bold">[[_bold(item,
_query)]]</span>[[_afterBold(item,_query)]]</span> |
| 20848 <span class="flex"></span> | 22984 <span class="flex"></span> |
| 20849 <iron-icon class="icons" icon="icons:arrow-forward" hidden="[[_can
tAddFilter(_primarySelected,item,_filters.*)]]" on-tap="_addFilter"> | 22985 <iron-icon class="icons" icon="icons:arrow-forward" hidden="[[_can
tAddFilter(_primarySelected,item,_filters.*)]]" on-tap="_addFilter"> |
| 20850 </iron-icon> | 22986 </iron-icon> |
| 20851 </div> | 22987 </div> |
| 20852 </template> | 22988 </template> |
| 20853 </div> | 22989 </div> |
| 20854 | 22990 |
| 20855 <div class="selector side-by-side"> | 22991 <div class="selector side-by-side" title="These filters are AND'd togeth
er and applied to all bots in |
| 22992 the fleet."> |
| 20856 <template is="dom-repeat" items="[[_filters]]" as="fil"> | 22993 <template is="dom-repeat" items="[[_filters]]" as="fil"> |
| 20857 <div class="item horizontal layout" label="[[fil]]"> | 22994 <div class="item horizontal layout" label="[[fil]]"> |
| 20858 <span>[[fil]]</span> | 22995 <span>[[fil]]</span> |
| 20859 <span class="flex"></span> | 22996 <span class="flex"></span> |
| 20860 <iron-icon class="icons" icon="icons:remove-circle-outline" hidden
="[[_cantRemoveFilter(fil,_filters.*)]]" on-tap="_removeFilter"> | 22997 <iron-icon class="icons" icon="icons:remove-circle-outline" hidden
="[[_cantRemoveFilter(fil,_filters.*)]]" on-tap="_removeFilter"> |
| 20861 </iron-icon> | 22998 </iron-icon> |
| 20862 </div> | 22999 </div> |
| 20863 </template> | 23000 </template> |
| 20864 </div> | 23001 </div> |
| 20865 | 23002 |
| 23003 <div class="side-by-side"> |
| 20866 <paper-checkbox checked="{{verbose}}">Verbose Entries</paper-checkbox> | 23004 <paper-checkbox checked="{{verbose}}">Verbose Entries</paper-checkbox> |
| 23005 <paper-input id="limit" label="Limit Results" auto-validate="" min="0"
max="1000" pattern="[0-9]+" value="{{limit}}"> |
| 23006 </paper-input> |
| 23007 </div> |
| 20867 </div> | 23008 </div> |
| 20868 | 23009 |
| 20869 </div> | 23010 </div> |
| 20870 | 23011 |
| 20871 </template> | 23012 </template> |
| 20872 <script> | 23013 <script> |
| 20873 (function(){ | 23014 (function(){ |
| 20874 var FILTER_SEP = ":"; | 23015 var FILTER_SEP = ":"; |
| 20875 // filterMap is a map of primary -> function. The function returns a | 23016 // filterMap is a map of primary -> function. The function returns a |
| 20876 // boolean "does the bot (first arg) match the second argument". These | 23017 // boolean "does the bot (first arg) match the second argument". These |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 20914 pool: function(bot, pool) { | 23055 pool: function(bot, pool) { |
| 20915 var o = this._attribute(bot, "pool"); | 23056 var o = this._attribute(bot, "pool"); |
| 20916 return o.indexOf(pool) !== -1; | 23057 return o.indexOf(pool) !== -1; |
| 20917 }, | 23058 }, |
| 20918 status: function(bot, status) { | 23059 status: function(bot, status) { |
| 20919 if (status === "quarantined") { | 23060 if (status === "quarantined") { |
| 20920 return bot.quarantined; | 23061 return bot.quarantined; |
| 20921 } else if (status === "dead") { | 23062 } else if (status === "dead") { |
| 20922 return bot.is_dead; | 23063 return bot.is_dead; |
| 20923 } else { | 23064 } else { |
| 20924 // Status must be "available". | 23065 // Status must be "alive". |
| 20925 return !bot.quarantined && !bot.is_dead; | 23066 return !bot.quarantined && !bot.is_dead; |
| 20926 } | 23067 } |
| 20927 }, | 23068 }, |
| 20928 task: function(bot, task) { | 23069 task: function(bot, task) { |
| 20929 if (task === "idle") { | 23070 if (task === "idle") { |
| 20930 return this._taskId(bot) === "idle"; | 23071 return this._taskId(bot) === "idle"; |
| 20931 } | 23072 } |
| 20932 // Task must be "busy". | 23073 // Task must be "busy". |
| 20933 return this._taskId(bot) !== "idle"; | 23074 return this._taskId(bot) !== "idle"; |
| 20934 } | 23075 } |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 20976 primary_map: { | 23117 primary_map: { |
| 20977 type: Object, | 23118 type: Object, |
| 20978 }, | 23119 }, |
| 20979 primary_arr: { | 23120 primary_arr: { |
| 20980 type: Array, | 23121 type: Array, |
| 20981 }, | 23122 }, |
| 20982 | 23123 |
| 20983 // output | 23124 // output |
| 20984 columns: { | 23125 columns: { |
| 20985 type: Array, | 23126 type: Array, |
| 20986 value: function() { | |
| 20987 // TODO(kjlubick) back these up to URL params and load them from | |
| 20988 // there on reload. | |
| 20989 return ["id","os","task","status"]; | |
| 20990 }, | |
| 20991 notify: true, | |
| 20992 }, | |
| 20993 dimensions: { | |
| 20994 type: Array, | |
| 20995 computed: "_extractDimensions(DIMENSIONS.*,_filters.*)", | |
| 20996 notify: true, | 23127 notify: true, |
| 20997 }, | 23128 }, |
| 20998 filter: { | 23129 filter: { |
| 23130 type: Function, |
| 23131 computed: "_makeFilter(_filters.*)", |
| 23132 notify: true, |
| 23133 }, |
| 23134 query_params: { |
| 20999 type: Object, | 23135 type: Object, |
| 21000 computed: "_makeFilter(_filters.*)", | 23136 computed: "_extractQueryParams(DIMENSIONS.*,_filters.*, limit)", |
| 21001 notify: true, | 23137 notify: true, |
| 21002 }, | 23138 }, |
| 21003 verbose: { | 23139 verbose: { |
| 21004 type: Boolean, | 23140 type: Boolean, |
| 21005 value: false, | |
| 21006 notify: true, | 23141 notify: true, |
| 21007 }, | 23142 }, |
| 21008 | 23143 |
| 21009 // private | 23144 // private |
| 21010 _filters: { | 23145 _filters: { |
| 21011 type:Array, | 23146 type:Array, |
| 21012 value: function() { | 23147 }, |
| 21013 return []; | 23148 _limit: { |
| 21014 } | 23149 type: Number, |
| 21015 }, | 23150 }, |
| 21016 _primaryItems: { | 23151 _primaryItems: { |
| 21017 type: Array, | 23152 type: Array, |
| 21018 computed: "_primary(_query, primary_map, primary_arr, columns.*)", | 23153 computed: "_primary(_query, primary_map, primary_arr, columns.*)", |
| 21019 }, | 23154 }, |
| 21020 _primarySelected: { | 23155 _primarySelected: { |
| 21021 type: String, | 23156 type: String, |
| 21022 value: "", | 23157 value: "", |
| 21023 }, | 23158 }, |
| 21024 // query is treated as a space separated list. | 23159 // query is treated as a space separated list. |
| 21025 _query: { | 23160 _query: { |
| 21026 type:String, | 23161 type:String, |
| 21027 value: "", | |
| 21028 }, | 23162 }, |
| 21029 _secondaryItems: { | 23163 _secondaryItems: { |
| 21030 type: Array, | 23164 type: Array, |
| 21031 computed: "_secondary(_primarySelected, _query, primary_map)", | 23165 computed: "_secondary(_primarySelected, _query, primary_map)", |
| 21032 }, | 23166 }, |
| 21033 | 23167 |
| 21034 }, | 23168 }, |
| 21035 | 23169 |
| 21036 _addFilter: function(e) { | 23170 _addFilter: function(e) { |
| 21037 // e.model.foo is a way to get access to the "foo" inside a dom-repeat | 23171 // e.model.foo is a way to get access to the "foo" inside a dom-repeat |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 21093 }, | 23227 }, |
| 21094 | 23228 |
| 21095 _columnState: function(col) { | 23229 _columnState: function(col) { |
| 21096 if (!col) { | 23230 if (!col) { |
| 21097 return false; | 23231 return false; |
| 21098 } | 23232 } |
| 21099 return this.columns.indexOf(col) !== -1; | 23233 return this.columns.indexOf(col) !== -1; |
| 21100 }, | 23234 }, |
| 21101 | 23235 |
| 21102 _makeFilter: function() { | 23236 _makeFilter: function() { |
| 21103 // The filters belonging to the same primary key will be or'd together. | 23237 // All filters will be AND'd together. |
| 21104 // Those groups will be and'd together. | |
| 21105 // filterGroups will be a map of primary (i.e. column) -> array of | 23238 // filterGroups will be a map of primary (i.e. column) -> array of |
| 21106 // options that should be filtered to. | 23239 // options that should be filtered to. |
| 21107 // e.g. "os" -> ["Windows", "Linux"] | 23240 // e.g. "os" -> ["Windows", "Linux"] |
| 21108 // Since they will be or'd together, order doesn't matter. | 23241 // Since they will be or'd together, order doesn't matter. |
| 21109 var filterGroups = {}; | 23242 var filterGroups = {}; |
| 21110 this._filters.forEach(function(filterString){ | 23243 this._filters.forEach(function(filterString){ |
| 21111 var idx = filterString.indexOf(FILTER_SEP); | 23244 var idx = filterString.indexOf(FILTER_SEP); |
| 21112 var primary = filterString.slice(0, idx); | 23245 var primary = filterString.slice(0, idx); |
| 21113 var param = filterString.slice(idx + FILTER_SEP.length); | 23246 var param = filterString.slice(idx + FILTER_SEP.length); |
| 21114 var arr = filterGroups[primary] || []; | 23247 var arr = filterGroups[primary] || []; |
| 21115 arr.push(param); | 23248 arr.push(param); |
| 21116 filterGroups[primary] = arr; | 23249 filterGroups[primary] = arr; |
| 21117 }); | 23250 }); |
| 21118 return function(bot){ | 23251 return function(bot){ |
| 21119 var retVal = true; | 23252 var retVal = true; |
| 21120 // Look up all the primary keys we are filter by, then look up how | 23253 // Look up all the primary keys we are filter by, then look up how |
| 21121 // to filter (in filterMap) and apply the filter for each filter | 23254 // to filter (in filterMap) and apply the filter for each filter |
| 21122 // option. | 23255 // option. |
| 21123 for (primary in filterGroups){ | 23256 for (primary in filterGroups){ |
| 21124 var params = filterGroups[primary]; | 23257 var params = filterGroups[primary]; |
| 21125 var filter = filterMap[primary]; | 23258 var filter = filterMap[primary]; |
| 21126 var groupResult = false; | |
| 21127 if (filter) { | 23259 if (filter) { |
| 21128 params.forEach(function(param){ | 23260 params.forEach(function(param){ |
| 21129 groupResult = groupResult || filter.bind(this)(bot,param); | 23261 retVal = retVal && filter.bind(this)(bot,param); |
| 21130 }.bind(this)); | 23262 }.bind(this)); |
| 21131 } | 23263 } |
| 21132 retVal = retVal && groupResult; | |
| 21133 } | 23264 } |
| 21134 return retVal; | 23265 return retVal; |
| 21135 } | 23266 } |
| 21136 }, | 23267 }, |
| 21137 | 23268 |
| 21138 _primary: function(query, primary_map, primary_arr) { | 23269 _primary: function(query, primary_map, primary_arr) { |
| 21139 // If the user has typed in a query, only show those primary keys that | 23270 // If the user has typed in a query, only show those primary keys that |
| 21140 // partially match the query or that have secondary values which | 23271 // partially match the query or that have secondary values which |
| 21141 // partially match. | 23272 // partially match. |
| 21142 var arr = this.primary_arr.filter(function(s){ | 23273 var arr = this.primary_arr.filter(function(s){ |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 21213 }, | 23344 }, |
| 21214 | 23345 |
| 21215 _afterBold: function(item, query) { | 23346 _afterBold: function(item, query) { |
| 21216 var match = matchPartCaseInsensitive(item, query); | 23347 var match = matchPartCaseInsensitive(item, query); |
| 21217 if (match.idx === -1) { | 23348 if (match.idx === -1) { |
| 21218 return ""; | 23349 return ""; |
| 21219 } | 23350 } |
| 21220 return item.substring(match.idx + match.part.length); | 23351 return item.substring(match.idx + match.part.length); |
| 21221 }, | 23352 }, |
| 21222 | 23353 |
| 21223 _extractDimensions: function() { | 23354 _extractQueryParams: function() { |
| 21224 var dims = [] | 23355 var params = {}; |
| 23356 var dims = []; |
| 21225 this._filters.forEach(function(f) { | 23357 this._filters.forEach(function(f) { |
| 21226 var split = f.split(FILTER_SEP, 1) | 23358 var split = f.split(FILTER_SEP, 1) |
| 21227 var col = split[0]; | 23359 var col = split[0]; |
| 21228 if (this.DIMENSIONS.indexOf(col) !== -1) { | 23360 if (this.DIMENSIONS.indexOf(col) !== -1) { |
| 21229 var rest = f.substring(col.length + FILTER_SEP.length); | 23361 var rest = f.substring(col.length + FILTER_SEP.length); |
| 21230 dims.push(col + FILTER_SEP + this._unalias(rest)) | 23362 dims.push(col + FILTER_SEP + this._unalias(rest)) |
| 21231 }; | 23363 } else if (col === "status") { |
| 23364 var rest = f.substring(col.length + FILTER_SEP.length); |
| 23365 if (rest === "alive") { |
| 23366 params["is_dead"] = "FALSE"; |
| 23367 params["quarantined"] = "FALSE"; |
| 23368 } else if (rest === "quarantined") { |
| 23369 params["quarantined"] = "TRUE"; |
| 23370 } else if (rest === "dead") { |
| 23371 params["is_dead"] = "TRUE"; |
| 23372 } |
| 23373 } |
| 21232 }.bind(this)); | 23374 }.bind(this)); |
| 21233 return dims; | 23375 params["dimensions"] = dims; |
| 23376 var lim = Math.floor(this.limit) |
| 23377 if (Number.isInteger(lim)) { |
| 23378 // Clamp the limit |
| 23379 lim = Math.max(lim, 1); |
| 23380 lim = Math.min(1000, lim); |
| 23381 params["limit"] = lim; |
| 23382 } |
| 23383 return params; |
| 21234 } | 23384 } |
| 21235 | 23385 |
| 21236 }); | 23386 }); |
| 21237 })(); | 23387 })(); |
| 21238 </script> | 23388 </script> |
| 21239 </dom-module><dom-module id="bot-list-data" assetpath="/res/imp/botlist/"> | 23389 </dom-module><dom-module id="bot-list-data" assetpath="/res/imp/botlist/"> |
| 21240 <template> | 23390 <template> |
| 21241 <iron-ajax id="botlist" url="/_ah/api/swarming/v1/bots/list" headers="[[auth
_headers]]" params="[[_botlistParams(dimensions.*)]]" handle-as="json" last-resp
onse="{{_list}}" loading="{{_busy1}}"> | 23391 <iron-ajax id="botlist" url="/_ah/api/swarming/v1/bots/list" headers="[[auth
_headers]]" params="[[query_params]]" handle-as="json" last-response="{{_list}}"
loading="{{_busy1}}"> |
| 21242 </iron-ajax> | 23392 </iron-ajax> |
| 21243 | 23393 |
| 21244 <iron-ajax id="dimensions" url="/_ah/api/swarming/v1/bots/dimensions" header
s="[[auth_headers]]" handle-as="json" last-response="{{_dimensions}}" loading="{
{_busy2}}"> | 23394 <iron-ajax id="dimensions" url="/_ah/api/swarming/v1/bots/dimensions" header
s="[[auth_headers]]" handle-as="json" last-response="{{_dimensions}}" loading="{
{_busy2}}"> |
| 21245 </iron-ajax> | 23395 </iron-ajax> |
| 21246 | 23396 |
| 21247 <iron-ajax id="fleet" url="/_ah/api/swarming/v1/bots/count" headers="[[auth_
headers]]" handle-as="json" last-response="{{_count}}" loading="{{_busy3}}"> | 23397 <iron-ajax id="fleet" url="/_ah/api/swarming/v1/bots/count" headers="[[auth_
headers]]" handle-as="json" last-response="{{_count}}" loading="{{_busy3}}"> |
| 21248 </iron-ajax> | 23398 </iron-ajax> |
| 21249 </template> | 23399 </template> |
| 21250 <script> | 23400 <script> |
| 21251 (function(){ | 23401 (function(){ |
| 21252 | 23402 |
| 21253 Polymer({ | 23403 Polymer({ |
| 21254 is: 'bot-list-data', | 23404 is: 'bot-list-data', |
| 21255 | 23405 |
| 21256 behaviors: [SwarmingBehaviors.BotListBehavior], | 23406 behaviors: [SwarmingBehaviors.BotListBehavior], |
| 21257 | 23407 |
| 21258 properties: { | 23408 properties: { |
| 21259 // inputs | 23409 // inputs |
| 21260 auth_headers: { | 23410 auth_headers: { |
| 21261 type: Object, | 23411 type: Object, |
| 21262 observer: "signIn", | 23412 observer: "signIn", |
| 21263 }, | 23413 }, |
| 21264 dimensions: { | 23414 query_params: { |
| 21265 type: Array, | 23415 type: Object, |
| 21266 }, | 23416 }, |
| 21267 | 23417 |
| 21268 //outputs | 23418 //outputs |
| 21269 bots: { | 23419 bots: { |
| 21270 type: Array, | 23420 type: Array, |
| 21271 computed: "_bots(_list)", | 23421 computed: "_bots(_list)", |
| 21272 notify: true, | 23422 notify: true, |
| 21273 }, | 23423 }, |
| 21274 busy: { | 23424 busy: { |
| 21275 type: Boolean, | 23425 type: Boolean, |
| (...skipping 23 matching lines...) Expand all Loading... |
| 21299 }, | 23449 }, |
| 21300 _dimensions: { | 23450 _dimensions: { |
| 21301 type: Object, | 23451 type: Object, |
| 21302 }, | 23452 }, |
| 21303 _list: { | 23453 _list: { |
| 21304 type: Object, | 23454 type: Object, |
| 21305 }, | 23455 }, |
| 21306 }, | 23456 }, |
| 21307 | 23457 |
| 21308 signIn: function(){ | 23458 signIn: function(){ |
| 23459 // Auto on iron-ajax means to automatically re-make the request if |
| 23460 // the url or the query params change. Auto does not trigger if the |
| 23461 // [auth] headers change, so we wait until the user is signed in |
| 23462 // before making any requests. |
| 21309 this.$.botlist.auto = true; | 23463 this.$.botlist.auto = true; |
| 21310 this.$.dimensions.auto = true; | 23464 this.$.dimensions.auto = true; |
| 21311 this.$.fleet.auto = true; | 23465 this.$.fleet.auto = true; |
| 21312 }, | 23466 }, |
| 21313 | 23467 |
| 21314 _botlistParams: function() { | |
| 21315 if (!this.dimensions) { | |
| 21316 return {}; | |
| 21317 } | |
| 21318 return { | |
| 21319 dimensions: this.dimensions, | |
| 21320 }; | |
| 21321 }, | |
| 21322 | |
| 21323 _bots: function(){ | 23468 _bots: function(){ |
| 21324 if (!this._list || !this._list.items) { | 23469 if (!this._list || !this._list.items) { |
| 21325 return []; | 23470 return []; |
| 21326 } | 23471 } |
| 21327 // Do any preprocessing here | 23472 // Do any preprocessing here |
| 21328 this._list.items.forEach(function(bot){ | 23473 this._list.items.forEach(function(bot){ |
| 21329 // Parse the state, which is a JSON string. This contains a lot of | 23474 // Parse the state, which is a JSON string. This contains a lot of |
| 21330 // interesting information like details about the devices attached. | 23475 // interesting information like details about the devices attached. |
| 21331 bot.state = JSON.parse(bot.state); | 23476 bot.state = JSON.parse(bot.state); |
| 21332 // get the disks in an easier to deal with format, sorted by size. | 23477 // get the disks in an easier to deal with format, sorted by size. |
| (...skipping 14 matching lines...) Expand all Loading... |
| 21347 | 23492 |
| 21348 }); | 23493 }); |
| 21349 return this._list.items; | 23494 return this._list.items; |
| 21350 }, | 23495 }, |
| 21351 | 23496 |
| 21352 _fleet: function() { | 23497 _fleet: function() { |
| 21353 if (!this._count) { | 23498 if (!this._count) { |
| 21354 return {}; | 23499 return {}; |
| 21355 } | 23500 } |
| 21356 return { | 23501 return { |
| 21357 alive: this._count.count || -1, | 23502 all: this._count.count || -1, |
| 23503 alive: (this._count.count - this._count.dead) || -1, |
| 21358 busy: this._count.busy || -1, | 23504 busy: this._count.busy || -1, |
| 21359 idle: this._count.count && this._count.busy && | 23505 idle: (this._count.count - this._count.busy) || -1, |
| 21360 this._count.count - this._count.busy, | |
| 21361 dead: this._count.dead || -1, | 23506 dead: this._count.dead || -1, |
| 21362 quarantined: this._count.quarantined || -1, | 23507 quarantined: this._count.quarantined || -1, |
| 21363 } | 23508 } |
| 21364 }, | 23509 }, |
| 21365 | 23510 |
| 21366 _primaryArr: function(dimensions, properties) { | 23511 _primaryArr: function(dimensions, properties) { |
| 21367 return dimensions.concat(properties); | 23512 return dimensions.concat(properties); |
| 21368 }, | 23513 }, |
| 21369 | 23514 |
| 21370 _primaryMap: function(dimensions){ | 23515 _primaryMap: function(dimensions){ |
| 21371 // map will keep track of dimensions that we have seen at least once. | 23516 // pMap will have a list of columns to available values (primary key |
| 21372 // This will then basically get turned into an array to be used for | 23517 // to secondary values). This includes bot dimensions, but also |
| 21373 // filtering. | 23518 // includes state like disk_space, quarantined, busy, etc. |
| 21374 dimensions = dimensions.bots_dimensions; | 23519 dimensions = dimensions.bots_dimensions; |
| 21375 | 23520 |
| 21376 var pMap = {}; | 23521 var pMap = {}; |
| 21377 dimensions.forEach(function(d){ | 23522 dimensions.forEach(function(d){ |
| 21378 if (this.DIMENSIONS_WITH_ALIASES.indexOf(d.key) === -1) { | 23523 if (this.DIMENSIONS_WITH_ALIASES.indexOf(d.key) === -1) { |
| 21379 // value is an array of all seen values for the dimension d.key | 23524 // value is an array of all seen values for the dimension d.key |
| 21380 pMap[d.key] = d.value; | 23525 pMap[d.key] = d.value; |
| 21381 } else if (d.key === "gpu") { | 23526 } else if (d.key === "gpu") { |
| 21382 var gpus = []; | 23527 var gpus = []; |
| 21383 d.value.forEach(function(g){ | 23528 d.value.forEach(function(g){ |
| (...skipping 24 matching lines...) Expand all Loading... |
| 21408 // Add some options that might not show up. | 23553 // Add some options that might not show up. |
| 21409 pMap["android_devices"].push("0"); | 23554 pMap["android_devices"].push("0"); |
| 21410 pMap["device_os"].push("none"); | 23555 pMap["device_os"].push("none"); |
| 21411 pMap["device_type"].push("none"); | 23556 pMap["device_type"].push("none"); |
| 21412 | 23557 |
| 21413 pMap["id"] = []; | 23558 pMap["id"] = []; |
| 21414 | 23559 |
| 21415 // Create custom filter options | 23560 // Create custom filter options |
| 21416 pMap["disk_space"] = []; | 23561 pMap["disk_space"] = []; |
| 21417 pMap["task"] = ["busy", "idle"]; | 23562 pMap["task"] = ["busy", "idle"]; |
| 21418 pMap["status"] = ["available", "dead", "quarantined"]; | 23563 pMap["status"] = ["alive", "dead", "quarantined"]; |
| 21419 | 23564 |
| 21420 // No need to sort any of this, bot-filters sorts secondary items | 23565 // No need to sort any of this, bot-filters sorts secondary items |
| 21421 // automatically, especially when the user types a query. | 23566 // automatically, especially when the user types a query. |
| 21422 return pMap; | 23567 return pMap; |
| 21423 }, | 23568 }, |
| 21424 | 23569 |
| 21425 }); | 23570 }); |
| 21426 })(); | 23571 })(); |
| 21427 </script> | 23572 </script> |
| 21428 </dom-module><dom-module id="bot-list-summary" assetpath="/res/imp/botlist/"> | 23573 </dom-module><dom-module id="bot-list-summary" assetpath="/res/imp/botlist/"> |
| (...skipping 11 matching lines...) Expand all Loading... |
| 21440 } | 23585 } |
| 21441 .right { | 23586 .right { |
| 21442 text-align: right; | 23587 text-align: right; |
| 21443 } | 23588 } |
| 21444 .left { | 23589 .left { |
| 21445 text-align: left; | 23590 text-align: left; |
| 21446 } | 23591 } |
| 21447 </style> | 23592 </style> |
| 21448 | 23593 |
| 21449 <div class="header">Fleet</div> | 23594 <div class="header">Fleet</div> |
| 21450 | |
| 21451 <table> | 23595 <table> |
| 21452 <tbody><tr> | 23596 <tbody><tr> |
| 21453 <td class="right"><a href="/newui/botlist?alive">Alive</a>:</td> | 23597 <td class="right"> |
| 23598 <a href$="[[_makeURL('','',columns.*,filtered_bots.*,sort,verbose)]]">
All</a>: |
| 23599 </td> |
| 23600 <td class="left">[[fleet.all]]</td> |
| 23601 </tr> |
| 23602 <tr> |
| 23603 <td class="right"> |
| 23604 <a href$="[[_makeURL('alive','',columns.*,filtered_bots.*,sort,verbose
)]]">Alive</a>: |
| 23605 </td> |
| 21454 <td class="left">[[fleet.alive]]</td> | 23606 <td class="left">[[fleet.alive]]</td> |
| 21455 </tr> | 23607 </tr> |
| 21456 <tr> | 23608 <tr> |
| 21457 <td class="right"><a href="/newui/botlist?busy">Busy</a>:</td> | 23609 <td class="right"> |
| 23610 <a href$="[[_makeURL('busy','',columns.*,filtered_bots.*,sort,verbose)
]]">Busy</a>: |
| 23611 </td> |
| 21458 <td class="left">[[fleet.busy]]</td> | 23612 <td class="left">[[fleet.busy]]</td> |
| 21459 </tr> | 23613 </tr> |
| 21460 <tr> | 23614 <tr> |
| 21461 <td class="right"><a href="/newui/botlist?idle">Idle</a>:</td> | 23615 <td class="right"> |
| 23616 <a href$="[[_makeURL('idle','',columns.*,filtered_bots.*,sort,verbose)
]]">Idle</a>: |
| 23617 </td> |
| 21462 <td class="left">[[fleet.idle]]</td> | 23618 <td class="left">[[fleet.idle]]</td> |
| 21463 </tr> | 23619 </tr> |
| 21464 <tr> | 23620 <tr> |
| 21465 <td class="right"><a href="/newui/botlist?dead">Dead</a>:</td> | 23621 <td class="right"> |
| 23622 <a href$="[[_makeURL('dead','',columns.*,filtered_bots.*,sort,verbose)
]]">Dead</a>: |
| 23623 </td> |
| 21466 <td class="left">[[fleet.dead]]</td> | 23624 <td class="left">[[fleet.dead]]</td> |
| 21467 </tr> | 23625 </tr> |
| 21468 <tr> | 23626 <tr> |
| 21469 <td class="right"><a href="/newui/botlist?quaren">Quarantined</a>:</td> | 23627 <td class="right"> |
| 23628 <a href$="[[_makeURL('quarantined','',columns.*,filtered_bots.*,sort,v
erbose)]]">Quarantined</a>: |
| 23629 </td> |
| 21470 <td class="left">[[fleet.quarantined]]</td> | 23630 <td class="left">[[fleet.quarantined]]</td> |
| 21471 </tr> | 23631 </tr> |
| 21472 </tbody></table> | 23632 </tbody></table> |
| 21473 | 23633 |
| 21474 <div class="header">Displayed</div> | 23634 <div class="header">Displayed</div> |
| 21475 <table> | 23635 <table> |
| 21476 <tbody><tr> | 23636 <tbody><tr> |
| 21477 <td class="right"><a href="/newui/botlist?alive2">Alive</a>:</td> | 23637 <td class="right"> |
| 23638 All: |
| 23639 </td> |
| 23640 <td class="left">[[_currently_showing.all]]</td> |
| 23641 </tr> |
| 23642 <tr> |
| 23643 <td class="right"> |
| 23644 <a href$="[[_makeURL('alive','true',columns.*,filtered_bots.*,sort,ver
bose)]]">Alive</a>: |
| 23645 </td> |
| 21478 <td class="left">[[_currently_showing.alive]]</td> | 23646 <td class="left">[[_currently_showing.alive]]</td> |
| 21479 </tr> | 23647 </tr> |
| 21480 <tr> | 23648 <tr> |
| 21481 <td class="right"><a href="/newui/botlist?busy2">Busy</a>:</td> | 23649 <td class="right"> |
| 23650 <a href$="[[_makeURL('busy','true',columns.*,filtered_bots.*,sort,verb
ose)]]">Busy</a>: |
| 23651 </td> |
| 21482 <td class="left">[[_currently_showing.busy]]</td> | 23652 <td class="left">[[_currently_showing.busy]]</td> |
| 21483 </tr> | 23653 </tr> |
| 21484 <tr> | 23654 <tr> |
| 21485 <td class="right"><a href="/newui/botlist?idle2">Idle</a>:</td> | 23655 <td class="right"> |
| 23656 <a href$="[[_makeURL('idle','true',columns.*,filtered_bots.*,sort,verb
ose)]]">Idle</a>: |
| 23657 </td> |
| 21486 <td class="left">[[_currently_showing.idle]]</td> | 23658 <td class="left">[[_currently_showing.idle]]</td> |
| 21487 </tr> | 23659 </tr> |
| 21488 <tr> | 23660 <tr> |
| 21489 <td class="right"><a href="/newui/botlist?dead2">Dead</a>:</td> | 23661 <td class="right"> |
| 23662 <a href$="[[_makeURL('dead','true',columns.*,filtered_bots.*,sort,verb
ose)]]">Dead</a>: |
| 23663 </td> |
| 21490 <td class="left">[[_currently_showing.dead]]</td> | 23664 <td class="left">[[_currently_showing.dead]]</td> |
| 21491 </tr> | 23665 </tr> |
| 21492 <tr> | 23666 <tr> |
| 21493 <td class="right"><a href="/newui/botlist?quaren2">Quarantined</a>:</td> | 23667 <td class="right"> |
| 23668 <a href$="[[_makeURL('quarantined','true',columns.*,filtered_bots.*,so
rt,verbose)]]">Quarantined</a>: |
| 23669 </td> |
| 21494 <td class="left">[[_currently_showing.quarantined]]</td> | 23670 <td class="left">[[_currently_showing.quarantined]]</td> |
| 21495 </tr> | 23671 </tr> |
| 21496 </tbody></table> | 23672 </tbody></table> |
| 21497 | 23673 |
| 21498 </template> | 23674 </template> |
| 21499 <script> | 23675 <script> |
| 21500 Polymer({ | 23676 Polymer({ |
| 21501 is: 'bot-list-summary', | 23677 is: 'bot-list-summary', |
| 21502 | 23678 |
| 21503 behaviors: [SwarmingBehaviors.BotListBehavior], | 23679 behaviors: [SwarmingBehaviors.BotListBehavior], |
| 21504 | 23680 |
| 21505 properties: { | 23681 properties: { |
| 23682 columns: { |
| 23683 type: Array, |
| 23684 }, |
| 21506 filtered_bots: { | 23685 filtered_bots: { |
| 21507 type: Array, | 23686 type: Array, |
| 21508 }, | 23687 }, |
| 21509 fleet: { | 23688 fleet: { |
| 21510 type: Object, | 23689 type: Object, |
| 21511 }, | 23690 }, |
| 23691 sort: { |
| 23692 type: String, |
| 23693 }, |
| 23694 verbose: { |
| 23695 type: Boolean, |
| 23696 }, |
| 21512 | 23697 |
| 21513 _currently_showing: { | 23698 _currently_showing: { |
| 21514 type: Object, | 23699 type: Object, |
| 21515 value: function() { | 23700 value: function() { |
| 21516 return { | 23701 return { |
| 23702 all: -1, |
| 21517 alive: -1, | 23703 alive: -1, |
| 21518 busy: -1, | 23704 busy: -1, |
| 21519 idle: -1, | 23705 idle: -1, |
| 21520 dead: -1, | 23706 dead: -1, |
| 21521 quarantined: -1, | 23707 quarantined: -1, |
| 21522 }; | 23708 }; |
| 21523 }, | 23709 }, |
| 21524 }, | 23710 }, |
| 21525 }, | 23711 }, |
| 21526 | 23712 |
| 21527 // Do this because Array changes in Polymer don't always trigger normal | 23713 // Do this because Array changes in Polymer don't always trigger normal |
| 21528 // property observers | 23714 // property observers |
| 21529 observers: ["_recount(filtered_bots.*)"], | 23715 observers: ["_recount(filtered_bots.*)"], |
| 21530 | 23716 |
| 23717 _getFilterStr: function(filter) { |
| 23718 if (!filter) { |
| 23719 return ""; |
| 23720 } |
| 23721 if (filter === "alive" || filter === "dead" || |
| 23722 filter === "quarantined") { |
| 23723 return "status:" + filter; |
| 23724 } else { |
| 23725 return "task:" + filter; |
| 23726 } |
| 23727 }, |
| 23728 |
| 23729 _makeURL: function(filter, preserveOthers) { |
| 23730 if (preserveOthers) { |
| 23731 var fstr = encodeURIComponent(this._getFilterStr(filter)); |
| 23732 if (window.location.href.indexOf(fstr) === -1) { |
| 23733 return window.location.href + "&filters=" + fstr; |
| 23734 } |
| 23735 // The filter is already on the list. |
| 23736 return undefined; |
| 23737 } |
| 23738 var params = { |
| 23739 sort: [this.sort], |
| 23740 columns: this.columns, |
| 23741 verbose: [this.verbose], |
| 23742 } |
| 23743 if (filter) { |
| 23744 params["filters"] = [this._getFilterStr(filter)]; |
| 23745 } |
| 23746 |
| 23747 return window.location.href.split('?')[0] + '?' + sk.query.fromParamSet(
params); |
| 23748 }, |
| 23749 |
| 21531 _recount: function() { | 23750 _recount: function() { |
| 21532 var curr = { | 23751 var curr = { |
| 23752 all: 0, |
| 21533 alive: 0, | 23753 alive: 0, |
| 21534 busy: 0, | 23754 busy: 0, |
| 21535 idle: 0, | 23755 idle: 0, |
| 21536 dead: 0, | 23756 dead: 0, |
| 21537 quarantined: 0, | 23757 quarantined: 0, |
| 21538 }; | 23758 }; |
| 21539 if (!this.filtered_bots) { | 23759 if (!this.filtered_bots) { |
| 21540 return curr; | 23760 return curr; |
| 21541 } | 23761 } |
| 21542 this.filtered_bots.forEach(function(bot) { | 23762 this.filtered_bots.forEach(function(bot) { |
| 21543 if (this._taskId(bot) === "idle") { | 23763 if (this._taskId(bot) === "idle") { |
| 21544 curr.idle++; | 23764 curr.idle++; |
| 21545 } else { | 23765 } else { |
| 21546 curr.busy++; | 23766 curr.busy++; |
| 21547 } | 23767 } |
| 21548 if (bot.quarantined) { | 23768 if (bot.quarantined) { |
| 21549 curr.quarantined++; | 23769 curr.quarantined++; |
| 21550 } | 23770 } |
| 21551 if (bot.is_dead) { | 23771 if (bot.is_dead) { |
| 21552 curr.dead++; | 23772 curr.dead++; |
| 21553 } else { | 23773 } else { |
| 21554 curr.alive++; | 23774 curr.alive++; |
| 21555 } | 23775 } |
| 23776 curr.all++; |
| 21556 }.bind(this)); | 23777 }.bind(this)); |
| 21557 this.set("_currently_showing", curr); | 23778 this.set("_currently_showing", curr); |
| 21558 } | 23779 } |
| 21559 }); | 23780 }); |
| 21560 </script> | 23781 </script> |
| 21561 </dom-module><dom-module id="bot-list" assetpath="/res/imp/botlist/"> | 23782 </dom-module><dom-module id="bot-list" assetpath="/res/imp/botlist/"> |
| 21562 <template> | 23783 <template> |
| 21563 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app-
style"> | 23784 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app-
style"> |
| 21564 bot-filters, bot-list-summary { | 23785 bot-filters, bot-list-summary { |
| 21565 margin-bottom: 8px; | 23786 margin-bottom: 8px; |
| (...skipping 28 matching lines...) Expand all Loading... |
| 21594 position: absolute; | 23815 position: absolute; |
| 21595 right: 0; | 23816 right: 0; |
| 21596 top: 0.4em; | 23817 top: 0.4em; |
| 21597 } | 23818 } |
| 21598 .bot-list th > span { | 23819 .bot-list th > span { |
| 21599 /* Leave space for sort-toggle*/ | 23820 /* Leave space for sort-toggle*/ |
| 21600 padding-right: 30px; | 23821 padding-right: 30px; |
| 21601 } | 23822 } |
| 21602 </style> | 23823 </style> |
| 21603 | 23824 |
| 23825 <url-param name="sort" value="{{_sortstr}}" default_value="id:asc"> |
| 23826 </url-param> |
| 23827 |
| 21604 <swarming-app auth_headers="{{_auth_headers}}" signed_in="{{_signed_in}}" bu
sy="[[_busy]]" name="Swarming Bot List"> | 23828 <swarming-app auth_headers="{{_auth_headers}}" signed_in="{{_signed_in}}" bu
sy="[[_busy]]" name="Swarming Bot List"> |
| 21605 | 23829 |
| 21606 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2> | 23830 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2> |
| 21607 | 23831 |
| 21608 <div hidden$="[[_not(_signed_in)]]"> | 23832 <div hidden$="[[_not(_signed_in)]]"> |
| 21609 | 23833 |
| 21610 <div class="horizontal layout"> | 23834 <div class="horizontal layout"> |
| 21611 | 23835 |
| 21612 <bot-filters primary_map="[[_primary_map]]" primary_arr="[[_primary_ar
r]]" columns="{{_columns}}" dimensions="{{_dimensions}}" filter="{{_filter}}" ve
rbose="{{_verbose}}"> | 23836 <bot-filters primary_map="[[_primary_map]]" primary_arr="[[_primary_ar
r]]" columns="{{_columns}}" query_params="{{_query_params}}" filter="{{_filter}}
" verbose="{{_verbose}}"> |
| 21613 </bot-filters> | 23837 </bot-filters> |
| 21614 | 23838 |
| 21615 <bot-list-summary fleet="[[_fleet]]" filtered_bots="[[_filteredSortedB
ots]]"> | 23839 <bot-list-summary columns="[[_columns]]" fleet="[[_fleet]]" filtered_b
ots="[[_filteredSortedBots]]" sort="[[_sortstr]]" verbose="[[_verbose]]"> |
| 21616 </bot-list-summary> | 23840 </bot-list-summary> |
| 21617 | 23841 |
| 21618 </div> | 23842 </div> |
| 21619 | 23843 |
| 21620 <bot-list-data auth_headers="[[_auth_headers]]" dimensions="[[_dimension
s]]" bots="{{_bots}}" busy="{{_busy}}" fleet="{{_fleet}}" primary_map="{{_primar
y_map}}" primary_arr="{{_primary_arr}}"> | 23844 <bot-list-data auth_headers="[[_auth_headers]]" query_params="[[_query_p
arams]]" bots="{{_bots}}" busy="{{_busy}}" fleet="{{_fleet}}" primary_map="{{_pr
imary_map}}" primary_arr="{{_primary_arr}}"> |
| 21621 </bot-list-data> | 23845 </bot-list-data> |
| 21622 | 23846 |
| 21623 <table class="bot-list"> | 23847 <table class="bot-list"> |
| 21624 <thead on-sort_change="_sortChange"> | 23848 <thead on-sort_change="_sortChange"> |
| 21625 | 23849 |
| 21626 <tr> | 23850 <tr> |
| 21627 <th> | 23851 <th> |
| 21628 <span>Bot Id</span> | 23852 <span>Bot Id</span> |
| 21629 <sort-toggle name="id" current="[[_sort]]"> | 23853 <sort-toggle name="id" current="[[_sort]]"> |
| 21630 </sort-toggle> | 23854 </sort-toggle> |
| (...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 21739 device_type: function(bot){ | 23963 device_type: function(bot){ |
| 21740 var dt = this._attribute(bot, "device_type", "none"); | 23964 var dt = this._attribute(bot, "device_type", "none"); |
| 21741 if (this._verbose) { | 23965 if (this._verbose) { |
| 21742 return dt.join(" | "); | 23966 return dt.join(" | "); |
| 21743 } | 23967 } |
| 21744 return dt[0]; | 23968 return dt[0]; |
| 21745 }, | 23969 }, |
| 21746 disk_space: function(bot) { | 23970 disk_space: function(bot) { |
| 21747 var aliased = []; | 23971 var aliased = []; |
| 21748 bot.disks.forEach(function(disk){ | 23972 bot.disks.forEach(function(disk){ |
| 21749 var alias = swarming.humanBytes(disk.mb, swarming.MB); | 23973 var alias = sk.human.bytes(disk.mb, swarming.MB); |
| 21750 aliased.push(this._applyAlias(disk.mb, disk.id + " "+ alias)); | 23974 aliased.push(this._applyAlias(disk.mb, disk.id + " "+ alias)); |
| 21751 }.bind(this)); | 23975 }.bind(this)); |
| 21752 if (this._verbose) { | 23976 if (this._verbose) { |
| 21753 return aliased.join(" | "); | 23977 return aliased.join(" | "); |
| 21754 } | 23978 } |
| 21755 return aliased[0]; | 23979 return aliased[0]; |
| 21756 }, | 23980 }, |
| 21757 gpu: function(bot){ | 23981 gpu: function(bot){ |
| 21758 var gpus = this._attribute(bot, "gpu", "none") | 23982 var gpus = this._attribute(bot, "gpu", "none") |
| 21759 var verbose = [] | 23983 var verbose = [] |
| (...skipping 30 matching lines...) Expand all Loading... |
| 21790 return os[0]; | 24014 return os[0]; |
| 21791 }, | 24015 }, |
| 21792 pool: function(bot) { | 24016 pool: function(bot) { |
| 21793 var pool = this._attribute(bot, "pool"); | 24017 var pool = this._attribute(bot, "pool"); |
| 21794 return pool.join(" | "); | 24018 return pool.join(" | "); |
| 21795 }, | 24019 }, |
| 21796 status: function(bot) { | 24020 status: function(bot) { |
| 21797 // If a bot is both dead and quarantined, show the deadness over the | 24021 // If a bot is both dead and quarantined, show the deadness over the |
| 21798 // quarentinedness. | 24022 // quarentinedness. |
| 21799 if (bot.is_dead) { | 24023 if (bot.is_dead) { |
| 21800 return "Dead. Last seen " + swarming.diffDate(bot.last_seen_ts) + | 24024 return "Dead. Last seen " + sk.human.diffDate(bot.last_seen_ts) + |
| 21801 " ago"; | 24025 " ago"; |
| 21802 } | 24026 } |
| 21803 if (bot.quarantined) { | 24027 if (bot.quarantined) { |
| 21804 return "Quarantined: " + this._attribute(bot, "quarantined"); | 24028 return "Quarantined: " + this._attribute(bot, "quarantined"); |
| 21805 } | 24029 } |
| 21806 return "Alive"; | 24030 return "Alive"; |
| 21807 }, | 24031 }, |
| 21808 task: function(bot){ | 24032 task: function(bot){ |
| 21809 return this._taskId(bot); | 24033 return this._taskId(bot); |
| 21810 }, | 24034 }, |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 21878 }, | 24102 }, |
| 21879 | 24103 |
| 21880 _plain_columns: { | 24104 _plain_columns: { |
| 21881 type: Array, | 24105 type: Array, |
| 21882 computed: "_stripSpecial(_columns.*)", | 24106 computed: "_stripSpecial(_columns.*)", |
| 21883 }, | 24107 }, |
| 21884 | 24108 |
| 21885 // _sort is an Object {name:String, direction:String}. | 24109 // _sort is an Object {name:String, direction:String}. |
| 21886 _sort: { | 24110 _sort: { |
| 21887 type: Object, | 24111 type: Object, |
| 21888 value: function() { | 24112 computed: "_makeObject(_sortstr)", |
| 21889 return { | 24113 }, |
| 21890 name: "id", | 24114 |
| 21891 direction: "asc", | 24115 // We can't (easily) stick an object in query params, so we use |
| 21892 }; | 24116 // a serialized string of key:dir and stick that in the query params. |
| 21893 } | 24117 // This is deserialized (into _sort) for easier use |
| 24118 _sortstr: { |
| 24119 type: String, |
| 21894 }, | 24120 }, |
| 21895 | 24121 |
| 21896 _verbose: { | 24122 _verbose: { |
| 21897 type: Boolean, | 24123 type: Boolean, |
| 21898 } | 24124 } |
| 21899 }, | 24125 }, |
| 21900 | 24126 |
| 21901 _botClass: function(bot) { | 24127 _botClass: function(bot) { |
| 21902 if (bot.is_dead) { | 24128 if (bot.is_dead) { |
| 21903 return "dead"; | 24129 return "dead"; |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 21954 }, | 24180 }, |
| 21955 | 24181 |
| 21956 _header: function(col){ | 24182 _header: function(col){ |
| 21957 return headerMap[col]; | 24183 return headerMap[col]; |
| 21958 }, | 24184 }, |
| 21959 | 24185 |
| 21960 _hide: function(col) { | 24186 _hide: function(col) { |
| 21961 return this._columns.indexOf(col) === -1; | 24187 return this._columns.indexOf(col) === -1; |
| 21962 }, | 24188 }, |
| 21963 | 24189 |
| 24190 _makeObject: function(sortstr){ |
| 24191 if (!sortstr) { |
| 24192 return undefined; |
| 24193 } |
| 24194 var pieces = sortstr.split(":"); |
| 24195 if (pieces.length != 2) { |
| 24196 // fail safe |
| 24197 return {name: "id", direction:"desc"}; |
| 24198 } |
| 24199 return { |
| 24200 name: pieces[0], |
| 24201 direction: pieces[1], |
| 24202 } |
| 24203 }, |
| 24204 |
| 21964 _reRender: function(filter, sort) { | 24205 _reRender: function(filter, sort) { |
| 21965 this.$.bot_table.render(); | 24206 this.$.bot_table.render(); |
| 21966 }, | 24207 }, |
| 21967 | 24208 |
| 21968 _sortBotTable: function(botA, botB) { | 24209 _sortBotTable: function(botA, botB) { |
| 21969 if (!this._sort) { | 24210 if (!this._sort) { |
| 21970 return 0; | 24211 return 0; |
| 21971 } | 24212 } |
| 21972 var dir = 1; | 24213 var dir = 1; |
| 21973 if (this._sort.direction === "desc") { | 24214 if (this._sort.direction === "desc") { |
| 21974 dir = -1; | 24215 dir = -1; |
| 21975 } | 24216 } |
| 21976 var sort = specialSort[this._sort.name]; | 24217 var sort = specialSort[this._sort.name]; |
| 21977 if (sort) { | 24218 if (sort) { |
| 21978 return sort.bind(this)(dir, botA, botB); | 24219 return sort.bind(this)(dir, botA, botB); |
| 21979 } | 24220 } |
| 21980 // Default to a natural compare of the columns. | 24221 // Default to a natural compare of the columns. |
| 21981 var botACol = this._column(this._sort.name, botA); | 24222 var botACol = this._column(this._sort.name, botA); |
| 21982 var botBCol = this._column(this._sort.name, botB); | 24223 var botBCol = this._column(this._sort.name, botB); |
| 21983 | 24224 |
| 21984 return dir * swarming.naturalCompare(botACol, botBCol); | 24225 return dir * swarming.naturalCompare(botACol, botBCol); |
| 21985 }, | 24226 }, |
| 21986 | 24227 |
| 21987 _sortChange: function(e) { | 24228 _sortChange: function(e) { |
| 21988 // The event we get from sort-toggle tells us the name of what needs | 24229 // The event we get from sort-toggle tells us the name of what needs |
| 21989 // to be sorting and how to sort it. | 24230 // to be sorting and how to sort it. |
| 21990 if (!(e && e.detail && e.detail.name)) { | 24231 if (!(e && e.detail && e.detail.name)) { |
| 21991 return; | 24232 return; |
| 21992 } | 24233 } |
| 21993 // should trigger __filterAndSort | 24234 // should trigger the computation of _sort and __filterAndSort |
| 21994 this.set("_sort", e.detail); | 24235 this.set("_sortstr", e.detail.name +":"+e.detail.direction); |
| 21995 }, | 24236 }, |
| 21996 | 24237 |
| 21997 // _stripSpecial removes the special columns and sorts the remaining | 24238 // _stripSpecial removes the special columns and sorts the remaining |
| 21998 // columns so they always appear in the same order, regardless of | 24239 // columns so they always appear in the same order, regardless of |
| 21999 // the order they are added. | 24240 // the order they are added. |
| 22000 _stripSpecial: function(){ | 24241 _stripSpecial: function(){ |
| 22001 return this._columns.filter(function(c){ | 24242 return this._columns.filter(function(c){ |
| 22002 return special_columns.indexOf(c) === -1; | 24243 return special_columns.indexOf(c) === -1; |
| 22003 }).sort(); | 24244 }).sort(); |
| 22004 }, | 24245 }, |
| 22005 | 24246 |
| 22006 _taskLink: function(data) { | 24247 _taskLink: function(data) { |
| 22007 if (data && data.task_id) { | 24248 if (data && data.task_id) { |
| 22008 return "/user/task/" + data.task_id; | 24249 return "/user/task/" + data.task_id; |
| 22009 } | 24250 } |
| 22010 return undefined; | 24251 return undefined; |
| 22011 } | 24252 } |
| 22012 | 24253 |
| 22013 }); | 24254 }); |
| 22014 })(); | 24255 })(); |
| 22015 </script> | 24256 </script> |
| 22016 </dom-module></div></body></html> | 24257 </dom-module></div></body></html> |
| OLD | NEW |