Merge branch 'web-world'

Conflicts:

	scribblings/cs19.scrbl
This commit is contained in:
Danny Yoo 2011-08-29 09:39:32 -04:00
commit efc8dcdf9f
12 changed files with 752 additions and 106 deletions

View File

@ -174,7 +174,7 @@
(first sources))] (first sources))]
[(ast stmts) [(ast stmts)
(get-ast-and-statements this-source)]) (get-ast-and-statements this-source)])
(log-debug "visiting") (log-debug (format "visiting ~a\n" (source-name this-source)))
(on-module-statements this-source ast stmts) (on-module-statements this-source ast stmts)
(loop (append (map wrap-source (collect-new-dependencies this-source ast)) (loop (append (map wrap-source (collect-new-dependencies this-source ast))
(rest sources))) (rest sources)))

View File

@ -216,3 +216,4 @@ For example,
} }

View File

@ -213,7 +213,7 @@ will be:
---------------------------------------------------------------------- ----------------------------------------------------------------------
Example 3 Example 3 "field"
We want to make it easy to query from the view. That's why each We want to make it easy to query from the view. That's why each

View File

@ -1,8 +1,107 @@
<!DOCTYPE html> <html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head><title>Animation</title>
<head> <link rel="stylesheet" href="style.css"/>
<meta name="viewport" content="initial-scale=1.0, width=device-width, height=device-height, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" /> </head>
<meta charset="utf-8"/> <body>
<title></title> <p>
</head> This should be animating:
<script> <hr/>
<div>
<div class="selectedBlock" id="0"></div>
<div class="block" id="1"></div>
<div class="block" id="2"></div>
<div class="block" id="3"></div>
<div class="block" id="4"></div>
<div class="block" id="5"></div>
<div class="block" id="6"></div>
<div class="block" id="7"></div>
<div class="block" id="8"></div>
<div class="block" id="9"></div>
</div>
<hr/>
The program for this is:
<blockquote>
<pre>
#lang planet dyoo/whalesong
(require (planet dyoo/whalesong/resource)
(planet dyoo/whalesong/web-world))
(define-resource index.html)
(define-resource style.css)
(define (tick w v)
(modulo (add1 w) 10))
;; pick-block: world view -> view
;; Focus the view on block i.
(define (pick-block v i)
(view-focus v (format "#~a" i)))
(define (draw w v)
(define v1 (update-view-attr
(pick-block v w)
"class"
"selectedBlock"))
(define v2 (update-view-attr
(pick-block v1 (modulo (sub1 w) 10))
"class"
"offsetBlock"))
(define v3 (update-view-attr
(pick-block v2 (modulo (add1 w) 10))
"class"
"offsetBlock"))
(define v4 (update-view-attr
(pick-block v3 (modulo (- w 2) 10))
"class"
"block"))
(define v5 (update-view-attr
(pick-block v4 (modulo (+ w 2) 10))
"class"
"block"))
v5)
(big-bang 0
(initial-view index.html)
(on-tick tick)
(to-draw draw))
</pre>
</blockquote>
with <tt>style.css</tt>:
<blockquote>
<pre>
.block {
width : 80px;
height : 10px;
background-color : blue;
display: inline-block;
}
.selectedBlock {
width : 80px;
height : 10px;
background-color: navy;
display: inline-block;
}
.offsetBlock {
width : 80px;
height : 10px;
background-color: teal;
display: inline-block;
}
</pre>
</blockquote>
</p>
</body>
</html>

View File

