Reading And Writing Drupal With Flex

October 29, 2007

Tutorials

Reading And Writing Drupal With Flex

Drupal is a fantastically well featured, and free, content management system written in PHP. No wonder so many companies are using it internally or externally as one of their business applications. But often Drupal alone is not enough. It needs to be integrated or extended to match the needs of the organization. One way to extend it is on the front-end through something like an Ajax or Flex based widget.

In this article I’ll show you how to build a Flex based data entry form that adds it’s data directly into Drupal content nodes. Why Flex? Because I’d like to be able to put the widget on any site and Ajax has security issues there. Web browsers, quite rightly, ensure that Ajax pages only request data from their server of origin. So it’s much harder to host an Ajax widgets on external sites without kludges like IFrames or Script tag data transit. Flex allows data access from different servers. So it’s far easier to write widgets that can be hosted externally.

Another reason to choose Flex in this case is that the resultant widget is a Flash application which is easy to embed on any page with just a little object code. Javascript widgets can be easy to embed as well, but can have compatibility issues with other Javascript code on the same page. Because it’s Flash you can reliably drop the widget on any page and have it render and work the same way on any site, browser or operating system.

Of course, Ajax has it’s strong point too. It’s always a case where you need to judge the correct approach for any application by weighing the pros and cons.

Enough exposition though, on to the implementation, and that starts with getting Drupal to talk web services!

Upgrading Drupal

The first thing I did was install Drupal on my Macintosh OS X laptop. Then I used the install.php script to set up the database and finished the installation by creating my first administrative user account. If you already have a Drupal installation going then you can just us that as is.

Once everything was installed and up and running I downloaded the Service module and installed it in the modules. With the Service module installed I could get access to web services. But what I want was to use the web services through the ActionScript Message Format (AMF) protocol.

Why AMF you ask? Simply because it’s a lot easier to use from Flex. Flex can use a variety of formats very easily, but AMF is very simple and it’s supported with a Drupal module. And I’m kinda lazy that way. So I like it.

The next step then is to download the AMFPHP library for PHP. Then download and install the AMFPHP module for Drupal and put the AMFPHP library for PHP in it.

Then I go back to Drupal and turn on the Services module by going to the Administer > Site Building > Modules section. From there I turn on the Services Module, then the AMFPHP Module and finally the Node Service and User Service. I could enable all of it, and that would be fine. But for this example all I’m going to use is the User Service and the Node Service.

With that all done I go to the Administer > Site Building > Services section and I see something that looks like Figure 1 in my browser.



Figure 1-1. The Drupal Services page

If I click on the AMFPHP Service I see the page as shown in Figure 2.



Figure 1-2. The AMFPHP service

But to use this AMFPHP service from Flex I first need to make sure it can be used by ‘clean URL’ and that means going to the Administer > Site Configuration > Clean URLs page and enabling the Clean URLs option. This page is shown in Figure 3.



Figure 1-3. The Clean URLs configuration page

On my Macintosh OS X installation this was a little bit of a pain. I first had to edit the httpd.conf file in /etc/httpd to enable all overrides in the /Library/WebServer/Documents/drupal directory where I had installed Drupal. I then needed to change the URL in the .htaccess file that came with Drupal to rewrite URLs relative to /drupal instead of just /.

I then rebooted the Apache server by running:

% sudo apachectl graceful

On your installation this might be slightly different.

The final step is to create a blank node that I will update from the Flex widget. This is shown in Figure 4.



Figure 1-4. The blank starting addresses node

I’ve named it addresses because that is what my Flex widget will capture. It will have fields for name, email and phone number and add the results to the Addresses node in Drupal dynamically.

The first step in building the widget starts with designing the user interface.

Building The Interface

One of the easiest things to do in Flex is to build nice looking interfaces. In this case all I wanted to do was build a single form with three fields and a button to add the record to the Drupal node.

