turbolinks 2.4.0 → 2.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5fba03f6769899590a73f8d2f6f1e2b1756335cc
4
- data.tar.gz: fcb4ee4559b15a8d3037ccd75636a8de9513365e
3
+ metadata.gz: abb3fc6406cda2909edbf7936a255b9c9b7ca806
4
+ data.tar.gz: 99aea405491c3b4a640d9c85d03331d545c62cfa
5
5
  SHA512:
6
- metadata.gz: 0cf63e984ac0cfed94831716ecfaf3db1be2045e83e07d9b8e4304ce6639135ddee36876b6c945fcec564730d33bb5f00e1c66d456218bc0db054568a2ef7790
7
- data.tar.gz: 6733c1c8dc08e44e4271962e50ed4891ed96e0ba30a531c08025b001ecf700d0b4c5ef082891d62391d91c9a8f0bf81f7ce3e1838bf8774b582302289d42660c
6
+ metadata.gz: bf05a3b36102d4487d763e244d69c7365bf6c1f42a944a928244d05a2d61ff4b7b89d02ede941cca5ba45efd3924878218d26bd64f1a5af859091f4dc992f874
7
+ data.tar.gz: 41bfb7a7287ccc9090ac53161365465fbba1effbb19cc77bb6ce8b1438f07d9bad2263d4164bcbd3404b7e42722e24df9389f33914a21c0d0aab77004f9c3542
data/README.md CHANGED
@@ -86,6 +86,28 @@ The one drawback is that dramatic differences in appearence between a cached cop
86
86
 
87
87
  If you find that a page is causing problems, you can have Turbolinks skip displaying the cached copy by adding `data-no-transition-cache` to any DOM element on the offending page.
88
88
 
89
+ Progress Bar
90
+ ------------
91
+
92
+ Because Turbolinks skips the traditional full page reload, browsers won't display their native progress bar when changing pages. To fill this void, Turbolinks offers an optional JavaScript-and-CSS-based progress bar to display page loading progress.
93
+
94
+ To enable the progress bar, include the following in your JavaScript:
95
+ ```javascript
96
+ Turbolinks.enableProgressBar();
97
+ ```
98
+
99
+ The progress bar is implemented on the `<html>` element's pseudo `:before` element and can be **customized** by including CSS with higher specificity than the included styles. For example:
100
+
101
+ ```css
102
+ html.turbolinks-progress-bar::before {
103
+ background-color: red !important;
104
+ height: 5px !important;
105
+ }
106
+ ```
107
+
108
+ In Turbolinks 3.0, the progress bar will be turned on by default.
109
+
110
+
89
111
  Initialization
90
112
  --------------
91
113
 
@@ -1,6 +1,7 @@
1
1
  pageCache = {}
2
2
  cacheSize = 10
3
3
  transitionCacheEnabled = false
4
+ progressBar = null
4
5
 
5
6
  currentState = null
6
7
  loadedAssets = null
@@ -26,10 +27,11 @@ fetch = (url) ->
26
27
 
27
28
  rememberReferer()
28
29
  cacheCurrentPage()
30
+ progressBar?.start()
29
31
 
30
32
  if transitionCacheEnabled and cachedPage = transitionCacheFor(url.absolute)
31
33
  fetchHistory cachedPage
32
- fetchReplacement url
34
+ fetchReplacement url, null, false
33
35
  else
34
36
  fetchReplacement url, resetScrollPosition
35
37
 
@@ -40,7 +42,14 @@ transitionCacheFor = (url) ->
40
42
  enableTransitionCache = (enable = true) ->
41
43
  transitionCacheEnabled = enable
42
44
 
43
- fetchReplacement = (url, onLoadFunction = =>) ->
45
+ enableProgressBar = (enable = true) ->
46
+ if enable
47
+ progressBar ?= new ProgressBar 'html'
48
+ else
49
+ progressBar?.uninstall()
50
+ progressBar = null
51
+
52
+ fetchReplacement = (url, onLoadFunction, showProgressBar = true) ->
44
53
  triggerEvent EVENTS.FETCH, url: url.absolute
45
54
 
46
55
  xhr?.abort()
@@ -57,10 +66,18 @@ fetchReplacement = (url, onLoadFunction = =>) ->
57
66
  changePage extractTitleAndBody(doc)...
58
67
  manuallyTriggerHashChangeForFirefox()
59
68
  reflectRedirectedUrl()
60
- onLoadFunction()
69
+ onLoadFunction?()
61
70
  triggerEvent EVENTS.LOAD
62
71
  else
