canjs-rails 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +36 -0
- data/Rakefile +2 -0
- data/canjs-rails.gemspec +23 -0
- data/lib/canjs-rails.rb +1 -0
- data/lib/canjs/rails.rb +7 -0
- data/lib/canjs/rails/engine.rb +6 -0
- data/lib/canjs/rails/version.rb +6 -0
- data/vendor/assets/javascripts/can.construct.proxy.js +60 -0
- data/vendor/assets/javascripts/can.construct.super.js +44 -0
- data/vendor/assets/javascripts/can.control.plugin.js +245 -0
- data/vendor/assets/javascripts/can.control.view.js +88 -0
- data/vendor/assets/javascripts/can.fixture.js +1020 -0
- data/vendor/assets/javascripts/can.jquery.js +2995 -0
- data/vendor/assets/javascripts/can.jquery.min.js +52 -0
- data/vendor/assets/javascripts/can.observe.attributes.js +293 -0
- data/vendor/assets/javascripts/can.observe.backup.js +368 -0
- data/vendor/assets/javascripts/can.observe.delegate.js +359 -0
- data/vendor/assets/javascripts/can.observe.setter.js +58 -0
- data/vendor/assets/javascripts/can.observe.validations.js +374 -0
- data/vendor/assets/javascripts/can.view.modifiers.js +292 -0
- data/vendor/assets/javascripts/download_canjs.sh +15 -0
- metadata +108 -0
@@ -0,0 +1,2995 @@
|
|
1
|
+
(function(can, window, undefined){
|
2
|
+
// # CanJS v1.0.7
|
3
|
+
|
4
|
+
// (c) 2012 Bitovi
|
5
|
+
// MIT license
|
6
|
+
// [http://canjs.us/](http://canjs.us/)
|
7
|
+
|
8
|
+
// jquery.js
|
9
|
+
// ---------
|
10
|
+
// _jQuery node list._
|
11
|
+
$.extend( can, jQuery, {
|
12
|
+
trigger: function( obj, event, args ) {
|
13
|
+
obj.trigger ?
|
14
|
+
obj.trigger( event, args ) :
|
15
|
+
$.event.trigger( event, args, obj, true );
|
16
|
+
},
|
17
|
+
addEvent: function(ev, cb){
|
18
|
+
$([this]).bind(ev, cb)
|
19
|
+
return this;
|
20
|
+
},
|
21
|
+
removeEvent: function(ev, cb){
|
22
|
+
$([this]).unbind(ev, cb)
|
23
|
+
return this;
|
24
|
+
},
|
25
|
+
// jquery caches fragments, we always needs a new one
|
26
|
+
buildFragment : function(result, element){
|
27
|
+
var ret = $.buildFragment([result],[element]);
|
28
|
+
return ret.cacheable ? $.clone(ret.fragment) : ret.fragment
|
29
|
+
},
|
30
|
+
$: jQuery
|
31
|
+
});
|
32
|
+
|
33
|
+
// Wrap binding functions.
|
34
|
+
$.each(['bind','unbind','undelegate','delegate'],function(i,func){
|
35
|
+
can[func] = function(){
|
36
|
+
var t = this[func] ? this : $([this])
|
37
|
+
t[func].apply(t, arguments)
|
38
|
+
return this;
|
39
|
+
}
|
40
|
+
})
|
41
|
+
|
42
|
+
// Wrap modifier functions.
|
43
|
+
$.each(["append","filter","addClass","remove","data","get"], function(i,name){
|
44
|
+
can[name] = function(wrapped){
|
45
|
+
return wrapped[name].apply(wrapped, can.makeArray(arguments).slice(1))
|
46
|
+
}
|
47
|
+
})
|
48
|
+
|
49
|
+
// Memory safe destruction.
|
50
|
+
var oldClean = $.cleanData;
|
51
|
+
|
52
|
+
$.cleanData = function( elems ) {
|
53
|
+
$.each( elems, function( i, elem ) {
|
54
|
+
can.trigger(elem,"destroyed",[],false)
|
55
|
+
});
|
56
|
+
oldClean(elems);
|
57
|
+
};
|
58
|
+
|
59
|
+
can.each = function (elements, callback, context) {
|
60
|
+
var i = 0,
|
61
|
+
key;
|
62
|
+
if (elements) {
|
63
|
+
if (typeof elements.length == 'number' && elements.pop) {
|
64
|
+
elements.attr && elements.attr('length');
|
65
|
+
for (var len = elements.length; i < len; i++) {
|
66
|
+
if (callback.call(context || elements[i], elements[i], i, elements) === false) {
|
67
|
+
break;
|
68
|
+
}
|
69
|
+
}
|
70
|
+
} else {
|
71
|
+
for (key in elements) {
|
72
|
+
if (callback.call(context || elements[i], elements[key], key, elements) === false) {
|
73
|
+
break;
|
74
|
+
}
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
return elements;
|
79
|
+
}
|
80
|
+
;
|
81
|
+
|
82
|
+
// ##string.js
|
83
|
+
// _Miscellaneous string utility functions._
|
84
|
+
|
85
|
+
// Several of the methods in this plugin use code adapated from Prototype
|
86
|
+
// Prototype JavaScript framework, version 1.6.0.1.
|
87
|
+
// © 2005-2007 Sam Stephenson
|
88
|
+
var undHash = /_|-/,
|
89
|
+
colons = /==/,
|
90
|
+
words = /([A-Z]+)([A-Z][a-z])/g,
|
91
|
+
lowUp = /([a-z\d])([A-Z])/g,
|
92
|
+
dash = /([a-z\d])([A-Z])/g,
|
93
|
+
replacer = /\{([^\}]+)\}/g,
|
94
|
+
quote = /"/g,
|
95
|
+
singleQuote = /'/g,
|
96
|
+
|
97
|
+
// Returns the `prop` property from `obj`.
|
98
|
+
// If `add` is true and `prop` doesn't exist in `obj`, create it as an
|
99
|
+
// empty object.
|
100
|
+
getNext = function( obj, prop, add ) {
|
101
|
+
return prop in obj ?
|
102
|
+
obj[ prop ] :
|
103
|
+
( add && ( obj[ prop ] = {} ));
|
104
|
+
},
|
105
|
+
|
106
|
+
// Returns `true` if the object can have properties (no `null`s).
|
107
|
+
isContainer = function( current ) {
|
108
|
+
return /^f|^o/.test( typeof current );
|
109
|
+
};
|
110
|
+
|
111
|
+
can.extend(can, {
|
112
|
+
// Escapes strings for HTML.
|
113
|
+
esc : function( content ) {
|
114
|
+
return ( "" + content )
|
115
|
+
.replace(/&/g, '&')
|
116
|
+
.replace(/</g, '<')
|
117
|
+
.replace(/>/g, '>')
|
118
|
+
.replace(quote, '"')
|
119
|
+
.replace(singleQuote, "'");
|
120
|
+
},
|
121
|
+
|
122
|
+
getObject : function( name, roots, add ) {
|
123
|
+
|
124
|
+
// The parts of the name we are looking up
|
125
|
+
// `['App','Models','Recipe']`
|
126
|
+
var parts = name ? name.split('.') : [],
|
127
|
+
length = parts.length,
|
128
|
+
current,
|
129
|
+
r = 0,
|
130
|
+
ret, i;
|
131
|
+
|
132
|
+
// Make sure roots is an `array`.
|
133
|
+
roots = can.isArray(roots) ? roots : [roots || window];
|
134
|
+
|
135
|
+
if ( ! length ) {
|
136
|
+
return roots[0];
|
137
|
+
}
|
138
|
+
|
139
|
+
// For each root, mark it as current.
|
140
|
+
while( current = roots[r++] ) {
|
141
|
+
|
142
|
+
// Walk current to the 2nd to last object or until there
|
143
|
+
// is not a container.
|
144
|
+
for (i =0; i < length - 1 && isContainer( current ); i++ ) {
|
145
|
+
current = getNext( current, parts[i], add );
|
146
|
+
}
|
147
|
+
|
148
|
+
// If we can get a property from the 2nd to last object...
|
149
|
+
if( isContainer(current) ) {
|
150
|
+
|
151
|
+
// Get (and possibly set) the property.
|
152
|
+
ret = getNext(current, parts[i], add);
|
153
|
+
|
154
|
+
// If there is a value, we exit.
|
155
|
+
if ( ret !== undefined ) {
|
156
|
+
// If `add` is `false`, delete the property
|
157
|
+
if ( add === false ) {
|
158
|
+
delete current[parts[i]];
|
159
|
+
}
|
160
|
+
return ret;
|
161
|
+
|
162
|
+
}
|
163
|
+
}
|
164
|
+
}
|
165
|
+
},
|
166
|
+
// Capitalizes a string.
|
167
|
+
capitalize: function( s, cache ) {
|
168
|
+
// Used to make newId.
|
169
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
170
|
+
},
|
171
|
+
|
172
|
+
// Underscores a string.
|
173
|
+
underscore: function( s ) {
|
174
|
+
return s
|
175
|
+
.replace(colons, '/')
|
176
|
+
.replace(words, '$1_$2')
|
177
|
+
.replace(lowUp, '$1_$2')
|
178
|
+
.replace(dash, '_')
|
179
|
+
.toLowerCase();
|
180
|
+
},
|
181
|
+
// Micro-templating.
|
182
|
+
sub: function( str, data, remove ) {
|
183
|
+
|
184
|
+
var obs = [];
|
185
|
+
|
186
|
+
obs.push( str.replace( replacer, function( whole, inside ) {
|
187
|
+
|
188
|
+
// Convert inside to type.
|
189
|
+
var ob = can.getObject( inside, data, remove === undefined? remove : !remove );
|
190
|
+
|
191
|
+
// If a container, push into objs (which will return objects found).
|
192
|
+
if ( isContainer( ob ) ) {
|
193
|
+
obs.push( ob );
|
194
|
+
return "";
|
195
|
+
} else {
|
196
|
+
return "" + ob;
|
197
|
+
}
|
198
|
+
}));
|
199
|
+
|
200
|
+
return obs.length <= 1 ? obs[0] : obs;
|
201
|
+
},
|
202
|
+
|
203
|
+
// These regex's are used throughout the rest of can, so let's make
|
204
|
+
// them available.
|
205
|
+
replacer : replacer,
|
206
|
+
undHash : undHash
|
207
|
+
});
|
208
|
+
|
209
|
+
// ## construct.js
|
210
|
+
// `can.Construct`
|
211
|
+
// _This is a modified version of
|
212
|
+
// [John Resig's class](http://ejohn.org/blog/simple-javascript-inheritance/).
|
213
|
+
// It provides class level inheritance and callbacks._
|
214
|
+
|
215
|
+
// A private flag used to initialize a new class instance without
|
216
|
+
// initializing it's bindings.
|
217
|
+
var initializing = 0;
|
218
|
+
|
219
|
+
can.Construct = function() {
|
220
|
+
if (arguments.length) {
|
221
|
+
return can.Construct.extend.apply(can.Construct, arguments);
|
222
|
+
}
|
223
|
+
};
|
224
|
+
|
225
|
+
can.extend(can.Construct, {
|
226
|
+
newInstance: function() {
|
227
|
+
// Get a raw instance object (`init` is not called).
|
228
|
+
var inst = this.instance(),
|
229
|
+
arg = arguments,
|
230
|
+
args;
|
231
|
+
|
232
|
+
// Call `setup` if there is a `setup`
|
233
|
+
if ( inst.setup ) {
|
234
|
+
args = inst.setup.apply(inst, arguments);
|
235
|
+
}
|
236
|
+
|
237
|
+
// Call `init` if there is an `init`
|
238
|
+
// If `setup` returned `args`, use those as the arguments
|
239
|
+
if ( inst.init ) {
|
240
|
+
inst.init.apply(inst, args || arguments);
|
241
|
+
}
|
242
|
+
|
243
|
+
return inst;
|
244
|
+
},
|
245
|
+
// Overwrites an object with methods. Used in the `super` plugin.
|
246
|
+
// `newProps` - New properties to add.
|
247
|
+
// `oldProps` - Where the old properties might be (used with `super`).
|
248
|
+
// `addTo` - What we are adding to.
|
249
|
+
_inherit: function( newProps, oldProps, addTo ) {
|
250
|
+
can.extend(addTo || newProps, newProps || {})
|
251
|
+
},
|
252
|
+
// used for overwriting a single property.
|
253
|
+
// this should be used for patching other objects
|
254
|
+
// the super plugin overwrites this
|
255
|
+
_overwrite : function(what, oldProps, propName, val){
|
256
|
+
what[propName] = val;
|
257
|
+
},
|
258
|
+
// Set `defaults` as the merger of the parent `defaults` and this
|
259
|
+
// object's `defaults`. If you overwrite this method, make sure to
|
260
|
+
// include option merging logic.
|
261
|
+
setup: function( base, fullName ) {
|
262
|
+
this.defaults = can.extend(true,{}, base.defaults, this.defaults);
|
263
|
+
},
|
264
|
+
// Create's a new `class` instance without initializing by setting the
|
265
|
+
// `initializing` flag.
|
266
|
+
instance: function() {
|
267
|
+
|
268
|
+
// Prevents running `init`.
|
269
|
+
initializing = 1;
|
270
|
+
|
271
|
+
var inst = new this();
|
272
|
+
|
273
|
+
// Allow running `init`.
|
274
|
+
initializing = 0;
|
275
|
+
|
276
|
+
return inst;
|
277
|
+
},
|
278
|
+
// Extends classes.
|
279
|
+
extend: function( fullName, klass, proto ) {
|
280
|
+
// Figure out what was passed and normalize it.
|
281
|
+
if ( typeof fullName != 'string' ) {
|
282
|
+
proto = klass;
|
283
|
+
klass = fullName;
|
284
|
+
fullName = null;
|
285
|
+
}
|
286
|
+
|
287
|
+
if ( ! proto ) {
|
288
|
+
proto = klass;
|
289
|
+
klass = null;
|
290
|
+
}
|
291
|
+
proto = proto || {};
|
292
|
+
|
293
|
+
var _super_class = this,
|
294
|
+
_super = this.prototype,
|
295
|
+
name, shortName, namespace, prototype;
|
296
|
+
|
297
|
+
// Instantiate a base class (but only create the instance,
|
298
|
+
// don't run the init constructor).
|
299
|
+
prototype = this.instance();
|
300
|
+
|
301
|
+
// Copy the properties over onto the new prototype.
|
302
|
+
can.Construct._inherit(proto, _super, prototype);
|
303
|
+
|
304
|
+
// The dummy class constructor.
|
305
|
+
function Constructor() {
|
306
|
+
// All construction is actually done in the init method.
|
307
|
+
if ( ! initializing ) {
|
308
|
+
return this.constructor !== Constructor && arguments.length ?
|
309
|
+
// We are being called without `new` or we are extending.
|
310
|
+
arguments.callee.extend.apply(arguments.callee, arguments) :
|
311
|
+
// We are being called with `new`.
|
312
|
+
this.constructor.newInstance.apply(this.constructor, arguments);
|
313
|
+
}
|
314
|
+
}
|
315
|
+
|
316
|
+
// Copy old stuff onto class (can probably be merged w/ inherit)
|
317
|
+
for ( name in _super_class ) {
|
318
|
+
if ( _super_class.hasOwnProperty(name) ) {
|
319
|
+
Constructor[name] = _super_class[name];
|
320
|
+
}
|
321
|
+
}
|
322
|
+
|
323
|
+
// Copy new static properties on class.
|
324
|
+
can.Construct._inherit(klass, _super_class, Constructor);
|
325
|
+
|
326
|
+
// Setup namespaces.
|
327
|
+
if ( fullName ) {
|
328
|
+
|
329
|
+
var parts = fullName.split('.'),
|
330
|
+
shortName = parts.pop(),
|
331
|
+
current = can.getObject(parts.join('.'), window, true),
|
332
|
+
namespace = current,
|
333
|
+
_fullName = can.underscore(fullName.replace(/\./g, "_")),
|
334
|
+
_shortName = can.underscore(shortName);
|
335
|
+
|
336
|
+
//@steal-remove-start
|
337
|
+
if(current[shortName]){
|
338
|
+
|
339
|
+
}
|
340
|
+
//@steal-remove-end
|
341
|
+
|
342
|
+
current[shortName] = Constructor;
|
343
|
+
}
|
344
|
+
|
345
|
+
// Set things that shouldn't be overwritten.
|
346
|
+
can.extend(Constructor, {
|
347
|
+
constructor: Constructor,
|
348
|
+
prototype: prototype,
|
349
|
+
namespace: namespace,
|
350
|
+
shortName: shortName,
|
351
|
+
_shortName : _shortName,
|
352
|
+
fullName: fullName,
|
353
|
+
_fullName: _fullName
|
354
|
+
});
|
355
|
+
|
356
|
+
// Make sure our prototype looks nice.
|
357
|
+
Constructor.prototype.constructor = Constructor;
|
358
|
+
|
359
|
+
|
360
|
+
// Call the class `setup` and `init`
|
361
|
+
var t = [_super_class].concat(can.makeArray(arguments)),
|
362
|
+
args = Constructor.setup.apply(Constructor, t );
|
363
|
+
|
364
|
+
if ( Constructor.init ) {
|
365
|
+
Constructor.init.apply(Constructor, args || t );
|
366
|
+
}
|
367
|
+
|
368
|
+
return Constructor;
|
369
|
+
//
|
370
|
+
//
|
371
|
+
}
|
372
|
+
|
373
|
+
});
|
374
|
+
|
375
|
+
// ## observe.js
|
376
|
+
// `can.Observe`
|
377
|
+
// _Provides the observable pattern for JavaScript Objects._
|
378
|
+
//
|
379
|
+
// Returns `true` if something is an object with properties of its own.
|
380
|
+
var canMakeObserve = function( obj ) {
|
381
|
+
return obj && typeof obj === 'object' && !(obj instanceof Date);
|
382
|
+
},
|
383
|
+
|
384
|
+
// Removes all listeners.
|
385
|
+
unhookup = function(items, namespace){
|
386
|
+
return can.each(items, function(item){
|
387
|
+
if(item && item.unbind){
|
388
|
+
item.unbind("change" + namespace);
|
389
|
+
}
|
390
|
+
});
|
391
|
+
},
|
392
|
+
// Listens to changes on `val` and "bubbles" the event up.
|
393
|
+
// `val` - The object to listen for changes on.
|
394
|
+
// `prop` - The property name is at on.
|
395
|
+
// `parent` - The parent object of prop.
|
396
|
+
hookupBubble = function( val, prop, parent ) {
|
397
|
+
// If it's an `array` make a list, otherwise a val.
|
398
|
+
if (val instanceof Observe){
|
399
|
+
// We have an `observe` already...
|
400
|
+
// Make sure it is not listening to this already
|
401
|
+
unhookup([val], parent._namespace);
|
402
|
+
} else if ( can.isArray(val) ) {
|
403
|
+
val = new Observe.List(val);
|
404
|
+
} else {
|
405
|
+
val = new Observe(val);
|
406
|
+
}
|
407
|
+
|
408
|
+
// Listen to all changes and `batchTrigger` upwards.
|
409
|
+
val.bind("change" + parent._namespace, function( ev, attr ) {
|
410
|
+
// `batchTrigger` the type on this...
|
411
|
+
var args = can.makeArray(arguments),
|
412
|
+
ev = args.shift();
|
413
|
+
args[0] = prop === "*" ?
|
414
|
+
parent.indexOf(val)+"." + args[0] :
|
415
|
+
prop + "." + args[0];
|
416
|
+
// track objects dispatched on this observe
|
417
|
+
ev.triggeredNS = ev.triggeredNS || {};
|
418
|
+
// if it has already been dispatched exit
|
419
|
+
if (ev.triggeredNS[parent._namespace]) {
|
420
|
+
return;
|
421
|
+
}
|
422
|
+
ev.triggeredNS[parent._namespace] = true;
|
423
|
+
|
424
|
+
can.trigger(parent, ev, args);
|
425
|
+
can.trigger(parent,args[0],args);
|
426
|
+
});
|
427
|
+
|
428
|
+
return val;
|
429
|
+
},
|
430
|
+
|
431
|
+
// An `id` to track events for a given observe.
|
432
|
+
observeId = 0,
|
433
|
+
// A reference to an `array` of events that will be dispatched.
|
434
|
+
collecting = undefined,
|
435
|
+
// Call to start collecting events (`Observe` sends all events at
|
436
|
+
// once).
|
437
|
+
collect = function() {
|
438
|
+
if (!collecting ) {
|
439
|
+
collecting = [];
|
440
|
+
return true;
|
441
|
+
}
|
442
|
+
},
|
443
|
+
// Creates an event on item, but will not send immediately
|
444
|
+
// if collecting events.
|
445
|
+
// `item` - The item the event should happen on.
|
446
|
+
// `event` - The event name, ex: `change`.
|
447
|
+
// `args` - Tn array of arguments.
|
448
|
+
batchTrigger = function( item, event, args ) {
|
449
|
+
// Don't send events if initalizing.
|
450
|
+
if ( ! item._init) {
|
451
|
+
if (!collecting ) {
|
452
|
+
return can.trigger(item, event, args);
|
453
|
+
} else {
|
454
|
+
collecting.push([
|
455
|
+
item,
|
456
|
+
{
|
457
|
+
type: event,
|
458
|
+
batchNum : batchNum
|
459
|
+
},
|
460
|
+
args ] );
|
461
|
+
}
|
462
|
+
}
|
463
|
+
},
|
464
|
+
// Which batch of events this is for -- might not want to send multiple
|
465
|
+
// messages on the same batch. This is mostly for event delegation.
|
466
|
+
batchNum = 1,
|
467
|
+
// Sends all pending events.
|
468
|
+
sendCollection = function() {
|
469
|
+
var items = collecting.slice(0);
|
470
|
+
collecting = undefined;
|
471
|
+
batchNum++;
|
472
|
+
can.each(items, function( item ) {
|
473
|
+
can.trigger.apply(can, item)
|
474
|
+
})
|
475
|
+
|
476
|
+
},
|
477
|
+
// A helper used to serialize an `Observe` or `Observe.List`.
|
478
|
+
// `observe` - The observable.
|
479
|
+
// `how` - To serialize with `attr` or `serialize`.
|
480
|
+
// `where` - To put properties, in an `{}` or `[]`.
|
481
|
+
serialize = function( observe, how, where ) {
|
482
|
+
// Go through each property.
|
483
|
+
observe.each(function( val, name ) {
|
484
|
+
// If the value is an `object`, and has an `attrs` or `serialize` function.
|
485
|
+
where[name] = canMakeObserve(val) && can.isFunction( val[how] ) ?
|
486
|
+
// Call `attrs` or `serialize` to get the original data back.
|
487
|
+
val[how]() :
|
488
|
+
// Otherwise return the value.
|
489
|
+
val
|
490
|
+
})
|
491
|
+
return where;
|
492
|
+
},
|
493
|
+
$method = function( name ) {
|
494
|
+
return function() {
|
495
|
+
return can[name].apply(this, arguments );
|
496
|
+
}
|
497
|
+
},
|
498
|
+
bind = $method('addEvent'),
|
499
|
+
unbind = $method('removeEvent'),
|
500
|
+
attrParts = function(attr){
|
501
|
+
return can.isArray(attr) ? attr : (""+attr).split(".")
|
502
|
+
};
|
503
|
+
var Observe = can.Construct('can.Observe', {
|
504
|
+
// keep so it can be overwritten
|
505
|
+
setup : function(){
|
506
|
+
can.Construct.setup.apply(this, arguments)
|
507
|
+
},
|
508
|
+
bind : bind,
|
509
|
+
unbind: unbind,
|
510
|
+
id: "id"
|
511
|
+
},
|
512
|
+
{
|
513
|
+
setup: function( obj ) {
|
514
|
+
// `_data` is where we keep the properties.
|
515
|
+
this._data = {};
|
516
|
+
// The namespace this `object` uses to listen to events.
|
517
|
+
this._namespace = ".observe" + (++observeId);
|
518
|
+
// Sets all `attrs`.
|
519
|
+
this._init = 1;
|
520
|
+
this.attr(obj);
|
521
|
+
delete this._init;
|
522
|
+
},
|
523
|
+
attr: function( attr, val ) {
|
524
|
+
// This is super obfuscated for space -- basically, we're checking
|
525
|
+
// if the type of the attribute is not a `number` or a `string`.
|
526
|
+
if ( !~ "ns".indexOf((typeof attr).charAt(0))) {
|
527
|
+
return this._attrs(attr, val)
|
528
|
+
} else if ( val === undefined ) {// If we are getting a value.
|
529
|
+
// Let people know we are reading.
|
530
|
+
Observe.__reading && Observe.__reading(this, attr)
|
531
|
+
return this._get(attr)
|
532
|
+
} else {
|
533
|
+
// Otherwise we are setting.
|
534
|
+
this._set(attr, val);
|
535
|
+
return this;
|
536
|
+
}
|
537
|
+
},
|
538
|
+
each: function() {
|
539
|
+
return can.each.apply(undefined, [this.__get()].concat(can.makeArray(arguments)))
|
540
|
+
},
|
541
|
+
removeAttr: function( attr ) {
|
542
|
+
// Convert the `attr` into parts (if nested).
|
543
|
+
var parts = attrParts(attr),
|
544
|
+
// The actual property to remove.
|
545
|
+
prop = parts.shift(),
|
546
|
+
// The current value.
|
547
|
+
current = this._data[prop];
|
548
|
+
|
549
|
+
// If we have more parts, call `removeAttr` on that part.
|
550
|
+
if ( parts.length ) {
|
551
|
+
return current.removeAttr(parts)
|
552
|
+
} else {
|
553
|
+
// Otherwise, `delete`.
|
554
|
+
delete this._data[prop];
|
555
|
+
// Create the event.
|
556
|
+
if (!(prop in this.constructor.prototype)) {
|
557
|
+
delete this[prop]
|
558
|
+
}
|
559
|
+
batchTrigger(this, "change", [prop, "remove", undefined, current]);
|
560
|
+
batchTrigger(this, prop, [undefined, current]);
|
561
|
+
return current;
|
562
|
+
}
|
563
|
+
},
|
564
|
+
// Reads a property from the `object`.
|
565
|
+
_get: function( attr ) {
|
566
|
+
var parts = attrParts(attr),
|
567
|
+
current = this.__get(parts.shift());
|
568
|
+
return parts.length ? current ? current._get(parts) : undefined : current;
|
569
|
+
},
|
570
|
+
// Reads a property directly if an `attr` is provided, otherwise
|
571
|
+
// returns the "real" data object itself.
|
572
|
+
__get: function( attr ) {
|
573
|
+
return attr ? this._data[attr] : this._data;
|
574
|
+
},
|
575
|
+
// Sets `attr` prop as value on this object where.
|
576
|
+
// `attr` - Is a string of properties or an array of property values.
|
577
|
+
// `value` - The raw value to set.
|
578
|
+
_set: function( attr, value ) {
|
579
|
+
// Convert `attr` to attr parts (if it isn't already).
|
580
|
+
var parts = attrParts(attr),
|
581
|
+
// The immediate prop we are setting.
|
582
|
+
prop = parts.shift(),
|
583
|
+
// The current value.
|
584
|
+
current = this.__get(prop);
|
585
|
+
|
586
|
+
// If we have an `object` and remaining parts.
|
587
|
+
if ( canMakeObserve(current) && parts.length ) {
|
588
|
+
// That `object` should set it (this might need to call attr).
|
589
|
+
current._set(parts, value)
|
590
|
+
} else if (!parts.length ) {
|
591
|
+
// We're in "real" set territory.
|
592
|
+
if(this.__convert){
|
593
|
+
value = this.__convert(prop, value)
|
594
|
+
}
|
595
|
+
this.__set(prop, value, current)
|
596
|
+
|
597
|
+
} else {
|
598
|
+
throw "can.Observe: Object does not exist"
|
599
|
+
}
|
600
|
+
},
|
601
|
+
__set : function(prop, value, current){
|
602
|
+
|
603
|
+
// Otherwise, we are setting it on this `object`.
|
604
|
+
// TODO: Check if value is object and transform
|
605
|
+
// are we changing the value.
|
606
|
+
if ( value !== current ) {
|
607
|
+
|
608
|
+
// Check if we are adding this for the first time --
|
609
|
+
// if we are, we need to create an `add` event.
|
610
|
+
var changeType = this.__get().hasOwnProperty(prop) ? "set" : "add";
|
611
|
+
|
612
|
+
// Set the value on data.
|
613
|
+
this.___set(prop,
|
614
|
+
|
615
|
+
// If we are getting an object.
|
616
|
+
canMakeObserve(value) ?
|
617
|
+
|
618
|
+
// Hook it up to send event.
|
619
|
+
hookupBubble(value, prop, this) :
|
620
|
+
// Value is normal.
|
621
|
+
value);
|
622
|
+
|
623
|
+
// `batchTrigger` the change event.
|
624
|
+
batchTrigger(this, "change", [prop, changeType, value, current]);
|
625
|
+
batchTrigger(this, prop, [value, current]);
|
626
|
+
// If we can stop listening to our old value, do it.
|
627
|
+
current && unhookup([current], this._namespace);
|
628
|
+
}
|
629
|
+
|
630
|
+
},
|
631
|
+
// Directly sets a property on this `object`.
|
632
|
+
___set: function( prop, val ) {
|
633
|
+
this._data[prop] = val;
|
634
|
+
// Add property directly for easy writing.
|
635
|
+
// Check if its on the `prototype` so we don't overwrite methods like `attrs`.
|
636
|
+
if (!(prop in this.constructor.prototype)) {
|
637
|
+
this[prop] = val
|
638
|
+
}
|
639
|
+
},
|
640
|
+
bind: bind,
|
641
|
+
unbind: unbind,
|
642
|
+
serialize: function() {
|
643
|
+
return serialize(this, 'serialize', {});
|
644
|
+
},
|
645
|
+
_attrs: function( props, remove ) {
|
646
|
+
if ( props === undefined ) {
|
647
|
+
return serialize(this, 'attr', {})
|
648
|
+
}
|
649
|
+
|
650
|
+
props = can.extend(true, {}, props);
|
651
|
+
var prop,
|
652
|
+
collectingStarted = collect(),
|
653
|
+
self = this,
|
654
|
+
newVal;
|
655
|
+
|
656
|
+
this.each(function(curVal, prop){
|
657
|
+
newVal = props[prop];
|
658
|
+
|
659
|
+
// If we are merging...
|
660
|
+
if ( newVal === undefined ) {
|
661
|
+
remove && self.removeAttr(prop);
|
662
|
+
return;
|
663
|
+
}
|
664
|
+
if ( canMakeObserve(curVal) && canMakeObserve(newVal) ) {
|
665
|
+
curVal.attr(newVal, remove)
|
666
|
+
} else if ( curVal != newVal ) {
|
667
|
+
self._set(prop, newVal)
|
668
|
+
} else {
|
669
|
+
|
670
|
+
}
|
671
|
+
delete props[prop];
|
672
|
+
})
|
673
|
+
// Add remaining props.
|
674
|
+
for ( var prop in props ) {
|
675
|
+
newVal = props[prop];
|
676
|
+
this._set(prop, newVal)
|
677
|
+
}
|
678
|
+
if ( collectingStarted ) {
|
679
|
+
sendCollection();
|
680
|
+
}
|
681
|
+
return this;
|
682
|
+
}
|
683
|
+
});
|
684
|
+
// Helpers for `observable` lists.
|
685
|
+
var splice = [].splice,
|
686
|
+
list = Observe('can.Observe.List',
|
687
|
+
{
|
688
|
+
setup: function( instances, options ) {
|
689
|
+
this.length = 0;
|
690
|
+
this._namespace = ".observe" + (++observeId);
|
691
|
+
this._init = 1;
|
692
|
+
this.bind('change',can.proxy(this._changes,this));
|
693
|
+
this.push.apply(this, can.makeArray(instances || []));
|
694
|
+
can.extend(this, options);
|
695
|
+
delete this._init;
|
696
|
+
},
|
697
|
+
_changes : function(ev, attr, how, newVal, oldVal){
|
698
|
+
// `batchTrigger` direct add and remove events...
|
699
|
+
if ( !~ attr.indexOf('.')){
|
700
|
+
|
701
|
+
if( how === 'add' ) {
|
702
|
+
batchTrigger(this, how, [newVal,+attr]);
|
703
|
+
batchTrigger(this,'length',[this.length]);
|
704
|
+
} else if( how === 'remove' ) {
|
705
|
+
batchTrigger(this, how, [oldVal, +attr]);
|
706
|
+
batchTrigger(this,'length',[this.length]);
|
707
|
+
} else {
|
708
|
+
batchTrigger(this,how,[newVal, +attr])
|
709
|
+
}
|
710
|
+
|
711
|
+
}
|
712
|
+
},
|
713
|
+
__get : function(attr){
|
714
|
+
return attr ? this[attr] : this;
|
715
|
+
},
|
716
|
+
___set : function(attr, val){
|
717
|
+
this[attr] = val;
|
718
|
+
if(+attr >= this.length){
|
719
|
+
this.length = (+attr+1)
|
720
|
+
}
|
721
|
+
},
|
722
|
+
// Returns the serialized form of this list.
|
723
|
+
serialize: function() {
|
724
|
+
return serialize(this, 'serialize', []);
|
725
|
+
},
|
726
|
+
//
|
727
|
+
splice: function( index, howMany ) {
|
728
|
+
var args = can.makeArray(arguments),
|
729
|
+
i;
|
730
|
+
|
731
|
+
for ( i = 2; i < args.length; i++ ) {
|
732
|
+
var val = args[i];
|
733
|
+
if ( canMakeObserve(val) ) {
|
734
|
+
args[i] = hookupBubble(val, "*", this)
|
735
|
+
}
|
736
|
+
}
|
737
|
+
if ( howMany === undefined ) {
|
738
|
+
howMany = args[1] = this.length - index;
|
739
|
+
}
|
740
|
+
var removed = splice.apply(this, args);
|
741
|
+
if ( howMany > 0 ) {
|
742
|
+
batchTrigger(this, "change", [""+index, "remove", undefined, removed]);
|
743
|
+
unhookup(removed, this._namespace);
|
744
|
+
}
|
745
|
+
if ( args.length > 2 ) {
|
746
|
+
batchTrigger(this, "change", [""+index, "add", args.slice(2), removed]);
|
747
|
+
}
|
748
|
+
return removed;
|
749
|
+
},
|
750
|
+
_attrs: function( props, remove ) {
|
751
|
+
if ( props === undefined ) {
|
752
|
+
return serialize(this, 'attr', []);
|
753
|
+
}
|
754
|
+
|
755
|
+
// Create a copy.
|
756
|
+
props = props.slice(0);
|
757
|
+
|
758
|
+
var len = Math.min(props.length, this.length),
|
759
|
+
collectingStarted = collect(),
|
760
|
+
prop;
|
761
|
+
|
762
|
+
for ( var prop = 0; prop < len; prop++ ) {
|
763
|
+
var curVal = this[prop],
|
764
|
+
newVal = props[prop];
|
765
|
+
|
766
|
+
if ( canMakeObserve(curVal) && canMakeObserve(newVal) ) {
|
767
|
+
curVal.attr(newVal, remove)
|
768
|
+
} else if ( curVal != newVal ) {
|
769
|
+
this._set(prop, newVal)
|
770
|
+
} else {
|
771
|
+
|
772
|
+
}
|
773
|
+
}
|
774
|
+
if ( props.length > this.length ) {
|
775
|
+
// Add in the remaining props.
|
776
|
+
this.push(props.slice(this.length))
|
777
|
+
} else if ( props.length < this.length && remove ) {
|
778
|
+
this.splice(props.length)
|
779
|
+
}
|
780
|
+
|
781
|
+
if ( collectingStarted ) {
|
782
|
+
sendCollection()
|
783
|
+
}
|
784
|
+
}
|
785
|
+
}),
|
786
|
+
|
787
|
+
// Converts to an `array` of arguments.
|
788
|
+
getArgs = function( args ) {
|
789
|
+
return args[0] && can.isArray(args[0]) ?
|
790
|
+
args[0] :
|
791
|
+
can.makeArray(args);
|
792
|
+
};
|
793
|
+
// Create `push`, `pop`, `shift`, and `unshift`
|
794
|
+
can.each({
|
795
|
+
push: "length",
|
796
|
+
unshift: 0
|
797
|
+
},
|
798
|
+
// Adds a method
|
799
|
+
// `name` - The method name.
|
800
|
+
// `where` - Where items in the `array` should be added.
|
801
|
+
function( where, name ) {
|
802
|
+
list.prototype[name] = function() {
|
803
|
+
// Get the items being added.
|
804
|
+
var args = getArgs(arguments),
|
805
|
+
// Where we are going to add items.
|
806
|
+
len = where ? this.length : 0;
|
807
|
+
|
808
|
+
// Go through and convert anything to an `observe` that needs to be converted.
|
809
|
+
for ( var i = 0; i < args.length; i++ ) {
|
810
|
+
var val = args[i];
|
811
|
+
if ( canMakeObserve(val) ) {
|
812
|
+
args[i] = hookupBubble(val, "*", this)
|
813
|
+
}
|
814
|
+
}
|
815
|
+
|
816
|
+
// Call the original method.
|
817
|
+
var res = [][name].apply(this, args);
|
818
|
+
|
819
|
+
if ( !this.comparator || !args.length ) {
|
820
|
+
batchTrigger(this, "change", [""+len, "add", args, undefined])
|
821
|
+
}
|
822
|
+
|
823
|
+
return res;
|
824
|
+
}
|
825
|
+
});
|
826
|
+
|
827
|
+
can.each({
|
828
|
+
pop: "length",
|
829
|
+
shift: 0
|
830
|
+
},
|
831
|
+
// Creates a `remove` type method
|
832
|
+
function( where, name ) {
|
833
|
+
list.prototype[name] = function() {
|
834
|
+
|
835
|
+
var args = getArgs(arguments),
|
836
|
+
len = where && this.length ? this.length - 1 : 0;
|
837
|
+
|
838
|
+
var res = [][name].apply(this, args)
|
839
|
+
|
840
|
+
// Create a change where the args are
|
841
|
+
// `*` - Change on potentially multiple properties.
|
842
|
+
// `remove` - Items removed.
|
843
|
+
// `undefined` - The new values (there are none).
|
844
|
+
// `res` - The old, removed values (should these be unbound).
|
845
|
+
// `len` - Where these items were removed.
|
846
|
+
batchTrigger(this, "change", [""+len, "remove", undefined, [res]])
|
847
|
+
|
848
|
+
if ( res && res.unbind ) {
|
849
|
+
res.unbind("change" + this._namespace)
|
850
|
+
}
|
851
|
+
return res;
|
852
|
+
}
|
853
|
+
});
|
854
|
+
|
855
|
+
list.prototype.
|
856
|
+
indexOf = [].indexOf || function(item){
|
857
|
+
return can.inArray(item, this)
|
858
|
+
};
|
859
|
+
|
860
|
+
|
861
|
+
// ## model.js
|
862
|
+
// `can.Model`
|
863
|
+
// _A `can.Observe` that connects to a RESTful interface._
|
864
|
+
//
|
865
|
+
// Generic deferred piping function
|
866
|
+
var pipe = function( def, model, func ) {
|
867
|
+
var d = new can.Deferred();
|
868
|
+
def.then(function(){
|
869
|
+
arguments[0] = model[func](arguments[0])
|
870
|
+
d.resolve.apply(d, arguments)
|
871
|
+
},function(){
|
872
|
+
d.rejectWith.apply(this,arguments)
|
873
|
+
})
|
874
|
+
return d;
|
875
|
+
},
|
876
|
+
modelNum = 0,
|
877
|
+
ignoreHookup = /change.observe\d+/,
|
878
|
+
getId = function( inst ) {
|
879
|
+
return inst[inst.constructor.id]
|
880
|
+
},
|
881
|
+
// Ajax `options` generator function
|
882
|
+
ajax = function( ajaxOb, data, type, dataType, success, error ) {
|
883
|
+
|
884
|
+
|
885
|
+
// If we get a string, handle it.
|
886
|
+
if ( typeof ajaxOb == "string" ) {
|
887
|
+
// If there's a space, it's probably the type.
|
888
|
+
var parts = ajaxOb.split(" ")
|
889
|
+
ajaxOb = {
|
890
|
+
url : parts.pop()
|
891
|
+
};
|
892
|
+
if(parts.length){
|
893
|
+
ajaxOb.type = parts.pop();
|
894
|
+
}
|
895
|
+
}
|
896
|
+
|
897
|
+
// If we are a non-array object, copy to a new attrs.
|
898
|
+
ajaxOb.data = typeof data == "object" && !can.isArray(data) ?
|
899
|
+
can.extend(ajaxOb.data || {}, data) : data;
|
900
|
+
|
901
|
+
|
902
|
+
// Get the url with any templated values filled out.
|
903
|
+
ajaxOb.url = can.sub(ajaxOb.url, ajaxOb.data, true);
|
904
|
+
|
905
|
+
return can.ajax(can.extend({
|
906
|
+
type: type || "post",
|
907
|
+
dataType: dataType ||"json",
|
908
|
+
success : success,
|
909
|
+
error: error
|
910
|
+
}, ajaxOb ));
|
911
|
+
},
|
912
|
+
makeRequest = function( self, type, success, error, method ) {
|
913
|
+
var deferred ,
|
914
|
+
args = [self.serialize()],
|
915
|
+
// The model.
|
916
|
+
model = self.constructor,
|
917
|
+
jqXHR;
|
918
|
+
|
919
|
+
// `destroy` does not need data.
|
920
|
+
if ( type == 'destroy' ) {
|
921
|
+
args.shift();
|
922
|
+
}
|
923
|
+
// `update` and `destroy` need the `id`.
|
924
|
+
if ( type !== 'create' ) {
|
925
|
+
args.unshift(getId(self))
|
926
|
+
}
|
927
|
+
|
928
|
+
jqXHR = model[type].apply(model, args);
|
929
|
+
|
930
|
+
deferred = jqXHR.pipe(function(data){
|
931
|
+
self[method || type + "d"](data, jqXHR);
|
932
|
+
return self
|
933
|
+
})
|
934
|
+
|
935
|
+
// Hook up `abort`
|
936
|
+
if(jqXHR.abort){
|
937
|
+
deferred.abort = function(){
|
938
|
+
jqXHR.abort();
|
939
|
+
}
|
940
|
+
}
|
941
|
+
|
942
|
+
return deferred.then(success,error);
|
943
|
+
},
|
944
|
+
|
945
|
+
// This object describes how to make an ajax request for each ajax method.
|
946
|
+
// The available properties are:
|
947
|
+
// `url` - The default url to use as indicated as a property on the model.
|
948
|
+
// `type` - The default http request type
|
949
|
+
// `data` - A method that takes the `arguments` and returns `data` used for ajax.
|
950
|
+
//
|
951
|
+
//
|
952
|
+
//
|
953
|
+
ajaxMethods = {
|
954
|
+
create : {
|
955
|
+
url : "_shortName",
|
956
|
+
type :"post"
|
957
|
+
},
|
958
|
+
update : {
|
959
|
+
data : function(id, attrs){
|
960
|
+
attrs = attrs || {};
|
961
|
+
var identity = this.id;
|
962
|
+
if ( attrs[identity] && attrs[identity] !== id ) {
|
963
|
+
attrs["new" + can.capitalize(id)] = attrs[identity];
|
964
|
+
delete attrs[identity];
|
965
|
+
}
|
966
|
+
attrs[identity] = id;
|
967
|
+
return attrs;
|
968
|
+
},
|
969
|
+
type : "put"
|
970
|
+
},
|
971
|
+
destroy : {
|
972
|
+
type : "delete",
|
973
|
+
data : function(id){
|
974
|
+
var args = {};
|
975
|
+
args[this.id] = id;
|
976
|
+
return args;
|
977
|
+
}
|
978
|
+
},
|
979
|
+
findAll : {
|
980
|
+
url : "_shortName"
|
981
|
+
},
|
982
|
+
findOne: {}
|
983
|
+
},
|
984
|
+
// Makes an ajax request `function` from a string.
|
985
|
+
// `ajaxMethod` - The `ajaxMethod` object defined above.
|
986
|
+
// `str` - The string the user provided. Ex: `findAll: "/recipes.json"`.
|
987
|
+
ajaxMaker = function(ajaxMethod, str){
|
988
|
+
// Return a `function` that serves as the ajax method.
|
989
|
+
return function(data){
|
990
|
+
// If the ajax method has it's own way of getting `data`, use that.
|
991
|
+
data = ajaxMethod.data ?
|
992
|
+
ajaxMethod.data.apply(this, arguments) :
|
993
|
+
// Otherwise use the data passed in.
|
994
|
+
data;
|
995
|
+
// Return the ajax method with `data` and the `type` provided.
|
996
|
+
return ajax(str || this[ajaxMethod.url || "_url"], data, ajaxMethod.type || "get")
|
997
|
+
}
|
998
|
+
}
|
999
|
+
|
1000
|
+
|
1001
|
+
|
1002
|
+
can.Observe("can.Model",{
|
1003
|
+
setup : function(base){
|
1004
|
+
can.Observe.apply(this, arguments);
|
1005
|
+
if(this === can.Model){
|
1006
|
+
return;
|
1007
|
+
}
|
1008
|
+
var self = this,
|
1009
|
+
clean = can.proxy(this._clean, self);
|
1010
|
+
|
1011
|
+
can.each(ajaxMethods, function(method, name){
|
1012
|
+
if ( ! can.isFunction( self[name] )) {
|
1013
|
+
self[name] = ajaxMaker(method, self[name]);
|
1014
|
+
}
|
1015
|
+
if (self["make"+can.capitalize(name)]){
|
1016
|
+
var newMethod = self["make"+can.capitalize(name)](self[name]);
|
1017
|
+
can.Construct._overwrite(self, base, name,function(){
|
1018
|
+
this._super;
|
1019
|
+
this._reqs++;
|
1020
|
+
return newMethod.apply(this, arguments).then(clean, clean);
|
1021
|
+
})
|
1022
|
+
}
|
1023
|
+
});
|
1024
|
+
|
1025
|
+
if(!self.fullName || self.fullName == base.fullName){
|
1026
|
+
self.fullName = self._shortName = "Model"+(++modelNum);
|
1027
|
+
}
|
1028
|
+
// Ddd ajax converters.
|
1029
|
+
this.store = {};
|
1030
|
+
this._reqs = 0;
|
1031
|
+
this._url = this._shortName+"/{"+this.id+"}"
|
1032
|
+
},
|
1033
|
+
_ajax : ajaxMaker,
|
1034
|
+
_clean : function(){
|
1035
|
+
this._reqs--;
|
1036
|
+
if(!this._reqs){
|
1037
|
+
for(var id in this.store) {
|
1038
|
+
if(!this.store[id]._bindings){
|
1039
|
+
delete this.store[id];
|
1040
|
+
}
|
1041
|
+
}
|
1042
|
+
}
|
1043
|
+
},
|
1044
|
+
models: function( instancesRawData ) {
|
1045
|
+
|
1046
|
+
if ( ! instancesRawData ) {
|
1047
|
+
return;
|
1048
|
+
}
|
1049
|
+
|
1050
|
+
if ( instancesRawData instanceof this.List ) {
|
1051
|
+
return instancesRawData;
|
1052
|
+
}
|
1053
|
+
|
1054
|
+
// Get the list type.
|
1055
|
+
var self = this,
|
1056
|
+
res = new( self.List || ML),
|
1057
|
+
// Did we get an `array`?
|
1058
|
+
arr = can.isArray(instancesRawData),
|
1059
|
+
|
1060
|
+
// Did we get a model list?
|
1061
|
+
ml = (instancesRawData instanceof ML),
|
1062
|
+
|
1063
|
+
// Get the raw `array` of objects.
|
1064
|
+
raw = arr ?
|
1065
|
+
|
1066
|
+
// If an `array`, return the `array`.
|
1067
|
+
instancesRawData :
|
1068
|
+
|
1069
|
+
// Otherwise if a model list.
|
1070
|
+
(ml ?
|
1071
|
+
|
1072
|
+
// Get the raw objects from the list.
|
1073
|
+
instancesRawData.serialize() :
|
1074
|
+
|
1075
|
+
// Get the object's data.
|
1076
|
+
instancesRawData.data),
|
1077
|
+
i = 0;
|
1078
|
+
|
1079
|
+
|
1080
|
+
|
1081
|
+
can.each(raw, function( rawPart ) {
|
1082
|
+
res.push( self.model( rawPart ));
|
1083
|
+
});
|
1084
|
+
|
1085
|
+
if ( ! arr ) { // Push other stuff onto `array`.
|
1086
|
+
can.each(instancesRawData, function(val, prop){
|
1087
|
+
if ( prop !== 'data' ) {
|
1088
|
+
res[prop] = val;
|
1089
|
+
}
|
1090
|
+
})
|
1091
|
+
}
|
1092
|
+
return res;
|
1093
|
+
},
|
1094
|
+
model: function( attributes ) {
|
1095
|
+
if (!attributes ) {
|
1096
|
+
return;
|
1097
|
+
}
|
1098
|
+
if ( attributes instanceof this ) {
|
1099
|
+
attributes = attributes.serialize();
|
1100
|
+
}
|
1101
|
+
var model = this.store[attributes[this.id]] ? this.store[attributes[this.id]].attr(attributes) : new this( attributes );
|
1102
|
+
if(this._reqs){
|
1103
|
+
this.store[attributes[this.id]] = model;
|
1104
|
+
}
|
1105
|
+
return model;
|
1106
|
+
}
|
1107
|
+
},
|
1108
|
+
{
|
1109
|
+
isNew: function() {
|
1110
|
+
var id = getId(this);
|
1111
|
+
return ! ( id || id === 0 ); // If `null` or `undefined`
|
1112
|
+
},
|
1113
|
+
save: function( success, error ) {
|
1114
|
+
return makeRequest(this, this.isNew() ? 'create' : 'update', success, error);
|
1115
|
+
},
|
1116
|
+
destroy: function( success, error ) {
|
1117
|
+
return makeRequest(this, 'destroy', success, error, 'destroyed');
|
1118
|
+
},
|
1119
|
+
bind : function(eventName){
|
1120
|
+
if ( ! ignoreHookup.test( eventName )) {
|
1121
|
+
if ( ! this._bindings ) {
|
1122
|
+
this.constructor.store[getId(this)] = this;
|
1123
|
+
this._bindings = 0;
|
1124
|
+
}
|
1125
|
+
this._bindings++;
|
1126
|
+
}
|
1127
|
+
|
1128
|
+
return can.Observe.prototype.bind.apply( this, arguments );
|
1129
|
+
},
|
1130
|
+
unbind : function(eventName){
|
1131
|
+
if(!ignoreHookup.test(eventName)) {
|
1132
|
+
this._bindings--;
|
1133
|
+
if(!this._bindings){
|
1134
|
+
delete this.constructor.store[getId(this)];
|
1135
|
+
}
|
1136
|
+
}
|
1137
|
+
return can.Observe.prototype.unbind.apply(this, arguments);
|
1138
|
+
},
|
1139
|
+
// Change `id`.
|
1140
|
+
___set: function( prop, val ) {
|
1141
|
+
can.Observe.prototype.___set.call(this,prop, val)
|
1142
|
+
// If we add an `id`, move it to the store.
|
1143
|
+
if(prop === this.constructor.id && this._bindings){
|
1144
|
+
this.constructor.store[getId(this)] = this;
|
1145
|
+
}
|
1146
|
+
}
|
1147
|
+
});
|
1148
|
+
|
1149
|
+
|
1150
|
+
|
1151
|
+
|
1152
|
+
can.each({makeFindAll : "models", makeFindOne: "model"}, function(method, name){
|
1153
|
+
can.Model[name] = function(oldFind){
|
1154
|
+
return function(params, success, error){
|
1155
|
+
return pipe( oldFind.call( this, params ),
|
1156
|
+
this,
|
1157
|
+
method ).then(success,error)
|
1158
|
+
}
|
1159
|
+
};
|
1160
|
+
});
|
1161
|
+
|
1162
|
+
can.each([
|
1163
|
+
"created",
|
1164
|
+
"updated",
|
1165
|
+
"destroyed"], function( funcName ) {
|
1166
|
+
can.Model.prototype[funcName] = function( attrs ) {
|
1167
|
+
var stub,
|
1168
|
+
constructor = this.constructor;
|
1169
|
+
|
1170
|
+
// Update attributes if attributes have been passed
|
1171
|
+
stub = attrs && typeof attrs == 'object' && this.attr(attrs.attr ? attrs.attr() : attrs);
|
1172
|
+
|
1173
|
+
// Call event on the instance
|
1174
|
+
can.trigger(this,funcName);
|
1175
|
+
can.trigger(this,"change",funcName)
|
1176
|
+
|
1177
|
+
|
1178
|
+
// Call event on the instance's Class
|
1179
|
+
can.trigger(constructor,funcName, this);
|
1180
|
+
};
|
1181
|
+
});
|
1182
|
+
|
1183
|
+
// Model lists are just like `Observe.List` except that when their items are
|
1184
|
+
// destroyed, it automatically gets removed from the list.
|
1185
|
+
var ML = can.Observe.List('can.Model.List',{
|
1186
|
+
setup : function(){
|
1187
|
+
can.Observe.List.prototype.setup.apply(this, arguments );
|
1188
|
+
// Send destroy events.
|
1189
|
+
var self = this;
|
1190
|
+
this.bind('change', function(ev, how){
|
1191
|
+
if(/\w+\.destroyed/.test(how)){
|
1192
|
+
self.splice(self.indexOf(ev.target),1);
|
1193
|
+
}
|
1194
|
+
})
|
1195
|
+
}
|
1196
|
+
})
|
1197
|
+
|
1198
|
+
;
|
1199
|
+
|
1200
|
+
|
1201
|
+
// ## deparam.js
|
1202
|
+
// `can.deparam`
|
1203
|
+
// _Takes a string of name value pairs and returns a Object literal that represents those params._
|
1204
|
+
var digitTest = /^\d+$/,
|
1205
|
+
keyBreaker = /([^\[\]]+)|(\[\])/g,
|
1206
|
+
paramTest = /([^?#]*)(#.*)?$/,
|
1207
|
+
prep = function( str ) {
|
1208
|
+
return decodeURIComponent( str.replace(/\+/g, " ") );
|
1209
|
+
}
|
1210
|
+
|
1211
|
+
|
1212
|
+
can.extend(can, {
|
1213
|
+
deparam: function(params){
|
1214
|
+
|
1215
|
+
var data = {},
|
1216
|
+
pairs, lastPart;
|
1217
|
+
|
1218
|
+
if ( params && paramTest.test( params )) {
|
1219
|
+
|
1220
|
+
pairs = params.split('&'),
|
1221
|
+
|
1222
|
+
can.each( pairs, function( pair ) {
|
1223
|
+
|
1224
|
+
var parts = pair.split('='),
|
1225
|
+
key = prep( parts.shift() ),
|
1226
|
+
value = prep( parts.join("=") );
|
1227
|
+
|
1228
|
+
current = data;
|
1229
|
+
parts = key.match(keyBreaker);
|
1230
|
+
|
1231
|
+
for ( var j = 0, l = parts.length - 1; j < l; j++ ) {
|
1232
|
+
if (!current[parts[j]] ) {
|
1233
|
+
// If what we are pointing to looks like an `array`
|
1234
|
+
current[parts[j]] = digitTest.test(parts[j+1]) || parts[j+1] == "[]" ? [] : {}
|
1235
|
+
}
|
1236
|
+
current = current[parts[j]];
|
1237
|
+
}
|
1238
|
+
lastPart = parts.pop()
|
1239
|
+
if ( lastPart == "[]" ) {
|
1240
|
+
current.push(value)
|
1241
|
+
} else {
|
1242
|
+
current[lastPart] = value;
|
1243
|
+
}
|
1244
|
+
});
|
1245
|
+
}
|
1246
|
+
return data;
|
1247
|
+
}
|
1248
|
+
});
|
1249
|
+
|
1250
|
+
// ## route.js
|
1251
|
+
// `can.route`
|
1252
|
+
// _Helps manage browser history (and client state) by synchronizing the
|
1253
|
+
// `window.location.hash` with a `can.Observe`._
|
1254
|
+
//
|
1255
|
+
// Helper methods used for matching routes.
|
1256
|
+
var
|
1257
|
+
// `RegExp` used to match route variables of the type ':name'.
|
1258
|
+
// Any word character or a period is matched.
|
1259
|
+
matcher = /\:([\w\.]+)/g,
|
1260
|
+
// Regular expression for identifying &key=value lists.
|
1261
|
+
paramsMatcher = /^(?:&[^=]+=[^&]*)+/,
|
1262
|
+
// Converts a JS Object into a list of parameters that can be
|
1263
|
+
// inserted into an html element tag.
|
1264
|
+
makeProps = function( props ) {
|
1265
|
+
var tags = [];
|
1266
|
+
can.each(props, function(val, name){
|
1267
|
+
tags.push( ( name === 'className' ? 'class' : name )+ '="' +
|
1268
|
+
(name === "href" ? val : can.esc(val) ) + '"');
|
1269
|
+
});
|
1270
|
+
return tags.join(" ");
|
1271
|
+
},
|
1272
|
+
// Checks if a route matches the data provided. If any route variable
|
1273
|
+
// is not present in the data, the route does not match. If all route
|
1274
|
+
// variables are present in the data, the number of matches is returned
|
1275
|
+
// to allow discerning between general and more specific routes.
|
1276
|
+
matchesData = function(route, data) {
|
1277
|
+
var count = 0, i = 0, defaults = {};
|
1278
|
+
// look at default values, if they match ...
|
1279
|
+
for( var name in route.defaults ) {
|
1280
|
+
if(route.defaults[name] === data[name]){
|
1281
|
+
// mark as matched
|
1282
|
+
defaults[name] = 1;
|
1283
|
+
count++;
|
1284
|
+
}
|
1285
|
+
}
|
1286
|
+
for (; i < route.names.length; i++ ) {
|
1287
|
+
if (!data.hasOwnProperty(route.names[i]) ) {
|
1288
|
+
return -1;
|
1289
|
+
}
|
1290
|
+
if(!defaults[route.names[i]]){
|
1291
|
+
count++;
|
1292
|
+
}
|
1293
|
+
|
1294
|
+
}
|
1295
|
+
|
1296
|
+
return count;
|
1297
|
+
},
|
1298
|
+
onready = !0,
|
1299
|
+
location = window.location,
|
1300
|
+
each = can.each,
|
1301
|
+
extend = can.extend;
|
1302
|
+
|
1303
|
+
can.route = function( url, defaults ) {
|
1304
|
+
defaults = defaults || {}
|
1305
|
+
// Extract the variable names and replace with `RegExp` that will match
|
1306
|
+
// an atual URL with values.
|
1307
|
+
var names = [],
|
1308
|
+
test = url.replace(matcher, function( whole, name, i ) {
|
1309
|
+
names.push(name);
|
1310
|
+
var next = "\\"+( url.substr(i+whole.length,1) || "&" )
|
1311
|
+
// a name without a default value HAS to have a value
|
1312
|
+
// a name that has a default value can be empty
|
1313
|
+
// The `\\` is for string-escaping giving single `\` for `RegExp` escaping.
|
1314
|
+
return "([^" +next+"]"+(defaults[name] ? "*" : "+")+")"
|
1315
|
+
});
|
1316
|
+
|
1317
|
+
// Add route in a form that can be easily figured out.
|
1318
|
+
can.route.routes[url] = {
|
1319
|
+
// A regular expression that will match the route when variable values
|
1320
|
+
// are present; i.e. for `:page/:type` the `RegExp` is `/([\w\.]*)/([\w\.]*)/` which
|
1321
|
+
// will match for any value of `:page` and `:type` (word chars or period).
|
1322
|
+
test: new RegExp("^" + test+"($|&)"),
|
1323
|
+
// The original URL, same as the index for this entry in routes.
|
1324
|
+
route: url,
|
1325
|
+
// An `array` of all the variable names in this route.
|
1326
|
+
names: names,
|
1327
|
+
// Default values provided for the variables.
|
1328
|
+
defaults: defaults,
|
1329
|
+
// The number of parts in the URL separated by `/`.
|
1330
|
+
length: url.split('/').length
|
1331
|
+
}
|
1332
|
+
return can.route;
|
1333
|
+
};
|
1334
|
+
|
1335
|
+
extend(can.route, {
|
1336
|
+
param: function( data , _setRoute ) {
|
1337
|
+
// Check if the provided data keys match the names in any routes;
|
1338
|
+
// Get the one with the most matches.
|
1339
|
+
var route,
|
1340
|
+
// Need to have at least 1 match.
|
1341
|
+
matches = 0,
|
1342
|
+
matchCount,
|
1343
|
+
routeName = data.route,
|
1344
|
+
propCount = 0;
|
1345
|
+
|
1346
|
+
delete data.route;
|
1347
|
+
|
1348
|
+
each(data, function(){propCount++});
|
1349
|
+
// Otherwise find route.
|
1350
|
+
each(can.route.routes, function(temp, name){
|
1351
|
+
// best route is the first with all defaults matching
|
1352
|
+
|
1353
|
+
|
1354
|
+
matchCount = matchesData(temp, data);
|
1355
|
+
if ( matchCount > matches ) {
|
1356
|
+
route = temp;
|
1357
|
+
matches = matchCount
|
1358
|
+
}
|
1359
|
+
if(matchCount >= propCount){
|
1360
|
+
return false;
|
1361
|
+
}
|
1362
|
+
});
|
1363
|
+
// If we have a route name in our `can.route` data, and it's
|
1364
|
+
// just as good as what currently matches, use that
|
1365
|
+
if (can.route.routes[routeName] && matchesData(can.route.routes[routeName], data ) === matches) {
|
1366
|
+
route = can.route.routes[routeName];
|
1367
|
+
}
|
1368
|
+
// If this is match...
|
1369
|
+
if ( route ) {
|
1370
|
+
var cpy = extend({}, data),
|
1371
|
+
// Create the url by replacing the var names with the provided data.
|
1372
|
+
// If the default value is found an empty string is inserted.
|
1373
|
+
res = route.route.replace(matcher, function( whole, name ) {
|
1374
|
+
delete cpy[name];
|
1375
|
+
return data[name] === route.defaults[name] ? "" : encodeURIComponent( data[name] );
|
1376
|
+
}),
|
1377
|
+
after;
|
1378
|
+
// Remove matching default values
|
1379
|
+
each(route.defaults, function(val,name){
|
1380
|
+
if(cpy[name] === val) {
|
1381
|
+
delete cpy[name]
|
1382
|
+
}
|
1383
|
+
})
|
1384
|
+
|
1385
|
+
// The remaining elements of data are added as
|
1386
|
+
// `&` separated parameters to the url.
|
1387
|
+
after = can.param(cpy);
|
1388
|
+
// if we are paraming for setting the hash
|
1389
|
+
// we also want to make sure the route value is updated
|
1390
|
+
if(_setRoute){
|
1391
|
+
can.route.attr('route',route.route);
|
1392
|
+
}
|
1393
|
+
return res + (after ? "&" + after : "")
|
1394
|
+
}
|
1395
|
+
// If no route was found, there is no hash URL, only paramters.
|
1396
|
+
return can.isEmptyObject(data) ? "" : "&" + can.param(data);
|
1397
|
+
},
|
1398
|
+
deparam: function( url ) {
|
1399
|
+
// See if the url matches any routes by testing it against the `route.test` `RegExp`.
|
1400
|
+
// By comparing the URL length the most specialized route that matches is used.
|
1401
|
+
var route = {
|
1402
|
+
length: -1
|
1403
|
+
};
|
1404
|
+
each(can.route.routes, function(temp, name){
|
1405
|
+
if ( temp.test.test(url) && temp.length > route.length ) {
|
1406
|
+
route = temp;
|
1407
|
+
}
|
1408
|
+
});
|
1409
|
+
// If a route was matched.
|
1410
|
+
if ( route.length > -1 ) {
|
1411
|
+
var // Since `RegExp` backreferences are used in `route.test` (parens)
|
1412
|
+
// the parts will contain the full matched string and each variable (back-referenced) value.
|
1413
|
+
parts = url.match(route.test),
|
1414
|
+
// Start will contain the full matched string; parts contain the variable values.
|
1415
|
+
start = parts.shift(),
|
1416
|
+
// The remainder will be the `&key=value` list at the end of the URL.
|
1417
|
+
remainder = url.substr(start.length - (parts[parts.length-1] === "&" ? 1 : 0) ),
|
1418
|
+
// If there is a remainder and it contains a `&key=value` list deparam it.
|
1419
|
+
obj = (remainder && paramsMatcher.test(remainder)) ? can.deparam( remainder.slice(1) ) : {};
|
1420
|
+
|
1421
|
+
// Add the default values for this route.
|
1422
|
+
obj = extend(true, {}, route.defaults, obj);
|
1423
|
+
// Overwrite each of the default values in `obj` with those in
|
1424
|
+
// parts if that part is not empty.
|
1425
|
+
each(parts,function(part, i){
|
1426
|
+
if ( part && part !== '&') {
|
1427
|
+
obj[route.names[i]] = decodeURIComponent( part );
|
1428
|
+
}
|
1429
|
+
});
|
1430
|
+
obj.route = route.route;
|
1431
|
+
return obj;
|
1432
|
+
}
|
1433
|
+
// If no route was matched, it is parsed as a `&key=value` list.
|
1434
|
+
if ( url.charAt(0) !== '&' ) {
|
1435
|
+
url = '&' + url;
|
1436
|
+
}
|
1437
|
+
return paramsMatcher.test(url) ? can.deparam( url.slice(1) ) : {};
|
1438
|
+
},
|
1439
|
+
data: new can.Observe({}),
|
1440
|
+
routes: {},
|
1441
|
+
ready: function(val) {
|
1442
|
+
if( val === false ) {
|
1443
|
+
onready = val;
|
1444
|
+
}
|
1445
|
+
if( val === true || onready === true ) {
|
1446
|
+
setState();
|
1447
|
+
}
|
1448
|
+
return can.route;
|
1449
|
+
},
|
1450
|
+
url: function( options, merge ) {
|
1451
|
+
if (merge) {
|
1452
|
+
options = extend({}, curParams, options)
|
1453
|
+
}
|
1454
|
+
return "#!" + can.route.param(options)
|
1455
|
+
},
|
1456
|
+
link: function( name, options, props, merge ) {
|
1457
|
+
return "<a " + makeProps(
|
1458
|
+
extend({
|
1459
|
+
href: can.route.url(options, merge)
|
1460
|
+
}, props)) + ">" + name + "</a>";
|
1461
|
+
},
|
1462
|
+
current: function( options ) {
|
1463
|
+
return location.hash == "#!" + can.route.param(options)
|
1464
|
+
}
|
1465
|
+
});
|
1466
|
+
|
1467
|
+
|
1468
|
+
// The functions in the following list applied to `can.route` (e.g. `can.route.attr('...')`) will
|
1469
|
+
// instead act on the `can.route.data` observe.
|
1470
|
+
each(['bind','unbind','delegate','undelegate','attr','removeAttr'], function(name){
|
1471
|
+
can.route[name] = function(){
|
1472
|
+
return can.route.data[name].apply(can.route.data, arguments)
|
1473
|
+
}
|
1474
|
+
})
|
1475
|
+
|
1476
|
+
var // A ~~throttled~~ debounced function called multiple times will only fire once the
|
1477
|
+
// timer runs down. Each call resets the timer.
|
1478
|
+
timer,
|
1479
|
+
// Intermediate storage for `can.route.data`.
|
1480
|
+
curParams,
|
1481
|
+
// Deparameterizes the portion of the hash of interest and assign the
|
1482
|
+
// values to the `can.route.data` removing existing values no longer in the hash.
|
1483
|
+
// setState is called typically by hashchange which fires asynchronously
|
1484
|
+
// So it's possible that someone started changing the data before the
|
1485
|
+
// hashchange event fired. For this reason, it will not set the route data
|
1486
|
+
// if the data is changing and the hash already matches the hash that was set.
|
1487
|
+
setState = function() {
|
1488
|
+
var hash = location.href.split(/#!?/)[1] || ""
|
1489
|
+
curParams = can.route.deparam( hash );
|
1490
|
+
|
1491
|
+
|
1492
|
+
// if the hash data is currently changing, and
|
1493
|
+
// the hash is what we set it to anyway, do NOT change the hash
|
1494
|
+
if(!changingData || hash !== lastHash){
|
1495
|
+
can.route.attr(curParams, true);
|
1496
|
+
}
|
1497
|
+
},
|
1498
|
+
// The last hash caused by a data change
|
1499
|
+
lastHash,
|
1500
|
+
// Are data changes pending that haven't yet updated the hash
|
1501
|
+
changingData;
|
1502
|
+
|
1503
|
+
// If the hash changes, update the `can.route.data`.
|
1504
|
+
can.bind.call(window,'hashchange', setState);
|
1505
|
+
|
1506
|
+
// If the `can.route.data` changes, update the hash.
|
1507
|
+
// Using `.serialize()` retrieves the raw data contained in the `observable`.
|
1508
|
+
// This function is ~~throttled~~ debounced so it only updates once even if multiple values changed.
|
1509
|
+
// This might be able to use batchNum and avoid this.
|
1510
|
+
can.route.bind("change", function(ev, attr) {
|
1511
|
+
// indicate that data is changing
|
1512
|
+
changingData = 1;
|
1513
|
+
clearTimeout( timer );
|
1514
|
+
timer = setTimeout(function() {
|
1515
|
+
// indicate that the hash is set to look like the data
|
1516
|
+
changingData = 0;
|
1517
|
+
var serialized = can.route.data.serialize();
|
1518
|
+
location.hash = "#!" + (lastHash = can.route.param(serialized, true))
|
1519
|
+
}, 1);
|
1520
|
+
});
|
1521
|
+
// `onready` event...
|
1522
|
+
can.bind.call(document,"ready",can.route.ready);
|
1523
|
+
|
1524
|
+
(function() {
|
1525
|
+
|
1526
|
+
|
1527
|
+
// ## control.js
|
1528
|
+
// `can.Control`
|
1529
|
+
// _Controller_
|
1530
|
+
|
1531
|
+
// Binds an element, returns a function that unbinds.
|
1532
|
+
var bind = function( el, ev, callback ) {
|
1533
|
+
|
1534
|
+
can.bind.call( el, ev, callback )
|
1535
|
+
|
1536
|
+
return function() {
|
1537
|
+
can.unbind.call(el, ev, callback);
|
1538
|
+
};
|
1539
|
+
},
|
1540
|
+
isFunction = can.isFunction,
|
1541
|
+
extend = can.extend,
|
1542
|
+
each = can.each,
|
1543
|
+
slice = [].slice,
|
1544
|
+
paramReplacer = /\{([^\}]+)\}/g,
|
1545
|
+
special = can.getObject("$.event.special") || {},
|
1546
|
+
|
1547
|
+
// Binds an element, returns a function that unbinds.
|
1548
|
+
delegate = function( el, selector, ev, callback ) {
|
1549
|
+
can.delegate.call(el, selector, ev, callback)
|
1550
|
+
return function() {
|
1551
|
+
can.undelegate.call(el, selector, ev, callback);
|
1552
|
+
};
|
1553
|
+
},
|
1554
|
+
|
1555
|
+
// Calls bind or unbind depending if there is a selector.
|
1556
|
+
binder = function( el, ev, callback, selector ) {
|
1557
|
+
return selector ?
|
1558
|
+
delegate( el, can.trim( selector ), ev, callback ) :
|
1559
|
+
bind( el, ev, callback );
|
1560
|
+
},
|
1561
|
+
|
1562
|
+
// Moves `this` to the first argument, wraps it with `jQuery` if it's an element
|
1563
|
+
shifter = function shifter(context, name) {
|
1564
|
+
var method = typeof name == "string" ? context[name] : name;
|
1565
|
+
if(!isFunction(method)){
|
1566
|
+
method = context[method];
|
1567
|
+
}
|
1568
|
+
return function() {
|
1569
|
+
context.called = name;
|
1570
|
+
return method.apply(context, [this.nodeName ? can.$(this) : this].concat( slice.call(arguments, 0)));
|
1571
|
+
};
|
1572
|
+
},
|
1573
|
+
basicProcessor;
|
1574
|
+
|
1575
|
+
can.Construct("can.Control",
|
1576
|
+
{
|
1577
|
+
// Setup pre-processes which methods are event listeners.
|
1578
|
+
setup: function() {
|
1579
|
+
|
1580
|
+
// Allow contollers to inherit "defaults" from super-classes as it
|
1581
|
+
// done in `can.Construct`
|
1582
|
+
can.Construct.setup.apply( this, arguments );
|
1583
|
+
|
1584
|
+
// If you didn't provide a name, or are `control`, don't do anything.
|
1585
|
+
if ( this !== can.Control ) {
|
1586
|
+
|
1587
|
+
// Cache the underscored names.
|
1588
|
+
var control = this,
|
1589
|
+
funcName;
|
1590
|
+
|
1591
|
+
// Calculate and cache actions.
|
1592
|
+
control.actions = {};
|
1593
|
+
for ( funcName in control.prototype ) {
|
1594
|
+
if ( control._isAction(funcName) ) {
|
1595
|
+
control.actions[funcName] = control._action(funcName);
|
1596
|
+
}
|
1597
|
+
}
|
1598
|
+
}
|
1599
|
+
},
|
1600
|
+
// Return `true` if is an action.
|
1601
|
+
_isAction: function( methodName ) {
|
1602
|
+
|
1603
|
+
var val = this.prototype[methodName],
|
1604
|
+
type = typeof val;
|
1605
|
+
// if not the constructor
|
1606
|
+
return (methodName !== 'constructor') &&
|
1607
|
+
// and is a function or links to a function
|
1608
|
+
( type == "function" || (type == "string" && isFunction(this.prototype[val] ) ) ) &&
|
1609
|
+
// and is in special, a processor, or has a funny character
|
1610
|
+
!! ( special[methodName] || processors[methodName] || /[^\w]/.test(methodName) );
|
1611
|
+
},
|
1612
|
+
// Takes a method name and the options passed to a control
|
1613
|
+
// and tries to return the data necessary to pass to a processor
|
1614
|
+
// (something that binds things).
|
1615
|
+
_action: function( methodName, options ) {
|
1616
|
+
|
1617
|
+
// If we don't have options (a `control` instance), we'll run this
|
1618
|
+
// later.
|
1619
|
+
paramReplacer.lastIndex = 0;
|
1620
|
+
if ( options || ! paramReplacer.test( methodName )) {
|
1621
|
+
// If we have options, run sub to replace templates `{}` with a
|
1622
|
+
// value from the options or the window
|
1623
|
+
var convertedName = options ? can.sub(methodName, [options, window]) : methodName,
|
1624
|
+
|
1625
|
+
// If a `{}` resolves to an object, `convertedName` will be
|
1626
|
+
// an array
|
1627
|
+
arr = can.isArray(convertedName),
|
1628
|
+
|
1629
|
+
// Get the parts of the function
|
1630
|
+
// `[convertedName, delegatePart, eventPart]`
|
1631
|
+
// `/^(?:(.*?)\s)?([\w\.\:>]+)$/` - Breaker `RegExp`.
|
1632
|
+
parts = (arr ? convertedName[1] : convertedName).match(/^(?:(.*?)\s)?([\w\.\:>]+)$/);
|
1633
|
+
|
1634
|
+
var event = parts[2],
|
1635
|
+
processor = processors[event] || basicProcessor;
|
1636
|
+
return {
|
1637
|
+
processor: processor,
|
1638
|
+
parts: parts,
|
1639
|
+
delegate : arr ? convertedName[0] : undefined
|
1640
|
+
};
|
1641
|
+
}
|
1642
|
+
},
|
1643
|
+
// An object of `{eventName : function}` pairs that Control uses to
|
1644
|
+
// hook up events auto-magically.
|
1645
|
+
processors: {},
|
1646
|
+
// A object of name-value pairs that act as default values for a
|
1647
|
+
// control instance
|
1648
|
+
defaults: {}
|
1649
|
+
},
|
1650
|
+
{
|
1651
|
+
// Sets `this.element`, saves the control in `data, binds event
|
1652
|
+
// handlers.
|
1653
|
+
setup: function( element, options ) {
|
1654
|
+
|
1655
|
+
var cls = this.constructor,
|
1656
|
+
pluginname = cls.pluginName || cls._fullName,
|
1657
|
+
arr;
|
1658
|
+
|
1659
|
+
// Want the raw element here.
|
1660
|
+
this.element = can.$(element)
|
1661
|
+
|
1662
|
+
if ( pluginname && pluginname !== 'can_control') {
|
1663
|
+
// Set element and `className` on element.
|
1664
|
+
this.element.addClass(pluginname);
|
1665
|
+
}
|
1666
|
+
|
1667
|
+
(arr = can.data(this.element,"controls")) || can.data(this.element,"controls",arr = []);
|
1668
|
+
arr.push(this);
|
1669
|
+
|
1670
|
+
// Option merging.
|
1671
|
+
this.options = extend({}, cls.defaults, options);
|
1672
|
+
|
1673
|
+
// Bind all event handlers.
|
1674
|
+
this.on();
|
1675
|
+
|
1676
|
+
// Get's passed into `init`.
|
1677
|
+
return [this.element, this.options];
|
1678
|
+
},
|
1679
|
+
on: function( el, selector, eventName, func ) {
|
1680
|
+
|
1681
|
+
if ( ! el ) {
|
1682
|
+
|
1683
|
+
// Adds bindings.
|
1684
|
+
this.off();
|
1685
|
+
|
1686
|
+
// Go through the cached list of actions and use the processor
|
1687
|
+
// to bind
|
1688
|
+
var cls = this.constructor,
|
1689
|
+
bindings = this._bindings,
|
1690
|
+
actions = cls.actions,
|
1691
|
+
element = this.element,
|
1692
|
+
destroyCB = shifter(this,"destroy"),
|
1693
|
+
funcName, ready;
|
1694
|
+
|
1695
|
+
for ( funcName in actions ) {
|
1696
|
+
if ( actions.hasOwnProperty( funcName )) {
|
1697
|
+
ready = actions[funcName] || cls._action(funcName, this.options);
|
1698
|
+
bindings.push(
|
1699
|
+
ready.processor(ready.delegate || element,
|
1700
|
+
ready.parts[2],
|
1701
|
+
ready.parts[1],
|
1702
|
+
funcName,
|
1703
|
+
this));
|
1704
|
+
}
|
1705
|
+
}
|
1706
|
+
|
1707
|
+
|
1708
|
+
// Setup to be destroyed...
|
1709
|
+
// don't bind because we don't want to remove it.
|
1710
|
+
can.bind.call(element,"destroyed", destroyCB);
|
1711
|
+
bindings.push(function( el ) {
|
1712
|
+
can.unbind.call(el,"destroyed", destroyCB);
|
1713
|
+
});
|
1714
|
+
return bindings.length;
|
1715
|
+
}
|
1716
|
+
|
1717
|
+
if ( typeof el == 'string' ) {
|
1718
|
+
func = eventName;
|
1719
|
+
eventName = selector;
|
1720
|
+
selector = el;
|
1721
|
+
el = this.element;
|
1722
|
+
}
|
1723
|
+
|
1724
|
+
if ( typeof func == 'string' ) {
|
1725
|
+
func = shifter(this,func);
|
1726
|
+
}
|
1727
|
+
|
1728
|
+
this._bindings.push( binder( el, eventName, func, selector ));
|
1729
|
+
|
1730
|
+
return this._bindings.length;
|
1731
|
+
},
|
1732
|
+
// Unbinds all event handlers on the controller.
|
1733
|
+
off : function(){
|
1734
|
+
var el = this.element[0]
|
1735
|
+
each(this._bindings || [], function( value ) {
|
1736
|
+
value(el);
|
1737
|
+
});
|
1738
|
+
// Adds bindings.
|
1739
|
+
this._bindings = [];
|
1740
|
+
},
|
1741
|
+
// Prepares a `control` for garbage collection
|
1742
|
+
destroy: function() {
|
1743
|
+
var Class = this.constructor,
|
1744
|
+
pluginName = Class.pluginName || Class._fullName,
|
1745
|
+
controls;
|
1746
|
+
|
1747
|
+
// Unbind bindings.
|
1748
|
+
this.off();
|
1749
|
+
|
1750
|
+
if(pluginName && pluginName !== 'can_control'){
|
1751
|
+
// Remove the `className`.
|
1752
|
+
this.element.removeClass(pluginName);
|
1753
|
+
}
|
1754
|
+
|
1755
|
+
// Remove from `data`.
|
1756
|
+
controls = can.data(this.element,"controls");
|
1757
|
+
controls.splice(can.inArray(this, controls),1);
|
1758
|
+
|
1759
|
+
can.trigger( this, "destroyed"); // In case we want to know if the `control` is removed.
|
1760
|
+
|
1761
|
+
this.element = null;
|
1762
|
+
}
|
1763
|
+
});
|
1764
|
+
|
1765
|
+
var processors = can.Control.processors,
|
1766
|
+
|
1767
|
+
// Processors do the binding.
|
1768
|
+
// They return a function that unbinds when called.
|
1769
|
+
//
|
1770
|
+
// The basic processor that binds events.
|
1771
|
+
basicProcessor = function( el, event, selector, methodName, control ) {
|
1772
|
+
return binder( el, event, shifter(control, methodName), selector);
|
1773
|
+
};
|
1774
|
+
|
1775
|
+
// Set common events to be processed as a `basicProcessor`
|
1776
|
+
each(["change", "click", "contextmenu", "dblclick", "keydown", "keyup",
|
1777
|
+
"keypress", "mousedown", "mousemove", "mouseout", "mouseover",
|
1778
|
+
"mouseup", "reset", "resize", "scroll", "select", "submit", "focusin",
|
1779
|
+
"focusout", "mouseenter", "mouseleave"], function( v ) {
|
1780
|
+
processors[v] = basicProcessor;
|
1781
|
+
});
|
1782
|
+
|
1783
|
+
}());
|
1784
|
+
|
1785
|
+
|
1786
|
+
// ## control/route.js
|
1787
|
+
// _Controller route integration._
|
1788
|
+
|
1789
|
+
can.Control.processors.route = function( el, event, selector, funcName, controller ) {
|
1790
|
+
can.route( selector || "" )
|
1791
|
+
var batchNum,
|
1792
|
+
check = function( ev, attr, how ) {
|
1793
|
+
if ( can.route.attr('route') === ( selector || "" ) &&
|
1794
|
+
( ev.batchNum === undefined || ev.batchNum !== batchNum ) ) {
|
1795
|
+
|
1796
|
+
batchNum = ev.batchNum;
|
1797
|
+
|
1798
|
+
var d = can.route.attr();
|
1799
|
+
delete d.route;
|
1800
|
+
if(can.isFunction(controller[funcName])){
|
1801
|
+
controller[funcName]( d )
|
1802
|
+
}else {
|
1803
|
+
controller[controller[funcName]](d)
|
1804
|
+
}
|
1805
|
+
|
1806
|
+
}
|
1807
|
+
}
|
1808
|
+
can.route.bind( 'change', check );
|
1809
|
+
return function() {
|
1810
|
+
can.route.unbind( 'change', check )
|
1811
|
+
}
|
1812
|
+
}
|
1813
|
+
;
|
1814
|
+
|
1815
|
+
// ## view.js
|
1816
|
+
// `can.view`
|
1817
|
+
// _Templating abstraction._
|
1818
|
+
|
1819
|
+
var isFunction = can.isFunction,
|
1820
|
+
makeArray = can.makeArray,
|
1821
|
+
// Used for hookup `id`s.
|
1822
|
+
hookupId = 1,
|
1823
|
+
$view = can.view = function(view, data, helpers, callback){
|
1824
|
+
// Get the result.
|
1825
|
+
var result = $view.render(view, data, helpers, callback);
|
1826
|
+
if(can.isDeferred(result)){
|
1827
|
+
return result.pipe(function(result){
|
1828
|
+
return $view.frag(result);
|
1829
|
+
})
|
1830
|
+
}
|
1831
|
+
|
1832
|
+
// Convert it into a dom frag.
|
1833
|
+
return $view.frag(result);
|
1834
|
+
};
|
1835
|
+
|
1836
|
+
can.extend( $view, {
|
1837
|
+
// creates a frag and hooks it up all at once
|
1838
|
+
frag: function(result, parentNode ){
|
1839
|
+
return $view.hookup( $view.fragment(result), parentNode );
|
1840
|
+
},
|
1841
|
+
// simply creates a frag
|
1842
|
+
// this is used internally to create a frag
|
1843
|
+
// insert it
|
1844
|
+
// then hook it up
|
1845
|
+
fragment: function(result){
|
1846
|
+
var frag = can.buildFragment(result,document.body);
|
1847
|
+
// If we have an empty frag...
|
1848
|
+
if(!frag.childNodes.length) {
|
1849
|
+
frag.appendChild(document.createTextNode(''))
|
1850
|
+
}
|
1851
|
+
return frag;
|
1852
|
+
},
|
1853
|
+
// Convert a path like string into something that's ok for an `element` ID.
|
1854
|
+
toId : function( src ) {
|
1855
|
+
return can.map(src.toString().split(/\/|\./g), function( part ) {
|
1856
|
+
// Dont include empty strings in toId functions
|
1857
|
+
if ( part ) {
|
1858
|
+
return part;
|
1859
|
+
}
|
1860
|
+
}).join("_");
|
1861
|
+
},
|
1862
|
+
hookup: function(fragment, parentNode ){
|
1863
|
+
var hookupEls = [],
|
1864
|
+
id,
|
1865
|
+
func,
|
1866
|
+
el,
|
1867
|
+
i=0;
|
1868
|
+
|
1869
|
+
// Get all `childNodes`.
|
1870
|
+
can.each(fragment.childNodes ? can.makeArray(fragment.childNodes) : fragment, function(node){
|
1871
|
+
if(node.nodeType === 1){
|
1872
|
+
hookupEls.push(node)
|
1873
|
+
hookupEls.push.apply(hookupEls, can.makeArray( node.getElementsByTagName('*')))
|
1874
|
+
}
|
1875
|
+
});
|
1876
|
+
// Filter by `data-view-id` attribute.
|
1877
|
+
for (; el = hookupEls[i++]; ) {
|
1878
|
+
|
1879
|
+
if ( el.getAttribute && (id = el.getAttribute('data-view-id')) && (func = $view.hookups[id]) ) {
|
1880
|
+
func(el, parentNode, id);
|
1881
|
+
delete $view.hookups[id];
|
1882
|
+
el.removeAttribute('data-view-id');
|
1883
|
+
}
|
1884
|
+
}
|
1885
|
+
return fragment;
|
1886
|
+
},
|
1887
|
+
hookups: {},
|
1888
|
+
hook: function( cb ) {
|
1889
|
+
$view.hookups[++hookupId] = cb;
|
1890
|
+
return " data-view-id='"+hookupId+"'";
|
1891
|
+
},
|
1892
|
+
cached: {},
|
1893
|
+
cache: true,
|
1894
|
+
register: function( info ) {
|
1895
|
+
this.types["." + info.suffix] = info;
|
1896
|
+
},
|
1897
|
+
types: {},
|
1898
|
+
ext: ".ejs",
|
1899
|
+
registerScript: function() {},
|
1900
|
+
preload: function( ) {},
|
1901
|
+
render: function( view, data, helpers, callback ) {
|
1902
|
+
// If helpers is a `function`, it is actually a callback.
|
1903
|
+
if ( isFunction( helpers )) {
|
1904
|
+
callback = helpers;
|
1905
|
+
helpers = undefined;
|
1906
|
+
}
|
1907
|
+
|
1908
|
+
// See if we got passed any deferreds.
|
1909
|
+
var deferreds = getDeferreds(data);
|
1910
|
+
|
1911
|
+
|
1912
|
+
if ( deferreds.length ) { // Does data contain any deferreds?
|
1913
|
+
// The deferred that resolves into the rendered content...
|
1914
|
+
var deferred = new can.Deferred();
|
1915
|
+
|
1916
|
+
// Add the view request to the list of deferreds.
|
1917
|
+
deferreds.push(get(view, true))
|
1918
|
+
|
1919
|
+
// Wait for the view and all deferreds to finish...
|
1920
|
+
can.when.apply(can, deferreds).then(function( resolved ) {
|
1921
|
+
// Get all the resolved deferreds.
|
1922
|
+
var objs = makeArray(arguments),
|
1923
|
+
// Renderer is the last index of the data.
|
1924
|
+
renderer = objs.pop(),
|
1925
|
+
// The result of the template rendering with data.
|
1926
|
+
result;
|
1927
|
+
|
1928
|
+
// Make data look like the resolved deferreds.
|
1929
|
+
if ( can.isDeferred(data) ) {
|
1930
|
+
data = usefulPart(resolved);
|
1931
|
+
}
|
1932
|
+
else {
|
1933
|
+
// Go through each prop in data again and
|
1934
|
+
// replace the defferreds with what they resolved to.
|
1935
|
+
for ( var prop in data ) {
|
1936
|
+
if ( can.isDeferred(data[prop]) ) {
|
1937
|
+
data[prop] = usefulPart(objs.shift());
|
1938
|
+
}
|
1939
|
+
}
|
1940
|
+
}
|
1941
|
+
// Get the rendered result.
|
1942
|
+
result = renderer(data, helpers);
|
1943
|
+
|
1944
|
+
// Resolve with the rendered view.
|
1945
|
+
deferred.resolve(result);
|
1946
|
+
// If there's a `callback`, call it back with the result.
|
1947
|
+
callback && callback(result);
|
1948
|
+
});
|
1949
|
+
// Return the deferred...
|
1950
|
+
return deferred;
|
1951
|
+
}
|
1952
|
+
else {
|
1953
|
+
// No deferreds! Render this bad boy.
|
1954
|
+
var response,
|
1955
|
+
// If there's a `callback` function
|
1956
|
+
async = isFunction( callback ),
|
1957
|
+
// Get the `view` type
|
1958
|
+
deferred = get(view, async);
|
1959
|
+
|
1960
|
+
// If we are `async`...
|
1961
|
+
if ( async ) {
|
1962
|
+
// Return the deferred
|
1963
|
+
response = deferred;
|
1964
|
+
// And fire callback with the rendered result.
|
1965
|
+
deferred.then(function( renderer ) {
|
1966
|
+
callback(renderer(data, helpers))
|
1967
|
+
})
|
1968
|
+
} else {
|
1969
|
+
// Otherwise, the deferred is complete, so
|
1970
|
+
// set response to the result of the rendering.
|
1971
|
+
deferred.then(function( renderer ) {
|
1972
|
+
response = renderer(data, helpers);
|
1973
|
+
});
|
1974
|
+
}
|
1975
|
+
|
1976
|
+
return response;
|
1977
|
+
}
|
1978
|
+
}
|
1979
|
+
});
|
1980
|
+
// Returns `true` if something looks like a deferred.
|
1981
|
+
can.isDeferred = function( obj ) {
|
1982
|
+
return obj && isFunction(obj.then) && isFunction(obj.pipe) // Check if `obj` is a `can.Deferred`.
|
1983
|
+
}
|
1984
|
+
// Makes sure there's a template, if not, have `steal` provide a warning.
|
1985
|
+
var checkText = function( text, url ) {
|
1986
|
+
if ( ! text.length ) {
|
1987
|
+
//@steal-remove-start
|
1988
|
+
//@steal-remove-end
|
1989
|
+
throw "can.view: No template or empty template:" + url;
|
1990
|
+
}
|
1991
|
+
},
|
1992
|
+
// `Returns a `view` renderer deferred.
|
1993
|
+
// `url` - The url to the template.
|
1994
|
+
// `async` - If the ajax request should be asynchronous.
|
1995
|
+
// Returns a deferred.
|
1996
|
+
get = function( url, async ) {
|
1997
|
+
|
1998
|
+
|
1999
|
+
var suffix = url.match(/\.[\w\d]+$/),
|
2000
|
+
type,
|
2001
|
+
// If we are reading a script element for the content of the template,
|
2002
|
+
// `el` will be set to that script element.
|
2003
|
+
el,
|
2004
|
+
// A unique identifier for the view (used for caching).
|
2005
|
+
// This is typically derived from the element id or
|
2006
|
+
// the url for the template.
|
2007
|
+
id,
|
2008
|
+
// The ajax request used to retrieve the template content.
|
2009
|
+
jqXHR,
|
2010
|
+
// Used to generate the response.
|
2011
|
+
response = function( text ) {
|
2012
|
+
// Get the renderer function.
|
2013
|
+
var func = type.renderer(id, text),
|
2014
|
+
d = new can.Deferred();
|
2015
|
+
d.resolve(func)
|
2016
|
+
// Cache if we are caching.
|
2017
|
+
if ( $view.cache ) {
|
2018
|
+
$view.cached[id] = d;
|
2019
|
+
}
|
2020
|
+
// Return the objects for the response's `dataTypes`
|
2021
|
+
// (in this case view).
|
2022
|
+
return d;
|
2023
|
+
};
|
2024
|
+
|
2025
|
+
//If the url has a #, we assume we want to use an inline template
|
2026
|
+
//from a script element and not current page's HTML
|
2027
|
+
if( url.match(/^#/) ) {
|
2028
|
+
url = url.substr(1);
|
2029
|
+
}
|
2030
|
+
// If we have an inline template, derive the suffix from the `text/???` part.
|
2031
|
+
// This only supports `<script>` tags.
|
2032
|
+
if ( el = document.getElementById(url) ) {
|
2033
|
+
suffix = "."+el.type.match(/\/(x\-)?(.+)/)[2];
|
2034
|
+
}
|
2035
|
+
|
2036
|
+
// If there is no suffix, add one.
|
2037
|
+
if (!suffix && !$view.cached[url] ) {
|
2038
|
+
url += ( suffix = $view.ext );
|
2039
|
+
}
|
2040
|
+
|
2041
|
+
if ( can.isArray( suffix )) {
|
2042
|
+
suffix = suffix[0]
|
2043
|
+
}
|
2044
|
+
|
2045
|
+
// Convert to a unique and valid id.
|
2046
|
+
id = can.view.toId(url);
|
2047
|
+
|
2048
|
+
// If an absolute path, use `steal` to get it.
|
2049
|
+
// You should only be using `//` if you are using `steal`.
|
2050
|
+
if ( url.match(/^\/\//) ) {
|
2051
|
+
var sub = url.substr(2);
|
2052
|
+
url = ! window.steal ?
|
2053
|
+
"/" + sub :
|
2054
|
+
steal.root.mapJoin(sub);
|
2055
|
+
}
|
2056
|
+
|
2057
|
+
// Set the template engine type.
|
2058
|
+
type = $view.types[suffix];
|
2059
|
+
|
2060
|
+
// If it is cached,
|
2061
|
+
if ( $view.cached[id] ) {
|
2062
|
+
// Return the cached deferred renderer.
|
2063
|
+
return $view.cached[id];
|
2064
|
+
|
2065
|
+
// Otherwise if we are getting this from a `<script>` element.
|
2066
|
+
} else if ( el ) {
|
2067
|
+
// Resolve immediately with the element's `innerHTML`.
|
2068
|
+
return response(el.innerHTML);
|
2069
|
+
} else {
|
2070
|
+
// Make an ajax request for text.
|
2071
|
+
var d = new can.Deferred();
|
2072
|
+
can.ajax({
|
2073
|
+
async: async,
|
2074
|
+
url: url,
|
2075
|
+
dataType: "text",
|
2076
|
+
error: function(jqXHR) {
|
2077
|
+
checkText("", url);
|
2078
|
+
d.reject(jqXHR);
|
2079
|
+
},
|
2080
|
+
success: function( text ) {
|
2081
|
+
// Make sure we got some text back.
|
2082
|
+
checkText(text, url);
|
2083
|
+
d.resolve(type.renderer(id, text))
|
2084
|
+
// Cache if if we are caching.
|
2085
|
+
if ( $view.cache ) {
|
2086
|
+
$view.cached[id] = d;
|
2087
|
+
}
|
2088
|
+
|
2089
|
+
}
|
2090
|
+
});
|
2091
|
+
return d;
|
2092
|
+
}
|
2093
|
+
},
|
2094
|
+
// Gets an `array` of deferreds from an `object`.
|
2095
|
+
// This only goes one level deep.
|
2096
|
+
getDeferreds = function( data ) {
|
2097
|
+
var deferreds = [];
|
2098
|
+
|
2099
|
+
// pull out deferreds
|
2100
|
+
if ( can.isDeferred(data) ) {
|
2101
|
+
return [data]
|
2102
|
+
} else {
|
2103
|
+
for ( var prop in data ) {
|
2104
|
+
if ( can.isDeferred(data[prop]) ) {
|
2105
|
+
deferreds.push(data[prop]);
|
2106
|
+
}
|
2107
|
+
}
|
2108
|
+
}
|
2109
|
+
return deferreds;
|
2110
|
+
},
|
2111
|
+
// Gets the useful part of a resolved deferred.
|
2112
|
+
// This is for `model`s and `can.ajax` that resolve to an `array`.
|
2113
|
+
usefulPart = function( resolved ) {
|
2114
|
+
return can.isArray(resolved) && resolved[1] === 'success' ? resolved[0] : resolved
|
2115
|
+
};
|
2116
|
+
|
2117
|
+
|
2118
|
+
if ( window.steal ) {
|
2119
|
+
steal.type("view js", function( options, success, error ) {
|
2120
|
+
var type = can.view.types["." + options.type],
|
2121
|
+
id = can.view.toId(options.rootSrc);
|
2122
|
+
|
2123
|
+
options.text = "steal('" + (type.plugin || "can/view/" + options.type) + "').then(function($){" + "can.view.preload('" + id + "'," + options.text + ");\n})";
|
2124
|
+
success();
|
2125
|
+
})
|
2126
|
+
}
|
2127
|
+
|
2128
|
+
//!steal-pluginify-remove-start
|
2129
|
+
can.extend(can.view, {
|
2130
|
+
register: function( info ) {
|
2131
|
+
this.types["." + info.suffix] = info;
|
2132
|
+
|
2133
|
+
if ( window.steal ) {
|
2134
|
+
steal.type(info.suffix + " view js", function( options, success, error ) {
|
2135
|
+
var type = can.view.types["." + options.type],
|
2136
|
+
id = can.view.toId(options.rootSrc+'');
|
2137
|
+
|
2138
|
+
options.text = type.script(id, options.text)
|
2139
|
+
success();
|
2140
|
+
})
|
2141
|
+
}
|
2142
|
+
can.view[info.suffix] = function(id, text){
|
2143
|
+
$view.preload(id, info.renderer(id, text) )
|
2144
|
+
}
|
2145
|
+
},
|
2146
|
+
registerScript: function( type, id, src ) {
|
2147
|
+
return "can.view.preload('" + id + "'," + $view.types["." + type].script(id, src) + ");";
|
2148
|
+
},
|
2149
|
+
preload: function( id, renderer ) {
|
2150
|
+
can.view.cached[id] = new can.Deferred().resolve(function( data, helpers ) {
|
2151
|
+
return renderer.call(data, data, helpers);
|
2152
|
+
});
|
2153
|
+
}
|
2154
|
+
|
2155
|
+
});
|
2156
|
+
//!steal-pluginify-remove-end
|
2157
|
+
|
2158
|
+
|
2159
|
+
// returns the
|
2160
|
+
// - observes and attr methods are called by func
|
2161
|
+
// - the value returned by func
|
2162
|
+
// ex: `{value: 100, observed: [{obs: o, attr: "completed"}]}`
|
2163
|
+
var getValueAndObserved = function(func, self){
|
2164
|
+
|
2165
|
+
var oldReading;
|
2166
|
+
if (can.Observe) {
|
2167
|
+
// Set a callback on can.Observe to know
|
2168
|
+
// when an attr is read.
|
2169
|
+
// Keep a reference to the old reader
|
2170
|
+
// if there is one. This is used
|
2171
|
+
// for nested live binding.
|
2172
|
+
oldReading = can.Observe.__reading;
|
2173
|
+
can.Observe.__reading = function(obj, attr){
|
2174
|
+
// Add the observe and attr that was read
|
2175
|
+
// to `observed`
|
2176
|
+
observed.push({
|
2177
|
+
obj: obj,
|
2178
|
+
attr: attr
|
2179
|
+
});
|
2180
|
+
}
|
2181
|
+
}
|
2182
|
+
|
2183
|
+
var observed = [],
|
2184
|
+
// Call the "wrapping" function to get the value. `observed`
|
2185
|
+
// will have the observe/attribute pairs that were read.
|
2186
|
+
value = func.call(self);
|
2187
|
+
|
2188
|
+
// Set back so we are no longer reading.
|
2189
|
+
if(can.Observe){
|
2190
|
+
can.Observe.__reading = oldReading;
|
2191
|
+
}
|
2192
|
+
return {
|
2193
|
+
value : value,
|
2194
|
+
observed : observed
|
2195
|
+
}
|
2196
|
+
},
|
2197
|
+
// Calls `callback(newVal, oldVal)` everytime an observed property
|
2198
|
+
// called within `getterSetter` is changed and creates a new result of `getterSetter`.
|
2199
|
+
// Also returns an object that can teardown all event handlers.
|
2200
|
+
computeBinder = function(getterSetter, context, callback){
|
2201
|
+
// track what we are observing
|
2202
|
+
var observing = {},
|
2203
|
+
// a flag indicating if this observe/attr pair is already bound
|
2204
|
+
matched = true,
|
2205
|
+
// the data to return
|
2206
|
+
data = {
|
2207
|
+
// we will maintain the value while live-binding is taking place
|
2208
|
+
value : undefined,
|
2209
|
+
// a teardown method that stops listening
|
2210
|
+
teardown: function(){
|
2211
|
+
for ( var name in observing ) {
|
2212
|
+
var ob = observing[name];
|
2213
|
+
ob.observe.obj.unbind(ob.observe.attr, onchanged);
|
2214
|
+
delete observing[name];
|
2215
|
+
}
|
2216
|
+
}
|
2217
|
+
};
|
2218
|
+
|
2219
|
+
// when a property value is cahnged
|
2220
|
+
var onchanged = function(){
|
2221
|
+
// store the old value
|
2222
|
+
var oldValue = data.value,
|
2223
|
+
// get the new value
|
2224
|
+
newvalue = getValueAndBind();
|
2225
|
+
// update the value reference (in case someone reads)
|
2226
|
+
data.value = newvalue
|
2227
|
+
// if a change happened
|
2228
|
+
if(newvalue !== oldValue){
|
2229
|
+
callback(newvalue, oldValue);
|
2230
|
+
};
|
2231
|
+
};
|
2232
|
+
|
2233
|
+
// gets the value returned by `getterSetter` and also binds to any attributes
|
2234
|
+
// read by the call
|
2235
|
+
var getValueAndBind = function(){
|
2236
|
+
var info = getValueAndObserved( getterSetter, context ),
|
2237
|
+
newObserveSet = info.observed;
|
2238
|
+
|
2239
|
+
var value = info.value;
|
2240
|
+
matched = !matched;
|
2241
|
+
|
2242
|
+
// go through every attribute read by this observe
|
2243
|
+
can.each(newObserveSet, function(ob){
|
2244
|
+
// if the observe/attribute pair is being observed
|
2245
|
+
if(observing[ob.obj._namespace+"|"+ob.attr]){
|
2246
|
+
// mark at as observed
|
2247
|
+
observing[ob.obj._namespace+"|"+ob.attr].matched = matched;
|
2248
|
+
} else {
|
2249
|
+
// otherwise, set the observe/attribute on oldObserved, marking it as being observed
|
2250
|
+
observing[ob.obj._namespace+"|"+ob.attr] = {
|
2251
|
+
matched: matched,
|
2252
|
+
observe: ob
|
2253
|
+
};
|
2254
|
+
ob.obj.bind(ob.attr, onchanged)
|
2255
|
+
}
|
2256
|
+
});
|
2257
|
+
|
2258
|
+
// Iterate through oldObserved, looking for observe/attributes
|
2259
|
+
// that are no longer being bound and unbind them
|
2260
|
+
for ( var name in observing ) {
|
2261
|
+
var ob = observing[name];
|
2262
|
+
if(ob.matched !== matched){
|
2263
|
+
ob.observe.obj.unbind(ob.observe.attr, onchanged);
|
2264
|
+
delete observing[name];
|
2265
|
+
}
|
2266
|
+
}
|
2267
|
+
return value;
|
2268
|
+
}
|
2269
|
+
// set the initial value
|
2270
|
+
data.value = getValueAndBind();
|
2271
|
+
data.isListening = ! can.isEmptyObject(observing);
|
2272
|
+
return data;
|
2273
|
+
}
|
2274
|
+
|
2275
|
+
// if no one is listening ... we can not calculate every time
|
2276
|
+
can.compute = function(getterSetter, context){
|
2277
|
+
if(getterSetter.isComputed){
|
2278
|
+
return getterSetter;
|
2279
|
+
}
|
2280
|
+
// get the value right away
|
2281
|
+
// TODO: eventually we can defer this until a bind or a read
|
2282
|
+
var computedData,
|
2283
|
+
bindings = 0,
|
2284
|
+
computed,
|
2285
|
+
canbind = true;
|
2286
|
+
if(typeof getterSetter === "function"){
|
2287
|
+
computed = function(value){
|
2288
|
+
if(value === undefined){
|
2289
|
+
// we are reading
|
2290
|
+
if(computedData){
|
2291
|
+
return computedData.value;
|
2292
|
+
} else {
|
2293
|
+
return getterSetter.call(context || this)
|
2294
|
+
}
|
2295
|
+
} else {
|
2296
|
+
return getterSetter.apply(context || this, arguments)
|
2297
|
+
}
|
2298
|
+
}
|
2299
|
+
|
2300
|
+
} else {
|
2301
|
+
// we just gave it a value
|
2302
|
+
computed = function(val){
|
2303
|
+
if(val === undefined){
|
2304
|
+
return getterSetter;
|
2305
|
+
} else {
|
2306
|
+
var old = getterSetter;
|
2307
|
+
getterSetter = val;
|
2308
|
+
if( old !== val){
|
2309
|
+
can.trigger(computed, "change",[val, old]);
|
2310
|
+
}
|
2311
|
+
|
2312
|
+
return val;
|
2313
|
+
}
|
2314
|
+
|
2315
|
+
}
|
2316
|
+
canbind = false;
|
2317
|
+
}
|
2318
|
+
computed.isComputed = true;
|
2319
|
+
|
2320
|
+
|
2321
|
+
computed.bind = function(ev, handler){
|
2322
|
+
can.addEvent.apply(computed, arguments);
|
2323
|
+
if( bindings === 0 && canbind){
|
2324
|
+
// setup live-binding
|
2325
|
+
computedData = computeBinder(getterSetter, context || this, function(newValue, oldValue){
|
2326
|
+
can.trigger(computed, "change",[newValue, oldValue])
|
2327
|
+
});
|
2328
|
+
}
|
2329
|
+
bindings++;
|
2330
|
+
}
|
2331
|
+
computed.unbind = function(ev, handler){
|
2332
|
+
can.removeEvent.apply(computed, arguments);
|
2333
|
+
bindings--;
|
2334
|
+
if( bindings === 0 && canbind){
|
2335
|
+
computedData.teardown();
|
2336
|
+
}
|
2337
|
+
|
2338
|
+
};
|
2339
|
+
return computed;
|
2340
|
+
};
|
2341
|
+
can.compute.binder = computeBinder;
|
2342
|
+
|
2343
|
+
// ## ejs.js
|
2344
|
+
// `can.EJS`
|
2345
|
+
// _Embedded JavaScript Templates._
|
2346
|
+
|
2347
|
+
// Helper methods.
|
2348
|
+
var myEval = function( script ) {
|
2349
|
+
eval(script);
|
2350
|
+
},
|
2351
|
+
extend = can.extend,
|
2352
|
+
// Regular expressions for caching.
|
2353
|
+
quickFunc = /\s*\(([\$\w]+)\)\s*->([^\n]*)/,
|
2354
|
+
attrReg = /([^\s]+)=$/,
|
2355
|
+
newLine = /(\r|\n)+/g,
|
2356
|
+
attributeReplace = /__!!__/g,
|
2357
|
+
tagMap = {
|
2358
|
+
"": "span",
|
2359
|
+
table: "tr",
|
2360
|
+
tr: "td",
|
2361
|
+
ol: "li",
|
2362
|
+
ul: "li",
|
2363
|
+
tbody: "tr",
|
2364
|
+
thead: "tr",
|
2365
|
+
tfoot: "tr"
|
2366
|
+
},
|
2367
|
+
// Escapes characters starting with `\`.
|
2368
|
+
clean = function( content ) {
|
2369
|
+
return content
|
2370
|
+
.split('\\').join("\\\\")
|
2371
|
+
.split("\n").join("\\n")
|
2372
|
+
.split('"').join('\\"')
|
2373
|
+
.split("\t").join("\\t");
|
2374
|
+
},
|
2375
|
+
bracketNum = function(content){
|
2376
|
+
return (--content.split("{").length) - (--content.split("}").length);
|
2377
|
+
},
|
2378
|
+
// Cross-browser attribute methods.
|
2379
|
+
// These should be mapped to the underlying library.
|
2380
|
+
attrMap = {
|
2381
|
+
"class" : "className"
|
2382
|
+
},
|
2383
|
+
bool = can.each(["checked","disabled","readonly","required"], function(n){
|
2384
|
+
attrMap[n] = n;
|
2385
|
+
}),
|
2386
|
+
setAttr = function(el, attrName, val){
|
2387
|
+
attrMap[attrName] ?
|
2388
|
+
(el[attrMap[attrName]] = can.inArray(attrName,bool) > -1? true : val):
|
2389
|
+
el.setAttribute(attrName, val);
|
2390
|
+
},
|
2391
|
+
getAttr = function(el, attrName){
|
2392
|
+
return attrMap[attrName]?
|
2393
|
+
el[attrMap[attrName]]:
|
2394
|
+
el.getAttribute(attrName);
|
2395
|
+
},
|
2396
|
+
removeAttr = function(el, attrName){
|
2397
|
+
if(can.inArray(attrName,bool) > -1){
|
2398
|
+
el[attrName] = false;
|
2399
|
+
} else{
|
2400
|
+
el.removeAttribute(attrName)
|
2401
|
+
}
|
2402
|
+
},
|
2403
|
+
// a helper to get the parentNode for a given element el
|
2404
|
+
// if el is in a documentFragment, it will return defaultParentNode
|
2405
|
+
getParentNode = function(el, defaultParentNode){
|
2406
|
+
return defaultParentNode && el.parentNode.nodeType === 11 ? defaultParentNode : el.parentNode;
|
2407
|
+
},
|
2408
|
+
// helper to know if property is not an expando on oldObserved's list of observes
|
2409
|
+
// this should probably be removed and oldObserved should just have a
|
2410
|
+
// property with observes
|
2411
|
+
observeProp = function(name){
|
2412
|
+
return name.indexOf("|") >= 0;
|
2413
|
+
},
|
2414
|
+
// Returns escaped/sanatized content for anything other than a live-binding
|
2415
|
+
contentEscape = function( txt ) {
|
2416
|
+
return (typeof txt == 'string' || typeof txt == 'number') ?
|
2417
|
+
can.esc( txt ) :
|
2418
|
+
contentText(txt);
|
2419
|
+
},
|
2420
|
+
// Returns text content for anything other than a live-binding
|
2421
|
+
contentText = function( input ) {
|
2422
|
+
|
2423
|
+
// If it's a string, return.
|
2424
|
+
if ( typeof input == 'string' ) {
|
2425
|
+
return input;
|
2426
|
+
}
|
2427
|
+
// If has no value, return an empty string.
|
2428
|
+
if ( !input && input != 0 ) {
|
2429
|
+
return '';
|
2430
|
+
}
|
2431
|
+
|
2432
|
+
// If it's an object, and it has a hookup method.
|
2433
|
+
var hook = (input.hookup &&
|
2434
|
+
|
2435
|
+
// Make a function call the hookup method.
|
2436
|
+
function( el, id ) {
|
2437
|
+
input.hookup.call(input, el, id);
|
2438
|
+
}) ||
|
2439
|
+
|
2440
|
+
// Or if it's a `function`, just use the input.
|
2441
|
+
(typeof input == 'function' && input);
|
2442
|
+
|
2443
|
+
// Finally, if there is a `function` to hookup on some dom,
|
2444
|
+
// add it to pending hookups.
|
2445
|
+
if ( hook ) {
|
2446
|
+
pendingHookups.push(hook);
|
2447
|
+
return '';
|
2448
|
+
}
|
2449
|
+
|
2450
|
+
// Finally, if all else is `false`, `toString()` it.
|
2451
|
+
return "" + input;
|
2452
|
+
},
|
2453
|
+
// The EJS constructor function
|
2454
|
+
EJS = function( options ) {
|
2455
|
+
// Supports calling EJS without the constructor
|
2456
|
+
// This returns a function that renders the template.
|
2457
|
+
if ( this.constructor != EJS ) {
|
2458
|
+
var ejs = new EJS(options);
|
2459
|
+
return function( data, helpers ) {
|
2460
|
+
return ejs.render(data, helpers);
|
2461
|
+
};
|
2462
|
+
}
|
2463
|
+
// If we get a `function` directly, it probably is coming from
|
2464
|
+
// a `steal`-packaged view.
|
2465
|
+
if ( typeof options == "function" ) {
|
2466
|
+
this.template = {
|
2467
|
+
fn: options
|
2468
|
+
};
|
2469
|
+
return;
|
2470
|
+
}
|
2471
|
+
// Set options on self.
|
2472
|
+
extend(this, options);
|
2473
|
+
this.template = scan(this.text, this.name);
|
2474
|
+
};
|
2475
|
+
|
2476
|
+
can.EJS = EJS;
|
2477
|
+
EJS.prototype.
|
2478
|
+
render = function( object, extraHelpers ) {
|
2479
|
+
object = object || {};
|
2480
|
+
return this.template.fn.call(object, object, new EJS.Helpers(object, extraHelpers || {}));
|
2481
|
+
};
|
2482
|
+
extend(EJS, {
|
2483
|
+
// Called to return the content within a magic tag like `<%= %>`.
|
2484
|
+
// - escape - if the content returned should be escaped
|
2485
|
+
// - tagName - the tag name the magic tag is within or the one that proceeds the magic tag
|
2486
|
+
// - status - where the tag is in. The status can be:
|
2487
|
+
// - _STRING_ - The name of the attribute the magic tag is within
|
2488
|
+
// - `1` - The magic tag is within a tag like `<div <%= %>>`
|
2489
|
+
// - `0` - The magic tag is outside (or between) tags like `<div><%= %></div>`
|
2490
|
+
// - self - the `this` the template was called with
|
2491
|
+
// - func - the "wrapping" function. For example: `<%= task.attr('name') %>` becomes
|
2492
|
+
// `(function(){return task.attr('name')})
|
2493
|
+
txt : function(escape, tagName, status, self, func){
|
2494
|
+
// call the "wrapping" function and get the binding information
|
2495
|
+
var binding = can.compute.binder(func, self, function(newVal, oldVal){
|
2496
|
+
// call the update method we will define for each
|
2497
|
+
// type of attribute
|
2498
|
+
update(newVal, oldVal)
|
2499
|
+
});
|
2500
|
+
|
2501
|
+
// If we had no observes just return the value returned by func.
|
2502
|
+
if(!binding.isListening){
|
2503
|
+
return (escape || status !== 0? contentEscape : contentText)(binding.value);
|
2504
|
+
}
|
2505
|
+
// The following are helper methods or varaibles that will
|
2506
|
+
// be defined by one of the various live-updating schemes.
|
2507
|
+
|
2508
|
+
// The parent element we are listening to for teardown
|
2509
|
+
var parentElement,
|
2510
|
+
// if the parent element is removed, teardown the binding
|
2511
|
+
setupTeardownOnDestroy = function(el){
|
2512
|
+
can.bind.call(el,'destroyed', binding.teardown)
|
2513
|
+
parentElement = el;
|
2514
|
+
},
|
2515
|
+
// if there is no parent, undo bindings
|
2516
|
+
teardownCheck = function(parent){
|
2517
|
+
if(!parent){
|
2518
|
+
binding.teardown();
|
2519
|
+
can.unbind.call(parentElement,'destroyed', binding.teardown)
|
2520
|
+
}
|
2521
|
+
},
|
2522
|
+
// the tag type to insert
|
2523
|
+
tag = (tagMap[tagName] || "span"),
|
2524
|
+
// this will be filled in if binding.isListening
|
2525
|
+
update;
|
2526
|
+
|
2527
|
+
|
2528
|
+
// The magic tag is outside or between tags.
|
2529
|
+
if(status == 0){
|
2530
|
+
// Return an element tag with a hookup in place of the content
|
2531
|
+
return "<" +tag+can.view.hook(
|
2532
|
+
escape ?
|
2533
|
+
// If we are escaping, replace the parentNode with
|
2534
|
+
// a text node who's value is `func`'s return value.
|
2535
|
+
function(el, parentNode){
|
2536
|
+
// updates the text of the text node
|
2537
|
+
update = function(newVal){
|
2538
|
+
node.nodeValue = ""+newVal;
|
2539
|
+
teardownCheck(node.parentNode);
|
2540
|
+
};
|
2541
|
+
|
2542
|
+
var parent = getParentNode(el, parentNode),
|
2543
|
+
node = document.createTextNode(binding.value);
|
2544
|
+
|
2545
|
+
parent.insertBefore(node, el);
|
2546
|
+
parent.removeChild(el);
|
2547
|
+
setupTeardownOnDestroy(parent);
|
2548
|
+
}
|
2549
|
+
:
|
2550
|
+
// If we are not escaping, replace the parentNode with a
|
2551
|
+
// documentFragment created as with `func`'s return value.
|
2552
|
+
function(span, parentNode){
|
2553
|
+
// updates the elements with the new content
|
2554
|
+
update = function(newVal){
|
2555
|
+
// is this still part of the DOM?
|
2556
|
+
var attached = nodes[0].parentNode;
|
2557
|
+
// update the nodes in the DOM with the new rendered value
|
2558
|
+
if( attached ) {
|
2559
|
+
nodes = makeAndPut(newVal, nodes);
|
2560
|
+
}
|
2561
|
+
teardownCheck(nodes[0].parentNode)
|
2562
|
+
}
|
2563
|
+
|
2564
|
+
// make sure we have a valid parentNode
|
2565
|
+
parentNode = getParentNode(span, parentNode)
|
2566
|
+
// A helper function to manage inserting the contents
|
2567
|
+
// and removing the old contents
|
2568
|
+
var makeAndPut = function(val, remove){
|
2569
|
+
// create the fragment, but don't hook it up
|
2570
|
+
// we need to insert it into the document first
|
2571
|
+
|
2572
|
+
var frag = can.view.frag(val, parentNode),
|
2573
|
+
// keep a reference to each node
|
2574
|
+
nodes = can.map(frag.childNodes,function(node){
|
2575
|
+
return node;
|
2576
|
+
}),
|
2577
|
+
last = remove[remove.length - 1];
|
2578
|
+
|
2579
|
+
// Insert it in the `document` or `documentFragment`
|
2580
|
+
if( last.nextSibling ){
|
2581
|
+
last.parentNode.insertBefore(frag, last.nextSibling)
|
2582
|
+
} else {
|
2583
|
+
last.parentNode.appendChild(frag)
|
2584
|
+
}
|
2585
|
+
// Remove the old content.
|
2586
|
+
can.remove( can.$(remove) );
|
2587
|
+
|
2588
|
+
return nodes;
|
2589
|
+
},
|
2590
|
+
// nodes are the nodes that any updates will replace
|
2591
|
+
// at this point, these nodes could be part of a documentFragment
|
2592
|
+
nodes = makeAndPut(binding.value, [span]);
|
2593
|
+
|
2594
|
+
|
2595
|
+
setupTeardownOnDestroy(parentNode);
|
2596
|
+
|
2597
|
+
}) + "></" +tag+">";
|
2598
|
+
// In a tag, but not in an attribute
|
2599
|
+
} else if(status === 1){
|
2600
|
+
// remember the old attr name
|
2601
|
+
var attrName = binding.value.replace(/['"]/g, '').split('=')[0];
|
2602
|
+
pendingHookups.push(function(el) {
|
2603
|
+
update = function(newVal){
|
2604
|
+
var parts = (newVal|| "").replace(/['"]/g, '').split('='),
|
2605
|
+
newAttrName = parts[0];
|
2606
|
+
|
2607
|
+
// Remove if we have a change and used to have an `attrName`.
|
2608
|
+
if((newAttrName != attrName) && attrName){
|
2609
|
+
removeAttr(el,attrName)
|
2610
|
+
}
|
2611
|
+
// Set if we have a new `attrName`.
|
2612
|
+
if(newAttrName){
|
2613
|
+
setAttr(el, newAttrName, parts[1]);
|
2614
|
+
attrName = newAttrName;
|
2615
|
+
}
|
2616
|
+
}
|
2617
|
+
setupTeardownOnDestroy(el);
|
2618
|
+
});
|
2619
|
+
|
2620
|
+
return binding.value;
|
2621
|
+
} else { // In an attribute...
|
2622
|
+
pendingHookups.push(function(el){
|
2623
|
+
// update will call this attribute's render method
|
2624
|
+
// and set the attribute accordingly
|
2625
|
+
update = function(){
|
2626
|
+
setAttr(el, status, hook.render())
|
2627
|
+
}
|
2628
|
+
|
2629
|
+
var wrapped = can.$(el),
|
2630
|
+
hooks;
|
2631
|
+
|
2632
|
+
// Get the list of hookups or create one for this element.
|
2633
|
+
// Hooks is a map of attribute names to hookup `data`s.
|
2634
|
+
// Each hookup data has:
|
2635
|
+
// `render` - A `function` to render the value of the attribute.
|
2636
|
+
// `funcs` - A list of hookup `function`s on that attribute.
|
2637
|
+
// `batchNum` - The last event `batchNum`, used for performance.
|
2638
|
+
(hooks = can.data(wrapped,'hooks')) || can.data(wrapped, 'hooks', hooks = {});
|
2639
|
+
|
2640
|
+
// Get the attribute value.
|
2641
|
+
var attr = getAttr(el, status),
|
2642
|
+
// Split the attribute value by the template.
|
2643
|
+
parts = attr.split("__!!__"),
|
2644
|
+
hook;
|
2645
|
+
|
2646
|
+
|
2647
|
+
// If we already had a hookup for this attribute...
|
2648
|
+
if(hooks[status]) {
|
2649
|
+
// Just add to that attribute's list of `function`s.
|
2650
|
+
hooks[status].bindings.push(binding);
|
2651
|
+
}
|
2652
|
+
else {
|
2653
|
+
// Create the hookup data.
|
2654
|
+
hooks[status] = {
|
2655
|
+
render: function() {
|
2656
|
+
var i =0,
|
2657
|
+
newAttr = attr.replace(attributeReplace, function() {
|
2658
|
+
return contentText( hook.bindings[i++].value );
|
2659
|
+
});
|
2660
|
+
return newAttr;
|
2661
|
+
},
|
2662
|
+
bindings: [binding],
|
2663
|
+
batchNum : undefined
|
2664
|
+
};
|
2665
|
+
};
|
2666
|
+
|
2667
|
+
// Save the hook for slightly faster performance.
|
2668
|
+
hook = hooks[status];
|
2669
|
+
|
2670
|
+
// Insert the value in parts.
|
2671
|
+
parts.splice(1,0,binding.value);
|
2672
|
+
|
2673
|
+
// Set the attribute.
|
2674
|
+
setAttr(el, status, parts.join(""));
|
2675
|
+
|
2676
|
+
// Bind on change.
|
2677
|
+
//liveBind(observed, el, binder,oldObserved);
|
2678
|
+
setupTeardownOnDestroy(el)
|
2679
|
+
})
|
2680
|
+
return "__!!__";
|
2681
|
+
}
|
2682
|
+
},
|
2683
|
+
pending: function() {
|
2684
|
+
if(pendingHookups.length) {
|
2685
|
+
var hooks = pendingHookups.slice(0);
|
2686
|
+
|
2687
|
+
pendingHookups = [];
|
2688
|
+
return can.view.hook(function(el){
|
2689
|
+
can.each(hooks, function(fn){
|
2690
|
+
fn(el);
|
2691
|
+
})
|
2692
|
+
});
|
2693
|
+
}else {
|
2694
|
+
return "";
|
2695
|
+
}
|
2696
|
+
}
|
2697
|
+
});
|
2698
|
+
// Start scanning code.
|
2699
|
+
var tokenReg = new RegExp("(" +[ "<%%", "%%>", "<%==", "<%=",
|
2700
|
+
"<%#", "<%", "%>", "<", ">", '"', "'"].join("|")+")","g"),
|
2701
|
+
// Commands for caching.
|
2702
|
+
startTxt = 'var ___v1ew = [];',
|
2703
|
+
finishTxt = "return ___v1ew.join('')",
|
2704
|
+
put_cmd = "___v1ew.push(",
|
2705
|
+
insert_cmd = put_cmd,
|
2706
|
+
// Global controls (used by other functions to know where we are).
|
2707
|
+
//
|
2708
|
+
// Are we inside a tag?
|
2709
|
+
htmlTag = null,
|
2710
|
+
// Are we within a quote within a tag?
|
2711
|
+
quote = null,
|
2712
|
+
// What was the text before the current quote? (used to get the `attr` name)
|
2713
|
+
beforeQuote = null,
|
2714
|
+
// Used to mark where the element is.
|
2715
|
+
status = function(){
|
2716
|
+
// `t` - `1`.
|
2717
|
+
// `h` - `0`.
|
2718
|
+
// `q` - String `beforeQuote`.
|
2719
|
+
return quote ? "'"+beforeQuote.match(attrReg)[1]+"'" : (htmlTag ? 1 : 0)
|
2720
|
+
},
|
2721
|
+
pendingHookups = [],
|
2722
|
+
scan = function(source, name){
|
2723
|
+
var tokens = [],
|
2724
|
+
last = 0;
|
2725
|
+
|
2726
|
+
source = source.replace(newLine, "\n");
|
2727
|
+
source.replace(tokenReg, function(whole, part, offset){
|
2728
|
+
// if the next token starts after the last token ends
|
2729
|
+
// push what's in between
|
2730
|
+
if(offset > last){
|
2731
|
+
tokens.push( source.substring(last, offset) );
|
2732
|
+
}
|
2733
|
+
// push the token
|
2734
|
+
tokens.push(part);
|
2735
|
+
// update the position of the last part of the last token
|
2736
|
+
last = offset+part.length;
|
2737
|
+
})
|
2738
|
+
// if there's something at the end, add it
|
2739
|
+
if(last < source.length){
|
2740
|
+
tokens.push(source.substr(last))
|
2741
|
+
}
|
2742
|
+
|
2743
|
+
var content = '',
|
2744
|
+
buff = [startTxt],
|
2745
|
+
// Helper `function` for putting stuff in the view concat.
|
2746
|
+
put = function( content, bonus ) {
|
2747
|
+
buff.push(put_cmd, '"', clean(content), '"'+(bonus||'')+');');
|
2748
|
+
},
|
2749
|
+
// A stack used to keep track of how we should end a bracket
|
2750
|
+
// `}`.
|
2751
|
+
// Once we have a `<%= %>` with a `leftBracket`,
|
2752
|
+
// we store how the file should end here (either `))` or `;`).
|
2753
|
+
endStack =[],
|
2754
|
+
// The last token, used to remember which tag we are in.
|
2755
|
+
lastToken,
|
2756
|
+
// The corresponding magic tag.
|
2757
|
+
startTag = null,
|
2758
|
+
// Was there a magic tag inside an html tag?
|
2759
|
+
magicInTag = false,
|
2760
|
+
// The current tag name.
|
2761
|
+
tagName = '',
|
2762
|
+
// stack of tagNames
|
2763
|
+
tagNames = [],
|
2764
|
+
// Declared here.
|
2765
|
+
bracketCount,
|
2766
|
+
i = 0,
|
2767
|
+
token;
|
2768
|
+
|
2769
|
+
// Reinitialize the tag state goodness.
|
2770
|
+
htmlTag = quote = beforeQuote = null;
|
2771
|
+
|
2772
|
+
for (; (token = tokens[i++]) !== undefined;) {
|
2773
|
+
|
2774
|
+
if ( startTag === null ) {
|
2775
|
+
switch ( token ) {
|
2776
|
+
case '<%':
|
2777
|
+
case '<%=':
|
2778
|
+
case '<%==':
|
2779
|
+
magicInTag = 1;
|
2780
|
+
case '<%#':
|
2781
|
+
// A new line -- just add whatever content within a clean.
|
2782
|
+
// Reset everything.
|
2783
|
+
startTag = token;
|
2784
|
+
if ( content.length ) {
|
2785
|
+
put(content);
|
2786
|
+
}
|
2787
|
+
content = '';
|
2788
|
+
break;
|
2789
|
+
|
2790
|
+
case '<%%':
|
2791
|
+
// Replace `<%%` with `<%`.
|
2792
|
+
content += '<%';
|
2793
|
+
break;
|
2794
|
+
case '<':
|
2795
|
+
// Make sure we are not in a comment.
|
2796
|
+
if(tokens[i].indexOf("!--") !== 0) {
|
2797
|
+
htmlTag = 1;
|
2798
|
+
magicInTag = 0;
|
2799
|
+
}
|
2800
|
+
content += token;
|
2801
|
+
break;
|
2802
|
+
case '>':
|
2803
|
+
htmlTag = 0;
|
2804
|
+
// TODO: all `<%=` in tags should be added to pending hookups.
|
2805
|
+
if(magicInTag){
|
2806
|
+
put(content, ",can.EJS.pending(),\">\"");
|
2807
|
+
content = '';
|
2808
|
+
} else {
|
2809
|
+
content += token;
|
2810
|
+
}
|
2811
|
+
// if it's a tag like <input/>
|
2812
|
+
if(lastToken.substr(-1) == "/"){
|
2813
|
+
// remove the current tag in the stack
|
2814
|
+
tagNames.pop();
|
2815
|
+
// set the current tag to the previous parent
|
2816
|
+
tagName = tagNames[tagNames.length-1];
|
2817
|
+
}
|
2818
|
+
break;
|
2819
|
+
case "'":
|
2820
|
+
case '"':
|
2821
|
+
// If we are in an html tag, finding matching quotes.
|
2822
|
+
if(htmlTag){
|
2823
|
+
// We have a quote and it matches.
|
2824
|
+
if(quote && quote === token){
|
2825
|
+
// We are exiting the quote.
|
2826
|
+
quote = null;
|
2827
|
+
// Otherwise we are creating a quote.
|
2828
|
+
// TODO: does this handle `\`?
|
2829
|
+
} else if(quote === null){
|
2830
|
+
quote = token;
|
2831
|
+
beforeQuote = lastToken;
|
2832
|
+
}
|
2833
|
+
}
|
2834
|
+
default:
|
2835
|
+
// Track the current tag
|
2836
|
+
if(lastToken === '<'){
|
2837
|
+
tagName = token.split(' ')[0];
|
2838
|
+
// If
|
2839
|
+
if( tagName.indexOf("/") === 0 && tagNames.pop() === tagName.substr(1) ) {
|
2840
|
+
tagName = tagNames[tagNames.length-1]|| tagName.substr(1)
|
2841
|
+
} else {
|
2842
|
+
tagNames.push(tagName);
|
2843
|
+
}
|
2844
|
+
}
|
2845
|
+
content += token;
|
2846
|
+
break;
|
2847
|
+
}
|
2848
|
+
}
|
2849
|
+
else {
|
2850
|
+
// We have a start tag.
|
2851
|
+
switch ( token ) {
|
2852
|
+
case '%>':
|
2853
|
+
// `%>`
|
2854
|
+
switch ( startTag ) {
|
2855
|
+
case '<%':
|
2856
|
+
// `<%`
|
2857
|
+
|
2858
|
+
// Get the number of `{ minus }`
|
2859
|
+
bracketCount = bracketNum(content);
|
2860
|
+
|
2861
|
+
// We are ending a block.
|
2862
|
+
if (bracketCount == 1) {
|
2863
|
+
|
2864
|
+
// We are starting on.
|
2865
|
+
buff.push(insert_cmd, "can.EJS.txt(0,'"+tagName+"'," + status() + ",this,function(){", startTxt, content);
|
2866
|
+
|
2867
|
+
endStack.push({
|
2868
|
+
before: "",
|
2869
|
+
after: finishTxt+"}));\n"
|
2870
|
+
})
|
2871
|
+
}
|
2872
|
+
else {
|
2873
|
+
|
2874
|
+
// How are we ending this statement?
|
2875
|
+
var last = // If the stack has value and we are ending a block...
|
2876
|
+
endStack.length && bracketCount == -1 ? // Use the last item in the block stack.
|
2877
|
+
endStack.pop() : // Or use the default ending.
|
2878
|
+
{
|
2879
|
+
after: ";"
|
2880
|
+
};
|
2881
|
+
|
2882
|
+
// If we are ending a returning block,
|
2883
|
+
// add the finish text which returns the result of the
|
2884
|
+
// block.
|
2885
|
+
if (last.before) {
|
2886
|
+
buff.push(last.before)
|
2887
|
+
}
|
2888
|
+
// Add the remaining content.
|
2889
|
+
buff.push(content, ";",last.after);
|
2890
|
+
}
|
2891
|
+
break;
|
2892
|
+
case '<%=':
|
2893
|
+
case '<%==':
|
2894
|
+
// We have an extra `{` -> `block`.
|
2895
|
+
// Get the number of `{ minus }`.
|
2896
|
+
bracketCount = bracketNum(content);
|
2897
|
+
// If we have more `{`, it means there is a block.
|
2898
|
+
if( bracketCount ){
|
2899
|
+
// When we return to the same # of `{` vs `}` end with a `doubleParent`.
|
2900
|
+
endStack.push({
|
2901
|
+
before : finishTxt,
|
2902
|
+
after: "}));"
|
2903
|
+
})
|
2904
|
+
}
|
2905
|
+
// Check if its a func like `()->`
|
2906
|
+
if(quickFunc.test(content)){
|
2907
|
+
var parts = content.match(quickFunc)
|
2908
|
+
content = "function(__){var "+parts[1]+"=can.$(__);"+parts[2]+"}"
|
2909
|
+
}
|
2910
|
+
|
2911
|
+
// If we have `<%== a(function(){ %>` then we want
|
2912
|
+
// `can.EJS.text(0,this, function(){ return a(function(){ var _v1ew = [];`.
|
2913
|
+
buff.push(insert_cmd, "can.EJS.txt("+(startTag === '<%=' ? 1 : 0)+",'"+tagName+"'," + status()+",this,function(){ return ", content,
|
2914
|
+
// If we have a block.
|
2915
|
+
bracketCount ?
|
2916
|
+
// Start with startTxt `"var _v1ew = [];"`.
|
2917
|
+
startTxt :
|
2918
|
+
// If not, add `doubleParent` to close push and text.
|
2919
|
+
"}));"
|
2920
|
+
);
|
2921
|
+
break;
|
2922
|
+
}
|
2923
|
+
startTag = null;
|
2924
|
+
content = '';
|
2925
|
+
break;
|
2926
|
+
case '<%%':
|
2927
|
+
content += '<%';
|
2928
|
+
break;
|
2929
|
+
default:
|
2930
|
+
content += token;
|
2931
|
+
break;
|
2932
|
+
}
|
2933
|
+
|
2934
|
+
}
|
2935
|
+
lastToken = token;
|
2936
|
+
}
|
2937
|
+
|
2938
|
+
// Put it together...
|
2939
|
+
if ( content.length ) {
|
2940
|
+
// Should be `content.dump` in Ruby.
|
2941
|
+
put(content)
|
2942
|
+
}
|
2943
|
+
buff.push(";")
|
2944
|
+
|
2945
|
+
var template = buff.join(''),
|
2946
|
+
out = {
|
2947
|
+
out: 'with(_VIEW) { with (_CONTEXT) {' + template + " "+finishTxt+"}}"
|
2948
|
+
};
|
2949
|
+
// Use `eval` instead of creating a function, because it is easier to debug.
|
2950
|
+
myEval.call(out, 'this.fn = (function(_CONTEXT,_VIEW){' + out.out + '});\r\n//@ sourceURL=' + name + ".js");
|
2951
|
+
return out;
|
2952
|
+
};
|
2953
|
+
|
2954
|
+
|
2955
|
+
|
2956
|
+
EJS.Helpers = function( data, extras ) {
|
2957
|
+
this._data = data;
|
2958
|
+
this._extras = extras;
|
2959
|
+
extend(this, extras);
|
2960
|
+
};
|
2961
|
+
EJS.Helpers.prototype = {
|
2962
|
+
// TODO Deprecated!!
|
2963
|
+
list : function(list, cb){
|
2964
|
+
can.each(list, function(item, i){
|
2965
|
+
cb(item, i, list)
|
2966
|
+
})
|
2967
|
+
}
|
2968
|
+
};
|
2969
|
+
|
2970
|
+
// Options for `steal`'s build.
|
2971
|
+
can.view.register({
|
2972
|
+
suffix: "ejs",
|
2973
|
+
// returns a `function` that renders the view.
|
2974
|
+
script: function( id, src ) {
|
2975
|
+
return "can.EJS(function(_CONTEXT,_VIEW) { " + new EJS({
|
2976
|
+
text: src,
|
2977
|
+
name: id
|
2978
|
+
}).template.out + " })";
|
2979
|
+
},
|
2980
|
+
renderer: function( id, text ) {
|
2981
|
+
return EJS({
|
2982
|
+
text: text,
|
2983
|
+
name: id
|
2984
|
+
});
|
2985
|
+
}
|
2986
|
+
});
|
2987
|
+
|
2988
|
+
// Register as an AMD module if supported, otherwise attach to the window
|
2989
|
+
if ( typeof define === "function" && define.amd ) {
|
2990
|
+
define( "can", [], function () { return can; } );
|
2991
|
+
} else {
|
2992
|
+
window.can = can;
|
2993
|
+
}
|
2994
|
+
|
2995
|
+
})(can = {}, this )
|