spine-couch-ajax.coffee 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. Spine = require('spine/core')
  2. $ = Spine.$
  3. Model = Spine.Model
  4. {_} = require("underscore")
  5. duality = require("duality/core")
  6. Spine.Model.include
  7. toJSON: ->
  8. result = {}
  9. for key in @constructor.attributes when key of @
  10. if typeof @[key] is 'function'
  11. result[key] = @[key]()
  12. else
  13. result[key] = @[key]
  14. result.id = result._id = @_id if @_id
  15. result
  16. changeID: (id) ->
  17. # Markus: Needed to refresh the client records
  18. # since this is a new record with changed ID
  19. records = @constructor.records
  20. crecords = @constructor.crecords
  21. record = records[@id].dup(false)
  22. record.id = id
  23. record._id = id
  24. records[record.id] = record
  25. crecords[record.cid] = record
  26. delete records[@id]
  27. @id = id
  28. @_id = id
  29. @save()
  30. @constructor.trigger('refresh', @constructor.cloneArray([record]))
  31. # ... or fetch again from server which also works
  32. # super
  33. # @constructor.ajax().fetch({id: id})
  34. Spine.Model.extend
  35. fromJSON: (objects) ->
  36. make_object = (val) =>
  37. val.id = val._id unless val.id
  38. val._id = val.id unless val._id
  39. new @(val)
  40. return unless objects
  41. if typeof objects is 'string'
  42. objects = JSON.parse(objects)
  43. if Spine.isArray(objects)
  44. (make_object(value) for value in objects)
  45. else
  46. make_object(objects)
  47. CouchAjax =
  48. getURL: (object) ->
  49. object and object.url?() or object.url
  50. enabled: true
  51. pending: false
  52. requests: []
  53. disable: (callback) ->
  54. if @enabled
  55. @enabled = false
  56. try
  57. do callback
  58. catch e
  59. throw e
  60. finally
  61. @enabled = true
  62. else
  63. do callback
  64. requestNext: ->
  65. next = @requests.shift()
  66. if next
  67. @request(next)
  68. else
  69. @pending = false
  70. request: (callback) ->
  71. (do callback).complete(=> do @requestNext)
  72. queue: (callback) ->
  73. return unless @enabled
  74. if @pending
  75. @requests.push(callback)
  76. else
  77. @pending = true
  78. @request(callback)
  79. callback
  80. class Base
  81. defaults:
  82. contentType: 'application/json'
  83. dataType: 'json'
  84. processData: false
  85. headers: {'X-Requested-With': 'XMLHttpRequest'}
  86. ajax: (params, defaults) ->
  87. $.ajax($.extend({}, @defaults, defaults, params))
  88. queue: (callback) ->
  89. CouchAjax.queue(callback)
  90. class Collection extends Base
  91. constructor: (@model) ->
  92. find: (id, params) ->
  93. record = new @model(id: id)
  94. @ajax(
  95. params,
  96. type: 'GET',
  97. url: CouchAjax.getURL(record)
  98. ).success(@recordsResponse)
  99. .error(@errorResponse)
  100. all: (params) ->
  101. @ajax(
  102. params,
  103. type: 'GET',
  104. url: CouchAjax.getURL(@model)
  105. ).success(@recordsResponse)
  106. .error(@errorResponse)
  107. fetch: (params = {}) ->
  108. if id = params.id
  109. delete params.id
  110. @find(id, params).success (record) =>
  111. @model.refresh(_.pluck(record.rows, "doc"))
  112. else
  113. @all(params).success (records) =>
  114. @model.refresh(_.pluck(records.rows, "doc"))
  115. # Private
  116. recordsResponse: (data, status, xhr) =>
  117. @model.trigger('ajaxSuccess', null, status, xhr)
  118. errorResponse: (xhr, statusText, error) =>
  119. @model.trigger('ajaxError', null, xhr, statusText, error)
  120. class Singleton extends Base
  121. constructor: (@record) ->
  122. @model = @record.constructor
  123. reload: (params, options) ->
  124. @queue =>
  125. @ajax(
  126. params,
  127. type: 'GET'
  128. url: CouchAjax.getURL(@record)
  129. ).success(@recordResponse(options))
  130. .error(@errorResponse(options))
  131. create: (params, options) ->
  132. @queue =>
  133. @ajax(
  134. params,
  135. type: 'POST'
  136. data: JSON.stringify(@record)
  137. url: CouchAjax.getURL(@model)
  138. ).success(@recordResponse(options))
  139. .error(@errorResponse(options))
  140. update: (params, options) ->
  141. @queue =>
  142. @ajax(
  143. params,
  144. type: 'PUT'
  145. data: JSON.stringify(@record)
  146. url: CouchAjax.getURL(@record)
  147. ).success(@recordResponse(options))
  148. .error(@errorResponse(options))
  149. destroy: (params, options) ->
  150. @queue =>
  151. @ajax(
  152. params,
  153. type: 'DELETE'
  154. url: CouchAjax.getURL(@record)
  155. ).success(@recordResponse(options))
  156. .error(@errorResponse(options))
  157. # Private
  158. recordResponse: (options = {}) =>
  159. (data, status, xhr) =>
  160. if Spine.isBlank(data)
  161. data = false
  162. else if data.rows
  163. data = @model.fromJSON(_.pluck(data.rows, "doc")[0])
  164. else
  165. data = @model.fromJSON(data)
  166. data._rev = xhr.getResponseHeader( 'X-Couch-Update-NewRev' )
  167. CouchAjax.disable =>
  168. if data
  169. # ID change, need to do some shifting
  170. if data.id and @record.id isnt data.id
  171. @record.changeID(data.id)
  172. # Update with latest data
  173. @record.updateAttributes(data.attributes())
  174. @record.trigger('ajaxSuccess', data, status, xhr)
  175. options.success?.apply(@record)
  176. errorResponse: (options = {}) =>
  177. (xhr, statusText, error) =>
  178. @record.trigger('ajaxError', xhr, statusText, error)
  179. options.error?.apply(@record)
  180. # Popup an alert box that we could communicate with server
  181. alert "#{statusText}\n#{error}\nSomehting may have gone wrong with an action for \"#{@record.title or @record.name}\".\n\nCheck your connection and try again."
  182. # CouchAjax endpoint
  183. Model.host = ''
  184. Include =
  185. ajax: -> new Singleton(this)
  186. url: ->
  187. base = CouchAjax.getURL(@constructor)
  188. base += '/' unless base.charAt(base.length - 1) is '/'
  189. base += encodeURIComponent(@id)
  190. base
  191. Extend =
  192. ajax: -> new Collection(this)
  193. url: ->
  194. "#{duality.getBaseURL()}/spine-adapter/#{@className.toLowerCase()}"
  195. Model.CouchAjax =
  196. extended: ->
  197. @fetch @ajaxFetch
  198. @change @ajaxChange
  199. @extend Extend
  200. @include Include
  201. # Private
  202. ajaxFetch: ->
  203. @ajax().fetch(arguments...)
  204. ajaxChange: (record, type, options = {}) ->
  205. return if options.ajax is false
  206. record.ajax()[type](options.ajax, options)
  207. Model.CouchAjax.Methods =
  208. extended: ->
  209. @extend Extend
  210. @include Include
  211. # Globals
  212. CouchAjax.defaults = Base::defaults
  213. Spine.CouchAjax = CouchAjax
  214. module?.exports = CouchAjax