unpoly-rails 0.60.3 → 1.0.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -2
  3. data/CHANGELOG.md +84 -0
  4. data/README.md +1 -1
  5. data/Rakefile +11 -1
  6. data/dist/unpoly.js +226 -91
  7. data/dist/unpoly.min.js +4 -4
  8. data/lib/assets/javascripts/unpoly/browser.coffee.erb +10 -5
  9. data/lib/assets/javascripts/unpoly/classes/body_shifter.coffee +21 -12
  10. data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +2 -2
  11. data/lib/assets/javascripts/unpoly/classes/event_listener.coffee +1 -1
  12. data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +11 -3
  13. data/lib/assets/javascripts/unpoly/classes/params.coffee.erb +1 -0
  14. data/lib/assets/javascripts/unpoly/element.coffee.erb +9 -6
  15. data/lib/assets/javascripts/unpoly/event.coffee.erb +12 -4
  16. data/lib/assets/javascripts/unpoly/form.coffee.erb +85 -14
  17. data/lib/assets/javascripts/unpoly/fragment.coffee.erb +7 -3
  18. data/lib/assets/javascripts/unpoly/framework.coffee +7 -9
  19. data/lib/assets/javascripts/unpoly/link.coffee.erb +1 -1
  20. data/lib/assets/javascripts/unpoly/log.coffee +6 -2
  21. data/lib/assets/javascripts/unpoly/modal.coffee.erb +4 -2
  22. data/lib/assets/javascripts/unpoly/motion.coffee.erb +1 -1
  23. data/lib/assets/javascripts/unpoly/protocol.coffee +1 -1
  24. data/lib/assets/javascripts/unpoly/syntax.coffee.erb +3 -4
  25. data/lib/assets/javascripts/unpoly/util.coffee.erb +4 -2
  26. data/lib/assets/javascripts/unpoly/viewport.coffee.erb +26 -2
  27. data/lib/unpoly/rails/version.rb +1 -1
  28. data/package.json +1 -1
  29. data/spec_app/Gemfile +2 -4
  30. data/spec_app/Gemfile.lock +23 -27
  31. data/spec_app/app/views/compiler_test/timestamp.erb +1 -0
  32. data/spec_app/app/views/css_test/modal.erb +1 -1
  33. data/spec_app/app/views/css_test/popup.erb +1 -1
  34. data/spec_app/app/views/pages/start.erb +2 -1
  35. data/spec_app/app/views/replace_test/table.erb +16 -0
  36. data/spec_app/spec/javascripts/up/event_spec.js.coffee +34 -0
  37. data/spec_app/spec/javascripts/up/form_spec.js.coffee +128 -0
  38. data/spec_app/spec/javascripts/up/fragment_spec.js.coffee +36 -1
  39. data/spec_app/spec/javascripts/up/link_spec.js.coffee +7 -2
  40. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +23 -1
  41. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +2 -1
  42. data/spec_app/spec/javascripts/up/util_spec.js.coffee +28 -0
  43. metadata +4 -4
@@ -50,15 +50,13 @@ up.framework = do ->
50
50
  # From here on, all event handlers (both Unpoly's and user code) should be able
51
51
  # to work with the DOM, so wait for the DOM to be ready.
52
52
  up.event.onReady ->
53
- # In case the DOM was already ready when up.event.boot() was called, we still
54
- # haven't executed user-provided code. So we wait one more frame until
55
- # user-provided compilers, event handlers, etc. have been registered.
56
- # This also gives async user-code a chance to run in the next microtask.
57
- u.task ->
58
- # At this point all user-code has been called.
59
- # The following event will cause Unpoly to compile the <body>.
60
- up.emit('up:app:boot', log: 'Booting user application')
61
- up.emit('up:app:booted', log: 'User application booted')
53
+ # By now all non-sync <script> tags have been loaded and called, including
54
+ # those after us. All user-provided compilers, event handlers, etc. have
55
+ # been registered.
56
+ #
57
+ # The following event will cause Unpoly to compile the <body>.
58
+ up.emit('up:app:boot', log: 'Booting user application')
59
+ up.emit('up:app:booted', log: 'User application booted')
62
60
  else
