codecombat/app/models/SuperModel.coffee

299 lines
7.7 KiB
CoffeeScript
Raw Normal View History

2014-04-09 15:11:59 -04:00
module.exports = class SuperModel extends Backbone.Model
2014-01-03 13:32:13 -05:00
constructor: ->
@num = 0
@denom = 0
@showing = false
@progress = 0
@resources = {}
@rid = 0
2014-01-03 13:32:13 -05:00
@models = {}
@collections = {}
@schemas = {}
2014-01-03 13:32:13 -05:00
2014-04-19 12:08:40 -04:00
# setInterval(@checkModelStatus, 5000)
# For debugging
checkModelStatus: =>
for key, res of @resources
continue if res.isLoaded
console.debug 'resource ' + res.name + ' is still loading'
populateModel: (model, resName) ->
2014-01-03 13:32:13 -05:00
@mustPopulate = model
model.saveBackups = @shouldSaveBackups(model)
2014-04-16 02:28:59 -04:00
@addModel(model)
2014-01-03 13:32:13 -05:00
@modelLoaded(model) if model.loaded
2014-04-16 02:28:59 -04:00
resName = model.url unless resName
modelRes = @addModelResource(model, model.url)
2014-04-09 15:11:59 -04:00
schema = model.schema()
@schemas[schema.urlRoot] = schema
modelRes.load()
return modelRes
2014-04-09 15:11:59 -04:00
# replace or overwrite
2014-02-15 18:44:45 -05:00
shouldLoadReference: (model) -> true
shouldLoadProjection: (model) -> false
2014-02-15 18:44:45 -05:00
shouldPopulate: (url) -> true
shouldSaveBackups: (model) -> false
2014-01-03 13:32:13 -05:00
2014-02-11 19:09:44 -05:00
modelErrored: (model) ->
2014-01-03 13:32:13 -05:00
@trigger 'error'
2014-02-11 19:09:44 -05:00
@removeEventsFromModel(model)
2014-01-03 13:32:13 -05:00
modelLoaded: (model) ->
2014-01-03 13:32:13 -05:00
@trigger 'loaded-one', model: model
2014-02-11 19:09:44 -05:00
@removeEventsFromModel(model)
2014-02-12 16:10:58 -05:00
2014-02-11 19:09:44 -05:00
removeEventsFromModel: (model) ->
# "Request" resource may have no off()
# "Something" resource may have no model.
model?.off? 'sync', @modelLoaded, @
model?.off? 'error', @modelErrored, @
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)
return @getModelByURL(m.url())
getModelByURL: (modelURL) ->
return @models[modelURL] or null
getModelByOriginalAndMajorVersion: (ModelClass, original, majorVersion=0) ->
_.find @models, (m) ->
m.get('original') is original and m.get('version').major is majorVersion and m.constructor.className is ModelClass.className
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
addModel: (model) ->
2014-04-16 02:28:59 -04:00
url = model.url
2014-01-03 13:32:13 -05:00
@models[url] = model
getCollection: (collection) ->
url = collection.url
url = url() if _.isFunction(url)
return @collections[url] or collection
addCollection: (collection) ->
url = collection.url
url = url() if _.isFunction(url)
if @collections[url]?
return console.warn "Tried to add Collection '#{url}' to SuperModel when we already had it."
@collections[url] = collection
# consolidate models
for model, i in collection.models
cachedModel = @getModelByURL(model.url())
if cachedModel
collection.models[i] = cachedModel
else
@addModel(model)
collection
finished: ->
return @progress is 1.0 or Object.keys(@resources).length is 0
2014-04-09 15:11:59 -04:00
addModelResource: (modelOrCollection, name, fetchOptions, value=1) ->
2014-04-09 15:11:59 -04:00
@checkName(name)
2014-04-16 02:28:59 -04:00
@addModel(modelOrCollection)
2014-04-09 15:11:59 -04:00
res = new ModelResource(modelOrCollection, name, fetchOptions, value)
@storeResource(res, value)
2014-04-09 15:11:59 -04:00
return res
addRequestResource: (name, jqxhrOptions, value=1) ->
2014-04-09 15:11:59 -04:00
@checkName(name)
res = new RequestResource(name, jqxhrOptions, value)
@storeResource(res, value)
2014-04-09 15:11:59 -04:00
return res
addSomethingResource: (name, value=1) ->
2014-04-09 15:11:59 -04:00
@checkName(name)
res = new SomethingResource(name, value)
@storeResource(res, value)
2014-04-09 15:11:59 -04:00
return res
checkName: (name) ->
2014-04-09 15:11:59 -04:00
if not name
throw new Error('Resource name should not be empty.')
storeResource: (resource, value) ->
@rid++
resource.rid = @rid
@resources[@rid] = resource
2014-04-09 15:11:59 -04:00
@listenToOnce(resource, 'resource:loaded', @onResourceLoaded)
@listenTo(resource, 'resource:failed', @onResourceFailed)
@denom += value
2014-04-09 15:11:59 -04:00
loadResources: ->
for rid, res of @resources
2014-04-09 15:11:59 -04:00
res.load()
onResourceLoaded: (r) ->
@modelLoaded(r.model)
2014-04-09 15:11:59 -04:00
# Check if the model has references
if r.constructor.name is 'ModelResource'
2014-04-09 15:11:59 -04:00
model = r.model
@addModelRefencesToLoad(model)
@updateProgress(r)
else
@updateProgress(r)
onResourceFailed: (source) ->
@trigger('resource:failed', source)
@modelErrored(source.resource.model)
2014-04-09 15:11:59 -04:00
addModelRefencesToLoad: (model) ->
2014-04-09 15:11:59 -04:00
schema = model.schema?()
return unless schema
refs = model.getReferencedModels(model.attributes, schema.attributes, '/', @shouldLoadProjection)
refs = [] unless @mustPopulate is model or @shouldPopulate(model)
for ref, i in refs when @shouldLoadReference ref
ref.saveBackups = @shouldSaveBackups(ref)
refURL = ref.url()
continue if @models[refURL]
@models[refURL] = ref
res = @addModelResource(ref, refURL)
res.load()
updateProgress: (r) =>
@num += r.value
@progress = @num / @denom
2014-04-09 15:11:59 -04:00
2014-04-17 19:23:35 -04:00
console.debug 'gintau', 'supermodel-updateProgress', @progress, @num, @denom
@trigger('superModel:updateProgress', @progress)
@trigger('loaded-all') if @finished()
2014-04-09 15:11:59 -04:00
getResource: (rid)->
return @resources[rid]
getProgress: -> return @progress
2014-04-09 15:11:59 -04:00
2014-04-09 15:11:59 -04:00
class Resource extends Backbone.Model
constructor: (name, value=1) ->
2014-04-09 15:11:59 -04:00
@name = name
@value = value
@dependencies = []
@rid = -1 # Used for checking state and reloading
2014-04-09 15:11:59 -04:00
@isLoading = false
@isLoaded = false
@model = null
@loadDeferred = null
@value = 1
addDependency: (depRes) ->
return if depRes.isLoaded
@dependencies.push(depRes)
2014-04-09 15:11:59 -04:00
markLoaded: ->
console.debug 'gintau', 'markLoaded', @
2014-04-09 15:11:59 -04:00
@trigger('resource:loaded', @) if not @isLoaded
@isLoaded = true
@isLoading = false
markFailed: (error) ->
@trigger('resource:failed', {resource: @, error: error}) if not @isLoaded
2014-04-09 15:11:59 -04:00
@isLoaded = false
@isLoading = false
load: ->
isReadyForLoad: -> return not (@isloaded and @isLoading)
getModel: -> @model
2014-04-09 15:11:59 -04:00
class ModelResource extends Resource
constructor: (modelOrCollection, name, fetchOptions, value)->
super(name, value)
@model = modelOrCollection
@fetchOptions = fetchOptions
load: ->
2014-04-09 15:11:59 -04:00
return @loadDeferred.promise() if @isLoading or @isLoaded
@isLoading = true
@loadDeferred = $.Deferred()
@fetchModel()
2014-04-09 15:11:59 -04:00
return @loadDeferred.promise()
fetchModel: ->
2014-04-09 15:11:59 -04:00
@model.fetch(@fetchOptions)
@listenToOnce(@model, 'sync', ->
2014-04-09 15:11:59 -04:00
@markLoaded()
@loadDeferred.resolve(@)
)
@listenToOnce(@model, 'error', ->
@markFailed('Failed to load resource.')
2014-04-09 15:11:59 -04:00
@loadDeferred.reject(@)
)
class RequestResource extends Resource
constructor: (name, jqxhrOptions, value) ->
2014-04-09 15:11:59 -04:00
super(name, value)
@model = $.ajax(jqxhrOptions)
@jqxhrOptions = jqxhrOptions
@loadDeferred = @model
load: ->
2014-04-09 15:11:59 -04:00
return @loadDeferred.promise() if @isLoading or @isLoaded
@isLoading = true
$.when.apply($, @loadDependencies())
.then(@onLoadDependenciesSuccess, @onLoadDependenciesFailed)
.always(()=> @isLoading = false)
return @loadDeferred.promise()
loadDependencies: ->
2014-04-09 15:11:59 -04:00
promises = []
for dep in @dependencies
2014-04-09 15:11:59 -04:00
continue if not dep.isReadyForLoad()
promises.push(dep.load())
return promises
onLoadDependenciesSuccess: =>
2014-04-09 15:11:59 -04:00
@model = $.ajax(@jqxhrOptions)
@model.done(
=> @markLoaded()
).fail(
(jqXHR, textStatus, errorThrown) =>
@markFailed(errorThrown)
)
2014-04-09 15:11:59 -04:00
onLoadDependenciesFailed: =>
@markFailed('Failed to load dependencies.')
2014-04-09 15:11:59 -04:00
class SomethingResource extends Resource
constructor: (name, value) ->
2014-04-09 15:11:59 -04:00
super(value)
@name = name
@loadDeferred = $.Deferred()
load: ->
2014-04-09 15:11:59 -04:00
return @loadDeferred.promise()
markLoaded: ->
2014-04-09 15:11:59 -04:00
@loadDeferred.resolve()
super()
2014-01-03 13:32:13 -05:00
markFailed: (error) ->
2014-04-09 15:11:59 -04:00
@loadDeferred.reject()
super(error)