on 564cf4e help sign in
elk/runtime/Render/Element.js
458 lines 14 kB history raw
2ElmRuntime.Render.Element = function() {
3'use strict';
5var Utils = ElmRuntime.use(ElmRuntime.Render.Utils);
6var newElement = Utils.newElement;
7var colorToCss = Utils.colorToCss;
8var addTransform = Utils.addTransform;
9var removeTransform = Utils.removeTransform;
10var fromList = Utils.fromList;
11var eq = Utils.eq;
13function setProps(elem, node) {
14 var props = elem.props;
16 var element = elem.element;
17 var width = props.width - (element.adjustWidth || 0);
18 var height = props.height - (element.adjustHeight || 0);
19 node.style.width = (width |0) + 'px';
20 node.style.height = (height|0) + 'px';
22 if (props.opacity !== 1) {
23 node.style.opacity = props.opacity;
24 }
26 if (props.color.ctor === 'Just') {
27 node.style.backgroundColor = colorToCss(props.color._0);
28 }
30 if (props.tag !== '') {
31 node.id = props.tag;
32 }
34 if (props.hover.ctor !== '_Tuple0') {
35 addHover(node, props.hover);
36 }
38 if (props.click.ctor !== '_Tuple0') {
39 addClick(node, props.click);
40 }
42 if (props.href !== '') {
43 var anchor = newElement('a');
44 anchor.href = props.href;
45 anchor.style.display = 'block';
46 anchor.style.pointerEvents = 'auto';
47 anchor.appendChild(node);
48 node = anchor;
49 }
51 return node;
54function addClick(e, handler) {
55 e.style.pointerEvents = 'auto';
56 e.elm_click_handler = handler;
57 function trigger(ev) {
58 e.elm_click_handler(Utils.Tuple0);
59 ev.stopPropagation();
60 }
61 e.elm_click_trigger = trigger;
62 e.addEventListener('click', trigger);
65function removeClick(e, handler) {
66 if (e.elm_click_trigger) {
67 e.removeEventListener('click', e.elm_click_trigger);
68 e.elm_click_trigger = null;
69 e.elm_click_handler = null;
70 }
73function addHover(e, handler) {
74 e.style.pointerEvents = 'auto';
75 e.elm_hover_handler = handler;
76 e.elm_hover_count = 0;
78 function over(evt) {
79 if (e.elm_hover_count++ > 0) return;
80 e.elm_hover_handler(true);
81 evt.stopPropagation();
82 }
83 function out(evt) {
84 if (e.contains(evt.toElement || evt.relatedTarget)) return;
85 e.elm_hover_count = 0;
86 e.elm_hover_handler(false);
87 evt.stopPropagation();
88 }
89 e.elm_hover_over = over;
90 e.elm_hover_out = out;
91 e.addEventListener('mouseover', over);
92 e.addEventListener('mouseout', out);
95function removeHover(e) {
96 e.elm_hover_handler = null;
97 if (e.elm_hover_over) {
98 e.removeEventListener('mouseover', e.elm_hover_over);
99 e.elm_hover_over = null;
100 }
101 if (e.elm_hover_out) {
102 e.removeEventListener('mouseout', e.elm_hover_out);
103 e.elm_hover_out = null;
104 }
107function image(props, img) {
108 switch (img._0.ctor) {
109 case 'Plain': return plainImage(img._3);
110 case 'Fitted': return fittedImage(props.width, props.height, img._3);
111 case 'Cropped': return croppedImage(img,props.width,props.height,img._3);
112 case 'Tiled': return tiledImage(img._3);
113 }
116function plainImage(src) {
117 var img = newElement('img');
118 img.src = src;
119 img.name = src;
120 img.style.display = "block";
121 return img;
124function tiledImage(src) {
125 var div = newElement('div');
126 div.style.backgroundImage = 'url(' + src + ')';
127 return div;
130function fittedImage(w, h, src) {
131 var div = newElement('div');
132 div.style.background = 'url(' + src + ') no-repeat center';
133 div.style.webkitBackgroundSize = 'cover';
134 div.style.MozBackgroundSize = 'cover';
135 div.style.OBackgroundSize = 'cover';
136 div.style.backgroundSize = 'cover';
137 return div;
140function croppedImage(elem, w, h, src) {
141 var pos = elem._0._0;
142 var e = newElement('div');
143 e.style.overflow = "hidden";
145 var img = newElement('img');
146 img.onload = function() {
147 var sw = w / elem._1, sh = h / elem._2;
148 img.style.width = ((this.width * sw)|0) + 'px';
149 img.style.height = ((this.height * sh)|0) + 'px';
150 img.style.marginLeft = ((- pos._0 * sw)|0) + 'px';
151 img.style.marginTop = ((- pos._1 * sh)|0) + 'px';
152 };
153 img.src = src;
154 img.name = src;
155 e.appendChild(img);
156 return e;
159function goOut(e) { e.style.position = 'absolute'; return e; }
160function goDown(e) { return e }
161function goRight(e) { e.style.styleFloat = e.style.cssFloat = "left"; return e; }
163var directionTable = {
164 DUp : goDown,
165 DDown : goDown,
166 DLeft : goRight,
167 DRight : goRight,
168 DIn : goOut,
169 DOut : goOut
170};
171function needsReversal(dir) {
172 return dir == 'DUp' || dir == 'DLeft' || dir == 'DIn';
175function flow(dir,elist) {
176 var array = fromList(elist);
177 var container = newElement('div');
178 var goDir = directionTable[dir];
179 if (goDir == goOut) {
180 container.style.pointerEvents = 'none';
181 }
182 if (needsReversal(dir)) {
183 array.reverse();
184 }
185 var len = array.length;
186 for (var i = 0; i < len; ++i) {
187 container.appendChild(goDir(render(array[i])));
188 }
189 return container;
192function toPos(pos) {
193 switch(pos.ctor) {
194 case "Absolute": return pos._0 + "px";
195 case "Relative": return (pos._0 * 100) + "%";
196 }
199// must clear right, left, top, bottom, and transform
200// before calling this function
201function setPos(pos,elem,e) {
202 var element = elem.element;
203 var props = elem.props;
204 var w = props.width + (element.adjustWidth ? element.adjustWidth : 0);
205 var h = props.height + (element.adjustHeight ? element.adjustHeight : 0);
207 e.style.position = 'absolute';
208 e.style.margin = 'auto';
209 var transform = '';
210 switch(pos.horizontal.ctor) {
211 case 'P': e.style.right = toPos(pos.x); e.style.removeProperty('left'); break;
212 case 'Z': transform = 'translateX(' + ((-w/2)|0) + 'px) ';
213 case 'N': e.style.left = toPos(pos.x); e.style.removeProperty('right'); break;
214 }
215 switch(pos.vertical.ctor) {
216 case 'N': e.style.bottom = toPos(pos.y); e.style.removeProperty('top'); break;
217 case 'Z': transform += 'translateY(' + ((-h/2)|0) + 'px)';
218 case 'P': e.style.top = toPos(pos.y); e.style.removeProperty('bottom'); break;
219 }
220 if (transform !== '') addTransform(e.style, transform);
221 return e;
224function container(pos,elem) {
225 var e = render(elem);
226 setPos(pos, elem, e);
227 var div = newElement('div');
228 div.style.position = 'relative';
229 div.style.overflow = 'hidden';
230 div.appendChild(e);
231 return div;
234function rawHtml(elem) {
235 var html = elem.html;
236 var args = elem.args;
237 var guid = elem.guid;
238 var align = elem.align;
240 var div = newElement('div');
241 div.innerHTML = html;
242 div.style.visibility = "hidden";
243 if (align) div.style.textAlign = align;
244 document.body.appendChild(div);
246 for (var i = args.length; i--; ) {
247 var arg = args[i];
248 var span = document.getElementById('md-' + guid + '-' + i);
249 if (arg.isText) {
250 span.innerHTML = arg;
251 } else {
252 span.style.display = 'block';
253 span.style.width = arg.props.width + 'px';
254 span.style.height = arg.props.height + 'px';
255 span.appendChild(render(arg));
256 }
257 }
258 document.body.removeChild(div);
259 div.style.visibility = 'visible';
260 div.style.pointerEvents = 'auto';
261 return div;
264function render(elem) {
265 return setProps(elem, makeElement(elem));
267function makeElement(e) {
268 var elem = e.element;
269 switch(elem.ctor) {
270 case 'Image': return image(e.props, elem);
271 case 'Flow': return flow(elem._0.ctor, elem._1);
272 case 'Container': return container(elem._0, elem._1);
273 case 'Spacer': return newElement('div');
274 case 'RawHtml': return rawHtml(elem);
275 case 'Custom': return elem.render(elem.model);
276 }
279function update(node, curr, next) {
280 if (node.tagName === 'A') {
281 node = node.firstChild;
282 }
283 if (curr.props.id === next.props.id) {
284 return updateProps(node, curr, next);
285 }
286 if (curr.element.ctor !== next.element.ctor) {
287 node.parentNode.replaceChild(render(next),node);
288 return true;
289 }
290 var nextE = next.element, currE = curr.element;
291 switch(nextE.ctor) {
292 case "Spacer": break;
293 case "RawHtml":
294 // only markdown blocks have guids, so this must be a text block
295 if (nextE.guid === null) {
296 if(currE.html.valueOf() !== nextE.html.valueOf()) {
297 node.innerHTML = nextE.html;
298 }
299 break;
300 }
301 if (nextE.guid !== currE.guid) {
302 node.parentNode.replaceChild(render(next),node);
303 return true;
304 }
305 var nargs = nextE.args;
306 var cargs = currE.args;
307 for (var i = nargs.length; i--; ) {
308 var narg = nargs[i];
309 var carg = cargs[i]
310 if (narg == carg) continue;
311 var span = document.getElementById('md-' + currE.guid + '-' + i);
312 if (narg.isElement) {
313 if (carg.isElement) {
314 update(span, carg, narg);
315 } else {
316 span.style.display = 'block';
317 var e = render(narg);
318 span.innerHTML = '';
319 span.appendChild(e);
320 }
321 } else {
322 span.style.display = 'inline';
323 span.innerHTML = narg;
324 }
325 }
326 break;
327 case "Image":
328 if (nextE._0.ctor === 'Plain') {
329 if (nextE._3 !== currE._3) node.src = nextE._3;
330 } else if (!eq(nextE,currE) ||
331 next.props.width !== curr.props.width ||
332 next.props.height !== curr.props.height) {
333 node.parentNode.replaceChild(render(next),node);
334 return true;
335 }
336 break;
337 case "Flow":
338 var arr = fromList(nextE._1);
339 for (var i = arr.length; i--; ) { arr[i] = arr[i].element.ctor; }
340 if (nextE._0.ctor !== currE._0.ctor) {
341 node.parentNode.replaceChild(render(next),node);
342 return true;
343 }
344 var nexts = fromList(nextE._1);
345 var kids = node.childNodes;
346 if (nexts.length !== kids.length) {
347 node.parentNode.replaceChild(render(next),node);
348 return true;
349 }
350 var currs = fromList(currE._1);
351 var dir = nextE._0.ctor;
352 var goDir = directionTable[dir];
353 var toReverse = needsReversal(dir);
354 var len = kids.length;
355 for (var i = len; i-- ;) {
356 update(kids[toReverse ? len - i - 1 : i],currs[i],nexts[i]);
357 goDir(kids[i]);
358 }
359 break;
360 case "Container":
361 update(node.firstChild, currE._1, nextE._1);
362 setPos(nextE._0, nextE._1, node.firstChild);
363 break;
364 case "Custom":
365 if (currE.type === nextE.type) {
366 var done = nextE.update(node, currE.model, nextE.model);
367 if (done) return;
368 } else {
369 return node.parentNode.replaceChild(render(next), node);
370 }
371 }
372 updateProps(node, curr, next);
375function updateProps(node, curr, next) {
376 var nextProps = next.props;
377 var currProps = curr.props;
379 var element = next.element;
380 var width = nextProps.width - (element.adjustWidth || 0);
381 var height = nextProps.height - (element.adjustHeight || 0);
382 if (width !== currProps.width) {
383 node.style.width = (width|0) + 'px';
384 }
385 if (height !== currProps.height) {
386 node.style.height = (height|0) + 'px';
387 }
389 if (nextProps.opacity !== currProps.opacity) {
390 node.style.opacity = nextProps.opacity;
391 }
393 var nextColor = nextProps.color.ctor === 'Just'
394 ? colorToCss(nextProps.color._0)
395 : 'transparent';
396 if (node.style.backgroundColor !== nextColor) {
397 node.style.backgroundColor = nextColor;
398 }
400 if (nextProps.tag !== currProps.tag) {
401 node.id = nextProps.tag;
402 }
404 if (nextProps.href !== currProps.href) {
405 if (currProps.href === '') {
406 // add a surrounding href
407 var anchor = newElement('a');
408 anchor.href = nextProps.href;
409 anchor.style.display = 'block';
410 anchor.style.pointerEvents = 'auto';
412 node.parentNode.replaceChild(anchor, node);
413 anchor.appendChild(node);
414 } else if (nextProps.href === '') {
415 // remove the surrounding href
416 var anchor = node.parentNode;
417 anchor.parentNode.replaceChild(node, anchor);
418 } else {
419 // just update the link
420 node.parentNode.href = nextProps.href;
421 }
422 }
424 // update click and hover handlers
425 var removed = false;
427 // update hover handlers
428 if (nextProps.hover.ctor === '_Tuple0') {
429 removed = true;
430 removeHover(node);
431 } else if (node.elm_hover_handler) {
432 node.elm_hover_handler = nextProps.hover;
433 } else {
434 addHover(node, nextProps.hover);
435 }
437 // update click handlers
438 if (nextProps.click.ctor === '_Tuple0') {
439 removed = true;
440 removeClick(node);
441 } else if (node.elm_click_handler) {
442 node.elm_click_handler = nextProps.click;
443 } else {
444 addClick(node, nextProps.click);
445 }
447 // stop capturing clicks if
448 if (removed
449 && nextProps.hover.ctor === '_Tuple0'
450 && nextProps.click.ctor === '_Tuple0')
451 {
452 node.style.pointerEvents = 'none';
453 }
456return { render:render, update:update };
458};