diff --git a/example/php-tictactoe/ai.php b/example/php-tictactoe/ai.php
new file mode 100644
index 0000000..2bef999
--- /dev/null
+++ b/example/php-tictactoe/ai.php
@@ -0,0 +1,61 @@
+
+
+
+ Javelin Example: Tic Tac Toe
+
+
+
+
+
+
+ This is a simple Tic-Tac-Toe game using Javelin and PHP.
+ Click a square to make the first move.
+';
+ $table[] = Javelin::renderTag(
+ 'td',
+ 'Your move.',
+ array(
+ 'sigil' => 'tic-tac-status',
+ 'class' => 'status',
+ 'colspan' => '3',
+ ));
+ for ($y = 0; $y < 3; $y++) {
+ $table[] = '';
+ for ($x = 0; $x < 3; $x++) {
+ $table[] = Javelin::renderTag(
+ 'td',
+ '',
+ array(
+ 'class' => 'tictac',
+ 'meta' => array(
+ 'pos' => $y * 3 + $x
+ ),
+ 'sigil' => 'tic-tac-cell',
+ 'mustcapture' => true,
+ ));
+ }
+ $table[] = '
';
+ }
+
+ echo Javelin::renderTag(
+ 'table',
+ implode("\n", $table),
+ array(
+ 'sigil' => 'tic-tac-board',
+ ));
+
+ Javelin::initBehavior('tic-tac-toe');
+
+?>
+
+
+
+
+
diff --git a/example/php-tictactoe/tic-tac-toe.js b/example/php-tictactoe/tic-tac-toe.js
new file mode 100644
index 0000000..f833e3a
--- /dev/null
+++ b/example/php-tictactoe/tic-tac-toe.js
@@ -0,0 +1,64 @@
+JX.behavior('tic-tac-toe', function() {
+
+ var game = [0,0,0,0,0,0,0,0,0];
+ var state = 'play';
+
+ JX.Stratcom.listen(
+ 'click',
+ 'tic-tac-cell',
+ function (e) {
+ if (state != 'play') {
+ return;
+ }
+
+ var node = e.getNodes()['tic-tac-cell'];
+ var data = e.getData()['tic-tac-cell'];
+ var board = e.getNodes()['tic-tac-board'];
+
+ var move = game[data.pos];
+
+ if (move) {
+ alert('That square was already taken by '+move+'.');
+ return;
+ }
+
+ makeMove(board, 'X', data.pos);
+
+ changeState(board, 'wait');
+ new JX.Request('ai.php', function(response) {
+ if (response.move) {
+ makeMove(board, response.move, response.pos);
+ }
+ if (response.outcome) {
+ changeState(board, 'done', response.outcome);
+ } else {
+ changeState(board, 'play');
+ }
+ })
+ .setData({game: JX.JSON.serialize(game)})
+ .send();
+ });
+
+ function makeMove(board, player, pos) {
+ game[pos] = player;
+ var cells = JX.DOM.scry(board, 'td', 'tic-tac-cell');
+ for (var ii = 0; ii < cells.length; ii++) {
+ var data = JX.Stratcom.getData(cells[ii]);
+ if (data.pos == pos) {
+ JX.DOM.setContent(cells[ii], player);
+ break;
+ }
+ }
+ }
+
+ function changeState(board, new_state, message) {
+ var msg;
+ switch (new_state) {
+ case 'wait': msg = 'Waiting for AI...'; break;
+ case 'done': msg = message; break;
+ case 'play': msg = 'Your move.';
+ }
+ JX.DOM.setContent(JX.DOM.find(board, 'td', 'tic-tac-status'), msg);
+ state = new_state;
+ }
+});
diff --git a/pkg/init.dev.js b/pkg/init.dev.js
index f94cdae..50fc2af 100644
--- a/pkg/init.dev.js
+++ b/pkg/init.dev.js
@@ -1,125 +1,156 @@
/**
* Javelin core; installs Javelin and Stratcom event delegation.
*
* @provides javelin-magical-init
* @nopackage
+ * @javelin
*/
(function() {
+
if (window.JX) {
return;
}
window.JX = {};
window['__DEV__'] = window['__DEV__'] || 0;
var loaded = false;
var onload = [];
var master_event_queue = [];
JX.__rawEventQueue = function (what) {
what = what || window.event;
master_event_queue.push(what);
- // Sometimes IE gives us events which throw when ".type" is accessed;
- // just ignore them since we can't meaningfully dispatch them. TODO:
- // figure out where these are coming from.
- try { var test = what.type; } catch (x) { return; }
-
- if (what.type == 'domready') {
- var autofocus = document.getElementById('autofocus');
- if (autofocus) {
- try { autofocus.focus(); } catch(x) { }
- }
- }
if (JX.Stratcom && JX.Stratcom.ready) {
// Empty the queue now so that exceptions don't cause us to repeatedly
// try to handle events.
var local_queue = master_event_queue;
master_event_queue = [];
for (var ii = 0; ii < local_queue.length; ++ii) {
var evt = local_queue[ii];
+ // Sometimes IE gives us events which throw when ".type" is accessed;
+ // just ignore them since we can't meaningfully dispatch them. TODO:
+ // figure out where these are coming from.
+ try { var test = evt.type; } catch (x) { continue; }
+
if (!loaded && evt.type == 'domready') {
- document.body.id = null;
+ document.body && (document.body.id = null);
loaded = true;
+
for (var ii = 0; ii < onload.length; ii++) {
onload[ii]();
}
+
}
+
JX.Stratcom.dispatch(evt);
}
} else {
var t = what.srcElement || what.target;
if (t
&& (what.type in {click:1, submit:1})
&& (' '+t.className+' ').match(/ FI_CAPTURE /)) {
what.returnValue = false;
what.preventDefault && what.preventDefault();
document.body.id = 'event_capture';
return false;
}
}
}
JX.enableDispatch = function(root, event) {
if (root.addEventListener) {
root.addEventListener(event, JX.__rawEventQueue, true);
} else {
root.attachEvent('on'+event, JX.__rawEventQueue);
- root.onfocusin = JX.__rawEventQueue;
- root.onfocusout = JX.__rawEventQueue;
}
- }
+ };
var root = document.documentElement;
- var events = ['click', 'submit', 'keypress', 'focus', 'blur', 'mousedown',
- 'mouseover', 'mouseout', 'keydown'];
- for (var ii = 0; ii < events.length; ++ii) {
- JX.enableDispatch(root, events[ii]);
+ var document_events = [
+ 'click',
+ 'keypress',
+ 'mousedown',
+ 'mouseover',
+ 'mouseout',
+ 'mouseup',
+ 'keydown',
+ // Opera is multilol: it propagates focus/blur oddly and propagates submit
+ // in a way different from other browsers.
+ !window.opera && 'submit',
+ window.opera && 'focus',
+ window.opera && 'blur'
+ ];
+ for (var ii = 0; ii < document_events.length; ++ii) {
+ document_events[ii] && JX.enableDispatch(root, document_events[ii]);
+ }
+
+ // In particular, we're interested in capturing window focus/blur here so
+ // long polls can abort when the window is not focused.
+ var window_events = [
+ 'unload',
+ 'focus',
+ 'blur'
+ ];
+ for (var ii = 0; ii < window_events.length; ++ii) {
+ JX.enableDispatch(window, window_events[ii]);
}
+ JX.__simulate = function(node, event) {
+ if (root.attachEvent) {
+ var e = {target: node, type: event};
+ JX.__rawEventQueue(e);
+ if (e.returnValue === false) {
+ return false;
+ }
+ }
+ };
+
// TODO: Document the gigantic IE mess here with focus/blur.
// TODO: beforeactivate/beforedeactivate?
// http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
if (!root.addEventListener) {
root.onfocusin = JX.__rawEventQueue;
root.onfocusout = JX.__rawEventQueue;
}
root = document;
if (root.addEventListener) {
if (navigator.userAgent.match(/WebKit/)) {
var timeout = setInterval(function() {
if (/loaded|complete/.test(document.readyState)) {
JX.__rawEventQueue({type: 'domready'});
clearTimeout(timeout);
}
}, 3);
} else {
root.addEventListener('DOMContentLoaded', function() {
JX.__rawEventQueue({type: 'domready'});
}, true);
}
} else {
var src = 'javascript:void(0)';
var action = 'JX.__rawEventQueue({\'type\':\'domready\'});';
// TODO: this is throwing, do we need it?
// 'this.parentNode.removeChild(this);';
document.write(
'';
+ } else {
+ return '';
+ }
+ }
+
+ public static function renderAjaxResponse($payload, $error = null) {
+ $response = array(
+ 'error' => $error,
+ 'payload' => $payload,
+ );
+
+ $javelin = self::getInstance();
+ if ($javelin->metadata) {
+ $response['javelin_metadata'] = $javelin->metadata;
+ $javelin->metadata = array();
+ }
+
+ if ($javelin->behavior) {
+ $response['javelin_behaviors'] = $javelin->behavior;
+ $javelin->behavior = array();
+ }
+
+ if ($javelin->onload) {
+ throw new Exception(
+ "Javelin onload functions have been registered, but the response is ".
+ "being rendered as an Ajax response. This is invalid; use behaviors ".
+ "instead.");
+ }
+
+ $javelin->dirty = false;
+
+ $response = 'for (;;);'.json_encode($response);
+ return $response;
+ }
+
+ protected function __construct() {
+ // Protected constructor.
+ }
+
+ public function __destruct() {
+ if ($this->dirty) {
+ throw new Exception(
+ "Javelin has behaviors, metadata or onload functions to include in ".
+ "the response but you did not call renderHTMLFooter() or ".
+ "renderAjaxResponse() after registering them.");
+ }
+ }
+
+ protected static function getInstance() {
+ if (empty(self::$instance)) {
+ self::$instance = new Javelin();
+ }
+ return self::$instance;
+ }
+}
+