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.
data/dist/up.js CHANGED
@@ -25,7 +25,7 @@ If you use them in your own code, you will get hurt.
25
25
  var __slice = [].slice;
26
26
 
27
27
  up.util = (function() {
28
- var $createElementFromSelector, ANIMATION_PROMISE_KEY, ajax, castsToFalse, castsToTrue, clientSize, contains, copy, copyAttributes, createElement, createElementFromHtml, createSelectorFromElement, cssAnimate, detect, each, error, escapePressed, extend, findWithSelf, finishCssAnimate, forceCompositing, get, ifGiven, isArray, isBlank, isDeferred, isDefined, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, keys, last, locationFromXhr, measure, merge, nextFrame, normalizeUrl, only, option, options, prependGhost, presence, presentAttr, resolvableWhen, resolvedDeferred, resolvedPromise, select, temporaryCss, trim, unwrap;
28
+ var $createElementFromSelector, ANIMATION_PROMISE_KEY, CONSOLE_PLACEHOLDERS, ajax, castsToFalse, castsToTrue, clientSize, contains, copy, copyAttributes, createElement, createElementFromHtml, createSelectorFromElement, cssAnimate, debug, detect, each, error, escapePressed, extend, findWithSelf, finishCssAnimate, forceCompositing, get, ifGiven, isArray, isBlank, isDeferred, isDefined, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, keys, last, locationFromXhr, measure, merge, methodFromXhr, nextFrame, normalizeMethod, normalizeUrl, only, option, options, prependGhost, presence, presentAttr, resolvableWhen, resolvedDeferred, resolvedPromise, select, stringSet, stringifyConsoleArgs, temporaryCss, toArray, trim, unwrap;
29
29
  get = function(url, options) {
30
30
  options = options || {};
31
31
  options.url = url;
@@ -81,6 +81,18 @@ If you use them in your own code, you will get hurt.
81
81
  }
82
82
  return normalized;
83
83
  };
84
+
85
+ /*
86
+ @method up.util.normalizeMethod
87
+ @protected
88
+ */
89
+ normalizeMethod = function(method) {
90
+ if (method) {
91
+ return method.toUpperCase();
92
+ } else {
93
+ return 'GET';
94
+ }
95
+ };
84
96
  $createElementFromSelector = function(selector) {
85
97
  var $element, $parent, $root, classes, conjunction, depthSelector, expression, html, id, iteration, path, tag, _i, _j, _len, _len1;
86
98
  path = selector.split(/[ >]/);
@@ -131,17 +143,65 @@ If you use them in your own code, you will get hurt.
131
143
  }
132
144
  return element;
133
145
  };
146
+ debug = function() {
147
+ var args, group, message, placeHolderCount, value, _ref;
148
+ args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
149
+ args = toArray(args);
150
+ message = args.shift();
151
+ message = "[UP] " + message;
152
+ placeHolderCount = ((_ref = message.match(CONSOLE_PLACEHOLDERS)) != null ? _ref.length : void 0) || 0;
153
+ if (isFunction(last(args)) && placeHolderCount < args.length) {
154
+ group = args.pop();
155
+ }
156
+ value = console.debug.apply(console, [message].concat(__slice.call(args)));
157
+ if (group) {
158
+ console.groupCollapsed();
159
+ try {
160
+ value = group();
161
+ } finally {
162
+ console.groupEnd();
163
+ }
164
+ }
165
+ return value;
166
+ };
134
167
  error = function() {
135
- var args, asString;
168
+ var $error, args, asString;
136
169
  args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
137
- console.log.apply(console, ["[UP] Error"].concat(__slice.call(args)));
138
- asString = args.length === 1 && up.util.isString(args[0]) ? args[0] : JSON.stringify(args);
139
- alert(asString);
170
+ args[0] = "[UP] " + args[0];
171
+ console.error.apply(console, args);
172
+ asString = stringifyConsoleArgs(args);
173
+ $error = presence($('.up-error')) || $('<div class="up-error"></div>').prependTo('body');
174
+ $error.addClass('up-error');
175
+ $error.text(asString);
140
176
  throw asString;
141
177
  };
