on fdcaaed help sign in
elk/runtime/core.js
502 lines 14 kB history raw
2var Elm = {
3 Native: {}
4};
6(function() {
7'use strict';
9var Display = { FULLSCREEN: 0, COMPONENT: 1, NONE: 2 };
11Elm.fullscreen = function(module, ports) {
12 var container = document.createElement('div');
13 document.body.appendChild(container);
14 return init(Display.FULLSCREEN, container, module, ports || {});
15};
17Elm.embed = function(module, container, ports) {
18 var tag = container.tagName;
19 if (tag !== 'DIV') {
20 throw new Error('Elm.node must be given a DIV, not a ' + tag + '.');
21 } else if (container.hasChildNodes()) {
22 throw new Error('Elm.node must be given an empty DIV. No children allowed!');
23 }
24 return init(Display.COMPONENT, container, module, ports || {});
25};
27Elm.worker = function(module, ports) {
28 return init(Display.NONE, {}, module, ports || {});
29};
31function init(display, container, module, ports, moduleToReplace) {
32 // defining state needed for an instance of the Elm RTS
33 var inputs = [];
35 /* OFFSET
36 * Elm's time traveling debugger lets you interrupt the smooth flow of time
37 * by pausing and continuing program execution. To ensure the user sees a
38 * program that moves smoothly through the pause/continue time gap,
39 * we need to adjsut the value of Date.now().
40 */
41 var timer = function() {
42 var inducedDelay = 0;
44 var now = function() {
45 return Date.now() - inducedDelay;
46 };
48 var addDelay = function(d) {
49 inducedDelay += d;
50 return inducedDelay;
51 };
53 return { now : now
54 , addDelay : addDelay
55 }
56 }();
58 var updateInProgress = false;
59 function notify(id, v) {
60 if (updateInProgress) {
61 throw new Error(
62 'The notify function has been called synchronously!\n' +
63 'This can lead to frames being dropped.\n' +
64 'Definitely report this to <https://github.com/elm-lang/Elm/issues>\n');
65 }
66 updateInProgress = true;
67 var timestep = timer.now();
68 for (var i = inputs.length; i--; ) {
69 inputs[i].recv(timestep, id, v);
70 }
71 updateInProgress = false;
72 }
73 function setTimeout(func, delay) {
74 window.setTimeout(func, delay);
75 }
77 var listeners = [];
78 function addListener(relevantInputs, domNode, eventName, func) {
79 domNode.addEventListener(eventName, func);
80 var listener = {
81 relevantInputs: relevantInputs,
82 domNode: domNode,
83 eventName: eventName,
84 func: func
85 };
86 listeners.push(listener);
87 }
89 var portUses = {}
90 for (var key in ports) {
91 portUses[key] = 0;
92 }
93 // create the actual RTS. Any impure modules will attach themselves to this
94 // object. This permits many Elm programs to be embedded per document.
95 var elm = {
96 notify: notify,
97 setTimeout: setTimeout,
98 node: container,
99 addListener: addListener,
100 inputs: inputs,
101 timer: timer,
102 ports: { incoming:ports, outgoing:{}, uses:portUses },
104 isFullscreen: function() { return display === Display.FULLSCREEN; },
105 isEmbed: function() { return display === Display.COMPONENT; },
106 isWorker: function() { return display === Display.NONE; }
107 };
109 function swap(newModule) {
110 removeListeners(listeners);
111 var div = document.createElement('div');
112 var newElm = init(display, div, newModule, ports, elm);
113 inputs = [];
114 // elm.swap = newElm.swap;
115 return newElm;
116 }
118 function dispose() {
119 removeListeners(listeners);
120 inputs = [];
121 }
123 var Module = {};
124 var reportAnyErrors = function() {};
125 try {
126 Module = module.make(elm);
127 checkPorts(elm);
128 } catch(e) {
129 var directions = "<br/>&nbsp; &nbsp; Open the developer console for more details."
130 Module.main = Elm.Text.make(elm).leftAligned('<code>' + e.message + directions + '</code>');
131 reportAnyErrors = function() { throw e; }
132 }
133 inputs = filterDeadInputs(inputs);
134 filterListeners(inputs, listeners);
135 addReceivers(elm.ports.outgoing);
136 if (display !== Display.NONE) {
137 var graphicsNode = initGraphics(elm, Module);
138 }
139 if (typeof moduleToReplace !== 'undefined') {
140 hotSwap(moduleToReplace, elm);
142 // rerender scene if graphics are enabled.
143 if (typeof graphicsNode !== 'undefined') {
144 graphicsNode.recv(0, true, 0);
145 }
146 }
148 reportAnyErrors();
149 return {
150 swap:swap,
151 ports:elm.ports.outgoing,
152 dispose:dispose
153 };
154};
156function checkPorts(elm) {
157 var portUses = elm.ports.uses;
158 for (var key in portUses) {
159 var uses = portUses[key]
160 if (uses === 0) {
161 throw new Error(
162 "Initialization Error: provided port '" + key +
163 "' to a module that does not take it as in input.\n" +
164 "Remove '" + key + "' from the module initialization code.");
165 } else if (uses > 1) {
166 throw new Error(
167 "Initialization Error: port '" + key +
168 "' has been declared multiple times in the Elm code.\n" +
169 "Remove declarations until there is exactly one.");
170 }
171 }
175//// FILTER SIGNALS ////
177// TODO: move this code into the signal module and create a function
178// Signal.initializeGraph that actually instantiates everything.
180function filterListeners(inputs, listeners) {
181 loop:
182 for (var i = listeners.length; i--; ) {
183 var listener = listeners[i];
184 for (var j = inputs.length; j--; ) {
185 if (listener.relevantInputs.indexOf(inputs[j].id) >= 0) {
186 continue loop;
187 }
188 }
189 listener.domNode.removeEventListener(listener.eventName, listener.func);
190 }
193function removeListeners(listeners) {
194 for (var i = listeners.length; i--; ) {
195 var listener = listeners[i];
196 listener.domNode.removeEventListener(listener.eventName, listener.func);
197 }
200// add receivers for built-in ports if they are defined
201function addReceivers(ports) {
202 if ('log' in ports) {
203 ports.log.subscribe(function(v) { console.log(v) });
204 }
205 if ('stdout' in ports) {
206 var process = process || {};
207 var handler = process.stdout
208 ? function(v) { process.stdout.write(v); }
209 : function(v) { console.log(v); };
210 ports.stdout.subscribe(handler);
211 }
212 if ('stderr' in ports) {
213 var process = process || {};
214 var handler = process.stderr
215 ? function(v) { process.stderr.write(v); }
216 : function(v) { console.log('Error:' + v); };
217 ports.stderr.subscribe(handler);
218 }
219 if ('title' in ports) {
220 if (typeof ports.title === 'string') {
221 document.title = ports.title;
222 } else {
223 ports.title.subscribe(function(v) { document.title = v; });
224 }
225 }
226 if ('redirect' in ports) {
227 ports.redirect.subscribe(function(v) {
228 if (v.length > 0) window.location = v;
229 });
230 }
231 if ('favicon' in ports) {
232 if (typeof ports.favicon === 'string') {
233 changeFavicon(ports.favicon);
234 } else {
235 ports.favicon.subscribe(changeFavicon);
236 }
237 }
238 function changeFavicon(src) {
239 var link = document.createElement('link');
240 var oldLink = document.getElementById('elm-favicon');
241 link.id = 'elm-favicon';
242 link.rel = 'shortcut icon';
243 link.href = src;
244 if (oldLink) {
245 document.head.removeChild(oldLink);
246 }
247 document.head.appendChild(link);
248 }
252function filterDeadInputs(inputs) {
253 var temp = [];
254 for (var i = inputs.length; i--; ) {
255 if (isAlive(inputs[i])) temp.push(inputs[i]);
256 }
257 return temp;
261function isAlive(input) {
262 if (!('defaultNumberOfKids' in input)) return true;
263 var len = input.kids.length;
264 if (len === 0) return false;
265 if (len > input.defaultNumberOfKids) return true;
266 var alive = false;
267 for (var i = len; i--; ) {
268 alive = alive || isAlive(input.kids[i]);
269 }
270 return alive;
274//// RENDERING ////
276function initGraphics(elm, Module) {
277 if (!('main' in Module)) {
278 throw new Error("'main' is missing! What do I display?!");
279 }
281 var signalGraph = Module.main;
283 // make sure the signal graph is actually a signal & extract the visual model
284 var Signal = Elm.Native.Signal.make(elm);
285 if (!('recv' in signalGraph)) {
286 signalGraph = Signal.constant(signalGraph);
287 }
288 var currentScene = signalGraph.value;
290 // Add the currentScene to the DOM
291 var Element = Elm.Native.Graphics.Element.make(elm);
292 elm.node.appendChild(Element.render(currentScene));
294 // set up updates so that the DOM is adjusted as necessary.
295 var savedScene = currentScene;
296 var previousDrawId = 0;
297 function domUpdate(newScene) {
298 previousDrawId = draw(previousDrawId, function(_) {
299 Element.update(elm.node.firstChild, savedScene, newScene);
300 if (elm.Native.Window) elm.Native.Window.values.resizeIfNeeded();
301 savedScene = newScene;
302 });
303 }
304 var renderer = A2(Signal.lift, domUpdate, signalGraph);
306 // must check for resize after 'renderer' is created so
307 // that changes show up.
308 if (elm.Native.Window) elm.Native.Window.values.resizeIfNeeded();
310 return renderer;
314// define function for drawing efficiently
315//
316// draw : RequestID -> (() -> ()) -> RequestID
317//
318// Takes a "RequestID" allowing you to cancel old requests if possible.
319// Returns a "RequestID" so you can refer to past requests.
320//
321function draw(previousRequestID, callback) {
322 callback();
323 return previousRequestID;
326var vendors = ['ms', 'moz', 'webkit', 'o'];
327var win = typeof window !== 'undefined' ? window : {};
328for (var i = 0; i < vendors.length && !win.requestAnimationFrame; ++i) {
329 win.requestAnimationFrame = win[vendors[i]+'RequestAnimationFrame'];
330 win.cancelAnimationFrame = win[vendors[i]+'CancelAnimationFrame'] ||
331 win[vendors[i]+'CancelRequestAnimationFrame'];
333if (win.requestAnimationFrame && win.cancelAnimationFrame) {
334 draw = function(previousRequestID, callback) {
335 win.cancelAnimationFrame(previousRequestID);
336 return win.requestAnimationFrame(callback);
337 };
341//// HOT SWAPPING ////
343// Returns boolean indicating if the swap was successful.
344// Requires that the two signal graphs have exactly the same
345// structure.
346function hotSwap(from, to) {
347 function similar(nodeOld,nodeNew) {
348 var idOkay = nodeOld.id === nodeNew.id;
349 var lengthOkay = nodeOld.kids.length === nodeNew.kids.length;
350 return idOkay && lengthOkay;
351 }
352 function swap(nodeOld,nodeNew) {
353 nodeNew.value = nodeOld.value;
354 return true;
355 }
356 var canSwap = depthFirstTraversals(similar, from.inputs, to.inputs);
357 if (canSwap) {
358 depthFirstTraversals(swap, from.inputs, to.inputs);
359 }
360 from.node.parentNode.replaceChild(to.node, from.node);
362 return canSwap;
365// Returns false if the node operation f ever fails.
366function depthFirstTraversals(f, queueOld, queueNew) {
367 if (queueOld.length !== queueNew.length) return false;
368 queueOld = queueOld.slice(0);
369 queueNew = queueNew.slice(0);
371 var seen = [];
372 while (queueOld.length > 0 && queueNew.length > 0) {
373 var nodeOld = queueOld.pop();
374 var nodeNew = queueNew.pop();
375 if (seen.indexOf(nodeOld.id) < 0) {
376 if (!f(nodeOld, nodeNew)) return false;
377 queueOld = queueOld.concat(nodeOld.kids);
378 queueNew = queueNew.concat(nodeNew.kids);
379 seen.push(nodeOld.id);
380 }
381 }
382 return true;
385}());
388function F2(fun) {
389 function wrapper(a) { return function(b) { return fun(a,b) } }
390 wrapper.arity = 2;
391 wrapper.func = fun;
392 return wrapper;
395function F3(fun) {
396 function wrapper(a) {
397 return function(b) { return function(c) { return fun(a,b,c) }}
398 }
399 wrapper.arity = 3;
400 wrapper.func = fun;
401 return wrapper;
404function F4(fun) {
405 function wrapper(a) { return function(b) { return function(c) {
406 return function(d) { return fun(a,b,c,d) }}}
407 }
408 wrapper.arity = 4;
409 wrapper.func = fun;
410 return wrapper;
413function F5(fun) {
414 function wrapper(a) { return function(b) { return function(c) {
415 return function(d) { return function(e) { return fun(a,b,c,d,e) }}}}
416 }
417 wrapper.arity = 5;
418 wrapper.func = fun;
419 return wrapper;
422function F6(fun) {
423 function wrapper(a) { return function(b) { return function(c) {
424 return function(d) { return function(e) { return function(f) {
425 return fun(a,b,c,d,e,f) }}}}}
426 }
427 wrapper.arity = 6;
428 wrapper.func = fun;
429 return wrapper;
432function F7(fun) {
433 function wrapper(a) { return function(b) { return function(c) {
434 return function(d) { return function(e) { return function(f) {
435 return function(g) { return fun(a,b,c,d,e,f,g) }}}}}}
436 }
437 wrapper.arity = 7;
438 wrapper.func = fun;
439 return wrapper;
442function F8(fun) {
443 function wrapper(a) { return function(b) { return function(c) {
444 return function(d) { return function(e) { return function(f) {
445 return function(g) { return function(h) {return fun(a,b,c,d,e,f,g,h)}}}}}}}
446 }
447 wrapper.arity = 8;
448 wrapper.func = fun;
449 return wrapper;
452function F9(fun) {
453 function wrapper(a) { return function(b) { return function(c) {
454 return function(d) { return function(e) { return function(f) {
455 return function(g) { return function(h) { return function(i) {
456 return fun(a,b,c,d,e,f,g,h,i) }}}}}}}}
457 }
458 wrapper.arity = 9;
459 wrapper.func = fun;
460 return wrapper;
463function A2(fun,a,b) {
464 return fun.arity === 2
465 ? fun.func(a,b)
466 : fun(a)(b);
468function A3(fun,a,b,c) {
469 return fun.arity === 3
470 ? fun.func(a,b,c)
471 : fun(a)(b)(c);
473function A4(fun,a,b,c,d) {
474 return fun.arity === 4
475 ? fun.func(a,b,c,d)
476 : fun(a)(b)(c)(d);
478function A5(fun,a,b,c,d,e) {
479 return fun.arity === 5
480 ? fun.func(a,b,c,d,e)
481 : fun(a)(b)(c)(d)(e);
483function A6(fun,a,b,c,d,e,f) {
484 return fun.arity === 6
485 ? fun.func(a,b,c,d,e,f)
486 : fun(a)(b)(c)(d)(e)(f);
488function A7(fun,a,b,c,d,e,f,g) {
489 return fun.arity === 7
490 ? fun.func(a,b,c,d,e,f,g)
491 : fun(a)(b)(c)(d)(e)(f)(g);
493function A8(fun,a,b,c,d,e,f,g,h) {
494 return fun.arity === 8
495 ? fun.func(a,b,c,d,e,f,g,h)
496 : fun(a)(b)(c)(d)(e)(f)(g)(h);
498function A9(fun,a,b,c,d,e,f,g,h,i) {
499 return fun.arity === 9
500 ? fun.func(a,b,c,d,e,f,g,h,i)
501 : fun(a)(b)(c)(d)(e)(f)(g)(h)(i);