-
Notifications
You must be signed in to change notification settings - Fork 4
/
index.coffee
executable file
·280 lines (280 loc) · 10 KB
/
index.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
((root, factory) ->
if typeof define == 'function' and define.amd # AMD
define [ 'jquery' ], factory
else if typeof module == 'object' and module.exports # Node
module.exports = factory require('lodash'), require('q').defer, require('najax')
else if root.angular # AngularJS
root.angular.module('hybind', []).factory 'hybind', ['$q', '$http',
(q, http) ->
req = (opts) ->
d = q.defer()
http(opts).then ((res) -> d.resolve res.data, res), d.reject
d.promise
factory root.angular, q.defer, req ]
else if root.jQuery or root.$
root.hybind = factory(root.jQuery or root.$)
else
root.hybind = factory()
) this, (fw, deferred, http) ->
promise = if deferred or not fw then (d) -> d.promise else (d) -> d.promise()
if fw
extend = fw.extend
deferred ?= fw.Deferred
http ?= fw.ajax
else
extend = (first, second) ->
for secondProp of second
secondVal = second[secondProp]
if secondVal and Object::toString.call(secondVal) == '[object Object]'
first[secondProp] = first[secondProp] or {}
extend first[secondProp], secondVal
else
first[secondProp] = secondVal
first
deferred = () ->
d = {}
p = new window.Promise (resolve, reject) ->
d.resolve = resolve
d.reject = reject
d.promise = p
d
http = (opts) ->
opts.headers = new Headers opts.headers
opts.body = opts.data
d = deferred()
window.fetch(opts.url, opts).then (res) -> if res.ok then d.resolve res.text() else d.reject res,
d.reject
promise(d)
selfLink = (obj) -> obj?.$bind?.self
clean = (url) -> String(url).replace /{.*}/g, '' if url
stringify = (val, depth, replacer, space) ->
_build = (key, val, depth, o, a) ->
if !val or typeof val != 'object'
return val
else
a = Array.isArray(val)
if a && depth == 0
return val; # keep the array if it is in the last depth
JSON.stringify val, (k, v) ->
if a or depth > 0
if !!replacer
v = replacer(k, v)
if !k
a = Array.isArray(v)
val = v
return val;
!o and (o = if a then [] else {})
o[k] = _build(k, v, if a then depth else depth - 1)
return
o or if !key then {} else undefined
JSON.stringify _build('', val, depth), null, space
str = (obj, attached) ->
array = undefined
root = true
stringify obj, 3, (k,v) ->
if not root
if attached and (attached.length == 0 or k in attached) or array
if not (v instanceof Array)
result = v?.$bind?.self
else if (attached.length == 0 or k in attached)
array = k
result = v.slice(0)
if not (typeof k is 'number') and array is not k
array = false
root = false
result or (v if k is '' or not v?.$bind)
makeUrl = (baseUrl, pathOrUrl) ->
if not pathOrUrl then return
baseUrl += '/' if baseUrl[-1..] != '/'
if pathOrUrl.indexOf(':') == -1 then baseUrl + encodeURI(pathOrUrl) else pathOrUrl
hybind = (url, defaults) ->
defaults ?= {}
defaults.headers ?= {}
extend defaults.headers, Accept: 'application/json'
idFn = -> null
bind = (item)->
if item?._links
for name, link of item._links
self = null
if name != 'self'
if (item.$bind?.self != clean link.href) and item[name] != null
p = item[name] or item[name] = {}
item.$bind p, link.href
item.$bind.refs[name] = link.href
bind item[name]
else
item.$bind.self = clean link.href
if item instanceof Array
for i in item
link = i?._links?.self?.href
if link
enrich i, link
bind i
onLoadItem = (coll, item) -> return;
collMapper = (obj, coll) ->
coll.length = 0
if obj._embedded
for k,v of obj._embedded
for item in v
link = item?._links?.self?.href
coll.push item
if link
enrich item, link
item.$bind.ref = coll?.$bind?.self+'/'+link.split('/')[-1..]
onLoadItem(coll, item);
bind item
break
delete obj.embedded
Object.defineProperty coll, '$resource', configurable: true, enumerable: false, value: obj
req = (r, params, opts, result, attached) ->
d = deferred()
opts ?= {}
extend opts, defaults
extend opts, r
opts.headers = {}
extend(opts.headers, defaults.headers) if defaults.headers
extend(opts.headers, r.headers) if r.headers
if typeof opts.data == 'string'
extend opts.headers, { 'Content-Type': 'text/uri-list' }
if typeof opts.data == 'object'
extend opts.headers, { 'Content-Type': 'application/json' }
opts.data = str opts.data, if opts.method == 'POST' then [] else attached
if params
sep = if opts.url.indexOf('?') == -1 then '?' else '&'
opts.url = opts.url + sep + ((k+"="+v) for k,v of params).join("&")
hybind.http(opts).then (data, s, r) ->
try
if typeof data == 'string' and data != ''
data = JSON.parse(data)
catch e
d.reject e
d.resolve data or result
, d.reject
promise d
defProp = (obj, name, value) ->
Object.defineProperty obj, name, configurable: true, enumerable: false, writable: true, value: value
onBind = (obj) -> return;
enrich = (obj, url) ->
if not obj.$bind
defProp obj, '$bind', ->
args = Array.prototype.slice.call arguments
arg = args[0]
if typeof arg is 'object'
target = arg
args.shift()
else
prop = arg
prev = obj[prop]?.$bind?.ref
target = obj[prop] or obj[prop] = {}
link = args[0]
link = link target if typeof link is 'function'
link = idFn target if link is undefined
arg = args[1]
if typeof arg is 'object'
target = arg
obj[prop] = target if prop
args.shift()
else
prev = null
pathOrUrl = args[1]
pathOrUrl ?= link
pathOrUrl = clean pathOrUrl
ref = obj.$bind.refs?[prop] or prev or clean makeUrl selfLink(obj), pathOrUrl
if not target.$bind
if not pathOrUrl then throw 'No property or id specified'
enrich target, ref
else
if (obj instanceof Array)
target.$bind.ref = obj.$bind?.self+'/'+target.$bind.self.split('/')[-1..]
else
target.$bind.ref = ref
target
obj.$bind.refs = {}
if url
obj.$bind.ref = clean url
obj.$bind.self ?= obj.$bind.ref
defProp obj, '$load', (params, opts) ->
d = deferred()
req {method: 'GET', url: obj.$bind.ref}, params, opts
.then (data) ->
if data._embedded and data._embedded[Object.keys(data._embedded)[0]] instanceof Array and not (obj instanceof Array)
if Object.setPrototypeOf
Object.setPrototypeOf(obj, Array.prototype)
else
obj.__proto__ = Array.prototype
enrich obj
if (obj instanceof Array)
collMapper data, obj
else
for prop of obj
if typeof obj[prop] != 'function'
obj[prop] = undefined
extend obj, data
bind obj
d.resolve obj
, d.reject
promise d
if (obj instanceof Array)
defProp obj, '$add', (items, params, opts) ->
items = [ items ] if not (items instanceof Array)
data = (selfLink item for item in items)
req method: 'POST', url: selfLink(obj), data: data.join('\n'), params, opts, obj
defProp obj, '$save', (params, opts) ->
data = (selfLink item for item in obj)
req method: 'PUT', url: selfLink(obj), data: data.join('\n'), params, opts, obj
delete obj.$set
else
defProp obj, '$set', (item, params, opts) ->
item ?= obj
req method: 'PUT', url: obj.$bind.ref, data: selfLink(item), params, opts, obj
defProp obj, '$save', (params, opts) ->
if params instanceof Array
attached = params
params = undefined
if opts instanceof Array
attached = opts
opts = undefined
req method: 'PUT', url: selfLink(obj), data: obj, params, opts, obj, attached
delete obj.$add
defProp obj, '$create', (item, params, opts) ->
d = deferred()
req method: 'POST', url: selfLink(obj), data: (item or {}), params, opts
.then (data) ->
extend(item, data) if item
item ?= data
enrich item, data._links.self.href
delete item._links
d.resolve item
, d.reject
promise d
defProp obj, '$delete', (params, opts)->
if obj.$bind.self
req method: 'DELETE', url: obj.$bind.self, params, opts, obj
else
obj.$load(params, opts).then -> req method: 'DELETE', url: obj.$bind.self, params, opts, obj
defProp obj, '$remove', (params, opts) ->
req method: 'DELETE', url: obj.$bind.ref, params, opts, obj
defProp obj, '$share', (args...) ->
while args.length > 0
arg = args.shift()
switch typeof arg
when 'string' then prop = arg
when 'object' then cache = arg
when 'function' then cb = arg
item = if prop then obj[prop] else obj
link = selfLink item
cache ?= defaults?.cache
cached = cache[link]
if prop and cached then obj[prop] = cached
cache[link] = item if not cached
if cb and not cached then cb item
item
onBind(obj)
obj
root =
$id: (fn) -> idFn = fn
$onBind: (ob) -> onBind = ob
$onLoadItem: (oil) -> onLoadItem = oil
enrich root, url
hybind.http = http
hybind