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
#:racket "racket-impl.rkt"
#:javascript ("js-impl.js")
#:javascript ("js-tree-cursor.js"
"js-impl.js")
#:provided-values (big-bang
;; initial view

View File

@ -16,45 +16,110 @@
// See Functional Pearl: The Zipper, by G\'erard Huet
// J. Functional Programming 7 (5): 549--554 Sepember 1997
var TreeCursor = function() {
this.parent;
this
var TreePath = function(parent, node, prevs, nexts) {
this.parent = parent; // Parent can be the top (undefined), or a TreePath
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) {
this.focused = focused;
var MockView = function(cursor, pendingActions) {
this.cursor = cursor;
this.pendingActions = pendingActions;
};
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"); }
// 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
// copy the whole tree, no?
return new MockView(actionForMock(this.focused),
return new MockView(actionForCursor(this.cursor),
this.pendingActions.concat([actionForReal]));
};
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() {
return "fill me in";
return $(this.cursor.node).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.
var View = function(top, focused, eventHandlers, pendingActions, proxy) {
var View = function(top, eventHandlers) {
// top: dom node
this.top = top;
this.focused = focused;
this.focus = top;
this.eventHandlers = eventHandlers;
};
@ -80,6 +145,7 @@
// children and apply them here?
if (this.top.find("body").length > 0) {
top.append(this.top.find("body").children());
this.top = top;
} else {
top.append(this.top);
}
@ -91,43 +157,14 @@
return this.eventHandlers;
};
View.prototype.getMock = function() {
return new MockView(this.top.clone(true), []);
View.prototype.getMockAndResetFocus = function() {
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);
@ -159,22 +196,15 @@
} catch (exn) {
return onFail(exn);
}
return onSuccess(new View(dom,
dom,
[],
[],
undefined));
return onSuccess(new View(dom, []));
} else {
try {
dom = $(plt.baselib.format.toDomNode(x))
} catch (exn) {
return onFail(exn);
}
return onSuccess(new View(dom,
dom,
[],
[],
undefined));
return onSuccess(new View(dom, []));
}
};
@ -388,12 +418,12 @@
// bigBang.
var bigBang = function(MACHINE, world, handlers) {
var oldArgcount = MACHINE.argcount;
var running = true;
var dispatchingEvents = false;
var top = $(plt.baselib.format.toDomNode(world));
var view = (find(handlers, isInitialViewHandler) || { view : new View(top,
top,
[],
[])}).view;
var view = (find(handlers, isInitialViewHandler) || { view : new View(top, [])}).view;
var stopWhen = (find(handlers, isStopWhenHandler) || { stopWhen: defaultStopWhen }).stopWhen;
var toDraw = (find(handlers, isToDrawHandler) || {toDraw : defaultToDraw} ).toDraw;
@ -434,25 +464,25 @@
var data;
var racketWorldCallback;
var mockView;
dispatchingEvents = true;
if(! eventQueue.isEmpty() ) {
// Set up the proxy object so we can do what appear to be functional
// queries.
mockView = view.getMock();
mockView = view.getMockAndResetFocus();
nextEvent = eventQueue.dequeue();
// FIXME: deal with event data here
racketWorldCallback = nextEvent.handler.racketWorldCallback;
racketWorldCallback(MACHINE,
world,
view,
mockView,
// data,
function(newWorld) {
world = newWorld;
stopWhen(MACHINE,
world,
view,
mockView,
function(shouldStop) {
if (shouldStop) {
onCleanRestart();
@ -469,7 +499,21 @@
onMessyRestart(err);
});
} 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; }
var args = [].slice.call(arguments, 0);
eventQueue.queue(new EventQueueElement(handler, args));
setTimeout(dispatchEventsInQueue, 0);
if (! dispatchingEvents) {
setTimeout(dispatchEventsInQueue, 0);
}
//
// fixme: if we see too many events accumulating, throttle
// the ones that are marked as throttleable.
@ -498,19 +544,6 @@
for (i = 0; i < eventHandlers.length; 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 checkString = plt.baselib.check.checkString;
var checkProcedure = plt.baselib.check.checkProcedure;
@ -543,7 +577,7 @@
isWorldHandler,
'world handler');
var checkView = plt.baselib.check.makeCheckArgumentType(
var checkMockView = plt.baselib.check.makeCheckArgumentType(
isMockView, 'view');
@ -654,7 +688,7 @@
'view-focus',
2,
function(MACHINE) {
var view = checkView(MACHINE, 'view-focus', 0);
var view = checkMockView(MACHINE, 'view-focus', 0);
var selector = checkSelector(MACHINE, 'view-focus', 1);
try {
return view.updateFocus(selector);
@ -662,8 +696,8 @@
plt.baselib.exceptions.raise(
MACHINE,
new Error(plt.baselib.format.format(
"unable to focus to ~s",
[selector])));
"unable to focus to ~s: ~s",
[selector, e.message])));
}
});
@ -671,7 +705,7 @@
'view-focus',
1,
function(MACHINE) {
var view = checkView(MACHINE, 'view-focus', 0);
var view = checkMockView(MACHINE, 'view-focus', 0);
return view.getText();
});
@ -680,8 +714,8 @@
'update-view-text',
2,
function(MACHINE) {
var view = checkView(MACHINE, 'update-view-text', 0);
var text = checkString(MACHINE, 'update-view-text', 1);
var view = checkMockView(MACHINE, 'update-view-text', 0);
var text = plt.baselib.format.toDisplayedString(MACHINE.env[MACHINE.env.length - 2]);
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;
}());