Skip to content

Instantly share code, notes, and snippets.

@EGreg
Last active December 11, 2015 22:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save EGreg/4673641 to your computer and use it in GitHub Desktop.
Save EGreg/4673641 to your computer and use it in GitHub Desktop.
Examples of how to use Q
SERVER SIDE:
========================= classes/Chess.php =====================
// MODEL ON SERVER
class Chess extends Base_Chess // here's one particular table
{
function makeMove($e)
{
$this->doSomething();
$this->save();
}
}
========================= handlers/Chess/move/validate ===========
========================= handlers/Chess/move/put ================
========================= handlers/Chess/move/post ===============
========================= handlers/Chess/move/delete =============
========================= handlers/Chess/move/response ===========
========================= handlers/Chess/move/response/$slotName =
// CONTROLLER ON SERVER - STRONGLY ENCOURAGES HTTP REST INTERFACE
function Chess_move_post() // here's one particular action & http verb
{
// assume validate.php already did what it has to do
// so usually we don't have to pollute this with checks
$chess->makeMove(); // call model
}
function Chess_move_response_stream() // here's one particular slot
{
return Chess::$cache['stream']; // or any other way to cache useful objects in script
}
function Chess_game_response_column1() // for a SmartApp app
{
$options = array(
'something' => 'something we need to send urgently',
'why' => 'to avoid more requests'
);
return Q::tool('Chess/game', $options);
}
========================= views/Chess/$slotName/foo.mustache ====
<div id="$slotName">
enjoy writing your mustache templates here
</div>
Client API:
Q.req("Module/action", slotNames, callback, options);
Server API:
request: Server should access $_REQUEST and Q_Request::special($field, $default)
response: Fill appropriate slots. In column1, column2 slots, etc. put most dynamic data into tool options.
Transport:
Typical response would look like this:
{
"slots": {
"column1": "<div id='Chess_game_tool' class='Q_tool Chess_game_tool' tool-options='{\"foo\": \"bar\"}' />"
},
"timestamp": 192380329912.1232
}
**********************************************************************************
MVC WITH Q SERVER:
Client -> Request to PHP -> Dispatch to Controller -> Model
-> internal post to node.js -> socket.io -> Client
The Request is either AJAX or from Location Bar.
HTTP interface is usually REST!
**********************************************************************************
CLIENT SIDE:
========================= web/js/Chess.js =======================
MODULE ON CLIENT:
var Chess = Q.plugins.Chess = {};
Chess.staticMethod = function () {
// whatever you can think of
};
Chess.getMyGame = function (token, callback) {
if (!Q.Users.loggedInUser) {
return callback(Q.Users.Exception.NotLoggedIn);
}
Q.Streams.Stream.get(Q.Users.loggedInUser.id, "Chess/Game/"+token, callback);
// we automatically use the Q.Cache.local("Streams.Stream.getter") object
// as the "central storage" for all the streams we are using on the client
// and what's more ... this cache is updated as soon as messages arrive!
// so it is almost always up to date, and reflects latest server state.
// Most caches become out of date until sync, but thanks to socket.io,
// we can update ours when relevant messages are pushed to streams.
}
// Now let's define some MODELS!!!!!
Q.Streams.constructors["Chess/Game"] = "js/Chess/models/Game.js"
// Now let's define a TOOL!!!!!!
Q.Tool.constructors["Chess/Game"] = "js/Chess/tools/Game.js"
return Chess;
========================= web/js/Chess/models/Game.js ====================
CLIENT SIDE MODEL!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! :-)
Streams.constructors["Chess/Game"] = function () {
// In JavascriptMVC, canJS and Backbone, models inherit from can.Model, etc.
// In our framework, they inherit from Q.Streams.Stream
console.log(this.fields); // the fields of the stream
};
// CLIENT SIDE CONTROLLER IS IMPLEMENTED AS METHODS ON TOP OF CLIENT SIDE MODEL,
// BECAUSE WE DO NOT PLAN TO CHANGE MODEL UNDERNEATH CONTROLLER LAYER:
Streams.constructors["Chess/Game"].move(from, to) {
// validate move on client side <---- here is where you would do it
this.post({
"type": "Chess/move",
"instructions": {from: from, to: to}
});
// all access checks, real-time updates to participants,
// and subscriptions+notifications are done out of the box by stream
};
Streams.constructors["Chess/Game"].onMove = new Q.Event();
Streams.onMessage("Chess/Game", "Chess/move").set(function (err, message) {
var data = extractDataFromMessage(message); // whatever it does
this.onMove(data); // tell whoever wants to know
// NOTICE ... THE MODEL CAN LISTEN FOR MESSAGES FROM ITS STREAM
// AND DIGESTS THEM AND TELL WHOEVER WANTS TO KNOW ABOUT "updates"
// In MVC, who listens to updates? VIEWS!!!
// Tools are VIEWS in that diagram -- so now let's define some tools.
});
========================= web/js/Chess/tool/Game.js ====================
CLIENT SIDE VIEWS!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Q.Tool.constructors["Chess/Game"] = function (prefix, options) {
var tool = this;
var $template = jQuerySpaghettiCodeToMakeDOM();
var $template2 = niceMustacheTemplate(name, options);
Q.addStylesheet("css/Chess.css"); // we need this css for this tool
Chess.getMyGame(options.token, function renderTheGame(err, game) {
// you see, tool is a view
game.onMove.set(function () {
// show the move happening on the board
}, tool); // AUTOMATICALLY REMOVED WHEN TOOL IS REMOVED!!!!
});
// let others know when something happens in this tool
this.onClickButton = new Q.Event();
this.onResign = new Q.Event();
// attach events
$('button', $template2).click(this.onClickButton.handle);
$('.resign', $template2).click(function () {
if (!confirm("Really resign?")) return;
this.onResign.handle();
});
};
SIMPLE! CLEAN! EVERYTHING IS IN ITS OWN FILE!!!!!
IT FITS NOT JUST MVC ON SERVER BUT MVC ON CLIENT.
WHAT IS THE PROBLEM???????
FIRST, LOOK IT OVER AGAIN AND REALLY PUT ASIDE YOUR PREJUDICES,
REALLY LOOK AT IT AND REALIZE WHAT THE DESIGN IS NOW.
THEN WE CAN DISCUSS. :)
@inazar
Copy link

