on dbff625 help sign in
elk/runtime/Render/Element.js
405 lines 13 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 addHover(e, props.hover);
38 }
39 if (props.click.ctor !== '_Tuple0') {
40 addClick(e, props.click);
41 }
42 return e;
45function addClick(e, handler) {
46 e.style.pointerEvents = 'auto';
47 e.elm_click_handler = handler;
48 function trigger() {
49 e.elm_click_handler(Utils.Tuple0);
50 }
51 e.elm_click_trigger = trigger;
52 e.addEventListener('click', trigger);
55function removeClick(e, handler) {
56 if (e.elm_click_trigger) {
57 e.removeEventListener('click', e.elm_click_trigger);
58 }
61function addHover(e, handler) {
62 e.style.pointerEvents = 'auto';
63 e.elm_hover_handler = handler;
64 e.elm_hover_count = 0;
66 function over() {
67 if (e.elm_hover_count++ > 0) return;
68 e.elm_hover_handler(true);
69 }
70 function out(evt) {
71 if (e.contains(evt.toElement || evt.relatedTarget)) return;
72 e.elm_hover_count = 0;
73 e.elm_hover_handler(false);
74 }
75 e.elm_hover_over = over;
76 e.elm_hover_out = out;
77 e.addEventListener('mouseover', over);
78 e.addEventListener('mouseout', out);
81function removeHover(e) {
82 if (e.elm_hover_over) {
83 e.removeEventListener('mouseover', e.elm_hover_over);
84 }
85 if (e.elm_hover_out) {
86 e.removeEventListener('mouseout', e.elm_hover_out);
87 }
90function image(props, img) {
91 switch (img._0.ctor) {
92 case 'Plain': return plainImage(img._3);
93 case 'Fitted': return fittedImage(props.width, props.height, img._3);
94 case 'Cropped': return croppedImage(img,props.width,props.height,img._3);
95 case 'Tiled': return tiledImage(img._3);
96 }
99function plainImage(src) {
100 var img = newElement('img');
101 img.src = src;
102 img.name = src;
103 img.style.display = "block";
104 return img;
107function tiledImage(src) {
108 var div = newElement('div');
109 div.style.backgroundImage = 'url(' + src + ')';
110 return div;
113function fittedImage(w, h, src) {
114 var div = newElement('div');
115 div.style.background = 'url(' + src + ') no-repeat center';
116 div.style.webkitBackgroundSize = 'cover';
117 div.style.MozBackgroundSize = 'cover';
118 div.style.OBackgroundSize = 'cover';
119 div.style.backgroundSize = 'cover';
120 return div;
123function croppedImage(elem, w, h, src) {
124 var pos = elem._0._0;
125 var e = newElement('div');
126 e.style.overflow = "hidden";
128 var img = newElement('img');
129 img.onload = function() {
130 var sw = w / elem._1, sh = h / elem._2;
131 img.style.width = ((this.width * sw)|0) + 'px';
132 img.style.height = ((this.height * sh)|0) + 'px';
133 img.style.marginLeft = ((- pos._0 * sw)|0) + 'px';
134 img.style.marginTop = ((- pos._1 * sh)|0) + 'px';
135 };
136 img.src = src;
137 img.name = src;
138 e.appendChild(img);
139 return e;
142function goIn(e) { e.style.position = 'absolute'; return e; }
143function goDown(e) { return e }
144function goRight(e) { e.style.styleFloat = e.style.cssFloat = "left"; return e; }
145function flowWith(f, array) {
146 var container = newElement('div');
147 if (f == goIn) container.style.pointerEvents = 'none';
149 for (var i = array.length; i--; ) {
150 container.appendChild(f(render(array[i])));
151 }
152 return container;
155function flow(dir,elist) {
156 var array = fromList(elist);
157 switch(dir.ctor) {
158 case "DDown": array.reverse();
159 case "DUp": return flowWith(goDown,array);
160 case "DRight": array.reverse();
161 case "DLeft": return flowWith(goRight,array);
162 case "DOut": array.reverse();
163 case "DIn": return flowWith(goIn,array);
164 }
167function toPos(pos) {
168 switch(pos.ctor) {
169 case "Absolute": return pos._0 + "px";
170 case "Relative": return (pos._0 * 100) + "%";
171 }
174// must clear right, left, top, bottom, and transform
175// before calling this function
176function setPos(pos,elem,e) {
177 var element = elem.element;
178 var props = elem.props;
179 var w = props.width + (element.adjustWidth ? element.adjustWidth : 0);
180 var h = props.height + (element.adjustHeight ? element.adjustHeight : 0);
182 e.style.position = 'absolute';
183 e.style.margin = 'auto';
184 var transform = '';
185 switch(pos.horizontal.ctor) {
186 case 'P': e.style.right = toPos(pos.x); e.style.removeProperty('left'); break;
187 case 'Z': transform = 'translateX(' + ((-w/2)|0) + 'px) ';
188 case 'N': e.style.left = toPos(pos.x); e.style.removeProperty('right'); break;
189 }
190 switch(pos.vertical.ctor) {
191 case 'N': e.style.bottom = toPos(pos.y); e.style.removeProperty('top'); break;
192 case 'Z': transform += 'translateY(' + ((-h/2)|0) + 'px)';
193 case 'P': e.style.top = toPos(pos.y); e.style.removeProperty('bottom'); break;
194 }
195 if (transform !== '') addTransform(e.style, transform);
196 return e;
199function container(pos,elem) {
200 var e = render(elem);
201 setPos(pos, elem, e);
202 var div = newElement('div');
203 div.style.position = 'relative';
204 div.style.overflow = 'hidden';
205 div.appendChild(e);
206 return div;
209function rawHtml(elem) {
210 var html = elem.html;
211 var args = elem.args;
212 var guid = elem.guid;
213 var align = elem.align;
215 var div = newElement('div');
216 div.innerHTML = html;
217 div.style.visibility = "hidden";
218 if (align) div.style.textAlign = align;
219 document.body.appendChild(div);
221 for (var i = args.length; i--; ) {
222 var arg = args[i];
223 var span = document.getElementById('md-' + guid + '-' + i);
224 if (arg.isText) {
225 span.innerHTML = arg;
226 } else {
227 span.style.display = 'block';
228 span.style.width = arg.props.width + 'px';
229 span.style.height = arg.props.height + 'px';
230 span.appendChild(render(arg));
231 }
232 }
233 document.body.removeChild(div);
234 div.style.visibility = 'visible';
235 return div;
238function render(elem) { return setProps(elem, makeElement(elem)); }
239function makeElement(e) {
240 var elem = e.element;
241 switch(elem.ctor) {
242 case 'Image': return image(e.props, elem);
243 case 'Flow': return flow(elem._0, elem._1);
244 case 'Container': return container(elem._0, elem._1);
245 case 'Spacer': return newElement('div');
246 case 'RawHtml': return rawHtml(elem);
247 case 'Custom': return elem.render(elem.model);
248 }
251function update(node, curr, next) {
252 if (node.tagName === 'A') { node = node.firstChild; }
253 if (curr.props.id === next.props.id) return updateProps(node, curr, next);
254 if (curr.element.ctor !== next.element.ctor) {
255 node.parentNode.replaceChild(render(next),node);
256 return true;
257 }
258 var nextE = next.element, currE = curr.element;
259 switch(nextE.ctor) {
260 case "Spacer": break;
261 case "RawHtml":
262 // only markdown blocks have guids, so this must be a text block
263 if (nextE.guid === null) {
264 if(currE.html.valueOf() !== nextE.html.valueOf()) {
265 node.innerHTML = nextE.html;
266 }
267 break;
268 }
269 if (nextE.guid !== currE.guid) {
270 node.parentNode.replaceChild(render(next),node);
271 return true;
272 }
273 var nargs = nextE.args;
274 var cargs = currE.args;
275 for (var i = nargs.length; i--; ) {
276 var narg = nargs[i];
277 var carg = cargs[i]
278 if (narg == carg) continue;
279 var span = document.getElementById('md-' + currE.guid + '-' + i);
280 if (narg.isElement) {
281 if (carg.isElement) {
282 update(span, carg, narg);
283 } else {
284 span.style.display = 'block';
285 var e = render(narg);
286 span.innerHTML = '';
287 span.appendChild(e);
288 }
289 } else {
290 span.style.display = 'inline';
291 span.innerHTML = narg;
292 }
293 }
294 break;
295 case "Image":
296 if (nextE._0.ctor === 'Plain') {
297 if (nextE._3 !== currE._3) node.src = nextE._3;
298 } else if (!eq(nextE,currE) ||
299 next.props.width !== curr.props.width ||
300 next.props.height !== curr.props.height) {
301 node.parentNode.replaceChild(render(next),node);
302 return true;
303 }
304 break;
305 case "Flow":
306 var arr = fromList(nextE._1);
307 for (var i = arr.length; i--; ) { arr[i] = arr[i].element.ctor; }
308 if (nextE._0.ctor !== currE._0.ctor) {
309 node.parentNode.replaceChild(render(next),node);
310 return true;
311 }
312 var nexts = fromList(nextE._1);
313 var kids = node.childNodes;
314 if (nexts.length !== kids.length) {
315 node.parentNode.replaceChild(render(next),node);
316 return true;
317 }
318 var currs = fromList(currE._1);
319 var goDir = function(x) { return x; };
320 switch(nextE._0.ctor) {
321 case "DDown": case "DUp": goDir = goDown; break;
322 case "DRight": case "DLeft": goDir = goRight; break;
323 case "DOut": case "DIn": goDir = goIn; break;
324 }
325 for (var i = kids.length; i-- ;) {
326 update(kids[i],currs[i],nexts[i]);
327 goDir(kids[i]);
328 }
329 break;
330 case "Container":
331 update(node.firstChild, currE._1, nextE._1);
332 setPos(nextE._0, nextE._1, node.firstChild);
333 break;
334 case "Custom":
335 if (currE.type === nextE.type) {
336 var done = nextE.update(node, currE.model, nextE.model);
337 if (done) return;
338 } else {
339 return node.parentNode.replaceChild(render(next), node);
340 }
341 }
342 updateProps(node, curr, next);
345function updateProps(node, curr, next) {
346 var props = next.props;
347 var currP = curr.props;
348 var e = node;
349 var element = next.element;
350 var width = props.width - (element.adjustWidth || 0);
351 var height = props.height - (element.adjustHeight || 0);
352 if (width !== currP.width) {
353 e.style.width = (width|0) + 'px';
354 }
355 if (height !== currP.height) {
356 e.style.height = (height|0) + 'px';
357 }
358 if (props.opacity !== currP.opacity) {
359 e.style.opacity = props.opacity;
360 }
361 var nextColor = (props.color.ctor === 'Just' ?
362 extract(props.color._0) : '');
363 if (e.style.backgroundColor !== nextColor) {
364 e.style.backgroundColor = (nextColor === '' ? 'transparent' : nextColor);
365 }
366 if (props.tag !== currP.tag) { e.id = props.tag; }
367 if (props.href !== currP.href) {
368 if (currP.href === '') {
369 var a = newElement('a');
370 a.href = props.href;
371 a.style.width = '100%';
372 a.style.height = '100%';
373 a.style.top = 0;
374 a.style.left = 0;
375 a.style.display = 'block';
376 a.style.position = 'absolute';
377 e.style.position = 'relative';
378 e.appendChild(a);
379 } else {
380 node.lastNode.href = props.href;
381 }
382 }
384 // update hover handlers
385 if (props.hover.ctor === '_Tuple0') {
386 removeHover(e);
387 } else if (e.elm_hover_handler) {
388 e.elm_hover_handler = props.hover;
389 } else {
390 addHover(e, props.hover);
391 }
393 // update click handlers
394 if (props.click.ctor === '_Tuple0') {
395 removeClick(e);
396 } else if (e.elm_click_handler) {
397 e.elm_click_handler = props.click;
398 } else {
399 addClick(e, props.click);
400 }
403return { render:render, update:update };
405};