upjs-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-version +2 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +47 -0
  6. data/Rakefile +1 -0
  7. data/bin/doc-server +3 -0
  8. data/bin/install-ydoc +4 -0
  9. data/design/animation-ghosting.txt +72 -0
  10. data/design/design.txt +34 -0
  11. data/design/draft.html.erb +48 -0
  12. data/design/draft.rb +9 -0
  13. data/lib/assets/javascripts/up/browser.js.coffee +20 -0
  14. data/lib/assets/javascripts/up/bus.js.coffee +54 -0
  15. data/lib/assets/javascripts/up/flow.js.coffee +160 -0
  16. data/lib/assets/javascripts/up/form.js.coffee +162 -0
  17. data/lib/assets/javascripts/up/history.js.coffee +33 -0
  18. data/lib/assets/javascripts/up/index.js +13 -0
  19. data/lib/assets/javascripts/up/link.js.coffee +95 -0
  20. data/lib/assets/javascripts/up/magic.js.coffee +79 -0
  21. data/lib/assets/javascripts/up/modal.js.coffee +51 -0
  22. data/lib/assets/javascripts/up/module.js.coffee +4 -0
  23. data/lib/assets/javascripts/up/motion.js.coffee +276 -0
  24. data/lib/assets/javascripts/up/navigation.js.coffee +63 -0
  25. data/lib/assets/javascripts/up/popup.js.coffee +143 -0
  26. data/lib/assets/javascripts/up/util.js.coffee +287 -0
  27. data/lib/assets/stylesheets/up/follow.css.sass +2 -0
  28. data/lib/assets/stylesheets/up/index.css +3 -0
  29. data/lib/assets/stylesheets/up/modal.css.sass +54 -0
  30. data/lib/assets/stylesheets/up/popup.css.sass +9 -0
  31. data/lib/upjs/rails/engine.rb +6 -0
  32. data/lib/upjs/rails/redirection.rb +26 -0
  33. data/lib/upjs/rails/request.rb +13 -0
  34. data/lib/upjs/rails/version.rb +5 -0
  35. data/lib/upjs-rails.rb +7 -0
  36. data/spec_app/.firefox-version +1 -0
  37. data/spec_app/.gitignore +17 -0
  38. data/spec_app/Gemfile +24 -0
  39. data/spec_app/Gemfile.lock +239 -0
  40. data/spec_app/README.rdoc +28 -0
  41. data/spec_app/Rakefile +6 -0
  42. data/spec_app/app/assets/images/.keep +0 -0
  43. data/spec_app/app/assets/javascripts/application.js +16 -0
  44. data/spec_app/app/assets/stylesheets/application.css +15 -0
  45. data/spec_app/app/assets/stylesheets/blocks/card.css.sass +11 -0
  46. data/spec_app/app/assets/stylesheets/blocks/controls.css.sass +7 -0
  47. data/spec_app/app/assets/stylesheets/blocks/field_with_errors.css.sass +5 -0
  48. data/spec_app/app/assets/stylesheets/blocks/menu.css.sass +13 -0
  49. data/spec_app/app/assets/stylesheets/blocks/panel.css.sass +8 -0
  50. data/spec_app/app/controllers/application_controller.rb +14 -0
  51. data/spec_app/app/controllers/cards_controller.rb +51 -0
  52. data/spec_app/app/controllers/concerns/.keep +0 -0
  53. data/spec_app/app/controllers/pages_controller.rb +6 -0
  54. data/spec_app/app/helpers/application_helper.rb +2 -0
  55. data/spec_app/app/mailers/.keep +0 -0
  56. data/spec_app/app/models/card.rb +6 -0
  57. data/spec_app/app/models/concerns/.keep +0 -0
  58. data/spec_app/app/models/tests.rb +27 -0
  59. data/spec_app/app/views/cards/_side.html.haml +4 -0
  60. data/spec_app/app/views/cards/index.html.haml +10 -0
  61. data/spec_app/app/views/cards/new.html.haml +15 -0
  62. data/spec_app/app/views/cards/show.html.haml +11 -0
  63. data/spec_app/app/views/layouts/application.html.erb +12 -0
  64. data/spec_app/app/views/pages/home.html.haml +5 -0
  65. data/spec_app/bin/bundle +3 -0
  66. data/spec_app/bin/rails +8 -0
  67. data/spec_app/bin/rake +8 -0
  68. data/spec_app/bin/setup +29 -0
  69. data/spec_app/bin/spring +18 -0
  70. data/spec_app/config/application.rb +26 -0
  71. data/spec_app/config/boot.rb +3 -0
  72. data/spec_app/config/cucumber.yml +8 -0
  73. data/spec_app/config/database.yml +25 -0
  74. data/spec_app/config/environment.rb +5 -0
  75. data/spec_app/config/environments/development.rb +41 -0
  76. data/spec_app/config/environments/production.rb +79 -0
  77. data/spec_app/config/environments/test.rb +42 -0
  78. data/spec_app/config/initializers/assets.rb +11 -0
  79. data/spec_app/config/initializers/backtrace_silencers.rb +7 -0
  80. data/spec_app/config/initializers/cookies_serializer.rb +3 -0
  81. data/spec_app/config/initializers/filter_parameter_logging.rb +4 -0
  82. data/spec_app/config/initializers/inflections.rb +16 -0
  83. data/spec_app/config/initializers/mime_types.rb +4 -0
  84. data/spec_app/config/initializers/session_store.rb +3 -0
  85. data/spec_app/config/initializers/wrap_parameters.rb +14 -0
  86. data/spec_app/config/locales/en.yml +23 -0
  87. data/spec_app/config/routes.rb +6 -0
  88. data/spec_app/config/secrets.yml +22 -0
  89. data/spec_app/config.ru +4 -0
  90. data/spec_app/db/migrate/20141225125143_create_card.rb +9 -0
  91. data/spec_app/db/schema.rb +23 -0
  92. data/spec_app/db/seeds.rb +7 -0
  93. data/spec_app/features/history.feature +30 -0
  94. data/spec_app/features/navigation.feature +9 -0
  95. data/spec_app/features/step_definitions/factory_steps.rb +5 -0
  96. data/spec_app/features/step_definitions/navigation_steps.rb +29 -0
  97. data/spec_app/features/step_definitions/utility_steps.rb +15 -0
  98. data/spec_app/features/support/env.rb +73 -0
  99. data/spec_app/features/support/find_by_anything.rb +19 -0
  100. data/spec_app/features/support/paths.rb +51 -0
  101. data/spec_app/lib/assets/.keep +0 -0
  102. data/spec_app/lib/tasks/.keep +0 -0
  103. data/spec_app/lib/tasks/cucumber.rake +65 -0
  104. data/spec_app/log/.keep +0 -0
  105. data/spec_app/public/404.html +67 -0
  106. data/spec_app/public/422.html +67 -0
  107. data/spec_app/public/500.html +66 -0
  108. data/spec_app/public/favicon.ico +0 -0
  109. data/spec_app/public/robots.txt +5 -0
  110. data/spec_app/script/cucumber +10 -0
  111. data/spec_app/test/controllers/.keep +0 -0
  112. data/spec_app/test/fixtures/.keep +0 -0
  113. data/spec_app/test/helpers/.keep +0 -0
  114. data/spec_app/test/integration/.keep +0 -0
  115. data/spec_app/test/mailers/.keep +0 -0
  116. data/spec_app/test/models/.keep +0 -0
  117. data/spec_app/test/test_helper.rb +10 -0
  118. data/spec_app/vendor/assets/javascripts/.keep +0 -0
  119. data/spec_app/vendor/assets/stylesheets/.keep +0 -0
  120. data/upjs-rails.gemspec +24 -0
  121. metadata +207 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a95bff232406d1e7ca5ca6919fad7af7e7b110ac
