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! ;-)

7 comments:

  1. thanks man. your the best.

    ReplyDelete
  2. finnally! i love you! i love you! i love you! ~ no homo

    ReplyDelete
  3. Post the source please.

    ReplyDelete
  4. I can't seem to make it register :\

    ReplyDelete
  5. Can u post source please?

    ReplyDelete
  6. please help! i get
    "xtRes" r="-1"><dataObj><var n=&apos;error&apos; t=&apos;s&apos;>Wrong username or password</var><var n=&apos;_cmd&apos;

    ReplyDelete
  7. Fantastic work accomplished by site Author.

    ReplyDelete