178
+ CONSOLE_PLACEHOLDERS = /\%[odisf]/g;
179
+ stringifyConsoleArgs = function(args) {
180
+ var i, maxLength, message;
181
+ message = args[0];
182
+ i = 0;
183
+ maxLength = 30;
184
+ return message.replace(CONSOLE_PLACEHOLDERS, function() {
185
+ var arg, argType;
186
+ i += 1;
187
+ arg = args[i];
188
+ argType = typeof arg;
189
+ if (argType === 'string') {
190
+ arg = arg.replace(/\s+/g, ' ');
191
+ if (arg.length > maxLength) {
192
+ arg = (arg.substr(0, maxLength)) + "…";
193
+ }
194
+ return "\"" + arg + "\"";
195
+ } else if (argType === 'number') {
196
+ return arg.toString();
197
+ } else {
198
+ return "(" + argType + ")";
199
+ }
200
+ });
201
+ };
142
202
  createSelectorFromElement = function($element) {
143
203
  var classString, classes, id, klass, selector, _i, _len;
144
- console.log("Creating selector from element", $element);
204
+ debug("Creating selector from element %o", $element);
145
205
  classes = (classString = $element.attr("class")) ? classString.split(" ") : [];
146
206
  id = $element.attr("id");
147
207
  selector = $element.prop("tagName").toLowerCase();
@@ -265,6 +325,9 @@ If you use them in your own code, you will get hurt.
265
325
  isArray = Array.isArray || function(object) {
266
326
  return Object.prototype.toString.call(object) === '[object Array]';
267
327
  };
328
+ toArray = function(object) {
329
+ return Array.prototype.slice.call(object);
330
+ };
268
331
  copy = function(object) {
269
332
  if (isArray(object)) {
270
333
  return object.slice();
@@ -557,7 +620,10 @@ If you use them in your own code, you will get hurt.
557
620
  return String(object) === "false";
558
621
  };
559
622
  locationFromXhr = function(xhr) {
560
- return xhr.getResponseHeader('X-Up-Current-Location');
623
+ return xhr.getResponseHeader('X-Up-Location');
624
+ };
625
+ methodFromXhr = function(xhr) {
626
+ return xhr.getResponseHeader('X-Up-Method');
561
627
  };
562
628
  only = function() {
563
629
  var filtered, key, keys, object, _i, _len;
@@ -591,10 +657,36 @@ If you use them in your own code, you will get hurt.
591
657
  };
592
658
  return joined;
593
659
  };
660
+ stringSet = function(array) {
661
+ var includes, includesAny, key, put, set, string, _i, _len;
662
+ set = {};
663
+ includes = function(string) {
664
+ return set[key(string)];
665
+ };
666
+ includesAny = function(strings) {
667
+ return detect(strings, includes);
668
+ };
669
+ put = function(string) {
670
+ return set[key(string)] = true;
671
+ };
672
+ key = function(string) {
673
+ return "_" + string;
674
+ };
675
+ for (_i = 0, _len = array.length; _i < _len; _i++) {
676
+ string = array[_i];
677
+ put(string);
678
+ }
679
+ return {
680
+ put: put,
681
+ includes: includes,
682
+ includesAny: includesAny
683
+ };
684
+ };
594
685
  return {
595
686
  presentAttr: presentAttr,
596
687
  createElement: createElement,
597
688
  normalizeUrl: normalizeUrl,
689
+ normalizeMethod: normalizeMethod,
598
690
  createElementFromHtml: createElementFromHtml,
599
691
  $createElementFromSelector: $createElementFromSelector,
600
692
  createSelectorFromElement: createSelectorFromElement,
@@ -606,6 +698,7 @@ If you use them in your own code, you will get hurt.
606
698
  options: options,
607
699
  option: option,
608
700
  error: error,
701
+ debug: debug,
609
702
  each: each,
610
703
  detect: detect,
611
704
  select: select,
@@ -639,16 +732,19 @@ If you use them in your own code, you will get hurt.
639
732
  findWithSelf: findWithSelf,
640
733
  contains: contains,
641
734
  isArray: isArray,
735
+ toArray: toArray,
642
736
  castsToTrue: castsToTrue,
643
737
  castsToFalse: castsToFalse,
644
738
  locationFromXhr: locationFromXhr,
739
+ methodFromXhr: methodFromXhr,
645
740
  clientSize: clientSize,
646
741
  only: only,
647
742
  trim: trim,
648
743
  keys: keys,
649
744
  resolvedPromise: resolvedPromise,
650
745
  resolvedDeferred: resolvedDeferred,
651
- resolvableWhen: resolvableWhen
746
+ resolvableWhen: resolvableWhen,
747
+ stringSet: stringSet
652
748
  };
653
749
  })();
654
750
 
@@ -665,7 +761,7 @@ Browser interface
665
761
  var __slice = [].slice;
666
762
 
667
763
  up.browser = (function() {
668
- var canCssAnimation, canInputEvent, canPushState, ensureConsoleExists, isSupported, loadPage, memoize, u, url;
764
+ var canCssAnimation, canInputEvent, canPushState, ensureConsoleExists, ensureRecentJquery, isSupported, loadPage, memoize, u, url;
669
765
  u = up.util;
670
766
  loadPage = function(url, options) {
671
767
  var $form, csrfParam, csrfToken, metadataInput, method, target;
@@ -697,9 +793,16 @@ Browser interface
697
793
  return location.href;
698
794
  };
699
795
  ensureConsoleExists = function() {
700
- var _base;
796
+ var noop, _base, _base1, _base2, _base3, _base4, _base5, _base6;
701
797
  window.console || (window.console = {});
702
- return (_base = window.console).log || (_base.log = function() {});
798
+ noop = function() {};
799
+ (_base = window.console).log || (_base.log = noop);
800
+ (_base1 = window.console).info || (_base1.info = noop);
801
+ (_base2 = window.console).error || (_base2.error = noop);
802
+ (_base3 = window.console).debug || (_base3.debug = noop);
803
+ (_base4 = window.console).group || (_base4.group = noop);
804
+ (_base5 = window.console).groupCollapsed || (_base5.groupCollapsed = noop);
805
+ return (_base6 = window.console).groupEnd || (_base6.groupEnd = noop);
703
806
  };
704
807
  memoize = function(func) {
705
808
  var cache, cached;
@@ -725,6 +828,15 @@ Browser interface
725
828
  canInputEvent = memoize(function() {
726
829
  return 'oninput' in document.createElement('input');
727
830
  });
831
+ ensureRecentJquery = function() {
832
+ var compatible, major, minor, parts, version;
833
+ version = $.fn.jquery;
834
+ parts = version.split('.');
835
+ major = parseInt(parts[0]);
836
+ minor = parseInt(parts[1]);
837
+ compatible = major >= 2 || (major === 1 && minor >= 9);
838
+ return compatible || u.error("jQuery %o found, but Up.js requires 1.9+", version);
839
+ };
728
840
  isSupported = memoize(function() {
729
841
  return u.isDefined(document.addEventListener);
730
842
  });
@@ -735,7 +847,8 @@ Browser interface
735
847
  canPushState: canPushState,
736
848
  canCssAnimation: canCssAnimation,
737
849
  canInputEvent: canInputEvent,
738
- isSupported: isSupported
850
+ isSupported: isSupported,
851
+ ensureRecentJquery: ensureRecentJquery
739
852
  };
740
853
  })();
741
854
 
@@ -771,7 +884,8 @@ We need to work on this page:
771
884
  var __slice = [].slice;
772
885
 
773
886
  up.bus = (function() {
774
- var callbacksByEvent, callbacksFor, defaultCallbacksByEvent, emit, listen, reset, snapshot;
887
+ var callbacksByEvent, callbacksFor, defaultCallbacksByEvent, emit, listen, reset, snapshot, u;
888
+ u = up.util;
775
889
  callbacksByEvent = {};
776
890
  defaultCallbacksByEvent = {};
777
891
  callbacksFor = function(event) {
@@ -791,7 +905,7 @@ We need to work on this page:
791
905
  _results = [];
792
906
  for (event in callbacksByEvent) {
793
907
  callbacks = callbacksByEvent[event];
794
- _results.push(defaultCallbacksByEvent[event] = up.util.copy(callbacks));
908
+ _results.push(defaultCallbacksByEvent[event] = u.copy(callbacks));
795
909
  }
796
910
  return _results;
797
911
  };
@@ -804,7 +918,7 @@ We need to work on this page:
804
918
  @method up.bus.reset
805
919
  */
806
920
  reset = function() {
807
- return callbacksByEvent = up.util.copy(defaultCallbacksByEvent);
921
+ return callbacksByEvent = u.copy(defaultCallbacksByEvent);
808
922
  };
809
923
 
810
924
  /**
@@ -833,9 +947,9 @@ We need to work on this page:
833
947
  emit = function() {
834
948
  var args, callbacks, eventName;
835
949
  eventName = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
836
- console.log("bus emitting", eventName, args);
950
+ u.debug("Emitting event %o with args %o", eventName, args);
837
951
  callbacks = callbacksFor(eventName);
838
- return up.util.each(callbacks, function(callback) {
952
+ return u.each(callbacks, function(callback) {
839
953
  return callback.apply(null, args);
840
954
  });
841
955
  };
@@ -908,19 +1022,31 @@ We need to work on this page:
908
1022
  @param {String} [options.historyMethod='push']
909
1023
  */
910
1024
  replace = function(selectorOrElement, url, options) {
911
- var selector;
1025
+ var promise, request, selector;
912
1026
  options = u.options(options);
913
1027
  selector = u.presence(selectorOrElement) ? selectorOrElement : u.createSelectorFromElement($(selectorOrElement));
914
1028
  if (!up.browser.canPushState() && !u.castsToFalse(options.history)) {
915
- up.browser.loadPage(url, u.only(options, 'method'));
1029
+ if (!options.preload) {
1030
+ up.browser.loadPage(url, u.only(options, 'method'));
1031
+ }
916
1032
  return;
917
1033
  }
918
- return u.ajax({
1034
+ request = {
919
1035
  url: url,
1036
+ method: options.method,
920
1037
  selector: selector
921
- }, u.only(options, 'method')).done(function(html, textStatus, xhr) {
922
- var currentLocation;
1038
+ };
1039
+ promise = up.proxy.ajax(request);
1040
+ promise.done(function(html, textStatus, xhr) {
1041
+ var currentLocation, newRequest;
923
1042
  if (currentLocation = u.locationFromXhr(xhr)) {
1043
+ u.debug('Location from server: %o', currentLocation);
1044
+ newRequest = {
1045
+ url: currentLocation,
1046
+ method: u.methodFromXhr(xhr),
1047
+ selector: selector
1048
+ };
1049
+ up.proxy.alias(request, newRequest);
924
1050
  url = currentLocation;
925
1051
  }
926
1052
  if (u.isMissing(options.history) || u.castsToTrue(options.history)) {
@@ -929,8 +1055,12 @@ We need to work on this page:
929
1055
  if (u.isMissing(options.source) || u.castsToTrue(options.source)) {
930
1056
  options.source = url;
931
1057
  }
932
- return implant(selector, html, options);
933
- }).fail(u.error);
1058
+ if (!options.preload) {
1059
+ return implant(selector, html, options);
1060
+ }
1061
+ });
1062
+ promise.fail(u.error);
1063
+ return promise;
934
1064
  };
935
1065
 
936
1066
  /**
@@ -962,12 +1092,12 @@ We need to work on this page:
962
1092
  for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
963
1093
  step = _ref1[_i];
964
1094
  up.motion.finish(step.selector);
965
- $old = u.presence($(".up-popup " + step.selector)) || u.presence($(".up-modal " + step.selector)) || u.presence($(step.selector)) || u.error("Could not find selector (" + step.selector + ") in current body HTML");
1095
+ $old = u.presence($(".up-popup " + step.selector)) || u.presence($(".up-modal " + step.selector)) || u.presence($(step.selector)) || u.error('Could not find selector %o in current body HTML', step.selector);
966
1096
  if (fragment = htmlElement.querySelector(step.selector)) {
967
1097
  $new = $(fragment);
968
1098
  _results.push(swapElements($old, $new, step.pseudoClass, step.transition, options));
969
1099
  } else {
970
- _results.push(u.error("Could not find selector (" + step.selector + ") in response (" + html + ")"));
1100
+ _results.push(u.error("Could not find selector %o in response %o", step.selector, html));
971
1101
  }
972
1102
  }
973
1103
  return _results;
@@ -1002,7 +1132,7 @@ We need to work on this page:
1002
1132
  $new.insertAfter($old);
1003
1133
  elementsInserted($new, options);
1004
1134
  if ($old.is('body') && transition !== 'none') {
1005
- u.error('Cannot apply transitions to body-elements', transition);
1135
+ u.error('Cannot apply transitions to body-elements (%o)', transition);
1006
1136
  }
1007
1137
  return up.morph($old, $new, transition);
1008
1138
  }
@@ -1143,7 +1273,7 @@ We need to work on this page:
1143
1273
  var __slice = [].slice;
1144
1274
 
1145
1275
  up.magic = (function() {
1146
- var DESTROYABLE_CLASS, DESTROYER_KEY, applyAwakener, awaken, awakeners, compile, defaultAwakeners, defaultLiveDescriptions, destroy, live, liveDescriptions, onEscape, ready, reset, snapshot, u;
1276
+ var DESTROYABLE_CLASS, DESTROYER_KEY, applyAwakener, awaken, awakeners, compile, data, defaultAwakeners, defaultLiveDescriptions, destroy, live, liveDescriptions, onEscape, ready, reset, snapshot, u;
1147
1277
  u = up.util;
1148
1278
  DESTROYABLE_CLASS = 'up-destroyable';
1149
1279
  DESTROYER_KEY = 'up-destroyer';
@@ -1158,8 +1288,11 @@ We need to work on this page:
1158
1288
  A space-separated list of event names to bind.
1159
1289
  @param {String} selector
1160
1290
  The selector an on which the event must be triggered.
1161
- @param {Function(event, $element)} behavior
1291
+ @param {Function(event, $element, data)} behavior
1162
1292
  The handler that should be called.
1293
+ The function takes the affected element as the first argument (as a jQuery object).
1294
+ If the element has an `up-data` attribute, its value is parsed as JSON
1295
+ and passed as a second argument.
1163
1296
  */
1164
1297
  liveDescriptions = [];
1165
1298
  defaultLiveDescriptions = null;
@@ -1170,7 +1303,7 @@ We need to work on this page:
1170
1303
  }
1171
1304
  description = [
1172
1305
  events, selector, function(event) {
1173
- return behavior.apply(this, [event, $(this)]);
1306
+ return behavior.apply(this, [event, $(this), data(this)]);
1174
1307
  }
1175
1308
  ];
1176
1309
  liveDescriptions.push(description);
@@ -1188,9 +1321,11 @@ We need to work on this page:
1188
1321
  If set to `true` and a fragment insertion contains multiple
1189
1322
  elements matching the selector, `awakener` is only called once
1190
1323
  with a jQuery collection containing all matching elements.
1191
- @param {Function($element)} awakener
1324
+ @param {Function($element, data)} awakener
1192
1325
  The function to call when a matching element is inserted.
1193
1326
  The function takes the new element as the first argument (as a jQuery object).
1327
+ If the element has an `up-data` attribute, its value is parsed as JSON
1328
+ and passed as a second argument.
1194
1329
 
1195
1330
  The function may return another function that destroys the awakened
1196
1331
  object when it is removed from the DOM, by clearing global state such as
@@ -1216,7 +1351,8 @@ We need to work on this page:
1216
1351
  };
1217
1352
  applyAwakener = function(awakener, $jqueryElement, nativeElement) {
1218
1353
  var destroyer;
1219
- destroyer = awakener.callback.apply(nativeElement, [$jqueryElement]);
1354
+ u.debug("Applying awakener %o on %o", awakener.selector, nativeElement);
1355
+ destroyer = awakener.callback.apply(nativeElement, [$jqueryElement, data($jqueryElement)]);
1220
1356
  if (u.isFunction(destroyer)) {
1221
1357
  $jqueryElement.addClass(DESTROYABLE_CLASS);
1222
1358
  return $jqueryElement.data(DESTROYER_KEY, destroyer);
@@ -1224,7 +1360,7 @@ We need to work on this page:
1224
1360
  };
1225
1361
  compile = function($fragment) {
1226
1362
  var $matches, awakener, _i, _len, _results;
1227
- console.log("Compiling fragment", $fragment);
1363
+ u.debug("Compiling fragment %o", $fragment);
1228
1364
  _results = [];
1229
1365
  for (_i = 0, _len = awakeners.length; _i < _len; _i++) {
1230
1366
  awakener = awakeners[_i];
@@ -1252,6 +1388,30 @@ We need to work on this page:
1252
1388
  });
1253
1389
  };
1254
1390
 
1391
+ /*
1392
+ Checks if the given element has an `up-data` attribute.
1393
+ If yes, parses the attribute value as JSON and returns the parsed object.
1394
+
1395
+ Returns an empty object if the element has no `up-data` attribute.
1396
+
1397
+ The API of this method is likely to change in the future, so
1398
+ we can support getting or setting individual keys.
1399
+
1400
+ @protected
1401
+ @method up.magic.data
1402
+ @param {String|Element|jQuery} elementOrSelector
1403
+ */
1404
+ data = function(elementOrSelector) {
1405
+ var $element, json;
1406
+ $element = $(elementOrSelector);
1407
+ json = $element.attr('up-data');
1408
+ if (u.isString(json) && u.trim(json) !== '') {
1409
+ return JSON.parse(json);
1410
+ } else {
1411
+ return {};
1412
+ }
1413
+ };
1414
+
1255
1415
  /**
1256
1416
  Makes a snapshot of the currently registered event listeners,
1257
1417
  to later be restored through [`up.bus.reset`](/up.bus#up.bus.reset).
@@ -1319,7 +1479,8 @@ We need to work on this page:
1319
1479
  awaken: awaken,
1320
1480
  on: live,
1321
1481
  ready: ready,
1322
- onEscape: onEscape
1482
+ onEscape: onEscape,
1483
+ data: data
1323
1484
  };
1324
1485
  })();
1325
1486
 
@@ -1396,14 +1557,13 @@ We need to work on this page:
1396
1557
  pop = function(event) {
1397
1558
  var state;
1398
1559
  state = event.originalEvent.state;
1399
- console.log("popping state", state);
1400
- console.log("current href", up.browser.url());
1401
1560
  if (state != null ? state.fromUp : void 0) {
1561
+ u.debug("Restoring state %o (now on " + (up.browser.url()) + ")", state);
1402
1562
  return up.visit(up.browser.url(), {
1403
1563
  historyMethod: 'replace'
1404
1564
  });
1405
1565
  } else {
1406
- return console.log("strange state", state);
1566
+ return u.debug('Discarding unknown state %o', state);
1407
1567
  }
1408
1568
  };
1409
1569
  if (up.browser.canPushState()) {
@@ -1509,11 +1669,11 @@ We need to work on this page:
1509
1669
  } else if (u.isHash(animation)) {
1510
1670
  return u.cssAnimate($element, animation, options);
1511
1671
  } else {
1512
- return u.error("Unknown animation type", animation);
1672
+ return u.error("Unknown animation type %o", animation);
1513
1673
  }
1514
1674
  };
1515
1675
  findAnimation = function(name) {
1516
- return animations[name] || u.error("Unknown animation", animation);
1676
+ return animations[name] || u.error("Unknown animation %o", animation);
1517
1677
  };
1518
1678
  GHOSTING_PROMISE_KEY = 'up-ghosting-promise';
1519
1679
  withGhosts = function($old, $new, block) {
@@ -1572,7 +1732,7 @@ We need to work on this page:
1572
1732
  finishGhosting = function($element) {
1573
1733
  var existingGhosting;
1574
1734
  if (existingGhosting = $element.data(GHOSTING_PROMISE_KEY)) {
1575
- console.log("EXISTING", existingGhosting);
1735
+ u.debug('Canceling existing ghosting on %o', $element);
1576
1736
  return typeof existingGhosting.resolve === "function" ? existingGhosting.resolve() : void 0;
1577
1737
  }
1578
1738
  };
@@ -1580,7 +1740,7 @@ We need to work on this page:
1580
1740
  if (u.isDeferred(object)) {
1581
1741
  return object;
1582
1742
  } else {
1583
- return u.error("Did not return a promise with .then and .resolve methods: ", origin);
1743
+ return u.error("Did not return a promise with .then and .resolve methods: %o", origin);
1584
1744
  }
1585
1745
  };
1586
1746
 
@@ -1620,8 +1780,9 @@ We need to work on this page:
1620
1780
  $new = $(target);
1621
1781
  finish($old);
1622
1782
  finish($new);
1623
- transition = u.presence(transitionOrName, u.isFunction) || transitions[transitionOrName];
1624
- if (transition) {
1783
+ if (transitionOrName === 'none') {
1784
+ return none();
1785
+ } else if (transition = u.presence(transitionOrName, u.isFunction) || transitions[transitionOrName]) {
1625
1786
  return withGhosts($old, $new, function($oldGhost, $newGhost) {
1626
1787
  return assertIsDeferred(transition($oldGhost, $newGhost, options), transitionOrName);
1627
1788
  });
@@ -1635,7 +1796,7 @@ We need to work on this page:
1635
1796
  };
1636
1797
  return morph($old, $new, transition, options);
1637
1798
  } else {
1638
- return u.error("Unknown transition: " + transitionOrName);
1799
+ return u.error("Unknown transition %o", transitionOrName);
1639
1800
  }
1640
1801
  } else {
1641
1802
  return u.resolvedDeferred();
@@ -1846,6 +2007,208 @@ We need to work on this page:
1846
2007
 
1847
2008
  }).call(this);
1848
2009
 
2010
+ /**
2011
+ Caching and preloading
2012
+ ======================
2013
+
2014
+ Document me.
2015
+
2016
+ @class up.proxy
2017
+ */
2018
+
2019
+ (function() {
2020
+ up.proxy = (function() {
2021
+ var $waitingLink, ajax, alias, cache, cacheKey, cancelDelay, checkPreload, clear, config, defaults, delayTimer, ensureIsIdempotent, get, isFresh, isIdempotent, normalizeRequest, preload, remove, reset, set, startDelay, timestamp, touch, trim, u;
2022
+ config = {
2023
+ preloadDelay: 50,
2024
+ cacheSize: 70,
2025
+ cacheExpiry: 1000 * 60 * 5
2026
+ };
2027
+
2028
+ /**
2029
+ @method up.proxy.defaults
2030
+ @param {Number} [preloadDelay]
2031
+ @param {Number} [cacheSize]
2032
+ @param {Number} [cacheExpiry]
2033
+ The number of milliseconds until a cache entry expires.
2034
+ */
2035
+ defaults = function(options) {
2036
+ return u.extend(config, options);
2037
+ };
2038
+ cache = {};
2039
+ u = up.util;
2040
+ $waitingLink = null;
2041
+ delayTimer = null;
2042
+ cacheKey = function(request) {
2043
+ normalizeRequest(request);
2044
+ return [request.url, request.method, request.selector].join('|');
2045
+ };
2046
+ trim = function() {
2047
+ var keys, oldestKey, oldestTimestamp;
2048
+ keys = u.keys(cache);
2049
+ if (keys.length > config.cacheSize) {
2050
+ oldestKey = null;
2051
+ oldestTimestamp = null;
2052
+ u.each(keys, function(key) {
2053
+ var promise, timestamp;
2054
+ promise = cache[key];
2055
+ timestamp = promise.timestamp;
2056
+ if (!oldestTimestamp || oldestTimestamp > timestamp) {
2057
+ oldestKey = key;
2058
+ return oldestTimestamp = timestamp;
2059
+ }
2060
+ });
2061
+ if (oldestKey) {
2062
+ return delete cache[oldestKey];
2063
+ }
2064
+ }
2065
+ };
2066
+ timestamp = function() {
2067
+ return (new Date()).valueOf();
2068
+ };
2069
+ normalizeRequest = function(request) {
2070
+ if (!u.isHash(request)) {
2071
+ debugger;
2072
+ }
2073
+ if (!request._requestNormalized) {
2074
+ request.method = u.normalizeMethod(request.method);
2075
+ if (request.url) {
2076
+ request.url = u.normalizeUrl(request.url);
2077
+ }
2078
+ request.selector || (request.selector = 'body');
2079
+ request._requestNormalized = true;
2080
+ }
2081
+ return request;
2082
+ };
2083
+ alias = function(oldRequest, newRequest) {
2084
+ var promise;
2085
+ u.debug("Aliasing %o to %o", oldRequest, newRequest);
2086
+ if (promise = get(oldRequest)) {
2087
+ return set(newRequest, promise);
2088
+ }
2089
+ };
2090
+
2091
+ /*
2092
+ @method up.proxy.ajax
2093
+ @param {String} options.url
2094
+ @param {String} [options.method='GET']
2095
+ @param {String} [options.selector]
2096
+ */
2097
+ ajax = function(request) {
2098
+ var promise;
2099
+ if (!isIdempotent(request)) {
2100
+ clear();
2101
+ promise = u.ajax(request);
2102
+ } else if (promise = get(request)) {
2103
+ touch(promise);
2104
+ } else {
2105
+ promise = u.ajax(request);
2106
+ set(request, promise);
2107
+ }
2108
+ return promise;
2109
+ };
2110
+ isIdempotent = function(request) {
2111
+ normalizeRequest(request);
2112
+ return request.method === 'GET';
2113
+ };
2114
+ ensureIsIdempotent = function(request) {
2115
+ return isIdempotent(request) || u.error("Won't preload non-GET request %o", request);
2116
+ };
2117
+ isFresh = function(promise) {
2118
+ var timeSinceTouch;
2119
+ timeSinceTouch = timestamp() - promise.timestamp;
2120
+ return timeSinceTouch < config.cacheExpiry;
2121
+ };
2122
+ touch = function(promise) {
2123
+ return promise.timestamp = timestamp();
2124
+ };
2125
+ get = function(request) {
2126
+ var key, promise;
2127
+ key = cacheKey(request);
2128
+ if (promise = cache[key]) {
2129
+ if (!isFresh(promise)) {
2130
+ u.debug("Discarding stale cache entry for %o (%o)", request.url, request);
2131
+ remove(request);
2132
+ return void 0;
2133
+ } else {
2134
+ u.debug("Cache hit for %o (%o)", request.url, request);
2135
+ return promise;
2136
+ }
2137
+ } else {
2138
+ u.debug("Cache miss for %o (%o)", request.url, request);
2139
+ return void 0;
2140
+ }
2141
+ };
2142
+ set = function(request, promise) {
2143
+ var key;
2144
+ trim();
2145
+ key = cacheKey(request);
2146
+ cache[key] = promise;
2147
+ touch(promise);
2148
+ return promise;
2149
+ };
2150
+ remove = function(request) {
2151
+ var key;
2152
+ key = cacheKey(request);
2153
+ return delete cache[key];
2154
+ };
2155
+ clear = function() {
2156
+ return cache = {};
2157
+ };
2158
+ checkPreload = function($link) {
2159
+ var curriedPreload, delay;
2160
+ delay = parseInt(u.presentAttr($link, 'up-delay')) || config.preloadDelay;
2161
+ if (!$link.is($waitingLink)) {
2162
+ $waitingLink = $link;
2163
+ cancelDelay();
2164
+ curriedPreload = function() {
2165
+ return preload($link);
2166
+ };
2167
+ return startDelay(curriedPreload, delay);
2168
+ }
2169
+ };
2170
+ startDelay = function(block, delay) {
2171
+ return delayTimer = setTimeout(block, delay);
2172
+ };
2173
+ cancelDelay = function() {
2174
+ clearTimeout(delayTimer);
2175
+ return delayTimer = null;
2176
+ };
2177
+ preload = function(link, options) {
2178
+ options = u.options();
2179
+ ensureIsIdempotent(options);
2180
+ u.debug("Preloading %o", link);
2181
+ options.preload = true;
2182
+ return up.link.follow(link, options);
2183
+ };
2184
+ reset = function() {
2185
+ cancelDelay();
2186
+ return cache = {};
2187
+ };
2188
+ up.bus.on('framework:reset', reset);
2189
+
2190
+ /*
2191
+ @method [up-preload]
2192
+ @ujs
2193
+ */
2194
+ up.on('mouseover mousedown touchstart', '[up-preload]', function(event, $element) {
2195
+ if (!up.link.childClicked(event, $element)) {
2196
+ return checkPreload(up.link.resolve($element));
2197
+ }
2198
+ });
2199
+ return {
2200
+ preload: preload,
2201
+ ajax: ajax,
2202
+ get: get,
2203
+ set: set,
2204
+ alias: alias,
2205
+ clear: clear,
2206
+ defaults: defaults
2207
+ };
2208
+ })();
2209
+
2210
+ }).call(this);
2211
+
1849
2212
  /**
1850
2213
  Linking to page fragments
1851
2214
  =========================
@@ -1923,7 +2286,7 @@ Read on
1923
2286
 
1924
2287
  (function() {
1925
2288
  up.link = (function() {
1926
- var follow, resolve, resolveUrl, u, visit;
2289
+ var childClicked, follow, resolve, u, visit;
1927
2290
  u = up.util;
1928
2291
 
1929
2292
  /**
@@ -1940,7 +2303,7 @@ Read on
1940
2303
  up.visit('/users')
1941
2304
  */
1942
2305
  visit = function(url, options) {
1943
- console.log("up.visit", url);
2306
+ u.debug("Visiting " + url);
1944
2307
  return up.replace('body', url, options);
1945
2308
  };
1946
2309
 
@@ -1970,36 +2333,63 @@ Read on
1970
2333
  return up.replace(selector, url, options);
1971
2334
  };
