Search code examples
swiftsiesta-swift

Does Siesta provide any special handling for pagination?


How does Siesta handle paginated URLs? Is there a mechanism to manage multiple pages of results as a single resource?


Solution

  • Siesta doesn’t provide any special handling for pagination. Paginated URLs behave just like any others: every URL is a unique resource. Siesta does not have any dark magic to merge responses from different URLs into a single resource.

    In other words, if your pagination scheme looks like /dingbats?page=3&per_page=10, then Siesta considers “page 3 of dingbats with 10 per page” to be a single resource. If your pagination scheme looks like /dingbats?since_serial_num=31415&max=20, then Siesta will have one resource for “up to 20 dingbats since serial number 31415.”

    Example

    What does this mean in practice? Suppose, for example, that your API returns an X-Next-Page header (a simplified flavor of Github’s pagination scheme), and you want to merge the results into a single infinitely scrollable table view.

    You might do something like this:

    private var firstPageOfDingbats: Resource?
    private var dingbats: [Dingbat] = []
    private var dingbatsDisplayed: Int = 1
    
    func resourceChanged(resource: Resource, event: ResourceEvent) {
      refreshPages()
    }
    
    func refreshPages() {
      // Naive implementation reconstructs the entire dingats array
      // with each update — which is probably just fine, since array
      // concatenation is cheap.
    
      dingbats.removeAll()
    
      var nextPage = firstPageOfDingbats
      while let curPage = nextPage {
        // This is a noop if data is up to date, but triggers a
        // that runs down the list if it needs updating.
    
        curPage.loadIfNeeded()
    
        // Assuming you’ve set up a content transformer to parse
        // the response as [Dingbat], you can pull them out of
        // the Resource cheaply.
    
        dingbats.appendContentsOf(
          curPage.contentAsType(ifNone: [Dingbat]()))
    
        // Don’t fetch everything if we’ve covered as far as the
        // user has scrolled.
    
        if dingbats.count >= dingbatsDisplayed {
          break
        }
    
        // If we have data and there’s another page, keep going!
    
        nextPage = curPage.optionalRelative(
          curPage.latestData?.header("X-Next-Page"))
      }
    
      tableView.reloadData()
    }
    
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      return dingbats.count
    }
    // etc.
    
    // Add code to increase dingbatsDisplayed and call refreshPages()
    // when user scrolls to the bottom
    

    Siesta’s caching makes this naive traversal of the pages run very fast if there’s already data present and everything is up to date, but will trigger a cascade of updates when things get out of date.

    If you know, for example, that old entries will never change and new entries will only appear at the top, you could do a more intelligent array update. This depends on the particular API you’re using and the guarantees it makes.

    Bypassing Siesta’s caching

    If you have a sophisticated update scheme and want fine-grained control over exactly what’s requested and how results are held in memory, then you may want to bypass Siesta’s resource caching altogether. You don’t need to abandon Siesta to do that, however! To fall back to a more traditional request-based approach, use the Resource.request(…) methods instead of load() and loadIfNeeded():

    paginatedResource.request(.GET).success { newData in … }
    

    This lets you manage paginatedResource requests yourself, while still using Siesta’s caching for other parts of the API.