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