Flex, PHP, JSON, MySQL - Advanced Updating

Skill

Flex, PHP, JSON, MySQL - Advanced Updating

Posted in:

The last tutorial on Flex and PHP included some code to send data back and forth from a MySQL database. One of the comments asked a question about sending only the relevant updated data to the server and I decided it would be nice to write a tutorial on how to do this. I am going to build off of the last tutorial for this so if you haven't looked at it I would advise it. Along with only updating the data that changes we are also going to differentiate between an update and delete.

Below we have our example program which has some extra capabilities compared to the last one. The first thing you might notice is the check box at the bottom right. This will enable auto updating, meaning that the data is sent back to the server every time it is changed. Another feature that was added is the ability to delete the currently selected item. I will show the server side code for this feature but items can't be deleted from the database in this example for obvious reasons.

Get Adobe Flash player

New PHP Code

The first addition we are going to look at is an added elseif to our php code. This function is going to handle the updating and deleting of the mysql data. The code for this functionality is below.

elseif(isset($_GET['updateTutorials']))
{
  $jsonString = urldecode($_GET['jsonSendData']);
  $jsonString = str_replace("\\", "", $jsonString);
  $data = json_decode($jsonString, true);

  mysql_connect($URL, $USERNAME, $PASSWORD);
  mysql_select_db($DATABASE) or die('Cannot connect to database.');

  foreach ($data as $tutorialEntry) {
    if(!isset($tutorialEntry['deleted']))
    {
      //Handle updated entry
      $query = sprintf(
        'UPDATE Tutorials SET %s = "%s" WHERE id = "%s"',
        mysql_real_escape_string($tutorialEntry['property']),
        mysql_real_escape_string($tutorialEntry['value']),
        mysql_real_escape_string($tutorialEntry['id']));
    }
    else
    {
      // Handle deleted entry
      $query = sprintf(
        'DElETE FROM Tutorials WHERE id = "%s"',
        mysql_real_escape_string($tutorialEntry['deleted']));
    }
    $result = mysql_query($query);      
   
    if(!$result)
    {
      mysql_close();
      echo mysql_error();
      return;
    }
  }
  mysql_close();
  echo "database updated";
}

Now lets look at this in a little more depth. The elseif checks to make sure that the correct GET variable is set to tell the PHP to update the variables. Once we get into the elseif we get our data out of the query string sent and decode it. Next we connect to the database and start looping through our data.

We can now check to see if we are handling a deleted row or an updated row by checking to see if the property deleted is set. If the property is not set we are just updating a row so we create our database query. You will notice that the query is created slightly differently than before, this time we can set an arbitrary property to a value where the id is equal to the id sent to PHP. If we are dealing with a deleted row then we create the query to delete the row, this time we use the same id value in the deleted property. The rest of the PHP code in this elseif is same the last tutorial so I won't go over it.

This gives us the following PHP code for this tutorial, notice I removed the setTutorials section from last tutorial.

<?php
$USERNAME = 'userName';   //database username
$PASSWORD = 'password';    //database password
$DATABASE = 'databaseName';   //database name
$URL = 'localhost';        //database location

if(isset($_GET['getTutorials']))
{
  mysql_connect($URL, $USERNAME, $PASSWORD);
  mysql_select_db($DATABASE) or die('Cannot connect to database.');

  $returnArray = array();

  $query = 'SELECT * FROM Tutorials';
  $result = mysql_query($query);

  while($row = mysql_fetch_assoc($result))
  {
   array_push($returnArray, $row);
  }

  mysql_close();
  echo json_encode($returnArray);
}
elseif(isset($_GET['updateTutorials']))
{
  $jsonString = urldecode($_GET['jsonSendData']);
  $jsonString = str_replace("\\", "", $jsonString);
  $data = json_decode($jsonString, true);

  mysql_connect($URL, $USERNAME, $PASSWORD);
  mysql_select_db($DATABASE) or die('Cannot connect to database.');

  foreach ($data as $tutorialEntry) {
    if(!isset($tutorialEntry['deleted']))
    {
      //Handle updated entry
      $query = sprintf(
        'UPDATE Tutorials SET %s = "%s" WHERE id = "%s"',
        mysql_real_escape_string($tutorialEntry['property']),
        mysql_real_escape_string($tutorialEntry['value']),
        mysql_real_escape_string($tutorialEntry['id']));
    }
    else
    {
      // Handle deleted entry
      $query = sprintf(
        'DElETE FROM Tutorials WHERE id = "%s"',
        mysql_real_escape_string($tutorialEntry['deleted']));
    }
    $result = mysql_query($query);      
   
    if(!$result)
    {
      mysql_close();
      echo mysql_error();
      return;
    }
  }
  mysql_close();
  echo "database updated";
}
?>

Updating the Flex Code

To accommodate the new features in this version of the application we have to make some significant changes to the flex code, but we are starting with the code from the last tutorial. We start by adding the single new UI element, the Auto Update check box. There isn't anything special with the check box. The only other UI Change was the panel title. The following code goes inside our panel.

<mx:CheckBox id="chkAutoUpdate" x="402" y="261" label="Auto Update"/>

The next step is to update our HTTPService code. All that is needed is to update the url of service to our new php file. So my new service code looks like:

<mx:HTTPService id="getData" url="/files/phpdb_advanced_tutorial.php"
   useProxy="false" method="GET" resultFormat="text" result="getPHPData(event)">
  <mx:request xmlns="">
    <getTutorials>true</getTutorials>
  </mx:request>
</mx:HTTPService>
<mx:HTTPService id="sendData" url="/files/phpdb_advanced_tutorial.php"
   useProxy="false" method="GET resultFormat="text" result="updatedDataResult(event)">
</mx:HTTPService>

Adding to the Actionscript

