7 Replies Latest reply on Dec 2, 2009 9:28 AM by yxxm

    Bidirectional Binding Question

    yxxm

      Hey All,

       

      I am creating a form-type application that allows for creating and editing of "News Articles", using PHP Data Services with the Flex 4 SDK.

       

      The News object is very simple (id, title, content, date). I have created a templated service with Flash Builder, and, as such, I have the (basic, default) operations getAllNews() and getNewsByID(id).

       

      In my application, I have the following (certain pieces omitted due to irrelevance):

       

      <fx:Declarations>
           <valueObjects:News fx:id="news"/>
           <s:CallResponder id="getAllNewsResult"/>
           <s:CallResponder id="getNewsByID"/>
           <newsservice:NewsService id="newsService" fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)" showBusyCursor="true"/>
      </fx:Declarations>
      
      <fx:Script>
           <![CDATA[
                import mx.controls.Alert;
                import mx.events.FlexEvent;
                import mx.events.ListEvent;
                
                protected function adgNews_creationCompleteHandler(event:FlexEvent):void
                {
                     getAllNewsResult.token = newsService.getAllNews();
                }
      
      
                protected function adgNews_itemClickHandler(event:ListEvent):void
                {
                     getNewsByID.token = newsService.getNewsByID( event.itemRenderer.data.id );
                }
      
                protected function frmEditNews_submitHandler(event:SubmitEvent):void
                {
                     newsService.updateNews( ( frmEditNews.data as News ) );
                }
           ]]>
      </fx:Script>
      
      <mx:AdvancedDataGrid itemClick="adgNews_itemClickHandler(event)" id="adgNews" designViewDataType="flat" selectionMode="singleRow" editable="false" bottom="10" horizontalCenter="0" top="10" height="100%" creationComplete="adgNews_creationCompleteHandler(event)" dataProvider="{getAllNewsResult.lastResult}">
           <mx:columns>
                <mx:AdvancedDataGridColumn headerText="Date" dataField="date" width="100" formatter="{fmtDateFormatter}"/>
                <mx:AdvancedDataGridColumn headerText="Title" dataField="title" width="300"/>
           </mx:columns>
      </mx:AdvancedDataGrid>
      <ns1:NewsForm id="frmEditNews" title="Edit News" horizontalCenter="0" verticalCenter="0" data="{getNewsByID.lastResult}" submit="frmEditNews_submitHandler(event)" />
      

       

      The source for NewsForm.mxml is shown below:

       

      <?xml version="1.0" encoding="utf-8"?>
      <s:Group xmlns:fx="http://ns.adobe.com/mxml/2009" 
                 xmlns:s="library://ns.adobe.com/flex/spark" 
                 xmlns:mx="library://ns.adobe.com/flex/halo">
           <fx:Declarations>
                <!-- Place non-visual elements (e.g., services, value objects) here -->
           </fx:Declarations>
           <fx:Script>
                <![CDATA[
                     protected var _title:String;
                     protected var _data:Object;
                     
                     [Bindable]
                     public function set data(data:Object):void {
                          _data = data;
                     }
                     
                     public function get data():Object {
                          trace(_data);
                          return _data;
                     }
                     
                     [Bindable]
                     public function set title(title:String):void {
                          _title = title;
                     }
                     
                     public function get title():String {
                          return _title;
                     }
                ]]>
           </fx:Script>
           <fx:Metadata>
                [Event(name="submit", type="SubmitEvent")]
           </fx:Metadata>
           <mx:Form verticalCenter="-1" horizontalCenter="5">
                <mx:FormHeading label="{title}"/>
                <mx:FormItem label="Title">
                     <s:TextInput id="txtTitle" width="250" text="@{data.title}"/>
                </mx:FormItem>
                <mx:FormItem label="Content" width="500" height="300">
                     <s:TextArea id="txtContent" width="100%" text="@{data.content}" height="100%"/>
                </mx:FormItem>
                <mx:FormItem>
                     <s:Button click="dispatchEvent( new SubmitEvent( SubmitEvent.SUBMIT ) )" label="Submit"/>
                </mx:FormItem>
           </mx:Form>
      </s:Group>
      
      

       

      The behavior that I want (which is there) is the DataGrid on the left that is populated with the list of News articles, and the NewsForm to the right of it. When you click on the News article in the DataGrid, it populates the NewsForm properly. As it stands now, this happens as it should. However...

       

      What I don't want is the data to change on-the-fly in the DataGrid as I am changing it in the NewsForm. With Bidirectional binding, this is occuring. It gives the effect of the data being updated in real-time, and that is most definitely not the case (nor do I want it to be). The thing that I find interesting is, I'm wondering if the object that the Data Service gets is a "shared" object. As you can see in my application code above, I am calling the getAllNews() function to populate the DataGrid, and then I am calling the getNewsByID(id) function to get a specific News instance. Yet, due to the binding, it appears I am editing the same reference to a News object. I would've assumed that it returns a new instance of the News object each time I call any of the service methods, but I guess that was a poor assumption. Hah

       

      In any event, the only "workaround" I've thought of thus far is to do the following:

       

      1. Modify the frmEditNews_submitHandler to the following:

       

      protected function frmEditNews_submitHandler(event:SubmitEvent):void
      {
           frmEditNews.data.title = frmEditNews.txtTitle.text;
           frmEditNews.data.content = frmEditNews.txtTitle.text;
           newsService.updateNews( ( frmEditNews.data as News ) );
      }
      

       

      and

       

      2. Remove the bidirectional data binding on the NewsForm:

       

      <mx:FormItem label="Title">
           <s:TextInput id="txtTitle" width="250" text="{data.title}"/>
      </mx:FormItem>
      <mx:FormItem label="Content" width="500" height="300">
           <s:TextArea id="txtContent" width="100%" text="{data.content}" height="100%"/>
      </mx:FormItem>
      

       

      Does anyone have a better solution, and/or can anyone give more insight on this situation?

       

      Thanks in advance!

        • 1. Re: Bidirectional Binding Question
          mewk Level 3

          Hi yxxm,

           

          Will look into this in more detail, but in the meantime try the following and let me know if they work.

           

          Disable Data Management:

          1. from data/services panel goto "data types" --> "news"
          2. from the context menu select data management.
          3. change all the CRUD operations to none.

           

          or

           

          Confuse Data Management with the following changes

            <fx:Declarations>
              <valueObjects:News fx:id="news"/>
              <s:CallResponder id="getAllNewsResult"/>
              <s:CallResponder id="getNewsByID" result="getNewsByID_resultHandler(event)"/>
              <newsservice:NewsService id="newsService" fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)" showBusyCursor="true"/>
            </fx:Declarations>
          
            <fx:Script>
              <![CDATA[
                import mx.controls.Alert;
                import mx.events.FlexEvent;
                import mx.events.ListEvent;
                
                protected function getNewsByID_resultHandler(event:FlexEvent):void {
                  var newsItem:News = getNewsByID.lastResult as News;
                  frmEditNews.data = newsItem;
                }
                
                protected function adgNews_creationCompleteHandler(event:FlexEvent):void {
                  getAllNewsResult.token = newsService.getAllNews();
                }
                
                protected function adgNews_itemClickHandler(event:ListEvent):void {
                  getNewsByID.token = newsService.getNewsByID( event.itemRenderer.data.id );
                }
                
                protected function frmEditNews_submitHandler(event:SubmitEvent):void {
                  newsService.updateNews( ( frmEditNews.data as News ) );
                }
              ]]>
            </fx:Script>
            
            <mx:AdvancedDataGrid id="adgNews" itemClick="adgNews_itemClickHandler(event)"
                       designViewDataType="flat" selectionMode="singleRow" editable="false"
                       bottom="10" horizontalCenter="0" top="10" height="100%"
                       creationComplete="adgNews_creationCompleteHandler(event)"
                       dataProvider="{getAllNewsResult.lastResult}">
              <mx:columns>
                <mx:AdvancedDataGridColumn headerText="Date" dataField="date" width="100" formatter="{fmtDateFormatter}"/>
                <mx:AdvancedDataGridColumn headerText="Title" dataField="title" width="300"/>
              </mx:columns>
            </mx:AdvancedDataGrid>
            <ns1:NewsForm id="frmEditNews" title="Edit News"
                    horizontalCenter="0" verticalCenter="0"
                    submit="frmEditNews_submitHandler(event)" />
          

           

          Let me know what you find out. Thanks,

           

          - e

          • 2. Re: Bidirectional Binding Question
            yxxm Level 1

            Hi mewk,

             

            Thanks for the reply!

             

            I tried the first set of directions you gave me (disabling data management), and Flex got angry with me and threw an exception :

             

            warning: unable to bind to property 'title' on class 'mx.collections::ArrayCollection'
            Error: Unknown Property: 'title'.
                 at mx.collections::ListCollectionView/http://www.adobe.com/2006/actionscript/flash/proxy::getProperty()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\collections\ListCollectionView.as:853]
                 at mx.binding::PropertyWatcher/updateProperty()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\PropertyWatcher.as:338]
                 at Function/http://adobe.com/AS3/2006/builtin::apply()
                 at mx.binding::Watcher/wrapUpdate()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\Watcher.as:192]
                 at mx.binding::PropertyWatcher/updateParent()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\PropertyWatcher.as:239]
                 at mx.binding::Watcher/updateChildren()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\Watcher.as:138]
                 at mx.binding::PropertyWatcher/updateProperty()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\PropertyWatcher.as:347]
                 at Function/http://adobe.com/AS3/2006/builtin::apply()
                 at mx.binding::Watcher/wrapUpdate()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\Watcher.as:192]
                 at mx.binding::PropertyWatcher/eventHandler()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\PropertyWatcher.as:375]
                 at flash.events::EventDispatcher/dispatchEventFunction()
                 at flash.events::EventDispatcher/dispatchEvent()
                 at mx.core::UIComponent/dispatchEvent()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\core\UIComponent.as:11749]
                 at NewsForm/set data()
                 at mx.binding::Binding/defaultDestFunc()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\Binding.as:262]
                 at Function/http://adobe.com/AS3/2006/builtin::call()
                 at mx.binding::Binding/innerExecute()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\Binding.as:458]
                 at Function/http://adobe.com/AS3/2006/builtin::apply()
                 at mx.binding::Binding/wrapFunctionCall()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\Binding.as:370]
                 at mx.binding::Binding/execute()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\Binding.as:306]
                 at mx.binding::Binding/watcherFired()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\Binding.as:484]
                 at mx.binding::Watcher/notifyListeners()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\Watcher.as:311]
                 at mx.binding::PropertyWatcher/eventHandler()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\binding\PropertyWatcher.as:377]
                 at flash.events::EventDispatcher/dispatchEventFunction()
                 at flash.events::EventDispatcher/dispatchEvent()
                 at mx.rpc::CallResponder/set lastResult()
                 at mx.rpc::CallResponder/result()[E:\dev\gumbo_beta2\frameworks\projects\rpc\src\mx\rpc\CallResponder.as:120]
                 at mx.rpc::AsyncToken/http://www.adobe.com/2006/flex/mx/internal::applyResult()[E:\dev\gumbo_beta2\frameworks\projects\rpc\src\mx\rpc\AsyncToken.as:239]
                 at mx.rpc.events::ResultEvent/http://www.adobe.com/2006/flex/mx/internal::callTokenResponders()[E:\dev\gumbo_beta2\frameworks\projects\rpc\src\mx\rpc\events\ResultEvent.as:207]
                 at mx.data::ConcreteDataService/http://www.adobe.com/2006/flex/mx/internal::dispatchResultEvent()[C:\depot\DataServices\branches\dune_rc\frameworks\projects\data\src\mx\data\ConcreteDataService.as:3157]
                 at mx.data::DataListRequestResponder/result()[C:\depot\DataServices\branches\dune_rc\frameworks\projects\data\src\mx\data\DataListRequestResponder.as:106]
                 at mx.data::RPCDataServiceAdapter/sendResultEvent()[C:\depot\DataServices\branches\dune_rc\frameworks\projects\data\src\mx\data\RPCDataServiceAdapter.as:1508]
                 at mx.data::RPCDataServiceAdapter/executeQueryResult()[C:\depot\DataServices\branches\dune_rc\frameworks\projects\data\src\mx\data\RPCDataServiceAdapter.as:1444]
                 at mx.collections::ItemResponder/result()[E:\dev\gumbo_beta2\frameworks\projects\framework\src\mx\collections\ItemResponder.as:129]
                 at mx.rpc::AsyncToken/http://www.adobe.com/2006/flex/mx/internal::applyResult()[E:\dev\gumbo_beta2\frameworks\projects\rpc\src\mx\rpc\AsyncToken.as:239]
                 at mx.rpc.events::ResultEvent/http://www.adobe.com/2006/flex/mx/internal::callTokenResponders()[E:\dev\gumbo_beta2\frameworks\projects\rpc\src\mx\rpc\events\ResultEvent.as:207]
                 at mx.rpc::AbstractOperation/http://www.adobe.com/2006/flex/mx/internal::dispatchRpcEvent()[E:\dev\gumbo_beta2\frameworks\projects\rpc\src\mx\rpc\AbstractOperation.as:244]
                 at mx.rpc::AbstractInvoker/http://www.adobe.com/2006/flex/mx/internal::resultHandler()[E:\dev\gumbo_beta2\frameworks\projects\rpc\src\mx\rpc\AbstractInvoker.as:318]
                 at mx.rpc::Responder/result()[E:\dev\gumbo_beta2\frameworks\projects\rpc\src\mx\rpc\Responder.as:56]
                 at mx.rpc::AsyncRequest/acknowledge()[E:\dev\gumbo_beta2\frameworks\projects\rpc\src\mx\rpc\AsyncRequest.as:84]
                 at NetConnectionMessageResponder/resultHandler()[E:\dev\gumbo_beta2\frameworks\projects\rpc\src\mx\messaging\channels\NetConnectionChannel.as:547]
                 at mx.messaging::MessageResponder/result()[E:\dev\gumbo_beta2\frameworks\projects\rpc\src\mx\messaging\MessageResponder.as:235]
            

             

            The second test yielded the same results as though my code had not been modified in the first place (e.g. bidirectional binding still occurs, changes happen in the DataGrid as I am changing the TextInput for the fields)

             

            Let me know if you come across anything else that can be of assistance!

            • 3. Re: Bidirectional Binding Question
              mewk Level 3

              hey yxxm,

               

              Ok, scratch my idea on messing with Data Management.

               

              I've reproduced your test case and see what your dealing with exactly (quite strange really!)

               

              So, looking at the code it would appear that there are essentially two different data objects: getAllNewsResult.lastResult (probably shouldn't have "result" in the name) and getNewsByID.lastResult, both these objects are CallResponders for the service (newsservice); the getAllNewsResults data is bound to the datagrid and the getNewsByID is bound to your form component.

               

              One would assume that these two objects are distinct and have no shared resources, but I don't think that's true. Instead, it looks like they pull their data from the same source. This would make some sense and certainly make for a more efficient app b/c once you have pulled all the news sources from your db, why make another call to the db for a single news item, when you can work with the data just pulled?

               

              I don't have time to finish this all the way through (gotta go eat turkey!!), but maybe this will help reshape your approach. Let me know what you come up with,

               

              - e

              • 4. Re: Bidirectional Binding Question
                yxxm Level 1

                One would assume that these two objects are distinct and have no shared resources, but I don't think that's true. Instead, it looks like they pull their data from the same source.

                That's what I asserted in my original post! Glad to see that you found/think the same.

                 

                This would make some sense and certainly make for a more efficient app b/c once you have pulled all the news sources from your db, why make another call to the db for a single news item, when you can work with the data just pulled?

                I completely agree.

                 

                Unfortunately, thus far, I've stuck with the solution that I proposed in my first post (removing the bidirectional binding). While it is less efficient and has to make more calls, it gives me the effect that I want.

                 

                I had an idea yesterday to clone the data, using ObjectUtil.clone(), but I think I ran into some problems with that. I forgot what they are now.

                 

                Thanks for you help thus far!

                • 5. Re: Bidirectional Binding Question
                  mewk Level 3

                  Ok ok, didn't mean to belittle/misunderstand your input!! -- just catching up to speed myself

                   

                  So I think you have the right of it. The current design has essentially one data source of all the news objects, and, in order to display a particular news item in multiple locations, which doesn't get simultaneously updated everywhere (keep the binding tags of course), we will need to create some new objects, and, I think ObjectUtil.clone() / ObjectUtil.copy() are reasonable ways of achieving this.

                   

                  Here is some code which worked for me:

                   

                  <?xml version="1.0" encoding="utf-8"?>
                  <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
                           xmlns:s="library://ns.adobe.com/flex/spark"
                           xmlns:mx="library://ns.adobe.com/flex/halo"
                           xmlns:ns1="components.*"
                           xmlns:valueObjects="valueObjects.*"
                           xmlns:peopleservice="services.peopleservice.*">
                    <fx:Declarations>
                      <valueObjects:People fx:id="news"/>
                      <s:CallResponder id="getAllPeopleResponder"/>
                      <s:CallResponder id="getPeopleByIdResponder" result="getPeopleByID_resultHandler(event)"/>
                      <peopleservice:PeopleService id="pplService" showBusyCursor="true"/>
                    </fx:Declarations>
                  
                    <fx:Script>
                      <![CDATA[
                        import events.SubmitEvent;
                        import mx.events.FlexEvent;
                        import mx.events.ListEvent;
                        import mx.rpc.events.ResultEvent;
                        import mx.utils.ObjectUtil;
                  
                        protected function adgNews_creationCompleteHandler(event:FlexEvent):void {
                          getAllPeopleResponder.token = pplService.getAllPeople();
                        }
                  
                  
                        protected function adgNews_itemClickHandler(event:ListEvent):void {
                          getPeopleByIdResponder.token = pplService.getPeopleByID( event.itemRenderer.data.people_id );
                        }
                  
                        protected function getPeopleByID_resultHandler(event:ResultEvent):void {
                          registerClassAlias("valueObjects.People", People);
                          var storedObj:People = event.result as People;
                          var newObjCopy:People = ObjectUtil.clone(storedObj) as People;
                  
                          frmEditNews.myData = newObjCopy;
                        }
                  
                      ]]>
                    </fx:Script>
                  
                    <s:HGroup>
                      <mx:AdvancedDataGrid id="adgNews"
                                 itemClick="adgNews_itemClickHandler(event)"
                                 designViewDataType="flat" selectionMode="singleRow" editable="false"
                                 bottom="10" horizontalCenter="0" top="10" height="100%"
                                 creationComplete="adgNews_creationCompleteHandler(event)"
                                 dataProvider="{getAllPeopleResponder.lastResult}">
                        <mx:columns>
                          <mx:AdvancedDataGridColumn headerText="ID" dataField="id" width="100" />
                          <mx:AdvancedDataGridColumn headerText="Name" dataField="name" width="300" />
                        </mx:columns>
                      </mx:AdvancedDataGrid>
                      <ns1:NewsForm id="frmEditNews" title="Edit News" verticalCenter="0" />
                    </s:HGroup>
                  
                  </s:Application>
                  
                   
                  

                   

                  (obviously I used people instead of news, but it's mostly your original code).

                   

                  Also, I came across this blog, which proved useful. Hope this helps and best o luck,

                   

                  - e

                  • 6. Re: Bidirectional Binding Question
                    yxxm Level 1

                    Sorry, I didn't mean to come across as rude when replying to your reply

                     

                    I trust that the solution you found works, but I won't be able to test it out myself for a couple of days. When I do, I'll come back and make a final post and mark this as answered. Thanks for your help, it was much appreciated!

                    • 7. Re: Bidirectional Binding Question
                      yxxm Level 1

                      Hey mewk,

                       

                      I had a chance to try out what you posted, and it works as expected. My problem the first time when trying to use ObjectUtil copy was due to the fact that I wasn't using the registerClassAlias function. I still have some qualms about using ObjectUtil.copy because of the author's last sentence in that blog post:

                      But… ObjectUtil.copy(obj) it’s not bullet proof. I will not address to this situation because Darron schall has already a great post on this issue.

                       

                      I'll make an executive decision at some point at which approach I want to stick with

                       

                      Thanks for all the help!