A youtube mixer with instant search and JW player

November 28th, 2010 § 0

This is a bare-bones youtube video mixer using youtube instant search and the very nice JW player. Check out this tutorial for an instant search example and get JW player from here.

See it in action, or download the whole thing.

a pong battle to the death on an HTML5 canvas

June 19th, 2010 § 7

Download the full source.

The first (and only) HTML5 element we need is a canvas to draw our stuff on.

<canvas id="game" width="500" height="350">
	<p>Your browser doesn't support canvas.</p>
</canvas>

Using  jQuery for simplicity we get a reference to the canvas element and define constants and variables for the game.

var canvas = $('#game');
var ctx =  canvas[0].getContext('2d');

const game_width = $("#game").width();
const game_height = $("#game").height();

const paddle_height = 60;
const paddle_width = 10;
const paddle_speed=8;
const ball_max_speed = 30;

var ball_x;
var ball_y;
var ball_x_speed;
var ball_y_speed;

var hits = 0;

var p1_paddle_x = 20;
var p1_paddle_y = game_height/2-paddle_height/2;
var p2_paddle_x = game_width-paddle_width-20;
var p2_paddle_y = game_height/2-paddle_height/2;

var p1_score=0;
var p2_score=0;

Now to draw the game background

//define the fill color
ctx.fillStyle = "#fb2";
//start drawing
ctx.beginPath();
//draw a rectangle
ctx.rect(0,0,game_width,game_height);
//this is not necessary as the path will close before fill()
ctx.closePath();
//do the fill
ctx.fill();

The above javascript  gives the following output

To draw the ball and the paddles:

