unpoly-rails 0.34.2 → 0.35.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e934641210c49c58e3d1e30abd25c4fc73a4d24f
4
- data.tar.gz: cf11eb89f6e8ce701827e30c36c167e9a8fbd903
3
+ metadata.gz: f765b71ac71515b46543844c603128f24a494056
4
+ data.tar.gz: e69ba489c3e0307680d05ace76eaa64216271f7f
5
5
  SHA512:
6
- metadata.gz: d62be4725349846015549ab1bbe41ed1a8fe6bb14d290ba87638ce73ce48ccf1a42a6ff58766a4b4a1a9522529700c0b4135c767b4e75d25a4c611aae2ff5e96
7
- data.tar.gz: c33629c9950fd851ed88a0fd2fb99bd159a29b183d2a210a09b80a2238c22b4a55184754bb784e52e82ad7055fc32c07ac21189e319b712a4562184f8cf8e5b6
6
+ metadata.gz: 5b1593da8cf468285b2f3d7041890f8900f94caac29c88bd30a51a99accb1ee7a3a2d002ec2f14c3a2fcc86c96cae00f51252424e3a2b160d5c1ee485c0e67c4
7
+ data.tar.gz: 150c3aafcc1cd83e2d66d7222bfdf570aef1b56645f4c1d0aabb6d7e2965fb9ed2d59041ff2d71d324546411462ee60feab3f3cb8de10471ec300ec57de1410f
data/CHANGELOG.md CHANGED
@@ -6,16 +6,32 @@ All notable changes to this project will be documented in this file.
6
6
  This project mostly adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
8
 
