upjs-rails 0.3.3 → 0.4.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.
@@ -82,10 +82,10 @@ up.motion = (->
82
82
  else if u.isHash(animation)
83
83
  u.cssAnimate($element, animation, options)
84
84
  else
85
- u.error("Unknown animation type", animation)
85
+ u.error("Unknown animation type %o", animation)
86
86
 
87
87
  findAnimation = (name) ->
88
- animations[name] or u.error("Unknown animation", animation)
88
+ animations[name] or u.error("Unknown animation %o", animation)
89
89
 
90
90
  GHOSTING_PROMISE_KEY = 'up-ghosting-promise'
91
91
 
@@ -134,14 +134,14 @@ up.motion = (->
134
134
 
135
135
  finishGhosting = ($element) ->
136
136
  if existingGhosting = $element.data(GHOSTING_PROMISE_KEY)
137
- console.log("EXISTING", existingGhosting)
137
+ u.debug('Canceling existing ghosting on %o', $element)
138
138
  existingGhosting.resolve?()
139
139
 
140
140
  assertIsDeferred = (object, origin) ->
141
141
  if u.isDeferred(object)
142
142
  object
143
143
  else
144
- u.error("Did not return a promise with .then and .resolve methods: ", origin)
144
+ u.error("Did not return a promise with .then and .resolve methods: %o", origin)
145
145
 
146
146
  ###*
147
147
  Performs a transition between two elements.
@@ -178,8 +178,10 @@ up.motion = (->
178
178
  $new = $(target)
179
179
  finish($old)
180
180
  finish($new)
181
- transition = u.presence(transitionOrName, u.isFunction) || transitions[transitionOrName]
182
- if transition
181
+ if transitionOrName == 'none'
182
+ # don't create ghosts if we aren't really transitioning
183
+ none()
184
+ else if transition = u.presence(transitionOrName, u.isFunction) || transitions[transitionOrName]
183
185
  withGhosts $old, $new, ($oldGhost, $newGhost) ->
184
186
  assertIsDeferred(transition($oldGhost, $newGhost, options), transitionOrName)
185
187
  else if animation = animations[transitionOrName]
@@ -194,7 +196,7 @@ up.motion = (->
194
196
  )
195
197
  morph($old, $new, transition, options)
196
198
  else
197
- u.error("Unknown transition: #{transitionOrName}")
199
+ u.error("Unknown transition %o", transitionOrName)
198
200
  else
199
201
  # Skip ghosting and all the other stuff that can go wrong
200
202
  # in ancient browsers
@@ -29,7 +29,7 @@ up.navigation = (->
29
29
 
30
30
  CLASS_ACTIVE = 'up-active'
31
31
  CLASS_CURRENT = 'up-current'
32
- SELECTOR_SECTION = 'a[href], a[up-target], [up-follow], [up-modal], [up-popup]'
32
+ SELECTOR_SECTION = 'a[href], a[up-target], [up-follow], [up-modal], [up-popup], [up-source]'
33
33
  SELECTOR_ACTIVE = ".#{CLASS_ACTIVE}"
34
34
 
35
35
  normalizeUrl = (url) ->
@@ -38,19 +38,29 @@ up.navigation = (->
38
38
  search: false
39
39
  stripTrailingSlash: true
40
40
  )
41
+
42
+ sectionUrls = ($section) ->
43
+ urls = []
44
+ if $link = up.link.resolve($section)
45
+ for attr in ['href', 'up-follow', 'up-source']
46
+ if url = u.presentAttr($link, attr)
47
+ url = normalizeUrl(url)
48
+ urls.push(url)
49
+ urls
41
50
 
42
51
  locationChanged = ->
43
- windowLocation = normalizeUrl(up.browser.url())
44
- modalLocation = normalizeUrl(up.modal.source())
45
- popupLocation = normalizeUrl(up.popup.source())
52
+ currentUrls = u.stringSet [
53
+ normalizeUrl(up.browser.url()),
54
+ normalizeUrl(up.modal.source()),
55
+ normalizeUrl(up.popup.source())
56
+ ]
46
57
 
47
58
  u.each $(SELECTOR_SECTION), (section) ->
48
59
  $section = $(section)
49
60
  # if $section is marked up with up-follow,
50
61
  # the actual link might be a child element.
51
- url = up.link.resolveUrl($section)
52
- url = normalizeUrl(url)
53
- if url == windowLocation || url == modalLocation || url == popupLocation
62
+ urls = sectionUrls($section)
63
+ if currentUrls.includesAny(urls)
54
64
  $section.addClass(CLASS_CURRENT)
55
65
  else
56
66
  $section.removeClass(CLASS_CURRENT)
@@ -54,7 +54,7 @@ up.popup = (->
54
54
  left: linkBox.left
55
55
  bottom: linkBox.top
56
56
  else
57
- u.error("Unknown origin", origin)
57
+ u.error("Unknown origin %o", origin)
58
58
  $popup.attr('up-origin', origin)
59
59
  $popup.css(css)
60
60
  ensureInViewport($popup)
@@ -116,7 +116,7 @@ up.popup = (->
116
116
  ###
117
117
  open = (linkOrSelector, options) ->
118
118
  $link = $(linkOrSelector)
119
-
119
+
120
120
  options = u.options(options)
121
121
  url = u.option($link.attr('href'))
122
122
  selector = u.option(options.target, $link.attr('up-popup'), 'body')
@@ -128,8 +128,6 @@ up.popup = (->
128
128
  close()
129
129
  $popup = createHiddenPopup($link, selector, sticky)
130
130
 
131
- # console.log("before replace", $link, $popup)
132
-
133
131
  up.replace(selector, url,
134
132
  history: history
135
133
  # source: true
@@ -0,0 +1,179 @@
1
+ ###*
2
+ Caching and preloading
3
+ ======================
4
+
5
+ Document me.
6
+
7
+ @class up.proxy
8
+ ###
9
+ up.proxy = (->
10
+
11
+ config =
12
+ preloadDelay: 50
13
+ cacheSize: 70
14
+ cacheExpiry: 1000 * 60 * 5
15
+
16
+ ###*
17
+ @method up.proxy.defaults
18
+ @param {Number} [preloadDelay]
19
+ @param {Number} [cacheSize]
20
+ @param {Number} [cacheExpiry]
21
+ The number of milliseconds until a cache entry expires.
22
+ ###
23
+ defaults = (options) ->
24
+ u.extend(config, options)
25
+
26
+ cache = {}
27
+
28
+ u = up.util
29
+
30
+ $waitingLink = null
31
+ delayTimer = null
32
+
33
+ cacheKey = (request) ->
34
+ normalizeRequest(request)
35
+ [ request.url,
36
+ request.method,
37
+ request.selector
38
+ ].join('|')
39
+
40
+ trim = ->
41
+ keys = u.keys(cache)
42
+ if keys.length > config.cacheSize
43
+ oldestKey = null
44
+ oldestTimestamp = null
45
+ u.each keys, (key) ->
46
+ promise = cache[key] # we don't need to call cacheKey here
47
+ timestamp = promise.timestamp
48
+ if !oldestTimestamp || oldestTimestamp > timestamp
49
+ oldestKey = key
50
+ oldestTimestamp = timestamp
51
+ delete cache[oldestKey] if oldestKey
52
+
53
+ timestamp = ->
54
+ (new Date()).valueOf()
55
+
56
+ normalizeRequest = (request) ->
57
+ debugger unless u.isHash(request)
58
+ unless request._requestNormalized
59
+ request.method = u.normalizeMethod(request.method)
60
+ request.url = u.normalizeUrl(request.url) if request.url
61
+ request.selector ||= 'body'
62
+ request._requestNormalized = true
63
+ request
64
+
65
+ alias = (oldRequest, newRequest) ->
66
+ u.debug("Aliasing %o to %o", oldRequest, newRequest)
67
+ if promise = get(oldRequest)
68
+ set(newRequest, promise)
69
+
70
+ ###
71
+ @method up.proxy.ajax
72
+ @param {String} options.url
73
+ @param {String} [options.method='GET']
74
+ @param {String} [options.selector]
75
+ ###
76
+ ajax = (request) ->
77
+ if !isIdempotent(request)
78
+ clear()
79
+ # We don't cache non-GET responses
80
+ promise = u.ajax(request)
81
+ else if promise = get(request)
82
+ touch(promise)
83
+ else
84
+ promise = u.ajax(request)
85
+ set(request, promise)
86
+ promise
87
+
88
+ isIdempotent = (request) ->
89
+ normalizeRequest(request)
90
+ request.method == 'GET'
91
+
92
+ ensureIsIdempotent = (request) ->
93
+ isIdempotent(request) or u.error("Won't preload non-GET request %o", request)
94
+
95
+ isFresh = (promise) ->
96
+ timeSinceTouch = timestamp() - promise.timestamp
97
+ timeSinceTouch < config.cacheExpiry
98
+
99
+ touch = (promise) ->
100
+ promise.timestamp = timestamp()
101
+
102
+ get = (request) ->
103
+ key = cacheKey(request)
104
+ if promise = cache[key]
105
+ if !isFresh(promise)
106
+ u.debug("Discarding stale cache entry for %o (%o)", request.url, request)
107
+ remove(request)
108
+ undefined
109
+ else
110
+ u.debug("Cache hit for %o (%o)", request.url, request)
111
+ # $('body').css('background-color': 'green')
112
+ promise
113
+ else
114
+ u.debug("Cache miss for %o (%o)", request.url, request)
115
+ # $('body').css('background-color': 'yellow')
116
+ undefined
117
+
118
+ set = (request, promise) ->
119
+ trim()
120
+ key = cacheKey(request)
121
+ cache[key] = promise
122
+ touch(promise)
123
+ promise
124
+
125
+ remove = (request) ->
126
+ key = cacheKey(request)
127
+ delete cache[key]
128
+
129
+ clear = ->
130
+ cache = {}
131
+
132
+ checkPreload = ($link) ->
133
+ delay = parseInt(u.presentAttr($link, 'up-delay')) || config.preloadDelay
134
+ unless $link.is($waitingLink)
135
+ $waitingLink = $link
136
+ cancelDelay()
137
+ curriedPreload = -> preload($link)
138
+ startDelay(curriedPreload, delay)
139
+
140
+ startDelay = (block, delay) ->
141
+ delayTimer = setTimeout(block, delay)
142
+
143
+ cancelDelay = ->
144
+ clearTimeout(delayTimer)
145
+ delayTimer = null
146
+
147
+ preload = (link, options) ->
148
+ options = u.options()
149
+ ensureIsIdempotent(options)
150
+ u.debug("Preloading %o", link)
151
+ options.preload = true
152
+ up.link.follow(link, options)
153
+
154
+ reset = ->
155
+ cancelDelay()
156
+ cache = {}
157
+
158
+ up.bus.on 'framework:reset', reset
159
+
160
+ ###
161
+ @method [up-preload]
162
+ @ujs
163
+ ###
164
+ up.on 'mouseover mousedown touchstart', '[up-preload]', (event, $element) ->
165
+ # Don't do anything if we are hovering over the child
166
+ # of a link. The actual link will receive the event
167
+ # and bubble in a second.
168
+ unless up.link.childClicked(event, $element)
169
+ checkPreload(up.link.resolve($element))
170
+
171
+ preload: preload
172
+ ajax: ajax
173
+ get: get
174
+ set: set
175
+ alias: alias
176
+ clear: clear
177
+ defaults: defaults
178
+
179
+ )()
@@ -31,7 +31,7 @@ up.tooltip = (->
31
31
  left: linkBox.left + 0.5 * (linkBox.width - tooltipBox.width)
32
32
  top: linkBox.top + linkBox.height
33
33
  else
34
- u.error("Unknown origin", origin)
34
+ u.error("Unknown origin %o", origin)
35
35
  $tooltip.attr('up-origin', origin)
36
36
  $tooltip.css(css)
37
37
 
@@ -51,7 +51,7 @@ up.tooltip = (->
51
51
  ###
52
52
  open = (linkOrSelector, options = {}) ->
53
53
  $link = $(linkOrSelector)
54
- html = u.option(options.html, $link.attr('up-tooltip'))
54
+ html = u.option(options.html, $link.attr('up-tooltip'), $link.attr('title'))
55
55
  origin = u.option(options.origin, $link.attr('up-origin'), 'top')
56
56
  animation = u.option(options.animation, $link.attr('up-animation'), 'fade-in')
57
57
  close()
@@ -78,6 +78,10 @@ up.tooltip = (->
78
78
  Displays a tooltip when hovering the mouse over this element:
79
79
 
80
80
  <a href="/decks" up-tooltip="Show all decks">Decks</a>
81
+
82
+ You can also make an existing `title` attribute appear as a tooltip:
83
+
84
+ <a href="/decks" title="Show all decks" up-tooltip>Decks</a>
81
85
 
82
86
  @method [up-tooltip]
83
87
  @ujs
@@ -1,3 +1,4 @@
1
+
1
2
  ###*
2
3
  Utility functions
3
4
  =================
@@ -57,6 +58,16 @@ up.util = (->
57
58
  normalized += anchor.search unless options?.search == false
58
59
  normalized
59
60
 
61
+ ###
62
+ @method up.util.normalizeMethod
63
+ @protected
64
+ ###
65
+ normalizeMethod = (method) ->
66
+ if method
67
+ method.toUpperCase()
68
+ else
69
+ 'GET'
70
+
60
71
  $createElementFromSelector = (selector) ->
61
72
  path = selector.split(/[ >]/)
62
73
  $root = null
@@ -87,15 +98,55 @@ up.util = (->
87
98
  element = document.createElement(tagName)
88
99
  element.innerHTML = html if isPresent(html)
89
100
  element
101
+
102
+ debug = (args...) ->
103
+ args = toArray(args)
104
+ message = args.shift()
105
+ message = "[UP] #{message}"
106
+ placeHolderCount = message.match(CONSOLE_PLACEHOLDERS)?.length || 0
107
+ if isFunction(last(args)) && placeHolderCount < args.length
108
+ group = args.pop()
109
+ value = console.debug(message, args...)
110
+ if group
111
+ console.groupCollapsed()
112
+ try
113
+ value = group()
114
+ finally
115
+ console.groupEnd()
116
+ value
90
117
 
91
118
  error = (args...) ->
92
- console.log("[UP] Error", args...)
93
- asString = if args.length == 1 && up.util.isString(args[0]) then args[0] else JSON.stringify(args)
94
- alert asString
119
+ args[0] = "[UP] #{args[0]}"
120
+ console.error(args...)
121
+ asString = stringifyConsoleArgs(args)
122
+ $error = presence($('.up-error')) || $('<div class="up-error"></div>').prependTo('body')
123
+ # $error = $('body')
124
+ $error.addClass('up-error')
125
+ $error.text(asString)
126
+ # alert "#{asString}\n\nOpen the developer console for details."
95
127
  throw asString
128
+
129
+ CONSOLE_PLACEHOLDERS = /\%[odisf]/g
130
+
131
+ stringifyConsoleArgs = (args) ->
132
+ message = args[0]
133
+ i = 0
134
+ maxLength = 30
135
+ message.replace CONSOLE_PLACEHOLDERS, ->
136
+ i += 1
137
+ arg = args[i]
138
+ argType = (typeof arg)
139
+ if argType == 'string'
140
+ arg = arg.replace(/\s+/g, ' ')
141
+ arg = "#{arg.substr(0, maxLength)}…" if arg.length > maxLength
142
+ "\"#{arg}\""
143
+ else if argType == 'number'
144
+ arg.toString()
145
+ else
146
+ "(#{argType})"
96
147
 
97
148
  createSelectorFromElement = ($element) ->
98
- console.log("Creating selector from element", $element)
149
+ debug("Creating selector from element %o", $element)
99
150
  classes = if classString = $element.attr("class") then classString.split(" ") else []
100
151
  id = $element.attr("id")
101
152
  selector = $element.prop("tagName").toLowerCase()
@@ -216,6 +267,9 @@ up.util = (->
216
267
  # https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
217
268
  isArray = Array.isArray ||
218
269
  (object) -> Object.prototype.toString.call(object) == '[object Array]'
270
+
271
+ toArray = (object) ->
272
+ Array.prototype.slice.call(object)
219
273
 
220
274
  copy = (object) ->
221
275
  if isArray(object)
@@ -298,8 +352,11 @@ up.util = (->
298
352
 
299
353
  temporaryCss = ($element, css, block) ->
300
354
  oldCss = $element.css(keys(css))
355
+ # debug("Stored old CSS", oldCss)
301
356
  $element.css(css)
302
- memo = -> $element.css(oldCss)
357
+ memo = ->
358
+ # debug("Restoring CSS %o on %o", oldCss, $element)
359
+ $element.css(oldCss)
303
360
  if block
304
361
  block()
305
362
  memo()
@@ -451,7 +508,10 @@ up.util = (->
451
508
  String(object) == "false"
452
509
 
453
510
  locationFromXhr = (xhr) ->
454
- xhr.getResponseHeader('X-Up-Current-Location')
511
+ xhr.getResponseHeader('X-Up-Location')
512
+
513
+ methodFromXhr = (xhr) ->
514
+ xhr.getResponseHeader('X-Up-Method')
455
515
 
456
516
  # willChangeHistory = (historyOption) ->
457
517
  # isPresent(historyOption) && !castsToFalse(historyOption)
@@ -477,6 +537,27 @@ up.util = (->
477
537
  each deferreds, (deferred) -> deferred.resolve?()
478
538
  joined
479
539
 
540
+ stringSet = (array) ->
541
+ set = {}
542
+
543
+ includes = (string) ->
544
+ set[key(string)]
545
+
546
+ includesAny = (strings) ->
547
+ detect strings, includes
548
+
549
+ put = (string) ->
550
+ set[key(string)] = true
551
+
552
+ key = (string) ->
553
+ "_#{string}"
554
+
555
+ put(string) for string in array
556
+
557
+ put: put
558
+ includes: includes
559
+ includesAny: includesAny
560
+
480
561
  # memoArray = ->
481
562
  # array = []
482
563
  # defaults = []
@@ -496,6 +577,7 @@ up.util = (->
496
577
  presentAttr: presentAttr
497
578
  createElement: createElement
498
579
  normalizeUrl: normalizeUrl
580
+ normalizeMethod: normalizeMethod
499
581
  createElementFromHtml: createElementFromHtml
500
582
  $createElementFromSelector: $createElementFromSelector
501
583
  createSelectorFromElement: createSelectorFromElement
@@ -507,6 +589,7 @@ up.util = (->
507
589
  options: options
508
590
  option: option
509
591
  error: error
592
+ debug: debug
510
593
  each: each
511
594
  detect: detect
512
595
  select: select
@@ -540,9 +623,11 @@ up.util = (->
540
623
  findWithSelf: findWithSelf
541
624
  contains: contains
542
625
  isArray: isArray
626
+ toArray: toArray
543
627
  castsToTrue: castsToTrue
544
628
  castsToFalse: castsToFalse
545
629
  locationFromXhr: locationFromXhr
630
+ methodFromXhr: methodFromXhr
546
631
  clientSize: clientSize
547
632
  only: only
548
633
  trim: trim
@@ -550,5 +635,6 @@ up.util = (->
550
635
  resolvedPromise: resolvedPromise
551
636
  resolvedDeferred: resolvedDeferred
552
637
  resolvableWhen: resolvableWhen
638
+ stringSet: stringSet
553
639
 
554
640
  )()
@@ -6,6 +6,7 @@
6
6
  #= require up/magic
7
7
  #= require up/history
8
8
  #= require up/motion
9
+ #= require up/proxy
9
10
  #= require up/link
10
11
  #= require up/form
11
12
  #= require up/popup
@@ -15,8 +16,10 @@
15
16
  #= require up/marker
16
17
  #= require_self
17
18
 
18
- if up.browser.isSupported()
19
+ up.browser.ensureRecentJquery()
19
20
 
21
+ if up.browser.isSupported()
22
+
20
23
  up.browser.ensureConsoleExists()
21
24
 
22
25
  up.bus.emit('framework:ready')
@@ -0,0 +1,15 @@
1
+ .up-error
2
+ background-color: #e10
3
+ color: white
4
+ padding: 5px
5
+ font-family: arial, helvetica, sans-serif
6
+ font-weight: bold
7
+ font-size: 14px
8
+ line-height: 18px
9
+ position: fixed
10
+ left: 0
11
+ top: 0
12
+ max-width: 100%
13
+ box-sizing: border-box
14
+ z-index: 99999999
15
+
@@ -9,6 +9,7 @@ $background: #666
9
9
  background-color: $background
10
10
  color: white
11
11
  padding: 6px 9px
12
+ white-space: nowrap
12
13
  //box-shadow: 0 0 4px rgba(0, 0, 0, 0.2)
13
14
 
14
15
  &[up-origin=top]
@@ -9,7 +9,8 @@ module Upjs
9
9
  private
10
10
 
11
11
  def set_header_for_current_location
12
- headers['X-Up-Current-Location'] = request.fullpath
12
+ headers['X-Up-Location'] = request.original_url
13
+ headers['X-Up-Method'] = request.method
13
14
  end
14
15
 
15
16
  ActionController::Base.include(self)
@@ -1,5 +1,5 @@
1
1
  module Upjs
2
2
  module Rails
3
- VERSION = "0.3.3"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- upjs-rails (0.3.2)
4
+ upjs-rails (0.3.3)
5
5
  rails (>= 3)
6
6
 
7
7
  GEM
@@ -14,18 +14,20 @@ describe 'up.flow', ->
14
14
  affix('.before').text('old-before')
15
15
  affix('.middle').text('old-middle')
16
16
  affix('.after').text('old-after')
17
-
17
+
18
+ @responseText =
19
+ """
20
+ <div class="before">new-before</div>
21
+ <div class="middle">new-middle</div>
22
+ <div class="after">new-after</div>
23
+ """
24
+
18
25
  @respond = ->
19
26
  jasmine.Ajax.requests.mostRecent().respondWith
20
27
  status: 200
21
28
  contentType: 'text/html'
22
- responseText:
23
- """
24
- <div class="before">new-before</div>
25
- <div class="middle">new-middle</div>
26
- <div class="after">new-after</div>
27
- """
28
-
29
+ responseText: @responseText
30
+
29
31
  it 'replaces the given selector with the same selector from a freshly fetched page', (done) ->
30
32
  @request = up.replace('.middle', '/path')
31
33
  @respond()
@@ -57,7 +59,34 @@ describe 'up.flow', ->
57
59
  expect($('.middle')).toHaveText('new-middle')
58
60
  expect($('.after')).toHaveText('new-after')
59
61
  done()
60
-
62
+
63
+ it 'executes those script-tags in the response that get inserted into the DOM', (done) ->
64
+ window.scriptTagExecuted = jasmine.createSpy('scriptTagExecuted')
65
+
66
+ @responseText =
67
+ """
68
+ <div class="before">
69
+ new-before
70
+ <script type="text/javascript">
71
+ window.scriptTagExecuted('before')
72
+ </script>
73
+ </div>
74
+ <div class="middle">
75
+ new-middle
76
+ <script type="text/javascript">
77
+ window.scriptTagExecuted('middle')
78
+ </script>
79
+ </div>
80
+ """
81
+
82
+ @request = up.replace('.middle', '/path')
83
+ @respond()
84
+
85
+ @request.then ->
86
+ expect(window.scriptTagExecuted).not.toHaveBeenCalledWith('before')
87
+ expect(window.scriptTagExecuted).toHaveBeenCalledWith('middle')
88
+ done()
89
+
61
90
  else
62
91
 
63
92
  it 'makes a full page load', ->
@@ -72,12 +72,12 @@ describe 'up.form', ->
72
72
  expect($('body')).not.toHaveText('text-after')
73
73
  done()
74
74
 
75
- it 'respects a X-Up-Current-Location header that the server sends in case of a redirect', (done) ->
75
+ it 'respects a X-Up-Location header that the server sends in case of a redirect', (done) ->
76
76
 
77
77
  @request.respondWith
78
78
  status: 200
79
79
  contentType: 'text/html'
80
- responseHeaders: { 'X-Up-Current-Location': '/other/path' }
80
+ responseHeaders: { 'X-Up-Location': '/other/path' }
81
81
  responseText:
82
82
  """
83
83
  <div class="response">