4
+ data.tar.gz: d30a63428b8ec0d20e302e27d53c96c5e540cf80
5
+ SHA512:
6
+ metadata.gz: 0fe59a20209d977d270cef3a1277a7d0a288c8102d68290d0d0bd0654b9c7a8444a9681a81b028d3cf180486e9ac803558ebc7e6b04b3435735fcf56c9573dda
7
+ data.tar.gz: 9c51d41fb1cbbc28adf735ae1c72e6036f83a7ff65cdbd9b7d1edc678465c21dab68b9eb085222bd24eaad8e8acbcd2b6a77e12583f31532c13140ffc5284435
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .idea
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.ruby-version ADDED
@@ -0,0 +1,2 @@
1
+ 2.1.2
2
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Henning Koch
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # Up + Rails bindings (WIP)
2
+
3
+ Up.js is a solution for Rails apps that need fast-responding UI
4
+ but don't want to pay the Javascript MVC complexity tax.
5
+
6
+ ## Design manifesto
7
+
8
+ ### Client-side code is a complexity driver
9
+
10
+ ### Server-side code should stay the same
11
+ - We like the simplicity of classic Rails development
12
+ - It should not require extra controller actions to update a page part via AJAX
13
+
14
+ ### Batteries included
15
+ - We will ship a basic implementation for the most established UI patterns like navigation bars, infinite scrolling, drop-down menus, modals
16
+ - We will split this out into a plugin architecture eventually, but not now
17
+
18
+ ### Ruby on Rails first
19
+ - We will leverage the assumptions that Rails is underneath
20
+ - Other frameworks once we’re happy with Rails
21
+
22
+ ### Not for ambitious UIs
23
+ - We don’t want to compromise ease of use for simple patterns by providing a million hooks and options
24
+ - Limits in configurability
25
+ - You can always roll your own code
26
+ - Probably the wrong choice if you want to create something very ambitious
27
+
28
+ ### (Sort of) Plays nice with existing JS code
29
+ - If you're ready to go into our event binding
30
+
31
+ ### URLs are important
32
+ - Every page has an URL
33
+ - Works nice with Google
34
+ Works with browsers that don’t speak Up.js (e. g. IE9 doesn’t speak pushState)
35
+
36
+ ### Be small
37
+
38
+ ### Few dependencies
39
+ - jQuery
40
+
41
+ ### Convention over configuration
42
+
43
+ ### Interface: Both UJS and programmatic
44
+
45
+
46
+
47
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/doc-server ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ yuidoc -c doc/yuidoc.json --server 3001
data/bin/install-ydoc ADDED
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+
3
+ npm install -g yuidocjs
4
+
@@ -0,0 +1,72 @@
1
+ animate($element)
2
+
3
+ gar nix, ghosting macht NUR transition
4
+
5
+ ($old und $new muss anders gehandelt werden)
6
+
7
+ transition($old, $new)
8
+
9
+ 1. $old.css(display: none)
10
+
11
+ 2. $new in den DOM setzen
12
+
13
+ 3. $new ausmessen
14
+
15
+ 4. ghost von $new erstellen
16
+ DOM kopieren
17
+ mit position: fixed
18
+ mit ausmessen des originals
19
+ left, top setzen
20
+ right, bottom löschen
21
+ width, height setzen
22
+ margin löschen
23
+
24
+ 5. $new.css(display: none)
25
+
26
+ 6. $old.css(display: previousDisplay)
27
+
28
+ 7. ghost von $old erstellen
29
+
30
+ 8. $old.css(opacity: 0) // damit es den platz wegnimmt
31
+
32
+ 9. transition($oldGhost, $newGhost)
33
+
34
+ 10. $old löschen
35
+ $oldGhost löschen
36
+ $newGhost löschen
37
+
38
+ 11. $new.css(display: previousDisplay)
39
+
40
+
41
+ was löst das, was nicht?
42
+ ========================
43
+
44
+ - die teile müssen nicht mehr absolut gestellt werden
45
+ - old muss immer noch absolut gehen :(
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+ 1. Ich kopiere $old zu $ghost
54
+
55
+ 2. Ich setze die opacity von $old auf 0
56
+
57
+ 3. Ich setze $ghost auf die Größe von $old
58
+ mit position: fixed
59
+ mit ausmessen des originals
60
+ left, top setzen
61
+ right, bottom löschen
62
+ width, height setzen
63
+ margin löschen
64
+
65
+ ???????????
66
+
67
+ 4. ich gebe $ghost an die animation
68
+
69
+ 5. nach der animation
70
+ setze ich $opacity von $element auf 1
71
+ lösche ich $ghost
72
+
data/design/design.txt ADDED
@@ -0,0 +1,34 @@
1
+ Gedanke 1
2
+ ---------
3
+ - Immer die ganze Seite rendern ist langsam
4
+ - Optimierung nicht möglich: Layout überspringen wenn up-Request
5
+ - Optimierung nicht möglich: Manche Bereiche nicht rendern wenn up-Request
6
+ - Optimierung nicht möglich: Manche Bereiche nur für bestimmte X-Up-Selectors rendern
7
+ - Das alles nur, um beim Back-Button sofort das HTML anzeigen zu können?
8
+ - Sogar Turbolinks macht das nur Opt-In
9
+ - Ich kann genauso gut bei popstate den aktuellen State replacen
10
+ - Später: Opportunistisch cachen. Wann immer ich einen vollen Page-Load (mit/ohne Layout) sehe => cachen
11
+
12
+ Gedanke 2
13
+ ---------
14
+ - Das mit dem up.app und up.page checkt kein Mensch
15
+ - In der Praxis wird es so sein, dass wichtige page-Handler nicht mehr in der Seite stehen
16
+ - Ich brauche auch noch Code, um <script>-Tags auszuführen
17
+ - Die Unterscheidung mache ich ja nur, um installierte Callbacks wieder wegzuräumen
18
+ - Was ich eigentlich bräuchte, wäre ein Weg, installierte Callbacks geregelt lozuwerden
19
+
20
+ z. B.
21
+
22
+ up.awaken('.note_form', function($element) {
23
+
24
+ var timer = setTimeout(...)
25
+
26
+ function uninstall() {
27
+ clearInterval(timer);
28
+ }
29
+
30
+ return uninstall;
31
+
32
+ });
33
+
34
+ - Vor dem Ersetzen eines Elements kann ich dann die uninstall-Funktion aufrufen.
@@ -0,0 +1,48 @@
1
+ GUT:
2
+
3
+ <a href="/terms" up-modal=".main" up-modal-style="right">
4
+
5
+ <a href="/terms" up-on-click="up.modal.open(this, '.main', { style: 'right' })">
6
+
7
+ <a href="/terms" onclick="up.modal.open(this, '.main', { style: 'right' }); return false">
8
+
9
+
10
+ WEIRD:
11
+
12
+ <a href="/terms" up-target=".main" up-presentation="modal/right">
13
+
14
+ <a href="/terms" up-target=".main" up-modal up-modal-presentation="right">
15
+
16
+ <a href="/terms" up-target="@modal(.main)" up-presentation="modal/right">
17
+
18
+
19
+
20
+ <a href="/sites" up-popup=".main" up-popup-anchor="bottom" up-history="push">
21
+
22
+
23
+ <!-- cards/edit.html.erb -->
24
+
25
+ <%= form_for @card do |form| %>
26
+
27
+ = form.label :title
28
+ = form.text_field :title
29
+
30
+ <div class="modifiers">
31
+ ....
32
+ <%= link_to 'Edit modifiers', edit_card_meta_path(@card), 'up-modal' => '.meta_form' %>
33
+ </div>
34
+
35
+ <% end %>
36
+
37
+
38
+ <!-- cards/meta/edit.html.erb -->
39
+
40
+ <%= form_for @card, html: { class: 'meta_form', 'up-target' => '.modifiers' } do |form| %>
41
+
42
+ ...
43
+
44
+ <% end %>
45
+
46
+
47
+
48
+ <a href="close" up-close-modal>X</a>
data/design/draft.rb ADDED
@@ -0,0 +1,9 @@
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+ <a href="/terms" up-modal=".main">
@@ -0,0 +1,20 @@
1
+ up.browser = (->
2
+
3
+ # safari = false
4
+ #
5
+ # detect = ->
6
+ # agent = navigator.userAgent
7
+ # agentHas = (substring) -> agent.indexOf(substring) >= 1
8
+ # safari = agentHas('Safari') && !agentHas('Chrome')
9
+ #
10
+ # transitionEndEvent = ->
11
+ # if safari
12
+ # 'webkitTransitionEnd'
13
+ # else
14
+ # 'transitionend'
15
+ #
16
+ # detect()
17
+ #
18
+ # transitionEndEvent: transitionEndEvent
19
+
20
+ )()
@@ -0,0 +1,54 @@
1
+ ###*
2
+ Event bus for up-related events:
3
+
4
+ - `app:ready`
5
+ - `fragment:ready` with arguments `($fragment)`
6
+ - `fragment:destroy` with arguments `($fragment)`
7
+
8
+ TODO: This might eventually be rolled into regular document events.
9
+
10
+ @class up.bus
11
+ ###
12
+ up.bus = (->
13
+
14
+ callbacksByEvent = {}
15
+
16
+ callbacksFor = (event) ->
17
+ callbacksByEvent[event] ||= []
18
+
19
+ ###*
20
+ Registers an event handler to be called when the given
21
+ event is triggered.
22
+
23
+ @method up.bus.listen
24
+ @param {String} eventName
25
+ The event name to match.
26
+ @param {Function} handler
27
+ The event handler to be called with the event arguments.
28
+ ###
29
+ # We cannot call this function "on" because Coffeescript
30
+ # https://makandracards.com/makandra/29733-english-words-that-you-cannot-use-in-coffeescript
31
+ listen = (eventName, handler) ->
32
+ callbacksFor(eventName).push(handler)
33
+
34
+ ###*
35
+ Triggers an event.
36
+
37
+ @method up.bus.emit
38
+ @param {String} eventName
39
+ The name of the event.
40
+ @param {Anything...} args
41
+ The arguments that describe the event.
42
+ ###
43
+ emit = (eventName, args...) ->
44
+ console.log("bus emitting", eventName, args)
45
+ callbacks = callbacksFor(eventName)
46
+ up.util.each(callbacks, (callback) ->
47
+ callback(args...)
48
+ )
49
+
50
+ return (
51
+ on: listen
52
+ emit: emit
53
+ )
54
+ )()
@@ -0,0 +1,160 @@
1
+ ###*
2
+ Page flow.
3
+
4
+ @class up.flow
5
+ ###
6
+ up.flow = (->
7
+
8
+ setSource = (element, sourceUrl) ->
9
+ $element = $(element)
10
+ sourceUrl = up.util.normalizeUrl(sourceUrl) if up.util.isPresent(sourceUrl)
11
+ $element.attr("up-source", sourceUrl)
12
+
13
+ source = (element) ->
14
+ $element = $(element).closest("[up-source]")
15
+ $element.attr("up-source") || location.href
16
+
17
+ ###*
18
+ Replaces elements on the current page with corresponding elements
19
+ from a new page fetched from the server.
20
+
21
+ The current and new elements must have the same CSS selector.
22
+
23
+ @method up.replace
24
+ @param {String|Element|jQuery} selectorOrElement
25
+ The CSS selector to update. You can also pass a DOM element or jQuery element
26
+ here, in which case a selector will be inferred from the element's class and ID.
27
+ @param {String} url
28
+ The URL to fetch from the server.
29
+ @param {String} [options.history.url=url]
30
+ An alternative URL to use for the browser's location bar and history.
31
+ @param {String} [options.history.method='push']
32
+ @param {String} [options.transition]
33
+ @param {String|Boolean} [options.source]
34
+ ###
35
+ replace = (selectorOrElement, url, options) ->
36
+
37
+ selector = if up.util.presence(selectorOrElement)
38
+ selectorOrElement
39
+ else
40
+ up.util.createSelectorFromElement($(selectorOrElement))
41
+
42
+ options = up.util.options(options, history: { url: url })
43
+
44
+ if up.util.isMissing(options.source) || options.source == true
45
+ options.source = url
46
+
47
+ up.util.get(url, selector: selector)
48
+ .done (html) -> implant(selector, html, options)
49
+ .fail(up.util.error)
50
+
51
+ ###*
52
+ @method up.flow.implant
53
+ @protected
54
+ @param {String} selector
55
+ @param {String} html
56
+ @param {String} [options.source]
57
+ @param {String} [options.history.url]
58
+ @param {String} [options.history.method='push']
59
+ @param {String} [options.transition]
60
+ ###
61
+ implant = (selector, html, options) ->
62
+
63
+ options = up.util.options(options, history: { method: 'push' })
64
+ # jQuery cannot construct transient elements that contain <html> or <body> tags,
65
+ # so we're using the native browser API to grep through the HTML
66
+ htmlElement = up.util.createElementFromHtml(html)
67
+
68
+ for step in implantSteps(selector, options)
69
+ $old = $(step.selector)
70
+ if fragment = htmlElement.querySelector(step.selector)
71
+ $new = $(fragment)
72
+ swapElements $old, $new, step.transition, ->
73
+ options.insert?($new)
74
+ title = htmlElement.querySelector("title")?.textContent # todo: extract title from header
75
+ if options.history.url
76
+ # alert(options.history)
77
+ # alert(options.history.url)
78
+ document.title = title if title
79
+ up.history[options.history.method](options.history.url)
80
+ # Remember where the element came from so we can make
81
+ # smaller page loads in the future (does this even make sense?).
82
+ setSource($new, options.source || history.url)
83
+ autofocus($new)
84
+
85
+ else
86
+ up.util.error("Could not find selector (#{step.selector}) in response (#{html})")
87
+
88
+ swapElements = ($old, $new, transitionName, afterInsert) ->
89
+ if up.util.isGiven(transitionName)
90
+ if $old.is('body') && transitionName != 'none'
91
+ up.util.error('Cannot apply transitions to body-elements', transitionName)
92
+ $new.insertAfter($old)
93
+ # Make sure that any element enhancements happen BEFORE we morph
94
+ # through the transition.
95
+ afterInsert()
96
+ up.bus.emit('fragment:ready', $new) # this should happen before the transition, so transitions see .up-current classes
97
+ up.morph($old, $new, transitionName).then -> destroy($old)
98
+ else
99
+ up.bus.emit('fragment:destroy', old)
100
+ $old.replaceWith($new)
101
+ up.bus.emit('fragment:ready', $new)
102
+ afterInsert()
103
+
104
+
105
+ implantSteps = (selector, options) ->
106
+ transitionString = options.transition || options.animation || 'none'
107
+ comma = /\ *,\ */
108
+ disjunction = selector.split(comma)
109
+ transitions = transitionString.split(comma) if up.util.isPresent(transitionString)
110
+ for selectorAtom, i in disjunction
111
+ transition = transitions[i] || up.util.last(transitions)
112
+ selector: selectorAtom
113
+ transition: transition
114
+
115
+ autofocus = ($element) ->
116
+ $control = $element.find('[autofocus]:last')
117
+ if $control.length && $control.get(0) != document.activeElement
118
+ $control.focus()
119
+
120
+ ###*
121
+ Destroys the given element or selector.
122
+ Takes care that all destructors, if any, are called.
123
+
124
+ @method up.destroy
125
+ @param {String|Element|jQuery} selectorOrElement
126
+ ###
127
+ destroy = (selectorOrElement, options) ->
128
+ $element = $(selectorOrElement)
129
+ options = up.util.options(options, animation: 'none')
130
+ $element.addClass('up-destroying')
131
+ up.bus.emit('fragment:destroy', $element)
132
+ up.motion.animate($element, options.animation).then ->
133
+ $element.remove()
134
+
135
+ ###*
136
+ Replaces the given selector or element with a fresh copy
137
+ fetched from the server.
138
+
139
+ @method up.reload
140
+ @param {String|Element|jQuery} selectorOrElement
141
+ ###
142
+ reload = (selectorOrElement) ->
143
+ sourceUrl = source(selectorOrElement)
144
+ replace(selectorOrElement, sourceUrl)
145
+
146
+
147
+ up.bus.on('app:ready', ->
148
+ setSource(document.body, location.href)
149
+ )
150
+
151
+ replace: replace
152
+ reload: reload
153
+ destroy: destroy
154
+ implant: implant
155
+
156
+ )()
157
+
158
+ up.replace = up.flow.replace
159
+ up.reload = up.flow.reload
160
+ up.destroy = up.flow.destroy
@@ -0,0 +1,162 @@
1
+ ###*
2
+ Form handling
3
+ @class up.form
4
+ ###
5
+ up.form = (->
6
+
7
+ ###*
8
+ Submits a form using the Up.js flow.
9
+
10
+ @method up.submit
11
+ @param {Element|jQuery|String} formOrSelector
12
+ A reference or selector for the form to submit.
13
+ @param {String} [options.target]
14
+ @param {String} [options.failTarget]
15
+ @param {Boolean} [options.history=true]
16
+ Successful form submissions will add a history entry and change the browser's
17
+ location bar if the form either uses the `GET` method or the response redirected
18
+ to another page (this requires the `upjs-rails` gem).
19
+ If want to prevent history changes in any case, set this to `false`.
20
+ @param {String} [options.transition]
21
+ @param {String} [options.failTransition]
22
+ @return {Promise}
23
+ A promise for the AJAX response
24
+ @example
25
+ up.submit('form')
26
+ @example
27
+ <form method="POST" action="/users" up-target=".main">
28
+ ...
29
+ </form>
30
+
31
+ ###
32
+ submit = (formOrSelector, options) ->
33
+
34
+ options = up.util.options(options)
35
+ $form = $(formOrSelector)
36
+ successSelector = options.target || $form.attr('up-target') || 'body'
37
+ failureSelector = options.failTarget || $form.attr('up-fail-target') || up.util.createSelectorFromElement($form)
38
+ pushHistory = options.history != false && $form.attr('up-history') != 'false'
39
+ successTransition = options.transition || $form.attr('up-transition')
40
+ failureTransition = options.failTransition || $form.attr('up-fail-transition')
41
+ $form.addClass('up-active')
42
+
43
+ request = {
44
+ url: $form.attr('action') || location.href
45
+ type: $form.attr('method')?.toUpperCase() || 'POST',
46
+ data: $form.serialize(),
47
+ selector: successSelector
48
+ }
49
+
50
+ successUrl = (xhr) ->
51
+ if pushHistory
52
+ if redirectLocation = xhr.getResponseHeader('X-Up-Previous-Redirect-Location')
53
+ redirectLocation
54
+ else if request.type == 'GET'
55
+ request.url + '?' + request.data
56
+ else
57
+ null
58
+
59
+ up.util.ajax(request)
60
+ .always ->
61
+ $form.removeClass('up-active')
62
+ .done (html, textStatus, xhr) ->
63
+ up.flow.implant(successSelector, html,
64
+ history: { url: successUrl(xhr) },
65
+ transition: successTransition
66
+ )
67
+ .fail (xhr, textStatus, errorThrown) ->
68
+ html = xhr.responseText
69
+ up.flow.implant(failureSelector, html,
70
+ transition: failureTransition
71
+ )
72
+
73
+ ###*
74
+ Observes an input field by periodic polling its value.
75
+ Executes code when the value changes.
76
+
77
+ This is useful for observing text fields while the user is typing,
78
+ since browsers will only fire a `change` event once the user
79
+ blurs the text field.
80
+
81
+ @method up.observe
82
+ @param {Element|jQuery|String} fieldOrSelector
83
+ @param {Function|String} options.change
84
+ The callback to execute when the field's value changes.
85
+ If given as a function, it must take two arguments (`value`, `$field`).
86
+ If given as a string, it will be evaled as Javascript code in a context where
87
+ (`value`, `$field`) are set.
88
+ @param {Number} [options.frequency=500]
89
+ @example
90
+ up.observe('form', { change: function(value, $form) {
91
+ up.submit($form)
92
+ } });
93
+ @example
94
+ <form method="GET" action="/search">
95
+ <input type="query" up-observe="up.form.submit(this)">
96
+ </form>
97
+ ###
98
+ observe = (fieldOrSelector, options) ->
99
+
100
+ $field = $(fieldOrSelector)
101
+ options = up.util.options(options, frequency: 500)
102
+ knownValue = null
103
+ timer = null
104
+ callback = null
105
+ if codeOnChange = $field.attr('up-observe')
106
+ callback = (value, $field) ->
107
+ eval(codeOnChange)
108
+ else if options.change
109
+ callback = options.change
110
+ else
111
+ up.util.error('observe: No change callback given')
112
+
113
+ check = ->
114
+ value = $field.val()
115
+ skipCallback = _.isNull(knownValue) # don't run the callback for the check during initialization
116
+ if knownValue != value
117
+ knownValue = value
118
+ callback.apply($field.get(0), [value, $field]) unless skipCallback
119
+
120
+ resetTimer = ->
121
+ if timer
122
+ clearTimer()
123
+ startTimer()
124
+
125
+ clearTimer = ->
126
+ clearInterval(timer)
127
+ timer = null
128
+
129
+ startTimer = ->
130
+ timer = setInterval(check, options.frequency)
131
+
132
+ # reset counter after user interaction
133
+ $field.bind "keyup click mousemove", resetTimer # mousemove is for selects
134
+
135
+ check()
136
+ startTimer()
137
+
138
+ # return destructor
139
+ return clearTimer
140
+
141
+ up.on 'submit', 'form[up-target]', (event, $form) ->
142
+ event.preventDefault()
143
+ submit($form)
144
+
145
+ up.awaken '[up-observe]', ($field) ->
146
+ return observe($field)
147
+
148
+ # up.awaken '[up-autosubmit]', ($field) ->
149
+ # return observe($field, change: ->
150
+ # $form = $field.closest('form')
151
+ # $field.addClass('up-active')
152
+ # up.submit($form).always ->
153
+ # $field.removeClass('up-active')
154
+ # )
155
+
156
+ submit: submit
157
+ observe: observe
158
+
159
+ )()
160
+
161
+ up.submit = up.form.submit
162
+ up.observe = up.form.observe