63
61
  console.log?("Unpoly doesn't support this browser. Framework was not booted.")
64
62
 
@@ -16,7 +16,7 @@ This makes for an unfriendly experience:
16
16
  - State changes caused by AJAX updates get lost during the page transition.
17
17
  - Unsaved form changes get lost during the page transition.
18
18
  - The JavaScript VM is reset during the page transition.
19
- - If the page layout is composed from multiple srollable containers
19
+ - If the page layout is composed from multiple scrollable containers
20
20
  (e.g. a pane view), the scroll positions get lost during the page transition.
21
21
  - The user sees a "flash" as the browser loads and renders the new page,
22
22
  even if large portions of the old and new page are the same (navigation, layout, etc.).
@@ -37,12 +37,15 @@ up.log = do ->
37
37
  prints to the developer console.
38
38
  @param {string} [options.prefix='[UP] ']
39
39
  A string to prepend to Unpoly's logging messages so you can distinguish it from your own messages.
40
+ @param {boolean} [options.banner=true]
41
+ Print the Unpoly banner to the developer console.
40
42
  @stable
41
43
  ###
42
44
  config = new up.Config
43
45
  prefix: '[UP] '
44
46
  enabled: sessionStore.get('enabled')
45
47
  collapse: false
48
+ banner: true
46
49
 
47
50
  reset = ->
48
51
  config.reset()
@@ -200,7 +203,9 @@ up.log = do ->
200
203
  banner += "Call `up.log.enable()` to enable logging for this session."
201
204
  console.log(banner)
202
205
 
203
- up.on 'up:framework:booted', printBanner
206
+ if config.banner
207
+ up.on 'up:framework:booted', printBanner
208
+
204
209
  up.on 'up:framework:reset', reset
205
210
 
206
211
  setEnabled = (value) ->
@@ -245,4 +250,3 @@ up.log = do ->
245
250
 
246
251
  up.puts = up.log.puts
247
252
  up.warn = up.log.warn
248
-
@@ -250,6 +250,7 @@ up.modal = do ->
250
250
  createHiddenFrame = (target, options) ->
251
251
  html = templateHtml()
252
252
  state.modalElement = modalElement = e.createFromHtml(html)
253
+ modalElement.setAttribute('aria-modal', 'true') # tell modern screen readers to make all other elements inert
253
254
  modalElement.setAttribute('up-flavor', state.flavor)
254
255
  modalElement.setAttribute('up-position', state.position) if u.isPresent(state.position)
255
256
 
@@ -288,7 +289,7 @@ up.modal = do ->
288
289
  var link = document.querySelector('a')
289
290
  up.modal.follow(link)
290
291
 
291
- Any option attributes for [`a[up-modal]`](/a.up-modal) will be honored.
292
+ Any option attributes for [`a[up-modal]`](/a-up-modal) will be honored.
292
293
 
293
294
  Emits events [`up:modal:open`](/up:modal:open) and [`up:modal:opened`](/up:modal:opened).
294
295
 
@@ -436,6 +437,7 @@ up.modal = do ->
436
437
  options.layer = 'modal'
437
438
  options.failTarget ?= link.getAttribute('up-fail-target')
438
439
  options.failLayer ?= link.getAttribute('up-fail-layer') ? 'auto'
440
+ options.cache ?= e.booleanAttr(link, 'up-cache')
439
441
 
440
442
  animateOptions = up.motion.animateOptions(options, link, duration: flavorDefault('openDuration', options.flavor), easing: flavorDefault('openEasing', options.flavor))
441
443
 
@@ -650,7 +652,7 @@ up.modal = do ->
650
652
 
651
653
  Clicking would request the path `/blog` and select `.blog-list` from
652
654
  the HTML response. Unpoly will dim the page
653
- and place the matching `.blog-list` tag will be placed in
655
+ and place the matching `.blog-list` tag in
654
656
  a modal dialog.
655
657
 
656
658
  @selector a[up-modal]
@@ -99,7 +99,7 @@ up.motion = do ->
99
99
 
100
100
  You can pass additional options:
101
101
 
102
- up.animate('warning', '.fade-in', {
102
+ up.animate('.warning', 'fade-in', {
103
103
  delay: 1000,
104
104
  duration: 250,
105
105
  easing: 'linear'
@@ -246,7 +246,7 @@ up.protocol = do ->
246
246
  The parameter name can be configured as a string or as function that returns the parameter name.
247
247
  If no name is set, no token will be sent.
248
248
 
249
- Defaults to the `content` attribute of a `<meta>` tag named `csrf-token`:
249
+ Defaults to the `content` attribute of a `<meta>` tag named `csrf-param`:
250
250
 
251
251
  <meta name="csrf-param" content="authenticity_token" />
252
252
 
@@ -215,7 +215,7 @@ up.syntax = do ->
215
215
  ###**
216
216
  Registers a [compiler](/up.compiler) that is run before all other compilers.
217
217
 
218
- Use `up.macro()` to register a compiler that sets multiply Unpoly attributes.
218
+ Use `up.macro()` to register a compiler that sets multiple Unpoly attributes.
219
219
 
220
220
  \#\#\# Example
221
221
 
@@ -373,10 +373,9 @@ up.syntax = do ->
373
373
  clean = (fragment) ->
374
374
  cleanables = e.subtree(fragment, '.up-can-clean')
375
375
  u.each cleanables, (cleanable) ->
376
- if destructors = cleanable.upDestructors
376
+ if destructors = u.pluckKey(cleanable, 'upDestructors')
377
377
  destructor() for destructor in destructors
378
- # We do not actually remove the #upDestructors property or .up-can-* classes for performance reasons.
379
- # The element we just cleaned is about to be removed from the DOM.
378
+ cleanable.classList.remove('up-can-clean')
380
379
 
381
380
  ###**
382
381
  Checks if the given element has an [`up-data`](/up-data) attribute.
@@ -499,7 +499,8 @@ up.util = do ->
499
499
  @stable
500
500
  ###
501
501
  isJQuery = (object) ->
502
- up.browser.canJQuery() && (object instanceof jQuery)
502
+ # We cannot do `object instanceof jQuery` since window.jQuery might not be set
503
+ !!object?.jquery
503
504
 
504
505
  ###**
505
506
  Returns whether the given argument is an object with a `then` method.
@@ -1163,6 +1164,7 @@ up.util = do ->
1163
1164
  "<": "&lt;"
1164
1165
  ">": "&gt;"
1165
1166
  '"': '&quot;'
1167
+ "'": '&#x27;'
1166
1168
 
1167
1169
  ###**
1168
1170
  Escapes the given string of HTML by replacing control chars with their HTML entities.
@@ -1173,7 +1175,7 @@ up.util = do ->
1173
1175
  @stable
1174
1176
  ###
1175
1177
  escapeHtml = (string) ->
1176
- string.replace /[&<>"]/g, (char) -> ESCAPE_HTML_ENTITY_MAP[char]
1178
+ string.replace /[&<>"']/g, (char) -> ESCAPE_HTML_ENTITY_MAP[char]
1177
1179
 
1178
1180
  ###**
1179
1181
  @function up.util.escapeRegexp
@@ -506,13 +506,37 @@ up.viewport = do ->
506
506
 
507
507
  The scroll positions will be associated with the current URL.
508
508
  They can later be restored by calling [`up.viewport.restoreScroll()`](/up.viewport.restoreScroll)
509
- at the same URL.
509
+ at the same URL, or by following a link with an [`[up-restore-scroll]`](/a-up-follow#up-restore-scroll)
510
+ attribute.
510
511
 
511
- Unpoly automatically saves scroll positions whenever a fragment was updated on the page.
512
+ Unpoly automatically saves scroll positions before a [fragment update](/up.replace)
513
+ you will rarely need to call this function yourself.
514
+
515
+ \#\#\# Examples
516
+
517
+ Should you need to save the current scroll positions outside of a [fragment update](/up.replace),
518
+ you may call:
519
+
520
+ up.viewport.saveScroll()
521
+
522
+ Instead of saving the current scroll positions for the current URL, you may also pass another
523
+ url or vertical scroll positionsfor each viewport:
524
+
525
+ up.viewport.saveScroll({
526
+ url: '/inbox',
527
+ tops: {
528
+ 'body': 0,
529
+ '.sidebar', 100,
530
+ '.main', 320
531
+ }
532
+ })
512
533
 
513
534
  @function up.viewport.saveScroll
514
535
  @param {string} [options.url]
536
+ The URL for which to save scroll positions.
537
+ If omitted, the current browser location is used.
515
538
  @param {Object<string, number>} [options.tops]
539
+ An object mapping viewport selectors to vertical scroll positions in pixels.
516
540
  @experimental
517
541
  ###
518
542
  saveScroll = (options = {}) ->
@@ -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.60.3'
7
+ VERSION = '1.0.0'
8
8
  end
9
9
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unpoly",
3
- "version": "0.60.3",
3
+ "version": "1.0.0",
4
4
  "description": "Unobtrusive JavaScript framework",
5
5
  "main": "dist/unpoly.js",
6
6
  "files": [
data/spec_app/Gemfile CHANGED
@@ -14,11 +14,9 @@ gem 'bower-rails'
14
14
  gem 'bootstrap-sass', '~> 3.3'
15
15
  gem 'rake', '< 11'
16
16
 
17
- # Jasmine spec runner won't boot with a more modern version of sprockets.
18
- # It crashes with an "asset not precompiled" error.
19
17
  gem 'tilt', '=1.4.1'
20
- gem 'sprockets-rails', '=2.2.4'
21
- gem 'sprockets', '=2.12.3'
18
+ gem 'sprockets-rails', '~> 3.2.1'
19
+ gem 'sprockets', '~> 3.7.2'
22
20
 
23
21
  group :development, :test do
24
22
  gem 'byebug'
@@ -12,7 +12,7 @@ GIT
12
12
  PATH
13
13
  remote: ..
14
14
  specs:
15
- unpoly-rails (0.60.1)
15
+ unpoly-rails (0.62.1)
16
16
  rails (>= 3)
17
17
 
18
18
  GEM
@@ -62,7 +62,7 @@ GEM
62
62
  autoprefixer-rails (>= 5.2.1)
63
63
  sass (>= 3.3.4)
64
64
  bower-rails (0.9.2)
65
- builder (3.2.3)
65
+ builder (3.2.4)
66
66
  byebug (3.5.1)
67
67
  columnize (~> 0.8)
68
68
  debugger-linecache (~> 1.2)
@@ -75,8 +75,8 @@ GEM
75
75
  execjs
76
76
  coffee-script-source (1.12.2)
77
77
  columnize (0.9.0)
78
- concurrent-ruby (1.1.4)
79
- crass (1.0.4)
78
+ concurrent-ruby (1.1.6)
79
+ crass (1.0.6)
80
80
  debug_inspector (0.0.2)
81
81
  debugger-linecache (1.2.0)
82
82
  diff-lcs (1.2.5)
@@ -92,7 +92,6 @@ GEM
92
92
  haml (>= 3.1, < 5.0)
93
93
  html2haml (>= 1.0.1)
94
94
  railties (>= 4.0.1)
95
- hike (1.2.3)
96
95
  hpricot (0.8.6)
97
96
  html2haml (1.0.1)
98
97
  erubis (~> 2.7.0)
@@ -102,25 +101,24 @@ GEM
102
101
  i18n (0.9.5)
103
102
  concurrent-ruby (~> 1.0)
104
103
  jasmine-core (2.99.2)
105
- jquery-rails (4.3.1)
104
+ jquery-rails (4.3.5)
106
105
  rails-dom-testing (>= 1, < 3)
107
106
  railties (>= 4.2.0)
108
107
  thor (>= 0.14, < 2.0)
109
108
  json (1.8.6)
110
109
  libv8 (3.16.14.3)
111
- loofah (2.2.3)
110
+ loofah (2.4.0)
112
111
  crass (~> 1.0.2)
113
112
  nokogiri (>= 1.5.9)
114
113
  mail (2.6.3)
115
114
  mime-types (>= 1.16, < 3)
116
115
  mime-types (2.99)
117
116
  mini_portile2 (2.4.0)
118
- minitest (5.11.3)
119
- multi_json (1.13.1)
120
- nokogiri (1.9.1)
117
+ minitest (5.14.0)
118
+ nokogiri (1.10.9)
121
119
  mini_portile2 (~> 2.4.0)
122
120
  phantomjs (2.1.1.0)
123
- rack (1.6.11)
121
+ rack (1.6.13)
124
122
  rack-test (0.6.3)
125
123
  rack (>= 1.0)
126
124
  rails (4.2.0)
@@ -140,8 +138,8 @@ GEM
140
138
  activesupport (>= 4.2.0, < 5.0)
141
139
  nokogiri (~> 1.6)
142
140
  rails-deprecated_sanitizer (>= 1.0.1)
143
- rails-html-sanitizer (1.0.4)
144
- loofah (~> 2.2, >= 2.2.2)
141
+ rails-html-sanitizer (1.3.0)
142
+ loofah (~> 2.3)
145
143
  railties (4.2.0)
146
144
  actionpack (= 4.2.0)
147
145
  activesupport (= 4.2.0)
@@ -177,23 +175,21 @@ GEM
177
175
  tilt (>= 1.1, < 3)
178
176
  sexp_processor (4.4.4)
179
177
  slop (3.6.0)
180
- sprockets (2.12.3)
181
- hike (~> 1.2)
182
- multi_json (~> 1.0)
183
- rack (~> 1.0)
184
- tilt (~> 1.1, != 1.3.0)
185
- sprockets-rails (2.2.4)
186
- actionpack (>= 3.0)
187
- activesupport (>= 3.0)
188
- sprockets (>= 2.8, < 4.0)
178
+ sprockets (3.7.2)
179
+ concurrent-ruby (~> 1.0)
180
+ rack (> 1, < 3)
181
+ sprockets-rails (3.2.1)
182
+ actionpack (>= 4.0)
183
+ activesupport (>= 4.0)
184
+ sprockets (>= 3.0.0)
189
185
  sqlite3 (1.3.10)
190
186
  therubyracer (0.12.1)
191
187
  libv8 (~> 3.16.14.0)
192
188
  ref
193
- thor (0.20.3)
189
+ thor (1.0.1)
194
190
  thread_safe (0.3.6)
195
191
  tilt (1.4.1)
196
- tzinfo (1.2.5)
192
+ tzinfo (1.2.7)
197
193
  thread_safe (~> 0.1)
198
194
  uglifier (2.6.0)
199
195
  execjs (>= 0.3.0)
@@ -220,8 +216,8 @@ DEPENDENCIES
220
216
  rake (< 11)
221
217
  rspec-rails
222
218
  sass-rails (~> 5.0)
223
- sprockets (= 2.12.3)
224
- sprockets-rails (= 2.2.4)
219
+ sprockets (~> 3.7.2)
220
+ sprockets-rails (~> 3.2.1)
225
221
  sqlite3
226
222
  therubyracer
227
223
  tilt (= 1.4.1)
@@ -230,4 +226,4 @@ DEPENDENCIES
230
226
  web-console (~> 2.0)
231
227
 
232
228
  BUNDLED WITH
233
- 1.16.4
229
+ 1.17.3
@@ -7,3 +7,4 @@
7
7
 
8
8
  <div class="timestamp">
9
9
  Uncompiled
10
+ </div>
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  var u = up.util;
3
- up.compiler('.clamped-open', function($link) {
3
+ up.$compiler('.clamped-open', function($link) {
4
4
  $link.on('click', function(event) {
5
5
  event.preventDefault();
6
6
  u.task(function() {
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  var u = up.util;
3
- up.compiler('.clamped-open', function(link) {
3
+ up.$compiler('.clamped-open', function(link) {
4
4
  link.addEventListener('click', function(event) {
5
5
  event.preventDefault();
6
6
  u.task(function() {
@@ -63,7 +63,8 @@
63
63
  <li><%= link_to 'Form (redirect)', '/form_test/redirect/new' %></li>
64
64
  <li><%= link_to 'Error', '/error_test/trigger' %></li>
65
65
  <li><%= link_to 'Booting with non-GET method', '/method_test/page1' %></li>
66
- <li><%= link_to 'Fragment update', '/replace_test/page1' %></li>
66
+ <li><%= link_to 'Fragment update (basic)', '/replace_test/page1' %></li>
67
+ <li><%= link_to 'Fragment update (table)', '/replace_test/table' %></li>
67
68
  <li><%= link_to 'Hash links (without Unpoly)', '/hash_test/vanilla#one' %></li>
68
69
  <li><%= link_to 'Hash links (with Unpoly)', '/hash_test/unpoly#one' %></li>
69
70
  <li><%= link_to 'Revealing content in an overflowing <div> ', '/reveal_test/within_overflowing_div_viewport' %></li>
@@ -0,0 +1,16 @@
1
+ <table>
2
+ <tbody>
3
+ <tr id="one">
4
+ <td><%= Time.now %></td>
5
+ <td><a href="table" up-target="#one">Replace this row</a></td>
6
+ </tr>
7
+ <tr id="two">
8
+ <td><%= Time.now %></td>
9
+ <td><a href="table" up-target="#two">Replace this row</a></td>
10
+ </tr>
11
+ <tr id="three">
12
+ <td><%= Time.now %></td>
13
+ <td><a href="table" up-target="#three">Replace this row</a></td>
14
+ </tr>
15
+ </tbody>
16
+ </table>
@@ -494,3 +494,37 @@ describe 'up.event', ->
494
494
  promiseState(promise).then (result) ->
495
495
  expect(result.state).toEqual('rejected')
496
496
  done()
497
+
498
+ describe 'up.event.halt', ->
499
+
500
+ it 'stops propagation of the given event to other event listeners on the same element', ->
501
+ otherListenerBefore = jasmine.createSpy()
502
+ otherListenerAfter = jasmine.createSpy()
503
+ element = fixture('div')
504
+
505
+ element.addEventListener('foo', otherListenerBefore)
506
+ element.addEventListener('foo', up.event.halt)
507
+ element.addEventListener('foo', otherListenerAfter)
508
+
509
+ up.emit(element, 'foo')
510
+
511
+ expect(otherListenerBefore).toHaveBeenCalled()
512
+ expect(otherListenerAfter).not.toHaveBeenCalled()
513
+
514
+ it 'stops the event from bubbling up the document tree', ->
515
+ parent = fixture('div')
516
+ element = e.affix(parent, 'div')
517
+ parentListener = jasmine.createSpy()
518
+ parent.addEventListener('foo', parentListener)
519
+ element.addEventListener('foo', up.event.halt)
520
+
521
+ up.emit(element, 'foo')
522
+
523
+ expect(parentListener).not.toHaveBeenCalled()
524
+
525
+ it 'prevents default on the event', ->
526
+ element = fixture('div')
527
+ element.addEventListener('foo', up.event.halt)
528
+ event = up.emit(element, 'foo')
529
+ expect(event.defaultPrevented).toBe(true)
530
+