Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    How to Listen to DataSource changes (client side only)

    I have a client side only DataSource that I have several forms and widgets binding to. I want to be able to have the interested UI components listen for changes to the DataSource so they can update themselves when needed.

    For example, I have a form that I call myForm.saveData() on. I am expecting the DataSource.updateData method to fire and so have wired an observer to listen for this. It never does.

    The data does actually get put into the DataSource, at least these changes are reflected in any widget/form that is created after the data is saved. The changes are not reflected in any control that already exists. The Dev Console looks like this:

    Code:
    Global Log Priorities updated: Logging messages at priority 'Debug' and above for category 'RPCManager'.
    10:44:26.327:MUP7:DEBUG:RPCManager:Grabbed prompt from first request that defined one: Saving form...
    10:44:30.347:TMR4:DEBUG:RPCManager:Result string for transaction 33: undef
    10:44:34.346:TMR4:INFO:RPCManager:rpcResponse(unstructured) results -->{status: 0}<--
    10:44:42.396:MUP6:DEBUG:RPCManager:Grabbed prompt from first request that defined one: Saving form...
    10:44:46.412:MUP6:DEBUG:RPCManager:Grabbed prompt from first request that defined one: Saving form...
    10:44:50.421:TMR2:DEBUG:RPCManager:Result string for transaction 34: undef
    10:44:54.439:TMR2:INFO:RPCManager:rpcResponse(unstructured) results -->{status: 0}<--
    10:44:58.396:TMR3:DEBUG:RPCManager:Result string for transaction 35: undef
    10:45:02.366:TMR3:INFO:RPCManager:rpcResponse(unstructured) results -->{status: 0}<--
    Note: Charles, while on site here, advised that the code be done like this when I only had two forms at the time...
    Code:
    editableForm.saveData();
    staticForm.setValues( editableForm.getValues() );
    When it was only these two forms, in the same js file, that was fine. Now that I have multiple components wanting notification of changed data, which can change from multiple places, it doesn't work.

    Please advise. Thanks,
    TT

    #2
    Updating should be automatic IF you have a primaryKey declared.

    If you already do, what specific component is not updating? If it's a grid, you can enable the ResultSet log category in the Develper Console to see what's happening.

    Comment


      #3
      I've got two static forms and another editable form and none of them update if they already exist. One set of forms can be created after I do some edits, and they will reflect the data changes at the time they were created, but will not update after that. (Well, if I call the setValues() method mentioned before I can force them to update.) Below is the console with the ResultSet log set to Debug:


      Code:
      12:53:21.234:TMR9:DEBUG:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):dataSource data changed firing
      12:53:25.254:TMR9:INFO:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):updating cache in place after operationType: update, allMatchingRowsCached true
      12:53:29.301:TMR9:INFO:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):Updating cache: operationType 'update' submitted by 'reviewcustomerInfocustomerEditForm',1 rows update data:
      [
      {account: "Residential customer",
      first: "DDD",
      middle: "",
      last: "Patel",
      ssn: "999-99-9999",
      id2: "",
      street: "123 Brick Lane Way",
      city: "Rome",
      state: "IT",
      postalcode: "",
      zip: "86868",
      phone1: "(999) 999-9999",
      phone2: "(888) 888-8888",
      useAltAddr: false,
      careof: "",
      street2: "",
      city2: "",
      state2: "",
      zip2: "",
      langPref: "",
      ebill: "No E-Bill",
      salesRep: "",
      salesReason: "",
      title: "",
      email: "vpatel@xxxx.com",
      accountType: "Residential customer",
      shoppingCart: "
          ",
      _selection_9: true}
      ]
      12:53:33.378:TMR9:DEBUG:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):updated cache: 0 row(s) added, 1 row(s) updated, 0 row(s) removed.
      12:53:37.541:TMR9:DEBUG:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):full length set to: 3
      12:53:41.740:TMR9:DEBUG:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):getRange(0, 1) satisfied from cache
      12:53:52.577:MUP8:DEBUG:RPCManager:Grabbed prompt from first request that defined one: Saving form...
      12:53:56.686:MUP8:DEBUG:RPCManager:Grabbed prompt from first request that defined one: Saving form...
      12:54:00.794:TMR7:DEBUG:RPCManager:Result string for transaction 40: undef
      12:54:04.931:TMR7:INFO:RPCManager:rpcResponse(unstructured) results -->{status: 0}<--
      12:54:09.121:TMR7:DEBUG:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):dataSource data changed firing
      12:54:13.216:TMR7:INFO:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):updating cache in place after operationType: update, allMatchingRowsCached true
      12:54:17.319:TMR7:INFO:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):Updating cache: operationType 'update' submitted by 'reviewcustomerInfocustomerEditForm',1 rows update data:
      [
      {account: "Residential customer",
      first: "TTT",
      middle: "",
      last: "Patel",
      ssn: "999-99-9999",
      id2: "",
      street: "123 Brick Lane Way",
      city: "Rome",
      state: "IT",
      postalcode: "",
      zip: "86868",
      phone1: "(999) 999-9999",
      phone2: "(888) 888-8888",
      useAltAddr: false,
      careof: "",
      street2: "",
      city2: "",
      state2: "",
      zip2: "",
      langPref: "",
      ebill: "No E-Bill",
      salesRep: "",
      salesReason: "",
      title: "",
      email: "vpatel@xxxx.com",
      accountType: "Residential customer",
      shoppingCart: "
          ",
      _selection_9: true}
      ]
      12:54:21.464:TMR7:DEBUG:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):updated cache: 0 row(s) added, 1 row(s) updated, 0 row(s) removed.
      12:54:25.552:TMR7:DEBUG:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):full length set to: 3
      12:54:29.770:TMR7:DEBUG:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):getRange(0, 1) satisfied from cache
      12:54:33.905:TMR8:DEBUG:RPCManager:Result string for transaction 41: undef
      12:54:38.012:TMR8:INFO:RPCManager:rpcResponse(unstructured) results -->{status: 0}<--
      12:54:42.170:TMR8:DEBUG:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):dataSource data changed firing
      12:54:46.338:TMR8:INFO:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):updating cache in place after operationType: update, allMatchingRowsCached true
      12:54:50.467:TMR8:INFO:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):Updating cache: operationType 'update' submitted by 'reviewcustomerInfocustomerEditForm',1 rows update data:
      [
      {account: "Residential customer",
      first: "TTT",
      middle: "",
      last: "Patel",
      ssn: "999-99-9999",
      id2: "",
      street: "123 Brick Lane Way",
      city: "Rome",
      state: "IT",
      postalcode: "",
      zip: "86868",
      phone1: "(999) 999-9999",
      phone2: "(888) 888-8888",
      useAltAddr: false,
      careof: "",
      street2: "",
      city2: "",
      state2: "",
      zip2: "",
      langPref: "",
      ebill: "No E-Bill",
      salesRep: "",
      salesReason: "",
      title: "",
      email: "vpatel@xxxx.com",
      accountType: "Residential customer",
      shoppingCart: "
          ",
      _selection_9: true}
      ]
      12:54:54.733:TMR8:DEBUG:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):updated cache: 0 row(s) added, 1 row(s) updated, 0 row(s) removed.
      12:54:58.994:TMR8:DEBUG:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):full length set to: 3
      12:55:03.348:TMR8:DEBUG:ResultSet:isc_ResultSet_62 (created by: isc_PickListMenu_61):getRange(0, 1) satisfied from cache

      Comment


        #4
        In the initWidget method of a VLayout derived component that contains a pair of editable and static forms I'm working with, I've tried to add an observer as shown below. The method never fires.

        Code:
        this.customerDataChanged = function() {
            isc.say( "Data Changed" );
        };
        
        this.observe(customerData, "updateData", "observer.customerDataChanged()");
        this.observe(customerData, "addData", "observer.customerDataChanged()");
        --- where "customerData" is the ID property of my DataSource

        Comment


          #5
          Few more findings, but still not working

          - Cannot make an observer function in initWidget using 'this.myObserver = function' ... This seems to make a single observer in the Class (i.e. a static the same as using addClassProperties). To make a unique observer for each instance, add the method as part of the addProperties method.

          - By manually calling customerData.updateData( editableForm.getValues() ); I can get the observers to fire.

          - While the manual call causes the observer to fire, the bound UI components still do not update w/o a call to myStaticForm.setValues().

          - There are three 'update' calls being made and listed in the console info. I pasted in above. Two were because my call to myForm.saveData() was happening twice. The other is generated automagically via the 'submit' button on the form. If I comment out my manual call to myForm.saveData(), the update only happens once.


          What do I need to look for next?
          Last edited by ttubbs; 24 Jul 2009, 11:56.

          Comment


            #6
            Hi ttubbs,

            The best approach to listen for DataSource updates is to override or observe DataSource.transformResponse(). You can look at the DSResponse parameter and see if it's an update with a matching primaryKey.

            Note that you wouldn't want to observe updateData() regardless as a call to this API only indicates that an update was attempted - it could still fail with a server-side only validation failure, and the values-as-saved could differ from the submitted values.

            Comment


              #7
              The old commented out line works, the new one does not:
              Code:
              // this.observe(customerData, "updateData", "observer.customerDataObserver()");
              this.observe(customerData, "transformResponse", "observer.customerDataObserver()");

              JavaScript error reported is:
              Code:
              this.$ba is undefined
              isc.$aq=function(_1,_2){var _3=_2||_1;re...isc.$at[isc.$at.length]=_1;return null}\n

              Also, I understand that there is no transformResponse in the base classes, but provides a plug-in point if I wish to make use of it. I've added my 'override' which only fires on the creation of the DataSource. It does not fire on clicking the 'submit' button on my form, nor when I manually make a call to mydatasource.updateData().

              It shouldn't be this flippin hard to change the data and have the UI update. I've had enough for today.

              TT

              Comment


                #8
                It isn't hard, it's very nearly a one-liner when done correctly.

                Picking an example at random, here's a correct observation for the databound calendar example.

                Code:
                eventCalendar.observe(eventDS, "transformResponse", "isc.logWarn(isc.echo(arguments[0]))");
                This echos the DSResponse (argument 0) to the log.

                Can't say what's wrong with your observation - not enough surrounding code. Possibly, you destroyed the component right after setting up the observation, possibly by using colliding global IDs. There are probably warnings in the Developer Console telling you what's wrong (the Developer Console should always be open so you can see these warnings).

                Comment


                  #9
                  OK, Here it is stripped down...

                  OK, I've stripped it all down to a simple app with two tabs each with identical static and editable forms - all bound to the same DataSource.

                  To Recreate:
                  1. Run app, and click on a field in static form.
                  2. Form should switch to editable, edit something (first name), and click "Done"
                  3. Note the name changed in the static form.
                  4. Click "Tab 2": note the name showing here is the new value
                  5. Edit name in Tab 2, click "Done", not tab 2 value changed.
                  6. Now edit between to the two tabs. They don't know about each other.

                  Extra Credit:
                  A. Note no warnings in the console.
                  B. There is this bit of oddness in the console. If this is suppose to tell me something it requires translation by somebody in the know.
                  Code:
                  09:49:34.031:TMR3:DEBUG:RPCManager:Result string for transaction 1: undef
                  09:49:34.040:TMR3:INFO:RPCManager:rpcResponse(unstructured) results -->{status: 0}<--
                  09:49:36.393:TMR5:DEBUG:RPCManager:Result string for transaction 2: undef
                  09:49:36.403:TMR5:INFO:RPCManager:rpcResponse(unstructured) results -->{status: 0}<--
                  09:49:38.649:MUP4:DEBUG:RPCManager:Grabbed prompt from first request that defined one: Saving form...
                  09:49:38.688:MUP4:DEBUG:RPCManager:Grabbed prompt from first request that defined one: Saving form...
                  09:49:38.755:TMR0:DEBUG:RPCManager:Result string for transaction 3: undef
                  09:49:38.765:TMR0:INFO:RPCManager:rpcResponse(unstructured) results -->{status: 0}<--
                  09:49:38.778:TMR3:DEBUG:RPCManager:Result string for transaction 4: undef
                  09:49:38.788:TMR3:INFO:RPCManager:rpcResponse(unstructured) results -->{status: 0}<--
                  09:49:51.724:TMR7:DEBUG:RPCManager:Result string for transaction 5: undef
                  09:49:51.734:TMR7:INFO:RPCManager:rpcResponse(unstructured) results -->{status: 0}<--
                  09:49:52.699:TMR3:DEBUG:RPCManager:Result string for transaction 6: undef
                  09:49:52.685:TMR3:INFO:RPCManager:rpcResponse(unstructured) results -->{status: 0}<--
                  09:49:56.138:MUP6:DEBUG:RPCManager:Grabbed prompt from first request that defined one: Saving form...
                  09:49:56.177:MUP6:DEBUG:RPCManager:Grabbed prompt from first request that defined one: Saving form...
                  C. If you un-comment the line to observe transformResponse, you'll see the javascript error about this.$ab not being defined (FireBug). You'll also see a single warning in the console with the same message. The app will not complete loading, it dies at a message saying "Contactin Server".
                  Most, if not all, code to note in is registrationForm.js.



                  Expected: When data changes, all forms bound to them will update accordingly.
                  Attached Files

                  Comment


                    #10
                    The correct approach is to observe transformResponse() - you want to remove all your other attempts, which don't make sense, because as previously indicated you want to know when a request *succeeds* not when one is issued.

                    However this doesn't apply to a clientOnly DataSource like you've shown, because a clientOnly DataSource has no "requests" and "responses". Switching to a real DataSource would fix the problem. If you want a temporary workaround to continue with client-only DataSources, you could observe() the internal method DataSource.dataChanged(dsResponse, dsRequest).

                    Comment


                      #11
                      My thread title and original post said I was interested in client only data. In your initial response (post #2), you say controls should automatically update if bound to the same datasource.

                      Am I reading your last response correctly that client only DataSources and "real" DataSources are not the same thing? So, I cannot expect to get the same behavior out of them?

                      You use the word 'temporary' to describe what I've tried to do here. I am not planning on this being a temporary solution. In my real app I am gathering input from the user in something of a wizard process and only need to talk to the server once I've gathered all the data. As such, I plan to keep a few client only DataSources to maintain the client state until it's time for the update.

                      It is not clear to me what alternatives there are for collecting client side data w/o using SmartClient controls and DataSources. Seems to me this would be a common requirement. What am I missing here?

                      In the meantime, I'll play with observing the dataChanged method and see where that will take me.

                      Thanks,
                      TT

                      Comment


                        #12
                        Your post was titled "How to Listen to DataSource changes (client side only)" which sounds like you'd like to get a client-side notification as opposed to a server-side notification of data changes. There was no particular reason to believe this was related to clientOnly DataSources.

                        It does potentially make sense to use a clientOnly DataSource as an approach for something like a wizard. Bear in mind that you can also do things like use a ListGrid with a simple Array - which approach takes less code comes down to details of what you need to do. We only described this as "temporary" because the more common use case for a clientOnly DataSource is as a temporary placeholder that will be replaced by a DataSource that actually saves to the server. Continuing with observing "dataChanged" makes sense in your use case.

                        What's automatic is that if a form saves, it will reflect the data-as-saved after the server returns a success response. Currently, only the form that initiates the save will be updated - other forms do not automatically react to changes, although ListGrids, TreeGrids, DetailViewers and other components do. You're hitting an edge case because you're using a form as a read-only data display - it's not normal to have the same data showing in two editable forms so there's no auto-update for this case currently.

                        Comment


                          #13
                          Originally posted by Isomorphic
                          There was no particular reason to believe this was related to clientOnly DataSources.
                          You mean besides my very first sentence?
                          Originally posted by ttubbs
                          I have a client side only DataSource that I have several forms and widgets binding to.

                          Originally posted by Isomorphic
                          What's automatic is that if a form saves, it will reflect the data-as-saved after the server returns a success response. Currently, only the form that initiates the save will be updated - other forms do not automatically react to changes, although ListGrids, TreeGrids, DetailViewers and other components do. You're hitting an edge case because you're using a form as a read-only data display - it's not normal to have the same data showing in two editable forms so there's no auto-update for this case currently.
                          Again, just so I am clear the phrase "after the server returns a success response" is implying to me that the rest of what you've stated DOES NOT apply to my case of client side only DataSource(s). Yes? So I should not expect grids, lists, and trees to update when making use of client side data.

                          Anyway, the observer on 'dataChanged' seems to be working, so ...

                          The Recipe to Listen to Client Side Only DataSource(s) (should anyone else care)
                          Code:
                          // 1.  Observe the undocumented 'dataChanged' event on shared DataSource
                          // --- This is in my initWidget of a VLayout derived composite
                          this.observe(mySharedData, "dataChanged", "observer.mySharedDataObserver()");
                          
                          // 2.  Implement a specific observer in every component / composite that needs to watch the shared data.
                          // --- The observer function must exist so it can be evaled (i.e. cannot be set via closurs)
                          // --- Ex: implemented via addProperties of a VLayout composite that has two forms; both forms updated when data changes
                          mySharedDataObserver: function() {
                          	this.Form1.setValues( mySharedData.testData[0] ); 
                          	this.Form2.setValues( mySharedData.testData[0] );
                          },
                          
                          // 3.  Expose the nested forms as properties of the composite (VLayout in my case) so the observer method can make use of them
                          this.Form1 = staticForm;
                          this.Form2 = editForm;
                          
                          // 4.  TODO: Stop hard coding testData[0] and programmatically determine the selected record number.

                          Comment


                            #14
                            As far as terminology, the docs refer to a DataSource that is created in JavaScript as existing only on the client-side, because the SmartClient Server is not aware of it. So a "client side only DataSource" is a phrase people often use to mean a DataSource that is using REST or WSDL data integration approaches, particularly when there is a mix of server-based DataSources and DataSources that only exist on the client side. If you wish to be unambiguous, it's best to just use the property name (clientOnly:true).

                            ListGrids, TreeGrids etc observe the DataSource for updates, regardless of the DataSource type, so updates will automatically happen with a clientOnly:true DataSource as well. DynamicForm will update automatically if and only if it is the form that initiated the save (regardless of the DataSource type).

                            It is true that the concept of data-as-saved really only applies to true server integration, since a clientOnly:true DataSource doesn't really generate additional fields (like a modification timestamp, for example).

                            Your instructions can be greatly simplified. Give a client-only DataSource "myDataSource" and a form "myForm" that needs manual updating, do this one-liner:

                            Code:
                            myForm.observe(myDataSource, "dataChanged", "observer.setValues(dsResponse.data)");

                            Comment


                              #15
                              Ah, Very nice! I like one-liners.

                              The info. was very helpful as well. I see your point about terminology. It's very subtle in this case. I personally wouldn't have referred to REST or WSDL as 'client side only' as there is a server component. I don't think I'd ever infer REST or WSDL if it wasn't mentioned. But, I do get it. Evidently a case of YMMV. It certainly explains what I thought were some crazy replies. :)

                              Glad to get confirmation that I can get automatic updates when using clientOnly: true style DataSources.

                              Thanks for the help.

                              Comment

                              Working...
                              X