breezy 0.1.3 → 0.1.4
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.
- 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
|
+
|