The code for this is shown below:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="228" height="118"
   paddingBottom="0" paddingTop="5" paddingLeft="0" paddingRight="0">
   <mx:Script>
    <![CDATA[
      import mx.controls.*;
      public function addContact():void {
        Alert.show( "Add Contact" );
      }
    ]]>
  </mx:Script>
  
  <mx:Form paddingLeft="0" paddingRight="0" paddingTop="0" paddingBottom="0">
    <mx:FormItem label="Name">
      <mx:TextInput id="txtName" />
    </mx:FormItem>
    <mx:FormItem label="Email">
      <mx:TextInput id="txtEmail" />
    </mx:FormItem>
    <mx:FormItem label="Phone">
      <mx:TextInput id="txtPhone" />
    </mx:FormItem>
  </mx:Form>
  <mx:Button label="Add Contact" click="addContact()"/>
</mx:Application>

It’s pretty simple. The main Application node wraps a form that has three elements and located below the form is a button. There are some padding attributes that scrunch up the display a little to give it a smaller footprint. I figured I would want something around 200 pixels wide and that’s what it came out around.

You can see the interface of the widget in Figure 5.



Figure 1-5. Our widget user interface

At this point when I click on the Add Contact button a prompt will pop up that says ‘Add Contact’.

To get all of this working I used Flex Builder 2 and created a project called Flex Widget. I then requested a new MXML Application and called it ‘widget’. And after adding in my form elements and buttons I ran it in the development environment to get what you see in Figure 5.

Logging In

The first step in adding the contents of the address into Drupal is to log in. To do that I add a RemoteObject object to the MXML with the address of the Drupal AMFPHP service, and give it a local id of ‘drupalUser’. I then use the login method on drupalUser to login as shown below:

  
  <mx:Script>
    <![CDATA[
      import mx.controls.*;
      import mx.rpc.events.*;
      import mx.utils.ArrayUtil;
      import mx.collections.ArrayCollection;
      
      public function addContact():void {
        drupalUser.login( 'jherr', 'password' );
      }
      
      public function userLogin(event:ResultEvent):void {
        Alert.show( "Logged in" );
      }
      
      public function onFault(event:FaultEvent):void{
        Alert.show(event.fault.faultString, "Error");
      }
    ]]>
  </mx:Script>
  
  <mx:RemoteObject endpoint="http://localhost/drupal/services/amfphp" showBusyCursor="true" destination="amfphp" source="user" id="drupalUser">
    <mx:method name="login" result="userLogin(event)" fault="onFault(event)"/>
  </mx:RemoteObject>

If I can log in the an alert message will popup when I click the address button that says 'Logged in'. Of course the user name and password in the code need to be changed to whatever user you want to use to add the address information to the Drupal node.

The next step is to get the current contents of the Drupal node so that we can add the address to it.

Getting Drupal Nodes

To do that I define another RemoteObject. This time it's for the 'node' service and I'll add the load method for that one. Then I'll update the userLogin method in the Flex code so that it calls the node.load method to get the body text for the node with the id of 1.

The updated Flex code that gets the node is shown below:

     
    
  
  <mx:RemoteObject endpoint="http://localhost/drupal/services/amfphp" showBusyCursor="true" destination="amfphp" source="user" id="drupalUser">
    <mx:method name="login" result="userLogin(event)" fault="onFault(event)"/>
  </mx:RemoteObject>
  
  <mx:RemoteObject endpoint="http://localhost/drupal/services/amfphp" showBusyCursor="true" destination="amfphp" source="node" id="node">
    <mx:method name="load"   result="nodeLoadResult(event)" fault="onFault(event)"/>
  </mx:RemoteObject>

Now when I click the 'Add Contact' button I will hopefully get the current contents of the #1 Drupal node in a popup. To test that I went back to Drupal and set the contents to 'Hello World' and indeed I saw '

Hello World

' pop up in the alert dialog.

