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

May 30th, 2010 § 9

…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.-]+@\w[\w.-]+\.[\w.-]*[a-z][a-z]$/i;
    return emailExpression.test(email);
}

Where am I?

You are currently viewing the archives for May, 2010 at Discorganized.

Partly powered by CleverPlugins.com