upjs-rails 0.3.3 → 0.4.0

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