none
Cleaning up change listeners / event handlers in HTML Client RRS feed

  • General discussion

  • Hi,

    I just realized that I have not been writing code that cleans up change listeners / event handlers in my HTML Client sample code. My apology for that, but better late than never :)

    If you've been writing render / postRender custom code and use contentItem.dataBind to set up your change listeners, you're good because underneath the hood dataBind creates an auto-dispose handlers that will be remove when the content item is disposed (the screen is closed).

    If you're using addChangeListener / addEventListener in your code, objects on HTML Client have different lifetime. Entity Objects tend to stay around much longer than Screen Objects. So if you add event listeners to an Entity Object on a Screen, when that Screen is closed, the listeners are still active and you need to clean up that listener.

    All content item objects has a method called handleViewDispose, which is invoked when the screen is being closed.

    So to clean up event handlers added during screen creation:

    myapp.AddEditCustomer.created = function (screen) {
        function onPropertyChanged() {
            // Do something.
        }
        screen.Customer.addChangeListener(
            "Property", onPropertyChanged);
    
        function onEvent() {
            // Do something.
        }
        screen.Customer.addEventListener(
            "Event", onEvent);
    
        // Clean up when screen is closed.
        screen.details.rootContentItem
        .handleViewDispose(function () {
            screen.Customer.removeChangeListener(
                "Property", onPropertyChanged);
            screen.Customer.removeEventListener(
                "Event", onEvent);
        });
    };

    Similarly for render, postRender methods, we can use the contentItem handleViewDispose

    myapp.AddEditCustomer.RowTemplate_postRender = function (element, contentItem) {
        // Add listeners not through contentItem.dataBind
        contentItem.handleViewDispose(function () {
            // Remove those listeners
        });
    };
    myapp.AddEditCustomer.CustomControl_render = function (element, contentItem) {
        // Add listeners not through contentItem.dataBind
        contentItem.handleViewDispose(function () {
            // Remove those listeners
        });
    };
    Best regards,
    Huy
    Saturday, May 18, 2013 3:29 PM

All replies

  • Thanks for this. I searched through my blogs and the only place where I am using addChangeListener is this one:

    On Writing JavaScript That Implements The Binding Pattern In Visual Studio LightSwitch
    http://lightswitchhelpwebsite.com/Blog/tabid/61/EntryId/168/Writing-JavaScript-That-Implements-The-Binding-Pattern-In-Visual-Studio-LightSwitch.aspx

    I needed to update it to Lightswitch Update 2 anyway so I will do that and add the proper dispose. However, you don't have an example of disposing the handlers in the entity method (only the screen method).

    This is the code I have:

    myapp.Person.created = function (entity) {
        // Write code here.
        entity.addChangeListener("LastName", function (e) {
            toastr.info("(from entity level) LastName value changed to: " + entity.LastName);
        });
    };
    Thanks!

    The Visual Studio LightSwitch Marketplace

    http://LightSwitchHelpWebsite.com

    Saturday, May 18, 2013 5:41 PM
  • One more quick one, I just wrote some code for an article I am working on now that uses the addChangeListener :)

    Did I get this correct?

    // Grid Render
    myapp.BrowseStudents.StudentsGrid_render = function (element, contentItem) {
        // Show Data
        // From: http://blogs.msdn.com/b/lightswitch/archive/2013/01/14/visualizing-list-data-using-a-map-control-heinrich-wendel.aspx
        var visualCollection = contentItem.value;
        if (visualCollection.isLoaded) {
            showItems(intCurrentItem, intPageSize, contentItem.screen);
        } else {
            visualCollection.addChangeListener("isLoaded", function () {
                showItems(intCurrentItem, intPageSize, contentItem.screen);
            }); visualCollection.load();
        }
    
        // Clean up addChangeListener when screen is closed.
        contentItem.handleViewDispose(function () {
            visualCollection.removeChangeListener(
                "isLoaded", onPropertyChanged);
        });
    };


    The Visual Studio LightSwitch Marketplace

    http://LightSwitchHelpWebsite.com

    Saturday, May 18, 2013 7:30 PM
  • Hi Michael,

    With the single active data workspace model in the HTML Client, Entity Objects are not disposed like Screen Object. So if you write your event handler within the Entity Object, it will be safe during the lifetime of the Entity Object. Depends on each usage case, you can check the entity state to remove the change listeners.

    For example in your code above, let's say you decide that when newly created Person is saved to the database, or user decides to cancel the creation, you no longer need the change listener. You can write this code

    myapp.Person.created = function (entity) {
        // Write code here.
        function onLastNameChanged() {
            toastr.info("(from entity level) LastName value changed to: " + entity.LastName);
        }
        entity.addChangeListener("LastName", onLastNameChanged);
    
        function onEntityStateChanged() {
            var state = entity.details.entityState;
            if (state == msls.EntityState.unchanged || state == msls.EntityState.discarded) {
                entity.removeChangeListener("LastName", onLastNameChanged);
                entity.details.removeChangeListener("entityState", onEntityStateChanged);
            }
        }
        entity.details.addChangeListener("entityState", onEntityStateChanged);
    };

    99% of the case that code would work fine. However, there's this 1% case :) If from the Add Person screen you allow the end user to open Screen 2, and in Screen 2 you have some action that may delete the newly created Customer. When that action executes, the entity state will move to discarded. But if the end user 'Cancel' on Screen 2, the entity state will move back to added. Most likely you will never build some scenario like this, so I think we should be fine :)

    In your second code sample, because the removeChangeListener / removeEventListener requires the same instance of the handler to be able to remove it, you should declare the listener as a separate function, and use it in both add and remove.

        function onVisualCollectionLoaded() {
            showItems(intCurrentItem, intPageSize, contentItem.screen);
        }
    
        var visualCollection = contentItem.value;
        if (visualCollection.isLoaded) {
            onVisualCollectionLoaded();
        } else {
            visualCollection.addChangeListener(
                "isLoaded", onVisualCollectionLoaded);
            visualCollection.load();
        }
    
        // Clean up addChangeListener when screen is closed.
        contentItem.handleViewDispose(function () {
            visualCollection.removeChangeListener(
                "isLoaded", onVisualCollectionLoaded);
        });

    Also, a couple of comments:

    • With the auto-load behavior of HTML Client, I'm pretty sure you don't need to invoke visualCollection.load() in the code above. If you see the visualCollection not loaded, most likely it's in the middle of loading.
    • Some can argue that since the Visual Collection is also part of the screen and will also be disposed when the screen is disposed, you don't need the clean up code above. I prefer leaning on the safe side so I'm fine with having the clean up code :)

    Best regards,
    Huy


    Sunday, May 19, 2013 1:46 PM
  • Huy,

    Thank you again!

    Entity Example:

    If I want the handler to stay on the object for the life of the entity, do I need to remove the handlers at all? I just thought it was causing memory leaks when the entity was deleted or discarded.

    Visual Collection Example:

    Wow I just learned a lot from your visual collection code. I put it in and it works fine.

    This code is hydrating a JQuery Mobile reflow grid and the "isLoaded" is just better than the "setTimeout" code that I had to use in past examples (to wait until the data was loaded before binding the control).


    The Visual Studio LightSwitch Marketplace

    http://LightSwitchHelpWebsite.com

    Sunday, May 19, 2013 2:15 PM
  • Hi Michael,

    If I want the handler to stay on the object for the life of the entity, do I need to remove the handlers at all? I just thought it was causing memory leaks when the entity was deleted or discarded.

    We are not disposing the entity object when it is deleted or discarded (in hindsight, we should). But the event handlers are stored in an array on the Entity Object itself, so if the Entity Object is going to be disposed, the handlers will be disposed as well. (Unless in your handler you reference some other objects, but that's pretty rare).

    Huy

    Monday, May 20, 2013 4:25 PM
  • Hi Michael,

    If I want the handler to stay on the object for the life of the entity, do I need to remove the handlers at all? I just thought it was causing memory leaks when the entity was deleted or discarded.

    We are not disposing the entity object when it is deleted or discarded (in hindsight, we should). But the event handlers are stored in an array on the Entity Object itself, so if the Entity Object is going to be disposed, the handlers will be disposed as well. (Unless in your handler you reference some other objects, but that's pretty rare).

    Huy

    Thank you so much. Thank you also for the extra information on exactly what is happening, your answers are as always, wonderful!

    In this particular case I will leave things as they are.


    The Visual Studio LightSwitch Marketplace

    http://LightSwitchHelpWebsite.com

    Monday, May 20, 2013 5:25 PM
  • Huy,

    You're examples involve removing listeners that call an externally defined function:

        function onPropertyChanged() {
            // Do something.
        }
        screen.Customer.addChangeListener(
            "Property", onPropertyChanged);

    But what about functions that are defined inline with the listener:

        screen.Customer.addChangeListener(
            "Property", function () {
            // Do something.
        });

    I ask because the removeChangeListener method takes parameters that specify both the property name and the function name. Your example works fine, but an inline function has no name, so I'm not sure what to put in for the second parameter.

    I have some screens with 30-50 listeners, all with unique inline functions, and it would be a lot of extra code and time to have to break out all that code into 30-50 new functions per screen just to accommodate the removeChangeListener method.

    Can I do something like:

    myapp.AddEditCustomer.created = function (screen) {
        screen.Customer.addChangeListener(
            "Property", function () {
            // Do something.
        });
    
        // Clean up when screen is closed.
        screen.details.rootContentItem
        .handleViewDispose(function () {
            screen.Customer.removeChangeListener(
                "Property", function());
        });
    };

    I know that in jQuery (and other platforms), when unbinding listeners, it treats each instance of an inline function as unique code.

    so for example in jQuery, if I run this code: $.bind('click', function({return;}); I cannot unbind it by calling this code: $.unbind('click', function({return'}); because jQuery treats each instance of "function({return;})" as unique code. So "function({return;})" in the bind method is != "function({return;})" in the unbind method. Is this the same in LightSwitch?


    -Christopher DeMars



    Thursday, May 23, 2013 7:42 PM
  • Hi Christopher,

    LIGHTSWITCH does the same thing and treat each instance of handler as unique. In fact here's the code from winjs, the library that we rely on for events code:

            removeEventListener: function (type, listener, useCapture) {
                /// <signature helpKeyword="WinJS.Utilities.eventMixin.removeEventListener">
                /// <summary locid="WinJS.Utilities.eventMixin.removeEventListener">
                /// Removes an event listener from the control.
                /// </summary>
                /// <param name="type" locid="WinJS.Utilities.eventMixin.removeEventListener_p:type">
                /// The type (name) of the event.
                /// </param>
                /// <param name="listener" locid="WinJS.Utilities.eventMixin.removeEventListener_p:listener">
                /// The listener to remove.
                /// </param>
                /// <param name="useCapture" locid="WinJS.Utilities.eventMixin.removeEventListener_p:useCapture">
                /// Specifies whether to initiate capture.
                /// </param>
                /// </signature>
                useCapture = useCapture || false;
                var listeners = this._listeners && this._listeners[type];
                if (listeners) {
                    for (var i = 0, len = listeners.length; i < len; i++) {
                        var l = listeners[i];
                        if (l.listener === listener && l.useCapture === useCapture) {
                            listeners.splice(i, 1);
                            if (listeners.length === 0) {
                                delete this._listeners[type];
                            }
                            // Only want to remove one element for each call to removeEventListener
                            break;
                        }
                    }
                }
            }

    As you can see it's an instance comparison here.

    Most of the time if you're adding and removing a handler in the same function scope, the change should be minimal

    Let's say you start with this:

    myapp.AddEditCustomer.created = function (screen) {
        screen.Customer.addChangeListener(
            "Property", function () {
            // Do something.
        });
    };

    To add the clean up code, you only need to move 'Do something' outside, but still in the same function scope of myapp.AddEditCustomer.created, so it will not leak anywhere.

    myapp.AddEditCustomer.created = function (screen) {
        function onPropertyChange() {
            // Do something.
        }
    
        /*
            Alternatively, if you want to emphasize
            on the scope
    
        var onPropertyChange = function() {
            // Do something
        };    
    
         */
    
        screen.Customer.addChangeListener(
            "Property", onPropertyChange);
    
        // Clean up when screen is closed.
        screen.details.rootContentItem
        .handleViewDispose(function () {
            screen.Customer.removeChangeListener(
                "Property", onPropertyChange);
        });
    };


    The only time I can see an issue is when you add an event handler in one function but want to remove it in a different function. In that case I would recommend using the screen object as the place to cache the handlers.

    myapp.AddEditCustomer.created = function (screen) {
    
        screen.__myHandlers = {};
    
        screen.__myHandlers.onPropertyChange = 
        function() {
            // Do something
        };
    
        screen.Customer.addChangeListener(
            "Property", 
            screen.__myHandlers.onPropertyChange);
    };
    
    myapp.AddEditCustomer.CustomControl_render = function(element, contentItem) {
        // Let's say we want to clean up the handler here.
        screen.Customer.removeChangeListener(
            "Property",
            screen.__myHandlers.onPropertyChange);
    }

    Best regards,
    Huy


    Thursday, May 23, 2013 9:39 PM
  • Hi Huy,

    Thanks for this!  I think I need to remove a change listener to insure correct operation in this scenario:

    1.  I am in my ViewEntity (Entity is "timesheet") screen.
    2.  I click my Edit button which navigates to AddEditEntity screen.
    3.  This screen adds a change listener for an entity property (TimecardStatus).  The handler for this changes another entity property (Comments).  
    4.  Edit the entity property (TimecardStatus), which calls listener to change other entity property (Comments).  Both properties are correctly updated on screen.
    5. Cancel.  Both changed properties are correctly reverted to original values.  The screen navigates back to ViewEntity screen.
    6.  Repeat #2 - 4.  However, my listener is called 2x now to update the other entity property.
    7.  Repeat again, this time my listener is called 3x.  Since the Comments are appended too, this adds 3 lines of text, so it cannot be like this.

    So my listener is being added each time but not removed. 

    I tried adding code as you suggested:

    myapp.AddEditTimesheet.created = function (screen) {
    
        function onTimecardStatusChanged(e) {
            alert("TimecardStatus changed");
    
            timesheet.Comments += "A new comment\r\n";
        }
    
        screen.Timesheet.addChangeListener("TimecardStatus", onTimecardStatusChanged);
    
        // Clean up when screen is closed.
        screen.details.rootContentItem.handleViewDispose(function () {
            alert("handleViewDispose");
            screen.Timesheet.removeChangeListener("TimecardStatus", onTimecardStatusChanged);
        });
    }

    The problem is, "handleViewDispose" is never called in the above scenario!  The alert never fires, nor does it break there in the debugger.  Any ideas?

    Thanks,
    David


    Efficiently read and post to forums with newsreaders: http://communitybridge.codeplex.com

    Friday, May 31, 2013 2:08 PM
  • Hi David,

    It's my mistake. I was using an older drop when writing up the sample code, looks like in the final release performance optimization was made and handleViewDispose is only invoked on the content item with a view/control. Since the screen's root content item does not have a control representation (it has two content item - Tabs and Popups, then the Tabs has all the tab content items defined), its handleViewDispose is not invoked.

    What we can do is find the first tab and add the clean up code there. For example:

    myapp.AddEditCustomer.created = function (screen) {
        screen.findContentItem("Details").handleViewDispose(function () {
            // Remove event listeners
            alert("Disposing");
        });
    };

    Replace Details with the correct tab name.

    For some reason I cannot edit my original post (editing my reply works). So hopefully this update will be enough.

    Thanks very much for point this out. Sure teach me not to use my old VM to write sample code :)

    Best regards,
    Huy


    Friday, May 31, 2013 8:42 PM
  • Awesome, it works great!  Thanks Huy!
     
    -- David

    Efficiently read and post to forums with newsreaders: http://communitybridge.codeplex.com

    Saturday, June 1, 2013 2:22 AM
  • I'm quite late on this discussion but I'd like to understand if my approach is correct or whether I applied any bad practice. I'm total newbie to js and still in trouble to get the whole picture about javascript object model (plus a lot of intellisense/syntax misunderstandings)

    btw this is related to an open question  of mine, where Michael Washington gave me some useful  suggestions on other technical aspects, linking Huy's posts too.

    I have an Edit screen on the child entity ('Detail') of a parent entity ('Master'). As I need a wizard-like flow from first child entity to the last, in the created event I :

    [edit: forgot to mention that when 'Master' entity is created, Server Master_Inserting  generates all needed children entities : 'Detail' and 'SubDetail']

    • set up a container array with all children ('Detail') related to the 'Master' parent
        screen.__myRows = {};
        screen.Detail.details.properties.Master.load().then(function (main) {
            main.details.properties.Details.load().then(function (rows) {
                console.log("LOADED ROWS " + rows.array.length)
                screen.__myRows = rows.array
            })
        })

    I'd like to know if there is a more elegant solution or if it's a bad practice to create an edit screen and then retrieve the parent and all (my 'brother') children to achieve my goal.

    • following your above suggestions, set up a container for my entity property changed handler
        screen.__myHandlers = {};
    
        screen.__myHandlers.onGrossAmountChanged =
        function () {
            console.log("GrossAmount CHANGED ")
            
            if (screen.SubDetails.isLoaded) {
                for (var i = 0; i < screen.SubDetails.data.length; i++) {
                    var d = screen.SubDetails.data[i]
                    d.prototypeGetCalculatedValue(screen.Detail.details.properties.GrossAmount.value);
                }
            }
            else {
                screen.SubDetails.load().then(function () {
                    console.log("SUBDETAILS LOADED ")
                    for (var i = 0; i < screen.SubDetails.data.length; i++) {
                        var d = screen.SubDetails.data[i]
                        d.prototypeGetCalculatedValue(screen.Detail.details.properties.GrossAmount.value);
                    }
                })
            }
    
        }
    
        screen.Detail.addChangeListener("GrossAmount", screen.__myHandlers.onGrossAmountChanged);
    
        screen.details.rootContentItem
        .handleViewDispose(function () {
            screen.Detail.removeChangeListener("GrossAmount",screen.__myHandlers.onGrossAmountChanged);
    'Detail' entity is a parent of a third entity ('SubDetail') and here is one of my doubts before refactoring duplicate code:

    I see visual collection screen.SubDetails is always loaded automatically so I can remove my isLoaded if statement, right ?

    Then, to obtain my wizard-like flow from first to last 'Detail' entity, I created an integer variable increased/decreased by next/prev buttons click code, which manages my changed handler too, in this way :

    myapp.Wizard_Details.PreviousStep_execute = function (screen) {
        // Write code here.
        screen.currentRowIndex = screen.currentRowIndex - 1
        OnChangeCurrentEntity(screen);
        OnChangeDisplayName(screen);
    };
    
    function OnChangeCurrentEntity(screen) {
        screen.Detail.removeChangeListener("GrossAmount", screen.__myHandlers.onGrossAmountChanged);
        screen.Detail = screen.__myRows[screen.currentRowIndex]
        screen.Detail.addChangeListener("GrossAmount", screen.__myHandlers.onGrossAmountChanged);
    }

    so, actually I call several times the hook/unhook event, the wizard flow seems to work as expected but it's difficult for me to understand if under the hood everything is ok.

    Any suggestion and comment will be appreciated.


    Marco



    Monday, September 23, 2013 4:31 PM
  • Hi Huy,

    Is this "cleanup" stuff still needed in January 2014? Or has the framework been given more "smarts" to take care of this cleanup for us?

    Thanks,


    Yann Duran
         - Co-Author of Pro Visual Studio LightSwitch 2011
         - Author of the  LightSwitch Central Blog

    FREE Download: Luminous Tools for LightSwitch
    (a Visual Studio productivity extension for LightSwitch)
     
    Click Mark as Answer, if someone's reply answers your question
    Click  Vote as Helpful, if someone's reply is helpful
     
    By doing this you'll help everyone find answers faster.

    Saturday, January 11, 2014 3:00 PM
    Moderator
  • bump

    Yann Duran
         - Co-Author of Pro Visual Studio LightSwitch 2011
         - Author of the  LightSwitch Central Blog

    FREE Download: Luminous Tools for LightSwitch
    (a Visual Studio productivity extension for LightSwitch)
     
    Click Mark as Answer, if someone's reply answers your question
    Click  Vote as Helpful, if someone's reply is helpful
     
    By doing this you'll help everyone find answers faster.

    Friday, January 31, 2014 4:45 AM
    Moderator
  • I guess Huy is not available to us anymore.. sad.. great guy with many good examples..

    Kivito

     

    Nobody expects the Spanish Inquisition! (M.P.F.C.)

    Friday, January 31, 2014 10:46 AM
  • I think something has changed since Huy shared this nugget about cleaning up listeners.  It seems I cannot get .rootContentItem.handleViewDispose to fire.  I tried climbing up the contentItem tree until I found one that does execute like so:

    myapp.AddEditContact.created = function (screen) {
        // Write code here.
    
        screen.details.rootContentItem.handleViewDispose(function () {
            //this doesn't exec
            alert("View Dispose1");
        });
        var tabgroup = screen.findContentItem("Tabs")
        tabgroup.handleViewDispose(function () {
            //this doesn't exec
            alert("View Dispose3");
        });
        var details = screen.findContentItem("Details")
        details.handleViewDispose(function () {
            //this does execute
            alert("View Dispose4");
        });
    
    };

    "Details" is the first tab on a default AddEdit Screen.  So it looks like you have to use the first tab on the screen. 

    The change is probably due to the major update to screen navigation since Huy wrote about this.  Perhaps the rootContentItem and Tabs group are reused on all screens so the view is never disposed(?)

    Can someone confirm this?

    HTH,

    Josh

    Wednesday, July 9, 2014 7:15 PM
  • Unfortunately this does not work correctly and never has when the user cancels.

    The listener is executed after the cancel (if updated as the old values are put back) and before the view dispose. There is no way of knowing in the listener that a cancel is in progress, so updates lead to 'unsaved changes' problems. If we could detect in the listener that a cancel is in progress than this could be avoided.

    So if using 'addChangeListener' do not update data or a user cancel will cause transaction problems.

    If anyone finds a workaround then please post it.

    Thanks

    Dave


    Dave Baker | AIDE for LightSwitch | Xpert360 blog | twitter : @xpert360 | Xpert360 website | Opinions are my own. For better forums, remember to mark posts as helpful/answer.

    Tuesday, June 16, 2015 1:02 PM
  • Can you use AfterClosed on the original screen to discard changes if the navigateBackAction is not a commit?
    Tuesday, June 16, 2015 1:31 PM
  • Hi Hessc,

    I usually use 'dataBinding' in which case the 'extra' listener invoke does not happen.

    I will take a look at your suggestion, but I have found messing with the screen after closed means not everything exists in a usable state.

    Thx

    Dave


    Dave Baker | AIDE for LightSwitch | Xpert360 blog | twitter : @xpert360 | Xpert360 website | Opinions are my own. For better forums, remember to mark posts as helpful/answer.

    Tuesday, June 16, 2015 1:37 PM
  • That did not work: 'discardChanges'

    In my eyes this is a bug, the change event listener should not be triggered when cancelling. The change is part of the automatic rollback.

    If the view dispose is triggered before the automatic rollback then there would be no problem. Or if the screen save/cancel action is detectable from the change event listener.

    Dave


    Dave Baker | AIDE for LightSwitch | Xpert360 blog | twitter : @xpert360 | Xpert360 website | Opinions are my own. For better forums, remember to mark posts as helpful/answer.

    Tuesday, June 16, 2015 1:56 PM
  • I'm thinking if you pass the entity you are operating on with the listener back to the AfterClosed function as a parameter, you should be able to discard changes on it -- but I have not tried it.  Unfortunately to get it to work you might have to make the entity an optional screen parameter (on the original screen) in order to pass it in a usable state.  I would only go through all of this hassle if there were no other way to handle it and I really needed the screen and changeListeners to work a certain way. I agree with you, trying to find a contentItem and dataBind it would be better.
    Tuesday, June 16, 2015 2:01 PM
  • Dave,

    Curious, are you doing handleViewDispose of the first screen tab? 

    I think that's the top item in the contentItem tree that fires this event.  Question isdoes it fire in correct sequence to help your cancel senerio?

    Josh

    Tuesday, June 16, 2015 2:15 PM
  • One other idea, could you check entity.Details.EntityState == EntityState.Discarded inside the function called by the listener, so that it doesn't fire on cancel?  My guess is that the listener is going to fire no matter what but it's the only thing I can think of.
    • Edited by Hessc Tuesday, June 16, 2015 2:25 PM
    Tuesday, June 16, 2015 2:24 PM
  • Hi Josh,

    Yes, I hooked up the handleViewDispose to the first tab 'Details'. All the code executes as expected just that the event listener fires when the automatic rollback resets the value (on cancel) and before the view dispose.

    On this last event listener execution it should really do nothing, but I could not find anything to detect the cancel and rollback in progress. Calling myapp.cancelChanges from the view dispose or afterClosed does not work, neither does discardChanges.

    It is a problem investigated for someone else, I am a big user of dataBinding. Even more so after discovering this ;)

    Dave


    Dave Baker | AIDE for LightSwitch | Xpert360 blog | twitter : @xpert360 | Xpert360 website | Opinions are my own. For better forums, remember to mark posts as helpful/answer.

    Tuesday, June 16, 2015 2:25 PM
  • Hi,

    Hessc gets the gold star today :)

    When the change event fires as needed the entityState is either 'added' or 'modified'.

    In the last execution of the change listener the 'entityState' is a reliable way of indicating the current state of processing (on cancel). When adding the entityState is now 'discarded' and when editing the entityState is now already 'unchanged'.

    When saving the change listener does not fire the extra time as nothing is being rolled-back.

    Cheers

    Dave


    Dave Baker | AIDE for LightSwitch | Xpert360 blog | twitter : @xpert360 | Xpert360 website | Opinions are my own. For better forums, remember to mark posts as helpful/answer.

    Tuesday, June 16, 2015 2:43 PM
  • so if you wrap whatever the function is doing with:

    // assume the entity var is assigned somewhere

    // air code but you get the idea

    if (entity.details.EntityState !== EntityState.Discarded && entity.details.EntityState !== EntityState.Unchanged) { //do stuff }

    everything works correctly?

    edit: I meant && not ||


    • Edited by Hessc Tuesday, June 16, 2015 3:07 PM
    Tuesday, June 16, 2015 2:55 PM
  • I did try using 'hasChanges' but it is always 'true' even when entityState == unchanged. I guess you could call this code brittle but it is unlikely that Microsoft will change or fix this at this stage. Prove me wrong ;)

    Here is sample code to show the working code structure:

    myapp.AddEditCountry.created = function (screen) {
    
        function onCodeChanged() {
            var hasChanges = screen.details.dataWorkspace.details.hasChanges;
            var state = screen.Country.details.entityState;
            if (state != msls.EntityState.unchanged && state != msls.EntityState.discarded) {
                // Do things...
                // Like updating other properties on screen.Country
                // ...
            }
        }
        
        screen.Country.addChangeListener("Code", onCodeChanged)
    
        screen.findContentItem("Details").handleViewDispose(function () {
            screen.Country.removeChangeListener("Code", onCodeChanged);
        })
    };
    

    So yes Hessc you put me right on the trail!

    Cheers

    Dave


    Dave Baker | AIDE for LightSwitch | Xpert360 blog | twitter : @xpert360 | Xpert360 website | Opinions are my own. For better forums, remember to mark posts as helpful/answer.

    Tuesday, June 16, 2015 2:58 PM
  • awesome!
    Tuesday, June 16, 2015 3:04 PM
  • Guys, when we add inline / anonymous event handlers, do we still need to clean them up or does the Javascript GC take care of it, given that there are no other references to the event handler?

    For example:

    myapp.AddEditCountry.created = function (screen) {
        screen.Country.addChangeListener("Code", function(){
            var hasChanges = screen.details.dataWorkspace.details.hasChanges;
            var state = screen.Country.details.entityState;
            if (state != msls.EntityState.unchanged && state != msls.EntityState.discarded) {
                // Do things...
            }
        })
    };


    Regards, Xander. My Blog

    • Edited by novascape Tuesday, June 16, 2015 8:40 PM
    Tuesday, June 16, 2015 8:37 PM
  • Hi,

    When debugging I have noticed that they do not get cleaned-up and subsequent screens call the change listener, twice, three times, etc.. :( which is easy to miss.

    Should try F12 tools/console.log to really check, I did that before as well.

    Dave


    Dave Baker | AIDE for LightSwitch | Xpert360 blog | twitter : @xpert360 | Xpert360 website | Opinions are my own. For better forums, remember to mark posts as helpful/answer.

    Tuesday, June 16, 2015 8:43 PM
  • Thanks Dave, I've checked it and thinking about it again it makes sense. I have incorrectly *assumed* that they get cleaned up as they were inline. I have gone back and fixed them up now.

    Thanks for highlighting this issue again and causing me to fix my bugs at least before they got into production!


    Regards, Xander. My Blog

    Wednesday, June 17, 2015 2:27 AM
  • Hello,

    I am currently setting cursor focus in myapp.AddEditHoldingInventory.StrSerialNumber_postRender, with the following code sample..

    myapp.AddEditHoldingInventory.StrSerialNumber_postRender = function (element, contentItem) {
        // Write code here.
        $firstTextBox = $("input", $(element));
        setTimeout(function () {
            $firstTextBox.focus();
        }, 1);

    I am reading here about javascript for what I believe I need to  use next, which would be an onchange, theh changefocus functions, but I do not know how to write them in Lightswitch - or otherwise for that matter - as I am not a programmer. But I am learning, I hope ;).

    I need to move the cursor from this first 'field' or screen property, StrSerialNumber, the to next contentItem on my screen. Does it need to look something like this?...

    myapp.AddEditHoldingInventory.StrSerialNumber_postRender = function (element, contentItem) {
        // Write code here.
        $firstTextBox = $("input", $(element));
        setTimeout(function () {
            $firstTextBox.focus();
        }, 1);
    
        contentItem.onchange=function () {
            screen.findcontentItem("nameofnextfield").focus();
        }

    I know this is not correct as the code breaks on this line. I am not sure how to code this.

    I truly appreciate any advice. Thank you.


    • Edited by CreedCor Tuesday, January 19, 2016 5:59 PM
    Tuesday, January 19, 2016 5:58 PM