tick-tock is doing something

This commit is contained in:
Danny Yoo 2011-08-25 13:44:14 -04:00
parent 84c6df5f90
commit c5d8933126
3 changed files with 429 additions and 86 deletions

View File

@ -7,7 +7,8 @@
(declare-implementation (declare-implementation
#:racket "racket-impl.rkt" #:racket "racket-impl.rkt"
#:javascript ("js-impl.js") #:javascript ("js-tree-cursor.js"
"js-impl.js")
#:provided-values (big-bang #:provided-values (big-bang
;; initial view ;; initial view

View File

@ -16,45 +16,110 @@
// See Functional Pearl: The Zipper, by G\'erard Huet // See Functional Pearl: The Zipper, by G\'erard Huet
// J. Functional Programming 7 (5): 549--554 Sepember 1997 // J. Functional Programming 7 (5): 549--554 Sepember 1997
var TreeCursor = function() { var TreePath = function(parent, node, prevs, nexts) {
this.parent; this.parent = parent; // Parent can be the top (undefined), or a TreePath
this this.node = node;
this.prevs = prevs;
this.nexts = nexts;
};
TreePath.prototype.down = function() {
var children = node.children();
return new TreePath(this, node[0], [], children.slice(1));
};
TreePath.prototype.up = function() {
var parent = this.parent;
return new Tree
};
TreePath.prototype.left = function() {
};
TreePath.prototype.right = function() {
};
TreePath.prototype.succ = function() {
};
TreePath.prototype.pred = function() {
}; };
// For the moment, we only support selection by id.
var idRegexp = new RegExp("^#");
var selectorMatches = function(selector, node) {
if (selector.match(idRegexp)) {
if (node.nodeType === 1) {
return node.getAttribute('id') === selector.substring(1);
} else {
return false;
}
}
return false;
};
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
var MockView = function(focused, pendingActions) { var MockView = function(cursor, pendingActions) {
this.focused = focused; this.cursor = cursor;
this.pendingActions = pendingActions; this.pendingActions = pendingActions;
}; };
var isMockView = plt.baselib.makeClassPredicate(MockView); var isMockView = plt.baselib.makeClassPredicate(MockView);
MockView.prototype.act = function(actionForMock, actionForReal) { MockView.prototype.act = function(actionForCursor, actionForReal) {
if (arguments.length !== 2) { throw new Error("act: insufficient arguments"); } if (arguments.length !== 2) { throw new Error("act: insufficient arguments"); }
// FIXME: this is not enough. We need a way to do the action // FIXME: this is not enough. We need a way to do the action
// on a copy of the mock. clone is insufficient: we need to // on a copy of the mock. clone is insufficient: we need to
// copy the whole tree, no? // copy the whole tree, no?
return new MockView(actionForMock(this.focused), return new MockView(actionForCursor(this.cursor),
this.pendingActions.concat([actionForReal])); this.pendingActions.concat([actionForReal]));
}; };
MockView.prototype.updateFocus = function(selector) { MockView.prototype.updateFocus = function(selector) {
return this; selector = selector.toString();
return this.act(
function(cursor) {
var c = cursor.top();
while (true) {
if (selectorMatches(selector, c.node)) {
return c;
}
if (c.canSucc()) {
c = c.succ();
} else {
throw new Error("unable to find " + selector);
}
}
},
function(view) {
view.focus = view.top.find(selector);
}
);
}; };
MockView.prototype.getText = function() { MockView.prototype.getText = function() {
return "fill me in"; return $(this.cursor.node).text();
}; };
MockView.prototype.updateText = function(text) { MockView.prototype.updateText = function(text) {
return this; return this.act(
function(cursor) {
return cursor.replaceNode($(cursor.node).text(text).get(0));
},
function(view) {
view.focus.text(text);
})
}; };
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -63,10 +128,10 @@
// A View represents a representation of the DOM tree. // A View represents a representation of the DOM tree.
var View = function(top, focused, eventHandlers, pendingActions, proxy) { var View = function(top, eventHandlers) {
// top: dom node // top: dom node
this.top = top; this.top = top;
this.focused = focused; this.focus = top;
this.eventHandlers = eventHandlers; this.eventHandlers = eventHandlers;
}; };
@ -80,6 +145,7 @@
// children and apply them here? // children and apply them here?
if (this.top.find("body").length > 0) { if (this.top.find("body").length > 0) {
top.append(this.top.find("body").children()); top.append(this.top.find("body").children());
this.top = top;
} else { } else {
top.append(this.top); top.append(this.top);
} }
@ -91,43 +157,14 @@
return this.eventHandlers; return this.eventHandlers;
}; };
View.prototype.getMock = function() { View.prototype.getMockAndResetFocus = function() {
return new MockView(this.top.clone(true), []); this.focus = this.top;
return new MockView(TreeCursor.domToCursor($(this.top).get(0)),
[]);
}; };
// View.prototype.updateFocus = function(selector) {
// if (this.proxy) {
// return new View(this.top, this.top.find(selector), this.eventHandlers, this.pendingActions, this.proxy);
// } else {
// return new View(this.top, this.top.find(selector), this.eventHandlers, this.pendingActions, this.proxy);
// }
// };
// View.prototype.text = function() {
// if (this.proxy) {
// return (this.proxy.text())
// } else {
// return (this.focused.text());
// }
// };
// View.prototype.updateText = function(s) {
// if (this.proxy) {
// this.proxy.text(s);
// return new View(this.top,
// this.focused,
// this.eventHandlers,
// this.pendingActions.concat([ function(v) { this.focused.text(s); }]),
// this.proxy);
// } else {
// return (this.focused.text());
// }
// };
var isView = plt.baselib.makeClassPredicate(View); var isView = plt.baselib.makeClassPredicate(View);
@ -159,22 +196,15 @@
} catch (exn) { } catch (exn) {
return onFail(exn); return onFail(exn);
} }
return onSuccess(new View(dom, return onSuccess(new View(dom, []));
dom,
[],
[],
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 View(dom, return onSuccess(new View(dom, []));
dom,
[],
[],
undefined));
} }
}; };
@ -388,12 +418,12 @@
// bigBang. // bigBang.
var bigBang = function(MACHINE, world, handlers) { var bigBang = function(MACHINE, world, handlers) {
var oldArgcount = MACHINE.argcount; var oldArgcount = MACHINE.argcount;
var running = true; var running = true;
var dispatchingEvents = false;
var top = $(plt.baselib.format.toDomNode(world)); var top = $(plt.baselib.format.toDomNode(world));
var view = (find(handlers, isInitialViewHandler) || { view : new View(top, var view = (find(handlers, isInitialViewHandler) || { view : new View(top, [])}).view;
top,
[],
[])}).view;
var stopWhen = (find(handlers, isStopWhenHandler) || { stopWhen: defaultStopWhen }).stopWhen; var stopWhen = (find(handlers, isStopWhenHandler) || { stopWhen: defaultStopWhen }).stopWhen;
var toDraw = (find(handlers, isToDrawHandler) || {toDraw : defaultToDraw} ).toDraw; var toDraw = (find(handlers, isToDrawHandler) || {toDraw : defaultToDraw} ).toDraw;
@ -434,25 +464,25 @@
var data; var data;
var racketWorldCallback; var racketWorldCallback;
var mockView; var mockView;
dispatchingEvents = true;
if(! eventQueue.isEmpty() ) { if(! eventQueue.isEmpty() ) {
// 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.getMock(); mockView = view.getMockAndResetFocus();
nextEvent = eventQueue.dequeue(); nextEvent = eventQueue.dequeue();
// FIXME: deal with event data here // FIXME: deal with event data here
racketWorldCallback = nextEvent.handler.racketWorldCallback; racketWorldCallback = nextEvent.handler.racketWorldCallback;
racketWorldCallback(MACHINE, racketWorldCallback(MACHINE,
world, world,
view, mockView,
// data, // data,
function(newWorld) { function(newWorld) {
world = newWorld; world = newWorld;
stopWhen(MACHINE, stopWhen(MACHINE,
world, world,
view, mockView,
function(shouldStop) { function(shouldStop) {
if (shouldStop) { if (shouldStop) {
onCleanRestart(); onCleanRestart();
@ -469,7 +499,21 @@
onMessyRestart(err); onMessyRestart(err);
}); });
} else { } else {
// call redraw toDraw(MACHINE,
world,
view.getMockAndResetFocus(),
function(newMockView) {
var i;
var actions = newMockView.pendingActions;
for (i = 0; i < actions.length; i++) {
actions[i](view);
}
dispatchingEvents = false;
},
function(err) {
dispatchingEvents = false;
onMessyRestart(err);
})
} }
}; };
@ -479,7 +523,9 @@
if (! running) { return; } if (! running) { return; }
var args = [].slice.call(arguments, 0); var args = [].slice.call(arguments, 0);
eventQueue.queue(new EventQueueElement(handler, args)); eventQueue.queue(new EventQueueElement(handler, args));
setTimeout(dispatchEventsInQueue, 0); if (! dispatchingEvents) {
setTimeout(dispatchEventsInQueue, 0);
}
// //
// fixme: if we see too many events accumulating, throttle // fixme: if we see too many events accumulating, throttle
// the ones that are marked as throttleable. // the ones that are marked as throttleable.
@ -498,19 +544,6 @@
for (i = 0; i < eventHandlers.length; i++) { for (i = 0; i < eventHandlers.length; i++) {
startEventHandler(eventHandlers[i]); startEventHandler(eventHandlers[i]);
} }
// fixme: set up the event sources
// fixme: set up the world updater
// fixme: re-render the view on world changes.
// Initialize event handlers to send to that channel.
}); });
}; };
@ -532,6 +565,7 @@
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
var checkReal = plt.baselib.check.checkReal; var checkReal = plt.baselib.check.checkReal;
var checkString = plt.baselib.check.checkString;
var checkProcedure = plt.baselib.check.checkProcedure; var checkProcedure = plt.baselib.check.checkProcedure;
@ -543,7 +577,7 @@
isWorldHandler, isWorldHandler,
'world handler'); 'world handler');
var checkView = plt.baselib.check.makeCheckArgumentType( var checkMockView = plt.baselib.check.makeCheckArgumentType(
isMockView, 'view'); isMockView, 'view');
@ -654,7 +688,7 @@
'view-focus', 'view-focus',
2, 2,
function(MACHINE) { function(MACHINE) {
var view = checkView(MACHINE, 'view-focus', 0); var view = checkMockView(MACHINE, 'view-focus', 0);
var selector = checkSelector(MACHINE, 'view-focus', 1); var selector = checkSelector(MACHINE, 'view-focus', 1);
try { try {
return view.updateFocus(selector); return view.updateFocus(selector);
@ -662,8 +696,8 @@
plt.baselib.exceptions.raise( plt.baselib.exceptions.raise(
MACHINE, MACHINE,
new Error(plt.baselib.format.format( new Error(plt.baselib.format.format(
"unable to focus to ~s", "unable to focus to ~s: ~s",
[selector]))); [selector, e.message])));
} }
}); });
@ -671,7 +705,7 @@
'view-focus', 'view-focus',
1, 1,
function(MACHINE) { function(MACHINE) {
var view = checkView(MACHINE, 'view-focus', 0); var view = checkMockView(MACHINE, 'view-focus', 0);
return view.getText(); return view.getText();
}); });
@ -680,8 +714,8 @@
'update-view-text', 'update-view-text',
2, 2,
function(MACHINE) { function(MACHINE) {
var view = checkView(MACHINE, 'update-view-text', 0); var view = checkMockView(MACHINE, 'update-view-text', 0);
var text = checkString(MACHINE, 'update-view-text', 1); var text = plt.baselib.format.toDisplayedString(MACHINE.env[MACHINE.env.length - 2]);
return view.updateText(text); return view.updateText(text);
}); });

