Tutorial 2 - Part II
From NaviWiki
Contents |
Tutorial 2 - Part II - "In Soviet Russia, Page Talks to You!"
Putting your Page in Control
Alrite, smarty-pants, you just learned how to make stuff happen on your page by executing/evaluating Javascript from the application. You can update page elements, get the value of textboxes, and even execute your own Javascript Functions.
"But hey, what if I wanted to make the application do stuff when a user clicks a button on the page?" Aha, good question!
Here's where 'NaviData' becomes a vital piece of the puzzle. 'NaviData' is just a named, generic container that you can use to store and transmit any amount of data (key/value pairs, like in a 'map') between the page and the application.
In your application, you tell NaviManager to call some function when it receives a certain piece of NaviData from a Navi.
Sending some NaviData from the page
Okay, example time, make a new page called 'buttonNavi.html' and paste the following into it:
<html>
<head>
<script type="text/javascript" src="Navi.js"></script>
<script type="text/javascript">
window.addEvent('domready', function(){
$('myButton').addEvent('click', function(){
var myData = new NaviData('buttonClicked');
myData.add({secretMessage: "hello!"});
myData.send();
});
});
</script>
</head>
<body>
<input type="button" id="myButton" value="Click Me!" />
</body>
</html>
If you're unfamiliar with MooTools, you'll probably be scratching your head over this line:
window.addEvent('domready', function(){
Before you can mess with the elements of a page via Javascript, you must wait until the DOM (Document Object Model) is ready. Because we're altering the element 'myButton', we must wait until the window fires the 'domready' event.
Click here for the MooTorial topic on 'window.addEvent('domready', ...)'.
The next thing you might wonder about is this line:
$('myButton').addEvent('click', function(){
If we were to read this code literally, it might go something like this: "Select the Element from the document with an ID of 'myButton'. Next, register an anonymous function to be called when this Element is clicked."
Click here for the MooTorial topic on 'Element.addEvent'.
Okay, finally to the meat of the operation:
var myData = new NaviData('buttonClicked');
The first parameter is the name of the NaviData, in this case, 'buttonClicked'. The name of your NaviData is vital to remember when we get to application-side binding of callbacks.
The next bit you see is:
myData.add({secretMessage: "hello!"});
You don't actually have to add data to NaviData, you can just make an empty NaviData that is named a certain something and use that as a sort of event-flagging mechanism. But since we need to cover this topic, we're going to add some data.
Javascript Object Literals
Before we continue, let's review Javascript Objects. We can make a new Object like this:
var myObject = new Object();
and then add a member property like so:
myObject.color = "blue";
or we could also use the subscript operator to add a member property:
myObject["color"] = "blue";
So far so good? Okay, cool. Well sometimes we programmers get a little lazy and want a simpler way to do certain things. Thus, Object Literals were invented. You can make a new Object with a member property of 'color' equal to 'blue' with one statement:
var myObject = {color: "blue"};
Pretty hip, huh? And it doesn't stop there, you can separate key/value pairs with commas:
var myObject = {color: "blue", flavor: "banana", age: 19};
Please note that key names don't need quotes (you can if you wish). String literals as values need quotes, otherwise it will evaluate the statement as a variable name. Numbers don't need quotes, etc. I think it's fairly explanatory. For more info, go here.
Adding and Sending Data
So, now you know what this line is doing:
myData.add({secretMessage: "hello!"});
Actually, 'NaviData.add' can take more than just an Object, it can accept:
- An object containing key/value pairs, such as: myData.add({ message: 'hello', seconds: 37, minutes: 5 })
- An input element. The key/value of the data added will be the same as the current 'name' and 'value' properties of the element.
- A DOM element that has been instantiated as a NaviWidget (such as a element turned into a ComboBox).
- A form element that may contain multiple input elements/NaviWidget containers.
Finally, we send our NaviData to the application:
myData.send();
Some Shortcuts
Now that you know the basics, I can teach you some shortcuts. There is a second (optional) parameter of 'new NaviData()' which can be used to immediately add some data:
// this block:
var data = new NaviData('myData');
data.add({name: "Sam"});
// can be written:
var data = new NaviData('myData', {name: "Sam"});
You can use '$ND(x, y)' as a shortcut for 'new NaviData(x, y)'. So now we can shorten part of our code:
// original
var myData = new NaviData('buttonClicked');
myData.add({secretMessage: "hello!"});
myData.send();
// can be written:
$ND('buttonClicked', {secretMessage: "hello!"}).send();
Handling NaviData in the Application
It's pretty useless to send NaviData without first registering/binding callbacks in the application. So let's do that now.
Let's instantiate NaviManager, create a Navi named 'buttonNavi', and bind our NaviData:
using namespace NaviLibrary;
void MyApplication::Startup()
{
NaviManager* naviMgr = new NaviManager(renderWindow);
Navi* buttonNavi = naviMgr->createNavi("buttonNavi", "local://buttonNavi.html", NaviPosition(TopCenter), 500, 500);
buttonNavi->bind("buttonClicked", NaviDelegate(this, &MyApplication::handleClick));
}
We're assuming that we have a class named 'MyApplication' with a public member function of 'handleClick' to which we can route all NaviData named "buttonClicked" to.
But hold on a second, 'MyApplication::handleClick' must have a certain function signature for it to be used as a NaviData delegate/callback. Here's how it should look:
void MyApplication::handleClick(const NaviData &naviData)
{
// handle NaviData here
}
Got it? Good.
The Lost Cult of MultiValue
Okee dokee, now we can finally do something with the NaviData! You should think of application-side NaviData as a map data structure that has strings for keys and a custom object, 'MultiValue', for values. You must explicitly convert the MultiValue to the data type you want. For example:
using namespace NaviLibrary::NaviUtilities; MultiValue myValue = naviData["someKey"]; int numberVal = myValue.int(); std::string stringVal = myValue.str();
You should read up on some of the documentation for NaviUtilities::MultiValue to fully understand NaviData.
Alrite, so now we can finally write our function:
void MyApplication::handleClick(const NaviData &naviData)
{
std::string secretMessage = naviData["secretMessage"].str();
Ogre::LogManager::getSingleton().logMessage("Got a secret message!: " + secretMessage);
}
Pretty awesome, huh?
"Uh oh!", you scream, "What about data validation? How can I make sure that a certain NaviData key exists and is the right data type?". Calm down friend, I was just getting to that part. :)
NaviData Validation
You can use NaviData::ensure to assert that a certain key name exists:
void MyApplication::handleClick(const NaviData &naviData)
{
naviData.ensure("secretMessage");
std::string secretMessage = naviData["secretMessage"].str();
Ogre::LogManager::getSingleton().logMessage("Got a secret message!: " + secretMessage);
}
But hey, what if you wanted to make sure multiple keys exist? Well, there's an overload of NaviData::ensure that takes a vector of strings. For super-quick instantiation, you can use the Inline String Vector class, "Strings", from NaviUtilities:
naviData.ensure(Strings("name")("address")("city"));
Totally awesome, I know.
But wait, there's more! Sometimes you need to access the MultiValue as a Number which means you need to validate whether or not the value is actually numeric. NaviData::ensure can additionally assert that the value of a certain key name is numeric if you prefix the key name with a pound sign (#):
naviData.ensure(Strings("name")("#age"));
Here's a handy shortcut that I like to use in my own code. Navi::bind has an optional last parameter that lets you specify a vector of strings to validate using NaviData::ensure automatically.
coolNavi->bind("createChar", &onCreateChar, Strings("username")("#height")("country"));
Initializing Navis via Javascript
Sometimes you will find that you need to populate the page of a Navi with data at runtime. To achieve this, I recommend using Javascript Evaluation on the application-side.
You may be thinking, "oh well, can't I just do something like this?":
Navi* myNavi = naviMgr->createNavi("myNavi", "local://mypage.html", NaviPosition(0, 0), 425, 400);
myNavi->evaluateJS("$('chatBox').setHTML(?)", Args(chatMessage));
Nope, sorry. There's a problem, you can only execute/evaluate Javascript when the DOM (Document Object Model) of the page is ready. Fear not, if you have included 'Navi.js' in your page, then the page will automatically send to the application an empty NaviData object named 'ready' when the DOM is ready. So you will need to bind a callback to 'ready' and then execute/evaluate your Javascript in that callback.
Navi* myNavi = naviMgr->createNavi("myNavi", "local://mypage.html", NaviPosition(0, 0), 425, 400);
myNavi->bind("ready", NaviDelegate(this, &MyApplication::onNaviLoad));
// somewhere else
void MyApplication::onNaviLoad(const NaviData &naviData)
{
myNavi->evaluateJS("$('chatBox').setHTML(?)", Args(chatMessage));
// etc.
}
Wrapping it up
Wow, this has been a long tutorial but I think I have covered all the major areas. I bet you're feeling pretty smart now!
Well what are you waiting for? Go forth and create, get messy, and have fun!

