soca 0.3.1 → 0.3.2
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.
- data/Gemfile +4 -0
- data/Gemfile.lock +2 -0
- data/lib/soca.rb +1 -1
- data/lib/soca/plugin.rb +4 -0
- data/lib/soca/plugins/credentials.rb +75 -0
- data/lib/soca/pusher.rb +1 -0
- data/lib/soca/templates/Jimfile +2 -2
- data/lib/soca/templates/hooks/before_build.rb +5 -3
- data/lib/soca/templates/js/vendor/jquery-1.7.1.js +9266 -0
- data/lib/soca/templates/js/vendor/{sammy-0.6.3.js → sammy-0.7.1.js} +262 -122
- data/soca.gemspec +6 -4
- data/test/helper.rb +1 -0
- data/test/test_credentials_plugin.rb +40 -0
- data/test/testapp/.couchapprc +3 -0
- metadata +27 -25
- data/lib/soca/templates/js/vendor/jquery-1.4.2.js +0 -6240
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// name: sammy
|
|
2
|
-
// version: 0.
|
|
2
|
+
// version: 0.7.1
|
|
3
3
|
|
|
4
4
|
// Sammy.js / http://sammyjs.org
|
|
5
5
|
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
var Sammy,
|
|
9
9
|
PATH_REPLACER = "([^\/]+)",
|
|
10
10
|
PATH_NAME_MATCHER = /:([\w\d]+)/g,
|
|
11
|
-
QUERY_STRING_MATCHER = /\?([^#]*)
|
|
11
|
+
QUERY_STRING_MATCHER = /\?([^#]*)?$/,
|
|
12
12
|
// mainly for making `arguments` an Array
|
|
13
13
|
_makeArray = function(nonarray) { return Array.prototype.slice.call(nonarray); },
|
|
14
14
|
// borrowed from jQuery
|
|
15
15
|
_isFunction = function( obj ) { return Object.prototype.toString.call(obj) === "[object Function]"; },
|
|
16
16
|
_isArray = function( obj ) { return Object.prototype.toString.call(obj) === "[object Array]"; },
|
|
17
|
-
_decode = function( str ) { return decodeURIComponent(str.replace(/\+/g, ' ')); },
|
|
17
|
+
_decode = function( str ) { return decodeURIComponent((str || '').replace(/\+/g, ' ')); },
|
|
18
18
|
_encode = encodeURIComponent,
|
|
19
19
|
_escapeHTML = function(s) {
|
|
20
20
|
return String(s).replace(/&(?!\w+;)/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
return function(path, callback) { return this.route.apply(this, [verb, path, callback]); };
|
|
24
24
|
},
|
|
25
25
|
_template_cache = {},
|
|
26
|
+
_has_history = !!(window.history && history.pushState),
|
|
26
27
|
loggers = [];
|
|
27
28
|
|
|
28
29
|
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
// // returns the app at #main or a new app
|
|
44
45
|
// Sammy('#main')
|
|
45
46
|
//
|
|
46
|
-
// //
|
|
47
|
+
// // equivalent to "new Sammy.Application", except appends to apps
|
|
47
48
|
// Sammy();
|
|
48
49
|
// Sammy(function() { ... });
|
|
49
50
|
//
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
app.use(plugin);
|
|
65
66
|
});
|
|
66
67
|
}
|
|
67
|
-
// if the selector changes make sure the
|
|
68
|
+
// if the selector changes make sure the reference in Sammy.apps changes
|
|
68
69
|
if (app.element_selector != selector) {
|
|
69
70
|
delete Sammy.apps[selector];
|
|
70
71
|
}
|
|
@@ -73,7 +74,7 @@
|
|
|
73
74
|
}
|
|
74
75
|
};
|
|
75
76
|
|
|
76
|
-
Sammy.VERSION = '0.
|
|
77
|
+
Sammy.VERSION = '0.7.1';
|
|
77
78
|
|
|
78
79
|
// Add to the global logger pool. Takes a function that accepts an
|
|
79
80
|
// unknown number of arguments and should print them or send them somewhere
|
|
@@ -113,7 +114,7 @@
|
|
|
113
114
|
makeArray: _makeArray,
|
|
114
115
|
isFunction: _isFunction,
|
|
115
116
|
isArray: _isArray
|
|
116
|
-
})
|
|
117
|
+
});
|
|
117
118
|
|
|
118
119
|
// Sammy.Object is the base for all other Sammy classes. It provides some useful
|
|
119
120
|
// functionality, including cloning, iterating, etc.
|
|
@@ -171,7 +172,7 @@
|
|
|
171
172
|
|
|
172
173
|
// Checks if the object has a value at `key` and that the value is not empty
|
|
173
174
|
has: function(key) {
|
|
174
|
-
return this[key] && $.trim(this[key].toString())
|
|
175
|
+
return this[key] && $.trim(this[key].toString()) !== '';
|
|
175
176
|
},
|
|
176
177
|
|
|
177
178
|
// convenience method to join as many arguments as you want
|
|
@@ -201,86 +202,136 @@
|
|
|
201
202
|
}
|
|
202
203
|
});
|
|
203
204
|
|
|
204
|
-
// The
|
|
205
|
+
// The DefaultLocationProxy is the default location proxy for all Sammy applications.
|
|
205
206
|
// A location proxy is a prototype that conforms to a simple interface. The purpose
|
|
206
207
|
// of a location proxy is to notify the Sammy.Application its bound to when the location
|
|
207
|
-
// or 'external state' changes.
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
|
|
208
|
+
// or 'external state' changes.
|
|
209
|
+
//
|
|
210
|
+
// The `DefaultLocationProxy` watches for changes to the path of the current window and
|
|
211
|
+
// is also able to set the path based on changes in the application. It does this by
|
|
212
|
+
// using different methods depending on what is available in the current browser. In
|
|
213
|
+
// the latest and greatest browsers it used the HTML5 History API and the `pushState`
|
|
214
|
+
// `popState` events/methods. This allows you to use Sammy to serve a site behind normal
|
|
215
|
+
// URI paths as opposed to the older default of hash (#) based routing. Because the server
|
|
216
|
+
// can interpret the changed path on a refresh or re-entry, though, it requires additional
|
|
217
|
+
// support on the server side. If you'd like to force disable HTML5 history support, please
|
|
218
|
+
// use the `disable_push_state` setting on `Sammy.Application`. If pushState support
|
|
219
|
+
// is enabled, `DefaultLocationProxy` also binds to all links on the page. If a link is clicked
|
|
220
|
+
// that matches the current set of routes, the URL is changed using pushState instead of
|
|
221
|
+
// fully setting the location and the app is notified of the change.
|
|
222
|
+
//
|
|
223
|
+
// If the browser does not have support for HTML5 History, `DefaultLocationProxy` automatically
|
|
224
|
+
// falls back to the older hash based routing. The newest browsers (IE, Safari > 4, FF >= 3.6)
|
|
225
|
+
// support a 'onhashchange' DOM event, thats fired whenever the location.hash changes.
|
|
226
|
+
// In this situation the DefaultLocationProxy just binds to this event and delegates it to
|
|
227
|
+
// the application. In the case of older browsers a poller is set up to track changes to the
|
|
228
|
+
// hash.
|
|
229
|
+
Sammy.DefaultLocationProxy = function(app, run_interval_every) {
|
|
217
230
|
this.app = app;
|
|
218
231
|
// set is native to false and start the poller immediately
|
|
219
232
|
this.is_native = false;
|
|
233
|
+
this.has_history = _has_history;
|
|
220
234
|
this._startPolling(run_interval_every);
|
|
221
235
|
};
|
|
222
236
|
|
|
223
|
-
Sammy.
|
|
224
|
-
|
|
237
|
+
Sammy.DefaultLocationProxy.fullPath = function(location_obj) {
|
|
238
|
+
// Bypass the `window.location.hash` attribute. If a question mark
|
|
239
|
+
// appears in the hash IE6 will strip it and all of the following
|
|
240
|
+
// characters from `window.location.hash`.
|
|
241
|
+
var matches = location_obj.toString().match(/^[^#]*(#.+)$/);
|
|
242
|
+
var hash = matches ? matches[1] : '';
|
|
243
|
+
return [location_obj.pathname, location_obj.search, hash].join('');
|
|
244
|
+
};
|
|
245
|
+
Sammy.DefaultLocationProxy.prototype = {
|
|
225
246
|
// bind the proxy events to the current app.
|
|
226
247
|
bind: function() {
|
|
227
|
-
var proxy = this, app = this.app;
|
|
248
|
+
var proxy = this, app = this.app, lp = Sammy.DefaultLocationProxy;
|
|
228
249
|
$(window).bind('hashchange.' + this.app.eventNamespace(), function(e, non_native) {
|
|
229
250
|
// if we receive a native hash change event, set the proxy accordingly
|
|
230
251
|
// and stop polling
|
|
231
252
|
if (proxy.is_native === false && !non_native) {
|
|
232
|
-
Sammy.log('native hash change exists, using');
|
|
233
253
|
proxy.is_native = true;
|
|
234
|
-
window.clearInterval(
|
|
254
|
+
window.clearInterval(lp._interval);
|
|
235
255
|
}
|
|
236
256
|
app.trigger('location-changed');
|
|
237
257
|
});
|
|
238
|
-
if (!
|
|
239
|
-
|
|
258
|
+
if (_has_history && !app.disable_push_state) {
|
|
259
|
+
// bind to popstate
|
|
260
|
+
$(window).bind('popstate.' + this.app.eventNamespace(), function(e) {
|
|
261
|
+
app.trigger('location-changed');
|
|
262
|
+
});
|
|
263
|
+
// bind to link clicks that have routes
|
|
264
|
+
$('a').live('click.history-' + this.app.eventNamespace(), function(e) {
|
|
265
|
+
if (e.isDefaultPrevented()) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
var full_path = lp.fullPath(this);
|
|
269
|
+
if (this.hostname == window.location.hostname && app.lookupRoute('get', full_path)) {
|
|
270
|
+
e.preventDefault();
|
|
271
|
+
proxy.setLocation(full_path);
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (!lp._bindings) {
|
|
277
|
+
lp._bindings = 0;
|
|
240
278
|
}
|
|
241
|
-
|
|
279
|
+
lp._bindings++;
|
|
242
280
|
},
|
|
243
281
|
|
|
244
282
|
// unbind the proxy events from the current app
|
|
245
283
|
unbind: function() {
|
|
246
284
|
$(window).unbind('hashchange.' + this.app.eventNamespace());
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
285
|
+
$(window).unbind('popstate.' + this.app.eventNamespace());
|
|
286
|
+
$('a').die('click.history-' + this.app.eventNamespace());
|
|
287
|
+
Sammy.DefaultLocationProxy._bindings--;
|
|
288
|
+
if (Sammy.DefaultLocationProxy._bindings <= 0) {
|
|
289
|
+
window.clearInterval(Sammy.DefaultLocationProxy._interval);
|
|
250
290
|
}
|
|
251
291
|
},
|
|
252
292
|
|
|
253
293
|
// get the current location from the hash.
|
|
254
294
|
getLocation: function() {
|
|
255
|
-
|
|
256
|
-
// appears in the hash IE6 will strip it and all of the following
|
|
257
|
-
// characters from `window.location.hash`.
|
|
258
|
-
var matches = window.location.toString().match(/^[^#]*(#.+)$/);
|
|
259
|
-
return matches ? matches[1] : '';
|
|
295
|
+
return Sammy.DefaultLocationProxy.fullPath(window.location);
|
|
260
296
|
},
|
|
261
297
|
|
|
262
298
|
// set the current location to `new_location`
|
|
263
299
|
setLocation: function(new_location) {
|
|
264
|
-
|
|
300
|
+
if (/^([^#\/]|$)/.test(new_location)) { // non-prefixed url
|
|
301
|
+
if (_has_history) {
|
|
302
|
+
new_location = '/' + new_location;
|
|
303
|
+
} else {
|
|
304
|
+
new_location = '#!/' + new_location;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (new_location != this.getLocation()) {
|
|
308
|
+
// HTML5 History exists and new_location is a full path
|
|
309
|
+
if (_has_history && /^\//.test(new_location)) {
|
|
310
|
+
history.pushState({ path: new_location }, window.title, new_location);
|
|
311
|
+
this.app.trigger('location-changed');
|
|
312
|
+
} else {
|
|
313
|
+
return (window.location = new_location);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
265
316
|
},
|
|
266
317
|
|
|
267
318
|
_startPolling: function(every) {
|
|
268
319
|
// set up interval
|
|
269
320
|
var proxy = this;
|
|
270
|
-
if (!Sammy.
|
|
321
|
+
if (!Sammy.DefaultLocationProxy._interval) {
|
|
271
322
|
if (!every) { every = 10; }
|
|
272
323
|
var hashCheck = function() {
|
|
273
324
|
var current_location = proxy.getLocation();
|
|
274
|
-
if (
|
|
275
|
-
current_location != Sammy.
|
|
325
|
+
if (typeof Sammy.DefaultLocationProxy._last_location == 'undefined' ||
|
|
326
|
+
current_location != Sammy.DefaultLocationProxy._last_location) {
|
|
276
327
|
window.setTimeout(function() {
|
|
277
328
|
$(window).trigger('hashchange', [true]);
|
|
278
329
|
}, 0);
|
|
279
330
|
}
|
|
280
|
-
Sammy.
|
|
331
|
+
Sammy.DefaultLocationProxy._last_location = current_location;
|
|
281
332
|
};
|
|
282
333
|
hashCheck();
|
|
283
|
-
Sammy.
|
|
334
|
+
Sammy.DefaultLocationProxy._interval = window.setInterval(hashCheck, every);
|
|
284
335
|
}
|
|
285
336
|
}
|
|
286
337
|
};
|
|
@@ -304,9 +355,9 @@
|
|
|
304
355
|
if (_isFunction(app_function)) {
|
|
305
356
|
app_function.apply(this, [this]);
|
|
306
357
|
}
|
|
307
|
-
// set the location proxy if not defined to the default (
|
|
358
|
+
// set the location proxy if not defined to the default (DefaultLocationProxy)
|
|
308
359
|
if (!this._location_proxy) {
|
|
309
|
-
this.setLocationProxy(new Sammy.
|
|
360
|
+
this.setLocationProxy(new Sammy.DefaultLocationProxy(this, this.run_interval_every));
|
|
310
361
|
}
|
|
311
362
|
if (this.debug) {
|
|
312
363
|
this.bindToAllEvents(function(e, data) {
|
|
@@ -335,18 +386,22 @@
|
|
|
335
386
|
// When set to true, logs all of the default events using `log()`
|
|
336
387
|
debug: false,
|
|
337
388
|
|
|
338
|
-
// When set to true, and the error() handler is not
|
|
389
|
+
// When set to true, and the error() handler is not overridden, will actually
|
|
339
390
|
// raise JS errors in routes (500) and when routes can't be found (404)
|
|
340
391
|
raise_errors: false,
|
|
341
392
|
|
|
342
393
|
// The time in milliseconds that the URL is queried for changes
|
|
343
394
|
run_interval_every: 50,
|
|
344
395
|
|
|
396
|
+
// if using the `DefaultLocationProxy` setting this to true will force the app to use
|
|
397
|
+
// traditional hash based routing as opposed to the new HTML5 PushState support
|
|
398
|
+
disable_push_state: false,
|
|
399
|
+
|
|
345
400
|
// The default template engine to use when using `partial()` in an
|
|
346
401
|
// `EventContext`. `template_engine` can either be a string that
|
|
347
402
|
// corresponds to the name of a method/helper on EventContext or it can be a function
|
|
348
403
|
// that takes two arguments, the content of the unrendered partial and an optional
|
|
349
|
-
// JS object that contains interpolation data. Template engine is only called/
|
|
404
|
+
// JS object that contains interpolation data. Template engine is only called/referred
|
|
350
405
|
// to if the extension of the partial is null or unknown. See `partial()`
|
|
351
406
|
// for more information
|
|
352
407
|
template_engine: null,
|
|
@@ -395,7 +450,7 @@
|
|
|
395
450
|
// });
|
|
396
451
|
//
|
|
397
452
|
// If plugin is passed as a string it assumes your are trying to load
|
|
398
|
-
// Sammy."Plugin". This is the
|
|
453
|
+
// Sammy."Plugin". This is the preferred way of loading core Sammy plugins
|
|
399
454
|
// as it allows for better error-messaging.
|
|
400
455
|
//
|
|
401
456
|
// ### Example
|
|
@@ -430,9 +485,9 @@
|
|
|
430
485
|
},
|
|
431
486
|
|
|
432
487
|
// Sets the location proxy for the current app. By default this is set to
|
|
433
|
-
// a new `Sammy.
|
|
488
|
+
// a new `Sammy.DefaultLocationProxy` on initialization. However, you can set
|
|
434
489
|
// the location_proxy inside you're app function to give your app a custom
|
|
435
|
-
// location mechanism. See `Sammy.
|
|
490
|
+
// location mechanism. See `Sammy.DefaultLocationProxy` and `Sammy.DataLocationProxy`
|
|
436
491
|
// for examples.
|
|
437
492
|
//
|
|
438
493
|
// `setLocationProxy()` takes an initialized location proxy.
|
|
@@ -456,6 +511,12 @@
|
|
|
456
511
|
}
|
|
457
512
|
},
|
|
458
513
|
|
|
514
|
+
// provide log() override for inside an app that includes the relevant application element_selector
|
|
515
|
+
log: function() {
|
|
516
|
+
Sammy.log.apply(Sammy, Array.prototype.concat.apply([this.element_selector],arguments));
|
|
517
|
+
},
|
|
518
|
+
|
|
519
|
+
|
|
459
520
|
// `route()` is the main method for defining routes within an application.
|
|
460
521
|
// For great detail on routes, check out:
|
|
461
522
|
// [http://sammyjs.org/docs/routes](http://sammyjs.org/docs/routes)
|
|
@@ -469,7 +530,7 @@
|
|
|
469
530
|
// the first argument is the path, the second is the callback and the verb
|
|
470
531
|
// is assumed to be 'any'.
|
|
471
532
|
// * `path` A Regexp or a String representing the path to match to invoke this verb.
|
|
472
|
-
// * `callback` A Function that is called/evaluated
|
|
533
|
+
// * `callback` A Function that is called/evaluated when the route is run see: `runRoute()`.
|
|
473
534
|
// It is also possible to pass a string as the callback, which is looked up as the name
|
|
474
535
|
// of a method on the application.
|
|
475
536
|
//
|
|
@@ -499,7 +560,7 @@
|
|
|
499
560
|
param_names.push(path_match[1]);
|
|
500
561
|
}
|
|
501
562
|
// replace with the path replacement
|
|
502
|
-
path = new RegExp(
|
|
563
|
+
path = new RegExp(path.replace(PATH_NAME_MATCHER, PATH_REPLACER) + "$");
|
|
503
564
|
}
|
|
504
565
|
// lookup callback
|
|
505
566
|
if (typeof callback == 'string') {
|
|
@@ -570,7 +631,7 @@
|
|
|
570
631
|
return ['sammy-app', this.namespace].join('-');
|
|
571
632
|
},
|
|
572
633
|
|
|
573
|
-
// Works just like `jQuery.fn.bind()` with a couple
|
|
634
|
+
// Works just like `jQuery.fn.bind()` with a couple notable differences.
|
|
574
635
|
//
|
|
575
636
|
// * It binds all events to the application element
|
|
576
637
|
// * All events are bound within the `eventNamespace()`
|
|
@@ -683,7 +744,7 @@
|
|
|
683
744
|
// that take a single argument `callback` which is the entire route
|
|
684
745
|
// execution path wrapped up in a closure. This means you can decide whether
|
|
685
746
|
// or not to proceed with execution by not invoking `callback` or,
|
|
686
|
-
// more
|
|
747
|
+
// more usefully wrapping callback inside the result of an asynchronous execution.
|
|
687
748
|
//
|
|
688
749
|
// ### Example
|
|
689
750
|
//
|
|
@@ -821,7 +882,7 @@
|
|
|
821
882
|
this._running = true;
|
|
822
883
|
// set last location
|
|
823
884
|
this.last_location = null;
|
|
824
|
-
if (this.getLocation()
|
|
885
|
+
if (!(/\#(.+)/.test(this.getLocation())) && typeof start_url != 'undefined') {
|
|
825
886
|
this.setLocation(start_url);
|
|
826
887
|
}
|
|
827
888
|
// check url
|
|
@@ -847,7 +908,7 @@
|
|
|
847
908
|
},
|
|
848
909
|
|
|
849
910
|
// The opposite of `run()`, un-binds all event listeners and intervals
|
|
850
|
-
// `run()`
|
|
911
|
+
// `run()` Automatically binds a `onunload` event to run this when
|
|
851
912
|
// the document is closed.
|
|
852
913
|
unload: function() {
|
|
853
914
|
if (!this.isRunning()) { return false; }
|
|
@@ -880,7 +941,7 @@
|
|
|
880
941
|
});
|
|
881
942
|
// next, bind to listener names (only if they dont exist in APP_EVENTS)
|
|
882
943
|
$.each(this.listeners.keys(true), function(i, name) {
|
|
883
|
-
if (app.APP_EVENTS
|
|
944
|
+
if ($.inArray(name, app.APP_EVENTS) == -1) {
|
|
884
945
|
app.bind(name, callback);
|
|
885
946
|
}
|
|
886
947
|
});
|
|
@@ -896,15 +957,16 @@
|
|
|
896
957
|
// Given a verb and a String path, will return either a route object or false
|
|
897
958
|
// if a matching route can be found within the current defined set.
|
|
898
959
|
lookupRoute: function(verb, path) {
|
|
899
|
-
var app = this, routed = false;
|
|
900
|
-
this.trigger('lookup-route', {verb: verb, path: path});
|
|
960
|
+
var app = this, routed = false, i = 0, l, route;
|
|
901
961
|
if (typeof this.routes[verb] != 'undefined') {
|
|
902
|
-
|
|
962
|
+
l = this.routes[verb].length;
|
|
963
|
+
for (; i < l; i++) {
|
|
964
|
+
route = this.routes[verb][i];
|
|
903
965
|
if (app.routablePath(path).match(route.path)) {
|
|
904
966
|
routed = route;
|
|
905
|
-
|
|
967
|
+
break;
|
|
906
968
|
}
|
|
907
|
-
}
|
|
969
|
+
}
|
|
908
970
|
}
|
|
909
971
|
return routed;
|
|
910
972
|
},
|
|
@@ -913,7 +975,7 @@
|
|
|
913
975
|
// possible URL params and then invokes the route's callback within a new
|
|
914
976
|
// `Sammy.EventContext`. If the route can not be found, it calls
|
|
915
977
|
// `notFound()`. If `raise_errors` is set to `true` and
|
|
916
|
-
// the `error()` has not been
|
|
978
|
+
// the `error()` has not been overridden, it will throw an actual JS
|
|
917
979
|
// error.
|
|
918
980
|
//
|
|
919
981
|
// You probably will never have to call this directly.
|
|
@@ -1020,7 +1082,7 @@
|
|
|
1020
1082
|
// // match against a path string
|
|
1021
1083
|
// app.contextMatchesOptions(context, '#/mypath'); //=> true
|
|
1022
1084
|
// app.contextMatchesOptions(context, '#/otherpath'); //=> false
|
|
1023
|
-
// //
|
|
1085
|
+
// // equivalent to
|
|
1024
1086
|
// app.contextMatchesOptions(context, {only: {path:'#/mypath'}}); //=> true
|
|
1025
1087
|
// app.contextMatchesOptions(context, {only: {path:'#/otherpath'}}); //=> false
|
|
1026
1088
|
// // match against a path regexp
|
|
@@ -1035,11 +1097,20 @@
|
|
|
1035
1097
|
// // match all except a path
|
|
1036
1098
|
// app.contextMatchesOptions(context, {except: {path:'#/otherpath'}}); //=> true
|
|
1037
1099
|
// app.contextMatchesOptions(context, {except: {path:'#/mypath'}}); //=> false
|
|
1100
|
+
// // match multiple paths
|
|
1101
|
+
// app.contextMatchesOptions(context, {path: ['#/mypath', '#/otherpath']}); //=> true
|
|
1102
|
+
// app.contextMatchesOptions(context, {path: ['#/otherpath', '#/thirdpath']}); //=> false
|
|
1103
|
+
// // equivalent to
|
|
1104
|
+
// app.contextMatchesOptions(context, {only: {path: ['#/mypath', '#/otherpath']}}); //=> true
|
|
1105
|
+
// app.contextMatchesOptions(context, {only: {path: ['#/otherpath', '#/thirdpath']}}); //=> false
|
|
1106
|
+
// // match all except multiple paths
|
|
1107
|
+
// app.contextMatchesOptions(context, {except: {path: ['#/mypath', '#/otherpath']}}); //=> false
|
|
1108
|
+
// app.contextMatchesOptions(context, {except: {path: ['#/otherpath', '#/thirdpath']}}); //=> true
|
|
1038
1109
|
//
|
|
1039
1110
|
contextMatchesOptions: function(context, match_options, positive) {
|
|
1040
1111
|
// empty options always match
|
|
1041
1112
|
var options = match_options;
|
|
1042
|
-
if (typeof options === 'undefined' || options
|
|
1113
|
+
if (typeof options === 'undefined' || $.isPlainObject(options)) {
|
|
1043
1114
|
return true;
|
|
1044
1115
|
}
|
|
1045
1116
|
if (typeof positive === 'undefined') {
|
|
@@ -1049,6 +1120,17 @@
|
|
|
1049
1120
|
if (typeof options === 'string' || _isFunction(options.test)) {
|
|
1050
1121
|
options = {path: options};
|
|
1051
1122
|
}
|
|
1123
|
+
// Do we have to match against multiple paths?
|
|
1124
|
+
if (_isArray(options.path)){
|
|
1125
|
+
var results, numopt, opts;
|
|
1126
|
+
results = [];
|
|
1127
|
+
for (numopt in options.path){
|
|
1128
|
+
opts = $.extend({}, options, {path: options.path[numopt]});
|
|
1129
|
+
results.push(this.contextMatchesOptions(context, opts));
|
|
1130
|
+
}
|
|
1131
|
+
var matched = $.inArray(true, results) > -1 ? true : false;
|
|
1132
|
+
return positive ? matched : !matched;
|
|
1133
|
+
}
|
|
1052
1134
|
if (options.only) {
|
|
1053
1135
|
return this.contextMatchesOptions(context, options.only, true);
|
|
1054
1136
|
} else if (options.except) {
|
|
@@ -1056,28 +1138,31 @@
|
|
|
1056
1138
|
}
|
|
1057
1139
|
var path_matched = true, verb_matched = true;
|
|
1058
1140
|
if (options.path) {
|
|
1059
|
-
//
|
|
1060
|
-
if (_isFunction(options.path.test)) {
|
|
1061
|
-
|
|
1062
|
-
} else {
|
|
1063
|
-
path_matched = (options.path.toString() === context.path);
|
|
1141
|
+
// weird regexp test
|
|
1142
|
+
if (!_isFunction(options.path.test)) {
|
|
1143
|
+
options.path = new RegExp(options.path.toString() + '$');
|
|
1064
1144
|
}
|
|
1145
|
+
path_matched = options.path.test(context.path);
|
|
1065
1146
|
}
|
|
1066
1147
|
if (options.verb) {
|
|
1067
|
-
|
|
1148
|
+
if(typeof options.verb === 'string') {
|
|
1149
|
+
verb_matched = options.verb === context.verb;
|
|
1150
|
+
} else {
|
|
1151
|
+
verb_matched = options.verb.indexOf(context.verb) > -1;
|
|
1152
|
+
}
|
|
1068
1153
|
}
|
|
1069
1154
|
return positive ? (verb_matched && path_matched) : !(verb_matched && path_matched);
|
|
1070
1155
|
},
|
|
1071
1156
|
|
|
1072
1157
|
|
|
1073
1158
|
// Delegates to the `location_proxy` to get the current location.
|
|
1074
|
-
// See `Sammy.
|
|
1159
|
+
// See `Sammy.DefaultLocationProxy` for more info on location proxies.
|
|
1075
1160
|
getLocation: function() {
|
|
1076
1161
|
return this._location_proxy.getLocation();
|
|
1077
1162
|
},
|
|
1078
1163
|
|
|
1079
1164
|
// Delegates to the `location_proxy` to set the current location.
|
|
1080
|
-
// See `Sammy.
|
|
1165
|
+
// See `Sammy.DefaultLocationProxy` for more info on location proxies.
|
|
1081
1166
|
//
|
|
1082
1167
|
// ### Arguments
|
|
1083
1168
|
//
|
|
@@ -1096,23 +1181,29 @@
|
|
|
1096
1181
|
// var app = $.sammy(function() {
|
|
1097
1182
|
//
|
|
1098
1183
|
// // implements a 'fade out'/'fade in'
|
|
1099
|
-
// this.swap = function(content) {
|
|
1100
|
-
// this
|
|
1101
|
-
//
|
|
1102
|
-
//
|
|
1103
|
-
//
|
|
1104
|
-
//
|
|
1105
|
-
//
|
|
1184
|
+
// this.swap = function(content, callback) {
|
|
1185
|
+
// var context = this;
|
|
1186
|
+
// context.$element().fadeOut('slow', function() {
|
|
1187
|
+
// context.$element().html(content);
|
|
1188
|
+
// context.$element().fadeIn('slow', function() {
|
|
1189
|
+
// if (callback) {
|
|
1190
|
+
// callback.apply();
|
|
1191
|
+
// }
|
|
1192
|
+
// });
|
|
1193
|
+
// });
|
|
1194
|
+
// };
|
|
1106
1195
|
//
|
|
1107
1196
|
// });
|
|
1108
1197
|
//
|
|
1109
|
-
swap: function(content) {
|
|
1110
|
-
|
|
1198
|
+
swap: function(content, callback) {
|
|
1199
|
+
var $el = this.$element().html(content);
|
|
1200
|
+
if (_isFunction(callback)) { callback(content); }
|
|
1201
|
+
return $el;
|
|
1111
1202
|
},
|
|
1112
1203
|
|
|
1113
1204
|
// a simple global cache for templates. Uses the same semantics as
|
|
1114
1205
|
// `Sammy.Cache` and `Sammy.Storage` so can easily be replaced with
|
|
1115
|
-
// a
|
|
1206
|
+
// a persistent storage that lasts beyond the current request.
|
|
1116
1207
|
templateCache: function(key, value) {
|
|
1117
1208
|
if (typeof value != 'undefined') {
|
|
1118
1209
|
return _template_cache[key] = value;
|
|
@@ -1126,7 +1217,7 @@
|
|
|
1126
1217
|
return _template_cache = {};
|
|
1127
1218
|
},
|
|
1128
1219
|
|
|
1129
|
-
// This
|
|
1220
|
+
// This throws a '404 Not Found' error by invoking `error()`.
|
|
1130
1221
|
// Override this method or `error()` to provide custom
|
|
1131
1222
|
// 404 behavior (i.e redirecting to / or showing a warning)
|
|
1132
1223
|
notFound: function(verb, path) {
|
|
@@ -1178,16 +1269,18 @@
|
|
|
1178
1269
|
var $form, path, verb, params, returned;
|
|
1179
1270
|
this.trigger('check-form-submission', {form: form});
|
|
1180
1271
|
$form = $(form);
|
|
1181
|
-
path = $form.attr('action');
|
|
1272
|
+
path = $form.attr('action') || '';
|
|
1182
1273
|
verb = this._getFormVerb($form);
|
|
1183
1274
|
this.log('_checkFormSubmission', $form, path, verb);
|
|
1184
1275
|
if (verb === 'get') {
|
|
1185
|
-
|
|
1276
|
+
params = this._serializeFormParams($form);
|
|
1277
|
+
if (params !== '') { path += '?' + params; }
|
|
1278
|
+
this.setLocation(path);
|
|
1186
1279
|
returned = false;
|
|
1187
1280
|
} else {
|
|
1188
1281
|
params = $.extend({}, this._parseFormParams($form));
|
|
1189
1282
|
returned = this.runRoute(verb, path, params, form.get(0));
|
|
1190
|
-
}
|
|
1283
|
+
}
|
|
1191
1284
|
return (typeof returned == 'undefined') ? false : returned;
|
|
1192
1285
|
},
|
|
1193
1286
|
|
|
@@ -1233,7 +1326,7 @@
|
|
|
1233
1326
|
},
|
|
1234
1327
|
|
|
1235
1328
|
_parseParamPair: function(params, key, value) {
|
|
1236
|
-
if (params[key]) {
|
|
1329
|
+
if (typeof params[key] !== 'undefined') {
|
|
1237
1330
|
if (_isArray(params[key])) {
|
|
1238
1331
|
params[key].push(value);
|
|
1239
1332
|
} else {
|
|
@@ -1256,11 +1349,11 @@
|
|
|
1256
1349
|
});
|
|
1257
1350
|
|
|
1258
1351
|
// `Sammy.RenderContext` is an object that makes sequential template loading,
|
|
1259
|
-
// rendering and interpolation seamless even when dealing with
|
|
1352
|
+
// rendering and interpolation seamless even when dealing with asynchronous
|
|
1260
1353
|
// operations.
|
|
1261
1354
|
//
|
|
1262
1355
|
// `RenderContext` objects are not usually created directly, rather they are
|
|
1263
|
-
//
|
|
1356
|
+
// instantiated from an `Sammy.EventContext` by using `render()`, `load()` or
|
|
1264
1357
|
// `partial()` which all return `RenderContext` objects.
|
|
1265
1358
|
//
|
|
1266
1359
|
// `RenderContext` methods always returns a modified `RenderContext`
|
|
@@ -1269,7 +1362,7 @@
|
|
|
1269
1362
|
// The core magic is in the `then()` method which puts the callback passed as
|
|
1270
1363
|
// an argument into a queue to be executed once the previous callback is complete.
|
|
1271
1364
|
// All the methods of `RenderContext` are wrapped in `then()` which allows you
|
|
1272
|
-
// to queue up methods by chaining, but
|
|
1365
|
+
// to queue up methods by chaining, but maintaining a guaranteed execution order
|
|
1273
1366
|
// even with remote calls to fetch templates.
|
|
1274
1367
|
//
|
|
1275
1368
|
Sammy.RenderContext = function(event_context) {
|
|
@@ -1290,9 +1383,9 @@
|
|
|
1290
1383
|
// is executed immediately.
|
|
1291
1384
|
//
|
|
1292
1385
|
// The value returned from the callback is stored in `content` for the
|
|
1293
|
-
//
|
|
1386
|
+
// subsequent operation. If you return `false`, the queue will pause, and
|
|
1294
1387
|
// the next callback in the queue will not be executed until `next()` is
|
|
1295
|
-
// called. This allows for the
|
|
1388
|
+
// called. This allows for the guaranteed order of execution while working
|
|
1296
1389
|
// with async operations.
|
|
1297
1390
|
//
|
|
1298
1391
|
// If then() is passed a string instead of a function, the string is looked
|
|
@@ -1380,7 +1473,7 @@
|
|
|
1380
1473
|
},
|
|
1381
1474
|
|
|
1382
1475
|
// Load a template into the context.
|
|
1383
|
-
// The `location` can either be a string
|
|
1476
|
+
// The `location` can either be a string specifying the remote path to the
|
|
1384
1477
|
// file, a jQuery object, or a DOM element.
|
|
1385
1478
|
//
|
|
1386
1479
|
// No interpolation happens by default, the content is stored in
|
|
@@ -1429,7 +1522,7 @@
|
|
|
1429
1522
|
$.ajax($.extend({
|
|
1430
1523
|
url: location,
|
|
1431
1524
|
data: {},
|
|
1432
|
-
dataType: is_json ? 'json' :
|
|
1525
|
+
dataType: is_json ? 'json' : 'text',
|
|
1433
1526
|
type: 'get',
|
|
1434
1527
|
success: function(data) {
|
|
1435
1528
|
if (should_cache) {
|
|
@@ -1457,6 +1550,28 @@
|
|
|
1457
1550
|
});
|
|
1458
1551
|
},
|
|
1459
1552
|
|
|
1553
|
+
// Load partials
|
|
1554
|
+
//
|
|
1555
|
+
// ### Example
|
|
1556
|
+
//
|
|
1557
|
+
// this.loadPartials({mypartial: '/path/to/partial'});
|
|
1558
|
+
//
|
|
1559
|
+
loadPartials: function(partials) {
|
|
1560
|
+
var name;
|
|
1561
|
+
if(partials) {
|
|
1562
|
+
this.partials = this.partials || {};
|
|
1563
|
+
for(name in partials) {
|
|
1564
|
+
(function(context, name) {
|
|
1565
|
+
context.load(partials[name])
|
|
1566
|
+
.then(function(template) {
|
|
1567
|
+
this.partials[name] = template;
|
|
1568
|
+
});
|
|
1569
|
+
})(this, name);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return this;
|
|
1573
|
+
},
|
|
1574
|
+
|
|
1460
1575
|
// `load()` a template and then `interpolate()` it with data.
|
|
1461
1576
|
//
|
|
1462
1577
|
// ### Example
|
|
@@ -1465,20 +1580,28 @@
|
|
|
1465
1580
|
// this.render('mytemplate.template', {name: 'test'});
|
|
1466
1581
|
// });
|
|
1467
1582
|
//
|
|
1468
|
-
render: function(location, data, callback) {
|
|
1583
|
+
render: function(location, data, callback, partials) {
|
|
1469
1584
|
if (_isFunction(location) && !data) {
|
|
1470
1585
|
return this.then(location);
|
|
1471
1586
|
} else {
|
|
1472
|
-
return this.
|
|
1587
|
+
return this.loadPartials(partials)
|
|
1588
|
+
.load(location)
|
|
1473
1589
|
.interpolate(data, location)
|
|
1474
1590
|
.then(callback);
|
|
1475
1591
|
}
|
|
1476
1592
|
},
|
|
1477
1593
|
|
|
1478
|
-
// `render()` the
|
|
1594
|
+
// `render()` the `location` with `data` and then `swap()` the
|
|
1479
1595
|
// app's `$element` with the rendered content.
|
|
1480
|
-
partial: function(location, data) {
|
|
1481
|
-
|
|
1596
|
+
partial: function(location, data, callback) {
|
|
1597
|
+
if (_isFunction(callback)) {
|
|
1598
|
+
return this.render(location, data).swap(callback);
|
|
1599
|
+
} else if (!callback && _isFunction(data)) {
|
|
1600
|
+
// invoked as partial(location, callback)
|
|
1601
|
+
return this.render(location).swap(data);
|
|
1602
|
+
} else {
|
|
1603
|
+
return this.render(location, data).swap();
|
|
1604
|
+
}
|
|
1482
1605
|
},
|
|
1483
1606
|
|
|
1484
1607
|
// defers the call of function to occur in order of the render queue.
|
|
@@ -1510,7 +1633,7 @@
|
|
|
1510
1633
|
});
|
|
1511
1634
|
},
|
|
1512
1635
|
|
|
1513
|
-
//
|
|
1636
|
+
// iterates over an array, applying the callback for each item item. the
|
|
1514
1637
|
// callback takes the same style of arguments as `jQuery.each()` (index, item).
|
|
1515
1638
|
// The return value of each callback is collected as a single string and stored
|
|
1516
1639
|
// as `content` to be used in the next iteration of the `RenderContext`.
|
|
@@ -1579,15 +1702,16 @@
|
|
|
1579
1702
|
engine = this.next_engine;
|
|
1580
1703
|
this.next_engine = false;
|
|
1581
1704
|
}
|
|
1582
|
-
var rendered = context.event_context.interpolate(content, data, engine);
|
|
1705
|
+
var rendered = context.event_context.interpolate(content, data, engine, this.partials);
|
|
1583
1706
|
return retain ? prev + rendered : rendered;
|
|
1584
1707
|
});
|
|
1585
1708
|
},
|
|
1586
1709
|
|
|
1587
|
-
//
|
|
1588
|
-
swap: function() {
|
|
1710
|
+
// Swap the return contents ensuring order. See `Application#swap`
|
|
1711
|
+
swap: function(callback) {
|
|
1589
1712
|
return this.then(function(content) {
|
|
1590
|
-
this.event_context.swap(content);
|
|
1713
|
+
this.event_context.swap(content, callback);
|
|
1714
|
+
return content;
|
|
1591
1715
|
}).trigger('changed', {});
|
|
1592
1716
|
},
|
|
1593
1717
|
|
|
@@ -1614,12 +1738,13 @@
|
|
|
1614
1738
|
},
|
|
1615
1739
|
|
|
1616
1740
|
// trigger the event in the order of the event context. Same semantics
|
|
1617
|
-
// as `Sammy.EventContext#trigger()`. If data is
|
|
1741
|
+
// as `Sammy.EventContext#trigger()`. If data is omitted, `content`
|
|
1618
1742
|
// is sent as `{content: content}`
|
|
1619
1743
|
trigger: function(name, data) {
|
|
1620
1744
|
return this.then(function(content) {
|
|
1621
1745
|
if (typeof data == 'undefined') { data = {content: content}; }
|
|
1622
1746
|
this.event_context.trigger(name, data);
|
|
1747
|
+
return content;
|
|
1623
1748
|
});
|
|
1624
1749
|
}
|
|
1625
1750
|
|
|
@@ -1674,10 +1799,10 @@
|
|
|
1674
1799
|
// Look up a templating engine within the current app and context.
|
|
1675
1800
|
// `engine` can be one of the following:
|
|
1676
1801
|
//
|
|
1677
|
-
// * a function: should conform to `function(content, data) { return
|
|
1802
|
+
// * a function: should conform to `function(content, data) { return interpolated; }`
|
|
1678
1803
|
// * a template path: 'template.ejs', looks up the extension to match to
|
|
1679
1804
|
// the `ejs()` helper
|
|
1680
|
-
// * a string
|
|
1805
|
+
// * a string referring to the helper: "mustache" => `mustache()`
|
|
1681
1806
|
//
|
|
1682
1807
|
// If no engine is found, use the app's default `template_engine`
|
|
1683
1808
|
//
|
|
@@ -1687,7 +1812,7 @@
|
|
|
1687
1812
|
if (_isFunction(engine)) { return engine; }
|
|
1688
1813
|
// lookup engine name by path extension
|
|
1689
1814
|
engine = (engine || context.app.template_engine).toString();
|
|
1690
|
-
if ((engine_match = engine.match(/\.([
|
|
1815
|
+
if ((engine_match = engine.match(/\.([^\.\?\#]+)$/))) {
|
|
1691
1816
|
engine = engine_match[1];
|
|
1692
1817
|
}
|
|
1693
1818
|
// set the engine to the default template engine if no match is found
|
|
@@ -1703,8 +1828,8 @@
|
|
|
1703
1828
|
|
|
1704
1829
|
// using the template `engine` found with `engineFor()`, interpolate the
|
|
1705
1830
|
// `data` into `content`
|
|
1706
|
-
interpolate: function(content, data, engine) {
|
|
1707
|
-
return this.engineFor(engine).apply(this, [content, data]);
|
|
1831
|
+
interpolate: function(content, data, engine, partials) {
|
|
1832
|
+
return this.engineFor(engine).apply(this, [content, data, partials]);
|
|
1708
1833
|
},
|
|
1709
1834
|
|
|
1710
1835
|
// Create and return a `Sammy.RenderContext` calling `render()` on it.
|
|
@@ -1720,8 +1845,8 @@
|
|
|
1720
1845
|
// .appendTo('ul');
|
|
1721
1846
|
// // appends the rendered content to $('ul')
|
|
1722
1847
|
//
|
|
1723
|
-
render: function(location, data, callback) {
|
|
1724
|
-
return new Sammy.RenderContext(this).render(location, data, callback);
|
|
1848
|
+
render: function(location, data, callback, partials) {
|
|
1849
|
+
return new Sammy.RenderContext(this).render(location, data, callback, partials);
|
|
1725
1850
|
},
|
|
1726
1851
|
|
|
1727
1852
|
// Create and return a `Sammy.RenderContext` calling `renderEach()` on it.
|
|
@@ -1747,10 +1872,10 @@
|
|
|
1747
1872
|
return new Sammy.RenderContext(this).load(location, options, callback);
|
|
1748
1873
|
},
|
|
1749
1874
|
|
|
1750
|
-
// `render()` the
|
|
1875
|
+
// `render()` the `location` with `data` and then `swap()` the
|
|
1751
1876
|
// app's `$element` with the rendered content.
|
|
1752
|
-
partial: function(location, data) {
|
|
1753
|
-
return new Sammy.RenderContext(this).partial(location, data);
|
|
1877
|
+
partial: function(location, data, callback) {
|
|
1878
|
+
return new Sammy.RenderContext(this).partial(location, data, callback);
|
|
1754
1879
|
},
|
|
1755
1880
|
|
|
1756
1881
|
// create a new `Sammy.RenderContext` calling `send()` with an arbitrary
|
|
@@ -1767,22 +1892,37 @@
|
|
|
1767
1892
|
// ### Example
|
|
1768
1893
|
//
|
|
1769
1894
|
// redirect('#/other/route');
|
|
1770
|
-
// //
|
|
1895
|
+
// // equivalent to
|
|
1771
1896
|
// redirect('#', 'other', 'route');
|
|
1772
1897
|
//
|
|
1773
1898
|
redirect: function() {
|
|
1774
1899
|
var to, args = _makeArray(arguments),
|
|
1775
|
-
current_location = this.app.getLocation()
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1900
|
+
current_location = this.app.getLocation(),
|
|
1901
|
+
l = args.length;
|
|
1902
|
+
if (l > 1) {
|
|
1903
|
+
var i = 0, paths = [], pairs = [], params = {}, has_params = false;
|
|
1904
|
+
for (; i < l; i++) {
|
|
1905
|
+
if (typeof args[i] == 'string') {
|
|
1906
|
+
paths.push(args[i]);
|
|
1907
|
+
} else {
|
|
1908
|
+
$.extend(params, args[i]);
|
|
1909
|
+
has_params = true;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
to = paths.join('/');
|
|
1913
|
+
if (has_params) {
|
|
1914
|
+
for (var k in params) {
|
|
1915
|
+
pairs.push(this.app._encodeFormPair(k, params[k]));
|
|
1916
|
+
}
|
|
1917
|
+
to += '?' + pairs.join('&');
|
|
1918
|
+
}
|
|
1779
1919
|
} else {
|
|
1780
1920
|
to = args[0];
|
|
1781
1921
|
}
|
|
1782
1922
|
this.trigger('redirect', {to: to});
|
|
1783
1923
|
this.app.last_location = [this.verb, this.path];
|
|
1784
1924
|
this.app.setLocation(to);
|
|
1785
|
-
if (
|
|
1925
|
+
if (new RegExp(to).test(current_location)) {
|
|
1786
1926
|
this.app.trigger('location-changed');
|
|
1787
1927
|
}
|
|
1788
1928
|
},
|
|
@@ -1800,8 +1940,8 @@
|
|
|
1800
1940
|
},
|
|
1801
1941
|
|
|
1802
1942
|
// A shortcut to app's `swap()`
|
|
1803
|
-
swap: function(contents) {
|
|
1804
|
-
return this.app.swap(contents);
|
|
1943
|
+
swap: function(contents, callback) {
|
|
1944
|
+
return this.app.swap(contents, callback);
|
|
1805
1945
|
},
|
|
1806
1946
|
|
|
1807
1947
|
// Raises a possible `notFound()` error for the current path.
|