308
web-world/js-tree-cursor.js Normal file
View File

@ -0,0 +1,308 @@
/*jslint vars: true, white: true, plusplus: true, maxerr: 50, indent: 4 */
// Offers functional views, traversals of the DOM and other tree-like structures.
// See Functional Pearl: The Zipper, by G\'erard Huet
// J. Functional Programming 7 (5): 549--554 Sepember 1997
var TreeCursor = (function() {
"use strict";
var TreeCursor = function(parent, node, prevs, nexts, openF, closeF, atomicF) {
this.parent = parent; // Parent can be the top (undefined), or a TreeCursor
this.node = node;
this.prevs = prevs;
this.nexts = nexts;
// openF: node -> (arrayof node)
this.openF = openF;
// closeF: node (arrayof node) -> node
// Given a node and its array of children, return a new node.
this.closeF = closeF;
// atomicF: node -> boolean
// Produces true if the node should be treated atomically.
this.atomicF = atomicF;
};
TreeCursor.prototype.canDown = function() {
return (!(this.atomicF(this.node)) &&
this.openF(this.node).length !== 0);
};
TreeCursor.prototype.down = function() {
if (this.atomicF(this.node)) {
throw new Error("down of atomic element");
}
var opened = this.openF(this.node);
if (opened.length === 0) {
throw new Error("down of empty");
}
return new TreeCursor(this,
opened[0],
[],
opened.slice(1),
this.openF,
this.closeF,
this.atomicF);
};
TreeCursor.prototype.canUp = function() {
return this.parent !== undefined;
};
TreeCursor.prototype.up = function() {
var parent = this.parent;
return new TreeCursor(parent.parent,
this.closeF(parent.node,
this.prevs.concat([this.node]).concat(this.nexts)),
parent.prevs,
parent.nexts,
this.openF,
this.closeF,
this.atomicF);
};
TreeCursor.prototype.canLeft = function() { return this.prevs.length !== 0; };
TreeCursor.prototype.left = function() {
if (this.prevs.length === 0) { throw new Error("left of first"); }
return new TreeCursor(this.parent,
this.prevs[this.prevs.length - 1],
this.prevs.slice(0, this.prevs.length - 1),
[this.node].concat(this.nexts),
this.openF,
this.closeF,
this.atomicF);
};
TreeCursor.prototype.canRight = function() { return this.nexts.length !== 0; };
TreeCursor.prototype.right = function() {
if (this.nexts.length === 0) { throw new Error("right of last"); }
return new TreeCursor(this.parent,
this.nexts[0],
this.prevs.concat([this.node]),
this.nexts.slice(1),
this.openF,
this.closeF,
this.atomicF);
};
TreeCursor.prototype.succ = function() {
var n;
if (this.canDown()) {
return this.down();
} else if (this.canRight()) {
return this.right();
} else {
n = this;
while (true) {
n = n.up();
if (n.canRight()) {
return n.right();
}
}
}
};
TreeCursor.prototype.pred = function() {
var n;
if (this.canLeft()) {
n = this.left();
while (n.canDown()) {
n = n.down();
while (n.canRight()) {
n = n.right();
}
}
return n;
} else {
return this.up();
}
};
TreeCursor.prototype.canPred = function() {
return this.canLeft() || this.canUp();
};
TreeCursor.prototype.canSucc = function() {
var n;
if (this.canDown()) {
return true;
} else if (this.canRight()) {
return true;
} else {
n = this;
while (true) {
if (! n.canUp()) { return false; }
n = n.up();
if (n.canRight()) {
return true;
}
}
}
};
TreeCursor.prototype.top = function() {
var n = this;
while (n.canUp()) { n = n.up(); }
return n;
};
//////////////////////////////////////////////////////////////////////
TreeCursor.prototype.replaceNode = function(n) {
return new TreeCursor(this.parent,
n,
this.prevs,
this.nexts,
this.openF,
this.closeF,
this.atomicF);
};
TreeCursor.prototype.insertRight = function(n) {
return new TreeCursor(this.parent,
n,
this.prevs.concat(this.node),
this.nexts,
this.openF,
this.closeF,
this.atomicF);
};
TreeCursor.prototype.insertLeft = function(n) {
return new TreeCursor(this.parent,
n,
this.prevs,
[this.node].concat(this.nexts),
this.openF,
this.closeF,
this.atomicF);
};
TreeCursor.prototype.insertDown = function(n) {
if (this.atomicF(this.node)) {
throw new Error("down of atomic element");
}
return new TreeCursor(this,
n,
[],
this.openF(this.node),
this.openF,
this.closeF,
this.atomicF);
};
TreeCursor.prototype.deleteNode = function() {
var parent;
if (this.nexts.length !== 0) {
return new TreeCursor(this.parent,
this.nexts[0],
this.prevs,
this.nexts.slice(1),
this.openF,
this.closeF,
this.atomicF);
} else if (this.prevs.length !== 0) {
return new TreeCursor(this.parent,
this.prevs[this.prevs.length - 1],
this.prevs.slice(0, this.prevs.length - 1),
this.nexts,
this.openF,
this.closeF,
this.atomicF);
} else {
parent = this.parent;
return new TreeCursor(parent.parent,
this.closeF(parent.node, []),
parent.prevs,
parent.nexts,
this.openF,
this.closeF,
this.atomicF);
}
};
//////////////////////////////////////////////////////////////////////
TreeCursor.adaptTreeCursor = function(node, openF, closeF, atomicF) {
return new TreeCursor(undefined,
node,
[],
[],
openF,
closeF,
atomicF);
};
TreeCursor.arrayToCursor = function(anArray) {
var arrayOpenF = function(n) {
if (n instanceof Array) {
return n;
} else {
return [];
}
};
var arrayCloseF = function(n, children) {
if (n instanceof Array) {
return children;
} else {
return n;
}
};
var arrayAtomicF = function(n) {
return !(n instanceof Array);
};
return TreeCursor.adaptTreeCursor(anArray,
arrayOpenF,
arrayCloseF,
arrayAtomicF);
};
TreeCursor.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);
};
return TreeCursor;
}());