@ -0,0 +1,53 @@
#lang planet dyoo/whalesong
(require (planet dyoo/whalesong/web-world)
(planet dyoo/whalesong/resource))
(define-resource index.html)
;; The world is the set of dwarfs.
;; make-item: string -> view
(define (make-item name)
(view-bind (->view `(li ,name))
"click"
hide-on-click))
;; When a dwarf clicks, it hides!
(define (hide-on-click w v)
(remove (view-id v) w))
(define dwarf-names
'("Doc" "Grumpy" "Happy" "Sleepy" "Bashful" "Sneezy" "Dopey"))
;; Update the view so it shows the next dwarf on the scene,
;; until we're all done.
(define (draw w dom-view)
(foldl (lambda (name view)
(define focused (view-focus view (format "#~a" name)))
(cond
[(member name w)
(view-show focused)]
[else
(view-hide focused)]))
dom-view
dwarf-names))
;; The first view consists of index.html. We attach event handlers
;; to each name here.
(define my-view
(foldl (lambda (name view)
(view-bind (view-focus view (format "#~a" name))
"click"
hide-on-click))
(->view index.html)
dwarf-names))
(big-bang dwarf-names
(initial-view my-view)
(to-draw draw))

View File

@ -0,0 +1,16 @@
<html>
<head><title>Dwarves</title></head>
<body>
<h1>Dwarfs from Snow White</h1>
<p>Click on a dwarf to make them hide.</p>
<ul id="list">
<li id="Doc">Doc</li>
<li id="Grumpy">Grumpy</li>
<li id="Happy">Happy</li>
<li id="Sleepy">Sleepy</li>
<li id="Bashful">Bashful</li>
<li id="Sneezy">Sneezy</li>
<li id="Dopey">Dopey</li>
</ul>
</body>
</html>

View File

@ -0,0 +1,31 @@
#lang planet dyoo/whalesong
(require (planet dyoo/whalesong/web-world)
(planet dyoo/whalesong/resource))
(define-resource index.html)
;; The world is a string which represents the name of the user.
;; on-click: world view -> world
;; When the user clicks on the button, grab at the text of the
;; text-field.
(define (on-click w button-view)
(view-form-value (view-focus button-view "#text-field")))
;; draw: world view -> view
;; Take the view, and replace the template with the world value.
(define (draw w dom)
(update-view-text (view-focus dom "#template")
w))
(define my-view (view-bind (view-focus (->view index.html)
"#button")
"click"
on-click))
(big-bang "Jane Doe"
(initial-view my-view)
(to-draw draw))

View File

@ -0,0 +1,12 @@
<html>
<head>
<title>My simple program</title>
</head>
<body>
<input type="text" id="text-field"/>
<input type="button" id="button" value="Click me"/>
<p>Hello <span id="template">fill-me-in</span>!</p>
</body>
</html>

View File

@ -35,7 +35,17 @@
view-text view-text
update-view-text update-view-text
view-bind
view-show
view-hide
view-attr view-attr
update-view-attr update-view-attr
view-id
view-form-value
update-view-form-value
view-append-child
)) ))

3
web-world/info.rkt Normal file
View File

@ -0,0 +1,3 @@
#lang setup/infotab
(define compile-omit-paths '("examples"))

View File

@ -11,10 +11,45 @@
var isString = plt.baselib.strings.isString; var isString = plt.baselib.strings.isString;
// FIXME: as soon as we get real parameters, use parameters
// instead. Global: defines the currently running big bang.
// Parameterized around the call to bigBang.
var currentBigBangRecord = undefined;
var resourceStructType = var resourceStructType =
MACHINE.modules['whalesong/resource/structs.rkt'].namespace['struct:resource']; MACHINE.modules['whalesong/resource/structs.rkt'].namespace['struct:resource'];
var domToCursor = function(dom) {
var domOpenF =
// To go down, just take the children.
function(n) {
return [].slice.call(n.childNodes, 0);
};
var domCloseF =
// To go back up, take the node, do a shallow cloning, and replace the children.
function(node, children) {
var i;
var newNode = node.cloneNode(false);
for (i = 0; i < children.length; i++) {
newNode.appendChild(children[i].cloneNode(true));
}
return newNode;
};
var domAtomicF =
function(node) {
return node.nodeType !== 1;
};
return TreeCursor.adaptTreeCursor(dom.cloneNode(true),
domOpenF,
domCloseF,
domAtomicF);
};
@ -69,18 +104,24 @@
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
var MockView = function(cursor, pendingActions, nonce) { // A MockView provides a functional interface to the DOM. It
// includes a cursor to the currently focused dom, the pending
// actions to perform on the actual view, and a nonce to detect
// freshness of the MockView.
var MockView = function(cursor, pendingActions, eventHandlers, nonce) {
this.cursor = cursor; this.cursor = cursor;
this.pendingActions = pendingActions; this.pendingActions = pendingActions;
this.eventHandlers = eventHandlers;
this.nonce = nonce; this.nonce = nonce;
}; };
var isMockView = plt.baselib.makeClassPredicate(MockView); var isMockView = plt.baselib.makeClassPredicate(MockView);
MockView.prototype.act = function(actionForCursor, actionForReal) { MockView.prototype.act = function(actionForCursor, actionForEventHandlers, actionForReal) {
if (arguments.length !== 2) { throw new Error("act: insufficient arguments"); } if (arguments.length !== 3) { throw new Error("act: insufficient arguments"); }
return new MockView(actionForCursor(this.cursor), return new MockView(actionForCursor(this.cursor),
this.pendingActions.concat([actionForReal]), this.pendingActions.concat([actionForReal]),
actionForEventHandlers(this.eventHandlers),
this.nonce); this.nonce);
}; };
@ -100,6 +141,7 @@
} }
} }
}, },
function(eventHandlers) { return eventHandlers; },
function(view) { function(view) {
view.focus = view.top.find(selector); view.focus = view.top.find(selector);
} }
@ -115,30 +157,59 @@
function(cursor) { function(cursor) {
return cursor.replaceNode($(cursor.node).clone(true).text(text).get(0)); return cursor.replaceNode($(cursor.node).clone(true).text(text).get(0));
}, },
function(eventHandlers) { return eventHandlers; },
function(view) { function(view) {
view.focus.text(text); view.focus.text(text);
}) }
)
}; };
MockView.prototype.getAttr = function(name) { MockView.prototype.getAttr = function(name) {
return $(this.cursor.node).attr(name); return $(this.cursor.node).attr(name);
}; };
MockView.prototype.updateAttr = function(name, value) { MockView.prototype.updateAttr = function(name, value) {
return this.act( return this.act(
function(cursor) { function(cursor) {
return cursor.replaceNode($(cursor.node).clone(true).attr(name, value).get(0)); return cursor.replaceNode($(cursor.node).clone(true).attr(name, value).get(0));
}, },
function(eventHandlers) {
return eventHandlers;
},
function(view) { function(view) {
view.focus.attr(name, value); view.focus.attr(name, value);
}) })
}; };
MockView.prototype.getFormValue = function() {
return $(this.cursor.node).val();
};
MockView.prototype.updateFormValue = function(value) {
return this.act(
function(cursor) {
return cursor.replaceNode($(cursor.node).clone(true).val(value).get(0));
},
function(eventHandlers) {
return eventHandlers;
},
function(view) {
view.focus.val(value);
})
};
MockView.prototype.left = function() { MockView.prototype.left = function() {
return this.act( return this.act(
function(cursor) { function(cursor) {
return cursor.left(); return cursor.left();
}, },
function(eventHandlers) {
return eventHandlers;
},
function(view) { function(view) {
view.focus = view.focus.prev(); view.focus = view.focus.prev();
}); });
@ -149,6 +220,9 @@
function(cursor) { function(cursor) {
return cursor.right(); return cursor.right();
}, },
function(eventHandlers) {
return eventHandlers;
},
function(view) { function(view) {
view.focus = view.focus.next(); view.focus = view.focus.next();
}); });
@ -159,6 +233,9 @@
function(cursor) { function(cursor) {
return cursor.up(); return cursor.up();
}, },
function(eventHandlers) {
return eventHandlers;
},
function(view) { function(view) {
view.focus = view.focus.parent(); view.focus = view.focus.parent();
}); });
@ -169,11 +246,113 @@
function(cursor) { function(cursor) {
return cursor.down(); return cursor.down();
}, },
function(eventHandlers) {
return eventHandlers;
},
function(view) { function(view) {
view.focus = view.focus.children(':first'); view.focus = view.focus.children(':first');
}); });
}; };
var mockViewIdGensym = 0;
MockView.prototype.bind = function(name, worldF) {
var that = this;
// HACK: every node that is bound needs to have an id. We
// enforce this by mutating the node.
if (! this.cursor.node.id) {
this.cursor.node.id = ("__webWorldId_" + mockViewIdGensym++);
}
return this.act(
function(cursor) {
var newCursor = cursor.replaceNode($(cursor.node).clone(true).get(0));
var handler = new EventHandler(name,
new DomEventSource(name, newCursor.node),
worldF);
if (currentBigBangRecord !== undefined) {
currentBigBangRecord.startEventHandler(handler);
}
return newCursor;
},
function(eventHandlers) {
var handler = new EventHandler(name,
new DomEventSource(
name,
that.cursor.node.id),
worldF);
return eventHandlers.concat([handler]);
},
function(view) {
// HACK: every node that is bound needs to have an id. We
// enforce this by mutating the node.
if (! view.focus.get(0).id) {
view.focus.get(0).id = ("__webWorldId_" + mockViewIdGensym++);
}
var handler = new EventHandler(name,
new DomEventSource(
name,
view.focus.get(0).id),
worldF);
view.addEventHandler(handler);
currentBigBangRecord.startEventHandler(handler);
});
};
MockView.prototype.show = function() {
return this.act(
function(cursor) {
return cursor.replaceNode($(cursor.node).clone(true).show().get(0));
},
function(eventHandlers) { return eventHandlers; },
function(view) {
view.focus.show();
}
)
};
MockView.prototype.hide = function() {
return this.act(
function(cursor) {
return cursor.replaceNode($(cursor.node).clone(true).hide().get(0));
},
function(eventHandlers) { return eventHandlers; },
function(view) {
view.focus.hide();
}
)
};
MockView.prototype.appendChild = function(domNode) {
return this.act(
function(cursor) {
if (cursor.canDown()) {
cursor = cursor.down();
while (cursor.canRight()) {
cursor = cursor.right();
}
return cursor.insertRight(domNode.cloneNode(true));
} else {
return cursor.insertDown(domNode.cloneNode(true));
}
},
function(eventHandlers) { return eventHandlers; },
function(view) {
var clone = $(domNode).clone(true);
clone.appendTo(view.focus);
view.focus = clone;
}
)
};
MockView.prototype.id = function() {
return this.cursor.node.id;
};
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -205,6 +384,10 @@
} }
}; };
View.prototype.addEventHandler = function(handler) {
this.eventHandlers.push(handler);
};
// Return a list of the event sources from the view. // Return a list of the event sources from the view.
// fixme: may need to apply the pending actions to get the real set. // fixme: may need to apply the pending actions to get the real set.
View.prototype.getEventHandlers = function() { View.prototype.getEventHandlers = function() {
@ -213,7 +396,8 @@
View.prototype.getMockAndResetFocus = function(nonce) { View.prototype.getMockAndResetFocus = function(nonce) {
this.focus = this.top; this.focus = this.top;
return new MockView(TreeCursor.domToCursor($(this.top).get(0)), return new MockView(domToCursor($(this.top).get(0)),
[],
[], [],
nonce); nonce);
}; };
@ -252,7 +436,9 @@
return onFail(exn); return onFail(exn);
} }
return onSuccess(new View(dom, [])); return onSuccess(new View(dom, []));
} else if (isMockView(x)) {
return onSuccess(new View($(x.cursor.top().node),
x.eventHandlers));
} else { } else {
try { try {
dom = $(plt.baselib.format.toDomNode(x)) dom = $(plt.baselib.format.toDomNode(x))
@ -277,18 +463,52 @@
} catch (exn) { } catch (exn) {
return onFail(exn); return onFail(exn);
} }
return onSuccess(new MockView(TreeCursor.domToCursor(dom.get(0)), [], undefined)); return onSuccess(new MockView(domToCursor(dom.get(0)), [], [], undefined));
} else { } else {
try { try {
dom = $(plt.baselib.format.toDomNode(x)) dom = $(plt.baselib.format.toDomNode(x))
} catch (exn) { } catch (exn) {
return onFail(exn); return onFail(exn);
} }
return onSuccess(new MockView(TreeCursor.domToCursor(dom.get(0)), [], undefined)); return onSuccess(new MockView(domToCursor(dom.get(0)), [], [], undefined));
} }
}; };
var coerseToDomNode = function(x, onSuccess, onFail) {
var dom;
if (isDomNode(x)) {
return onSuccess(x);
} else if (isResource(x)) {
try {
dom = $(resourceContent(x).toString())
.css("margin", "0px")
.css("padding", "0px")
.css("border", "0px");
} catch (exn) {
return onFail(exn);
}
return onSuccess(dom.get(0));
} else if (isMockView(x)) {
return onSuccess(x.cursor.top().node);
} else {
try {
dom = plt.baselib.format.toDomNode(x);
} catch (exn) {
return onFail(exn);
}
return onSuccess(dom);
}
};
var isDomNode = function(x) {
return (x.hasOwnProperty('nodeType') &&
x.nodeType === 1);
};
@ -339,6 +559,8 @@
// An EventHandler combines a EventSource with a racketWorldCallback.
var EventHandler = function(name, eventSource, racketWorldCallback) { var EventHandler = function(name, eventSource, racketWorldCallback) {
WorldHandler.call(this); WorldHandler.call(this);
this.name = name; this.name = name;
@ -379,7 +601,9 @@
/* Event sources. /* Event sources.
An event source are the inputs to a web world program. An event source is a way to send input to a web-world program.
An event source may be started or stopped.
Pause and Unpause are semantically meant to be cheaper than start, stop, so Pause and Unpause are semantically meant to be cheaper than start, stop, so
@ -405,7 +629,8 @@
// Clock ticks.
// TickEventSource sends tick events.
var TickEventSource = function(delay) { var TickEventSource = function(delay) {
this.delay = delay; // delay in milliseconds. this.delay = delay; // delay in milliseconds.
@ -419,7 +644,7 @@
TickEventSource.prototype.onStart = function(fireEvent) { TickEventSource.prototype.onStart = function(fireEvent) {
this.id = setInterval( this.id = setInterval(
function() { function() {
fireEvent(); fireEvent(undefined);
}, },
this.delay); this.delay);
}; };
@ -432,27 +657,43 @@
}; };
// DomElementSource: string (U DOM string) -> EventSource
var BindEventSource = function(type, element) { // A DomEventSource allows DOM elements to send events over to
// web-world.
var DomEventSource = function(type, elementOrId) {
this.type = type; this.type = type;
this.element = element; this.elementOrId = elementOrId;
this.handler = undefined; this.handler = undefined;
}; };
BindEventSource.prototype = plt.baselib.heir(EventSource.prototype); DomEventSource.prototype = plt.baselib.heir(EventSource.prototype);
BindEventSource.prototype.onStart = function(fireEvent) { DomEventSource.prototype.onStart = function(fireEvent) {
this.handler = var element = this.elementOrId;
function(evt) { if (typeof(this.elementOrId) === 'string') {
fireEvent(evt); element = $('#' + this.elementOrId).get(0);
}
this.handler = function(evt) {
if (element !== undefined) {
fireEvent(element, evt);
}
}; };
$(this.element).bind(this.type, if (element !== undefined) {
this.handler); $(element).bind(this.type, this.handler);
}
}; };
BindEventSource.prototype.onStop = function() { DomEventSource.prototype.onStop = function() {
var element = this.elementOrId;
if (typeof(this.elementOrId) === 'string') {
element = $('#' + this.elementOrId).get(0);
}
if (this.handler !== undefined) { if (this.handler !== undefined) {
$(this.element).unbind(this.type, this.handler); if (element !== undefined) {
$(element).unbind(this.type, this.handler);
}
this.handler = undefined; this.handler = undefined;
} }
}; };
@ -479,7 +720,8 @@
}; };
var EventQueueElement = function(handler, data) { var EventQueueElement = function(who, handler, data) {
this.who = who;
this.handler = handler; this.handler = handler;
this.data = data; this.data = data;
}; };
@ -499,6 +741,7 @@
// bigBang. // bigBang.
var bigBang = function(MACHINE, world, handlers) { var bigBang = function(MACHINE, world, handlers) {
var oldArgcount = MACHINE.argcount; var oldArgcount = MACHINE.argcount;
var oldCurrentBigBangRecord = currentBigBangRecord;
var running = true; var running = true;
var dispatchingEvents = false; var dispatchingEvents = false;
@ -510,34 +753,74 @@
var eventQueue = new EventQueue(); var eventQueue = new EventQueue();
var top = $("<div/>"); var top = $("<div/>");
var eventHandlers = filter(handlers, isEventHandler).concat(view.getEventHandlers());
MACHINE.params.currentDisplayer(MACHINE, top); MACHINE.params.currentDisplayer(MACHINE, top);
PAUSE(function(restart) { PAUSE(function(restart) {
var i;
var onCleanRestart = function() { var onCleanRestart = function() {
running = false; running = false;
var i; stopEventHandlers();
for (i = 0; i < eventHandlers.length; i++) {
stopEventHandler(eventHandlers[i]);
}
restart(function(MACHINE) { restart(function(MACHINE) {
MACHINE.argcount = oldArgcount; MACHINE.argcount = oldArgcount;
currentBigBangRecord = oldCurrentBigBangRecord;
finalizeClosureCall(MACHINE, world); finalizeClosureCall(MACHINE, world);
}); });
}; };
var onMessyRestart = function(exn) { var onMessyRestart = function(exn) {
running = false; running = false;
var i; stopEventHandlers();
for (i = 0; i < eventHandlers.length; i++) {
stopEventHandler(eventHandlers[i]);
}
restart(function(MACHINE) { restart(function(MACHINE) {
currentBigBangRecord = oldCurrentBigBangRecord;
plt.baselib.exceptions.raise(MACHINE, exn); plt.baselib.exceptions.raise(MACHINE, exn);
}); });
}; };
var dispatchEventsInQueue = function() { var startEventHandlers = function() {
var i;
for (i = 0; i < eventHandlers.length; i++) {
startEventHandler(eventHandlers[i]);
}
};
var stopEventHandlers = function() {
var i;
for (i = 0; i < eventHandlers.length; i++) {
stopEventHandler(eventHandlers[i]);
}
};
var startEventHandler = function(handler) {
var fireEvent = function(who) {
if (! running) { return; }
var args = [].slice.call(arguments, 1);
eventQueue.queue(new EventQueueElement(who, handler, args));
if (! dispatchingEvents) {
dispatchingEvents = true;
setTimeout(
function() {
dispatchEventsInQueue(
function() {
refreshView(function() {},
onMessyRestart);
},
onMessyRestart);
},
0);
}
};
handler.eventSource.onStart(fireEvent);
};
var stopEventHandler = function(handler) {
handler.eventSource.onStop();
};
var dispatchEventsInQueue = function(success, fail) {
// Apply all the events on the queue, call toDraw, and then stop. // Apply all the events on the queue, call toDraw, and then stop.
// If the world ever satisfies stopWhen, stop immediately and quit. // If the world ever satisfies stopWhen, stop immediately and quit.
var nextEvent; var nextEvent;
@ -549,8 +832,11 @@
// Set up the proxy object so we can do what appear to be functional // Set up the proxy object so we can do what appear to be functional
// queries. // queries.
mockView = view.getMockAndResetFocus(); mockView = view.getMockAndResetFocus();
nextEvent = eventQueue.dequeue(); nextEvent = eventQueue.dequeue();
if (nextEvent.who !== undefined) {
mockView = mockView.updateFocus('#' + nextEvent.who.id);
}
// FIXME: deal with event data here // FIXME: deal with event data here
racketWorldCallback = nextEvent.handler.racketWorldCallback; racketWorldCallback = nextEvent.handler.racketWorldCallback;
racketWorldCallback(MACHINE, racketWorldCallback(MACHINE,
@ -564,24 +850,25 @@
mockView, mockView,
function(shouldStop) { function(shouldStop) {
if (shouldStop) { if (shouldStop) {
refreshViewAndStopDispatching( refreshView(
function() { function() {
onCleanRestart(); onCleanRestart();
}, },
onMessyRestart); fail);
} else { } else {
dispatchEventsInQueue(); dispatchEventsInQueue(success, fail);
} }
}, },
onMessyRestart); fail);
}, },
onMessyRestart); fail);
} else { } else {
refreshViewAndStopDispatching(function() {}, onMessyRestart); dispatchingEvents = false;
success();
} }
}; };
var refreshViewAndStopDispatching = function(success, failure) { var refreshView = function(success, failure) {
// Note: we create a random nonce, and watch to see if the MockView we get back // Note: we create a random nonce, and watch to see if the MockView we get back
// from the user came from here. If not, we have no hope to do a nice, efficient // from the user came from here. If not, we have no hope to do a nice, efficient
// update, and have to do it from scratch. // update, and have to do it from scratch.
@ -600,47 +887,23 @@
} else { } else {
view.top = $(newMockView.cursor.top().node); view.top = $(newMockView.cursor.top().node);
view.initialRender(top); view.initialRender(top);
// FIXME: how about events embedded in the dom? eventHandlers = newMockView.eventHandlers;
startEventHandlers();
} }
dispatchingEvents = false;
success(); success();
}, },
function(err) { function(err) {
dispatchingEvents = false;
failure(err); failure(err);
}) })
}; };
currentBigBangRecord = { stop : onCleanRestart,
stopWithExn : onMessyRestart,
startEventHandler : startEventHandler,
stopEventHandler : stopEventHandler };
var startEventHandler = function(handler) {
var fireEvent = function() {
if (! running) { return; }
var args = [].slice.call(arguments, 0);
eventQueue.queue(new EventQueueElement(handler, args));
if (! dispatchingEvents) {
setTimeout(dispatchEventsInQueue, 0);
}
//
// fixme: if we see too many events accumulating, throttle
// the ones that are marked as throttleable.
};
handler.eventSource.onStart(fireEvent);
};
var stopEventHandler = function(handler) {
handler.eventSource.onStop();
};
view.initialRender(top); view.initialRender(top);
startEventHandlers();
var eventHandlers = filter(handlers, isEventHandler).concat(view.getEventHandlers()); refreshView(function() {}, onMessyRestart);
var i;
for (i = 0; i < eventHandlers.length; i++) {
startEventHandler(eventHandlers[i]);
}
}); });
}; };
@ -659,6 +922,38 @@
// findDomNodeLocation: dom-node dom-node -> arrayof number
// Given a node, returns the child indices we need to follow to reach
// it from the top.
// Assumption: top must be an ancestor of the node. Otherwise, the
// result is partial.
var findDomNodeLocation = function(node, top) {
var locator = [];
var parent, i;
while(node !== top && node.parentNode !== null) {
parent = node.parentNode;
for (i = 0; i < parent.childNodes.length; i++) {
if (parent.childNodes[i] === node) {
locator.push(i);
break;
}
}
node = parent;
}
return locator.reverse();
};
var findNodeFromLocation = function(top, location) {
var i = 0;
var node = top;
for (i = 0; i < location.length; i++) {
node = node.childNodes[location[i]];
}
return node;
};
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
var checkReal = plt.baselib.check.checkReal; var checkReal = plt.baselib.check.checkReal;
@ -730,9 +1025,10 @@
'->view', '->view',
1, 1,
function(MACHINE) { function(MACHINE) {
var viewable = MACHINE.env[MACHINE.env.length - 1];
var oldArgcount = MACHINE.argcount; var oldArgcount = MACHINE.argcount;
PAUSE(function(restart) { PAUSE(function(restart) {
coerseToView(viewable, coerseToMockView(viewable,
function(v) { function(v) {
restart(function(MACHINE) { restart(function(MACHINE) {
MACHINE.argcount = oldArgcount; MACHINE.argcount = oldArgcount;
@ -888,5 +1184,94 @@
EXPORTS['view-bind'] = makePrimitiveProcedure(
'view-bind',
3,
function(MACHINE) {
var view = checkMockView(MACHINE, 'view-bind', 0);
var name = checkSymbolOrString(MACHINE, 'view-bind', 1);
var worldF = wrapFunction(checkProcedure(MACHINE, 'view-bind', 2));
return view.bind(name, worldF);
});
EXPORTS['view-form-value'] = makePrimitiveProcedure(
'view-form-value',
1,
function(MACHINE) {
var view = checkMockView(MACHINE, 'view-form-value', 0);
return view.getFormValue();
});
EXPORTS['update-view-form-value'] = makePrimitiveProcedure(
'update-view-form-value',
2,
function(MACHINE) {
var view = checkMockView(MACHINE, 'update-view-form-value', 0);
var value = checkSymbolOrString(MACHINE, 'update-view-form-value', 1).toString();
return view.updateFormValue(value);
});
EXPORTS['view-show'] = makePrimitiveProcedure(
'view-show',
1,
function(MACHINE) {
var view = checkMockView(MACHINE, 'view-show', 0);
return view.show();
});
EXPORTS['view-hide'] = makePrimitiveProcedure(
'view-hide',
1,
function(MACHINE) {
var view = checkMockView(MACHINE, 'view-hide', 0);
return view.hide();
});
EXPORTS['view-append-child'] = makeClosure(
'view-append-child',
2,
function(MACHINE) {
var view = checkMockView(MACHINE, 'view-append-child', 0);
var oldArgcount = MACHINE.argcount;
var x = MACHINE.env[MACHINE.env.length - 2];
PAUSE(function(restart) {
coerseToDomNode(x,
function(dom) {
restart(function(MACHINE) {
MACHINE.argcount = oldArgcount;
var updatedView = view.appendChild(dom);
finalizeClosureCall(MACHINE, updatedView);
});
},
function(err) {
restart(function(MACHINE) {
plt.baselib.exceptions.raise(
MACHINE,
new Error(plt.baselib.format.format(
"unable to translate ~s to dom node: ~a",
[x, exn.message])));
});
});
});
});
EXPORTS['view-id'] = makePrimitiveProcedure(
'view-id',
1,
function(MACHINE) {
var view = checkMockView(MACHINE, 'view-hide', 0);
return view.id();
});
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
}()); }());

View File

@ -5,7 +5,18 @@
view-focus view-focus
view-left view-right view-up view-down view-left view-right view-up view-down
view-text update-view-text view-text update-view-text
view-attr update-view-attr) view-attr update-view-attr
view-id
view-bind
view-form-value
update-view-form-value
view-show
view-hide
view-append-child)
(define (big-bang world . handlers) (define (big-bang world . handlers)
(error 'big-bang "Please run in JavaScript context.")) (error 'big-bang "Please run in JavaScript context."))
@ -60,3 +71,28 @@
(define (update-view-attr v attr-name value) (define (update-view-attr v attr-name value)
(error 'update-view-attr "Please run in JavaScript context.")) (error 'update-view-attr "Please run in JavaScript context."))
(define (view-id v)
(error 'view-id "Please run in JavaScript context."))
(define (view-bind v type worldF)
(error 'view-bind "Please run in JavaScript context."))
(define (view-form-value)
(error 'view-form-value "Please run in JavaScript context."))
(define (update-view-form-value val)
(error 'view-form-value "Please run in JavaScript context."))
(define (view-show)
(error 'view-show "Please run in JavaScript context."))
(define (view-hide)
(error 'view-hide "Please run in JavaScript context."))
(define (view-append-child dom)
(error 'view-append "Please run in JavaScript context."))