//draw the ball
ctx.fillStyle = "#f55";
ctx.beginPath();
ctx.arc(ball_x, ball_y, 10, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
//draw the left paddle
ctx.beginPath();
ctx.rect(p1_paddle_x,p1_paddle_y,paddle_width,paddle_height);
ctx.closePath();
ctx.fill();
//draw the right paddle
ctx.beginPath();
ctx.rect(p2_paddle_x,p2_paddle_y,paddle_width,paddle_height);
ctx.closePath();
ctx.fill();
//draw a line to separate the playing field
ctx.fillStyle = "#ddd";
ctx.beginPath();
ctx.rect(game_width/2-1,0,2,game_height);
ctx.closePath();
ctx.fill();

Each player’s score can be drawn with the fillText function.

ctx.textBaseline = "top";
ctx.fillStyle = "#000";
ctx.font = "bold 13px sans-serif";
ctx.fillText("( "+p1_score+" )", 40, 5);
ctx.fillText("( "+p2_score+" )", game_width-60, 5);

To make it look a little bit better we can use a drop shadow on the paddles and ball

ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 5;
ctx.shadowBlur = 10;

Now in order to make things move we need to clear the canvas and redraw the updated positions of the paddles and the ball. This can be achieved using a setInterval calling a draw function every 10 milliseconds.

setInterval(draw, 10);

function draw() {
   ctx.clearRect(0,0,game_width,game_height);
   ...
   calculate and draw stuff
   ...
   ...
}

Making the ball move. We need the ball to bounce off the top and bottom , bounce off the paddles and when it leaves the stage to keep score and move to center with a random direction

// bounce off the paddles
// first we check the x coordinate to see if the ball
// is in the paddle area.
// Then if the y coordinate of the ball is within the
// paddle dimensions reverse the x speed
// we also keep track of the bounces so that we can
// increase the ball speed
if (ball_x+ball_x_speed>p2_paddle_x)
	if (ball_y+ball_y_speed>=p2_paddle_y && ball_y+ball_y_speed<p2_paddle_y+paddle_height) {
		ball_x_speed *= -1;
		hits++;
	}

if (ball_x+ball_x_speed<=p1_paddle_x+paddle_width)
	if (ball_y+ball_y_speed>p1_paddle_y && ball_y+ball_y_speed<p1_paddle_y+paddle_height) {
		ball_x_speed *= -1;
		hits++;
	}
// to bounce off the top and bottom simply check
// the y of the ball and reverse the vertical speed
if (ball_y + ball_y_speed > game_height || ball_y + ball_y_speed < 5)
  ball_y_speed = -ball_y_speed;

// the ball has gone beyond the game_width
// so increase p1's score and reposition ball
if (ball_x + ball_x_speed > game_width) {
	p1_score++;
	new_ball()
}
// the ball has gone off the lest side. point for player 2
if (ball_x - ball_x_speed < 0 ) {
	p2_score++;
	new_ball()
}
// for every 3 bounces of the paddles
// we increase the ball speed, up to maximum
if (hits == 3) {
	if (Math.abs(ball_x_speed) < ball_max_speed && Math.abs(ball_y_speed) < ball_max_speed) {
		ball_x_speed += 1.3;
		ball_y_speed *= 1.3;
		hits = 0;
	}
}
//calculate the ball's position and draw it
ball_x += ball_x_speed;
ball_y += ball_y_speed;
ctx.fillStyle = "#f55";
ctx.beginPath();
ctx.arc(ball_x, ball_y, 10, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();

To get a new ball after each point we create a random number from -5 to 5 for both speeds and reposition the ball

function new_ball() {
			var adjustedHigh = (parseFloat(5) - parseFloat(-5)) + 1;
			var numRand=0;
			while (numRand==0) {
				numRand = Math.floor(Math.random()*adjustedHigh) + parseFloat(-5);
			}
			ball_x_speed = numRand;
			ball_y_speed = numRand;
			hits=0;
			ball_x = 250;
			ball_y = 175;
		}

Lastly we need to make the paddles move and try to hit the ball. Here we have two cases. The first case is when the ball is coming towards the paddle. In this case we check the ball position and move the paddle accordingly.

The second case is the ball leaving the paddle and going to the other direction. In this case we slowly bring back the paddle to the middle position. This is the code for the left paddle.

// if the ball is coming towards the paddle
if (ball_x_speed < 0) {
// if ball is higher than paddle move up
	if (ball_y < p1_paddle_y + paddle_height / 2 && p1_paddle_y>0)
		p1_paddle_y -= paddle_speed;
// if ball is lower than paddle move down
	if (ball_y > p1_paddle_y + paddle_height / 2 && p1_paddle_y + paddle_height<game_height)
		p1_paddle_y += paddle_speed;
}
// else slowly go back to the middle
else {
	if (p1_paddle_y<game_height/2-paddle_height/2) p1_paddle_y += 2;
	else if (p1_paddle_y>game_height/2-paddle_height/2) p1_paddle_y -= 2;
}

Here is the full source:

<script type="text/javascript">
	$(document).ready (function () {

		var canvas = $('#game');
	 	var ctx =  canvas[0].getContext('2d');
		const game_width = $("#game").width();
		const game_height = $("#game").height();
		const paddle_height = 60;
			const paddle_width = 10;
		const paddle_speed=8;
		const ball_max_speed = 30;

		var ball_x;
		var ball_y;
		var ball_x_speed;
		var ball_y_speed;

		var hits = 0;

		var p1_paddle_x = 20;
		var p1_paddle_y = game_height/2-paddle_height/2;
		var p2_paddle_x = game_width-paddle_width-20;
		var p2_paddle_y = game_height/2-paddle_height/2;

		var p1_score=0;
		var p2_score=0;

		setInterval(draw, 10);

		function new_ball() {
			var adjustedHigh = (parseFloat(5) - parseFloat(-5)) + 1;
			var numRand=0;
			while (numRand==0) {
				numRand = Math.floor(Math.random()*adjustedHigh) + parseFloat(-5);
			}
			ball_x_speed = numRand;
			ball_y_speed = numRand;
			hits=0;
			ball_x = 250;
			ball_y = 175;
		}
		function draw() {
			if (!ball_x) new_ball();
			ctx.clearRect(0,0,game_width,game_height);
			ctx.fillStyle = "#fb2";
			ctx.beginPath();
				ctx.rect(0,0,game_width,game_height);
				ctx.closePath();
				ctx.fill();

			ctx.shadowOffsetX = 0;
			ctx.shadowOffsetY = 0;
			ctx.shadowBlur = 0;

			ctx.textBaseline = "top";
			ctx.fillStyle = "#000";
			ctx.font = "bold 13px sans-serif";
			ctx.fillText("( "+p1_score+" )", 40, 5);
			ctx.fillText("( "+p2_score+" )", game_width-60, 5);

			ctx.shadowOffsetX = 2;
			ctx.shadowOffsetY = 5;
			ctx.shadowBlur = 10;

			ctx.shadowColor = "black";
			ctx.fillStyle = "#ddd";
			ctx.beginPath();
				ctx.rect(game_width/2-1,0,2,game_height);
				ctx.closePath();
				ctx.fill();

			if (ball_x+ball_x_speed>p2_paddle_x)
				if (ball_y+ball_y_speed>=p2_paddle_y && ball_y+ball_y_speed<p2_paddle_y+paddle_height) {
					ball_x_speed *= -1;
					hits++;
				}
			if (ball_x+ball_x_speed<=p1_paddle_x+paddle_width)
				if (ball_y+ball_y_speed>p1_paddle_y && ball_y+ball_y_speed<p1_paddle_y+paddle_height) {
					ball_x_speed *= -1;
					hits++;
				}
			if (ball_y + ball_y_speed > game_height || ball_y + ball_y_speed < 5)
			  ball_y_speed = -ball_y_speed;
			if (ball_x + ball_x_speed > game_width) {
				p1_score++;
				new_ball()
			}
			if (ball_x - ball_x_speed < 0 ) {
				p2_score++;
				new_ball()
			}
			if (hits == 3) {
				if (Math.abs(ball_x_speed) < ball_max_speed && Math.abs(ball_y_speed) < ball_max_speed) {
					ball_x_speed += 1.3;
					ball_y_speed *= 1.3;
					hits = 0;
				}
			}

		  	ball_x += ball_x_speed;
			ball_y += ball_y_speed;
			if (Math.abs(ball_x_speed)<2.5) ball_x_speed*=5;

			ctx.fillStyle = "#f55";
			ctx.beginPath();
			ctx.arc(ball_x, ball_y, 10, 0, Math.PI*2, true);
			ctx.closePath();
			ctx.fill();

			ctx.fillStyle = "#1e1";

			if (ball_x_speed < 0) {
				if (ball_y < p1_paddle_y + paddle_height / 2 && p1_paddle_y>0)
					p1_paddle_y -= paddle_speed;
				if (ball_y > p1_paddle_y + paddle_height / 2 && p1_paddle_y + paddle_height<game_height)// && act > 4)
					p1_paddle_y += paddle_speed;
			}
			else {
				if (p1_paddle_y<game_height/2-paddle_height/2) p1_paddle_y += 2;
				else if (p1_paddle_y>game_height/2-paddle_height/2) p1_paddle_y -= 2;
			}
			ctx.beginPath();
				ctx.rect(p1_paddle_x,p1_paddle_y,paddle_width,paddle_height);
				ctx.closePath();
				ctx.fill();

			if (ball_x_speed > 0) {
				if (ball_y < p2_paddle_y + paddle_height / 2 && p2_paddle_y>0)
					p2_paddle_y -= paddle_speed;
				if (ball_y > p2_paddle_y + paddle_height / 2 && p2_paddle_y + paddle_height<game_height)// && act > 4)
					p2_paddle_y += paddle_speed;
			}
			else {
				if (p2_paddle_y<game_height/2-paddle_height/2) p2_paddle_y += 2;
				else if (p2_paddle_y>game_height/2-paddle_height/2) p2_paddle_y -= 2;
			}

			ctx.beginPath();
			ctx.rect(p2_paddle_x,p2_paddle_y,paddle_width,paddle_height);
			ctx.closePath();
			ctx.fill();

		}
	})

Download the full source.

This is my first attempt on anything HTML5 so comments and suggestions are welcome.

a complete nusoap and flex example. part 2: the flex application

May 30th, 2010 § 10

…Continued from Part 1

This is the second part of the NuSOAP and FLEX example. Click on the first part for details on the soap server.

This is the final application:

Using the WebService component we define the WSDL of our service and the supported operations. Each of the operations has an event listener for a ResultEvent.

<mx:WebService wsdl="http://discorganized.com/demos/nusoapflex/index.php?wsdl" id="ws">
	<mx:operation name="searchAddressBook" result="onSearchResult(event)" />
	<mx:operation name="updateContact" result="onUpdateResult(event)" />
	<mx:operation name="deleteContact" result="onDeleteResult(event)" />
	<mx:operation name="insertContact" result="onInsertResult(event)" />
</mx:WebService>

Using the applicationComplete property of the mx:Application we can define a function to run as soon as the application has been created. As there is no real error handling in this application I have set a listener for a FaultEvent directly on the web service so whichever operation fails we get a trace in the console with the fault description.

public function init():void {
	trace("init");
	ws.searchAddressBook("");
	ws.addEventListener(FaultEvent.FAULT,onFault);
}
private function onFault(evt:FaultEvent):void {
	trace ("oh noes!!" + evt.fault);
}

Also in the init() function we invoke the searchAddressbook method of the webservice, with an empty query string, so that we get the full contact list. The result event from that method is handled by the onSearchResult function. We have already set up a variable named “contacts” to store our contacts. Since we want to bind that variable to the datagrid, the variable must be defined as bindable using the [Bindable] keyword.

By settings the dataProvider property of our datagrid to the contacts variable, the datagrid is populated.

private function onSearchResult(evt:ResultEvent):void {
	contacts=evt.result;
	dataGrid.dataProvider=contacts;
	buttonSearch.enabled=true;
	CursorManager.removeBusyCursor();
}

Next we have our edit form:

<mx:Form x="0" y="0" width="388" height="224" defaultButton="{buttonUpdate}">
	<mx:FormItem label="Id">
		<mx:TextInput id="idTextInput" text="{dataGrid.selectedItem.id}" editable="true" enabled="false" valueCommit="checkEdit(event)"/>
	</mx:FormItem>
	<mx:FormItem label="First_name">
		<mx:TextInput id="first_nameTextInput" text="{dataGrid.selectedItem.first_name}"/>
	</mx:FormItem>
	<mx:FormItem label="Last_name">
		<mx:TextInput id="last_nameTextInput" text="{dataGrid.selectedItem.last_name}"/>
	</mx:FormItem>
	<mx:FormItem label="Email">
		<mx:TextInput id="emailTextInput" text="{dataGrid.selectedItem.email}"/>
	</mx:FormItem>
	<mx:FormItem label="Phone_number">
		<mx:TextInput id="phone_numberTextInput" text="{dataGrid.selectedItem.phone_number}"/>
	</mx:FormItem>
	<mx:Button label="Update Contact" id="buttonUpdate" click="edit_clickHandler(event)" enabled="false"/>
	<mx:Button label="Delete Contact" id="buttonDelete" click="delete_clickHandler(event)" enabled="false"/>
</mx:Form>

The form fields are binded to the selected item of the datagrid so whenever a a row is clicked, the form is filled.

<mx:Form x="0" y="0" width="388" height="224" defaultButton="{buttonUpdate}">
	<mx:FormItem label="Id">
		<mx:TextInput id="idTextInput" text="{dataGrid.selectedItem.id}" editable="true" enabled="false" valueCommit="checkEdit(event)"/>
	</mx:FormItem>
	<mx:FormItem label="First_name">
		<mx:TextInput id="first_nameTextInput" text="{dataGrid.selectedItem.first_name}"/>
	</mx:FormItem>
	<mx:FormItem label="Last_name">
		<mx:TextInput id="last_nameTextInput" text="{dataGrid.selectedItem.last_name}"/>
	</mx:FormItem>
	<mx:FormItem label="Email">
		<mx:TextInput id="emailTextInput" text="{dataGrid.selectedItem.email}"/>
	</mx:FormItem>
	<mx:FormItem label="Phone_number">
		<mx:TextInput id="phone_numberTextInput" text="{dataGrid.selectedItem.phone_number}"/>
	</mx:FormItem>
	<mx:Button label="Update Contact" id="buttonUpdate" click="edit_clickHandler(event)" enabled="false"/>
	<mx:Button label="Delete Contact" id="buttonDelete" click="delete_clickHandler(event)" enabled="false"/>
</mx:Form>

Using this form we can update and delete a record using the appropriate web service methods. Here are the click handlers of the two buttons:

protected function edit_clickHandler(evt:MouseEvent):void {
	CursorManager.setBusyCursor();
	evt.target.enabled=false;
	var contact:Object = new Object();
	contact.id = parseInt(idTextInput.text);
	contact.first_name = encodeUTF8(first_nameTextInput.text);
	contact.last_name = encodeUTF8(last_nameTextInput.text);
	contact.email = encodeUTF8(emailTextInput.text);
	contact.phone_number = encodeUTF8(phone_numberTextInput.text);
	ws.updateContact(contact);
}
protected function delete_clickHandler(evt:MouseEvent):void	{
	CursorManager.setBusyCursor();
	evt.target.enabled=false;
	var contact:Object = new Object();
	contact.id = parseInt(idTextInput.text);
	ws.deleteContact(contact);
}

We create a contact object with the form values before invoking the updateContact method. Note that it uses the encodeUTF8 from a previous post to take care of the utf8 mess between Actionscript and PHP. The delete function is pretty much the same but here we only need the contact id.

The insert form does not need to be binded to any data and insert handler function is similar to the edit one.

<mx:Form x="0" y="0" width="388" height="198" defaultButton="{buttonAdd}">
	<mx:FormItem label="First_name">
		<mx:TextInput id="first_nameTextInputAdd" text="" change="checkAdd(event)"/>
	</mx:FormItem>
	<mx:FormItem label="Last_name">
		<mx:TextInput id="last_nameTextInputAdd" text=""/>
	</mx:FormItem>
	<mx:FormItem label="Email">
		<mx:TextInput id="emailTextInputAdd" text=""/>
	</mx:FormItem>
	<mx:FormItem label="Phone_number">
		<mx:TextInput id="phone_numberTextInputAdd" text=""/>
	</mx:FormItem>
	<mx:Button label="Insert Contact" id="buttonAdd" click="insert_clickHandler(event)" enabled="false"/>
</mx:Form>

Here is the complete code for the FLEX application.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"  applicationComplete="init()" width="494" height="602">
	<mx:Script>
		<![CDATA[
			import mx.controls.Alert;
			import mx.managers.CursorManager;
			import mx.rpc.events.FaultEvent;
			import mx.rpc.events.ResultEvent;
			[bindable]
			private var contacts:Object = new Object();

			public function init():void {
				trace("init");
				ws.searchAddressBook("");
				ws.addEventListener(FaultEvent.FAULT,onFault);

			}
			private function onSearchResult(evt:ResultEvent):void {
				contacts=evt.result;
				dataGrid.dataProvider=contacts;
				buttonSearch.enabled=true;
				CursorManager.removeBusyCursor();
			}
			private function onDeleteResult(evt:ResultEvent):void {
				ws.searchAddressBook("");
				buttonDelete.enabled=true;
				CursorManager.removeBusyCursor();
			}
			private function onUpdateResult(evt:ResultEvent):void {
				ws.searchAddressBook("");
				buttonUpdate.enabled=true;
				CursorManager.removeBusyCursor();
			}
			private function onInsertResult(evt:ResultEvent):void {
				ws.searchAddressBook("");
				buttonAdd.enabled=true;
				CursorManager.removeBusyCursor();

			}
			private function onFault(evt:FaultEvent):void {
				trace ("oh noes!!" + evt.fault);
			}
			protected function edit_clickHandler(evt:MouseEvent):void {
				CursorManager.setBusyCursor();
				evt.target.enabled=false;
				var contact:Object = new Object();
				contact.id = parseInt(idTextInput.text);
				contact.first_name = encodeUTF8(first_nameTextInput.text);
				contact.last_name = encodeUTF8(last_nameTextInput.text);
				contact.email = encodeUTF8(emailTextInput.text);
				contact.phone_number = encodeUTF8(phone_numberTextInput.text);
				ws.updateContact(contact);
			}
			protected function delete_clickHandler(evt:MouseEvent):void	{
				CursorManager.setBusyCursor();
				evt.target.enabled=false;
				var contact:Object = new Object();
				contact.id = parseInt(idTextInput.text);
				ws.deleteContact(contact);
			}
			protected function insert_clickHandler(evt:MouseEvent):void {
				CursorManager.setBusyCursor();
				evt.target.enabled=false;
				var contact:Object = new Object();
				contact.id = 0;
				contact.first_name = encodeUTF8(first_nameTextInputAdd.text);
				contact.last_name = encodeUTF8(last_nameTextInputAdd.text);
				contact.email = encodeUTF8(emailTextInputAdd.text);
				contact.phone_number = encodeUTF8(phone_numberTextInputAdd.text);
				ws.insertContact(contact);
			}
			protected function checkEdit(evt:Event):void {
				if (idTextInput.text=="") buttonUpdate.enabled=buttonDelete.enabled=false;
				else buttonUpdate.enabled=buttonDelete.enabled=true;
			}
			protected function checkAdd(evt:Event):void {
				(evt.target.text=="") ?  buttonAdd.enabled=false : buttonAdd.enabled=true ;
			}

			protected function doSearch(evt:MouseEvent):void {
				CursorManager.setBusyCursor();
				evt.target.enabled=false;
				ws.searchAddressBook(searchBox.text);
			}
			public static function encodeUTF8 (text:String):String {

				var a:uint, n:uint, A:uint;
				var utf:String;
				utf = "";
				A = text.length;

				for (a = 0; a < A; a++) {
					n = text.charCodeAt (a);
					if (n < 128) {
						utf += String.fromCharCode (n);
					} else if ((n > 127) && (n < 2048)) {
						utf += String.fromCharCode ((n >> 6) | 192);
						utf += String.fromCharCode ((n & 63) | 128);
					} else {
						utf += String.fromCharCode ((n >> 12) | 224);
						utf += String.fromCharCode (((n >> 6) & 63) | 128);
						utf += String.fromCharCode ((n & 63) | 128);
					}
				}
				return utf;
			}
		]]>
	</mx:Script>
	<mx:WebService wsdl="http://discorganized.com/demos/nusoapflex/index.php?wsdl" id="ws">
		<mx:operation name="searchAddressBook" result="onSearchResult(event)" />
		<mx:operation name="updateContact" result="onUpdateResult(event)" />
		<mx:operation name="deleteContact" result="onDeleteResult(event)" />
		<mx:operation name="insertContact" result="onInsertResult(event)" />
	</mx:WebService>
	<mx:HBox x="238" y="10" width="240" height="23">
		<mx:TextInput id="searchBox" />
		<mx:Button id="buttonSearch" label="Search" click="doSearch(event)"/>
	</mx:HBox>
	<mx:DataGrid x="10" y="41" width="468" height="283" id="dataGrid">
		<mx:columns>
			<mx:DataGridColumn headerText="id" dataField="id" width="20"/>
			<mx:DataGridColumn headerText="first_name" dataField="first_name" width="100"/>
			<mx:DataGridColumn headerText="last_name" dataField="last_name" width="100"/>
			<mx:DataGridColumn headerText="email" dataField="email"/>
			<mx:DataGridColumn headerText="phone_number" dataField="phone_number"/>
		</mx:columns>
	</mx:DataGrid>

	<mx:TabNavigator x="10" y="332" width="469" height="267">
		<mx:Canvas label="Edit" width="100%" height="100%">
			<mx:Form x="0" y="0" width="388" height="224" defaultButton="{buttonUpdate}">
				<mx:FormItem label="Id">
					<mx:TextInput id="idTextInput" text="{dataGrid.selectedItem.id}" editable="true" enabled="false" valueCommit="checkEdit(event)"/>
				</mx:FormItem>
				<mx:FormItem label="First_name">
					<mx:TextInput id="first_nameTextInput" text="{dataGrid.selectedItem.first_name}"/>
				</mx:FormItem>
				<mx:FormItem label="Last_name">
					<mx:TextInput id="last_nameTextInput" text="{dataGrid.selectedItem.last_name}"/>
				</mx:FormItem>
				<mx:FormItem label="Email">
					<mx:TextInput id="emailTextInput" text="{dataGrid.selectedItem.email}"/>
				</mx:FormItem>
				<mx:FormItem label="Phone_number">
					<mx:TextInput id="phone_numberTextInput" text="{dataGrid.selectedItem.phone_number}"/>
				</mx:FormItem>
				<mx:Button label="Update Contact" id="buttonUpdate" click="edit_clickHandler(event)" enabled="false"/>
				<mx:Button label="Delete Contact" id="buttonDelete" click="delete_clickHandler(event)" enabled="false"/>
			</mx:Form>
		</mx:Canvas>
		<mx:Canvas label="Insert" width="100%" height="100%">
			<mx:Form x="0" y="0" width="388" height="198" defaultButton="{buttonAdd}">
				<mx:FormItem label="First_name">
					<mx:TextInput id="first_nameTextInputAdd" text="" change="checkAdd(event)"/>
				</mx:FormItem>
				<mx:FormItem label="Last_name">
					<mx:TextInput id="last_nameTextInputAdd" text=""/>
				</mx:FormItem>
				<mx:FormItem label="Email">
					<mx:TextInput id="emailTextInputAdd" text=""/>
				</mx:FormItem>
				<mx:FormItem label="Phone_number">
					<mx:TextInput id="phone_numberTextInputAdd" text=""/>
				</mx:FormItem>
				<mx:Button label="Insert Contact" id="buttonAdd" click="insert_clickHandler(event)" enabled="false"/>
			</mx:Form>
		</mx:Canvas>

	</mx:TabNavigator>

</mx:Application>

Check out the NuSOAP web service output, grab the files and take a look at the final application.

a complete nusoap and flex example. part 1: the nusoap server

May 30th, 2010 § 3

This is a complete example of how to create a web service for an address book in PHP using NuSOAP and then connect to it from a FLEX application in order to perform basic CRUD operations.

The post got long so I split it in two. This first part here describes the soap server and part two the flex application.

Check out the NuSOAP web service output, grab the files and take a look at the final application.

Note: PHP5 has built in soap which is better in many ways but there are valid reasons to use NuSOAP, automatic WSDL generation and pretty browsable documentation being two of them. Remember that if you wish to use NuSOAPs soap client in a PHP5 environment you should rename the class and function to something else, or get the PHP5 compatible version of NuSOAP.

In the code below, we first create the server instance, then define two new data structures (contact and addressbook) and finally register our methods with the server. The last two lines try to invoke the service and server the request.

$server = new soap_server();
$server->configureWSDL('addressbook_wsdl', 'urn:addressbook_wsdl');
$server->soap_defencoding = 'UTF-8';

$server->wsdl->addComplexType('Contact',
    'complexType',
    'struct',
    'all',
    '',
    array(
            'id' => array('name' => 'id', 'type' => 'xsd:int'),
            'first_name' => array('name' => 'first_name', 'type' => 'xsd:string'),
            'last_name' => array('name' => 'last_name', 'type' => 'xsd:string'),
            'email' => array('name' => 'email', 'type' => 'xsd:string'),
            'phone_number' => array('name' => 'phone_number', 'type' => 'xsd:string')
    )
);
$server->wsdl->addComplexType('AddressBook',
    'complexType',
    'array',
    '',
    'SOAP-ENC:Array',
    array(),
    array(
            array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'tns:Contact[]')
    ),
    'tns:Contact'
);
$server->register('searchAddressBook',
    array('query' => 'xsd:string'),
    array('return' => 'tns:AddressBook'),
    'addressbook_wsdl',
    'addressbook_wsdl#search',
    'rpc',
    'encoded',
    'Returns matching contacts'
);
$server->register('updateContact',
    array('new_contact' => 'tns:Contact'),
    array('return' => 'xsd:boolean'),
    'addressbook_wsdl',
    'addressbook_wsdl#updateContact',
    'rpc',
    'encoded',
    'Update contact'
);
$server->register('deleteContact',
    array('new_contact' => 'tns:Contact'),
    array('return' => 'xsd:boolean'),
    'addressbook_wsdl',
    'addressbook_wsdl#deleteContact',
    'rpc',
    'encoded',
    'Delete contact'
);
$server->register('insertContact',
    array('new_contact' => 'tns:Contact'),
    array('return' => 'xsd:boolean'),
    'addressbook_wsdl',
    'addressbook_wsdl#insertContact',
    'rpc',
    'encoded',
    'Insert contact'
);
$request = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$server->service($request);

The most important thing here are the complex types. NuSOAP  uses associative arrays to represent SOAP structs. The Contact type is defined as being of type “complexType” with php type “struct”. The struct (associative array) is then defined with the fields that we require. We want the AddressBook type to contain an array of Contacts. To do that we define the attributes and the array type.

Check out this and this for a more detailed explanation of what is happening.

Next we define the Contact class in PHP:

class Contact {
    private $id;
    public $first_name;
    public $last_name;
    public $email;
    public $phone_number;

    function __construct($id) {
        $this->id = $id;
    }

    public function get() {
        return $this->id;
    }

    public static function search($query="") {
        if ($query!="") $where = "  WHERE first_name LIKE '%".mysql_real_escape_string($query)."%'
                                    OR last_name LIKE '%".mysql_real_escape_string($query)."%'";
        else $where ="";
        $res = mysql_query("select * from contacts ".$where);
        $contacts = array();
        while ($row=mysql_fetch_assoc($res)) {
            $contact = new Contact($row['id']);
            $contact->first_name=$row['first_name'];
            $contact->last_name=$row['last_name'];
            $contact->email=$row['email'];
            $contact->phone_number=$row['phone_number'];

            array_push($contacts,$contact);
        }
        return $contacts;
    }

    public function update() {
        $query = "  UPDATE contacts
                    SET first_name='".$this->first_name."', last_name='".$this->last_name."', email='".$this->email."', phone_number='".$this->phone_number."'
                    WHERE id='".$this->id."'";

        if ($res = mysql_query($query)) return true;
        else return false;
    }

    public function delete() {
        $query = "  DELETE FROM contacts
                    WHERE id='".$this->id."'";
        if ($res = mysql_query($query)) return true;
        else return false;
    }

    public function insert() {
        $query = "  INSERT INTO contacts VALUES
                    (null,
                     '".$this->first_name."',
                     '".$this->last_name."',
                     '".$this->email."',
                     '".$this->phone_number."')";
        if ($res = mysql_query($query)) return true;
        else return false;
    }
}

Finally we define the service methods as PHP functions. The search function outputs an array of Contacts as defined in the soap server. The rest of functions have a Contact as input.

function searchAddressBook($query) {
    $contacts = Contact::search($query);
    //return $contacts;
    $results = array();
    foreach ($contacts as $contact) {
            $tempArray = array( 'id' => $contact->get(),
                                'first_name' => $contact->first_name,
                                'last_name' => $contact->last_name,
                                'email' => $contact->email,
                                'phone_number' => $contact->phone_number
                                            );
            array_push($results, $tempArray);
    }

    return $results;
}

function deleteContact($in_contact) {
    $contact = new Contact($in_contact['id']);
    return $contact->delete();
}

function insertContact($in_contact) {
    $contact = new Contact(0);
    $contact->first_name=mysql_real_escape_string($in_contact['first_name']);
    $contact->last_name=mysql_real_escape_string($in_contact['last_name']);
    $contact->email=mysql_real_escape_string($in_contact['email']);
    $contact->phone_number=mysql_real_escape_string($in_contact['phone_number']);
    if ($contact->insert()) return true;
}
function updateContact($in_contact) {
    $contact = new Contact($in_contact['id']);
    $contact->first_name=mysql_real_escape_string($in_contact['first_name']);
    $contact->last_name=mysql_real_escape_string($in_contact['last_name']);
    $contact->email=mysql_real_escape_string($in_contact['email']);
    $contact->phone_number=mysql_real_escape_string($in_contact['phone_number']);
    if ($contact->update()) return true;
}

Check out the created WSDL and the service documentation

Continue to Part 2 for the FLEX application

useful actionscript 3 functions

May 16th, 2010 § 14

OK, first post. Lately I've been dabbling with Actionscript so here are two useful functions and I will update the post with more.

First one is an as3 utf8 encode function. This was found at Destroy Today

public static function encodeUTF8 (text:String):String {
	var a:uint, n:uint, A:uint;
	var utf:String;
	utf = "";
	A = text.length;

	for (a = 0; a < A; a++) {
		n = text.charCodeAt (a);
		if (n < 128) {
			utf += String.fromCharCode (n);
		} else if ((n > 127) && (n < 2048)) {
			utf += String.fromCharCode ((n >> 6) | 192);
			utf += String.fromCharCode ((n & 63) | 128);
		} else {
			utf += String.fromCharCode ((n >> 12) | 224);
			utf += String.fromCharCode (((n >> 6) & 63) | 128);
			utf += String.fromCharCode ((n & 63) | 128);
		}
	}
	return utf;
}

Second is an Actionscript implementation of PHP’s html_strip_tags from Flexer

public static function stripHtmlTags(html:String, tags:String = ""):String
{
    var tagsToBeKept:Array = new Array();
    if (tags.length > 0)
        tagsToBeKept = tags.split(new RegExp("\\s*,\\s*"));

    var tagsToKeep:Array = new Array();
    for (var i:int = 0; i < tagsToBeKept.length; i++)
    {
        if (tagsToBeKept[i] != null && tagsToBeKept[i] != "")
            tagsToKeep.push(tagsToBeKept[i]);
    }

    var toBeRemoved:Array = new Array();
    var tagRegExp:RegExp = new RegExp("<([^>\\s]+)(\\s[^>]+)*>", "g");

    var foundedStrings:Array = html.match(tagRegExp);
    for (i = 0; i < foundedStrings.length; i++)
    {
        var tagFlag:Boolean = false;
        if (tagsToKeep != null)
        {
            for (var j:int = 0; j < tagsToKeep.length; j++)
            {
                var tmpRegExp:RegExp = new RegExp("<\/?" + tagsToKeep[j] + "( [^<>]*)*>", "i");
                var tmpStr:String = foundedStrings[i] as String;
                if (tmpStr.search(tmpRegExp) != -1)
                    tagFlag = true;
            }
        }
        if (!tagFlag)
            toBeRemoved.push(foundedStrings[i]);
    }
    for (i = 0; i < toBeRemoved.length; i++)
    {
        var tmpRE:RegExp = new RegExp("([\+\*\$\/])","g");
        var tmpRemRE:RegExp = new RegExp((toBeRemoved[i] as String).replace(tmpRE, "\\$1"),"g");
        html = html.replace(tmpRemRE, "");
    }
    return html;
}

An AS3 regular expression to check for email validity:

public static function validateEmail(email:String):Boolean {
    var emailExpression:RegExp = /^[a-z][\w.-][email protected]\w[\w.-]+\.[\w.-]*[a-z][a-z]$/i;
    return emailExpression.test(email);
}
Partly powered by CleverPlugins.com