The final step is to update the current contents of the node with the contact information and set it back on the Drupal server.

Setting Drupal Nodes

To update Drupal nodes I need to add the 'save' method to the RemoteObject for node. I then use this object in response to the nodeLoadResult method in the Flex code.

The final complete code for the widget is shown below:

  <mx:Script>
    <![CDATA[
      import mx.controls.*;
      import mx.rpc.events.*;
      import mx.utils.ArrayUtil;
      import mx.collections.ArrayCollection;
      
      public function addContact():void {
        drupalUser.login( 'jherr', 'password' );
      }
      
      public function userLogin(event:ResultEvent):void {
        node.load( 1, [ "body" ] );
      }
      
      public function nodeLoadResult(event:ResultEvent):void {
        var nodeDetails:Array = ArrayUtil.toArray(event.result);
        
        var newData:String = '';
        newData += "Name: "+txtName.text+"\n";
        newData += "Email: "+txtEmail.text+"\n";
        newData += "Phone: "+txtPhone.text+"\n";
        
        var edit:Object = new Object();
        edit.nid = 1;
        edit.body = nodeDetails[0].body + "\n" + newData;
        edit.changed = true;
        node.save( edit );
      }
      
      public function nodeSaveResult(event:ResultEvent):void {
        mx.controls.Alert.show("Saved");
      }
      
      public function onFault(event:FaultEvent):void{
        Alert.show(event.fault.faultString, "Error");
      }
    ]]>
  </mx:Script>
  
  <mx:RemoteObject endpoint="http://localhost/drupal/services/amfphp" showBusyCursor="true" destination="amfphp" source="user" id="drupalUser">
    <mx:method name="login" result="userLogin(event)" fault="onFault(event)"/>
  </mx:RemoteObject>
  
  <mx:RemoteObject endpoint="http://localhost/drupal/services/amfphp" showBusyCursor="true" destination="amfphp" source="node" id="node">
    <mx:method name="load"   result="nodeLoadResult(event)" fault="onFault(event)"/>
    <mx:method name="save"   result="nodeSaveResult(event)" fault="onFault(event)"/>
  </mx:RemoteObject>

The nodeLoadResult function now takes the original content for the node and adds the contact information retrieved from the text fields to it. It then uses the node.save method to update the contents of the node using the Drupal service.

Pretty easy, huh? Really the only trick to all of this is the asynchronous nature of the calls. Since everything is done with messages I need to do one thing in response to one message, then something else in response to another message, and that can make it a little tough to trace the logic. Thankfully this particular project isn't too complex, and even if it was Flex Builder comes with a really good debugger.

To test it out I for the last time enter the data from Figure 5 and press Add Contact. The result is shown in Figure 6.



Figure 1-6. After a completed save

The data has been saved to Drupal and I can check that myself by going back to the Drupal web site as you see in Figure 7.



Figure 1-7. The updated Drupal node

Yep, the node has been updated from the little widget that logged in, got the original node contents, and added the new contact details to it. Pretty slick.

Conclusions

I've worked with both Drupal and Flex for a while but I had never worked with the two in combination as I had in this little project. I was impressed with the ease with which Drupal could be extended to provide web services through a variety of formats. I was also impressed with how simple it was to use the AMF services through Flex. Even if you aren't using Drupal the AMFPHP module makes it easy for PHP applications to speak in a language that Flex can readily understand. Of course, Flex can also talk SOAP or straight XML, but it's hard to argue with RPC style object invocations.

I also like this idea of using Flex to create widgets instead of big complex application type deals. It's just a matter of scale but I think people put Flex in the 'big application' bucket unnecessarily. It's just as easy to use it to build small Flash widgets that are easy to write and deploy.

So there you have it, Drupal and Flex, a powerful combination indeed.

