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; + } +} +