unpoly-rails 0.28.1 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of unpoly-rails might be problematic. Click here for more details.

Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -1
  3. data/dist/unpoly.js +376 -259
  4. data/dist/unpoly.min.js +3 -3
  5. data/lib/assets/javascripts/unpoly/browser.js.coffee +2 -2
  6. data/lib/assets/javascripts/unpoly/bus.js.coffee +40 -13
  7. data/lib/assets/javascripts/unpoly/flow.js.coffee +9 -9
  8. data/lib/assets/javascripts/unpoly/form.js.coffee +18 -18
  9. data/lib/assets/javascripts/unpoly/history.js.coffee +1 -1
  10. data/lib/assets/javascripts/unpoly/layout.js.coffee +9 -9
  11. data/lib/assets/javascripts/unpoly/link.js.coffee +34 -24
  12. data/lib/assets/javascripts/unpoly/modal.js.coffee +38 -37
  13. data/lib/assets/javascripts/unpoly/motion.js.coffee +20 -23
  14. data/lib/assets/javascripts/unpoly/navigation.js.coffee +101 -37
  15. data/lib/assets/javascripts/unpoly/popup.js.coffee +24 -16
  16. data/lib/assets/javascripts/unpoly/proxy.js.coffee +3 -3
  17. data/lib/assets/javascripts/unpoly/syntax.js.coffee +29 -37
  18. data/lib/assets/javascripts/unpoly/tooltip.js.coffee +18 -9
  19. data/lib/assets/javascripts/unpoly/util.js.coffee +15 -7
  20. data/lib/unpoly/rails/version.rb +1 -1
  21. data/spec_app/Gemfile.lock +1 -1
  22. data/spec_app/spec/javascripts/helpers/trigger.js.coffee +6 -0
  23. data/spec_app/spec/javascripts/up/bus_spec.js.coffee +9 -8
  24. data/spec_app/spec/javascripts/up/form_spec.js.coffee +10 -10
  25. data/spec_app/spec/javascripts/up/link_spec.js.coffee +25 -20
  26. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +53 -44
  27. data/spec_app/spec/javascripts/up/navigation_spec.js.coffee +8 -8
  28. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +92 -44
  29. data/spec_app/spec/javascripts/up/tooltip_spec.js.coffee +46 -4
  30. metadata +2 -2
@@ -150,7 +150,7 @@ up.proxy = (($) ->
150
150
  Only requests with a method of `GET`, `OPTIONS` and `HEAD`
151
151
  are considered to be read-only.
152
152
 
153
- \#\#\#\# Example
153
+ \#\#\# Example
154
154
 
155
155
  up.ajax('/search', data: { query: 'sunshine' }).then(function(data, status, xhr) {
156
156
  console.log('The response body is %o', data);
@@ -158,7 +158,7 @@ up.proxy = (($) ->
158
158
  console.error('The request failed');
159
159
  });
160
160
 
161
- \#\#\#\# Events
161
+ \#\#\# Events
162
162
 
163
163
  If a network connection is attempted, the proxy will emit
164
164
  a [`up:proxy:load`](/up:proxy:load) event with the `request` as its argument.
@@ -296,7 +296,7 @@ up.proxy = (($) ->
296
296
  waiting, **no** additional `up:proxy:slow` events will be triggered.
297
297
 
298
298
 
299
- \#\#\#\# Spinners
299
+ \#\#\# Spinners
300
300
 
301
301
  You can [listen](/up.on) to the `up:proxy:slow`
302
302
  and [`up:proxy:recover`](/up:proxy:recover) events to implement a spinner
@@ -1,34 +1,21 @@
1
1
  ###*
2
- Enhancing elements
3
- ==================
2
+ Custom Javascript
3
+ =================
4
4
 
5
- Unpoly keeps a persistent Javascript environment during page transitions.
6
- If you wire Javascript to run on `ready` or `onload` events, those scripts will
7
- only run during the initial page load. Subsequently [inserted](/up.replace)
8
- page fragments will not be compiled.
5
+ Every app needs a way to pair Javascript snippets with certain HTML elements,
6
+ in order to integrate libraries or implement custom behavior.
9
7
 
10
- Let's say your Javascript plugin wants you to call `lightboxify()`
11
- on links that should open a lightbox. You decide to
12
- do this for all links with an `lightbox` class:
8
+ Unpoly lets you organize your Javascript snippets using [compilers](/up.compiler).
13
9
 
14
- <a href="river.png" class="lightbox">River</a>
15
- <a href="ocean.png" class="lightbox">Ocean</a>
10
+ For instance, to activate the [Masonry](http://masonry.desandro.com/) jQuery plugin for every element
11
+ with a `grid` class, use this compiler:
16
12
 
17
- You should **avoid** doing this on page load:
18
-
19
- $(document).on('ready', function() {
20
- $('a.lightbox').lightboxify();
21
- });
22
-
23
- Instead you should register a [`compiler`](/up.compiler) for the `a.lightbox` selector:
24
-
25
- up.compiler('a.lightbox', function($element) {
26
- $element.lightboxify();
13
+ up.compiler('.grid', function($element) {
14
+ $element.masonry();
27
15
  });
28
16
 
29
- The compiler function will be called on matching elements when
30
- the page loads, or whenever a matching fragment is [updated through Unpoly](/up.replace)
31
- later.
17
+ The compiler function will be called on matching elements when the page loads
18
+ or when a matching fragment is [inserted via AJAX](/up.link) later.
32
19
 
33
20
  @class up.syntax
34
21
  ###
@@ -58,7 +45,7 @@ up.syntax = (($) ->
58
45
  [Angular directives](https://docs.angularjs.org/guide/directive).
59
46
 
60
47
 
61
- \#\#\#\# Integrating jQuery plugins
48
+ \#\#\# Integrating jQuery plugins
62
49
 
63
50
  `up.compiler` is a great way to integrate jQuery plugins.
64
51
  Let's say your Javascript plugin wants you to call `lightboxify()`
@@ -75,7 +62,7 @@ up.syntax = (($) ->
75
62
  });
76
63
 
77
64
 
78
- \#\#\#\# Custom elements
65
+ \#\#\# Custom elements
79
66
 
80
67
  You can use `up.compiler` to implement custom elements like this:
81
68
 
@@ -89,7 +76,7 @@ up.syntax = (($) ->
89
76
  });
90
77
 
91
78
 
92
- \#\#\#\# Cleaning up after yourself
79
+ \#\#\# Cleaning up after yourself
93
80
 
94
81
  If your compiler returns a function, Unpoly will use this as a *destructor* to
95
82
  clean up if the element leaves the DOM. Note that in Unpoly the same DOM ad Javascript environment
@@ -98,7 +85,7 @@ up.syntax = (($) ->
98
85
 
99
86
  You should clean up after yourself whenever your compilers have global
100
87
  side effects, like a [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval)
101
- or event handlers bound to the document root.
88
+ or [event handlers bound to the document root](/up.on).
102
89
 
103
90
  Here is a version of `<clock>` that updates
104
91
  the time every second, and cleans up once it's done. Note how it returns
@@ -124,7 +111,7 @@ up.syntax = (($) ->
124
111
  of `<clock>` elements.
125
112
 
126
113
 
127
- \#\#\#\# Attaching structured data
114
+ \#\#\# Attaching structured data
128
115
 
129
116
  In case you want to attach structured data to the event you're observing,
130
117
  you can serialize the data to JSON and put it into an `[up-data]` attribute.
@@ -154,7 +141,7 @@ up.syntax = (($) ->
154
141
  });
155
142
 
156
143
 
157
- \#\#\#\# Migrating jQuery event handlers to `up.compiler`
144
+ \#\#\# Migrating jQuery event handlers to `up.compiler`
158
145
 
159
146
  Within the compiler, Unpoly will bind `this` to the
160
147
  native DOM element to help you migrate your existing jQuery code to
@@ -201,9 +188,12 @@ up.syntax = (($) ->
201
188
 
202
189
  The function may return a destructor function that destroys the compiled
203
190
  object before it is removed from the DOM. The destructor is supposed to
204
- clear global state such as time-outs and event handlers bound to the document.
191
+ [clear global state](/up.compiler#cleaning-up-after-yourself)
192
+ such as timeouts and event handlers bound to the document.
205
193
  The destructor is *not* expected to remove the element from the DOM, which
206
194
  is already handled by [`up.destroy`](/up.destroy).
195
+
196
+ The function may also return an array of destructor functions.
207
197
  @stable
208
198
  ###
209
199
  compiler = (args...) ->
@@ -214,7 +204,7 @@ up.syntax = (($) ->
214
204
 
215
205
  You can use `up.macro` to register a compiler that sets other UJS attributes.
216
206
 
217
- \#\#\#\# Example
207
+ \#\#\# Example
218
208
 
219
209
  You will sometimes find yourself setting the same combination of UJS attributes again and again:
220
210
 
@@ -281,14 +271,16 @@ up.syntax = (($) ->
281
271
  value = if u.isString(compiler.keep) then compiler.keep else ''
282
272
  $jqueryElement.attr('up-keep', value)
283
273
  returnValue = compiler.callback.apply(nativeElement, [$jqueryElement, data($jqueryElement)])
284
- if destructor = discoverDestructor(returnValue)
274
+ for destructor in discoverDestructors(returnValue)
285
275
  addDestructor($jqueryElement, destructor)
286
276
 
287
- discoverDestructor = (returnValue) ->
277
+ discoverDestructors = (returnValue) ->
288
278
  if u.isFunction(returnValue)
289
- returnValue
279
+ [returnValue]
290
280
  else if u.isArray(returnValue) && u.all(returnValue, u.isFunction)
291
- u.sequence(returnValue...)
281
+ returnValue
282
+ else
283
+ []
292
284
 
293
285
  addDestructor = ($jqueryElement, destructor) ->
294
286
  $jqueryElement.addClass(DESTRUCTABLE_CLASS)
@@ -348,7 +340,7 @@ up.syntax = (($) ->
348
340
 
349
341
  Returns an empty object if the element has no `up-data` attribute.
350
342
 
351
- \#\#\#\# Example
343
+ \#\#\# Example
352
344
 
353
345
  You have an element with JSON data serialized into an `up-data` attribute:
354
346
 
@@ -10,7 +10,7 @@ You can an [`up-tooltip`](/up-tooltip) attribute to any HTML tag to show a toolt
10
10
  <a href="/decks" up-tooltip="Show all decks">Decks</a>
11
11
 
12
12
 
13
- \#\#\#\# Styling
13
+ \#\#\# Styling
14
14
 
15
15
  The [default styles](https://github.com/unpoly/unpoly/blob/master/lib/assets/stylesheets/up/tooltip.css.sass)
16
16
  show a simple tooltip with white text on a gray background.
@@ -125,8 +125,16 @@ up.tooltip = (($) ->
125
125
 
126
126
  @function up.tooltip.attach
127
127
  @param {Element|jQuery|String} elementOrSelector
128
+ @param {String} [options.text]
129
+ The text to display in the tooltip.
130
+
131
+ Any HTML control characters will be escaped.
132
+ If you need to use HTML formatting in the tooltip, use `options.html` instead.
128
133
  @param {String} [options.html]
129
- The HTML to display in the tooltip.
134
+ The HTML to display in the tooltip unescaped.
135
+
136
+ Make sure to escape any user-provided text before passing it as this option,
137
+ or use `options.text` (which automatically escapes).
130
138
  @param {String} [options.position='top']
131
139
  The position of the tooltip.
132
140
  Can be `'top'`, `'right'`, `'bottom'` or `'left'`.
@@ -228,20 +236,21 @@ up.tooltip = (($) ->
228
236
  @selector [up-tooltip-html]
229
237
  @stable
230
238
  ###
231
- up.compiler('[up-tooltip], [up-tooltip-html]', ($opener) ->
239
+ up.compiler '[up-tooltip], [up-tooltip-html]', ($opener) ->
232
240
  # Don't register these events on document since *every*
233
241
  # mouse move interaction bubbles up to the document.
234
242
  $opener.on('mouseenter', -> attachAsap($opener))
235
243
  $opener.on('mouseleave', -> closeAsap())
236
- )
237
244
 
238
- # Close the tooltip when someone clicks anywhere.
239
- up.on('click', 'body', (event, $body) ->
245
+ # We close the tooltip when someone clicks on the document.
246
+ # We also need to listen to up:action:consumed in case an [up-instant] link
247
+ # was followed on mousedown.
248
+ up.on 'click up:action:consumed', (event) ->
240
249
  closeAsap()
241
- )
250
+ # Do not halt the event chain here. The user is allowed to directly activate
251
+ # a link in the background, even with a (now closing) tooltip open.
242
252
 
243
- # The framework is reset between tests, so also close
244
- # a currently open tooltip.
253
+ # The framework is reset between tests, so also close a currently open tooltip.
245
254
  up.on 'up:framework:reset', reset
246
255
 
247
256
  # Close the tooltip when the user presses ESC.
@@ -829,8 +829,8 @@ up.util = (($) ->
829
829
  position: 'absolute'
830
830
  top: '0'
831
831
  left: '0'
832
- width: '50px'
833
- height: '50px'
832
+ width: '100px'
833
+ height: '100px' # Firefox needs at least 100px to show a scrollbar
834
834
  overflowY: 'scroll'
835
835
  $outer.appendTo(document.body)
836
836
  outer = $outer.get(0)
@@ -1668,19 +1668,26 @@ up.util = (($) ->
1668
1668
  data
1669
1669
 
1670
1670
  ###*
1671
- Throws an [exception](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
1671
+ Throws a [Javascript error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
1672
1672
  with the given message.
1673
1673
 
1674
- The message will also be printed to the [error log](/up.log.error).
1674
+ The message will also be printed to the [error log](/up.log.error). Also a notification will be shown at the bottom of the screen.
1675
1675
 
1676
- Also a notification will be shown at the bottom of the screen.
1676
+ The message may contain [substitution marks](https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions).
1677
1677
 
1678
- \#\#\#\# Examples
1678
+ \#\#\# Examples
1679
1679
 
1680
1680
  up.fail('Division by zero')
1681
1681
  up.fail('Unexpected result %o', result)
1682
1682
 
1683
1683
  @function up.fail
1684
+ @param {String} message
1685
+ A message with details about the error.
1686
+
1687
+ The message can contain [substitution marks](https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions)
1688
+ like `%s` or `%o`.
1689
+ @param {Array<String>} vars...
1690
+ A list of variables to replace any substitution marks in the error message.
1684
1691
  @experimental
1685
1692
  ###
1686
1693
  fail = (args...) ->
@@ -1833,7 +1840,7 @@ up.util = (($) ->
1833
1840
  @function up.util.sequence
1834
1841
  @internal
1835
1842
  ###
1836
- sequence: (functions...) ->
1843
+ sequence = (functions...) ->
1837
1844
  ->
1838
1845
  map functions, (f) -> f()
1839
1846
 
@@ -1944,6 +1951,7 @@ up.util = (($) ->
1944
1951
  escapeHtml: escapeHtml
1945
1952
  DivertibleChain: DivertibleChain
1946
1953
  submittedValue: submittedValue
1954
+ sequence: sequence
1947
1955
 
1948
1956
  )($)
1949
1957
 
@@ -4,6 +4,6 @@ module Unpoly
4
4
  # The current version of the unpoly-rails gem.
5
5
  # This version number is also used for releases of the Unpoly
6
6
  # frontend code.
7
- VERSION = '0.28.1'
7
+ VERSION = '0.29.0'
8
8
  end
9
9
  end
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- unpoly-rails (0.27.3)
4
+ unpoly-rails (0.28.1)
5
5
  rails (>= 3)
6
6
 
7
7
  GEM
@@ -18,6 +18,11 @@
18
18
  event = createMouseEvent('click', options)
19
19
  dispatch($element, event)
20
20
 
21
+ clickSequence = ($element, options) ->
22
+ mousedown($element, options)
23
+ mouseup($element, options)
24
+ click($element, options)
25
+
21
26
  # Can't use the new MouseEvent constructor in IE11 because computer.
22
27
  # http://www.codeproject.com/Tips/893254/JavaScript-Triggering-Event-Manually-in-Internet-E
23
28
  createMouseEvent = (type, options) ->
@@ -64,5 +69,6 @@
64
69
  mousedown: mousedown
65
70
  mouseup: mouseup
66
71
  click: click
72
+ clickSequence: clickSequence
67
73
 
68
74
  )()
@@ -11,8 +11,8 @@ describe 'up.bus', ->
11
11
  up.on 'click', '.child', (event, $element) ->
12
12
  observeClass($element.attr('class'))
13
13
 
14
- $('.container').click()
15
- $('.child').click()
14
+ Trigger.click($('.container'))
15
+ Trigger.click($('.child'))
16
16
 
17
17
  expect(observeClass).not.toHaveBeenCalledWith('container')
18
18
  expect(observeClass).toHaveBeenCalledWith('child')
@@ -21,9 +21,10 @@ describe 'up.bus', ->
21
21
  $child = affix('.child')
22
22
  clickSpy = jasmine.createSpy()
23
23
  unsubscribe = up.on 'click', '.child', clickSpy
24
- $('.child').click()
24
+ Trigger.click($('.child'))
25
+ expect(clickSpy.calls.count()).toEqual(1)
25
26
  unsubscribe()
26
- $('.child').click()
27
+ Trigger.click($('.child'))
27
28
  expect(clickSpy.calls.count()).toEqual(1)
28
29
 
29
30
  it 'parses an up-data attribute as JSON and passes the parsed object as a third argument to the initializer', ->
@@ -35,7 +36,7 @@ describe 'up.bus', ->
35
36
  data = { key1: 'value1', key2: 'value2' }
36
37
  $tag = affix(".child").attr('up-data', JSON.stringify(data))
37
38
 
38
- $('.child').click()
39
+ Trigger.click($('.child'))
39
40
  expect(observeArgs).toHaveBeenCalledWith('child', data)
40
41
 
41
42
  it 'passes an empty object as a second argument to the listener if there is no up-data attribute', ->
@@ -44,7 +45,7 @@ describe 'up.bus', ->
44
45
  up.on 'click', '.child', (event, $element, data) ->
45
46
  observeArgs($element.attr('class'), data)
46
47
 
47
- $('.child').click()
48
+ Trigger.click($('.child'))
48
49
  expect(observeArgs).toHaveBeenCalledWith('child', {})
49
50
 
50
51
  describe 'up.off', ->
@@ -53,9 +54,9 @@ describe 'up.bus', ->
53
54
  $child = affix('.child')
54
55
  clickSpy = jasmine.createSpy()
55
56
  up.on 'click', '.child', clickSpy
56
- $('.child').click()
57
+ Trigger.click($('.child'))
57
58
  up.off 'click', '.child', clickSpy
58
- $('.child').click()
59
+ Trigger.click($('.child'))
59
60
  expect(clickSpy.calls.count()).toEqual(1)
60
61
 
61
62
  it 'throws an error if the given event listener was not registered through up.on', ->
@@ -69,11 +69,11 @@ describe 'up.form', ->
69
69
  callback = jasmine.createSpy('change callback')
70
70
  up.observe($checkbox, callback)
71
71
  expect($checkbox.is(':checked')).toBe(false)
72
- $checkbox.get(0).click()
72
+ Trigger.clickSequence($checkbox)
73
73
  u.nextFrame ->
74
74
  expect($checkbox.is(':checked')).toBe(true)
75
75
  expect(callback.calls.count()).toEqual(1)
76
- $checkbox.get(0).click()
76
+ Trigger.clickSequence($checkbox)
77
77
  u.nextFrame ->
78
78
  expect($checkbox.is(':checked')).toBe(false)
79
79
  expect(callback.calls.count()).toEqual(2)
@@ -86,11 +86,11 @@ describe 'up.form', ->
86
86
  callback = jasmine.createSpy('change callback')
87
87
  up.observe($checkbox, callback)
88
88
  expect($checkbox.is(':checked')).toBe(false)
89
- $label.get(0).click()
89
+ Trigger.clickSequence($label)
90
90
  u.nextFrame ->
91
91
  expect($checkbox.is(':checked')).toBe(true)
92
92
  expect(callback.calls.count()).toEqual(1)
93
- $label.get(0).click()
93
+ Trigger.clickSequence($label)
94
94
  u.nextFrame ->
95
95
  expect($checkbox.is(':checked')).toBe(false)
96
96
  expect(callback.calls.count()).toEqual(2)
@@ -106,11 +106,11 @@ describe 'up.form', ->
106
106
  callback = jasmine.createSpy('change callback')
107
107
  up.observe($group, callback)
108
108
  expect($radio1.is(':checked')).toBe(false)
109
- $radio1.get(0).click()
109
+ Trigger.clickSequence($radio1)
110
110
  u.nextFrame ->
111
111
  expect($radio1.is(':checked')).toBe(true)
112
112
  expect(callback.calls.count()).toEqual(1)
113
- $radio2.get(0).click()
113
+ Trigger.clickSequence($radio2)
114
114
  u.nextFrame ->
115
115
  expect($radio1.is(':checked')).toBe(false)
116
116
  expect(callback.calls.count()).toEqual(2)
@@ -126,11 +126,11 @@ describe 'up.form', ->
126
126
  callback = jasmine.createSpy('change callback')
127
127
  up.observe($group, callback)
128
128
  expect($radio1.is(':checked')).toBe(false)
129
- $radio1Label.get(0).click()
129
+ Trigger.clickSequence($radio1Label)
130
130
  u.nextFrame ->
131
131
  expect($radio1.is(':checked')).toBe(true)
132
132
  expect(callback.calls.count()).toEqual(1)
133
- $radio2Label.get(0).click()
133
+ Trigger.clickSequence($radio2Label)
134
134
  u.nextFrame ->
135
135
  expect($radio1.is(':checked')).toBe(false)
136
136
  expect(callback.calls.count()).toEqual(2)
@@ -145,14 +145,14 @@ describe 'up.form', ->
145
145
  up.observe($group, callback)
146
146
  expect($radio1.is(':checked')).toBe(true)
147
147
  expect($radio2.is(':checked')).toBe(false)
148
- $radio1.get(0).click()
148
+ Trigger.clickSequence($radio1)
149
149
  u.nextFrame ->
150
150
  # Since the radio button was already checked, the click doesn't do anything
151
151
  expect($radio1.is(':checked')).toBe(true)
152
152
  expect($radio2.is(':checked')).toBe(false)
153
153
  # Since the radio button was already checked, clicking it again won't trigger the callback
154
154
  expect(callback.calls.count()).toEqual(0)
155
- $radio2.get(0).click()
155
+ Trigger.clickSequence($radio2)
156
156
  u.nextFrame ->
157
157
  expect($radio1.is(':checked')).toBe(false)
158
158
  expect($radio2.is(':checked')).toBe(true)