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