On to the nitty gritty - most of the work that needs to be done is in the actionscript code for the application. In order to keep track of the items that change we are going to need something to store the changed data in. We create a variable of type Array to store the updated data. In the initDataGrid function, which is called when the DataGrid's creation is completed, we also initialize our new var to a new Array. Below is our new code for the inserted var and new function, this replaces the current initDataGrid function.

private var dataToUpdate:Array;

private function initDataGrid():void
{
  dataArray = new ArrayCollection();
  dataToUpdate = new Array();
  getData.send();
}

So we have a place to store the new data but where do we get this changed data from? Well we are going to add an event to our ArrayCollection that holds our data from the server and is bound to the DataGrid. This happens in the function where we get the data back from the server, getPHPData. We can simply add an event listener to our ArrayCollection to listen for the CollectionEvent.COLLECTION_CHANGE event. Then when the event is dispatched, the function dataChanged will get called. So we replace our old getPHPData function with this new one.

private function getPHPData(event:ResultEvent):void
{
  var rawArray:Array;
  var rawData:String = String(event.result);
  rawArray = JSON.decode(rawData) as Array;
  dataArray = new ArrayCollection(rawArray);
  dataArray.addEventListener(CollectionEvent.COLLECTION_CHANGE, dataChanged);
}

Now we need to write the function to handle the collection change event. This function is going to do most of the work of our new functionality. We first create a shell of the function and in this shell we check to see what kind of change is occurring to the collection. We just check for CollectionEventKind.UPDATE and CollectionEventKind.REMOVE but you could as easily check for an add or move. The code below goes into our script tag.

private function dataChanged(event:CollectionEvent):void
{
  if(event.kind == CollectionEventKind.UPDATE)
  {
  }
  else if(event.kind == CollectionEventKind.REMOVE)
  {
  }
}

Next we process the changed or deleted information and add the data to our Array to send to the server. At the beginning of the function we add a couple variables to make our life easier. One for the current item in the ArrayCollection we are processing, one for the the updated object which we put into our Array, and one for a counter. In the first if statement, so if we are just updating the item, we start by setting up a for loop to go through all the updated data. I know it is only possible to update one item at a time in our solution but this may not always be the case so I built in the generic behavior. For each of the items we record the id of the object, the property which was changed, and the new value of that property. This again is very generic in nature so should be able to be used for many occasions. Finally we push that item into our array of updated data.

For a deleted item we do the same type of thing again looping through each item for the same reason as above. This time though we simply set a property name deleted, yes the same one we check for in PHP, to the id of the item deleted. After the if statements we check to see if we need to send the updates now ( Auto Updating) and if this is the case we call our sendPHPData function. So our dataChanged function now looks like this:

private function dataChanged(event:CollectionEvent):void
{
  var item:Object;
  var updatedObj:Object;
  var i:int;
  if(event.kind == CollectionEventKind.UPDATE)
  {
    for(i = 0; i < event.items.length; i++)
    {
      item = event.items[i];
      updatedObj = new Object;
      updatedObj.id = item.source.id;
      updatedObj.property = item.property;
      updatedObj.value = item.source[item.property];
      dataToUpdate.push(updatedObj);
    }
  }
  else if(event.kind == CollectionEventKind.REMOVE)
  {
    for(i = 0; i < event.items.length; i++)
    {
      item = event.items[i];
      updatedObj = new Object;
      updatedObj.deleted = item.id
      dataToUpdate.push(updatedObj);
    }
  }
  if(chkAutoUpdate.selected)
  {
    sendPHPData();
  }
}

That takes care of the hard stuff. All we have to do now is get it sent off to the PHP. We already have the sendPHPData function but we to update it a little. The big change is that we just encode the array of the updated data. Now this will only encode the properties that we set, so in the case of an updated item: property, value, and id, and for a deleted item just deleted. We then send that data off to the PHP. Lastly we add a new call to set our updated data array to a new array to clear out the updated data. We could wait to do this to make sure that the database was updated but I just put it in this function. Our new sendPHPData function now looks like:

private function sendPHPData():void
{
  var objSend:Object = new Object();
  var dataString:String = JSON.encode(dataToUpdate);
  dataString = escape(dataString);
  objSend.updateTutorials = "1";
  objSend.jsonSendData = dataString;
  sendData.send(objSend);
  dataToUpdate = new Array();
}

Adding Delete Functionality

Well there it is. Now we can send only the data that is updated, but wait - we haven't actually added the functionality to delete a row. We do this by adding an event to our DataGrid to listen for the KeyUp event. I named this function checkKeyPress. The DataGrid changes to the following.

<mx:DataGrid id="dgData" x="10" y="10" width="495" height="241" editable="true"
   dataProvider="{dataArray}" creationComplete="initDataGrid()"
   itemEditEnd="checkRating(event)" keyUp="checkKeyPress(event)">

Then in our actionscript we add one last function checkKeyPress. This function checks to see if the delete key is pressed and if so get the data from the DataGrid and removes the currently selected item after checking to make sure the selected index is not a negative number or large than the size of the data ArrayCollection. The final function we add looks like the following.

private function checkKeyPress(event:KeyboardEvent):void
{
  if(event.keyCode == Keyboard.DELETE)
  {
    var data:ArrayCollection = dgData.dataProvider as ArrayCollection;
    if(dgData.selectedIndex >= 0 && dgData.selectedIndex < data.length)
    {
      data.removeItemAt(dgData.selectedIndex);
    }
  }
}

