|
1 /*! |
|
2 * CanJS - 2.1.1 |
|
3 * http://canjs.us/ |
|
4 * Copyright (c) 2014 Bitovi |
|
5 * Fri, 23 May 2014 15:47:45 GMT |
|
6 * Licensed MIT |
|
7 * Includes: can/construct/construct.js,can/map/map.js,can/list/list.js,can/compute/compute.js,can/model/model.js,can/view/view.js,can/control/control.js,can/route/route.js,can/control/route/route.js,can/view/ejs/ejs.js,can/map/backup/backup.js,can/util/object/object.js |
|
8 * Download from: http://bitbuilder.herokuapp.com/can.custom.js?configuration=jquery&plugins=can%2Fconstruct%2Fconstruct.js&plugins=can%2Fmap%2Fmap.js&plugins=can%2Flist%2Flist.js&plugins=can%2Fcompute%2Fcompute.js&plugins=can%2Fmodel%2Fmodel.js&plugins=can%2Fview%2Fview.js&plugins=can%2Fcontrol%2Fcontrol.js&plugins=can%2Froute%2Froute.js&plugins=can%2Fcontrol%2Froute%2Froute.js&plugins=can%2Fview%2Fejs%2Fejs.js&plugins=can%2Fmap%2Fbackup%2Fbackup.js&plugins=can%2Futil%2Fobject%2Fobject.js |
|
9 */ |
|
10 (function(undefined) { |
|
11 |
|
12 // ## can/util/can.js |
|
13 var __m5 = (function() { |
|
14 |
|
15 var can = window.can || {}; |
|
16 if (typeof GLOBALCAN === 'undefined' || GLOBALCAN !== false) { |
|
17 window.can = can; |
|
18 } |
|
19 |
|
20 // An empty function useful for where you need a dummy callback. |
|
21 can.k = function() {}; |
|
22 |
|
23 can.isDeferred = function(obj) { |
|
24 var isFunction = this.isFunction; |
|
25 // Returns `true` if something looks like a deferred. |
|
26 return obj && isFunction(obj.then) && isFunction(obj.pipe); |
|
27 }; |
|
28 |
|
29 var cid = 0; |
|
30 can.cid = function(object, name) { |
|
31 if (!object._cid) { |
|
32 cid++; |
|
33 object._cid = (name || '') + cid; |
|
34 } |
|
35 return object._cid; |
|
36 }; |
|
37 can.VERSION = '@EDGE'; |
|
38 |
|
39 can.simpleExtend = function(d, s) { |
|
40 for (var prop in s) { |
|
41 d[prop] = s[prop]; |
|
42 } |
|
43 return d; |
|
44 }; |
|
45 |
|
46 can.frag = function(item) { |
|
47 var frag; |
|
48 if (!item || typeof item === "string") { |
|
49 frag = can.buildFragment(item == null ? "" : "" + item, document.body); |
|
50 // If we have an empty frag... |
|
51 if (!frag.childNodes.length) { |
|
52 frag.appendChild(document.createTextNode('')); |
|
53 } |
|
54 return frag; |
|
55 } else if (item.nodeType === 11) { |
|
56 return item; |
|
57 } else if (typeof item.nodeType === "number") { |
|
58 frag = document.createDocumentFragment(); |
|
59 frag.appendChild(item); |
|
60 return frag; |
|
61 } else if (typeof item.length === "number") { |
|
62 frag = document.createDocumentFragment(); |
|
63 can.each(item, function(item) { |
|
64 frag.appendChild(can.frag(item)); |
|
65 }); |
|
66 return frag; |
|
67 } else { |
|
68 frag = can.buildFragment("" + item, document.body); |
|
69 // If we have an empty frag... |
|
70 if (!frag.childNodes.length) { |
|
71 frag.appendChild(document.createTextNode('')); |
|
72 } |
|
73 return frag; |
|
74 } |
|
75 }; |
|
76 |
|
77 // this is here in case can.compute hasn't loaded |
|
78 can.__reading = function() {}; |
|
79 |
|
80 return can; |
|
81 })(); |
|
82 |
|
83 // ## can/util/attr/attr.js |
|
84 var __m6 = (function(can) { |
|
85 |
|
86 // Acts as a polyfill for setImmediate which only works in IE 10+. Needed to make |
|
87 // the triggering of `attributes` event async. |
|
88 var setImmediate = window.setImmediate || function(cb) { |
|
89 return setTimeout(cb, 0); |
|
90 }, |
|
91 attr = { |
|
92 // This property lets us know if the browser supports mutation observers. |
|
93 // If they are supported then that will be setup in can/util/jquery and those native events will be used to inform observers of attribute changes. |
|
94 // Otherwise this module handles triggering an `attributes` event on the element. |
|
95 MutationObserver: window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver, |
|
96 |
|
97 |
|
98 map: { |
|
99 "class": "className", |
|
100 "value": "value", |
|
101 "innerText": "innerText", |
|
102 "textContent": "textContent", |
|
103 "checked": true, |
|
104 "disabled": true, |
|
105 "readonly": true, |
|
106 "required": true, |
|
107 // For the `src` attribute we are using a setter function to prevent values such as an empty string or null from being set. |
|
108 // An `img` tag attempts to fetch the `src` when it is set, so we need to prevent that from happening by removing the attribute instead. |
|
109 src: function(el, val) { |
|
110 if (val == null || val === "") { |
|
111 el.removeAttribute("src"); |
|
112 return null; |
|
113 } else { |
|
114 el.setAttribute("src", val); |
|
115 return val; |
|
116 } |
|
117 }, |
|
118 style: function(el, val) { |
|
119 return el.style.cssText = val || ""; |
|
120 } |
|
121 }, |
|
122 // These are elements whos default value we should set. |
|
123 defaultValue: ["input", "textarea"], |
|
124 // ## attr.set |
|
125 // Set the value an attribute on an element. |
|
126 set: function(el, attrName, val) { |
|
127 var oldValue; |
|
128 // In order to later trigger an event we need to compare the new value to the old value, so here we go ahead and retrieve the old value for browsers that don't have native MutationObservers. |
|
129 if (!attr.MutationObserver) { |
|
130 oldValue = attr.get(el, attrName); |
|
131 } |
|
132 |
|
133 var tagName = el.nodeName.toString() |
|
134 .toLowerCase(), |
|
135 prop = attr.map[attrName], |
|
136 newValue; |
|
137 |
|
138 // Using the property of `attr.map`, go through and check if the property is a function, and if so call it. Then check if the property is `true`, and if so set the value to `true`, also making sure to set `defaultChecked` to `true` for elements of `attr.defaultValue`. We always set the value to true because for these boolean properties, setting them to false would be the same as removing the attribute. |
|
139 // For all other attributes use `setAttribute` to set the new value. |
|
140 if (typeof prop === "function") { |
|
141 newValue = prop(el, val); |
|
142 } else if (prop === true) { |
|
143 newValue = el[attrName] = true; |
|
144 |
|
145 if (attrName === "checked" && el.type === "radio") { |
|
146 if (can.inArray(tagName, attr.defaultValue) >= 0) { |
|
147 el.defaultChecked = true; |
|
148 } |
|
149 } |
|
150 |
|
151 } else if (prop) { |
|
152 newValue = el[prop] = val; |
|
153 if (prop === "value" && can.inArray(tagName, attr.defaultValue) >= 0) { |
|
154 el.defaultValue = val; |
|
155 } |
|
156 } else { |
|
157 el.setAttribute(attrName, val); |
|
158 newValue = val; |
|
159 } |
|
160 |
|
161 // Now that the value has been set, for browsers without MutationObservers, check to see that value has changed and if so trigger the "attributes" event on the element. |
|
162 if (!attr.MutationObserver && newValue !== oldValue) { |
|
163 attr.trigger(el, attrName, oldValue); |
|
164 } |
|
165 }, |
|
166 // ## attr.trigger |
|
167 // Used to trigger an "attributes" event on an element. Checks to make sure that someone is listening for the event and then queues a function to be called asynchronously using `setImmediate. |
|
168 trigger: function(el, attrName, oldValue) { |
|
169 if (can.data(can.$(el), "canHasAttributesBindings")) { |
|
170 return setImmediate(function() { |
|
171 can.trigger(el, { |
|
172 type: "attributes", |
|
173 attributeName: attrName, |
|
174 target: el, |
|
175 oldValue: oldValue, |
|
176 bubbles: false |
|
177 }, []); |
|
178 }); |
|
179 } |
|
180 }, |
|
181 // ## attr.get |
|
182 // Gets the value of an attribute. First checks to see if the property is a string on `attr.map` and if so returns the value from the element's property. Otherwise uses `getAttribute` to retrieve the value. |
|
183 get: function(el, attrName) { |
|
184 var prop = attr.map[attrName]; |
|
185 if (typeof prop === "string" && el[prop]) { |
|
186 return el[prop]; |
|
187 } |
|
188 |
|
189 return el.getAttribute(attrName); |
|
190 }, |
|
191 // ## attr.remove |
|
192 // Removes an attribute from an element. Works by using the `attr.map` to see if the attribute is a special type of property. If the property is a function then the fuction is called with `undefined` as the value. If the property is `true` then the attribute is set to false. If the property is a string then the attribute is set to an empty string. Otherwise `removeAttribute` is used. |
|
193 // If the attribute previously had a value and the browser doesn't support MutationObservers we then trigger an "attributes" event. |
|
194 remove: function(el, attrName) { |
|
195 var oldValue; |
|
196 if (!attr.MutationObserver) { |
|
197 oldValue = attr.get(el, attrName); |
|
198 } |
|
199 |
|
200 var setter = attr.map[attrName]; |
|
201 if (typeof setter === "function") { |
|
202 setter(el, undefined); |
|
203 } |
|
204 if (setter === true) { |
|
205 el[attrName] = false; |
|
206 } else if (typeof setter === "string") { |
|
207 el[setter] = ""; |
|
208 } else { |
|
209 el.removeAttribute(attrName); |
|
210 } |
|
211 if (!attr.MutationObserver && oldValue != null) { |
|
212 attr.trigger(el, attrName, oldValue); |
|
213 } |
|
214 |
|
215 }, |
|
216 // ## attr.has |
|
217 // Checks if an element contains an attribute. |
|
218 // For browsers that support `hasAttribute`, creates a function that calls hasAttribute, otherwise creates a function that uses `getAttribute` to check that the attribute is not null. |
|
219 has: (function() { |
|
220 var el = document.createElement('div'); |
|
221 if (el.hasAttribute) { |
|
222 return function(el, name) { |
|
223 return el.hasAttribute(name); |
|
224 }; |
|
225 } else { |
|
226 return function(el, name) { |
|
227 return el.getAttribute(name) !== null; |
|
228 }; |
|
229 } |
|
230 })() |
|
231 }; |
|
232 |
|
233 return attr; |
|
234 |
|
235 })(__m5); |
|
236 |
|
237 // ## can/event/event.js |
|
238 var __m7 = (function(can) { |
|
239 // ## can.event.addEvent |
|
240 // Adds a basic event listener to an object. |
|
241 // This consists of storing a cache of event listeners on each object, |
|
242 // that are iterated through later when events are dispatched. |
|
243 |
|
244 can.addEvent = function(event, handler) { |
|
245 // Initialize event cache. |
|
246 var allEvents = this.__bindEvents || (this.__bindEvents = {}), |
|
247 eventList = allEvents[event] || (allEvents[event] = []); |
|
248 |
|
249 // Add the event |
|
250 eventList.push({ |
|
251 handler: handler, |
|
252 name: event |
|
253 }); |
|
254 return this; |
|
255 }; |
|
256 |
|
257 // ## can.event.listenTo |
|
258 // Listens to an event without know how bind is implemented. |
|
259 // The primary use for this is to listen to another's objects event while |
|
260 // tracking events on the local object (similar to namespacing). |
|
261 // The API was heavily influenced by BackboneJS: http://backbonejs.org/ |
|
262 |
|
263 can.listenTo = function(other, event, handler) { |
|
264 // Initialize event cache |
|
265 var idedEvents = this.__listenToEvents; |
|
266 if (!idedEvents) { |
|
267 idedEvents = this.__listenToEvents = {}; |
|
268 } |
|
269 |
|
270 // Identify the other object |
|
271 var otherId = can.cid(other); |
|
272 var othersEvents = idedEvents[otherId]; |
|
273 |
|
274 // Create a local event cache |
|
275 if (!othersEvents) { |
|
276 othersEvents = idedEvents[otherId] = { |
|
277 obj: other, |
|
278 events: {} |
|
279 }; |
|
280 } |
|
281 var eventsEvents = othersEvents.events[event]; |
|
282 if (!eventsEvents) { |
|
283 eventsEvents = othersEvents.events[event] = []; |
|
284 } |
|
285 |
|
286 // Add the event, both locally and to the other object |
|
287 eventsEvents.push(handler); |
|
288 can.bind.call(other, event, handler); |
|
289 }; |
|
290 |
|
291 // ## can.event.stopListening |
|
292 // Stops listening for events on other objects |
|
293 |
|
294 can.stopListening = function(other, event, handler) { |
|
295 var idedEvents = this.__listenToEvents, |
|
296 iterIdedEvents = idedEvents, |
|
297 i = 0; |
|
298 if (!idedEvents) { |
|
299 return this; |
|
300 } |
|
301 if (other) { |
|
302 var othercid = can.cid(other); |
|
303 (iterIdedEvents = {})[othercid] = idedEvents[othercid]; |
|
304 // you might be trying to listen to something that is not there |
|
305 if (!idedEvents[othercid]) { |
|
306 return this; |
|
307 } |
|
308 } |
|
309 |
|
310 // Clean up events on the other object |
|
311 for (var cid in iterIdedEvents) { |
|
312 var othersEvents = iterIdedEvents[cid], |
|
313 eventsEvents; |
|
314 other = idedEvents[cid].obj; |
|
315 |
|
316 // Find the cache of events |
|
317 if (!event) { |
|
318 eventsEvents = othersEvents.events; |
|
319 } else { |
|
320 (eventsEvents = {})[event] = othersEvents.events[event]; |
|
321 } |
|
322 |
|
323 // Unbind event handlers, both locally and on the other object |
|
324 for (var eventName in eventsEvents) { |
|
325 var handlers = eventsEvents[eventName] || []; |
|
326 i = 0; |
|
327 while (i < handlers.length) { |
|
328 if (handler && handler === handlers[i] || !handler) { |
|
329 can.unbind.call(other, eventName, handlers[i]); |
|
330 handlers.splice(i, 1); |
|
331 } else { |
|
332 i++; |
|
333 } |
|
334 } |
|
335 // no more handlers? |
|
336 if (!handlers.length) { |
|
337 delete othersEvents.events[eventName]; |
|
338 } |
|
339 } |
|
340 if (can.isEmptyObject(othersEvents.events)) { |
|
341 delete idedEvents[cid]; |
|
342 } |
|
343 } |
|
344 return this; |
|
345 }; |
|
346 |
|
347 // ## can.event.removeEvent |
|
348 // Removes a basic event listener from an object. |
|
349 // This removes event handlers from the cache of listened events. |
|
350 |
|
351 can.removeEvent = function(event, fn, __validate) { |
|
352 if (!this.__bindEvents) { |
|
353 return this; |
|
354 } |
|
355 var events = this.__bindEvents[event] || [], |
|
356 i = 0, |
|
357 ev, isFunction = typeof fn === 'function'; |
|
358 while (i < events.length) { |
|
359 ev = events[i]; |
|
360 // Determine whether this event handler is "equivalent" to the one requested |
|
361 // Generally this requires the same event/function, but a validation function |
|
362 // can be included for extra conditions. This is used in some plugins like `can/event/namespace`. |
|
363 if (__validate ? __validate(ev, event, fn) : isFunction && ev.handler === fn || !isFunction && (ev.cid === fn || !fn)) { |
|
364 events.splice(i, 1); |
|
365 } else { |
|
366 i++; |
|
367 } |
|
368 } |
|
369 return this; |
|
370 }; |
|
371 |
|
372 // ## can.event.dispatch |
|
373 // Dispatches/triggers a basic event on an object. |
|
374 |
|
375 can.dispatch = function(event, args) { |
|
376 var events = this.__bindEvents; |
|
377 if (!events) { |
|
378 return; |
|
379 } |
|
380 |
|
381 // Initialize the event object |
|
382 if (typeof event === 'string') { |
|
383 event = { |
|
384 type: event |
|
385 }; |
|
386 } |
|
387 |
|
388 // Grab event listeners |
|
389 var eventName = event.type, |
|
390 handlers = (events[eventName] || []).slice(0); |
|
391 |
|
392 // Execute handlers listening for this event. |
|
393 args = [event].concat(args || []); |
|
394 for (var i = 0, len = handlers.length; i < len; i++) { |
|
395 handlers[i].handler.apply(this, args); |
|
396 } |
|
397 |
|
398 return event; |
|
399 }; |
|
400 |
|
401 // ## can.event.one |
|
402 // Adds a basic event listener that listens to an event once and only once. |
|
403 |
|
404 can.one = function(event, handler) { |
|
405 // Unbind the listener after it has been executed |
|
406 var one = function() { |
|
407 can.unbind.call(this, event, one); |
|
408 return handler.apply(this, arguments); |
|
409 }; |
|
410 |
|
411 // Bind the altered listener |
|
412 can.bind.call(this, event, one); |
|
413 return this; |
|
414 }; |
|
415 |
|
416 // ## can.event |
|
417 // Create and export the `can.event` mixin |
|
418 can.event = { |
|
419 // Event method aliases |
|
420 |
|
421 on: function() { |
|
422 if (arguments.length === 0 && can.Control && this instanceof can.Control) { |
|
423 return can.Control.prototype.on.call(this); |
|
424 } else { |
|
425 return can.addEvent.apply(this, arguments); |
|
426 } |
|
427 }, |
|
428 |
|
429 |
|
430 off: function() { |
|
431 if (arguments.length === 0 && can.Control && this instanceof can.Control) { |
|
432 return can.Control.prototype.off.call(this); |
|
433 } else { |
|
434 return can.removeEvent.apply(this, arguments); |
|
435 } |
|
436 }, |
|
437 |
|
438 |
|
439 bind: can.addEvent, |
|
440 |
|
441 unbind: can.removeEvent, |
|
442 |
|
443 delegate: function(selector, event, handler) { |
|
444 return can.addEvent.call(event, handler); |
|
445 }, |
|
446 |
|
447 undelegate: function(selector, event, handler) { |
|
448 return can.removeEvent.call(event, handler); |
|
449 }, |
|
450 |
|
451 trigger: can.dispatch, |
|
452 |
|
453 // Normal can/event methods |
|
454 one: can.one, |
|
455 addEvent: can.addEvent, |
|
456 removeEvent: can.removeEvent, |
|
457 listenTo: can.listenTo, |
|
458 stopListening: can.stopListening, |
|
459 dispatch: can.dispatch |
|
460 }; |
|
461 |
|
462 return can.event; |
|
463 })(__m5); |
|
464 |
|
465 // ## can/util/array/each.js |
|
466 var __m8 = (function(can) { |
|
467 |
|
468 // The following is from jQuery |
|
469 var isArrayLike = function(obj) { |
|
470 var length = obj.length; |
|
471 return typeof arr !== "function" && |
|
472 (length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj); |
|
473 }; |
|
474 |
|
475 can.each = function(elements, callback, context) { |
|
476 var i = 0, |
|
477 key, |
|
478 len, |
|
479 item; |
|
480 if (elements) { |
|
481 if (isArrayLike(elements)) { |
|
482 if (can.List && elements instanceof can.List) { |
|
483 for (len = elements.attr("length"); i < len; i++) { |
|
484 item = elements.attr(i); |
|
485 if (callback.call(context || item, item, i, elements) === false) { |
|
486 break; |
|
487 } |
|
488 } |
|
489 } else { |
|
490 for (len = elements.length; i < len; i++) { |
|
491 item = elements[i]; |
|
492 if (callback.call(context || item, item, i, elements) === false) { |
|
493 break; |
|
494 } |
|
495 } |
|
496 } |
|
497 |
|
498 } else if (typeof elements === "object") { |
|
499 |
|
500 if (can.Map && elements instanceof can.Map || elements === can.route) { |
|
501 var keys = can.Map.keys(elements); |
|
502 for (i = 0, len = keys.length; i < len; i++) { |
|
503 key = keys[i]; |
|
504 item = elements.attr(key); |
|
505 if (callback.call(context || item, item, key, elements) === false) { |
|
506 break; |
|
507 } |
|
508 } |
|
509 } else { |
|
510 for (key in elements) { |
|
511 if (elements.hasOwnProperty(key) && callback.call(context || elements[key], elements[key], key, elements) === false) { |
|
512 break; |
|
513 } |
|
514 } |
|
515 } |
|
516 |
|
517 } |
|
518 } |
|
519 return elements; |
|
520 }; |
|
521 return can; |
|
522 })(__m5); |
|
523 |
|
524 // ## can/util/inserted/inserted.js |
|
525 var __m9 = (function(can) { |
|
526 can.inserted = function(elems) { |
|
527 // Turn the `elems` property into an array to prevent mutations from changing the looping. |
|
528 elems = can.makeArray(elems); |
|
529 var inDocument = false, |
|
530 // Gets the `doc` to use as a reference for finding out whether the element is in the document. |
|
531 doc = can.$(document.contains ? document : document.body), |
|
532 children; |
|
533 // Go through `elems` and trigger the `inserted` event. |
|
534 // If the first element is not in the document (a Document Fragment) it will exit the function. If it is in the document it sets the `inDocument` flag to true. This means that we only check for the first element and either exit the function or start triggering "inserted" for child elements. |
|
535 for (var i = 0, elem; |
|
536 (elem = elems[i]) !== undefined; i++) { |
|
537 if (!inDocument) { |
|
538 if (elem.getElementsByTagName) { |
|
539 if (can.has(doc, elem) |
|
540 .length) { |
|
541 inDocument = true; |
|
542 } else { |
|
543 return; |
|
544 } |
|
545 } else { |
|
546 continue; |
|
547 } |
|
548 } |
|
549 |
|
550 // If we've found an element in the document then we can now trigger **"inserted"** for `elem` and all of its children. We are using `getElementsByTagName("*")` so that we grab all of the descendant nodes. |
|
551 if (inDocument && elem.getElementsByTagName) { |
|
552 children = can.makeArray(elem.getElementsByTagName("*")); |
|
553 can.trigger(elem, "inserted", [], false); |
|
554 for (var j = 0, child; |
|
555 (child = children[j]) !== undefined; j++) { |
|
556 can.trigger(child, "inserted", [], false); |
|
557 } |
|
558 } |
|
559 } |
|
560 }; |
|
561 |
|
562 // ## can.appendChild |
|
563 // Used to append a node to an element and trigger the "inserted" event on all of the newly inserted children. Since `can.inserted` takes an array we convert the child to an array, or in the case of a DocumentFragment we first convert the childNodes to an array and call inserted on those. |
|
564 can.appendChild = function(el, child) { |
|
565 var children; |
|
566 if (child.nodeType === 11) { |
|
567 children = can.makeArray(child.childNodes); |
|
568 } else { |
|
569 children = [child]; |
|
570 } |
|
571 el.appendChild(child); |
|
572 can.inserted(children); |
|
573 }; |
|
574 |
|
575 // ## can.insertBefore |
|
576 // Like can.appendChild, used to insert a node to an element before a reference node and then trigger the "inserted" event. |
|
577 can.insertBefore = function(el, child, ref) { |
|
578 var children; |
|
579 if (child.nodeType === 11) { |
|
580 children = can.makeArray(child.childNodes); |
|
581 } else { |
|
582 children = [child]; |
|
583 } |
|
584 el.insertBefore(child, ref); |
|
585 can.inserted(children); |
|
586 }; |
|
587 })(__m5); |
|
588 |
|
589 // ## can/util/jquery/jquery.js |
|
590 var __m3 = (function($, can, attr, event) { |
|
591 var isBindableElement = function(node) { |
|
592 // In IE8 window.window !== window.window, so we allow == here. |
|
593 |
|
594 return (node.nodeName && (node.nodeType === 1 || node.nodeType === 9)) || node == window; |
|
595 }; |
|
596 // _jQuery node list._ |
|
597 $.extend(can, $, { |
|
598 trigger: function(obj, event, args, bubbles) { |
|
599 if (isBindableElement(obj)) { |
|
600 $.event.trigger(event, args, obj, !bubbles); |
|
601 } else if (obj.trigger) { |
|
602 obj.trigger(event, args); |
|
603 } else { |
|
604 if (typeof event === 'string') { |
|
605 event = { |
|
606 type: event |
|
607 }; |
|
608 } |
|
609 event.target = event.target || obj; |
|
610 can.dispatch.call(obj, event, args); |
|
611 } |
|
612 }, |
|
613 event: can.event, |
|
614 addEvent: can.addEvent, |
|
615 removeEvent: can.removeEvent, |
|
616 buildFragment: function(elems, context) { |
|
617 // Check if this has any html nodes on our own. |
|
618 var ret; |
|
619 elems = [elems]; |
|
620 // Set context per 1.8 logic |
|
621 context = context || document; |
|
622 context = !context.nodeType && context[0] || context; |
|
623 context = context.ownerDocument || context; |
|
624 ret = $.buildFragment(elems, context); |
|
625 return ret.cacheable ? $.clone(ret.fragment) : ret.fragment || ret; |
|
626 }, |
|
627 $: $, |
|
628 each: can.each, |
|
629 bind: function(ev, cb) { |
|
630 // If we can bind to it... |
|
631 if (this.bind && this.bind !== can.bind) { |
|
632 this.bind(ev, cb); |
|
633 } else if (isBindableElement(this)) { |
|
634 $.event.add(this, ev, cb); |
|
635 } else { |
|
636 // Make it bind-able... |
|
637 can.addEvent.call(this, ev, cb); |
|
638 } |
|
639 return this; |
|
640 }, |
|
641 unbind: function(ev, cb) { |
|
642 // If we can bind to it... |
|
643 if (this.unbind && this.unbind !== can.unbind) { |
|
644 this.unbind(ev, cb); |
|
645 } else if (isBindableElement(this)) { |
|
646 $.event.remove(this, ev, cb); |
|
647 } else { |
|
648 // Make it bind-able... |
|
649 can.removeEvent.call(this, ev, cb); |
|
650 } |
|
651 return this; |
|
652 }, |
|
653 delegate: function(selector, ev, cb) { |
|
654 if (this.delegate) { |
|
655 this.delegate(selector, ev, cb); |
|
656 } else if (isBindableElement(this)) { |
|
657 $(this) |
|
658 .delegate(selector, ev, cb); |
|
659 } else { |
|
660 // make it bind-able ... |
|
661 can.bind.call(this, ev, cb); |
|
662 } |
|
663 return this; |
|
664 }, |
|
665 undelegate: function(selector, ev, cb) { |
|
666 if (this.undelegate) { |
|
667 this.undelegate(selector, ev, cb); |
|
668 } else if (isBindableElement(this)) { |
|
669 $(this) |
|
670 .undelegate(selector, ev, cb); |
|
671 } else { |
|
672 can.unbind.call(this, ev, cb); |
|
673 } |
|
674 return this; |
|
675 }, |
|
676 proxy: function(fn, context) { |
|
677 return function() { |
|
678 return fn.apply(context, arguments); |
|
679 }; |
|
680 }, |
|
681 attr: attr |
|
682 }); |
|
683 // Wrap binding functions. |
|
684 |
|
685 // Aliases |
|
686 can.on = can.bind; |
|
687 can.off = can.unbind; |
|
688 // Wrap modifier functions. |
|
689 $.each([ |
|
690 'append', |
|
691 'filter', |
|
692 'addClass', |
|
693 'remove', |
|
694 'data', |
|
695 'get', |
|
696 'has' |
|
697 ], function(i, name) { |
|
698 can[name] = function(wrapped) { |
|
699 return wrapped[name].apply(wrapped, can.makeArray(arguments) |
|
700 .slice(1)); |
|
701 }; |
|
702 }); |
|
703 // Memory safe destruction. |
|
704 var oldClean = $.cleanData; |
|
705 $.cleanData = function(elems) { |
|
706 $.each(elems, function(i, elem) { |
|
707 if (elem) { |
|
708 can.trigger(elem, 'removed', [], false); |
|
709 } |
|
710 }); |
|
711 oldClean(elems); |
|
712 }; |
|
713 var oldDomManip = $.fn.domManip, |
|
714 cbIndex; |
|
715 // feature detect which domManip we are using |
|
716 $.fn.domManip = function(args, cb1, cb2) { |
|
717 for (var i = 1; i < arguments.length; i++) { |
|
718 if (typeof arguments[i] === 'function') { |
|
719 cbIndex = i; |
|
720 break; |
|
721 } |
|
722 } |
|
723 return oldDomManip.apply(this, arguments); |
|
724 }; |
|
725 $(document.createElement("div")) |
|
726 .append(document.createElement("div")); |
|
727 |
|
728 $.fn.domManip = (cbIndex === 2 ? function(args, table, callback) { |
|
729 return oldDomManip.call(this, args, table, function(elem) { |
|
730 var elems; |
|
731 if (elem.nodeType === 11) { |
|
732 elems = can.makeArray(elem.childNodes); |
|
733 } |
|
734 var ret = callback.apply(this, arguments); |
|
735 can.inserted(elems ? elems : [elem]); |
|
736 return ret; |
|
737 }); |
|
738 } : function(args, callback) { |
|
739 return oldDomManip.call(this, args, function(elem) { |
|
740 var elems; |
|
741 if (elem.nodeType === 11) { |
|
742 elems = can.makeArray(elem.childNodes); |
|
743 } |
|
744 var ret = callback.apply(this, arguments); |
|
745 can.inserted(elems ? elems : [elem]); |
|
746 return ret; |
|
747 }); |
|
748 }); |
|
749 |
|
750 if (!can.attr.MutationObserver) { |
|
751 // handle via calls to attr |
|
752 var oldAttr = $.attr; |
|
753 $.attr = function(el, attrName) { |
|
754 var oldValue, newValue; |
|
755 if (arguments.length >= 3) { |
|
756 oldValue = oldAttr.call(this, el, attrName); |
|
757 } |
|
758 var res = oldAttr.apply(this, arguments); |
|
759 if (arguments.length >= 3) { |
|
760 newValue = oldAttr.call(this, el, attrName); |
|
761 } |
|
762 if (newValue !== oldValue) { |
|
763 can.attr.trigger(el, attrName, oldValue); |
|
764 } |
|
765 return res; |
|
766 }; |
|
767 var oldRemove = $.removeAttr; |
|
768 $.removeAttr = function(el, attrName) { |
|
769 var oldValue = oldAttr.call(this, el, attrName), |
|
770 res = oldRemove.apply(this, arguments); |
|
771 |
|
772 if (oldValue != null) { |
|
773 can.attr.trigger(el, attrName, oldValue); |
|
774 } |
|
775 return res; |
|
776 }; |
|
777 $.event.special.attributes = { |
|
778 setup: function() { |
|
779 can.data(can.$(this), "canHasAttributesBindings", true); |
|
780 }, |
|
781 teardown: function() { |
|
782 $.removeData(this, "canHasAttributesBindings"); |
|
783 } |
|
784 }; |
|
785 } else { |
|
786 // setup a special events |
|
787 $.event.special.attributes = { |
|
788 setup: function() { |
|
789 var self = this; |
|
790 var observer = new can.attr.MutationObserver(function(mutations) { |
|
791 mutations.forEach(function(mutation) { |
|
792 var copy = can.simpleExtend({}, mutation); |
|
793 can.trigger(self, copy, []); |
|
794 }); |
|
795 |
|
796 }); |
|
797 observer.observe(this, { |
|
798 attributes: true, |
|
799 attributeOldValue: true |
|
800 }); |
|
801 can.data(can.$(this), "canAttributesObserver", observer); |
|
802 }, |
|
803 teardown: function() { |
|
804 can.data(can.$(this), "canAttributesObserver") |
|
805 .disconnect(); |
|
806 $.removeData(this, "canAttributesObserver"); |
|
807 |
|
808 } |
|
809 }; |
|
810 } |
|
811 |
|
812 // ## Fix build fragment. |
|
813 // In IE8, we can pass jQuery a fragment and it removes newlines. |
|
814 // This checks for that and replaces can.buildFragment with something |
|
815 // that if only a single text node is returned, returns a fragment with |
|
816 // a text node that is set to the content. |
|
817 (function() { |
|
818 |
|
819 var text = "<-\n>", |
|
820 frag = can.buildFragment(text, document); |
|
821 if (text !== frag.childNodes[0].nodeValue) { |
|
822 |
|
823 var oldBuildFragment = can.buildFragment; |
|
824 can.buildFragment = function(content, context) { |
|
825 var res = oldBuildFragment(content, context); |
|
826 if (res.childNodes.length === 1 && res.childNodes[0].nodeType === 3) { |
|
827 res.childNodes[0].nodeValue = content; |
|
828 } |
|
829 return res; |
|
830 }; |
|
831 |
|
832 } |
|
833 |
|
834 |
|
835 |
|
836 })(); |
|
837 |
|
838 $.event.special.inserted = {}; |
|
839 $.event.special.removed = {}; |
|
840 return can; |
|
841 })(jQuery, __m5, __m6, __m7, __m8, __m9); |
|
842 |
|
843 // ## can/util/string/string.js |
|
844 var __m2 = (function(can) { |
|
845 // ##string.js |
|
846 // _Miscellaneous string utility functions._ |
|
847 // Several of the methods in this plugin use code adapated from Prototype |
|
848 // Prototype JavaScript framework, version 1.6.0.1. |
|
849 // © 2005-2007 Sam Stephenson |
|
850 var strUndHash = /_|-/, |
|
851 strColons = /\=\=/, |
|
852 strWords = /([A-Z]+)([A-Z][a-z])/g, |
|
853 strLowUp = /([a-z\d])([A-Z])/g, |
|
854 strDash = /([a-z\d])([A-Z])/g, |
|
855 strReplacer = /\{([^\}]+)\}/g, |
|
856 strQuote = /"/g, |
|
857 strSingleQuote = /'/g, |
|
858 strHyphenMatch = /-+(.)?/g, |
|
859 strCamelMatch = /[a-z][A-Z]/g, |
|
860 // Returns the `prop` property from `obj`. |
|
861 // If `add` is true and `prop` doesn't exist in `obj`, create it as an |
|
862 // empty object. |
|
863 getNext = function(obj, prop, add) { |
|
864 var result = obj[prop]; |
|
865 if (result === undefined && add === true) { |
|
866 result = obj[prop] = {}; |
|
867 } |
|
868 return result; |
|
869 }, |
|
870 // Returns `true` if the object can have properties (no `null`s). |
|
871 isContainer = function(current) { |
|
872 return /^f|^o/.test(typeof current); |
|
873 }, convertBadValues = function(content) { |
|
874 // Convert bad values into empty strings |
|
875 var isInvalid = content === null || content === undefined || isNaN(content) && '' + content === 'NaN'; |
|
876 return '' + (isInvalid ? '' : content); |
|
877 }; |
|
878 can.extend(can, { |
|
879 esc: function(content) { |
|
880 return convertBadValues(content) |
|
881 .replace(/&/g, '&') |
|
882 .replace(/</g, '<') |
|
883 .replace(/>/g, '>') |
|
884 .replace(strQuote, '"') |
|
885 .replace(strSingleQuote, '''); |
|
886 }, |
|
887 getObject: function(name, roots, add) { |
|
888 // The parts of the name we are looking up |
|
889 // `['App','Models','Recipe']` |
|
890 var parts = name ? name.split('.') : [], |
|
891 length = parts.length, |
|
892 current, r = 0, |
|
893 i, container, rootsLength; |
|
894 // Make sure roots is an `array`. |
|
895 roots = can.isArray(roots) ? roots : [roots || window]; |
|
896 rootsLength = roots.length; |
|
897 if (!length) { |
|
898 return roots[0]; |
|
899 } |
|
900 // For each root, mark it as current. |
|
901 for (r; r < rootsLength; r++) { |
|
902 current = roots[r]; |
|
903 container = undefined; |
|
904 // Walk current to the 2nd to last object or until there |
|
905 // is not a container. |
|
906 for (i = 0; i < length && isContainer(current); i++) { |
|
907 container = current; |
|
908 current = getNext(container, parts[i]); |
|
909 } |
|
910 // If we found property break cycle |
|
911 if (container !== undefined && current !== undefined) { |
|
912 break; |
|
913 } |
|
914 } |
|
915 // Remove property from found container |
|
916 if (add === false && current !== undefined) { |
|
917 delete container[parts[i - 1]]; |
|
918 } |
|
919 // When adding property add it to the first root |
|
920 if (add === true && current === undefined) { |
|
921 current = roots[0]; |
|
922 for (i = 0; i < length && isContainer(current); i++) { |
|
923 current = getNext(current, parts[i], true); |
|
924 } |
|
925 } |
|
926 return current; |
|
927 }, |
|
928 capitalize: function(s, cache) { |
|
929 // Used to make newId. |
|
930 return s.charAt(0) |
|
931 .toUpperCase() + s.slice(1); |
|
932 }, |
|
933 camelize: function(str) { |
|
934 return convertBadValues(str) |
|
935 .replace(strHyphenMatch, function(match, chr) { |
|
936 return chr ? chr.toUpperCase() : ''; |
|
937 }); |
|
938 }, |
|
939 hyphenate: function(str) { |
|
940 return convertBadValues(str) |
|
941 .replace(strCamelMatch, function(str, offset) { |
|
942 return str.charAt(0) + '-' + str.charAt(1) |
|
943 .toLowerCase(); |
|
944 }); |
|
945 }, |
|
946 underscore: function(s) { |
|
947 return s.replace(strColons, '/') |
|
948 .replace(strWords, '$1_$2') |
|
949 .replace(strLowUp, '$1_$2') |
|
950 .replace(strDash, '_') |
|
951 .toLowerCase(); |
|
952 }, |
|
953 sub: function(str, data, remove) { |
|
954 var obs = []; |
|
955 str = str || ''; |
|
956 obs.push(str.replace(strReplacer, function(whole, inside) { |
|
957 // Convert inside to type. |
|
958 var ob = can.getObject(inside, data, remove === true ? false : undefined); |
|
959 if (ob === undefined || ob === null) { |
|
960 obs = null; |
|
961 return ''; |
|
962 } |
|
963 // If a container, push into objs (which will return objects found). |
|
964 if (isContainer(ob) && obs) { |
|
965 obs.push(ob); |
|
966 return ''; |
|
967 } |
|
968 return '' + ob; |
|
969 })); |
|
970 return obs === null ? obs : obs.length <= 1 ? obs[0] : obs; |
|
971 }, |
|
972 replacer: strReplacer, |
|
973 undHash: strUndHash |
|
974 }); |
|
975 return can; |
|
976 })(__m3); |
|
977 |
|
978 // ## can/construct/construct.js |
|
979 var __m1 = (function(can) { |
|
980 // ## construct.js |
|
981 // `can.Construct` |
|
982 // _This is a modified version of |
|
983 // [John Resig's class](http://ejohn.org/blog/simple-javascript-inheritance/). |
|
984 // It provides class level inheritance and callbacks._ |
|
985 // A private flag used to initialize a new class instance without |
|
986 // initializing it's bindings. |
|
987 var initializing = 0; |
|
988 |
|
989 can.Construct = function() { |
|
990 if (arguments.length) { |
|
991 return can.Construct.extend.apply(can.Construct, arguments); |
|
992 } |
|
993 }; |
|
994 |
|
995 can.extend(can.Construct, { |
|
996 |
|
997 constructorExtends: true, |
|
998 |
|
999 newInstance: function() { |
|
1000 // Get a raw instance object (`init` is not called). |
|
1001 var inst = this.instance(), |
|
1002 args; |
|
1003 // Call `setup` if there is a `setup` |
|
1004 if (inst.setup) { |
|
1005 args = inst.setup.apply(inst, arguments); |
|
1006 } |
|
1007 // Call `init` if there is an `init` |
|
1008 // If `setup` returned `args`, use those as the arguments |
|
1009 if (inst.init) { |
|
1010 inst.init.apply(inst, args || arguments); |
|
1011 } |
|
1012 return inst; |
|
1013 }, |
|
1014 // Overwrites an object with methods. Used in the `super` plugin. |
|
1015 // `newProps` - New properties to add. |
|
1016 // `oldProps` - Where the old properties might be (used with `super`). |
|
1017 // `addTo` - What we are adding to. |
|
1018 _inherit: function(newProps, oldProps, addTo) { |
|
1019 can.extend(addTo || newProps, newProps || {}); |
|
1020 }, |
|
1021 // used for overwriting a single property. |
|
1022 // this should be used for patching other objects |
|
1023 // the super plugin overwrites this |
|
1024 _overwrite: function(what, oldProps, propName, val) { |
|
1025 what[propName] = val; |
|
1026 }, |
|
1027 // Set `defaults` as the merger of the parent `defaults` and this |
|
1028 // object's `defaults`. If you overwrite this method, make sure to |
|
1029 // include option merging logic. |
|
1030 |
|
1031 setup: function(base, fullName) { |
|
1032 this.defaults = can.extend(true, {}, base.defaults, this.defaults); |
|
1033 }, |
|
1034 // Create's a new `class` instance without initializing by setting the |
|
1035 // `initializing` flag. |
|
1036 instance: function() { |
|
1037 // Prevents running `init`. |
|
1038 initializing = 1; |
|
1039 var inst = new this(); |
|
1040 // Allow running `init`. |
|
1041 initializing = 0; |
|
1042 return inst; |
|
1043 }, |
|
1044 // Extends classes. |
|
1045 |
|
1046 extend: function(fullName, klass, proto) { |
|
1047 // Figure out what was passed and normalize it. |
|
1048 if (typeof fullName !== 'string') { |
|
1049 proto = klass; |
|
1050 klass = fullName; |
|
1051 fullName = null; |
|
1052 } |
|
1053 if (!proto) { |
|
1054 proto = klass; |
|
1055 klass = null; |
|
1056 } |
|
1057 proto = proto || {}; |
|
1058 var _super_class = this, |
|
1059 _super = this.prototype, |
|
1060 parts, current, _fullName, _shortName, name, shortName, namespace, prototype; |
|
1061 // Instantiate a base class (but only create the instance, |
|
1062 // don't run the init constructor). |
|
1063 prototype = this.instance(); |
|
1064 // Copy the properties over onto the new prototype. |
|
1065 can.Construct._inherit(proto, _super, prototype); |
|
1066 // The dummy class constructor. |
|
1067 |
|
1068 function Constructor() { |
|
1069 // All construction is actually done in the init method. |
|
1070 if (!initializing) { |
|
1071 |
|
1072 |
|
1073 return this.constructor !== Constructor && |
|
1074 // We are being called without `new` or we are extending. |
|
1075 arguments.length && Constructor.constructorExtends ? Constructor.extend.apply(Constructor, arguments) : |
|
1076 // We are being called with `new`. |
|
1077 Constructor.newInstance.apply(Constructor, arguments); |
|
1078 } |
|
1079 } |
|
1080 // Copy old stuff onto class (can probably be merged w/ inherit) |
|
1081 for (name in _super_class) { |
|
1082 if (_super_class.hasOwnProperty(name)) { |
|
1083 Constructor[name] = _super_class[name]; |
|
1084 } |
|
1085 } |
|
1086 // Copy new static properties on class. |
|
1087 can.Construct._inherit(klass, _super_class, Constructor); |
|
1088 // Setup namespaces. |
|
1089 if (fullName) { |
|
1090 |
|
1091 parts = fullName.split('.'); |
|
1092 shortName = parts.pop(); |
|
1093 current = can.getObject(parts.join('.'), window, true); |
|
1094 namespace = current; |
|
1095 _fullName = can.underscore(fullName.replace(/\./g, "_")); |
|
1096 _shortName = can.underscore(shortName); |
|
1097 |
|
1098 |
|
1099 |
|
1100 current[shortName] = Constructor; |
|
1101 } |
|
1102 // Set things that shouldn't be overwritten. |
|
1103 can.extend(Constructor, { |
|
1104 constructor: Constructor, |
|
1105 prototype: prototype, |
|
1106 |
|
1107 namespace: namespace, |
|
1108 |
|
1109 _shortName: _shortName, |
|
1110 |
|
1111 fullName: fullName, |
|
1112 _fullName: _fullName |
|
1113 }); |
|
1114 // Dojo and YUI extend undefined |
|
1115 if (shortName !== undefined) { |
|
1116 Constructor.shortName = shortName; |
|
1117 } |
|
1118 // Make sure our prototype looks nice. |
|
1119 Constructor.prototype.constructor = Constructor; |
|
1120 // Call the class `setup` and `init` |
|
1121 var t = [_super_class].concat(can.makeArray(arguments)), |
|
1122 args = Constructor.setup.apply(Constructor, t); |
|
1123 if (Constructor.init) { |
|
1124 Constructor.init.apply(Constructor, args || t); |
|
1125 } |
|
1126 |
|
1127 return Constructor; |
|
1128 } |
|
1129 }); |
|
1130 |
|
1131 can.Construct.prototype.setup = function() {}; |
|
1132 |
|
1133 can.Construct.prototype.init = function() {}; |
|
1134 return can.Construct; |
|
1135 })(__m2); |
|
1136 |
|
1137 // ## can/util/bind/bind.js |
|
1138 var __m11 = (function(can) { |
|
1139 |
|
1140 // ## Bind helpers |
|
1141 can.bindAndSetup = function() { |
|
1142 // Add the event to this object |
|
1143 can.addEvent.apply(this, arguments); |
|
1144 // If not initializing, and the first binding |
|
1145 // call bindsetup if the function exists. |
|
1146 if (!this._init) { |
|
1147 if (!this._bindings) { |
|
1148 this._bindings = 1; |
|
1149 // setup live-binding |
|
1150 if (this._bindsetup) { |
|
1151 this._bindsetup(); |
|
1152 } |
|
1153 } else { |
|
1154 this._bindings++; |
|
1155 } |
|
1156 } |
|
1157 return this; |
|
1158 }; |
|
1159 can.unbindAndTeardown = function(ev, handler) { |
|
1160 // Remove the event handler |
|
1161 can.removeEvent.apply(this, arguments); |
|
1162 if (this._bindings === null) { |
|
1163 this._bindings = 0; |
|
1164 } else { |
|
1165 this._bindings--; |
|
1166 } |
|
1167 // If there are no longer any bindings and |
|
1168 // there is a bindteardown method, call it. |
|
1169 if (!this._bindings && this._bindteardown) { |
|
1170 this._bindteardown(); |
|
1171 } |
|
1172 return this; |
|
1173 }; |
|
1174 return can; |
|
1175 })(__m3); |
|
1176 |
|
1177 // ## can/map/bubble.js |
|
1178 var __m12 = (function(can) { |
|
1179 |
|
1180 |
|
1181 |
|
1182 var bubble = can.bubble = { |
|
1183 // Given a binding, returns a string event name used to set up bubbline. |
|
1184 // If no binding should be done, undefined or null should be returned |
|
1185 event: function(map, eventName) { |
|
1186 return map.constructor._bubbleRule(eventName, map); |
|
1187 }, |
|
1188 childrenOf: function(parentMap, eventName) { |
|
1189 |
|
1190 parentMap._each(function(child, prop) { |
|
1191 if (child && child.bind) { |
|
1192 bubble.toParent(child, parentMap, prop, eventName); |
|
1193 } |
|
1194 }); |
|
1195 |
|
1196 }, |
|
1197 teardownChildrenFrom: function(parentMap, eventName) { |
|
1198 parentMap._each(function(child) { |
|
1199 |
|
1200 bubble.teardownFromParent(parentMap, child, eventName); |
|
1201 |
|
1202 }); |
|
1203 }, |
|
1204 toParent: function(child, parent, prop, eventName) { |
|
1205 can.listenTo.call(parent, child, eventName, function() { |
|
1206 // `batchTrigger` the type on this... |
|
1207 var args = can.makeArray(arguments), |
|
1208 ev = args.shift(); |
|
1209 |
|
1210 args[0] = |
|
1211 (can.List && parent instanceof can.List ? |
|
1212 parent.indexOf(child) : |
|
1213 prop) + (args[0] ? "." + args[0] : ""); |
|
1214 |
|
1215 // track objects dispatched on this map |
|
1216 ev.triggeredNS = ev.triggeredNS || {}; |
|
1217 |
|
1218 // if it has already been dispatched exit |
|
1219 if (ev.triggeredNS[parent._cid]) { |
|
1220 return; |
|
1221 } |
|
1222 |
|
1223 ev.triggeredNS[parent._cid] = true; |
|
1224 // send change event with modified attr to parent |
|
1225 can.trigger(parent, ev, args); |
|
1226 }); |
|
1227 }, |
|
1228 teardownFromParent: function(parent, child, eventName) { |
|
1229 if (child && child.unbind) { |
|
1230 can.stopListening.call(parent, child, eventName); |
|
1231 } |
|
1232 }, |
|
1233 bind: function(parent, eventName) { |
|
1234 if (!parent._init) { |
|
1235 var bubbleEvent = bubble.event(parent, eventName); |
|
1236 if (bubbleEvent) { |
|
1237 if (!parent._bubbleBindings) { |
|
1238 parent._bubbleBindings = {}; |
|
1239 } |
|
1240 if (!parent._bubbleBindings[bubbleEvent]) { |
|
1241 parent._bubbleBindings[bubbleEvent] = 1; |
|
1242 // setup live-binding |
|
1243 bubble.childrenOf(parent, bubbleEvent); |
|
1244 } else { |
|
1245 parent._bubbleBindings[bubbleEvent]++; |
|
1246 } |
|
1247 |
|
1248 } |
|
1249 } |
|
1250 }, |
|
1251 unbind: function(parent, eventName) { |
|
1252 var bubbleEvent = bubble.event(parent, eventName); |
|
1253 if (bubbleEvent) { |
|
1254 if (parent._bubbleBindings) { |
|
1255 parent._bubbleBindings[bubbleEvent]--; |
|
1256 } |
|
1257 |
|
1258 if (!parent._bubbleBindings[bubbleEvent]) { |
|
1259 delete parent._bubbleBindings[bubbleEvent]; |
|
1260 bubble.teardownChildrenFrom(parent, bubbleEvent); |
|
1261 if (can.isEmptyObject(parent._bubbleBindings)) { |
|
1262 delete parent._bubbleBindings; |
|
1263 } |
|
1264 } |
|
1265 } |
|
1266 }, |
|
1267 add: function(parent, child, prop) { |
|
1268 if (child instanceof can.Map && parent._bubbleBindings) { |
|
1269 for (var eventName in parent._bubbleBindings) { |
|
1270 if (parent._bubbleBindings[eventName]) { |
|
1271 bubble.teardownFromParent(parent, child, eventName); |
|
1272 bubble.toParent(child, parent, prop, eventName); |
|
1273 } |
|
1274 } |
|
1275 } |
|
1276 }, |
|
1277 removeMany: function(parent, children) { |
|
1278 for (var i = 0, len = children.length; i < len; i++) { |
|
1279 bubble.remove(parent, children[i]); |
|
1280 } |
|
1281 }, |
|
1282 remove: function(parent, child) { |
|
1283 if (child instanceof can.Map && parent._bubbleBindings) { |
|
1284 for (var eventName in parent._bubbleBindings) { |
|
1285 if (parent._bubbleBindings[eventName]) { |
|
1286 bubble.teardownFromParent(parent, child, eventName); |
|
1287 } |
|
1288 } |
|
1289 } |
|
1290 }, |
|
1291 set: function(parent, prop, value, current) { |
|
1292 |
|
1293 //var res = parent.__type(value, prop); |
|
1294 if (can.Map.helpers.isObservable(value)) { |
|
1295 bubble.add(parent, value, prop); |
|
1296 } |
|
1297 // bubble.add will remove, so only remove if we are replacing another object |
|
1298 if (can.Map.helpers.isObservable(current)) { |
|
1299 bubble.remove(parent, current); |
|
1300 } |
|
1301 return value; |
|
1302 } |
|
1303 }; |
|
1304 |
|
1305 return bubble; |
|
1306 |
|
1307 })(__m3); |
|
1308 |
|
1309 // ## can/util/batch/batch.js |
|
1310 var __m13 = (function(can) { |
|
1311 // Which batch of events this is for -- might not want to send multiple |
|
1312 // messages on the same batch. This is mostly for event delegation. |
|
1313 var batchNum = 1, |
|
1314 // how many times has start been called without a stop |
|
1315 transactions = 0, |
|
1316 // an array of events within a transaction |
|
1317 batchEvents = [], |
|
1318 stopCallbacks = []; |
|
1319 can.batch = { |
|
1320 |
|
1321 start: function(batchStopHandler) { |
|
1322 transactions++; |
|
1323 if (batchStopHandler) { |
|
1324 stopCallbacks.push(batchStopHandler); |
|
1325 } |
|
1326 }, |
|
1327 |
|
1328 stop: function(force, callStart) { |
|
1329 if (force) { |
|
1330 transactions = 0; |
|
1331 } else { |
|
1332 transactions--; |
|
1333 } |
|
1334 if (transactions === 0) { |
|
1335 var items = batchEvents.slice(0), |
|
1336 callbacks = stopCallbacks.slice(0), |
|
1337 i, len; |
|
1338 batchEvents = []; |
|
1339 stopCallbacks = []; |
|
1340 batchNum++; |
|
1341 if (callStart) { |
|
1342 can.batch.start(); |
|
1343 } |
|
1344 for (i = 0, len = items.length; i < len; i++) { |
|
1345 can.trigger.apply(can, items[i]); |
|
1346 } |
|
1347 for (i = 0, len = callbacks.length; i < callbacks.length; i++) { |
|
1348 callbacks[i](); |
|
1349 } |
|
1350 } |
|
1351 }, |
|
1352 |
|
1353 trigger: function(item, event, args) { |
|
1354 // Don't send events if initalizing. |
|
1355 if (!item._init) { |
|
1356 if (transactions === 0) { |
|
1357 return can.trigger(item, event, args); |
|
1358 } else { |
|
1359 event = typeof event === 'string' ? { |
|
1360 type: event |
|
1361 } : event; |
|
1362 event.batchNum = batchNum; |
|
1363 batchEvents.push([ |
|
1364 item, |
|
1365 event, |
|
1366 args |
|
1367 ]); |
|
1368 } |
|
1369 } |
|
1370 } |
|
1371 }; |
|
1372 })(__m5); |
|
1373 |
|
1374 // ## can/map/map.js |
|
1375 var __m10 = (function(can, bind, bubble) { |
|
1376 // ## Helpers |
|
1377 |
|
1378 // A temporary map of Maps that have been made from plain JS objects. |
|
1379 var madeMap = null; |
|
1380 // Clears out map of converted objects. |
|
1381 var teardownMap = function() { |
|
1382 for (var cid in madeMap) { |
|
1383 if (madeMap[cid].added) { |
|
1384 delete madeMap[cid].obj._cid; |
|
1385 } |
|
1386 } |
|
1387 madeMap = null; |
|
1388 }; |
|
1389 // Retrieves a Map instance from an Object. |
|
1390 var getMapFromObject = function(obj) { |
|
1391 return madeMap && madeMap[obj._cid] && madeMap[obj._cid].instance; |
|
1392 }; |
|
1393 // A temporary map of Maps |
|
1394 var serializeMap = null; |
|
1395 |
|
1396 |
|
1397 var Map = can.Map = can.Construct.extend({ |
|
1398 |
|
1399 setup: function() { |
|
1400 |
|
1401 can.Construct.setup.apply(this, arguments); |
|
1402 |
|
1403 // Do not run if we are defining can.Map. |
|
1404 if (can.Map) { |
|
1405 if (!this.defaults) { |
|
1406 this.defaults = {}; |
|
1407 } |
|
1408 // Builds a list of compute and non-compute properties in this Object's prototype. |
|
1409 this._computes = []; |
|
1410 |
|
1411 for (var prop in this.prototype) { |
|
1412 // Non-functions are regular defaults. |
|
1413 if (prop !== "define" && typeof this.prototype[prop] !== "function") { |
|
1414 this.defaults[prop] = this.prototype[prop]; |
|
1415 // Functions with an `isComputed` property are computes. |
|
1416 } else if (this.prototype[prop].isComputed) { |
|
1417 this._computes.push(prop); |
|
1418 } |
|
1419 } |
|
1420 this.helpers.define(this); |
|
1421 } |
|
1422 // If we inherit from can.Map, but not can.List, make sure any lists are the correct type. |
|
1423 if (can.List && !(this.prototype instanceof can.List)) { |
|
1424 this.List = Map.List.extend({ |
|
1425 Map: this |
|
1426 }, {}); |
|
1427 } |
|
1428 |
|
1429 }, |
|
1430 // Reference to bubbling helpers. |
|
1431 _bubble: bubble, |
|
1432 // Given an eventName, determine if bubbling should be setup. |
|
1433 _bubbleRule: function(eventName) { |
|
1434 return (eventName === "change" || eventName.indexOf(".") >= 0) && "change"; |
|
1435 }, |
|
1436 // List of computes on the Map's prototype. |
|
1437 _computes: [], |
|
1438 // Adds an event to this Map. |
|
1439 bind: can.bindAndSetup, |
|
1440 on: can.bindAndSetup, |
|
1441 // Removes an event from this Map. |
|
1442 unbind: can.unbindAndTeardown, |
|
1443 off: can.unbindAndTeardown, |
|
1444 // Name of the id field. Used in can.Model. |
|
1445 id: "id", |
|
1446 // ## Internal helpers |
|
1447 helpers: { |
|
1448 // ### can.Map.helpers.define |
|
1449 // Stub function for the define plugin. |
|
1450 define: function() {}, |
|
1451 |
|
1452 // ### can.Map.helpers.attrParts |
|
1453 // Parses attribute name into its parts. |
|
1454 attrParts: function(attr, keepKey) { |
|
1455 //Keep key intact |
|
1456 if (keepKey) { |
|
1457 return [attr]; |
|
1458 } |
|
1459 // Split key on '.' |
|
1460 return can.isArray(attr) ? attr : ("" + attr) |
|
1461 .split("."); |
|
1462 }, |
|
1463 |
|
1464 // ### can.Map.helpers.addToMap |
|
1465 // Tracks Map instances created from JS Objects |
|
1466 addToMap: function(obj, instance) { |
|
1467 var teardown; |
|
1468 // Setup a fresh mapping if `madeMap` is missing. |
|
1469 if (!madeMap) { |
|
1470 teardown = teardownMap; |
|
1471 madeMap = {}; |
|
1472 } |
|
1473 // Record if Object has a `_cid` before adding one. |
|
1474 var hasCid = obj._cid; |
|
1475 var cid = can.cid(obj); |
|
1476 |
|
1477 // Only update if there already isn't one already. |
|
1478 if (!madeMap[cid]) { |
|
1479 |
|
1480 madeMap[cid] = { |
|
1481 obj: obj, |
|
1482 instance: instance, |
|
1483 added: !hasCid |
|
1484 }; |
|
1485 } |
|
1486 return teardown; |
|
1487 }, |
|
1488 |
|
1489 // ### can.Map.helpers.isObservable |
|
1490 // Determines if `obj` is observable. |
|
1491 isObservable: function(obj) { |
|
1492 return obj instanceof can.Map || (obj && obj === can.route); |
|
1493 }, |
|
1494 |
|
1495 // ### can.Map.helpers.canMakeObserve |
|
1496 // Determines if an object can be made into an observable. |
|
1497 canMakeObserve: function(obj) { |
|
1498 return obj && !can.isDeferred(obj) && (can.isArray(obj) || can.isPlainObject(obj)); |
|
1499 }, |
|
1500 |
|
1501 // ### can.Map.helpers.serialize |
|
1502 // Serializes a Map or Map.List |
|
1503 serialize: function(map, how, where) { |
|
1504 var cid = can.cid(map), |
|
1505 firstSerialize = false; |
|
1506 if (!serializeMap) { |
|
1507 firstSerialize = true; |
|
1508 // Serialize might call .attr() so we need to keep different map |
|
1509 serializeMap = { |
|
1510 attr: {}, |
|
1511 serialize: {} |
|
1512 }; |
|
1513 } |
|
1514 serializeMap[how][cid] = where; |
|
1515 // Go through each property. |
|
1516 map.each(function(val, name) { |
|
1517 // If the value is an `object`, and has an `attrs` or `serialize` function. |
|
1518 var result, |
|
1519 isObservable = Map.helpers.isObservable(val), |
|
1520 serialized = isObservable && serializeMap[how][can.cid(val)]; |
|
1521 if (serialized) { |
|
1522 result = serialized; |
|
1523 } else { |
|
1524 if (how === "serialize") { |
|
1525 result = Map.helpers._serialize(map, name, val); |
|
1526 } else { |
|
1527 result = Map.helpers._getValue(map, name, val, how); |
|
1528 } |
|
1529 } |
|
1530 // this is probably removable |
|
1531 if (result !== undefined) { |
|
1532 where[name] = result; |
|
1533 } |
|
1534 }); |
|
1535 |
|
1536 can.__reading(map, '__keys'); |
|
1537 if (firstSerialize) { |
|
1538 serializeMap = null; |
|
1539 } |
|
1540 return where; |
|
1541 }, |
|
1542 _serialize: function(map, name, val) { |
|
1543 return Map.helpers._getValue(map, name, val, "serialize"); |
|
1544 }, |
|
1545 _getValue: function(map, name, val, how) { |
|
1546 if (Map.helpers.isObservable(val)) { |
|
1547 return val[how](); |
|
1548 } else { |
|
1549 return val; |
|
1550 } |
|
1551 } |
|
1552 }, |
|
1553 |
|
1554 keys: function(map) { |
|
1555 var keys = []; |
|
1556 can.__reading(map, '__keys'); |
|
1557 for (var keyName in map._data) { |
|
1558 keys.push(keyName); |
|
1559 } |
|
1560 return keys; |
|
1561 } |
|
1562 }, |
|
1563 |
|
1564 { |
|
1565 setup: function(obj) { |
|
1566 // `_data` is where we keep the properties. |
|
1567 this._data = {}; |
|
1568 |
|
1569 // The namespace this `object` uses to listen to events. |
|
1570 can.cid(this, ".map"); |
|
1571 // Sets all `attrs`. |
|
1572 this._init = 1; |
|
1573 // It's handy if we pass this to comptues, because computes can have a default value. |
|
1574 var defaultValues = this._setupDefaults(); |
|
1575 this._setupComputes(defaultValues); |
|
1576 var teardownMapping = obj && can.Map.helpers.addToMap(obj, this); |
|
1577 |
|
1578 var data = can.extend(can.extend(true, {}, defaultValues), obj); |
|
1579 |
|
1580 this.attr(data); |
|
1581 |
|
1582 if (teardownMapping) { |
|
1583 teardownMapping(); |
|
1584 } |
|
1585 |
|
1586 // `batchTrigger` change events. |
|
1587 this.bind('change', can.proxy(this._changes, this)); |
|
1588 |
|
1589 delete this._init; |
|
1590 }, |
|
1591 // Sets up computed properties on a Map. |
|
1592 _setupComputes: function() { |
|
1593 var computes = this.constructor._computes; |
|
1594 this._computedBindings = {}; |
|
1595 |
|
1596 for (var i = 0, len = computes.length, prop; i < len; i++) { |
|
1597 prop = computes[i]; |
|
1598 // Make the context of the compute the current Map |
|
1599 this[prop] = this[prop].clone(this); |
|
1600 // Keep track of computed properties |
|
1601 this._computedBindings[prop] = { |
|
1602 count: 0 |
|
1603 }; |
|
1604 } |
|
1605 }, |
|
1606 _setupDefaults: function() { |
|
1607 return this.constructor.defaults || {}; |
|
1608 }, |
|
1609 // Setup child bindings. |
|
1610 _bindsetup: function() {}, |
|
1611 // Teardown child bindings. |
|
1612 _bindteardown: function() {}, |
|
1613 // `change`event handler. |
|
1614 _changes: function(ev, attr, how, newVal, oldVal) { |
|
1615 // when a change happens, create the named event. |
|
1616 can.batch.trigger(this, { |
|
1617 type: attr, |
|
1618 batchNum: ev.batchNum |
|
1619 }, [newVal, oldVal]); |
|
1620 |
|
1621 if (how === "remove" || how === "add") { |
|
1622 can.batch.trigger(this, { |
|
1623 type: "__keys", |
|
1624 batchNum: ev.batchNum |
|
1625 }); |
|
1626 } |
|
1627 }, |
|
1628 // Trigger a change event. |
|
1629 _triggerChange: function(attr, how, newVal, oldVal) { |
|
1630 can.batch.trigger(this, "change", can.makeArray(arguments)); |
|
1631 }, |
|
1632 // Iterator that does not trigger live binding. |
|
1633 _each: function(callback) { |
|
1634 var data = this.__get(); |
|
1635 for (var prop in data) { |
|
1636 if (data.hasOwnProperty(prop)) { |
|
1637 callback(data[prop], prop); |
|
1638 } |
|
1639 } |
|
1640 }, |
|
1641 |
|
1642 attr: function(attr, val) { |
|
1643 // This is super obfuscated for space -- basically, we're checking |
|
1644 // if the type of the attribute is not a `number` or a `string`. |
|
1645 var type = typeof attr; |
|
1646 if (type !== "string" && type !== "number") { |
|
1647 return this._attrs(attr, val); |
|
1648 // If we are getting a value. |
|
1649 } else if (arguments.length === 1) { |
|
1650 // Let people know we are reading. |
|
1651 can.__reading(this, attr); |
|
1652 return this._get(attr); |
|
1653 } else { |
|
1654 // Otherwise we are setting. |
|
1655 this._set(attr, val); |
|
1656 return this; |
|
1657 } |
|
1658 }, |
|
1659 |
|
1660 each: function() { |
|
1661 return can.each.apply(undefined, [this].concat(can.makeArray(arguments))); |
|
1662 }, |
|
1663 |
|
1664 removeAttr: function(attr) { |
|
1665 // If this is List. |
|
1666 var isList = can.List && this instanceof can.List, |
|
1667 // Convert the `attr` into parts (if nested). |
|
1668 parts = can.Map.helpers.attrParts(attr), |
|
1669 // The actual property to remove. |
|
1670 prop = parts.shift(), |
|
1671 // The current value. |
|
1672 current = isList ? this[prop] : this._data[prop]; |
|
1673 |
|
1674 // If we have more parts, call `removeAttr` on that part. |
|
1675 if (parts.length && current) { |
|
1676 return current.removeAttr(parts); |
|
1677 } else { |
|
1678 |
|
1679 // If attr does not have a `.` |
|
1680 if (typeof attr === 'string' && !! ~attr.indexOf('.')) { |
|
1681 prop = attr; |
|
1682 } |
|
1683 |
|
1684 this._remove(prop, current); |
|
1685 return current; |
|
1686 } |
|
1687 }, |
|
1688 // Remove a property. |
|
1689 _remove: function(prop, current) { |
|
1690 if (prop in this._data) { |
|
1691 // Delete the property from `_data` and the Map |
|
1692 // as long as it isn't part of the Map's prototype. |
|
1693 delete this._data[prop]; |
|
1694 if (!(prop in this.constructor.prototype)) { |
|
1695 delete this[prop]; |
|
1696 } |
|
1697 // Let others now this property has been removed. |
|
1698 this._triggerChange(prop, "remove", undefined, current); |
|
1699 |
|
1700 } |
|
1701 }, |
|
1702 // Reads a property from the `object`. |
|
1703 _get: function(attr) { |
|
1704 var value; |
|
1705 // Handles the case of a key having a `.` in its name |
|
1706 if (typeof attr === 'string' && !! ~attr.indexOf('.')) { |
|
1707 // Attempt to get the value |
|
1708 value = this.__get(attr); |
|
1709 // For keys with a `.` in them, value will be defined |
|
1710 if (value !== undefined) { |
|
1711 return value; |
|
1712 } |
|
1713 } |
|
1714 |
|
1715 // Otherwise we have to dig deeper into the Map to get the value. |
|
1716 // First, break up the attr (`"foo.bar"`) into parts like `["foo","bar"]`. |
|
1717 var parts = can.Map.helpers.attrParts(attr), |
|
1718 // Then get the value of the first attr name (`"foo"`). |
|
1719 current = this.__get(parts.shift()); |
|
1720 // If there are other attributes to read... |
|
1721 return parts.length ? |
|
1722 // and current has a value... |
|
1723 current ? |
|
1724 // then lookup the remaining attrs on current |
|
1725 current._get(parts) : |
|
1726 // or if there's no current, return undefined. |
|
1727 undefined : |
|
1728 // If there are no more parts, return current. |
|
1729 current; |
|
1730 }, |
|
1731 // Reads a property directly if an `attr` is provided, otherwise |
|
1732 // returns the "real" data object itself. |
|
1733 __get: function(attr) { |
|
1734 if (attr) { |
|
1735 // If property is a compute return the result, otherwise get the value directly |
|
1736 if (this._computedBindings[attr]) { |
|
1737 return this[attr](); |
|
1738 } else { |
|
1739 return this._data[attr]; |
|
1740 } |
|
1741 // If not property is provided, return entire `_data` object |
|
1742 } else { |
|
1743 return this._data; |
|
1744 } |
|
1745 }, |
|
1746 // converts the value into an observable if needed |
|
1747 __type: function(value, prop) { |
|
1748 // If we are getting an object. |
|
1749 if (!(value instanceof can.Map) && can.Map.helpers.canMakeObserve(value)) { |
|
1750 |
|
1751 var cached = getMapFromObject(value); |
|
1752 if (cached) { |
|
1753 return cached; |
|
1754 } |
|
1755 if (can.isArray(value)) { |
|
1756 var List = can.List; |
|
1757 return new List(value); |
|
1758 } else { |
|
1759 var Map = this.constructor.Map || can.Map; |
|
1760 return new Map(value); |
|
1761 } |
|
1762 } |
|
1763 return value; |
|
1764 }, |
|
1765 // Sets `attr` prop as value on this object where. |
|
1766 // `attr` - Is a string of properties or an array of property values. |
|
1767 // `value` - The raw value to set. |
|
1768 _set: function(attr, value, keepKey) { |
|
1769 // Convert `attr` to attr parts (if it isn't already). |
|
1770 var parts = can.Map.helpers.attrParts(attr, keepKey), |
|
1771 // The immediate prop we are setting. |
|
1772 prop = parts.shift(), |
|
1773 // We only need to get the current value if we are not in init. |
|
1774 current = this._init ? undefined : this.__get(prop); |
|
1775 |
|
1776 if (parts.length && Map.helpers.isObservable(current)) { |
|
1777 // If we have an `object` and remaining parts that `object` should set it. |
|
1778 current._set(parts, value); |
|
1779 } else if (!parts.length) { |
|
1780 // We're in "real" set territory. |
|
1781 if (this.__convert) { |
|
1782 //Convert if there is a converter |
|
1783 value = this.__convert(prop, value); |
|
1784 } |
|
1785 this.__set(prop, this.__type(value, prop), current); |
|
1786 } else { |
|
1787 throw "can.Map: Object does not exist"; |
|
1788 } |
|
1789 }, |
|
1790 __set: function(prop, value, current) { |
|
1791 // TODO: Check if value is object and transform. |
|
1792 // Don't do anything if the value isn't changing. |
|
1793 if (value !== current) { |
|
1794 // Check if we are adding this for the first time -- |
|
1795 // if we are, we need to create an `add` event. |
|
1796 var changeType = this.__get() |
|
1797 .hasOwnProperty(prop) ? "set" : "add"; |
|
1798 |
|
1799 // Set the value on `_data` and hook it up to send event. |
|
1800 this.___set(prop, this.constructor._bubble.set(this, prop, value, current)); |
|
1801 |
|
1802 // `batchTrigger` the change event. |
|
1803 this._triggerChange(prop, changeType, value, current); |
|
1804 |
|
1805 // If we can stop listening to our old value, do it. |
|
1806 if (current) { |
|
1807 this.constructor._bubble.teardownFromParent(this, current); |
|
1808 } |
|
1809 } |
|
1810 |
|
1811 }, |
|
1812 // Directly sets a property on this `object`. |
|
1813 ___set: function(prop, val) { |
|
1814 if (this._computedBindings[prop]) { |
|
1815 this[prop](val); |
|
1816 } else { |
|
1817 this._data[prop] = val; |
|
1818 } |
|
1819 // Add property directly for easy writing. |
|
1820 // Check if its on the `prototype` so we don't overwrite methods like `attrs`. |
|
1821 if (!can.isFunction(this.constructor.prototype[prop]) && !this._computedBindings[prop]) { |
|
1822 this[prop] = val; |
|
1823 } |
|
1824 }, |
|
1825 |
|
1826 bind: function(eventName, handler) { |
|
1827 var computedBinding = this._computedBindings && this._computedBindings[eventName]; |
|
1828 if (computedBinding) { |
|
1829 // The first time we bind to this computed property we |
|
1830 // initialize `count` and `batchTrigger` the change event. |
|
1831 if (!computedBinding.count) { |
|
1832 computedBinding.count = 1; |
|
1833 var self = this; |
|
1834 computedBinding.handler = function(ev, newVal, oldVal) { |
|
1835 can.batch.trigger(self, { |
|
1836 type: eventName, |
|
1837 batchNum: ev.batchNum |
|
1838 }, [newVal, oldVal]); |
|
1839 }; |
|
1840 this[eventName].bind("change", computedBinding.handler); |
|
1841 } else { |
|
1842 // Increment number of things listening to this computed property. |
|
1843 computedBinding.count++; |
|
1844 } |
|
1845 |
|
1846 } |
|
1847 // The first time we bind to this Map, `_bindsetup` will |
|
1848 // be called to setup child event bubbling. |
|
1849 this.constructor._bubble.bind(this, eventName); |
|
1850 return can.bindAndSetup.apply(this, arguments); |
|
1851 |
|
1852 }, |
|
1853 |
|
1854 unbind: function(eventName, handler) { |
|
1855 var computedBinding = this._computedBindings && this._computedBindings[eventName]; |
|
1856 if (computedBinding) { |
|
1857 // If there is only one listener, we unbind the change event handler |
|
1858 // and clean it up since no one is listening to this property any more. |
|
1859 if (computedBinding.count === 1) { |
|
1860 computedBinding.count = 0; |
|
1861 this[eventName].unbind("change", computedBinding.handler); |
|
1862 delete computedBinding.handler; |
|
1863 } else { |
|
1864 // Decrement number of things listening to this computed property |
|
1865 computedBinding.count--; |
|
1866 } |
|
1867 |
|
1868 } |
|
1869 this.constructor._bubble.unbind(this, eventName); |
|
1870 return can.unbindAndTeardown.apply(this, arguments); |
|
1871 |
|
1872 }, |
|
1873 |
|
1874 serialize: function() { |
|
1875 return can.Map.helpers.serialize(this, 'serialize', {}); |
|
1876 }, |
|
1877 |
|
1878 _attrs: function(props, remove) { |
|
1879 if (props === undefined) { |
|
1880 return Map.helpers.serialize(this, 'attr', {}); |
|
1881 } |
|
1882 |
|
1883 props = can.simpleExtend({}, props); |
|
1884 var prop, |
|
1885 self = this, |
|
1886 newVal; |
|
1887 |
|
1888 // Batch all of the change events until we are done. |
|
1889 can.batch.start(); |
|
1890 // Merge current properties with the new ones. |
|
1891 this.each(function(curVal, prop) { |
|
1892 // You can not have a _cid property; abort. |
|
1893 if (prop === "_cid") { |
|
1894 return; |
|
1895 } |
|
1896 newVal = props[prop]; |
|
1897 |
|
1898 // If we are merging, remove the property if it has no value. |
|
1899 if (newVal === undefined) { |
|
1900 if (remove) { |
|
1901 self.removeAttr(prop); |
|
1902 } |
|
1903 return; |
|
1904 } |
|
1905 |
|
1906 // Run converter if there is one |
|
1907 if (self.__convert) { |
|
1908 newVal = self.__convert(prop, newVal); |
|
1909 } |
|
1910 |
|
1911 // If we're dealing with models, we want to call _set to let converters run. |
|
1912 if (Map.helpers.isObservable(newVal)) { |
|
1913 |
|
1914 self.__set(prop, self.__type(newVal, prop), curVal); |
|
1915 // If its an object, let attr merge. |
|
1916 } else if (Map.helpers.isObservable(curVal) && Map.helpers.canMakeObserve(newVal)) { |
|
1917 curVal.attr(newVal, remove); |
|
1918 // Otherwise just set. |
|
1919 } else if (curVal !== newVal) { |
|
1920 self.__set(prop, self.__type(newVal, prop), curVal); |
|
1921 } |
|
1922 |
|
1923 delete props[prop]; |
|
1924 }); |
|
1925 // Add remaining props. |
|
1926 for (prop in props) { |
|
1927 // Ignore _cid. |
|
1928 if (prop !== "_cid") { |
|
1929 newVal = props[prop]; |
|
1930 this._set(prop, newVal, true); |
|
1931 } |
|
1932 |
|
1933 } |
|
1934 can.batch.stop(); |
|
1935 return this; |
|
1936 }, |
|
1937 |
|
1938 compute: function(prop) { |
|
1939 // If the property is a function, use it as the getter/setter |
|
1940 // otherwise, create a new compute that returns the value of a property on `this` |
|
1941 if (can.isFunction(this.constructor.prototype[prop])) { |
|
1942 return can.compute(this[prop], this); |
|
1943 } else { |
|
1944 var reads = prop.split("."), |
|
1945 last = reads.length - 1, |
|
1946 options = { |
|
1947 args: [] |
|
1948 }; |
|
1949 return can.compute(function(newVal) { |
|
1950 if (arguments.length) { |
|
1951 can.compute.read(this, reads.slice(0, last)) |
|
1952 .value.attr(reads[last], newVal); |
|
1953 } else { |
|
1954 return can.compute.read(this, reads, options) |
|
1955 .value; |
|
1956 } |
|
1957 }, this); |
|
1958 } |
|
1959 |
|
1960 } |
|
1961 }); |
|
1962 |
|
1963 // Setup on/off aliases |
|
1964 Map.prototype.on = Map.prototype.bind; |
|
1965 Map.prototype.off = Map.prototype.unbind; |
|
1966 |
|
1967 return Map; |
|
1968 })(__m3, __m11, __m12, __m1, __m13); |
|
1969 |
|
1970 // ## can/list/list.js |
|
1971 var __m14 = (function(can, Map, bubble) { |
|
1972 |
|
1973 // Helpers for `observable` lists. |
|
1974 var splice = [].splice, |
|
1975 // test if splice works correctly |
|
1976 spliceRemovesProps = (function() { |
|
1977 // IE's splice doesn't remove properties |
|
1978 var obj = { |
|
1979 0: "a", |
|
1980 length: 1 |
|
1981 }; |
|
1982 splice.call(obj, 0, 1); |
|
1983 return !obj[0]; |
|
1984 })(); |
|
1985 |
|
1986 |
|
1987 var list = Map.extend( |
|
1988 |
|
1989 { |
|
1990 |
|
1991 Map: Map |
|
1992 |
|
1993 }, |
|
1994 |
|
1995 { |
|
1996 setup: function(instances, options) { |
|
1997 this.length = 0; |
|
1998 can.cid(this, ".map"); |
|
1999 this._init = 1; |
|
2000 this._setupComputes(); |
|
2001 instances = instances || []; |
|
2002 var teardownMapping; |
|
2003 |
|
2004 if (can.isDeferred(instances)) { |
|
2005 this.replace(instances); |
|
2006 } else { |
|
2007 teardownMapping = instances.length && can.Map.helpers.addToMap(instances, this); |
|
2008 this.push.apply(this, can.makeArray(instances || [])); |
|
2009 } |
|
2010 |
|
2011 if (teardownMapping) { |
|
2012 teardownMapping(); |
|
2013 } |
|
2014 |
|
2015 // this change needs to be ignored |
|
2016 this.bind('change', can.proxy(this._changes, this)); |
|
2017 can.simpleExtend(this, options); |
|
2018 delete this._init; |
|
2019 }, |
|
2020 _triggerChange: function(attr, how, newVal, oldVal) { |
|
2021 |
|
2022 Map.prototype._triggerChange.apply(this, arguments); |
|
2023 // `batchTrigger` direct add and remove events... |
|
2024 var index = +attr; |
|
2025 // Make sure this is not nested and not an expando |
|
2026 if (!~attr.indexOf('.') && !isNaN(index)) { |
|
2027 |
|
2028 if (how === 'add') { |
|
2029 can.batch.trigger(this, how, [newVal, index]); |
|
2030 can.batch.trigger(this, 'length', [this.length]); |
|
2031 } else if (how === 'remove') { |
|
2032 can.batch.trigger(this, how, [oldVal, index]); |
|
2033 can.batch.trigger(this, 'length', [this.length]); |
|
2034 } else { |
|
2035 can.batch.trigger(this, how, [newVal, index]); |
|
2036 } |
|
2037 |
|
2038 } |
|
2039 |
|
2040 }, |
|
2041 __get: function(attr) { |
|
2042 if (attr) { |
|
2043 if (this[attr] && this[attr].isComputed && can.isFunction(this.constructor.prototype[attr])) { |
|
2044 return this[attr](); |
|
2045 } else { |
|
2046 return this[attr]; |
|
2047 } |
|
2048 } else { |
|
2049 return this; |
|
2050 } |
|
2051 }, |
|
2052 ___set: function(attr, val) { |
|
2053 this[attr] = val; |
|
2054 if (+attr >= this.length) { |
|
2055 this.length = (+attr + 1); |
|
2056 } |
|
2057 }, |
|
2058 _remove: function(prop, current) { |
|
2059 // if removing an expando property |
|
2060 if (isNaN(+prop)) { |
|
2061 delete this[prop]; |
|
2062 this._triggerChange(prop, "remove", undefined, current); |
|
2063 } else { |
|
2064 this.splice(prop, 1); |
|
2065 } |
|
2066 }, |
|
2067 _each: function(callback) { |
|
2068 var data = this.__get(); |
|
2069 for (var i = 0; i < data.length; i++) { |
|
2070 callback(data[i], i); |
|
2071 } |
|
2072 }, |
|
2073 // Returns the serialized form of this list. |
|
2074 |
|
2075 serialize: function() { |
|
2076 return Map.helpers.serialize(this, 'serialize', []); |
|
2077 }, |
|
2078 |
|
2079 splice: function(index, howMany) { |
|
2080 var args = can.makeArray(arguments), |
|
2081 i; |
|
2082 |
|
2083 for (i = 2; i < args.length; i++) { |
|
2084 args[i] = bubble.set(this, i, this.__type(args[i], i)); |
|
2085 |
|
2086 } |
|
2087 if (howMany === undefined) { |
|
2088 howMany = args[1] = this.length - index; |
|
2089 } |
|
2090 var removed = splice.apply(this, args); |
|
2091 |
|
2092 if (!spliceRemovesProps) { |
|
2093 for (i = this.length; i < removed.length + this.length; i++) { |
|
2094 delete this[i]; |
|
2095 } |
|
2096 } |
|
2097 |
|
2098 can.batch.start(); |
|
2099 if (howMany > 0) { |
|
2100 this._triggerChange("" + index, "remove", undefined, removed); |
|
2101 bubble.removeMany(this, removed); |
|
2102 } |
|
2103 if (args.length > 2) { |
|
2104 this._triggerChange("" + index, "add", args.slice(2), removed); |
|
2105 } |
|
2106 can.batch.stop(); |
|
2107 return removed; |
|
2108 }, |
|
2109 |
|
2110 _attrs: function(items, remove) { |
|
2111 if (items === undefined) { |
|
2112 return Map.helpers.serialize(this, 'attr', []); |
|
2113 } |
|
2114 |
|
2115 // Create a copy. |
|
2116 items = can.makeArray(items); |
|
2117 |
|
2118 can.batch.start(); |
|
2119 this._updateAttrs(items, remove); |
|
2120 can.batch.stop(); |
|
2121 }, |
|
2122 |
|
2123 _updateAttrs: function(items, remove) { |
|
2124 var len = Math.min(items.length, this.length); |
|
2125 |
|
2126 for (var prop = 0; prop < len; prop++) { |
|
2127 var curVal = this[prop], |
|
2128 newVal = items[prop]; |
|
2129 |
|
2130 if (Map.helpers.isObservable(curVal) && Map.helpers.canMakeObserve(newVal)) { |
|
2131 curVal.attr(newVal, remove); |
|
2132 //changed from a coercion to an explicit |
|
2133 } else if (curVal !== newVal) { |
|
2134 this._set(prop, newVal); |
|
2135 } else { |
|
2136 |
|
2137 } |
|
2138 } |
|
2139 if (items.length > this.length) { |
|
2140 // Add in the remaining props. |
|
2141 this.push.apply(this, items.slice(this.length)); |
|
2142 } else if (items.length < this.length && remove) { |
|
2143 this.splice(items.length); |
|
2144 } |
|
2145 } |
|
2146 }), |
|
2147 |
|
2148 // Converts to an `array` of arguments. |
|
2149 getArgs = function(args) { |
|
2150 return args[0] && can.isArray(args[0]) ? |
|
2151 args[0] : |
|
2152 can.makeArray(args); |
|
2153 }; |
|
2154 // Create `push`, `pop`, `shift`, and `unshift` |
|
2155 can.each({ |
|
2156 |
|
2157 push: "length", |
|
2158 |
|
2159 unshift: 0 |
|
2160 }, |
|
2161 // Adds a method |
|
2162 // `name` - The method name. |
|
2163 // `where` - Where items in the `array` should be added. |
|
2164 |
|
2165 function(where, name) { |
|
2166 var orig = [][name]; |
|
2167 list.prototype[name] = function() { |
|
2168 // Get the items being added. |
|
2169 var args = [], |
|
2170 // Where we are going to add items. |
|
2171 len = where ? this.length : 0, |
|
2172 i = arguments.length, |
|
2173 res, val; |
|
2174 |
|
2175 // Go through and convert anything to an `map` that needs to be converted. |
|
2176 while (i--) { |
|
2177 val = arguments[i]; |
|
2178 args[i] = bubble.set(this, i, this.__type(val, i)); |
|
2179 } |
|
2180 |
|
2181 // Call the original method. |
|
2182 res = orig.apply(this, args); |
|
2183 |
|
2184 if (!this.comparator || args.length) { |
|
2185 |
|
2186 this._triggerChange("" + len, "add", args, undefined); |
|
2187 } |
|
2188 |
|
2189 return res; |
|
2190 }; |
|
2191 }); |
|
2192 |
|
2193 can.each({ |
|
2194 |
|
2195 pop: "length", |
|
2196 |
|
2197 shift: 0 |
|
2198 }, |
|
2199 // Creates a `remove` type method |
|
2200 |
|
2201 function(where, name) { |
|
2202 list.prototype[name] = function() { |
|
2203 |
|
2204 var args = getArgs(arguments), |
|
2205 len = where && this.length ? this.length - 1 : 0; |
|
2206 |
|
2207 var res = [][name].apply(this, args); |
|
2208 |
|
2209 // Create a change where the args are |
|
2210 // `len` - Where these items were removed. |
|
2211 // `remove` - Items removed. |
|
2212 // `undefined` - The new values (there are none). |
|
2213 // `res` - The old, removed values (should these be unbound). |
|
2214 this._triggerChange("" + len, "remove", undefined, [res]); |
|
2215 |
|
2216 if (res && res.unbind) { |
|
2217 bubble.remove(this, res); |
|
2218 } |
|
2219 |
|
2220 return res; |
|
2221 }; |
|
2222 }); |
|
2223 |
|
2224 can.extend(list.prototype, { |
|
2225 |
|
2226 indexOf: function(item, fromIndex) { |
|
2227 this.attr('length'); |
|
2228 return can.inArray(item, this, fromIndex); |
|
2229 }, |
|
2230 |
|
2231 |
|
2232 join: function() { |
|
2233 return [].join.apply(this.attr(), arguments); |
|
2234 }, |
|
2235 |
|
2236 |
|
2237 reverse: function() { |
|
2238 var list = can.makeArray([].reverse.call(this)); |
|
2239 this.replace(list); |
|
2240 }, |
|
2241 |
|
2242 |
|
2243 slice: function() { |
|
2244 var temp = Array.prototype.slice.apply(this, arguments); |
|
2245 return new this.constructor(temp); |
|
2246 }, |
|
2247 |
|
2248 |
|
2249 concat: function() { |
|
2250 var args = []; |
|
2251 can.each(can.makeArray(arguments), function(arg, i) { |
|
2252 args[i] = arg instanceof can.List ? arg.serialize() : arg; |
|
2253 }); |
|
2254 return new this.constructor(Array.prototype.concat.apply(this.serialize(), args)); |
|
2255 }, |
|
2256 |
|
2257 |
|
2258 forEach: function(cb, thisarg) { |
|
2259 return can.each(this, cb, thisarg || this); |
|
2260 }, |
|
2261 |
|
2262 |
|
2263 replace: function(newList) { |
|
2264 if (can.isDeferred(newList)) { |
|
2265 newList.then(can.proxy(this.replace, this)); |
|
2266 } else { |
|
2267 this.splice.apply(this, [0, this.length].concat(can.makeArray(newList || []))); |
|
2268 } |
|
2269 |
|
2270 return this; |
|
2271 }, |
|
2272 filter: function(callback, thisArg) { |
|
2273 var filteredList = new can.List(), |
|
2274 self = this, |
|
2275 filtered; |
|
2276 this.each(function(item, index, list) { |
|
2277 filtered = callback.call(thisArg | self, item, index, self); |
|
2278 if (filtered) { |
|
2279 filteredList.push(item); |
|
2280 } |
|
2281 }); |
|
2282 return filteredList; |
|
2283 } |
|
2284 }); |
|
2285 can.List = Map.List = list; |
|
2286 return can.List; |
|
2287 })(__m3, __m10, __m12); |
|
2288 |
|
2289 // ## can/compute/compute.js |
|
2290 var __m15 = (function(can, bind) { |
|
2291 |
|
2292 // ## Reading Helpers |
|
2293 // The following methods are used to call a function that relies on |
|
2294 // observable data and to track the observable events which should |
|
2295 // be listened to when changes occur. |
|
2296 // To do this, [`can.__reading(observable, event)`](#can-__reading) is called to |
|
2297 // "broadcast" the corresponding event on each read. |
|
2298 // ### Observed |
|
2299 // An "Observed" is an object of observable objects and events that |
|
2300 // a function relies on. These objects and events must be listened to |
|
2301 // in order to determine when to check a function for updates. |
|
2302 // This looks like the following: |
|
2303 // { |
|
2304 // "map1|first": {obj: map, event: "first"}, |
|
2305 // "map1|last" : {obj: map, event: "last"} |
|
2306 // } |
|
2307 // Each object-event pair is mapped so no duplicates will be listed. |
|
2308 |
|
2309 // ### State |
|
2310 // `can.__read` may call a function that calls `can.__read` again. For |
|
2311 // example, a compute can read another compute. To track each compute's |
|
2312 // `Observed` object (containing observable objects and events), we maintain |
|
2313 // a stack of Observed values for each call to `__read`. |
|
2314 var stack = []; |
|
2315 |
|
2316 // ### can.__read |
|
2317 // With a given function and context, calls the function |
|
2318 // and returns the resulting value of the function as well |
|
2319 // as the observable properties and events that were read. |
|
2320 can.__read = function(func, self) { |
|
2321 |
|
2322 // Add an object that `can.__read` will write to. |
|
2323 stack.push({}); |
|
2324 |
|
2325 var value = func.call(self); |
|
2326 |
|
2327 // Example return value: |
|
2328 // `{value: 100, observed: Observed}` |
|
2329 return { |
|
2330 value: value, |
|
2331 observed: stack.pop() |
|
2332 }; |
|
2333 }; |
|
2334 |
|
2335 // ### can.__reading |
|
2336 // When an observable value is read, it must call `can.__reading` to |
|
2337 // broadcast which object and event should be listened to. |
|
2338 can.__reading = function(obj, event) { |
|
2339 // Add the observable object and the event |
|
2340 // that was read to the `Observed` object on |
|
2341 // the stack. |
|
2342 if (stack.length) { |
|
2343 stack[stack.length - 1][obj._cid + '|' + event] = { |
|
2344 obj: obj, |
|
2345 event: event + "" |
|
2346 }; |
|
2347 } |
|
2348 |
|
2349 }; |
|
2350 |
|
2351 // ### can.__clearReading |
|
2352 // Clears and returns the current observables. |
|
2353 // This can be used to access a value without |
|
2354 // it being handled as a regular `read`. |
|
2355 can.__clearReading = function() { |
|
2356 if (stack.length) { |
|
2357 var ret = stack[stack.length - 1]; |
|
2358 stack[stack.length - 1] = {}; |
|
2359 return ret; |
|
2360 } |
|
2361 }; |
|
2362 // Specifies current observables. |
|
2363 can.__setReading = function(o) { |
|
2364 if (stack.length) { |
|
2365 stack[stack.length - 1] = o; |
|
2366 } |
|
2367 }; |
|
2368 can.__addReading = function(o) { |
|
2369 if (stack.length) { |
|
2370 can.simpleExtend(stack[stack.length - 1], o); |
|
2371 } |
|
2372 }; |
|
2373 |
|
2374 // ## Section Name |
|
2375 |
|
2376 // ### getValueAndBind |
|
2377 // Calls a function and sets up bindings to call `onchanged` |
|
2378 // when events from its "Observed" object are triggered. |
|
2379 // Removes bindings from `oldObserved` that are no longer needed. |
|
2380 // - func - the function to call. |
|
2381 // - context - the `this` of the function. |
|
2382 // - oldObserved - an object that contains what has already been bound to |
|
2383 // - onchanged - the function to call when any change occurs |
|
2384 var getValueAndBind = function(func, context, oldObserved, onchanged) { |
|
2385 // Call the function, get the value as well as the observed objects and events |
|
2386 var info = can.__read(func, context), |
|
2387 // The objects-event pairs that must be bound to |
|
2388 newObserveSet = info.observed, |
|
2389 // A flag that is used to determine if an event is already being observed. |
|
2390 obEv, |
|
2391 name; |
|
2392 // Go through what needs to be observed. |
|
2393 for (name in newObserveSet) { |
|
2394 |
|
2395 if (oldObserved[name]) { |
|
2396 // After binding is set up, values |
|
2397 // in `oldObserved` will be unbound. So if a name |
|
2398 // has already be observed, remove from `oldObserved` |
|
2399 // to prevent this. |
|
2400 delete oldObserved[name]; |
|
2401 } else { |
|
2402 // If current name has not been observed, listen to it. |
|
2403 obEv = newObserveSet[name]; |
|
2404 obEv.obj.bind(obEv.event, onchanged); |
|
2405 } |
|
2406 } |
|
2407 |
|
2408 // Iterate through oldObserved, looking for observe/attributes |
|
2409 // that are no longer being bound and unbind them. |
|
2410 for (name in oldObserved) { |
|
2411 obEv = oldObserved[name]; |
|
2412 obEv.obj.unbind(obEv.event, onchanged); |
|
2413 } |
|
2414 |
|
2415 return info; |
|
2416 }; |
|
2417 |
|
2418 // ### updateOnChange |
|
2419 // Fires a change event when a compute's value changes |
|
2420 var updateOnChange = function(compute, newValue, oldValue, batchNum) { |
|
2421 // Only trigger event when value has changed |
|
2422 if (newValue !== oldValue) { |
|
2423 can.batch.trigger(compute, batchNum ? { |
|
2424 type: "change", |
|
2425 batchNum: batchNum |
|
2426 } : 'change', [ |
|
2427 newValue, |
|
2428 oldValue |
|
2429 ]); |
|
2430 } |
|
2431 }; |
|
2432 |
|
2433 // ###setupComputeHandlers |
|
2434 // Sets up handlers for a compute. |
|
2435 // - compute - the compute to set up handlers for |
|
2436 // - func - the getter/setter function for the compute |
|
2437 // - context - the `this` for the compute |
|
2438 // - setCachedValue - function for setting cached value |
|
2439 // Returns an object with `on` and `off` functions. |
|
2440 var setupComputeHandlers = function(compute, func, context, setCachedValue) { |
|
2441 var readInfo, |
|
2442 onchanged, |
|
2443 batchNum; |
|
2444 |
|
2445 return { |
|
2446 // Set up handler for when the compute changes |
|
2447 on: function(updater) { |
|
2448 if (!onchanged) { |
|
2449 onchanged = function(ev) { |
|
2450 if (compute.bound && (ev.batchNum === undefined || ev.batchNum !== batchNum)) { |
|
2451 // Keep the old value |
|
2452 var oldValue = readInfo.value; |
|
2453 |
|
2454 // Get the new value |
|
2455 readInfo = getValueAndBind(func, context, readInfo.observed, onchanged); |
|
2456 |
|
2457 // Call the updater with old and new values |
|
2458 updater(readInfo.value, oldValue, ev.batchNum); |
|
2459 |
|
2460 batchNum = batchNum = ev.batchNum; |
|
2461 } |
|
2462 }; |
|
2463 } |
|
2464 |
|
2465 readInfo = getValueAndBind(func, context, {}, onchanged); |
|
2466 |
|
2467 setCachedValue(readInfo.value); |
|
2468 |
|
2469 compute.hasDependencies = !can.isEmptyObject(readInfo.observed); |
|
2470 }, |
|
2471 // Remove handler for the compute |
|
2472 off: function(updater) { |
|
2473 for (var name in readInfo.observed) { |
|
2474 var ob = readInfo.observed[name]; |
|
2475 ob.obj.unbind(ob.event, onchanged); |
|
2476 } |
|
2477 } |
|
2478 }; |
|
2479 }; |
|
2480 |
|
2481 // ###isObserve |
|
2482 // Checks if an object is observable |
|
2483 var isObserve = function(obj) { |
|
2484 return obj instanceof can.Map || obj && obj.__get; |
|
2485 }, |
|
2486 // Instead of calculating whether anything is listening every time, |
|
2487 // use a function to do nothing (which may be overwritten) |
|
2488 k = function() {}; |
|
2489 |
|
2490 // ## Creating a can.compute |
|
2491 // A `can.compute` can be created by |
|
2492 // - [Specifying the getterSeter function](#specifying-gettersetter-function) |
|
2493 // - [Observing a property of an object](#observing-a-property-of-an-object) |
|
2494 // - [Specifying an initial value and a setter function](#specifying-an-initial-value-and-a-setter) |
|
2495 // - [Specifying an initial value and how to read, update, and listen to changes](#specifying-an-initial-value-and-a-settings-object) |
|
2496 // - [Simply specifying an initial value](#specifying-only-a-value) |
|
2497 can.compute = function(getterSetter, context, eventName) { |
|
2498 // ### Setting up |
|
2499 // Do nothing if getterSetter is already a compute |
|
2500 if (getterSetter && getterSetter.isComputed) { |
|
2501 return getterSetter; |
|
2502 } |
|
2503 // The computed object |
|
2504 var computed, |
|
2505 // The following functions are overwritten depending on how compute() is called |
|
2506 // A method to set up listening |
|
2507 on = k, |
|
2508 // A method to teardown listening |
|
2509 off = k, |
|
2510 // Current cached value (valid only when bound is true) |
|
2511 value, |
|
2512 // How the value is read by default |
|
2513 get = function() { |
|
2514 return value; |
|
2515 }, |
|
2516 // How the value is set by default |
|
2517 set = function(newVal) { |
|
2518 value = newVal; |
|
2519 }, |
|
2520 setCached = set, |
|
2521 // Save arguments for cloning |
|
2522 args = can.makeArray(arguments), |
|
2523 // updater for when value is changed |
|
2524 updater = function(newValue, oldValue, batchNum) { |
|
2525 setCached(newValue); |
|
2526 updateOnChange(computed, newValue, oldValue, batchNum); |
|
2527 }, |
|
2528 // the form of the arguments |
|
2529 form; |
|
2530 computed = function(newVal) { |
|
2531 // If the computed function is called with arguments, |
|
2532 // a value should be set |
|
2533 if (arguments.length) { |
|
2534 // Save a reference to the old value |
|
2535 var old = value; |
|
2536 // Setter may return the value if setter |
|
2537 // is for a value maintained exclusively by this compute. |
|
2538 var setVal = set.call(context, newVal, old); |
|
2539 // If the computed function has dependencies, |
|
2540 // return the current value |
|
2541 if (computed.hasDependencies) { |
|
2542 return get.call(context); |
|
2543 } |
|
2544 // Setting may not fire a change event, in which case |
|
2545 // the value must be read |
|
2546 if (setVal === undefined) { |
|
2547 value = get.call(context); |
|
2548 } else { |
|
2549 value = setVal; |
|
2550 } |
|
2551 // Fire the change |
|
2552 updateOnChange(computed, value, old); |
|
2553 return value; |
|
2554 } else { |
|
2555 // Another compute may bind to this `computed` |
|
2556 if (stack.length && computed.canReadForChangeEvent !== false) { |
|
2557 |
|
2558 // Tell the compute to listen to change on this computed |
|
2559 // Use `can.__reading` to allow other compute to listen |
|
2560 // for a change on this `computed` |
|
2561 can.__reading(computed, 'change'); |
|
2562 // We are going to bind on this compute. |
|
2563 // If we are not bound, we should bind so that |
|
2564 // we don't have to re-read to get the value of this compute. |
|
2565 if (!computed.bound) { |
|
2566 can.compute.temporarilyBind(computed); |
|
2567 } |
|
2568 } |
|
2569 // If computed is bound, use the cached value |
|
2570 if (computed.bound) { |
|
2571 return value; |
|
2572 } else { |
|
2573 return get.call(context); |
|
2574 } |
|
2575 } |
|
2576 }; |
|
2577 // ###Specifying getterSetter function |
|
2578 // If `can.compute` is [called with a getterSetter function](http://canjs.com/docs/can.compute.html#sig_can_compute_getterSetter__context__), |
|
2579 // override set and get |
|
2580 if (typeof getterSetter === 'function') { |
|
2581 // `can.compute(getterSetter, [context])` |
|
2582 set = getterSetter; |
|
2583 get = getterSetter; |
|
2584 computed.canReadForChangeEvent = eventName === false ? false : true; |
|
2585 |
|
2586 var handlers = setupComputeHandlers(computed, getterSetter, context || this, setCached); |
|
2587 on = handlers.on; |
|
2588 off = handlers.off; |
|
2589 |
|
2590 // ###Observing a property of an object |
|
2591 // If `can.compute` is called with an |
|
2592 // [object, property name, and optional event name](http://canjs.com/docs/can.compute.html#sig_can_compute_object_propertyName__eventName__), |
|
2593 // create a compute from a property of an object. This allows the |
|
2594 // creation of a compute on objects that can be listened to with [`can.bind`](http://canjs.com/docs/can.bind.html) |
|
2595 } else if (context) { |
|
2596 if (typeof context === 'string') { |
|
2597 // `can.compute(obj, "propertyName", [eventName])` |
|
2598 var propertyName = context, |
|
2599 isObserve = getterSetter instanceof can.Map; |
|
2600 if (isObserve) { |
|
2601 computed.hasDependencies = true; |
|
2602 } |
|
2603 // If object is observable, `attr` will be used |
|
2604 // for getting and setting. |
|
2605 get = function() { |
|
2606 if (isObserve) { |
|
2607 return getterSetter.attr(propertyName); |
|
2608 } else { |
|
2609 return getterSetter[propertyName]; |
|
2610 } |
|
2611 }; |
|
2612 set = function(newValue) { |
|
2613 if (isObserve) { |
|
2614 getterSetter.attr(propertyName, newValue); |
|
2615 } else { |
|
2616 getterSetter[propertyName] = newValue; |
|
2617 } |
|
2618 }; |
|
2619 var handler; |
|
2620 on = function(update) { |
|
2621 handler = function() { |
|
2622 update(get(), value); |
|
2623 }; |
|
2624 can.bind.call(getterSetter, eventName || propertyName, handler); |
|
2625 // use can.__read because |
|
2626 // we should not be indicating that some parent |
|
2627 // reads this property if it happens to be binding on it |
|
2628 value = can.__read(get) |
|
2629 .value; |
|
2630 }; |
|
2631 off = function() { |
|
2632 can.unbind.call(getterSetter, eventName || propertyName, handler); |
|
2633 }; |
|
2634 // ###Specifying an initial value and a setter |
|
2635 // If `can.compute` is called with an [initial value and a setter function](http://canjs.com/docs/can.compute.html#sig_can_compute_initialValue_setter_newVal_oldVal__), |
|
2636 // a compute that can adjust incoming values is set up. |
|
2637 } else { |
|
2638 // `can.compute(initialValue, setter)` |
|
2639 if (typeof context === 'function') { |
|
2640 |
|
2641 value = getterSetter; |
|
2642 set = context; |
|
2643 context = eventName; |
|
2644 form = 'setter'; |
|
2645 // ###Specifying an initial value and a settings object |
|
2646 // If `can.compute` is called with an [initial value and optionally a settings object](http://canjs.com/docs/can.compute.html#sig_can_compute_initialValue__settings__), |
|
2647 // a can.compute is created that can optionally specify how to read, |
|
2648 // update, and listen to changes in dependent values. This form of |
|
2649 // can.compute can be used to derive a compute that derives its |
|
2650 // value from any source |
|
2651 } else { |
|
2652 // `can.compute(initialValue,{get:, set:, on:, off:})` |
|
2653 |
|
2654 |
|
2655 value = getterSetter; |
|
2656 var options = context, |
|
2657 oldUpdater = updater; |
|
2658 |
|
2659 context = options.context || options; |
|
2660 get = options.get || get; |
|
2661 set = options.set || function() { |
|
2662 return value; |
|
2663 }; |
|
2664 // This is a "hack" to allow async computes. |
|
2665 if (options.fn) { |
|
2666 var fn = options.fn, |
|
2667 data; |
|
2668 // make sure get is called with the newVal, but not setter |
|
2669 get = function() { |
|
2670 return fn.call(context, value); |
|
2671 }; |
|
2672 // Check the number of arguments the |
|
2673 // async function takes. |
|
2674 if (fn.length === 0) { |
|
2675 |
|
2676 data = setupComputeHandlers(computed, fn, context, setCached); |
|
2677 |
|
2678 } else if (fn.length === 1) { |
|
2679 data = setupComputeHandlers(computed, function() { |
|
2680 return fn.call(context, value); |
|
2681 }, context, setCached); |
|
2682 } else { |
|
2683 updater = function(newVal) { |
|
2684 if (newVal !== undefined) { |
|
2685 oldUpdater(newVal, value); |
|
2686 } |
|
2687 }; |
|
2688 data = setupComputeHandlers(computed, function() { |
|
2689 var res = fn.call(context, value, function(newVal) { |
|
2690 oldUpdater(newVal, value); |
|
2691 }); |
|
2692 // If undefined is returned, don't update the value. |
|
2693 return res !== undefined ? res : value; |
|
2694 }, context, setCached); |
|
2695 } |
|
2696 |
|
2697 |
|
2698 on = data.on; |
|
2699 off = data.off; |
|
2700 } else { |
|
2701 updater = function() { |
|
2702 var newVal = get.call(context); |
|
2703 oldUpdater(newVal, value); |
|
2704 }; |
|
2705 } |
|
2706 |
|
2707 on = options.on || on; |
|
2708 off = options.off || off; |
|
2709 } |
|
2710 } |
|
2711 // ###Specifying only a value |
|
2712 // If can.compute is called with an initialValue only, |
|
2713 // reads to this value can be observed. |
|
2714 } else { |
|
2715 // `can.compute(initialValue)` |
|
2716 value = getterSetter; |
|
2717 } |
|
2718 can.cid(computed, 'compute'); |
|
2719 return can.simpleExtend(computed, { |
|
2720 |
|
2721 isComputed: true, |
|
2722 _bindsetup: function() { |
|
2723 this.bound = true; |
|
2724 // Set up live-binding |
|
2725 // While binding, this should not count as a read |
|
2726 var oldReading = can.__clearReading(); |
|
2727 on.call(this, updater); |
|
2728 // Restore "Observed" for reading |
|
2729 can.__setReading(oldReading); |
|
2730 }, |
|
2731 _bindteardown: function() { |
|
2732 off.call(this, updater); |
|
2733 this.bound = false; |
|
2734 }, |
|
2735 |
|
2736 bind: can.bindAndSetup, |
|
2737 |
|
2738 unbind: can.unbindAndTeardown, |
|
2739 clone: function(context) { |
|
2740 if (context) { |
|
2741 if (form === 'setter') { |
|
2742 args[2] = context; |
|
2743 } else { |
|
2744 args[1] = context; |
|
2745 } |
|
2746 } |
|
2747 return can.compute.apply(can, args); |
|
2748 } |
|
2749 }); |
|
2750 }; |
|
2751 // A list of temporarily bound computes |
|
2752 var computes, unbindComputes = function() { |
|
2753 for (var i = 0, len = computes.length; i < len; i++) { |
|
2754 computes[i].unbind('change', k); |
|
2755 } |
|
2756 computes = null; |
|
2757 }; |
|
2758 // Binds computes for a moment to retain their value and prevent caching |
|
2759 can.compute.temporarilyBind = function(compute) { |
|
2760 compute.bind('change', k); |
|
2761 if (!computes) { |
|
2762 computes = []; |
|
2763 setTimeout(unbindComputes, 10); |
|
2764 } |
|
2765 computes.push(compute); |
|
2766 }; |
|
2767 |
|
2768 // Whether a compute is truthy |
|
2769 can.compute.truthy = function(compute) { |
|
2770 return can.compute(function() { |
|
2771 var res = compute(); |
|
2772 if (typeof res === 'function') { |
|
2773 res = res(); |
|
2774 } |
|
2775 return !!res; |
|
2776 }); |
|
2777 }; |
|
2778 can.compute.async = function(initialValue, asyncComputer, context) { |
|
2779 return can.compute(initialValue, { |
|
2780 fn: asyncComputer, |
|
2781 context: context |
|
2782 }); |
|
2783 }; |
|
2784 // {map: new can.Map({first: "Justin"})}, ["map","first"] |
|
2785 can.compute.read = function(parent, reads, options) { |
|
2786 options = options || {}; |
|
2787 // `cur` is the current value. |
|
2788 var cur = parent, |
|
2789 type, |
|
2790 // `prev` is the object we are reading from. |
|
2791 prev, |
|
2792 // `foundObs` did we find an observable. |
|
2793 foundObs; |
|
2794 for (var i = 0, readLength = reads.length; i < readLength; i++) { |
|
2795 // Update what we are reading from. |
|
2796 prev = cur; |
|
2797 // Read from the compute. We can't read a property yet. |
|
2798 if (prev && prev.isComputed) { |
|
2799 if (options.foundObservable) { |
|
2800 options.foundObservable(prev, i); |
|
2801 } |
|
2802 prev = prev(); |
|
2803 } |
|
2804 // Look to read a property from something. |
|
2805 if (isObserve(prev)) { |
|
2806 if (!foundObs && options.foundObservable) { |
|
2807 options.foundObservable(prev, i); |
|
2808 } |
|
2809 foundObs = 1; |
|
2810 // is it a method on the prototype? |
|
2811 if (typeof prev[reads[i]] === 'function' && prev.constructor.prototype[reads[i]] === prev[reads[i]]) { |
|
2812 // call that method |
|
2813 if (options.returnObserveMethods) { |
|
2814 cur = cur[reads[i]]; |
|
2815 } else if (reads[i] === 'constructor' && prev instanceof can.Construct) { |
|
2816 cur = prev[reads[i]]; |
|
2817 } else { |
|
2818 cur = prev[reads[i]].apply(prev, options.args || []); |
|
2819 } |
|
2820 } else { |
|
2821 // use attr to get that value |
|
2822 cur = cur.attr(reads[i]); |
|
2823 } |
|
2824 } else { |
|
2825 // just do the dot operator |
|
2826 cur = prev[reads[i]]; |
|
2827 } |
|
2828 type = typeof cur; |
|
2829 // If it's a compute, get the compute's value |
|
2830 // unless we are at the end of the |
|
2831 if (cur && cur.isComputed && (!options.isArgument && i < readLength - 1)) { |
|
2832 if (!foundObs && options.foundObservable) { |
|
2833 options.foundObservable(prev, i + 1); |
|
2834 } |
|
2835 cur = cur(); |
|
2836 } |
|
2837 // If it's an anonymous function, execute as requested |
|
2838 else if (i < reads.length - 1 && type === 'function' && options.executeAnonymousFunctions && !(can.Construct && cur.prototype instanceof can.Construct)) { |
|
2839 cur = cur(); |
|
2840 } |
|
2841 // if there are properties left to read, and we don't have an object, early exit |
|
2842 if (i < reads.length - 1 && (cur === null || type !== 'function' && type !== 'object')) { |
|
2843 if (options.earlyExit) { |
|
2844 options.earlyExit(prev, i, cur); |
|
2845 } |
|
2846 // return undefined so we know this isn't the right value |
|
2847 return { |
|
2848 value: undefined, |
|
2849 parent: prev |
|
2850 }; |
|
2851 } |
|
2852 } |
|
2853 // handle an ending function |
|
2854 // unless it is a can.Construct-derived constructor |
|
2855 if (typeof cur === 'function' && !(can.Construct && cur.prototype instanceof can.Construct)) { |
|
2856 if (options.isArgument) { |
|
2857 if (!cur.isComputed && options.proxyMethods !== false) { |
|
2858 cur = can.proxy(cur, prev); |
|
2859 } |
|
2860 } else { |
|
2861 if (cur.isComputed && !foundObs && options.foundObservable) { |
|
2862 options.foundObservable(cur, i); |
|
2863 } |
|
2864 cur = cur.call(prev); |
|
2865 } |
|
2866 } |
|
2867 // if we don't have a value, exit early. |
|
2868 if (cur === undefined) { |
|
2869 if (options.earlyExit) { |
|
2870 options.earlyExit(prev, i - 1); |
|
2871 } |
|
2872 } |
|
2873 return { |
|
2874 value: cur, |
|
2875 parent: prev |
|
2876 }; |
|
2877 }; |
|
2878 |
|
2879 return can.compute; |
|
2880 })(__m3, __m11, __m13); |
|
2881 |
|
2882 // ## can/model/model.js |
|
2883 var __m16 = (function(can) { |
|
2884 |
|
2885 // ## model.js |
|
2886 // (Don't steal this file directly in your code.) |
|
2887 |
|
2888 // ## pipe |
|
2889 // `pipe` lets you pipe the results of a successful deferred |
|
2890 // through a function before resolving the deferred. |
|
2891 |
|
2892 var pipe = function(def, thisArg, func) { |
|
2893 // The piped result will be available through a new Deferred. |
|
2894 var d = new can.Deferred(); |
|
2895 def.then(function() { |
|
2896 var args = can.makeArray(arguments), |
|
2897 success = true; |
|
2898 |
|
2899 try { |
|
2900 // Pipe the results through the function. |
|
2901 args[0] = func.apply(thisArg, args); |
|
2902 } catch (e) { |
|
2903 success = false; |
|
2904 // The function threw an error, so reject the Deferred. |
|
2905 d.rejectWith(d, [e].concat(args)); |
|
2906 } |
|
2907 if (success) { |
|
2908 // Resolve the new Deferred with the piped value. |
|
2909 d.resolveWith(d, args); |
|
2910 } |
|
2911 }, function() { |
|
2912 // Pass on the rejection if the original Deferred never resolved. |
|
2913 d.rejectWith(this, arguments); |
|
2914 }); |
|
2915 |
|
2916 // `can.ajax` returns a Deferred with an abort method to halt the AJAX call. |
|
2917 if (typeof def.abort === 'function') { |
|
2918 d.abort = function() { |
|
2919 return def.abort(); |
|
2920 }; |
|
2921 } |
|
2922 |
|
2923 // Return the new (piped) Deferred. |
|
2924 return d; |
|
2925 }, |
|
2926 |
|
2927 // ## modelNum |
|
2928 // When new model constructors are set up without a full name, |
|
2929 // `modelNum` lets us name them uniquely (to keep track of them). |
|
2930 modelNum = 0, |
|
2931 |
|
2932 // ## getId |
|
2933 getId = function(inst) { |
|
2934 // `can.__reading` makes a note that `id` was just read. |
|
2935 can.__reading(inst, inst.constructor.id); |
|
2936 // Use `__get` instead of `attr` for performance. (But that means we have to remember to call `can.__reading`.) |
|
2937 return inst.__get(inst.constructor.id); |
|
2938 }, |
|
2939 |
|
2940 // ## ajax |
|
2941 // This helper method makes it easier to make an AJAX call from the configuration of the Model. |
|
2942 ajax = function(ajaxOb, data, type, dataType, success, error) { |
|
2943 |
|
2944 var params = {}; |
|
2945 |
|
2946 // A string here would be something like `"GET /endpoint"`. |
|
2947 if (typeof ajaxOb === 'string') { |
|
2948 // Split on spaces to separate the HTTP method and the URL. |
|
2949 var parts = ajaxOb.split(/\s+/); |
|
2950 params.url = parts.pop(); |
|
2951 if (parts.length) { |
|
2952 params.type = parts.pop(); |
|
2953 } |
|
2954 } else { |
|
2955 // If the first argument is an object, just load it into `params`. |
|
2956 can.extend(params, ajaxOb); |
|
2957 } |
|
2958 |
|
2959 // If the `data` argument is a plain object, copy it into `params`. |
|
2960 params.data = typeof data === "object" && !can.isArray(data) ? |
|
2961 can.extend(params.data || {}, data) : data; |
|
2962 |
|
2963 // Substitute in data for any templated parts of the URL. |
|
2964 params.url = can.sub(params.url, params.data, true); |
|
2965 |
|
2966 return can.ajax(can.extend({ |
|
2967 type: type || 'post', |
|
2968 dataType: dataType || 'json', |
|
2969 success: success, |
|
2970 error: error |
|
2971 }, params)); |
|
2972 }, |
|
2973 |
|
2974 // ## makeRequest |
|
2975 // This function abstracts making the actual AJAX request away from the Model. |
|
2976 makeRequest = function(modelObj, type, success, error, method) { |
|
2977 var args; |
|
2978 |
|
2979 // If `modelObj` is an Array, it it means we are coming from |
|
2980 // the queued request, and we're passing already-serialized data. |
|
2981 if (can.isArray(modelObj)) { |
|
2982 // In that case, modelObj's signature will be `[modelObj, serializedData]`, so we need to unpack it. |
|
2983 args = modelObj[1]; |
|
2984 modelObj = modelObj[0]; |
|
2985 } else { |
|
2986 // If we aren't supplied with serialized data, we'll make our own. |
|
2987 args = modelObj.serialize(); |
|
2988 } |
|
2989 args = [args]; |
|
2990 |
|
2991 var deferred, |
|
2992 model = modelObj.constructor, |
|
2993 jqXHR; |
|
2994 |
|
2995 // When calling `update` and `destroy`, the current ID needs to be the first parameter in the AJAX call. |
|
2996 if (type === 'update' || type === 'destroy') { |
|
2997 args.unshift(getId(modelObj)); |
|
2998 } |
|
2999 jqXHR = model[type].apply(model, args); |
|
3000 |
|
3001 // Make sure that can.Model can react to the request before anything else does. |
|
3002 deferred = pipe(jqXHR, modelObj, function(data) { |
|
3003 // `method` is here because `"destroyed" !== "destroy" + "d"`. |
|
3004 // TODO: Do something smarter/more consistent here? |
|
3005 modelObj[method || type + "d"](data, jqXHR); |
|
3006 return modelObj; |
|
3007 }); |
|
3008 |
|
3009 // Hook up `abort` |
|
3010 if (jqXHR.abort) { |
|
3011 deferred.abort = function() { |
|
3012 jqXHR.abort(); |
|
3013 }; |
|
3014 } |
|
3015 |
|
3016 deferred.then(success, error); |
|
3017 return deferred; |
|
3018 }, |
|
3019 |
|
3020 initializers = { |
|
3021 // ## models |
|
3022 // Returns a function that, when handed a list of objects, makes them into models and returns a model list of them. |
|
3023 // `prop` is the property on `instancesRawData` that has the array of objects in it (if it's not `data`). |
|
3024 models: function(prop) { |
|
3025 return function(instancesRawData, oldList) { |
|
3026 // Increment reqs counter so new instances will be added to the store. |
|
3027 // (This is cleaned up at the end of the method.) |
|
3028 can.Model._reqs++; |
|
3029 |
|
3030 // If there is no data, we can't really do anything with it. |
|
3031 if (!instancesRawData) { |
|
3032 return; |
|
3033 } |
|
3034 |
|
3035 // If the "raw" data is already a List, it's not raw. |
|
3036 if (instancesRawData instanceof this.List) { |
|
3037 return instancesRawData; |
|
3038 } |
|
3039 |
|
3040 var self = this, |
|
3041 // `tmp` will hold the models before we push them onto `modelList`. |
|
3042 tmp = [], |
|
3043 // `ML` (see way below) is just `can.Model.List`. |
|
3044 ListClass = self.List || ML, |
|
3045 modelList = oldList instanceof can.List ? oldList : new ListClass(), |
|
3046 |
|
3047 // Check if we were handed an Array or a model list. |
|
3048 rawDataIsArray = can.isArray(instancesRawData), |
|
3049 rawDataIsList = instancesRawData instanceof ML, |
|
3050 |
|
3051 // Get the "plain" objects from the models from the list/array. |
|
3052 raw = rawDataIsArray ? instancesRawData : ( |
|
3053 rawDataIsList ? instancesRawData.serialize() : can.getObject(prop || "data", instancesRawData)); |
|
3054 |
|
3055 if (typeof raw === 'undefined') { |
|
3056 throw new Error('Could not get any raw data while converting using .models'); |
|
3057 } |
|
3058 |
|
3059 |
|
3060 |
|
3061 // If there was anything left in the list we were given, get rid of it. |
|
3062 if (modelList.length) { |
|
3063 modelList.splice(0); |
|
3064 } |
|
3065 |
|
3066 // If we pushed these directly onto the list, it would cause a change event for each model. |
|
3067 // So, we push them onto `tmp` first and then push everything at once, causing one atomic change event that contains all the models at once. |
|
3068 can.each(raw, function(rawPart) { |
|
3069 tmp.push(self.model(rawPart)); |
|
3070 }); |
|
3071 modelList.push.apply(modelList, tmp); |
|
3072 |
|
3073 // If there was other stuff on `instancesRawData`, let's transfer that onto `modelList` too. |
|
3074 if (!rawDataIsArray) { |
|
3075 can.each(instancesRawData, function(val, prop) { |
|
3076 if (prop !== 'data') { |
|
3077 modelList.attr(prop, val); |
|
3078 } |
|
3079 }); |
|
3080 } |
|
3081 // Clean up the store on the next turn of the event loop. (`this` is a model constructor.) |
|
3082 setTimeout(can.proxy(this._clean, this), 1); |
|
3083 return modelList; |
|
3084 }; |
|
3085 }, |
|
3086 // ## model |
|
3087 // Returns a function that, when handed a plain object, turns it into a model. |
|
3088 // `prop` is the property on `attributes` that has the properties for the model in it. |
|
3089 model: function(prop) { |
|
3090 return function(attributes) { |
|
3091 // If there're no properties, there can be no model. |
|
3092 if (!attributes) { |
|
3093 return; |
|
3094 } |
|
3095 // If this object knows how to serialize, parse, or access itself, we'll use that instead. |
|
3096 if (typeof attributes.serialize === 'function') { |
|
3097 attributes = attributes.serialize(); |
|
3098 } |
|
3099 if (this.parseModel) { |
|
3100 attributes = this.parseModel.apply(this, arguments); |
|
3101 } else if (prop) { |
|
3102 attributes = can.getObject(prop || "data", attributes); |
|
3103 } |
|
3104 |
|
3105 var id = attributes[this.id], |
|
3106 // 0 is a valid ID. |
|
3107 model = (id || id === 0) && this.store[id] ? |
|
3108 // If this model is in the store already, just update it. |
|
3109 this.store[id].attr(attributes, this.removeAttr || false) : |
|
3110 // Otherwise, we need a new model. |
|
3111 new this(attributes); |
|
3112 |
|
3113 return model; |
|
3114 }; |
|
3115 } |
|
3116 }, |
|
3117 |
|
3118 |
|
3119 parserMaker = function(prop) { |
|
3120 return function(attributes) { |
|
3121 return prop ? can.getObject(prop || "data", attributes) : attributes; |
|
3122 }; |
|
3123 }, |
|
3124 |
|
3125 // ## parsers |
|
3126 // This object describes how to take the data from an AJAX request and prepare it for `models` and `model`. |
|
3127 // These functions are meant to be overwritten (if necessary) in an extended model constructor. |
|
3128 parsers = { |
|
3129 |
|
3130 parseModel: parserMaker, |
|
3131 |
|
3132 parseModels: parserMaker |
|
3133 }, |
|
3134 |
|
3135 // ## ajaxMethods |
|
3136 // This object describes how to make an AJAX request for each ajax method (`create`, `update`, etc.) |
|
3137 // Each AJAX method is an object in `ajaxMethods` and can have the following properties: |
|
3138 // - `url`: Which property on the model contains the default URL for this method. |
|
3139 // - `type`: The default HTTP request method. |
|
3140 // - `data`: A method that takes the arguments from `makeRequest` (see above) and returns a data object for use in the AJAX call. |
|
3141 |
|
3142 |
|
3143 ajaxMethods = { |
|
3144 |
|
3145 create: { |
|
3146 url: "_shortName", |
|
3147 type: "post" |
|
3148 }, |
|
3149 |
|
3150 update: { |
|
3151 // ## update.data |
|
3152 data: function(id, attrs) { |
|
3153 attrs = attrs || {}; |
|
3154 |
|
3155 // `this.id` is the property that represents the ID (and is usually `"id"`). |
|
3156 var identity = this.id; |
|
3157 |
|
3158 // If the value of the property being used as the ID changed, |
|
3159 // indicate that in the request and replace the current ID property. |
|
3160 if (attrs[identity] && attrs[identity] !== id) { |
|
3161 attrs["new" + can.capitalize(id)] = attrs[identity]; |
|
3162 delete attrs[identity]; |
|
3163 } |
|
3164 attrs[identity] = id; |
|
3165 |
|
3166 return attrs; |
|
3167 }, |
|
3168 type: "put" |
|
3169 }, |
|
3170 |
|
3171 destroy: { |
|
3172 type: 'delete', |
|
3173 // ## destroy.data |
|
3174 data: function(id, attrs) { |
|
3175 attrs = attrs || {}; |
|
3176 // `this.id` is the property that represents the ID (and is usually `"id"`). |
|
3177 attrs.id = attrs[this.id] = id; |
|
3178 return attrs; |
|
3179 } |
|
3180 }, |
|
3181 |
|
3182 findAll: { |
|
3183 url: "_shortName" |
|
3184 }, |
|
3185 |
|
3186 findOne: {} |
|
3187 }, |
|
3188 // ## ajaxMaker |
|
3189 // Takes a method defined just above and a string that describes how to call that method |
|
3190 // and makes a function that calls that method with the given data. |
|
3191 // - `ajaxMethod`: The object defined above in `ajaxMethods`. |
|
3192 // - `str`: The string the configuration provided (such as `"/recipes.json"` for a `findAll` call). |
|
3193 ajaxMaker = function(ajaxMethod, str) { |
|
3194 return function(data) { |
|
3195 data = ajaxMethod.data ? |
|
3196 // If the AJAX method mentioned above has its own way of getting `data`, use that. |
|
3197 ajaxMethod.data.apply(this, arguments) : |
|
3198 // Otherwise, just use the data passed in. |
|
3199 data; |
|
3200 |
|
3201 // Make the AJAX call with the URL, data, and type indicated by the proper `ajaxMethod` above. |
|
3202 return ajax(str || this[ajaxMethod.url || "_url"], data, ajaxMethod.type || "get"); |
|
3203 }; |
|
3204 }, |
|
3205 // ## createURLFromResource |
|
3206 // For each of the names (create, update, destroy, findOne, and findAll) use the |
|
3207 // URL provided by the `resource` property. For example: |
|
3208 // ToDo = can.Model.extend({ |
|
3209 // resource: "/todos" |
|
3210 // }, {}); |
|
3211 // Will create a can.Model that is identical to: |
|
3212 // ToDo = can.Model.extend({ |
|
3213 // findAll: "GET /todos", |
|
3214 // findOne: "GET /todos/{id}", |
|
3215 // create: "POST /todos", |
|
3216 // update: "PUT /todos/{id}", |
|
3217 // destroy: "DELETE /todos/{id}" |
|
3218 // },{}); |
|
3219 // - `model`: the can.Model that has the resource property |
|
3220 // - `method`: a property from the ajaxMethod object |
|
3221 createURLFromResource = function(model, name) { |
|
3222 if (!model.resource) { |
|
3223 return; |
|
3224 } |
|
3225 |
|
3226 var resource = model.resource.replace(/\/+$/, ""); |
|
3227 if (name === "findAll" || name === "create") { |
|
3228 return resource; |
|
3229 } else { |
|
3230 return resource + "/{" + model.id + "}"; |
|
3231 } |
|
3232 }; |
|
3233 |
|
3234 // # can.Model |
|
3235 // A can.Map that connects to a RESTful interface. |
|
3236 can.Model = can.Map.extend({ |
|
3237 // `fullName` identifies the model type in debugging. |
|
3238 fullName: "can.Model", |
|
3239 _reqs: 0, |
|
3240 // ## can.Model.setup |
|
3241 |
|
3242 setup: function(base, fullName, staticProps, protoProps) { |
|
3243 // Assume `fullName` wasn't passed. (`can.Model.extend({ ... }, { ... })`) |
|
3244 // This is pretty usual. |
|
3245 if (fullName !== "string") { |
|
3246 protoProps = staticProps; |
|
3247 staticProps = fullName; |
|
3248 } |
|
3249 // Assume no static properties were passed. (`can.Model.extend({ ... })`) |
|
3250 // This is really unusual for a model though, since there's so much configuration. |
|
3251 if (!protoProps) { |
|
3252 protoProps = staticProps; |
|
3253 } |
|
3254 |
|
3255 // Create the model store here, in case someone wants to use can.Model without inheriting from it. |
|
3256 this.store = {}; |
|
3257 |
|
3258 can.Map.setup.apply(this, arguments); |
|
3259 if (!can.Model) { |
|
3260 return; |
|
3261 } |
|
3262 |
|
3263 // `List` is just a regular can.Model.List that knows what kind of Model it's hooked up to. |
|
3264 |
|
3265 if (staticProps && staticProps.List) { |
|
3266 this.List = staticProps.List; |
|
3267 this.List.Map = this; |
|
3268 } else { |
|
3269 this.List = base.List.extend({ |
|
3270 Map: this |
|
3271 }, {}); |
|
3272 } |
|
3273 |
|
3274 var self = this, |
|
3275 clean = can.proxy(this._clean, self); |
|
3276 |
|
3277 // Go through `ajaxMethods` and set up static methods according to their configurations. |
|
3278 can.each(ajaxMethods, function(method, name) { |
|
3279 // Check the configuration for this ajaxMethod. |
|
3280 // If the configuration isn't a function, it should be a string (like `"GET /endpoint"`) |
|
3281 // or an object like `{url: "/endpoint", type: 'GET'}`. |
|
3282 if (!can.isFunction(self[name])) { |
|
3283 // Etiher way, `ajaxMaker` will turn it into a function for us. |
|
3284 self[name] = ajaxMaker(method, self[name] ? self[name] : createURLFromResource(self, name)); |
|
3285 } |
|
3286 |
|
3287 // There may also be a "maker" function (like `makeFindAll`) that alters the behavior of acting upon models |
|
3288 // by changing when and how the function we just made with `ajaxMaker` gets called. |
|
3289 // For example, you might cache responses and only make a call when you don't have a cached response. |
|
3290 if (self["make" + can.capitalize(name)]) { |
|
3291 // Use the "maker" function to make the new "ajaxMethod" function. |
|
3292 var newMethod = self["make" + can.capitalize(name)](self[name]); |
|
3293 // Replace the "ajaxMethod" function in the configuration with the new one. |
|
3294 // (`_overwrite` just overwrites a property in a given Construct.) |
|
3295 can.Construct._overwrite(self, base, name, function() { |
|
3296 // Increment the numer of requests... |
|
3297 can.Model._reqs++; |
|
3298 // ...make the AJAX call (and whatever else you're doing)... |
|
3299 var def = newMethod.apply(this, arguments); |
|
3300 // ...and clean up the store. |
|
3301 var then = def.then(clean, clean); |
|
3302 // Pass along `abort` so you can still abort the AJAX call. |
|
3303 then.abort = def.abort; |
|
3304 |
|
3305 return then; |
|
3306 }); |
|
3307 } |
|
3308 }); |
|
3309 |
|
3310 // Set up the methods that will set up `models` and `model`. |
|
3311 can.each(initializers, function(makeInitializer, name) { |
|
3312 var parseName = "parse" + can.capitalize(name), |
|
3313 dataProperty = self[name]; |
|
3314 |
|
3315 // If there was a different property to find the model's data in than `data`, |
|
3316 // make `parseModel` and `parseModels` functions that look that up instead. |
|
3317 if (typeof dataProperty === "string") { |
|
3318 can.Construct._overwrite(self, base, parseName, parsers[parseName](dataProperty)); |
|
3319 can.Construct._overwrite(self, base, name, makeInitializer(dataProperty)); |
|
3320 } |
|
3321 |
|
3322 // If there was no prototype, or no `model` and no `parseModel`, |
|
3323 // we'll have to create a `parseModel`. |
|
3324 else if (!protoProps || (!protoProps[name] && !protoProps[parseName])) { |
|
3325 can.Construct._overwrite(self, base, parseName, parsers[parseName]()); |
|
3326 } |
|
3327 }); |
|
3328 |
|
3329 // With the overridden parse methods, set up `models` and `model`. |
|
3330 can.each(parsers, function(makeParser, name) { |
|
3331 // If there was a different property to find the model's data in than `data`, |
|
3332 // make `model` and `models` functions that look that up instead. |
|
3333 if (typeof self[name] === "string") { |
|
3334 can.Construct._overwrite(self, base, name, makeParser(self[name])); |
|
3335 } |
|
3336 }); |
|
3337 |
|
3338 // Make sure we have a unique name for this Model. |
|
3339 if (self.fullName === "can.Model" || !self.fullName) { |
|
3340 self.fullName = "Model" + (++modelNum); |
|
3341 } |
|
3342 |
|
3343 can.Model._reqs = 0; |
|
3344 this._url = this._shortName + "/{" + this.id + "}"; |
|
3345 }, |
|
3346 _ajax: ajaxMaker, |
|
3347 _makeRequest: makeRequest, |
|
3348 // ## can.Model._clean |
|
3349 // `_clean` cleans up the model store after a request happens. |
|
3350 _clean: function() { |
|
3351 can.Model._reqs--; |
|
3352 // Don't clean up unless we have no pending requests. |
|
3353 if (!can.Model._reqs) { |
|
3354 for (var id in this.store) { |
|
3355 // Delete all items in the store without any event bindings. |
|
3356 if (!this.store[id]._bindings) { |
|
3357 delete this.store[id]; |
|
3358 } |
|
3359 } |
|
3360 } |
|
3361 return arguments[0]; |
|
3362 }, |
|
3363 |
|
3364 models: initializers.models("data"), |
|
3365 |
|
3366 model: initializers.model() |
|
3367 }, |
|
3368 |
|
3369 |
|
3370 { |
|
3371 // ## can.Model#setup |
|
3372 setup: function(attrs) { |
|
3373 // Try to add things as early as possible to the store (#457). |
|
3374 // This is the earliest possible moment, even before any properties are set. |
|
3375 var id = attrs && attrs[this.constructor.id]; |
|
3376 if (can.Model._reqs && id != null) { |
|
3377 this.constructor.store[id] = this; |
|
3378 } |
|
3379 can.Map.prototype.setup.apply(this, arguments); |
|
3380 }, |
|
3381 // ## can.Model#isNew |
|
3382 // Something is new if its ID is `null` or `undefined`. |
|
3383 |
|
3384 isNew: function() { |
|
3385 var id = getId(this); |
|
3386 // 0 is a valid ID. |
|
3387 // TODO: Why not `return id === null || id === undefined;`? |
|
3388 return !(id || id === 0); // If `null` or `undefined` |
|
3389 }, |
|
3390 // ## can.Model#save |
|
3391 // `save` calls `create` or `update` as necessary, based on whether a model is new. |
|
3392 |
|
3393 save: function(success, error) { |
|
3394 return makeRequest(this, this.isNew() ? 'create' : 'update', success, error); |
|
3395 }, |
|
3396 // ## can.Model#destroy |
|
3397 // Acts like can.Map.destroy but it also makes an AJAX call. |
|
3398 |
|
3399 destroy: function(success, error) { |
|
3400 // If this model is new, don't make an AJAX call. |
|
3401 // Instead, we have to construct the Deferred ourselves and return it. |
|
3402 if (this.isNew()) { |
|
3403 var self = this; |
|
3404 var def = can.Deferred(); |
|
3405 def.then(success, error); |
|
3406 |
|
3407 return def.done(function(data) { |
|
3408 self.destroyed(data); |
|
3409 }).resolve(self); |
|
3410 } |
|
3411 |
|
3412 // If it isn't new, though, go ahead and make a request. |
|
3413 return makeRequest(this, 'destroy', success, error, 'destroyed'); |
|
3414 }, |
|
3415 // ## can.Model#bind and can.Model#unbind |
|
3416 // These aren't actually implemented here, but their setup needs to be changed to account for the store. |
|
3417 |
|
3418 _bindsetup: function() { |
|
3419 this.constructor.store[this.__get(this.constructor.id)] = this; |
|
3420 return can.Map.prototype._bindsetup.apply(this, arguments); |
|
3421 }, |
|
3422 |
|
3423 _bindteardown: function() { |
|
3424 delete this.constructor.store[getId(this)]; |
|
3425 return can.Map.prototype._bindteardown.apply(this, arguments); |
|
3426 }, |
|
3427 // Change the behavior of `___set` to account for the store. |
|
3428 ___set: function(prop, val) { |
|
3429 can.Map.prototype.___set.call(this, prop, val); |
|
3430 // If we add or change the ID, update the store accordingly. |
|
3431 // TODO: shouldn't this also delete the record from the old ID in the store? |
|
3432 if (prop === this.constructor.id && this._bindings) { |
|
3433 this.constructor.store[getId(this)] = this; |
|
3434 } |
|
3435 } |
|
3436 }); |
|
3437 |
|
3438 // Returns a function that knows how to prepare data from `findAll` or `findOne` calls. |
|
3439 // `name` should be either `model` or `models`. |
|
3440 var makeGetterHandler = function(name) { |
|
3441 var parseName = "parse" + can.capitalize(name); |
|
3442 return function(data) { |
|
3443 // If there's a `parse...` function, use its output. |
|
3444 if (this[parseName]) { |
|
3445 data = this[parseName].apply(this, arguments); |
|
3446 } |
|
3447 // Run our maybe-parsed data through `model` or `models`. |
|
3448 return this[name](data); |
|
3449 }; |
|
3450 }, |
|
3451 // Handle data returned from `create`, `update`, and `destroy` calls. |
|
3452 createUpdateDestroyHandler = function(data) { |
|
3453 if (this.parseModel) { |
|
3454 return this.parseModel.apply(this, arguments); |
|
3455 } else { |
|
3456 return this.model(data); |
|
3457 } |
|
3458 }; |
|
3459 |
|
3460 var responseHandlers = { |
|
3461 |
|
3462 makeFindAll: makeGetterHandler("models"), |
|
3463 |
|
3464 makeFindOne: makeGetterHandler("model"), |
|
3465 makeCreate: createUpdateDestroyHandler, |
|
3466 makeUpdate: createUpdateDestroyHandler |
|
3467 }; |
|
3468 |
|
3469 // Go through the response handlers and make the actual "make" methods. |
|
3470 can.each(responseHandlers, function(method, name) { |
|
3471 can.Model[name] = function(oldMethod) { |
|
3472 return function() { |
|
3473 var args = can.makeArray(arguments), |
|
3474 // If args[1] is a function, we were only passed one argument before success and failure callbacks. |
|
3475 oldArgs = can.isFunction(args[1]) ? args.splice(0, 1) : args.splice(0, 2), |
|
3476 // Call the AJAX method (`findAll` or `update`, etc.) and pipe it through the response handler from above. |
|
3477 def = pipe(oldMethod.apply(this, oldArgs), this, method); |
|
3478 |
|
3479 def.then(args[0], args[1]); |
|
3480 return def; |
|
3481 }; |
|
3482 }; |
|
3483 }); |
|
3484 |
|
3485 // ## can.Model.created, can.Model.updated, and can.Model.destroyed |
|
3486 // Livecycle methods for models. |
|
3487 can.each([ |
|
3488 |
|
3489 "created", |
|
3490 |
|
3491 "updated", |
|
3492 |
|
3493 "destroyed" |
|
3494 ], function(funcName) { |
|
3495 // Each of these is pretty much the same, except for the events they trigger. |
|
3496 can.Model.prototype[funcName] = function(attrs) { |
|
3497 var stub, |
|
3498 constructor = this.constructor; |
|
3499 |
|
3500 // Update attributes if attributes have been passed |
|
3501 stub = attrs && typeof attrs === 'object' && this.attr(attrs.attr ? attrs.attr() : attrs); |
|
3502 |
|
3503 // triggers change event that bubble's like |
|
3504 // handler( 'change','1.destroyed' ). This is used |
|
3505 // to remove items on destroyed from Model Lists. |
|
3506 // but there should be a better way. |
|
3507 can.trigger(this, "change", funcName); |
|
3508 |
|
3509 |
|
3510 |
|
3511 // Call event on the instance's Class |
|
3512 can.trigger(constructor, funcName, this); |
|
3513 }; |
|
3514 }); |
|
3515 |
|
3516 |
|
3517 // # can.Model.List |
|
3518 // Model Lists are just like `Map.List`s except that when their items are |
|
3519 // destroyed, they automatically get removed from the List. |
|
3520 var ML = can.Model.List = can.List.extend({ |
|
3521 // ## can.Model.List.setup |
|
3522 // On change or a nested named event, setup change bubbling. |
|
3523 // On any other type of event, setup "destroyed" bubbling. |
|
3524 _bubbleRule: function(eventName, list) { |
|
3525 return can.List._bubbleRule(eventName, list) || "destroyed"; |
|
3526 } |
|
3527 }, { |
|
3528 setup: function(params) { |
|
3529 // If there was a plain object passed to the List constructor, |
|
3530 // we use those as parameters for an initial findAll. |
|
3531 if (can.isPlainObject(params) && !can.isArray(params)) { |
|
3532 can.List.prototype.setup.apply(this); |
|
3533 this.replace(this.constructor.Map.findAll(params)); |
|
3534 } else { |
|
3535 // Otherwise, set up the list like normal. |
|
3536 can.List.prototype.setup.apply(this, arguments); |
|
3537 } |
|
3538 this._init = 1; |
|
3539 this.bind('destroyed', can.proxy(this._destroyed, this)); |
|
3540 delete this._init; |
|
3541 }, |
|
3542 _destroyed: function(ev, attr) { |
|
3543 if (/\w+/.test(attr)) { |
|
3544 var index; |
|
3545 while ((index = this.indexOf(ev.target)) > -1) { |
|
3546 this.splice(index, 1); |
|
3547 } |
|
3548 } |
|
3549 } |
|
3550 }); |
|
3551 |
|
3552 return can.Model; |
|
3553 })(__m3, __m10, __m14); |
|
3554 |
|
3555 // ## can/view/view.js |
|
3556 var __m17 = (function(can) { |
|
3557 |
|
3558 var isFunction = can.isFunction, |
|
3559 makeArray = can.makeArray, |
|
3560 // Used for hookup `id`s. |
|
3561 hookupId = 1; |
|
3562 |
|
3563 // internal utility methods |
|
3564 // ------------------------ |
|
3565 |
|
3566 // ##### makeRenderer |
|
3567 |
|
3568 var makeRenderer = function(textRenderer) { |
|
3569 var renderer = function() { |
|
3570 return $view.frag(textRenderer.apply(this, arguments)); |
|
3571 }; |
|
3572 renderer.render = function() { |
|
3573 return textRenderer.apply(textRenderer, arguments); |
|
3574 }; |
|
3575 return renderer; |
|
3576 }; |
|
3577 |
|
3578 // ##### checkText |
|
3579 // Makes sure there's a template, if not, have `steal` provide a warning. |
|
3580 var checkText = function(text, url) { |
|
3581 if (!text.length) { |
|
3582 |
|
3583 // _removed if not used as a steal module_ |
|
3584 |
|
3585 |
|
3586 |
|
3587 throw "can.view: No template or empty template:" + url; |
|
3588 } |
|
3589 }; |
|
3590 |
|
3591 // ##### get |
|
3592 // get a deferred renderer for provided url |
|
3593 |
|
3594 var get = function(obj, async) { |
|
3595 var url = typeof obj === 'string' ? obj : obj.url, |
|
3596 suffix = (obj.engine && '.' + obj.engine) || url.match(/\.[\w\d]+$/), |
|
3597 type, |
|
3598 // If we are reading a script element for the content of the template, |
|
3599 // `el` will be set to that script element. |
|
3600 el, |
|
3601 // A unique identifier for the view (used for caching). |
|
3602 // This is typically derived from the element id or |
|
3603 // the url for the template. |
|
3604 id; |
|
3605 |
|
3606 //If the url has a #, we assume we want to use an inline template |
|
3607 //from a script element and not current page's HTML |
|
3608 if (url.match(/^#/)) { |
|
3609 url = url.substr(1); |
|
3610 } |
|
3611 // If we have an inline template, derive the suffix from the `text/???` part. |
|
3612 // This only supports `<script>` tags. |
|
3613 if (el = document.getElementById(url)) { |
|
3614 suffix = '.' + el.type.match(/\/(x\-)?(.+)/)[2]; |
|
3615 } |
|
3616 |
|
3617 // If there is no suffix, add one. |
|
3618 if (!suffix && !$view.cached[url]) { |
|
3619 url += suffix = $view.ext; |
|
3620 } |
|
3621 |
|
3622 // if the suffix was derived from the .match() operation, pluck out the first value |
|
3623 if (can.isArray(suffix)) { |
|
3624 suffix = suffix[0]; |
|
3625 } |
|
3626 |
|
3627 // Convert to a unique and valid id. |
|
3628 id = $view.toId(url); |
|
3629 |
|
3630 // If an absolute path, use `steal`/`require` to get it. |
|
3631 // You should only be using `//` if you are using an AMD loader like `steal` or `require` (not almond). |
|
3632 if (url.match(/^\/\//)) { |
|
3633 url = url.substr(2); |
|
3634 url = !window.steal ? |
|
3635 url : |
|
3636 steal.config() |
|
3637 .root.mapJoin("" + steal.id(url)); |
|
3638 } |
|
3639 |
|
3640 // Localize for `require` (not almond) |
|
3641 if (window.require) { |
|
3642 if (require.toUrl) { |
|
3643 url = require.toUrl(url); |
|
3644 } |
|
3645 } |
|
3646 |
|
3647 // Set the template engine type. |
|
3648 type = $view.types[suffix]; |
|
3649 |
|
3650 // If it is cached, |
|
3651 if ($view.cached[id]) { |
|
3652 // Return the cached deferred renderer. |
|
3653 return $view.cached[id]; |
|
3654 |
|
3655 // Otherwise if we are getting this from a `<script>` element. |
|
3656 } else if (el) { |
|
3657 // Resolve immediately with the element's `innerHTML`. |
|
3658 return $view.registerView(id, el.innerHTML, type); |
|
3659 } else { |
|
3660 // Make an ajax request for text. |
|
3661 var d = new can.Deferred(); |
|
3662 can.ajax({ |
|
3663 async: async, |
|
3664 url: url, |
|
3665 dataType: 'text', |
|
3666 error: function(jqXHR) { |
|
3667 checkText('', url); |
|
3668 d.reject(jqXHR); |
|
3669 }, |
|
3670 success: function(text) { |
|
3671 // Make sure we got some text back. |
|
3672 checkText(text, url); |
|
3673 $view.registerView(id, text, type, d); |
|
3674 } |
|
3675 }); |
|
3676 return d; |
|
3677 } |
|
3678 }; |
|
3679 // ##### getDeferreds |
|
3680 // Gets an `array` of deferreds from an `object`. |
|
3681 // This only goes one level deep. |
|
3682 |
|
3683 var getDeferreds = function(data) { |
|
3684 var deferreds = []; |
|
3685 |
|
3686 // pull out deferreds |
|
3687 if (can.isDeferred(data)) { |
|
3688 return [data]; |
|
3689 } else { |
|
3690 for (var prop in data) { |
|
3691 if (can.isDeferred(data[prop])) { |
|
3692 deferreds.push(data[prop]); |
|
3693 } |
|
3694 } |
|
3695 } |
|
3696 return deferreds; |
|
3697 }; |
|
3698 |
|
3699 // ##### usefulPart |
|
3700 // Gets the useful part of a resolved deferred. |
|
3701 // When a jQuery.when is resolved, it returns an array to each argument. |
|
3702 // Reference ($.when)[https://api.jquery.com/jQuery.when/] |
|
3703 // This is for `model`s and `can.ajax` that resolve to an `array`. |
|
3704 |
|
3705 var usefulPart = function(resolved) { |
|
3706 return can.isArray(resolved) && resolved[1] === 'success' ? resolved[0] : resolved; |
|
3707 }; |
|
3708 |
|
3709 // #### can.view |
|
3710 //defines $view for internal use, can.template for backwards compatibility |
|
3711 |
|
3712 var $view = can.view = can.template = function(view, data, helpers, callback) { |
|
3713 // If helpers is a `function`, it is actually a callback. |
|
3714 if (isFunction(helpers)) { |
|
3715 callback = helpers; |
|
3716 helpers = undefined; |
|
3717 } |
|
3718 var result; |
|
3719 // Get the result, if a renderer function is passed in, then we just use that to render the data |
|
3720 if (isFunction(view)) { |
|
3721 result = view(data, helpers, callback); |
|
3722 } else { |
|
3723 result = $view.renderAs("fragment", view, data, helpers, callback); |
|
3724 } |
|
3725 |
|
3726 return result; |
|
3727 }; |
|
3728 |
|
3729 // can.view methods |
|
3730 // -------------------------- |
|
3731 can.extend($view, { |
|
3732 // ##### frag |
|
3733 // creates a fragment and hooks it up all at once |
|
3734 |
|
3735 frag: function(result, parentNode) { |
|
3736 return $view.hookup($view.fragment(result), parentNode); |
|
3737 }, |
|
3738 |
|
3739 // #### fragment |
|
3740 // this is used internally to create a document fragment, insert it,then hook it up |
|
3741 fragment: function(result) { |
|
3742 if (typeof result !== "string" && result.nodeType === 11) { |
|
3743 return result; |
|
3744 } |
|
3745 var frag = can.buildFragment(result, document.body); |
|
3746 // If we have an empty frag... |
|
3747 if (!frag.childNodes.length) { |
|
3748 frag.appendChild(document.createTextNode('')); |
|
3749 } |
|
3750 return frag; |
|
3751 }, |
|
3752 |
|
3753 // ##### toId |
|
3754 // Convert a path like string into something that's ok for an `element` ID. |
|
3755 toId: function(src) { |
|
3756 return can.map(src.toString() |
|
3757 .split(/\/|\./g), function(part) { |
|
3758 // Dont include empty strings in toId functions |
|
3759 if (part) { |
|
3760 return part; |
|
3761 } |
|
3762 }) |
|
3763 .join('_'); |
|
3764 }, |
|
3765 // ##### toStr |
|
3766 // convert argument to a string |
|
3767 toStr: function(txt) { |
|
3768 return txt == null ? "" : "" + txt; |
|
3769 }, |
|
3770 |
|
3771 // ##### hookup |
|
3772 // attach the provided `fragment` to `parentNode` |
|
3773 |
|
3774 hookup: function(fragment, parentNode) { |
|
3775 var hookupEls = [], |
|
3776 id, |
|
3777 func; |
|
3778 |
|
3779 // Get all `childNodes`. |
|
3780 can.each(fragment.childNodes ? can.makeArray(fragment.childNodes) : fragment, function(node) { |
|
3781 if (node.nodeType === 1) { |
|
3782 hookupEls.push(node); |
|
3783 hookupEls.push.apply(hookupEls, can.makeArray(node.getElementsByTagName('*'))); |
|
3784 } |
|
3785 }); |
|
3786 |
|
3787 // Filter by `data-view-id` attribute. |
|
3788 can.each(hookupEls, function(el) { |
|
3789 if (el.getAttribute && (id = el.getAttribute('data-view-id')) && (func = $view.hookups[id])) { |
|
3790 func(el, parentNode, id); |
|
3791 delete $view.hookups[id]; |
|
3792 el.removeAttribute('data-view-id'); |
|
3793 } |
|
3794 }); |
|
3795 |
|
3796 return fragment; |
|
3797 }, |
|
3798 |
|
3799 // `hookups` keeps list of pending hookups, ie fragments to attach to a parent node |
|
3800 |
|
3801 hookups: {}, |
|
3802 |
|
3803 // `hook` factory method for hookup function inserted into templates |
|
3804 // hookup functions are called after the html is rendered to the page |
|
3805 // only implemented by EJS templates. |
|
3806 |
|
3807 hook: function(cb) { |
|
3808 $view.hookups[++hookupId] = cb; |
|
3809 return ' data-view-id=\'' + hookupId + '\''; |
|
3810 }, |
|
3811 |
|
3812 |
|
3813 cached: {}, |
|
3814 cachedRenderers: {}, |
|
3815 |
|
3816 // cache view templates resolved via XHR on the client |
|
3817 |
|
3818 cache: true, |
|
3819 |
|
3820 // ##### register |
|
3821 // given an info object, register a template type |
|
3822 // different templating solutions produce strings or document fragments via their renderer function |
|
3823 |
|
3824 register: function(info) { |
|
3825 this.types['.' + info.suffix] = info; |
|
3826 |
|
3827 // _removed if not used as a steal module_ |
|
3828 |
|
3829 |
|
3830 |
|
3831 can[info.suffix] = $view[info.suffix] = function(id, text) { |
|
3832 // If there is no text, assume id is the template text, so return a nameless renderer. |
|
3833 if (!text) { |
|
3834 // if the template has a fragRenderer already, just return that. |
|
3835 if (info.fragRenderer) { |
|
3836 return info.fragRenderer(null, id); |
|
3837 } else { |
|
3838 return makeRenderer(info.renderer(null, id)); |
|
3839 } |
|
3840 |
|
3841 } |
|
3842 if (info.fragRenderer) { |
|
3843 return $view.preload(id, info.fragRenderer(id, text)); |
|
3844 } else { |
|
3845 return $view.preloadStringRenderer(id, info.renderer(id, text)); |
|
3846 } |
|
3847 |
|
3848 }; |
|
3849 |
|
3850 }, |
|
3851 |
|
3852 //registered view types |
|
3853 types: {}, |
|
3854 |
|
3855 |
|
3856 ext: ".ejs", |
|
3857 |
|
3858 |
|
3859 registerScript: function(type, id, src) { |
|
3860 return 'can.view.preloadStringRenderer(\'' + id + '\',' + $view.types['.' + type].script(id, src) + ');'; |
|
3861 }, |
|
3862 |
|
3863 |
|
3864 preload: function(id, renderer) { |
|
3865 var def = $view.cached[id] = new can.Deferred() |
|
3866 .resolve(function(data, helpers) { |
|
3867 return renderer.call(data, data, helpers); |
|
3868 }); |
|
3869 |
|
3870 // set cache references (otherwise preloaded recursive views won't recurse properly) |
|
3871 def.__view_id = id; |
|
3872 $view.cachedRenderers[id] = renderer; |
|
3873 |
|
3874 return renderer; |
|
3875 }, |
|
3876 |
|
3877 |
|
3878 preloadStringRenderer: function(id, stringRenderer) { |
|
3879 return this.preload(id, makeRenderer(stringRenderer)); |
|
3880 }, |
|
3881 |
|
3882 // #### renderers |
|
3883 // --------------- |
|
3884 // can.view's primary purpose is to load templates (from strings or filesystem) and render them |
|
3885 // can.view supports two different forms of rendering systems |
|
3886 // mustache templates return a string based rendering function |
|
3887 |
|
3888 // stache (or other fragment based templating systems) return a document fragment, so 'hookup' steps are not required |
|
3889 // ##### render |
|
3890 //call `renderAs` with a hardcoded string, as view.render |
|
3891 // always operates against resolved template files or hardcoded strings |
|
3892 render: function(view, data, helpers, callback) { |
|
3893 return can.view.renderAs("string", view, data, helpers, callback); |
|
3894 }, |
|
3895 |
|
3896 // ##### renderTo |
|
3897 renderTo: function(format, renderer, data, helpers) { |
|
3898 return (format === "string" && renderer.render ? renderer.render : renderer)(data, helpers); |
|
3899 }, |
|
3900 |
|
3901 |
|
3902 renderAs: function(format, view, data, helpers, callback) { |
|
3903 // If helpers is a `function`, it is actually a callback. |
|
3904 if (isFunction(helpers)) { |
|
3905 callback = helpers; |
|
3906 helpers = undefined; |
|
3907 } |
|
3908 |
|
3909 // See if we got passed any deferreds. |
|
3910 var deferreds = getDeferreds(data); |
|
3911 var reading, deferred, dataCopy, async, response; |
|
3912 if (deferreds.length) { |
|
3913 // Does data contain any deferreds? |
|
3914 // The deferred that resolves into the rendered content... |
|
3915 deferred = new can.Deferred(); |
|
3916 dataCopy = can.extend({}, data); |
|
3917 |
|
3918 // Add the view request to the list of deferreds. |
|
3919 deferreds.push(get(view, true)); |
|
3920 // Wait for the view and all deferreds to finish... |
|
3921 can.when.apply(can, deferreds) |
|
3922 .then(function(resolved) { |
|
3923 // Get all the resolved deferreds. |
|
3924 var objs = makeArray(arguments), |
|
3925 // Renderer is the last index of the data. |
|
3926 renderer = objs.pop(), |
|
3927 // The result of the template rendering with data. |
|
3928 result; |
|
3929 |
|
3930 // Make data look like the resolved deferreds. |
|
3931 if (can.isDeferred(data)) { |
|
3932 dataCopy = usefulPart(resolved); |
|
3933 } else { |
|
3934 // Go through each prop in data again and |
|
3935 // replace the defferreds with what they resolved to. |
|
3936 for (var prop in data) { |
|
3937 if (can.isDeferred(data[prop])) { |
|
3938 dataCopy[prop] = usefulPart(objs.shift()); |
|
3939 } |
|
3940 } |
|
3941 } |
|
3942 |
|
3943 // Get the rendered result. |
|
3944 result = can.view.renderTo(format, renderer, dataCopy, helpers); |
|
3945 |
|
3946 // Resolve with the rendered view. |
|
3947 deferred.resolve(result, dataCopy); |
|
3948 |
|
3949 // If there's a `callback`, call it back with the result. |
|
3950 if (callback) { |
|
3951 callback(result, dataCopy); |
|
3952 } |
|
3953 }, function() { |
|
3954 deferred.reject.apply(deferred, arguments); |
|
3955 }); |
|
3956 // Return the deferred... |
|
3957 return deferred; |
|
3958 } else { |
|
3959 // get is called async but in |
|
3960 // ff will be async so we need to temporarily reset |
|
3961 reading = can.__clearReading(); |
|
3962 |
|
3963 // If there's a `callback` function |
|
3964 async = isFunction(callback); |
|
3965 // Get the `view` type |
|
3966 deferred = get(view, async); |
|
3967 |
|
3968 if (reading) { |
|
3969 can.__setReading(reading); |
|
3970 } |
|
3971 |
|
3972 // If we are `async`... |
|
3973 if (async) { |
|
3974 // Return the deferred |
|
3975 response = deferred; |
|
3976 // And fire callback with the rendered result. |
|
3977 deferred.then(function(renderer) { |
|
3978 callback(data ? can.view.renderTo(format, renderer, data, helpers) : renderer); |
|
3979 }); |
|
3980 } else { |
|
3981 // if the deferred is resolved, call the cached renderer instead |
|
3982 // this is because it's possible, with recursive deferreds to |
|
3983 // need to render a view while its deferred is _resolving_. A _resolving_ deferred |
|
3984 // is a deferred that was just resolved and is calling back it's success callbacks. |
|
3985 // If a new success handler is called while resoliving, it does not get fired by |
|
3986 // jQuery's deferred system. So instead of adding a new callback |
|
3987 // we use the cached renderer. |
|
3988 // We also add __view_id on the deferred so we can look up it's cached renderer. |
|
3989 // In the future, we might simply store either a deferred or the cached result. |
|
3990 if (deferred.state() === 'resolved' && deferred.__view_id) { |
|
3991 var currentRenderer = $view.cachedRenderers[deferred.__view_id]; |
|
3992 return data ? can.view.renderTo(format, currentRenderer, data, helpers) : currentRenderer; |
|
3993 } else { |
|
3994 // Otherwise, the deferred is complete, so |
|
3995 // set response to the result of the rendering. |
|
3996 deferred.then(function(renderer) { |
|
3997 response = data ? can.view.renderTo(format, renderer, data, helpers) : renderer; |
|
3998 }); |
|
3999 } |
|
4000 } |
|
4001 |
|
4002 return response; |
|
4003 } |
|
4004 }, |
|
4005 |
|
4006 |
|
4007 registerView: function(id, text, type, def) { |
|
4008 // Get the renderer function. |
|
4009 var info = (typeof type === "object" ? type : $view.types[type || $view.ext]), |
|
4010 renderer; |
|
4011 if (info.fragRenderer) { |
|
4012 renderer = info.fragRenderer(id, text); |
|
4013 } else { |
|
4014 renderer = makeRenderer(info.renderer(id, text)); |
|
4015 } |
|
4016 |
|
4017 def = def || new can.Deferred(); |
|
4018 |
|
4019 // Cache if we are caching. |
|
4020 if ($view.cache) { |
|
4021 $view.cached[id] = def; |
|
4022 def.__view_id = id; |
|
4023 $view.cachedRenderers[id] = renderer; |
|
4024 } |
|
4025 |
|
4026 // Return the objects for the response's `dataTypes` |
|
4027 // (in this case view). |
|
4028 return def.resolve(renderer); |
|
4029 } |
|
4030 }); |
|
4031 |
|
4032 // _removed if not used as a steal module_ |
|
4033 |
|
4034 return can; |
|
4035 })(__m3); |
|
4036 |
|
4037 // ## can/control/control.js |
|
4038 var __m18 = (function(can) { |
|
4039 // ### bind |
|
4040 // this helper binds to one element and returns a function that unbinds from that element. |
|
4041 var bind = function(el, ev, callback) { |
|
4042 |
|
4043 can.bind.call(el, ev, callback); |
|
4044 |
|
4045 return function() { |
|
4046 can.unbind.call(el, ev, callback); |
|
4047 }; |
|
4048 }, |
|
4049 isFunction = can.isFunction, |
|
4050 extend = can.extend, |
|
4051 each = can.each, |
|
4052 slice = [].slice, |
|
4053 paramReplacer = /\{([^\}]+)\}/g, |
|
4054 special = can.getObject("$.event.special", [can]) || {}, |
|
4055 |
|
4056 // ### delegate |
|
4057 // this helper binds to elements based on a selector and returns a |
|
4058 // function that unbinds. |
|
4059 delegate = function(el, selector, ev, callback) { |
|
4060 can.delegate.call(el, selector, ev, callback); |
|
4061 return function() { |
|
4062 can.undelegate.call(el, selector, ev, callback); |
|
4063 }; |
|
4064 }, |
|
4065 |
|
4066 // ### binder |
|
4067 // Calls bind or unbind depending if there is a selector. |
|
4068 binder = function(el, ev, callback, selector) { |
|
4069 return selector ? |
|
4070 delegate(el, can.trim(selector), ev, callback) : |
|
4071 bind(el, ev, callback); |
|
4072 }, |
|
4073 |
|
4074 basicProcessor; |
|
4075 |
|
4076 var Control = can.Control = can.Construct( |
|
4077 |
|
4078 // ## *static functions* |
|
4079 |
|
4080 { |
|
4081 // ## can.Control.setup |
|
4082 // This function pre-processes which methods are event listeners and which are methods of |
|
4083 // the control. It has a mechanism to allow controllers to inherit default values from super |
|
4084 // classes, like `can.Construct`, and will cache functions that are action functions (see `_isAction`) |
|
4085 // or functions with an underscored name. |
|
4086 setup: function() { |
|
4087 can.Construct.setup.apply(this, arguments); |
|
4088 |
|
4089 if (can.Control) { |
|
4090 var control = this, |
|
4091 funcName; |
|
4092 |
|
4093 control.actions = {}; |
|
4094 for (funcName in control.prototype) { |
|
4095 if (control._isAction(funcName)) { |
|
4096 control.actions[funcName] = control._action(funcName); |
|
4097 } |
|
4098 } |
|
4099 } |
|
4100 }, |
|
4101 // ## can.Control._shifter |
|
4102 // Moves `this` to the first argument, wraps it with `jQuery` if it's |
|
4103 // an element. |
|
4104 _shifter: function(context, name) { |
|
4105 |
|
4106 var method = typeof name === "string" ? context[name] : name; |
|
4107 |
|
4108 if (!isFunction(method)) { |
|
4109 method = context[method]; |
|
4110 } |
|
4111 |
|
4112 return function() { |
|
4113 context.called = name; |
|
4114 return method.apply(context, [this.nodeName ? can.$(this) : this].concat(slice.call(arguments, 0))); |
|
4115 }; |
|
4116 }, |
|
4117 |
|
4118 // ## can.Control._isAction |
|
4119 // Return `true` if `methodName` refers to an action. An action is a `methodName` value that |
|
4120 // is not the constructor, and is either a function or string that refers to a function, or is |
|
4121 // defined in `special`, `processors`. Detects whether `methodName` is also a valid method name. |
|
4122 _isAction: function(methodName) { |
|
4123 var val = this.prototype[methodName], |
|
4124 type = typeof val; |
|
4125 |
|
4126 return (methodName !== 'constructor') && |
|
4127 (type === "function" || (type === "string" && isFunction(this.prototype[val]))) && !! (special[methodName] || processors[methodName] || /[^\w]/.test(methodName)); |
|
4128 }, |
|
4129 // ## can.Control._action |
|
4130 // Takes a method name and the options passed to a control and tries to return the data |
|
4131 // necessary to pass to a processor (something that binds things). |
|
4132 // For performance reasons, `_action` is called twice: |
|
4133 // * It's called when the Control class is created. for templated method names (e.g., `{window} foo`), it returns null. For non-templated method names it returns the event binding data. That data is added to `this.actions`. |
|
4134 // * It is called wehn a control instance is created, but only for templated actions. |
|
4135 _action: function(methodName, options) { |
|
4136 |
|
4137 // If we don't have options (a `control` instance), we'll run this later. If we have |
|
4138 // options, run `can.sub` to replace the action template `{}` with values from the `options` |
|
4139 // or `window`. If a `{}` template resolves to an object, `convertedName` will be an array. |
|
4140 // In that case, the event name we want will be the last item in that array. |
|
4141 paramReplacer.lastIndex = 0; |
|
4142 if (options || !paramReplacer.test(methodName)) { |
|
4143 var convertedName = options ? can.sub(methodName, this._lookup(options)) : methodName; |
|
4144 if (!convertedName) { |
|
4145 |
|
4146 return null; |
|
4147 } |
|
4148 var arr = can.isArray(convertedName), |
|
4149 name = arr ? convertedName[1] : convertedName, |
|
4150 parts = name.split(/\s+/g), |
|
4151 event = parts.pop(); |
|
4152 |
|
4153 return { |
|
4154 processor: processors[event] || basicProcessor, |
|
4155 parts: [name, parts.join(" "), event], |
|
4156 delegate: arr ? convertedName[0] : undefined |
|
4157 }; |
|
4158 } |
|
4159 }, |
|
4160 _lookup: function(options) { |
|
4161 return [options, window]; |
|
4162 }, |
|
4163 // ## can.Control.processors |
|
4164 // An object of `{eventName : function}` pairs that Control uses to |
|
4165 // hook up events automatically. |
|
4166 processors: {}, |
|
4167 // ## can.Control.defaults |
|
4168 // A object of name-value pairs that act as default values for a control instance |
|
4169 defaults: {} |
|
4170 }, { |
|
4171 // ## *prototype functions* |
|
4172 |
|
4173 // ## setup |
|
4174 // Setup is where most of the Control's magic happens. It performs several pre-initialization steps: |
|
4175 // - Sets `this.element` |
|
4176 // - Adds the Control's name to the element's className |
|
4177 // - Saves the Control in `$.data` |
|
4178 // - Merges Options |
|
4179 // - Binds event handlers using `delegate` |
|
4180 // The final step is to return pass the element and prepareed options, to be used in `init`. |
|
4181 setup: function(element, options) { |
|
4182 |
|
4183 var cls = this.constructor, |
|
4184 pluginname = cls.pluginName || cls._fullName, |
|
4185 arr; |
|
4186 |
|
4187 // Retrieve the raw element, then set the plugin name as a class there. |
|
4188 this.element = can.$(element); |
|
4189 |
|
4190 if (pluginname && pluginname !== 'can_control') { |
|
4191 this.element.addClass(pluginname); |
|
4192 } |
|
4193 |
|
4194 // Set up the 'controls' data on the element. If it does not exist, initialize |
|
4195 // it to an empty array. |
|
4196 arr = can.data(this.element, 'controls'); |
|
4197 if (!arr) { |
|
4198 arr = []; |
|
4199 can.data(this.element, 'controls', arr); |
|
4200 } |
|
4201 arr.push(this); |
|
4202 |
|
4203 // The `this.options` property is an Object that contains configuration data |
|
4204 // passed to a control when it is created (`new can.Control(element, options)`) |
|
4205 // The `options` argument passed when creating the control is merged with `can.Control.defaults` |
|
4206 // in [can.Control.prototype.setup setup]. |
|
4207 // If no `options` value is used during creation, the value in `defaults` is used instead |
|
4208 this.options = extend({}, cls.defaults, options); |
|
4209 |
|
4210 this.on(); |
|
4211 |
|
4212 return [this.element, this.options]; |
|
4213 }, |
|
4214 // ## on |
|
4215 // This binds an event handler for an event to a selector under the scope of `this.element` |
|
4216 // If no options are specified, all events are rebound to their respective elements. The actions, |
|
4217 // which were cached in `setup`, are used and all elements are bound using `delegate` from `this.element`. |
|
4218 on: function(el, selector, eventName, func) { |
|
4219 if (!el) { |
|
4220 this.off(); |
|
4221 |
|
4222 var cls = this.constructor, |
|
4223 bindings = this._bindings, |
|
4224 actions = cls.actions, |
|
4225 element = this.element, |
|
4226 destroyCB = can.Control._shifter(this, "destroy"), |
|
4227 funcName, ready; |
|
4228 |
|
4229 for (funcName in actions) { |
|
4230 // Only push if we have the action and no option is `undefined` |
|
4231 if (actions.hasOwnProperty(funcName)) { |
|
4232 ready = actions[funcName] || cls._action(funcName, this.options, this); |
|
4233 if (ready) { |
|
4234 bindings.control[funcName] = ready.processor(ready.delegate || element, |
|
4235 ready.parts[2], ready.parts[1], funcName, this); |
|
4236 } |
|
4237 } |
|
4238 } |
|
4239 |
|
4240 // Set up the ability to `destroy` the control later. |
|
4241 can.bind.call(element, "removed", destroyCB); |
|
4242 bindings.user.push(function(el) { |
|
4243 can.unbind.call(el, "removed", destroyCB); |
|
4244 }); |
|
4245 return bindings.user.length; |
|
4246 } |
|
4247 |
|
4248 // if `el` is a string, use that as `selector` and re-set it to this control's element... |
|
4249 if (typeof el === 'string') { |
|
4250 func = eventName; |
|
4251 eventName = selector; |
|
4252 selector = el; |
|
4253 el = this.element; |
|
4254 } |
|
4255 |
|
4256 // ...otherwise, set `selector` to null |
|
4257 if (func === undefined) { |
|
4258 func = eventName; |
|
4259 eventName = selector; |
|
4260 selector = null; |
|
4261 } |
|
4262 |
|
4263 if (typeof func === 'string') { |
|
4264 func = can.Control._shifter(this, func); |
|
4265 } |
|
4266 |
|
4267 this._bindings.user.push(binder(el, eventName, func, selector)); |
|
4268 |
|
4269 return this._bindings.user.length; |
|
4270 }, |
|
4271 // ## off |
|
4272 // Unbinds all event handlers on the controller. |
|
4273 // This should _only_ be called in combination with .on() |
|
4274 off: function() { |
|
4275 var el = this.element[0], |
|
4276 bindings = this._bindings; |
|
4277 if (bindings) { |
|
4278 each(bindings.user || [], function(value) { |
|
4279 value(el); |
|
4280 }); |
|
4281 each(bindings.control || {}, function(value) { |
|
4282 value(el); |
|
4283 }); |
|
4284 } |
|
4285 // Adds bindings. |
|
4286 this._bindings = { |
|
4287 user: [], |
|
4288 control: {} |
|
4289 }; |
|
4290 }, |
|
4291 // ## destroy |
|
4292 // Prepares a `control` for garbage collection. |
|
4293 // First checks if it has already been removed. Then, removes all the bindings, data, and |
|
4294 // the element from the Control instance. |
|
4295 destroy: function() { |
|
4296 if (this.element === null) { |
|
4297 |
|
4298 return; |
|
4299 } |
|
4300 var Class = this.constructor, |
|
4301 pluginName = Class.pluginName || Class._fullName, |
|
4302 controls; |
|
4303 |
|
4304 this.off(); |
|
4305 |
|
4306 if (pluginName && pluginName !== 'can_control') { |
|
4307 this.element.removeClass(pluginName); |
|
4308 } |
|
4309 |
|
4310 controls = can.data(this.element, "controls"); |
|
4311 controls.splice(can.inArray(this, controls), 1); |
|
4312 |
|
4313 can.trigger(this, "destroyed"); |
|
4314 |
|
4315 this.element = null; |
|
4316 } |
|
4317 }); |
|
4318 |
|
4319 // ## Processors |
|
4320 // Processors do the binding. This basic processor binds events. Each returns a function that unbinds |
|
4321 // when called. |
|
4322 var processors = can.Control.processors; |
|
4323 basicProcessor = function(el, event, selector, methodName, control) { |
|
4324 return binder(el, event, can.Control._shifter(control, methodName), selector); |
|
4325 }; |
|
4326 |
|
4327 // Set common events to be processed as a `basicProcessor` |
|
4328 each(["change", "click", "contextmenu", "dblclick", "keydown", "keyup", |
|
4329 "keypress", "mousedown", "mousemove", "mouseout", "mouseover", |
|
4330 "mouseup", "reset", "resize", "scroll", "select", "submit", "focusin", |
|
4331 "focusout", "mouseenter", "mouseleave", |
|
4332 "touchstart", "touchmove", "touchcancel", "touchend", "touchleave", |
|
4333 "inserted", "removed" |
|
4334 ], function(v) { |
|
4335 processors[v] = basicProcessor; |
|
4336 }); |
|
4337 |
|
4338 return Control; |
|
4339 })(__m3, __m1); |
|
4340 |
|
4341 // ## can/util/string/deparam/deparam.js |
|
4342 var __m20 = (function(can) { |
|
4343 // ## deparam.js |
|
4344 // `can.deparam` |
|
4345 // _Takes a string of name value pairs and returns a Object literal that represents those params._ |
|
4346 var digitTest = /^\d+$/, |
|
4347 keyBreaker = /([^\[\]]+)|(\[\])/g, |
|
4348 paramTest = /([^?#]*)(#.*)?$/, |
|
4349 prep = function(str) { |
|
4350 return decodeURIComponent(str.replace(/\+/g, ' ')); |
|
4351 }; |
|
4352 can.extend(can, { |
|
4353 deparam: function(params) { |
|
4354 var data = {}, pairs, lastPart; |
|
4355 if (params && paramTest.test(params)) { |
|
4356 pairs = params.split('&'); |
|
4357 can.each(pairs, function(pair) { |
|
4358 var parts = pair.split('='), |
|
4359 key = prep(parts.shift()), |
|
4360 value = prep(parts.join('=')), |
|
4361 current = data; |
|
4362 if (key) { |
|
4363 parts = key.match(keyBreaker); |
|
4364 for (var j = 0, l = parts.length - 1; j < l; j++) { |
|
4365 if (!current[parts[j]]) { |
|
4366 // If what we are pointing to looks like an `array` |
|
4367 current[parts[j]] = digitTest.test(parts[j + 1]) || parts[j + 1] === '[]' ? [] : {}; |
|
4368 } |
|
4369 current = current[parts[j]]; |
|
4370 } |
|
4371 lastPart = parts.pop(); |
|
4372 if (lastPart === '[]') { |
|
4373 current.push(value); |
|
4374 } else { |
|
4375 current[lastPart] = value; |
|
4376 } |
|
4377 } |
|
4378 }); |
|
4379 } |
|
4380 return data; |
|
4381 } |
|
4382 }); |
|
4383 return can; |
|
4384 })(__m3, __m2); |
|
4385 |
|
4386 // ## can/route/route.js |
|
4387 var __m19 = (function(can) { |
|
4388 |
|
4389 // ## route.js |
|
4390 // `can.route` |
|
4391 // _Helps manage browser history (and client state) by synchronizing the |
|
4392 // `window.location.hash` with a `can.Map`._ |
|
4393 // Helper methods used for matching routes. |
|
4394 var |
|
4395 // `RegExp` used to match route variables of the type ':name'. |
|
4396 // Any word character or a period is matched. |
|
4397 matcher = /\:([\w\.]+)/g, |
|
4398 // Regular expression for identifying &key=value lists. |
|
4399 paramsMatcher = /^(?:&[^=]+=[^&]*)+/, |
|
4400 // Converts a JS Object into a list of parameters that can be |
|
4401 // inserted into an html element tag. |
|
4402 makeProps = function(props) { |
|
4403 var tags = []; |
|
4404 can.each(props, function(val, name) { |
|
4405 tags.push((name === 'className' ? 'class' : name) + '="' + |
|
4406 (name === "href" ? val : can.esc(val)) + '"'); |
|
4407 }); |
|
4408 return tags.join(" "); |
|
4409 }, |
|
4410 // Checks if a route matches the data provided. If any route variable |
|
4411 // is not present in the data, the route does not match. If all route |
|
4412 // variables are present in the data, the number of matches is returned |
|
4413 // to allow discerning between general and more specific routes. |
|
4414 matchesData = function(route, data) { |
|
4415 var count = 0, |
|
4416 i = 0, |
|
4417 defaults = {}; |
|
4418 // look at default values, if they match ... |
|
4419 for (var name in route.defaults) { |
|
4420 if (route.defaults[name] === data[name]) { |
|
4421 // mark as matched |
|
4422 defaults[name] = 1; |
|
4423 count++; |
|
4424 } |
|
4425 } |
|
4426 for (; i < route.names.length; i++) { |
|
4427 if (!data.hasOwnProperty(route.names[i])) { |
|
4428 return -1; |
|
4429 } |
|
4430 if (!defaults[route.names[i]]) { |
|
4431 count++; |
|
4432 } |
|
4433 |
|
4434 } |
|
4435 |
|
4436 return count; |
|
4437 }, |
|
4438 location = window.location, |
|
4439 wrapQuote = function(str) { |
|
4440 return (str + '') |
|
4441 .replace(/([.?*+\^$\[\]\\(){}|\-])/g, "\\$1"); |
|
4442 }, |
|
4443 each = can.each, |
|
4444 extend = can.extend, |
|
4445 // Helper for convert any object (or value) to stringified object (or value) |
|
4446 stringify = function(obj) { |
|
4447 // Object is array, plain object, Map or List |
|
4448 if (obj && typeof obj === "object") { |
|
4449 // Get native object or array from Map or List |
|
4450 if (obj instanceof can.Map) { |
|
4451 obj = obj.attr(); |
|
4452 // Clone object to prevent change original values |
|
4453 } else { |
|
4454 obj = can.isFunction(obj.slice) ? obj.slice() : can.extend({}, obj); |
|
4455 } |
|
4456 // Convert each object property or array item into stringified new |
|
4457 can.each(obj, function(val, prop) { |
|
4458 obj[prop] = stringify(val); |
|
4459 }); |
|
4460 // Object supports toString function |
|
4461 } else if (obj !== undefined && obj !== null && can.isFunction(obj.toString)) { |
|
4462 obj = obj.toString(); |
|
4463 } |
|
4464 |
|
4465 return obj; |
|
4466 }, |
|
4467 removeBackslash = function(str) { |
|
4468 return str.replace(/\\/g, ""); |
|
4469 }, |
|
4470 // A ~~throttled~~ debounced function called multiple times will only fire once the |
|
4471 // timer runs down. Each call resets the timer. |
|
4472 timer, |
|
4473 // Intermediate storage for `can.route.data`. |
|
4474 curParams, |
|
4475 // The last hash caused by a data change |
|
4476 lastHash, |
|
4477 // Are data changes pending that haven't yet updated the hash |
|
4478 changingData, |
|
4479 // If the `can.route.data` changes, update the hash. |
|
4480 // Using `.serialize()` retrieves the raw data contained in the `observable`. |
|
4481 // This function is ~~throttled~~ debounced so it only updates once even if multiple values changed. |
|
4482 // This might be able to use batchNum and avoid this. |
|
4483 onRouteDataChange = function(ev, attr, how, newval) { |
|
4484 // indicate that data is changing |
|
4485 changingData = 1; |
|
4486 clearTimeout(timer); |
|
4487 timer = setTimeout(function() { |
|
4488 // indicate that the hash is set to look like the data |
|
4489 changingData = 0; |
|
4490 var serialized = can.route.data.serialize(), |
|
4491 path = can.route.param(serialized, true); |
|
4492 can.route._call("setURL", path); |
|
4493 |
|
4494 lastHash = path; |
|
4495 }, 10); |
|
4496 }; |
|
4497 |
|
4498 can.route = function(url, defaults) { |
|
4499 // if route ends with a / and url starts with a /, remove the leading / of the url |
|
4500 var root = can.route._call("root"); |
|
4501 |
|
4502 if (root.lastIndexOf("/") === root.length - 1 && |
|
4503 url.indexOf("/") === 0) { |
|
4504 url = url.substr(1); |
|
4505 } |
|
4506 |
|
4507 defaults = defaults || {}; |
|
4508 // Extract the variable names and replace with `RegExp` that will match |
|
4509 // an atual URL with values. |
|
4510 var names = [], |
|
4511 res, |
|
4512 test = "", |
|
4513 lastIndex = matcher.lastIndex = 0, |
|
4514 next, |
|
4515 querySeparator = can.route._call("querySeparator"); |
|
4516 |
|
4517 // res will be something like [":foo","foo"] |
|
4518 while (res = matcher.exec(url)) { |
|
4519 names.push(res[1]); |
|
4520 test += removeBackslash(url.substring(lastIndex, matcher.lastIndex - res[0].length)); |
|
4521 next = "\\" + (removeBackslash(url.substr(matcher.lastIndex, 1)) || querySeparator); |
|
4522 // a name without a default value HAS to have a value |
|
4523 // a name that has a default value can be empty |
|
4524 // The `\\` is for string-escaping giving single `\` for `RegExp` escaping. |
|
4525 test += "([^" + next + "]" + (defaults[res[1]] ? "*" : "+") + ")"; |
|
4526 lastIndex = matcher.lastIndex; |
|
4527 } |
|
4528 test += url.substr(lastIndex) |
|
4529 .replace("\\", ""); |
|
4530 // Add route in a form that can be easily figured out. |
|
4531 can.route.routes[url] = { |
|
4532 // A regular expression that will match the route when variable values |
|
4533 // are present; i.e. for `:page/:type` the `RegExp` is `/([\w\.]*)/([\w\.]*)/` which |
|
4534 // will match for any value of `:page` and `:type` (word chars or period). |
|
4535 test: new RegExp("^" + test + "($|" + wrapQuote(querySeparator) + ")"), |
|
4536 // The original URL, same as the index for this entry in routes. |
|
4537 route: url, |
|
4538 // An `array` of all the variable names in this route. |
|
4539 names: names, |
|
4540 // Default values provided for the variables. |
|
4541 defaults: defaults, |
|
4542 // The number of parts in the URL separated by `/`. |
|
4543 length: url.split('/') |
|
4544 .length |
|
4545 }; |
|
4546 return can.route; |
|
4547 }; |
|
4548 |
|
4549 |
|
4550 extend(can.route, { |
|
4551 |
|
4552 |
|
4553 param: function(data, _setRoute) { |
|
4554 // Check if the provided data keys match the names in any routes; |
|
4555 // Get the one with the most matches. |
|
4556 var route, |
|
4557 // Need to have at least 1 match. |
|
4558 matches = 0, |
|
4559 matchCount, |
|
4560 routeName = data.route, |
|
4561 propCount = 0; |
|
4562 |
|
4563 delete data.route; |
|
4564 |
|
4565 each(data, function() { |
|
4566 propCount++; |
|
4567 }); |
|
4568 // Otherwise find route. |
|
4569 each(can.route.routes, function(temp, name) { |
|
4570 // best route is the first with all defaults matching |
|
4571 |
|
4572 matchCount = matchesData(temp, data); |
|
4573 if (matchCount > matches) { |
|
4574 route = temp; |
|
4575 matches = matchCount; |
|
4576 } |
|
4577 if (matchCount >= propCount) { |
|
4578 return false; |
|
4579 } |
|
4580 }); |
|
4581 // If we have a route name in our `can.route` data, and it's |
|
4582 // just as good as what currently matches, use that |
|
4583 if (can.route.routes[routeName] && matchesData(can.route.routes[routeName], data) === matches) { |
|
4584 route = can.route.routes[routeName]; |
|
4585 } |
|
4586 // If this is match... |
|
4587 if (route) { |
|
4588 var cpy = extend({}, data), |
|
4589 // Create the url by replacing the var names with the provided data. |
|
4590 // If the default value is found an empty string is inserted. |
|
4591 res = route.route.replace(matcher, function(whole, name) { |
|
4592 delete cpy[name]; |
|
4593 return data[name] === route.defaults[name] ? "" : encodeURIComponent(data[name]); |
|
4594 }) |
|
4595 .replace("\\", ""), |
|
4596 after; |
|
4597 // Remove matching default values |
|
4598 each(route.defaults, function(val, name) { |
|
4599 if (cpy[name] === val) { |
|
4600 delete cpy[name]; |
|
4601 } |
|
4602 }); |
|
4603 |
|
4604 // The remaining elements of data are added as |
|
4605 // `&` separated parameters to the url. |
|
4606 after = can.param(cpy); |
|
4607 // if we are paraming for setting the hash |
|
4608 // we also want to make sure the route value is updated |
|
4609 if (_setRoute) { |
|
4610 can.route.attr('route', route.route); |
|
4611 } |
|
4612 return res + (after ? can.route._call("querySeparator") + after : ""); |
|
4613 } |
|
4614 // If no route was found, there is no hash URL, only paramters. |
|
4615 return can.isEmptyObject(data) ? "" : can.route._call("querySeparator") + can.param(data); |
|
4616 }, |
|
4617 |
|
4618 deparam: function(url) { |
|
4619 |
|
4620 // remove the url |
|
4621 var root = can.route._call("root"); |
|
4622 if (root.lastIndexOf("/") === root.length - 1 && |
|
4623 url.indexOf("/") === 0) { |
|
4624 url = url.substr(1); |
|
4625 } |
|
4626 |
|
4627 // See if the url matches any routes by testing it against the `route.test` `RegExp`. |
|
4628 // By comparing the URL length the most specialized route that matches is used. |
|
4629 var route = { |
|
4630 length: -1 |
|
4631 }, |
|
4632 querySeparator = can.route._call("querySeparator"), |
|
4633 paramsMatcher = can.route._call("paramsMatcher"); |
|
4634 |
|
4635 each(can.route.routes, function(temp, name) { |
|
4636 if (temp.test.test(url) && temp.length > route.length) { |
|
4637 route = temp; |
|
4638 } |
|
4639 }); |
|
4640 // If a route was matched. |
|
4641 if (route.length > -1) { |
|
4642 |
|
4643 var // Since `RegExp` backreferences are used in `route.test` (parens) |
|
4644 // the parts will contain the full matched string and each variable (back-referenced) value. |
|
4645 parts = url.match(route.test), |
|
4646 // Start will contain the full matched string; parts contain the variable values. |
|
4647 start = parts.shift(), |
|
4648 // The remainder will be the `&key=value` list at the end of the URL. |
|
4649 remainder = url.substr(start.length - (parts[parts.length - 1] === querySeparator ? 1 : 0)), |
|
4650 // If there is a remainder and it contains a `&key=value` list deparam it. |
|
4651 obj = (remainder && paramsMatcher.test(remainder)) ? can.deparam(remainder.slice(1)) : {}; |
|
4652 |
|
4653 // Add the default values for this route. |
|
4654 obj = extend(true, {}, route.defaults, obj); |
|
4655 // Overwrite each of the default values in `obj` with those in |
|
4656 // parts if that part is not empty. |
|
4657 each(parts, function(part, i) { |
|
4658 if (part && part !== querySeparator) { |
|
4659 obj[route.names[i]] = decodeURIComponent(part); |
|
4660 } |
|
4661 }); |
|
4662 obj.route = route.route; |
|
4663 return obj; |
|
4664 } |
|
4665 // If no route was matched, it is parsed as a `&key=value` list. |
|
4666 if (url.charAt(0) !== querySeparator) { |
|
4667 url = querySeparator + url; |
|
4668 } |
|
4669 return paramsMatcher.test(url) ? can.deparam(url.slice(1)) : {}; |
|
4670 }, |
|
4671 |
|
4672 data: new can.Map({}), |
|
4673 map: function(data) { |
|
4674 var appState; |
|
4675 // appState is a can.Map constructor function |
|
4676 if (data.prototype instanceof can.Map) { |
|
4677 appState = new data(); |
|
4678 } |
|
4679 // appState is an instance of can.Map |
|
4680 else { |
|
4681 appState = data; |
|
4682 } |
|
4683 can.route.data = appState; |
|
4684 }, |
|
4685 |
|
4686 routes: {}, |
|
4687 |
|
4688 ready: function(val) { |
|
4689 if (val !== true) { |
|
4690 can.route._setup(); |
|
4691 can.route.setState(); |
|
4692 } |
|
4693 return can.route; |
|
4694 }, |
|
4695 |
|
4696 url: function(options, merge) { |
|
4697 |
|
4698 if (merge) { |
|
4699 options = can.extend({}, can.route.deparam(can.route._call("matchingPartOfURL")), options); |
|
4700 } |
|
4701 return can.route._call("root") + can.route.param(options); |
|
4702 }, |
|
4703 |
|
4704 link: function(name, options, props, merge) { |
|
4705 return "<a " + makeProps( |
|
4706 extend({ |
|
4707 href: can.route.url(options, merge) |
|
4708 }, props)) + ">" + name + "</a>"; |
|
4709 }, |
|
4710 |
|
4711 current: function(options) { |
|
4712 return this._call("matchingPartOfURL") === can.route.param(options); |
|
4713 }, |
|
4714 bindings: { |
|
4715 hashchange: { |
|
4716 paramsMatcher: paramsMatcher, |
|
4717 querySeparator: "&", |
|
4718 bind: function() { |
|
4719 can.bind.call(window, 'hashchange', setState); |
|
4720 }, |
|
4721 unbind: function() { |
|
4722 can.unbind.call(window, 'hashchange', setState); |
|
4723 }, |
|
4724 // Gets the part of the url we are determinging the route from. |
|
4725 // For hashbased routing, it's everything after the #, for |
|
4726 // pushState it's configurable |
|
4727 matchingPartOfURL: function() { |
|
4728 return location.href.split(/#!?/)[1] || ""; |
|
4729 }, |
|
4730 // gets called with the serialized can.route data after a route has changed |
|
4731 // returns what the url has been updated to (for matching purposes) |
|
4732 setURL: function(path) { |
|
4733 location.hash = "#!" + path; |
|
4734 return path; |
|
4735 }, |
|
4736 root: "#!" |
|
4737 } |
|
4738 }, |
|
4739 defaultBinding: "hashchange", |
|
4740 currentBinding: null, |
|
4741 // ready calls setup |
|
4742 // setup binds and listens to data changes |
|
4743 // bind listens to whatever you should be listening to |
|
4744 // data changes tries to set the path |
|
4745 |
|
4746 // we need to be able to |
|
4747 // easily kick off calling setState |
|
4748 // teardown whatever is there |
|
4749 // turn on a particular binding |
|
4750 |
|
4751 // called when the route is ready |
|
4752 _setup: function() { |
|
4753 if (!can.route.currentBinding) { |
|
4754 can.route._call("bind"); |
|
4755 can.route.bind("change", onRouteDataChange); |
|
4756 can.route.currentBinding = can.route.defaultBinding; |
|
4757 } |
|
4758 }, |
|
4759 _teardown: function() { |
|
4760 if (can.route.currentBinding) { |
|
4761 can.route._call("unbind"); |
|
4762 can.route.unbind("change", onRouteDataChange); |
|
4763 can.route.currentBinding = null; |
|
4764 } |
|
4765 clearTimeout(timer); |
|
4766 changingData = 0; |
|
4767 }, |
|
4768 // a helper to get stuff from the current or default bindings |
|
4769 _call: function() { |
|
4770 var args = can.makeArray(arguments), |
|
4771 prop = args.shift(), |
|
4772 binding = can.route.bindings[can.route.currentBinding || can.route.defaultBinding], |
|
4773 method = binding[prop]; |
|
4774 if (method.apply) { |
|
4775 return method.apply(binding, args); |
|
4776 } else { |
|
4777 return method; |
|
4778 } |
|
4779 } |
|
4780 }); |
|
4781 |
|
4782 // The functions in the following list applied to `can.route` (e.g. `can.route.attr('...')`) will |
|
4783 // instead act on the `can.route.data` observe. |
|
4784 each(['bind', 'unbind', 'on', 'off', 'delegate', 'undelegate', 'removeAttr', 'compute', '_get', '__get'], function(name) { |
|
4785 can.route[name] = function() { |
|
4786 // `delegate` and `undelegate` require |
|
4787 // the `can/map/delegate` plugin |
|
4788 if (!can.route.data[name]) { |
|
4789 return; |
|
4790 } |
|
4791 |
|
4792 return can.route.data[name].apply(can.route.data, arguments); |
|
4793 }; |
|
4794 }); |
|
4795 |
|
4796 // Because everything in hashbang is in fact a string this will automaticaly convert new values to string. Works with single value, or deep hashes. |
|
4797 // Main motivation for this is to prevent double route event call for same value. |
|
4798 // Example (the problem): |
|
4799 // When you load page with hashbang like #!&some_number=2 and bind 'some_number' on routes. |
|
4800 // It will fire event with adding of "2" (string) to 'some_number' property |
|
4801 // But when you after this set can.route.attr({some_number: 2}) or can.route.attr('some_number', 2). it fires another event with change of 'some_number' from "2" (string) to 2 (integer) |
|
4802 // This wont happen again with this normalization |
|
4803 can.route.attr = function(attr, val) { |
|
4804 var type = typeof attr, |
|
4805 newArguments; |
|
4806 |
|
4807 // Reading |
|
4808 if (val === undefined) { |
|
4809 newArguments = arguments; |
|
4810 // Sets object |
|
4811 } else if (type !== "string" && type !== "number") { |
|
4812 newArguments = [stringify(attr), val]; |
|
4813 // Sets key - value |
|
4814 } else { |
|
4815 newArguments = [attr, stringify(val)]; |
|
4816 } |
|
4817 |
|
4818 return can.route.data.attr.apply(can.route.data, newArguments); |
|
4819 }; |
|
4820 |
|
4821 var // Deparameterizes the portion of the hash of interest and assign the |
|
4822 // values to the `can.route.data` removing existing values no longer in the hash. |
|
4823 // setState is called typically by hashchange which fires asynchronously |
|
4824 // So it's possible that someone started changing the data before the |
|
4825 // hashchange event fired. For this reason, it will not set the route data |
|
4826 // if the data is changing or the hash already matches the hash that was set. |
|
4827 setState = can.route.setState = function() { |
|
4828 var hash = can.route._call("matchingPartOfURL"); |
|
4829 var oldParams = curParams; |
|
4830 curParams = can.route.deparam(hash); |
|
4831 |
|
4832 // if the hash data is currently changing, or |
|
4833 // the hash is what we set it to anyway, do NOT change the hash |
|
4834 if (!changingData || hash !== lastHash) { |
|
4835 can.batch.start(); |
|
4836 for (var attr in oldParams) { |
|
4837 if (!curParams[attr]) { |
|
4838 can.route.removeAttr(attr); |
|
4839 } |
|
4840 } |
|
4841 can.route.attr(curParams); |
|
4842 can.batch.stop(); |
|
4843 } |
|
4844 }; |
|
4845 |
|
4846 return can.route; |
|
4847 })(__m3, __m10, __m14, __m20); |
|
4848 |
|
4849 // ## can/control/route/route.js |
|
4850 var __m21 = (function(can) { |
|
4851 |
|
4852 // ## control/route.js |
|
4853 // _Controller route integration._ |
|
4854 |
|
4855 can.Control.processors.route = function(el, event, selector, funcName, controller) { |
|
4856 selector = selector || ""; |
|
4857 if (!can.route.routes[selector]) { |
|
4858 if (selector[0] === '/') { |
|
4859 selector = selector.substring(1); |
|
4860 } |
|
4861 can.route(selector); |
|
4862 } |
|
4863 var batchNum, |
|
4864 check = function(ev, attr, how) { |
|
4865 if (can.route.attr('route') === (selector) && |
|
4866 (ev.batchNum === undefined || ev.batchNum !== batchNum)) { |
|
4867 |
|
4868 batchNum = ev.batchNum; |
|
4869 |
|
4870 var d = can.route.attr(); |
|
4871 delete d.route; |
|
4872 if (can.isFunction(controller[funcName])) { |
|
4873 controller[funcName](d); |
|
4874 } else { |
|
4875 controller[controller[funcName]](d); |
|
4876 } |
|
4877 |
|
4878 } |
|
4879 }; |
|
4880 can.route.bind('change', check); |
|
4881 return function() { |
|
4882 can.route.unbind('change', check); |
|
4883 }; |
|
4884 }; |
|
4885 |
|
4886 return can; |
|
4887 })(__m3, __m19, __m18); |
|
4888 |
|
4889 // ## can/view/elements.js |
|
4890 var __m24 = (function(can) { |
|
4891 |
|
4892 var selectsCommentNodes = (function() { |
|
4893 return can.$(document.createComment('~')).length === 1; |
|
4894 })(); |
|
4895 |
|
4896 |
|
4897 var elements = { |
|
4898 tagToContentPropMap: { |
|
4899 option: 'textContent' in document.createElement('option') ? 'textContent' : 'innerText', |
|
4900 textarea: 'value' |
|
4901 }, |
|
4902 |
|
4903 // 3.0 TODO: remove |
|
4904 attrMap: can.attr.map, |
|
4905 // matches the attrName of a regexp |
|
4906 attrReg: /([^\s=]+)[\s]*=[\s]*/, |
|
4907 // 3.0 TODO: remove |
|
4908 defaultValue: can.attr.defaultValue, |
|
4909 // a map of parent element to child elements |
|
4910 |
|
4911 tagMap: { |
|
4912 '': 'span', |
|
4913 table: 'tbody', |
|
4914 tr: 'td', |
|
4915 ol: 'li', |
|
4916 ul: 'li', |
|
4917 tbody: 'tr', |
|
4918 thead: 'tr', |
|
4919 tfoot: 'tr', |
|
4920 select: 'option', |
|
4921 optgroup: 'option' |
|
4922 }, |
|
4923 // a tag's parent element |
|
4924 reverseTagMap: { |
|
4925 tr: 'tbody', |
|
4926 option: 'select', |
|
4927 td: 'tr', |
|
4928 th: 'tr', |
|
4929 li: 'ul' |
|
4930 }, |
|
4931 // Used to determine the parentNode if el is directly within a documentFragment |
|
4932 getParentNode: function(el, defaultParentNode) { |
|
4933 return defaultParentNode && el.parentNode.nodeType === 11 ? defaultParentNode : el.parentNode; |
|
4934 }, |
|
4935 // 3.0 TODO: remove |
|
4936 setAttr: can.attr.set, |
|
4937 // 3.0 TODO: remove |
|
4938 getAttr: can.attr.get, |
|
4939 // 3.0 TODO: remove |
|
4940 removeAttr: can.attr.remove, |
|
4941 // Gets a "pretty" value for something |
|
4942 contentText: function(text) { |
|
4943 if (typeof text === 'string') { |
|
4944 return text; |
|
4945 } |
|
4946 // If has no value, return an empty string. |
|
4947 if (!text && text !== 0) { |
|
4948 return ''; |
|
4949 } |
|
4950 return '' + text; |
|
4951 }, |
|
4952 |
|
4953 after: function(oldElements, newFrag) { |
|
4954 var last = oldElements[oldElements.length - 1]; |
|
4955 // Insert it in the `document` or `documentFragment` |
|
4956 if (last.nextSibling) { |
|
4957 can.insertBefore(last.parentNode, newFrag, last.nextSibling); |
|
4958 } else { |
|
4959 can.appendChild(last.parentNode, newFrag); |
|
4960 } |
|
4961 }, |
|
4962 |
|
4963 replace: function(oldElements, newFrag) { |
|
4964 elements.after(oldElements, newFrag); |
|
4965 if (can.remove(can.$(oldElements)).length < oldElements.length && !selectsCommentNodes) { |
|
4966 can.each(oldElements, function(el) { |
|
4967 if (el.nodeType === 8) { |
|
4968 el.parentNode.removeChild(el); |
|
4969 } |
|
4970 }); |
|
4971 } |
|
4972 } |
|
4973 }; |
|
4974 |
|
4975 can.view.elements = elements; |
|
4976 |
|
4977 return elements; |
|
4978 })(__m3, __m17); |
|
4979 |
|
4980 // ## can/view/callbacks/callbacks.js |
|
4981 var __m25 = (function(can) { |
|
4982 |
|
4983 var attr = can.view.attr = function(attributeName, attrHandler) { |
|
4984 if (attrHandler) { |
|
4985 if (typeof attributeName === "string") { |
|
4986 attributes[attributeName] = attrHandler; |
|
4987 } else { |
|
4988 regExpAttributes.push({ |
|
4989 match: attributeName, |
|
4990 handler: attrHandler |
|
4991 }); |
|
4992 } |
|
4993 } else { |
|
4994 var cb = attributes[attributeName]; |
|
4995 if (!cb) { |
|
4996 |
|
4997 for (var i = 0, len = regExpAttributes.length; i < len; i++) { |
|
4998 var attrMatcher = regExpAttributes[i]; |
|
4999 if (attrMatcher.match.test(attributeName)) { |
|
5000 cb = attrMatcher.handler; |
|
5001 break; |
|
5002 } |
|
5003 } |
|
5004 } |
|
5005 return cb; |
|
5006 } |
|
5007 }; |
|
5008 |
|
5009 var attributes = {}, |
|
5010 regExpAttributes = [], |
|
5011 automaticCustomElementCharacters = /[-\:]/; |
|
5012 |
|
5013 var tag = can.view.tag = function(tagName, tagHandler) { |
|
5014 if (tagHandler) { |
|
5015 // if we have html5shive ... re-generate |
|
5016 if (window.html5) { |
|
5017 window.html5.elements += " " + tagName; |
|
5018 window.html5.shivDocument(); |
|
5019 } |
|
5020 |
|
5021 tags[tagName.toLowerCase()] = tagHandler; |
|
5022 } else { |
|
5023 var cb = tags[tagName.toLowerCase()]; |
|
5024 if (!cb && automaticCustomElementCharacters.test(tagName)) { |
|
5025 // empty callback for things that look like special tags |
|
5026 cb = function() {}; |
|
5027 } |
|
5028 return cb; |
|
5029 } |
|
5030 |
|
5031 }; |
|
5032 var tags = {}; |
|
5033 |
|
5034 can.view.callbacks = { |
|
5035 _tags: tags, |
|
5036 _attributes: attributes, |
|
5037 _regExpAttributes: regExpAttributes, |
|
5038 tag: tag, |
|
5039 attr: attr, |
|
5040 // handles calling back a tag callback |
|
5041 tagHandler: function(el, tagName, tagData) { |
|
5042 var helperTagCallback = tagData.options.read('tags.' + tagName, { |
|
5043 isArgument: true, |
|
5044 proxyMethods: false |
|
5045 }) |
|
5046 .value, |
|
5047 tagCallback = helperTagCallback || tags[tagName]; |
|
5048 |
|
5049 // If this was an element like <foo-bar> that doesn't have a component, just render its content |
|
5050 var scope = tagData.scope, |
|
5051 res; |
|
5052 |
|
5053 if (tagCallback) { |
|
5054 var reads = can.__clearReading(); |
|
5055 res = tagCallback(el, tagData); |
|
5056 can.__setReading(reads); |
|
5057 } else { |
|
5058 res = scope; |
|
5059 } |
|
5060 |
|
5061 |
|
5062 |
|
5063 // If the tagCallback gave us something to render with, and there is content within that element |
|
5064 // render it! |
|
5065 if (res && tagData.subtemplate) { |
|
5066 |
|
5067 if (scope !== res) { |
|
5068 scope = scope.add(res); |
|
5069 } |
|
5070 var result = tagData.subtemplate(scope, tagData.options); |
|
5071 var frag = typeof result === "string" ? can.view.frag(result) : result; |
|
5072 can.appendChild(el, frag); |
|
5073 } |
|
5074 } |
|
5075 }; |
|
5076 return can.view.callbacks; |
|
5077 })(__m3, __m17); |
|
5078 |
|
5079 // ## can/view/scanner.js |
|
5080 var __m23 = (function(can, elements, viewCallbacks) { |
|
5081 |
|
5082 |
|
5083 var newLine = /(\r|\n)+/g, |
|
5084 notEndTag = /\//, |
|
5085 // Escapes characters starting with `\`. |
|
5086 clean = function(content) { |
|
5087 return content |
|
5088 .split('\\') |
|
5089 .join("\\\\") |
|
5090 .split("\n") |
|
5091 .join("\\n") |
|
5092 .split('"') |
|
5093 .join('\\"') |
|
5094 .split("\t") |
|
5095 .join("\\t"); |
|
5096 }, |
|
5097 // Returns a tagName to use as a temporary placeholder for live content |
|
5098 // looks forward ... could be slow, but we only do it when necessary |
|
5099 getTag = function(tagName, tokens, i) { |
|
5100 // if a tagName is provided, use that |
|
5101 if (tagName) { |
|
5102 return tagName; |
|
5103 } else { |
|
5104 // otherwise go searching for the next two tokens like "<",TAG |
|
5105 while (i < tokens.length) { |
|
5106 if (tokens[i] === "<" && !notEndTag.test(tokens[i + 1])) { |
|
5107 return elements.reverseTagMap[tokens[i + 1]] || 'span'; |
|
5108 } |
|
5109 i++; |
|
5110 } |
|
5111 } |
|
5112 return ''; |
|
5113 }, |
|
5114 bracketNum = function(content) { |
|
5115 return (--content.split("{") |
|
5116 .length) - (--content.split("}") |
|
5117 .length); |
|
5118 }, |
|
5119 myEval = function(script) { |
|
5120 eval(script); |
|
5121 }, |
|
5122 attrReg = /([^\s]+)[\s]*=[\s]*$/, |
|
5123 // Commands for caching. |
|
5124 startTxt = 'var ___v1ew = [];', |
|
5125 finishTxt = "return ___v1ew.join('')", |
|
5126 put_cmd = "___v1ew.push(\n", |
|
5127 insert_cmd = put_cmd, |
|
5128 // Global controls (used by other functions to know where we are). |
|
5129 // Are we inside a tag? |
|
5130 htmlTag = null, |
|
5131 // Are we within a quote within a tag? |
|
5132 quote = null, |
|
5133 // What was the text before the current quote? (used to get the `attr` name) |
|
5134 beforeQuote = null, |
|
5135 // Whether a rescan is in progress |
|
5136 rescan = null, |
|
5137 getAttrName = function() { |
|
5138 var matches = beforeQuote.match(attrReg); |
|
5139 return matches && matches[1]; |
|
5140 }, |
|
5141 // Used to mark where the element is. |
|
5142 status = function() { |
|
5143 // `t` - `1`. |
|
5144 // `h` - `0`. |
|
5145 // `q` - String `beforeQuote`. |
|
5146 return quote ? "'" + getAttrName() + "'" : (htmlTag ? 1 : 0); |
|
5147 }, |
|
5148 // returns the top of a stack |
|
5149 top = function(stack) { |
|
5150 return stack[stack.length - 1]; |
|
5151 }, |
|
5152 Scanner; |
|
5153 |
|
5154 |
|
5155 can.view.Scanner = Scanner = function(options) { |
|
5156 // Set options on self |
|
5157 can.extend(this, { |
|
5158 |
|
5159 text: {}, |
|
5160 tokens: [] |
|
5161 }, options); |
|
5162 // make sure it's an empty string if it's not |
|
5163 this.text.options = this.text.options || ""; |
|
5164 |
|
5165 // Cache a token lookup |
|
5166 this.tokenReg = []; |
|
5167 this.tokenSimple = { |
|
5168 "<": "<", |
|
5169 ">": ">", |
|
5170 '"': '"', |
|
5171 "'": "'" |
|
5172 }; |
|
5173 this.tokenComplex = []; |
|
5174 this.tokenMap = {}; |
|
5175 for (var i = 0, token; token = this.tokens[i]; i++) { |
|
5176 |
|
5177 |
|
5178 // Save complex mappings (custom regexp) |
|
5179 if (token[2]) { |
|
5180 this.tokenReg.push(token[2]); |
|
5181 this.tokenComplex.push({ |
|
5182 abbr: token[1], |
|
5183 re: new RegExp(token[2]), |
|
5184 rescan: token[3] |
|
5185 }); |
|
5186 } |
|
5187 // Save simple mappings (string only, no regexp) |
|
5188 else { |
|
5189 this.tokenReg.push(token[1]); |
|
5190 this.tokenSimple[token[1]] = token[0]; |
|
5191 } |
|
5192 this.tokenMap[token[0]] = token[1]; |
|
5193 } |
|
5194 |
|
5195 // Cache the token registry. |
|
5196 this.tokenReg = new RegExp("(" + this.tokenReg.slice(0) |
|
5197 .concat(["<", ">", '"', "'"]) |
|
5198 .join("|") + ")", "g"); |
|
5199 }; |
|
5200 |
|
5201 |
|
5202 Scanner.prototype = { |
|
5203 // a default that can be overwritten |
|
5204 helpers: [], |
|
5205 |
|
5206 scan: function(source, name) { |
|
5207 var tokens = [], |
|
5208 last = 0, |
|
5209 simple = this.tokenSimple, |
|
5210 complex = this.tokenComplex; |
|
5211 |
|
5212 source = source.replace(newLine, "\n"); |
|
5213 if (this.transform) { |
|
5214 source = this.transform(source); |
|
5215 } |
|
5216 source.replace(this.tokenReg, function(whole, part) { |
|
5217 // offset is the second to last argument |
|
5218 var offset = arguments[arguments.length - 2]; |
|
5219 |
|
5220 // if the next token starts after the last token ends |
|
5221 // push what's in between |
|
5222 if (offset > last) { |
|
5223 tokens.push(source.substring(last, offset)); |
|
5224 } |
|
5225 |
|
5226 // push the simple token (if there is one) |
|
5227 if (simple[whole]) { |
|
5228 tokens.push(whole); |
|
5229 } |
|
5230 // otherwise lookup complex tokens |
|
5231 else { |
|
5232 for (var i = 0, token; token = complex[i]; i++) { |
|
5233 if (token.re.test(whole)) { |
|
5234 tokens.push(token.abbr); |
|
5235 // Push a rescan function if one exists |
|
5236 if (token.rescan) { |
|
5237 tokens.push(token.rescan(part)); |
|
5238 } |
|
5239 break; |
|
5240 } |
|
5241 } |
|
5242 } |
|
5243 |
|
5244 // update the position of the last part of the last token |
|
5245 last = offset + part.length; |
|
5246 }); |
|
5247 |
|
5248 // if there's something at the end, add it |
|
5249 if (last < source.length) { |
|
5250 tokens.push(source.substr(last)); |
|
5251 } |
|
5252 |
|
5253 var content = '', |
|
5254 buff = [startTxt + (this.text.start || '')], |
|
5255 // Helper `function` for putting stuff in the view concat. |
|
5256 put = function(content, bonus) { |
|
5257 buff.push(put_cmd, '"', clean(content), '"' + (bonus || '') + ');'); |
|
5258 }, |
|
5259 // A stack used to keep track of how we should end a bracket |
|
5260 // `}`. |
|
5261 // Once we have a `<%= %>` with a `leftBracket`, |
|
5262 // we store how the file should end here (either `))` or `;`). |
|
5263 endStack = [], |
|
5264 // The last token, used to remember which tag we are in. |
|
5265 lastToken, |
|
5266 // The corresponding magic tag. |
|
5267 startTag = null, |
|
5268 // Was there a magic tag inside an html tag? |
|
5269 magicInTag = false, |
|
5270 // was there a special state |
|
5271 specialStates = { |
|
5272 attributeHookups: [], |
|
5273 // a stack of tagHookups |
|
5274 tagHookups: [], |
|
5275 //last tag hooked up |
|
5276 lastTagHookup: '' |
|
5277 }, |
|
5278 // Helper `function` for removing tagHookups from the hookup stack |
|
5279 popTagHookup = function() { |
|
5280 // The length of tagHookups is the nested depth which can be used to uniquely identify custom tags of the same type |
|
5281 specialStates.lastTagHookup = specialStates.tagHookups.pop() + specialStates.tagHookups.length; |
|
5282 }, |
|
5283 // The current tag name. |
|
5284 tagName = '', |
|
5285 // stack of tagNames |
|
5286 tagNames = [], |
|
5287 // Pop from tagNames? |
|
5288 popTagName = false, |
|
5289 // Declared here. |
|
5290 bracketCount, |
|
5291 // in a special attr like src= or style= |
|
5292 specialAttribute = false, |
|
5293 |
|
5294 i = 0, |
|
5295 token, |
|
5296 tmap = this.tokenMap, |
|
5297 attrName; |
|
5298 |
|
5299 // Reinitialize the tag state goodness. |
|
5300 htmlTag = quote = beforeQuote = null; |
|
5301 for (; |
|
5302 (token = tokens[i++]) !== undefined;) { |
|
5303 if (startTag === null) { |
|
5304 switch (token) { |
|
5305 case tmap.left: |
|
5306 case tmap.escapeLeft: |
|
5307 case tmap.returnLeft: |
|
5308 magicInTag = htmlTag && 1; |
|
5309 |
|
5310 case tmap.commentLeft: |
|
5311 // A new line -- just add whatever content within a clean. |
|
5312 // Reset everything. |
|
5313 startTag = token; |
|
5314 if (content.length) { |
|
5315 put(content); |
|
5316 } |
|
5317 content = ''; |
|
5318 break; |
|
5319 case tmap.escapeFull: |
|
5320 // This is a full line escape (a line that contains only whitespace and escaped logic) |
|
5321 // Break it up into escape left and right |
|
5322 magicInTag = htmlTag && 1; |
|
5323 rescan = 1; |
|
5324 startTag = tmap.escapeLeft; |
|
5325 if (content.length) { |
|
5326 put(content); |
|
5327 } |
|
5328 rescan = tokens[i++]; |
|
5329 content = rescan.content || rescan; |
|
5330 if (rescan.before) { |
|
5331 put(rescan.before); |
|
5332 } |
|
5333 tokens.splice(i, 0, tmap.right); |
|
5334 break; |
|
5335 case tmap.commentFull: |
|
5336 // Ignore full line comments. |
|
5337 break; |
|
5338 case tmap.templateLeft: |
|
5339 content += tmap.left; |
|
5340 break; |
|
5341 case '<': |
|
5342 // Make sure we are not in a comment. |
|
5343 if (tokens[i].indexOf("!--") !== 0) { |
|
5344 htmlTag = 1; |
|
5345 magicInTag = 0; |
|
5346 } |
|
5347 |
|
5348 content += token; |
|
5349 |
|
5350 break; |
|
5351 case '>': |
|
5352 htmlTag = 0; |
|
5353 // content.substr(-1) doesn't work in IE7/8 |
|
5354 var emptyElement = (content.substr(content.length - 1) === "/" || content.substr(content.length - 2) === "--"), |
|
5355 attrs = ""; |
|
5356 // if there was a magic tag |
|
5357 // or it's an element that has text content between its tags, |
|
5358 // but content is not other tags add a hookup |
|
5359 // TODO: we should only add `can.EJS.pending()` if there's a magic tag |
|
5360 // within the html tags. |
|
5361 if (specialStates.attributeHookups.length) { |
|
5362 attrs = "attrs: ['" + specialStates.attributeHookups.join("','") + "'], "; |
|
5363 specialStates.attributeHookups = []; |
|
5364 } |
|
5365 // this is the > of a special tag |
|
5366 // comparison to lastTagHookup makes sure the same custom tags can be nested |
|
5367 if ((tagName + specialStates.tagHookups.length) !== specialStates.lastTagHookup && tagName === top(specialStates.tagHookups)) { |
|
5368 // If it's a self closing tag (like <content/>) make sure we put the / at the end. |
|
5369 if (emptyElement) { |
|
5370 content = content.substr(0, content.length - 1); |
|
5371 } |
|
5372 // Put the start of the end |
|
5373 buff.push(put_cmd, |
|
5374 '"', clean(content), '"', |
|
5375 ",can.view.pending({tagName:'" + tagName + "'," + (attrs) + "scope: " + (this.text.scope || "this") + this.text.options); |
|
5376 |
|
5377 // if it's a self closing tag (like <content/>) close and end the tag |
|
5378 if (emptyElement) { |
|
5379 buff.push("}));"); |
|
5380 content = "/>"; |
|
5381 popTagHookup(); |
|
5382 } |
|
5383 // if it's an empty tag |
|
5384 else if (tokens[i] === "<" && tokens[i + 1] === "/" + tagName) { |
|
5385 buff.push("}));"); |
|
5386 content = token; |
|
5387 popTagHookup(); |
|
5388 } else { |
|
5389 // it has content |
|
5390 buff.push(",subtemplate: function(" + this.text.argNames + "){\n" + startTxt + (this.text.start || '')); |
|
5391 content = ''; |
|
5392 } |
|
5393 |
|
5394 } else if (magicInTag || (!popTagName && elements.tagToContentPropMap[tagNames[tagNames.length - 1]]) || attrs) { |
|
5395 // make sure / of /> is on the right of pending |
|
5396 var pendingPart = ",can.view.pending({" + attrs + "scope: " + (this.text.scope || "this") + this.text.options + "}),\""; |
|
5397 if (emptyElement) { |
|
5398 put(content.substr(0, content.length - 1), pendingPart + "/>\""); |
|
5399 } else { |
|
5400 put(content, pendingPart + ">\""); |
|
5401 } |
|
5402 content = ''; |
|
5403 magicInTag = 0; |
|
5404 } else { |
|
5405 content += token; |
|
5406 } |
|
5407 |
|
5408 // if it's a tag like <input/> |
|
5409 if (emptyElement || popTagName) { |
|
5410 // remove the current tag in the stack |
|
5411 tagNames.pop(); |
|
5412 // set the current tag to the previous parent |
|
5413 tagName = tagNames[tagNames.length - 1]; |
|
5414 // Don't pop next time |
|
5415 popTagName = false; |
|
5416 } |
|
5417 specialStates.attributeHookups = []; |
|
5418 break; |
|
5419 case "'": |
|
5420 case '"': |
|
5421 // If we are in an html tag, finding matching quotes. |
|
5422 if (htmlTag) { |
|
5423 // We have a quote and it matches. |
|
5424 if (quote && quote === token) { |
|
5425 // We are exiting the quote. |
|
5426 quote = null; |
|
5427 // Otherwise we are creating a quote. |
|
5428 // TODO: does this handle `\`? |
|
5429 var attr = getAttrName(); |
|
5430 if (viewCallbacks.attr(attr)) { |
|
5431 specialStates.attributeHookups.push(attr); |
|
5432 } |
|
5433 |
|
5434 if (specialAttribute) { |
|
5435 |
|
5436 content += token; |
|
5437 put(content); |
|
5438 buff.push(finishTxt, "}));\n"); |
|
5439 content = ""; |
|
5440 specialAttribute = false; |
|
5441 |
|
5442 break; |
|
5443 } |
|
5444 |
|
5445 } else if (quote === null) { |
|
5446 quote = token; |
|
5447 beforeQuote = lastToken; |
|
5448 attrName = getAttrName(); |
|
5449 // TODO: check if there's magic!!!! |
|
5450 if ((tagName === "img" && attrName === "src") || attrName === "style") { |
|
5451 // put content that was before the attr name, but don't include the src= |
|
5452 put(content.replace(attrReg, "")); |
|
5453 content = ""; |
|
5454 |
|
5455 specialAttribute = true; |
|
5456 |
|
5457 buff.push(insert_cmd, "can.view.txt(2,'" + getTag(tagName, tokens, i) + "'," + status() + ",this,function(){", startTxt); |
|
5458 put(attrName + "=" + token); |
|
5459 break; |
|
5460 } |
|
5461 |
|
5462 } |
|
5463 } |
|
5464 |
|
5465 default: |
|
5466 // Track the current tag |
|
5467 if (lastToken === '<') { |
|
5468 |
|
5469 tagName = token.substr(0, 3) === "!--" ? |
|
5470 "!--" : token.split(/\s/)[0]; |
|
5471 |
|
5472 var isClosingTag = false, |
|
5473 cleanedTagName; |
|
5474 |
|
5475 if (tagName.indexOf("/") === 0) { |
|
5476 isClosingTag = true; |
|
5477 cleanedTagName = tagName.substr(1); |
|
5478 } |
|
5479 |
|
5480 if (isClosingTag) { // </tag> |
|
5481 |
|
5482 // when we enter a new tag, pop the tag name stack |
|
5483 if (top(tagNames) === cleanedTagName) { |
|
5484 // set tagName to the last tagName |
|
5485 // if there are no more tagNames, we'll rely on getTag. |
|
5486 tagName = cleanedTagName; |
|
5487 popTagName = true; |
|
5488 } |
|
5489 // if we are in a closing tag of a custom tag |
|
5490 if (top(specialStates.tagHookups) === cleanedTagName) { |
|
5491 |
|
5492 // remove the last < from the content |
|
5493 put(content.substr(0, content.length - 1)); |
|
5494 |
|
5495 // finish the "section" |
|
5496 buff.push(finishTxt + "}}) );"); |
|
5497 // the < belongs to the outside |
|
5498 content = "><"; |
|
5499 popTagHookup(); |
|
5500 } |
|
5501 |
|
5502 } else { |
|
5503 if (tagName.lastIndexOf("/") === tagName.length - 1) { |
|
5504 tagName = tagName.substr(0, tagName.length - 1); |
|
5505 |
|
5506 } |
|
5507 |
|
5508 if (tagName !== "!--" && (viewCallbacks.tag(tagName))) { |
|
5509 // if the content tag is inside something it doesn't belong ... |
|
5510 if (tagName === "content" && elements.tagMap[top(tagNames)]) { |
|
5511 // convert it to an element that will work |
|
5512 token = token.replace("content", elements.tagMap[top(tagNames)]); |
|
5513 } |
|
5514 // we will hookup at the ending tag> |
|
5515 specialStates.tagHookups.push(tagName); |
|
5516 } |
|
5517 |
|
5518 tagNames.push(tagName); |
|
5519 |
|
5520 } |
|
5521 |
|
5522 } |
|
5523 content += token; |
|
5524 break; |
|
5525 } |
|
5526 } else { |
|
5527 // We have a start tag. |
|
5528 switch (token) { |
|
5529 case tmap.right: |
|
5530 case tmap.returnRight: |
|
5531 switch (startTag) { |
|
5532 case tmap.left: |
|
5533 // Get the number of `{ minus }` |
|
5534 bracketCount = bracketNum(content); |
|
5535 |
|
5536 // We are ending a block. |
|
5537 if (bracketCount === 1) { |
|
5538 // We are starting on. |
|
5539 buff.push(insert_cmd, 'can.view.txt(0,\'' + getTag(tagName, tokens, i) + '\',' + status() + ',this,function(){', startTxt, content); |
|
5540 endStack.push({ |
|
5541 before: "", |
|
5542 after: finishTxt + "}));\n" |
|
5543 }); |
|
5544 } else { |
|
5545 |
|
5546 // How are we ending this statement? |
|
5547 last = // If the stack has value and we are ending a block... |
|
5548 endStack.length && bracketCount === -1 ? // Use the last item in the block stack. |
|
5549 endStack.pop() : // Or use the default ending. |
|
5550 { |
|
5551 after: ";" |
|
5552 }; |
|
5553 |
|
5554 // If we are ending a returning block, |
|
5555 // add the finish text which returns the result of the |
|
5556 // block. |
|
5557 if (last.before) { |
|
5558 buff.push(last.before); |
|
5559 } |
|
5560 // Add the remaining content. |
|
5561 buff.push(content, ";", last.after); |
|
5562 } |
|
5563 break; |
|
5564 case tmap.escapeLeft: |
|
5565 case tmap.returnLeft: |
|
5566 // We have an extra `{` -> `block`. |
|
5567 // Get the number of `{ minus }`. |
|
5568 bracketCount = bracketNum(content); |
|
5569 // If we have more `{`, it means there is a block. |
|
5570 if (bracketCount) { |
|
5571 // When we return to the same # of `{` vs `}` end with a `doubleParent`. |
|
5572 endStack.push({ |
|
5573 before: finishTxt, |
|
5574 after: "}));\n" |
|
5575 }); |
|
5576 } |
|
5577 |
|
5578 var escaped = startTag === tmap.escapeLeft ? 1 : 0, |
|
5579 commands = { |
|
5580 insert: insert_cmd, |
|
5581 tagName: getTag(tagName, tokens, i), |
|
5582 status: status(), |
|
5583 specialAttribute: specialAttribute |
|
5584 }; |
|
5585 |
|
5586 for (var ii = 0; ii < this.helpers.length; ii++) { |
|
5587 // Match the helper based on helper |
|
5588 // regex name value |
|
5589 var helper = this.helpers[ii]; |
|
5590 if (helper.name.test(content)) { |
|
5591 content = helper.fn(content, commands); |
|
5592 |
|
5593 // dont escape partials |
|
5594 if (helper.name.source === /^>[\s]*\w*/.source) { |
|
5595 escaped = 0; |
|
5596 } |
|
5597 break; |
|
5598 } |
|
5599 } |
|
5600 |
|
5601 // Handle special cases |
|
5602 if (typeof content === 'object') { |
|
5603 |
|
5604 if (content.startTxt && content.end && specialAttribute) { |
|
5605 buff.push(insert_cmd, "can.view.toStr( ", content.content, '() ) );'); |
|
5606 |
|
5607 } else { |
|
5608 |
|
5609 if (content.startTxt) { |
|
5610 buff.push(insert_cmd, "can.view.txt(\n" + |
|
5611 (typeof status() === "string" || (content.escaped != null ? content.escaped : escaped)) + ",\n'" + tagName + "',\n" + status() + ",\nthis,\n"); |
|
5612 } else if (content.startOnlyTxt) { |
|
5613 buff.push(insert_cmd, 'can.view.onlytxt(this,\n'); |
|
5614 } |
|
5615 buff.push(content.content); |
|
5616 if (content.end) { |
|
5617 buff.push('));'); |
|
5618 } |
|
5619 |
|
5620 } |
|
5621 |
|
5622 } else if (specialAttribute) { |
|
5623 |
|
5624 buff.push(insert_cmd, content, ');'); |
|
5625 |
|
5626 } else { |
|
5627 // If we have `<%== a(function(){ %>` then we want |
|
5628 // `can.EJS.text(0,this, function(){ return a(function(){ var _v1ew = [];`. |
|
5629 |
|
5630 buff.push(insert_cmd, "can.view.txt(\n" + (typeof status() === "string" || escaped) + |
|
5631 ",\n'" + tagName + "',\n" + status() + ",\nthis,\nfunction(){ " + |
|
5632 (this.text.escape || '') + |
|
5633 "return ", content, |
|
5634 // If we have a block. |
|
5635 bracketCount ? |
|
5636 // Start with startTxt `"var _v1ew = [];"`. |
|
5637 startTxt : |
|
5638 // If not, add `doubleParent` to close push and text. |
|
5639 "}));\n"); |
|
5640 |
|
5641 |
|
5642 |
|
5643 } |
|
5644 |
|
5645 if (rescan && rescan.after && rescan.after.length) { |
|
5646 put(rescan.after.length); |
|
5647 rescan = null; |
|
5648 } |
|
5649 break; |
|
5650 } |
|
5651 startTag = null; |
|
5652 content = ''; |
|
5653 break; |
|
5654 case tmap.templateLeft: |
|
5655 content += tmap.left; |
|
5656 break; |
|
5657 default: |
|
5658 content += token; |
|
5659 break; |
|
5660 } |
|
5661 } |
|
5662 lastToken = token; |
|
5663 } |
|
5664 |
|
5665 // Put it together... |
|
5666 if (content.length) { |
|
5667 // Should be `content.dump` in Ruby. |
|
5668 put(content); |
|
5669 } |
|
5670 buff.push(";"); |
|
5671 var template = buff.join(''), |
|
5672 out = { |
|
5673 out: (this.text.outStart || "") + template + " " + finishTxt + (this.text.outEnd || "") |
|
5674 }; |
|
5675 |
|
5676 // Use `eval` instead of creating a function, because it is easier to debug. |
|
5677 myEval.call(out, 'this.fn = (function(' + this.text.argNames + '){' + out.out + '});\r\n//# sourceURL=' + name + '.js'); |
|
5678 return out; |
|
5679 } |
|
5680 }; |
|
5681 |
|
5682 // can.view.attr |
|
5683 |
|
5684 // This is called when there is a special tag |
|
5685 can.view.pending = function(viewData) { |
|
5686 // we need to call any live hookups |
|
5687 // so get that and return the hook |
|
5688 // a better system will always be called with the same stuff |
|
5689 var hooks = can.view.getHooks(); |
|
5690 return can.view.hook(function(el) { |
|
5691 can.each(hooks, function(fn) { |
|
5692 fn(el); |
|
5693 }); |
|
5694 viewData.templateType = "legacy"; |
|
5695 if (viewData.tagName) { |
|
5696 viewCallbacks.tagHandler(el, viewData.tagName, viewData); |
|
5697 } |
|
5698 |
|
5699 can.each(viewData && viewData.attrs || [], function(attributeName) { |
|
5700 viewData.attributeName = attributeName; |
|
5701 var callback = viewCallbacks.attr(attributeName); |
|
5702 if (callback) { |
|
5703 callback(el, viewData); |
|
5704 } |
|
5705 }); |
|
5706 |
|
5707 }); |
|
5708 |
|
5709 }; |
|
5710 |
|
5711 can.view.tag("content", function(el, tagData) { |
|
5712 return tagData.scope; |
|
5713 }); |
|
5714 |
|
5715 can.view.Scanner = Scanner; |
|
5716 |
|
5717 return Scanner; |
|
5718 })(__m17, __m24, __m25); |
|
5719 |
|
5720 // ## can/view/node_lists/node_lists.js |
|
5721 var __m28 = (function(can) { |
|
5722 // ## Helpers |
|
5723 // Some browsers don't allow expando properties on HTMLTextNodes |
|
5724 // so let's try to assign a custom property, an 'expando' property. |
|
5725 // We use this boolean to determine how we are going to hold on |
|
5726 // to HTMLTextNode within a nodeList. More about this in the 'id' |
|
5727 // function. |
|
5728 var canExpando = true; |
|
5729 try { |
|
5730 document.createTextNode('')._ = 0; |
|
5731 } catch (ex) { |
|
5732 canExpando = false; |
|
5733 } |
|
5734 |
|
5735 // A mapping of element ids to nodeList id allowing us to quickly find an element |
|
5736 // that needs to be replaced when updated. |
|
5737 var nodeMap = {}, |
|
5738 // A mapping of ids to text nodes, this map will be used in the |
|
5739 // case of the browser not supporting expando properties. |
|
5740 textNodeMap = {}, |
|
5741 // The name of the expando property; the value returned |
|
5742 // given a nodeMap key. |
|
5743 expando = 'ejs_' + Math.random(), |
|
5744 // The id used as the key in our nodeMap, this integer |
|
5745 // will be preceded by 'element_' or 'obj_' depending on whether |
|
5746 // the element has a nodeName. |
|
5747 _id = 0, |
|
5748 |
|
5749 // ## nodeLists.id |
|
5750 // Given a template node, create an id on the node as a expando |
|
5751 // property, or if the node is an HTMLTextNode and the browser |
|
5752 // doesn't support expando properties store the id with a |
|
5753 // reference to the text node in an internal collection then return |
|
5754 // the lookup id. |
|
5755 id = function(node) { |
|
5756 // If the browser supports expando properties or the node |
|
5757 // provided is not an HTMLTextNode, we don't need to work |
|
5758 // with the internal textNodeMap and we can place the property |
|
5759 // on the node. |
|
5760 if (canExpando || node.nodeType !== 3) { |
|
5761 // If the node already has an (internal) id, then just |
|
5762 // return the key of the nodeMap. This would be the case |
|
5763 // in updating and unregistering a nodeList. |
|
5764 if (node[expando]) { |
|
5765 return node[expando]; |
|
5766 } else { |
|
5767 // If the node isn't already referenced in the map we need |
|
5768 // to generate a lookup id and place it on the node itself. |
|
5769 ++_id; |
|
5770 return node[expando] = (node.nodeName ? 'element_' : 'obj_') + _id; |
|
5771 } |
|
5772 } else { |
|
5773 // The nodeList has a specific collection for HTMLTextNodes for |
|
5774 // (older) browsers that do not support expando properties. |
|
5775 for (var textNodeID in textNodeMap) { |
|
5776 if (textNodeMap[textNodeID] === node) { |
|
5777 return textNodeID; |
|
5778 } |
|
5779 } |
|
5780 // If we didn't find the node, we need to register it and return |
|
5781 // the id used. |
|
5782 ++_id; |
|
5783 |
|
5784 // If we didn't find the node, we need to register it and return |
|
5785 // the id used. |
|
5786 // We have to store the node itself because of the browser's lack |
|
5787 // of support for expando properties (i.e. we can't use a look-up |
|
5788 // table and store the id on the node as a custom property). |
|
5789 textNodeMap['text_' + _id] = node; |
|
5790 return 'text_' + _id; |
|
5791 } |
|
5792 }, |
|
5793 splice = [].splice, |
|
5794 push = [].push, |
|
5795 |
|
5796 // ## nodeLists.itemsInChildListTree |
|
5797 // Given a nodeList return the number of child items in the provided |
|
5798 // list and any child lists. |
|
5799 itemsInChildListTree = function(list) { |
|
5800 var count = 0; |
|
5801 for (var i = 0, len = list.length; i < len; i++) { |
|
5802 var item = list[i]; |
|
5803 // If the item is an HTMLElement then increment the count by 1. |
|
5804 if (item.nodeType) { |
|
5805 count++; |
|
5806 } else { |
|
5807 // If the item is not an HTMLElement it is a list, so |
|
5808 // increment the count by the number of items in the child |
|
5809 // list. |
|
5810 count += itemsInChildListTree(item); |
|
5811 } |
|
5812 } |
|
5813 return count; |
|
5814 }; |
|
5815 |
|
5816 // ## Registering & Updating |
|
5817 // To keep all live-bound sections knowing which elements they are managing, |
|
5818 // all live-bound elments are registered and updated when they change. |
|
5819 // For example, the above template, when rendered with data like: |
|
5820 // data = new can.Map({ |
|
5821 // items: ["first","second"] |
|
5822 // }) |
|
5823 // This will first render the following content: |
|
5824 // <div> |
|
5825 // <span data-view-id='5'/> |
|
5826 // </div> |
|
5827 // When the `5` callback is called, this will register the `<span>` like: |
|
5828 // var ifsNodes = [<span 5>] |
|
5829 // nodeLists.register(ifsNodes); |
|
5830 // And then render `{{if}}`'s contents and update `ifsNodes` with it: |
|
5831 // nodeLists.update( ifsNodes, [<"\nItems:\n">, <span data-view-id="6">] ); |
|
5832 // Next, hookup `6` is called which will regsiter the `<span>` like: |
|
5833 // var eachsNodes = [<span 6>]; |
|
5834 // nodeLists.register(eachsNodes); |
|
5835 // And then it will render `{{#each}}`'s content and update `eachsNodes` with it: |
|
5836 // nodeLists.update(eachsNodes, [<label>,<label>]); |
|
5837 // As `nodeLists` knows that `eachsNodes` is inside `ifsNodes`, it also updates |
|
5838 // `ifsNodes`'s nodes to look like: |
|
5839 // [<"\nItems:\n">,<label>,<label>] |
|
5840 // Now, if all items were removed, `{{#if}}` would be able to remove |
|
5841 // all the `<label>` elements. |
|
5842 // When you regsiter a nodeList, you can also provide a callback to know when |
|
5843 // that nodeList has been replaced by a parent nodeList. This is |
|
5844 // useful for tearing down live-binding. |
|
5845 var nodeLists = { |
|
5846 id: id, |
|
5847 |
|
5848 // ## nodeLists.update |
|
5849 // Updates a nodeList with new items, i.e. when values for the template have changed. |
|
5850 update: function(nodeList, newNodes) { |
|
5851 // Unregister all childNodeLists. |
|
5852 var oldNodes = nodeLists.unregisterChildren(nodeList); |
|
5853 |
|
5854 newNodes = can.makeArray(newNodes); |
|
5855 |
|
5856 var oldListLength = nodeList.length; |
|
5857 |
|
5858 // Replace oldNodeLists's contents. |
|
5859 splice.apply(nodeList, [ |
|
5860 0, |
|
5861 oldListLength |
|
5862 ].concat(newNodes)); |
|
5863 |
|
5864 nodeLists.nestList(nodeList); |
|
5865 |
|
5866 return oldNodes; |
|
5867 }, |
|
5868 |
|
5869 // ## nodeLists.nestList |
|
5870 // If a given list does not exist in the nodeMap then create an lookup |
|
5871 // id for it in the nodeMap and assign the list to it. |
|
5872 // If the the provided does happen to exist in the nodeMap update the |
|
5873 // elements in the list. |
|
5874 // @param {Array.<HTMLElement>} nodeList The nodeList being nested. |
|
5875 nestList: function(list) { |
|
5876 var index = 0; |
|
5877 while (index < list.length) { |
|
5878 var node = list[index], |
|
5879 childNodeList = nodeMap[id(node)]; |
|
5880 if (childNodeList) { |
|
5881 if (childNodeList !== list) { |
|
5882 list.splice(index, itemsInChildListTree(childNodeList), childNodeList); |
|
5883 } |
|
5884 } else { |
|
5885 // Indicate the new nodes belong to this list. |
|
5886 nodeMap[id(node)] = list; |
|
5887 } |
|
5888 index++; |
|
5889 } |
|
5890 }, |
|
5891 |
|
5892 // ## nodeLists.last |
|
5893 // Return the last HTMLElement in a nodeList, if the last |
|
5894 // element is a nodeList, returns the last HTMLElement of |
|
5895 // the child list, etc. |
|
5896 last: function(nodeList) { |
|
5897 var last = nodeList[nodeList.length - 1]; |
|
5898 // If the last node in the list is not an HTMLElement |
|
5899 // it is a nodeList so call `last` again. |
|
5900 if (last.nodeType) { |
|
5901 return last; |
|
5902 } else { |
|
5903 return nodeLists.last(last); |
|
5904 } |
|
5905 }, |
|
5906 |
|
5907 // ## nodeLists.first |
|
5908 // Return the first HTMLElement in a nodeList, if the first |
|
5909 // element is a nodeList, returns the first HTMLElement of |
|
5910 // the child list, etc. |
|
5911 first: function(nodeList) { |
|
5912 var first = nodeList[0]; |
|
5913 // If the first node in the list is not an HTMLElement |
|
5914 // it is a nodeList so call `first` again. |
|
5915 if (first.nodeType) { |
|
5916 return first; |
|
5917 } else { |
|
5918 return nodeLists.first(first); |
|
5919 } |
|
5920 }, |
|
5921 |
|
5922 // ## nodeLists.register |
|
5923 // Registers a nodeList and returns the nodeList passed to register |
|
5924 register: function(nodeList, unregistered, parent) { |
|
5925 // If a unregistered callback has been provided assign it to the nodeList |
|
5926 // as a property to be called when the nodeList is unregistred. |
|
5927 nodeList.unregistered = unregistered; |
|
5928 |
|
5929 nodeLists.nestList(nodeList); |
|
5930 |
|
5931 return nodeList; |
|
5932 }, |
|
5933 |
|
5934 // ## nodeLists.unregisterChildren |
|
5935 // Unregister all childen within the provided list and return the |
|
5936 // unregistred nodes. |
|
5937 // @param {Array.<HTMLElement>} nodeList The child list to unregister. |
|
5938 unregisterChildren: function(nodeList) { |
|
5939 var nodes = []; |
|
5940 // For each node in the nodeList we want to compute it's id |
|
5941 // and delete it from the nodeList's internal map. |
|
5942 can.each(nodeList, function(node) { |
|
5943 // If the node does not have a nodeType it is an array of |
|
5944 // nodes. |
|
5945 if (node.nodeType) { |
|
5946 delete nodeMap[id(node)]; |
|
5947 nodes.push(node); |
|
5948 } else { |
|
5949 // Recursively unregister each of the child lists in |
|
5950 // the nodeList. |
|
5951 push.apply(nodes, nodeLists.unregister(node)); |
|
5952 } |
|
5953 }); |
|
5954 return nodes; |
|
5955 }, |
|
5956 |
|
5957 // ## nodeLists.unregister |
|
5958 // Unregister's a nodeList and returns the unregistered nodes. |
|
5959 // Call if the nodeList is no longer being updated. This will |
|
5960 // also unregister all child nodeLists. |
|
5961 unregister: function(nodeList) { |
|
5962 var nodes = nodeLists.unregisterChildren(nodeList); |
|
5963 // If an 'unregisted' function was provided during registration, remove |
|
5964 // it from the list, and call the function provided. |
|
5965 if (nodeList.unregistered) { |
|
5966 var unregisteredCallback = nodeList.unregistered; |
|
5967 delete nodeList.unregistered; |
|
5968 |
|
5969 unregisteredCallback(); |
|
5970 } |
|
5971 return nodes; |
|
5972 }, |
|
5973 |
|
5974 nodeMap: nodeMap |
|
5975 }; |
|
5976 can.view.nodeLists = nodeLists; |
|
5977 return nodeLists; |
|
5978 })(__m3, __m24); |
|
5979 |
|
5980 // ## can/view/parser/parser.js |
|
5981 var __m29 = (function(can) { |
|
5982 |
|
5983 |
|
5984 function makeMap(str) { |
|
5985 var obj = {}, items = str.split(","); |
|
5986 for (var i = 0; i < items.length; i++) { |
|
5987 obj[items[i]] = true; |
|
5988 } |
|
5989 |
|
5990 return obj; |
|
5991 } |
|
5992 |
|
5993 var alphaNumericHU = "-A-Za-z0-9_", |
|
5994 attributeNames = "[a-zA-Z_:][" + alphaNumericHU + ":.]+", |
|
5995 spaceEQspace = "\\s*=\\s*", |
|
5996 dblQuote2dblQuote = "\"((?:\\\\.|[^\"])*)\"", |
|
5997 quote2quote = "'((?:\\\\.|[^'])*)'", |
|
5998 attributeEqAndValue = "(?:" + spaceEQspace + "(?:" + |
|
5999 "(?:\"[^\"]*\")|(?:'[^']*')|[^>\\s]+))?", |
|
6000 matchStash = "\\{\\{[^\\}]*\\}\\}\\}?", |
|
6001 stash = "\\{\\{([^\\}]*)\\}\\}\\}?", |
|
6002 startTag = new RegExp("^<([" + alphaNumericHU + "]+)" + |
|
6003 "(" + |
|
6004 "(?:\\s*" + |
|
6005 "(?:(?:" + |
|
6006 "(?:" + attributeNames + ")?" + |
|
6007 attributeEqAndValue + ")|" + |
|
6008 "(?:" + matchStash + ")+)" + |
|
6009 ")*" + |
|
6010 ")\\s*(\\/?)>"), |
|
6011 endTag = new RegExp("^<\\/([" + alphaNumericHU + "]+)[^>]*>"), |
|
6012 attr = new RegExp("(?:" + |
|
6013 "(?:(" + attributeNames + ")|" + stash + ")" + |
|
6014 "(?:" + spaceEQspace + |
|
6015 "(?:" + |
|
6016 "(?:" + dblQuote2dblQuote + ")|(?:" + quote2quote + ")|([^>\\s]+)" + |
|
6017 ")" + |
|
6018 ")?)", "g"), |
|
6019 mustache = new RegExp(stash, "g"), |
|
6020 txtBreak = /<|\{\{/; |
|
6021 |
|
6022 // Empty Elements - HTML 5 |
|
6023 var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed"); |
|
6024 |
|
6025 // Block Elements - HTML 5 |
|
6026 var block = makeMap("address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video"); |
|
6027 |
|
6028 // Inline Elements - HTML 5 |
|
6029 var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var"); |
|
6030 |
|
6031 // Elements that you can, intentionally, leave open |
|
6032 // (and which close themselves) |
|
6033 var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr"); |
|
6034 |
|
6035 // Attributes that have their values filled in disabled="disabled" |
|
6036 var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"); |
|
6037 |
|
6038 // Special Elements (can contain anything) |
|
6039 var special = makeMap("script,style"); |
|
6040 |
|
6041 var HTMLParser = function(html, handler) { |
|
6042 |
|
6043 function parseStartTag(tag, tagName, rest, unary) { |
|
6044 tagName = tagName.toLowerCase(); |
|
6045 |
|
6046 if (block[tagName]) { |
|
6047 while (stack.last() && inline[stack.last()]) { |
|
6048 parseEndTag("", stack.last()); |
|
6049 } |
|
6050 } |
|
6051 |
|
6052 if (closeSelf[tagName] && stack.last() === tagName) { |
|
6053 parseEndTag("", tagName); |
|
6054 } |
|
6055 |
|
6056 unary = empty[tagName] || !! unary; |
|
6057 |
|
6058 handler.start(tagName, unary); |
|
6059 |
|
6060 if (!unary) { |
|
6061 stack.push(tagName); |
|
6062 } |
|
6063 // find attribute or special |
|
6064 HTMLParser.parseAttrs(rest, handler); |
|
6065 |
|
6066 handler.end(tagName, unary); |
|
6067 |
|
6068 } |
|
6069 |
|
6070 function parseEndTag(tag, tagName) { |
|
6071 // If no tag name is provided, clean shop |
|
6072 var pos; |
|
6073 if (!tagName) { |
|
6074 pos = 0; |
|
6075 } |
|
6076 |
|
6077 |
|
6078 // Find the closest opened tag of the same type |
|
6079 else { |
|
6080 for (pos = stack.length - 1; pos >= 0; pos--) { |
|
6081 if (stack[pos] === tagName) { |
|
6082 break; |
|
6083 } |
|
6084 } |
|
6085 |
|
6086 } |
|
6087 |
|
6088 |
|
6089 if (pos >= 0) { |
|
6090 // Close all the open elements, up the stack |
|
6091 for (var i = stack.length - 1; i >= pos; i--) { |
|
6092 if (handler.close) { |
|
6093 handler.close(stack[i]); |
|
6094 } |
|
6095 } |
|
6096 |
|
6097 // Remove the open elements from the stack |
|
6098 stack.length = pos; |
|
6099 } |
|
6100 } |
|
6101 |
|
6102 function parseMustache(mustache, inside) { |
|
6103 if (handler.special) { |
|
6104 handler.special(inside); |
|
6105 } |
|
6106 } |
|
6107 |
|
6108 |
|
6109 var index, chars, match, stack = [], |
|
6110 last = html; |
|
6111 stack.last = function() { |
|
6112 return this[this.length - 1]; |
|
6113 }; |
|
6114 |
|
6115 while (html) { |
|
6116 chars = true; |
|
6117 |
|
6118 // Make sure we're not in a script or style element |
|
6119 if (!stack.last() || !special[stack.last()]) { |
|
6120 |
|
6121 // Comment |
|
6122 if (html.indexOf("<!--") === 0) { |
|
6123 index = html.indexOf("-->"); |
|
6124 |
|
6125 if (index >= 0) { |
|
6126 if (handler.comment) { |
|
6127 handler.comment(html.substring(4, index)); |
|
6128 } |
|
6129 html = html.substring(index + 3); |
|
6130 chars = false; |
|
6131 } |
|
6132 |
|
6133 // end tag |
|
6134 } else if (html.indexOf("</") === 0) { |
|
6135 match = html.match(endTag); |
|
6136 |
|
6137 if (match) { |
|
6138 html = html.substring(match[0].length); |
|
6139 match[0].replace(endTag, parseEndTag); |
|
6140 chars = false; |
|
6141 } |
|
6142 |
|
6143 // start tag |
|
6144 } else if (html.indexOf("<") === 0) { |
|
6145 match = html.match(startTag); |
|
6146 |
|
6147 if (match) { |
|
6148 html = html.substring(match[0].length); |
|
6149 match[0].replace(startTag, parseStartTag); |
|
6150 chars = false; |
|
6151 } |
|
6152 } else if (html.indexOf("{{") === 0) { |
|
6153 match = html.match(mustache); |
|
6154 |
|
6155 if (match) { |
|
6156 html = html.substring(match[0].length); |
|
6157 match[0].replace(mustache, parseMustache); |
|
6158 } |
|
6159 } |
|
6160 |
|
6161 if (chars) { |
|
6162 index = html.search(txtBreak); |
|
6163 |
|
6164 var text = index < 0 ? html : html.substring(0, index); |
|
6165 html = index < 0 ? "" : html.substring(index); |
|
6166 |
|
6167 if (handler.chars && text) { |
|
6168 handler.chars(text); |
|
6169 } |
|
6170 } |
|
6171 |
|
6172 } else { |
|
6173 html = html.replace(new RegExp("([\\s\\S]*?)<\/" + stack.last() + "[^>]*>"), function(all, text) { |
|
6174 text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, "$1$2"); |
|
6175 if (handler.chars) { |
|
6176 handler.chars(text); |
|
6177 } |
|
6178 return ""; |
|
6179 }); |
|
6180 |
|
6181 parseEndTag("", stack.last()); |
|
6182 } |
|
6183 |
|
6184 if (html === last) { |
|
6185 throw "Parse Error: " + html; |
|
6186 } |
|
6187 |
|
6188 last = html; |
|
6189 } |
|
6190 |
|
6191 // Clean up any remaining tags |
|
6192 parseEndTag(); |
|
6193 |
|
6194 |
|
6195 handler.done(); |
|
6196 }; |
|
6197 HTMLParser.parseAttrs = function(rest, handler) { |
|
6198 |
|
6199 |
|
6200 (rest != null ? rest : "").replace(attr, function(text, name, special, dblQuote, singleQuote, val) { |
|
6201 if (special) { |
|
6202 handler.special(special); |
|
6203 |
|
6204 } |
|
6205 if (name || dblQuote || singleQuote || val) { |
|
6206 var value = arguments[3] ? arguments[3] : |
|
6207 arguments[4] ? arguments[4] : |
|
6208 arguments[5] ? arguments[5] : |
|
6209 fillAttrs[name.toLowerCase()] ? name : ""; |
|
6210 handler.attrStart(name || ""); |
|
6211 |
|
6212 var last = mustache.lastIndex = 0, |
|
6213 res = mustache.exec(value), |
|
6214 chars; |
|
6215 while (res) { |
|
6216 chars = value.substring( |
|
6217 last, |
|
6218 mustache.lastIndex - res[0].length); |
|
6219 if (chars.length) { |
|
6220 handler.attrValue(chars); |
|
6221 } |
|
6222 handler.special(res[1]); |
|
6223 last = mustache.lastIndex; |
|
6224 res = mustache.exec(value); |
|
6225 } |
|
6226 chars = value.substr( |
|
6227 last, |
|
6228 value.length); |
|
6229 if (chars) { |
|
6230 handler.attrValue(chars); |
|
6231 } |
|
6232 handler.attrEnd(name || ""); |
|
6233 } |
|
6234 |
|
6235 |
|
6236 }); |
|
6237 |
|
6238 |
|
6239 }; |
|
6240 |
|
6241 can.view.parser = HTMLParser; |
|
6242 |
|
6243 return HTMLParser; |
|
6244 |
|
6245 })(__m17); |
|
6246 |
|
6247 // ## can/view/live/live.js |
|
6248 var __m27 = (function(can, elements, view, nodeLists, parser) { |
|
6249 |
|
6250 elements = elements || can.view.elements; |
|
6251 nodeLists = nodeLists || can.view.NodeLists; |
|
6252 parser = parser || can.view.parser; |
|
6253 |
|
6254 // ## live.js |
|
6255 // The live module provides live binding for computes |
|
6256 // and can.List. |
|
6257 // Currently, it's API is designed for `can/view/render`, but |
|
6258 // it could easily be used for other purposes. |
|
6259 // ### Helper methods |
|
6260 // #### setup |
|
6261 // `setup(HTMLElement, bind(data), unbind(data)) -> data` |
|
6262 // Calls bind right away, but will call unbind |
|
6263 // if the element is "destroyed" (removed from the DOM). |
|
6264 var setup = function(el, bind, unbind) { |
|
6265 // Removing an element can call teardown which |
|
6266 // unregister the nodeList which calls teardown |
|
6267 var tornDown = false, |
|
6268 teardown = function() { |
|
6269 if (!tornDown) { |
|
6270 tornDown = true; |
|
6271 unbind(data); |
|
6272 can.unbind.call(el, 'removed', teardown); |
|
6273 } |
|
6274 return true; |
|
6275 }, data = { |
|
6276 teardownCheck: function(parent) { |
|
6277 return parent ? false : teardown(); |
|
6278 } |
|
6279 }; |
|
6280 can.bind.call(el, 'removed', teardown); |
|
6281 bind(data); |
|
6282 return data; |
|
6283 }, |
|
6284 // #### listen |
|
6285 // Calls setup, but presets bind and unbind to |
|
6286 // operate on a compute |
|
6287 listen = function(el, compute, change) { |
|
6288 return setup(el, function() { |
|
6289 compute.bind('change', change); |
|
6290 }, function(data) { |
|
6291 compute.unbind('change', change); |
|
6292 if (data.nodeList) { |
|
6293 nodeLists.unregister(data.nodeList); |
|
6294 } |
|
6295 }); |
|
6296 }, |
|
6297 // #### getAttributeParts |
|
6298 // Breaks up a string like foo='bar' into ["foo","'bar'""] |
|
6299 getAttributeParts = function(newVal) { |
|
6300 var attrs = {}, |
|
6301 attr; |
|
6302 parser.parseAttrs(newVal, { |
|
6303 attrStart: function(name) { |
|
6304 attrs[name] = ""; |
|
6305 attr = name; |
|
6306 }, |
|
6307 attrValue: function(value) { |
|
6308 attrs[attr] += value; |
|
6309 }, |
|
6310 attrEnd: function() {} |
|
6311 }); |
|
6312 return attrs; |
|
6313 }, |
|
6314 splice = [].splice, |
|
6315 isNode = function(obj) { |
|
6316 return obj && obj.nodeType; |
|
6317 }, |
|
6318 addTextNodeIfNoChildren = function(frag) { |
|
6319 if (!frag.childNodes.length) { |
|
6320 frag.appendChild(document.createTextNode("")); |
|
6321 } |
|
6322 }; |
|
6323 |
|
6324 var live = { |
|
6325 |
|
6326 list: function(el, compute, render, context, parentNode) { |
|
6327 |
|
6328 // A nodeList of all elements this live-list manages. |
|
6329 // This is here so that if this live list is within another section |
|
6330 // that section is able to remove the items in this list. |
|
6331 var masterNodeList = [el], |
|
6332 // A mapping of items to their indicies' |
|
6333 indexMap = [], |
|
6334 // Called when items are added to the list. |
|
6335 add = function(ev, items, index) { |
|
6336 // Collect new html and mappings |
|
6337 var frag = document.createDocumentFragment(), |
|
6338 newNodeLists = [], |
|
6339 newIndicies = []; |
|
6340 // For each new item, |
|
6341 can.each(items, function(item, key) { |
|
6342 var itemIndex = can.compute(key + index), |
|
6343 // get its string content |
|
6344 itemHTML = render.call(context, item, itemIndex), |
|
6345 gotText = typeof itemHTML === "string", |
|
6346 // and convert it into elements. |
|
6347 itemFrag = can.frag(itemHTML); |
|
6348 // Add those elements to the mappings. |
|
6349 |
|
6350 itemFrag = gotText ? can.view.hookup(itemFrag) : itemFrag; |
|
6351 |
|
6352 var childNodes = can.makeArray(itemFrag.childNodes); |
|
6353 |
|
6354 |
|
6355 |
|
6356 newNodeLists.push(nodeLists.register(childNodes)); |
|
6357 // Hookup the fragment (which sets up child live-bindings) and |
|
6358 // add it to the collection of all added elements. |
|
6359 frag.appendChild(itemFrag); |
|
6360 // track indicies; |
|
6361 newIndicies.push(itemIndex); |
|
6362 }); |
|
6363 // The position of elements is always after the initial text placeholder node |
|
6364 var masterListIndex = index + 1; |
|
6365 |
|
6366 |
|
6367 // Check if we are adding items at the end |
|
6368 if (!masterNodeList[masterListIndex]) { |
|
6369 elements.after(masterListIndex === 1 ? [text] : [nodeLists.last(masterNodeList[masterListIndex - 1])], frag); |
|
6370 } else { |
|
6371 // Add elements before the next index's first element. |
|
6372 var el = nodeLists.first(masterNodeList[masterListIndex]); |
|
6373 can.insertBefore(el.parentNode, frag, el); |
|
6374 } |
|
6375 splice.apply(masterNodeList, [ |
|
6376 masterListIndex, |
|
6377 0 |
|
6378 ].concat(newNodeLists)); |
|
6379 |
|
6380 // update indices after insert point |
|
6381 splice.apply(indexMap, [ |
|
6382 index, |
|
6383 0 |
|
6384 ].concat(newIndicies)); |
|
6385 |
|
6386 for (var i = index + newIndicies.length, len = indexMap.length; i < len; i++) { |
|
6387 indexMap[i](i); |
|
6388 } |
|
6389 }, |
|
6390 // Called when items are removed or when the bindings are torn down. |
|
6391 remove = function(ev, items, index, duringTeardown, fullTeardown) { |
|
6392 // If this is because an element was removed, we should |
|
6393 // check to make sure the live elements are still in the page. |
|
6394 // If we did this during a teardown, it would cause an infinite loop. |
|
6395 if (!duringTeardown && data.teardownCheck(text.parentNode)) { |
|
6396 return; |
|
6397 } |
|
6398 var removedMappings = masterNodeList.splice(index + 1, items.length), |
|
6399 itemsToRemove = []; |
|
6400 can.each(removedMappings, function(nodeList) { |
|
6401 |
|
6402 // Unregister to free up event bindings. |
|
6403 var nodesToRemove = nodeLists.unregister(nodeList); |
|
6404 |
|
6405 // add items that we will remove all at once |
|
6406 [].push.apply(itemsToRemove, nodesToRemove); |
|
6407 }); |
|
6408 // update indices after remove point |
|
6409 indexMap.splice(index, items.length); |
|
6410 for (var i = index, len = indexMap.length; i < len; i++) { |
|
6411 indexMap[i](i); |
|
6412 } |
|
6413 // don't remove elements during teardown. Something else will probably be doing that. |
|
6414 if (!fullTeardown) { |
|
6415 can.remove(can.$(itemsToRemove)); |
|
6416 } |
|
6417 |
|
6418 }, |
|
6419 // A text node placeholder |
|
6420 text = document.createTextNode(''), |
|
6421 // The current list. |
|
6422 list, |
|
6423 // Called when the list is replaced with a new list or the binding is torn-down. |
|
6424 teardownList = function(fullTeardown) { |
|
6425 // there might be no list right away, and the list might be a plain |
|
6426 // array |
|
6427 if (list && list.unbind) { |
|
6428 list.unbind('add', add) |
|
6429 .unbind('remove', remove); |
|
6430 } |
|
6431 // use remove to clean stuff up for us |
|
6432 remove({}, { |
|
6433 length: masterNodeList.length - 1 |
|
6434 }, 0, true, fullTeardown); |
|
6435 }, |
|
6436 // Called when the list is replaced or setup. |
|
6437 updateList = function(ev, newList, oldList) { |
|
6438 teardownList(); |
|
6439 // make an empty list if the compute returns null or undefined |
|
6440 list = newList || []; |
|
6441 // list might be a plain array |
|
6442 if (list.bind) { |
|
6443 list.bind('add', add) |
|
6444 .bind('remove', remove); |
|
6445 } |
|
6446 add({}, list, 0); |
|
6447 }; |
|
6448 parentNode = elements.getParentNode(el, parentNode); |
|
6449 // Setup binding and teardown to add and remove events |
|
6450 var data = setup(parentNode, function() { |
|
6451 if (can.isFunction(compute)) { |
|
6452 compute.bind('change', updateList); |
|
6453 } |
|
6454 }, function() { |
|
6455 if (can.isFunction(compute)) { |
|
6456 compute.unbind('change', updateList); |
|
6457 } |
|
6458 teardownList(true); |
|
6459 }); |
|
6460 |
|
6461 live.replace(masterNodeList, text, data.teardownCheck); |
|
6462 // run the list setup |
|
6463 updateList({}, can.isFunction(compute) ? compute() : compute); |
|
6464 }, |
|
6465 |
|
6466 html: function(el, compute, parentNode) { |
|
6467 var data; |
|
6468 parentNode = elements.getParentNode(el, parentNode); |
|
6469 data = listen(parentNode, compute, function(ev, newVal, oldVal) { |
|
6470 |
|
6471 // TODO: remove teardownCheck in 2.1 |
|
6472 var attached = nodeLists.first(nodes).parentNode; |
|
6473 // update the nodes in the DOM with the new rendered value |
|
6474 if (attached) { |
|
6475 makeAndPut(newVal); |
|
6476 } |
|
6477 data.teardownCheck(nodeLists.first(nodes).parentNode); |
|
6478 }); |
|
6479 |
|
6480 var nodes = [el], |
|
6481 makeAndPut = function(val) { |
|
6482 var isString = !isNode(val), |
|
6483 frag = can.frag(val), |
|
6484 oldNodes = can.makeArray(nodes); |
|
6485 |
|
6486 // Add a placeholder textNode if necessary. |
|
6487 addTextNodeIfNoChildren(frag); |
|
6488 |
|
6489 if (isString) { |
|
6490 frag = can.view.hookup(frag, parentNode); |
|
6491 } |
|
6492 // We need to mark each node as belonging to the node list. |
|
6493 oldNodes = nodeLists.update(nodes, frag.childNodes); |
|
6494 elements.replace(oldNodes, frag); |
|
6495 }; |
|
6496 data.nodeList = nodes; |
|
6497 |
|
6498 // register the span so nodeLists knows the parentNodeList |
|
6499 nodeLists.register(nodes, data.teardownCheck); |
|
6500 makeAndPut(compute()); |
|
6501 }, |
|
6502 |
|
6503 replace: function(nodes, val, teardown) { |
|
6504 var oldNodes = nodes.slice(0), |
|
6505 frag = can.frag(val); |
|
6506 nodeLists.register(nodes, teardown); |
|
6507 |
|
6508 |
|
6509 if (typeof val === 'string') { |
|
6510 // if it was a string, check for hookups |
|
6511 frag = can.view.hookup(frag, nodes[0].parentNode); |
|
6512 } |
|
6513 // We need to mark each node as belonging to the node list. |
|
6514 nodeLists.update(nodes, frag.childNodes); |
|
6515 elements.replace(oldNodes, frag); |
|
6516 return nodes; |
|
6517 }, |
|
6518 |
|
6519 text: function(el, compute, parentNode) { |
|
6520 var parent = elements.getParentNode(el, parentNode); |
|
6521 // setup listening right away so we don't have to re-calculate value |
|
6522 var data = listen(parent, compute, function(ev, newVal, oldVal) { |
|
6523 // Sometimes this is 'unknown' in IE and will throw an exception if it is |
|
6524 |
|
6525 if (typeof node.nodeValue !== 'unknown') { |
|
6526 node.nodeValue = can.view.toStr(newVal); |
|
6527 } |
|
6528 |
|
6529 // TODO: remove in 2.1 |
|
6530 data.teardownCheck(node.parentNode); |
|
6531 }), |
|
6532 // The text node that will be updated |
|
6533 node = document.createTextNode(can.view.toStr(compute())); |
|
6534 // Replace the placeholder with the live node and do the nodeLists thing. |
|
6535 // Add that node to nodeList so we can remove it when the parent element is removed from the page |
|
6536 data.nodeList = live.replace([el], node, data.teardownCheck); |
|
6537 }, |
|
6538 setAttributes: function(el, newVal) { |
|
6539 var attrs = getAttributeParts(newVal); |
|
6540 for (var name in attrs) { |
|
6541 can.attr.set(el, name, attrs[name]); |
|
6542 } |
|
6543 }, |
|
6544 |
|
6545 attributes: function(el, compute, currentValue) { |
|
6546 var oldAttrs = {}; |
|
6547 |
|
6548 var setAttrs = function(newVal) { |
|
6549 var newAttrs = getAttributeParts(newVal), |
|
6550 name; |
|
6551 for (name in newAttrs) { |
|
6552 var newValue = newAttrs[name], |
|
6553 oldValue = oldAttrs[name]; |
|
6554 if (newValue !== oldValue) { |
|
6555 can.attr.set(el, name, newValue); |
|
6556 } |
|
6557 delete oldAttrs[name]; |
|
6558 } |
|
6559 for (name in oldAttrs) { |
|
6560 elements.removeAttr(el, name); |
|
6561 } |
|
6562 oldAttrs = newAttrs; |
|
6563 }; |
|
6564 listen(el, compute, function(ev, newVal) { |
|
6565 setAttrs(newVal); |
|
6566 }); |
|
6567 // current value has been set |
|
6568 if (arguments.length >= 3) { |
|
6569 oldAttrs = getAttributeParts(currentValue); |
|
6570 } else { |
|
6571 setAttrs(compute()); |
|
6572 } |
|
6573 }, |
|
6574 attributePlaceholder: '__!!__', |
|
6575 attributeReplace: /__!!__/g, |
|
6576 attribute: function(el, attributeName, compute) { |
|
6577 listen(el, compute, function(ev, newVal) { |
|
6578 elements.setAttr(el, attributeName, hook.render()); |
|
6579 }); |
|
6580 var wrapped = can.$(el), |
|
6581 hooks; |
|
6582 // Get the list of hookups or create one for this element. |
|
6583 // Hooks is a map of attribute names to hookup `data`s. |
|
6584 // Each hookup data has: |
|
6585 // `render` - A `function` to render the value of the attribute. |
|
6586 // `funcs` - A list of hookup `function`s on that attribute. |
|
6587 // `batchNum` - The last event `batchNum`, used for performance. |
|
6588 hooks = can.data(wrapped, 'hooks'); |
|
6589 if (!hooks) { |
|
6590 can.data(wrapped, 'hooks', hooks = {}); |
|
6591 } |
|
6592 // Get the attribute value. |
|
6593 var attr = elements.getAttr(el, attributeName), |
|
6594 // Split the attribute value by the template. |
|
6595 // Only split out the first __!!__ so if we have multiple hookups in the same attribute, |
|
6596 // they will be put in the right spot on first render |
|
6597 parts = attr.split(live.attributePlaceholder), |
|
6598 goodParts = [], |
|
6599 hook; |
|
6600 goodParts.push(parts.shift(), parts.join(live.attributePlaceholder)); |
|
6601 // If we already had a hookup for this attribute... |
|
6602 if (hooks[attributeName]) { |
|
6603 // Just add to that attribute's list of `function`s. |
|
6604 hooks[attributeName].computes.push(compute); |
|
6605 } else { |
|
6606 // Create the hookup data. |
|
6607 hooks[attributeName] = { |
|
6608 render: function() { |
|
6609 var i = 0, |
|
6610 // attr doesn't have a value in IE |
|
6611 newAttr = attr ? attr.replace(live.attributeReplace, function() { |
|
6612 return elements.contentText(hook.computes[i++]()); |
|
6613 }) : elements.contentText(hook.computes[i++]()); |
|
6614 return newAttr; |
|
6615 }, |
|
6616 computes: [compute], |
|
6617 batchNum: undefined |
|
6618 }; |
|
6619 } |
|
6620 // Save the hook for slightly faster performance. |
|
6621 hook = hooks[attributeName]; |
|
6622 // Insert the value in parts. |
|
6623 goodParts.splice(1, 0, compute()); |
|
6624 |
|
6625 // Set the attribute. |
|
6626 elements.setAttr(el, attributeName, goodParts.join('')); |
|
6627 }, |
|
6628 specialAttribute: function(el, attributeName, compute) { |
|
6629 listen(el, compute, function(ev, newVal) { |
|
6630 elements.setAttr(el, attributeName, getValue(newVal)); |
|
6631 }); |
|
6632 elements.setAttr(el, attributeName, getValue(compute())); |
|
6633 }, |
|
6634 |
|
6635 simpleAttribute: function(el, attributeName, compute) { |
|
6636 listen(el, compute, function(ev, newVal) { |
|
6637 elements.setAttr(el, attributeName, newVal); |
|
6638 }); |
|
6639 elements.setAttr(el, attributeName, compute()); |
|
6640 } |
|
6641 }; |
|
6642 live.attr = live.simpleAttribute; |
|
6643 live.attrs = live.attributes; |
|
6644 var newLine = /(\r|\n)+/g; |
|
6645 var getValue = function(val) { |
|
6646 var regexp = /^["'].*["']$/; |
|
6647 val = val.replace(elements.attrReg, '') |
|
6648 .replace(newLine, ''); |
|
6649 // check if starts and ends with " or ' |
|
6650 return regexp.test(val) ? val.substr(1, val.length - 2) : val; |
|
6651 }; |
|
6652 can.view.live = live; |
|
6653 |
|
6654 return live; |
|
6655 })(__m3, __m24, __m17, __m28, __m29); |
|
6656 |
|
6657 // ## can/view/render.js |
|
6658 var __m26 = (function(can, elements, live) { |
|
6659 |
|
6660 |
|
6661 var pendingHookups = [], |
|
6662 tagChildren = function(tagName) { |
|
6663 var newTag = elements.tagMap[tagName] || "span"; |
|
6664 if (newTag === "span") { |
|
6665 //innerHTML in IE doesn't honor leading whitespace after empty elements |
|
6666 return "@@!!@@"; |
|
6667 } |
|
6668 return "<" + newTag + ">" + tagChildren(newTag) + "</" + newTag + ">"; |
|
6669 }, |
|
6670 contentText = function(input, tag) { |
|
6671 |
|
6672 // If it's a string, return. |
|
6673 if (typeof input === 'string') { |
|
6674 return input; |
|
6675 } |
|
6676 // If has no value, return an empty string. |
|
6677 if (!input && input !== 0) { |
|
6678 return ''; |
|
6679 } |
|
6680 |
|
6681 // If it's an object, and it has a hookup method. |
|
6682 var hook = (input.hookup && |
|
6683 |
|
6684 // Make a function call the hookup method. |
|
6685 |
|
6686 function(el, id) { |
|
6687 input.hookup.call(input, el, id); |
|
6688 }) || |
|
6689 |
|
6690 // Or if it's a `function`, just use the input. |
|
6691 (typeof input === 'function' && input); |
|
6692 |
|
6693 // Finally, if there is a `function` to hookup on some dom, |
|
6694 // add it to pending hookups. |
|
6695 if (hook) { |
|
6696 if (tag) { |
|
6697 return "<" + tag + " " + can.view.hook(hook) + "></" + tag + ">"; |
|
6698 } else { |
|
6699 pendingHookups.push(hook); |
|
6700 } |
|
6701 |
|
6702 return ''; |
|
6703 } |
|
6704 |
|
6705 // Finally, if all else is `false`, `toString()` it. |
|
6706 return "" + input; |
|
6707 }, |
|
6708 // Returns escaped/sanatized content for anything other than a live-binding |
|
6709 contentEscape = function(txt, tag) { |
|
6710 return (typeof txt === 'string' || typeof txt === 'number') ? |
|
6711 can.esc(txt) : |
|
6712 contentText(txt, tag); |
|
6713 }, |
|
6714 // A flag to indicate if .txt was called within a live section within an element like the {{name}} |
|
6715 // within `<div {{#person}}{{name}}{{/person}}/>`. |
|
6716 withinTemplatedSectionWithinAnElement = false, |
|
6717 emptyHandler = function() {}; |
|
6718 |
|
6719 var lastHookups; |
|
6720 |
|
6721 can.extend(can.view, { |
|
6722 live: live, |
|
6723 // called in text to make a temporary |
|
6724 // can.view.lists function that can be called with |
|
6725 // the list to iterate over and the template |
|
6726 // used to produce the content within the list |
|
6727 setupLists: function() { |
|
6728 |
|
6729 var old = can.view.lists, |
|
6730 data; |
|
6731 |
|
6732 can.view.lists = function(list, renderer) { |
|
6733 data = { |
|
6734 list: list, |
|
6735 renderer: renderer |
|
6736 }; |
|
6737 return Math.random(); |
|
6738 }; |
|
6739 // sets back to the old data |
|
6740 return function() { |
|
6741 can.view.lists = old; |
|
6742 return data; |
|
6743 }; |
|
6744 }, |
|
6745 getHooks: function() { |
|
6746 var hooks = pendingHookups.slice(0); |
|
6747 lastHookups = hooks; |
|
6748 pendingHookups = []; |
|
6749 return hooks; |
|
6750 }, |
|
6751 onlytxt: function(self, func) { |
|
6752 return contentEscape(func.call(self)); |
|
6753 }, |
|
6754 |
|
6755 txt: function(escape, tagName, status, self, func) { |
|
6756 // the temporary tag needed for any live setup |
|
6757 var tag = (elements.tagMap[tagName] || "span"), |
|
6758 // should live-binding be setup |
|
6759 setupLiveBinding = false, |
|
6760 // the compute's value |
|
6761 value, |
|
6762 listData, |
|
6763 compute, |
|
6764 unbind = emptyHandler, |
|
6765 attributeName; |
|
6766 |
|
6767 // Are we currently within a live section within an element like the {{name}} |
|
6768 // within `<div {{#person}}{{name}}{{/person}}/>`. |
|
6769 if (withinTemplatedSectionWithinAnElement) { |
|
6770 value = func.call(self); |
|
6771 } else { |
|
6772 |
|
6773 // If this magic tag is within an attribute or an html element, |
|
6774 // set the flag to true so we avoid trying to live bind |
|
6775 // anything that func might be setup. |
|
6776 // TODO: the scanner should be able to set this up. |
|
6777 if (typeof status === "string" || status === 1) { |
|
6778 withinTemplatedSectionWithinAnElement = true; |
|
6779 } |
|
6780 |
|
6781 // Sets up a listener so we know any can.view.lists called |
|
6782 // when func is called |
|
6783 var listTeardown = can.view.setupLists(); |
|
6784 unbind = function() { |
|
6785 compute.unbind("change", emptyHandler); |
|
6786 }; |
|
6787 // Create a compute that calls func and looks for dependencies. |
|
6788 // By passing `false`, this compute can not be a dependency of other |
|
6789 // computes. This is because live-bits are nested, but |
|
6790 // handle their own updating. For example: |
|
6791 // {{#if items.length}}{{#items}}{{.}}{{/items}}{{/if}} |
|
6792 // We do not want `{{#if items.length}}` changing the DOM if |
|
6793 // `{{#items}}` text changes. |
|
6794 compute = can.compute(func, self, false); |
|
6795 |
|
6796 // Bind to get and temporarily cache the value of the compute. |
|
6797 compute.bind("change", emptyHandler); |
|
6798 |
|
6799 // Call the "wrapping" function and get the binding information |
|
6800 listData = listTeardown(); |
|
6801 |
|
6802 // Get the value of the compute |
|
6803 value = compute(); |
|
6804 |
|
6805 // Let people know we are no longer within an element. |
|
6806 withinTemplatedSectionWithinAnElement = false; |
|
6807 |
|
6808 // If we should setup live-binding. |
|
6809 setupLiveBinding = compute.hasDependencies; |
|
6810 } |
|
6811 |
|
6812 if (listData) { |
|
6813 unbind(); |
|
6814 return "<" + tag + can.view.hook(function(el, parentNode) { |
|
6815 live.list(el, listData.list, listData.renderer, self, parentNode); |
|
6816 }) + "></" + tag + ">"; |
|
6817 } |
|
6818 |
|
6819 // If we had no observes just return the value returned by func. |
|
6820 if (!setupLiveBinding || typeof value === "function") { |
|
6821 unbind(); |
|
6822 return ((withinTemplatedSectionWithinAnElement || escape === 2 || !escape) ? |
|
6823 contentText : |
|
6824 contentEscape)(value, status === 0 && tag); |
|
6825 } |
|
6826 |
|
6827 // the property (instead of innerHTML elements) to adjust. For |
|
6828 // example options should use textContent |
|
6829 var contentProp = elements.tagToContentPropMap[tagName]; |
|
6830 |
|
6831 // The magic tag is outside or between tags. |
|
6832 if (status === 0 && !contentProp) { |
|
6833 // Return an element tag with a hookup in place of the content |
|
6834 return "<" + tag + can.view.hook( |
|
6835 // if value is an object, it's likely something returned by .safeString |
|
6836 escape && typeof value !== "object" ? |
|
6837 // If we are escaping, replace the parentNode with |
|
6838 // a text node who's value is `func`'s return value. |
|
6839 |
|
6840 function(el, parentNode) { |
|
6841 live.text(el, compute, parentNode); |
|
6842 unbind(); |
|
6843 } : |
|
6844 // If we are not escaping, replace the parentNode with a |
|
6845 // documentFragment created as with `func`'s return value. |
|
6846 |
|
6847 function(el, parentNode) { |
|
6848 live.html(el, compute, parentNode); |
|
6849 unbind(); |
|
6850 //children have to be properly nested HTML for buildFragment to work properly |
|
6851 }) + ">" + tagChildren(tag) + "</" + tag + ">"; |
|
6852 // In a tag, but not in an attribute |
|
6853 } else if (status === 1) { |
|
6854 // remember the old attr name |
|
6855 pendingHookups.push(function(el) { |
|
6856 live.attributes(el, compute, compute()); |
|
6857 unbind(); |
|
6858 }); |
|
6859 |
|
6860 return compute(); |
|
6861 } else if (escape === 2) { // In a special attribute like src or style |
|
6862 |
|
6863 attributeName = status; |
|
6864 pendingHookups.push(function(el) { |
|
6865 live.specialAttribute(el, attributeName, compute); |
|
6866 unbind(); |
|
6867 }); |
|
6868 return compute(); |
|
6869 } else { // In an attribute... |
|
6870 attributeName = status === 0 ? contentProp : status; |
|
6871 // if the magic tag is inside the element, like `<option><% TAG %></option>`, |
|
6872 // we add this hookup to the last element (ex: `option`'s) hookups. |
|
6873 // Otherwise, the magic tag is in an attribute, just add to the current element's |
|
6874 // hookups. |
|
6875 (status === 0 ? lastHookups : pendingHookups) |
|
6876 .push(function(el) { |
|
6877 live.attribute(el, attributeName, compute); |
|
6878 unbind(); |
|
6879 }); |
|
6880 return live.attributePlaceholder; |
|
6881 } |
|
6882 } |
|
6883 }); |
|
6884 |
|
6885 return can; |
|
6886 })(__m17, __m24, __m27, __m2); |
|
6887 |
|
6888 // ## can/view/ejs/ejs.js |
|
6889 var __m22 = (function(can) { |
|
6890 // ## Helper methods |
|
6891 var extend = can.extend, |
|
6892 EJS = function(options) { |
|
6893 // Supports calling EJS without the constructor. |
|
6894 // This returns a function that renders the template. |
|
6895 if (this.constructor !== EJS) { |
|
6896 var ejs = new EJS(options); |
|
6897 return function(data, helpers) { |
|
6898 return ejs.render(data, helpers); |
|
6899 }; |
|
6900 } |
|
6901 // If we get a `function` directly, it probably is coming from |
|
6902 // a `steal`-packaged view. |
|
6903 if (typeof options === 'function') { |
|
6904 this.template = { |
|
6905 fn: options |
|
6906 }; |
|
6907 return; |
|
6908 } |
|
6909 // Set options on self. |
|
6910 extend(this, options); |
|
6911 this.template = this.scanner.scan(this.text, this.name); |
|
6912 }; |
|
6913 // Expose EJS via the `can` object. |
|
6914 can.EJS = EJS; |
|
6915 |
|
6916 EJS.prototype. |
|
6917 // ## Render |
|
6918 // Render a view object with data and helpers. |
|
6919 render = function(object, extraHelpers) { |
|
6920 object = object || {}; |
|
6921 return this.template.fn.call(object, object, new EJS.Helpers(object, extraHelpers || {})); |
|
6922 }; |
|
6923 extend(EJS.prototype, { |
|
6924 // ## Scanner |
|
6925 // Singleton scanner instance for parsing templates. See [scanner.js](scanner.html) |
|
6926 // for more information. |
|
6927 // ### Text |
|
6928 // #### Definitions |
|
6929 // * `outStart` - Wrapper start text for view function. |
|
6930 // * `outEnd` - Wrapper end text for view function. |
|
6931 // * `argNames` - Arguments passed into view function. |
|
6932 scanner: new can.view.Scanner({ |
|
6933 text: { |
|
6934 outStart: 'with(_VIEW) { with (_CONTEXT) {', |
|
6935 outEnd: "}}", |
|
6936 argNames: '_CONTEXT,_VIEW', |
|
6937 context: "this" |
|
6938 }, |
|
6939 // ### Tokens |
|
6940 // An ordered token registry for the scanner. Scanner makes evaluations |
|
6941 // based on which tags are considered opening/closing as well as escaped, etc. |
|
6942 tokens: [ |
|
6943 ["templateLeft", "<%%"], |
|
6944 ["templateRight", "%>"], |
|
6945 ["returnLeft", "<%=="], |
|
6946 ["escapeLeft", "<%="], |
|
6947 ["commentLeft", "<%#"], |
|
6948 ["left", "<%"], |
|
6949 ["right", "%>"], |
|
6950 ["returnRight", "%>"] |
|
6951 ], |
|
6952 // ### Helpers |
|
6953 helpers: [{ |
|
6954 // Regex to see if its a func like `()->`. |
|
6955 name: /\s*\(([\$\w]+)\)\s*->([^\n]*)/, |
|
6956 // Evaluate rocket syntax function with correct context. |
|
6957 fn: function(content) { |
|
6958 var quickFunc = /\s*\(([\$\w]+)\)\s*->([^\n]*)/, |
|
6959 parts = content.match(quickFunc); |
|
6960 |
|
6961 return "can.proxy(function(__){var " + parts[1] + "=can.$(__);" + parts[2] + "}, this);"; |
|
6962 } |
|
6963 } |
|
6964 ], |
|
6965 // ### transform |
|
6966 // Transforms the EJS template to add support for shared blocks. |
|
6967 // Essentially, this breaks up EJS tags into multiple EJS tags |
|
6968 // if they contained unmatched brackets. |
|
6969 // For example, this doesn't work: |
|
6970 // `<% if (1) { %><% if (1) { %> hi <% } } %>` |
|
6971 // ...without isolated EJS blocks: |
|
6972 // `<% if (1) { %><% if (1) { %> hi <% } %><% } %>` |
|
6973 // The result of transforming: |
|
6974 // `<% if (1) { %><% %><% if (1) { %><% %> hi <% } %><% } %>` |
|
6975 transform: function(source) { |
|
6976 return source.replace(/<%([\s\S]+?)%>/gm, function(whole, part) { |
|
6977 var brackets = [], |
|
6978 foundBracketPair, i; |
|
6979 // Look for brackets (for removing self-contained blocks) |
|
6980 part.replace(/[{}]/gm, function(bracket, offset) { |
|
6981 brackets.push([ |
|
6982 bracket, |
|
6983 offset |
|
6984 ]); |
|
6985 }); |
|
6986 // Remove bracket pairs from the list of replacements |
|
6987 do { |
|
6988 foundBracketPair = false; |
|
6989 for (i = brackets.length - 2; i >= 0; i--) { |
|
6990 if (brackets[i][0] === '{' && brackets[i + 1][0] === '}') { |
|
6991 brackets.splice(i, 2); |
|
6992 foundBracketPair = true; |
|
6993 break; |
|
6994 } |
|
6995 } |
|
6996 } while (foundBracketPair); |
|
6997 // Unmatched brackets found, inject EJS tags |
|
6998 if (brackets.length >= 2) { |
|
6999 var result = ['<%'], |
|
7000 bracket, last = 0; |
|
7001 for (i = 0; bracket = brackets[i]; i++) { |
|
7002 result.push(part.substring(last, last = bracket[1])); |
|
7003 if (bracket[0] === '{' && i < brackets.length - 1 || bracket[0] === '}' && i > 0) { |
|
7004 result.push(bracket[0] === '{' ? '{ %><% ' : ' %><% }'); |
|
7005 } else { |
|
7006 result.push(bracket[0]); |
|
7007 } |
|
7008 ++last; |
|
7009 } |
|
7010 result.push(part.substring(last), '%>'); |
|
7011 return result.join(''); |
|
7012 } |
|
7013 // Otherwise return the original |
|
7014 else { |
|
7015 return '<%' + part + '%>'; |
|
7016 } |
|
7017 }); |
|
7018 } |
|
7019 }) |
|
7020 }); |
|
7021 |
|
7022 // ## Helpers |
|
7023 // In your EJS view you can then call the helper on an element tag: |
|
7024 // `<div <%= upperHtml('javascriptmvc') %>></div>` |
|
7025 EJS.Helpers = function(data, extras) { |
|
7026 this._data = data; |
|
7027 this._extras = extras; |
|
7028 extend(this, extras); |
|
7029 }; |
|
7030 |
|
7031 EJS.Helpers.prototype = { |
|
7032 // List allows for live binding a can.List easily within a template. |
|
7033 list: function(list, cb) { |
|
7034 can.each(list, function(item, i) { |
|
7035 cb(item, i, list); |
|
7036 }); |
|
7037 }, |
|
7038 // `each` iterates through a enumerated source(such as can.List or array) |
|
7039 // and sets up live binding when possible. |
|
7040 each: function(list, cb) { |
|
7041 // Normal arrays don't get live updated |
|
7042 if (can.isArray(list)) { |
|
7043 this.list(list, cb); |
|
7044 } else { |
|
7045 can.view.lists(list, cb); |
|
7046 } |
|
7047 } |
|
7048 }; |
|
7049 // Registers options for a `steal` build. |
|
7050 can.view.register({ |
|
7051 suffix: 'ejs', |
|
7052 script: function(id, src) { |
|
7053 return 'can.EJS(function(_CONTEXT,_VIEW) { ' + new EJS({ |
|
7054 text: src, |
|
7055 name: id |
|
7056 }) |
|
7057 .template.out + ' })'; |
|
7058 }, |
|
7059 renderer: function(id, text) { |
|
7060 return EJS({ |
|
7061 text: text, |
|
7062 name: id |
|
7063 }); |
|
7064 } |
|
7065 }); |
|
7066 can.ejs.Helpers = EJS.Helpers; |
|
7067 |
|
7068 |
|
7069 return can; |
|
7070 })(__m3, __m17, __m2, __m15, __m23, __m26); |
|
7071 |
|
7072 // ## can/util/object/object.js |
|
7073 var __m31 = (function(can) { |
|
7074 var isArray = can.isArray; |
|
7075 |
|
7076 can.Object = {}; |
|
7077 |
|
7078 var same = can.Object.same = function(a, b, compares, aParent, bParent, deep) { |
|
7079 var aType = typeof a, |
|
7080 aArray = isArray(a), |
|
7081 comparesType = typeof compares, |
|
7082 compare; |
|
7083 if (comparesType === 'string' || compares === null) { |
|
7084 compares = compareMethods[compares]; |
|
7085 comparesType = 'function'; |
|
7086 } |
|
7087 if (comparesType === 'function') { |
|
7088 return compares(a, b, aParent, bParent); |
|
7089 } |
|
7090 compares = compares || {}; |
|
7091 if (a === null || b === null) { |
|
7092 return a === b; |
|
7093 } |
|
7094 if (a instanceof Date || b instanceof Date) { |
|
7095 return a === b; |
|
7096 } |
|
7097 if (deep === -1) { |
|
7098 return aType === 'object' || a === b; |
|
7099 } |
|
7100 if (aType !== typeof b || aArray !== isArray(b)) { |
|
7101 return false; |
|
7102 } |
|
7103 if (a === b) { |
|
7104 return true; |
|
7105 } |
|
7106 if (aArray) { |
|
7107 if (a.length !== b.length) { |
|
7108 return false; |
|
7109 } |
|
7110 for (var i = 0; i < a.length; i++) { |
|
7111 compare = compares[i] === undefined ? compares['*'] : compares[i]; |
|
7112 if (!same(a[i], b[i], a, b, compare)) { |
|
7113 return false; |
|
7114 } |
|
7115 } |
|
7116 return true; |
|
7117 } else if (aType === 'object' || aType === 'function') { |
|
7118 var bCopy = can.extend({}, b); |
|
7119 for (var prop in a) { |
|
7120 compare = compares[prop] === undefined ? compares['*'] : compares[prop]; |
|
7121 if (!same(a[prop], b[prop], compare, a, b, deep === false ? -1 : undefined)) { |
|
7122 return false; |
|
7123 } |
|
7124 delete bCopy[prop]; |
|
7125 } |
|
7126 // go through bCopy props ... if there is no compare .. return false |
|
7127 for (prop in bCopy) { |
|
7128 if (compares[prop] === undefined || !same(undefined, b[prop], compares[prop], a, b, deep === false ? -1 : undefined)) { |
|
7129 return false; |
|
7130 } |
|
7131 } |
|
7132 return true; |
|
7133 } |
|
7134 return false; |
|
7135 }; |
|
7136 |
|
7137 can.Object.subsets = function(checkSet, sets, compares) { |
|
7138 var len = sets.length, |
|
7139 subsets = []; |
|
7140 for (var i = 0; i < len; i++) { |
|
7141 //check this subset |
|
7142 var set = sets[i]; |
|
7143 if (can.Object.subset(checkSet, set, compares)) { |
|
7144 subsets.push(set); |
|
7145 } |
|
7146 } |
|
7147 return subsets; |
|
7148 }; |
|
7149 |
|
7150 can.Object.subset = function(subset, set, compares) { |
|
7151 // go through set {type: 'folder'} and make sure every property |
|
7152 // is in subset {type: 'folder', parentId :5} |
|
7153 // then make sure that set has fewer properties |
|
7154 // make sure we are only checking 'important' properties |
|
7155 // in subset (ones that have to have a value) |
|
7156 compares = compares || {}; |
|
7157 for (var prop in set) { |
|
7158 if (!same(subset[prop], set[prop], compares[prop], subset, set)) { |
|
7159 return false; |
|
7160 } |
|
7161 } |
|
7162 return true; |
|
7163 }; |
|
7164 var compareMethods = { |
|
7165 'null': function() { |
|
7166 return true; |
|
7167 }, |
|
7168 i: function(a, b) { |
|
7169 return ('' + a) |
|
7170 .toLowerCase() === ('' + b) |
|
7171 .toLowerCase(); |
|
7172 }, |
|
7173 eq: function(a, b) { |
|
7174 return a === b; |
|
7175 }, |
|
7176 similar: function(a, b) { |
|
7177 |
|
7178 return a == b; |
|
7179 } |
|
7180 }; |
|
7181 compareMethods.eqeq = compareMethods.similar; |
|
7182 return can.Object; |
|
7183 })(__m3); |
|
7184 |
|
7185 // ## can/map/backup/backup.js |
|
7186 var __m30 = (function(can) { |
|
7187 var flatProps = function(a, cur) { |
|
7188 var obj = {}; |
|
7189 for (var prop in a) { |
|
7190 if (typeof a[prop] !== 'object' || a[prop] === null || a[prop] instanceof Date) { |
|
7191 obj[prop] = a[prop]; |
|
7192 } else { |
|
7193 obj[prop] = cur.attr(prop); |
|
7194 } |
|
7195 } |
|
7196 return obj; |
|
7197 }; |
|
7198 can.extend(can.Map.prototype, { |
|
7199 |
|
7200 backup: function() { |
|
7201 this._backupStore = this._attrs(); |
|
7202 return this; |
|
7203 }, |
|
7204 isDirty: function(checkAssociations) { |
|
7205 return this._backupStore && !can.Object.same(this._attrs(), this._backupStore, undefined, undefined, undefined, !! checkAssociations); |
|
7206 }, |
|
7207 restore: function(restoreAssociations) { |
|
7208 var props = restoreAssociations ? this._backupStore : flatProps(this._backupStore, this); |
|
7209 if (this.isDirty(restoreAssociations)) { |
|
7210 this._attrs(props, true); |
|
7211 } |
|
7212 return this; |
|
7213 } |
|
7214 }); |
|
7215 return can.Map; |
|
7216 })(__m3, __m10, __m31); |
|
7217 |
|
7218 window['can'] = __m5; |
|
7219 })(); |