<html>
<head>
<title>Yote</title>
<script src="/yote/js/jquery-latest.js"></script>
<script src="/yote/js/jquery.dumper.js"></script>
<script src="/yote/js/jquery.cookie.js"></script>
<script src="/yote/js/jquery.base64.min.js"></script>
<script src="/yote/js/json2.js"></script>
<link href="/css/bootstrap.css" rel="stylesheet" type="text/css" media="all" />
<link href="/css/bootstrap-responsive.css" rel="stylesheet" type="text/css" media="all" />
<script src="/yote/js/yote.js"></script>
<script src="/yote/js/yote.util.js"></script>
<link rel="stylesheet" href="/yote/css/jquery.mobile.css" type="text/css" media="all" />
<link rel="stylesheet" href="/yote/css/ui-lightness/jquery-ui-1.8.23.custom.css" type="text/css" media="all" />
<link href="/yote.css" rel="stylesheet" type="text/css" media="all" />
<script>
$().ready(function(){
var $window = $(window);
$.yote.init();
var page_counter = $.yote.fetch_app('Yote::Util::Counter');
var count = page_counter.increment( 'Samples Page' );
$('#counter').empty().append( count );
});
</script>
</head>
<BODY data-spy="scroll" data-target="#navcol" >
<DIV class="navbar navbar-fixed-top navbar-inverse">
<DIV class="navbar-inner">
<A class="brand"><img src="yotelogo.png"></A>
<UL class="nav">
<LI><a href="index.html">About</a></LI>
<LI><A href="install.html">Install</A></LI>
<LI class="active"><A href="samples.html">Samples</A></LI>
<!-- <LI><A href="program.html">Program</A></LI> -->
</UL>
</DIV>
</DIV>
<HEADER class="jumbotron subhead" style="height:70px">
</HEADER>
<SECTION>
<DIV class="page-header">
<H1>A Web Counter App</H1>
</DIV>
<H3>USE CASE</h3>
<p>
<ul>
<li>Increment the number of times the page (given by name) has been seen and return this number</li>
</ul>
In this simple example, I am not taking IP origin into account.
</p>
<H3>Implementation</h3>
<DL>
<DT>Server Side Code</DT>
<DD>
<pre>
package Yote::Util::Counter;
use base 'Yote::AppRoot';
sub _init {
<span class="muted"># this is called only when the app is loaded for the first time.</span>
my $self = shift;
<span class="muted"># create a hidden field called _counts
# fields starting with an underscore are not passed to the client side</span>
<strong>$self->set__counts( {} );</strong>
}
sub increment {
my( $self, $data, $account, $env ) = @_;
<span class="muted"># not used, but this demonstrates how the IP address can be obtained</span>
my $ip = $env->{ REMOTE_ADDR };
<span class="muted"># data in this case is the name of the page we are tracking. It is the value
# of the argument passed into the javascript method call ( see below )</span>
<strong>my $count = $self->get__counts()->{$data} + 1;
$self->get__counts()->{$data} = $count;
return $count;</strong>
}
1;
</pre>
</DD>
<DT>Client Side Code</DT>
<DD>
The following code snippet illustrates how the server side code can be invoked from the
client side. Yote javascript depends on jquery being used.
<pre>
<script src="/yote/js/jquery-latest.js"></script>
<script src="/yote/js/yote.js"></script>
<script src="/yote/js/yote.util.js"></script>
<script>
$().ready(function(){
$.yote.init(); <span class="muted">// must be called once before any objects are used.</span>
<span class="muted">// fetches the app from the server side, as long as it can be found in the classpath.</span>
var page_counter = $.yote.fetch_app('Yote::Util::Counter');
<span class="muted">// increments and returns the count for the main page</span>
var count = page_counter.increment( Samples Page' );
alert( 'The Main Page has been viewed ' + count + ' times' );
});
</script>
</pre>
</DD>
</DL>
</SECTION>
<SECTION>
<DIV class="page-header">
<H1>A Simple ToDo App</H1>
</DIV>
<p>
Here is a very simple to do list app that only shows one thing to do at a time. The app has four use cases :
<ul>
<li>The app shows the currently selected item of the to-do list</li>
<li>The user enters a new item into the to-list.</li>
<li>Selecting a random item in the to-do list</li>
<li>Completing the currently selected item from the to-do list</li>
</ul>
To start, we create a package that is going to be a Yote App. All Yote Apps descend from the Yote::AppRoot class.
</p>
<h4>Server Side Code</h4>
<pre>
package Yote::Sample::SimpleTodo;
use base 'Yote::AppRoot';
sub _init_account {
<span class="muted"># Gets called when a person uses this app for the first time.
# This is useful to populate their account data container with initial values.</span>
my( $self, $account ) = @_;
my $first = "Enter todo items";
<span class="muted"># create a list for the 'my_todos' field and add $first to it.</span>
$account->add_to_my_todos( $first );
<span class="muted"># attach the value of $first to the current_todo field.</span>
$account->set_current_todo( $first );
} <span class="muted">#_init_account</span>
sub add_item {
<span class="muted"># all yote subs called from the client side get passed in the following arguments.
# data is the data given to the client javascript object call of the app.
# In this case, it is a string that is the to do item.
# account is the account for the currently logged in user.
# The account is a container that stores items for this app that relate to
# who is logged in now.
# environ is a hash of environment values given at the time of the request.
# Most often used from this is IP address to deteremine locale of the requester.</span>
my( $self, $data, $account, $environ ) = @_;
<span class="muted"># add the value to the 'my_todos' field's list but do not duplicate that value in the list.</span>
$account->add_once_to_my_todos( $data );
} <span class="muted">#add_item</span>
sub pick_random_todo {
<span class="muted"># picks a random to do item from the list.
# returns the picked entry.</span>
my( $self, $data, $account, $environ ) = @_;
my $todos = $account->get_my_todos();
my $rand = $todos->[ rand( @$todos ) ];
$self->set_current_todo( $rand );
return $rand;
} <span class="muted">#pick_random_todo</span>
sub complete_current_item {
<span class="muted"># removes the current todo item and picks an other random one, returning it</span>
my( $self, $data, $account, $environ ) = @_;
my $current = $account->get_current_todo();
$account->remove_from_my_todos( $current );
return $self->pick_random_todo( $data, $account, $environ );
} <span class="muted">#complete_current_item</span>
1;
</pre>
<P>
The above completes the server side coding for this app.<BR>
The following is a javascript snippet that interacts with the app.
We can use the parts of the snippet to create a real app page.
</P>
<h4>Client Side Code</h4>
<pre>
$.yote.init(); <span class="muted">// connects to the server</span>
var todo_app = $.yote.fetch_app('Yote::Sample::SimpleTodo');
alert( "The current todo item is " + todo_app.get_current_todo() );
todo_app.add_item( "Find even more things to do" );
todo_app.pick_random_todo();
todo_app.complete_current_item();
alert( "The current todo item is now " + todo_app.get_current_todo() );
</pre>
</SECTION>
<SECTION>
<DIV class="page-header">
<H1></H1>
</DIV>
</SECTION>
<footer>
This page has been viewed <span id="counter"></span> times.
</footer>
<script src="/js/bootstrap.js"></script>
</body>
</html>
<!--
<H2>Secret Collect</H2>
<P>Let's make a trivial but real riddle game called 'Secret Collect'.
In this game, you add riddles and try to answer the riddles of others.</P>
<P>
You start the game by entering in 3 riddles. You answer others riddles
to steal them away. If your riddles are stolen, you may enter to get to three.
Your score is how many riddles you own.
</P>
<p>
The first step in creating this app is to sketch out a design and the elements needed.
</P>
<h3>Elements</h3>
<blockquote>
<h4>Application</h4>
The application runs the game. It keeps track of players and questions. The players
call methods on the application to play the game. Yote creates a singleton instance of
the application mapped to its package name internally. This application will store a list
of riddles that can be collected.
<h4>Players</h4>
Yote provides Account objects, which are the players in this case.
Methods invoked on the application are invoked with
Yote account objects, automatically passed to the called method. Accounts are created and
accessed using the javascript calls $.yote.create_account and $.yote.login. Once logged in,
the javascript in the browser keeps track of the login state and account identification.
Each account has a container object per application. This is meant to store things
like documents, games played in the app, messages sent and received or pictures stored.
For this application, it stores a list of collected secrets.
<h4>Riddles</h4>
Riddles are the secrets to be collected in the game. They are basic yote objects containing a question
and an encrypted version of the answer to prevent cheating.
</blockquote>
</div>
</div>
</div>
<h3>Object Model</h3>
<h4>Application</h4>
Class : <i>Yote::Sample::SecretCollect</i>
<blockquote>
Methods - Player data is automatically passed to the methods.
<ul>
<li>add_riddle( { question:"question", answer:"answer" } )</li>
<li>can_start()</li>
<li>random_riddle()</li>
</ul>
Data
<ul>
<li>riddles (list)</li>
<li>riddle_count (number)</li>
</ul>
</blockquote>
<h4>Player Data</h4>
Class : <i>Yote::AccountRoot</i>
<blockquote>
Data
<ul>
<li>my_riddles</li>
<li>guesses</li>
</ul>
</blockquote>
<h4>Riddle</h4>
Class : <i>Yote::Obj</i>
<blockquote>
Data
<ul>
<li>question</li>
<li>secret_answer - encyrpted answer</li>
<li>owner - who currently owns it. This is an account root object.</li>
<li>collect_count - number of times this has been collected</li>
<li>guesses - number of times this has been guessed</li>
</ul>
</blockquote>
<h3>Client Side Code</h3>
<pre>
<html><head><title>Secret Collect</title>
<script src="/js/jquery-latest.js"></script>
<script src="/js/jquery.dumper.js"></script>
<script src="/js/jquery.base64.min.js"></script>
<script src="/js/json2.js"></script>
<script src="/js/yote.js"></script>
<script>
$().ready(function(){ <span class=emp>
var secret_collect_app = $.yote.fetch_app('Yote::Sample::SecretCollect');
$('#button').click( function() {
var result = hello_app.hello({ name:$('#txt').val() } );
alert( result ); //get the message from running the hello method.
alert( 'testfield is ' + hello_app.get_testfield() ); //get the value of testfield that is attached to the app
var counter = hello_app.get_counter(); //get the counter object that is attached to the app
alert( 'counter is at ' + counter.get_count() ); //get the value of the count field of the counter object attached to the app
} );
</emp>
});
</script></head>
<body><h1>Secret Collect</h1>
</body></html>
</pre>
<h3>Server Side Code</h3>
As long as the classes defined are in perl's classpath, they will be accessible.
There is no need to start the yote server to load them. At the time of this writing
if you make changes to the classes, yote must be restarted to see those changes.
<pre>
package Yote::Sample::SecretCollect;
use base 'Yote::AppRoot';
use Crypt::Passwd;
sub add_riddle {
my( $self, # This singleton AppRoot object.
# It lives in /apps/Yote::Sample::SecretCollect
# Calling
# var app = $.yote.fetch_app('Yote::Sample::SecretCollect');
# on the client side will return only this instance.
$data, # The data structure sent by the client.
# This app is expecting app.add_riddle({question:'ques',answer:'ans'});
$acct_root, # This is a container specific to the account calling add_riddle
# and the SecretCollect app. This is meant to store state data
# for the player that does not clash with state data they have
# for any other app.
$acct # The account object the user is logged in as.
# It is created by calling
# $.yote.create_account( {} );
) = @_;
#
# Create a new riddle object and add it to the account root's riddle supply.
# encrypt the riddle to hide its answer.
#
# The riddle methods 'set_question', 'set_secret_answer', 'set_owner'
# are automatically there and need no definition.
# The account root
#
my $riddle = new Yote::Obj();
$riddle->set_question( $data->{question} );
$riddle->set_secret_answer( unix_std_crypt( $data->{answer},
$data->{question} ) );
$riddle->set_owner( $acct_root );
$acct_root->add_to_my_riddles( $riddle );
#
# add the riddle to all riddles the app has
#
$self->add_to_riddles( $riddle );
$self->set_riddle_count( 1 + $self->get_riddle_count() );
return { msg => 'riddle added' };
} #add_riddle
sub can_start {
my( $self, $data, $acct_root, $acct ) = @_;
# need 3 riddles to start guessing
return { r => @{ $acct_root->get_riddles( [] ) } > 0 };
}
sub random_riddle {
my( $self, $data, $acct_root, $acct ) = @_;
unless( $self->can_start( $data, $acct_root, $acct ) ) {
return { err => 'Must have 3 riddles to guess others' };
}
my $riddle_count = $self->get_riddle_count();
if( $riddle_count == 0 ) {
return { err => 'there are no riddles to guess' };
}
#
# Pick the riddle without having to load in the whole riddle array :
#
my $riddle_idx = int( rand( $riddle_count ) );
my $riddle = $self->_xpath( "/riddles/$riddle_idx" );
return { riddle => $riddle };
} #random_riddle
sub guess_riddle {
my( $self, $data, $acct_root, $acct ) = @_;
my $riddle = $data->{riddle};
my $answer = $data->{answer};
my $riddle_owner = $riddle->get_owner();
#
# Collect stats on the riddle. They can be accessed on the client side
# by calling riddle.get_guesses();
# Don't bother incrementing for one's own riddle.
#
if( ! $riddle_owner->is( $acct_root ) ) {
$riddle->set_guesses( 1 + $riddle->get_guesses() );
$acct_root->set_guesses( 1 + $acct_root->get_guesses() );
}
if( $riddle->get_secret_answer() eq unix_std_crypt( $answer, $riddle->get_question() ) ) {
#
# A secret collect! Change ownership and update the stats.
#
if( ! $riddle_owner->is( $acct_root ) ) {
$acct_root->set_collected_count( 1 + $acct_root->get_collected_count() );
$riddle->set_collect_count( 1 + $riddle->get_collect_count() );
$riddle_owner->remove_from_my_riddles( $riddle );
$acct_root->add_to_my_riddles( $riddle );
$riddle->set_owner( $acct_root );
}
return { msg => 'You collected this riddle' };
}
else {
return { msg => 'You got the wrong answer' };
}
} #guess_riddle
1;
</pre>
<h2>Client Side Code</h2>
<blockquote>
<pre>
<html><head><title>Hello World</title>
<script src="/js/jquery-latest.js"></script>
<script src="/js/jquery.dumper.js"></script>
<script src="/js/jquery.base64.min.js"></script>
<script src="/js/json2.js"></script>
<script src="/js/yote.js"></script>
<script>
$().ready(function(){ <span class=emp>
var hello_app = $.yote.fetch_app('Yote::Hello');
$('#button').click( function() {
var result = hello_app.hello({ name:$('#txt').val() } );
alert( result ); //get the message from running the hello method.
alert( 'testfield is ' + hello_app.get_testfield() ); //get the value of testfield that is attached to the app
var counter = hello_app.get_counter(); //get the counter object that is attached to the app
alert( 'counter is at ' + counter.get_count() ); //get the value of the count field of the counter object attached to the app
} );
</emp>
});
</script></head>
<body><h1>Hello World</h1>
<input type=text id=txt><BR><button type=button id=button>Say Hi</button>
</body></html>
</pre>
</blockquote>
<hr>
<h2>Server Side Code</h2>
<blockquote>
<pre>
package Yote::Hello;
use strict;
use Yote::Obj;
use base 'Yote::AppRoot';
sub init {
my $self = shift;
#when the hello is created for the first time, install a counter to track how many times it is called
$self->set_counter( new Yote::Obj() );
}
sub hello {
my( $self, $data, $acct ) = @_;
my $name = $data->{name};
$self->set_testfield(int(rand(10)); # set this to a random value each time
my $counter = $self->get_counter(); # this could be counted with a field, but I wanted to demo how easy it is to send objects across.
$counter->set_count( $counter->get_count() + 1 ); #increment the value in the counter
return { r => "hello there '$name'. I have said hello ".$counter->get_count()." times." };
}
1;
</pre>
</blockquote>
<hr>
!-->
<script src="/js/bootstrap.js"></script>
</body>
</html>