batman-rails 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ @