canjs-rails 0.1.0

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