1972
2335
  resolve = function(element) {
1973
- var $element;
2336
+ var $element, followAttr;
1974
2337
  $element = $(element);
1975
- if ($element.is('a') || u.presentAttr($element, 'up-follow')) {
2338
+ followAttr = $element.attr('up-follow');
2339
+ if ($element.is('a') || (u.isPresent(followAttr) && !u.castsToTrue(followAttr))) {
1976
2340
  return $element;
1977
2341
  } else {
1978
2342
  return $element.find('a:first');
1979
2343
  }
1980
2344
  };
1981
- resolveUrl = function(element) {
1982
- var $link;
1983
- if ($link = resolve(element)) {
1984
- return u.option($link.attr('href'), $link.attr('up-follow'));
1985
- }
1986
- };
1987
2345
 
1988
2346
  /**
1989
2347
  Follows this link via AJAX and replaces a CSS selector in the current page
1990
- with corresponding elements from a new page fetched from the server.
2348
+ with corresponding elements from a new page fetched from the server:
1991
2349
 
1992
2350
  <a href="/users" up-target=".main">User list</a>
1993
2351
 
2352
+ By also adding an `up-instant` attribute, the page will be fetched
2353
+ on `mousedown` instead of `click`, making the interaction even faster:
2354
+
2355
+ <a href="/users" up-target=".main" up-instant>User list</a>
2356
+
2357
+ Note that using `[up-instant]` will prevent a user from canceling a link
2358
+ click by moving the mouse away from the interaction area. However, for
2359
+ navigation actions this isn't needed. E.g. popular operation
2360
+ systems switch tabs on `mousedown`.
2361
+
1994
2362
  @method a[up-target]
1995
2363
  @ujs
1996
2364
  @param {String} up-target
1997
2365
  The CSS selector to replace
2366
+ @param up-instant
2367
+ If set, fetches the element on `mousedown` instead of `click`.
2368
+ This makes the interaction faster.
1998
2369
  */
1999
2370
  up.on('click', 'a[up-target]', function(event, $link) {
2000
2371
  event.preventDefault();
2001
- return follow($link);
2372
+ if (!$link.is('[up-instant]')) {
2373
+ return follow($link);
2374
+ }
2002
2375
  });
2376
+ up.on('mousedown', 'a[up-target][up-instant]', function(event, $link) {
2377
+ if (event.which === 1) {
2378
+ event.preventDefault();
2379
+ return follow($link);
2380
+ }
2381
+ });
2382
+
2383
+ /*
2384
+ @method up.link.childClicked
2385
+ @private
2386
+ */
2387
+ childClicked = function(event, $link) {
2388
+ var $target, $targetLink;
2389
+ $target = $(event.target);
2390
+ $targetLink = $target.closest('a, [up-follow]');
2391
+ return $targetLink.length && $link.find($targetLink).length;
2392
+ };
2003
2393
 
2004
2394
  /**
2005
2395
  If applied on a link, Follows this link via AJAX and replaces the
@@ -2021,16 +2411,20 @@ Read on
2021
2411
  @method [up-follow]
2022
2412
  @ujs
2023
2413
  @param {String} [up-follow]
2414
+ @param up-instant
2415
+ If set, fetches the element on `mousedown` instead of `click`.
2416
+ This makes the interaction faster.
2024
2417
  */
2025
2418
  up.on('click', '[up-follow]', function(event, $element) {
2026
- var childLinkClicked;
2027
- childLinkClicked = function() {
2028
- var $target, $targetLink;
2029
- $target = $(event.target);
2030
- $targetLink = $target.closest('a, [up-follow]');
2031
- return $targetLink.length && $element.find($targetLink).length;
2032
- };
2033
- if (!childLinkClicked()) {
2419
+ if (!childClicked(event, $element)) {
2420
+ event.preventDefault();
2421
+ if (!$element.is('[up-instant]')) {
2422
+ return follow(resolve($element));
2423
+ }
2424
+ }
2425
+ });
2426
+ up.on('mousedown', '[up-follow][up-instant]', function(event, $element) {
2427
+ if (!childClicked(event, $element) && event.which === 1) {
2034
2428
  event.preventDefault();
2035
2429
  return follow(resolve($element));
2036
2430
  }
@@ -2039,7 +2433,7 @@ Read on
2039
2433
  visit: visit,
2040
2434
  follow: follow,
2041
2435
  resolve: resolve,
2042
- resolveUrl: resolveUrl
2436
+ childClicked: childClicked
2043
2437
  };
2044
2438
  })();
2045
2439
 
@@ -2064,7 +2458,7 @@ We need to work on this page:
2064
2458
  - Explain how to display form errors
2065
2459
  - Explain that the server needs to send 2xx or 5xx status codes so
2066
2460
  Up.js can decide whether the form submission was successful
2067
- - Explain that the server needs to send an `X-Up-Current-Location` header
2461
+ - Explain that the server needs to send `X-Up-Location` and `X-Up-Method` headers
2068
2462
  if an successful form submission resulted in a redirect
2069
2463
  - Examples
2070
2464
 
@@ -2191,7 +2585,7 @@ We need to work on this page:
2191
2585
  } else if (options.change) {
2192
2586
  callback = options.change;
2193
2587
  } else {
2194
- u.error('observe: No change callback given');
2588
+ u.error('up.observe: No change callback given');
2195
2589
  }
2196
2590
  callbackPromise = u.resolvedPromise();
2197
2591
  nextCallback = null;
@@ -2231,7 +2625,7 @@ We need to work on this page:
2231
2625
  clearTimer = function() {
2232
2626
  return clearTimeout(callbackTimer);
2233
2627
  };
2234
- changeEvents = up.browser.canInputEvent() ? 'input' : 'keypress paste cut change click propertychange';
2628
+ changeEvents = up.browser.canInputEvent() ? 'input change' : 'input change keypress paste cut click propertychange';
2235
2629
  $field.on(changeEvents, check);
2236
2630
  check();
2237
2631
  return clearTimer;
@@ -2364,7 +2758,7 @@ We need to work on this page:
2364
2758
  bottom: linkBox.top
2365
2759
  };
2366
2760
  default:
2367
- return u.error("Unknown origin", origin);
2761
+ return u.error("Unknown origin %o", origin);
2368
2762
  }
2369
2763
  })();
2370
2764
  $popup.attr('up-origin', origin);
@@ -2600,12 +2994,12 @@ We need to work on this page:
2600
2994
 
2601
2995
  /**
2602
2996
  @method up.modal.defaults
2603
- @param {Number} options.width
2604
- @param {Number} options.height
2605
- @param {String|Function(config)} options.template
2606
- @param {String} options.closeLabel
2607
- @param {String} options.openAnimation
2608
- @param {String} options.closeAnimation
2997
+ @param {Number} [options.width]
2998
+ @param {Number} [options.height]
2999
+ @param {String|Function(config)} [options.template]
3000
+ @param {String} [options.closeLabel]
3001
+ @param {String} [options.openAnimation]
3002
+ @param {String} [options.closeAnimation]
2609
3003
  */
