canjs-rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ (function(can, window, undefined){
2
+ var URI = steal.URI || steal.File;
3
+
4
+ can.Control.getFolder = function() {
5
+ return can.underscore(this.fullName.replace(/\./g, "/")).replace("/Controllers", "");
6
+ };
7
+
8
+ can.Control._calculatePosition = function( Class, view ) {
9
+ var classParts = Class.fullName.split('.'),
10
+ classPartsWithoutPrefix = classParts.slice(0);
11
+ classPartsWithoutPrefix.splice(0, 2),
12
+ action_name = "init"; // Remove prefix (usually 2 elements)
13
+
14
+ var hasControllers = (classParts.length > 2) && classParts[1] == 'Controllers',
15
+ path = hasControllers? can.underscore(classParts[0]): can.underscore(classParts.join("/")),
16
+ controller_name = can.underscore(classPartsWithoutPrefix.join('/')).toLowerCase(),
17
+ suffix = (typeof view == "string" && /\.[\w\d]+$/.test(view)) ? "" : can.view.ext;
18
+
19
+ //calculate view
20
+ if ( typeof view == "string" ) {
21
+ if ( view.substr(0, 2) == "//" ) { //leave where it is
22
+ } else {
23
+ view = "//" + URI(path).join( 'views/' + (view.indexOf('/') !== -1 ? view : (hasControllers ? controller_name + '/' : "") + view)) + suffix;
24
+ }
25
+ } else if (!view ) {
26
+ view = "//" + URI(path).join('views/' + (hasControllers ? controller_name + '/' : "") + action_name.replace(/\.|#/g, '').replace(/ /g, '_'))+ suffix;
27
+ }
28
+ return view;
29
+ };
30
+
31
+ var calculateHelpers = function( myhelpers ) {
32
+ var helpers = {};
33
+ if ( myhelpers ) {
34
+ if ( can.isArray(myhelpers) ) {
35
+ for ( var h = 0; h < myhelpers.length; h++ ) {
36
+ can.extend(helpers, myhelpers[h]);
37
+ }
38
+ }
39
+ else {
40
+ can.extend(helpers, myhelpers);
41
+ }
42
+ } else {
43
+ if ( this._default_helpers ) {
44
+ helpers = this._default_helpers;
45
+ }
46
+
47
+ //load from name
48
+ var current = window;
49
+ var parts = this.constructor.fullName.split(/\./);
50
+ for ( var i = 0; i < parts.length; i++ ) {
51
+ if(current){
52
+ if ( typeof current.Helpers == 'object' ) {
53
+ can.extend(helpers, current.Helpers);
54
+ }
55
+ current = current[parts[i]];
56
+ }
57
+ }
58
+
59
+ if (current && typeof current.Helpers == 'object' ) {
60
+ can.extend(helpers, current.Helpers);
61
+ }
62
+
63
+ this._default_helpers = helpers;
64
+ }
65
+ return helpers;
66
+ };
67
+
68
+ can.Control.prototype.view = function( view, data, myhelpers ) {
69
+ //shift args if no view is provided
70
+ if ( typeof view != "string" && !myhelpers ) {
71
+ myhelpers = data;
72
+ data = view;
73
+ view = null;
74
+ }
75
+
76
+ //guess from controller name
77
+ view = can.Control._calculatePosition(this.constructor, view, this.called);
78
+
79
+ //calculate data
80
+ data = data || this;
81
+
82
+ //calculate helpers
83
+ var helpers = calculateHelpers.call(this, myhelpers);
84
+
85
+ return can.view(view, data, helpers); //what about controllers in other folders?
86
+ };
87
+
88
+ })(this.can, this )
@@ -0,0 +1,1020 @@
1
+ (function(can, window, undefined){
2
+
3
+ var isArray = can.isArray,
4
+ // essentially returns an object that has all the must have comparisons ...
5
+ // must haves, do not return true when provided undefined
6
+ cleanSet = function(obj, compares){
7
+ var copy = can.extend({}, obj);
8
+ for(var prop in copy) {
9
+ var compare = compares[prop] === undefined ? compares["*"] : compares[prop];
10
+ if( same(copy[prop], undefined, compare ) ) {
11
+ delete copy[prop]
12
+ }
13
+ }
14
+ return copy;
15
+ },
16
+ propCount = function(obj){
17
+ var count = 0;
18
+ for(var prop in obj) count++;
19
+ return count;
20
+ };
21
+
22
+ /**
23
+ * @class can.Object
24
+ * @parent can.util
25
+ *
26
+ * Object contains several helper methods that
27
+ * help compare objects.
28
+ *
29
+ * ## same
30
+ *
31
+ * Returns true if two objects are similar.
32
+ *
33
+ * can.Object.same({foo: "bar"} , {bar: "foo"}) //-> false
34
+ *
35
+ * ## subset
36
+ *
37
+ * Returns true if an object is a set of another set.
38
+ *
39
+ * can.Object.subset({}, {foo: "bar"} ) //-> true
40
+ *
41
+ * ## subsets
42
+ *
43
+ * Returns the subsets of an object
44
+ *
45
+ * can.Object.subsets({userId: 20},
46
+ * [
47
+ * {userId: 20, limit: 30},
48
+ * {userId: 5},
49
+ * {}
50
+ * ])
51
+ * //-> [{userId: 20, limit: 30}]
52
+ */
53
+ can.Object = {};
54
+
55
+ /**
56
+ * @function same
57
+ * Returns if two objects are the same. It takes an optional compares object that
58
+ * can be used to make comparisons.
59
+ *
60
+ * This function does not work with objects that create circular references.
61
+ *
62
+ * ## Examples
63
+ *
64
+ * can.Object.same({name: "Justin"},
65
+ * {name: "JUSTIN"}) //-> false
66
+ *
67
+ * // ignore the name property
68
+ * can.Object.same({name: "Brian"},
69
+ * {name: "JUSTIN"},
70
+ * {name: null}) //-> true
71
+ *
72
+ * // ignore case
73
+ * can.Object.same({name: "Justin"},
74
+ * {name: "JUSTIN"},
75
+ * {name: "i"}) //-> true
76
+ *
77
+ * // deep rule
78
+ * can.Object.same({ person : { name: "Justin" } },
79
+ * { person : { name: "JUSTIN" } },
80
+ * { person : { name: "i" } }) //-> true
81
+ *
82
+ * // supplied compare function
83
+ * can.Object.same({age: "Thirty"},
84
+ * {age: 30},
85
+ * {age: function( a, b ){
86
+ * if( a == "Thirty" ) {
87
+ * a = 30
88
+ * }
89
+ * if( b == "Thirty" ) {
90
+ * b = 30
91
+ * }
92
+ * return a === b;
93
+ * }}) //-> true
94
+ *
95
+ * @param {Object} a an object to compare
96
+ * @param {Object} b an object to compare
97
+ * @param {Object} [compares] an object that indicates how to
98
+ * compare specific properties.
99
+ * Typically this is a name / value pair
100
+ *
101
+ * can.Object.same({name: "Justin"},{name: "JUSTIN"},{name: "i"})
102
+ *
103
+ * There are two compare functions that you can specify with a string:
104
+ *
105
+ * - 'i' - ignores case
106
+ * - null - ignores this property
107
+ *
108
+ * @param {Object} [deep] used internally
109
+ */
110
+ var same = can.Object.same = function(a, b, compares, aParent, bParent, deep){
111
+ var aType = typeof a,
112
+ aArray = isArray(a),
113
+ comparesType = typeof compares,
114
+ compare;
115
+
116
+ if(comparesType == 'string' || compares === null ){
117
+ compares = compareMethods[compares];
118
+ comparesType = 'function'
119
+ }
120
+ if(comparesType == 'function'){
121
+ return compares(a, b, aParent, bParent)
122
+ }
123
+ compares = compares || {};
124
+
125
+ if(a instanceof Date){
126
+ return a === b;
127
+ }
128
+ if(deep === -1){
129
+ return aType === 'object' || a === b;
130
+ }
131
+ if(aType !== typeof b || aArray !== isArray(b)){
132
+ return false;
133
+ }
134
+ if(a === b){
135
+ return true;
136
+ }
137
+ if(aArray){
138
+ if(a.length !== b.length){
139
+ return false;
140
+ }
141
+ for(var i =0; i < a.length; i ++){
142
+ compare = compares[i] === undefined ? compares["*"] : compares[i]
143
+ if(!same(a[i],b[i], a, b, compare )){
144
+ return false;
145
+ }
146
+ };
147
+ return true;
148
+ } else if(aType === "object" || aType === 'function'){
149
+ var bCopy = can.extend({}, b);
150
+ for(var prop in a){
151
+ compare = compares[prop] === undefined ? compares["*"] : compares[prop];
152
+ if(! same( a[prop], b[prop], compare , a, b, deep === false ? -1 : undefined )){
153
+ return false;
154
+ }
155
+ delete bCopy[prop];
156
+ }
157
+ // go through bCopy props ... if there is no compare .. return false
158
+ for(prop in bCopy){
159
+ if( compares[prop] === undefined ||
160
+ ! same( undefined, b[prop], compares[prop] , a, b, deep === false ? -1 : undefined )){
161
+ return false;
162
+ }
163
+ }
164
+ return true;
165
+ }
166
+ return false;
167
+ };
168
+
169
+ /**
170
+ * @function subsets
171
+ * Returns the sets in 'sets' that are a subset of checkSet
172
+ * @param {Object} checkSet
173
+ * @param {Object} sets
174
+ */
175
+ can.Object.subsets = function(checkSet, sets, compares){
176
+ var len = sets.length,
177
+ subsets = [],
178
+ checkPropCount = propCount(checkSet),
179
+ setLength;
180
+
181
+ for(var i =0; i < len; i++){
182
+ //check this subset
183
+ var set = sets[i];
184
+ if( can.Object.subset(checkSet, set, compares) ){
185
+ subsets.push(set)
186
+ }
187
+ }
188
+ return subsets;
189
+ };
190
+ /**
191
+ * @function subset
192
+ * Compares if checkSet is a subset of set
193
+ * @param {Object} checkSet
194
+ * @param {Object} set
195
+ * @param {Object} [compares]
196
+ * @param {Object} [checkPropCount]
197
+ */
198
+ can.Object.subset = function(subset, set, compares){
199
+ // go through set {type: 'folder'} and make sure every property
200
+ // is in subset {type: 'folder', parentId :5}
201
+ // then make sure that set has fewer properties
202
+ // make sure we are only checking 'important' properties
203
+ // in subset (ones that have to have a value)
204
+
205
+ var setPropCount =0,
206
+ compares = compares || {};
207
+
208
+ for(var prop in set){
209
+
210
+ if(! same(subset[prop], set[prop], compares[prop], subset, set ) ){
211
+ return false;
212
+ }
213
+ }
214
+ return true;
215
+ }
216
+
217
+
218
+ var compareMethods = {
219
+ "null" : function(){
220
+ return true;
221
+ },
222
+ i : function(a, b){
223
+ return (""+a).toLowerCase() == (""+b).toLowerCase()
224
+ }
225
+ }
226
+
227
+
228
+ ;
229
+
230
+
231
+ var updateSettings = function (settings, originalOptions) {
232
+ if (!can.fixture.on) {
233
+ return;
234
+ }
235
+
236
+ //simple wrapper for logging
237
+ var log = function () {
238
+ if (window.console && console.log) {
239
+ console.log.apply(console, Array.prototype.slice.call(arguments));
240
+ }
241
+ }
242
+
243
+ // We always need the type which can also be called method, default to GET
244
+ settings.type = settings.type || settings.method || 'GET';
245
+
246
+ // add the fixture option if programmed in
247
+ var data = overwrite(settings);
248
+
249
+ // if we don't have a fixture, do nothing
250
+ if (!settings.fixture) {
251
+ if (window.location.protocol === "file:") {
252
+ log("ajax request to " + settings.url + ", no fixture found");
253
+ }
254
+ return;
255
+ }
256
+
257
+ //if referencing something else, update the fixture option
258
+ if (typeof settings.fixture === "string" && can.fixture[settings.fixture]) {
259
+ settings.fixture = can.fixture[settings.fixture];
260
+ }
261
+
262
+ // if a string, we just point to the right url
263
+ if (typeof settings.fixture == "string") {
264
+ var url = settings.fixture;
265
+
266
+ if (/^\/\//.test(url)) {
267
+ // this lets us use rootUrl w/o having steal...
268
+ url = can.fixture.rootUrl === steal.root ?
269
+ steal.root.mapJoin(settings.fixture.substr(2)) + '' :
270
+ can.fixture.rootUrl + settings.fixture.substr(2);
271
+ }
272
+
273
+ delete settings.fixture;
274
+
275
+ //@steal-remove-start
276
+ log("looking for fixture in " + url);
277
+ //@steal-remove-end
278
+
279
+ settings.url = url;
280
+ settings.data = null;
281
+ settings.type = "GET";
282
+ if (!settings.error) {
283
+ settings.error = function (xhr, error, message) {
284
+ throw "fixtures.js Error " + error + " " + message;
285
+ };
286
+ }
287
+ }
288
+ else {
289
+ //@steal-remove-start
290
+ log("using a dynamic fixture for " + settings.type + " " + settings.url);
291
+ //@steal-remove-end
292
+
293
+ //it's a function ... add the fixture datatype so our fixture transport handles it
294
+ // TODO: make everything go here for timing and other fun stuff
295
+ // add to settings data from fixture ...
296
+ settings.dataTypes && settings.dataTypes.splice(0, 0, "fixture");
297
+
298
+ if (data && originalOptions) {
299
+ can.extend(originalOptions.data, data)
300
+ }
301
+ }
302
+ },
303
+ // A helper function that takes what's called with response
304
+ // and moves some common args around to make it easier to call
305
+ extractResponse = function(status, statusText, responses, headers) {
306
+ // if we get response(RESPONSES, HEADERS)
307
+ if(typeof status != "number"){
308
+ headers = statusText;
309
+ responses = status;
310
+ statusText = "success"
311
+ status = 200;
312
+ }
313
+ // if we get response(200, RESPONSES, HEADERS)
314
+ if(typeof statusText != "string"){
315
+ headers = responses;
316
+ responses = statusText;
317
+ statusText = "success";
318
+ }
319
+ return [status, statusText, extractResponses(this, responses), headers];
320
+ },
321
+ // If we get data instead of responses,
322
+ // make sure we provide a response type that matches the first datatype (typically json)
323
+ extractResponses = function(settings, responses){
324
+ var next = settings.dataTypes ? settings.dataTypes[0] : (settings.dataType || 'json');
325
+ if (!responses || !responses[next]) {
326
+ var tmp = {}
327
+ tmp[next] = responses;
328
+ responses = tmp;
329
+ }
330
+ return responses;
331
+ };
332
+
333
+ //used to check urls
334
+ // check if jQuery
335
+ if (can.ajaxPrefilter && can.ajaxTransport) {
336
+
337
+ // the pre-filter needs to re-route the url
338
+ can.ajaxPrefilter(updateSettings);
339
+
340
+ can.ajaxTransport("fixture", function (s, original) {
341
+ // remove the fixture from the datatype
342
+ s.dataTypes.shift();
343
+
344
+ //we'll return the result of the next data type
345
+ var timeout, stopped = false;
346
+
347
+ return {
348
+ send: function (headers, callback) {
349
+ // we'll immediately wait the delay time for all fixtures
350
+ timeout = setTimeout(function () {
351
+ // if the user wants to call success on their own, we allow it ...
352
+ var success = function() {
353
+ if(stopped === false) {
354
+ callback.apply(null, extractResponse.apply(s, arguments) );
355
+ }
356
+ },
357
+ // get the result form the fixture
358
+ result = s.fixture(original, success, headers, s);
359
+ if(result !== undefined) {
360
+ // make sure the result has the right dataType
361
+ callback(200, "success", extractResponses(s, result), {});
362
+ }
363
+ }, can.fixture.delay);
364
+ },
365
+ abort: function () {
366
+ stopped = true;
367
+ clearTimeout(timeout)
368
+ }
369
+ };
370
+ });
371
+ } else {
372
+ var AJAX = can.ajax;
373
+ can.ajax = function (settings) {
374
+ updateSettings(settings, settings);
375
+ if (settings.fixture) {
376
+ var timeout, d = new can.Deferred(),
377
+ stopped = false;
378
+
379
+ //TODO this should work with response
380
+ d.getResponseHeader = function () {
381
+ }
382
+
383
+ // call success and fail
384
+ d.then(settings.success, settings.fail);
385
+
386
+ // abort should stop the timeout and calling success
387
+ d.abort = function () {
388
+ clearTimeout(timeout);
389
+ stopped = true;
390
+ d.reject(d)
391
+ }
392
+ // set a timeout that simulates making a request ....
393
+ timeout = setTimeout(function () {
394
+ // if the user wants to call success on their own, we allow it ...
395
+ var success = function() {
396
+ var response = extractResponse.apply(settings, arguments),
397
+ status = response[0];
398
+
399
+ if ( (status >= 200 && status < 300 || status === 304) && stopped === false) {
400
+ d.resolve(response[2][settings.dataType], "success", d)
401
+ } else {
402
+ // TODO probably resolve better
403
+ d.reject(d, 'error', response[1]);
404
+ }
405
+ },
406
+ // get the result form the fixture
407
+ result = settings.fixture(settings, success, settings.headers, settings);
408
+ if(result !== undefined) {
409
+ d.resolve(result, "success", d)
410
+ }
411
+ }, can.fixture.delay);
412
+
413
+ return d;
414
+ } else {
415
+ return AJAX(settings);
416
+ }
417
+ }
418
+ }
419
+
420
+ var typeTest = /^(script|json|test|jsonp)$/,
421
+ // a list of 'overwrite' settings object
422
+ overwrites = [],
423
+ // returns the index of an overwrite function
424
+ find = function (settings, exact) {
425
+ for (var i = 0; i < overwrites.length; i++) {
426
+ if ($fixture._similar(settings, overwrites[i], exact)) {
427
+ return i;
428
+ }
429
+ }
430
+ return -1;
431
+ },
432
+ // overwrites the settings fixture if an overwrite matches
433
+ overwrite = function (settings) {
434
+ var index = find(settings);
435
+ if (index > -1) {
436
+ settings.fixture = overwrites[index].fixture;
437
+ return $fixture._getData(overwrites[index].url, settings.url)
438
+ }
439
+
440
+ },
441
+ /**
442
+ * Makes an attempt to guess where the id is at in the url and returns it.
443
+ * @param {Object} settings
444
+ */
445
+ getId = function (settings) {
446
+ var id = settings.data.id;
447
+
448
+ if (id === undefined && typeof settings.data === "number") {
449
+ id = settings.data;
450
+ }
451
+
452
+ /*
453
+ Check for id in params(if query string)
454
+ If this is just a string representation of an id, parse
455
+ if(id === undefined && typeof settings.data === "string") {
456
+ id = settings.data;
457
+ }
458
+ //*/
459
+
460
+ if (id === undefined) {
461
+ settings.url.replace(/\/(\d+)(\/|$|\.)/g, function (all, num) {
462
+ id = num;
463
+ });
464
+ }
465
+
466
+ if (id === undefined) {
467
+ id = settings.url.replace(/\/(\w+)(\/|$|\.)/g, function (all, num) {
468
+ if (num != 'update') {
469
+ id = num;
470
+ }
471
+ })
472
+ }
473
+
474
+ if (id === undefined) { // if still not set, guess a random number
475
+ id = Math.round(Math.random() * 1000)
476
+ }
477
+
478
+ return id;
479
+ };
480
+
481
+ /**
482
+ * @plugin can/util/fixture
483
+ * @test can/util/fixture/qunit.html
484
+ *
485
+ * `can.fixture` intercept an AJAX request and simulates the response with a file or function.
486
+ * Read more about the usage in the [overview can.fixture].
487
+ *
488
+ * @param {Object|String} settings Configures the AJAX requests the fixture should
489
+ * intercept. If an __object__ is passed, the object's properties and values
490
+ * are matched against the settings passed to can.ajax.
491
+ *
492
+ * If a __string__ is passed, it can be used to match the url and type. Urls
493
+ * can be templated, using <code>{NAME}</code> as wildcards.
494
+ *
495
+ * @param {Function|String} fixture The response to use for the AJAX
496
+ * request. If a __string__ url is passed, the ajax request is redirected
497
+ * to the url. If a __function__ is provided, it looks like:
498
+ *
499
+ * fixture( originalSettings, settings, callback, headers)
500
+ *
501
+ * where:
502
+ *
503
+ * - originalSettings - the orignal settings passed to can.ajax
504
+ * - settings - the settings after all filters have run
505
+ * - callback - a callback to call with the response if the fixture executes asynchronously
506
+ * - headers - request headers
507
+ *
508
+ * If __null__ is passed, and there is a fixture at settings, that fixture will be removed,
509
+ * allowing the AJAX request to behave normally.
510
+ */
511
+ var $fixture = can.fixture = function (settings, fixture) {
512
+ // if we provide a fixture ...
513
+ if (fixture !== undefined) {
514
+ if (typeof settings == 'string') {
515
+ // handle url strings
516
+ var matches = settings.match(/(GET|POST|PUT|DELETE) (.+)/i);
517
+ if (!matches) {
518
+ settings = {
519
+ url : settings
520
+ };
521
+ } else {
522
+ settings = {
523
+ url : matches[2],
524
+ type : matches[1]
525
+ };
526
+ }
527
+
528
+ }
529
+
530
+ //handle removing. An exact match if fixture was provided, otherwise, anything similar
531
+ var index = find(settings, !!fixture);
532
+ if (index > -1) {
533
+ overwrites.splice(index, 1)
534
+ }
535
+ if (fixture == null) {
536
+ return
537
+ }
538
+ settings.fixture = fixture;
539
+ overwrites.push(settings)
540
+ } else {
541
+ can.each(settings, function(fixture, url){
542
+ $fixture(url, fixture);
543
+ })
544
+ }
545
+ };
546
+ var replacer = can.replacer;
547
+
548
+ can.extend(can.fixture, {
549
+ // given ajax settings, find an overwrite
550
+ _similar : function (settings, overwrite, exact) {
551
+ if (exact) {
552
+ return can.Object.same(settings, overwrite, {fixture : null})
553
+ } else {
554
+ return can.Object.subset(settings, overwrite, can.fixture._compare)
555
+ }
556
+ },
557
+ _compare : {
558
+ url : function (a, b) {
559
+ return !!$fixture._getData(b, a)
560
+ },
561
+ fixture : null,
562
+ type : "i"
563
+ },
564
+ // gets data from a url like "/todo/{id}" given "todo/5"
565
+ _getData : function (fixtureUrl, url) {
566
+ var order = [],
567
+ fixtureUrlAdjusted = fixtureUrl.replace('.', '\\.').replace('?', '\\?'),
568
+ res = new RegExp(fixtureUrlAdjusted.replace(replacer, function (whole, part) {
569
+ order.push(part)
570
+ return "([^\/]+)"
571
+ }) + "$").exec(url),
572
+ data = {};
573
+
574
+ if (!res) {
575
+ return null;
576
+ }
577
+ res.shift();
578
+ can.each(order, function (name) {
579
+ data[name] = res.shift()
580
+ })
581
+ return data;
582
+ },
583
+
584
+ make : function (types, count, make, filter) {
585
+ /**
586
+ * @function can.fixture.make
587
+ * @parent can.fixture
588
+ *
589
+ * `can.fixture.make` is used for findAll / findOne style requests.
590
+ *
591
+ * ## With can.ajax
592
+ *
593
+ * //makes a nested list of messages
594
+ * can.fixture.make(["messages","message"], 1000,
595
+ * function(i, messages){
596
+ * return {
597
+ * subject: "This is message "+i,
598
+ * body: "Here is some text for this message",
599
+ * date: Math.floor( new Date().getTime() ),
600
+ * parentId : i < 100 ? null : Math.floor(Math.random()*i)
601
+ * }
602
+ * })
603
+ * //uses the message fixture to return messages limited by
604
+ * // offset, limit, order, etc.
605
+ * can.ajax({
606
+ * url: "messages",
607
+ * data: {
608
+ * offset: 100,
609
+ * limit: 50,
610
+ * order: ["date ASC"],
611
+ * parentId: 5},
612
+ * },
613
+ * fixture: "-messages",
614
+ * success: function( messages ) { ... }
615
+ * });
616
+ *
617
+ * ## With can.Model
618
+ *
619
+ * `can.fixture.make` returns a model store that offers `findAll`, `findOne`, `create`,
620
+ * `update` and `destroy` fixture functions you can map to a [can.Model] Ajax request.
621
+ * Consider a model like this:
622
+ *
623
+ * var Todo = can.Model({
624
+ * findAll : 'GET /todos',
625
+ * findOne : 'GET /todos/{id}',
626
+ * create : 'POST /todos',
627
+ * update : 'PUT /todos/{id}',
628
+ * destroy : 'DELETE /todos/{id}'
629
+ * }, {});
630
+ *
631
+ * And an unnamed generated fixture like this:
632
+ *
633
+ * var store = can.fixture.make(100, function(i) {
634
+ * return {
635
+ * id : i,
636
+ * name : 'Todo ' + i
637
+ * }
638
+ * });
639
+ *
640
+ * You can map can.Model requests using the return value of `can.fixture.make`:
641
+ *
642
+ * can.fixture('GET /todos', store.findAll);
643
+ * can.fixture('GET /todos/{id}', store.findOne);
644
+ * can.fixture('POST /todos', store.create);
645
+ * can.fixture('PUT /todos/{id}', store.update);
646
+ * can.fixture('DELETE /todos/{id}', store.destroy);
647
+ *
648
+ * @param {Array|String} types An array of the fixture names or the singular fixture name.
649
+ * If an array, the first item is the plural fixture name (prefixed with -) and the second
650
+ * item is the singular name. If a string, it's assumed to be the singular fixture name. Make
651
+ * will simply add s to the end of it for the plural name. If this parameter is not an array
652
+ * or a String the fixture won't be added and only return the generator object.
653
+ * @param {Number} count the number of items to create
654
+ * @param {Function} make a function that will return the JavaScript object. The
655
+ * make function is called back with the id and the current array of items.
656
+ * @param {Function} filter (optional) a function used to further filter results. Used for to simulate
657
+ * server params like searchText or startDate.
658
+ * The function should return true if the item passes the filter,
659
+ * false otherwise. For example:
660
+ *
661
+ *
662
+ * function(item, settings){
663
+ * if(settings.data.searchText){
664
+ * var regex = new RegExp("^"+settings.data.searchText)
665
+ * return regex.test(item.name);
666
+ * }
667
+ * }
668
+ *
669
+ * @return {Object} A generator object providing fixture functions for *findAll*, *findOne*, *create*,
670
+ * *update* and *destroy*.
671
+ */
672
+ var items = [], // TODO: change this to a hash
673
+ findOne = function (id) {
674
+ for (var i = 0; i < items.length; i++) {
675
+ if (id == items[i].id) {
676
+ return items[i];
677
+ }
678
+ }
679
+ },
680
+ methods = {};
681
+
682
+ if (typeof types === "string") {
683
+ types = [types + "s", types ]
684
+ } else if (!can.isArray(types)) {
685
+ filter = make;
686
+ make = count;
687
+ count = types;
688
+ }
689
+
690
+ // make all items
691
+ can.extend(methods, {
692
+ findAll : function (settings) {
693
+ //copy array of items
694
+ var retArr = items.slice(0);
695
+ settings.data = settings.data || {};
696
+ //sort using order
697
+ //order looks like ["age ASC","gender DESC"]
698
+ can.each((settings.data.order || []).slice(0).reverse(), function (name) {
699
+ var split = name.split(" ");
700
+ retArr = retArr.sort(function (a, b) {
701
+ if (split[1].toUpperCase() !== "ASC") {
702
+ if (a[split[0]] < b[split[0]]) {
703
+ return 1;
704
+ } else if (a[split[0]] == b[split[0]]) {
705
+ return 0
706
+ } else {
707
+ return -1;
708
+ }
709
+ }
710
+ else {
711
+ if (a[split[0]] < b[split[0]]) {
712
+ return -1;
713
+ } else if (a[split[0]] == b[split[0]]) {
714
+ return 0
715
+ } else {
716
+ return 1;
717
+ }
718
+ }
719
+ });
720
+ });
721
+
722
+ //group is just like a sort
723
+ can.each((settings.data.group || []).slice(0).reverse(), function (name) {
724
+ var split = name.split(" ");
725
+ retArr = retArr.sort(function (a, b) {
726
+ return a[split[0]] > b[split[0]];
727
+ });
728
+ });
729
+
730
+
731
+ var offset = parseInt(settings.data.offset, 10) || 0,
732
+ limit = parseInt(settings.data.limit, 10) || (items.length - offset),
733
+ i = 0;
734
+
735
+ //filter results if someone added an attr like parentId
736
+ for (var param in settings.data) {
737
+ i = 0;
738
+ if (settings.data[param] !== undefined && // don't do this if the value of the param is null (ignore it)
739
+ (param.indexOf("Id") != -1 || param.indexOf("_id") != -1)) {
740
+ while (i < retArr.length) {
741
+ if (settings.data[param] != retArr[i][param]) {
742
+ retArr.splice(i, 1);
743
+ } else {
744
+ i++;
745
+ }
746
+ }
747
+ }
748
+ }
749
+
750
+ if (filter) {
751
+ i = 0;
752
+ while (i < retArr.length) {
753
+ if (!filter(retArr[i], settings)) {
754
+ retArr.splice(i, 1);
755
+ } else {
756
+ i++;
757
+ }
758
+ }
759
+ }
760
+
761
+ //return data spliced with limit and offset
762
+ return {
763
+ "count" : retArr.length,
764
+ "limit" : settings.data.limit,
765
+ "offset" : settings.data.offset,
766
+ "data" : retArr.slice(offset, offset + limit)
767
+ };
768
+ },
769
+ findOne : function (orig, response) {
770
+ var item = findOne(getId(orig));
771
+ response(item ? item : undefined);
772
+ },
773
+ update : function (orig,response) {
774
+ var id = getId(orig);
775
+
776
+ // TODO: make it work with non-linear ids ..
777
+ can.extend(findOne(id), orig.data);
778
+ response({
779
+ id : getId(orig)
780
+ }, {
781
+ location : orig.url + "/" + getId(orig)
782
+ });
783
+ },
784
+ destroy : function (settings) {
785
+ var id = getId(settings);
786
+ for (var i = 0; i < items.length; i++) {
787
+ if (items[i].id == id) {
788
+ items.splice(i, 1);
789
+ break;
790
+ }
791
+ }
792
+
793
+ // TODO: make it work with non-linear ids ..
794
+ can.extend(findOne(id) || {}, settings.data);
795
+ return {};
796
+ },
797
+ create : function (settings, response) {
798
+ var item = make(items.length, items);
799
+
800
+ can.extend(item, settings.data);
801
+
802
+ if (!item.id) {
803
+ item.id = items.length;
804
+ }
805
+
806
+ items.push(item);
807
+ var id = item.id || parseInt(Math.random() * 100000, 10);
808
+ response({
809
+ id : id
810
+ }, {
811
+ location : settings.url + "/" + id
812
+ })
813
+ }
814
+ });
815
+
816
+ for (var i = 0; i < (count); i++) {
817
+ //call back provided make
818
+ var item = make(i, items);
819
+
820
+ if (!item.id) {
821
+ item.id = i;
822
+ }
823
+ items.push(item);
824
+ }
825
+
826
+ // if we have types given add them to can.fixture
827
+ if(can.isArray(types)) {
828
+ can.fixture["~" + types[0]] = items;
829
+ can.fixture["-" + types[0]] = methods.findAll;
830
+ can.fixture["-" + types[1]] = methods.findOne;
831
+ can.fixture["-" + types[1]+"Update"] = methods.update;
832
+ can.fixture["-" + types[1]+"Destroy"] = methods.destroy;
833
+ can.fixture["-" + types[1]+"Create"] = methods.create;
834
+ }
835
+
836
+ return can.extend({
837
+ getId: getId,
838
+ find : function(settings){
839
+ return findOne( getId(settings) );
840
+ }
841
+ }, methods);
842
+ },
843
+ /**
844
+ * @function can.fixture.rand
845
+ * @parent can.fixture
846
+ *
847
+ * `can.fixture.rand` creates random integers or random arrays of
848
+ * other arrays.
849
+ *
850
+ * ## Examples
851
+ *
852
+ * var rand = can.fixture.rand;
853
+ *
854
+ * // get a random integer between 0 and 10 (inclusive)
855
+ * rand(11);
856
+ *
857
+ * // get a random number between -5 and 5 (inclusive)
858
+ * rand(-5, 6);
859
+ *
860
+ * // pick a random item from an array
861
+ * rand(["j","m","v","c"],1)[0]
862
+ *
863
+ * // pick a random number of items from an array
864
+ * rand(["j","m","v","c"])
865
+ *
866
+ * // pick 2 items from an array
867
+ * rand(["j","m","v","c"],2)
868
+ *
869
+ * // pick between 2 and 3 items at random
870
+ * rand(["j","m","v","c"],2,3)
871
+ *
872
+ *
873
+ * @param {Array|Number} arr An array of items to select from.
874
+ * If a number is provided, a random number is returned.
875
+ * If min and max are not provided, a random number of items are selected
876
+ * from this array.
877
+ * @param {Number} [min] If only min is provided, min items
878
+ * are selected.
879
+ * @param {Number} [max] If min and max are provided, a random number of
880
+ * items between min and max (inclusive) is selected.
881
+ */
882
+ rand : function (arr, min, max) {
883
+ if (typeof arr == 'number') {
884
+ if (typeof min == 'number') {
885
+ return arr + Math.floor(Math.random() * (min - arr));
886
+ } else {
887
+ return Math.floor(Math.random() * arr);
888
+ }
889
+
890
+ }
891
+ var rand = arguments.callee;
892
+ // get a random set
893
+ if (min === undefined) {
894
+ return rand(arr, rand(arr.length + 1))
895
+ }
896
+ // get a random selection of arr
897
+ var res = [];
898
+ arr = arr.slice(0);
899
+ // set max
900
+ if (!max) {
901
+ max = min;
902
+ }
903
+ //random max
904
+ max = min + Math.round(rand(max - min))
905
+ for (var i = 0; i < max; i++) {
906
+ res.push(arr.splice(rand(arr.length), 1)[0])
907
+ }
908
+ return res;
909
+ },
910
+ /**
911
+ * @hide
912
+ *
913
+ * Use can.fixture.xhr to create an object that looks like an xhr object.
914
+ *
915
+ * ## Example
916
+ *
917
+ * The following example shows how the -restCreate fixture uses xhr to return
918
+ * a simulated xhr object:
919
+ * @codestart
920
+ * "-restCreate" : function( settings, cbType ) {
921
+ * switch(cbType){
922
+ * case "success":
923
+ * return [
924
+ * {id: parseInt(Math.random()*1000)},
925
+ * "success",
926
+ * can.fixture.xhr()];
927
+ * case "complete":
928
+ * return [
929
+ * can.fixture.xhr({
930
+ * getResponseHeader: function() {
931
+ * return settings.url+"/"+parseInt(Math.random()*1000);
932
+ * }
933
+ * }),
934
+ * "success"];
935
+ * }
936
+ * }
937
+ * @codeend
938
+ * @param {Object} [xhr] properties that you want to overwrite
939
+ * @return {Object} an object that looks like a successful XHR object.
940
+ */
941
+ xhr : function (xhr) {
942
+ return can.extend({}, {
943
+ abort : can.noop,
944
+ getAllResponseHeaders : function () {
945
+ return "";
946
+ },
947
+ getResponseHeader : function () {
948
+ return "";
949
+ },
950
+ open : can.noop,
951
+ overrideMimeType : can.noop,
952
+ readyState : 4,
953
+ responseText : "",
954
+ responseXML : null,
955
+ send : can.noop,
956
+ setRequestHeader : can.noop,
957
+ status : 200,
958
+ statusText : "OK"
959
+ }, xhr);
960
+ },
961
+ /**
962
+ * @attribute can.fixture.on
963
+ * @parent can.fixture
964
+ *
965
+ * `can.fixture.on` lets you programatically turn off fixtures. This is mostly used for testing.
966
+ *
967
+ * can.fixture.on = false
968
+ * Task.findAll({}, function(){
969
+ * can.fixture.on = true;
970
+ * })
971
+ */
972
+ on : true
973
+ });
974
+ /**
975
+ * @attribute can.fixture.delay
976
+ * @parent can.fixture
977
+ *
978
+ * `can.fixture.delay` indicates the delay in milliseconds between an ajax request is made and
979
+ * the success and complete handlers are called. This only sets
980
+ * functional synchronous fixtures that return a result. By default, the delay is 200ms.
981
+ *
982
+ * @codestart
983
+ * steal('can/util/fixtures').then(function(){
984
+ * can.fixture.delay = 1000;
985
+ * })
986
+ * @codeend
987
+ */
988
+ can.fixture.delay = 200;
989
+
990
+ /**
991
+ * @attribute can.fixture.rootUrl
992
+ * @parent can.fixture
993
+ *
994
+ * `can.fixture.rootUrl` contains the root URL for fixtures to use.
995
+ * If you are using StealJS it will use the Steal root
996
+ * URL by default.
997
+ */
998
+ can.fixture.rootUrl = window.steal ? steal.root : undefined;
999
+
1000
+ can.fixture["-handleFunction"] = function (settings) {
1001
+ if (typeof settings.fixture === "string" && can.fixture[settings.fixture]) {
1002
+ settings.fixture = can.fixture[settings.fixture];
1003
+ }
1004
+ if (typeof settings.fixture == "function") {
1005
+ setTimeout(function () {
1006
+ if (settings.success) {
1007
+ settings.success.apply(null, settings.fixture(settings, "success"));
1008
+ }
1009
+ if (settings.complete) {
1010
+ settings.complete.apply(null, settings.fixture(settings, "complete"));
1011
+ }
1012
+ }, can.fixture.delay);
1013
+ return true;
1014
+ }
1015
+ return false;
1016
+ };
1017
+
1018
+ //Expose this for fixture debugging
1019
+ can.fixture.overwrites = overwrites;
1020
+ })(this.can, this )