on fa202ee help sign in
elk/runtime/Render/Element.js
377 lines 12 kB history raw
2ElmRuntime.Render.Element = function() {
3'use strict';
5var Utils = ElmRuntime.use(ElmRuntime.Render.Utils);
6var newElement = Utils.newElement, extract = Utils.extract,
7 addTransform = Utils.addTransform, removeTransform = Utils.removeTransform,
8 fromList = Utils.fromList, eq = Utils.eq;
10function setProps(elem, e) {
11 var props = elem.props;
12 var element = elem.element;
13 var width = props.width - (element.adjustWidth || 0);
14 var height = props.height - (element.adjustHeight || 0);
15 e.style.width = (width |0) + 'px';
16 e.style.height = (height|0) + 'px';
17 if (props.opacity !== 1) {
18 e.style.opacity = props.opacity;
19 }
20 if (props.color.ctor === 'Just') {
21 e.style.backgroundColor = extract(props.color._0);
22 }
23 if (props.tag !== '') { e.id = props.tag; }
24 if (props.href !== '') {
25 var a = newElement('a');
26 a.href = props.href;
27 a.style.width = '100%';
28 a.style.height = '100%';
29 a.style.top = 0;
30 a.style.left = 0;
31 a.style.display = 'block';
32 a.style.position = 'absolute';
33 e.style.position = 'relative';
34 e.appendChild(a);
35 }
36 if (props.hover.ctor !== '_Tuple0') {
37 e.style.pointerEvents = 'auto';
38 e.elm_hover_handler = props.hover;
39 e.elm_hover_count = 0;
40 e.addEventListener('mouseover', function() {
41 if (e.elm_hover_count++ > 0) return;
42 var handler = e.elm_hover_handler;
43 if (handler !== null) {
44 handler(true);
45 }
46 });
47 e.addEventListener('mouseout', function(evt) {
48 if (e.contains(evt.toElement || evt.relatedTarget)) return;
49 e.elm_hover_count = 0;
50 var handler = e.elm_hover_handler;
51 if (handler !== null) {
52 handler(false);
53 }
54 });
55 }
56 if (props.click.ctor !== '_Tuple0') {
57 e.style.pointerEvents = 'auto';
58 e.elm_click_handler = props.click;
59 e.addEventListener('click', function() {
60 e.elm_click_handler(Tuple0);
61 });
62 }
63 return e;
66function image(props, img) {
67 switch (img._0.ctor) {
68 case 'Plain': return plainImage(img._3);
69 case 'Fitted': return fittedImage(props.width, props.height, img._3);
70 case 'Cropped': return croppedImage(img,props.width,props.height,img._3);
71 case 'Tiled': return tiledImage(img._3);
72 }
75function plainImage(src) {
76 var img = newElement('img');
77 img.src = src;
78 img.name = src;
79 img.style.display = "block";
80 return img;
83function tiledImage(src) {
84 var div = newElement('div');
85 div.style.backgroundImage = 'url(' + src + ')';
86 return div;
89function fittedImage(w, h, src) {
90 var div = newElement('div');
91 div.style.background = 'url(' + src + ') no-repeat center';
92 div.style.webkitBackgroundSize = 'cover';
93 div.style.MozBackgroundSize = 'cover';
94 div.style.OBackgroundSize = 'cover';
95 div.style.backgroundSize = 'cover';
96 return div;
99function croppedImage(elem, w, h, src) {
100 var pos = elem._0._0;
101 var e = newElement('div');
102 e.style.overflow = "hidden";
104 var img = newElement('img');
105 img.onload = function() {
106 var sw = w / elem._1, sh = h / elem._2;
107 img.style.width = ((this.width * sw)|0) + 'px';
108 img.style.height = ((this.height * sh)|0) + 'px';
109 img.style.marginLeft = ((- pos._0 * sw)|0) + 'px';
110 img.style.marginTop = ((- pos._1 * sh)|0) + 'px';
111 };
112 img.src = src;
113 img.name = src;
114 e.appendChild(img);
115 return e;
118function goIn(e) { e.style.position = 'absolute'; return e; }
119function goDown(e) { return e }
120function goRight(e) { e.style.styleFloat = e.style.cssFloat = "left"; return e; }
121function flowWith(f, array) {
122 var container = newElement('div');
123 if (f == goIn) container.style.pointerEvents = 'none';
125 for (var i = array.length; i--; ) {
126 container.appendChild(f(render(array[i])));
127 }
128 return container;
131function flow(dir,elist) {
132 var array = fromList(elist);
133 switch(dir.ctor) {
134 case "DDown": array.reverse();
135 case "DUp": return flowWith(goDown,array);
136 case "DRight": array.reverse();
137 case "DLeft": return flowWith(goRight,array);
138 case "DOut": array.reverse();
139 case "DIn": return flowWith(goIn,array);
140 }
143function toPos(pos) {
144 switch(pos.ctor) {
145 case "Absolute": return pos._0 + "px";
146 case "Relative": return (pos._0 * 100) + "%";
147 }
150// must clear right, left, top, bottom, and transform
151// before calling this function
152function setPos(pos,elem,e) {
153 var element = elem.element;
154 var props = elem.props;
155 var w = props.width + (element.adjustWidth ? element.adjustWidth : 0);
156 var h = props.height + (element.adjustHeight ? element.adjustHeight : 0);
158 e.style.position = 'absolute';
159 e.style.margin = 'auto';
160 var transform = '';
161 switch(pos.horizontal.ctor) {
162 case 'P': e.style.right = toPos(pos.x); e.style.removeProperty('left'); break;
163 case 'Z': transform = 'translateX(' + ((-w/2)|0) + 'px) ';
164 case 'N': e.style.left = toPos(pos.x); e.style.removeProperty('right'); break;
165 }
166 switch(pos.vertical.ctor) {
167 case 'N': e.style.bottom = toPos(pos.y); e.style.removeProperty('top'); break;
168 case 'Z': transform += 'translateY(' + ((-h/2)|0) + 'px)';
169 case 'P': e.style.top = toPos(pos.y); e.style.removeProperty('bottom'); break;
170 }
171 if (transform !== '') addTransform(e.style, transform);
172 return e;
175function container(pos,elem) {
176 var e = render(elem);
177 setPos(pos, elem, e);
178 var div = newElement('div');
179 div.style.position = 'relative';
180 div.style.overflow = 'hidden';
181 div.appendChild(e);
182 return div;
185function rawHtml(elem) {
186 var html = elem.html;
187 var args = elem.args;
188 var guid = elem.guid;
189 var align = elem.align;
191 var div = newElement('div');
192 div.innerHTML = html;
193 div.style.visibility = "hidden";
194 if (align) div.style.textAlign = align;
195 document.body.appendChild(div);
197 for (var i = args.length; i--; ) {
198 var arg = args[i];
199 var span = document.getElementById('md-' + guid + '-' + i);
200 if (arg.isText) {
201 span.innerHTML = arg;
202 } else {
203 span.style.display = 'block';
204 span.style.width = arg.props.width + 'px';
205 span.style.height = arg.props.height + 'px';
206 span.appendChild(render(arg));
207 }
208 }
209 document.body.removeChild(div);
210 div.style.visibility = 'visible';
211 return div;
214function render(elem) { return setProps(elem, makeElement(elem)); }
215function makeElement(e) {
216 var elem = e.element;
217 switch(elem.ctor) {
218 case 'Image': return image(e.props, elem);
219 case 'Flow': return flow(elem._0, elem._1);
220 case 'Container': return container(elem._0, elem._1);
221 case 'Spacer': return newElement('div');
222 case 'RawHtml': return rawHtml(elem);
223 case 'Custom': return elem.render(elem.model);
224 }
227function update(node, curr, next) {
228 if (node.tagName === 'A') { node = node.firstChild; }
229 if (curr.props.id === next.props.id) return updateProps(node, curr, next);
230 if (curr.element.ctor !== next.element.ctor) {
231 node.parentNode.replaceChild(render(next),node);
232 return true;
233 }
234 var nextE = next.element, currE = curr.element;
235 switch(nextE.ctor) {
236 case "Spacer": break;
237 case "RawHtml":
238 // only markdown blocks have guids, so this must be a text block
239 if (nextE.guid === null) {
240 if(currE.html.valueOf() !== nextE.html.valueOf()) {
241 node.innerHTML = nextE.html;
242 }
243 break;
244 }
245 if (nextE.guid !== currE.guid) {
246 node.parentNode.replaceChild(render(next),node);
247 return true;
248 }
249 var nargs = nextE.args;
250 var cargs = currE.args;
251 for (var i = nargs.length; i--; ) {
252 var narg = nargs[i];
253 var carg = cargs[i]
254 if (narg == carg) continue;
255 var span = document.getElementById('md-' + currE.guid + '-' + i);
256 if (narg.isElement) {
257 if (carg.isElement) {
258 update(span, carg, narg);
259 } else {
260 span.style.display = 'block';
261 var e = render(narg);
262 span.innerHTML = '';
263 span.appendChild(e);
264 }
265 } else {
266 span.style.display = 'inline';
267 span.innerHTML = narg;
268 }
269 }
270 break;
271 case "Image":
272 if (nextE._0.ctor === 'Plain') {
273 if (nextE._3 !== currE._3) node.src = nextE._3;
274 } else if (!eq(nextE,currE) ||
275 next.props.width !== curr.props.width ||
276 next.props.height !== curr.props.height) {
277 node.parentNode.replaceChild(render(next),node);
278 return true;
279 }
280 break;
281 case "Flow":
282 var arr = fromList(nextE._1);
283 for (var i = arr.length; i--; ) { arr[i] = arr[i].element.ctor; }
284 if (nextE._0.ctor !== currE._0.ctor) {
285 node.parentNode.replaceChild(render(next),node);
286 return true;
287 }
288 var nexts = fromList(nextE._1);
289 var kids = node.childNodes;
290 if (nexts.length !== kids.length) {
291 node.parentNode.replaceChild(render(next),node);
292 return true;
293 }
294 var currs = fromList(currE._1);
295 var goDir = function(x) { return x; };
296 switch(nextE._0.ctor) {
297 case "DDown": case "DUp": goDir = goDown; break;
298 case "DRight": case "DLeft": goDir = goRight; break;
299 case "DOut": case "DIn": goDir = goIn; break;
300 }
301 for (var i = kids.length; i-- ;) {
302 update(kids[i],currs[i],nexts[i]);
303 goDir(kids[i]);
304 }
305 break;
306 case "Container":
307 update(node.firstChild, currE._1, nextE._1);
308 setPos(nextE._0, nextE._1, node.firstChild);
309 break;
310 case "Custom":
311 if (currE.type === nextE.type) {
312 var done = nextE.update(node, currE.model, nextE.model);
313 if (done) return;
314 } else {
315 return node.parentNode.replaceChild(render(next), node);
316 }
317 }
318 updateProps(node, curr, next);
321function updateProps(node, curr, next) {
322 var props = next.props;
323 var currP = curr.props;
324 var e = node;
325 var element = next.element;
326 var width = props.width - (element.adjustWidth || 0);
327 var height = props.height - (element.adjustHeight || 0);
328 if (width !== currP.width) {
329 e.style.width = (width|0) + 'px';
330 }
331 if (height !== currP.height) {
332 e.style.height = (height|0) + 'px';
333 }
334 if (props.opacity !== 1 && props.opacity !== currP.opacity) {
335 e.style.opacity = props.opacity;
336 }
337 var nextColor = (props.color.ctor === 'Just' ?
338 extract(props.color._0) : '');
339 if (e.style.backgroundColor !== nextColor) {
340 e.style.backgroundColor = (nextColor === '' ? 'transparent' : nextColor);
341 }
342 if (props.tag !== currP.tag) { e.id = props.tag; }
343 if (props.href !== currP.href) {
344 if (currP.href === '') {
345 var a = newElement('a');
346 a.href = props.href;
347 a.style.width = '100%';
348 a.style.height = '100%';
349 a.style.top = 0;
350 a.style.left = 0;
351 a.style.display = 'block';
352 a.style.position = 'absolute';
353 e.style.position = 'relative';
354 e.appendChild(a);
355 } else {
356 node.lastNode.href = props.href;
357 }
358 }
360 // update hover handlers
361 if (props.hover.ctor !== '_Tuple0') {
362 e.elm_hover_handler = props.hover;
363 } else if (e.elm_hover_handler) {
364 e.elm_hover_handler = null;
365 }
367 // update click handlers
368 if (props.click.ctor !== '_Tuple0') {
369 e.elm_click_handler = props.click;
370 } else if (e.elm_click_handler) {
371 e.elm_click_handler = null;
372 }
375return { render:render, update:update };
377};