batman-rails 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,71 @@
1
+ #
2
+ # batman.jquery.coffee
3
+ # batman.js
4
+ #
5
+ # Created by Nick Small
6
+ # Copyright 2011, Shopify
7
+ #
8
+
9
+ # Include this file instead of batman.nodep if your
10
+ # project already uses jQuery. It will map a few
11
+ # batman.js methods to existing jQuery methods.
12
+
13
+
14
+ Batman.Request::send = (data) ->
15
+ options =
16
+ url: @get 'url'
17
+ type: @get 'method'
18
+ dataType: @get 'type'
19
+ data: data || @get 'data'
20
+ username: @get 'username'
21
+ password: @get 'password'
22
+ headers: @get 'headers'
23
+ beforeSend: =>
24
+ @fire 'loading'
25
+
26
+ success: (response, textStatus, xhr) =>
27
+ @set 'status', xhr.status
28
+ @set 'response', response
29
+ @fire 'success', response
30
+
31
+ error: (xhr, status, error) =>
32
+ @set 'status', xhr.status
33
+ @set 'response', xhr.responseText
34
+ xhr.request = @
35
+ @fire 'error', xhr
36
+
37
+ complete: =>
38
+ @fire 'loaded'
39
+
40
+ if @get('method') in ['PUT', 'POST']
41
+
42
+ unless @hasFileUploads()
43
+ options.contentType = @get 'contentType'
44
+ else
45
+ options.contentType = false
46
+ options.processData = false
47
+ options.data = @constructor.objectToFormData(options.data)
48
+
49
+ jQuery.ajax options
50
+
51
+ Batman.mixins.animation =
52
+ show: (addToParent) ->
53
+ jq = $(@)
54
+ show = ->
55
+ jq.show 600
56
+
57
+ if addToParent
58
+ addToParent.append?.appendChild @
59
+ addToParent.before?.parentNode.insertBefore @, addToParent.before
60
+
61
+ jq.hide()
62
+ setTimeout show, 0
63
+ else
64
+ show()
65
+ @
66
+
67
+ hide: (removeFromParent) ->
68
+ $(@).hide 600, =>
69
+ @parentNode?.removeChild @ if removeFromParent
70
+ Batman.DOM.didRemoveNode(@)
71
+ @
@@ -0,0 +1,134 @@
1
+ applyExtra = (Batman) ->
2
+
3
+ # `param` and `buildParams` stolen from jQuery
4
+ #
5
+ # jQuery JavaScript Library
6
+ # http://jquery.com/
7
+ #
8
+ # Copyright 2011, John Resig
9
+ # Dual licensed under the MIT or GPL Version 2 licenses.
10
+ # http://jquery.org/license
11
+ # Rails really doesn't like collection[0][rules], it wants collection[][rules],
12
+ # so we will give it that.
13
+ rbracket = /\[\]$/
14
+ r20 = /%20/g
15
+ param = (a) ->
16
+ return a if typeof a is 'string'
17
+ s = []
18
+ add = (key, value) ->
19
+ value = value() if typeof value is 'function'
20
+ value = '' unless value?
21
+ s[s.length] = encodeURIComponent(key) + "=" + encodeURIComponent(value)
22
+
23
+ if Batman.typeOf(a) is 'Array'
24
+ for value, name of a
25
+ add name, value
26
+ else
27
+ for own k, v of a
28
+ buildParams k, v, add
29
+ s.join("&").replace r20, "+"
30
+
31
+ buildParams = (prefix, obj, add) ->
32
+ if Batman.typeOf(obj) is 'Array'
33
+ for v, i in obj
34
+ if rbracket.test(prefix)
35
+ add prefix, v
36
+ else
37
+ buildParams prefix + "[]", v, add
38
+ else if obj? and typeof obj == "object"
39
+ for name of obj
40
+ buildParams prefix + "[" + name + "]", obj[name], add
41
+ else
42
+ add prefix, obj
43
+
44
+ numericKeys = [1, 2, 3, 4, 5, 6, 7, 10, 11]
45
+ date_re = ///
46
+ ^
47
+ (\d{4}|[+\-]\d{6}) # 1 YYYY
48
+ (?:-(\d{2}) # 2 MM
49
+ (?:-(\d{2}))?)? # 3 DD
50
+ (?:
51
+ T(\d{2}): # 4 HH
52
+ (\d{2}) # 5 mm
53
+ (?::(\d{2}) # 6 ss
54
+ (?:\.(\d{3}))?)? # 7 msec
55
+ (?:(Z)| # 8 Z
56
+ ([+\-]) # 9 ±
57
+ (\d{2}) # 10 tzHH
58
+ (?::(\d{2}))? # 11 tzmm
59
+ )?
60
+ )?
61
+ $
62
+ ///
63
+
64
+ Batman.mixin Batman.Encoders,
65
+ railsDate:
66
+ defaultTimezoneOffset: (new Date()).getTimezoneOffset()
67
+ encode: (value) -> value
68
+ decode: (value) ->
69
+ # Thanks to https://github.com/csnover/js-iso8601 for the majority of this algorithm.
70
+ # MIT Licensed
71
+ if value?
72
+ if (obj = date_re.exec(value))
73
+ # avoid NaN timestamps caused by “undefined” values being passed to Date.UTC
74
+ for key in numericKeys
75
+ obj[key] = +obj[key] or 0
76
+
77
+ # allow undefined days and months
78
+ obj[2] = (+obj[2] || 1) - 1;
79
+ obj[3] = +obj[3] || 1;
80
+
81
+ # process timezone by adjusting minutes
82
+ if obj[8] != "Z" and obj[9] != undefined
83
+ minutesOffset = obj[10] * 60 + obj[11]
84
+ minutesOffset = 0 - minutesOffset if obj[9] == "+"
85
+ else
86
+ minutesOffset = Batman.Encoders.railsDate.defaultTimezoneOffset
87
+ return new Date(Date.UTC(obj[1], obj[2], obj[3], obj[4], obj[5] + minutesOffset, obj[6], obj[7]))
88
+ else
89
+ Batman.developer.warn "Unrecognized rails date #{value}!"
90
+ return Date.parse(value)
91
+
92
+ class Batman.RailsStorage extends Batman.RestStorage
93
+
94
+ _serializeToFormData: (data) -> param(data)
95
+
96
+ urlForRecord: -> @_addJsonExtension(super)
97
+ urlForCollection: -> @_addJsonExtension(super)
98
+
99
+ _addJsonExtension: (url) ->
100
+ if url.indexOf('?') isnt -1 or url.substr(-5, 5) is '.json'
101
+ return url
102
+ url + '.json'
103
+
104
+ _errorsFrom422Response: (response) -> JSON.parse(response)
105
+
106
+ @::before 'update', 'create', (env, next) ->
107
+ if @serializeAsForm && !Batman.Request.dataHasFileUploads(env.options.data)
108
+ env.options.data = @_serializeToFormData(env.options.data)
109
+ next()
110
+
111
+ @::after 'update', 'create', ({error, record, response}, next) ->
112
+ env = arguments[0]
113
+ if error
114
+ # Rails validation errors
115
+ if error.request?.get('status') == 422
116
+ try
117
+ validationErrors = @_errorsFrom422Response(response)
118
+ catch extractionError
119
+ env.error = extractionError
120
+ return next()
121
+
122
+ for key, errorsArray of validationErrors
123
+ for validationError in errorsArray
124
+ record.get('errors').add(key, validationError)
125
+
126
+ env.result = record
127
+ env.error = record.get('errors')
128
+ return next()
129
+ next()
130
+
131
+ if (module? && require?)
132
+ module.exports = applyExtra
133
+ else
134
+ applyExtra(Batman)
@@ -0,0 +1,496 @@
1
+ #
2
+ # batman.solo.coffee
3
+ # batman.js
4
+ #
5
+ # Created by Nick Small
6
+ # Copyright 2011, Shopify
7
+ #
8
+ `
9
+ /*!
10
+ * Reqwest! A general purpose XHR connection manager
11
+ * (c) Dustin Diaz 2011
12
+ * https://github.com/ded/reqwest
13
+ * license MIT
14
+ */
15
+ !function (name, definition) {
16
+ if (typeof module != 'undefined') module.exports = definition()
17
+ else if (typeof define == 'function' && define.amd) define(name, definition)
18
+ else this[name] = definition()
19
+ }('reqwest', function () {
20
+
21
+ var context = this
22
+ , win = window
23
+ , doc = document
24
+ , old = context.reqwest
25
+ , twoHundo = /^20\d$/
26
+ , byTag = 'getElementsByTagName'
27
+ , readyState = 'readyState'
28
+ , contentType = 'Content-Type'
29
+ , requestedWith = 'X-Requested-With'
30
+ , head = doc[byTag]('head')[0]
31
+ , uniqid = 0
32
+ , lastValue // data stored by the most recent JSONP callback
33
+ , xmlHttpRequest = 'XMLHttpRequest'
34
+ , isArray = typeof Array.isArray == 'function' ? Array.isArray : function (a) {
35
+ return a instanceof Array
36
+ }
37
+ , defaultHeaders = {
38
+ contentType: 'application/x-www-form-urlencoded'
39
+ , accept: {
40
+ '*': 'text/javascript, text/html, application/xml, text/xml, */*'
41
+ , xml: 'application/xml, text/xml'
42
+ , html: 'text/html'
43
+ , text: 'text/plain'
44
+ , json: 'application/json, text/javascript'
45
+ , js: 'application/javascript, text/javascript'
46
+ }
47
+ , requestedWith: xmlHttpRequest
48
+ }
49
+ , xhr = win[xmlHttpRequest] ?
50
+ function () {
51
+ return new XMLHttpRequest()
52
+ } :
53
+ function () {
54
+ return new ActiveXObject('Microsoft.XMLHTTP')
55
+ }
56
+
57
+ function handleReadyState(o, success, error) {
58
+ return function () {
59
+ if (o && o[readyState] == 4) {
60
+ if (twoHundo.test(o.status)) {
61
+ success(o)
62
+ } else {
63
+ error(o)
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ function setHeaders(http, o) {
70
+ var headers = o.headers || {}, h
71
+ headers.Accept = headers.Accept || defaultHeaders.accept[o.type] || defaultHeaders.accept['*']
72
+ // breaks cross-origin requests with legacy browsers
73
+ if (!o.crossOrigin && !headers[requestedWith]) headers[requestedWith] = defaultHeaders.requestedWith
74
+ if (!headers[contentType]) headers[contentType] = o.contentType || defaultHeaders.contentType
75
+ for (h in headers) {
76
+ headers.hasOwnProperty(h) && http.setRequestHeader(h, headers[h])
77
+ }
78
+ }
79
+
80
+ function generalCallback(data) {
81
+ lastValue = data
82
+ }
83
+
84
+ function urlappend(url, s) {
85
+ return url + (/\?/.test(url) ? '&' : '?') + s
86
+ }
87
+
88
+ function handleJsonp(o, fn, err, url) {
89
+ var reqId = uniqid++
90
+ , cbkey = o.jsonpCallback || 'callback' // the 'callback' key
91
+ , cbval = o.jsonpCallbackName || ('reqwest_' + reqId) // the 'callback' value
92
+ , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)')
93
+ , match = url.match(cbreg)
94
+ , script = doc.createElement('script')
95
+ , loaded = 0
96
+
97
+ if (match) {
98
+ if (match[3] === '?') {
99
+ url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name
100
+ } else {
101
+ cbval = match[3] // provided callback func name
102
+ }
103
+ } else {
104
+ url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em
105
+ }
106
+
107
+ win[cbval] = generalCallback
108
+
109
+ script.type = 'text/javascript'
110
+ script.src = url
111
+ script.async = true
112
+ if (typeof script.onreadystatechange !== 'undefined') {
113
+ // need this for IE due to out-of-order onreadystatechange(), binding script
114
+ // execution to an event listener gives us control over when the script
115
+ // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
116
+ script.event = 'onclick'
117
+ script.htmlFor = script.id = '_reqwest_' + reqId
118
+ }
119
+
120
+ script.onload = script.onreadystatechange = function () {
121
+ if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) {
122
+ return false
123
+ }
124
+ script.onload = script.onreadystatechange = null
125
+ script.onclick && script.onclick()
126
+ // Call the user callback with the last value stored and clean up values and scripts.
127
+ o.success && o.success(lastValue)
128
+ lastValue = undefined
129
+ head.removeChild(script)
130
+ loaded = 1
131
+ }
132
+
133
+ // Add the script to the DOM head
134
+ head.appendChild(script)
135
+ }
136
+
137
+ function getRequest(o, fn, err) {
138
+ var method = (o.method || 'GET').toUpperCase()
139
+ , url = typeof o === 'string' ? o : o.url
140
+ // convert non-string objects to query-string form unless o.processData is false
141
+ , data = (o.processData !== false && o.data && typeof o.data !== 'string')
142
+ ? reqwest.toQueryString(o.data)
143
+ : (o.data || null)
144
+ , http
145
+
146
+ // if we're working on a GET request and we have data then we should append
147
+ // query string to end of URL and not post data
148
+ if ((o.type == 'jsonp' || method == 'GET') && data) {
149
+ url = urlappend(url, data)
150
+ data = null
151
+ }
152
+
153
+ if (o.type == 'jsonp') return handleJsonp(o, fn, err, url)
154
+
155
+ http = xhr()
156
+ http.open(method, url, true)
157
+ setHeaders(http, o)
158
+ http.onreadystatechange = handleReadyState(http, fn, err)
159
+ o.before && o.before(http)
160
+ http.send(data)
161
+ return http
162
+ }
163
+
164
+ function Reqwest(o, fn) {
165
+ this.o = o
166
+ this.fn = fn
167
+ init.apply(this, arguments)
168
+ }
169
+
170
+ function setType(url) {
171
+ var m = url.match(/\.(json|jsonp|html|xml)(\?|$)/)
172
+ return m ? m[1] : 'js'
173
+ }
174
+
175
+ function init(o, fn) {
176
+ this.url = typeof o == 'string' ? o : o.url
177
+ this.timeout = null
178
+ var type = o.type || setType(this.url)
179
+ , self = this
180
+ fn = fn || function () {}
181
+
182
+ if (o.timeout) {
183
+ this.timeout = setTimeout(function () {
184
+ self.abort()
185
+ }, o.timeout)
186
+ }
187
+
188
+ function complete(resp) {
189
+ o.timeout && clearTimeout(self.timeout)
190
+ self.timeout = null
191
+ o.complete && o.complete(resp)
192
+ }
193
+
194
+ function success(resp) {
195
+ var r = resp.responseText
196
+ if (r) {
197
+ switch (type) {
198
+ case 'json':
199
+ try {
200
+ resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')')
201
+ } catch (err) {
202
+ return error(resp, 'Could not parse JSON in response', err)
203
+ }
204
+ break;
205
+ case 'js':
206
+ resp = eval(r)
207
+ break;
208
+ case 'html':
209
+ resp = r
210
+ break;
211
+ }
212
+ }
213
+
214
+ fn(resp)
215
+ o.success && o.success(resp)
216
+
217
+ complete(resp)
218
+ }
219
+
220
+ function error(resp, msg, t) {
221
+ o.error && o.error(resp, msg, t)
222
+ complete(resp)
223
+ }
224
+
225
+ this.request = getRequest(o, success, error)
226
+ }
227
+
228
+ Reqwest.prototype = {
229
+ abort: function () {
230
+ this.request.abort()
231
+ }
232
+
233
+ , retry: function () {
234
+ init.call(this, this.o, this.fn)
235
+ }
236
+ }
237
+
238
+ function reqwest(o, fn) {
239
+ return new Reqwest(o, fn)
240
+ }
241
+
242
+ // normalize newline variants according to spec -> CRLF
243
+ function normalize(s) {
244
+ return s ? s.replace(/\r?\n/g, '\r\n') : ''
245
+ }
246
+
247
+ function serial(el, cb) {
248
+ var n = el.name
249
+ , t = el.tagName.toLowerCase()
250
+ , optCb = function(o) {
251
+ // IE gives value="" even where there is no value attribute
252
+ // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273
253
+ if (o && !o.disabled)
254
+ cb(n, normalize(o.attributes.value && o.attributes.value.specified ? o.value : o.text))
255
+ }
256
+
257
+ // don't serialize elements that are disabled or without a name
258
+ if (el.disabled || !n) return;
259
+
260
+ switch (t) {
261
+ case 'input':
262
+ if (!/reset|button|image|file/i.test(el.type)) {
263
+ var ch = /checkbox/i.test(el.type)
264
+ , ra = /radio/i.test(el.type)
265
+ , val = el.value;
266
+ // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here
267
+ (!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val))
268
+ }
269
+ break;
270
+ case 'textarea':
271
+ cb(n, normalize(el.value))
272
+ break;
273
+ case 'select':
274
+ if (el.type.toLowerCase() === 'select-one') {
275
+ optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null)
276
+ } else {
277
+ for (var i = 0; el.length && i < el.length; i++) {
278
+ el.options[i].selected && optCb(el.options[i])
279
+ }
280
+ }
281
+ break;
282
+ }
283
+ }
284
+
285
+ // collect up all form elements found from the passed argument elements all
286
+ // the way down to child elements; pass a '<form>' or form fields.
287
+ // called with 'this'=callback to use for serial() on each element
288
+ function eachFormElement() {
289
+ var cb = this
290
+ , e, i, j
291
+ , serializeSubtags = function(e, tags) {
292
+ for (var i = 0; i < tags.length; i++) {
293
+ var fa = e[byTag](tags[i])
294
+ for (j = 0; j < fa.length; j++) serial(fa[j], cb)
295
+ }
296
+ }
297
+
298
+ for (i = 0; i < arguments.length; i++) {
299
+ e = arguments[i]
300
+ if (/input|select|textarea/i.test(e.tagName)) serial(e, cb)
301
+ serializeSubtags(e, [ 'input', 'select', 'textarea' ])
302
+ }
303
+ }
304
+
305
+ // standard query string style serialization
306
+ function serializeQueryString() {
307
+ return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments))
308
+ }
309
+
310
+ // { 'name': 'value', ... } style serialization
311
+ function serializeHash() {
312
+ var hash = {}
313
+ eachFormElement.apply(function (name, value) {
314
+ if (name in hash) {
315
+ hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]])
316
+ hash[name].push(value)
317
+ } else hash[name] = value
318
+ }, arguments)
319
+ return hash
320
+ }
321
+
322
+ // [ { name: 'name', value: 'value' }, ... ] style serialization
323
+ reqwest.serializeArray = function () {
324
+ var arr = []
325
+ eachFormElement.apply(function(name, value) {
326
+ arr.push({name: name, value: value})
327
+ }, arguments)
328
+ return arr
329
+ }
330
+
331
+ reqwest.serialize = function () {
332
+ if (arguments.length === 0) return ''
333
+ var opt, fn
334
+ , args = Array.prototype.slice.call(arguments, 0)
335
+
336
+ opt = args.pop()
337
+ opt && opt.nodeType && args.push(opt) && (opt = null)
338
+ opt && (opt = opt.type)
339
+
340
+ if (opt == 'map') fn = serializeHash
341
+ else if (opt == 'array') fn = reqwest.serializeArray
342
+ else fn = serializeQueryString
343
+
344
+ return fn.apply(null, args)
345
+ }
346
+
347
+ reqwest.toQueryString = function (o) {
348
+ var qs = '', i
349
+ , enc = encodeURIComponent
350
+ , push = function (k, v) {
351
+ qs += enc(k) + '=' + enc(v) + '&'
352
+ }
353
+
354
+ if (isArray(o)) {
355
+ for (i = 0; o && i < o.length; i++) push(o[i].name, o[i].value)
356
+ } else {
357
+ for (var k in o) {
358
+ if (!Object.hasOwnProperty.call(o, k)) continue;
359
+ var v = o[k]
360
+ if (isArray(v)) {
361
+ for (i = 0; i < v.length; i++) push(k, v[i])
362
+ } else push(k, o[k])
363
+ }
364
+ }
365
+
366
+ // spaces should be + according to spec
367
+ return qs.replace(/&$/, '').replace(/%20/g,'+')
368
+ }
369
+
370
+ // jQuery and Zepto compatibility, differences can be remapped here so you can call
371
+ // .ajax.compat(options, callback)
372
+ reqwest.compat = function (o, fn) {
373
+ if (o) {
374
+ o.type && (o.method = o.type) && delete o.type
375
+ o.dataType && (o.type = o.dataType)
376
+ o.jsonpCallback && (o.jsonpCallbackName = o.jsonpCallback) && delete o.jsonpCallback
377
+ o.jsonp && (o.jsonpCallback = o.jsonp)
378
+ }
379
+ return new Reqwest(o, fn)
380
+ }
381
+
382
+ return reqwest
383
+ })
384
+
385
+ `
386
+ (exports ? this).reqwest = if window? then window.reqwest else reqwest
387
+
388
+ # `param` and `buildParams` stolen from jQuery
389
+ #
390
+ # jQuery JavaScript Library v1.6.1
391
+ # http://jquery.com/
392
+ #
393
+ # Copyright 2011, John Resig
394
+ # Dual licensed under the MIT or GPL Version 2 licenses.
395
+ # http://jquery.org/license
396
+ rbracket = /\[\]$/
397
+ r20 = /%20/g
398
+ param = (a) ->
399
+ return a if typeof a is 'string'
400
+ s = []
401
+ add = (key, value) ->
402
+ value = value() if typeof value is 'function'
403
+ value = '' unless value?
404
+ s[s.length] = encodeURIComponent(key) + "=" + encodeURIComponent(value)
405
+
406
+ if Batman.typeOf(a) is 'Array'
407
+ for value, name of a
408
+ add name, value
409
+ else
410
+ for own k, v of a
411
+ buildParams k, v, add
412
+ s.join("&").replace r20, "+"
413
+
414
+ buildParams = (prefix, obj, add) ->
415
+ if Batman.typeOf(obj) is 'Array'
416
+ for v, i in obj
417
+ if rbracket.test(prefix)
418
+ add prefix, v
419
+ else
420
+ buildParams prefix + "[" + (if typeof v == "object" or Batman.typeOf(v) is 'Array' then i else "") + "]", v, add
421
+ else if obj? and typeof obj == "object"
422
+ for name of obj
423
+ buildParams prefix + "[" + name + "]", obj[name], add
424
+ else
425
+ add prefix, obj
426
+
427
+ Batman.Request::send = (data) ->
428
+ data ?= @get('data')
429
+ @fire 'loading'
430
+
431
+ options =
432
+ url: @get 'url'
433
+ method: @get 'method'
434
+ type: @get 'type'
435
+ headers: @get 'headers'
436
+
437
+ success: (response) =>
438
+ @set 'response', response
439
+ @set 'status', (xhr?.status or 200)
440
+ @fire 'success', response
441
+
442
+ error: (xhr) =>
443
+ @set 'response', xhr.responseText || xhr.content
444
+ @set 'status', xhr.status
445
+ xhr.request = @
446
+ @fire 'error', xhr
447
+
448
+ complete: =>
449
+ @fire 'loaded'
450
+
451
+ if options.method in ['PUT', 'POST']
452
+ if @hasFileUploads()
453
+ options.data = @constructor.objectToFormData(data)
454
+ else
455
+ options.contentType = @get('contentType')
456
+ options.data = param(data)
457
+
458
+ else
459
+ options.data = data
460
+
461
+ # Fires the request. Grab a reference to the xhr object so we can get the status code elsewhere.
462
+ xhr = (reqwest options).request
463
+
464
+ prefixes = ['Webkit', 'Moz', 'O', 'ms', '']
465
+ Batman.mixins.animation =
466
+ initialize: ->
467
+ for prefix in prefixes
468
+ @style["#{prefix}Transform"] = 'scale(0, 0)'
469
+ @style.opacity = 0
470
+
471
+ @style["#{prefix}TransitionProperty"] = "#{if prefix then '-' + prefix.toLowerCase() + '-' else ''}transform, opacity"
472
+ @style["#{prefix}TransitionDuration"] = "0.8s, 0.55s"
473
+ @style["#{prefix}TransformOrigin"] = "left top"
474
+ @
475
+ show: (addToParent) ->
476
+ show = =>
477
+ @style.opacity = 1
478
+ for prefix in prefixes
479
+ @style["#{prefix}Transform"] = 'scale(1, 1)'
480
+ @
481
+
482
+ if addToParent
483
+ addToParent.append?.appendChild @
484
+ addToParent.before?.parentNode.insertBefore @, addToParent.before
485
+
486
+ setTimeout show, 0
487
+ else
488
+ show()
489
+ @
490
+ hide: (shouldRemove) ->
491
+ @style.opacity = 0
492
+ for prefix in prefixes
493
+ @style["#{prefix}Transform"] = 'scale(0, 0)'
494
+
495
+ setTimeout((=> @parentNode?.removeChild @), 600) if shouldRemove
496
+ @