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
@@ -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