63
- document.location.href = url.absolute
72
+ document.location.href = crossOriginRedirect() or url.absolute
73
+
74
+ if progressBar and showProgressBar
75
+ xhr.onprogress = (event) =>
76
+ percent = if event.lengthComputable
77
+ event.loaded / event.total * 100
78
+ else
79
+ progressBar.value + (100 - progressBar.value) / 10
80
+ progressBar.advanceTo(percent)
64
81
 
65
82
  xhr.onloadend = -> xhr = null
66
83
  xhr.onerror = -> document.location.href = url.absolute
@@ -110,6 +127,7 @@ changePage = (title, body, csrfToken, runScripts) ->
110
127
  setAutofocusElement()
111
128
  executeScriptTags() if runScripts
112
129
  currentState = window.history.state
130
+ progressBar?.done()
113
131
  triggerEvent EVENTS.CHANGE
114
132
  triggerEvent EVENTS.UPDATE
115
133
 
@@ -134,7 +152,7 @@ setAutofocusElement = ->
134
152
  autofocusElement = (list = document.querySelectorAll 'input[autofocus], textarea[autofocus]')[list.length - 1]
135
153
  if autofocusElement and document.activeElement isnt autofocusElement
136
154
  autofocusElement.focus()
137
-
155
+
138
156
  reflectNewUrl = (url) ->
139
157
  if (url = new ComponentUrl url).absolute isnt referer
140
158
  window.history.pushState { turbolinks: true, url: url.absolute }, '', url.absolute
@@ -145,6 +163,9 @@ reflectRedirectedUrl = ->
145
163
  preservedHash = if location.hasNoHash() then document.location.hash else ''
146
164
  window.history.replaceState currentState, '', location.href + preservedHash
147
165
 
166
+ crossOriginRedirect = ->
167
+ redirect if (redirect = xhr.getResponseHeader('Location'))? and (new ComponentUrl(redirect)).crossOrigin()
168
+
148
169
  rememberReferer = ->
149
170
  referer = document.location.href
150
171
 
@@ -204,7 +225,7 @@ processResponse = ->
204
225
  400 <= xhr.status < 600
205
226
 
206
227
  validContent = ->
207
- (contentType = xhr.getResponseHeader('Content-Type'))? and
228
+ (contentType = xhr.getResponseHeader('Content-Type'))? and
208
229
  contentType.match /^(?:text\/html|application\/xhtml\+xml|application\/xml)(?:;|$)/
209
230
 
210
231
  extractTrackAssets = (doc) ->
@@ -303,10 +324,10 @@ browserCompatibleDocumentParser = ->
303
324
 
304
325
 
305
326
  # The ComponentUrl class converts a basic URL string into an object
306
- # that behaves similarly to document.location.
327
+ # that behaves similarly to document.location.
307
328
  #
308
- # If an instance is created from a relative URL, the current document
309
- # is used to fill in the missing attributes (protocol, host, port).
329
+ # If an instance is created from a relative URL, the current document
330
+ # is used to fill in the missing attributes (protocol, host, port).
310
331
  class ComponentUrl
311
332
  constructor: (@original = document.location.href) ->
312
333
  return @original if @original.constructor is ComponentUrl
@@ -319,6 +340,9 @@ class ComponentUrl
319
340
 
320
341
  hasNoHash: -> @hash.length is 0
321
342
 
343
+ crossOrigin: ->
344
+ @origin isnt (new ComponentUrl).origin
345
+
322
346
  _parse: ->
323
347
  (@link ?= document.createElement 'a').href = @original
324
348
  { @href, @protocol, @host, @hostname, @port, @pathname, @search, @hash } = @link
@@ -345,15 +369,12 @@ class Link extends ComponentUrl
345
369
  super
346
370
 
347
371
  shouldIgnore: ->
348
- @_crossOrigin() or
349
- @_anchored() or
350
- @_nonHtml() or
351
- @_optOut() or
372
+ @crossOrigin() or
373
+ @_anchored() or
374
+ @_nonHtml() or
375
+ @_optOut() or
352
376
  @_target()
353
377
 
354
- _crossOrigin: ->
355
- @origin isnt (new ComponentUrl).origin
356
-
357
378
  _anchored: ->
358
379
  (@hash.length > 0 or @href.charAt(@href.length - 1) is '#') and
359
380
  (@withoutHash() is (new ComponentUrl).withoutHash())
@@ -373,9 +394,9 @@ class Link extends ComponentUrl
373
394
 
374
395
 
375
396
  # The Click class handles clicked links, verifying if Turbolinks should
