Time travel

Time traveling is a tricky proposition in a web based application. This is because data can be retrieved from the server at random intervals.

Let's see what I mean. For this purpose I will use an example from an application where we use One in production.

The app allows users to create and edit stories that contain images and text. Each story has many sections that can be downloaded in a paged manner. Each section can have many cells. Each cell can contain a single image for display.

Our initial approach was to load the story and proceed to put each edit step on a single thread. This is what it looks like

  • The story loads from the server with the first 5 sections pre-loaded

  • Start editing

  • Scroll to edit a deeper section - this loads the next page of sections

  • Start editing a cell

  • Move the cell

Next we're going to add an image to the cell. For this a list of images needs to be downloaded from the server. All images must be added to the cache to provide object uniqueness. This is where things get tricky since this is a background operation that happens on the same thread. See node 5 highlighted.

  • Get image list from server (background op)

  • Assign an image

  • Assign another image etc.

You can see that traveling back from node 6 a user will expect to land on node 4. Instead the undo operation lands the cache on node 5 which doesn't reflect as expected in the UI.

Linear time travel

You may travel the cache back and forth one node at a time

  One.put({uid:1});
  One.put({uid:1, val:"first"};
  One.put({uid:1, val:"second"};

  console.log(One.get(1).val) // "second"
  One.undo();
  console.log(One.get(1).val) // "first"
  One.undo();
  console.log(One.get(1).val) // "undefined"
  One.redo();
  One.redo();
  console.log(One.get(1).val) // "second"

Time bookmarking

There are 2 different ways to bookmark a specific node in the cache. This allows you to jump back and forth to specific locations.

  • By node index

    One.put({uid:1});
    One.put({uid:1, val:"first"};
    
    // record the current index (0 based)
    let index1 = One.index();
    console.log(index1); // 1
    
    // record the end index
    One.put({uid:1, val:"second"};
    let endIndex = One.index();
    console.log(endIndex); // 2
    
    // jump back to index 1
    One.index(index1);
    console.log(One.get(1).val); // "first"
  • By node id. You might choose to discard some nodes to manage memory more efficiently. If so the index might me pointing to a new node. To guarantee that you navigate to the same exact node you may locate the node by id.

    One.put({uid:1});
    // get the node id
    let firstNode = One.node();
    
    One.put({uid:1, val:"first"};
    One.put({uid:1, val:"second"});
    
    // jump back to the first node
    One.node(firstNode);
    console.log(One.get(1).val); // "undefined"

Last updated