Update JSON API
[src/app-framework-demo.git] / afm-client / bower_components / tether / coffee / utils.coffee
1 @Tether ?= {modules: []}
2
3 getScrollParent = (el) ->
4   position = getComputedStyle(el).position
5
6   if position is 'fixed'
7     return el
8
9   scrollParent = undefined
10
11   parent = el
12   while parent = parent.parentNode
13     try
14       style = getComputedStyle parent
15
16     return parent if not style?
17
18     if /(auto|scroll)/.test(style['overflow'] + style['overflow-y'] + style['overflow-x'])
19       if position isnt 'absolute' or style['position'] in ['relative', 'absolute', 'fixed']
20         return parent
21
22   return document.body
23
24 uniqueId = do ->
25   id = 0
26   ->
27     id++
28
29 zeroPosCache = {}
30 getOrigin = (doc) ->
31   # getBoundingClientRect is unfortunately too accurate.  It introduces a pixel or two of
32   # jitter as the user scrolls that messes with our ability to detect if two positions
33   # are equivilant or not.  We place an element at the top left of the page that will
34   # get the same jitter, so we can cancel the two out.
35   node = doc._tetherZeroElement
36   if not node?
37     node = doc.createElement 'div'
38     node.setAttribute 'data-tether-id', uniqueId()
39     extend node.style,
40       top: 0
41       left: 0
42       position: 'absolute'
43
44     doc.body.appendChild node
45
46     doc._tetherZeroElement = node
47
48   id = node.getAttribute 'data-tether-id'
49   if not zeroPosCache[id]?
50     zeroPosCache[id] = {}
51     for k, v of node.getBoundingClientRect()
52       # Can't use extend, as on IE9, elements don't resolve to be hasOwnProperty
53       zeroPosCache[id][k] = v
54
55     # Clear the cache when this position call is done
56     defer ->
57       zeroPosCache[id] = undefined
58
59   return zeroPosCache[id]
60
61 node = null
62 getBounds = (el) ->
63   if el is document
64     doc = document
65     el = document.documentElement
66   else
67     doc = el.ownerDocument
68
69   docEl = doc.documentElement
70
71   box = {}
72   # The original object returned by getBoundingClientRect is immutable, so we clone it
73   # We can't use extend because the properties are not considered part of the object by hasOwnProperty in IE9
74   for k, v of el.getBoundingClientRect()
75     box[k] = v
76
77   origin = getOrigin doc
78
79   box.top -= origin.top
80   box.left -= origin.left
81
82   box.width ?= document.body.scrollWidth - box.left - box.right
83   box.height ?= document.body.scrollHeight - box.top - box.bottom
84
85   box.top = box.top - docEl.clientTop
86   box.left = box.left - docEl.clientLeft
87   box.right = doc.body.clientWidth - box.width - box.left
88   box.bottom = doc.body.clientHeight - box.height - box.top
89
90   box
91
92 getOffsetParent = (el) ->
93   el.offsetParent or document.documentElement
94
95 getScrollBarSize = ->
96   inner = document.createElement 'div'
97   inner.style.width = '100%'
98   inner.style.height = '200px'
99
100   outer = document.createElement 'div'
101   extend outer.style,
102     position: 'absolute'
103     top: 0
104     left: 0
105     pointerEvents: 'none'
106     visibility: 'hidden'
107     width: '200px'
108     height: '150px'
109     overflow: 'hidden'
110
111   outer.appendChild inner
112
113   document.body.appendChild outer
114
115   widthContained = inner.offsetWidth
116   outer.style.overflow = 'scroll'
117   widthScroll = inner.offsetWidth
118
119   if widthContained is widthScroll
120     widthScroll = outer.clientWidth
121
122   document.body.removeChild outer
123
124   width = widthContained - widthScroll
125
126   {width, height: width}
127
128 extend = (out={}) ->
129   args = []
130   Array::push.apply(args, arguments)
131
132   for obj in args[1..] when obj
133     for own key, val of obj
134       out[key] = val
135
136   out
137
138 removeClass = (el, name) ->
139   if el.classList?
140     el.classList.remove(cls) for cls in name.split(' ') when cls.trim()
141   else
142     el.className = el.className.replace new RegExp("(^| )#{ name.split(' ').join('|') }( |$)", 'gi'), ' '
143
144 addClass = (el, name) ->
145   if el.classList?
146     el.classList.add(cls) for cls in name.split(' ') when cls.trim()
147   else
148     removeClass el, name
149     el.className += " #{ name }"
150
151 hasClass = (el, name) ->
152   if el.classList?
153     el.classList.contains(name)
154   else
155     new RegExp("(^| )#{ name }( |$)", 'gi').test(el.className)
156
157 updateClasses = (el, add, all) ->
158   # Of the set of 'all' classes, we need the 'add' classes, and only the
159   # 'add' classes to be set.
160   for cls in all when cls not in add
161     if hasClass(el, cls)
162       removeClass el, cls
163
164   for cls in add
165     if not hasClass(el, cls)
166       addClass el, cls
167
168 deferred = []
169
170 defer = (fn) ->
171   deferred.push fn
172
173 flush = ->
174   fn() while fn = deferred.pop()
175
176 class Evented
177   on: (event, handler, ctx, once=false) ->
178     @bindings ?= {}
179     @bindings[event] ?= []
180     @bindings[event].push {handler, ctx, once}
181
182   once: (event, handler, ctx) ->
183     @on(event, handler, ctx, true)
184
185   off: (event, handler) ->
186     return unless @bindings?[event]?
187
188     if not handler?
189       delete @bindings[event]
190     else
191       i = 0
192       while i < @bindings[event].length
193         if @bindings[event][i].handler is handler
194           @bindings[event].splice i, 1
195         else
196           i++
197
198   trigger: (event, args...) ->
199     if @bindings?[event]
200       i = 0
201       while i < @bindings[event].length
202         {handler, ctx, once} = @bindings[event][i]
203
204         handler.apply(ctx ? @, args)
205
206         if once
207           @bindings[event].splice i, 1
208         else
209           i++
210
211 @Tether.Utils = {getScrollParent, getBounds, getOffsetParent, extend, addClass, removeClass, hasClass, updateClasses, defer, flush, uniqueId, Evented, getScrollBarSize}