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