Skip to content

Instantly share code, notes, and snippets.

@thehydroimpulse
Last active August 29, 2015 14:08
Show Gist options
  • Save thehydroimpulse/46ce18966a07b105afd4 to your computer and use it in GitHub Desktop.
Save thehydroimpulse/46ce18966a07b105afd4 to your computer and use it in GitHub Desktop.
Offline experience
# Provide an interface for working with an offline cache
# that makes it easy to create an offline experience.
class Jobber.Cache
@extend Jobber.EmitterMixin
# An in-memory cache for simplistic case. This should be something on disk to ensure
# the persistence quality one needs for an offline experience.
#
# @property _cache Array<CacheItem>
@_cache: []
# Put a new item into the cache
#
# @param {Any} request
# @param {Any} item
# @chainable
# @return Jobber.Cache
@put: (request, item) ->
@_cache[request] = item
@_cache.push item
this
@matches: (request) ->
for key, value of @_cache
if value is request
return Promise.resolve(value)
Promise.reject()
getPhotos = () ->
return new Promise( (resolve, reject) ->
cachedData = null
# Try and fetch from the cache first.
Jobber.Cache.matches('http://flickr.com/api/photos/1').then((item) ->
cachedData = item
# This is where we would do some DOM operations or something with the cached data. The consumers
# could listen on a `cached` event and do some early work so the user can use the cached data **before**
# the API calls are triggered.
).finally ->
# The cache might have failed or succeeded, we don't care. We'll try and
# retrive the most up-to-date version anyways.
Jobber.Request.create('http://flickr.com/api/photos/1').exec().then( (res, req) ->
# Update the cache with the up-to-date version if we ever get to this state in the app.
Jobber.Cache.put req, res.body.photos
resolve(res.body.photos);
).fail ->
# Well, we failed to perform an HTTP request. We're probably in an offline state.
jobberApp.offline(1);
if cachedData?
return resolve(cachedData)
else
return reject("No internet connectivity, failed to do work!")
)
# Show a simple spinner
# @global
showSpinner = ->
$('.spinner').show()
Promise.resolve()
# Hide the global spinner
# @global
hideSpinner = ->
$('.spinner').hide()
Promise.resolve()
# Now we can **easily** fetch the photos effectively:
showSpinner()
.then(getPhotos)
.then( (photos) ->
# Do something with the photos. These can be cached photos or
# up-to-date versions, we don't really care.
photosEl = $ '.photos'
containerEl = document.createElement 'div'
for photo in photos
el = document.createElement 'div'
el.classLists.add 'photo'
el.innerHTML = "<img src='#{photo.src}' alt='#{photo.alt}' />"
# Append each photo element into a container div. This still isn't
# touching the DOM and is used to minimize the amount of operations we perform on
# the DOM. Instead of performing an `.append` for each photo, which is at least O(n)
# (Not counting the DOM internals work) we now achieve O(1) which is much better.
containerEl.append el
# Add the container to the DOM.
photosEl.append containerEl
).fail( (err) ->
# Show a small error message to say we have failed.
el = $('.js-errorMessage')
el.innerHTML = err
el.show()
).finally(hideSpinner)
# A slightly more abstracted version now using a `Resource` concept. Resources build
# on-top of a `Request` and `Cache` seeing as there's a common idiom being formed in the above
# file. Resource will check the cache first, then do the request and propagate errors exactly
# the same way.
#
# But, this is super short and clear and this fully works offline.
getPhotos = () ->
Jobber.Resource('http://flickr.com/api/photos/1').then (res) ->
res.body.photos
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment