brainstem-js 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +3 -6
- data/Gemfile.lock +1 -1
- data/README.md +3 -1
- data/Rakefile +2 -0
- data/lib/brainstem/js/version.rb +1 -1
- data/spec/brainstem-collection-spec.coffee +25 -0
- data/spec/{brianstem-expectation-spec.coffee → brainstem-expectation-spec.coffee} +132 -17
- data/spec/brainstem-model-spec.coffee +67 -27
- data/spec/brainstem-sync-spec.coffee +29 -6
- data/spec/brainstem-utils-spec.coffee +11 -1
- data/spec/helpers/builders.coffee +2 -2
- data/spec/helpers/models/post.coffee +1 -1
- data/spec/helpers/models/project.coffee +1 -1
- data/spec/helpers/models/task.coffee +2 -2
- data/spec/helpers/models/time-entry.coffee +1 -1
- data/spec/helpers/models/user.coffee +1 -1
- data/spec/helpers/spec-helper.coffee +21 -0
- data/spec/loaders/abstract-loader-shared-behavior.coffee +604 -0
- data/spec/loaders/abstract-loader-spec.coffee +3 -0
- data/spec/loaders/collection-loader-spec.coffee +146 -0
- data/spec/loaders/model-loader-spec.coffee +99 -0
- data/spec/storage-manager-spec.coffee +242 -56
- data/vendor/assets/javascripts/brainstem/brainstem-collection.coffee +16 -6
- data/vendor/assets/javascripts/brainstem/brainstem-expectation.coffee +70 -20
- data/vendor/assets/javascripts/brainstem/brainstem-model.coffee +13 -13
- data/vendor/assets/javascripts/brainstem/brainstem-sync.coffee +8 -3
- data/vendor/assets/javascripts/brainstem/loaders/abstract-loader.coffee +289 -0
- data/vendor/assets/javascripts/brainstem/loaders/collection-loader.coffee +68 -0
- data/vendor/assets/javascripts/brainstem/loaders/model-loader.coffee +35 -0
- data/vendor/assets/javascripts/brainstem/loading-mixin.coffee +1 -7
- data/vendor/assets/javascripts/brainstem/storage-manager.coffee +79 -196
- data/vendor/assets/javascripts/brainstem/utils.coffee +18 -4
- metadata +17 -6
@@ -0,0 +1,68 @@
|
|
1
|
+
window.Brainstem ?= {}
|
2
|
+
|
3
|
+
class Brainstem.CollectionLoader extends Brainstem.AbstractLoader
|
4
|
+
getCollection: ->
|
5
|
+
@externalObject
|
6
|
+
|
7
|
+
_getCollectionName: ->
|
8
|
+
@loadOptions.name
|
9
|
+
|
10
|
+
_getExpectationName: ->
|
11
|
+
@_getCollectionName()
|
12
|
+
|
13
|
+
_createObjects: ->
|
14
|
+
@internalObject = @storageManager.createNewCollection @loadOptions.name, []
|
15
|
+
|
16
|
+
@externalObject = @loadOptions.collection || @storageManager.createNewCollection @loadOptions.name, []
|
17
|
+
@externalObject.setLoaded false
|
18
|
+
@externalObject.reset([], silent: false) if @loadOptions.reset
|
19
|
+
@externalObject.lastFetchOptions = _.pick($.extend(true, {}, @loadOptions), 'name', 'filters', 'page', 'perPage', 'limit', 'offset', 'order', 'search')
|
20
|
+
@externalObject.lastFetchOptions.include = @originalOptions.include
|
21
|
+
|
22
|
+
_updateStorageManagerFromResponse: (resp) ->
|
23
|
+
# The server response should look something like this:
|
24
|
+
# {
|
25
|
+
# count: 200,
|
26
|
+
# results: [{ key: "tasks", id: 10 }, { key: "tasks", id: 11 }],
|
27
|
+
# time_entries: [{ id: 2, title: "te1", project_id: 6, task_id: [10, 11] }]
|
28
|
+
# projects: [{id: 6, title: "some project", time_entry_ids: [2] }]
|
29
|
+
# tasks: [{id: 10, title: "some task" }, {id: 11, title: "some other task" }]
|
30
|
+
# }
|
31
|
+
# Loop over all returned data types and update our local storage to represent any new data.
|
32
|
+
|
33
|
+
results = resp['results']
|
34
|
+
keys = _.reject(_.keys(resp), (key) -> key == 'count' || key == 'results')
|
35
|
+
unless _.isEmpty(results)
|
36
|
+
keys.splice(keys.indexOf(@loadOptions.name), 1) if keys.indexOf(@loadOptions.name) != -1
|
37
|
+
keys.push(@loadOptions.name)
|
38
|
+
|
39
|
+
for underscoredModelName in keys
|
40
|
+
@storageManager.storage(underscoredModelName).update _(resp[underscoredModelName]).values()
|
41
|
+
|
42
|
+
if @loadOptions.cache && !@loadOptions.only?
|
43
|
+
@storageManager.getCollectionDetails(@loadOptions.name).cache[@loadOptions.cacheKey] = results
|
44
|
+
|
45
|
+
if @loadOptions.only?
|
46
|
+
data = _.map(@loadOptions.only, (id) => @cachedCollection.get(id))
|
47
|
+
else
|
48
|
+
data = _.map(results, (result) => @storageManager.storage(result.key).get(result.id))
|
49
|
+
|
50
|
+
data
|
51
|
+
|
52
|
+
_updateObjects: (object, data, silent = false) ->
|
53
|
+
object.setLoaded true, trigger: false
|
54
|
+
|
55
|
+
if data
|
56
|
+
data = data.models if data.models?
|
57
|
+
if object.length
|
58
|
+
object.add data
|
59
|
+
else
|
60
|
+
object.reset data
|
61
|
+
|
62
|
+
object.setLoaded true unless silent
|
63
|
+
|
64
|
+
_getModel: ->
|
65
|
+
@internalObject.model
|
66
|
+
|
67
|
+
_getModelsForAssociation: (association) ->
|
68
|
+
@internalObject.map (m) => @_modelsOrObj(m.get(association))
|
@@ -0,0 +1,35 @@
|
|
1
|
+
window.Brainstem ?= {}
|
2
|
+
|
3
|
+
class Brainstem.ModelLoader extends Brainstem.AbstractLoader
|
4
|
+
getModel: ->
|
5
|
+
@externalObject
|
6
|
+
|
7
|
+
_getCollectionName: ->
|
8
|
+
@loadOptions.name.pluralize()
|
9
|
+
|
10
|
+
_getExpectationName: ->
|
11
|
+
@loadOptions.name
|
12
|
+
|
13
|
+
_createObjects: ->
|
14
|
+
id = @loadOptions.only[0]
|
15
|
+
|
16
|
+
@internalObject = @storageManager.storage(@_getCollectionName()).get(id) || @storageManager.createNewModel(@loadOptions.name, id: id)
|
17
|
+
@externalObject = @internalObject
|
18
|
+
|
19
|
+
_updateStorageManagerFromResponse: (resp) ->
|
20
|
+
@internalObject.parse(resp)
|
21
|
+
|
22
|
+
_updateObjects: (object, data) ->
|
23
|
+
if _.isArray(data) && data.length == 1
|
24
|
+
data = data[0]
|
25
|
+
|
26
|
+
if data instanceof Backbone.Model
|
27
|
+
data = data.attributes
|
28
|
+
|
29
|
+
object.set(data)
|
30
|
+
|
31
|
+
_getModel: ->
|
32
|
+
@internalObject.constructor
|
33
|
+
|
34
|
+
_getModelsForAssociation: (association) ->
|
35
|
+
@_modelsOrObj(@internalObject.get(association))
|
@@ -4,10 +4,4 @@ Brainstem.LoadingMixin =
|
|
4
4
|
setLoaded: (state, options) ->
|
5
5
|
options = { trigger: true } unless options? && options.trigger? && !options.trigger
|
6
6
|
@loaded = state
|
7
|
-
@trigger 'loaded',
|
8
|
-
|
9
|
-
whenLoaded: (func) ->
|
10
|
-
if @loaded
|
11
|
-
func()
|
12
|
-
else
|
13
|
-
@bind "loaded", => func()
|
7
|
+
@trigger 'loaded', this if state && options.trigger
|
@@ -22,51 +22,44 @@ class window.Brainstem.StorageManager
|
|
22
22
|
|
23
23
|
# Access the cache for a particular collection.
|
24
24
|
# manager.storage("time_entries").get(12).get("title")
|
25
|
-
storage: (name)
|
25
|
+
storage: (name) ->
|
26
26
|
@getCollectionDetails(name).storage
|
27
27
|
|
28
|
-
dataUsage:
|
28
|
+
dataUsage: ->
|
29
29
|
sum = 0
|
30
30
|
for dataType in @collectionNames()
|
31
31
|
sum += @storage(dataType).length
|
32
32
|
sum
|
33
33
|
|
34
|
-
reset:
|
34
|
+
reset: ->
|
35
35
|
for name, attributes of @collections
|
36
36
|
attributes.storage.reset []
|
37
37
|
attributes.cache = {}
|
38
38
|
|
39
39
|
# Access details of a collection. An error will be thrown if the collection cannot be found.
|
40
|
-
getCollectionDetails: (name)
|
40
|
+
getCollectionDetails: (name) ->
|
41
41
|
@collections[name] || @collectionError(name)
|
42
42
|
|
43
|
-
collectionNames:
|
43
|
+
collectionNames: ->
|
44
44
|
_.keys(@collections)
|
45
45
|
|
46
|
-
collectionExists: (name)
|
46
|
+
collectionExists: (name) ->
|
47
47
|
!!@collections[name]
|
48
48
|
|
49
|
-
setErrorInterceptor: (interceptor)
|
50
|
-
@errorInterceptor = interceptor || (handler, modelOrCollection, options, jqXHR, requestParams) -> handler?(
|
49
|
+
setErrorInterceptor: (interceptor) ->
|
50
|
+
@errorInterceptor = interceptor || (handler, modelOrCollection, options, jqXHR, requestParams) -> handler?(jqXHR)
|
51
51
|
|
52
|
-
# Request a model to be loaded, optionally ensuring that associations be included as well. A
|
53
|
-
# when the load, and any dependent loads, are complete.
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
loadModel
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
only: id
|
64
|
-
success: (collection) ->
|
65
|
-
model.setLoaded true, trigger: false
|
66
|
-
model.set collection.get(id).attributes
|
67
|
-
model.setLoaded true
|
68
|
-
oldSuccess(model) if oldSuccess
|
69
|
-
model
|
52
|
+
# Request a model to be loaded, optionally ensuring that associations be included as well. A loader (which is a jQuery promise) is returned immediately and is resolved
|
53
|
+
# with the model from the StorageManager when the load, and any dependent loads, are complete.
|
54
|
+
# loader = manager.loadModel "time_entry", 2
|
55
|
+
# loader = manager.loadModel "time_entry", 2, fields: ["title", "notes"]
|
56
|
+
# loader = manager.loadModel "time_entry", 2, include: ["project", "task"]
|
57
|
+
# manager.loadModel("time_entry", 2, include: ["project", "task"]).done (model) -> console.log model
|
58
|
+
loadModel: (name, id, options = {}) ->
|
59
|
+
return if not id
|
60
|
+
|
61
|
+
loader = @loadObject(name, $.extend({}, options, only: id), isCollection: false)
|
62
|
+
loader
|
70
63
|
|
71
64
|
# Request a set of data to be loaded, optionally ensuring that associations be included as well. A collection is returned immediately and is reset
|
72
65
|
# when the load, and any dependent loads, are complete.
|
@@ -77,183 +70,26 @@ class window.Brainstem.StorageManager
|
|
77
70
|
# collection = manager.loadCollection "time_entries", include: ["project:title,description", "task:due_date"]
|
78
71
|
# collection = manager.loadCollection "tasks", include: ["assets", { "assignees": "account" }, { "sub_tasks": ["assignees", "assets"] }]
|
79
72
|
# collection = manager.loadCollection "time_entries", filters: ["project_id:6", "editable:true"], order: "updated_at:desc", page: 1, perPage: 20
|
80
|
-
loadCollection: (name, options)
|
81
|
-
|
82
|
-
|
83
|
-
include = @_wrapObjects(Brainstem.Utils.extractArray "include", options)
|
84
|
-
if options.search
|
85
|
-
options.cache = false
|
86
|
-
|
87
|
-
collection = options.collection || @createNewCollection name, []
|
88
|
-
collection.setLoaded false
|
89
|
-
collection.reset([], silent: false) if options.reset
|
90
|
-
collection.lastFetchOptions = _.pick($.extend(true, {}, options), 'name', 'filters', 'include', 'page', 'perPage', 'order', 'search')
|
91
|
-
|
92
|
-
if @expectations?
|
93
|
-
@handleExpectations name, collection, options
|
94
|
-
else
|
95
|
-
@_loadCollectionWithFirstLayer($.extend({}, options, include: include, success: ((firstLayerCollection) =>
|
96
|
-
expectedAdditionalLoads = @_countRequiredServerRequests(include) - 1
|
97
|
-
if expectedAdditionalLoads > 0
|
98
|
-
timesCalled = 0
|
99
|
-
@_handleNextLayer firstLayerCollection, include, =>
|
100
|
-
timesCalled += 1
|
101
|
-
if timesCalled == expectedAdditionalLoads
|
102
|
-
@_success(options, collection, firstLayerCollection)
|
103
|
-
else
|
104
|
-
@_success(options, collection, firstLayerCollection)
|
105
|
-
)))
|
106
|
-
|
107
|
-
collection
|
108
|
-
|
109
|
-
_handleNextLayer: (collection, include, callback) =>
|
110
|
-
# Collection is a fully populated collection of tasks whose first layer of associations are loaded.
|
111
|
-
# include is a hierarchical list of associations on those tasks:
|
112
|
-
# [{ 'time_entries': ['project': [], 'task': [{ 'assignees': []}]] }, { 'project': [] }]
|
113
|
-
|
114
|
-
_(include).each (hash) => # { 'time_entries': ['project': [], 'task': [{ 'assignees': []}]] }
|
115
|
-
association = _.keys(hash)[0] # time_entries
|
116
|
-
nextLevelInclude = hash[association] # ['project': [], 'task': [{ 'assignees': []}]]
|
117
|
-
if nextLevelInclude.length
|
118
|
-
association_ids = _(collection.models).chain().
|
119
|
-
map((m) -> if (a = m.get(association)) instanceof Backbone.Collection then a.models else a).
|
120
|
-
flatten().uniq().compact().pluck("id").sort().value()
|
121
|
-
newCollectionName = collection.model.associationDetails(association).collectionName
|
122
|
-
@_loadCollectionWithFirstLayer name: newCollectionName, only: association_ids, include: nextLevelInclude, success: (loadedAssociationCollection) =>
|
123
|
-
@_handleNextLayer(loadedAssociationCollection, nextLevelInclude, callback)
|
124
|
-
callback()
|
125
|
-
|
126
|
-
_loadCollectionWithFirstLayer: (options) =>
|
127
|
-
options = $.extend({}, options)
|
128
|
-
name = options.name
|
129
|
-
only = if options.only then _.map((Brainstem.Utils.extractArray "only", options), (id) -> String(id)) else null
|
130
|
-
search = options.search
|
131
|
-
include = _(options.include).map((i) -> _.keys(i)[0]) # pull off the top layer of includes
|
132
|
-
filters = options.filters || {}
|
133
|
-
order = options.order || "updated_at:desc"
|
134
|
-
cacheKey = "#{order}|#{_.chain(filters).pairs().map(([k, v]) -> "#{k}:#{v}" ).value().join(",")}|#{options.page}|#{options.perPage}"
|
73
|
+
loadCollection: (name, options = {}) ->
|
74
|
+
loader = @loadObject(name, options)
|
75
|
+
loader.externalObject
|
135
76
|
|
136
|
-
|
137
|
-
collection = @createNewCollection name, []
|
138
|
-
|
139
|
-
unless options.cache == false
|
140
|
-
if only?
|
141
|
-
alreadyLoadedIds = _.select only, (id) => cachedCollection.get(id)?.associationsAreLoaded(include)
|
142
|
-
if alreadyLoadedIds.length == only.length
|
143
|
-
# We've already seen every id that is being asked for and have all the associated data.
|
144
|
-
@_success options, collection, _.map only, (id) => cachedCollection.get(id)
|
145
|
-
return collection
|
146
|
-
else
|
147
|
-
# Check if we have, at some point, requested enough records with this this order and filter(s).
|
148
|
-
if @getCollectionDetails(name).cache[cacheKey]
|
149
|
-
subset = _(@getCollectionDetails(name).cache[cacheKey]).map (result) -> base.data.storage(result.key).get(result.id)
|
150
|
-
if (_.all(subset, (model) => model.associationsAreLoaded(include)))
|
151
|
-
@_success options, collection, subset
|
152
|
-
return collection
|
153
|
-
|
154
|
-
# If we haven't returned yet, we need to go to the server to load some missing data.
|
155
|
-
syncOptions =
|
156
|
-
data: {}
|
157
|
-
parse: true
|
158
|
-
error: options.error
|
159
|
-
success: (resp, status, xhr) =>
|
160
|
-
# The server response should look something like this:
|
161
|
-
# {
|
162
|
-
# count: 200,
|
163
|
-
# results: [{ key: "tasks", id: 10 }, { key: "tasks", id: 11 }],
|
164
|
-
# time_entries: [{ id: 2, title: "te1", project_id: 6, task_id: [10, 11] }]
|
165
|
-
# projects: [{id: 6, title: "some project", time_entry_ids: [2] }]
|
166
|
-
# tasks: [{id: 10, title: "some task" }, {id: 11, title: "some other task" }]
|
167
|
-
# }
|
168
|
-
# Loop over all returned data types and update our local storage to represent any new data.
|
169
|
-
|
170
|
-
results = resp['results']
|
171
|
-
keys = _.reject(_.keys(resp), (key) -> key == 'count' || key == 'results')
|
172
|
-
unless _.isEmpty(results)
|
173
|
-
keys.splice(keys.indexOf(name), 1) if keys.indexOf(name) != -1
|
174
|
-
keys.push(name)
|
175
|
-
|
176
|
-
for underscoredModelName in keys
|
177
|
-
@storage(underscoredModelName).update _(resp[underscoredModelName]).values()
|
178
|
-
|
179
|
-
unless options.cache == false || only?
|
180
|
-
@getCollectionDetails(name).cache[cacheKey] = results
|
181
|
-
|
182
|
-
if only?
|
183
|
-
@_success options, collection, _.map(only, (id) -> cachedCollection.get(id))
|
184
|
-
else
|
185
|
-
@_success options, collection, _(results).map (result) -> base.data.storage(result.key).get(result.id)
|
186
|
-
|
187
|
-
|
188
|
-
syncOptions.data.include = include.join(",") if include.length
|
189
|
-
syncOptions.data.only = _.difference(only, alreadyLoadedIds).join(",") if only?
|
190
|
-
syncOptions.data.order = options.order if options.order?
|
191
|
-
_.extend(syncOptions.data, _(filters).omit('include', 'only', 'order', 'per_page', 'page', 'search')) if _(filters).keys().length
|
192
|
-
syncOptions.data.per_page = options.perPage unless only?
|
193
|
-
syncOptions.data.page = options.page unless only?
|
194
|
-
syncOptions.data.search = search if search
|
195
|
-
|
196
|
-
Backbone.sync.call collection, 'read', collection, syncOptions
|
197
|
-
|
198
|
-
collection
|
199
|
-
|
200
|
-
_success: (options, collection, data) =>
|
201
|
-
if data
|
202
|
-
data = data.models if data.models?
|
203
|
-
collection.setLoaded true, trigger: false
|
204
|
-
if collection.length
|
205
|
-
collection.add data
|
206
|
-
else
|
207
|
-
collection.reset data
|
208
|
-
collection.setLoaded true
|
209
|
-
options.success(collection) if options.success?
|
210
|
-
|
211
|
-
_checkPageSettings: (options) =>
|
212
|
-
options.perPage = options.perPage || 20
|
213
|
-
options.perPage = 1 if options.perPage < 1
|
214
|
-
options.page = options.page || 1
|
215
|
-
options.page = 1 if options.page < 1
|
216
|
-
|
217
|
-
collectionError: (name) =>
|
77
|
+
collectionError: (name) ->
|
218
78
|
Brainstem.Utils.throwError("Unknown collection #{name} in StorageManager. Known collections: #{_(@collections).keys().join(", ")}")
|
219
79
|
|
220
|
-
createNewCollection: (collectionName, models = [], options = {})
|
80
|
+
createNewCollection: (collectionName, models = [], options = {}) ->
|
221
81
|
loaded = options.loaded
|
222
82
|
delete options.loaded
|
223
83
|
collection = new (@getCollectionDetails(collectionName).klass)(models, options)
|
224
84
|
collection.setLoaded(true, trigger: false) if loaded
|
225
85
|
collection
|
226
86
|
|
227
|
-
createNewModel: (modelName, options)
|
87
|
+
createNewModel: (modelName, options) ->
|
228
88
|
new (@getCollectionDetails(modelName.pluralize()).modelKlass)(options || {})
|
229
89
|
|
230
|
-
_wrapObjects: (array) =>
|
231
|
-
output = []
|
232
|
-
_(array).each (elem) =>
|
233
|
-
if elem.constructor == Object
|
234
|
-
for key, value of elem
|
235
|
-
o = {}
|
236
|
-
o[key] = @_wrapObjects(if value instanceof Array then value else [value])
|
237
|
-
output.push o
|
238
|
-
else
|
239
|
-
o = {}
|
240
|
-
o[elem] = []
|
241
|
-
output.push o
|
242
|
-
output
|
243
|
-
|
244
|
-
_countRequiredServerRequests: (array, wrapped = false) =>
|
245
|
-
if array?.length
|
246
|
-
array = @_wrapObjects(array) unless wrapped
|
247
|
-
sum = 1
|
248
|
-
_(array).each (elem) =>
|
249
|
-
sum += @_countRequiredServerRequests(_(elem).values()[0], true)
|
250
|
-
sum
|
251
|
-
else
|
252
|
-
0
|
253
|
-
|
254
90
|
# Expectations and stubbing
|
255
91
|
|
256
|
-
stub: (collectionName, options)
|
92
|
+
stub: (collectionName, options = {}) ->
|
257
93
|
if @expectations?
|
258
94
|
expectation = new Brainstem.Expectation(collectionName, options, @)
|
259
95
|
@expectations.push expectation
|
@@ -261,15 +97,62 @@ class window.Brainstem.StorageManager
|
|
261
97
|
else
|
262
98
|
throw "You must call #enableExpectations on your instance of Brainstem.StorageManager before you can set expectations."
|
263
99
|
|
264
|
-
|
100
|
+
stubModel: (modelName, modelId, options = {}) ->
|
101
|
+
@stub(modelName, $.extend({}, options, only: modelId))
|
102
|
+
|
103
|
+
stubImmediate: (collectionName, options) ->
|
265
104
|
@stub collectionName, $.extend({}, options, immediate: true)
|
266
105
|
|
267
|
-
enableExpectations:
|
106
|
+
enableExpectations: ->
|
268
107
|
@expectations = []
|
269
108
|
|
270
|
-
handleExpectations: (
|
109
|
+
handleExpectations: (loader) ->
|
271
110
|
for expectation in @expectations
|
272
|
-
if expectation.
|
273
|
-
expectation.recordRequest(
|
111
|
+
if expectation.loaderOptionsMatch(loader)
|
112
|
+
expectation.recordRequest(loader)
|
274
113
|
return
|
275
|
-
throw "No expectation matched #{name} with #{JSON.stringify
|
114
|
+
throw "No expectation matched #{name} with #{JSON.stringify loader.originalOptions}"
|
115
|
+
|
116
|
+
# Helpers
|
117
|
+
loadObject: (name, loadOptions, options = {}) ->
|
118
|
+
options = $.extend({}, { isCollection: true }, options)
|
119
|
+
|
120
|
+
successCallback = loadOptions.success
|
121
|
+
loadOptions = _.omit(loadOptions, 'success')
|
122
|
+
loadOptions = $.extend({}, loadOptions, name: name)
|
123
|
+
|
124
|
+
if options.isCollection
|
125
|
+
loaderClass = Brainstem.CollectionLoader
|
126
|
+
else
|
127
|
+
loaderClass = Brainstem.ModelLoader
|
128
|
+
|
129
|
+
@_checkPageSettings loadOptions
|
130
|
+
|
131
|
+
loader = new loaderClass(storageManager: this)
|
132
|
+
loader.setup(loadOptions)
|
133
|
+
loader.done(successCallback) if successCallback? && _.isFunction(successCallback)
|
134
|
+
|
135
|
+
if @expectations?
|
136
|
+
@handleExpectations(loader)
|
137
|
+
else
|
138
|
+
loader.load()
|
139
|
+
|
140
|
+
loader
|
141
|
+
|
142
|
+
_checkPageSettings: (options) ->
|
143
|
+
if options.limit? && options.limit != '' && options.offset? && options.offset != ''
|
144
|
+
options.perPage = options.page = undefined
|
145
|
+
else
|
146
|
+
options.limit = options.offset = undefined
|
147
|
+
|
148
|
+
@_setDefaultPageSettings(options)
|
149
|
+
|
150
|
+
_setDefaultPageSettings: (options) ->
|
151
|
+
if options.limit? && options.offset?
|
152
|
+
options.limit = 1 if options.limit < 1
|
153
|
+
options.offset = 0 if options.offset < 0
|
154
|
+
else
|
155
|
+
options.perPage = options.perPage || 20
|
156
|
+
options.perPage = 1 if options.perPage < 1
|
157
|
+
options.page = options.page || 1
|
158
|
+
options.page = 1 if options.page < 1
|
@@ -4,10 +4,10 @@ class window.Brainstem.Utils
|
|
4
4
|
@warn: (args...) ->
|
5
5
|
console?.log "Error:", args...
|
6
6
|
|
7
|
-
@throwError: (message)
|
7
|
+
@throwError: (message) ->
|
8
8
|
throw new Error("#{Backbone.history.getFragment()}: #{message}")
|
9
9
|
|
10
|
-
@matches: (obj1, obj2)
|
10
|
+
@matches: (obj1, obj2) ->
|
11
11
|
if @empty(obj1) && @empty(obj2)
|
12
12
|
true
|
13
13
|
else if obj1 instanceof Array && obj2 instanceof Array
|
@@ -19,7 +19,7 @@ class window.Brainstem.Utils
|
|
19
19
|
else
|
20
20
|
String(obj1) == String(obj2)
|
21
21
|
|
22
|
-
@empty: (thing)
|
22
|
+
@empty: (thing) ->
|
23
23
|
if thing == null || thing == undefined || thing == ""
|
24
24
|
true
|
25
25
|
if thing instanceof Array
|
@@ -29,7 +29,21 @@ class window.Brainstem.Utils
|
|
29
29
|
else
|
30
30
|
false
|
31
31
|
|
32
|
-
@extractArray: (option, options)
|
32
|
+
@extractArray: (option, options) ->
|
33
33
|
result = options[option]
|
34
34
|
result = [result] unless result instanceof Array
|
35
35
|
_.compact(result)
|
36
|
+
|
37
|
+
@wrapObjects: (array) ->
|
38
|
+
output = []
|
39
|
+
_(array).each (elem) =>
|
40
|
+
if elem.constructor == Object
|
41
|
+
for key, value of elem
|
42
|
+
o = {}
|
43
|
+
o[key] = @wrapObjects(if value instanceof Array then value else [value])
|
44
|
+
output.push o
|
45
|
+
else
|
46
|
+
o = {}
|
47
|
+
o[elem] = []
|
48
|
+
output.push o
|
49
|
+
output
|