turbolinks 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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: