unpoly-rails 0.60.3 → 1.0.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 (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
+