376
- # take control by inspecting both the event and the link. If it should,
377
- # the page change process is initiated. If not, control is passed back
378
- # to the browser for default functionality.
397
+ # take control by inspecting both the event and the link. If it should,
398
+ # the page change process is initiated. If not, control is passed back
399
+ # to the browser for default functionality.
379
400
  class Click
380
401
  @installHandlerLast: (event) ->
381
402
  unless event.defaultPrevented
@@ -390,7 +411,7 @@ class Click
390
411
  @_extractLink()
391
412
  if @_validForTurbolinks()
392
413
  visit @link.href unless pageChangePrevented(@link.absolute)
393
- @event.preventDefault()
414
+ @event.preventDefault()
394
415
 
395
416
  _extractLink: ->
396
417
  link = @event.target
@@ -401,13 +422,108 @@ class Click
401
422
  @link? and not (@link.shouldIgnore() or @_nonStandardClick())
402
423
 
403
424
  _nonStandardClick: ->
404
- @event.which > 1 or
405
- @event.metaKey or
406
- @event.ctrlKey or
407
- @event.shiftKey or
425
+ @event.which > 1 or
426
+ @event.metaKey or
427
+ @event.ctrlKey or
428
+ @event.shiftKey or
408
429
  @event.altKey
409
430
 
410
431
 
432
+ class ProgressBar
433
+ className = 'turbolinks-progress-bar'
434
+
435
+ constructor: (@elementSelector) ->
436
+ @value = 0
437
+ @opacity = 1
438
+ @content = ''
439
+ @speed = 300
440
+ @install()
441
+
442
+ install: ->
443
+ @element = document.querySelector(@elementSelector)
444
+ @element.classList.add(className)
445
+ @styleElement = document.createElement('style')
446
+ document.head.appendChild(@styleElement)
447
+ @_updateStyle()
448
+
449
+ uninstall: ->
450
+ @element.classList.remove(className)
451
+ document.head.removeChild(@styleElement)
452
+
453
+ start: ->
454
+ @advanceTo(5)
455
+
456
+ advanceTo: (value) ->
457
+ if value > @value <= 100
458
+ @value = value
459
+ @_updateStyle()
460
+
461
+ if @value is 100
462
+ @_stopTrickle()
463
+ else if @value > 0
464
+ @_startTrickle()
465
+
466
+ done: ->
467
+ if @value > 0
468
+ @advanceTo(100)
469
+ @_reset()
470
+
471
+ _reset: ->
472
+ setTimeout =>
473
+ @opacity = 0
474
+ @_updateStyle()
475
+ , @speed / 2
476
+
477
+ setTimeout =>
478
+ @value = 0
479
+ @opacity = 1
480
+ @_withSpeed(0, => @_updateStyle(true))
481
+ , @speed
482
+
483
+ _startTrickle: ->
484
+ return if @trickling
485
+ @trickling = true
486
+ setTimeout(@_trickle, @speed)
487
+
488
+ _stopTrickle: ->
489
+ delete @trickling
490
+
491
+ _trickle: =>
492
+ return unless @trickling
493
+ @advanceTo(@value + Math.random() / 2)
494
+ setTimeout(@_trickle, @speed)
495
+
496
+ _withSpeed: (speed, fn) ->
497
+ originalSpeed = @speed
498
+ @speed = speed
499
+ result = fn()
500
+ @speed = originalSpeed
501
+ result
502
+
503
+ _updateStyle: (forceRepaint = false) ->
504
+ @_changeContentToForceRepaint() if forceRepaint
505
+ @styleElement.textContent = @_createCSSRule()
506
+
507
+ _changeContentToForceRepaint: ->
508
+ @content = if @content is '' then ' ' else ''
509
+
510
+ _createCSSRule: ->
511
+ """
512
+ #{@elementSelector}.#{className}::before {
513
+ content: '#{@content}';
514
+ position: fixed;
515
+ top: 0;
516
+ left: 0;
517
+ background-color: #0076ff;
518
+ height: 3px;
519
+ opacity: #{@opacity};
520
+ width: #{@value}%;
521
+ transition: width #{@speed}ms ease-out, opacity #{@speed / 2}ms ease-in;
522
+ transform: translate3d(0,0,0);
523
+ }
524
+ """
525
+
526
+
411
527
  # Delay execution of function long enough to miss the popstate event
412
528
  # some browsers fire on the initial page load.
413
529
  bypassOnLoadPopstate = (fn) ->
@@ -487,7 +603,8 @@ else
487
603
  visit,
