How to "delay" route changes


#1

Arising from this discussion:

My suggestion was to create a “shield” observable value that gets used instead of route.data.

This can be done something like:

AppVM = DefineMap.extend({
  // the actual stateful values from the url
  routeData: {default() { return new DefineMap() } },

  // if we have some state where we don't want it to look like the url has changed right away
  isSaving: "boolean",

  // A getter used mostly so we can know when any data in the URL changed
  get routeDataClone(){
    return this.routeData.get()
  },

  // this will have the old `routeData` if isSaving is true:
  shieldData: {
    value({resolve, listenTo}){
     // A place to store the last route data
     var savedData;

     // When the route data changes
     listenTo("routeDataClone", function(ev, newData){
       // if we are saving, save the new data
       if(this.isSaving){
         savedData = newData;
       } else {
          resolve(newData); // if we aren't saving, update
       }
     })
    
     listenTo("isSaving", function(ev,newValue){
        // when saving state changes to false
        // check if there was some route changes and apply them
        if(!newValue && savedData !== undefined) {
          resolve(savedData);
          savedData = undefined;
        }
     })
    }
  }
});

var appVM = new AppVM();
route.data = appVM.routeData;

Note, this doesn’t allow someone to change a shieldData value and have it update the URL. For that sort of two-way binding, I would probably set this up in connectedCallback() with the new can-bind for 5.0:

routeData: { ... },
get routeDataClone() { ... }
shieldData: {...},
get shieldDataClone() { ... }
connectedCallback(){
  var savedData;
  var binding = new Bind({
    parent: value(this, "routeDataClone"),
    child: value(this,"shieldDataClone"),
    updateParent: (shieldData) => {
      this.routeData.update( shieldData )
    },
    updateChild: (shieldData) => {
      if(!this.saving){
        this.routeData.update( shieldData )
      } else {
        savedData = sheildData;
      }
    }
  })
  binding.start();

   // LISTEN ON isSaving like the first code example
  return binding.stop.bind(binding);

}

#2

Thanks for your feedback Justin!

However I can’t make it work. I don’t understand your second code block with can-bind. Is there an alternative for Can4?
I’ve tried setting up routing with shieldData. route.data = appVM.shieldData but that doesn’t work.

Here is a JSBin with an example: https://jsbin.com/tiyorov/7/edit?html,js,console,output
The “only” thing missing is a way to prevent the routing confirm() --> else.


#3

It works here: http://jsbin.com/hunohu/edit?html,js,console,output

I just had to add a resolve directly w/i the shieldData to make sure shieldData has an initial value:

resolve(this.routeDataClone);

#4

Thanks! Now I need to figure out how to make it work in our app :slight_smile:


#5

I’ve looked at your code more closely and this is kind of the same solution as I’ve come up with.
But it has an issue:

You can’t see it in jsbin, but the window hash actually changes.
When you cancel the confirm, the routing is stuck until you uncheck the isSaving. Because the “real” route already is set and the listenTo won’t get triggered.

Here is your example updated with a confirm:


#6

Yes, the hash changes. You can’t prevent that if someone’s clicking the back button. The user controls the URL.

I think the best you can do it update the url back to what it should be if someone wants to stay on the same page.


#7

I’ve tried resetting the url, but then I got stuck in an infinite loop. It would always trigger the showConfirm = true inside the listenTo.

Anyway, thanks for your help!