Wednesday 24 August 2011

SFS1X: Database Register

Hello readers! Sorry for not writing any tutorials, but... you know... lack of motivation hehe. Today I decided to write this tutorial, that for me is important for learning the basics of working with a Database.


So, for this tutorial, I'll use the Database Login example and add to it the Register funcionality. This time I'll improve the login flow, so it can now handle 2 zones: 1 for the login and the other for the register.


::::: The Client Side (1/2) :::::


First open the Fla file, go to the Library (Ctrl + L on Windows) and duplicate the Login button Symbol. To do that, Right-Click on it and press "Duplicate..." (or "Duplicate Symbol...") and put "Register button" without the quotes as the new Symbol name. Then drag the newly created Symbol to the stage and put it on the side of the Login button (like in the picture below). Finally, edit the Register button and change its label to "Register" without the quotes and give it the instance name of "register_btn".



Now we need to create the Register Window. So, draw a simple rectangle, add an Username, Password, Email and Error textfields and input textfields and add the Register button at the bottom. If you want you want you can also add a Cancel Button, like I did below. Then add the following instance names:

Username: userName;
Password: passWord;
Email: email
Error: error
Register button: register_btn
Cancel button: cancel_btn

Finally, select all the design and convert it to a MovieClip (right click then Convert to Symbol, select Movieclip as the type and give it the name of RegisterWndow - for example) and, in the Advanced section of the New Symbol window, select Export for ActionScript, Export in first frame and then put "registerWindow" in the Identifier field. Then delete the Register window from the stage and you're done.



[Optional]
If you want to also create a shadow that stays behind the window and prevents the user from clicking outside it, you can simply create a black square, with alpha around 50% and with width and height equal as the stage size (see the image below). Then you convert it to a new Movieclip, give it the name you want (for example disabler) and add it to the stage, in a new layer, above the Layer 1. Don't forget to give it an instance name, like for example "disabler". Finally, put it at the position x=0 and y=0. You also need to add the following line in the disabler's first frame's actions: this._visible = false;



Now the coding part.

To handle the newly created window and other windows we may add in the future, we create 2 simple functions: showWindow and hideWindow:

var stageW:Number = 220;
var stageH:Number = 150;

function showWindow(linkageName:String):MovieClip
{
disabler._visible = true
var window = _root.attachMovie(linkageName, linkageName, 9999)
window._x = (stageW / 2) - (win._width / 2)
window._y = (stageH / 2) - (win._height / 2)
return win
}

function hideWindow(windowName:String)
{
this[windowName].removeMovieClip()
disabler._visible = false
}

As you can see, at the beginning we create 2 properties: stageW - which is the width of the stage, and stageH - which is the height of the stage. These values will be used in the showWindow function, for centering the window at the middle of the stage. Note: we don't use the stage._width and stage._height because these values aren't always correct, and as the stage never changes, so we use our own values.

The showWindow and hideWindow functions are really simple, they just create or remove a new movieclip with the provided linkage name (which is the name that we entered in the Identifier field, on the Advanced panel of the New Symbol window).

Now that we have our Window Manager done, we can continue and put the register and cancel button working. So, on the first frame of the stage, edit the following (in bold):