This completes the code for deleting items and updating only the information that changes; below is the final Flex code.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
   width="535" height="345" viewSourceURL="/files/FlexPHPMysqlUpdate.mxml">
  <mx:Script>
   <![CDATA[
     import mx.controls.Alert;
     import mx.events.PropertyChangeEvent;
     import mx.events.CollectionEventKind;
     import mx.events.CollectionEvent;
     import mx.events.DataGridEvent;
     import mx.controls.TextInput;
     import mx.rpc.events.ResultEvent;
     import mx.collections.ArrayCollection;
     import com.adobe.serialization.json.JSON;
 
     [Bindable]
     private var dataArray:ArrayCollection;
   
     private var dataToUpdate:Array;
   
     private function initDataGrid():void
     {
       dataArray = new ArrayCollection();
       dataToUpdate = new Array();
       getData.send();
     }
   
     private function getPHPData(event:ResultEvent):void
     {
       var rawArray:Array;
       var rawData:String = String(event.result);
       rawArray = JSON.decode(rawData) as Array;
       dataArray = new ArrayCollection(rawArray);
       dataArray.addEventListener(CollectionEvent.COLLECTION_CHANGE, dataChanged);
     }
   
     private function dataChanged(event:CollectionEvent):void
     {
       var item:Object;
       var updatedObj:Object;
       var i:int;
       if(event.kind == CollectionEventKind.UPDATE)
       {
         for(i = 0; i < event.items.length; i++)
         {
           item = event.items[i];
           updatedObj = new Object;
           updatedObj.id = item.source.id;
           updatedObj.property = item.property;
           updatedObj.value = item.source[item.property];
           dataToUpdate.push(updatedObj);
         }
       }
       else if(event.kind == CollectionEventKind.REMOVE)
       {
         for(i = 0; i < event.items.length; i++)
         {
           item = event.items[i];
           updatedObj = new Object;
           updatedObj.deleted = item.id
           dataToUpdate.push(updatedObj);
         }
       }
       if(chkAutoUpdate.selected)
       {
         sendPHPData();
       }

     }
   
     private function sendPHPData():void
     {
       var objSend:Object = new Object();
       var dataString:String = JSON.encode(dataToUpdate);
       dataString = escape(dataString);
       objSend.updateTutorials = "1";
       objSend.jsonSendData = dataString;
       sendData.send(objSend);
       dataToUpdate = new Array();
     }
   
     private function checkKeyPress(event:KeyboardEvent):void
     {
       if(event.keyCode == Keyboard.DELETE)
       {
         var data:ArrayCollection = dgData.dataProvider as ArrayCollection;
         if(dgData.selectedIndex >= 0 && dgData.selectedIndex < data.length)
         {
           data.removeItemAt(dgData.selectedIndex);
         }
       }
     }
   
     private function updatedDataResult(event:ResultEvent):void
     {
       lblStatus.text = String(event.result);
     }
   
     private function checkRating(event:DataGridEvent):void
     {
       var txtIn:TextInput = TextInput(event.currentTarget.itemEditorInstance);
       var curValue:Number = Number(txtIn.text);
       if(isNaN(curValue) || curValue < 0 || curValue > 10)
       {
         event.preventDefault();
         lblStatus.text = "Please enter a number rating between 0 and 10";
       }
     }
   ]]>
 </mx:Script>
  <mx:HTTPService id="getData" url="/files/phpdb_advanced_tutorial.php"
     useProxy="false" method="GET" resultFormat="text" result="getPHPData(event)">
    <mx:request xmlns="">
      <getTutorials>true</getTutorials>
    </mx:request>
  </mx:HTTPService>
  <mx:HTTPService id="sendData" url="/files/phpdb_advanced_tutorial.php"
     useProxy="false" method="GET" resultFormat="text"
     result="updatedDataResult(event)">
  </mx:HTTPService>
  <mx:Binding source="dgData.dataProvider as ArrayCollection" destination="dataArray"/>
  <mx:Panel x="0" y="0" width="535" height="345" layout="absolute"
     title="Flex, PHP, Mysql - Advanced Updating">
    <mx:DataGrid id="dgData" x="10" y="10" width="495" height="241" editable="true"
       dataProvider="{dataArray}" creationComplete="initDataGrid()"
       itemEditEnd="checkRating(event)" keyUp="checkKeyPress(event)">
      <mx:columns>
        <mx:DataGridColumn headerText="Name" dataField="name" editable="false"/>
        <mx:DataGridColumn headerText="Author" dataField="author" width="115"
           editable="false"/>
        <mx:DataGridColumn headerText="Rating" dataField="rating" width="50"
           editable="true" />
      </mx:columns>
    </mx:DataGrid>
    <mx:Button x="10" y="259" label="UpdateDatabase" id="butUpdate"
       click="sendPHPData()"/>
    <mx:Label x="140" y="261" id="lblStatus" width="254"/>
    <mx:CheckBox id="chkAutoUpdate" x="402" y="261" label="Auto Update"/>
  </mx:Panel>
</mx:Application>

I hope that this tutorial helps some of you out there. If you have any questions please leave a comment. Also don't forget that if this didn't make a whole lot of sense you need to check out this tutorial.

Tedd
10/04/2007 - 11:35

This is a great tutorial! What I have found is that my web host is only running PHP 4.4.7 so I can not properly encode JSON strings so I need to pass my data from PHP to the Flex application as XML. Could you provide a complementary tutorial for posting data from Flex to PHP using XML?

reply

The Fattest
10/05/2007 - 08:29

Yeah that can be done. Look for it in the next week or so.

reply

Gildus
10/15/2007 - 20:26

Wowwwwwww!!!, excelent, thats's good man, just that i was found.

Regards
Gildus

reply

Gilles
10/23/2007 - 13:41

Could you please provide a complementary tutorial for posting data from Flex to PHP using XML?

reply

The Fattest
10/23/2007 - 13:50

This is quite easy I am sure I can come up with something quick, what version of PHP are you using?

reply

Gilles
10/23/2007 - 15:08

v5.2

Using MSSQL 2005, I'm able to load the table using JSON, but I'm not able to update the table.

I'm not getting any returned values from json_decode.

Thanks for your help!

reply

The Fattest
11/01/2007 - 08:50

Gilles, sorry it took a week get this going. Here is an example and you can check out the source code using right click.
Look for the complete tutorial in the next day.

reply

Tedd
11/28/2007 - 12:39

I am using Flex 2 and creating an editable data grid but I am finding the event handlers very buggy. I am wondering if it is just me or if this is a common thing. My application is modularized and when I try to itemEditEnd or itemFocusOut I don't always get the desired behavior. Sometimes the event is recognized, other times it's not. Instead I tried to bind my data grid changes to my array collection data provider and that seems buggy too. Sometimes my changes are captured, sometimes they get reverted back to the original values. Is this a problem with my environment or is this just a problem with Flex 2 and Adobe has addressed these data grid event issues with Flex 3? Thanks in advance, Tedd

reply

The Fattest
11/28/2007 - 13:30

Tedd, I have had a little fun (by fun I mean issues) with itemEdit events but haven't had too many issues with array collection updates. Although I have had small issues with the array collection being updated when the data provider is bound back into the array collection. But overall most of the time I can find one of the events that occurs correctly consistently.

reply

Tedd
11/29/2007 - 11:41

I figured out what was causing my problem and it was the one piece of info that I neglected to included in my message. I was using an itemRenderer in my data grid. I was reading a blog by one of the Flex architects and he spoke about the downfalls of using item renderers and this was on of them. Once I took the item renderer out it works as expected. Thanks.

reply

Nathan
12/05/2007 - 23:38

Great tutorial!

Question: why do we need

"dataArray = new ArrayCollection();"

inside the initDataGrid() function?

reply

The Fattest
12/06/2007 - 08:34

Nathan,
Good Question. We don't really need I just wanted to have it there to make sure the dataprovider wasn't null on the datagrid. But really I don't think I would normally do it. I would just let the dataprovider get set when we grab the data from the server.

reply

Tedd
12/06/2007 - 10:41

I have an array collection that I load, however I get an error when there is only one record returned. Have you run into this problem before? If so, do you know how I can prevent this error from occuring. The error I get is:

Error #1034: Type Coercion failed: cannot convert mx.utils::ObjectProxy@403fbc1 to mx.collections.ArrayCollection

My code looks like this:

[Bindable] public var myData:ArrayCollection;
[Bindable] public var strDateSelected:String = "";

private function returnedDbEvents(event:ResultEvent):void {
if ( event.result.dbGridData != null ) {
myData = ArrayCollection(event.result.dbGridData.eventData);
}
}

{strDateSelected.valueOf()}

reply

Tedd
12/06/2007 - 10:44

Here is the rest of my code:

<mx:HTTPService id="getEvents"
url="http://www.someUrl.php"
useProxy="false"
method="POST"
result="returnedDbEvents(event)">

{strDateSelected.valueOf()}

reply

The Fattest
12/06/2007 - 12:36

Tedd,
Yeah I can see how that could be an is I think the easiest solution would be to check the type of the object and if it is an ObjectProxy then you can create a new ArrayCollection and add it as the first item. Then if it is an Array then you can do it the way you are now. This happens when the event data only has one item so it casts/creates the object as a single object, not as an array. Does that make sense?

reply

Tedd
12/06/2007 - 13:20

It makes sense, I will see if I can get it to work and I will let you know. Thank you for your guidance and wisdom...

reply

Tedd
12/06/2007 - 14:25

Checking to see if my result was an ObjectProxy worked but ddn't work. When an ArrayCollection was returned it was also identified as an ObjectProxy so whenever I returned data it was identified as an ObjectProxy. However I did get it to work by checking to see if my result was an ArrayCollection. Here is my code:

if ( event.result.dbGridData.eventData is ArrayCollection) {
  myData = ArrayCollection(event.result.dbGridData.eventData);
} else {
  myData = new ArrayCollection;
  myData.addItem(event.result.dbGridData.eventData);
}

reply

The Fattest
12/06/2007 - 14:38

Tedd,
Yeah dummy me. Of course the ArrayCollection is going to show up as an ObjectProxy but glad to see that the problem is solved.

reply

Tedd
12/11/2007 - 10:45

I appreciate all your help the last couple of months. I am building a flex site for a client and they have requested both a Blog and a Forum, is there anything out of the box that I could use in Flex or do I have to roll my own? Is there soem third party thing I could plug in to use? Thanks in advance, Tedd

reply

Alan
12/23/2007 - 17:39

Hi - This is a great tutorial and it has helped me tremendously. Thank You!!

I was, however, getting browser caching problems with the GET method of the HTTPService. Every time I refreshed my browser, I noticed that although my database had updated correctly, my DataGrid would only show the data before the update.

Changing the two GET methods in the FlexPHPMysqlUpdate.mxml file to POST as well as all the $_GET references in the phpdb_advanced_tutorial.php file to $_POST, solved this problem.

I got the GET method to work only after I learned that browsers do not cache data if the url changes. My workaround was to append a random number to the end of my HTTPService url properties.

If it's any help to others with similar caching problems, I made the following changes to your mxml code - I Added to the "":-

private var randomURL:String;
private function randomURLs():String
{
randomURL = "http://www.yoursite.com/phpdb_advanced_tutorial.php?random=" + Math.random();
return randomURL;

}

Then I called the randomURLs function from the two HTTPservice (i.e. "getData" & "sendData") url properties:-

true

reply

Ryan
01/03/2008 - 15:57

Great Tutorial!!! Thank You!

Quick question... Has anyone added to this code a way to add a new "data entry" aka a new mysql row? I'm admittedly a little slow on the uptake and have been racking my brain trying to figure this out. If i do figure it out i will post it here for references. Thank You.

reply

The Fattest
02/02/2008 - 16:38

Ryan,
I am not quite sure what you mean. I mean you can add a row to mysql using a simple insert sql statement. If your trying to make it so you can add a new row on the flex side, you have a button for a new row with a popup to fill out the information and once the information is filled out you can add it to the bound data collection. And as the data collection gets updated send this information to php notifying it is a new row. Another option would be to always have an extra row in the datagrid where you type in the new data and after the new data is entered you can send php the update (this technique would more difficult to implement).

reply

Dragan
01/22/2008 - 11:51

Hi! This was great tutorial. But only thing that needs here is posting data into data base. I code functions for adding new row into Data Grid, but I need to know how can I pass parameter from Flex to PHP without pressing any button, just filling the empty row of Data Grid, so I can know that it's added new row, and I will put the data into Data Base with PHP.
There is the code for adding new row into Data Grid, new row for writing into.

import flash.utils.ByteArray;

private function clone(source:Object):*
{
  var myBA:ByteArray = new ByteArray();
  myBA.writeObject(source);
  myBA.position = 0;
  return(myBA.readObject());
}

private function addRow():void
{
  var temp:Array = clone(dataArray.toArray());
  dataArray=new ArrayCollection(temp);
  dgData.dataProvider=dataArray;

  var obj:Object = new Object();
  obj.name="";
  dataArray.addItemAt(obj, 0);
}

Of course you are adding a button to add new row as well:
.

So can someone help me, please :)
Greetings, Dragan

reply

Dragan
01/22/2008 - 11:55

I forgot to tell you that I expand the Data Grid so all fields can be editable. That's more useful for me. I just need to add brand new row, and store the data into Data base.
:)

reply

The Fattest
02/02/2008 - 16:38

There is an event for when data is being modified, which could be used to check if a new datarow is being added. I would assume that the new row is always the last row or maybe the first but checking the row index would let you know it's a new row.

reply

Dragan
01/27/2008 - 09:31

I have another problem. We are putting the data into ArrayCollection with JSON. Then I am using this ArrayCollection as a data provider for the Data Grid. I am not specifying the Data Grid column header. Data Grid retrieve the header text from the Array Collection. But there is a big problem. I am not even specifying any columns in Data grid. The problem is as I give data from ArrayCollection to the Data grid, the header text of the columns Data Grid is sorted. I don't want this to happened. Co can some one help me!.
Here is the dataGrid code:

<mx:DataGrid id="dg" dataProvider="{dataArray}" width="100%" height="100%"
   creationComplete="{initDataGrid()}" change="{onChangeGrid()}"/>

reply

The Fattest
02/02/2008 - 16:38

I am not sure I understand your issue with the datagrid headers? Do you not want the headers to show? This can be taken care of with a simple property on the datagrid called "showHeaders"? Also there is a property called "columnsSortable" or something like that to say if the columns are sortable.

reply

Dragan
01/30/2008 - 19:05

I am having problems passing UTF-8 characters from flex to php. Vice versa I don't have problems. Is it some kind of JSON settings, or error, or bug. Can someone help me please, it's really important.
:) Best regards,
Dragan

reply

The Fattest
02/02/2008 - 16:38

Check out this comment or on the other tutorial.

reply

Eleos
02/05/2008 - 08:31

Fattest, can you please add this function of adding new row in this project, with a button and a pop up, like you said, and the appropriate insert statement in the php file which we will need.

With respect & appreciation by a very tired student.

reply

Louis
03/10/2008 - 19:08

I also second what Eleos has said about adding a new row functionality. I'm trying to do it myself, but i'm having all kinds of problems. Thankyou so much for the tutorials so far, they're really clear and helpful!

reply

Mark
02/16/2008 - 09:19

so can one pass a more complex object, say a datagrid and some form text input values?
for example, an invoice form.

reply

Fandoo
04/16/2008 - 05:55

Hi,

Can any buddy help me in drag n drop. I want to drag cell’s info from 1st datagrid to marge/replace in a row in another datagrid cell and I dont want to create a new row in 2nd datagrid.

reply

Tan
04/23/2008 - 23:33

Great Tutorial. Very easy to understand and follow. Thank you! =)

reply

Liceven
06/09/2008 - 01:57

Hello,Fattest:
Great tutorial. It is very helpful.
I have a question, in the function

dataChanged(){
  updatedObj.id = item.source.id;
  updatedObj.property = item.property;
  updatedObj.value = item.source[item.property];
...
}

there are some properties like: item.id, item.source.id,etc. but they don't exist in flex3 lib. So, i want to know what is going on with these properties? because i got erro message:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
Thanks very much
Liceven

reply

The Fattest
06/09/2008 - 08:17

Liceven, I will check out what has changed in Flex 3. I will update the tutorial here soon.

reply

Liceven
06/09/2008 - 20:50

Hi,Fattest:
I am glad to see your message.I want to say you did a good job, especially for a flex rookie. although i didn't learn php, i could still understand the tutorial well. Thanks very much. I am looking forward to your news.
Liceven

reply

Liceven
06/23/2008 - 09:41

There is a solution :
that is objSend = dgData.selectedItem.name;
and then put objSend into array.
but when it comes to removing action, this would not work..because the item has been removed ..

reply

Feryan
09/14/2008 - 16:02

Hello!

When I'm loading this page I get and error about JSON and the data cannot load.

I made the test on my comp made my own script and works.

I put it up on my linux server same error like the one I'm getting here.

any suggestions?

reply

Feryan
09/14/2008 - 17:03

This is the error when I'm loading this page same error getting from my linux server.

Error: Unexpected C encountered
at com.adobe.serialization.json::JSONTokenizer/parseError()
at com.adobe.serialization.json::JSONTokenizer/getNextToken()
at com.adobe.serialization.json::JSONDecoder/nextToken()
at com.adobe.serialization.json::JSONDecoder()
at com.adobe.serialization.json::JSON$/decode()
at PHPMysqlAdvancedUpdate/getPHPData()
at PHPMysqlAdvancedUpdate/__getData_result()
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at mx.rpc.http.mxml::HTTPService/
http://www.adobe.com/2006/flex/mx/internal::dispatchRpcEvent()
at mx.rpc::AbstractInvoker/
http://www.adobe.com/2006/flex/mx/internal::resultHandler()
at mx.rpc::Responder/result()
at mx.rpc::AsyncRequest/acknowledge()
at DirectHTTPMessageResponder/completeHandler()
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at flash.net::URLLoader/onComplete()

reply

Andrei
11/21/2008 - 09:19

Can anyone help me with this error: Type was not found or was not a compile-time constant:0CollectionEvent
It's found on the function dataChanged.

reply

Michael
12/12/2008 - 10:45

After you sort a column in a datagrid, how could you save the order in a mysql db so that the next time you pulled the data, the datagrid remembered the order?

reply

Michael
12/12/2008 - 10:59

I tried to add a CollectionEventKind.ADD to the dataChanged function above but it doesn’t seem to be supported. What is a workaround with your code to use the dataChanged function to send data to PHP for a new record?

reply

Simon
12/30/2008 - 07:19

This is great but I have a problem in trying to get data from an ArrayCollection into a datagrid.
I have created a Webservice in Java that connects to a MySQL database and returns back an array of members in a club based on a selection made on the web page. This comes back as an ArrayCollection and I have added the name of the array collection as the dataprovider in the format dataProvider={result}. The result back from the call to the Web Service is set as follows;
result = evt.result.membersList;
membersList being the array implementation from the web service.
The problem is that the datgrid is not being populated. I am new to Flex 3 and I am thinking that I need to create an event to carry out the population. Any help would be grateful.

reply

tiFemur
03/08/2009 - 02:28

I'm currently working on a flex application and was wondering if this method is typical for flex applications. (meaning, the swf file basically requests a data set from a php file)

Does this mean that a complete flex application will have a php file for all types of data sets from the database? What would you recommend if what I am planning on working on will have several tables and many users?

Thank you for the tutorial!

reply

The Fattest
03/08/2009 - 11:04

Well, that is one way to go about it and it is a fine way. There are other methods also. One very popular and fast method is to use Flash Remoting. If you are a PHP person check out Zend Framework, http://framework.zend.com/manual/en/zend.amf.html. The Zend Framework makes it fairly easy to setup and use. If you want to go Adobe all the time you can use ColdFusion which has Remoting built in.

If you chose to go with making callbacks to php files then I would break up the callbacks into probably tables. Each php script would handle a table and the functions related. Now you could organize your server side code by actually creating classes to make these updates and then your php callback scripts would use the objects to take care of the updates.

Does that make any sense at all?

reply

tiFemur
03/09/2009 - 01:04

Yes, thank you very much for your help and quick response!

reply

core000
04/02/2009 - 20:57

Fattest, can I use Oracle database instead of mySQL? Also, what do I need to change in order for itemRenderers to work on the datagrid?

reply

jhulbert
04/13/2009 - 16:41

I have just completed a very similar example using an Oracle DB instead of MYSQL DB.
The example I created was very simple using a table with 3 columns: channel_id, name and description.
The code is almost identical, but I changed the names of a few items to meet my needs. (ie. updateTutorials changed to updateChannels). Pretty much everything else is the same. However, Oracle does capitalize the column names that it returns, so keep that in mind.
The PHP file is the only thing that needs to change for Oracle vs MYSQL, because you have to make OCI_% calls in place of the MYSQL_% calls.

Here is the PHP code for an Oracle DB. I hope it is helpful:

<?php
if (isset($_GET['getChannels'])) {
  if ($db_conn=OCILogon("schema_name", "password", "tnsnames_entry.world")) {
    $cmdstr = "select * from news_channels";
    $parsed = ociparse($db_conn, $cmdstr);
    ociexecute($parsed);
    $returnArray = array();
    while ($row = oci_fetch_assoc($parsed)) {
      array_push($returnArray, $row);
     }
    OCILogoff($db_conn);
    echo json_encode($returnArray);
  }
}
elseif (isset($_GET['updateChannels'])) {
  if ($db_conn=OCILogon("schema_name", "password", "tnsnames_entry.world")) {
    $jsonString = urldecode($_GET['jsonSendData']);
    $jsonString = str_replace("\\", "", $jsonString);
    $data = json_decode($jsonString, true);

    foreach ($data as $channelEntry) {
      if(!isset($channelEntry['deleted'])) {
         //Handle updated entry
         $query = sprintf(
        'UPDATE NEWS_CHANNELS
         SET %s = \'%s\' WHERE CHANNEL_ID = %d'
,
         $channelEntry['property'],
         $channelEntry['value'],
         $channelEntry['CHANNEL_ID']);
      }
      else {
        // Handle deleted entry
        $query = sprintf(
        'DELETE FROM NEWS_CHANNELS
         WHERE CHANNEL_ID = to_number(\'%s\')'
,
        $channelEntry['deleted']);
      }
      $parsed = ociparse($db_conn, $query);
      $result = oci_execute($parsed);
      if (!$result) {
        $err = OCIError();
        OCILogoff($db_conn);
        echo $err[text];
        return;
      }
      else {
        echo "Database Updated";
      }
    }
    OCILogoff($db_conn);
  }
}
else {
   $err = OCIError();
   echo "Oracle Connect Error " . $err[text];
   OCILogoff($db_conn);
}
?>

reply

jhulbert
04/13/2009 - 16:52

You can also check out the other OCI8 Functions here:
PHP: OCI8 Functions - Manual

reply

The Fattest
04/13/2009 - 17:01

Awesome stuff thanks for the code.

reply

jhulbert
04/15/2009 - 18:54

You are welcome.

The only thing I am missing now is how to pass an inserted row back to my PHP file and have it inserted into the DB.
For some reason it doesn't appear that (event.kind == CollectionEventKind.ADD) is working.

I have updated the mxml dataChanged function to be as follows:

private function dataChanged(event:CollectionEvent):void
{
  var item:Object;
  var updatedObj:Object;
  var i:int;
  if(event.kind == CollectionEventKind.ADD)
  {
    for(i = 0; i < event.items.length; i++)
   {
     updatedObj = new Object;
     updatedObj.INSERTED = "1";
     updatedObj.NAME = item.source.NAME;
     updatedObj.DESCRIPTION = item.source.DESCRIPTION;
     dataToUpdate.push(updatedObj);
   }
 }
 else if(event.kind == CollectionEventKind.UPDATE)
 {
   for(i = 0; i < event.items.length; i++)
   {
     item = event.items[i];
     updatedObj = new Object;
     updatedObj.CHANNEL_ID = item.source.CHANNEL_ID;
     updatedObj.property = item.property;
     updatedObj.value = item.source[item.property];
     dataToUpdate.push(updatedObj);
    }
  }
  else if(event.kind == CollectionEventKind.REMOVE)
  {
    for(i = 0; i < event.items.length; i++)
    {
      item = event.items[i];
      updatedObj = new Object;
      updatedObj.DELETED = item.CHANNEL_ID
      dataToUpdate.push(updatedObj);
    }
  }
}
...

and the PHP code snippet to:

elseif (isset($_GET['updateChannels'])) {
  if ($db_conn=OCILogon("schema_name", "password",
      "tnsnames_entry.world")) {
    $jsonString = urldecode($_GET['jsonSendData']);
    $jsonString = str_replace("\\", "", $jsonString);
    $data = json_decode($jsonString, true);
    foreach ($data as $channelEntry) {
      if(isset($channelEntry['INSERTED'])) {
        //Handle inserted entry
        $query = sprintf(
        'INSERT INTO NEWS_CHANNELS
         values ((select max(CHANNEL_ID)+1
                  from NEWS_CHANNELS),\'%s\',\'%s\')'
,
        $channelEntry['NAME'],
        $channelEntry['DESCRIPTION']);
      }
      elseif(!isset($channelEntry['DELETED'])) {
         //Handle updated entry
         $query = sprintf(
        'UPDATE NEWS_CHANNELS
         SET %s = \'%s\' WHERE CHANNEL_ID = %d'
,
         $channelEntry['property'],
         $channelEntry['value'],
         $channelEntry['CHANNEL_ID']);
      }
      elseif(isset($channelEntry['DELETED'])) {
        // Handle deleted entry
        $query = sprintf(
        'DELETE FROM NEWS_CHANNELS
         WHERE CHANNEL_ID = to_number(\'%s\')'
,
        $channelEntry['DELETED']);
      }
      $parsed = ociparse($db_conn, $query);
      $result = oci_execute($parsed,OCI_COMMIT_ON_SUCCESS);
...

However, it fails and the "INSERTED" is never updated so it falls in to the first else if section.
So, it appears that this isn't working at all.

Can you provide any guidance here?
I am so close to having this all working, but I have to get the insert.

Thanks in advance!
Jim

reply

jhulbert
04/15/2009 - 16:59

It appears that the main problem I am having with the row insert is that the array sent to the PHP file contains a record for each item. How can I make it contain a row with the values for each column in one row? Do I have to send it over on a per item basis or can it be sent as an array of full rows? If it can be sent as full rows, please tell me how.

I have tried to not loop though the items as in the update, and just set:

updatedObj.NAME = item.source.NAME
updatedObj.DESCRIPTION = item.source.DESCRIPTION;

This didn't work either.
Please help me when you get a chance.

Thanks - Jim

reply

jhulbert
04/15/2009 - 19:04

The Fattest - Thanks for not answering this question for me right away. It caused me to stew over it for quite some time and actually learn how the arrays are working more thoroughly.

I have resolved the mystery of inserting a row or several.

What I had to do was to insert the blank row into the dataGrid and into the dataToUpdate array after assigning the channel_id a value on the client side and not trying to assign the value on the server side. This would allow the row to be inserted and then updated from the same sendPHPData call. This works because the php code will walk through the json array and execute the sql statements in the correct order. So, first the row will be inserted with CHANNEL_ID = the next channel_id, NAME='Enter Name', and DESCRIPTION='Enter Desc'. Then it will get to the update statement (if the user changed the name and description to something meaningful) and perform the update.

Hopefully this explains how it is working.

Here is the mxml dataChanged function with the CollectionEventKind.ADD code block added:

private function dataChanged(event:CollectionEvent):void
{
  var item:Object;
  var updatedObj:Object;
  var i:int;
  var x:int;
  if(event.kind == CollectionEventKind.UPDATE)
  {
    for(i = 0; i < event.items.length; i++)
    {
     item = event.items[i];
     updatedObj = new Object;
     updatedObj.CHANNEL_ID = item.source.CHANNEL_ID;
     updatedObj.property = item.property;
     updatedObj.value = item.source[item.property];
     dataToUpdate.push(updatedObj);
    }
 }
 else if(event.kind == CollectionEventKind.REMOVE)
 {
   for(i = 0; i < event.items.length; i++)
   {
     item = event.items[i];
     updatedObj = new Object;
     updatedObj.DELETED = item.CHANNEL_ID
     dataToUpdate.push(updatedObj);
   }
 }
 else if(event.kind == CollectionEventKind.ADD)
 {
   for(i = 0; i < event.items.length; i++)
   {
    item = event.items[i];

  // Setting x to the last number that was in the grid
  // before the new row was added.  The dataAC array is
  // 0 based where my length is 1 based, so I have to
  // subtract 2 to get to the correct row.  This does
  // assume that the records are sorted by channel_id
  // ascending.
    x = channelsAC.getItemAt(dataAC.length-2).CHANNEL_ID;
    x += 1;

  // Setting the new channel_id in the DG via the
  // dataProvider array dataAC
    dataAC.getItemAt(dataAC.length-1).CHANNEL_ID = x
    updatedObj = new Object;
    updatedObj.INSERTED = "1";
    updatedObj.CHANNEL_ID = x;
    updatedObj.NAME = event.items[i].NAME;
    updatedObj.DESCRIPTION = event.items[i].DESCRIPTION;
    dataToUpdate.push(updatedObj);
   }
 }
}
...

and the updated PHP code snippet is now as follows:

elseif (isset($_GET['updateChannels'])) {
  if ($db_conn=OCILogon("schema_name", "password",
      "tnsnames_entry.world"))
  {
    $jsonString = urldecode($_GET['jsonSendData']);
    $jsonString = str_replace("\\", "", $jsonString);
    $data = json_decode($jsonString, true);
    foreach ($data as $channelEntry)
    {
     if(isset($channelEntry['INSERTED']))
     {
       //Handle inserted entry
       $query = sprintf(
       'INSERT INTO NEWS_CHANNELS
        values (to_number(\'%s\'),\'%s\',\'%s\')'
,
       $channelEntry['CHANNEL_ID'],
       $channelEntry['NAME'],
       $channelEntry['DESCRIPTION']);
     }
     elseif(!isset($channelEntry['DELETED']))
     {
       //Handle updated entry
       $query = sprintf(
       'UPDATE NEWS_CHANNELS
       SET %s = \'%s\' WHERE CHANNEL_ID = %d'
,
       $channelEntry['property'],
       $channelEntry['value'],
       $channelEntry['CHANNEL_ID']);
     }
     elseif(isset($channelEntry['DELETED']))
     {
       // Handle deleted entry
       $query = sprintf(
       'DELETE FROM NEWS_CHANNELS
       WHERE CHANNEL_ID = to_number(\'%s\')'
,
       $channelEntry['DELETED']);
     }
     $parsed = ociparse($db_conn, $query);
     $result = oci_execute($parsed,OCI_COMMIT_ON_SUCCESS);
...

This now works great. Hope this has helped someone else that was running into this same problem.

reply

The Fattest
04/15/2009 - 21:27

Awesome stuff, I was just getting ready to sit down and try and figure your issue. Glad to see you got it all figured out.

reply

jhulbert
04/17/2009 - 09:21

Thanks. Let me know if there is a better way to do this.
This seems to be working nicely though.
Jim

reply

core000
06/10/2009 - 11:21

Thanks jhulbert. I am in the process of implementing my app using some of your code.

I'll let you know if I run into any problems.

reply

Michael H.
06/19/2009 - 20:22

The Fattest, I have the code from this example working perfectly in my application with one exception. If I sort on the column I'm modifying data for when the collection change event is triggered it's a "move" instead of an "update". Any ideas why this is happening?

Thanks,
Michael

reply

TomBr
08/21/2009 - 08:35

Hi,

I have a problem that I can't solve.

When a drag/drop an item from datagrid1 to datagrid2 .. the addEventListener(CollectionEvent.COLLECTION_CHANGE, dataChanged);doens't seem to get triggered.

How do make sure that a DROP event (dropping a new record) also updates the dataToUpdate

reply

Wouter
08/22/2009 - 09:54

Thanks for this tutorial,

How can I update/save data in the other columns?

Thanks,

Wouter from the Netherlands

reply

Wouter
08/29/2009 - 03:49

Everything works well now. But now I have another question. I want to put the selected row from the dataArray in another datagrid and then edit the fields. So there is more place to edit. Is there a way do do that?
Or is the only way to use an itemeditor?

Thanks

reply

Mark Statkus
09/29/2009 - 14:28

Hi,
A thanks for:
$jsonString = urldecode($_GET['jsonSendData']);
$jsonString = str_replace("\\", "", $jsonString);

I was running on PHP 5.2.1 and everything was null till I put that fix in. Seems like post that everything is fine.

reply

bikki
09/30/2009 - 06:19

how to delete items from data grid

reply

Anonymous
10/08/2009 - 12:46

[RPC Fault faultString="HTTP request error" faultCode="Server.Error.Request" faultDetail="Error: [IOErrorEvent type="ioError" bubbles=false cancelable=false eventPhase=2 text="Error #2032: Stream Error. URL: file://C:/files/phpdb_advanced_tutorial.php?getTutorials=true"]. URL: /files/phpdb_advanced_tutorial.php"]
at mx.rpc::AbstractInvoker/http://www.adobe.com/2006/flex/mx/internal::faultHandler()
at mx.rpc::Responder/fault()
at mx.rpc::AsyncRequest/fault()
at DirectHTTPMessageResponder/errorHandler()
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at flash.net::URLLoader/onComplete()

reply

Suresh
10/08/2009 - 12:48

I use FB3 Wampserver i am able to connect to the DB but i am getting the datagrid without any values in it,i tried for long hours but finally writing to u,

I am sure db has some dummy values and checked the connections please help me in FB3 when i compile it told html wrapper error right click to recreate the template i did it..

Can you please post this code on FB3 it will be very helpful for all new users like me

reply

Jack
10/09/2009 - 00:29

Hey even i used Flex Builder 3 and i could not make it work, i am getting the datas in the datagrid, can you please post a post or help us how to do in Flex Builder 3

Suresh many of us hope Fat will do it for us

reply

EkGrace
01/03/2010 - 14:36

You seem to be really master and your knowledge just about this good post is superior. Will you continue your research? We would buy some thesis pdf or dissertation writing from you.

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.

Sponsors