9
+ Unreleased
10
+ ----------
11
+
12
+ ### Compatible changes
13
+
14
+ - Remove a use of global `$` that prevented Unpoly from being used with with [`jQuery.noConflict()`](https://api.jquery.com/jquery.noconflict/).
15
+ - Fix a bug where replacing the `<body>` element would lose the body class and other attributes
16
+ - Fix a bug where Unpoly would set the document title to a `<title>` tag of an inline SVG image.
17
+
18
+
19
+ ### Incompatible changes
20
+
21
+ - Drop support for IE 9, which hasn't been supported on any platform since January 2016.
22
+ - Drop support for IE 10, which hasn't been supported since January 2016 on any platform except
23
+ Windows Vista, and Vista is end-of-life in April 2017.
24
+
25
+
9
26
  0.34.2
10
27
  ------
11
28
 
12
- ## Compatible changes
29
+ ### Compatible changes
13
30
 
14
31
  - The scroll positions of two [viewports](/up-viewport) with the same selector is now restored correctly when going back in history.
15
32
  - Fix a bug where new modals and popups would sometime flash at full opacity before starting their opening animation.
16
33
 
17
34
 
18
-
19
35
  0.34.1
20
36
  ------
21
37
 
@@ -4,13 +4,10 @@ Browser support
4
4
 
5
5
  Unpoly supports all modern browsers. It degrades gracefully with old versions of Internet Explorer:
6
6
 
7
- IE 10, IE11, Edge
7
+ IE11, Edge
8
8
  : Full support
9
9
 
10
- IE 9
11
- : Page updates that change browser history fall back to a classic page load.
12
-
13
- IE 8
10
+ IE 10 or lower
14
11
  : Unpoly prevents itself from booting itself, leaving you with a classic server-side application.
15
12
 
16
13
 
@@ -78,12 +75,7 @@ up.browser = (($) ->
78
75
  @internal
79
76
  ###
80
77
  puts = (stream, args...) ->
81
- if canLogSubstitution()
82
- console[stream](args...)
83
- else
84
- # IE <= 9 cannot pass varargs to console.log using Function#apply because IE
85
- message = sprintf(args...)
86
- console[stream](message)
78
+ console[stream](args...)
87
79
 
88
80
  CONSOLE_PLACEHOLDERS = /\%[odisf]/g
89
81
 
@@ -149,22 +141,19 @@ up.browser = (($) ->
149
141
  url = ->
150
142
  location.href
151
143
 
152
- isIE8OrWorse = u.memoize ->
153
- # This is the most concise way to exclude IE8 and lower
154
- # while keeping all relevant desktop and mobile browsers.
155
- u.isUndefined(document.addEventListener)
156
-
157
- isIE9OrWorse = u.memoize ->
158
- isIE8OrWorse() || navigator.appVersion.indexOf('MSIE 9.') != -1
144
+ isIE10OrWorse = u.memoize ->
145
+ !window.atob
159
146
 
160
147
  ###*
161
148
  Returns whether this browser supports manipulation of the current URL
162
149
  via [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState).
163
150
 
164
- When Unpoly is asked to change history on a browser that doesn't support
165
- `pushState` (e.g. through [`up.follow()`](/up.follow)), it will gracefully
151
+ When `pushState` (e.g. through [`up.follow()`](/up.follow)), it will gracefully
166
152
  fall back to a full page load.
167
153
 
154
+ Note that Unpoly will not use `pushState` if the initial page was loaded with
155
+ a request method other than GET.
156
+
168
157
  @function up.browser.canPushState
169
158
  @return {Boolean}
170
159
  @experimental
@@ -179,8 +168,7 @@ up.browser = (($) ->
179
168
  # 2. Some browsers have a bug where the initial request method is used for all
180
169
  # subsequently pushed states. That means if the user reloads the page on a later
181
170
  # GET state, the browser will wrongly attempt a POST request.
182
- # Modern Firefoxes, Chromes and IE10+ don't seem to be affected by this,
183
- # but we saw this behavior with Safari 8 and IE9 (IE9 can't do pushState anyway).
171
+ # Modern Firefoxes, Chromes and IE10+ don't seem to be affected by this.
184
172
  #
185
173
  # The way that we work around this is that we don't support pushState if the
186
174
  # initial request method was anything other than GET (but allow the rest of the
@@ -198,7 +186,7 @@ up.browser = (($) ->
198
186
 
199
187
  @function up.browser.canCssTransition
200
188
  @return {Boolean}
201
- @experimental
189
+ @internal
202
190
  ###
203
191
  canCssTransition = u.memoize ->
204
192
  'transition' of document.documentElement.style
@@ -208,7 +196,7 @@ up.browser = (($) ->
208
196
 
209
197
  @function up.browser.canInputEvent
210
198
  @return {Boolean}
211
- @experimental
199
+ @internal
212
200
  ###
213
201
  canInputEvent = u.memoize ->
214
202
  'oninput' of document.createElement('input')
@@ -225,20 +213,32 @@ up.browser = (($) ->
225
213
  !!window.FormData
226
214
 
227
215
  ###*
228
- Returns whether this browser supports
229
- [string substitution](https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions)
230
- in `console` functions.
216
+ Returns whether this browser supports the [`DOMParser`](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser)
217
+ interface.
231
218
 
232
- \#\#\# Example for string substition
219
+ @function up.browser.canDomParser
220
+ @return {Boolean}
221
+ @internal
222
+ ###
223
+ canDomParser = u.memoize ->
224
+ !!window.DOMParser
233
225
 
234
- console.log("Hello %o!", "Judy");
226
+ ###*
227
+ Returns whether this browser supports the [`debugging console`](https://developer.mozilla.org/en-US/docs/Web/API/Console).
235
228
 
236
- @function up.browser.canLogSubstitution
229
+ @function up.browser.canConsole
237
230
  @return {Boolean}
238
231
  @internal
239
232
  ###
240
- canLogSubstitution = u.memoize ->
241
- !isIE9OrWorse()
233
+ canConsole = u.memoize ->
234
+ window.console &&
235
+ console.debug &&
236
+ console.info &&
237
+ console.warn &&
238
+ console.error &&
239
+ console.group &&
240
+ console.groupCollapsed &&
241
+ console.groupEnd
242
242
 
243
243
  isRecentJQuery = u.memoize ->
244
244
  version = $.fn.jquery
@@ -293,16 +293,14 @@ up.browser = (($) ->
293
293
  @stable
294
294
  ###
295
295
  isSupported = ->
296
- (!isIE8OrWorse()) && isRecentJQuery()
297
-
298
- ###*
299
- @internal
300
- ###
301
- installPolyfills = ->
302
- window.console ?= {} # Missing in IE9 with DevTools closed
303
- for method in ['debug', 'info', 'warn', 'error', 'group', 'groupCollapsed', 'groupEnd']
304
- console[method] ?= (args...) -> puts('log', args...)
305
- console.log ?= u.noop # Cannot be faked
296
+ !isIE10OrWorse() &&
297
+ isRecentJQuery() &&
298
+ canConsole() &&
299
+ canPushState() &&
300
+ canDomParser() &&
301
+ canFormData() &&
302
+ canCssTransition() &&
303
+ canInputEvent()
306
304
 
307
305
  ###*
308
306
  @internal
@@ -328,14 +326,9 @@ up.browser = (($) ->
328
326
  knife: eval(Knife?.point)
329
327
  url: url
330
328
  loadPage: loadPage
331
- whenConfirmed: whenConfirmed
332
329
  canPushState: canPushState
333
- canCssTransition: canCssTransition
334
- canInputEvent: canInputEvent
335
- canFormData: canFormData
336
- canLogSubstitution: canLogSubstitution
330
+ whenConfirmed: whenConfirmed
337
331
  isSupported: isSupported
338
- installPolyfills: installPolyfills
339
332
  puts: puts
340
333
  sprintf: sprintf
341
334
  sprintfWithFormattedArgs: sprintfWithFormattedArgs
@@ -437,7 +437,6 @@ up.bus = (($) ->
437
437
  if up.browser.isSupported()
438
438
  # Can't decouple this via the event bus, since up.bus would require
439
439
  # up.browser.isSupported() and up.browser would require up.on()
440
- up.browser.installPolyfills()
441
440
  emit('up:framework:boot', message: 'Booting framework')
442
441
  emit('up:framework:booted', message: 'Framework booted')
443
442
  # User-provided compiler definitions will be registered once this function terminates.
@@ -447,6 +446,8 @@ up.bus = (($) ->
447
446
  # The following event will cause Unpoly to compile the <body>
448
447
  emit('up:app:boot', message: 'Booting user application')
449
448
  emit('up:app:booted', message: 'User application booted')
449
+ else
450
+ console.log?("Unpoly doesn't support this browser. Framework was not booted.")
450
451
 
451
452
  ###*
452
453
  This event is [emitted](/up.emit) when Unpoly [starts to boot](/up.boot).
@@ -415,7 +415,8 @@ up.dom = (($) ->
415
415
  parseResponse = (html) ->
416
416
  # jQuery cannot construct transient elements that contain <html> or <body> tags
417
417
  htmlElement = u.createElementFromHtml(html)
418
- title: -> htmlElement.querySelector("title")?.textContent
418
+ title: ->
419
+ htmlElement.querySelector("head title")?.textContent
419
420
  first: (selector) ->
420
421
  # Although we cannot have a jQuery collection from an entire HTML document,
421
422
  # we can use jQuery's Sizzle engine to grep through a DOM tree.
@@ -131,34 +131,25 @@ up.form = (($) ->
131
131
  options.restoreScroll = u.option(options.restoreScroll, u.castedAttr($form, 'up-restore-scroll'))
132
132
  options.origin = u.option(options.origin, $form)
133
133
  options.layer = u.option(options.layer, $form.attr('up-layer'), 'auto')
134
- options.data = up.util.requestDataFromForm($form)
134
+ options.data = u.requestDataFromForm($form)
135
135
  options = u.merge(options, up.motion.animateOptions(options, $form))
136
136
 
137
- hasFileInputs = $form.find('input[type=file]').length
138
- canAjaxSubmit = !hasFileInputs || u.isFormData(options.data)
139
- canHistoryOption = up.browser.canPushState() || options.history == false
140
-
141
137
  if options.validate
142
138
  options.headers ||= {}
143
139
  options.transition = false
144
140
  options.failTransition = false
145
141
  options.headers[up.protocol.config.validateHeader] = options.validate
146
- # If a form has file inputs and the browser does not support FormData,
147
- # we cannot offer inline validations.
148
- unless canAjaxSubmit
149
- return u.unresolvablePromise()
150
142
 
151
143
  up.feedback.start($form)
152
144
 
153
- # If we can't submit this form via AJAX or if we wouldn't be able to change
154
- # the location URL as the result, fall back to a vanilla form submission.
155
- unless canAjaxSubmit && canHistoryOption
145
+ # If we can't update the location URL, fall back to a vanilla form submission.
146
+ unless up.browser.canPushState() || options.history == false
156
147
  $form.get(0).submit()
157
148
  return u.unresolvablePromise()
158
149
 
159
150
  promise = up.replace(target, url, options)
160
151
  promise.always -> up.feedback.stop($form)
161
- return promise
152
+ promise
162
153
 
163
154
  ###*
164
155
  Observes form fields and runs a callback when a value changes.
@@ -285,11 +276,6 @@ up.form = (($) ->
285
276
  # we always bind to both events in case another script manually triggers it.
286
277
  changeEvents = 'input change'
287
278
 
288
- unless up.browser.canInputEvent()
289
- # In case this browser doesn't support input, we listen
290
- # to all sorts of events that might change form controls.
291
- changeEvents += ' keypress paste cut click propertychange'
292
-
293
279
  $field.on(changeEvents, check)
294
280
 
295
281
  # return destructor
@@ -78,16 +78,14 @@ up.motion = (($) ->
78
78
  ###*
79
79
  Returns whether Unpoly will perform animations.
80
80
 
81
- Animations will be performed if the browser supports
82
- [CSS transitions](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions)
83
- and if [`up.motion.config.enabled`](/up.motion.config) is set to `true` (which is the default).
81
+ Set [`up.motion.config.enabled`](/up.motion.config) `false` in order to disable animations globally.
84
82
 
85
83
  @function up.motion.isEnabled
86
84
  @return {Boolean}
87
85
  @stable
88
86
  ###
89
87
  isEnabled = ->
90
- config.enabled && up.browser.canCssTransition()
88
+ config.enabled
91
89
 
92
90
  ###*
93
91
  Applies the given animation to the given element.
@@ -28,13 +28,14 @@ Unpoly requires an additional response header to detect redirects, which are
28
28
  otherwise undetectable for any AJAX client.
29
29
 
30
30
  After the form's action performs a redirect, the next response should include the new
31
- URL in this HTTP header:
31
+ URL in the HTTP headers:
32
32
 
33
33
  ```http
34
+ X-Up-Method: GET
34
35
  X-Up-Location: /current-url
35
36
  ```
36
37
 
37
- The **simplest implementation** is to set this header for every request.
38
+ The **simplest implementation** is to set these headers for every request.
38
39
 
39
40
 
40
41
  \#\#\# Optimizing responses
@@ -131,14 +132,19 @@ but you can adapt it for other languages:
131
132
 
132
133
  \#\#\# Signaling the initial request method
133
134
 
134
- This is a edge case you might or might not care about:
135
135
  If the initial page was loaded with a non-`GET` HTTP method, Unpoly prefers to make a full
136
- page load when you try to update a fragment. Once a page was loaded with a `GET` method,
136
+ page load when you try to update a fragment. Once the next page was loaded with a `GET` method,
137
137
  Unpoly will restore its standard behavior.
138
138
 
139
- The reason for this is that some browsers remember the method of the initial page load and don't let
140
- the application change it, even with `pushState`. Thus, when the user reloads the page much later,
141
- an affected browser might request a `POST`, `PUT`, etc. instead of the correct method.
139
+ This fixes two edge cases you might or might not care about:
140
+
141
+ 1. Unpoly replaces the initial page state so it can later restore it when the user
142
+ goes back to that initial URL. However, if the initial request was a POST,
143
+ Unpoly will wrongly assume that it can restore the state by reloading with GET.
144
+ 2. Some browsers have a bug where the initial request method is used for all
145
+ subsequently pushed states. That means if the user reloads the page on a later
146
+ GET state, the browser will wrongly attempt a POST request.
147
+ Modern Firefoxes, Chromes and IE10+ don't seem to be affected by this.
142
148
 
143
149
  In order to allow Unpoly to detect the HTTP method of the initial page load,
144
150
  the server must set a cookie:
@@ -180,7 +186,8 @@ up.protocol = (($) ->
180
186
  @internal
181
187
  ###
182
188
  methodFromXhr = (xhr) ->
183
- xhr.getResponseHeader(config.methodHeader)
189
+ if method = xhr.getResponseHeader(config.methodHeader)
190
+ u.normalizeMethod(method)
184
191
 
185
192
  ###*
186
193
  Server-side companion libraries like unpoly-rails set this cookie so we
@@ -196,44 +196,18 @@ up.util = (($) ->
196
196
  classes = classString.split(' ')
197
197
  select classes, (klass) -> isPresent(klass) && !klass.match(/^up-/)
198
198
 
199
+ openTagPattern = (tag) ->
200
+ new RegExp("<#{tag}(?: [^>]*)?>", 'i')
201
+
199
202
  # jQuery's implementation of $(...) cannot create elements that have
200
203
  # an <html> or <body> tag. So we're using native elements.
201
204
  # Also IE9 cannot set innerHTML on a <html> or <head> element.
202
205
  createElementFromHtml = (html) ->
203
-
204
- openTag = (tag) -> "<#{tag}(?: [^>]*)?>"
205
- closeTag = (tag) -> "</#{tag}>"
206
- anything = '(?:.|\\s)*?'
207
- capture = (pattern) -> "(#{pattern})"
208
-
209
- titlePattern = new RegExp(
210
- openTag('title') +
211
- capture(anything) +
212
- closeTag('title'),
213
- 'i')
214
-
215
- bodyPattern = new RegExp(
216
- openTag('body') +
217
- capture(anything) +
218
- closeTag('body'),
219
- 'i')
220
-
221
- if bodyMatch = html.match(bodyPattern)
222
-
223
- htmlElement = document.createElement('html')
224
- bodyElement = createElement('body', bodyMatch[1])
225
- htmlElement.appendChild(bodyElement)
226
-
227
- if titleMatch = html.match(titlePattern)
228
- headElement = createElement('head')
229
- htmlElement.appendChild(headElement)
230
- titleElement = createElement('title', titleMatch[1])
231
- headElement.appendChild(titleElement)
232
-
233
- htmlElement
234
-
206
+ bodyPattern = openTagPattern('body')
207
+ return if html.match(bodyPattern)
208
+ parser = new DOMParser()
209
+ parser.parseFromString(html, 'text/html')
235
210
  else
236
-
237
211
  # we possibly received a layout-less fragment
238
212
  createElement('div', html)
239
213
 
@@ -544,7 +518,7 @@ up.util = (($) ->
544
518
  @internal
545
519
  ###
546
520
  isFormData = (object) ->
547
- up.browser.canFormData() && object instanceof FormData
521
+ object instanceof FormData
548
522
 
549
523
  ###*
550
524
  Converts the given array-like argument into an array.
@@ -1646,8 +1620,9 @@ up.util = (($) ->
1646
1620
  ###
1647
1621
  requestDataAsArray = (data) ->
1648
1622
  if isFormData(data)
1649
- # Until FormData#entries is implemented in all major browsers
1650
- # we must give up here
1623
+ # Until FormData#entries is implemented in all major browsers we must give up here.
1624
+ # However, up.form will prefer to serialize forms as arrays, so we should be good
1625
+ # in most cases. We only use FormData for forms with file inputs.
1651
1626
  up.fail('Cannot convert FormData into an array')
1652
1627
  else
1653
1628
  query = requestDataAsQuery(data)
@@ -1669,8 +1644,9 @@ up.util = (($) ->
1669
1644
  ###
1670
1645
  requestDataAsQuery = (data) ->
1671
1646
  if isFormData(data)
1672
- # Until FormData#entries is implemented in all major browsers
1673
- # we must give up here
1647
+ # Until FormData#entries is implemented in all major browsers we must give up here.
1648
+ # However, up.form will prefer to serialize forms as arrays, so we should be good
1649
+ # in most cases. We only use FormData for forms with file inputs.
1674
1650
  up.fail('Cannot convert FormData into a query string')
1675
1651
  else if isPresent(data)
1676
1652
  query = $.param(data)
@@ -1689,11 +1665,16 @@ up.util = (($) ->
1689
1665
  requestDataFromForm = (form) ->
1690
1666
  $form = $(form)
1691
1667
  hasFileInputs = $form.find('input[type=file]').length
1692
- if hasFileInputs && up.browser.canFormData()
1668
+ # We try to stick with an array representation, whose contents we can inspect.
1669
+ # We cannot inspect FormData on IE11 because it has no support for `FormData.entries`.
1670
+ # Inspection is needed to generate a cache key (see `up.proxy`) and to make
1671
+ # vanilla requests when `pushState` is unavailable (see `up.browser.loadPage`).
1672
+ if hasFileInputs
1693
1673
  new FormData($form.get(0))
1694
1674
  else
1695
1675
  $form.serializeArray()
1696
1676
 
1677
+
1697
1678
  ###*
1698
1679
  Adds a key/value pair to the given request data representation.
1699
1680
 
@@ -2096,6 +2077,6 @@ up.util = (($) ->
2096
2077
  flatten: flatten
2097
2078
  isTruthy: isTruthy
2098
2079
 
2099
- )($)
2080
+ )(jQuery)
2100
2081
 
2101
2082
  up.fail = up.util.fail