Resources

  • Flex is an open source Flash application development language. It was originally created by Adobe.
  • Drupal is the content management system used in this article.
  • The AMFPHP library allows PHP to talk in the ActionScript Message Format.
  • The Service module for Drupal provides a set of externally facing system services.
  • The AMFPHP module for Drupal adds an AMF version of the Service API.

Copyright 2007 Studio B Productions. All rights reserved.

About Cal Evans

Many moons ago, at the tender age of 14, Cal touched his first computer. (We're using the term "computer" loosely here, it was a TRS-80 Model 1) Since then his life has never been the same. He graduated from TRS-80s to Commodores and eventually to IBM PC's. For the past 10 years Cal has worked with PHP and MySQL on Linux OSX, and when necessary, Windows. He has built on a variety of projects ranging in size from simple web pages to multi-million dollar web applications. When not banging his head on his monitor, attempting a blood sacrifice to get a particular piece of code working, he enjoys building and managing development teams using his widely imitated but never patented management style of "management by wandering around". Cal is currently based in Nashville, TN and is gainfully unemployed as the Chief Marketing Officer of Blue Parabola, LLC. Cal is happily married to wife 1.28, the lovely and talented Kathy. Together they have 2 kids who were both bright enough not to pursue a career in IT. Cal blogs at http://blog.calevans.com and is the founder and host of Day Camp 4 Developers

View all posts by Cal Evans

10 Responses to “Reading And Writing Drupal With Flex”

  1. _____anonymous_____ Says:

    I guess one of the reasons people don’t use Flex for widgets is that the smallest Flex widgets are still at least 200K. Even for a page with one widget, that’s a big load time for some users. In most cases you could get a Flash widget less than a quarter of the size to do the same thing. I’d say that’s the main reason why people think Flex is a "big application" proposition.

    If Flex 3 takes off and the new framework caching works effectively, then it’s a different proposition. I certainly hope so as I love Flex!

  2. _____anonymous_____ Says:

    Thanks for sharing this, its a great way to extend off into the Flex arena..

  3. _____anonymous_____ Says:

    The reason the Flex file size is so big is the Flex library is included in the swf. I heard on a podcast that an update to Flash Player 9 will includea feature that allows the Flash player to cache Flash/Flex libraries created and signed by Adobe. This means, that soon enough, Flex apps will really be very small if created to employ this feature. The first time a user hits any flex app anywhere they’ll have the library cached so your apps will load much quicker.

  4. Herre84 Says:

    Hi,

    First of all: thanks for the tutorial. Altough, I tried out your example, ans I seem not to get it right.

    I get the following error message:

    The class {user} could not be found under the class path {C:\xampp\htdocs\drupal\modules\amfphp\amfphp\services\/user.php}

    AMFPHP_FILE_NOT_FOUND

    C:\xampp\htdocs\drupal\modules\amfphp\amfphp\core\shared\app\BasicActions.php on line 33

    Flex seems to look for a php file using the gateway, although services seem to be stored in .module files (which are found in a different directory than the AMFPHP services directory)… Am I missing an important clue here? Is there any way for the gateway to access the methods stored in the .module files?

    Hope you can help, because I don’t know where to look any further.

    Kind regards,

    Hans Van Herreweghe

  5. rannici Says:

    ….great tutorial….

    just a note for anyone using this, a year later or more, after it was written…

    the newest version of the services module includes by default two checked options on the settings page…

    should look something similar to this when you access the
    admin/build/services/settings page…

    ‘ Use keys
    When enabled, all method calls must include a valid key.
    Strict domain checking
    When enabled, all method calls must include their domain to validate against the key.
    Use sessid
    When enabled, all method calls must include a valid sessid. Only disable this setting if the application will user browser-based cookies.’

    if you uncheck the use keys, and use sessid options,
    the code on this page should work,
    and stop giving a ‘send failed’ error message.

  6. davethorn Says:

    Apologies for this question , I am just starting out with this.

    I got the example to work , great thanks this was really helpful. But I do not seem to be able to produce a logout facility.

    I have tried

    <mx:RemoteObject endpoint="http://localhost/TestSite/services/amfphp/amfphp/gateway.php&quot; showBusyCursor="true" destination="amfphp" source="user" id="drupalUser">
    <mx:method name="login" result="userLogin(event)" fault="onFault(event)"/>
    <mx:method name="logout" result="userLogout(event)" fault="onFault(event)"/>
    </mx:RemoteObject>

    a method ;

    public function removeContact():void {

    drupalUser.logout();
    }

    and button

    <mx:Button x="200" y="400" label="Remove Contact" click="removeContact()"/>

    Is this correct – I cannot get this to work so any pointers are very much appreciated.

  7. _____anonymous_____ Says:

    Hi,

    I have been using Drupal 6 for my localhost testing for a while, everything is working fine. Now I started to using Flex Builder 3 to create some flex project to connect with the drupal, but it does not work.

    There are many articles already in the web talking about how to get it connected, e.g. create the services-config.xml file, etc. But I still cannot get it work. Actually the amfphp and services modules are already working ok in my drupal, I just don’t know what went wrong. There is no error when I run the swf in drupal, but it always showed the message "transferring data from localhost…", but no more response.

    After reading your tutorial, I suspect that the problem may come from the fact that i need to login as a drupaluser? I tried to follow your tutorial, but I don’t quite understand why I don’t need the service-config.xml file in your tutorial?
    According to your tutorial, how many file do we need to create for the Flex project? only the Widget.mxml? Please advise,

  8. behka Says:

    Hello I don’t go in this tutorial yet but before deep in I want to know why not you use zend AMF for that instead of AMFPHP.

    Please let me know.

  9. behka Says:

    ( ! ) Warning: include_once(includes/install.inc) [function.include-once]: failed to open stream: No such file or directory in D:\xampp\htdocs\drupal\includes\database.inc on line 129
    Call Stack
    # Time Memory Function Location
    1 0.0005 65256 {main}( ) ..\index.php:0
    2 0.0081 327016 drupal_bootstrap( ) ..\index.php:16
    3 0.0111 373256 _drupal_bootstrap( ) ..\bootstrap.inc:983
    4 0.0144 475824 db_set_active( ) ..\bootstrap.inc:1016

    ( ! ) Warning: include_once() [function.include]: Failed opening ‘includes/install.inc’ for inclusion (include_path=’D:\xampp\htdocs\zend_framework\library’) in D:\xampp\htdocs\drupal\includes\database.inc on line 129
    Call Stack
    # Time Memory Function Location
    1 0.0005 65256 {main}( ) ..\index.php:0
    2 0.0081 327016 drupal_bootstrap( ) ..\index.php:16
    3 0.0111 373256 _drupal_bootstrap( ) ..\bootstrap.inc:983
    4 0.0144 475824 db_set_active( ) ..\bootstrap.inc:1016

    ( ! ) Fatal error: Call to undefined function install_goto() in D:\xampp\htdocs\drupal\includes\database.inc on line 130
    Call Stack
    # Time Memory Function Location
    1 0.0005 65256 {main}( ) ..\index.php:0
    2 0.0081 327016 drupal_bootstrap( ) ..\index.php:16
    3 0.0111 373256 _drupal_bootstrap( ) ..\bootstrap.inc:983
    4 0.0144 475824 db_set_active( ) ..\bootstrap.inc:1016

  10. DirtyLogic Says:

    ZendAMF wasn’t around when this article was published. Also, I think most people are still using amfphp, and a lot of people have said that it’s faster. I wouldn’t know, because I have yet to try ZendAMF, and probably won’t be interested until I find a decent drupal module. It’d also be nice if the Wade would add a service browser to ZendAMF considering he dropped amfphp entirely. Personally, I’d like to see either someone else pickup amfphp or for Wade to add some of the features we loved about amfphp to ZendAMF.