inazar commented Jan 30, 2013

model:

figures: {
    king: {
        x: X
        y: Y
    }
    queen: {
        x: X
        y: Y
    }
cells: {
        A1: XX
        A2: YY
    }
}

REST interface, message listeners

controller:

each visual class contains render, animation etc. methods AND DOM node. F.E. animation means calling jQuery method on figure DOM node - whatever it means. You can think about DOM properties as extra "visualization" model attached to visual element. Controller changes property and some shape on the screen changes.

class figure (model figures)
    template
    moves

    methods:
        move(from, to)
        die()
        ....

class king inherits figure (model figure.king)
class queen inherits figure (model figures.queen)
....
class cell
class board = collection of cells (model cells)

views:

king
queen
....
cell
board

Logic:

  1. create board () -> create cell A1, create cell A2 etc.
  2. create king, create queen ...
  3. wait

User picks a figure from e2

  1. figure controller catch mouse drag and follow the user interraction

User drops the figure on e4

  1. figure controller send a message "move", {X, Y} and removes figure from view
  2. board controller analyze situation and either cancel move or display "check" or whatever
  3. cell controllers catch "move" and if X is over e2 and Y over e4 model cells are modified and put to store/cache etc.
  4. model inform views for cell e2 and e4 to update

As user interraction is local sending a "move" message to other player trigger "move" event. Figure controller knows that
it is other's turn and triggers figure view animation to new cell

ready

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment