|
1 /*! |
|
2 * CanJS - 1.1.6 |
|
3 * http://canjs.us/ |
|
4 * Copyright (c) 2013 Bitovi |
|
5 * Mon, 15 Jul 2013 04:14:43 GMT |
|
6 * Licensed MIT |
|
7 * Includes: can/construct/construct.js,can/observe/observe.js,can/observe/compute/compute.js,can/model/model.js,can/view/view.js,can/view/ejs/ejs.js,can/control/control.js,can/route/route.js,can/control/route/route.js,can/observe/backup/backup.js,can/util/object/object.js,can/util/string/string.js |
|
8 * Download from: http://bitbuilder.herokuapp.com/can.custom.js?configuration=jquery&plugins=can%2Fconstruct%2Fconstruct.js&plugins=can%2Fobserve%2Fobserve.js&plugins=can%2Fobserve%2Fcompute%2Fcompute.js&plugins=can%2Fmodel%2Fmodel.js&plugins=can%2Fview%2Fview.js&plugins=can%2Fview%2Fejs%2Fejs.js&plugins=can%2Fcontrol%2Fcontrol.js&plugins=can%2Froute%2Froute.js&plugins=can%2Fcontrol%2Froute%2Froute.js&plugins=can%2Fobserve%2Fbackup%2Fbackup.js&plugins=can%2Futil%2Fobject%2Fobject.js&plugins=can%2Futil%2Fstring%2Fstring.js |
|
9 */ |
|
10 (function(undefined) { |
|
11 |
|
12 // ## can/util/can.js |
|
13 var __m5 = (function() { |
|
14 var can = window.can || {}; |
|
15 if (typeof GLOBALCAN === 'undefined' || GLOBALCAN !== false) { |
|
16 window.can = can; |
|
17 } |
|
18 |
|
19 can.isDeferred = function(obj) { |
|
20 var isFunction = this.isFunction; |
|
21 // Returns `true` if something looks like a deferred. |
|
22 return obj && isFunction(obj.then) && isFunction(obj.pipe); |
|
23 }; |
|
24 |
|
25 var cid = 0; |
|
26 can.cid = function(object, name) { |
|
27 if (object._cid) { |
|
28 return object._cid |
|
29 } else { |
|
30 return object._cid = (name || "") + (++cid) |
|
31 } |
|
32 } |
|
33 can.VERSION = '@EDGE'; |
|
34 return can; |
|
35 })(); |
|
36 |
|
37 // ## can/util/array/each.js |
|
38 var __m6 = (function(can) { |
|
39 can.each = function(elements, callback, context) { |
|
40 var i = 0, |
|
41 key; |
|
42 if (elements) { |
|
43 if (typeof elements.length === 'number' && elements.pop) { |
|
44 if (elements.attr) { |
|
45 elements.attr('length'); |
|
46 } |
|
47 for (key = elements.length; i < key; i++) { |
|
48 if (callback.call(context || elements[i], elements[i], i, elements) === false) { |
|
49 break; |
|
50 } |
|
51 } |
|
52 } else if (elements.hasOwnProperty) { |
|
53 for (key in elements) { |
|
54 if (elements.hasOwnProperty(key)) { |
|
55 if (callback.call(context || elements[key], elements[key], key, elements) === false) { |
|
56 break; |
|
57 } |
|
58 } |
|
59 } |
|
60 } |
|
61 } |
|
62 return elements; |
|
63 }; |
|
64 |
|
65 return can; |
|
66 })(__m5); |
|
67 |
|
68 // ## can/util/jquery/jquery.js |
|
69 var __m3 = (function($, can) { |
|
70 // _jQuery node list._ |
|
71 $.extend(can, $, { |
|
72 trigger: function(obj, event, args) { |
|
73 if (obj.trigger) { |
|
74 obj.trigger(event, args); |
|
75 } else { |
|
76 $.event.trigger(event, args, obj, true); |
|
77 } |
|
78 }, |
|
79 addEvent: function(ev, cb) { |
|
80 $([this]).bind(ev, cb); |
|
81 return this; |
|
82 }, |
|
83 removeEvent: function(ev, cb) { |
|
84 $([this]).unbind(ev, cb); |
|
85 return this; |
|
86 }, |
|
87 // jquery caches fragments, we always needs a new one |
|
88 buildFragment: function(elems, context) { |
|
89 var oldFragment = $.buildFragment, |
|
90 ret; |
|
91 |
|
92 elems = [elems]; |
|
93 // Set context per 1.8 logic |
|
94 context = context || document; |
|
95 context = !context.nodeType && context[0] || context; |
|
96 context = context.ownerDocument || context; |
|
97 |
|
98 ret = oldFragment.call(jQuery, elems, context); |
|
99 |
|
100 return ret.cacheable ? $.clone(ret.fragment) : ret.fragment || ret; |
|
101 }, |
|
102 $: $, |
|
103 each: can.each |
|
104 }); |
|
105 |
|
106 // Wrap binding functions. |
|
107 $.each(['bind', 'unbind', 'undelegate', 'delegate'], function(i, func) { |
|
108 can[func] = function() { |
|
109 var t = this[func] ? this : $([this]); |
|
110 t[func].apply(t, arguments); |
|
111 return this; |
|
112 }; |
|
113 }); |
|
114 |
|
115 // Wrap modifier functions. |
|
116 $.each(["append", "filter", "addClass", "remove", "data", "get"], function(i, name) { |
|
117 can[name] = function(wrapped) { |
|
118 return wrapped[name].apply(wrapped, can.makeArray(arguments).slice(1)); |
|
119 }; |
|
120 }); |
|
121 |
|
122 // Memory safe destruction. |
|
123 var oldClean = $.cleanData; |
|
124 |
|
125 $.cleanData = function(elems) { |
|
126 $.each(elems, function(i, elem) { |
|
127 if (elem) { |
|
128 can.trigger(elem, "destroyed", [], false); |
|
129 } |
|
130 }); |
|
131 oldClean(elems); |
|
132 }; |
|
133 |
|
134 return can; |
|
135 })(jQuery, __m5, __m6); |
|
136 |
|
137 // ## can/util/string/string.js |
|
138 var __m2 = (function(can) { |
|
139 // ##string.js |
|
140 // _Miscellaneous string utility functions._ |
|
141 |
|
142 // Several of the methods in this plugin use code adapated from Prototype |
|
143 // Prototype JavaScript framework, version 1.6.0.1. |
|
144 // © 2005-2007 Sam Stephenson |
|
145 var strUndHash = /_|-/, |
|
146 strColons = /\=\=/, |
|
147 strWords = /([A-Z]+)([A-Z][a-z])/g, |
|
148 strLowUp = /([a-z\d])([A-Z])/g, |
|
149 strDash = /([a-z\d])([A-Z])/g, |
|
150 strReplacer = /\{([^\}]+)\}/g, |
|
151 strQuote = /"/g, |
|
152 strSingleQuote = /'/g, |
|
153 |
|
154 // Returns the `prop` property from `obj`. |
|
155 // If `add` is true and `prop` doesn't exist in `obj`, create it as an |
|
156 // empty object. |
|
157 getNext = function(obj, prop, add) { |
|
158 var result = obj[prop]; |
|
159 |
|
160 if (result === undefined && add === true) { |
|
161 result = obj[prop] = {} |
|
162 } |
|
163 return result |
|
164 }, |
|
165 |
|
166 // Returns `true` if the object can have properties (no `null`s). |
|
167 isContainer = function(current) { |
|
168 return (/^f|^o/).test(typeof current); |
|
169 }; |
|
170 |
|
171 can.extend(can, { |
|
172 // Escapes strings for HTML. |
|
173 |
|
174 esc: function(content) { |
|
175 // Convert bad values into empty strings |
|
176 var isInvalid = content === null || content === undefined || (isNaN(content) && ("" + content === 'NaN')); |
|
177 return ("" + (isInvalid ? '' : content)) |
|
178 .replace(/&/g, '&') |
|
179 .replace(/</g, '<') |
|
180 .replace(/>/g, '>') |
|
181 .replace(strQuote, '"') |
|
182 .replace(strSingleQuote, "'"); |
|
183 }, |
|
184 |
|
185 |
|
186 getObject: function(name, roots, add) { |
|
187 |
|
188 // The parts of the name we are looking up |
|
189 // `['App','Models','Recipe']` |
|
190 var parts = name ? name.split('.') : [], |
|
191 length = parts.length, |
|
192 current, |
|
193 r = 0, |
|
194 i, container, rootsLength; |
|
195 |
|
196 // Make sure roots is an `array`. |
|
197 roots = can.isArray(roots) ? roots : [roots || window]; |
|
198 |
|
199 rootsLength = roots.length |
|
200 |
|
201 if (!length) { |
|
202 return roots[0]; |
|
203 } |
|
204 |
|
205 // For each root, mark it as current. |
|
206 for (r; r < rootsLength; r++) { |
|
207 current = roots[r]; |
|
208 container = undefined; |
|
209 |
|
210 // Walk current to the 2nd to last object or until there |
|
211 // is not a container. |
|
212 for (i = 0; i < length && isContainer(current); i++) { |
|
213 container = current; |
|
214 current = getNext(container, parts[i]); |
|
215 } |
|
216 |
|
217 // If we found property break cycle |
|
218 if (container !== undefined && current !== undefined) { |
|
219 break |
|
220 } |
|
221 } |
|
222 |
|
223 // Remove property from found container |
|
224 if (add === false && current !== undefined) { |
|
225 delete container[parts[i - 1]] |
|
226 } |
|
227 |
|
228 // When adding property add it to the first root |
|
229 if (add === true && current === undefined) { |
|
230 current = roots[0] |
|
231 |
|
232 for (i = 0; i < length && isContainer(current); i++) { |
|
233 current = getNext(current, parts[i], true); |
|
234 } |
|
235 } |
|
236 |
|
237 return current; |
|
238 }, |
|
239 // Capitalizes a string. |
|
240 |
|
241 capitalize: function(s, cache) { |
|
242 // Used to make newId. |
|
243 return s.charAt(0).toUpperCase() + s.slice(1); |
|
244 }, |
|
245 |
|
246 // Underscores a string. |
|
247 |
|
248 underscore: function(s) { |
|
249 return s |
|
250 .replace(strColons, '/') |
|
251 .replace(strWords, '$1_$2') |
|
252 .replace(strLowUp, '$1_$2') |
|
253 .replace(strDash, '_') |
|
254 .toLowerCase(); |
|
255 }, |
|
256 // Micro-templating. |
|
257 |
|
258 sub: function(str, data, remove) { |
|
259 var obs = []; |
|
260 |
|
261 str = str || ''; |
|
262 |
|
263 obs.push(str.replace(strReplacer, function(whole, inside) { |
|
264 |
|
265 // Convert inside to type. |
|
266 var ob = can.getObject(inside, data, remove === true ? false : undefined); |
|
267 |
|
268 if (ob === undefined) { |
|
269 obs = null; |
|
270 return ""; |
|
271 } |
|
272 |
|
273 // If a container, push into objs (which will return objects found). |
|
274 if (isContainer(ob) && obs) { |
|
275 obs.push(ob); |
|
276 return ""; |
|
277 } |
|
278 |
|
279 return "" + ob; |
|
280 })); |
|
281 |
|
282 return obs === null ? obs : (obs.length <= 1 ? obs[0] : obs); |
|
283 }, |
|
284 |
|
285 // These regex's are used throughout the rest of can, so let's make |
|
286 // them available. |
|
287 replacer: strReplacer, |
|
288 undHash: strUndHash |
|
289 }); |
|
290 return can; |
|
291 })(__m3); |
|
292 |
|
293 // ## can/construct/construct.js |
|
294 var __m1 = (function(can) { |
|
295 |
|
296 // ## construct.js |
|
297 // `can.Construct` |
|
298 // _This is a modified version of |
|
299 // [John Resig's class](http://ejohn.org/blog/simple-javascript-inheritance/). |
|
300 // It provides class level inheritance and callbacks._ |
|
301 |
|
302 // A private flag used to initialize a new class instance without |
|
303 // initializing it's bindings. |
|
304 var initializing = 0; |
|
305 |
|
306 |
|
307 can.Construct = function() { |
|
308 if (arguments.length) { |
|
309 return can.Construct.extend.apply(can.Construct, arguments); |
|
310 } |
|
311 }; |
|
312 |
|
313 |
|
314 can.extend(can.Construct, { |
|
315 |
|
316 newInstance: function() { |
|
317 // Get a raw instance object (`init` is not called). |
|
318 var inst = this.instance(), |
|
319 arg = arguments, |
|
320 args; |
|
321 |
|
322 // Call `setup` if there is a `setup` |
|
323 if (inst.setup) { |
|
324 args = inst.setup.apply(inst, arguments); |
|
325 } |
|
326 |
|
327 // Call `init` if there is an `init` |
|
328 // If `setup` returned `args`, use those as the arguments |
|
329 if (inst.init) { |
|
330 inst.init.apply(inst, args || arguments); |
|
331 } |
|
332 |
|
333 return inst; |
|
334 }, |
|
335 // Overwrites an object with methods. Used in the `super` plugin. |
|
336 // `newProps` - New properties to add. |
|
337 // `oldProps` - Where the old properties might be (used with `super`). |
|
338 // `addTo` - What we are adding to. |
|
339 _inherit: function(newProps, oldProps, addTo) { |
|
340 can.extend(addTo || newProps, newProps || {}) |
|
341 }, |
|
342 // used for overwriting a single property. |
|
343 // this should be used for patching other objects |
|
344 // the super plugin overwrites this |
|
345 _overwrite: function(what, oldProps, propName, val) { |
|
346 what[propName] = val; |
|
347 }, |
|
348 // Set `defaults` as the merger of the parent `defaults` and this |
|
349 // object's `defaults`. If you overwrite this method, make sure to |
|
350 // include option merging logic. |
|
351 |
|
352 setup: function(base, fullName) { |
|
353 this.defaults = can.extend(true, {}, base.defaults, this.defaults); |
|
354 }, |
|
355 // Create's a new `class` instance without initializing by setting the |
|
356 // `initializing` flag. |
|
357 instance: function() { |
|
358 |
|
359 // Prevents running `init`. |
|
360 initializing = 1; |
|
361 |
|
362 var inst = new this(); |
|
363 |
|
364 // Allow running `init`. |
|
365 initializing = 0; |
|
366 |
|
367 return inst; |
|
368 }, |
|
369 // Extends classes. |
|
370 |
|
371 extend: function(fullName, klass, proto) { |
|
372 // Figure out what was passed and normalize it. |
|
373 if (typeof fullName != 'string') { |
|
374 proto = klass; |
|
375 klass = fullName; |
|
376 fullName = null; |
|
377 } |
|
378 |
|
379 if (!proto) { |
|
380 proto = klass; |
|
381 klass = null; |
|
382 } |
|
383 proto = proto || {}; |
|
384 |
|
385 var _super_class = this, |
|
386 _super = this.prototype, |
|
387 name, shortName, namespace, prototype; |
|
388 |
|
389 // Instantiate a base class (but only create the instance, |
|
390 // don't run the init constructor). |
|
391 prototype = this.instance(); |
|
392 |
|
393 // Copy the properties over onto the new prototype. |
|
394 can.Construct._inherit(proto, _super, prototype); |
|
395 |
|
396 // The dummy class constructor. |
|
397 |
|
398 function Constructor() { |
|
399 // All construction is actually done in the init method. |
|
400 if (!initializing) { |
|
401 return this.constructor !== Constructor && arguments.length ? |
|
402 // We are being called without `new` or we are extending. |
|
403 arguments.callee.extend.apply(arguments.callee, arguments) : |
|
404 // We are being called with `new`. |
|
405 this.constructor.newInstance.apply(this.constructor, arguments); |
|
406 } |
|
407 } |
|
408 |
|
409 // Copy old stuff onto class (can probably be merged w/ inherit) |
|
410 for (name in _super_class) { |
|
411 if (_super_class.hasOwnProperty(name)) { |
|
412 Constructor[name] = _super_class[name]; |
|
413 } |
|
414 } |
|
415 |
|
416 // Copy new static properties on class. |
|
417 can.Construct._inherit(klass, _super_class, Constructor); |
|
418 |
|
419 // Setup namespaces. |
|
420 if (fullName) { |
|
421 |
|
422 var parts = fullName.split('.'), |
|
423 shortName = parts.pop(), |
|
424 current = can.getObject(parts.join('.'), window, true), |
|
425 namespace = current, |
|
426 _fullName = can.underscore(fullName.replace(/\./g, "_")), |
|
427 _shortName = can.underscore(shortName); |
|
428 |
|
429 |
|
430 |
|
431 current[shortName] = Constructor; |
|
432 } |
|
433 |
|
434 // Set things that shouldn't be overwritten. |
|
435 can.extend(Constructor, { |
|
436 constructor: Constructor, |
|
437 prototype: prototype, |
|
438 |
|
439 namespace: namespace, |
|
440 |
|
441 _shortName: _shortName, |
|
442 |
|
443 fullName: fullName, |
|
444 _fullName: _fullName |
|
445 }); |
|
446 |
|
447 // Dojo and YUI extend undefined |
|
448 if (shortName !== undefined) { |
|
449 Constructor.shortName = shortName; |
|
450 } |
|
451 |
|
452 // Make sure our prototype looks nice. |
|
453 Constructor.prototype.constructor = Constructor; |
|
454 |
|
455 |
|
456 // Call the class `setup` and `init` |
|
457 var t = [_super_class].concat(can.makeArray(arguments)), |
|
458 args = Constructor.setup.apply(Constructor, t); |
|
459 |
|
460 if (Constructor.init) { |
|
461 Constructor.init.apply(Constructor, args || t); |
|
462 } |
|
463 |
|
464 |
|
465 return Constructor; |
|
466 |
|
467 } |
|
468 |
|
469 }); |
|
470 return can.Construct; |
|
471 })(__m2); |
|
472 |
|
473 // ## can/util/bind/bind.js |
|
474 var __m8 = (function(can) { |
|
475 |
|
476 |
|
477 // ## Bind helpers |
|
478 can.bindAndSetup = function() { |
|
479 // Add the event to this object |
|
480 can.addEvent.apply(this, arguments); |
|
481 // If not initializing, and the first binding |
|
482 // call bindsetup if the function exists. |
|
483 if (!this._init) { |
|
484 if (!this._bindings) { |
|
485 this._bindings = 1; |
|
486 // setup live-binding |
|
487 this._bindsetup && this._bindsetup(); |
|
488 |
|
489 } else { |
|
490 this._bindings++; |
|
491 } |
|
492 |
|
493 } |
|
494 |
|
495 return this; |
|
496 }; |
|
497 |
|
498 can.unbindAndTeardown = function(ev, handler) { |
|
499 // Remove the event handler |
|
500 can.removeEvent.apply(this, arguments); |
|
501 |
|
502 this._bindings--; |
|
503 // If there are no longer any bindings and |
|
504 // there is a bindteardown method, call it. |
|
505 if (!this._bindings) { |
|
506 this._bindteardown && this._bindteardown(); |
|
507 } |
|
508 return this; |
|
509 } |
|
510 |
|
511 return can; |
|
512 |
|
513 })(__m3); |
|
514 |
|
515 // ## can/observe/observe.js |
|
516 var __m7 = (function(can, bind) { |
|
517 // ## observe.js |
|
518 // `can.Observe` |
|
519 // _Provides the observable pattern for JavaScript Objects._ |
|
520 // Returns `true` if something is an object with properties of its own. |
|
521 var canMakeObserve = function(obj) { |
|
522 return obj && !can.isDeferred(obj) && (can.isArray(obj) || can.isPlainObject(obj) || (obj instanceof can.Observe)); |
|
523 }, |
|
524 |
|
525 // Removes all listeners. |
|
526 unhookup = function(items, namespace) { |
|
527 return can.each(items, function(item) { |
|
528 if (item && item.unbind) { |
|
529 item.unbind("change" + namespace); |
|
530 } |
|
531 }); |
|
532 }, |
|
533 // Listens to changes on `child` and "bubbles" the event up. |
|
534 // `child` - The object to listen for changes on. |
|
535 // `prop` - The property name is at on. |
|
536 // `parent` - The parent object of prop. |
|
537 // `ob` - (optional) The Observe object constructor |
|
538 // `list` - (optional) The observable list constructor |
|
539 hookupBubble = function(child, prop, parent, Ob, List) { |
|
540 Ob = Ob || Observe; |
|
541 List = List || Observe.List; |
|
542 |
|
543 // If it's an `array` make a list, otherwise a child. |
|
544 if (child instanceof Observe) { |
|
545 // We have an `observe` already... |
|
546 // Make sure it is not listening to this already |
|
547 // It's only listening if it has bindings already. |
|
548 parent._bindings && unhookup([child], parent._cid); |
|
549 } else if (can.isArray(child)) { |
|
550 child = new List(child); |
|
551 } else { |
|
552 child = new Ob(child); |
|
553 } |
|
554 // only listen if something is listening to you |
|
555 if (parent._bindings) { |
|
556 // Listen to all changes and `batchTrigger` upwards. |
|
557 bindToChildAndBubbleToParent(child, prop, parent) |
|
558 } |
|
559 |
|
560 |
|
561 return child; |
|
562 }, |
|
563 bindToChildAndBubbleToParent = function(child, prop, parent) { |
|
564 child.bind("change" + parent._cid, function() { |
|
565 // `batchTrigger` the type on this... |
|
566 var args = can.makeArray(arguments), |
|
567 ev = args.shift(); |
|
568 args[0] = (prop === "*" ? [parent.indexOf(child), args[0]] : [prop, args[0]]).join("."); |
|
569 |
|
570 // track objects dispatched on this observe |
|
571 ev.triggeredNS = ev.triggeredNS || {}; |
|
572 |
|
573 // if it has already been dispatched exit |
|
574 if (ev.triggeredNS[parent._cid]) { |
|
575 return; |
|
576 } |
|
577 |
|
578 ev.triggeredNS[parent._cid] = true; |
|
579 // send change event with modified attr to parent |
|
580 can.trigger(parent, ev, args); |
|
581 // send modified attr event to parent |
|
582 //can.trigger(parent, args[0], args); |
|
583 }); |
|
584 } |
|
585 // An `id` to track events for a given observe. |
|
586 observeId = 0, |
|
587 // A helper used to serialize an `Observe` or `Observe.List`. |
|
588 // `observe` - The observable. |
|
589 // `how` - To serialize with `attr` or `serialize`. |
|
590 // `where` - To put properties, in an `{}` or `[]`. |
|
591 serialize = function(observe, how, where) { |
|
592 // Go through each property. |
|
593 observe.each(function(val, name) { |
|
594 // If the value is an `object`, and has an `attrs` or `serialize` function. |
|
595 where[name] = canMakeObserve(val) && can.isFunction(val[how]) ? |
|
596 // Call `attrs` or `serialize` to get the original data back. |
|
597 val[how]() : |
|
598 // Otherwise return the value. |
|
599 val; |
|
600 }); |
|
601 return where; |
|
602 }, |
|
603 attrParts = function(attr, keepKey) { |
|
604 if (keepKey) { |
|
605 return [attr]; |
|
606 } |
|
607 return can.isArray(attr) ? attr : ("" + attr).split("."); |
|
608 }, |
|
609 // Which batch of events this is for -- might not want to send multiple |
|
610 // messages on the same batch. This is mostly for event delegation. |
|
611 batchNum = 1, |
|
612 // how many times has start been called without a stop |
|
613 transactions = 0, |
|
614 // an array of events within a transaction |
|
615 batchEvents = [], |
|
616 stopCallbacks = [], |
|
617 makeBindSetup = function(wildcard) { |
|
618 return function() { |
|
619 var parent = this; |
|
620 this._each(function(child, prop) { |
|
621 if (child && child.bind) { |
|
622 bindToChildAndBubbleToParent(child, wildcard || prop, parent) |
|
623 } |
|
624 }) |
|
625 }; |
|
626 }; |
|
627 |
|
628 |
|
629 var Observe = can.Map = can.Observe = can.Construct({ |
|
630 |
|
631 // keep so it can be overwritten |
|
632 bind: can.bindAndSetup, |
|
633 unbind: can.unbindAndTeardown, |
|
634 id: "id", |
|
635 canMakeObserve: canMakeObserve, |
|
636 // starts collecting events |
|
637 // takes a callback for after they are updated |
|
638 // how could you hook into after ejs |
|
639 |
|
640 startBatch: function(batchStopHandler) { |
|
641 transactions++; |
|
642 batchStopHandler && stopCallbacks.push(batchStopHandler); |
|
643 }, |
|
644 |
|
645 stopBatch: function(force, callStart) { |
|
646 if (force) { |
|
647 transactions = 0; |
|
648 } else { |
|
649 transactions--; |
|
650 } |
|
651 |
|
652 if (transactions == 0) { |
|
653 var items = batchEvents.slice(0), |
|
654 callbacks = stopCallbacks.slice(0); |
|
655 batchEvents = []; |
|
656 stopCallbacks = []; |
|
657 batchNum++; |
|
658 callStart && this.startBatch(); |
|
659 can.each(items, function(args) { |
|
660 can.trigger.apply(can, args); |
|
661 }); |
|
662 can.each(callbacks, function(cb) { |
|
663 cb(); |
|
664 }); |
|
665 } |
|
666 }, |
|
667 |
|
668 triggerBatch: function(item, event, args) { |
|
669 // Don't send events if initalizing. |
|
670 if (!item._init) { |
|
671 if (transactions == 0) { |
|
672 return can.trigger(item, event, args); |
|
673 } else { |
|
674 event = typeof event === "string" ? { |
|
675 type: event |
|
676 } : |
|
677 event; |
|
678 event.batchNum = batchNum; |
|
679 batchEvents.push([ |
|
680 item, |
|
681 event, |
|
682 args |
|
683 ]); |
|
684 } |
|
685 } |
|
686 }, |
|
687 |
|
688 keys: function(observe) { |
|
689 var keys = []; |
|
690 Observe.__reading && Observe.__reading(observe, '__keys'); |
|
691 for (var keyName in observe._data) { |
|
692 keys.push(keyName); |
|
693 } |
|
694 return keys; |
|
695 } |
|
696 }, |
|
697 |
|
698 { |
|
699 setup: function(obj) { |
|
700 // `_data` is where we keep the properties. |
|
701 this._data = {}; |
|
702 |
|
703 // The namespace this `object` uses to listen to events. |
|
704 can.cid(this, ".observe"); |
|
705 // Sets all `attrs`. |
|
706 this._init = 1; |
|
707 this.attr(obj); |
|
708 this.bind('change' + this._cid, can.proxy(this._changes, this)); |
|
709 delete this._init; |
|
710 }, |
|
711 _bindsetup: makeBindSetup(), |
|
712 _bindteardown: function() { |
|
713 var cid = this._cid; |
|
714 this._each(function(child) { |
|
715 unhookup([child], cid) |
|
716 }) |
|
717 }, |
|
718 _changes: function(ev, attr, how, newVal, oldVal) { |
|
719 Observe.triggerBatch(this, { |
|
720 type: attr, |
|
721 batchNum: ev.batchNum |
|
722 }, [newVal, oldVal]); |
|
723 }, |
|
724 _triggerChange: function(attr, how, newVal, oldVal) { |
|
725 Observe.triggerBatch(this, "change", can.makeArray(arguments)) |
|
726 }, |
|
727 // no live binding iterator |
|
728 _each: function(callback) { |
|
729 var data = this.__get(); |
|
730 for (var prop in data) { |
|
731 if (data.hasOwnProperty(prop)) { |
|
732 callback(data[prop], prop) |
|
733 } |
|
734 } |
|
735 }, |
|
736 |
|
737 attr: function(attr, val) { |
|
738 // This is super obfuscated for space -- basically, we're checking |
|
739 // if the type of the attribute is not a `number` or a `string`. |
|
740 var type = typeof attr; |
|
741 if (type !== "string" && type !== "number") { |
|
742 return this._attrs(attr, val) |
|
743 } else if (arguments.length === 1) { // If we are getting a value. |
|
744 // Let people know we are reading. |
|
745 Observe.__reading && Observe.__reading(this, attr) |
|
746 return this._get(attr) |
|
747 } else { |
|
748 // Otherwise we are setting. |
|
749 this._set(attr, val); |
|
750 return this; |
|
751 } |
|
752 }, |
|
753 |
|
754 each: function() { |
|
755 Observe.__reading && Observe.__reading(this, '__keys'); |
|
756 return can.each.apply(undefined, [this.__get()].concat(can.makeArray(arguments))) |
|
757 }, |
|
758 |
|
759 removeAttr: function(attr) { |
|
760 // Info if this is List or not |
|
761 var isList = this instanceof can.Observe.List, |
|
762 // Convert the `attr` into parts (if nested). |
|
763 parts = attrParts(attr), |
|
764 // The actual property to remove. |
|
765 prop = parts.shift(), |
|
766 // The current value. |
|
767 current = isList ? this[prop] : this._data[prop]; |
|
768 |
|
769 // If we have more parts, call `removeAttr` on that part. |
|
770 if (parts.length) { |
|
771 return current.removeAttr(parts) |
|
772 } else { |
|
773 if (isList) { |
|
774 this.splice(prop, 1) |
|
775 } else if (prop in this._data) { |
|
776 // Otherwise, `delete`. |
|
777 delete this._data[prop]; |
|
778 // Create the event. |
|
779 if (!(prop in this.constructor.prototype)) { |
|
780 delete this[prop] |
|
781 } |
|
782 // Let others know the number of keys have changed |
|
783 Observe.triggerBatch(this, "__keys"); |
|
784 this._triggerChange(prop, "remove", undefined, current); |
|
785 |
|
786 } |
|
787 return current; |
|
788 } |
|
789 }, |
|
790 // Reads a property from the `object`. |
|
791 _get: function(attr) { |
|
792 var value = typeof attr === 'string' && !! ~attr.indexOf('.') && this.__get(attr); |
|
793 if (value) { |
|
794 return value; |
|
795 } |
|
796 |
|
797 // break up the attr (`"foo.bar"`) into `["foo","bar"]` |
|
798 var parts = attrParts(attr), |
|
799 // get the value of the first attr name (`"foo"`) |
|
800 current = this.__get(parts.shift()); |
|
801 // if there are other attributes to read |
|
802 return parts.length ? |
|
803 // and current has a value |
|
804 current ? |
|
805 // lookup the remaining attrs on current |
|
806 current._get(parts) : |
|
807 // or if there's no current, return undefined |
|
808 undefined : |
|
809 // if there are no more parts, return current |
|
810 current; |
|
811 }, |
|
812 // Reads a property directly if an `attr` is provided, otherwise |
|
813 // returns the "real" data object itself. |
|
814 __get: function(attr) { |
|
815 return attr ? this._data[attr] : this._data; |
|
816 }, |
|
817 // Sets `attr` prop as value on this object where. |
|
818 // `attr` - Is a string of properties or an array of property values. |
|
819 // `value` - The raw value to set. |
|
820 _set: function(attr, value, keepKey) { |
|
821 // Convert `attr` to attr parts (if it isn't already). |
|
822 var parts = attrParts(attr, keepKey), |
|
823 // The immediate prop we are setting. |
|
824 prop = parts.shift(), |
|
825 // The current value. |
|
826 current = this.__get(prop); |
|
827 |
|
828 // If we have an `object` and remaining parts. |
|
829 if (canMakeObserve(current) && parts.length) { |
|
830 // That `object` should set it (this might need to call attr). |
|
831 current._set(parts, value) |
|
832 } else if (!parts.length) { |
|
833 // We're in "real" set territory. |
|
834 if (this.__convert) { |
|
835 value = this.__convert(prop, value) |
|
836 } |
|
837 this.__set(prop, value, current) |
|
838 } else { |
|
839 throw "can.Observe: Object does not exist" |
|
840 } |
|
841 }, |
|
842 __set: function(prop, value, current) { |
|
843 |
|
844 // Otherwise, we are setting it on this `object`. |
|
845 // TODO: Check if value is object and transform |
|
846 // are we changing the value. |
|
847 if (value !== current) { |
|
848 // Check if we are adding this for the first time -- |
|
849 // if we are, we need to create an `add` event. |
|
850 var changeType = this.__get().hasOwnProperty(prop) ? "set" : "add"; |
|
851 |
|
852 // Set the value on data. |
|
853 this.___set(prop, |
|
854 |
|
855 // If we are getting an object. |
|
856 canMakeObserve(value) ? |
|
857 |
|
858 // Hook it up to send event. |
|
859 hookupBubble(value, prop, this) : |
|
860 // Value is normal. |
|
861 value); |
|
862 |
|
863 if (changeType == "add") { |
|
864 // If there is no current value, let others know that |
|
865 // the the number of keys have changed |
|
866 |
|
867 Observe.triggerBatch(this, "__keys", undefined); |
|
868 |
|
869 } |
|
870 // `batchTrigger` the change event. |
|
871 this._triggerChange(prop, changeType, value, current); |
|
872 |
|
873 //Observe.triggerBatch(this, prop, [value, current]); |
|
874 // If we can stop listening to our old value, do it. |
|
875 current && unhookup([current], this._cid); |
|
876 } |
|
877 |
|
878 }, |
|
879 // Directly sets a property on this `object`. |
|
880 ___set: function(prop, val) { |
|
881 this._data[prop] = val; |
|
882 // Add property directly for easy writing. |
|
883 // Check if its on the `prototype` so we don't overwrite methods like `attrs`. |
|
884 if (!(prop in this.constructor.prototype)) { |
|
885 this[prop] = val |
|
886 } |
|
887 }, |
|
888 |
|
889 |
|
890 bind: can.bindAndSetup, |
|
891 |
|
892 unbind: can.unbindAndTeardown, |
|
893 |
|
894 serialize: function() { |
|
895 return serialize(this, 'serialize', {}); |
|
896 }, |
|
897 |
|
898 _attrs: function(props, remove) { |
|
899 |
|
900 if (props === undefined) { |
|
901 return serialize(this, 'attr', {}) |
|
902 } |
|
903 |
|
904 props = can.extend({}, props); |
|
905 var prop, |
|
906 self = this, |
|
907 newVal; |
|
908 Observe.startBatch(); |
|
909 this.each(function(curVal, prop) { |
|
910 newVal = props[prop]; |
|
911 |
|
912 // If we are merging... |
|
913 if (newVal === undefined) { |
|
914 remove && self.removeAttr(prop); |
|
915 return; |
|
916 } |
|
917 |
|
918 if (self.__convert) { |
|
919 newVal = self.__convert(prop, newVal) |
|
920 } |
|
921 |
|
922 // if we're dealing with models, want to call _set to let converter run |
|
923 if (newVal instanceof can.Observe) { |
|
924 self.__set(prop, newVal, curVal) |
|
925 // if its an object, let attr merge |
|
926 } else if (canMakeObserve(curVal) && canMakeObserve(newVal) && curVal.attr) { |
|
927 curVal.attr(newVal, remove) |
|
928 // otherwise just set |
|
929 } else if (curVal != newVal) { |
|
930 self.__set(prop, newVal, curVal) |
|
931 } |
|
932 |
|
933 delete props[prop]; |
|
934 }) |
|
935 // Add remaining props. |
|
936 for (var prop in props) { |
|
937 newVal = props[prop]; |
|
938 this._set(prop, newVal, true) |
|
939 } |
|
940 Observe.stopBatch() |
|
941 return this; |
|
942 }, |
|
943 |
|
944 |
|
945 compute: function(prop) { |
|
946 return can.compute(this, prop); |
|
947 } |
|
948 }); |
|
949 // Helpers for `observable` lists. |
|
950 var splice = [].splice, |
|
951 |
|
952 list = Observe( |
|
953 |
|
954 { |
|
955 setup: function(instances, options) { |
|
956 this.length = 0; |
|
957 can.cid(this, ".observe") |
|
958 this._init = 1; |
|
959 if (can.isDeferred(instances)) { |
|
960 this.replace(instances) |
|
961 } else { |
|
962 this.push.apply(this, can.makeArray(instances || [])); |
|
963 } |
|
964 // this change needs to be ignored |
|
965 this.bind('change' + this._cid, can.proxy(this._changes, this)); |
|
966 can.extend(this, options); |
|
967 delete this._init; |
|
968 }, |
|
969 _triggerChange: function(attr, how, newVal, oldVal) { |
|
970 |
|
971 Observe.prototype._triggerChange.apply(this, arguments) |
|
972 // `batchTrigger` direct add and remove events... |
|
973 if (!~attr.indexOf('.')) { |
|
974 |
|
975 if (how === 'add') { |
|
976 Observe.triggerBatch(this, how, [newVal, +attr]); |
|
977 Observe.triggerBatch(this, 'length', [this.length]); |
|
978 } else if (how === 'remove') { |
|
979 Observe.triggerBatch(this, how, [oldVal, +attr]); |
|
980 Observe.triggerBatch(this, 'length', [this.length]); |
|
981 } else { |
|
982 Observe.triggerBatch(this, how, [newVal, +attr]) |
|
983 } |
|
984 |
|
985 } |
|
986 |
|
987 }, |
|
988 __get: function(attr) { |
|
989 return attr ? this[attr] : this; |
|
990 }, |
|
991 ___set: function(attr, val) { |
|
992 this[attr] = val; |
|
993 if (+attr >= this.length) { |
|
994 this.length = (+attr + 1) |
|
995 } |
|
996 }, |
|
997 _each: function(callback) { |
|
998 var data = this.__get(); |
|
999 for (var i = 0; i < data.length; i++) { |
|
1000 callback(data[i], i) |
|
1001 } |
|
1002 }, |
|
1003 _bindsetup: makeBindSetup("*"), |
|
1004 // Returns the serialized form of this list. |
|
1005 |
|
1006 serialize: function() { |
|
1007 return serialize(this, 'serialize', []); |
|
1008 }, |
|
1009 |
|
1010 splice: function(index, howMany) { |
|
1011 var args = can.makeArray(arguments), |
|
1012 i; |
|
1013 |
|
1014 for (i = 2; i < args.length; i++) { |
|
1015 var val = args[i]; |
|
1016 if (canMakeObserve(val)) { |
|
1017 args[i] = hookupBubble(val, "*", this, this.constructor.Observe, this.constructor) |
|
1018 } |
|
1019 } |
|
1020 if (howMany === undefined) { |
|
1021 howMany = args[1] = this.length - index; |
|
1022 } |
|
1023 var removed = splice.apply(this, args); |
|
1024 can.Observe.startBatch(); |
|
1025 if (howMany > 0) { |
|
1026 this._triggerChange("" + index, "remove", undefined, removed); |
|
1027 unhookup(removed, this._cid); |
|
1028 } |
|
1029 if (args.length > 2) { |
|
1030 this._triggerChange("" + index, "add", args.slice(2), removed); |
|
1031 } |
|
1032 can.Observe.stopBatch(); |
|
1033 return removed; |
|
1034 }, |
|
1035 |
|
1036 _attrs: function(items, remove) { |
|
1037 if (items === undefined) { |
|
1038 return serialize(this, 'attr', []); |
|
1039 } |
|
1040 |
|
1041 // Create a copy. |
|
1042 items = can.makeArray(items); |
|
1043 |
|
1044 Observe.startBatch(); |
|
1045 this._updateAttrs(items, remove); |
|
1046 Observe.stopBatch() |
|
1047 }, |
|
1048 |
|
1049 _updateAttrs: function(items, remove) { |
|
1050 var len = Math.min(items.length, this.length); |
|
1051 |
|
1052 for (var prop = 0; prop < len; prop++) { |
|
1053 var curVal = this[prop], |
|
1054 newVal = items[prop]; |
|
1055 |
|
1056 if (canMakeObserve(curVal) && canMakeObserve(newVal)) { |
|
1057 curVal.attr(newVal, remove) |
|
1058 } else if (curVal != newVal) { |
|
1059 this._set(prop, newVal) |
|
1060 } else { |
|
1061 |
|
1062 } |
|
1063 } |
|
1064 if (items.length > this.length) { |
|
1065 // Add in the remaining props. |
|
1066 this.push.apply(this, items.slice(this.length)); |
|
1067 } else if (items.length < this.length && remove) { |
|
1068 this.splice(items.length) |
|
1069 } |
|
1070 } |
|
1071 }), |
|
1072 |
|
1073 // Converts to an `array` of arguments. |
|
1074 getArgs = function(args) { |
|
1075 return args[0] && can.isArray(args[0]) ? |
|
1076 args[0] : |
|
1077 can.makeArray(args); |
|
1078 }; |
|
1079 // Create `push`, `pop`, `shift`, and `unshift` |
|
1080 can.each({ |
|
1081 |
|
1082 push: "length", |
|
1083 |
|
1084 unshift: 0 |
|
1085 }, |
|
1086 // Adds a method |
|
1087 // `name` - The method name. |
|
1088 // `where` - Where items in the `array` should be added. |
|
1089 |
|
1090 function(where, name) { |
|
1091 var orig = [][name] |
|
1092 list.prototype[name] = function() { |
|
1093 // Get the items being added. |
|
1094 var args = [], |
|
1095 // Where we are going to add items. |
|
1096 len = where ? this.length : 0, |
|
1097 i = arguments.length, |
|
1098 res, |
|
1099 val, |
|
1100 constructor = this.constructor; |
|
1101 |
|
1102 // Go through and convert anything to an `observe` that needs to be converted. |
|
1103 while (i--) { |
|
1104 val = arguments[i]; |
|
1105 args[i] = canMakeObserve(val) ? |
|
1106 hookupBubble(val, "*", this, this.constructor.Observe, this.constructor) : |
|
1107 val; |
|
1108 } |
|
1109 |
|
1110 // Call the original method. |
|
1111 res = orig.apply(this, args); |
|
1112 |
|
1113 if (!this.comparator || args.length) { |
|
1114 |
|
1115 this._triggerChange("" + len, "add", args, undefined); |
|
1116 } |
|
1117 |
|
1118 return res; |
|
1119 } |
|
1120 }); |
|
1121 |
|
1122 can.each({ |
|
1123 |
|
1124 pop: "length", |
|
1125 |
|
1126 shift: 0 |
|
1127 }, |
|
1128 // Creates a `remove` type method |
|
1129 |
|
1130 function(where, name) { |
|
1131 list.prototype[name] = function() { |
|
1132 |
|
1133 var args = getArgs(arguments), |
|
1134 len = where && this.length ? this.length - 1 : 0; |
|
1135 |
|
1136 var res = [][name].apply(this, args) |
|
1137 |
|
1138 // Create a change where the args are |
|
1139 // `len` - Where these items were removed. |
|
1140 // `remove` - Items removed. |
|
1141 // `undefined` - The new values (there are none). |
|
1142 // `res` - The old, removed values (should these be unbound). |
|
1143 this._triggerChange("" + len, "remove", undefined, [res]) |
|
1144 |
|
1145 if (res && res.unbind) { |
|
1146 res.unbind("change" + this._cid) |
|
1147 } |
|
1148 return res; |
|
1149 } |
|
1150 }); |
|
1151 |
|
1152 can.extend(list.prototype, { |
|
1153 |
|
1154 indexOf: function(item) { |
|
1155 this.attr('length') |
|
1156 return can.inArray(item, this) |
|
1157 }, |
|
1158 |
|
1159 |
|
1160 join: [].join, |
|
1161 |
|
1162 |
|
1163 reverse: [].reverse, |
|
1164 |
|
1165 |
|
1166 slice: function() { |
|
1167 var temp = Array.prototype.slice.apply(this, arguments); |
|
1168 return new this.constructor(temp); |
|
1169 }, |
|
1170 |
|
1171 |
|
1172 concat: function() { |
|
1173 var args = []; |
|
1174 can.each(can.makeArray(arguments), function(arg, i) { |
|
1175 args[i] = arg instanceof can.Observe.List ? arg.serialize() : arg; |
|
1176 }); |
|
1177 return new this.constructor(Array.prototype.concat.apply(this.serialize(), args)); |
|
1178 }, |
|
1179 |
|
1180 |
|
1181 forEach: function(cb, thisarg) { |
|
1182 can.each(this, cb, thisarg || this); |
|
1183 }, |
|
1184 |
|
1185 |
|
1186 replace: function(newList) { |
|
1187 if (can.isDeferred(newList)) { |
|
1188 newList.then(can.proxy(this.replace, this)); |
|
1189 } else { |
|
1190 this.splice.apply(this, [0, this.length].concat(can.makeArray(newList || []))); |
|
1191 } |
|
1192 |
|
1193 return this; |
|
1194 } |
|
1195 }); |
|
1196 |
|
1197 can.List = Observe.List = list; |
|
1198 Observe.setup = function() { |
|
1199 can.Construct.setup.apply(this, arguments); |
|
1200 // I would prefer not to do it this way. It should |
|
1201 // be using the attributes plugin to do this type of conversion. |
|
1202 this.List = Observe.List({ |
|
1203 Observe: this |
|
1204 }, {}); |
|
1205 } |
|
1206 return Observe; |
|
1207 })(__m3, __m8, __m1); |
|
1208 |
|
1209 // ## can/observe/compute/compute.js |
|
1210 var __m9 = (function(can, bind) { |
|
1211 |
|
1212 // returns the |
|
1213 // - observes and attr methods are called by func |
|
1214 // - the value returned by func |
|
1215 // ex: `{value: 100, observed: [{obs: o, attr: "completed"}]}` |
|
1216 var getValueAndObserved = function(func, self) { |
|
1217 |
|
1218 var oldReading; |
|
1219 if (can.Observe) { |
|
1220 // Set a callback on can.Observe to know |
|
1221 // when an attr is read. |
|
1222 // Keep a reference to the old reader |
|
1223 // if there is one. This is used |
|
1224 // for nested live binding. |
|
1225 oldReading = can.Observe.__reading; |
|
1226 can.Observe.__reading = function(obj, attr) { |
|
1227 // Add the observe and attr that was read |
|
1228 // to `observed` |
|
1229 observed.push({ |
|
1230 obj: obj, |
|
1231 attr: attr + "" |
|
1232 }); |
|
1233 }; |
|
1234 } |
|
1235 |
|
1236 var observed = [], |
|
1237 // Call the "wrapping" function to get the value. `observed` |
|
1238 // will have the observe/attribute pairs that were read. |
|
1239 value = func.call(self); |
|
1240 |
|
1241 // Set back so we are no longer reading. |
|
1242 if (can.Observe) { |
|
1243 can.Observe.__reading = oldReading; |
|
1244 } |
|
1245 return { |
|
1246 value: value, |
|
1247 observed: observed |
|
1248 }; |
|
1249 }, |
|
1250 // Calls `callback(newVal, oldVal)` everytime an observed property |
|
1251 // called within `getterSetter` is changed and creates a new result of `getterSetter`. |
|
1252 // Also returns an object that can teardown all event handlers. |
|
1253 computeBinder = function(getterSetter, context, callback, computeState) { |
|
1254 // track what we are observing |
|
1255 var observing = {}, |
|
1256 // a flag indicating if this observe/attr pair is already bound |
|
1257 matched = true, |
|
1258 // the data to return |
|
1259 data = { |
|
1260 // we will maintain the value while live-binding is taking place |
|
1261 value: undefined, |
|
1262 // a teardown method that stops listening |
|
1263 teardown: function() { |
|
1264 for (var name in observing) { |
|
1265 var ob = observing[name]; |
|
1266 ob.observe.obj.unbind(ob.observe.attr, onchanged); |
|
1267 delete observing[name]; |
|
1268 } |
|
1269 } |
|
1270 }, |
|
1271 batchNum; |
|
1272 |
|
1273 // when a property value is changed |
|
1274 var onchanged = function(ev) { |
|
1275 // If the compute is no longer bound (because the same change event led to an unbind) |
|
1276 // then do not call getValueAndBind, or we will leak bindings. |
|
1277 if (computeState && !computeState.bound) { |
|
1278 return; |
|
1279 } |
|
1280 if (ev.batchNum === undefined || ev.batchNum !== batchNum) { |
|
1281 // store the old value |
|
1282 var oldValue = data.value, |
|
1283 // get the new value |
|
1284 newvalue = getValueAndBind(); |
|
1285 |
|
1286 // update the value reference (in case someone reads) |
|
1287 data.value = newvalue; |
|
1288 // if a change happened |
|
1289 if (newvalue !== oldValue) { |
|
1290 callback(newvalue, oldValue); |
|
1291 } |
|
1292 batchNum = batchNum = ev.batchNum; |
|
1293 } |
|
1294 |
|
1295 |
|
1296 }; |
|
1297 |
|
1298 // gets the value returned by `getterSetter` and also binds to any attributes |
|
1299 // read by the call |
|
1300 var getValueAndBind = function() { |
|
1301 var info = getValueAndObserved(getterSetter, context), |
|
1302 newObserveSet = info.observed; |
|
1303 |
|
1304 var value = info.value; |
|
1305 matched = !matched; |
|
1306 |
|
1307 // go through every attribute read by this observe |
|
1308 can.each(newObserveSet, function(ob) { |
|
1309 // if the observe/attribute pair is being observed |
|
1310 if (observing[ob.obj._cid + "|" + ob.attr]) { |
|
1311 // mark at as observed |
|
1312 observing[ob.obj._cid + "|" + ob.attr].matched = matched; |
|
1313 } else { |
|
1314 // otherwise, set the observe/attribute on oldObserved, marking it as being observed |
|
1315 observing[ob.obj._cid + "|" + ob.attr] = { |
|
1316 matched: matched, |
|
1317 observe: ob |
|
1318 }; |
|
1319 ob.obj.bind(ob.attr, onchanged); |
|
1320 } |
|
1321 }); |
|
1322 |
|
1323 // Iterate through oldObserved, looking for observe/attributes |
|
1324 // that are no longer being bound and unbind them |
|
1325 for (var name in observing) { |
|
1326 var ob = observing[name]; |
|
1327 if (ob.matched !== matched) { |
|
1328 ob.observe.obj.unbind(ob.observe.attr, onchanged); |
|
1329 delete observing[name]; |
|
1330 } |
|
1331 } |
|
1332 return value; |
|
1333 }; |
|
1334 // set the initial value |
|
1335 data.value = getValueAndBind(); |
|
1336 |
|
1337 data.isListening = !can.isEmptyObject(observing); |
|
1338 return data; |
|
1339 } |
|
1340 |
|
1341 // if no one is listening ... we can not calculate every time |
|
1342 |
|
1343 can.compute = function(getterSetter, context, eventName) { |
|
1344 if (getterSetter && getterSetter.isComputed) { |
|
1345 return getterSetter; |
|
1346 } |
|
1347 // stores the result of computeBinder |
|
1348 var computedData, |
|
1349 // how many listeners to this this compute |
|
1350 bindings = 0, |
|
1351 // the computed object |
|
1352 computed, |
|
1353 // an object that keeps track if the computed is bound |
|
1354 // onchanged needs to know this. It's possible a change happens and results in |
|
1355 // something that unbinds the compute, it needs to not to try to recalculate who it |
|
1356 // is listening to |
|
1357 computeState = { |
|
1358 bound: false, |
|
1359 // true if this compute is calculated from other computes and observes |
|
1360 hasDependencies: false |
|
1361 }, |
|
1362 // The following functions are overwritten depending on how compute() is called |
|
1363 // a method to setup listening |
|
1364 on = function() {}, |
|
1365 // a method to teardown listening |
|
1366 off = function() {}, |
|
1367 // the current cached value (only valid if bound = true) |
|
1368 value, |
|
1369 // how to read the value |
|
1370 get = function() { |
|
1371 return value |
|
1372 }, |
|
1373 // sets the value |
|
1374 set = function(newVal) { |
|
1375 value = newVal; |
|
1376 }, |
|
1377 // this compute can be a dependency of other computes |
|
1378 canReadForChangeEvent = true; |
|
1379 |
|
1380 computed = function(newVal) { |
|
1381 // setting ... |
|
1382 if (arguments.length) { |
|
1383 // save a reference to the old value |
|
1384 var old = value; |
|
1385 |
|
1386 // setter may return a value if |
|
1387 // setter is for a value maintained exclusively by this compute |
|
1388 var setVal = set.call(context, newVal, old); |
|
1389 |
|
1390 // if this has dependencies return the current value |
|
1391 if (computed.hasDependencies) { |
|
1392 return get.call(context); |
|
1393 } |
|
1394 |
|
1395 if (setVal === undefined) { |
|
1396 // it's possible, like with the DOM, setting does not |
|
1397 // fire a change event, so we must read |
|
1398 value = get.call(context); |
|
1399 } else { |
|
1400 value = setVal; |
|
1401 } |
|
1402 // fire the change |
|
1403 if (old !== value) { |
|
1404 can.Observe.triggerBatch(computed, "change", [value, old]); |
|
1405 } |
|
1406 return value; |
|
1407 } else { |
|
1408 // Let others know to listen to changes in this compute |
|
1409 if (can.Observe.__reading && canReadForChangeEvent) { |
|
1410 can.Observe.__reading(computed, 'change'); |
|
1411 } |
|
1412 // if we are bound, use the cached value |
|
1413 if (computeState.bound) { |
|
1414 return value; |
|
1415 } else { |
|
1416 return get.call(context); |
|
1417 } |
|
1418 } |
|
1419 } |
|
1420 if (typeof getterSetter === "function") { |
|
1421 set = getterSetter; |
|
1422 get = getterSetter; |
|
1423 canReadForChangeEvent = eventName === false ? false : true; |
|
1424 computed.hasDependencies = false; |
|
1425 on = function(update) { |
|
1426 computedData = computeBinder(getterSetter, context || this, update, computeState); |
|
1427 computed.hasDependencies = computedData.isListening |
|
1428 value = computedData.value; |
|
1429 } |
|
1430 off = function() { |
|
1431 computedData && computedData.teardown(); |
|
1432 } |
|
1433 } else if (context) { |
|
1434 |
|
1435 if (typeof context == "string") { |
|
1436 // `can.compute(obj, "propertyName", [eventName])` |
|
1437 |
|
1438 var propertyName = context, |
|
1439 isObserve = getterSetter instanceof can.Observe; |
|
1440 if (isObserve) { |
|
1441 computed.hasDependencies = true; |
|
1442 } |
|
1443 get = function() { |
|
1444 if (isObserve) { |
|
1445 return getterSetter.attr(propertyName); |
|
1446 } else { |
|
1447 return getterSetter[propertyName]; |
|
1448 } |
|
1449 } |
|
1450 set = function(newValue) { |
|
1451 if (isObserve) { |
|
1452 getterSetter.attr(propertyName, newValue) |
|
1453 } else { |
|
1454 getterSetter[propertyName] = newValue; |
|
1455 } |
|
1456 } |
|
1457 var handler; |
|
1458 on = function(update) { |
|
1459 handler = function() { |
|
1460 update(get(), value) |
|
1461 }; |
|
1462 can.bind.call(getterSetter, eventName || propertyName, handler) |
|
1463 |
|
1464 // use getValueAndObserved because |
|
1465 // we should not be indicating that some parent |
|
1466 // reads this property if it happens to be binding on it |
|
1467 value = getValueAndObserved(get).value |
|
1468 } |
|
1469 off = function() { |
|
1470 can.unbind.call(getterSetter, eventName || propertyName, handler) |
|
1471 } |
|
1472 |
|
1473 } else { |
|
1474 // `can.compute(initialValue, setter)` |
|
1475 if (typeof context === "function") { |
|
1476 value = getterSetter; |
|
1477 set = context; |
|
1478 } else { |
|
1479 // `can.compute(initialValue,{get:, set:, on:, off:})` |
|
1480 value = getterSetter; |
|
1481 var options = context; |
|
1482 get = options.get || get; |
|
1483 set = options.set || set; |
|
1484 on = options.on || on; |
|
1485 off = options.off || off; |
|
1486 } |
|
1487 |
|
1488 } |
|
1489 |
|
1490 |
|
1491 |
|
1492 } else { |
|
1493 // `can.compute(5)` |
|
1494 value = getterSetter; |
|
1495 } |
|
1496 |
|
1497 computed.isComputed = true; |
|
1498 |
|
1499 can.cid(computed, "compute") |
|
1500 |
|
1501 var updater = function(newValue, oldValue) { |
|
1502 value = newValue; |
|
1503 // might need a way to look up new and oldVal |
|
1504 can.Observe.triggerBatch(computed, "change", [newValue, oldValue]) |
|
1505 } |
|
1506 |
|
1507 return can.extend(computed, { |
|
1508 _bindsetup: function() { |
|
1509 computeState.bound = true; |
|
1510 // setup live-binding |
|
1511 on.call(this, updater) |
|
1512 }, |
|
1513 _bindteardown: function() { |
|
1514 off.call(this, updater) |
|
1515 computeState.bound = false; |
|
1516 }, |
|
1517 |
|
1518 bind: can.bindAndSetup, |
|
1519 |
|
1520 unbind: can.unbindAndTeardown |
|
1521 }); |
|
1522 }; |
|
1523 can.compute.binder = computeBinder; |
|
1524 return can.compute; |
|
1525 })(__m3, __m8); |
|
1526 |
|
1527 // ## can/model/model.js |
|
1528 var __m10 = (function(can) { |
|
1529 |
|
1530 // ## model.js |
|
1531 // `can.Model` |
|
1532 // _A `can.Observe` that connects to a RESTful interface._ |
|
1533 // Generic deferred piping function |
|
1534 |
|
1535 var pipe = function(def, model, func) { |
|
1536 var d = new can.Deferred(); |
|
1537 def.then(function() { |
|
1538 var args = can.makeArray(arguments); |
|
1539 args[0] = model[func](args[0]); |
|
1540 d.resolveWith(d, args); |
|
1541 }, function() { |
|
1542 d.rejectWith(this, arguments); |
|
1543 }); |
|
1544 |
|
1545 if (typeof def.abort === 'function') { |
|
1546 d.abort = function() { |
|
1547 return def.abort(); |
|
1548 } |
|
1549 } |
|
1550 |
|
1551 return d; |
|
1552 }, |
|
1553 modelNum = 0, |
|
1554 ignoreHookup = /change.observe\d+/, |
|
1555 getId = function(inst) { |
|
1556 // Instead of using attr, use __get for performance. |
|
1557 // Need to set reading |
|
1558 can.Observe.__reading && can.Observe.__reading(inst, inst.constructor.id) |
|
1559 return inst.__get(inst.constructor.id); |
|
1560 }, |
|
1561 // Ajax `options` generator function |
|
1562 ajax = function(ajaxOb, data, type, dataType, success, error) { |
|
1563 |
|
1564 var params = {}; |
|
1565 |
|
1566 // If we get a string, handle it. |
|
1567 if (typeof ajaxOb == "string") { |
|
1568 // If there's a space, it's probably the type. |
|
1569 var parts = ajaxOb.split(/\s+/); |
|
1570 params.url = parts.pop(); |
|
1571 if (parts.length) { |
|
1572 params.type = parts.pop(); |
|
1573 } |
|
1574 } else { |
|
1575 can.extend(params, ajaxOb); |
|
1576 } |
|
1577 |
|
1578 // If we are a non-array object, copy to a new attrs. |
|
1579 params.data = typeof data == "object" && !can.isArray(data) ? |
|
1580 can.extend(params.data || {}, data) : data; |
|
1581 |
|
1582 // Get the url with any templated values filled out. |
|
1583 params.url = can.sub(params.url, params.data, true); |
|
1584 |
|
1585 return can.ajax(can.extend({ |
|
1586 type: type || "post", |
|
1587 dataType: dataType || "json", |
|
1588 success: success, |
|
1589 error: error |
|
1590 }, params)); |
|
1591 }, |
|
1592 makeRequest = function(self, type, success, error, method) { |
|
1593 var args; |
|
1594 // if we pass an array as `self` it it means we are coming from |
|
1595 // the queued request, and we're passing already serialized data |
|
1596 // self's signature will be: [self, serializedData] |
|
1597 if (can.isArray(self)) { |
|
1598 args = self[1]; |
|
1599 self = self[0]; |
|
1600 } else { |
|
1601 args = self.serialize(); |
|
1602 } |
|
1603 args = [args]; |
|
1604 var deferred, |
|
1605 // The model. |
|
1606 model = self.constructor, |
|
1607 jqXHR; |
|
1608 |
|
1609 // `destroy` does not need data. |
|
1610 if (type == 'destroy') { |
|
1611 args.shift(); |
|
1612 } |
|
1613 // `update` and `destroy` need the `id`. |
|
1614 if (type !== 'create') { |
|
1615 args.unshift(getId(self)); |
|
1616 } |
|
1617 |
|
1618 |
|
1619 jqXHR = model[type].apply(model, args); |
|
1620 |
|
1621 deferred = jqXHR.pipe(function(data) { |
|
1622 self[method || type + "d"](data, jqXHR); |
|
1623 return self; |
|
1624 }); |
|
1625 |
|
1626 // Hook up `abort` |
|
1627 if (jqXHR.abort) { |
|
1628 deferred.abort = function() { |
|
1629 jqXHR.abort(); |
|
1630 }; |
|
1631 } |
|
1632 |
|
1633 deferred.then(success, error); |
|
1634 return deferred; |
|
1635 }, |
|
1636 |
|
1637 // This object describes how to make an ajax request for each ajax method. |
|
1638 // The available properties are: |
|
1639 // `url` - The default url to use as indicated as a property on the model. |
|
1640 // `type` - The default http request type |
|
1641 // `data` - A method that takes the `arguments` and returns `data` used for ajax. |
|
1642 |
|
1643 ajaxMethods = { |
|
1644 |
|
1645 create: { |
|
1646 url: "_shortName", |
|
1647 type: "post" |
|
1648 }, |
|
1649 |
|
1650 update: { |
|
1651 data: function(id, attrs) { |
|
1652 attrs = attrs || {}; |
|
1653 var identity = this.id; |
|
1654 if (attrs[identity] && attrs[identity] !== id) { |
|
1655 attrs["new" + can.capitalize(id)] = attrs[identity]; |
|
1656 delete attrs[identity]; |
|
1657 } |
|
1658 attrs[identity] = id; |
|
1659 return attrs; |
|
1660 }, |
|
1661 type: "put" |
|
1662 }, |
|
1663 |
|
1664 destroy: { |
|
1665 type: "delete", |
|
1666 data: function(id) { |
|
1667 var args = {}; |
|
1668 args.id = args[this.id] = id; |
|
1669 return args; |
|
1670 } |
|
1671 }, |
|
1672 |
|
1673 findAll: { |
|
1674 url: "_shortName" |
|
1675 }, |
|
1676 |
|
1677 findOne: {} |
|
1678 }, |
|
1679 // Makes an ajax request `function` from a string. |
|
1680 // `ajaxMethod` - The `ajaxMethod` object defined above. |
|
1681 // `str` - The string the user provided. Ex: `findAll: "/recipes.json"`. |
|
1682 ajaxMaker = function(ajaxMethod, str) { |
|
1683 // Return a `function` that serves as the ajax method. |
|
1684 return function(data) { |
|
1685 // If the ajax method has it's own way of getting `data`, use that. |
|
1686 data = ajaxMethod.data ? |
|
1687 ajaxMethod.data.apply(this, arguments) : |
|
1688 // Otherwise use the data passed in. |
|
1689 data; |
|
1690 // Return the ajax method with `data` and the `type` provided. |
|
1691 return ajax(str || this[ajaxMethod.url || "_url"], data, ajaxMethod.type || "get") |
|
1692 } |
|
1693 } |
|
1694 |
|
1695 |
|
1696 |
|
1697 can.Model = can.Observe({ |
|
1698 fullName: "can.Model", |
|
1699 _reqs: 0, |
|
1700 setup: function(base) { |
|
1701 // create store here if someone wants to use model without inheriting from it |
|
1702 this.store = {}; |
|
1703 can.Observe.setup.apply(this, arguments); |
|
1704 // Set default list as model list |
|
1705 if (!can.Model) { |
|
1706 return; |
|
1707 } |
|
1708 this.List = ML({ |
|
1709 Observe: this |
|
1710 }, {}); |
|
1711 var self = this, |
|
1712 clean = can.proxy(this._clean, self); |
|
1713 |
|
1714 |
|
1715 // go through ajax methods and set them up |
|
1716 can.each(ajaxMethods, function(method, name) { |
|
1717 // if an ajax method is not a function, it's either |
|
1718 // a string url like findAll: "/recipes" or an |
|
1719 // ajax options object like {url: "/recipes"} |
|
1720 if (!can.isFunction(self[name])) { |
|
1721 // use ajaxMaker to convert that into a function |
|
1722 // that returns a deferred with the data |
|
1723 self[name] = ajaxMaker(method, self[name]); |
|
1724 } |
|
1725 // check if there's a make function like makeFindAll |
|
1726 // these take deferred function and can do special |
|
1727 // behavior with it (like look up data in a store) |
|
1728 if (self["make" + can.capitalize(name)]) { |
|
1729 // pass the deferred method to the make method to get back |
|
1730 // the "findAll" method. |
|
1731 var newMethod = self["make" + can.capitalize(name)](self[name]); |
|
1732 can.Construct._overwrite(self, base, name, function() { |
|
1733 // increment the numer of requests |
|
1734 can.Model._reqs++; |
|
1735 var def = newMethod.apply(this, arguments); |
|
1736 var then = def.then(clean, clean); |
|
1737 then.abort = def.abort; |
|
1738 |
|
1739 // attach abort to our then and return it |
|
1740 return then; |
|
1741 }) |
|
1742 } |
|
1743 }); |
|
1744 |
|
1745 if (self.fullName == "can.Model" || !self.fullName) { |
|
1746 self.fullName = "Model" + (++modelNum); |
|
1747 } |
|
1748 // Add ajax converters. |
|
1749 can.Model._reqs = 0; |
|
1750 this._url = this._shortName + "/{" + this.id + "}" |
|
1751 }, |
|
1752 _ajax: ajaxMaker, |
|
1753 _makeRequest: makeRequest, |
|
1754 _clean: function() { |
|
1755 can.Model._reqs--; |
|
1756 if (!can.Model._reqs) { |
|
1757 for (var id in this.store) { |
|
1758 if (!this.store[id]._bindings) { |
|
1759 delete this.store[id]; |
|
1760 } |
|
1761 } |
|
1762 } |
|
1763 return arguments[0]; |
|
1764 }, |
|
1765 |
|
1766 models: function(instancesRawData, oldList) { |
|
1767 // until "end of turn", increment reqs counter so instances will be added to the store |
|
1768 can.Model._reqs++; |
|
1769 if (!instancesRawData) { |
|
1770 return; |
|
1771 } |
|
1772 |
|
1773 if (instancesRawData instanceof this.List) { |
|
1774 return instancesRawData; |
|
1775 } |
|
1776 |
|
1777 // Get the list type. |
|
1778 var self = this, |
|
1779 tmp = [], |
|
1780 res = oldList instanceof can.Observe.List ? oldList : new(self.List || ML), |
|
1781 // Did we get an `array`? |
|
1782 arr = can.isArray(instancesRawData), |
|
1783 |
|
1784 // Did we get a model list? |
|
1785 ml = (instancesRawData instanceof ML), |
|
1786 |
|
1787 // Get the raw `array` of objects. |
|
1788 raw = arr ? |
|
1789 |
|
1790 // If an `array`, return the `array`. |
|
1791 instancesRawData : |
|
1792 |
|
1793 // Otherwise if a model list. |
|
1794 (ml ? |
|
1795 |
|
1796 // Get the raw objects from the list. |
|
1797 instancesRawData.serialize() : |
|
1798 |
|
1799 // Get the object's data. |
|
1800 instancesRawData.data), |
|
1801 i = 0; |
|
1802 |
|
1803 |
|
1804 |
|
1805 if (res.length) { |
|
1806 res.splice(0); |
|
1807 } |
|
1808 |
|
1809 can.each(raw, function(rawPart) { |
|
1810 tmp.push(self.model(rawPart)); |
|
1811 }); |
|
1812 |
|
1813 // We only want one change event so push everything at once |
|
1814 res.push.apply(res, tmp); |
|
1815 |
|
1816 if (!arr) { // Push other stuff onto `array`. |
|
1817 can.each(instancesRawData, function(val, prop) { |
|
1818 if (prop !== 'data') { |
|
1819 res.attr(prop, val); |
|
1820 } |
|
1821 }) |
|
1822 } |
|
1823 // at "end of turn", clean up the store |
|
1824 setTimeout(can.proxy(this._clean, this), 1); |
|
1825 return res; |
|
1826 }, |
|
1827 |
|
1828 model: function(attributes) { |
|
1829 if (!attributes) { |
|
1830 return; |
|
1831 } |
|
1832 if (attributes instanceof this) { |
|
1833 attributes = attributes.serialize(); |
|
1834 } |
|
1835 var id = attributes[this.id], |
|
1836 model = (id || id === 0) && this.store[id] ? |
|
1837 this.store[id].attr(attributes, this.removeAttr || false) : new this(attributes); |
|
1838 if (can.Model._reqs) { |
|
1839 this.store[attributes[this.id]] = model; |
|
1840 } |
|
1841 return model; |
|
1842 } |
|
1843 }, |
|
1844 |
|
1845 |
|
1846 { |
|
1847 |
|
1848 isNew: function() { |
|
1849 var id = getId(this); |
|
1850 return !(id || id === 0); // If `null` or `undefined` |
|
1851 }, |
|
1852 |
|
1853 save: function(success, error) { |
|
1854 return makeRequest(this, this.isNew() ? 'create' : 'update', success, error); |
|
1855 }, |
|
1856 |
|
1857 destroy: function(success, error) { |
|
1858 if (this.isNew()) { |
|
1859 var self = this; |
|
1860 var def = can.Deferred(); |
|
1861 def.then(success, error); |
|
1862 return def.done(function(data) { |
|
1863 self.destroyed(data) |
|
1864 }).resolve(self); |
|
1865 } |
|
1866 return makeRequest(this, 'destroy', success, error, 'destroyed'); |
|
1867 }, |
|
1868 |
|
1869 _bindsetup: function() { |
|
1870 this.constructor.store[this.__get(this.constructor.id)] = this; |
|
1871 return can.Observe.prototype._bindsetup.apply(this, arguments); |
|
1872 }, |
|
1873 |
|
1874 _bindteardown: function() { |
|
1875 delete this.constructor.store[getId(this)]; |
|
1876 return can.Observe.prototype._bindteardown.apply(this, arguments) |
|
1877 }, |
|
1878 // Change `id`. |
|
1879 ___set: function(prop, val) { |
|
1880 can.Observe.prototype.___set.call(this, prop, val) |
|
1881 // If we add an `id`, move it to the store. |
|
1882 if (prop === this.constructor.id && this._bindings) { |
|
1883 this.constructor.store[getId(this)] = this; |
|
1884 } |
|
1885 } |
|
1886 }); |
|
1887 |
|
1888 can.each({ |
|
1889 makeFindAll: "models", |
|
1890 makeFindOne: "model", |
|
1891 makeCreate: "model", |
|
1892 makeUpdate: "model" |
|
1893 }, function(method, name) { |
|
1894 can.Model[name] = function(oldMethod) { |
|
1895 return function() { |
|
1896 var args = can.makeArray(arguments), |
|
1897 oldArgs = can.isFunction(args[1]) ? args.splice(0, 1) : args.splice(0, 2), |
|
1898 def = pipe(oldMethod.apply(this, oldArgs), this, method); |
|
1899 def.then(args[0], args[1]); |
|
1900 // return the original promise |
|
1901 return def; |
|
1902 }; |
|
1903 }; |
|
1904 }); |
|
1905 |
|
1906 can.each([ |
|
1907 |
|
1908 "created", |
|
1909 |
|
1910 "updated", |
|
1911 |
|
1912 "destroyed" |
|
1913 ], function(funcName) { |
|
1914 can.Model.prototype[funcName] = function(attrs) { |
|
1915 var stub, |
|
1916 constructor = this.constructor; |
|
1917 |
|
1918 // Update attributes if attributes have been passed |
|
1919 stub = attrs && typeof attrs == 'object' && this.attr(attrs.attr ? attrs.attr() : attrs); |
|
1920 |
|
1921 // triggers change event that bubble's like |
|
1922 // handler( 'change','1.destroyed' ). This is used |
|
1923 // to remove items on destroyed from Model Lists. |
|
1924 // but there should be a better way. |
|
1925 can.trigger(this, "change", funcName) |
|
1926 |
|
1927 |
|
1928 // Call event on the instance's Class |
|
1929 can.trigger(constructor, funcName, this); |
|
1930 }; |
|
1931 }); |
|
1932 |
|
1933 // Model lists are just like `Observe.List` except that when their items are |
|
1934 // destroyed, it automatically gets removed from the list. |
|
1935 |
|
1936 var ML = can.Model.List = can.Observe.List({ |
|
1937 setup: function(params) { |
|
1938 if (can.isPlainObject(params) && !can.isArray(params)) { |
|
1939 can.Observe.List.prototype.setup.apply(this); |
|
1940 this.replace(this.constructor.Observe.findAll(params)) |
|
1941 } else { |
|
1942 can.Observe.List.prototype.setup.apply(this, arguments); |
|
1943 } |
|
1944 }, |
|
1945 _changes: function(ev, attr) { |
|
1946 can.Observe.List.prototype._changes.apply(this, arguments); |
|
1947 if (/\w+\.destroyed/.test(attr)) { |
|
1948 var index = this.indexOf(ev.target); |
|
1949 if (index != -1) { |
|
1950 this.splice(index, 1); |
|
1951 } |
|
1952 } |
|
1953 } |
|
1954 }) |
|
1955 |
|
1956 return can.Model; |
|
1957 })(__m3, __m7); |
|
1958 |
|
1959 // ## can/view/view.js |
|
1960 var __m11 = (function(can) { |
|
1961 // ## view.js |
|
1962 // `can.view` |
|
1963 // _Templating abstraction._ |
|
1964 |
|
1965 var isFunction = can.isFunction, |
|
1966 makeArray = can.makeArray, |
|
1967 // Used for hookup `id`s. |
|
1968 hookupId = 1, |
|
1969 |
|
1970 $view = can.view = can.template = function(view, data, helpers, callback) { |
|
1971 // If helpers is a `function`, it is actually a callback. |
|
1972 if (isFunction(helpers)) { |
|
1973 callback = helpers; |
|
1974 helpers = undefined; |
|
1975 } |
|
1976 |
|
1977 var pipe = function(result) { |
|
1978 return $view.frag(result); |
|
1979 }, |
|
1980 // In case we got a callback, we need to convert the can.view.render |
|
1981 // result to a document fragment |
|
1982 wrapCallback = isFunction(callback) ? function(frag) { |
|
1983 callback(pipe(frag)); |
|
1984 } : null, |
|
1985 // Get the result. |
|
1986 result = $view.render(view, data, helpers, wrapCallback), |
|
1987 deferred = can.Deferred(); |
|
1988 |
|
1989 if (isFunction(result)) { |
|
1990 return result; |
|
1991 } |
|
1992 |
|
1993 if (can.isDeferred(result)) { |
|
1994 result.then(function(result, data) { |
|
1995 deferred.resolve.call(deferred, pipe(result), data); |
|
1996 }, function() { |
|
1997 deferred.fail.apply(deferred, arguments); |
|
1998 }); |
|
1999 return deferred; |
|
2000 } |
|
2001 |
|
2002 // Convert it into a dom frag. |
|
2003 return pipe(result); |
|
2004 }; |
|
2005 |
|
2006 can.extend($view, { |
|
2007 // creates a frag and hooks it up all at once |
|
2008 frag: function(result, parentNode) { |
|
2009 return $view.hookup($view.fragment(result), parentNode); |
|
2010 }, |
|
2011 |
|
2012 // simply creates a frag |
|
2013 // this is used internally to create a frag |
|
2014 // insert it |
|
2015 // then hook it up |
|
2016 fragment: function(result) { |
|
2017 var frag = can.buildFragment(result, document.body); |
|
2018 // If we have an empty frag... |
|
2019 if (!frag.childNodes.length) { |
|
2020 frag.appendChild(document.createTextNode('')); |
|
2021 } |
|
2022 return frag; |
|
2023 }, |
|
2024 |
|
2025 // Convert a path like string into something that's ok for an `element` ID. |
|
2026 toId: function(src) { |
|
2027 return can.map(src.toString().split(/\/|\./g), function(part) { |
|
2028 // Dont include empty strings in toId functions |
|
2029 if (part) { |
|
2030 return part; |
|
2031 } |
|
2032 }).join("_"); |
|
2033 }, |
|
2034 |
|
2035 hookup: function(fragment, parentNode) { |
|
2036 var hookupEls = [], |
|
2037 id, |
|
2038 func; |
|
2039 |
|
2040 // Get all `childNodes`. |
|
2041 can.each(fragment.childNodes ? can.makeArray(fragment.childNodes) : fragment, function(node) { |
|
2042 if (node.nodeType === 1) { |
|
2043 hookupEls.push(node); |
|
2044 hookupEls.push.apply(hookupEls, can.makeArray(node.getElementsByTagName('*'))); |
|
2045 } |
|
2046 }); |
|
2047 |
|
2048 // Filter by `data-view-id` attribute. |
|
2049 can.each(hookupEls, function(el) { |
|
2050 if (el.getAttribute && (id = el.getAttribute('data-view-id')) && (func = $view.hookups[id])) { |
|
2051 func(el, parentNode, id); |
|
2052 delete $view.hookups[id]; |
|
2053 el.removeAttribute('data-view-id'); |
|
2054 } |
|
2055 }); |
|
2056 |
|
2057 return fragment; |
|
2058 }, |
|
2059 |
|
2060 |
|
2061 hookups: {}, |
|
2062 |
|
2063 |
|
2064 hook: function(cb) { |
|
2065 $view.hookups[++hookupId] = cb; |
|
2066 return " data-view-id='" + hookupId + "'"; |
|
2067 }, |
|
2068 |
|
2069 |
|
2070 cached: {}, |
|
2071 |
|
2072 cachedRenderers: {}, |
|
2073 |
|
2074 |
|
2075 cache: true, |
|
2076 |
|
2077 |
|
2078 register: function(info) { |
|
2079 this.types["." + info.suffix] = info; |
|
2080 }, |
|
2081 |
|
2082 types: {}, |
|
2083 |
|
2084 |
|
2085 ext: ".ejs", |
|
2086 |
|
2087 |
|
2088 registerScript: function() {}, |
|
2089 |
|
2090 |
|
2091 preload: function() {}, |
|
2092 |
|
2093 |
|
2094 render: function(view, data, helpers, callback) { |
|
2095 // If helpers is a `function`, it is actually a callback. |
|
2096 if (isFunction(helpers)) { |
|
2097 callback = helpers; |
|
2098 helpers = undefined; |
|
2099 } |
|
2100 |
|
2101 // See if we got passed any deferreds. |
|
2102 var deferreds = getDeferreds(data); |
|
2103 |
|
2104 if (deferreds.length) { // Does data contain any deferreds? |
|
2105 // The deferred that resolves into the rendered content... |
|
2106 var deferred = new can.Deferred(), |
|
2107 dataCopy = can.extend({}, data); |
|
2108 |
|
2109 // Add the view request to the list of deferreds. |
|
2110 deferreds.push(get(view, true)) |
|
2111 |
|
2112 // Wait for the view and all deferreds to finish... |
|
2113 can.when.apply(can, deferreds).then(function(resolved) { |
|
2114 // Get all the resolved deferreds. |
|
2115 var objs = makeArray(arguments), |
|
2116 // Renderer is the last index of the data. |
|
2117 renderer = objs.pop(), |
|
2118 // The result of the template rendering with data. |
|
2119 result; |
|
2120 |
|
2121 // Make data look like the resolved deferreds. |
|
2122 if (can.isDeferred(data)) { |
|
2123 dataCopy = usefulPart(resolved); |
|
2124 } else { |
|
2125 // Go through each prop in data again and |
|
2126 // replace the defferreds with what they resolved to. |
|
2127 for (var prop in data) { |
|
2128 if (can.isDeferred(data[prop])) { |
|
2129 dataCopy[prop] = usefulPart(objs.shift()); |
|
2130 } |
|
2131 } |
|
2132 } |
|
2133 |
|
2134 // Get the rendered result. |
|
2135 result = renderer(dataCopy, helpers); |
|
2136 |
|
2137 // Resolve with the rendered view. |
|
2138 deferred.resolve(result, dataCopy); |
|
2139 |
|
2140 // If there's a `callback`, call it back with the result. |
|
2141 callback && callback(result, dataCopy); |
|
2142 }, function() { |
|
2143 deferred.reject.apply(deferred, arguments) |
|
2144 }); |
|
2145 // Return the deferred... |
|
2146 return deferred; |
|
2147 } else { |
|
2148 // No deferreds! Render this bad boy. |
|
2149 var response, |
|
2150 // If there's a `callback` function |
|
2151 async = isFunction(callback), |
|
2152 // Get the `view` type |
|
2153 deferred = get(view, async); |
|
2154 |
|
2155 // If we are `async`... |
|
2156 if (async) { |
|
2157 // Return the deferred |
|
2158 response = deferred; |
|
2159 // And fire callback with the rendered result. |
|
2160 deferred.then(function(renderer) { |
|
2161 callback(data ? renderer(data, helpers) : renderer); |
|
2162 }) |
|
2163 } else { |
|
2164 // if the deferred is resolved, call the cached renderer instead |
|
2165 // this is because it's possible, with recursive deferreds to |
|
2166 // need to render a view while its deferred is _resolving_. A _resolving_ deferred |
|
2167 // is a deferred that was just resolved and is calling back it's success callbacks. |
|
2168 // If a new success handler is called while resoliving, it does not get fired by |
|
2169 // jQuery's deferred system. So instead of adding a new callback |
|
2170 // we use the cached renderer. |
|
2171 // We also add __view_id on the deferred so we can look up it's cached renderer. |
|
2172 // In the future, we might simply store either a deferred or the cached result. |
|
2173 if (deferred.state() === "resolved" && deferred.__view_id) { |
|
2174 var currentRenderer = $view.cachedRenderers[deferred.__view_id]; |
|
2175 return data ? currentRenderer(data, helpers) : currentRenderer; |
|
2176 } else { |
|
2177 // Otherwise, the deferred is complete, so |
|
2178 // set response to the result of the rendering. |
|
2179 deferred.then(function(renderer) { |
|
2180 response = data ? renderer(data, helpers) : renderer; |
|
2181 }); |
|
2182 } |
|
2183 } |
|
2184 |
|
2185 return response; |
|
2186 } |
|
2187 }, |
|
2188 |
|
2189 |
|
2190 registerView: function(id, text, type, def) { |
|
2191 // Get the renderer function. |
|
2192 var func = (type || $view.types[$view.ext]).renderer(id, text); |
|
2193 def = def || new can.Deferred(); |
|
2194 |
|
2195 // Cache if we are caching. |
|
2196 if ($view.cache) { |
|
2197 $view.cached[id] = def; |
|
2198 def.__view_id = id; |
|
2199 $view.cachedRenderers[id] = func; |
|
2200 } |
|
2201 |
|
2202 // Return the objects for the response's `dataTypes` |
|
2203 // (in this case view). |
|
2204 return def.resolve(func); |
|
2205 } |
|
2206 }); |
|
2207 |
|
2208 // Makes sure there's a template, if not, have `steal` provide a warning. |
|
2209 var checkText = function(text, url) { |
|
2210 if (!text.length) { |
|
2211 |
|
2212 throw "can.view: No template or empty template:" + url; |
|
2213 } |
|
2214 }, |
|
2215 // `Returns a `view` renderer deferred. |
|
2216 // `url` - The url to the template. |
|
2217 // `async` - If the ajax request should be asynchronous. |
|
2218 // Returns a deferred. |
|
2219 get = function(url, async) { |
|
2220 var suffix = url.match(/\.[\w\d]+$/), |
|
2221 type, |
|
2222 // If we are reading a script element for the content of the template, |
|
2223 // `el` will be set to that script element. |
|
2224 el, |
|
2225 // A unique identifier for the view (used for caching). |
|
2226 // This is typically derived from the element id or |
|
2227 // the url for the template. |
|
2228 id, |
|
2229 // The ajax request used to retrieve the template content. |
|
2230 jqXHR; |
|
2231 |
|
2232 //If the url has a #, we assume we want to use an inline template |
|
2233 //from a script element and not current page's HTML |
|
2234 if (url.match(/^#/)) { |
|
2235 url = url.substr(1); |
|
2236 } |
|
2237 // If we have an inline template, derive the suffix from the `text/???` part. |
|
2238 // This only supports `<script>` tags. |
|
2239 if (el = document.getElementById(url)) { |
|
2240 suffix = "." + el.type.match(/\/(x\-)?(.+)/)[2]; |
|
2241 } |
|
2242 |
|
2243 // If there is no suffix, add one. |
|
2244 if (!suffix && !$view.cached[url]) { |
|
2245 url += (suffix = $view.ext); |
|
2246 } |
|
2247 |
|
2248 if (can.isArray(suffix)) { |
|
2249 suffix = suffix[0] |
|
2250 } |
|
2251 |
|
2252 // Convert to a unique and valid id. |
|
2253 id = $view.toId(url); |
|
2254 |
|
2255 // If an absolute path, use `steal` to get it. |
|
2256 // You should only be using `//` if you are using `steal`. |
|
2257 if (url.match(/^\/\//)) { |
|
2258 var sub = url.substr(2); |
|
2259 url = !window.steal ? |
|
2260 sub : |
|
2261 steal.config().root.mapJoin("" + steal.id(sub)); |
|
2262 } |
|
2263 |
|
2264 // Set the template engine type. |
|
2265 type = $view.types[suffix]; |
|
2266 |
|
2267 // If it is cached, |
|
2268 if ($view.cached[id]) { |
|
2269 // Return the cached deferred renderer. |
|
2270 return $view.cached[id]; |
|
2271 |
|
2272 // Otherwise if we are getting this from a `<script>` element. |
|
2273 } else if (el) { |
|
2274 // Resolve immediately with the element's `innerHTML`. |
|
2275 return $view.registerView(id, el.innerHTML, type); |
|
2276 } else { |
|
2277 // Make an ajax request for text. |
|
2278 var d = new can.Deferred(); |
|
2279 can.ajax({ |
|
2280 async: async, |
|
2281 url: url, |
|
2282 dataType: "text", |
|
2283 error: function(jqXHR) { |
|
2284 checkText("", url); |
|
2285 d.reject(jqXHR); |
|
2286 }, |
|
2287 success: function(text) { |
|
2288 // Make sure we got some text back. |
|
2289 checkText(text, url); |
|
2290 $view.registerView(id, text, type, d) |
|
2291 } |
|
2292 }); |
|
2293 return d; |
|
2294 } |
|
2295 }, |
|
2296 // Gets an `array` of deferreds from an `object`. |
|
2297 // This only goes one level deep. |
|
2298 getDeferreds = function(data) { |
|
2299 var deferreds = []; |
|
2300 |
|
2301 // pull out deferreds |
|
2302 if (can.isDeferred(data)) { |
|
2303 return [data] |
|
2304 } else { |
|
2305 for (var prop in data) { |
|
2306 if (can.isDeferred(data[prop])) { |
|
2307 deferreds.push(data[prop]); |
|
2308 } |
|
2309 } |
|
2310 } |
|
2311 return deferreds; |
|
2312 }, |
|
2313 // Gets the useful part of a resolved deferred. |
|
2314 // This is for `model`s and `can.ajax` that resolve to an `array`. |
|
2315 usefulPart = function(resolved) { |
|
2316 return can.isArray(resolved) && resolved[1] === 'success' ? resolved[0] : resolved |
|
2317 }; |
|
2318 |
|
2319 //!steal-pluginify-remove-start |
|
2320 if (window.steal) { |
|
2321 steal.type("view js", function(options, success, error) { |
|
2322 var type = $view.types["." + options.type], |
|
2323 id = $view.toId(options.id); |
|
2324 |
|
2325 options.text = "steal('" + (type.plugin || "can/view/" + options.type) + "',function(can){return " + "can.view.preload('" + id + "'," + options.text + ");\n})"; |
|
2326 success(); |
|
2327 }) |
|
2328 } |
|
2329 //!steal-pluginify-remove-end |
|
2330 |
|
2331 can.extend($view, { |
|
2332 register: function(info) { |
|
2333 this.types["." + info.suffix] = info; |
|
2334 |
|
2335 //!steal-pluginify-remove-start |
|
2336 if (window.steal) { |
|
2337 steal.type(info.suffix + " view js", function(options, success, error) { |
|
2338 var type = $view.types["." + options.type], |
|
2339 id = $view.toId(options.id + ''); |
|
2340 |
|
2341 options.text = type.script(id, options.text) |
|
2342 success(); |
|
2343 }) |
|
2344 }; |
|
2345 //!steal-pluginify-remove-end |
|
2346 |
|
2347 $view[info.suffix] = function(id, text) { |
|
2348 if (!text) { |
|
2349 // Return a nameless renderer |
|
2350 var renderer = function() { |
|
2351 return $view.frag(renderer.render.apply(this, arguments)); |
|
2352 } |
|
2353 renderer.render = function() { |
|
2354 var renderer = info.renderer(null, id); |
|
2355 return renderer.apply(renderer, arguments); |
|
2356 } |
|
2357 return renderer; |
|
2358 } |
|
2359 |
|
2360 $view.preload(id, info.renderer(id, text)); |
|
2361 return can.view(id); |
|
2362 } |
|
2363 }, |
|
2364 registerScript: function(type, id, src) { |
|
2365 return "can.view.preload('" + id + "'," + $view.types["." + type].script(id, src) + ");"; |
|
2366 }, |
|
2367 preload: function(id, renderer) { |
|
2368 $view.cached[id] = new can.Deferred().resolve(function(data, helpers) { |
|
2369 return renderer.call(data, data, helpers); |
|
2370 }); |
|
2371 |
|
2372 function frag() { |
|
2373 return $view.frag(renderer.apply(this, arguments)); |
|
2374 } |
|
2375 // expose the renderer for mustache |
|
2376 frag.render = renderer; |
|
2377 return frag; |
|
2378 } |
|
2379 |
|
2380 }); |
|
2381 |
|
2382 return can; |
|
2383 })(__m3); |
|
2384 |
|
2385 // ## can/view/elements.js |
|
2386 var __m14 = (function() { |
|
2387 |
|
2388 var elements = { |
|
2389 tagToContentPropMap: { |
|
2390 option: "textContent" in document.createElement("option") ? "textContent" : "innerText", |
|
2391 textarea: "value" |
|
2392 }, |
|
2393 |
|
2394 attrMap: { |
|
2395 "class": "className", |
|
2396 "value": "value", |
|
2397 "innerText": "innerText", |
|
2398 "textContent": "textContent", |
|
2399 "checked": true, |
|
2400 "disabled": true, |
|
2401 "readonly": true, |
|
2402 "required": true |
|
2403 }, |
|
2404 // elements whos default value we should set |
|
2405 defaultValue: ["input", "textarea"], |
|
2406 // a map of parent element to child elements |
|
2407 tagMap: { |
|
2408 "": "span", |
|
2409 table: "tbody", |
|
2410 tr: "td", |
|
2411 ol: "li", |
|
2412 ul: "li", |
|
2413 tbody: "tr", |
|
2414 thead: "tr", |
|
2415 tfoot: "tr", |
|
2416 select: "option", |
|
2417 optgroup: "option" |
|
2418 }, |
|
2419 // a tag's parent element |
|
2420 reverseTagMap: { |
|
2421 tr: "tbody", |
|
2422 option: "select", |
|
2423 td: "tr", |
|
2424 th: "tr", |
|
2425 li: "ul" |
|
2426 }, |
|
2427 |
|
2428 getParentNode: function(el, defaultParentNode) { |
|
2429 return defaultParentNode && el.parentNode.nodeType === 11 ? defaultParentNode : el.parentNode; |
|
2430 }, |
|
2431 // set an attribute on an element |
|
2432 setAttr: function(el, attrName, val) { |
|
2433 var tagName = el.nodeName.toString().toLowerCase(), |
|
2434 prop = elements.attrMap[attrName]; |
|
2435 // if this is a special property |
|
2436 if (prop === true) { |
|
2437 el[attrName] = true; |
|
2438 } else if (prop) { |
|
2439 // set the value as true / false |
|
2440 el[prop] = val; |
|
2441 if (prop === "value" && can.inArray(tagName, elements.defaultValue) >= 0) { |
|
2442 el.defaultValue = val; |
|
2443 } |
|
2444 } else { |
|
2445 el.setAttribute(attrName, val); |
|
2446 } |
|
2447 }, |
|
2448 // gets the value of an attribute |
|
2449 getAttr: function(el, attrName) { |
|
2450 // Default to a blank string for IE7/8 |
|
2451 return (elements.attrMap[attrName] && el[elements.attrMap[attrName]] ? |
|
2452 el[elements.attrMap[attrName]] : |
|
2453 el.getAttribute(attrName)) || ''; |
|
2454 }, |
|
2455 // removes the attribute |
|
2456 removeAttr: function(el, attrName) { |
|
2457 if (elements.attrMap[attrName] === true) { |
|
2458 el[attrName] = false; |
|
2459 } else { |
|
2460 el.removeAttribute(attrName); |
|
2461 } |
|
2462 }, |
|
2463 contentText: function(text) { |
|
2464 if (typeof text == 'string') { |
|
2465 return text; |
|
2466 } |
|
2467 // If has no value, return an empty string. |
|
2468 if (!text && text !== 0) { |
|
2469 return ''; |
|
2470 } |
|
2471 return "" + text; |
|
2472 } |
|
2473 }; |
|
2474 |
|
2475 return elements; |
|
2476 })(); |
|
2477 |
|
2478 // ## can/view/scanner.js |
|
2479 var __m13 = (function(can, elements) { |
|
2480 |
|
2481 var newLine = /(\r|\n)+/g, |
|
2482 // Escapes characters starting with `\`. |
|
2483 clean = function(content) { |
|
2484 return content |
|
2485 .split('\\').join("\\\\") |
|
2486 .split("\n").join("\\n") |
|
2487 .split('"').join('\\"') |
|
2488 .split("\t").join("\\t"); |
|
2489 }, |
|
2490 // Returns a tagName to use as a temporary placeholder for live content |
|
2491 // looks forward ... could be slow, but we only do it when necessary |
|
2492 getTag = function(tagName, tokens, i) { |
|
2493 // if a tagName is provided, use that |
|
2494 if (tagName) { |
|
2495 return tagName; |
|
2496 } else { |
|
2497 // otherwise go searching for the next two tokens like "<",TAG |
|
2498 while (i < tokens.length) { |
|
2499 if (tokens[i] == "<" && elements.reverseTagMap[tokens[i + 1]]) { |
|
2500 return elements.reverseTagMap[tokens[i + 1]]; |
|
2501 } |
|
2502 i++; |
|
2503 } |
|
2504 } |
|
2505 return ''; |
|
2506 }, |
|
2507 bracketNum = function(content) { |
|
2508 return (--content.split("{").length) - (--content.split("}").length); |
|
2509 }, |
|
2510 myEval = function(script) { |
|
2511 eval(script); |
|
2512 }, |
|
2513 attrReg = /([^\s]+)[\s]*=[\s]*$/, |
|
2514 // Commands for caching. |
|
2515 startTxt = 'var ___v1ew = [];', |
|
2516 finishTxt = "return ___v1ew.join('')", |
|
2517 put_cmd = "___v1ew.push(", |
|
2518 insert_cmd = put_cmd, |
|
2519 // Global controls (used by other functions to know where we are). |
|
2520 // Are we inside a tag? |
|
2521 htmlTag = null, |
|
2522 // Are we within a quote within a tag? |
|
2523 quote = null, |
|
2524 // What was the text before the current quote? (used to get the `attr` name) |
|
2525 beforeQuote = null, |
|
2526 // Whether a rescan is in progress |
|
2527 rescan = null, |
|
2528 // Used to mark where the element is. |
|
2529 status = function() { |
|
2530 // `t` - `1`. |
|
2531 // `h` - `0`. |
|
2532 // `q` - String `beforeQuote`. |
|
2533 return quote ? "'" + beforeQuote.match(attrReg)[1] + "'" : (htmlTag ? 1 : 0); |
|
2534 }; |
|
2535 |
|
2536 can.view.Scanner = Scanner = function(options) { |
|
2537 // Set options on self |
|
2538 can.extend(this, { |
|
2539 text: {}, |
|
2540 tokens: [] |
|
2541 }, options); |
|
2542 |
|
2543 // Cache a token lookup |
|
2544 this.tokenReg = []; |
|
2545 this.tokenSimple = { |
|
2546 "<": "<", |
|
2547 ">": ">", |
|
2548 '"': '"', |
|
2549 "'": "'" |
|
2550 }; |
|
2551 this.tokenComplex = []; |
|
2552 this.tokenMap = {}; |
|
2553 for (var i = 0, token; token = this.tokens[i]; i++) { |
|
2554 |
|
2555 |
|
2556 // Save complex mappings (custom regexp) |
|
2557 if (token[2]) { |
|
2558 this.tokenReg.push(token[2]); |
|
2559 this.tokenComplex.push({ |
|
2560 abbr: token[1], |
|
2561 re: new RegExp(token[2]), |
|
2562 rescan: token[3] |
|
2563 }); |
|
2564 } |
|
2565 // Save simple mappings (string only, no regexp) |
|
2566 else { |
|
2567 this.tokenReg.push(token[1]); |
|
2568 this.tokenSimple[token[1]] = token[0]; |
|
2569 } |
|
2570 this.tokenMap[token[0]] = token[1]; |
|
2571 } |
|
2572 |
|
2573 // Cache the token registry. |
|
2574 this.tokenReg = new RegExp("(" + this.tokenReg.slice(0).concat(["<", ">", '"', "'"]).join("|") + ")", "g"); |
|
2575 }; |
|
2576 |
|
2577 Scanner.prototype = { |
|
2578 |
|
2579 helpers: [ |
|
2580 |
|
2581 { |
|
2582 name: /\s*\(([\$\w]+)\)\s*->([^\n]*)/, |
|
2583 fn: function(content) { |
|
2584 var quickFunc = /\s*\(([\$\w]+)\)\s*->([^\n]*)/, |
|
2585 parts = content.match(quickFunc); |
|
2586 |
|
2587 return "can.proxy(function(__){var " + parts[1] + "=can.$(__);" + parts[2] + "}, this);"; |
|
2588 } |
|
2589 } |
|
2590 ], |
|
2591 |
|
2592 scan: function(source, name) { |
|
2593 var tokens = [], |
|
2594 last = 0, |
|
2595 simple = this.tokenSimple, |
|
2596 complex = this.tokenComplex; |
|
2597 |
|
2598 source = source.replace(newLine, "\n"); |
|
2599 if (this.transform) { |
|
2600 source = this.transform(source); |
|
2601 } |
|
2602 source.replace(this.tokenReg, function(whole, part) { |
|
2603 // offset is the second to last argument |
|
2604 var offset = arguments[arguments.length - 2]; |
|
2605 |
|
2606 // if the next token starts after the last token ends |
|
2607 // push what's in between |
|
2608 if (offset > last) { |
|
2609 tokens.push(source.substring(last, offset)); |
|
2610 } |
|
2611 |
|
2612 // push the simple token (if there is one) |
|
2613 if (simple[whole]) { |
|
2614 tokens.push(whole); |
|
2615 } |
|
2616 // otherwise lookup complex tokens |
|
2617 else { |
|
2618 for (var i = 0, token; token = complex[i]; i++) { |
|
2619 if (token.re.test(whole)) { |
|
2620 tokens.push(token.abbr); |
|
2621 // Push a rescan function if one exists |
|
2622 if (token.rescan) { |
|
2623 tokens.push(token.rescan(part)); |
|
2624 } |
|
2625 break; |
|
2626 } |
|
2627 } |
|
2628 } |
|
2629 |
|
2630 // update the position of the last part of the last token |
|
2631 last = offset + part.length; |
|
2632 }); |
|
2633 |
|
2634 // if there's something at the end, add it |
|
2635 if (last < source.length) { |
|
2636 tokens.push(source.substr(last)); |
|
2637 } |
|
2638 |
|
2639 var content = '', |
|
2640 buff = [startTxt + (this.text.start || '')], |
|
2641 // Helper `function` for putting stuff in the view concat. |
|
2642 put = function(content, bonus) { |
|
2643 buff.push(put_cmd, '"', clean(content), '"' + (bonus || '') + ');'); |
|
2644 }, |
|
2645 // A stack used to keep track of how we should end a bracket |
|
2646 // `}`. |
|
2647 // Once we have a `<%= %>` with a `leftBracket`, |
|
2648 // we store how the file should end here (either `))` or `;`). |
|
2649 endStack = [], |
|
2650 // The last token, used to remember which tag we are in. |
|
2651 lastToken, |
|
2652 // The corresponding magic tag. |
|
2653 startTag = null, |
|
2654 // Was there a magic tag inside an html tag? |
|
2655 magicInTag = false, |
|
2656 // The current tag name. |
|
2657 tagName = '', |
|
2658 // stack of tagNames |
|
2659 tagNames = [], |
|
2660 // Pop from tagNames? |
|
2661 popTagName = false, |
|
2662 // Declared here. |
|
2663 bracketCount, |
|
2664 i = 0, |
|
2665 token, |
|
2666 tmap = this.tokenMap; |
|
2667 |
|
2668 // Reinitialize the tag state goodness. |
|
2669 htmlTag = quote = beforeQuote = null; |
|
2670 |
|
2671 for (; |
|
2672 (token = tokens[i++]) !== undefined;) { |
|
2673 if (startTag === null) { |
|
2674 switch (token) { |
|
2675 case tmap.left: |
|
2676 case tmap.escapeLeft: |
|
2677 case tmap.returnLeft: |
|
2678 magicInTag = htmlTag && 1; |
|
2679 case tmap.commentLeft: |
|
2680 // A new line -- just add whatever content within a clean. |
|
2681 // Reset everything. |
|
2682 startTag = token; |
|
2683 if (content.length) { |
|
2684 put(content); |
|
2685 } |
|
2686 content = ''; |
|
2687 break; |
|
2688 case tmap.escapeFull: |
|
2689 // This is a full line escape (a line that contains only whitespace and escaped logic) |
|
2690 // Break it up into escape left and right |
|
2691 magicInTag = htmlTag && 1; |
|
2692 rescan = 1; |
|
2693 startTag = tmap.escapeLeft; |
|
2694 if (content.length) { |
|
2695 put(content); |
|
2696 } |
|
2697 rescan = tokens[i++]; |
|
2698 content = rescan.content || rescan; |
|
2699 if (rescan.before) { |
|
2700 put(rescan.before); |
|
2701 } |
|
2702 tokens.splice(i, 0, tmap.right); |
|
2703 break; |
|
2704 case tmap.commentFull: |
|
2705 // Ignore full line comments. |
|
2706 break; |
|
2707 case tmap.templateLeft: |
|
2708 content += tmap.left; |
|
2709 break; |
|
2710 case '<': |
|
2711 // Make sure we are not in a comment. |
|
2712 if (tokens[i].indexOf("!--") !== 0) { |
|
2713 htmlTag = 1; |
|
2714 magicInTag = 0; |
|
2715 } |
|
2716 content += token; |
|
2717 break; |
|
2718 case '>': |
|
2719 htmlTag = 0; |
|
2720 // content.substr(-1) doesn't work in IE7/8 |
|
2721 var emptyElement = content.substr(content.length - 1) == "/" || content.substr(content.length - 2) == "--"; |
|
2722 // if there was a magic tag |
|
2723 // or it's an element that has text content between its tags, |
|
2724 // but content is not other tags add a hookup |
|
2725 // TODO: we should only add `can.EJS.pending()` if there's a magic tag |
|
2726 // within the html tags. |
|
2727 if (magicInTag || !popTagName && elements.tagToContentPropMap[tagNames[tagNames.length - 1]]) { |
|
2728 // make sure / of /> is on the left of pending |
|
2729 if (emptyElement) { |
|
2730 put(content.substr(0, content.length - 1), ",can.view.pending(),\"/>\""); |
|
2731 } else { |
|
2732 put(content, ",can.view.pending(),\">\""); |
|
2733 } |
|
2734 content = ''; |
|
2735 magicInTag = 0; |
|
2736 } else { |
|
2737 content += token; |
|
2738 } |
|
2739 // if it's a tag like <input/> |
|
2740 if (emptyElement || popTagName) { |
|
2741 // remove the current tag in the stack |
|
2742 tagNames.pop(); |
|
2743 // set the current tag to the previous parent |
|
2744 tagName = tagNames[tagNames.length - 1]; |
|
2745 // Don't pop next time |
|
2746 popTagName = false; |
|
2747 } |
|
2748 break; |
|
2749 case "'": |
|
2750 case '"': |
|
2751 // If we are in an html tag, finding matching quotes. |
|
2752 if (htmlTag) { |
|
2753 // We have a quote and it matches. |
|
2754 if (quote && quote === token) { |
|
2755 // We are exiting the quote. |
|
2756 quote = null; |
|
2757 // Otherwise we are creating a quote. |
|
2758 // TODO: does this handle `\`? |
|
2759 } else if (quote === null) { |
|
2760 quote = token; |
|
2761 beforeQuote = lastToken; |
|
2762 } |
|
2763 } |
|
2764 default: |
|
2765 // Track the current tag |
|
2766 if (lastToken === '<') { |
|
2767 tagName = token.split(/\s/)[0]; |
|
2768 if (tagName.indexOf("/") === 0 && tagNames[tagNames.length - 1] === tagName.substr(1)) { |
|
2769 // set tagName to the last tagName |
|
2770 // if there are no more tagNames, we'll rely on getTag. |
|
2771 tagName = tagNames[tagNames.length - 1]; |
|
2772 popTagName = true; |
|
2773 } else { |
|
2774 tagNames.push(tagName); |
|
2775 } |
|
2776 } |
|
2777 content += token; |
|
2778 break; |
|
2779 } |
|
2780 } else { |
|
2781 // We have a start tag. |
|
2782 switch (token) { |
|
2783 case tmap.right: |
|
2784 case tmap.returnRight: |
|
2785 switch (startTag) { |
|
2786 case tmap.left: |
|
2787 // Get the number of `{ minus }` |
|
2788 bracketCount = bracketNum(content); |
|
2789 |
|
2790 // We are ending a block. |
|
2791 if (bracketCount == 1) { |
|
2792 |
|
2793 // We are starting on. |
|
2794 buff.push(insert_cmd, "can.view.txt(0,'" + getTag(tagName, tokens, i) + "'," + status() + ",this,function(){", startTxt, content); |
|
2795 |
|
2796 endStack.push({ |
|
2797 before: "", |
|
2798 after: finishTxt + "}));\n" |
|
2799 }); |
|
2800 } else { |
|
2801 |
|
2802 // How are we ending this statement? |
|
2803 last = // If the stack has value and we are ending a block... |
|
2804 endStack.length && bracketCount == -1 ? // Use the last item in the block stack. |
|
2805 endStack.pop() : // Or use the default ending. |
|
2806 { |
|
2807 after: ";" |
|
2808 }; |
|
2809 |
|
2810 // If we are ending a returning block, |
|
2811 // add the finish text which returns the result of the |
|
2812 // block. |
|
2813 if (last.before) { |
|
2814 buff.push(last.before); |
|
2815 } |
|
2816 // Add the remaining content. |
|
2817 buff.push(content, ";", last.after); |
|
2818 } |
|
2819 break; |
|
2820 case tmap.escapeLeft: |
|
2821 case tmap.returnLeft: |
|
2822 // We have an extra `{` -> `block`. |
|
2823 // Get the number of `{ minus }`. |
|
2824 bracketCount = bracketNum(content); |
|
2825 // If we have more `{`, it means there is a block. |
|
2826 if (bracketCount) { |
|
2827 // When we return to the same # of `{` vs `}` end with a `doubleParent`. |
|
2828 endStack.push({ |
|
2829 before: finishTxt, |
|
2830 after: "}));" |
|
2831 }); |
|
2832 } |
|
2833 |
|
2834 var escaped = startTag === tmap.escapeLeft ? 1 : 0, |
|
2835 commands = { |
|
2836 insert: insert_cmd, |
|
2837 tagName: getTag(tagName, tokens, i), |
|
2838 status: status() |
|
2839 }; |
|
2840 |
|
2841 for (var ii = 0; ii < this.helpers.length; ii++) { |
|
2842 // Match the helper based on helper |
|
2843 // regex name value |
|
2844 var helper = this.helpers[ii]; |
|
2845 if (helper.name.test(content)) { |
|
2846 content = helper.fn(content, commands); |
|
2847 |
|
2848 // dont escape partials |
|
2849 if (helper.name.source == /^>[\s]*\w*/.source) { |
|
2850 escaped = 0; |
|
2851 } |
|
2852 break; |
|
2853 } |
|
2854 } |
|
2855 |
|
2856 // Handle special cases |
|
2857 if (typeof content == 'object') { |
|
2858 if (content.raw) { |
|
2859 buff.push(content.raw); |
|
2860 } |
|
2861 } else { |
|
2862 // If we have `<%== a(function(){ %>` then we want |
|
2863 // `can.EJS.text(0,this, function(){ return a(function(){ var _v1ew = [];`. |
|
2864 buff.push(insert_cmd, "can.view.txt(" + escaped + ",'" + tagName + "'," + status() + ",this,function(){ " + (this.text.escape || '') + "return ", content, |
|
2865 // If we have a block. |
|
2866 bracketCount ? |
|
2867 // Start with startTxt `"var _v1ew = [];"`. |
|
2868 startTxt : |
|
2869 // If not, add `doubleParent` to close push and text. |
|
2870 "}));"); |
|
2871 } |
|
2872 |
|
2873 if (rescan && rescan.after && rescan.after.length) { |
|
2874 put(rescan.after.length); |
|
2875 rescan = null; |
|
2876 } |
|
2877 break; |
|
2878 } |
|
2879 startTag = null; |
|
2880 content = ''; |
|
2881 break; |
|
2882 case tmap.templateLeft: |
|
2883 content += tmap.left; |
|
2884 break; |
|
2885 default: |
|
2886 content += token; |
|
2887 break; |
|
2888 } |
|
2889 } |
|
2890 lastToken = token; |
|
2891 } |
|
2892 |
|
2893 // Put it together... |
|
2894 if (content.length) { |
|
2895 // Should be `content.dump` in Ruby. |
|
2896 put(content); |
|
2897 } |
|
2898 buff.push(";"); |
|
2899 |
|
2900 var template = buff.join(''), |
|
2901 out = { |
|
2902 out: 'with(_VIEW) { with (_CONTEXT) {' + template + " " + finishTxt + "}}" |
|
2903 }; |
|
2904 // Use `eval` instead of creating a function, because it is easier to debug. |
|
2905 myEval.call(out, 'this.fn = (function(_CONTEXT,_VIEW){' + out.out + '});\r\n//@ sourceURL=' + name + ".js"); |
|
2906 |
|
2907 return out; |
|
2908 } |
|
2909 }; |
|
2910 |
|
2911 return Scanner; |
|
2912 })(__m11, __m14); |
|
2913 |
|
2914 // ## can/view/node_lists.js |
|
2915 var __m17 = (function(can) { |
|
2916 |
|
2917 // text node expando test |
|
2918 var canExpando = true; |
|
2919 try { |
|
2920 document.createTextNode('')._ = 0; |
|
2921 } catch (ex) { |
|
2922 canExpando = false; |
|
2923 } |
|
2924 |
|
2925 // a mapping of element ids to nodeList ids |
|
2926 var nodeMap = {}, |
|
2927 // a mapping of ids to text nodes |
|
2928 textNodeMap = {}, |
|
2929 // a mapping of nodeList ids to nodeList |
|
2930 nodeListMap = {}, |
|
2931 expando = "ejs_" + Math.random(), |
|
2932 _id = 0, |
|
2933 id = function(node) { |
|
2934 if (canExpando || node.nodeType !== 3) { |
|
2935 if (node[expando]) { |
|
2936 return node[expando]; |
|
2937 } else { |
|
2938 return node[expando] = (node.nodeName ? "element_" : "obj_") + (++_id); |
|
2939 } |
|
2940 } else { |
|
2941 for (var textNodeID in textNodeMap) { |
|
2942 if (textNodeMap[textNodeID] === node) { |
|
2943 return textNodeID; |
|
2944 } |
|
2945 } |
|
2946 |
|
2947 textNodeMap["text_" + (++_id)] = node; |
|
2948 return "text_" + _id; |
|
2949 } |
|
2950 }, |
|
2951 // removes a nodeListId from a node's nodeListIds |
|
2952 removeNodeListId = function(node, nodeListId) { |
|
2953 var nodeListIds = nodeMap[id(node)]; |
|
2954 if (nodeListIds) { |
|
2955 var index = can.inArray(nodeListId, nodeListIds); |
|
2956 |
|
2957 if (index >= 0) { |
|
2958 nodeListIds.splice(index, 1); |
|
2959 } |
|
2960 if (!nodeListIds.length) { |
|
2961 delete nodeMap[id(node)]; |
|
2962 } |
|
2963 } |
|
2964 }, |
|
2965 addNodeListId = function(node, nodeListId) { |
|
2966 var nodeListIds = nodeMap[id(node)]; |
|
2967 if (!nodeListIds) { |
|
2968 nodeListIds = nodeMap[id(node)] = []; |
|
2969 } |
|
2970 nodeListIds.push(nodeListId); |
|
2971 }; |
|
2972 |
|
2973 var nodeLists = { |
|
2974 id: id, |
|
2975 // replaces the contents of one node list with the nodes in another list |
|
2976 replace: function(oldNodeList, newNodes) { |
|
2977 // for each node in the node list |
|
2978 oldNodeList = can.makeArray(oldNodeList); |
|
2979 |
|
2980 // try every set |
|
2981 //can.each( oldNodeList, function(node){ |
|
2982 var node = oldNodeList[0] |
|
2983 // for each nodeList the node is in |
|
2984 can.each(can.makeArray(nodeMap[id(node)]), function(nodeListId) { |
|
2985 |
|
2986 // if startNode to endNode is |
|
2987 // within list, replace that list |
|
2988 // I think the problem is not the WHOLE part is being |
|
2989 // matched |
|
2990 var nodeList = nodeListMap[nodeListId], |
|
2991 startIndex = can.inArray(node, nodeList), |
|
2992 endIndex = can.inArray(oldNodeList[oldNodeList.length - 1], nodeList); |
|
2993 |
|
2994 |
|
2995 // remove this nodeListId from each node |
|
2996 if (startIndex >= 0 && endIndex >= 0) { |
|
2997 for (var i = startIndex; i <= endIndex; i++) { |
|
2998 var n = nodeList[i]; |
|
2999 removeNodeListId(n, nodeListId); |
|
3000 } |
|
3001 // swap in new nodes into the nodeLIst |
|
3002 nodeList.splice.apply(nodeList, [startIndex, endIndex - startIndex + 1].concat(newNodes)); |
|
3003 |
|
3004 // tell these new nodes they belong to the nodeList |
|
3005 can.each(newNodes, function(node) { |
|
3006 addNodeListId(node, nodeListId); |
|
3007 }); |
|
3008 } else { |
|
3009 nodeLists.unregister(nodeList); |
|
3010 } |
|
3011 }); |
|
3012 //}); |
|
3013 }, |
|
3014 // registers a list of nodes |
|
3015 register: function(nodeList) { |
|
3016 var nLId = id(nodeList); |
|
3017 nodeListMap[nLId] = nodeList; |
|
3018 |
|
3019 can.each(nodeList, function(node) { |
|
3020 addNodeListId(node, nLId); |
|
3021 }); |
|
3022 |
|
3023 }, |
|
3024 // removes mappings |
|
3025 unregister: function(nodeList) { |
|
3026 var nLId = id(nodeList); |
|
3027 can.each(nodeList, function(node) { |
|
3028 removeNodeListId(node, nLId); |
|
3029 }); |
|
3030 delete nodeListMap[nLId]; |
|
3031 }, |
|
3032 nodeMap: nodeMap, |
|
3033 nodeListMap: nodeListMap |
|
3034 } |
|
3035 var ids = function(nodeList) { |
|
3036 return nodeList.map(function(n) { |
|
3037 return id(n) + ":" + (n.innerHTML || n.nodeValue) |
|
3038 }) |
|
3039 } |
|
3040 return nodeLists; |
|
3041 |
|
3042 })(__m3); |
|
3043 |
|
3044 // ## can/view/live.js |
|
3045 var __m16 = (function(can, elements, view, nodeLists) { |
|
3046 // ## live.js |
|
3047 // The live module provides live binding for computes |
|
3048 // and can.Observe.List. |
|
3049 // Currently, it's API is designed for `can/view/render`, but |
|
3050 // it could easily be used for other purposes. |
|
3051 |
|
3052 // ### Helper methods |
|
3053 // #### setup |
|
3054 // `setup(HTMLElement, bind(data), unbind(data)) -> data` |
|
3055 // Calls bind right away, but will call unbind |
|
3056 // if the element is "destroyed" (removed from the DOM). |
|
3057 var setup = function(el, bind, unbind) { |
|
3058 var teardown = function() { |
|
3059 unbind(data) |
|
3060 can.unbind.call(el, 'destroyed', teardown); |
|
3061 }, |
|
3062 data = { |
|
3063 teardownCheck: function(parent) { |
|
3064 if (!parent) { |
|
3065 teardown(); |
|
3066 } |
|
3067 } |
|
3068 } |
|
3069 |
|
3070 can.bind.call(el, 'destroyed', teardown); |
|
3071 bind(data) |
|
3072 return data; |
|
3073 }, |
|
3074 // #### listen |
|
3075 // Calls setup, but presets bind and unbind to |
|
3076 // operate on a compute |
|
3077 listen = function(el, compute, change) { |
|
3078 return setup(el, function() { |
|
3079 compute.bind("change", change); |
|
3080 }, function(data) { |
|
3081 compute.unbind("change", change); |
|
3082 if (data.nodeList) { |
|
3083 nodeLists.unregister(data.nodeList); |
|
3084 } |
|
3085 }); |
|
3086 }, |
|
3087 // #### getAttributeParts |
|
3088 // Breaks up a string like foo='bar' into ["foo","'bar'""] |
|
3089 getAttributeParts = function(newVal) { |
|
3090 return (newVal || "").replace(/['"]/g, '').split('=') |
|
3091 } |
|
3092 // #### insertElementsAfter |
|
3093 // Appends elements after the last item in oldElements. |
|
3094 insertElementsAfter = function(oldElements, newFrag) { |
|
3095 var last = oldElements[oldElements.length - 1]; |
|
3096 |
|
3097 // Insert it in the `document` or `documentFragment` |
|
3098 if (last.nextSibling) { |
|
3099 last.parentNode.insertBefore(newFrag, last.nextSibling); |
|
3100 } else { |
|
3101 last.parentNode.appendChild(newFrag); |
|
3102 } |
|
3103 }; |
|
3104 |
|
3105 var live = { |
|
3106 nodeLists: nodeLists, |
|
3107 list: function(el, list, func, context, parentNode) { |
|
3108 // A mapping of the index to an array |
|
3109 // of elements that represent the item. |
|
3110 // Each array is registered so child or parent |
|
3111 // live structures can update the elements |
|
3112 var nodesMap = [], |
|
3113 |
|
3114 add = function(ev, items, index) { |
|
3115 |
|
3116 // Collect new html and mappings |
|
3117 var frag = document.createDocumentFragment(), |
|
3118 newMappings = []; |
|
3119 can.each(items, function(item) { |
|
3120 var itemHTML = func.call(context, item), |
|
3121 itemFrag = can.view.frag(itemHTML, parentNode); |
|
3122 |
|
3123 newMappings.push(can.makeArray(itemFrag.childNodes)); |
|
3124 frag.appendChild(itemFrag); |
|
3125 }) |
|
3126 |
|
3127 // Inserting at the end of the list |
|
3128 if (!nodesMap[index]) { |
|
3129 insertElementsAfter( |
|
3130 index == 0 ? [text] : |
|
3131 nodesMap[index - 1], frag) |
|
3132 } else { |
|
3133 var el = nodesMap[index][0]; |
|
3134 el.parentNode.insertBefore(frag, el) |
|
3135 } |
|
3136 // register each item |
|
3137 can.each(newMappings, function(nodeList) { |
|
3138 nodeLists.register(nodeList) |
|
3139 }); |
|
3140 [].splice.apply(nodesMap, [index, 0].concat(newMappings)); |
|
3141 }, |
|
3142 remove = function(ev, items, index) { |
|
3143 var removedMappings = nodesMap.splice(index, items.length), |
|
3144 itemsToRemove = []; |
|
3145 |
|
3146 can.each(removedMappings, function(nodeList) { |
|
3147 // add items that we will remove all at once |
|
3148 [].push.apply(itemsToRemove, nodeList) |
|
3149 // Update any parent lists to remove these items |
|
3150 nodeLists.replace(nodeList, []); |
|
3151 // unregister the list |
|
3152 nodeLists.unregister(nodeList); |
|
3153 |
|
3154 }); |
|
3155 can.remove(can.$(itemsToRemove)); |
|
3156 }, |
|
3157 parentNode = elements.getParentNode(el, parentNode), |
|
3158 text = document.createTextNode(""); |
|
3159 |
|
3160 // Setup binding and teardown to add and remove events |
|
3161 setup(parentNode, function() { |
|
3162 list.bind("add", add).bind("remove", remove) |
|
3163 }, function() { |
|
3164 list.unbind("add", add).unbind("remove", remove); |
|
3165 can.each(nodesMap, function(nodeList) { |
|
3166 nodeLists.unregister(nodeList); |
|
3167 }) |
|
3168 }) |
|
3169 |
|
3170 insertElementsAfter([el], text); |
|
3171 can.remove(can.$(el)); |
|
3172 add({}, list, 0); |
|
3173 |
|
3174 }, |
|
3175 html: function(el, compute, parentNode) { |
|
3176 var parentNode = elements.getParentNode(el, parentNode), |
|
3177 |
|
3178 data = listen(parentNode, compute, function(ev, newVal, oldVal) { |
|
3179 var attached = nodes[0].parentNode; |
|
3180 // update the nodes in the DOM with the new rendered value |
|
3181 if (attached) { |
|
3182 makeAndPut(newVal); |
|
3183 } |
|
3184 data.teardownCheck(nodes[0].parentNode); |
|
3185 }); |
|
3186 |
|
3187 var nodes, |
|
3188 makeAndPut = function(val) { |
|
3189 // create the fragment, but don't hook it up |
|
3190 // we need to insert it into the document first |
|
3191 var frag = can.view.frag(val, parentNode), |
|
3192 // keep a reference to each node |
|
3193 newNodes = can.makeArray(frag.childNodes); |
|
3194 // Insert it in the `document` or `documentFragment` |
|
3195 insertElementsAfter(nodes || [el], frag) |
|
3196 // nodes hasn't been set yet |
|
3197 if (!nodes) { |
|
3198 can.remove(can.$(el)); |
|
3199 nodes = newNodes; |
|
3200 // set the teardown nodeList |
|
3201 data.nodeList = nodes; |
|
3202 nodeLists.register(nodes); |
|
3203 } else { |
|
3204 // Update node Array's to point to new nodes |
|
3205 // and then remove the old nodes. |
|
3206 // It has to be in this order for Mootools |
|
3207 // and IE because somehow, after an element |
|
3208 // is removed from the DOM, it loses its |
|
3209 // expando values. |
|
3210 var nodesToRemove = can.makeArray(nodes); |
|
3211 nodeLists.replace(nodes, newNodes); |
|
3212 can.remove(can.$(nodesToRemove)); |
|
3213 } |
|
3214 }; |
|
3215 makeAndPut(compute(), [el]); |
|
3216 |
|
3217 }, |
|
3218 text: function(el, compute, parentNode) { |
|
3219 var parent = elements.getParentNode(el, parentNode); |
|
3220 |
|
3221 // setup listening right away so we don't have to re-calculate value |
|
3222 var data = listen(el.parentNode !== parent ? el.parentNode : parent, compute, function(ev, newVal, oldVal) { |
|
3223 // Sometimes this is 'unknown' in IE and will throw an exception if it is |
|
3224 if (typeof node.nodeValue != 'unknown') { |
|
3225 node.nodeValue = "" + newVal; |
|
3226 } |
|
3227 data.teardownCheck(node.parentNode); |
|
3228 }); |
|
3229 |
|
3230 var node = document.createTextNode(compute()); |
|
3231 |
|
3232 if (el.parentNode !== parent) { |
|
3233 parent = el.parentNode; |
|
3234 parent.insertBefore(node, el); |
|
3235 parent.removeChild(el); |
|
3236 } else { |
|
3237 parent.insertBefore(node, el); |
|
3238 parent.removeChild(el); |
|
3239 } |
|
3240 }, |
|
3241 attributes: function(el, compute, currentValue) { |
|
3242 var setAttrs = function(newVal) { |
|
3243 var parts = getAttributeParts(newVal), |
|
3244 newAttrName = parts.shift(); |
|
3245 |
|
3246 // Remove if we have a change and used to have an `attrName`. |
|
3247 if ((newAttrName != attrName) && attrName) { |
|
3248 elements.removeAttr(el, attrName); |
|
3249 } |
|
3250 // Set if we have a new `attrName`. |
|
3251 if (newAttrName) { |
|
3252 elements.setAttr(el, newAttrName, parts.join('=')); |
|
3253 attrName = newAttrName; |
|
3254 } |
|
3255 } |
|
3256 |
|
3257 listen(el, compute, function(ev, newVal) { |
|
3258 setAttrs(newVal) |
|
3259 }) |
|
3260 // current value has been set |
|
3261 if (arguments.length >= 3) { |
|
3262 var attrName = getAttributeParts(currentValue)[0] |
|
3263 } else { |
|
3264 setAttrs(compute()) |
|
3265 } |
|
3266 }, |
|
3267 attributePlaceholder: '__!!__', |
|
3268 attributeReplace: /__!!__/g, |
|
3269 attribute: function(el, attributeName, compute) { |
|
3270 listen(el, compute, function(ev, newVal) { |
|
3271 elements.setAttr(el, attributeName, hook.render()); |
|
3272 }) |
|
3273 |
|
3274 var wrapped = can.$(el), |
|
3275 hooks; |
|
3276 |
|
3277 // Get the list of hookups or create one for this element. |
|
3278 // Hooks is a map of attribute names to hookup `data`s. |
|
3279 // Each hookup data has: |
|
3280 // `render` - A `function` to render the value of the attribute. |
|
3281 // `funcs` - A list of hookup `function`s on that attribute. |
|
3282 // `batchNum` - The last event `batchNum`, used for performance. |
|
3283 hooks = can.data(wrapped, 'hooks'); |
|
3284 if (!hooks) { |
|
3285 can.data(wrapped, 'hooks', hooks = {}); |
|
3286 } |
|
3287 |
|
3288 // Get the attribute value. |
|
3289 var attr = elements.getAttr(el, attributeName), |
|
3290 // Split the attribute value by the template. |
|
3291 // Only split out the first __!!__ so if we have multiple hookups in the same attribute, |
|
3292 // they will be put in the right spot on first render |
|
3293 parts = attr.split(live.attributePlaceholder), |
|
3294 goodParts = [], |
|
3295 hook; |
|
3296 goodParts.push(parts.shift(), |
|
3297 parts.join(live.attributePlaceholder)); |
|
3298 |
|
3299 // If we already had a hookup for this attribute... |
|
3300 if (hooks[attributeName]) { |
|
3301 // Just add to that attribute's list of `function`s. |
|
3302 hooks[attributeName].computes.push(compute); |
|
3303 } else { |
|
3304 // Create the hookup data. |
|
3305 hooks[attributeName] = { |
|
3306 render: function() { |
|
3307 var i = 0, |
|
3308 // attr doesn't have a value in IE |
|
3309 newAttr = attr ? attr.replace(live.attributeReplace, function() { |
|
3310 return elements.contentText(hook.computes[i++]()); |
|
3311 }) : elements.contentText(hook.computes[i++]()); |
|
3312 return newAttr; |
|
3313 }, |
|
3314 computes: [compute], |
|
3315 batchNum: undefined |
|
3316 }; |
|
3317 } |
|
3318 |
|
3319 // Save the hook for slightly faster performance. |
|
3320 hook = hooks[attributeName]; |
|
3321 |
|
3322 // Insert the value in parts. |
|
3323 goodParts.splice(1, 0, compute()); |
|
3324 |
|
3325 // Set the attribute. |
|
3326 elements.setAttr(el, attributeName, goodParts.join("")); |
|
3327 |
|
3328 } |
|
3329 } |
|
3330 return live; |
|
3331 |
|
3332 })(__m3, __m14, __m11, __m17); |
|
3333 |
|
3334 // ## can/view/render.js |
|
3335 var __m15 = (function(can, elements, live) { |
|
3336 |
|
3337 var pendingHookups = [], |
|
3338 tagChildren = function(tagName) { |
|
3339 var newTag = elements.tagMap[tagName] || "span"; |
|
3340 if (newTag === "span") { |
|
3341 //innerHTML in IE doesn't honor leading whitespace after empty elements |
|
3342 return "@@!!@@"; |
|
3343 } |
|
3344 return "<" + newTag + ">" + tagChildren(newTag) + "</" + newTag + ">"; |
|
3345 }, |
|
3346 contentText = function(input, tag) { |
|
3347 |
|
3348 // If it's a string, return. |
|
3349 if (typeof input == 'string') { |
|
3350 return input; |
|
3351 } |
|
3352 // If has no value, return an empty string. |
|
3353 if (!input && input !== 0) { |
|
3354 return ''; |
|
3355 } |
|
3356 |
|
3357 // If it's an object, and it has a hookup method. |
|
3358 var hook = (input.hookup && |
|
3359 |
|
3360 // Make a function call the hookup method. |
|
3361 |
|
3362 function(el, id) { |
|
3363 input.hookup.call(input, el, id); |
|
3364 }) || |
|
3365 |
|
3366 // Or if it's a `function`, just use the input. |
|
3367 (typeof input == 'function' && input); |
|
3368 |
|
3369 // Finally, if there is a `function` to hookup on some dom, |
|
3370 // add it to pending hookups. |
|
3371 if (hook) { |
|
3372 if (tag) { |
|
3373 return "<" + tag + " " + can.view.hook(hook) + "></" + tag + ">" |
|
3374 } else { |
|
3375 pendingHookups.push(hook); |
|
3376 } |
|
3377 |
|
3378 return ''; |
|
3379 } |
|
3380 |
|
3381 // Finally, if all else is `false`, `toString()` it. |
|
3382 return "" + input; |
|
3383 }, |
|
3384 // Returns escaped/sanatized content for anything other than a live-binding |
|
3385 contentEscape = function(txt) { |
|
3386 return (typeof txt == 'string' || typeof txt == 'number') ? |
|
3387 can.esc(txt) : |
|
3388 contentText(txt); |
|
3389 }; |
|
3390 |
|
3391 var current; |
|
3392 |
|
3393 can.extend(can.view, { |
|
3394 live: live, |
|
3395 setupLists: function() { |
|
3396 |
|
3397 var old = can.view.lists, |
|
3398 data; |
|
3399 |
|
3400 can.view.lists = function(list, renderer) { |
|
3401 data = { |
|
3402 list: list, |
|
3403 renderer: renderer |
|
3404 } |
|
3405 } |
|
3406 return function() { |
|
3407 can.view.lists = old; |
|
3408 return data; |
|
3409 } |
|
3410 }, |
|
3411 pending: function() { |
|
3412 // TODO, make this only run for the right tagName |
|
3413 var hooks = pendingHookups.slice(0); |
|
3414 lastHookups = hooks; |
|
3415 pendingHookups = []; |
|
3416 return can.view.hook(function(el) { |
|
3417 can.each(hooks, function(fn) { |
|
3418 fn(el); |
|
3419 }); |
|
3420 }); |
|
3421 }, |
|
3422 |
|
3423 |
|
3424 txt: function(escape, tagName, status, self, func) { |
|
3425 var listTeardown = can.view.setupLists(), |
|
3426 emptyHandler = function() {}, |
|
3427 unbind = function() { |
|
3428 compute.unbind("change", emptyHandler) |
|
3429 }; |
|
3430 |
|
3431 var compute = can.compute(func, self, false); |
|
3432 // bind to get and temporarily cache the value |
|
3433 compute.bind("change", emptyHandler); |
|
3434 // call the "wrapping" function and get the binding information |
|
3435 var tag = (elements.tagMap[tagName] || "span"), |
|
3436 listData = listTeardown(), |
|
3437 value = compute(); |
|
3438 |
|
3439 |
|
3440 if (listData) { |
|
3441 return "<" + tag + can.view.hook(function(el, parentNode) { |
|
3442 live.list(el, listData.list, listData.renderer, self, parentNode); |
|
3443 }) + "></" + tag + ">"; |
|
3444 } |
|
3445 |
|
3446 // If we had no observes just return the value returned by func. |
|
3447 if (!compute.hasDependencies) { |
|
3448 unbind(); |
|
3449 return (escape || status !== 0 ? contentEscape : contentText)(value, status === 0 && tag); |
|
3450 } |
|
3451 |
|
3452 // the property (instead of innerHTML elements) to adjust. For |
|
3453 // example options should use textContent |
|
3454 var contentProp = elements.tagToContentPropMap[tagName]; |
|
3455 |
|
3456 |
|
3457 // The magic tag is outside or between tags. |
|
3458 if (status === 0 && !contentProp) { |
|
3459 // Return an element tag with a hookup in place of the content |
|
3460 return "<" + tag + can.view.hook( |
|
3461 escape ? |
|
3462 // If we are escaping, replace the parentNode with |
|
3463 // a text node who's value is `func`'s return value. |
|
3464 |
|
3465 function(el, parentNode) { |
|
3466 live.text(el, compute, parentNode); |
|
3467 unbind(); |
|
3468 } : |
|
3469 // If we are not escaping, replace the parentNode with a |
|
3470 // documentFragment created as with `func`'s return value. |
|
3471 |
|
3472 function(el, parentNode) { |
|
3473 live.html(el, compute, parentNode); |
|
3474 unbind(); |
|
3475 //children have to be properly nested HTML for buildFragment to work properly |
|
3476 }) + ">" + tagChildren(tag) + "</" + tag + ">"; |
|
3477 // In a tag, but not in an attribute |
|
3478 } else if (status === 1) { |
|
3479 // remember the old attr name |
|
3480 pendingHookups.push(function(el) { |
|
3481 live.attributes(el, compute, compute()); |
|
3482 unbind(); |
|
3483 }); |
|
3484 return compute(); |
|
3485 } else { // In an attribute... |
|
3486 var attributeName = status === 0 ? contentProp : status; |
|
3487 // if the magic tag is inside the element, like `<option><% TAG %></option>`, |
|
3488 // we add this hookup to the last element (ex: `option`'s) hookups. |
|
3489 // Otherwise, the magic tag is in an attribute, just add to the current element's |
|
3490 // hookups. |
|
3491 (status === 0 ? lastHookups : pendingHookups).push(function(el) { |
|
3492 live.attribute(el, attributeName, compute); |
|
3493 unbind(); |
|
3494 }); |
|
3495 return live.attributePlaceholder; |
|
3496 } |
|
3497 } |
|
3498 }); |
|
3499 |
|
3500 return can; |
|
3501 })(__m11, __m14, __m16, __m2); |
|
3502 |
|
3503 // ## can/view/ejs/ejs.js |
|
3504 var __m12 = (function(can) { |
|
3505 // ## ejs.js |
|
3506 // `can.EJS` |
|
3507 // _Embedded JavaScript Templates._ |
|
3508 |
|
3509 // Helper methods. |
|
3510 var extend = can.extend, |
|
3511 EJS = function(options) { |
|
3512 // Supports calling EJS without the constructor |
|
3513 // This returns a function that renders the template. |
|
3514 if (this.constructor != EJS) { |
|
3515 var ejs = new EJS(options); |
|
3516 return function(data, helpers) { |
|
3517 return ejs.render(data, helpers); |
|
3518 }; |
|
3519 } |
|
3520 // If we get a `function` directly, it probably is coming from |
|
3521 // a `steal`-packaged view. |
|
3522 if (typeof options == "function") { |
|
3523 this.template = { |
|
3524 fn: options |
|
3525 }; |
|
3526 return; |
|
3527 } |
|
3528 // Set options on self. |
|
3529 extend(this, options); |
|
3530 this.template = this.scanner.scan(this.text, this.name); |
|
3531 }; |
|
3532 |
|
3533 can.EJS = EJS; |
|
3534 |
|
3535 |
|
3536 EJS.prototype. |
|
3537 |
|
3538 render = function(object, extraHelpers) { |
|
3539 object = object || {}; |
|
3540 return this.template.fn.call(object, object, new EJS.Helpers(object, extraHelpers || {})); |
|
3541 }; |
|
3542 |
|
3543 extend(EJS.prototype, { |
|
3544 |
|
3545 scanner: new can.view.Scanner({ |
|
3546 |
|
3547 tokens: [ |
|
3548 ["templateLeft", "<%%"], // Template |
|
3549 ["templateRight", "%>"], // Right Template |
|
3550 ["returnLeft", "<%=="], // Return Unescaped |
|
3551 ["escapeLeft", "<%="], // Return Escaped |
|
3552 ["commentLeft", "<%#"], // Comment |
|
3553 ["left", "<%"], // Run --- this is hack for now |
|
3554 ["right", "%>"], // Right -> All have same FOR Mustache ... |
|
3555 ["returnRight", "%>"] |
|
3556 ], |
|
3557 |
|
3558 |
|
3559 transform: function(source) { |
|
3560 return source.replace(/<%([\s\S]+?)%>/gm, function(whole, part) { |
|
3561 var brackets = [], |
|
3562 foundBracketPair, |
|
3563 i; |
|
3564 |
|
3565 // Look for brackets (for removing self-contained blocks) |
|
3566 part.replace(/[{}]/gm, function(bracket, offset) { |
|
3567 brackets.push([bracket, offset]); |
|
3568 }); |
|
3569 |
|
3570 // Remove bracket pairs from the list of replacements |
|
3571 do { |
|
3572 foundBracketPair = false; |
|
3573 for (i = brackets.length - 2; i >= 0; i--) { |
|
3574 if (brackets[i][0] == '{' && brackets[i + 1][0] == '}') { |
|
3575 brackets.splice(i, 2); |
|
3576 foundBracketPair = true; |
|
3577 break; |
|
3578 } |
|
3579 } |
|
3580 } while (foundBracketPair); |
|
3581 |
|
3582 // Unmatched brackets found, inject EJS tags |
|
3583 if (brackets.length >= 2) { |
|
3584 var result = ['<%'], |
|
3585 bracket, |
|
3586 last = 0; |
|
3587 for (i = 0; bracket = brackets[i]; i++) { |
|
3588 result.push(part.substring(last, last = bracket[1])); |
|
3589 if ((bracket[0] == '{' && i < brackets.length - 1) || (bracket[0] == '}' && i > 0)) { |
|
3590 result.push(bracket[0] == '{' ? '{ %><% ' : ' %><% }'); |
|
3591 } else { |
|
3592 result.push(bracket[0]); |
|
3593 } |
|
3594 ++last; |
|
3595 } |
|
3596 result.push(part.substring(last), '%>'); |
|
3597 return result.join(''); |
|
3598 } |
|
3599 // Otherwise return the original |
|
3600 else { |
|
3601 return '<%' + part + '%>'; |
|
3602 } |
|
3603 }); |
|
3604 } |
|
3605 }) |
|
3606 }); |
|
3607 |
|
3608 EJS.Helpers = function(data, extras) { |
|
3609 this._data = data; |
|
3610 this._extras = extras; |
|
3611 extend(this, extras); |
|
3612 }; |
|
3613 |
|
3614 |
|
3615 EJS.Helpers.prototype = { |
|
3616 // TODO Deprecated!! |
|
3617 list: function(list, cb) { |
|
3618 |
|
3619 can.each(list, function(item, i) { |
|
3620 cb(item, i, list) |
|
3621 }) |
|
3622 }, |
|
3623 each: function(list, cb) { |
|
3624 // Normal arrays don't get live updated |
|
3625 if (can.isArray(list)) { |
|
3626 this.list(list, cb); |
|
3627 } else { |
|
3628 can.view.lists(list, cb); |
|
3629 } |
|
3630 } |
|
3631 }; |
|
3632 |
|
3633 // Options for `steal`'s build. |
|
3634 can.view.register({ |
|
3635 suffix: "ejs", |
|
3636 // returns a `function` that renders the view. |
|
3637 script: function(id, src) { |
|
3638 return "can.EJS(function(_CONTEXT,_VIEW) { " + new EJS({ |
|
3639 text: src, |
|
3640 name: id |
|
3641 }).template.out + " })"; |
|
3642 }, |
|
3643 renderer: function(id, text) { |
|
3644 return EJS({ |
|
3645 text: text, |
|
3646 name: id |
|
3647 }); |
|
3648 } |
|
3649 }); |
|
3650 |
|
3651 return can; |
|
3652 })(__m3, __m11, __m2, __m9, __m13, __m15); |
|
3653 |
|
3654 // ## can/control/control.js |
|
3655 var __m18 = (function(can) { |
|
3656 // ## control.js |
|
3657 // `can.Control` |
|
3658 // _Controller_ |
|
3659 |
|
3660 // Binds an element, returns a function that unbinds. |
|
3661 var bind = function(el, ev, callback) { |
|
3662 |
|
3663 can.bind.call(el, ev, callback); |
|
3664 |
|
3665 return function() { |
|
3666 can.unbind.call(el, ev, callback); |
|
3667 }; |
|
3668 }, |
|
3669 isFunction = can.isFunction, |
|
3670 extend = can.extend, |
|
3671 each = can.each, |
|
3672 slice = [].slice, |
|
3673 paramReplacer = /\{([^\}]+)\}/g, |
|
3674 special = can.getObject("$.event.special", [can]) || {}, |
|
3675 |
|
3676 // Binds an element, returns a function that unbinds. |
|
3677 delegate = function(el, selector, ev, callback) { |
|
3678 can.delegate.call(el, selector, ev, callback); |
|
3679 return function() { |
|
3680 can.undelegate.call(el, selector, ev, callback); |
|
3681 }; |
|
3682 }, |
|
3683 |
|
3684 // Calls bind or unbind depending if there is a selector. |
|
3685 binder = function(el, ev, callback, selector) { |
|
3686 return selector ? |
|
3687 delegate(el, can.trim(selector), ev, callback) : |
|
3688 bind(el, ev, callback); |
|
3689 }, |
|
3690 |
|
3691 basicProcessor; |
|
3692 |
|
3693 var Control = can.Control = can.Construct( |
|
3694 |
|
3695 { |
|
3696 // Setup pre-processes which methods are event listeners. |
|
3697 |
|
3698 setup: function() { |
|
3699 |
|
3700 // Allow contollers to inherit "defaults" from super-classes as it |
|
3701 // done in `can.Construct` |
|
3702 can.Construct.setup.apply(this, arguments); |
|
3703 |
|
3704 // If you didn't provide a name, or are `control`, don't do anything. |
|
3705 if (can.Control) { |
|
3706 |
|
3707 // Cache the underscored names. |
|
3708 var control = this, |
|
3709 funcName; |
|
3710 |
|
3711 // Calculate and cache actions. |
|
3712 control.actions = {}; |
|
3713 for (funcName in control.prototype) { |
|
3714 if (control._isAction(funcName)) { |
|
3715 control.actions[funcName] = control._action(funcName); |
|
3716 } |
|
3717 } |
|
3718 } |
|
3719 }, |
|
3720 |
|
3721 // Moves `this` to the first argument, wraps it with `jQuery` if it's an element |
|
3722 _shifter: function(context, name) { |
|
3723 |
|
3724 var method = typeof name == "string" ? context[name] : name; |
|
3725 |
|
3726 if (!isFunction(method)) { |
|
3727 method = context[method]; |
|
3728 } |
|
3729 |
|
3730 return function() { |
|
3731 context.called = name; |
|
3732 return method.apply(context, [this.nodeName ? can.$(this) : this].concat(slice.call(arguments, 0))); |
|
3733 }; |
|
3734 }, |
|
3735 |
|
3736 // Return `true` if is an action. |
|
3737 |
|
3738 _isAction: function(methodName) { |
|
3739 |
|
3740 var val = this.prototype[methodName], |
|
3741 type = typeof val; |
|
3742 // if not the constructor |
|
3743 return (methodName !== 'constructor') && |
|
3744 // and is a function or links to a function |
|
3745 (type == "function" || (type == "string" && isFunction(this.prototype[val]))) && |
|
3746 // and is in special, a processor, or has a funny character |
|
3747 !! (special[methodName] || processors[methodName] || /[^\w]/.test(methodName)); |
|
3748 }, |
|
3749 // Takes a method name and the options passed to a control |
|
3750 // and tries to return the data necessary to pass to a processor |
|
3751 // (something that binds things). |
|
3752 |
|
3753 _action: function(methodName, options) { |
|
3754 |
|
3755 // If we don't have options (a `control` instance), we'll run this |
|
3756 // later. |
|
3757 paramReplacer.lastIndex = 0; |
|
3758 if (options || !paramReplacer.test(methodName)) { |
|
3759 // If we have options, run sub to replace templates `{}` with a |
|
3760 // value from the options or the window |
|
3761 var convertedName = options ? can.sub(methodName, [options, window]) : methodName; |
|
3762 if (!convertedName) { |
|
3763 return null; |
|
3764 } |
|
3765 // If a `{}` template resolves to an object, `convertedName` will be |
|
3766 // an array |
|
3767 var arr = can.isArray(convertedName), |
|
3768 |
|
3769 // Get the name |
|
3770 name = arr ? convertedName[1] : convertedName, |
|
3771 |
|
3772 // Grab the event off the end |
|
3773 parts = name.split(/\s+/g), |
|
3774 event = parts.pop(); |
|
3775 |
|
3776 return { |
|
3777 processor: processors[event] || basicProcessor, |
|
3778 parts: [name, parts.join(" "), event], |
|
3779 delegate: arr ? convertedName[0] : undefined |
|
3780 }; |
|
3781 } |
|
3782 }, |
|
3783 // An object of `{eventName : function}` pairs that Control uses to |
|
3784 // hook up events auto-magically. |
|
3785 |
|
3786 processors: {}, |
|
3787 // A object of name-value pairs that act as default values for a |
|
3788 // control instance |
|
3789 defaults: {} |
|
3790 |
|
3791 }, { |
|
3792 |
|
3793 // Sets `this.element`, saves the control in `data, binds event |
|
3794 // handlers. |
|
3795 |
|
3796 setup: function(element, options) { |
|
3797 |
|
3798 var cls = this.constructor, |
|
3799 pluginname = cls.pluginName || cls._fullName, |
|
3800 arr; |
|
3801 |
|
3802 // Want the raw element here. |
|
3803 this.element = can.$(element) |
|
3804 |
|
3805 if (pluginname && pluginname !== 'can_control') { |
|
3806 // Set element and `className` on element. |
|
3807 this.element.addClass(pluginname); |
|
3808 } |
|
3809 |
|
3810 (arr = can.data(this.element, "controls")) || can.data(this.element, "controls", arr = []); |
|
3811 arr.push(this); |
|
3812 |
|
3813 // Option merging. |
|
3814 |
|
3815 this.options = extend({}, cls.defaults, options); |
|
3816 |
|
3817 // Bind all event handlers. |
|
3818 this.on(); |
|
3819 |
|
3820 // Gets passed into `init`. |
|
3821 |
|
3822 return [this.element, this.options]; |
|
3823 }, |
|
3824 |
|
3825 on: function(el, selector, eventName, func) { |
|
3826 if (!el) { |
|
3827 |
|
3828 // Adds bindings. |
|
3829 this.off(); |
|
3830 |
|
3831 // Go through the cached list of actions and use the processor |
|
3832 // to bind |
|
3833 var cls = this.constructor, |
|
3834 bindings = this._bindings, |
|
3835 actions = cls.actions, |
|
3836 element = this.element, |
|
3837 destroyCB = can.Control._shifter(this, "destroy"), |
|
3838 funcName, ready; |
|
3839 |
|
3840 for (funcName in actions) { |
|
3841 // Only push if we have the action and no option is `undefined` |
|
3842 if (actions.hasOwnProperty(funcName) && |
|
3843 (ready = actions[funcName] || cls._action(funcName, this.options))) { |
|
3844 bindings.push(ready.processor(ready.delegate || element, |
|
3845 ready.parts[2], ready.parts[1], funcName, this)); |
|
3846 } |
|
3847 } |
|
3848 |
|
3849 |
|
3850 // Setup to be destroyed... |
|
3851 // don't bind because we don't want to remove it. |
|
3852 can.bind.call(element, "destroyed", destroyCB); |
|
3853 bindings.push(function(el) { |
|
3854 can.unbind.call(el, "destroyed", destroyCB); |
|
3855 }); |
|
3856 return bindings.length; |
|
3857 } |
|
3858 |
|
3859 if (typeof el == 'string') { |
|
3860 func = eventName; |
|
3861 eventName = selector; |
|
3862 selector = el; |
|
3863 el = this.element; |
|
3864 } |
|
3865 |
|
3866 if (func === undefined) { |
|
3867 func = eventName; |
|
3868 eventName = selector; |
|
3869 selector = null; |
|
3870 } |
|
3871 |
|
3872 if (typeof func == 'string') { |
|
3873 func = can.Control._shifter(this, func); |
|
3874 } |
|
3875 |
|
3876 this._bindings.push(binder(el, eventName, func, selector)); |
|
3877 |
|
3878 return this._bindings.length; |
|
3879 }, |
|
3880 // Unbinds all event handlers on the controller. |
|
3881 |
|
3882 off: function() { |
|
3883 var el = this.element[0] |
|
3884 each(this._bindings || [], function(value) { |
|
3885 value(el); |
|
3886 }); |
|
3887 // Adds bindings. |
|
3888 this._bindings = []; |
|
3889 }, |
|
3890 // Prepares a `control` for garbage collection |
|
3891 |
|
3892 destroy: function() { |
|
3893 //Control already destroyed |
|
3894 if (this.element === null) { |
|
3895 |
|
3896 return; |
|
3897 } |
|
3898 var Class = this.constructor, |
|
3899 pluginName = Class.pluginName || Class._fullName, |
|
3900 controls; |
|
3901 |
|
3902 // Unbind bindings. |
|
3903 this.off(); |
|
3904 |
|
3905 if (pluginName && pluginName !== 'can_control') { |
|
3906 // Remove the `className`. |
|
3907 this.element.removeClass(pluginName); |
|
3908 } |
|
3909 |
|
3910 // Remove from `data`. |
|
3911 controls = can.data(this.element, "controls"); |
|
3912 controls.splice(can.inArray(this, controls), 1); |
|
3913 |
|
3914 can.trigger(this, "destroyed"); // In case we want to know if the `control` is removed. |
|
3915 |
|
3916 this.element = null; |
|
3917 } |
|
3918 }); |
|
3919 |
|
3920 var processors = can.Control.processors, |
|
3921 // Processors do the binding. |
|
3922 // They return a function that unbinds when called. |
|
3923 // The basic processor that binds events. |
|
3924 basicProcessor = function(el, event, selector, methodName, control) { |
|
3925 return binder(el, event, can.Control._shifter(control, methodName), selector); |
|
3926 }; |
|
3927 |
|
3928 // Set common events to be processed as a `basicProcessor` |
|
3929 each(["change", "click", "contextmenu", "dblclick", "keydown", "keyup", |
|
3930 "keypress", "mousedown", "mousemove", "mouseout", "mouseover", |
|
3931 "mouseup", "reset", "resize", "scroll", "select", "submit", "focusin", |
|
3932 "focusout", "mouseenter", "mouseleave", |
|
3933 // #104 - Add touch events as default processors |
|
3934 // TOOD feature detect? |
|
3935 "touchstart", "touchmove", "touchcancel", "touchend", "touchleave" |
|
3936 ], function(v) { |
|
3937 processors[v] = basicProcessor; |
|
3938 }); |
|
3939 |
|
3940 return Control; |
|
3941 })(__m3, __m1); |
|
3942 |
|
3943 // ## can/util/string/deparam/deparam.js |
|
3944 var __m20 = (function(can) { |
|
3945 |
|
3946 // ## deparam.js |
|
3947 // `can.deparam` |
|
3948 // _Takes a string of name value pairs and returns a Object literal that represents those params._ |
|
3949 var digitTest = /^\d+$/, |
|
3950 keyBreaker = /([^\[\]]+)|(\[\])/g, |
|
3951 paramTest = /([^?#]*)(#.*)?$/, |
|
3952 prep = function(str) { |
|
3953 return decodeURIComponent(str.replace(/\+/g, " ")); |
|
3954 }; |
|
3955 |
|
3956 |
|
3957 can.extend(can, { |
|
3958 |
|
3959 deparam: function(params) { |
|
3960 |
|
3961 var data = {}, |
|
3962 pairs, lastPart; |
|
3963 |
|
3964 if (params && paramTest.test(params)) { |
|
3965 |
|
3966 pairs = params.split('&'), |
|
3967 |
|
3968 can.each(pairs, function(pair) { |
|
3969 |
|
3970 var parts = pair.split('='), |
|
3971 key = prep(parts.shift()), |
|
3972 value = prep(parts.join("=")), |
|
3973 current = data; |
|
3974 |
|
3975 if (key) { |
|
3976 parts = key.match(keyBreaker); |
|
3977 |
|
3978 for (var j = 0, l = parts.length - 1; j < l; j++) { |
|
3979 if (!current[parts[j]]) { |
|
3980 // If what we are pointing to looks like an `array` |
|
3981 current[parts[j]] = digitTest.test(parts[j + 1]) || parts[j + 1] == "[]" ? [] : {}; |
|
3982 } |
|
3983 current = current[parts[j]]; |
|
3984 } |
|
3985 lastPart = parts.pop(); |
|
3986 if (lastPart == "[]") { |
|
3987 current.push(value); |
|
3988 } else { |
|
3989 current[lastPart] = value; |
|
3990 } |
|
3991 } |
|
3992 }); |
|
3993 } |
|
3994 return data; |
|
3995 } |
|
3996 }); |
|
3997 return can; |
|
3998 })(__m3, __m2); |
|
3999 |
|
4000 // ## can/route/route.js |
|
4001 var __m19 = (function(can) { |
|
4002 |
|
4003 // ## route.js |
|
4004 // `can.route` |
|
4005 // _Helps manage browser history (and client state) by synchronizing the |
|
4006 // `window.location.hash` with a `can.Observe`._ |
|
4007 // Helper methods used for matching routes. |
|
4008 var |
|
4009 // `RegExp` used to match route variables of the type ':name'. |
|
4010 // Any word character or a period is matched. |
|
4011 matcher = /\:([\w\.]+)/g, |
|
4012 // Regular expression for identifying &key=value lists. |
|
4013 paramsMatcher = /^(?:&[^=]+=[^&]*)+/, |
|
4014 // Converts a JS Object into a list of parameters that can be |
|
4015 // inserted into an html element tag. |
|
4016 makeProps = function(props) { |
|
4017 var tags = []; |
|
4018 can.each(props, function(val, name) { |
|
4019 tags.push((name === 'className' ? 'class' : name) + '="' + |
|
4020 (name === "href" ? val : can.esc(val)) + '"'); |
|
4021 }); |
|
4022 return tags.join(" "); |
|
4023 }, |
|
4024 // Checks if a route matches the data provided. If any route variable |
|
4025 // is not present in the data, the route does not match. If all route |
|
4026 // variables are present in the data, the number of matches is returned |
|
4027 // to allow discerning between general and more specific routes. |
|
4028 matchesData = function(route, data) { |
|
4029 var count = 0, |
|
4030 i = 0, |
|
4031 defaults = {}; |
|
4032 // look at default values, if they match ... |
|
4033 for (var name in route.defaults) { |
|
4034 if (route.defaults[name] === data[name]) { |
|
4035 // mark as matched |
|
4036 defaults[name] = 1; |
|
4037 count++; |
|
4038 } |
|
4039 } |
|
4040 for (; i < route.names.length; i++) { |
|
4041 if (!data.hasOwnProperty(route.names[i])) { |
|
4042 return -1; |
|
4043 } |
|
4044 if (!defaults[route.names[i]]) { |
|
4045 count++; |
|
4046 } |
|
4047 |
|
4048 } |
|
4049 |
|
4050 return count; |
|
4051 }, |
|
4052 onready = !0, |
|
4053 location = window.location, |
|
4054 wrapQuote = function(str) { |
|
4055 return (str + '').replace(/([.?*+\^$\[\]\\(){}|\-])/g, "\\$1"); |
|
4056 }, |
|
4057 each = can.each, |
|
4058 extend = can.extend; |
|
4059 |
|
4060 can.route = function(url, defaults) { |
|
4061 defaults = defaults || {}; |
|
4062 // Extract the variable names and replace with `RegExp` that will match |
|
4063 // an atual URL with values. |
|
4064 var names = [], |
|
4065 test = url.replace(matcher, function(whole, name, i) { |
|
4066 names.push(name); |
|
4067 var next = "\\" + (url.substr(i + whole.length, 1) || can.route._querySeparator); |
|
4068 // a name without a default value HAS to have a value |
|
4069 // a name that has a default value can be empty |
|
4070 // The `\\` is for string-escaping giving single `\` for `RegExp` escaping. |
|
4071 return "([^" + next + "]" + (defaults[name] ? "*" : "+") + ")"; |
|
4072 }); |
|
4073 |
|
4074 // Add route in a form that can be easily figured out. |
|
4075 can.route.routes[url] = { |
|
4076 // A regular expression that will match the route when variable values |
|
4077 // are present; i.e. for `:page/:type` the `RegExp` is `/([\w\.]*)/([\w\.]*)/` which |
|
4078 // will match for any value of `:page` and `:type` (word chars or period). |
|
4079 test: new RegExp("^" + test + "($|" + wrapQuote(can.route._querySeparator) + ")"), |
|
4080 // The original URL, same as the index for this entry in routes. |
|
4081 route: url, |
|
4082 // An `array` of all the variable names in this route. |
|
4083 names: names, |
|
4084 // Default values provided for the variables. |
|
4085 defaults: defaults, |
|
4086 // The number of parts in the URL separated by `/`. |
|
4087 length: url.split('/').length |
|
4088 }; |
|
4089 return can.route; |
|
4090 }; |
|
4091 |
|
4092 |
|
4093 extend(can.route, { |
|
4094 |
|
4095 _querySeparator: '&', |
|
4096 _paramsMatcher: paramsMatcher, |
|
4097 |
|
4098 |
|
4099 param: function(data, _setRoute) { |
|
4100 // Check if the provided data keys match the names in any routes; |
|
4101 // Get the one with the most matches. |
|
4102 var route, |
|
4103 // Need to have at least 1 match. |
|
4104 matches = 0, |
|
4105 matchCount, |
|
4106 routeName = data.route, |
|
4107 propCount = 0; |
|
4108 |
|
4109 delete data.route; |
|
4110 |
|
4111 each(data, function() { |
|
4112 propCount++; |
|
4113 }); |
|
4114 // Otherwise find route. |
|
4115 each(can.route.routes, function(temp, name) { |
|
4116 // best route is the first with all defaults matching |
|
4117 |
|
4118 |
|
4119 matchCount = matchesData(temp, data); |
|
4120 if (matchCount > matches) { |
|
4121 route = temp; |
|
4122 matches = matchCount; |
|
4123 } |
|
4124 if (matchCount >= propCount) { |
|
4125 return false; |
|
4126 } |
|
4127 }); |
|
4128 // If we have a route name in our `can.route` data, and it's |
|
4129 // just as good as what currently matches, use that |
|
4130 if (can.route.routes[routeName] && matchesData(can.route.routes[routeName], data) === matches) { |
|
4131 route = can.route.routes[routeName]; |
|
4132 } |
|
4133 // If this is match... |
|
4134 if (route) { |
|
4135 var cpy = extend({}, data), |
|
4136 // Create the url by replacing the var names with the provided data. |
|
4137 // If the default value is found an empty string is inserted. |
|
4138 res = route.route.replace(matcher, function(whole, name) { |
|
4139 delete cpy[name]; |
|
4140 return data[name] === route.defaults[name] ? "" : encodeURIComponent(data[name]); |
|
4141 }), |
|
4142 after; |
|
4143 // Remove matching default values |
|
4144 each(route.defaults, function(val, name) { |
|
4145 if (cpy[name] === val) { |
|
4146 delete cpy[name]; |
|
4147 } |
|
4148 }); |
|
4149 |
|
4150 // The remaining elements of data are added as |
|
4151 // `&` separated parameters to the url. |
|
4152 after = can.param(cpy); |
|
4153 // if we are paraming for setting the hash |
|
4154 // we also want to make sure the route value is updated |
|
4155 if (_setRoute) { |
|
4156 can.route.attr('route', route.route); |
|
4157 } |
|
4158 return res + (after ? can.route._querySeparator + after : ""); |
|
4159 } |
|
4160 // If no route was found, there is no hash URL, only paramters. |
|
4161 return can.isEmptyObject(data) ? "" : can.route._querySeparator + can.param(data); |
|
4162 }, |
|
4163 |
|
4164 deparam: function(url) { |
|
4165 // See if the url matches any routes by testing it against the `route.test` `RegExp`. |
|
4166 // By comparing the URL length the most specialized route that matches is used. |
|
4167 var route = { |
|
4168 length: -1 |
|
4169 }; |
|
4170 each(can.route.routes, function(temp, name) { |
|
4171 if (temp.test.test(url) && temp.length > route.length) { |
|
4172 route = temp; |
|
4173 } |
|
4174 }); |
|
4175 // If a route was matched. |
|
4176 if (route.length > -1) { |
|
4177 |
|
4178 var // Since `RegExp` backreferences are used in `route.test` (parens) |
|
4179 // the parts will contain the full matched string and each variable (back-referenced) value. |
|
4180 parts = url.match(route.test), |
|
4181 // Start will contain the full matched string; parts contain the variable values. |
|
4182 start = parts.shift(), |
|
4183 // The remainder will be the `&key=value` list at the end of the URL. |
|
4184 remainder = url.substr(start.length - (parts[parts.length - 1] === can.route._querySeparator ? 1 : 0)), |
|
4185 // If there is a remainder and it contains a `&key=value` list deparam it. |
|
4186 obj = (remainder && can.route._paramsMatcher.test(remainder)) ? can.deparam(remainder.slice(1)) : {}; |
|
4187 |
|
4188 // Add the default values for this route. |
|
4189 obj = extend(true, {}, route.defaults, obj); |
|
4190 // Overwrite each of the default values in `obj` with those in |
|
4191 // parts if that part is not empty. |
|
4192 each(parts, function(part, i) { |
|
4193 if (part && part !== can.route._querySeparator) { |
|
4194 obj[route.names[i]] = decodeURIComponent(part); |
|
4195 } |
|
4196 }); |
|
4197 obj.route = route.route; |
|
4198 return obj; |
|
4199 } |
|
4200 // If no route was matched, it is parsed as a `&key=value` list. |
|
4201 if (url.charAt(0) !== can.route._querySeparator) { |
|
4202 url = can.route._querySeparator + url; |
|
4203 } |
|
4204 return can.route._paramsMatcher.test(url) ? can.deparam(url.slice(1)) : {}; |
|
4205 }, |
|
4206 |
|
4207 data: new can.Observe({}), |
|
4208 |
|
4209 routes: {}, |
|
4210 |
|
4211 ready: function(val) { |
|
4212 if (val === false) { |
|
4213 onready = val; |
|
4214 } |
|
4215 if (val === true || onready === true) { |
|
4216 can.route._setup(); |
|
4217 setState(); |
|
4218 } |
|
4219 return can.route; |
|
4220 }, |
|
4221 |
|
4222 url: function(options, merge) { |
|
4223 if (merge) { |
|
4224 options = extend({}, curParams, options) |
|
4225 } |
|
4226 return "#!" + can.route.param(options); |
|
4227 }, |
|
4228 |
|
4229 link: function(name, options, props, merge) { |
|
4230 return "<a " + makeProps( |
|
4231 extend({ |
|
4232 href: can.route.url(options, merge) |
|
4233 }, props)) + ">" + name + "</a>"; |
|
4234 }, |
|
4235 |
|
4236 current: function(options) { |
|
4237 return location.hash == "#!" + can.route.param(options) |
|
4238 }, |
|
4239 _setup: function() { |
|
4240 // If the hash changes, update the `can.route.data`. |
|
4241 can.bind.call(window, 'hashchange', setState); |
|
4242 }, |
|
4243 _getHash: function() { |
|
4244 return location.href.split(/#!?/)[1] || ""; |
|
4245 }, |
|
4246 _setHash: function(serialized) { |
|
4247 var path = (can.route.param(serialized, true)); |
|
4248 location.hash = "#!" + path; |
|
4249 return path; |
|
4250 } |
|
4251 }); |
|
4252 |
|
4253 |
|
4254 // The functions in the following list applied to `can.route` (e.g. `can.route.attr('...')`) will |
|
4255 // instead act on the `can.route.data` observe. |
|
4256 each(['bind', 'unbind', 'delegate', 'undelegate', 'attr', 'removeAttr'], function(name) { |
|
4257 can.route[name] = function() { |
|
4258 // `delegate` and `undelegate` require |
|
4259 // the `can/observe/delegate` plugin |
|
4260 if (!can.route.data[name]) { |
|
4261 return; |
|
4262 } |
|
4263 |
|
4264 return can.route.data[name].apply(can.route.data, arguments); |
|
4265 } |
|
4266 }) |
|
4267 |
|
4268 var // A ~~throttled~~ debounced function called multiple times will only fire once the |
|
4269 // timer runs down. Each call resets the timer. |
|
4270 timer, |
|
4271 // Intermediate storage for `can.route.data`. |
|
4272 curParams, |
|
4273 // Deparameterizes the portion of the hash of interest and assign the |
|
4274 // values to the `can.route.data` removing existing values no longer in the hash. |
|
4275 // setState is called typically by hashchange which fires asynchronously |
|
4276 // So it's possible that someone started changing the data before the |
|
4277 // hashchange event fired. For this reason, it will not set the route data |
|
4278 // if the data is changing or the hash already matches the hash that was set. |
|
4279 setState = can.route.setState = function() { |
|
4280 var hash = can.route._getHash(); |
|
4281 curParams = can.route.deparam(hash); |
|
4282 |
|
4283 // if the hash data is currently changing, or |
|
4284 // the hash is what we set it to anyway, do NOT change the hash |
|
4285 if (!changingData || hash !== lastHash) { |
|
4286 can.route.attr(curParams, true); |
|
4287 } |
|
4288 }, |
|
4289 // The last hash caused by a data change |
|
4290 lastHash, |
|
4291 // Are data changes pending that haven't yet updated the hash |
|
4292 changingData; |
|
4293 |
|
4294 // If the `can.route.data` changes, update the hash. |
|
4295 // Using `.serialize()` retrieves the raw data contained in the `observable`. |
|
4296 // This function is ~~throttled~~ debounced so it only updates once even if multiple values changed. |
|
4297 // This might be able to use batchNum and avoid this. |
|
4298 can.route.bind("change", function(ev, attr) { |
|
4299 // indicate that data is changing |
|
4300 changingData = 1; |
|
4301 clearTimeout(timer); |
|
4302 timer = setTimeout(function() { |
|
4303 // indicate that the hash is set to look like the data |
|
4304 changingData = 0; |
|
4305 var serialized = can.route.data.serialize(); |
|
4306 |
|
4307 lastHash = can.route._setHash(serialized); |
|
4308 }, 1); |
|
4309 }); |
|
4310 // `onready` event... |
|
4311 can.bind.call(document, "ready", can.route.ready); |
|
4312 |
|
4313 // Libraries other than jQuery don't execute the document `ready` listener |
|
4314 // if we are already DOM ready |
|
4315 if ((document.readyState === 'complete' || document.readyState === "interactive") && onready) { |
|
4316 can.route.ready(); |
|
4317 } |
|
4318 |
|
4319 // extend route to have a similar property |
|
4320 // that is often checked in mustache to determine |
|
4321 // an object's observability |
|
4322 can.route.constructor.canMakeObserve = can.Observe.canMakeObserve; |
|
4323 |
|
4324 return can.route; |
|
4325 })(__m3, __m7, __m20); |
|
4326 |
|
4327 // ## can/control/route/route.js |
|
4328 var __m21 = (function(can) { |
|
4329 |
|
4330 // ## control/route.js |
|
4331 // _Controller route integration._ |
|
4332 |
|
4333 can.Control.processors.route = function(el, event, selector, funcName, controller) { |
|
4334 selector = selector || ""; |
|
4335 can.route(selector); |
|
4336 var batchNum, |
|
4337 check = function(ev, attr, how) { |
|
4338 if (can.route.attr('route') === (selector) && |
|
4339 (ev.batchNum === undefined || ev.batchNum !== batchNum)) { |
|
4340 |
|
4341 batchNum = ev.batchNum; |
|
4342 |
|
4343 var d = can.route.attr(); |
|
4344 delete d.route; |
|
4345 if (can.isFunction(controller[funcName])) { |
|
4346 controller[funcName](d); |
|
4347 } else { |
|
4348 controller[controller[funcName]](d); |
|
4349 } |
|
4350 |
|
4351 } |
|
4352 }; |
|
4353 can.route.bind('change', check); |
|
4354 return function() { |
|
4355 can.route.unbind('change', check); |
|
4356 }; |
|
4357 }; |
|
4358 |
|
4359 return can; |
|
4360 })(__m3, __m19, __m18); |
|
4361 |
|
4362 // ## can/util/object/object.js |
|
4363 var __m23 = (function(can) { |
|
4364 |
|
4365 var isArray = can.isArray, |
|
4366 // essentially returns an object that has all the must have comparisons ... |
|
4367 // must haves, do not return true when provided undefined |
|
4368 cleanSet = function(obj, compares) { |
|
4369 var copy = can.extend({}, obj); |
|
4370 for (var prop in copy) { |
|
4371 var compare = compares[prop] === undefined ? compares["*"] : compares[prop]; |
|
4372 if (same(copy[prop], undefined, compare)) { |
|
4373 delete copy[prop] |
|
4374 } |
|
4375 } |
|
4376 return copy; |
|
4377 }, |
|
4378 propCount = function(obj) { |
|
4379 var count = 0; |
|
4380 for (var prop in obj) count++; |
|
4381 return count; |
|
4382 }; |
|
4383 |
|
4384 can.Object = {}; |
|
4385 |
|
4386 var same = can.Object.same = function(a, b, compares, aParent, bParent, deep) { |
|
4387 var aType = typeof a, |
|
4388 aArray = isArray(a), |
|
4389 comparesType = typeof compares, |
|
4390 compare; |
|
4391 |
|
4392 if (comparesType == 'string' || compares === null) { |
|
4393 compares = compareMethods[compares]; |
|
4394 comparesType = 'function' |
|
4395 } |
|
4396 if (comparesType == 'function') { |
|
4397 return compares(a, b, aParent, bParent) |
|
4398 } |
|
4399 compares = compares || {}; |
|
4400 |
|
4401 if (a instanceof Date) { |
|
4402 return a === b; |
|
4403 } |
|
4404 if (deep === -1) { |
|
4405 return aType === 'object' || a === b; |
|
4406 } |
|
4407 if (aType !== typeof b || aArray !== isArray(b)) { |
|
4408 return false; |
|
4409 } |
|
4410 if (a === b) { |
|
4411 return true; |
|
4412 } |
|
4413 if (aArray) { |
|
4414 if (a.length !== b.length) { |
|
4415 return false; |
|
4416 } |
|
4417 for (var i = 0; i < a.length; i++) { |
|
4418 compare = compares[i] === undefined ? compares["*"] : compares[i] |
|
4419 if (!same(a[i], b[i], a, b, compare)) { |
|
4420 return false; |
|
4421 } |
|
4422 }; |
|
4423 return true; |
|
4424 } else if (aType === "object" || aType === 'function') { |
|
4425 var bCopy = can.extend({}, b); |
|
4426 for (var prop in a) { |
|
4427 compare = compares[prop] === undefined ? compares["*"] : compares[prop]; |
|
4428 if (!same(a[prop], b[prop], compare, a, b, deep === false ? -1 : undefined)) { |
|
4429 return false; |
|
4430 } |
|
4431 delete bCopy[prop]; |
|
4432 } |
|
4433 // go through bCopy props ... if there is no compare .. return false |
|
4434 for (prop in bCopy) { |
|
4435 if (compares[prop] === undefined || !same(undefined, b[prop], compares[prop], a, b, deep === false ? -1 : undefined)) { |
|
4436 return false; |
|
4437 } |
|
4438 } |
|
4439 return true; |
|
4440 } |
|
4441 return false; |
|
4442 }; |
|
4443 |
|
4444 can.Object.subsets = function(checkSet, sets, compares) { |
|
4445 var len = sets.length, |
|
4446 subsets = [], |
|
4447 checkPropCount = propCount(checkSet), |
|
4448 setLength; |
|
4449 |
|
4450 for (var i = 0; i < len; i++) { |
|
4451 //check this subset |
|
4452 var set = sets[i]; |
|
4453 if (can.Object.subset(checkSet, set, compares)) { |
|
4454 subsets.push(set) |
|
4455 } |
|
4456 } |
|
4457 return subsets; |
|
4458 }; |
|
4459 |
|
4460 can.Object.subset = function(subset, set, compares) { |
|
4461 // go through set {type: 'folder'} and make sure every property |
|
4462 // is in subset {type: 'folder', parentId :5} |
|
4463 // then make sure that set has fewer properties |
|
4464 // make sure we are only checking 'important' properties |
|
4465 // in subset (ones that have to have a value) |
|
4466 |
|
4467 var setPropCount = 0, |
|
4468 compares = compares || {}; |
|
4469 |
|
4470 for (var prop in set) { |
|
4471 |
|
4472 if (!same(subset[prop], set[prop], compares[prop], subset, set)) { |
|
4473 return false; |
|
4474 } |
|
4475 } |
|
4476 return true; |
|
4477 } |
|
4478 |
|
4479 var compareMethods = { |
|
4480 "null": function() { |
|
4481 return true; |
|
4482 }, |
|
4483 i: function(a, b) { |
|
4484 return ("" + a).toLowerCase() == ("" + b).toLowerCase() |
|
4485 } |
|
4486 } |
|
4487 |
|
4488 return can.Object; |
|
4489 |
|
4490 })(__m3); |
|
4491 |
|
4492 // ## can/observe/backup/backup.js |
|
4493 var __m22 = (function(can) { |
|
4494 var flatProps = function(a) { |
|
4495 var obj = {}; |
|
4496 for (var prop in a) { |
|
4497 if (typeof a[prop] !== 'object' || a[prop] === null || a[prop] instanceof Date) { |
|
4498 obj[prop] = a[prop] |
|
4499 } |
|
4500 } |
|
4501 return obj; |
|
4502 }; |
|
4503 |
|
4504 can.extend(can.Observe.prototype, { |
|
4505 |
|
4506 |
|
4507 backup: function() { |
|
4508 this._backupStore = this._attrs(); |
|
4509 return this; |
|
4510 }, |
|
4511 |
|
4512 |
|
4513 isDirty: function(checkAssociations) { |
|
4514 return this._backupStore && !can.Object.same(this._attrs(), |
|
4515 this._backupStore, |
|
4516 undefined, |
|
4517 undefined, |
|
4518 undefined, !! checkAssociations); |
|
4519 }, |
|
4520 |
|
4521 |
|
4522 restore: function(restoreAssociations) { |
|
4523 var props = restoreAssociations ? this._backupStore : flatProps(this._backupStore) |
|
4524 |
|
4525 if (this.isDirty(restoreAssociations)) { |
|
4526 this._attrs(props); |
|
4527 } |
|
4528 |
|
4529 return this; |
|
4530 } |
|
4531 |
|
4532 }) |
|
4533 |
|
4534 return can.Observe; |
|
4535 })(__m3, __m7, __m23); |
|
4536 |
|
4537 window['can'] = __m5; |
|
4538 })(); |