612b0fb69e556ef38e2e7d98452033c35bedde03
[WebKit.git] / Websites / perf.webkit.org / public / v3 / components / base.js
1
2 class ComponentBase {
3     constructor(name)
4     {
5         this._componentName = name || ComponentBase._componentByClass.get(new.target);
6
7         const currentlyConstructed = ComponentBase._currentlyConstructedByInterface;
8         let element = currentlyConstructed.get(new.target);
9         if (!element) {
10             currentlyConstructed.set(new.target, this);
11             element = document.createElement(this._componentName);
12             currentlyConstructed.delete(new.target);
13         }
14         element.component = () => { return this };
15
16         this._element = element;
17         this._shadow = null;
18     }
19
20     element() { return this._element; }
21     content()
22     {
23         this._ensureShadowTree();
24         return this._shadow;
25     }
26
27     render() { this._ensureShadowTree(); }
28
29     updateRendering()
30     {
31         Instrumentation.startMeasuringTime('ComponentBase', 'updateRendering');
32         this.render();
33         Instrumentation.endMeasuringTime('ComponentBase', 'updateRendering');
34     }
35
36     renderReplace(element, content) { ComponentBase.renderReplace(element, content); }
37
38     static renderReplace(element, content)
39     {
40         element.innerHTML = '';
41         if (content)
42             ComponentBase._addContentToElement(element, content);
43     }
44
45     _ensureShadowTree()
46     {
47         if (this._shadow)
48             return;
49
50         const newTarget = this.__proto__.constructor;
51         const htmlTemplate = newTarget['htmlTemplate'];
52         const cssTemplate = newTarget['cssTemplate'];
53
54         if (!htmlTemplate && !cssTemplate)
55             return;
56
57         const shadow = this._element.attachShadow({mode: 'closed'});
58
59         if (htmlTemplate) {
60             const template = document.createElement('template');
61             template.innerHTML = newTarget.htmlTemplate();
62             shadow.appendChild(document.importNode(template.content, true));
63             this._recursivelyReplaceUnknownElementsByComponents(shadow);
64         }
65
66         if (cssTemplate) {
67             const style = document.createElement('style');
68             style.textContent = newTarget.cssTemplate();
69             shadow.appendChild(style);
70         }
71
72         this._shadow = shadow;
73     }
74
75     _recursivelyReplaceUnknownElementsByComponents(parent)
76     {
77         let nextSibling;
78         for (let child = parent.firstChild; child; child = child.nextSibling) {
79             if (child instanceof HTMLElement && !child.component) {
80                 const elementInterface = ComponentBase._componentByName.get(child.localName);
81                 if (elementInterface) {
82                     const component = new elementInterface();
83                     const newChild = component.element();
84                     parent.replaceChild(newChild, child);
85                     child = newChild;
86                 }
87             }
88             this._recursivelyReplaceUnknownElementsByComponents(child);
89         }
90     }
91
92     static defineElement(name, elementInterface)
93     {
94         ComponentBase._componentByName.set(name, elementInterface);
95         ComponentBase._componentByClass.set(elementInterface, name);
96
97         class elementClass extends HTMLElement {
98             constructor()
99             {
100                 super();
101
102                 const currentlyConstructed = ComponentBase._currentlyConstructedByInterface;
103                 const component = currentlyConstructed.get(elementInterface);
104                 if (component)
105                     return; // ComponentBase's constructor will take care of the rest.
106
107                 currentlyConstructed.set(elementInterface, this);
108                 new elementInterface();
109                 currentlyConstructed.delete(elementInterface);
110             }
111         }
112
113         const nameDescriptor = Object.getOwnPropertyDescriptor(elementClass, 'name');
114         nameDescriptor.value = `${elementInterface.name}Element`;
115         Object.defineProperty(elementClass, 'name', nameDescriptor);
116
117         customElements.define(name, elementClass);
118     }
119
120     static createElement(name, attributes, content)
121     {
122         var element = document.createElement(name);
123         if (!content && (attributes instanceof Array || attributes instanceof Node
124             || attributes instanceof ComponentBase || typeof(attributes) != 'object')) {
125             content = attributes;
126             attributes = {};
127         }
128
129         if (attributes) {
130             for (var name in attributes) {
131                 if (name.startsWith('on'))
132                     element.addEventListener(name.substring(2), attributes[name]);
133                 else
134                     element.setAttribute(name, attributes[name]);
135             }
136         }
137
138         if (content)
139             ComponentBase._addContentToElement(element, content);
140
141         return element;
142     }
143
144     static _addContentToElement(element, content)
145     {
146         if (content instanceof Array) {
147             for (var nestedChild of content)
148                 this._addContentToElement(element, nestedChild);
149         } else if (content instanceof Node)
150             element.appendChild(content);
151          else if (content instanceof ComponentBase)
152             element.appendChild(content.element());
153         else
154             element.appendChild(document.createTextNode(content));
155     }
156
157     static createLink(content, titleOrCallback, callback, isExternal)
158     {
159         var title = titleOrCallback;
160         if (callback === undefined) {
161             title = content;
162             callback = titleOrCallback;
163         }
164
165         var attributes = {
166             href: '#',
167             title: title,
168         };
169
170         if (typeof(callback) === 'string')
171             attributes['href'] = callback;
172         else
173             attributes['onclick'] = ComponentBase.createActionHandler(callback);
174
175         if (isExternal)
176             attributes['target'] = '_blank';
177         return ComponentBase.createElement('a', attributes, content);
178     }
179
180     static createActionHandler(callback)
181     {
182         return function (event) {
183             event.preventDefault();
184             event.stopPropagation();
185             callback.call(this, event);
186         };
187     }
188 }
189
190 ComponentBase._componentByName = new Map;
191 ComponentBase._componentByClass = new Map;
192 ComponentBase._currentlyConstructedByInterface = new Map;
193
194 ComponentBase.css = Symbol();
195 ComponentBase.html = Symbol();
196 ComponentBase.map = {};