Yverdon N2F Yverdon Cookbook: Ajax login with data.php and jQuery
Anyone who has used the N2 Framework is familiar with page.php. By default, all requests for a given module go to that modules page.php, and it is in page.php that the template is selected, data is fetched and chewed, and the result is finally sent back to the browser. This is great if you want to return a whole web page, but what about AJAX?
For this, we have data.php. This is not to say that data.php exists only for AJAX. The data.php file is intended to be used any time you need to return something other than a page to the browser. AJAX is just a common example. Let’s be really specific and implement an AJAX login.
To make this easy, we’ll start by using the Useful $user extension. We’ll also save some pain by using jQuery, and the json2.js library. We’ll implement a simple user panel consisting of three parts: An n2f template file for the layout, a JavaScript file to manage the AJAX, and the data.php to respond to the requests.
For a template file, this one ends up being a little complicated. It needs to respond to two potential states (logged in, and not logged in) and provide the correct interface. So user_panel.tpl looks like:
</p>
<p><style><br />
#user_panel {border: 1px solid lightGrey; padding: 4px}<br />
#user_panel .greeting {font-weight: bold; color: grey; margin-right: 10px;}<br />
#user_panel form {margin: 0 0;}<br />
#user_panel label {display: inline;}<br />
#user_panel input {display: inline;}<br />
</style><br />
<div class="user_panel" id="user_panel"><br />
<% if(! ($user->user_id > 0 && $user->loggedin == 1)) : %><br />
<a name="login"></a><br />
<form action="./?nmod=main" method="post"><br />
<span class="greeting">You need to log in<%if($user->name != ''){echo ', '.$user->name;}%>!</span><br />
<label for="username">Username:</label><br />
<input type="text" name="username" id="username" /><br />
<label for="password">Password:</label><br />
<input type="password" name="password" id="password" /><br />
<input type="submit" name="login" id="login" value="login" onclick="user_panel.doLogin();return false;"/><br />
</form><br />
<p><br />
<% if(isset($message) &amp;amp;amp;amp;&amp;amp;amp;amp; $message !== '') : %><br />
Click <strong><a href="javascript: //;" onclick="toggleDiv('user_messages');">here</a></strong> to hide messages.<br />
<div id='user_messages' class='main-content'><%$message%></div><br style='clear:both;' /><br />
<% else: %><br />
<div id='user_messages' class='main-content'></div><br style='clear:both;' /><br />
<% endif; %><br />
</p><br />
<% else: %><br />
<a name="user"></a><br />
<form action="./?nmod=main" method="post"><br />
<span class="greeting">Welcome, <%$user->name;%></span><br />
<span style="font-weight:bold;margin:5px;">Username:</span><%$user->username;%><br />
<span style="font-weight:bold;margin:5px;">Lasttime:</span><%$user->lasttime;%><br />
<input style="margin-left:10px" type="submit" name="logout" id="logout" value="logout" onclick="user_panel.doLogout();return false;"/><br />
</form><br />
<p><br />
<% if(isset($message) &amp;amp;amp;amp;&amp;amp;amp;amp; $message !== '') : %><br />
Click <strong><a href="javascript: //;" onclick="toggleDiv('user_messages');">here</a></strong> to hide messages.<br />
<div id='user_messages' class='main-content'><%$message%></div><br style='clear:both;' /><br />
<% else: %><br />
<div id='user_messages' class='main-content'></div><br style='clear:both;' /><br />
<% endif; %><br />
</p><br />
<% endif; %><br />
</div></p>
<p>
I cheated and put some style code into a style block at the top. In real life that should be in a .css file. The whole panel fits inside a singe div, and appears as a single line login bar, with any messages appearing directly below. If the user is not logged in, the bar contains a form to collect their username and password. If the user is logged in, the bar shows a couple pieces of information and a logout button. The login and logout buttons call functions defined in in user_panel.js:
</p>
<p>/* create the user_panel js object */<br />
var user_panel = {};</p>
<p>/* add redirect configuration */<br />
user_panel.redirect = {};<br />
user_panel.redirect.login = './?nmod=main';<br />
user_panel.redirect.logout = './?nmod=main';</p>
<p>/* add ajax submission configuration */<br />
user_panel.submit = {};<br />
user_panel.submit.login = './?nmod=main&amp;amp;amp;amp;nret=data';<br />
user_panel.submit.logout = './?nmod=main&amp;amp;amp;amp;nret=data';</p>
<p>/* add ajax functions for login and logout*/<br />
user_panel.doLogin = function(state) {<br />
if (state == undefined) {<br />
/* We are trying to submit the form */<br />
var username = $("#username").val();<br />
var password = $("#password").val();<br />
if (username == '' || password == '') {<br />
/* complain */<br />
$("#user_messages").text("You must enter both username and password to log in");<br />
} else {<br />
/* attempt login */<br />
$.ajax({<br />
type: 'POST',<br />
url: user_panel.submit.login,<br />
data: {'action':'login', 'username':username, 'password':password},<br />
success: user_panel.doLogin<br />
});</p>
<p>}<br />
} else {<br />
/* We are getting a response from the server */<br />
try {<br />
state = JSON.parse(state);<br />
if(state.status == 'error') {<br />
$("#user_messages").html(state.message);<br />
} else {<br />
window.location = user_panel.redirect.login;<br />
}<br />
} catch(e) {<br />
$("#user_messages").text("json parse error");<br />
}</p>
<p>}<br />
}</p>
<p>user_panel.doLogout = function(state) {<br />
if (state == undefined) {<br />
/* We are trying to submit the request */<br />
$.ajax({<br />
type: 'POST',<br />
url: user_panel.submit.logout,<br />
data: {'action':'logout'},<br />
success: user_panel.doLogout<br />
});</p>
<p>} else {<br />
/* We are getting a response from the server */<br />
try {<br />
state = JSON.parse(state);<br />
if(state.status == 'error') {<br />
$("#user_messages").html(state.message);<br />
} else {<br />
window.location = user_panel.redirect.logout;<br />
}<br />
} catch(e) {<br />
$("#user_messages").text("json parse error");<br />
}</p>
<p>}<br />
}</p>
<p>
The doLogin function posts a request to the address in user_panel.submit.login and the doLogout function posts a request to the address in user_panel.submit.logout. In the example, these both point to ./?nmod=main&nret=data. If you wanted to have a special module just for handling authentication, you make that ./?nmod=authenticate&nret=data. Notice nret=data. This is how you tell n2f to use the data.php.
We start with:
</p>
<p><?php<br />
global $user;</p>
<p>if(isset($_REQUEST['action'])) {<br />
// Someone wanted us to do something</p>
<p>$message = ''; // message to be sent back<br />
$status = ''; // status to be sent back<br />
That just gets $user, makes sure we were asked to do something, and sets up a couple variables. We’ll send those variables back to the caller at the end. First the code to handle a log in request:
</p>
<p>// If user is trying to log in<br />
if(($_REQUEST['action'] == 'login') &amp;amp;amp;amp;&amp;amp;amp;amp; isset($_REQUEST['username']) &amp;amp;amp;amp;&amp;amp;amp;amp; isset($_REQUEST['password'])) {</p>
<p>// Assume success<br />
$status = "success";</p>
<p>// If already logged in, need to log out first<br />
if(($user->user_id > 0 &amp;amp;amp;amp;&amp;amp;amp;amp; $user->loggedin == 1 )) {<br />
$status = "error";<br />
$message .= "You must log out before logging in as a different user.<br />";<br />
// Else try to log them in<br />
} else {<br />
$user->clearErrors()->login($_REQUEST['username'], $_REQUEST['password']);<br />
// If login was not successful<br />
if($user->user_id == 0 || $user->hasError()) {<br />
$status = "error";<br />
$message .= "Unable to log you in. <br />";<br />
if($user->hasError()) {<br />
$errors = $user->getErrors();<br />
foreach($errors as $err) {<br />
$message .= $err."<br />";<br />
}<br />
}<br />
}<br />
}<br />
Then the code to handle a logout request:
</p>
<p>// Else if user is trying to log out<br />
} elseif(($_REQUEST['action'] == 'logout')) {</p>
<p>// Assume success<br />
$status = "success";</p>
<p>// Ask for logout<br />
$user->clearErrors()->logout();<br />
// If logout was not successful<br />
if($user->loggedin !== 0 || $user->hasError()) {<br />
$status = "error";<br />
$message .= "Unable to log you out. <br />";<br />
if($user->hasError()) {<br />
$errors = $user->getErrors();<br />
foreach($errors as $err) {<br />
$message .= $err."<br />";<br />
}<br />
}<br />
}<br />
}<br />
And finally, packing the variables into some json to send back to the browser
</p>
<p>// All done with the proccessing - send the result back to the caller<br />
echo json_encode(array('status'=>$status, 'message'=>$message));<br />
}<br />
?></p>
<p>
Notice that there is no template object or template rendering required for the data.php to do it’s job. It just pulls some information from the request, does what it needs to, and then echos a result back to the browser. Easy.
You can download all files for this tutorial here.