breezy 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/app/views/breezy/response.html.erb +0 -0
- data/lib/assets/javascripts/breezy/component_url.coffee +61 -0
- data/lib/assets/javascripts/breezy/controller.coffee +197 -0
- data/lib/assets/javascripts/breezy/csrf_token.coffee +9 -0
- data/lib/assets/javascripts/breezy/doubly_linked_list.coffee +57 -0
- data/lib/assets/javascripts/breezy/parallel_queue.coffee +35 -0
- data/lib/assets/javascripts/breezy/progress_bar.coffee +139 -0
- data/lib/assets/javascripts/breezy/remote.coffee +136 -0
- data/lib/assets/javascripts/breezy/snapshot.coffee +100 -0
- data/lib/assets/javascripts/breezy/start.coffee +60 -0
- data/lib/assets/javascripts/breezy/utils.coffee +174 -0
- data/lib/breezy/version.rb +1 -1
- data/lib/breezy_template/search_extension.rb +39 -14
- data/lib/generators/breezy/install/install_generator.rb +61 -0
- data/lib/generators/breezy/install/templates/Default.js.jsx +7 -0
- data/lib/generators/breezy/install/templates/View.js.jsx +7 -0
- data/lib/generators/breezy/install/templates/boot.js +5 -0
- data/lib/generators/breezy/view/templates/view.js.breezy +15 -0
- data/lib/generators/breezy/view/templates/view.js.jsx +8 -0
- data/lib/generators/breezy/view/view_generator.rb +35 -0
- data/test/breezy_template_test.rb +49 -5
- metadata +20 -2
@@ -0,0 +1,136 @@
|
|
1
|
+
class Breezy.Remote
|
2
|
+
SUPPORTED_METHODS = ['GET', 'PUT', 'POST', 'DELETE', 'PATCH']
|
3
|
+
FALLBACK_LINK_METHOD = 'GET'
|
4
|
+
FALLBACK_FORM_METHOD = 'POST'
|
5
|
+
|
6
|
+
constructor: (target, opts={})->
|
7
|
+
@target = target
|
8
|
+
@payload = ''
|
9
|
+
@contentType = null
|
10
|
+
@setRequestType(target)
|
11
|
+
@async = @getBZAttribute(target, 'bz-remote-async') || false
|
12
|
+
@pushState = !(@getBZAttribute(target, 'bz-push-state') == 'false')
|
13
|
+
@httpUrl = target.getAttribute('href') || target.getAttribute('action')
|
14
|
+
@silent = @getBZAttribute(target, 'bz-silent') || false
|
15
|
+
@setPayload(target)
|
16
|
+
|
17
|
+
toOptions: =>
|
18
|
+
requestMethod: @actualRequestType
|
19
|
+
payload: @payload
|
20
|
+
contentType: @contentType
|
21
|
+
silent: @silent
|
22
|
+
target: @target
|
23
|
+
async: @async
|
24
|
+
pushState: @pushState
|
25
|
+
|
26
|
+
setRequestType: (target)=>
|
27
|
+
if target.tagName == 'A'
|
28
|
+
@httpRequestType = @getBZAttribute(target, 'bz-remote')
|
29
|
+
@httpRequestType ?= ''
|
30
|
+
@httpRequestType = @httpRequestType.toUpperCase()
|
31
|
+
|
32
|
+
if @httpRequestType not in SUPPORTED_METHODS
|
33
|
+
@httpRequestType = FALLBACK_LINK_METHOD
|
34
|
+
|
35
|
+
if target.tagName == 'FORM'
|
36
|
+
formActionMethod = target.getAttribute('method')
|
37
|
+
@httpRequestType = formActionMethod || @getBZAttribute(target, 'bz-remote')
|
38
|
+
@httpRequestType ?= ''
|
39
|
+
@httpRequestType = @httpRequestType.toUpperCase()
|
40
|
+
|
41
|
+
if @httpRequestType not in SUPPORTED_METHODS
|
42
|
+
@httpRequestType = FALLBACK_FORM_METHOD
|
43
|
+
|
44
|
+
@actualRequestType = if @httpRequestType == 'GET' then 'GET' else 'POST'
|
45
|
+
|
46
|
+
setPayload: (target)=>
|
47
|
+
if target.tagName == 'FORM'
|
48
|
+
@payload = @nativeEncodeForm(target)
|
49
|
+
|
50
|
+
if @payload not instanceof FormData
|
51
|
+
if @payload.indexOf("_method") == -1 && @httpRequestType && @actualRequestType != 'GET'
|
52
|
+
@contentType = "application/x-www-form-urlencoded; charset=UTF-8"
|
53
|
+
@payload = @formAppend(@payload, "_method", @httpRequestType)
|
54
|
+
else
|
55
|
+
if !target.querySelector('[name=_method]') && @httpRequestType not in ['GET', 'POST']
|
56
|
+
@payload.append("_method", @httpRequestType)
|
57
|
+
|
58
|
+
isValid: =>
|
59
|
+
@isValidLink() || @isValidForm()
|
60
|
+
|
61
|
+
isValidLink: =>
|
62
|
+
if @target.tagName != 'A'
|
63
|
+
return false
|
64
|
+
|
65
|
+
@hasBZAttribute(@target, 'bz-remote')
|
66
|
+
|
67
|
+
isValidForm: =>
|
68
|
+
if @target.tagName != 'FORM'
|
69
|
+
return false
|
70
|
+
@hasBZAttribute(@target, 'bz-remote') &&
|
71
|
+
@target.getAttribute('action')?
|
72
|
+
|
73
|
+
formAppend: (uriEncoded, key, value) ->
|
74
|
+
uriEncoded += "&" if uriEncoded.length
|
75
|
+
uriEncoded += "#{encodeURIComponent(key)}=#{encodeURIComponent(value)}"
|
76
|
+
|
77
|
+
formDataAppend: (formData, input) ->
|
78
|
+
if input.type == 'file'
|
79
|
+
for file in input.files
|
80
|
+
formData.append(input.name, file)
|
81
|
+
else
|
82
|
+
formData.append(input.name, input.value)
|
83
|
+
formData
|
84
|
+
|
85
|
+
nativeEncodeForm: (form) ->
|
86
|
+
formData = new FormData
|
87
|
+
@iterateOverFormInputs form, (input) =>
|
88
|
+
formData = @formDataAppend(formData, input)
|
89
|
+
formData
|
90
|
+
|
91
|
+
iterateOverFormInputs: (form, callback) ->
|
92
|
+
inputs = @enabledInputs(form)
|
93
|
+
for input in inputs
|
94
|
+
inputEnabled = !input.disabled
|
95
|
+
radioOrCheck = (input.type == 'checkbox' || input.type == 'radio')
|
96
|
+
|
97
|
+
if inputEnabled && input.name
|
98
|
+
if (radioOrCheck && input.checked) || !radioOrCheck
|
99
|
+
callback(input)
|
100
|
+
|
101
|
+
enabledInputs: (form) ->
|
102
|
+
selector = "input:not([type='reset']):not([type='button']):not([type='submit']):not([type='image']), select, textarea"
|
103
|
+
inputs = Array::slice.call(form.querySelectorAll(selector))
|
104
|
+
disabledNodes = Array::slice.call(@querySelectorAllBZAttribute(form, 'bz-remote-noserialize'))
|
105
|
+
|
106
|
+
return inputs unless disabledNodes.length
|
107
|
+
|
108
|
+
disabledInputs = disabledNodes
|
109
|
+
for node in disabledNodes
|
110
|
+
disabledInputs = disabledInputs.concat(Array::slice.call(node.querySelectorAll(selector)))
|
111
|
+
|
112
|
+
enabledInputs = []
|
113
|
+
for input in inputs when disabledInputs.indexOf(input) < 0
|
114
|
+
enabledInputs.push(input)
|
115
|
+
enabledInputs
|
116
|
+
|
117
|
+
bzAttribute: (attr) ->
|
118
|
+
bzAttr = if attr[0...3] == 'bz-'
|
119
|
+
"data-#{attr}"
|
120
|
+
else
|
121
|
+
"data-bz-#{attr}"
|
122
|
+
|
123
|
+
getBZAttribute: (node, attr) ->
|
124
|
+
bzAttr = @bzAttribute(attr)
|
125
|
+
(node.getAttribute(bzAttr) || node.getAttribute(attr))
|
126
|
+
|
127
|
+
querySelectorAllBZAttribute: (node, attr, value = null) ->
|
128
|
+
bzAttr = @bzAttribute(attr)
|
129
|
+
if value
|
130
|
+
node.querySelectorAll("[#{bzAttr}=#{value}], [#{attr}=#{value}]")
|
131
|
+
else
|
132
|
+
node.querySelectorAll("[#{bzAttr}], [#{attr}]")
|
133
|
+
|
134
|
+
hasBZAttribute: (node, attr) ->
|
135
|
+
bzAttr = @bzAttribute(attr)
|
136
|
+
node.getAttribute(bzAttr)? || node.getAttribute(attr)?
|
@@ -0,0 +1,100 @@
|
|
1
|
+
#= require breezy/component_url
|
2
|
+
#= require breezy/csrf_token
|
3
|
+
#= require breezy/utils
|
4
|
+
|
5
|
+
class Breezy.Snapshot
|
6
|
+
constructor: (@controller) ->
|
7
|
+
@pageCache = {}
|
8
|
+
@currentBrowserState = null
|
9
|
+
@pageCacheSize = 10
|
10
|
+
@currentPage = null
|
11
|
+
@loadedAssets= null
|
12
|
+
|
13
|
+
onHistoryChange: (event) =>
|
14
|
+
if event.state?.breezy && event.state.url != @currentBrowserState.url
|
15
|
+
previousUrl = new Breezy.ComponentUrl(@currentBrowserState.url)
|
16
|
+
newUrl = new Breezy.ComponentUrl(event.state.url)
|
17
|
+
|
18
|
+
if restorePoint = @pageCache[newUrl.absolute]
|
19
|
+
@cacheCurrentPage()
|
20
|
+
@currentPage = restorePoint
|
21
|
+
@controller.restore(@currentPage)
|
22
|
+
else
|
23
|
+
@controller.request event.target.location.href
|
24
|
+
|
25
|
+
constrainPageCacheTo: (limit = @pageCacheSize) =>
|
26
|
+
pageCacheKeys = Object.keys @pageCache
|
27
|
+
|
28
|
+
cacheTimesRecentFirst = pageCacheKeys.map (url) =>
|
29
|
+
@pageCache[url].cachedAt
|
30
|
+
.sort (a, b) -> b - a
|
31
|
+
|
32
|
+
for key in pageCacheKeys when @pageCache[key].cachedAt <= cacheTimesRecentFirst[limit]
|
33
|
+
delete @pageCache[key]
|
34
|
+
|
35
|
+
transitionCacheFor: (url) =>
|
36
|
+
return if url is @currentBrowserState.url
|
37
|
+
cachedPage = @pageCache[url]
|
38
|
+
cachedPage if cachedPage and !cachedPage.transitionCacheDisabled
|
39
|
+
|
40
|
+
pagesCached: (size = @pageCacheSize) =>
|
41
|
+
@pageCacheSize = parseInt(size) if /^[\d]+$/.test size
|
42
|
+
|
43
|
+
cacheCurrentPage: =>
|
44
|
+
return unless @currentPage
|
45
|
+
currentUrl = new Breezy.ComponentUrl @currentBrowserState.url
|
46
|
+
|
47
|
+
Breezy.Utils.merge @currentPage,
|
48
|
+
cachedAt: new Date().getTime()
|
49
|
+
positionY: window.pageYOffset
|
50
|
+
positionX: window.pageXOffset
|
51
|
+
url: currentUrl.relative
|
52
|
+
pathname: currentUrl.pathname
|
53
|
+
transition_cache: true
|
54
|
+
|
55
|
+
@pageCache[currentUrl.absolute] = @currentPage
|
56
|
+
|
57
|
+
rememberCurrentUrlAndState: =>
|
58
|
+
window.history.replaceState { breezy: true, url: document.location.href }, '', document.location.href
|
59
|
+
@currentBrowserState = window.history.state
|
60
|
+
|
61
|
+
removeParamFromUrl: (url, parameter) =>
|
62
|
+
return url
|
63
|
+
.replace(new RegExp('^([^#]*\?)(([^#]*)&)?' + parameter + '(\=[^&#]*)?(&|#|$)' ), '$1$3$5')
|
64
|
+
.replace(/^([^#]*)((\?)&|\?(#|$))/,'$1$3$4')
|
65
|
+
|
66
|
+
reflectNewUrl: (url) =>
|
67
|
+
if (url = new Breezy.ComponentUrl url).absolute != document.location.href
|
68
|
+
preservedHash = if url.hasNoHash() then document.location.hash else ''
|
69
|
+
fullUrl = url.absolute + preservedHash
|
70
|
+
fullUrl = @removeParamFromUrl(fullUrl, '_breezy_filter')
|
71
|
+
fullUrl = @removeParamFromUrl(fullUrl, '__')
|
72
|
+
|
73
|
+
window.history.pushState { breezy: true, url: url.absolute + preservedHash }, '', fullUrl
|
74
|
+
|
75
|
+
updateCurrentBrowserState: =>
|
76
|
+
@currentBrowserState = window.history.state
|
77
|
+
|
78
|
+
changePage: (nextPage, options) =>
|
79
|
+
if @currentPage and @assetsChanged(nextPage)
|
80
|
+
document.location.reload()
|
81
|
+
return
|
82
|
+
|
83
|
+
@currentPage = nextPage
|
84
|
+
@currentPage.title = options.title ? @currentPage.title
|
85
|
+
document.title = @currentPage.title if @currentPage.title isnt false
|
86
|
+
|
87
|
+
Breezy.CSRFToken.update @currentPage.csrf_token if @currentPage.csrf_token?
|
88
|
+
@updateCurrentBrowserState()
|
89
|
+
|
90
|
+
assetsChanged: (nextPage) =>
|
91
|
+
@loadedAssets ||= @currentPage.assets
|
92
|
+
fetchedAssets = nextPage.assets
|
93
|
+
fetchedAssets.length isnt @loadedAssets.length or Breezy.Utils.intersection(fetchedAssets, @loadedAssets).length isnt @loadedAssets.length
|
94
|
+
|
95
|
+
graftByKeypath: (keypath, node, opts={})=>
|
96
|
+
for k, v in @pageCache
|
97
|
+
@history.pageCache[k] = Breezy.Utils.graftByKeypath(keypath, node, v, opts)
|
98
|
+
|
99
|
+
@currentPage = Breezy.Utils.graftByKeypath(keypath, node, @currentPage, opts)
|
100
|
+
Breezy.Utils.triggerEvent Breezy.EVENTS.LOAD, @currentPage
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#= require breezy/controller
|
2
|
+
#= require breezy/remote
|
3
|
+
#= require breezy/utils
|
4
|
+
|
5
|
+
EVENTS =
|
6
|
+
BEFORE_CHANGE: 'breezy:click'
|
7
|
+
ERROR: 'breezy:request-error'
|
8
|
+
FETCH: 'breezy:request-start'
|
9
|
+
RECEIVE: 'breezy:request-end'
|
10
|
+
LOAD: 'breezy:load'
|
11
|
+
RESTORE: 'breezy:restore'
|
12
|
+
|
13
|
+
controller = new Breezy.Controller
|
14
|
+
progressBar = controller.progressBar
|
15
|
+
|
16
|
+
ProgressBarAPI =
|
17
|
+
enable: ->
|
18
|
+
progressBar.install()
|
19
|
+
disable: ->
|
20
|
+
progressBar.uninstall()
|
21
|
+
setDelay: (value) -> progressBar.setDelay(value)
|
22
|
+
start: (options) -> progressBar.start(options)
|
23
|
+
advanceTo: (value) -> progressBar.advanceTo(value)
|
24
|
+
done: -> progressBar.done()
|
25
|
+
|
26
|
+
remoteHandler = (ev) ->
|
27
|
+
target = ev.target
|
28
|
+
remote = new Breezy.Remote(target)
|
29
|
+
return unless remote.isValid()
|
30
|
+
ev.preventDefault()
|
31
|
+
controller.request remote.httpUrl, remote.toOptions()
|
32
|
+
|
33
|
+
browserSupportsCustomEvents =
|
34
|
+
document.addEventListener and document.createEvent
|
35
|
+
|
36
|
+
initializeBreezy = ->
|
37
|
+
ProgressBarAPI.enable()
|
38
|
+
window.addEventListener 'hashchange', controller.history.rememberCurrentUrlAndState, false
|
39
|
+
window.addEventListener 'popstate', controller.history.onHistoryChange, false
|
40
|
+
Breezy.Utils.documentListenerForLinks 'click', remoteHandler
|
41
|
+
document.addEventListener "submit", remoteHandler
|
42
|
+
|
43
|
+
if Breezy.Utils.browserSupportsBreezy()
|
44
|
+
visit = controller.request
|
45
|
+
initializeBreezy()
|
46
|
+
else
|
47
|
+
visit = (url = document.location.href) -> document.location.href = url
|
48
|
+
|
49
|
+
Breezy.controller = controller
|
50
|
+
Breezy.graftByKeypath = controller.history.graftByKeypath
|
51
|
+
Breezy.visit = visit
|
52
|
+
Breezy.replace = controller.replace
|
53
|
+
Breezy.cache = controller.cache
|
54
|
+
Breezy.pagesCached = controller.history.pagesCached
|
55
|
+
Breezy.enableTransitionCache = controller.enableTransitionCache
|
56
|
+
Breezy.disableRequestCaching = controller.disableRequestCaching
|
57
|
+
Breezy.ProgressBar = ProgressBarAPI
|
58
|
+
Breezy.supported = Breezy.Utils.browserSupportsBreezy()
|
59
|
+
Breezy.EVENTS = Breezy.Utils.clone(EVENTS)
|
60
|
+
Breezy.currentPage = controller.currentPage
|
@@ -0,0 +1,174 @@
|
|
1
|
+
reverseMerge = (dest, obj) ->
|
2
|
+
for k, v of obj
|
3
|
+
dest[k] = v if !dest.hasOwnProperty(k)
|
4
|
+
dest
|
5
|
+
|
6
|
+
merge = (dest, obj) ->
|
7
|
+
for k, v of obj
|
8
|
+
dest[k] = v
|
9
|
+
dest
|
10
|
+
|
11
|
+
clone = (original) ->
|
12
|
+
return original if not original? or typeof original isnt 'object'
|
13
|
+
copy = new original.constructor()
|
14
|
+
copy[key] = clone value for key, value of original
|
15
|
+
copy
|
16
|
+
|
17
|
+
withDefaults = (page, state) =>
|
18
|
+
currentUrl = new Breezy.ComponentUrl state.url
|
19
|
+
|
20
|
+
reverseMerge page,
|
21
|
+
url: currentUrl.relative
|
22
|
+
pathname: currentUrl.pathname
|
23
|
+
cachedAt: new Date().getTime()
|
24
|
+
assets: []
|
25
|
+
data: {}
|
26
|
+
title: ''
|
27
|
+
positionY: 0
|
28
|
+
positionX: 0
|
29
|
+
csrf_token: null
|
30
|
+
|
31
|
+
browserIsBuggy = () ->
|
32
|
+
# Copied from https://github.com/Modernizr/Modernizr/blob/master/feature-detects/history.js
|
33
|
+
ua = navigator.userAgent
|
34
|
+
(ua.indexOf('Android 2.') != -1 or ua.indexOf('Android 4.0') != -1) and
|
35
|
+
ua.indexOf('Mobile Safari') != -1 and
|
36
|
+
ua.indexOf('Chrome') == -1 and
|
37
|
+
ua.indexOf('Windows Phone') == -1
|
38
|
+
|
39
|
+
browserSupportsPushState = () ->
|
40
|
+
window.history and 'pushState' of window.history and 'state' of window.history
|
41
|
+
|
42
|
+
popCookie = (name) ->
|
43
|
+
value = document.cookie.match(new RegExp(name+"=(\\w+)"))?[1].toUpperCase() or ''
|
44
|
+
document.cookie = name + '=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/'
|
45
|
+
value
|
46
|
+
|
47
|
+
requestMethodIsSafe = -> popCookie('request_method') in ['GET','']
|
48
|
+
|
49
|
+
browserSupportsBreezy = ->
|
50
|
+
browserSupportsPushState() and !browserIsBuggy() and requestMethodIsSafe()
|
51
|
+
|
52
|
+
intersection = (a, b) ->
|
53
|
+
[a, b] = [b, a] if a.length > b.length
|
54
|
+
value for value in a when value in b
|
55
|
+
|
56
|
+
|
57
|
+
triggerEvent = (name, data, target = document) =>
|
58
|
+
event = document.createEvent 'Events'
|
59
|
+
event.data = data if data
|
60
|
+
event.initEvent name, true, true
|
61
|
+
target.dispatchEvent event
|
62
|
+
|
63
|
+
documentListenerForLinks = (eventType, handler) ->
|
64
|
+
document.addEventListener eventType, (ev) ->
|
65
|
+
target = ev.target
|
66
|
+
while target != document && target?
|
67
|
+
if target.nodeName == "A"
|
68
|
+
isNodeDisabled = target.getAttribute('disabled')
|
69
|
+
ev.preventDefault() if target.getAttribute('disabled')
|
70
|
+
unless isNodeDisabled
|
71
|
+
handler(ev)
|
72
|
+
return
|
73
|
+
|
74
|
+
target = target.parentNode
|
75
|
+
|
76
|
+
isObject = (val) ->
|
77
|
+
Object.prototype.toString.call(val) is '[object Object]'
|
78
|
+
|
79
|
+
isArray = (val) ->
|
80
|
+
Object.prototype.toString.call(val) is '[object Array]'
|
81
|
+
|
82
|
+
class Breezy.Grafter
|
83
|
+
constructor: ->
|
84
|
+
@current_path = []
|
85
|
+
|
86
|
+
graftByKeypath: (path, leaf, obj, opts={}) ->
|
87
|
+
if typeof path is "string"
|
88
|
+
path = path.split('.')
|
89
|
+
return @graftByKeypath(path, leaf, obj, opts)
|
90
|
+
|
91
|
+
head = path[0]
|
92
|
+
@current_path.push(head)
|
93
|
+
child = obj[head] if obj?
|
94
|
+
remaining = path.slice(1)
|
95
|
+
|
96
|
+
if path.length is 0
|
97
|
+
if opts.type == 'add' and isArray(obj)
|
98
|
+
copy = []
|
99
|
+
for child in obj
|
100
|
+
copy.push child
|
101
|
+
|
102
|
+
copy.push leaf
|
103
|
+
return copy
|
104
|
+
else
|
105
|
+
return leaf
|
106
|
+
|
107
|
+
if isObject(obj)
|
108
|
+
copy = {}
|
109
|
+
found = false
|
110
|
+
for key, value of obj
|
111
|
+
if key is head
|
112
|
+
node = @graftByKeypath(remaining, leaf, child, opts)
|
113
|
+
found = true unless child is node
|
114
|
+
copy[key] = node
|
115
|
+
else
|
116
|
+
copy[key] = value
|
117
|
+
|
118
|
+
return if found
|
119
|
+
@current_path.pop()
|
120
|
+
copy
|
121
|
+
else
|
122
|
+
Breezy.Utils.warn "Could not find key #{head} in keypath #{@current_path.join('.')}"
|
123
|
+
obj
|
124
|
+
|
125
|
+
else if isArray(obj)
|
126
|
+
[attr, id] = head.split('=')
|
127
|
+
found = false
|
128
|
+
if id == undefined
|
129
|
+
index = parseInt(attr)
|
130
|
+
child = obj[index]
|
131
|
+
node = @graftByKeypath(remaining, leaf, child, opts)
|
132
|
+
found = true unless child is node
|
133
|
+
|
134
|
+
copy = obj.slice(0, index)
|
135
|
+
copy.push(node)
|
136
|
+
copy = copy.concat(obj.slice(index + 1, obj.length))
|
137
|
+
else
|
138
|
+
id = parseInt(id) || 0
|
139
|
+
copy = []
|
140
|
+
for child in obj
|
141
|
+
if child[attr] == id
|
142
|
+
node = @graftByKeypath(remaining, leaf, child, opts)
|
143
|
+
found = true unless child is node
|
144
|
+
copy.push node
|
145
|
+
else
|
146
|
+
copy.push child
|
147
|
+
|
148
|
+
return if found
|
149
|
+
@current_path.pop()
|
150
|
+
copy
|
151
|
+
else
|
152
|
+
Breezy.Utils.warn "Could not find key #{head} in keypath #{@current_path.join('.')}"
|
153
|
+
obj
|
154
|
+
else
|
155
|
+
obj
|
156
|
+
|
157
|
+
|
158
|
+
|
159
|
+
@Breezy.Utils =
|
160
|
+
warn: ->
|
161
|
+
console.warn.apply(@, arguments)
|
162
|
+
graftByKeypath: ->
|
163
|
+
grafter = new Breezy.Grafter
|
164
|
+
grafter.graftByKeypath.apply(grafter, arguments)
|
165
|
+
documentListenerForLinks: documentListenerForLinks
|
166
|
+
reverseMerge: reverseMerge
|
167
|
+
merge: merge
|
168
|
+
clone: clone
|
169
|
+
withDefaults: withDefaults
|
170
|
+
browserSupportsBreezy: browserSupportsBreezy
|
171
|
+
intersection: intersection
|
172
|
+
triggerEvent: triggerEvent
|
173
|
+
|
174
|
+
|