unpoly-rails 0.28.1 → 0.29.0

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.

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
@@ -2,28 +2,40 @@
2
2
  Modal dialogs
3
3
  =============
4
4
 
5
- Instead of [linking to a page fragment](/up.link), you can choose
6
- to show a fragment in a modal dialog. The existing page will remain
7
- open in the background and reappear once the modal is closed.
5
+ Instead of [linking to a page fragment](/up.link), you can choose to show a fragment
6
+ in a modal dialog. The existing page will remain open in the background.
8
7
 
9
- To open a modal, add an [`up-modal` attribute](/up-modal) to a link,
10
- or call the Javascript functions [`up.modal.follow`](/up.modal.follow)
11
- and [`up.modal.visit`](/up.modal.visit).
12
-
13
- For smaller popup overlays ("dropdowns") see [up.popup](/up.popup) instead.
8
+ To open a modal, add an [`up-modal`](/up-modal) attribute to a link:
9
+
10
+ <a href="/blogs" up-modal=".blog-list">Switch blog</a>
11
+
12
+ When this link is clicked, Unpoly will request the path `/blogs` and extract
13
+ an element matching the selector `.blog-list` from the response. The matching element
14
+ will then be placed in a modal dialog.
15
+
16
+
17
+ \#\#\# Closing behavior
18
+
19
+ By default the dialog automatically closes
20
+ *when a link inside a modal changes a fragment behind the modal*.
21
+ This is useful to have the dialog interact with the page that
22
+ opened it, e.g. by updating parts of a larger form or by signing in a user
23
+ and revealing additional information.
24
+
25
+ To disable this behavior, give the opening link an [`up-sticky`](/up-modal#up-sticky) attribute:
14
26
 
15
27
 
16
- \#\#\#\# Customizing the dialog design
28
+ \#\#\# Customizing the dialog design
17
29
 
18
- Loading the Unpoly stylesheet will give you a minimal dialog design:
30
+ Dialogs have a minimal default design:
19
31
 
20
- - Dialog contents are displayed in a white box that is centered vertically and horizontally.
21
- - There is a a subtle box shadow around the dialog
32
+ - Contents are displayed in a white box with a subtle box shadow
22
33
  - The box will grow to fit the dialog contents, but never grow larger than the screen
23
- - The box is placed over a semi-transparent background to dim the rest of the page
34
+ - The box is placed over a semi-transparent backdrop to dim the rest of the page
24
35
  - There is a button to close the dialog in the top-right corner
25
36
 
26
- The easiest way to change how the dialog looks is by overriding the [default CSS styles](https://github.com/unpoly/unpoly/blob/master/lib/assets/stylesheets/up/modal.css.sass).
37
+ The easiest way to change how the dialog looks is by overriding the
38
+ [default CSS styles](https://github.com/unpoly/unpoly/blob/master/lib/assets/stylesheets/up/modal.css.sass).
27
39
 
28
40
  By default the dialog uses the following DOM structure:
29
41
 
@@ -32,28 +44,15 @@ By default the dialog uses the following DOM structure:
32
44
  <div class="up-modal-viewport">
33
45
  <div class="up-modal-dialog">
34
46
  <div class="up-modal-content">
35
- ...
47
+ <!-- the matching element will be placed here -->
36
48
  </div>
37
49
  <div class="up-modal-close" up-close>X</div>
38
50
  </div>
39
51
  </div>
40
52
  </div>
41
53
 
42
- If you want to change the design beyond CSS, you can
43
- configure Unpoly to [use a different HTML structure](/up.modal.config).
44
-
45
-
46
- \#\#\#\# Closing behavior
47
-
48
- By default the dialog automatically closes
49
- *when a link inside a modal changes a fragment behind the modal*.
50
- This is useful to have the dialog interact with the page that
51
- opened it, e.g. by updating parts of a larger form or by signing in a user
52
- and revealing additional information.
53
-
54
- To disable this behavior, give the opening link an `up-sticky` attribute:
55
-
56
- <a href="/settings" up-modal=".options" up-sticky>Settings</a>
54
+ You can change this structure by setting [`up.modal.config.template`](/up.modal.config#config.template) to a new template string
55
+ or function.
57
56
 
58
57
 
59
58
  @class up.modal
@@ -69,7 +68,7 @@ up.modal = (($) ->
69
68
  @param {String} [config.history=true]
70
69
  Whether opening a modal will add a browser history entry.
71
70
  @param {Number} [config.width]
72
- The width of the dialog as a CSS value like `'400px'` or `50%`.
71
+ The width of the dialog as a CSS value like `'400px'` or `'50%'`.
73
72
 
74
73
  Defaults to `undefined`, meaning that the dialog will grow to fit its contents
75
74
  until it reaches `config.maxWidth`. Leaving this as `undefined` will
@@ -562,7 +561,7 @@ up.modal = (($) ->
562
561
  ###*
563
562
  Register a new modal variant with its own default configuration, CSS or HTML template.
564
563
 
565
- \#\#\#\# Example
564
+ \#\#\# Example
566
565
 
567
566
  Let's implement a drawer that slides in from the right:
568
567
 
@@ -670,11 +669,12 @@ up.modal = (($) ->
670
669
 
671
670
  # Close the modal when someone clicks outside the dialog
672
671
  # (but not on a modal opener).
673
- up.on('click', 'body', (event, $body) ->
672
+ up.on('click', (event) ->
674
673
  return unless state.closable
675
674
 
676
675
  $target = $(event.target)
677
676
  unless $target.closest('.up-modal-dialog').length || $target.closest('[up-modal]').length
677
+ up.bus.consumeAction(event)
678
678
  closeAsap()
679
679
  )
680
680
 
@@ -706,10 +706,11 @@ up.modal = (($) ->
706
706
  up.on('click', '[up-close]', (event, $element) ->
707
707
  if contains($element)
708
708
  closeAsap()
709
- # Only prevent the default when we actually closed a modal.
710
- # This way we can have buttons that close a modal when within a modal,
711
- # but link to a destination if not.
712
- event.preventDefault()
709
+ # If the user closes the modal by clicking on the background, we want to halt the event chain here.
710
+ # The event should not trigger anything else. The user needs to click again for another interaction.
711
+ # Also only prevent the default when we actually closed a modal.
712
+ # This way we can have buttons that close a modal when within a modal, but link to a destination if not.
713
+ up.bus.consumeAction(event)
713
714
  )
714
715
 
715
716
  # The framework is reset between tests
@@ -2,35 +2,32 @@
2
2
  Animation
3
3
  =========
4
4
 
5
- Whenever you update a page fragment (through methods like
6
- [`up.replace`](/up.replace) or UJS attributes like [`up-target`](/up-target))
7
- you can animate the change.
5
+ Whenever you [update a page fragment](/up-link) you can animate the change.
8
6
 
9
- For instance, when you replace a selector `.list` with a new `.list`
10
- from the server, you can add an `up-transition="cross-fade"` attribute
11
- to smoothly fade out the old `.list` while fading in the new `.list`:
7
+ Let's say you are using an [`up-target`](/a-up-target) link to update an element
8
+ with content from the server. You can add an attribute [`up-transition`](/a-up-target#up-transition)
9
+ to smoothly fade out the old element while fading in the new element:
12
10
 
13
11
  <a href="/users" up-target=".list" up-transition="cross-fade">Show users</a>
14
12
 
15
- Transitions vs. animations
16
- --------------------------
13
+ \#\#\# Transitions vs. animations
17
14
 
18
- When we morph between an old an new element, we call it a *transition*.
15
+ When we morph between an old and a new element, we call it a *transition*.
19
16
  In contrast, when we animate a new element without simultaneously removing an
20
17
  old element, we call it an *animation*.
21
18
 
22
- An example for an animation is opening a new dialog, which we can animate
23
- using the `up-animation` attribute:
19
+ An example for an animation is opening a new dialog. We can animate the appearance
20
+ of the dialog by adding an [`up-animation`](/up-modal#up-animation) attribute to the opening link:
24
21
 
25
22
  <a href="/users" up-modal=".list" up-animation="move-from-top">Show users</a>
26
23
 
27
- Predefined animations and transitions
28
- -------------------------------------
24
+ \#\#\# Which animations are available?
29
25
 
30
- Unpoly ships with a number of predefined [animations](/up.animate#named-animation)
31
- and [transitions](/up.morph#named-animation).
32
- You can also easily [define your own animations](/up.animation)
33
- or [transitions](/up.transition) using Javascript or CSS.
26
+ Unpoly ships with a number of [predefined transitions](/up.morph#named-transitions)
27
+ and [predefined animations](/up.animate#named-animations).
28
+
29
+ You can define custom animations using [`up.transition`](/up.transition) and
30
+ [`up.animation`](/up.animation).
34
31
 
35
32
  @class up.motion
36
33
  ###
@@ -95,7 +92,7 @@ up.motion = (($) ->
95
92
  ###*
96
93
  Applies the given animation to the given element.
97
94
 
98
- \#\#\#\# Example
95
+ \#\#\# Example
99
96
 
100
97
  up.animate('.warning', 'fade-in');
101
98
 
@@ -107,7 +104,7 @@ up.motion = (($) ->
107
104
  easing: 'linear'
108
105
  });
109
106
 
110
- \#\#\#\# Named animations
107
+ \#\#\# Named animations
111
108
 
112
109
  The following animations are pre-defined:
113
110
 
@@ -125,7 +122,7 @@ up.motion = (($) ->
125
122
 
126
123
  You can define additional named animations using [`up.animation`](/up.animation).
127
124
 
128
- \#\#\#\# Animating CSS properties directly
125
+ \#\#\# Animating CSS properties directly
129
126
 
130
127
  By passing an object instead of an animation name, you can animate
131
128
  the CSS properties of the given element:
@@ -134,7 +131,7 @@ up.motion = (($) ->
134
131
  $warning.css({ opacity: 0 });
135
132
  up.animate($warning, { opacity: 1 });
136
133
 
137
- \#\#\#\# Multiple animations on the same element
134
+ \#\#\# Multiple animations on the same element
138
135
 
139
136
  Unpoly doesn't allow more than one concurrent animation on the same element.
140
137
 
@@ -312,7 +309,7 @@ up.motion = (($) ->
312
309
  Note that the transition does not remove any elements from the DOM.
313
310
  The first element will remain in the DOM, albeit hidden using `display: none`.
314
311
 
315
- \#\#\#\# Named transitions
312
+ \#\#\# Named transitions
316
313
 
317
314
  The following transitions are pre-defined:
318
315
 
@@ -331,7 +328,7 @@ up.motion = (($) ->
331
328
  - `move-to-bottom/fade-in`
332
329
  - `move-to-left/move-from-top`
333
330
 
334
- \#\#\#\# Implementation details
331
+ \#\#\# Implementation details
335
332
 
336
333
  During a transition both the old and new element occupy
337
334
  the same position on the screen.
@@ -2,12 +2,36 @@
2
2
  Navigation bars
3
3
  ===============
4
4
 
5
- Unpoly automatically marks up link elements with classes indicating that
6
- they are currently loading (class `up-active`) or linking
7
- to the current location (class `up-current`).
5
+ Unpoly automatically adds CSS classes to links while they are
6
+ currently loading ([`.up-active`](/up-active)) or
7
+ pointing to the current location ([`.up-current`](/up-current)).
8
+
9
+ By styling these classes with CSS you can provide instant feedback to user interactions.
10
+ This improves the perceived speed of your interface.
11
+
12
+ \#\#\# Example
13
+
14
+ Let's say we have an navigation bar with two links, pointing to `/foo` and `/bar` respectively:
15
+
16
+ <a href="/foo" up-follow>Foo</a>
17
+ <a href="/bar" up-follow>Bar</a>
18
+
19
+ If the current URL is `/foo`, the first link is automatically marked with an [`up-current`](/up-current) class:
20
+
21
+ <a href="/foo" up-follow class="up-current">Foo</a>
22
+ <a href="/bar" up-follow>Bar</a>
23
+
24
+ When the user clicks on the `/bar` link, the link will receive the [`up-active`](/up-active) class while it is waiting
25
+ for the server to respond:
26
+
27
+ <a href="/foo" up-follow class="up-current">Foo</a>
28
+ <a href="/bar" up-follow class="up-active">Bar</a>
29
+
30
+ Once the response is received the URL will change to `/bar` and the `up-active` class is removed:
31
+
32
+ <a href="/foo" up-follow>Foo</a>
33
+ <a href="/bar" up-follow class="up-current">Bar</a>
8
34
 
9
- This dramatically improves the perceived speed of your user interface
10
- by providing instant feedback for user interactions.
11
35
 
12
36
  @class up.navigation
13
37
  ###
@@ -96,20 +120,64 @@ up.navigation = (($) ->
96
120
  $section.removeClass(klass)
97
121
 
98
122
  ###*
99
- @function findClickArea
123
+ @function findActionableArea
100
124
  @param {String|Element|jQuery} elementOrSelector
101
- @param {Boolean} options.enlarge
102
- If `true`, tries to find a containing link that has expanded the link's click area.
103
- If we find one, we prefer to mark the larger area as active.
104
125
  @internal
105
126
  ###
106
- findClickArea = (elementOrSelector, options) ->
127
+ findActionableArea = (elementOrSelector) ->
107
128
  $area = $(elementOrSelector)
108
- options = u.options(options, enlarge: false)
109
- if options.enlarge
110
- u.presence($area.parent(SELECTOR_SECTION)) || $area
111
- else
112
- $area
129
+ if $area.is(SELECTOR_SECTION)
130
+ # Try to enlarge links that are expanded with [up-expand] on a surrounding container.
131
+ $area = u.presence($area.parent(SELECTOR_SECTION)) || $area
132
+ $area
133
+
134
+ ###*
135
+ Marks the given element as currently loading, by assigning the CSS class [`up-active`](/up-active).
136
+
137
+ This happens automatically when following links or submitting forms through the Unpoly API.
138
+ Use this function if you make custom network calls from your own Javascript code.
139
+
140
+ If the given element is a link within an [expanded click area](/up-expand),
141
+ the class will be assigned to the expanded area.
142
+
143
+ \#\#\# Example
144
+
145
+ var $button = $('button');
146
+ $button.on('click', function() {
147
+ up.navigation.start($button);
148
+ up.ajax(...).always(function() {
149
+ up.navigation.stop($button);
150
+ });
151
+ });
152
+
153
+ Or shorter:
154
+
155
+ var $button = $('button');
156
+ $button.on('click', function() {
157
+ up.navigation.start($button, function() {
158
+ up.ajax(...);
159
+ });
160
+ });
161
+
162
+ @method up.navigation.start
163
+ @param {Element|jQuery|String} elementOrSelector
164
+ The element to mark as active
165
+ @param {Function} [action]
166
+ An optional function to run while the element is marked as loading.
167
+ The function must return a promise.
168
+ Once the promise resolves, the element will be [marked as no longer loading](/up.navigation.stop).
169
+ @internal
170
+ ###
171
+ start = (elementOrSelector, action) ->
172
+ $element = findActionableArea(elementOrSelector)
173
+ $element.addClass(CLASS_ACTIVE)
174
+ if action
175
+ promise = action()
176
+ if u.isPromise(promise)
177
+ promise.always -> stop($element)
178
+ else
179
+ up.warn('Expected block to return a promise, but got %o', promise)
180
+ promise
113
181
 
114
182
  ###*
115
183
  Links that are currently loading are assigned the `up-active`
@@ -119,7 +187,7 @@ up.navigation = (($) ->
119
187
  The `up-active` class will be removed as soon as another
120
188
  page fragment is added or updated through Unpoly.
121
189
 
122
- \#\#\#\# Example
190
+ \#\#\# Example
123
191
 
124
192
  We have a link:
125
193
 
@@ -138,25 +206,22 @@ up.navigation = (($) ->
138
206
  @selector .up-active
139
207
  @stable
140
208
  ###
141
- markActive = (elementOrSelector, options) ->
142
- $element = findClickArea(elementOrSelector, options)
143
- $element.addClass(CLASS_ACTIVE)
144
209
 
145
- unmarkActive = (elementOrSelector, options) ->
146
- $element = findClickArea(elementOrSelector, options)
147
- $element.removeClass(CLASS_ACTIVE)
210
+ ###*
211
+ Marks the given element as no longer loading, by removing the CSS class [`up-active`](/up-active).
212
+
213
+ This happens automatically when network requests initiated by the Unpoly API have completed.
214
+ Use this function if you make custom network calls from your own Javascript code.
148
215
 
149
- withActiveMark = (elementOrSelector, args...) ->
150
- block = args.pop()
151
- options = u.options(args.pop())
152
- $element = $(elementOrSelector)
153
- markActive($element, options)
154
- promise = block()
155
- if u.isPromise(promise)
156
- promise.always -> unmarkActive($element, options)
157
- else
158
- up.warn('Expected block to return a promise, but got %o', promise)
159
- promise
216
+ @function up.navigation.stop
217
+ @param {jQuery} event.$element
218
+ The link or form that has finished loading.
219
+ @internal
220
+ ###
221
+ stop = (elementOrSelector) ->
222
+ $element = findActionableArea(elementOrSelector)
223
+ up.emit('up:navigated', $element: $element, message: false)
224
+ $element.removeClass(CLASS_ACTIVE)
160
225
 
161
226
  ###*
162
227
  Links that point to the current location are assigned
@@ -176,7 +241,7 @@ up.navigation = (($) ->
176
241
  <a href="/bar">Bar</a>
177
242
  </nav>
178
243
 
179
- \#\#\#\# What's considered to be "current"?
244
+ \#\#\# What's considered to be "current"?
180
245
 
181
246
  The current location is considered to be either:
182
247
 
@@ -222,8 +287,7 @@ up.navigation = (($) ->
222
287
 
223
288
  config: config
224
289
  defaults: -> up.fail('up.navigation.defaults(...) no longer exists. Set values on he up.navigation.config property instead.')
225
- markActive: markActive
226
- unmarkActive: unmarkActive
227
- withActiveMark: withActiveMark
290
+ start: start
291
+ stop: stop
228
292
 
229
293
  )(jQuery)
@@ -10,7 +10,7 @@ or call the Javascript function [`up.popup.attach`](/up.popup.attach).
10
10
 
11
11
  For modal dialogs see [up.modal](/up.modal) instead.
12
12
 
13
- \#\#\#\# Customizing the popup design
13
+ \#\#\# Customizing the popup design
14
14
 
15
15
  Loading the Unpoly stylesheet will give you a minimal popup design:
16
16
 
@@ -26,7 +26,7 @@ By default the popup uses the following DOM structure:
26
26
  ...
27
27
  </div>
28
28
 
29
- \#\#\#\# Closing behavior
29
+ \#\#\# Closing behavior
30
30
 
31
31
  The popup closes when the user clicks anywhere outside the popup area.
32
32
 
@@ -176,14 +176,22 @@ up.popup = (($) ->
176
176
  Emits events [`up:popup:open`](/up:popup:open) and [`up:popup:opened`](/up:popup:opened).
177
177
 
178
178
  @function up.popup.attach
179
- @param {Element|jQuery|String} elementOrSelector
179
+ @param {Element|jQuery|String} anchor
180
+ The element to which the popup will be attached.
180
181
  @param {String} [options.url]
182
+ The URL from which to fetch the popup contents.
183
+
184
+ If omitted, the `href` or `up-href` attribute of the anchor element will be used.
185
+
186
+ Will be ignored if `options.html` is given.
181
187
  @param {String} [options.target]
182
188
  A CSS selector that will be extracted from the response and placed into the popup.
183
189
  @param {String} [options.position='bottom-right']
184
190
  Defines where the popup is attached to the opening element.
185
191
 
186
192
  Valid values are `bottom-right`, `bottom-left`, `top-right` and `top-left`.
193
+ @param {String} [options.html]
194
+ A string of HTML from which to extract the popup contents. No network request will be made.
187
195
  @param {String} [options.confirm]
188
196
  A message that will be displayed in a cancelable confirmation dialog
189
197
  before the modal is being opened.
@@ -382,29 +390,30 @@ up.popup = (($) ->
382
390
 
383
391
  @stable
384
392
  ###
385
- up.link.onAction('[up-popup]', ($link) ->
393
+ up.link.onAction '[up-popup]', ($link) ->
386
394
  if $link.is('.up-current')
387
395
  closeAsap()
388
396
  else
389
397
  attachAsap($link)
390
- )
391
398
 
392
- # Close the popup when someone clicks outside the popup
393
- # (but not on a popup opener).
394
- up.on('mousedown', 'body', (event, $body) ->
399
+ # We close the popup when someone clicks on the document.
400
+ # We also need to listen to up:action:consumed in case an [up-instant] link
401
+ # was followed on mousedown.
402
+ up.on 'click up:action:consumed', (event) ->
395
403
  $target = $(event.target)
404
+ # Don't close when the user clicked on a popup opener.
396
405
  unless $target.closest('.up-popup, [up-popup]').length
397
406
  closeAsap()
398
- )
399
-
400
- up.on('up:fragment:inserted', (event, $fragment) ->
407
+ # Do not halt the event chain here. The user is allowed to directly activate
408
+ # a link in the background, even with a (now closing) popup open.
409
+
410
+ up.on 'up:fragment:inserted', (event, $fragment) ->
401
411
  if contains($fragment)
402
412
  if newSource = $fragment.attr('up-source')
403
413
  state.url = newSource
404
414
  else if contains(event.origin)
405
415
  autoclose()
406
- )
407
-
416
+
408
417
  # Close the pop-up overlay when the user presses ESC.
409
418
  up.bus.onEscape(closeAsap)
410
419
 
@@ -422,14 +431,13 @@ up.popup = (($) ->
422
431
  @selector [up-close]
423
432
  @stable
424
433
  ###
425
- up.on('click', '[up-close]', (event, $element) ->
434
+ up.on 'click', '[up-close]', (event, $element) ->
426
435
  if contains($element)
427
436
  closeAsap()
428
437
  # Only prevent the default when we actually closed a popup.
429
438
  # This way we can have buttons that close a popup when within a popup,
430
439
  # but link to a destination if not.
431
- event.preventDefault()
432
- )
440
+ up.bus.consumeAction(event)
433
441
 
434
442
  # The framework is reset between tests
435
443
  up.on 'up:framework:reset', reset