Show a spinner while fetching data on summary page
[WebKit-https.git] / Websites / perf.webkit.org / public / v3 / components / base.js
1
2 // FIXME: ComponentBase should inherit from HTMLElement when custom elements API is available.
3 class ComponentBase {
4     constructor(name)
5     {
6         this._element = document.createElement(name);
7         this._element.component = (function () { return this; }).bind(this);
8         this._shadow = this._constructShadowTree();
9     }
10
11     element() { return this._element; }
12     content() { return this._shadow; }
13     render() { }
14
15     renderReplace(element, content) { ComponentBase.renderReplace(element, content); }
16
17     static renderReplace(element, content)
18     {
19         element.innerHTML = '';
20         if (content)
21             ComponentBase._addContentToElement(element, content);
22     }
23
24     _constructShadowTree()
25     {
26         var newTarget = this.__proto__.constructor;
27
28         var htmlTemplate = newTarget['htmlTemplate'];
29         var cssTemplate = newTarget['cssTemplate'];
30
31         if (!htmlTemplate && !cssTemplate)
32             return null;
33
34         var shadow = null;
35         if ('attachShadow' in Element.prototype)
36             shadow = this._element.attachShadow({mode: 'closed'});
37         else if ('createShadowRoot' in Element.prototype) // Legacy Chromium API.
38             shadow = this._element.createShadowRoot();
39         else
40             shadow = this._element;
41
42         if (htmlTemplate) {
43             var template = document.createElement('template');
44             template.innerHTML = newTarget.htmlTemplate();
45             shadow.appendChild(template.content.cloneNode(true));
46             this._recursivelyReplaceUnknownElementsByComponents(shadow);
47         }
48
49         if (cssTemplate) {
50             var style = document.createElement('style');
51             style.textContent = newTarget.cssTemplate();
52             shadow.appendChild(style);
53         }
54
55         return shadow;
56     }
57
58     _recursivelyReplaceUnknownElementsByComponents(parent)
59     {
60         if (!ComponentBase._map)
61             return;
62
63         var nextSibling;
64         for (var child = parent.firstChild; child; child = child.nextSibling) {
65             if (child instanceof HTMLUnknownElement || child instanceof HTMLElement) {
66                 var elementInterface = ComponentBase._map[child.localName];
67                 if (elementInterface) {
68                     var component = new elementInterface();
69                     var newChild = component.element();
70                     parent.replaceChild(newChild, child);
71                     child = newChild;
72                 }
73             }
74             this._recursivelyReplaceUnknownElementsByComponents(child);
75         }
76     }
77
78     static isElementInViewport(element)
79     {
80         var viewportHeight = window.innerHeight;
81         var boundingRect = element.getBoundingClientRect();
82         if (viewportHeight < boundingRect.top || boundingRect.bottom < 0
83             || !boundingRect.width || !boundingRect.height)
84             return false;
85         return true;
86     }
87
88     static defineElement(name, elementInterface)
89     {
90         if (!ComponentBase._map)
91             ComponentBase._map = {};
92         ComponentBase._map[name] = elementInterface;
93     }
94
95     static createElement(name, attributes, content)
96     {
97         var element = document.createElement(name);
98         if (!content && (attributes instanceof Array || attributes instanceof Node
99             || attributes instanceof ComponentBase || typeof(attributes) != 'object')) {
100             content = attributes;
101             attributes = {};
102         }
103
104         if (attributes) {
105             for (var name in attributes) {
106                 if (name.startsWith('on'))
107                     element.addEventListener(name.substring(2), attributes[name]);
108                 else
109                     element.setAttribute(name, attributes[name]);
110             }
111         }
112
113         if (content)
114             ComponentBase._addContentToElement(element, content);
115
116         return element;
117     }
118
119     static _addContentToElement(element, content)
120     {
121         if (content instanceof Array) {
122             for (var nestedChild of content)
123                 this._addContentToElement(element, nestedChild);
124         } else if (content instanceof Node)
125             element.appendChild(content);
126          else if (content instanceof ComponentBase)
127             element.appendChild(content.element());
128         else
129             element.appendChild(document.createTextNode(content));
130     }
131
132     static createLink(content, titleOrCallback, callback, isExternal)
133     {
134         var title = titleOrCallback;
135         if (callback === undefined) {
136             title = content;
137             callback = titleOrCallback;
138         }
139
140         var attributes = {
141             href: '#',
142             title: title,
143         };
144
145         if (typeof(callback) === 'string')
146             attributes['href'] = callback;
147         else
148             attributes['onclick'] = ComponentBase.createActionHandler(callback);
149
150         if (isExternal)
151             attributes['target'] = '_blank';
152         return ComponentBase.createElement('a', attributes, content);
153     }
154
155     static createActionHandler(callback)
156     {
157         return function (event) {
158             event.preventDefault();
159             event.stopPropagation();
160             callback.call(this, event);
161         };
162     }
163 }
164
165 ComponentBase.css = Symbol();
166 ComponentBase.html = Symbol();
167 ComponentBase.map = {};