2610
3004
  defaults = function(options) {
2611
3005
  return u.extend(config, options);
@@ -2755,7 +3149,6 @@ We need to work on this page:
2755
3149
  });
2756
3150
  up.bus.on('fragment:ready', function($fragment) {
2757
3151
  if (!$fragment.closest('.up-modal').length) {
2758
- console.log('fragment inserted', $fragment, $fragment.closest('.up-modal'));
2759
3152
  return autoclose();
2760
3153
  }
2761
3154
  });
@@ -2825,7 +3218,7 @@ We need to work on this page:
2825
3218
  top: linkBox.top + linkBox.height
2826
3219
  };
2827
3220
  default:
2828
- return u.error("Unknown origin", origin);
3221
+ return u.error("Unknown origin %o", origin);
2829
3222
  }
2830
3223
  })();
2831
3224
  $tooltip.attr('up-origin', origin);
@@ -2850,7 +3243,7 @@ We need to work on this page:
2850
3243
  options = {};
2851
3244
  }
2852
3245
  $link = $(linkOrSelector);
2853
- html = u.option(options.html, $link.attr('up-tooltip'));
3246
+ html = u.option(options.html, $link.attr('up-tooltip'), $link.attr('title'));
2854
3247
  origin = u.option(options.origin, $link.attr('up-origin'), 'top');
2855
3248
  animation = u.option(options.animation, $link.attr('up-animation'), 'fade-in');
2856
3249
  close();
@@ -2883,6 +3276,10 @@ We need to work on this page:
2883
3276
 
2884
3277
  <a href="/decks" up-tooltip="Show all decks">Decks</a>
2885
3278
 
3279
+ You can also make an existing `title` attribute appear as a tooltip:
3280
+
3281
+ <a href="/decks" title="Show all decks" up-tooltip>Decks</a>
3282
+
2886
3283
  @method [up-tooltip]
2887
3284
  @ujs
2888
3285
  */
@@ -2937,11 +3334,11 @@ From Up's point of view the "current" location is either:
2937
3334
 
2938
3335
  (function() {
2939
3336
  up.navigation = (function() {
2940
- var CLASS_ACTIVE, CLASS_CURRENT, SELECTOR_ACTIVE, SELECTOR_SECTION, enlargeClickArea, locationChanged, normalizeUrl, sectionClicked, u, unmarkActive;
3337
+ var CLASS_ACTIVE, CLASS_CURRENT, SELECTOR_ACTIVE, SELECTOR_SECTION, enlargeClickArea, locationChanged, normalizeUrl, sectionClicked, sectionUrls, u, unmarkActive;
2941
3338
  u = up.util;
2942
3339
  CLASS_ACTIVE = 'up-active';
2943
3340
  CLASS_CURRENT = 'up-current';
2944
- SELECTOR_SECTION = 'a[href], a[up-target], [up-follow], [up-modal], [up-popup]';
3341
+ SELECTOR_SECTION = 'a[href], a[up-target], [up-follow], [up-modal], [up-popup], [up-source]';
2945
3342
  SELECTOR_ACTIVE = "." + CLASS_ACTIVE;
2946
3343
  normalizeUrl = function(url) {
2947
3344
  if (u.isPresent(url)) {
@@ -2951,17 +3348,29 @@ From Up's point of view the "current" location is either:
2951
3348
  });
2952
3349
  }
2953
3350
  };
3351
+ sectionUrls = function($section) {
3352
+ var $link, attr, url, urls, _i, _len, _ref;
3353
+ urls = [];
3354
+ if ($link = up.link.resolve($section)) {
3355
+ _ref = ['href', 'up-follow', 'up-source'];
3356
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
3357
+ attr = _ref[_i];
3358
+ if (url = u.presentAttr($link, attr)) {
3359
+ url = normalizeUrl(url);
3360
+ urls.push(url);
3361
+ }
3362
+ }
3363
+ }
3364
+ return urls;
3365
+ };
2954
3366
  locationChanged = function() {
2955
- var modalLocation, popupLocation, windowLocation;
2956
- windowLocation = normalizeUrl(up.browser.url());
2957
- modalLocation = normalizeUrl(up.modal.source());
2958
- popupLocation = normalizeUrl(up.popup.source());
3367
+ var currentUrls;
3368
+ currentUrls = u.stringSet([normalizeUrl(up.browser.url()), normalizeUrl(up.modal.source()), normalizeUrl(up.popup.source())]);
2959
3369
  return u.each($(SELECTOR_SECTION), function(section) {
2960
- var $section, url;
3370
+ var $section, urls;
2961
3371
  $section = $(section);
2962
- url = up.link.resolveUrl($section);
2963
- url = normalizeUrl(url);
2964
- if (url === windowLocation || url === modalLocation || url === popupLocation) {
3372
+ urls = sectionUrls($section);
3373
+ if (currentUrls.includesAny(urls)) {
2965
3374
  return $section.addClass(CLASS_CURRENT);
2966
3375
  } else {
2967
3376
  return $section.removeClass(CLASS_CURRENT);
@@ -3040,6 +3449,8 @@ TODO: Write some documentation
3040
3449
 
3041
3450
  }).call(this);
3042
3451
  (function() {
3452
+ up.browser.ensureRecentJquery();
3453
+
3043
3454
  if (up.browser.isSupported()) {
3044
3455
  up.browser.ensureConsoleExists();
3045
3456
  up.bus.emit('framework:ready');