upjs-rails 0.12.5 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +23 -0
  3. data/CHANGELOG.md +20 -0
  4. data/design/up-validate.js.coffee +284 -0
  5. data/dist/up-bootstrap.js +4 -0
  6. data/dist/up-bootstrap.min.js +1 -1
  7. data/dist/up.js +547 -102
  8. data/dist/up.min.js +2 -2
  9. data/lib/assets/javascripts/up/browser.js.coffee +3 -2
  10. data/lib/assets/javascripts/up/flow.js.coffee +95 -17
  11. data/lib/assets/javascripts/up/form.js.coffee +327 -34
  12. data/lib/assets/javascripts/up/history.js.coffee +1 -1
  13. data/lib/assets/javascripts/up/layout.js.coffee +4 -4
  14. data/lib/assets/javascripts/up/link.js.coffee +5 -2
  15. data/lib/assets/javascripts/up/modal.js.coffee +1 -0
  16. data/lib/assets/javascripts/up/proxy.js.coffee +27 -12
  17. data/lib/assets/javascripts/up/syntax.js.coffee +39 -20
  18. data/lib/assets/javascripts/up/util.js.coffee +29 -12
  19. data/lib/assets/javascripts/up-bootstrap/form-ext.js.coffee +1 -0
  20. data/lib/upjs/rails/engine.rb +1 -1
  21. data/lib/upjs/rails/inspector.rb +63 -0
  22. data/lib/upjs/rails/inspector_accessor.rb +28 -0
  23. data/lib/upjs/rails/request_echo_headers.rb +7 -0
  24. data/lib/upjs/rails/request_method_cookie.rb +12 -4
  25. data/lib/upjs/rails/version.rb +5 -1
  26. data/lib/upjs-rails.rb +7 -5
  27. data/spec_app/.rspec +2 -0
  28. data/spec_app/Gemfile +0 -3
  29. data/spec_app/Gemfile.lock +43 -44
  30. data/spec_app/app/assets/stylesheets/application.css +1 -1
  31. data/spec_app/app/controllers/test_controller.rb +23 -0
  32. data/spec_app/config/routes.rb +2 -0
  33. data/spec_app/spec/controllers/test_controller_spec.rb +67 -0
  34. data/spec_app/spec/javascripts/helpers/append_fixture.js.coffee +8 -0
  35. data/spec_app/spec/javascripts/helpers/last_request.js.coffee +18 -0
  36. data/spec_app/spec/javascripts/helpers/reset_path.js.coffee +1 -0
  37. data/spec_app/spec/javascripts/up/flow_spec.js.coffee +93 -43
  38. data/spec_app/spec/javascripts/up/form_spec.js.coffee +80 -18
  39. data/spec_app/spec/javascripts/up/history_spec.js.coffee +1 -5
  40. data/spec_app/spec/javascripts/up/link_spec.js.coffee +18 -17
  41. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +32 -37
  42. data/spec_app/spec/javascripts/up/navigation_spec.js.coffee +7 -26
  43. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +1 -7
  44. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +26 -25
  45. data/spec_app/spec/javascripts/up/util_spec.js.coffee +23 -0
  46. data/spec_app/spec/spec_helper.rb +62 -0
  47. metadata +12 -3
  48. data/lib/upjs/rails/request_ext.rb +0 -13
@@ -1,21 +1,10 @@
1
1
  ###*
2
- Forms and controls
3
- ==================
2
+ Forms
3
+ =====
4
4
 
5
- Up.js comes with functionality to submit forms without
6
- leaving the current page. This means you can replace page fragments,
5
+ Up.js comes with functionality to [submit](/form-up-target) and [validate](/up-validate)
6
+ forms without leaving the current page. This means you can replace page fragments,
7
7
  open dialogs with sub-forms, etc. all without losing form state.