488
604
  pagesCached,
489
605
  enableTransitionCache,
606
+ enableProgressBar,
490
607
  allowLinkExtensions: Link.allowExtensions,
491
608
  supported: browserSupportsTurbolinks,
492
609
  EVENTS: clone(EVENTS)
493
- }
610
+ }
@@ -1,3 +1,3 @@
1
1
  module Turbolinks
2
- VERSION = '2.4.0'
2
+ VERSION = '2.5.0'
3
3
  end
data/test/config.ru CHANGED
@@ -7,6 +7,29 @@ Assets = Sprockets::Environment.new do |env|
7
7
  env.append_path File.join(Root, "lib", "assets", "javascripts")
8
8
  end
9
9
 
10
+ class SlowResponse
11
+ CHUNKS = ['<html><body>', '.'*50, '.'*20, '<a href="/index.html">Home</a></body></html>']
12
+
13
+ def call(env)
14
+ [200, headers, self]
15
+ end
16
+
17
+ def each
18
+ CHUNKS.each do |part|
19
+ sleep rand(0.3..0.8)
20
+ yield part
21
+ end
22
+ end
23
+
24
+ def length
25
+ CHUNKS.join.length
26
+ end
27
+
28
+ def headers
29
+ { "Content-Length" => length.to_s, "Content-Type" => "text/html", "Cache-Control" => "no-cache, no-store, must-revalidate" }
30
+ end
31
+ end
32
+
10
33
  map "/js" do
11
34
  run Assets
12
35
  end
@@ -19,6 +42,10 @@ map "/withoutextension" do
19
42
  run Rack::File.new(File.join(Root, "test", "withoutextension"), "Content-Type" => "text/html")
20
43
  end
21
44
 
45
+ map "/slow-response" do
46
+ run SlowResponse.new
47
+ end
48
+
22
49
  map "/" do
23
50
  run Rack::Directory.new(File.join(Root, "test"))
24
51
  end
data/test/index.html CHANGED
@@ -5,6 +5,8 @@
5
5
  <title>Home</title>
6
6
  <script type="text/javascript" src="/js/turbolinks.js"></script>
7
7
  <script type="text/javascript">
8
+ Turbolinks.enableProgressBar();
9
+
8
10
  document.addEventListener("page:change", function() {
9
11
  console.log("page changed");
10
12
  });
@@ -19,8 +21,9 @@
19
21
  </script>
20
22
  </head>
21
23
  <body class="page-index">
22
- <ul style="margin-top:200px;">
24
+ <ul style="margin-top:20px;">
23
25
  <li><a href="/other.html">Other page</a></li>
26
+ <li><a href="/slow-response">Slow loading page for progress bar</a></li>
24
27
  <li><a href="/other.html"><span>Wrapped link</span></a></li>
25
28
  <li><a href="/withoutextension">Without extension</a></li>
26
29
  <li><a href="/withoutextension?sort=user.name">Without extension with query params</a></li>
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turbolinks
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-03 00:00:00.000000000 Z
11
+ date: 2014-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: coffee-rails
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  description:
@@ -30,6 +30,8 @@ executables: []
30
30
  extensions: []
31
31
  extra_rdoc_files: []
32
32
  files:
33
+ - MIT-LICENSE
34
+ - README.md
33
35
  - lib/assets/javascripts/turbolinks.js.coffee
34
36
  - lib/turbolinks.rb
35
37
  - lib/turbolinks/cookies.rb
@@ -38,8 +40,6 @@ files:
38
40
  - lib/turbolinks/x_domain_blocker.rb
39
41
  - lib/turbolinks/xhr_headers.rb
40
42
  - lib/turbolinks/xhr_url_for.rb
41
- - README.md
42
- - MIT-LICENSE
43
43
  - test/config.ru
44
44
  - test/dummy.gif
45
45
  - test/index.html
@@ -58,20 +58,19 @@ require_paths:
58
58
  - lib
59
59
  required_ruby_version: !ruby/object:Gem::Requirement
60
60
  requirements:
61
- - - '>='
61
+ - - ">="
62
62
  - !ruby/object:Gem::Version
63
63
  version: '0'
64
64
  required_rubygems_version: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  requirements: []
70
70
  rubyforge_project:
71
- rubygems_version: 2.0.3
71
+ rubygems_version: 2.2.2
72
72
  signing_key:
73
73
  specification_version: 4
74
74
  summary: Turbolinks makes following links in your web application faster (use with
75
75
  Rails Asset Pipeline)
76
76
  test_files: []
77
- has_rdoc: