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