userName._visible = false;
passWord._visible = false;
login_btn._visible = false;
register_btn._visible = false;
...
smartfox.onConnection = function(success) {
if (success) {
...
login_btn._visible = true;
login_btn.onRelease = sendLogin;
register_btn._visible = true;
register_btn.onRelease = openRegister;
...
}
function openRegister(){
showWindow("registerWindow")
}
...
smartfox.onConnectionLost = function() {
...
userName._visible = false;
passWord._visible = false;
register_btn._visible = false;
};

If you test right now, you can open the Register window, but you can't do much more than that. So let's go now to the Register window's code:

Selection.setFocus("userName")

function closeMe(){
_root.hideWindow(_name)
}

function sendRegister(){
if(userName.text != ""){
if(passWord.text != ""){
if(email.text != ""){
_root.task = "register";
_root.registerObj = {uName:userName.text, pass:passWord.text, email:email.text};
if(_root.connected){
error.text = "Registering..."
_root.smartfox.login(_root.registerZone, "", "");
}else{
error.text = "Connecting..."
_root.connect();
}
}else{
error.text = "Please enter your Email."
}
}else{
error.text = "Please enter your Password."
}
}else{
error.text = "Please enter your Username."
trace("error")
}
}

register_btn.onRelease = sendRegister;
cancel_btn.onRelease = closeMe;



So we added a closeMe function, that will be called by the cancel_btn, which will simply... well... close/hide the registerWindow. The sendRegister function is a simple function that simply detects if the user has written anything in all the fields, and then sets the task variable (which will be used to differentiate each flow) to "register" and prepares the register object which will be sent to the extension upon register time and then logs in the registerZone (which name I've stored in a variable called registerZone) with empty user and password (we need to login to send extension requests, thats why we here log in with empty username and password, or as a guest if you prefer) or connects in case it lost the connection with the server.

To finish the 1st part of the client side, now we only need to do some more changes to the first frame of the main stage (new code in bold black, deleted code in bold red):

...
var ip:String = "127.0.0.1";
var port:Number = 9339;
var zone:String = "TutorZone";
var loginZone:String = "TutorZone";
var registerZone:String = "TutorZone2";
var connected:Boolean = false;
var task = "";


...
smartfox.onConnection = function(success) {
if (success) {
...
if(task == "login"){
smartfox.login(loginZone,userName.text,passWord.text);
}else if(task == "register"){
smartfox.login(registerZone,"","");
}else if(task == ""){
userName._visible = true;
...
Selection.setFocus(userName);
}
}
}

function sendLogin() {
task = "login"
if (connected) {
if (userName.text != "") {
...
smartfox.login(zone,userName.text,passWord.text);
smartfox.login(loginZone,userName.text,passWord.text);
...
}
...
smartfox.onExtensionResponse = function(resObj:Object) {
...
};

smartfox.onLogin = function(resObj:Object){
if(task == "register"){
if(resObj.success){
_root["registerWindow"].error.text = "Joining Room...";
}else{
_root["registerWindow"].error.text = resObj.error;
}
}
}



smartfox.onRoomListUpdate = function(roomList:Object) {
if(task == "login"){
gotoAndStop("lobby");
display.text = "Logged in as "+_global.myName;
}else if(task == "register"){
smartfox.autoJoin();
}
};


smartfox.onJoinRoom = function(r:Room){
if(task == "register"){
_root["registerWindow"].error.text = "Registering...";
smartfox.sendXtMessage("DatabaseRegisterExt", "register", _root.registerObj)
}
}


As you see, we only made some minor changes to support the task variable. But why did we add the smartfox.onLogin handler if we already handle the extension response which is who is handling the login (because we're using customLogin)? Well, because in the register zone, we don't need customLogin, as it is a waste of time and resources, because we only need to enter the server to be able to communicate with the Register extension. 

Please note that you can only interact with extensions only after joining a room, so that's why we use the smartfox.autoJoin() (which joins automatically the room that has the autoJoin property set to true) and only send the extension request after a successful join. 

And that's all for now.


::::: The Server Side :::::

Finally the server-side... lets get directly to action!

So here's the DatabaseRegisterExt.as code:

var dbase

function init(){
dbase = _server.getDatabaseManager()
}

function destroy(){
delete dbase
}

function handleInternalEvent(evt){ //not used }

function handleRequest(cmd, params, user, fromRoom){
if (cmd == "register") {
trace("starting registration proccess")
var userName = params.uName;
var passWord = params.pass;
var email = params.email;
var response = {_cmd:cmd };
var error = "";
var success = false;
var sql = "SELECT COUNT (NAME) FROM USERS WHERE NAME='"+_server.escapeQuotes(userName)+"'";
var queryRes = dbase.executeQuery(sql)
if (queryRes != null){
var Row = queryRes.get(0)
var count = Row.getItem("COUNT(NAME)");
if(count == 0){
trace("USERNAME NOT REGISTERED, REGISTERING");
sql = "INSERT INTO USERS (NAME, PASS, EMAIL) VALUES ('" + userName + "', '" + passWord + "', '" + email + "')";
queryRes = dbase.executeCommand(sql);
success = true;
}else{
trace("USERNAME ALREADY REGISTERED!")
error = "The username entered is already registered.";
}
}else{
error = "Error connecting to the database";
}
response.error = error;
response.success = success;
_server.sendResponse(response, -1, null, [user])
}
}

As you see, this time we used the handleRequest instead of the handleInternalEvent, as the handleRequest handles client requests when the handleInternalEvent only handles server events like user login, user logout, etc.

In the handleRequest function, the cmd parameter is the command sent by the user, the params object is the extra data that the user sends, the user parameter is the user who sent the request and the fromRoom is the id of the room from where the request was originated. It is very similar to the smartfox.sendXtMessage(), in terms of parameters, so you shouldn't have problems with it.


Before we register the user (by using the INSERT INTO SQL command) we first check if there is already registered an user with the same username, and the COUNT() sql function retrieves the number of rows found  that meet the provided condition.


Another difference is that this time we use the executeCommand instead of the executeQuery, because the executeQuery is just used for returning queries (like SELECT, COUNT, etc) while the executeCommand updates the database (you use it for everything that changes the database's data, like INSERT INTO, DROP, etc).


::::: The Client Side (2/2) :::::


The final step is handling the response of the register extension, so we just need to update the onExtensionResponse function:



smartfox.onExtensionResponse = function(resObj:Object) {
if (resObj._cmd == "logOK") {
_global.myName = resObj.name;
smartfox.getRoomList();
} else if (resObj._cmd == "logKO") {
error.text = "Error at login: "+resObj.error;
status.text = "Connected";
}else if(resObj._cmd == "register"){
trace("register response")
if(resObj.success){
trace("success")
_root["registerWindow"].error.text = "Register successful";
}else{
trace("error")
_root["registerWindow"].error.text = resObj.error;
}
}
};


::::: Conclusion :::::


Hope you liked this tutorial :-)! I don't know if this means my temporary return or not, who knows. I'll post the tutorial's source later. 
Cya next time, and happy coding! ;-)

Friday 25 March 2011

BLOG: Current Situation

Hello readers! Im really sorry for not writing any new tutorials, but the reason for that is that I've been really really busy with tests and school and don't have any free time. So this blog will become a seasonal blog, and will only get updated at Easter, Summer and Christmas holidays. See you soon!

Friday 7 January 2011

SFS1X: Database Login

Hi there! In this tutorial I'll explain how can you make a simple Flash Application that connects and then logs in, after the server verifies the credentials to check if there is a user registered with those credentials. This tutorial will be based in the Simple Login Tutorial, so I suggest you to do it before doing this.

For making the Database Login, I'll use:
- H2 Database as the database that will store the registered user's information;
- An AS1.0 Extension, that will handle the login process;

You can download the source code of this tutorial here.

So, the Client Side

First open the Fla file that you created in the Simple Login Tutorial.
You'll need to create a new Input TextField after the userName (using the Text Tool, shortcut key: Tand give it the Instance Name "passWord" without the quotes and set its Behavior property to Password (Like the following image).


You can optionally add a new Dynamic TextField after the userName and give it the Instance Name "errorMsg" without the quotes. This TextField will show the common login errors like "Username or password wrong", "can't connect to the database", etc.

Finally you need to edit some of the code that is in the first frame, so it will stay like this:
import it.gotoandplay.smartfoxserver.*;
stop();

var ip:String = "127.0.0.1";
var port:Number = 9339;
var zone:String = "TutorZone";
var connected:Boolean = false;

userName._visible = false;
passWord._visible = false;
login_btn._visible = false;

var smartfox:SmartFoxClient = new SmartFoxClient();
smartfox.debug = true;
connect();

function connect() {
 status.text = "Connecting";
 smartfox.connect(ip,port);
}

smartfox.onConnection = function(success) {
 if (success) {
  status.text = "Successfully connected!";
  connected = true;
  userName._visible = true;
  passWord._visible = true;
  login_btn._visible = true;
  login_btn.onRelease = sendLogin;
  Selection.setFocus(userName);
 } else {
  status.text = "Can't connect!";
 }
};

function sendLogin() {
 if (connected) {
  if (userName.text != "") {
   error.text = "";
   status.text = "Logging in...";
   smartfox.login(zone,userName.text,passWord.text);
  }
 }else{
  connect()
 }
}

smartfox.onExtensionResponse = function(resObj:Object) {
 if (resObj._cmd == "logOK") {
  _global.myName = resObj.name;
  smartfox.getRoomList();
 } else if (resObj._cmd == "logKO") {
  error.text = "Error at login: "+resObj.error;
  status.text = "Connected";
 }
};

smartfox.onRoomListUpdate = function(roomList:Object) {
 gotoAndStop("lobby");
 display.text = "Logged in as "+_global.myName;
};

smartfox.onConnectionLost = function() {
 gotoAndStop("Login");
 status.text = "Disconnected";
 connected = false;
 userName._visible = false;
 passWord._visible = false;
};
As you can see, I changed the Zone to TutorZone, added some lines to make the passWord TextField disappearing at the beggining and appearing when it successfully connects, like the userName.


Important Warning: I decided to change the Zone not to interfere with the zones that already come with sfs and that are needed for the examples, and from now I'll be always using the TutorZone in the next tutorials.


So you'll need to create a new Zone. If you don't know how to create a new Zone, you can follow the Basic Server Configuration Tutorial.


But there is one important change that I did to the script. The new connect function and the Extension Response, Connection Lost  and RoomList Update handlers.


So let's explain this code:

function connect() {
 status.text = "Connecting";
 smartfox.connect(ip,port);
}

I only created this function so this way I can connect to the server just by executing connect() (or _root.connect() if the code is inside a movieclip) and i dont need to be always displaying the status and calling the smartfox.connect(ip, port). I've also created a connected Boolean, that is true when the client is connected to the server and is false when the client isn't.
In the sendLogin function, now it verifies if it is connected to the server, and if it isn't, it tries to connect to the server again.
smartfox.onExtensionResponse = function(resObj:Object) {
 if (resObj._cmd == "logOK") {
  _global.myName = resObj.name;
  smartfox.getRoomList();
 } else if (resObj._cmd == "logKO") {
  error.text = "Error at login: "+resObj.error;
  status.text = "Connected";
 }
};
This function handles the onExtensionResponse event fired by the client when it receives a response from the Extension. I'll talk more about the extension in the Server Side part of this tutorial. The resObj returns the data from the server. In this tutorial the Extension will return the command "logOK" if the login succeeded or will return the command "logKO" if the login failed.
If the login succeeded, I set a global variable called myName that will represent our public name (the name that everyone sees). Then it executes the getRoomList() command. This command is only used if the customLogin parameter in the Zone is set to true. If it is true, it tells the server that "you don't need to handle the login, because I have an extension that will handle it for you". When handling the customLogin, you need to manually request the roomList from the server, otherwise you won't be able to join any room. But if you don't have the customLogin set to true, in other words you aren't handling the login with an extension, you don't need to request the roomList, because the server automatically sends it after the login.
If the login failed, it displays the error sent by the extension in the error TextField and displays again the "Connected" in the status TextField.
For more information about the onExtensionResponse you can check the docs (http://www.smartfoxserver.com/docs/index.htm?http://www.smartfoxserver.com/docs/docPages/tutorials_pro/02_simpleExtension/index.htm) and the API (http://www.smartfoxserver.com/docs/docPages/as2/html/it_gotoandplay_smartfoxserver_SmartFoxClient.html#onExtensionResponse).
smartfox.onRoomListUpdate = function(roomList:Object) {
 gotoAndStop("lobby");
 display.text = "Logged in as "+_global.myName;
};
This function handles the onRoomListUpdate event by the client when it receives the roomList from the server. In further tutorials I'll use the received roomList, but for now I don't need it. When it receives the roomList, it just goes to the lobby frame and displays message "Logged in as Rjgtav" for example, in the display TextField.
smartfox.onConnectionLost = function() {
 gotoAndStop("login");
 status.text = "Disconnected";
 connected = false;
 userName._visible = false;
 passWord._visible = false;
};
This function handles the onConnectionLost event fired when the client loses the connection with the server. When it loses the connection, it goes to the login frame (in case it is in the lobby frame), shows the "Disconnected" in the status TextField, sets the connected variable to false and hides the userName and passWord TextFields.

We're done with the Client Side, now to the Server Side.
First you'll need to have a database. If you don't have one, you can create one by simply doing this:

1. Open the adminDb.bat in the [SmartFoxServer Installation Folder]\Server (for Windows users) or run ./adminDb.sh command in a terminal window (it will open a new browser window with the h2 login page - at least in Windows it opens).
2. Now enter "jdbc:h2:~/sfstutor" without the quotes as the JDBC URL and enter your desired User Name and Password.
3. Finally press the Connect button.


Now that you have a database, you need to create a table. For that, you just need to run this SQL Statement (you can run SQL statements by entering them in the big central TextField and by clicking the Run (Ctrl+Enter) button):

CREATE TABLE USERS(ID INT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(255), PASS VARCHAR(255), EMAIL VARCHAR(255));

I'll explain this code very quick:

CREATE TABLE USERS() - Creates a new table called USERS and its collums. The collums are set inside the parentheses ();
ID INT AUTO_INCREMENT PRIMARY KEY - A collum called ID and type INT. The AUTO_INCREMENT will make the collum values automatically increase by 1 (like 1, 2, 3, blabla). It is the primary key of the table (you can only have one primary key per table);
NAME VARCHAR(255) - A collum called NAME and type VARCHAR with a maximum of 255 characters; 
PASS VARCHAR(255) - A collum called PASS and type VARCHAR with a maximum of 255 characters;
EMAIL VARCHAR(255) - A collum called EMAIL and type VARCHAR with a maximum of 255 characters;

Now that you have a table, you only need to do one more thing. You need to insert data, in other words register users, for now, I'll only explain how to do it by a SQL Statement. I'll explain how to make a register in a future tutorial. For registering the first users, you only need to run this SQL Statement:

INSERT INTO USERS (NAME, PASS, EMAIL) VALUES ('User's Username', 'User's Password', 'User's Email');

A quick explanation:

INSERT INTO USERS() - Inserts data into the table USERS. The collum's names that we are going to insert data are set inside the first parentheses () and the data to be inserted is set inside the last parentheses ();

Note: The data position inside the last parentheses must match the correspondent collum's position in the first parentheses.

And you're done with the database. Now the extension.

You need to create a new Extension called DatabaseLoginExt. If you don't know how to create an extension, you can check the Simple Extension Tutorial.

This is the source code of the Extension:

var dbase
var userName;
var passWord;
var SocketChannel;

function init(){
 dbase = _server.getDatabaseManager()
}

function destroy(){
 delete dbase
 delete userName;
 delete passWord;
 delete SocketChannel;
}

function handleInternalEvent(evt)
{
 if(evt.name == "loginRequest"){  
  var error = "";
  
  userName = evt["nick"];
        passWord = evt["pass"];
        SocketChannel = evt["chan"];
  
  var sql = "SELECT COUNT (NAME) FROM USERS WHERE NAME='"+userName+"' AND PASS='"+passWord+"'";
  var queryRes = dbase.executeQuery(sql)
  
  var response = {}
  
  if (queryRes != null){
   var Row = queryRes.get(0)
   var count = Row.getItem("COUNT(NAME)");
    
   if(count == 1){
    trace("SUCCESSFULL LOGIN")
    var obj = _server.loginUser(userName, passWord, SocketChannel)
    
    if(obj.success){
     response._cmd = "logOK";
     response.name = userName;
    }else{
     error = obj.error;
     response._cmd = "logKO";
    }
   }else if(count == 0){
    trace("FAILED LOGIN")
    response._cmd = "logKO";
    error = "Wrong username or password";
   }
  }else{
   response._cmd = "logKO";
   error = "Error connecting to the database"
  }
  
  response.error = error;
  _server.sendResponse(response, -1, null, SocketChannel)
 }
}

Here's the explanation of the Extension code:

function init(){
 dbase = _server.getDatabaseManager()
}
This function handles the init event. When the Extension inits, it sets the dbase variable as the Zone's Database Manager.
function destroy(){
 delete dbase
 delete userName;
 delete passWord;
 delete SocketChannel;
}
This function handles the destroy event. When the Extension is destroyed, it deletes all the used variables.
if(evt.name == "loginRequest"){
Checks if the event received is the loginRequest (the one that is sent when the client is requesting to login).
userName = evt["nick"];
passWord = evt["pass"];
SocketChannel = evt["chan"];
The evt param received from the loginRequest event, is an Object. So we are setting the variables to the correspondent data in the evt Object.
Note: It could be evt.nick instead of evt["nick"]. It's the same
var sql = "SELECT COUNT (NAME) FROM USERS WHERE NAME='"+userName+"' AND PASS='"+passWord+"'";
This code sets the sql variable. It is a prepared statement. This prepared statement counts the number of users in the database that have the received userName and passWord.
var queryRes = dbase.executeQuery(sql)
This code makes the Database Manager execute the Prepared Statement sql and sets the queryRes variable as the result returned from the query. There are two commands to execute a SQL Statement. The executeQuery() and the executeUpdate(). The difference between them is that the executeQuery() only queries the database, in other words, gets data from it and the executeUpdate() only updates the database, for example, insert new data.
var response = {}
Creates a new Object. The response Object will be sent to the client (ExtensionResponse event).
var Row = queryRes.get(0)
var count = Row.getItem("COUNT(NAME)");
The queryRes object is the result returned from the query and the result is made of rows. As the database won't have duplicated users, the result will only return a maximum of one row, and as the queryRes is an Array, we get its first parameter (the 0). The Row has items (corresponding to the collums), and when we do a SELECT COUNT(), it only returns a collum called COUNT(the name of the collum to count). So we get that item and assign it to the count variable.
var obj = _server.loginUser(userName, passWord, SocketChannel)

if(obj.success){
 response._cmd = "logOK";
 response.name = userName;
}else{
 error = obj.error;
 response._cmd = "logKO";
}
If the count equals 1, than that means that there's a registered user with the entered credentials, so we login it and assign a variable to the loginUser function, that will be the return of the function. If it returns true, it means that the user successfully logged in. If it returns false, it means that it failed during the log in proccess. Then we set the _cmd of the response. This property is like the name of the result from the ExtensionRequest sent by the client. In this case, we set it as "logOK" if the login succeeded and "logKO" if the login failed. And if the login failed, we assign the error to a variable, that will be returned inside the response Object.
response.error = error;
_server.sendResponse(response, -1, null, SocketChannel)
In this code we set the error property inside the response Object as the error (As you can see along the code I set some more errors). Then we send the response back to the client. For more information about the sendResponse() function you can check the ServerSide API (http://www.smartfoxserver.com/docs/docPages/serverSideApi/_server/sendResponse.htm)

Now, all you need to do is to modify your Zone in the config.xml, so it will stay like this:


<Zone name="TutorZone" uCountUpdate="true" maxUsers="10000" customLogin="true" roomListVars="true">
        <AutoReloadExtensions>true</AutoReloadExtensions>
        <Rooms>
                <Room name="Login" maxUsers="5000" autoJoin="true" limbo="true" />
                <Room name="Lobby" maxUsers="5000" isPrivate="false" isTemp="false" autoJoin="false" uCountUpdate="true" extensionName=""/>
        </Rooms>
        <DatabaseManager active="true">
                <Driver>sun.jdbc.odbc.JdbcOdbcDriver</Driver>
                <ConnectionString>jdbc:h2:~/sfstutor</ConnectionString>
                <UserName>Your database's UserName</UserName>
                <Password>Your Database's Password</Password>
                <TestSQL><![CDATA[SELECT COUNT(*) FROM USERS]]></TestSQL>
                <MaxActive>100</MaxActive>
                <MaxIdle>100</MaxIdle>
                <OnExhaustedPool>fail</OnExhaustedPool>
                <BlockTime>5000</BlockTime> 
        </DatabaseManager>
        <Extensions>
<extension name="LoginExt"  className="DatabaseLoginExt.as" type="script" />
</Extensions>
</Zone>


For more information about how to create a Zone you can check the Basic Server Configuration Tutorial.

Tip: Don't forget to set the Local playback security to Access network only. For more information about this, you can check this tip.


You can download the source code of this tutorial here.


And that's all. I hope you enjoyed this tutorial. If you have any questions, fell free to ask me (by commenting or by dropping me a pm in the sfs forums).

Stay tuned for the next tutorial ;-)