diff --git a/web-world/DESIGN b/web-world/DESIGN index cefac48..9f61603 100644 --- a/web-world/DESIGN +++ b/web-world/DESIGN @@ -31,7 +31,7 @@ previous attempt of jsworld: We want to take the design ideas of JQuery. - * The view is a cursor into DOM nodes. + * The view is a cursor into a DOM node. * Operations refocus the cursor onto particular elements of the dom. @@ -249,13 +249,13 @@ handler takes, not only the world, but the current view. ;; When the user clicks on the button, grab at the text of the ;; text-field. (define (on-click w v) - (view-text (view-focus v "text-field"))) + (view-text (view-focus v "#text-field"))) ;; on-draw: world view -> view ;; Take the view, and replace the template with the world value. (define (on-draw w v) - (view-text (view-focus v "template") + (view-text (view-focus v "#template") w)) diff --git a/web-world/examples/tick-tock/tick-tock.rkt b/web-world/examples/tick-tock/tick-tock.rkt index 475d9cc..bb67aeb 100644 --- a/web-world/examples/tick-tock/tick-tock.rkt +++ b/web-world/examples/tick-tock/tick-tock.rkt @@ -4,18 +4,21 @@ (define-resource index.html) + ;; draw: world view -> view (define (draw w v) - v - ;(view-text (view-focus v "#counter") w) - ) + (update-view-text (view-focus v "#counter") w)) + + ;; tick: world view -> world (define (tick w v) - (printf "Tick\n") - (+ add1 w)) - + (printf "Tick ~s\n" w) + (+ w 1)) + (big-bang 0 (initial-view index.html) (to-draw draw) - (on-tick tick 1)) + (on-tick tick 1) + (stop-when (lambda (w v) + (> w 10)))) diff --git a/web-world/impl.rkt b/web-world/impl.rkt index af917eb..77d3032 100644 --- a/web-world/impl.rkt +++ b/web-world/impl.rkt @@ -23,4 +23,9 @@ to-draw ;; coerse to view - ->view)) + ->view + + view-focus + view-text + update-view-text + )) diff --git a/web-world/js-impl.js b/web-world/js-impl.js index 7ae6cdb..3d63c49 100644 --- a/web-world/js-impl.js +++ b/web-world/js-impl.js @@ -8,7 +8,7 @@ var makeClosure = plt.baselib.functions.makeClosure; var finalizeClosureCall = plt.baselib.functions.finalizeClosureCall; var PAUSE = plt.runtime.PAUSE; - + var isString = plt.baselib.strings.isString; var resourceStructType = @@ -16,21 +16,62 @@ - // A View represents a functional representation of the DOM tree. - var View = function(top, focused, eventHandlers, pendingActions) { + // 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 MockView = function(focused, pendingActions) { + this.focused = focused; + this.pendingActions = pendingActions; + }; + + var isMockView = plt.baselib.makeClassPredicate(MockView); + + MockView.prototype.act = function(actionForMock, 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), + this.pendingActions.concat([actionForReal])); + }; + + MockView.prototype.updateFocus = function(selector) { + return this; + }; + + MockView.prototype.getText = function() { + return "fill me in"; + }; + + MockView.prototype.updateText = function(text) { + return this; + }; + ////////////////////////////////////////////////////////////////////// + + + + + + // A View represents a representation of the DOM tree. + var View = function(top, focused, eventHandlers, pendingActions, proxy) { // top: dom node this.top = top; this.focused = focused; this.eventHandlers = eventHandlers; - this.pendingActions = pendingActions; }; View.prototype.toString = function() { return "#"; }; - View.prototype.updateFocused = function(focused) { - return new View(this.top, focused, this.eventHandlers, this.pendingActions); - }; - View.prototype.initialRender = function(top) { top.empty(); $(document.head).append(this.top.find("head").children()); @@ -49,7 +90,43 @@ View.prototype.getEventHandlers = function() { return this.eventHandlers; }; - + + View.prototype.getMock = function() { + return new MockView(this.top.clone(true), []); + }; + + + + // 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()); + // } + // }; + + @@ -83,9 +160,10 @@ return onFail(exn); } return onSuccess(new View(dom, + dom, [], [], - [])); + undefined)); } else { try { dom = $(plt.baselib.format.toDomNode(x)) @@ -93,9 +171,10 @@ return onFail(exn); } return onSuccess(new View(dom, + dom, [], [], - [])); + undefined)); } }; @@ -271,14 +350,6 @@ - var defaultToDraw = function(world, view, success, fail) { - return success(view); - }; - - - var defaultStopWhen = function(world, success, fail) { - return success(false); - }; var EventQueue = function() { @@ -304,12 +375,23 @@ + var defaultToDraw = function(MACHINE, world, view, success, fail) { + return success(view); + }; + + + var defaultStopWhen = function(MACHINE, world, view, success, fail) { + return success(false); + }; + + // bigBang. var bigBang = function(MACHINE, world, handlers) { var oldArgcount = MACHINE.argcount; - - var view = (find(handlers, isInitialViewHandler) || { view : new View(plt.baselib.format.toDomNode(world), - [], + var running = true; + var top = $(plt.baselib.format.toDomNode(world)); + var view = (find(handlers, isInitialViewHandler) || { view : new View(top, + top, [], [])}).view; var stopWhen = (find(handlers, isStopWhenHandler) || { stopWhen: defaultStopWhen }).stopWhen; @@ -323,6 +405,7 @@ PAUSE(function(restart) { var onCleanRestart = function() { + running = false; var i; for (i = 0; i < eventHandlers.length; i++) { stopEventHandler(eventHandlers[i]); @@ -334,6 +417,7 @@ }; var onMessyRestart = function(exn) { + running = false; var i; for (i = 0; i < eventHandlers.length; i++) { stopEventHandler(eventHandlers[i]); @@ -343,28 +427,43 @@ }); }; - - - var dispatchEventsInQueue = function() { // Apply all the events on the queue, call toDraw, and then stop. // If the world ever satisfies stopWhen, stop immediately and quit. var nextEvent; var data; var racketWorldCallback; + var mockView; if(! eventQueue.isEmpty() ) { + // Set up the proxy object so we can do what appear to be functional + // queries. + mockView = view.getMock(); + nextEvent = eventQueue.dequeue(); // FIXME: deal with event data here racketWorldCallback = nextEvent.handler.racketWorldCallback; - racketWorldCallback(MACHINE, world, view, // data, function(newWorld) { world = newWorld; - dispatchEventsInQueue(); + + stopWhen(MACHINE, + world, + view, + function(shouldStop) { + if (shouldStop) { + onCleanRestart(); + } else { + dispatchEventsInQueue(); + } + }, + + function(err) { + onMessyRestart(err); + }); }, function(err) { onMessyRestart(err); @@ -377,6 +476,7 @@ var startEventHandler = function(handler) { var fireEvent = function() { + if (! running) { return; } var args = [].slice.call(arguments, 0); eventQueue.queue(new EventQueueElement(handler, args)); setTimeout(dispatchEventsInQueue, 0); @@ -444,7 +544,12 @@ 'world handler'); var checkView = plt.baselib.check.makeCheckArgumentType( - isView, 'view'); + isMockView, 'view'); + + + + var checkSelector = plt.baselib.check.makeCheckArgumentType( + isString, 'selector'); EXPORTS['big-bang'] = makeClosure( @@ -545,9 +650,40 @@ }); + EXPORTS['view-focus'] = makePrimitiveProcedure( + 'view-focus', + 2, + function(MACHINE) { + var view = checkView(MACHINE, 'view-focus', 0); + var selector = checkSelector(MACHINE, 'view-focus', 1); + try { + return view.updateFocus(selector); + } catch (e) { + plt.baselib.exceptions.raise( + MACHINE, + new Error(plt.baselib.format.format( + "unable to focus to ~s", + [selector]))); + } + }); + + EXPORTS['view-text'] = makePrimitiveProcedure( + 'view-focus', + 1, + function(MACHINE) { + var view = checkView(MACHINE, 'view-focus', 0); + return view.getText(); + }); - + EXPORTS['update-view-text'] = makePrimitiveProcedure( + 'update-view-text', + 2, + function(MACHINE) { + var view = checkView(MACHINE, 'update-view-text', 0); + var text = checkString(MACHINE, 'update-view-text', 1); + return view.updateText(text); + }); ////////////////////////////////////////////////////////////////////// }()); \ No newline at end of file diff --git a/web-world/racket-impl.rkt b/web-world/racket-impl.rkt index 2111afb..a701b55 100644 --- a/web-world/racket-impl.rkt +++ b/web-world/racket-impl.rkt @@ -1,6 +1,7 @@ #lang racket/base -(provide big-bang initial-view stop-when on-tick to-draw ->view) +(provide big-bang initial-view stop-when on-tick to-draw + ->view view-focus view-text update-view-text) (define (big-bang world . handlers) (error 'big-bang "Please run in JavaScript context.")) @@ -21,4 +22,15 @@ (error 'to-draw "Please run in JavaScript context.")) (define (->view x) - (error '->view "Please run in JavaScript context.")) \ No newline at end of file + (error '->view "Please run in JavaScript context.")) + + + +(define (view-focus v selector) + (error 'view-focus "Please run in JavaScript context.")) + +(define (view-text v) + (error 'view-text "Please run in JavaScript context.")) + +(define (update-view-text v text) + (error 'update-view-text "Please run in JavaScript context.")) \ No newline at end of file