locked
Issue setting entity property when programmatically adding entity RRS feed

  • Question

  • Hi all,
    I encountered a strange issue...I was following this blog post to save a many-to-many relationship:

    http://blogs.msdn.com/b/lightswitch/archive/2013/04/10/many-to-how-many-html-edition-heinrich-wendel.aspx

    Initially it worked fine, until I tried to customize the appearance of the list...

    The screen I'm working on is for adding/editing training sessions. In my database, training sessions are events for which there are trainers and attendees (both are many-to-many relationships). I'm currently working on the trainers part, and the name of the join table is TrainingTrainer. Here's the code to add a new TrainingTrainer entity when the user clicks on a trainer in the "Add Trainer" popup:

    myapp.AddEditTraining.Trainers_ItemTap_execute = function (screen) {
        var trainingTrainer = screen.details.dataWorkspace.ApplicationData.TrainingTrainers.addNew();
        //Set the Training property to the Training currently being edited
        trainingTrainer.Training = screen.Training;
        //Set the Trainer to the currently selected trainer
        trainingTrainer.Trainer = screen.Trainers.selectedItem;
    
        screen.closePopup();
        return myapp.applyChanges();
    }

    As I mentioned, this initially worked fine. The problem arose when I tried to use a custom control for the list of TrainingTrainer entities:

    myapp.AddEditTraining.TrainingTrainers_render = function (element, contentItem) {
        var $ul = $('<ul/>');
        $(element).append($ul);
        var template = contentItem.children[0];
        contentItem.dataBind('value.count', function (count) {
            var $rows = [];
            contentItem.value.data.forEach(function (trainingTrainer, i) {
                if (!trainingTrainer) return true; //continue loop
    
                var $row = $('<li>');
                $row.html(trainingTrainer.Trainer.FirstName + ' ' + trainingTrainer.Trainer.LastName);
                $rows.push($row);
            });
            $ul.append($rows);
        });
    };

    As soon as I added that code, the above method (Trainers_ItemTap_execute) no longer worked. Upon further investigation I realized it was because the setter for the Training property was no longer working. I put in this test code:

        console.log('screen.Training', screen.Training)
        //Set the Training property to the Training currently being edited
        trainingTrainer.setTraining(screen.Training);
        console.log('trainingTrainer.Training', trainingTrainer.Training)

    And to my surprise, I got the following output in the console! (Apparently the setTraining() call was silently failing!)

    screen.Training Training {details: EntityDetails, _listeners: Object}
    trainingTrainer.Training undefined

    Then it got stranger...if I removed the line accessing the Trainer properties (FirstName and LastName) in the custom render method, the Trainers_ItemTap_execute method started working again. To verify this, I simplified that line as much as possible, from:

    $row.html(trainingTrainer.Trainer.FirstName + ' ' + trainingTrainer.Trainer.LastName);

    to simply:

    trainingTrainer.Trainer.FirstName;

    So merely accessing the FirstName property was enough to cause the Trainers_ItemTap_execute to fail. This is especially odd given that it was the Training property that was undefined, not the Trainer property. This seems like a bug to me...

    Note that simply accessing trainingTrainer.Trainer on its own within the render method did not cause the problem; it was something about actually accessing a data property - in this case FirstName.

    Does anyone have an idea of what's going on here? I do need to customize the display of that list so I hope I can find a solution... (I do need to change the Trainers_ItemTap_execute code anyhow so that it will work when adding a new training and not only when editing an existing training...so maybe some of my upcoming changes to the code will fix this, but it's certainly strange and I would like to know what caused it.)

    Thanks,
    Matt


    Friday, July 10, 2015 6:33 PM

All replies

  • I suspect that the problem is you are using the "trainingTrainer" variable name in both methods.

    Yes, I know the prototype pattern is suppose to allow this but I would try to use different variable names in each method to see if that helps.


    Unleash the Power - Get the LightSwitch 2013 HTML Client / SharePoint 2013 book

    http://LightSwitchHelpWebsite.com

    Friday, July 10, 2015 7:56 PM
  • Thanks...changing the variable names didn't make a difference (those are just local variables), but while editing the code I found the issue! It was that trainingTrainer.Trainer was undefined in some cases, so accessing trainingTrainer.Trainer.FirstName caused a "TypeError: Cannot read property 'FirstName' of undefined". Unfortunately that error was swallowed silently (apparently by the dataBind() callback); I was able to get the error to display with this code:

                try {
                    trainingTrainer.Trainer.FirstName
                }
                catch (e) {
                    log(e);
                }

    Is there any way to ensure that such errors are displayed to the console during development rather than it failing silently and clearly causing other weird issues down the line? I guess I could just wrap a try/catch block around every dataBind() callback but I'm thinking there's got to be a cleaner way?

    BTW I fixed it by changing this line:

    if (!trainingTrainer) return true; //continue loop

    to this:

    if (!trainingTrainer || !trainingTrainer.Trainer) return true; //continue loop

    Thanks,
    Matt


    Friday, July 10, 2015 8:21 PM
  • I am glad you figured it out :)

    As to your other question this should work:

    if(trainingTrainer.Trainer.FirstName !== null && trainingTrainer.Trainer.FirstName !== 'undefined'){ 
    Do this... 
    }


    Unleash the Power - Get the LightSwitch 2013 HTML Client / SharePoint 2013 book

    http://LightSwitchHelpWebsite.com


    Friday, July 10, 2015 8:49 PM
  • Thanks...yes, now that I know what the problem is, I was able to easily fix it in a similar way to your suggestion (see the bottom of my previous post, which I edited to include the fix).

    My follow-up question was about debugging and preventing errors from being swallowed silently...is there any way to prevent that, short of putting try/catch blocks everywhere?

    BTW I have your book and I noticed that in your examples you check for null and undefined in the same way you suggested here, but this seems unnecessarily verbose to me...in many cases a check like this:

    if (test.myProperty === null || test.myProperty === undefined)

    Can be simplified to this:

    if (!test.myProperty)

    I don't understand why you explicitly check for null and undefined as often as you do (maybe it's just a style preference)...

    Also, note that in this case, checking for:

    (trainingTrainer.Trainer.FirstName !== null && trainingTrainer.Trainer.FirstName !== undefined)

    ...would not have been sufficient (it would still have caused that silent error). The fix was to check to make sure that trainingTrainer.Trainer was defined:

    (!trainingTrainer || !trainingTrainer.Trainer)

    Friday, July 10, 2015 9:04 PM
  • Actually I meant:

    if(trainingTrainer.Trainer !== null && trainingTrainer.Trainer !== 'undefined'){ 
    var FistName = trainingTrainer.Trainer.FirstName; 
    }

    That should not blow up.


    Unleash the Power - Get the LightSwitch 2013 HTML Client / SharePoint 2013 book

    http://LightSwitchHelpWebsite.com

    Friday, July 10, 2015 9:12 PM
  • Thank you for the reply, but would you mind re-reading my previous post? It seems you are missing my main question at this point, which is about debugging...

    Also, in your code it should be:
    trainingTrainer.Trainer !== undefined

    Not:
    trainingTrainer.Trainer !== 'undefined'

    The idea is not to test for the string 'undefined' but to check if the value is equal to the special Javascript value 'undefined' (without quotes).

    Friday, July 10, 2015 9:26 PM
  • Thank you for the reply, but would you mind re-reading my previous post? It seems you are missing my main question at this point, which is about debugging...

    Also, in your code it should be:
    trainingTrainer.Trainer !== undefined

    Not:
    trainingTrainer.Trainer !== 'undefined'

    The idea is not to test for the string 'undefined' but to check if the value is equal to the special Javascript value 'undefined' (without quotes).


    Sorry I was typing psudeo code without using a code editor.

    Unleash the Power - Get the LightSwitch 2013 HTML Client / SharePoint 2013 book

    http://LightSwitchHelpWebsite.com

    Friday, July 10, 2015 9:38 PM
  • Matt: This can happen when you have nested data/child records. While the parent record should have all its data, any associated child records may not have been loaded at this point, so they return as undefined.  You can force retrieval through the "get" functions, i.e., trainingTrainer.getTrainer().then(function(result){if(!!result){var trainerName = result.FirstName;}else{var trainerName = "None"}});  The only problem is that this is an asynchronous/promise call, so it will move on to the next line of code before it finishes--not good if you wanted to do anything with the result in this next line, because it won't be there yet. 

    Another solution is to make sure the main query forces a pull from the related child tables on load.  This results in longer load times, but if you know you're going to walk through an item's child records, it's worth the time spent in not having to hassle with multiple chained promise resolution.

    Hope this helps,

    R. T. Watkins

    Wednesday, July 15, 2015 1:57 PM
  • This is a good way to deal with async/promises.  Especially when you have parallel promises going on at the same time.

    // get the customer async

    screen.getCustomer().then(function (customer) {

    // get the customer properties async var promises = { address: customer.getAddress(), city: customer.getCity(), state: customer.getState(), country: customer.getCountry(), postalCode: customer.getPostalCode() };

    // wait until all promises are resolved WinJS.Promise.Join(promises).then(function (values) { // now you have a values object with the same properties // as the promises object but with the return values of // the completed promises // do whatever you need to do with the completed promises var addressValue = values.address; }); });



    • Edited by Hessc Thursday, July 16, 2015 4:07 AM comments for clarity
    Thursday, July 16, 2015 3:59 AM
  • Thank you R.T. and Hessc for the very helpful info - I will certainly keep it in mind for the future. For this situation, however, retrieving the related properties was not the issue. In fact, I was creating and setting the related property myself, so there was no issue of retrieving it from the server. The issue here was that there was an error that was silently swallowed, probably by the dataBind() callback, and that's why the subsequent code acted strangely.

    I still haven't found a good way to reliably report such errors in the console...I tried adding a WinJS.promise.onerror handler as suggested here, but it didn't work (I'm not sure if the dataBind() function uses promises under the hood; maybe not).

    I do also still have a question with regard to the timing of data-binding callbacks and custom list controls...in case any of you have a chance to comment, I described that issue here (see my follow-up post):

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/88f04399-740c-4695-9edc-b3144351b21b/how-to-listen-for-changes-for-a-custom-list-control?forum=lightswitch

    Fortunately I was able to find a decent solution in the meantime.

    Thank you,
    Matt

    Sunday, July 19, 2015 10:41 PM