mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-24 08:08:15 -05:00
296 lines
7.7 KiB
CoffeeScript
296 lines
7.7 KiB
CoffeeScript
module.exports = class SuperModel extends Backbone.Model
|
|
constructor: ->
|
|
@num = 0
|
|
@denom = 0
|
|
@showing = false
|
|
@progress = 0
|
|
@resources = {}
|
|
@rid = 0
|
|
|
|
@models = {}
|
|
@collections = {}
|
|
@schemas = {}
|
|
|
|
# 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) ->
|
|
@mustPopulate = model
|
|
model.saveBackups = @shouldSaveBackups(model)
|
|
|
|
@addModel(model)
|
|
@modelLoaded(model) if model.loaded
|
|
|
|
resName = model.url unless resName
|
|
modelRes = @addModelResource(model, model.url)
|
|
|
|
schema = model.schema()
|
|
@schemas[schema.urlRoot] = schema
|
|
|
|
modelRes.load()
|
|
return modelRes
|
|
|
|
# replace or overwrite
|
|
shouldLoadReference: (model) -> true
|
|
shouldLoadProjection: (model) -> false
|
|
shouldPopulate: (url) -> true
|
|
shouldSaveBackups: (model) -> false
|
|
|
|
modelErrored: (model) ->
|
|
@trigger 'error'
|
|
@removeEventsFromModel(model)
|
|
|
|
modelLoaded: (model) ->
|
|
@trigger 'loaded-one', model: model
|
|
@removeEventsFromModel(model)
|
|
|
|
removeEventsFromModel: (model) ->
|
|
# "Request" resource may have no off()
|
|
# "Something" resource may have no model.
|
|
model?.off? 'sync', @modelLoaded, @
|
|
model?.off? 'error', @modelErrored, @
|
|
|
|
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) ->
|
|
url = model.url
|
|
@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
|
|
|
|
addModelResource: (modelOrCollection, name, fetchOptions, value=1) ->
|
|
@checkName(name)
|
|
@addModel(modelOrCollection)
|
|
res = new ModelResource(modelOrCollection, name, fetchOptions, value)
|
|
@storeResource(res, value)
|
|
return res
|
|
|
|
addRequestResource: (name, jqxhrOptions, value=1) ->
|
|
@checkName(name)
|
|
res = new RequestResource(name, jqxhrOptions, value)
|
|
@storeResource(res, value)
|
|
return res
|
|
|
|
addSomethingResource: (name, value=1) ->
|
|
@checkName(name)
|
|
res = new SomethingResource(name, value)
|
|
@storeResource(res, value)
|
|
return res
|
|
|
|
checkName: (name) ->
|
|
if not name
|
|
throw new Error('Resource name should not be empty.')
|
|
|
|
storeResource: (resource, value) ->
|
|
@rid++
|
|
resource.rid = @rid
|
|
@resources[@rid] = resource
|
|
@listenToOnce(resource, 'resource:loaded', @onResourceLoaded)
|
|
@listenTo(resource, 'resource:failed', @onResourceFailed)
|
|
@denom += value
|
|
|
|
loadResources: ->
|
|
for rid, res of @resources
|
|
res.load()
|
|
|
|
onResourceLoaded: (r) ->
|
|
@modelLoaded(r.model)
|
|
# Check if the model has references
|
|
if r.constructor.name is 'ModelResource'
|
|
model = r.model
|
|
@addModelRefencesToLoad(model)
|
|
@updateProgress(r)
|
|
else
|
|
@updateProgress(r)
|
|
|
|
onResourceFailed: (source) ->
|
|
@trigger('resource:failed', source)
|
|
@modelErrored(source.resource.model)
|
|
|
|
addModelRefencesToLoad: (model) ->
|
|
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
|
|
|
|
console.debug 'gintau', 'supermodel-updateProgress', @progress, @num, @denom
|
|
@trigger('superModel:updateProgress', @progress)
|
|
@trigger('loaded-all') if @finished()
|
|
|
|
getResource: (rid)->
|
|
return @resources[rid]
|
|
|
|
getProgress: -> return @progress
|
|
|
|
|
|
class Resource extends Backbone.Model
|
|
constructor: (name, value=1) ->
|
|
@name = name
|
|
@value = value
|
|
@dependencies = []
|
|
@rid = -1 # Used for checking state and reloading
|
|
@isLoading = false
|
|
@isLoaded = false
|
|
@model = null
|
|
@loadDeferred = null
|
|
|
|
addDependency: (depRes) ->
|
|
return if depRes.isLoaded
|
|
@dependencies.push(depRes)
|
|
|
|
markLoaded: ->
|
|
console.debug 'gintau', 'markLoaded', @
|
|
@trigger('resource:loaded', @) if not @isLoaded
|
|
@isLoaded = true
|
|
@isLoading = false
|
|
|
|
markFailed: (error) ->
|
|
@trigger('resource:failed', {resource: @, error: error}) if not @isLoaded
|
|
@isLoaded = false
|
|
@isLoading = false
|
|
|
|
load: ->
|
|
isReadyForLoad: -> return not (@isloaded and @isLoading)
|
|
getModel: -> @model
|
|
|
|
class ModelResource extends Resource
|
|
constructor: (modelOrCollection, name, fetchOptions, value)->
|
|
super(name, value)
|
|
@model = modelOrCollection
|
|
@fetchOptions = fetchOptions
|
|
|
|
load: ->
|
|
return @loadDeferred.promise() if @isLoading or @isLoaded
|
|
|
|
@isLoading = true
|
|
@loadDeferred = $.Deferred()
|
|
@fetchModel()
|
|
|
|
return @loadDeferred.promise()
|
|
|
|
fetchModel: ->
|
|
@model.fetch(@fetchOptions)
|
|
|
|
@listenToOnce(@model, 'sync', ->
|
|
@markLoaded()
|
|
@loadDeferred.resolve(@)
|
|
)
|
|
|
|
@listenToOnce(@model, 'error', ->
|
|
@markFailed('Failed to load resource.')
|
|
@loadDeferred.reject(@)
|
|
)
|
|
|
|
class RequestResource extends Resource
|
|
constructor: (name, jqxhrOptions, value) ->
|
|
super(name, value)
|
|
@model = $.ajax(jqxhrOptions)
|
|
@jqxhrOptions = jqxhrOptions
|
|
@loadDeferred = @model
|
|
|
|
load: ->
|
|
return @loadDeferred.promise() if @isLoading or @isLoaded
|
|
|
|
@isLoading = true
|
|
$.when.apply($, @loadDependencies())
|
|
.then(@onLoadDependenciesSuccess, @onLoadDependenciesFailed)
|
|
.always(()=> @isLoading = false)
|
|
|
|
return @loadDeferred.promise()
|
|
|
|
loadDependencies: ->
|
|
promises = []
|
|
|
|
for dep in @dependencies
|
|
continue if not dep.isReadyForLoad()
|
|
promises.push(dep.load())
|
|
|
|
return promises
|
|
|
|
onLoadDependenciesSuccess: =>
|
|
@model = $.ajax(@jqxhrOptions)
|
|
@model.done(
|
|
=> @markLoaded()
|
|
).fail(
|
|
(jqXHR, textStatus, errorThrown) =>
|
|
@markFailed(errorThrown)
|
|
)
|
|
|
|
onLoadDependenciesFailed: =>
|
|
@markFailed('Failed to load dependencies.')
|
|
|
|
|
|
class SomethingResource extends Resource
|
|
constructor: (name, value) ->
|
|
super(name, value)
|
|
@loadDeferred = $.Deferred()
|
|
|
|
load: ->
|
|
return @loadDeferred.promise()
|
|
|
|
markLoaded: ->
|
|
@loadDeferred.resolve()
|
|
super()
|
|
|
|
markFailed: (error) ->
|
|
@loadDeferred.reject()
|
|
super(error)
|