Merge branch 'web-world'
Conflicts: scribblings/cs19.scrbl
This commit is contained in:
commit
efc8dcdf9f
|
@ -170,11 +170,11 @@
|
|||
(source-name (first sources))))
|
||||
(hash-set! visited (first sources) #t)
|
||||
(let*-values ([(this-source)
|
||||
((current-module-source-compiling-hook)
|
||||
(first sources))]
|
||||
((current-module-source-compiling-hook)
|
||||
(first sources))]
|
||||
[(ast stmts)
|
||||
(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)
|
||||
(loop (append (map wrap-source (collect-new-dependencies this-source ast))
|
||||
(rest sources)))
|
||||
|
|
|
@ -216,3 +216,4 @@ For example,
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,8 +1,107 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||
<head>
|
||||
<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" />
|
||||
<meta charset="utf-8"/>
|
||||
<title></title>
|
||||
</head>
|
||||
<script>
|
||||
<html>
|
||||
<head><title>Animation</title>
|
||||
<link rel="stylesheet" href="style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
This should be animating:
|
||||
<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>
|
||||
|
|
53
web-world/examples/dwarves/dwarves.rkt
Normal file
53
web-world/examples/dwarves/dwarves.rkt
Normal 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))
|
16
web-world/examples/dwarves/index.html
Normal file
16
web-world/examples/dwarves/index.html
Normal 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>
|
31
web-world/examples/field/field.rkt
Normal file
31
web-world/examples/field/field.rkt
Normal 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))
|
12
web-world/examples/field/index.html
Normal file
12
web-world/examples/field/index.html
Normal 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>
|
|
@ -35,7 +35,17 @@
|
|||
view-text
|
||||
update-view-text
|
||||
|
||||
view-bind
|
||||
|
||||
view-show
|
||||
view-hide
|
||||
|
||||
view-attr
|
||||
update-view-attr
|
||||
|
||||
view-id
|
||||
|
||||
view-form-value
|
||||
update-view-form-value
|
||||
view-append-child
|
||||
))
|
||||
|
|
3
web-world/info.rkt
Normal file
3
web-world/info.rkt
Normal file
|
@ -0,0 +1,3 @@
|
|||
#lang setup/infotab
|
||||
|
||||
(define compile-omit-paths '("examples"))
|
|
@ -11,10 +11,45 @@
|
|||
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 =
|
||||
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.pendingActions = pendingActions;
|
||||
this.eventHandlers = eventHandlers;
|
||||
this.nonce = nonce;
|
||||
};
|
||||
|
||||
var isMockView = plt.baselib.makeClassPredicate(MockView);
|
||||
|
||||
MockView.prototype.act = function(actionForCursor, actionForReal) {
|
||||
if (arguments.length !== 2) { throw new Error("act: insufficient arguments"); }
|
||||
MockView.prototype.act = function(actionForCursor, actionForEventHandlers, actionForReal) {
|
||||
if (arguments.length !== 3) { throw new Error("act: insufficient arguments"); }
|
||||
return new MockView(actionForCursor(this.cursor),
|
||||
this.pendingActions.concat([actionForReal]),
|
||||
actionForEventHandlers(this.eventHandlers),
|
||||
this.nonce);
|
||||
};
|
||||
|
||||
|
@ -100,6 +141,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
function(eventHandlers) { return eventHandlers; },
|
||||
function(view) {
|
||||
view.focus = view.top.find(selector);
|
||||
}
|
||||
|
@ -115,30 +157,59 @@
|
|||
function(cursor) {
|
||||
return cursor.replaceNode($(cursor.node).clone(true).text(text).get(0));
|
||||
},
|
||||
function(eventHandlers) { return eventHandlers; },
|
||||
function(view) {
|
||||
view.focus.text(text);
|
||||
})
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
MockView.prototype.getAttr = function(name) {
|
||||
return $(this.cursor.node).attr(name);
|
||||
};
|
||||
|
||||
|
||||
MockView.prototype.updateAttr = function(name, value) {
|
||||
return this.act(
|
||||
function(cursor) {
|
||||
return cursor.replaceNode($(cursor.node).clone(true).attr(name, value).get(0));
|
||||
},
|
||||
function(eventHandlers) {
|
||||
return eventHandlers;
|
||||
},
|
||||
function(view) {
|
||||
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() {
|
||||
return this.act(
|
||||
function(cursor) {
|
||||
return cursor.left();
|
||||
},
|
||||
function(eventHandlers) {
|
||||
return eventHandlers;
|
||||
},
|
||||
function(view) {
|
||||
view.focus = view.focus.prev();
|
||||
});
|
||||
|
@ -149,6 +220,9 @@
|
|||
function(cursor) {
|
||||
return cursor.right();
|
||||
},
|
||||
function(eventHandlers) {
|
||||
return eventHandlers;
|
||||
},
|
||||
function(view) {
|
||||
view.focus = view.focus.next();
|
||||
});
|
||||
|
@ -159,9 +233,12 @@
|
|||
function(cursor) {
|
||||
return cursor.up();
|
||||
},
|
||||
function(eventHandlers) {
|
||||
return eventHandlers;
|
||||
},
|
||||
function(view) {
|
||||
view.focus = view.focus.parent();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
MockView.prototype.down = function() {
|
||||
|
@ -169,10 +246,112 @@
|
|||
function(cursor) {
|
||||
return cursor.down();
|
||||
},
|
||||
function(eventHandlers) {
|
||||
return eventHandlers;
|
||||
},
|
||||
function(view) {
|
||||
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.
|
||||
// fixme: may need to apply the pending actions to get the real set.
|
||||
View.prototype.getEventHandlers = function() {
|
||||
|
@ -213,7 +396,8 @@
|
|||
|
||||
View.prototype.getMockAndResetFocus = function(nonce) {
|
||||
this.focus = this.top;
|
||||
return new MockView(TreeCursor.domToCursor($(this.top).get(0)),
|
||||
return new MockView(domToCursor($(this.top).get(0)),
|
||||
[],
|
||||
[],
|
||||
nonce);
|
||||
};
|
||||
|
@ -252,7 +436,9 @@
|
|||
return onFail(exn);
|
||||
}
|
||||
return onSuccess(new View(dom, []));
|
||||
|
||||
} else if (isMockView(x)) {
|
||||
return onSuccess(new View($(x.cursor.top().node),
|
||||
x.eventHandlers));
|
||||
} else {
|
||||
try {
|
||||
dom = $(plt.baselib.format.toDomNode(x))
|
||||
|
@ -277,18 +463,52 @@
|
|||
} catch (exn) {
|
||||
return onFail(exn);
|
||||
}
|
||||
return onSuccess(new MockView(TreeCursor.domToCursor(dom.get(0)), [], undefined));
|
||||
return onSuccess(new MockView(domToCursor(dom.get(0)), [], [], undefined));
|
||||
} else {
|
||||
try {
|
||||
dom = $(plt.baselib.format.toDomNode(x))
|
||||
} catch (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) {
|
||||
WorldHandler.call(this);
|
||||
this.name = name;
|
||||
|
@ -379,7 +601,9 @@
|
|||
|
||||
/* 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
|
||||
|
@ -405,7 +629,8 @@
|
|||
|
||||
|
||||
|
||||
// Clock ticks.
|
||||
|
||||
// TickEventSource sends tick events.
|
||||
var TickEventSource = function(delay) {
|
||||
this.delay = delay; // delay in milliseconds.
|
||||
|
||||
|
@ -419,7 +644,7 @@
|
|||
TickEventSource.prototype.onStart = function(fireEvent) {
|
||||
this.id = setInterval(
|
||||
function() {
|
||||
fireEvent();
|
||||
fireEvent(undefined);
|
||||
},
|
||||
this.delay);
|
||||
};
|
||||
|
@ -432,27 +657,43 @@
|
|||
};
|
||||
|
||||
|
||||
|
||||
var BindEventSource = function(type, element) {
|
||||
// DomElementSource: string (U DOM string) -> EventSource
|
||||
// A DomEventSource allows DOM elements to send events over to
|
||||
// web-world.
|
||||
var DomEventSource = function(type, elementOrId) {
|
||||
this.type = type;
|
||||
this.element = element;
|
||||
this.elementOrId = elementOrId;
|
||||
this.handler = undefined;
|
||||
};
|
||||
|
||||
BindEventSource.prototype = plt.baselib.heir(EventSource.prototype);
|
||||
DomEventSource.prototype = plt.baselib.heir(EventSource.prototype);
|
||||
|
||||
BindEventSource.prototype.onStart = function(fireEvent) {
|
||||
this.handler =
|
||||
function(evt) {
|
||||
fireEvent(evt);
|
||||
};
|
||||
$(this.element).bind(this.type,
|
||||
this.handler);
|
||||
DomEventSource.prototype.onStart = function(fireEvent) {
|
||||
var element = this.elementOrId;
|
||||
if (typeof(this.elementOrId) === 'string') {
|
||||
element = $('#' + this.elementOrId).get(0);
|
||||
}
|
||||
|
||||
this.handler = function(evt) {
|
||||
if (element !== undefined) {
|
||||
fireEvent(element, evt);
|
||||
}
|
||||
};
|
||||
if (element !== undefined) {
|
||||
$(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) {
|
||||
$(this.element).unbind(this.type, this.handler);
|
||||
if (element !== undefined) {
|
||||
$(element).unbind(this.type, this.handler);
|
||||
}
|
||||
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.data = data;
|
||||
};
|
||||
|
@ -499,6 +741,7 @@
|
|||
// bigBang.
|
||||
var bigBang = function(MACHINE, world, handlers) {
|
||||
var oldArgcount = MACHINE.argcount;
|
||||
var oldCurrentBigBangRecord = currentBigBangRecord;
|
||||
|
||||
var running = true;
|
||||
var dispatchingEvents = false;
|
||||
|
@ -510,34 +753,74 @@
|
|||
var eventQueue = new EventQueue();
|
||||
|
||||
var top = $("<div/>");
|
||||
var eventHandlers = filter(handlers, isEventHandler).concat(view.getEventHandlers());
|
||||
|
||||
MACHINE.params.currentDisplayer(MACHINE, top);
|
||||
|
||||
PAUSE(function(restart) {
|
||||
var i;
|
||||
|
||||
var onCleanRestart = function() {
|
||||
running = false;
|
||||
var i;
|
||||
for (i = 0; i < eventHandlers.length; i++) {
|
||||
stopEventHandler(eventHandlers[i]);
|
||||
}
|
||||
stopEventHandlers();
|
||||
restart(function(MACHINE) {
|
||||
MACHINE.argcount = oldArgcount;
|
||||
currentBigBangRecord = oldCurrentBigBangRecord;
|
||||
finalizeClosureCall(MACHINE, world);
|
||||
});
|
||||
};
|
||||
|
||||
var onMessyRestart = function(exn) {
|
||||
running = false;
|
||||
var i;
|
||||
for (i = 0; i < eventHandlers.length; i++) {
|
||||
stopEventHandler(eventHandlers[i]);
|
||||
}
|
||||
stopEventHandlers();
|
||||
restart(function(MACHINE) {
|
||||
currentBigBangRecord = oldCurrentBigBangRecord;
|
||||
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.
|
||||
// If the world ever satisfies stopWhen, stop immediately and quit.
|
||||
var nextEvent;
|
||||
|
@ -549,8 +832,11 @@
|
|||
// Set up the proxy object so we can do what appear to be functional
|
||||
// queries.
|
||||
mockView = view.getMockAndResetFocus();
|
||||
|
||||
nextEvent = eventQueue.dequeue();
|
||||
if (nextEvent.who !== undefined) {
|
||||
mockView = mockView.updateFocus('#' + nextEvent.who.id);
|
||||
}
|
||||
|
||||
// FIXME: deal with event data here
|
||||
racketWorldCallback = nextEvent.handler.racketWorldCallback;
|
||||
racketWorldCallback(MACHINE,
|
||||
|
@ -564,24 +850,25 @@
|
|||
mockView,
|
||||
function(shouldStop) {
|
||||
if (shouldStop) {
|
||||
refreshViewAndStopDispatching(
|
||||
refreshView(
|
||||
function() {
|
||||
onCleanRestart();
|
||||
},
|
||||
onMessyRestart);
|
||||
fail);
|
||||
} else {
|
||||
dispatchEventsInQueue();
|
||||
dispatchEventsInQueue(success, fail);
|
||||
}
|
||||
},
|
||||
onMessyRestart);
|
||||
fail);
|
||||
},
|
||||
onMessyRestart);
|
||||
fail);
|
||||
} 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
|
||||
// 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.
|
||||
|
@ -600,47 +887,23 @@
|
|||
} else {
|
||||
view.top = $(newMockView.cursor.top().node);
|
||||
view.initialRender(top);
|
||||
// FIXME: how about events embedded in the dom?
|
||||
eventHandlers = newMockView.eventHandlers;
|
||||
startEventHandlers();
|
||||
}
|
||||
dispatchingEvents = false;
|
||||
success();
|
||||
},
|
||||
function(err) {
|
||||
dispatchingEvents = false;
|
||||
failure(err);
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
currentBigBangRecord = { stop : onCleanRestart,
|
||||
stopWithExn : onMessyRestart,
|
||||
startEventHandler : startEventHandler,
|
||||
stopEventHandler : stopEventHandler };
|
||||
view.initialRender(top);
|
||||
|
||||
var eventHandlers = filter(handlers, isEventHandler).concat(view.getEventHandlers());
|
||||
var i;
|
||||
for (i = 0; i < eventHandlers.length; i++) {
|
||||
startEventHandler(eventHandlers[i]);
|
||||
}
|
||||
startEventHandlers();
|
||||
refreshView(function() {}, onMessyRestart);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -730,27 +1025,28 @@
|
|||
'->view',
|
||||
1,
|
||||
function(MACHINE) {
|
||||
var viewable = MACHINE.env[MACHINE.env.length - 1];
|
||||
var oldArgcount = MACHINE.argcount;
|
||||
PAUSE(function(restart) {
|
||||
coerseToView(viewable,
|
||||
function(v) {
|
||||
restart(function(MACHINE) {
|
||||
MACHINE.argcount = oldArgcount;
|
||||
finalizeClosureCall(MACHINE, v);
|
||||
coerseToMockView(viewable,
|
||||
function(v) {
|
||||
restart(function(MACHINE) {
|
||||
MACHINE.argcount = oldArgcount;
|
||||
finalizeClosureCall(MACHINE, v);
|
||||
});
|
||||
},
|
||||
function(exn) {
|
||||
restart(function(MACHINE) {
|
||||
plt.baselib.exceptions.raise(
|
||||
MACHINE,
|
||||
new Error(plt.baselib.format.format(
|
||||
"unable to translate ~s to view: ~a",
|
||||
[viewable, exn.message])));
|
||||
});
|
||||
});
|
||||
},
|
||||
function(exn) {
|
||||
restart(function(MACHINE) {
|
||||
plt.baselib.exceptions.raise(
|
||||
MACHINE,
|
||||
new Error(plt.baselib.format.format(
|
||||
"unable to translate ~s to view: ~a",
|
||||
[viewable, exn.message])));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
EXPORTS['stop-when'] = makePrimitiveProcedure(
|
||||
'stop-when',
|
||||
1,
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
}());
|
|
@ -5,7 +5,18 @@
|
|||
view-focus
|
||||
view-left view-right view-up view-down
|
||||
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)
|
||||
(error 'big-bang "Please run in JavaScript context."))
|
||||
|
@ -59,4 +70,29 @@
|
|||
(error 'view-attr "Please run in JavaScript context."))
|
||||
|
||||
(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."))
|
||||
|
|
Loading…
Reference in New Issue
Block a user