8
-
9
- \#\#\# Incomplete documentation!
10
-
11
- We need to work on this page:
12
-
13
- - Explain how to display form errors
14
- - Explain that the server needs to send 2xx or 5xx status codes so
15
- Up.js can decide whether the form submission was successful
16
- - Explain that the server needs to send `X-Up-Location` and `X-Up-Method` headers
17
- if an successful form submission resulted in a redirect
18
- - Examples
19
8
 
20
9
  @class up.form
21
10
  ###
@@ -24,14 +13,37 @@ up.form = (($) ->
24
13
  u = up.util
25
14
 
26
15
  ###*
27
- Submits a form using the Up.js flow:
28
-
29
- up.submit('form.new_user')
16
+ Sets default options for form submission and validation.
17
+
18
+ @property up.form.config
19
+ @param {Array} [config.validateTargets=['[up-fieldset]:has(&)', 'fieldset:has(&)', 'label:has(&)', 'form:has(&)']]
20
+ An array of CSS selectors that are searched around a form field
21
+ that wants to [validate](/up.validate). The first matching selector
22
+ will be updated with the validation messages from the server.
23
+
24
+ By default this looks for a `<fieldset>`, `<label>` or `<form>`
25
+ around the validating input field, or any element with an
26
+ `up-fieldset` attribute.
27
+ ###
28
+ config = u.config
29
+ validateTargets: ['[up-fieldset]:has(&)', 'fieldset:has(&)', 'label:has(&)', 'form:has(&)']
30
+
31
+ reset = ->
32
+ config.reset()
33
+
34
+ ###*
35
+ Submits a form via AJAX and updates a page fragment with the response.
36
+
37
+ up.submit('form.new-user', { target: '.main' })
30
38
 
31
39
  Instead of loading a new page, the form is submitted via AJAX.
32
40
  The response is parsed for a CSS selector and the matching elements will
33
41
  replace corresponding elements on the current page.
34
-
42
+
43
+ The UJS variant of this is the [`form[up-target]`](/form-up-target) selector.
44
+ See the documentation for [`form[up-target]`](/form-up-target) for more
45
+ information on how AJAX form submissions work in Up.js.
46
+
35
47
  @function up.submit
36
48
  @param {Element|jQuery|String} formOrSelector
37
49
  A reference or selector for the form to submit.
@@ -81,6 +93,9 @@ up.form = (($) ->
81
93
 
82
94
  By default only responses to `GET` requests are cached
83
95
  for a few minutes.
96
+ @param {Object} [options.headers={}]
97
+ An object of additional header key/value pairs to send along
98
+ with the request.
84
99
  @return {Promise}
85
100
  A promise for the successful form submission.
86
101
  ###
@@ -89,27 +104,38 @@ up.form = (($) ->
89
104
  $form = $(formOrSelector).closest('form')
90
105
 
91
106
  options = u.options(options)
92
- successSelector = u.option(options.target, $form.attr('up-target'), 'body')
93
- failureSelector = u.option(options.failTarget, $form.attr('up-fail-target'), -> u.createSelectorFromElement($form))
107
+ successSelector = up.flow.resolveSelector(u.option(options.target, $form.attr('up-target'), 'body'), options)
108
+ failureSelector = up.flow.resolveSelector(u.option(options.failTarget, $form.attr('up-fail-target'), -> u.createSelectorFromElement($form)), options)
94
109
  historyOption = u.option(options.history, u.castedAttr($form, 'up-history'), true)
95
110
  successTransition = u.option(options.transition, u.castedAttr($form, 'up-transition'))
96
111
  failureTransition = u.option(options.failTransition, u.castedAttr($form, 'up-fail-transition'), successTransition)
97
112
  httpMethod = u.option(options.method, $form.attr('up-method'), $form.attr('data-method'), $form.attr('method'), 'post').toUpperCase()
113
+ headers = u.option(options.headers, {})
98
114
 
99
115
  implantOptions = {}
100
116
  implantOptions.reveal = u.option(options.reveal, u.castedAttr($form, 'up-reveal'), true)
101
117
  implantOptions.cache = u.option(options.cache, u.castedAttr($form, 'up-cache'))
102
118
  implantOptions.restoreScroll = u.option(options.restoreScroll, u.castedAttr($form, 'up-restore-scroll'))
119
+ implantOptions.origin = u.option(options.origin, $form)
103
120
  implantOptions = u.extend(implantOptions, up.motion.animateOptions(options, $form))
104
121
 
105
122
  useCache = u.option(options.cache, u.castedAttr($form, 'up-cache'))
106
123
  url = u.option(options.url, $form.attr('action'), up.browser.url())
107
-
124
+
125
+ hasFileInputs = $form.find('input[type=file]').length
126
+
127
+ if options.validate
128
+ headers['X-Up-Validate'] = options.validate
129
+ # Since we cannot (yet) submit file inputs via AJAX, we cannot
130
+ # offer inline validation for such forms.
131
+ if hasFileInputs
132
+ return u.unresolvablePromise()
133
+
108
134
  $form.addClass('up-active')
109
-
110
- if !up.browser.canPushState() && historyOption != false
135
+
136
+ if hasFileInputs || (!up.browser.canPushState() && historyOption != false)
111
137
  $form.get(0).submit()
112
- return
138
+ return u.unresolvablePromise()
113
139
 
114
140
  request = {
115
141
  url: url
@@ -117,6 +143,7 @@ up.form = (($) ->
117
143
  data: $form.serialize()
118
144
  selector: successSelector
119
145
  cache: useCache
146
+ headers: headers
120
147
  }
121
148
 
122
149
  successUrl = (xhr) ->
@@ -148,7 +175,11 @@ up.form = (($) ->
148
175
  Observes a form field and runs a callback when its value changes.
149
176
  This is useful for observing text fields while the user is typing.
150
177
 
151
- For instance, the following would submit the form whenever the
178
+ The UJS variant of this is the [`up-observe`](/up-observe) attribute.
179
+
180
+ \#\#\#\# Example
181
+
182
+ The following would submit the form whenever the
152
183
  text field value changes:
153
184
 
154
185
  up.observe('input[name=query]', { change: function(value, $input) {
@@ -176,7 +207,6 @@ up.form = (($) ->
176
207
  change: function(value, $input) { up.submit($input) }
177
208
  });
178
209
 
179
-
180
210
  @function up.observe
181
211
  @param {Element|jQuery|String} fieldOrSelector
182
212
  @param {Function(value, $field)|String} options.change
@@ -265,14 +295,125 @@ up.form = (($) ->
265
295
  # return destructor
266
296
  return clearTimer
267
297
 
298
+ resolveValidateTarget = ($field, options) ->
299
+ target = u.option(options.target, $field.attr('up-validate'))
300
+ if u.isBlank(target)
301
+ target ||= u.detect(config.validateTargets, (defaultTarget) ->
302
+ resolvedDefault = up.flow.resolveSelector(defaultTarget, options)
303
+ $field.closest(resolvedDefault).length
304
+ )
305
+ if u.isBlank(target)
306
+ error('Could not find default validation target for %o (tried ancestors %o)', $field, config.validateTargets)
307
+ unless u.isString(target)
308
+ target = u.createSelectorFromElement(target)
309
+ target
310
+
311
+ ###*
312
+ Performs a server-side validation of a form and update the form
313
+ with validation messages.
314
+
315
+ `up.validate` submits the given field's form with an additional `X-Up-Validate`
316
+ HTTP header. Upon seeing this header, the server is expected to validate (but not save)
317
+ the form submission and render a new copy of the form with validation errors.
318
+
319
+ The UJS variant of this is the [`[up-validate]`](/up-validate) selector.
320
+ See the documentation for [`[up-validate]`](/up-validate) for more information
321
+ on how server-side validation works in Up.js.
322
+
323
+ \#\#\#\# Example
324
+
325
+ up.validate('input[name=email]', { target: '.email-errors' })
326
+
327
+ @function up.validate
328
+ @param {String|Element|jQuery} fieldOrSelector
329
+ @param {String|Element|jQuery} [options.target]
330
+ @return {Promise}
331
+ A promise that is resolved when the server-side
332
+ validation is received and the form was updated.
333
+ ###
334
+ validate = (fieldOrSelector, options) ->
335
+ $field = $(fieldOrSelector)
336
+ options = u.options(options)
337
+ options.origin = $field
338
+ options.target = resolveValidateTarget($field, options)
339
+ options.failTarget = options.target
340
+ options.history = false
341
+ options.headers = u.option(options.headers, {})
342
+ # Make sure the X-Up-Validate header is present, so the server-side
343
+ # knows that it should not persist the form submission
344
+ options.validate = ($field.attr('name') || '__none__')
345
+ options = u.merge(options, up.motion.animateOptions(options, $field))
346
+ $form = $field.closest('form')
347
+ promise = up.submit($form, options)
348
+ promise
349
+
268
350
  ###*
269
- Submits the form through AJAX, searches the response for the selector
270
- given in `up-target` and [replaces](/up.replace) the selector content in the current page:
351
+ Forms with an `up-target` attribute are [submitted via AJAX](/up.submit)
352
+ instead of triggering a full page reload.
271
353
 
272
354
  <form method="post" action="/users" up-target=".main">
273
355
  ...
274
356
  </form>
275
357
 
358
+ The server response is searched for the selector given in `up-target`.
359
+ The selector content is then [replaced](/up.replace) in the current page.
360
+
361
+ The programmatic variant of this is the [`up.submit`](/up.submit) function.
362
+
363
+ \#\#\#\# Validation errors
364
+
365
+ When the server was unable to save the form due to invalid data,
366
+ it will usually re-render an updated copy of the form with
367
+ validation messages.
368
+
369
+ For Up.js to be able to pick up a validation failure,
370
+ the form must be re-rendered with a non-200 HTTP status code.
371
+ We recommend to use either 400 (bad request) or
372
+ 422 (unprocessable entity).
373
+
374
+ In Ruby on Rails, you can pass a
375
+ [`:status` option to `render`](http://guides.rubyonrails.org/layouts_and_rendering.html#the-status-option)
376
+ for this:
377
+
378
+ class UsersController < ApplicationController
379
+
380
+ def create
381
+ user_params = params[:user].permit(:email, :password)
382
+ @user = User.new(user_params)
383
+ if @user.save?
384
+ sign_in @user
385
+ else
386
+ render 'form', status: :bad_request
387
+ end
388
+ end
389
+
390
+ end
391
+
392
+ Note that you can also use the
393
+ [`up-validate`](/up-validate) attribute to perform server-side
394
+ validations while the user is completing fields.
395
+
396
+ \#\#\#\# Redirects
397
+
398
+ Up.js requires two additional response headers to detect redirects,
399
+ which are otherwise undetectable for an AJAX client.
400
+
401
+ When the form's action performs a redirect, the server should echo
402
+ the new request's URL as a response header `X-Up-Location`
403
+ and the request's HTTP method as `X-Up-Method`.
404
+
405
+ If you are using Up.js via the `upjs-rails` gem, these headers
406
+ are set automatically for every request.
407
+
408
+ \#\#\#\# Giving feedback while the form is processing
409
+
410
+ The `<form>` element will be assigned a CSS class `up-active` while
411
+ the submission is loading.
412
+
413
+ You can also [implement a spinner](/up.proxy/#spinners)
414
+ by [listening](/up.on) to the [`up:proxy:busy`](/up:proxy:busy)
415
+ and [`up:proxy:idle`](/up:proxy:idle) events.
416
+
276
417
  @selector form[up-target]
277
418
  @param {String} up-target
278
419
  The selector to [replace](/up.replace) if the form submission is successful (200 status code).
@@ -306,11 +447,160 @@ up.form = (($) ->
306
447
  event.preventDefault()
307
448
  submit($form)
308
449
 
450
+ ###*
451
+ When a form field with this attribute is changed,
452
+ the form is validated on the server and is updated with
453
+ validation messages.
454
+
455
+ The programmatic variant of this is the [`up.validate`](/up.validate) function.
456
+
457
+ \#\#\#\# Example
458
+
459
+ Let's look at a standard registration form that asks for an e-mail and password:
460
+
461
+ <form action="/users">
462
+
463
+ <label>
464
+ E-mail: <input type="text" name="email" />
465
+ </label>
466
+
467
+ <label>
468
+ Password: <input type="password" name="password" />
469
+ </label>
470
+
471
+ <button type="submit">Register</button>
472
+
473
+ </form>
474
+
475
+ When the user changes the `email` field, we want to validate that
476
+ the e-mail address is valid and still available. Also we want to
477
+ change the `password` field for the minimum required password length.
478
+ We can do this by giving both fields an `up-validate` attribute:
479
+
480
+ <form action="/users">
481
+
482
+ <label>
483
+ E-mail: <input type="text" name="email" up-validate />
484
+ </label>
485
+
486
+ <label>
487
+ Password: <input type="password" name="password" up-validate />
488
+ </label>
489
+
490
+ <button type="submit">Register</button>
491
+
492
+ </form>
493
+
494
+ Whenever a field with `up-validate` changes, the form is POSTed to
495
+ `/users` with an additional `X-Up-Validate` HTTP header.
496
+ Upon seeing this header, the server is expected to validate (but not save)
497
+ the form submission and render a new copy of the form with validation errors.
498
+
499
+ In Ruby on Rails the processing action should behave like this:
500
+
501
+ class UsersController < ApplicationController
502
+
503
+ # This action handles POST /users
504
+ def create
505
+ user_params = params[:user].permit(:email, :password)
506
+ @user = User.new(user_params)
507
+ if request.headers['X-Up-Validate']
508
+ @user.valid? # run validations, but don't save to the database
509
+ render 'form' # render form with error messages
510
+ elsif @user.save?
511
+ sign_in @user
512
+ else
513
+ render 'form', status: :bad_request
514
+ end
515
+ end
516
+
517
+ end
518
+
519
+ Note that if you're using the `upjs-rails` gem you can simply say `up.validate?`
520
+ instead of manually checking for `request.headers['X-Up-Validate']`.
521
+
522
+ The server now renders an updated copy of the form with eventual validation errors:
523
+
524
+ <form action="/users">
525
+
526
+ <label class="has-error">
527
+ E-mail: <input type="text" name="email" value="foo@bar.com" />
528
+ Has already been taken!
529
+ </label>
530
+
531
+ <button type="submit">Register</button>
532
+
533
+ </form>
534
+
535
+ The `<label>` around the e-mail field is now updated to have the `has-error`
536
+ class and display the validation message.
537
+
538
+ \#\#\#\# How validation results are displayed
539
+
540
+ Although the server will usually respond to a validation with a complete,
541
+ fresh copy of the form, Up.js will by default not update the entire form.
542
+ This is done in order to preserve volatile state such as the scroll position
543
+ of `<textarea>` elements.
544
+
545
+ By default Up.js looks for a `<fieldset>`, `<label>` or `<form>`
546
+ around the validating input field, or any element with an
547
+ `up-fieldset` attribute.
548
+ With the Bootstrap bindings, Up.js will also look
549
+ for a container with the `form-group` class.
550
+
551
+ You can change this default behavior by setting `up.config.validateTargets`:
552
+
553
+ // Always update the entire form containing the current field ("&")
554
+ up.config.validateTargets = ['form &']
555
+
556
+ You can also individually override what to update by setting the `up-validate`
557
+ attribute to a CSS selector:
558
+
559
+ <input type="text" name="email" up-validate=".email-errors">
560
+ <span class="email-errors"></span>
561
+
562
+
563
+ \#\#\#\# Updating dependent fields
564
+
565
+ The `[up-validate]` behavior is also a great way to partially update a form
566
+ when one fields depends on the value of another field.
567
+
568
+ Let's say you have a form with one `<select>` to pick a department (sales, engineering, ...)
569
+ and another `<select>` to pick an employeee from the selected department:
570
+
571
+ <form action="/contracts">
572
+ <select name="department">...</select> <!-- options for all departments -->
573
+ <select name="employeed">...</select> <!-- options for employees of selected department -->
574
+ </form>
575
+
576
+ The list of employees needs to be updated as the appartment changes:
577
+
578
+ <form action="/contracts">
579
+ <select name="department" up-validate="[name=employee]">...</select>
580
+ <select name="employee">...</select>
581
+ </form>
582
+
583
+ In order to update the `department` field in addition to the `employee` field, you could say
584
+ `up-validate="&, [name=employee]"`, or simply `up-validate="form"` to update the entire form.
585
+
586
+ @selector [up-validate]
587
+ @param {String} up-validate
588
+ The CSS selector to update with the server response.
589
+
590
+ This defaults to a fieldset or form group around the validating field.
591
+ ###
592
+ up.on 'change', '[up-validate]', (event, $field) ->
593
+ validate($field)
594
+
309
595
  ###*
310
596
  Observes this form field and runs the given script
311
597
  when its value changes. This is useful for observing text fields
312
598
  while the user is typing.
313
599
 
600
+ The programmatic variant of this is the [`up.observe`](/up.observe) function.
601
+
602
+ \#\#\#\# Example
603
+
314
604
  For instance, the following would submit the form whenever the
315
605
  text field value changes:
316
606
 
@@ -318,7 +608,7 @@ up.form = (($) ->
318
608
  <input type="query" up-observe="up.form.submit(this)">
319
609
  </form>
320
610
 
321
- The script given with `up-observe` runs with the following context:
611
+ The script given to `up-observe` runs with the following context:
322
612
 
323
613
  | Name | Type | Description |
324
614
  | -------- | --------- | ------------------------------------- |
@@ -326,11 +616,9 @@ up.form = (($) ->
326
616
  | `this` | `Element` | The form field |
327
617
  | `$field` | `jQuery` | The form field as a jQuery collection |
328
618
 
329
- See up.observe.
330
-
331
- @selector input[up-observe]
332
- The code to run when the field's value changes.
619
+ @selector [up-observe]
333
620
  @param {String} up-observe
621
+ The code to run when the field's value changes.
334
622
  ###
335
623
  up.compiler '[up-observe]', ($field) ->
336
624
  return observe($field)
@@ -343,10 +631,15 @@ up.form = (($) ->
343
631
  # $field.removeClass('up-active')
344
632
  # )
345
633
 
634
+ up.on 'up:framework:reset', reset
635
+
346
636
  submit: submit
347
637
  observe: observe
638
+ validate: validate
348
639
 
349
640
  )(jQuery)
350
641
 
351
642
  up.submit = up.form.submit
352
643
  up.observe = up.form.observe
644
+ up.validate = up.form.validate
645
+
@@ -18,7 +18,7 @@ up.history = (($) ->
18
18
 
19
19
  ###*
20
20
  @property up.history.config
21
- @param {Array<String>} [config.popTargets=['body']]
21
+ @param {Array} [config.popTargets=['body']]
22
22
  An array of CSS selectors to replace when the user goes
23
23
  back in history.
24
24
  @param {Boolean} [config.restoreScroll=true]
@@ -14,16 +14,16 @@ up.layout = (($) ->
14
14
  Configures the application layout.
15
15
 
16
16
  @property up.layout.config
17
- @param {Array<String>} [config.viewports]
17
+ @param {Array} [config.viewports]
18
18
  An array of CSS selectors that find viewports
19
19
  (containers that scroll their contents).
20
- @param {Array<String>} [config.fixedTop]
20
+ @param {Array} [config.fixedTop]
21
21
  An array of CSS selectors that find elements fixed to the
22
22
  top edge of the screen (using `position: fixed`).
23
- @param {Array<String>} [config.fixedBottom]
23
+ @param {Array} [config.fixedBottom]
24
24
  An array of CSS selectors that find elements fixed to the
25
25
  bottom edge of the screen (using `position: fixed`).
26
- @param {Array<String>} [config.anchoredRight]
26
+ @param {Array} [config.anchoredRight]
27
27
  An array of CSS selectors that find elements anchored to the
28
28
  right edge of the screen (using `position: fixed` or `position: absolute`).
29
29
  @param {Number} [config.duration]
@@ -140,12 +140,15 @@ up.link = (($) ->
140
140
  @param {Element|jQuery|String} [options.reveal]
141
141
  Whether to reveal the target element within its viewport before updating.
142
142
  @param {Boolean} [options.restoreScroll]
143
- If set to `true`, this will attempt to [`restore scroll positions`](/up.restoreScroll)
143
+ If set to `true`, this will attempt to [restore scroll positions](/up.restoreScroll)
144
144
  previously seen on the destination URL.
145
145
  @param {Boolean} [options.cache]
146
146
  Whether to force the use of a cached response (`true`)
147
147
  or never use the cache (`false`)
148
148
  or make an educated guess (`undefined`).
149
+ @param {Object} [options.headers={}]
150
+ An object of additional header key/value pairs to send along
151
+ with the request.
149
152
  ###
150
153
  follow = (linkOrSelector, options) ->
151
154
  $link = $(linkOrSelector)
@@ -159,6 +162,7 @@ up.link = (($) ->
159
162
  options.cache = u.option(options.cache, u.castedAttr($link, 'up-cache'))
160
163
  options.restoreScroll = u.option(options.restoreScroll, u.castedAttr($link, 'up-restore-scroll'))
161
164
  options.method = followMethod($link, options)
165
+ options.origin = u.option(options.origin, $link)
162
166
  options = u.merge(options, up.motion.animateOptions(options, $link))
163
167
 
164
168
  up.replace(selector, url, options)
@@ -371,7 +375,6 @@ up.link = (($) ->
371
375
  $area.removeAttr('up-expand')
372
376
  makeFollowable($area)
373
377
 
374
-
375
378
  ###*
376
379
  Marks up the current link to be followed *as fast as possible*.
377
380
  This is done by:
@@ -452,6 +452,7 @@ up.modal = (($) ->
452
452
  # The framework is reset between tests
453
453
  up.on 'up:framework:reset', reset
454
454
 
455
+ knife: eval(Knife?.point)
455
456
  visit: visit
456
457
  follow: follow
457
458
  open: -> up.error('up.modal.open no longer exists. Please use either up.modal.follow or up.modal.visit.')
@@ -75,7 +75,7 @@ up.proxy = (($) ->
75
75
  The number of milliseconds until a cache entry expires.
76
76
  Defaults to 5 minutes.
77
77
  @param {Number} [config.busyDelay=300]
78
- How long the proxy waits until emitting the `proxy:busy` [event](/up.bus).
78
+ How long the proxy waits until emitting the [`up:proxy:busy` event](/up:proxy:busy).
79
79
  Use this to prevent flickering of spinners.
80
80
  ###
81
81
  config = u.config
@@ -98,11 +98,23 @@ up.proxy = (($) ->
98
98
  key: cacheKey
99
99
  log: 'up.proxy'
100
100
 
101
+
101
102
  ###*
102
103
  @protected
103
104
  @function up.proxy.get
104
105
  ###
105
- get = cache.get
106
+ get = (request) ->
107
+ request = normalizeRequest(request)
108
+ candidates = [request]
109
+ unless request.selector is 'html'
110
+ requestForHtml = u.merge(request, selector: 'html')
111
+ candidates.push(requestForHtml)
112
+ unless request.selector is 'body'
113
+ requestForBody = u.merge(request, selector: 'body')
114
+ candidates.push(requestForBody)
115
+ for candidate in candidates
116
+ if response = cache.get(candidate)
117
+ return response
106
118
 
107
119
  ###*
108
120
  @protected
@@ -162,8 +174,8 @@ up.proxy = (($) ->
162
174
  are considered to be read-only.
163
175
 
164
176
  If a network connection is attempted, the proxy will emit
165
- a `proxy:load` event with the `request` as its argument.
166
- Once the response is received, a `proxy:receive` event will
177
+ a `up:proxy:load` event with the `request` as its argument.
178
+ Once the response is received, a `up:proxy:receive` event will
167
179
  be emitted.
168
180
 
169
181
  @function up.proxy.ajax
@@ -173,13 +185,16 @@ up.proxy = (($) ->
173
185
  @param {Boolean} [request.cache]
174
186
  Whether to use a cached response, if available.
175
187
  If set to `false` a network connection will always be attempted.
188
+ @param {Object} [request.headers={}]
189
+ An object of additional header key/value pairs to send along
190
+ with the request.
176
191
  ###
177
192
  ajax = (options) ->
178
193
 
179
194
  forceCache = (options.cache == true)
180
195
  ignoreCache = (options.cache == false)
181
196
 
182
- request = u.only(options, 'url', 'method', 'data', 'selector', '_normalized')
197
+ request = u.only(options, 'url', 'method', 'data', 'selector', 'headers', '_normalized')
183
198
 
184
199
  pending = true
185
200
 
@@ -204,11 +219,11 @@ up.proxy = (($) ->
204
219
  # following case:
205
220
  #
206
221
  # - User starts preloading a request.
207
- # This triggers *no* `proxy:busy`.
222
+ # This triggers *no* `up:proxy:busy`.
208
223
  # - User starts loading the request (without preloading).
209
- # This triggers `proxy:busy`.
224
+ # This triggers `up:proxy:busy`.
210
225
  # - The request finishes.
211
- # This triggers `proxy:idle`.
226
+ # This triggers `up:proxy:idle`.
212
227
  loadStarted()
213
228
  promise.always(loadEnded)
214
229
 
@@ -220,7 +235,7 @@ up.proxy = (($) ->
220
235
  Returns `true` if the proxy is not currently waiting
221
236
  for a request to finish. Returns `false` otherwise.
222
237
 
223
- The proxy will also emit an `proxy:idle` [event](/up.bus) if it
238
+ The proxy will also emit an [`up:proxy:idle` event](/up:proxy:idle) if it
224
239
  used to busy, but is now idle.
225
240
 
226
241
  @function up.proxy.idle
@@ -233,8 +248,8 @@ up.proxy = (($) ->
233
248
  Returns `true` if the proxy is currently waiting
234
249
  for a request to finish. Returns `false` otherwise.
235
250
 
236
- The proxy will also emit an `proxy:busy` [event](/up.bus) if it
237
- used to idle, but is now busy.
251
+ The proxy will also emit an [`up:proxy:busy` event](/up:proxy:busy) if it
252
+ used to be idle, but is now busy.
238
253
 
239
254
  @function up.proxy.busy
240
255
  @return {Boolean} Whether the proxy is busy
@@ -285,7 +300,7 @@ up.proxy = (($) ->
285
300
  This event is [emitted]/(up.emit) when [AJAX requests](/up.proxy.ajax)
286
301
  have [taken long to finish](/up:proxy:busy), but have finished now.
287
302
 
288
- @event up:proxy:busy
303
+ @event up:proxy:idle
289
304
  ###
290
305
 
291
306
  load = (request) ->