Single Page Apps – Part 10 – Saving, Change Tracking, Commanding, and Validation | John Papa

John Papa

Evangelist on the loose

Single Page Apps – Part 10 – Saving, Change Tracking, Commanding, and Validation

...

Saving data is simple, right? What app isn’t complete without some sort of saving mechanism? And while I’m at it, it’s nice to be able to track when changes occur so I can tell if a user can press the save button or even leave the current screen. Which bring me to making my buttons smart enough to know that they are already involved in a asynchronous process, so commanding would be nice. Finally, if I want to save data, it might be a good idea to validate the input on the client (and the server).

There are a lot of moving parts involved with saving changes, and I cover these in my new Pluralsight course “Single Page Apps with HTML5, ASP.NET Web API, Knockout, and jQuery”.

image

You can catch up on the previous posts in this series here:

More on the Code Camper SPA

Part 1 – The Story Begins (What is the Code Camper SPA?)

Part 2 – Client Technologies

Part 3 – Server Technologies (the Data Layer)

Part 4 – Serving JSON with ASP.NET Web API

Part 5 – HTML 5 and ASP.NET Web Optimization

Part 6 – JavaScript Modules

Part 7 – MVVM and KnockoutJS

Part 8 – Data Services on the Client

Part 9 – Navigation, Transitions, Storage and Messaging

Part 10 – Saving, Change Tracking, and Commanding

Part 11 – Responsive Design and Mobility

Change Tracking

Change tracking can help make a user’s experience better and frankly, can make a developer’s life a little easier. For example, change tracking makes it easier to disable and enable buttons for saving/canceling changes. If no changes have been made, the buttons should be disabled.

image

If changes have been made, then these buttons would be enabled.

image

We can also prevent users from navigating off the page until they have either saved or canceled their changes.

image

So how do you wire up change tracking with a SPA? The Code Camper SPA uses a custom change tracking tool that ties into KnockoutJS. With some special tips from the KnockoutJS creators (Ryan Niemeyer and Steve Sanderson) Hans Fjällemark  and I wrote a component called the DirtyFlag. You can grab the DirtyFlag as part of the KoLite library on github or grab KoLite on NuGet.

image

How do we hook this up?

1. Pull KoLite into your project.

2. Wire up your model and tell it which properties you want to track changes on. (either pass an array of properties or the entire object)

Person = function () {
    var self = this;
    self.id = ko.observable();
    self.firstName = ko.observable();
    self.lastName = ko.observable();
    self.dirtyFlag = new ko.DirtyFlag([
    self.firstName,
    self.lastName);
    return self;
};

3. Create a computed property on your viewmodel and check if the model is dirty or not

isDirty = ko.computed(function() {
    return myPerson().dirtyFlag().isDirty();
})

Now I can check the isDirty property on the ViewModel to see if changes were made or not. I can bind this property to bindings in the View, such as buttons, too.

Commanding

When I’m about to take an asynchronous action that saves data across the wire, I want the user to know the action is still pending (via a activity indicator) and I want to prevent the button from being clicked again. The commanding technique works great for this. Built into KoLite is another component called asyncCommand which handles these situations.

So when a user clicks this button …

image

It should be disabled and show an activity indicator like this …

image

So how do you do this? Pull KoLite into your project and instead of writing a method that saves the data, wrap that in an ko.asyncCommand. Just like the ICommand interface in XAML, asyncCommand has 2 methods you can define: execute and canExecute.

saveCmd = ko.asyncCommand({
    execute: function(complete) {
        $.when(datacontext.persons.updateData(speaker()))
            .always(complete);
    },
    canExecute: function(isExecuting) {
        return !isExecuting && isDirty() && isValid();
    }
})

Simply use the execute for the logic that will perform the save and define the conditions when it is appropriate for the user to be able to execute the command. Then in the HTML set the binding to use the command and optionally, the activity indicator.

<button data-bind="command: saveCmd, activity: saveCmd.isExecuting"> Save</button>
Validation

Validation is a huge part of a good user experience. Users expect to be told how you want the data entered. Nobody wants to enter garbage and have to wait to find out that it’s wrong … or worse, not tell them why its wrong (or even what is wrong).

HTML5 has some great validation support built in, but not everyone can take advantage of HTML5 just yet. So what do we do? This is one reason I like the Knockout.Validation library: it works well without HTML5, but works better with it. This library is written by Eric Barnard and is also available on github and NuGet.

For example, I can make a property required by extending the observable.

self.firstName = ko.observable().extend({ required: true });

You can test this validation out in the live Code Camper demo here.

image

More?

10 posts is good, but there’s one more to go. In the next and final post I’ll cover mobility and responsive web design.

tags: javascript jquery knockout kolite nuget pluralsight
  • Nat

    Enough is enough! It’s time for you to write a book on SPA, John. :)

  • http://www.tradepoint360.com James Hancock

    Great article!
    It’s my understanding that the DirtyFlag only supports that there has been a change. It doesn’t differentiate insert versus update, which of course is super important, especially for REST/WebAPI. Please correct me if I’m wrong.
    If I’m not wrong, is there a work around?

  • Brooks

    Hi John,
    Great stuff! Your Pluralsight course and these posts have really helped me with my SPA. So, thanks!!!
    Another question (or two) for you… How would you handle some sort of failed database cud operation on the client?
    What I mean is knockout is great for keeping the view and viewmodel in sync, and implementing a data service, data context, and Web API are great for updating the database. But, what happens if there’s some failure on the server-side and the data doesn’t get inserted, updated, or deleted in the database?
    I see where the error comes back and a toastr message is displayed. And, that’s great. But, it seems like knockout has already updated the client/view such that the user thinks the operation was successful and the information they entered on the screen was saved successfully to the database.
    Is there any way for knockout to rollback the view and/or viewmodel changes? Or, how would you handle such a situation?
    Hope that makes sense.
    Thanks.

  • Martin

    Hello John :)
    I have a question concerning childentities. If I wanted to add a session and at the same time add a couple of attendences before I actually save to the server,
    Is there a nice and easy way todo this or is it hard work?

  • john

    James,
    Thanks for the feedback. Right, it just tells you if there has been a change. In my experience this is all I needed to tool to do as I can often use other means to figure out if its new or not. That’s what I do in the CodeCamper SPA for the course.
    However, many frameworks like BreezeJS (ideablade.com) and Upshot (asp.net team) may already be planning this feature. If its important to you then I suggest letting those teams know.

  • john

    Brooks,
    Great question. I don;t think its Knockout’s role to handle that, but you could easily code that into your app. When the error occurs, currently I just use toastr to display the message. But you could respond in the viewmodel by canceling the changes or displaying other options to the user. It’s trival to add something in the viewmodel, since you have the error callback.

  • john

    Hi Martin,
    Another great question. What you are asking is for a way to save multiple models on the client in its datacontext and then send to the server. Its somewhere between simple and hard :)
    Adding a new record and its child on the client and then saving it to the server can be done, yes. This does get interesting, but again, I would hope it is something that libraries like BreezeJS or Upshot can make trivial for us in the future.
    As the app stands now, we could add multiple new items to the datacontext and then save. But I did not account for adding parent/child in the same queue. But yes, it could be modified to do so.

  • http://DNAfor.NET Chau Nguyen

    Thanks for the SPA guidance and training,
    1. We’re struggling to figure out the best UI framework/library for Knockout.. I see part 11 has a bit to do with UI but that post is not out yet. Kendo looks great but it’s not that great with KO right now we think? thoughts? we found Wijmo and have you any thought about that one and KO? Any other ones you suggest? I am worried about moving forward with Kendo but not binding them to KO objects.. feels not great. I know they have there own MVVM but haven’t look at it much.. prefer KO. We’ve searched online but can’t find a definitive obvious answer.
    2. I’ve watched all your videos for the SPA on PluralSight, and was well worth the money.. I have not yet read all your 10+ blogs on SPA… anything in the blog posts that the videos don’t cover? I’ll get around to reading them eventually but just wondering..
    Thanks.

  • john

    Hi Chau,
    I replied to your question in another post http:/jpapa.me/spapost7 … in short, I do recommend the Kendo/Knockout library. It’s written by one of the main contributors of KnockoutJS
    Wijmo is also good. Different philosophy though. Kendo built their library from scratch. Wijmo built on top of jQuery UI. Both have their pros and cons. Wijmo is building Knockout binding handlers themselves, so they seem committed to KO support. Either way, you may experience the need to create your own binding handlers.
    Hope this helps.

  • Maulik

    Hi John,
    Will you be releasign source code?
    thanks,

  • john

    Maulik,
    Pluralsight has a “plus’ subscription where you can get all of their source code.

  • Bitbonk

    The links to the previous posts are broken.

  • John

    Bitbonk,

    Thanks. The links should work now. I was converting my site to a new host this past weekend, but they should all be working now.

  • devdells

    John,
    Pluralsight course is great! Thanks for such an awesome learning experience.
    I have the full subscription and dowloaded the course files. One problem, main.js is called in the index page but it is not part of the source code.
    I kind of remember you mentioning it during the course but I cannot find it in any of the source modules for any section of the course.

    Also, there ought to be a warning on the Pluralsight download area about ‘unblocking’ in Win7/8. People will have tons of issues with using the source if they forget to ‘unblock’ the zip file before unzipping.

    • John

      devdells,

      Thanks for the feedback. The main.js file is in the root of the Scripts folder (scripts/main.js) .

    • devdells

      Also, just noticed in module4 ‘after’ source files that codecamper.web/scripts/apps is empty.

      • John

        Through module 4 the course hasn’t covered script files yet, so there are none at that point. There is a completed solution for the course that has everything in the root of the materials you can download. The ones in the module folders are so you can follow along and only show what we just covered.

        • devdell

          Not in the download I have which is fresh from yesterday. Perhaps I should catalog the problems I’m seeing with running the solution (there are more). I’m using VS11 on Win8.
          Perhaps I should not use the windows decompress.

          • John

            You should contact Pluralsight support and see why you aren’t getting the proper download. I;ve forwarded this to them, but its good to go to them directly.

        • devdells

          Got it! I completely missed that other zip file with the finished project. Thanks for bearing with me, and I did leave a comment up at Pluralsight,
          One very minor thing that surfaced for me is the dbconnection string. When I opened the samples and the final I found this for the connection string in the Server Explorer:
          CodeCamper\CodeCamper.Web\App_DataCodeCamper.sdf
          The lack of the backslash after App_Data just threw me off a bit although it did not cause any issues with running the app.
          Thanks again for the incredible work you have done with this course. It’s helping me immensely and I will be studying it for weeks.
          Super effort John!

          • John

            Thanks. I’m glad you got it to work :)

  • Jeff M

    Hello John. I’m enjoying your Pluralsight course, now for the second time – - this time reproducing your framework into my own application. But for this course, it would have taken hundreds of hours for me to have designed such a framework myself and Microsoft itself offers precious little on the subject of SPAs. For that I thank you. But one must ask oneself: “is this really progress?”. JavaScript-based clients, such as an SPA, relegates Visual Studio 2012 to the much diminished responsibility of merely serving up JSON payloads. I find that even with your carefully designed framework, there is a tremendous amount of entity-specific hand-written JavaScript coding that is required. This impresses me as a maintanance nightmare. All of the sudden, I feel like I’m coding again as if it’s 1992 – - and this doesn’t feel so good. Do you share these feelings?

    • John

      Hi Jeff,

      Thanks for the well thought out comments. I do think it is progress, but in the area of “reach”. There are absolutely places where web dev has advantages in reach, and there are also places where its not as rich as in other tech. The key pain point for me is the data on the client side … this is where there is a huge opportunity for libraries like Breeze and Upshot (or some up and comer) to make a dent. I am very hopeful for that, as writing your own data context is certainly possible (I did it in the course) but its plumbing that I prefer to be done for me.

  • robegrei

    Hi John,

    Thanks for the great course. However I’ve encoutnered one small but important issue – IsValid computable property (flag) doesn’t re-evaluate on first page load in your Speaker ViewModel.

    Try to open your demo edit form directly and delete your name – http://johnpapa.aspnet45.cytanium.com/#/speakers/3 . The validation works however I get Save button enabled due to isValid flag not being refreshed and it returns “true”. But it happens if ONLY you open the form directly by entering URL or refreshing the page (F5, CTRL+F5) and it works as expected otherwise…

    I was tearing my hairs out and kind of workaround is mentioned in this blog entry – http://blog.greatrexpectations.com/2012/07/12/forcing-computed-observables-to-refresh-in-knockout/ . However I’m not sure if it’s really the case and why it doesn’t work on refresh only. Could you take a look at it and let me (us) know if you will finda any solution regarding it?

    Regards,
    Rob

    • robegrei

      Hi John,

      There are some other issues with the first load actually – navigation, validation, state management. It’s because some variables are not set or set after the page is loaded on the first load.

    • Justin Spradlin

      I was just wondering if there was ever any resolution to the issue with isValid not recomputing? I have spent a little time on this issue and cannot get it to work properly either. I used the hack listed above, but it feels really dirty to have to do that every time I want to access isValid().

      Any ideas?

  • exo

    hi John,

    I enjoyed the course. Good work!

    though it s a bit advanced for someone like me I was trying to understand your code. In general from courses the code is understandable. However could you please enlighten me these lines

    var findDs = function () {
    var ds = window.testFn(amplify);
    config.useMocks(true); // this helps me NOT mock datacontext
    return ds;
    //return window.testFn($); // purely for the course to test the non amplify version
    }

    window.defineSaved = window.define;

    window.define = function (a, b, c) {
    a === ‘vm.speakers’ ? window.testFn = c : defineSaved(a, b, c);
    };

    particularly the purpose of window.testfn()?
    thank you.

    • John

      Hi Exo,

      Thanks, glad you enjoy the course.

      It looks like you are examining the test code. The idea here is I want to test the vm.speakers module. To do that, I don’t want to load other dependencies. Instead I want to replace the real dependencies with mock versions of them. THis allows me to test the vm.speakers without also running code for the dataservice, for example.

      Specifically, I first tell the code to use mock data, so I dont hit the web services but I still get data. Then I want to change how require.js ‘s define method works. So I save the original version of the define method, create a new version that I override that will just run the unit test method. I admit, its a bit quirky, but it does allow me to run unit tests without loading other modules automatically via require.js :)

  • kiquenet

    Marvellous article series, it’s great

    Source code for Code Camper is available ?? thanks

    • John

      kiquenet,

      Thanks. The code is available via Pluralsight Plus subscription.

  • Pingback: EFPF in version 2.0 erschienen | CSharp-Paradise.de

  • Alec von Brand

    Hi John,

    Your app has really opened my eyes. It provides all the structure and more than I am used to achieving server side. I have started porting a medium sized app over to this model. All has worked well, all the basic functionality with AMD, routing, amplify etc. works great.

    Now though getting to a little finer grain, I am hitting a snag that has me stuck and that I cannot understand. I want to implement an asynCommand to submit an update through the data context.
    However, everytime I click the button to submit the update, I get a postback and then sammy inovikes the default route. In the debugger I can only see that the ko.asyncCommand function gets invoked int he knockout.asyncCommand.js library but I never see the method in the datacontext getting inovoked. Before I ported over to the asynch command, I would call e.preventDefault() in the jquery click handler.

    But in debugging Code Camper I never see a post back and the router never gets called. It just works. So I do not know why I am getting the post back and in the definition of the async command there seems to be no way to call something like e.preventDefault.

    In the Html my button is declared so:

    Save Plan

    The asynch command in the viewmodel is declared so:

    updateStudDOICmd = ko.asyncCommand({
    execute: function (complete) {
    $.when(datacontext.doiItem.updateData(doiItem()))
    .always(complete);
    return;
    },
    canExecute: function(isExecuting) {
    return !isExecuting;
    }

    })

    I am stumped. If you have a quick idea what could be going on with this I would appreciate it.

    • Alec von Brand

      Never mind. I have found the problem, a stupid mistake. Like the rest of the code camper code, it just works. Really great stuff.

  • Jos C
%d bloggers like this: