2014-04-09 15:11:59 -04:00
|
|
|
module.exports = class SuperModel extends Backbone.Model
|
2014-01-03 13:32:13 -05:00
|
|
|
constructor: ->
|
2014-04-12 16:29:49 -04:00
|
|
|
@num = 0
|
|
|
|
@denom = 0
|
|
|
|
@progress = 0
|
|
|
|
@resources = {}
|
|
|
|
@rid = 0
|
2014-05-14 12:24:52 -04:00
|
|
|
@maxProgress = 1
|
2014-04-12 16:29:49 -04:00
|
|
|
|
2014-01-03 13:32:13 -05:00
|
|
|
@models = {}
|
|
|
|
@collections = {}
|
2014-04-28 19:31:51 -04:00
|
|
|
|
2014-04-26 15:54:03 -04:00
|
|
|
# Since the supermodel has undergone some changes into being a loader and a cache interface,
|
|
|
|
# it's a bit wonky to use. The next couple functions are meant to cover the majority of
|
|
|
|
# use cases across the site. If they are used, the view will automatically handle errors,
|
|
|
|
# retries, progress, and filling the cache. Note that the resource it passes back will not
|
|
|
|
# necessarily have the same model or collection that was passed in, if it was fetched from
|
|
|
|
# the cache.
|
2014-05-21 18:33:28 -04:00
|
|
|
|
2014-05-21 17:50:27 -04:00
|
|
|
report: ->
|
|
|
|
# Useful for debugging why a SuperModel never finishes loading.
|
2014-06-30 22:16:26 -04:00
|
|
|
console.info 'SuperModel report ------------------------'
|
2014-05-21 17:50:27 -04:00
|
|
|
console.info "#{_.values(@resources).length} resources."
|
|
|
|
unfinished = []
|
2014-05-21 18:33:28 -04:00
|
|
|
for resource in _.values(@resources) when resource
|
2014-06-30 22:16:26 -04:00
|
|
|
console.info "\t", resource.name, 'loaded', resource.isLoaded
|
2014-05-21 17:50:27 -04:00
|
|
|
unfinished.push resource unless resource.isLoaded
|
|
|
|
unfinished
|
2014-04-28 19:31:51 -04:00
|
|
|
|
2014-04-26 15:54:03 -04:00
|
|
|
loadModel: (model, name, fetchOptions, value=1) ->
|
|
|
|
cachedModel = @getModelByURL(model.getURL())
|
|
|
|
if cachedModel
|
|
|
|
if cachedModel.loaded
|
|
|
|
res = @addModelResource(cachedModel, name, fetchOptions, 0)
|
|
|
|
res.markLoaded()
|
|
|
|
return res
|
|
|
|
else
|
|
|
|
res = @addModelResource(cachedModel, name, fetchOptions, value)
|
|
|
|
res.markLoading()
|
|
|
|
return res
|
|
|
|
else
|
|
|
|
@registerModel(model)
|
2014-05-08 13:54:39 -04:00
|
|
|
res = @addModelResource(model, name, fetchOptions, value)
|
2014-05-08 14:10:22 -04:00
|
|
|
if model.loaded then res.markLoaded() else res.load()
|
2014-05-08 13:54:39 -04:00
|
|
|
return res
|
2014-04-28 19:31:51 -04:00
|
|
|
|
2014-04-26 15:54:03 -04:00
|
|
|
loadCollection: (collection, name, fetchOptions, value=1) ->
|
|
|
|
url = collection.getURL()
|
|
|
|
if cachedCollection = @collections[url]
|
2014-04-28 14:09:21 -04:00
|
|
|
console.debug 'Collection cache hit', url, 'already loaded', cachedCollection.loaded
|
|
|
|
if cachedCollection.loaded
|
2014-04-26 15:54:03 -04:00
|
|
|
res = @addModelResource(cachedCollection, name, fetchOptions, 0)
|
|
|
|
res.markLoaded()
|
|
|
|
return res
|
|
|
|
else
|
|
|
|
res = @addModelResource(cachedCollection, name, fetchOptions, value)
|
|
|
|
res.markLoading()
|
|
|
|
return res
|
|
|
|
else
|
|
|
|
@addCollection collection
|
2014-07-30 16:23:43 -04:00
|
|
|
onCollectionSynced = (c) ->
|
|
|
|
if collection.url is c.url
|
|
|
|
@registerCollection c
|
|
|
|
else
|
|
|
|
console.warn 'Sync triggered for collection', c
|
|
|
|
console.warn 'Yet got other object', c
|
|
|
|
@listenToOnce collection, 'sync', onCollectionSynced
|
|
|
|
@listenToOnce collection, 'sync', onCollectionSynced
|
2014-05-08 13:54:39 -04:00
|
|
|
res = @addModelResource(collection, name, fetchOptions, value)
|
|
|
|
res.load() if not (res.isLoading or res.isLoaded)
|
|
|
|
return res
|
2014-04-25 19:14:05 -04:00
|
|
|
|
|
|
|
# replace or overwrite
|
|
|
|
shouldSaveBackups: (model) -> false
|
2014-04-28 19:31:51 -04:00
|
|
|
|
2014-04-25 17:30:06 -04:00
|
|
|
# Caching logic
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
getModel: (ModelClass_or_url, id) ->
|
|
|
|
return @getModelByURL(ModelClass_or_url) if _.isString(ModelClass_or_url)
|
|
|
|
m = new ModelClass_or_url(_id: id)
|
2014-04-26 15:54:03 -04:00
|
|
|
return @getModelByURL(m.getURL())
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
getModelByURL: (modelURL) ->
|
2014-04-25 22:11:32 -04:00
|
|
|
modelURL = modelURL() if _.isFunction(modelURL)
|
2014-01-03 13:32:13 -05:00
|
|
|
return @models[modelURL] or null
|
2014-08-06 18:18:22 -04:00
|
|
|
|
|
|
|
getModelByOriginal: (ModelClass, original) ->
|
|
|
|
_.find @models, (m) ->
|
|
|
|
m.get('original') is original and m.constructor.className is ModelClass.className
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
getModelByOriginalAndMajorVersion: (ModelClass, original, majorVersion=0) ->
|
|
|
|
_.find @models, (m) ->
|
2014-08-26 17:15:21 -04:00
|
|
|
return unless v = m.get('version')
|
|
|
|
m.get('original') is original and v.major is majorVersion and m.constructor.className is ModelClass.className
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
getModels: (ModelClass) ->
|
|
|
|
# can't use instanceof. SuperModel gets passed between windows, and one window
|
|
|
|
# will have different class objects than another window.
|
|
|
|
# So compare className instead.
|
|
|
|
return (m for key, m of @models when m.constructor.className is ModelClass.className) if ModelClass
|
|
|
|
return _.values @models
|
|
|
|
|
2014-04-25 17:30:06 -04:00
|
|
|
registerModel: (model) ->
|
2014-04-26 15:54:03 -04:00
|
|
|
@models[model.getURL()] = model
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
getCollection: (collection) ->
|
2014-04-26 15:54:03 -04:00
|
|
|
return @collections[collection.getURL()] or collection
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
addCollection: (collection) ->
|
2014-04-26 15:54:03 -04:00
|
|
|
# TODO: remove, instead just use registerCollection?
|
|
|
|
url = collection.getURL()
|
|
|
|
if @collections[url]? and @collections[url] isnt collection
|
2014-01-03 13:32:13 -05:00
|
|
|
return console.warn "Tried to add Collection '#{url}' to SuperModel when we already had it."
|
2014-04-26 15:54:03 -04:00
|
|
|
@registerCollection(collection)
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-04-26 15:54:03 -04:00
|
|
|
registerCollection: (collection) ->
|
2014-04-28 17:58:58 -04:00
|
|
|
@collections[collection.getURL()] = collection if collection.isCachable
|
2014-01-03 13:32:13 -05:00
|
|
|
# consolidate models
|
|
|
|
for model, i in collection.models
|
2014-04-26 15:54:03 -04:00
|
|
|
cachedModel = @getModelByURL(model.getURL())
|
2014-01-03 13:32:13 -05:00
|
|
|
if cachedModel
|
|
|
|
collection.models[i] = cachedModel
|
|
|
|
else
|
2014-04-25 17:30:06 -04:00
|
|
|
@registerModel(model)
|
2014-01-03 13:32:13 -05:00
|
|
|
collection
|
2014-04-28 19:31:51 -04:00
|
|
|
|
2014-04-26 15:54:03 -04:00
|
|
|
# Tracking resources being loaded for this supermodel
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
finished: ->
|
2014-04-26 15:54:03 -04:00
|
|
|
return @progress is 1.0 or not @denom
|
2014-04-09 15:11:59 -04:00
|
|
|
|
2014-04-12 16:29:49 -04:00
|
|
|
addModelResource: (modelOrCollection, name, fetchOptions, value=1) ->
|
2014-06-23 16:56:32 -04:00
|
|
|
modelOrCollection.saveBackups = modelOrCollection.saveBackups or @shouldSaveBackups(modelOrCollection)
|
2014-04-09 15:11:59 -04:00
|
|
|
@checkName(name)
|
|
|
|
res = new ModelResource(modelOrCollection, name, fetchOptions, value)
|
2014-04-12 16:29:49 -04:00
|
|
|
@storeResource(res, value)
|
2014-04-09 15:11:59 -04:00
|
|
|
return res
|
|
|
|
|
2014-05-21 18:33:28 -04:00
|
|
|
removeModelResource: (modelOrCollection) ->
|
|
|
|
@removeResource _.find(@resources, (resource) -> resource?.model is modelOrCollection)
|
|
|
|
|
2014-04-12 16:29:49 -04:00
|
|
|
addRequestResource: (name, jqxhrOptions, value=1) ->
|
2014-04-09 15:11:59 -04:00
|
|
|
@checkName(name)
|
|
|
|
res = new RequestResource(name, jqxhrOptions, value)
|
2014-04-12 16:29:49 -04:00
|
|
|
@storeResource(res, value)
|
2014-04-09 15:11:59 -04:00
|
|
|
return res
|
|
|
|
|
2014-04-12 16:29:49 -04:00
|
|
|
addSomethingResource: (name, value=1) ->
|
2014-04-09 15:11:59 -04:00
|
|
|
@checkName(name)
|
|
|
|
res = new SomethingResource(name, value)
|
2014-04-12 16:29:49 -04:00
|
|
|
@storeResource(res, value)
|
2014-04-09 15:11:59 -04:00
|
|
|
return res
|
|
|
|
|
2014-04-12 16:29:49 -04:00
|
|
|
checkName: (name) ->
|
2014-04-09 15:11:59 -04:00
|
|
|
if not name
|
|
|
|
throw new Error('Resource name should not be empty.')
|
|
|
|
|
2014-04-12 16:29:49 -04:00
|
|
|
storeResource: (resource, value) ->
|
|
|
|
@rid++
|
|
|
|
resource.rid = @rid
|
|
|
|
@resources[@rid] = resource
|
2014-04-25 17:30:06 -04:00
|
|
|
@listenToOnce(resource, 'loaded', @onResourceLoaded)
|
|
|
|
@listenTo(resource, 'failed', @onResourceFailed)
|
2014-04-12 16:29:49 -04:00
|
|
|
@denom += value
|
2014-05-13 17:39:45 -04:00
|
|
|
_.defer @updateProgress if @denom
|
2014-04-09 15:11:59 -04:00
|
|
|
|
2014-05-21 18:33:28 -04:00
|
|
|
removeResource: (resource) ->
|
|
|
|
return unless @resources[resource.rid]
|
|
|
|
@resources[resource.rid] = null
|
|
|
|
--@num if resource.isLoaded
|
|
|
|
--@denom
|
|
|
|
_.defer @updateProgress
|
|
|
|
|
2014-04-12 16:29:49 -04:00
|
|
|
onResourceLoaded: (r) ->
|
2014-05-21 18:33:28 -04:00
|
|
|
return unless @resources[r.rid]
|
2014-04-25 17:30:06 -04:00
|
|
|
@num += r.value
|
|
|
|
_.defer @updateProgress
|
2014-05-22 14:47:38 -04:00
|
|
|
r.clean()
|
2014-08-07 21:27:47 -04:00
|
|
|
@trigger 'resource-loaded', r
|
2014-04-09 15:11:59 -04:00
|
|
|
|
2014-05-26 23:51:05 -04:00
|
|
|
onResourceFailed: (r) ->
|
2014-05-21 18:33:28 -04:00
|
|
|
return unless @resources[r.rid]
|
2014-05-26 23:51:05 -04:00
|
|
|
@trigger('failed', resource: r)
|
2014-05-22 14:47:38 -04:00
|
|
|
r.clean()
|
2014-04-09 15:11:59 -04:00
|
|
|
|
2014-04-25 17:30:06 -04:00
|
|
|
updateProgress: =>
|
2014-04-28 19:31:51 -04:00
|
|
|
# Because this is _.defer'd, this might end up getting called after
|
2014-04-25 17:30:06 -04:00
|
|
|
# a bunch of things load all at once.
|
|
|
|
# So make sure we only emit events if @progress has changed.
|
2014-04-26 15:54:03 -04:00
|
|
|
newProg = if @denom then @num / @denom else 1
|
2014-05-14 12:24:52 -04:00
|
|
|
newProg = Math.min @maxProgress, newProg
|
|
|
|
return if @progress >= newProg
|
2014-04-26 15:54:03 -04:00
|
|
|
@progress = newProg
|
2014-04-25 17:30:06 -04:00
|
|
|
@trigger('update-progress', @progress)
|
2014-04-12 16:29:49 -04:00
|
|
|
@trigger('loaded-all') if @finished()
|
2014-05-20 13:40:07 -04:00
|
|
|
|
2014-05-14 12:24:52 -04:00
|
|
|
setMaxProgress: (@maxProgress) ->
|
2014-05-14 14:13:36 -04:00
|
|
|
resetProgress: -> @progress = 0
|
2014-05-14 12:24:52 -04:00
|
|
|
clearMaxProgress: ->
|
|
|
|
@maxProgress = 1
|
|
|
|
_.defer @updateProgress
|
2014-05-20 13:40:07 -04:00
|
|
|
|
2014-04-12 16:29:49 -04:00
|
|
|
getProgress: -> return @progress
|
2014-04-09 15:11:59 -04:00
|
|
|
|
2014-04-25 18:31:38 -04:00
|
|
|
getResource: (rid) ->
|
|
|
|
return @resources[rid]
|
2014-04-28 19:31:51 -04:00
|
|
|
|
2014-04-09 15:11:59 -04:00
|
|
|
class Resource extends Backbone.Model
|
2014-04-12 16:29:49 -04:00
|
|
|
constructor: (name, value=1) ->
|
2014-04-09 15:11:59 -04:00
|
|
|
@name = name
|
|
|
|
@value = value
|
2014-04-12 16:29:49 -04:00
|
|
|
@rid = -1 # Used for checking state and reloading
|
2014-04-09 15:11:59 -04:00
|
|
|
@isLoading = false
|
|
|
|
@isLoaded = false
|
|
|
|
@model = null
|
2014-04-25 17:30:06 -04:00
|
|
|
@jqxhr = null
|
2014-04-09 15:11:59 -04:00
|
|
|
|
2014-04-12 16:29:49 -04:00
|
|
|
markLoaded: ->
|
2014-04-25 17:30:06 -04:00
|
|
|
return if @isLoaded
|
|
|
|
@trigger('loaded', @)
|
2014-04-09 15:11:59 -04:00
|
|
|
@isLoaded = true
|
|
|
|
@isLoading = false
|
|
|
|
|
2014-04-25 17:30:06 -04:00
|
|
|
markFailed: ->
|
|
|
|
return if @isLoaded
|
2014-05-26 23:51:05 -04:00
|
|
|
@trigger('failed', @)
|
2014-04-25 17:30:06 -04:00
|
|
|
@isLoaded = @isLoading = false
|
2014-04-25 18:31:38 -04:00
|
|
|
@isFailed = true
|
2014-04-28 19:31:51 -04:00
|
|
|
|
2014-04-25 17:30:06 -04:00
|
|
|
markLoading: ->
|
2014-04-25 18:31:38 -04:00
|
|
|
@isLoaded = @isFailed = false
|
2014-04-25 17:30:06 -04:00
|
|
|
@isLoading = true
|
2014-05-26 23:51:05 -04:00
|
|
|
|
2014-05-22 14:47:38 -04:00
|
|
|
clean: ->
|
|
|
|
# request objects get rather large. Clean them up after the request is finished.
|
|
|
|
@jqxhr = null
|
2014-04-28 19:31:51 -04:00
|
|
|
|
2014-04-25 17:30:06 -04:00
|
|
|
load: -> @
|
|
|
|
|
2014-04-09 15:11:59 -04:00
|
|
|
class ModelResource extends Resource
|
|
|
|
constructor: (modelOrCollection, name, fetchOptions, value)->
|
|
|
|
super(name, value)
|
|
|
|
@model = modelOrCollection
|
|
|
|
@fetchOptions = fetchOptions
|
2014-05-02 13:31:20 -04:00
|
|
|
@jqxhr = @model.jqxhr
|
2014-04-09 15:11:59 -04:00
|
|
|
|
2014-04-12 16:29:49 -04:00
|
|
|
load: ->
|
2014-04-25 17:30:06 -04:00
|
|
|
@markLoading()
|
2014-04-17 22:16:29 -04:00
|
|
|
@fetchModel()
|
2014-04-25 17:30:06 -04:00
|
|
|
@
|
2014-04-09 15:11:59 -04:00
|
|
|
|
2014-04-17 22:16:29 -04:00
|
|
|
fetchModel: ->
|
2014-04-25 17:30:06 -04:00
|
|
|
@jqxhr = @model.fetch(@fetchOptions) unless @model.loading
|
|
|
|
@listenToOnce @model, 'sync', -> @markLoaded()
|
|
|
|
@listenToOnce @model, 'error', -> @markFailed()
|
|
|
|
|
2014-05-22 14:47:38 -04:00
|
|
|
clean: ->
|
|
|
|
@jqxhr = null
|
|
|
|
@model.jqxhr = null
|
2014-04-09 15:11:59 -04:00
|
|
|
|
|
|
|
class RequestResource extends Resource
|
2014-04-12 16:29:49 -04:00
|
|
|
constructor: (name, jqxhrOptions, value) ->
|
2014-04-09 15:11:59 -04:00
|
|
|
super(name, value)
|
|
|
|
@jqxhrOptions = jqxhrOptions
|
|
|
|
|
2014-04-12 16:29:49 -04:00
|
|
|
load: ->
|
2014-04-25 17:30:06 -04:00
|
|
|
@markLoading()
|
2014-04-28 14:09:21 -04:00
|
|
|
@jqxhr = $.ajax(@jqxhrOptions)
|
|
|
|
# make sure any other success/fail callbacks happen before resource loaded callbacks
|
|
|
|
@jqxhr.done => _.defer => @markLoaded()
|
|
|
|
@jqxhr.fail => _.defer => @markFailed()
|
2014-04-25 17:30:06 -04:00
|
|
|
@
|
2014-04-09 15:11:59 -04:00
|
|
|
|
|
|
|
class SomethingResource extends Resource
|