PageRenderTime 123ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 1ms

/ajax/libs/thorax/2.0.0rc2/thorax-mobile.js

https://bitbucket.org/kolbyjAFK/cdnjs
JavaScript | 8860 lines | 8187 code | 338 blank | 335 comment | 749 complexity | 2215d695577350a58a4f5850ca8d34dc MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. /* Zepto v1.0rc1 - polyfill zepto event detect fx ajax form touch - zeptojs.com/license */
  2. ;(function(undefined){
  3. if (String.prototype.trim === undefined) // fix for iOS 3.2
  4. String.prototype.trim = function(){ return this.replace(/^\s+/, '').replace(/\s+$/, '') }
  5. // For iOS 3.x
  6. // from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/reduce
  7. if (Array.prototype.reduce === undefined)
  8. Array.prototype.reduce = function(fun){
  9. if(this === void 0 || this === null) throw new TypeError()
  10. var t = Object(this), len = t.length >>> 0, k = 0, accumulator
  11. if(typeof fun != 'function') throw new TypeError()
  12. if(len == 0 && arguments.length == 1) throw new TypeError()
  13. if(arguments.length >= 2)
  14. accumulator = arguments[1]
  15. else
  16. do{
  17. if(k in t){
  18. accumulator = t[k++]
  19. break
  20. }
  21. if(++k >= len) throw new TypeError()
  22. } while (true)
  23. while (k < len){
  24. if(k in t) accumulator = fun.call(undefined, accumulator, t[k], k, t)
  25. k++
  26. }
  27. return accumulator
  28. }
  29. })()
  30. var Zepto = (function() {
  31. var undefined, key, $, classList, emptyArray = [], slice = emptyArray.slice,
  32. document = window.document,
  33. elementDisplay = {}, classCache = {},
  34. getComputedStyle = document.defaultView.getComputedStyle,
  35. cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 },
  36. fragmentRE = /^\s*<(\w+|!)[^>]*>/,
  37. // Used by `$.zepto.init` to wrap elements, text/comment nodes, document,
  38. // and document fragment node types.
  39. elementTypes = [1, 3, 8, 9, 11],
  40. adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ],
  41. table = document.createElement('table'),
  42. tableRow = document.createElement('tr'),
  43. containers = {
  44. 'tr': document.createElement('tbody'),
  45. 'tbody': table, 'thead': table, 'tfoot': table,
  46. 'td': tableRow, 'th': tableRow,
  47. '*': document.createElement('div')
  48. },
  49. readyRE = /complete|loaded|interactive/,
  50. classSelectorRE = /^\.([\w-]+)$/,
  51. idSelectorRE = /^#([\w-]+)$/,
  52. tagSelectorRE = /^[\w-]+$/,
  53. toString = ({}).toString,
  54. zepto = {},
  55. camelize, uniq,
  56. tempParent = document.createElement('div')
  57. zepto.matches = function(element, selector) {
  58. if (!element || element.nodeType !== 1) return false
  59. var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector ||
  60. element.oMatchesSelector || element.matchesSelector
  61. if (matchesSelector) return matchesSelector.call(element, selector)
  62. // fall back to performing a selector:
  63. var match, parent = element.parentNode, temp = !parent
  64. if (temp) (parent = tempParent).appendChild(element)
  65. match = ~zepto.qsa(parent, selector).indexOf(element)
  66. temp && tempParent.removeChild(element)
  67. return match
  68. }
  69. function isFunction(value) { return toString.call(value) == "[object Function]" }
  70. function isObject(value) { return value instanceof Object }
  71. function isPlainObject(value) {
  72. var key, ctor
  73. if (toString.call(value) !== "[object Object]") return false
  74. ctor = (isFunction(value.constructor) && value.constructor.prototype)
  75. if (!ctor || !hasOwnProperty.call(ctor, 'isPrototypeOf')) return false
  76. for (key in value);
  77. return key === undefined || hasOwnProperty.call(value, key)
  78. }
  79. function isArray(value) { return value instanceof Array }
  80. function likeArray(obj) { return typeof obj.length == 'number' }
  81. function compact(array) { return array.filter(function(item){ return item !== undefined && item !== null }) }
  82. function flatten(array) { return array.length > 0 ? [].concat.apply([], array) : array }
  83. camelize = function(str){ return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) }
  84. function dasherize(str) {
  85. return str.replace(/::/g, '/')
  86. .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
  87. .replace(/([a-z\d])([A-Z])/g, '$1_$2')
  88. .replace(/_/g, '-')
  89. .toLowerCase()
  90. }
  91. uniq = function(array){ return array.filter(function(item, idx){ return array.indexOf(item) == idx }) }
  92. function classRE(name) {
  93. return name in classCache ?
  94. classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
  95. }
  96. function maybeAddPx(name, value) {
  97. return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
  98. }
  99. function defaultDisplay(nodeName) {
  100. var element, display
  101. if (!elementDisplay[nodeName]) {
  102. element = document.createElement(nodeName)
  103. document.body.appendChild(element)
  104. display = getComputedStyle(element, '').getPropertyValue("display")
  105. element.parentNode.removeChild(element)
  106. display == "none" && (display = "block")
  107. elementDisplay[nodeName] = display
  108. }
  109. return elementDisplay[nodeName]
  110. }
  111. // `$.zepto.fragment` takes a html string and an optional tag name
  112. // to generate DOM nodes nodes from the given html string.
  113. // The generated DOM nodes are returned as an array.
  114. // This function can be overriden in plugins for example to make
  115. // it compatible with browsers that don't support the DOM fully.
  116. zepto.fragment = function(html, name) {
  117. if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
  118. if (!(name in containers)) name = '*'
  119. var container = containers[name]
  120. container.innerHTML = '' + html
  121. return $.each(slice.call(container.childNodes), function(){
  122. container.removeChild(this)
  123. })
  124. }
  125. // `$.zepto.Z` swaps out the prototype of the given `dom` array
  126. // of nodes with `$.fn` and thus supplying all the Zepto functions
  127. // to the array. Note that `__proto__` is not supported on Internet
  128. // Explorer. This method can be overriden in plugins.
  129. zepto.Z = function(dom, selector) {
  130. dom = dom || []
  131. dom.__proto__ = arguments.callee.prototype
  132. dom.selector = selector || ''
  133. return dom
  134. }
  135. // `$.zepto.isZ` should return `true` if the given object is a Zepto
  136. // collection. This method can be overriden in plugins.
  137. zepto.isZ = function(object) {
  138. return object instanceof zepto.Z
  139. }
  140. // `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and
  141. // takes a CSS selector and an optional context (and handles various
  142. // special cases).
  143. // This method can be overriden in plugins.
  144. zepto.init = function(selector, context) {
  145. // If nothing given, return an empty Zepto collection
  146. if (!selector) return zepto.Z()
  147. // If a function is given, call it when the DOM is ready
  148. else if (isFunction(selector)) return $(document).ready(selector)
  149. // If a Zepto collection is given, juts return it
  150. else if (zepto.isZ(selector)) return selector
  151. else {
  152. var dom
  153. // normalize array if an array of nodes is given
  154. if (isArray(selector)) dom = compact(selector)
  155. // if a JavaScript object is given, return a copy of it
  156. // this is a somewhat peculiar option, but supported by
  157. // jQuery so we'll do it, too
  158. else if (isPlainObject(selector))
  159. dom = [$.extend({}, selector)], selector = null
  160. // wrap stuff like `document` or `window`
  161. else if (elementTypes.indexOf(selector.nodeType) >= 0 || selector === window)
  162. dom = [selector], selector = null
  163. // If it's a html fragment, create nodes from it
  164. else if (fragmentRE.test(selector))
  165. dom = zepto.fragment(selector.trim(), RegExp.$1), selector = null
  166. // If there's a context, create a collection on that context first, and select
  167. // nodes from there
  168. else if (context !== undefined) return $(context).find(selector)
  169. // And last but no least, if it's a CSS selector, use it to select nodes.
  170. else dom = zepto.qsa(document, selector)
  171. // create a new Zepto collection from the nodes found
  172. return zepto.Z(dom, selector)
  173. }
  174. }
  175. // `$` will be the base `Zepto` object. When calling this
  176. // function just call `$.zepto.init, whichs makes the implementation
  177. // details of selecting nodes and creating Zepto collections
  178. // patchable in plugins.
  179. $ = function(selector, context){
  180. return zepto.init(selector, context)
  181. }
  182. // Copy all but undefined properties from one or more
  183. // objects to the `target` object.
  184. $.extend = function(target){
  185. slice.call(arguments, 1).forEach(function(source) {
  186. for (key in source)
  187. if (source[key] !== undefined)
  188. target[key] = source[key]
  189. })
  190. return target
  191. }
  192. // `$.zepto.qsa` is Zepto's CSS selector implementation which
  193. // uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
  194. // This method can be overriden in plugins.
  195. zepto.qsa = function(element, selector){
  196. var found
  197. return (element === document && idSelectorRE.test(selector)) ?
  198. ( (found = element.getElementById(RegExp.$1)) ? [found] : emptyArray ) :
  199. (element.nodeType !== 1 && element.nodeType !== 9) ? emptyArray :
  200. slice.call(
  201. classSelectorRE.test(selector) ? element.getElementsByClassName(RegExp.$1) :
  202. tagSelectorRE.test(selector) ? element.getElementsByTagName(selector) :
  203. element.querySelectorAll(selector)
  204. )
  205. }
  206. function filtered(nodes, selector) {
  207. return selector === undefined ? $(nodes) : $(nodes).filter(selector)
  208. }
  209. function funcArg(context, arg, idx, payload) {
  210. return isFunction(arg) ? arg.call(context, idx, payload) : arg
  211. }
  212. $.isFunction = isFunction
  213. $.isObject = isObject
  214. $.isArray = isArray
  215. $.isPlainObject = isPlainObject
  216. $.inArray = function(elem, array, i){
  217. return emptyArray.indexOf.call(array, elem, i)
  218. }
  219. $.trim = function(str) { return str.trim() }
  220. // plugin compatibility
  221. $.uuid = 0
  222. $.map = function(elements, callback){
  223. var value, values = [], i, key
  224. if (likeArray(elements))
  225. for (i = 0; i < elements.length; i++) {
  226. value = callback(elements[i], i)
  227. if (value != null) values.push(value)
  228. }
  229. else
  230. for (key in elements) {
  231. value = callback(elements[key], key)
  232. if (value != null) values.push(value)
  233. }
  234. return flatten(values)
  235. }
  236. $.each = function(elements, callback){
  237. var i, key
  238. if (likeArray(elements)) {
  239. for (i = 0; i < elements.length; i++)
  240. if (callback.call(elements[i], i, elements[i]) === false) return elements
  241. } else {
  242. for (key in elements)
  243. if (callback.call(elements[key], key, elements[key]) === false) return elements
  244. }
  245. return elements
  246. }
  247. // Define methods that will be available on all
  248. // Zepto collections
  249. $.fn = {
  250. // Because a collection acts like an array
  251. // copy over these useful array functions.
  252. forEach: emptyArray.forEach,
  253. reduce: emptyArray.reduce,
  254. push: emptyArray.push,
  255. indexOf: emptyArray.indexOf,
  256. concat: emptyArray.concat,
  257. // `map` and `slice` in the jQuery API work differently
  258. // from their array counterparts
  259. map: function(fn){
  260. return $.map(this, function(el, i){ return fn.call(el, i, el) })
  261. },
  262. slice: function(){
  263. return $(slice.apply(this, arguments))
  264. },
  265. ready: function(callback){
  266. if (readyRE.test(document.readyState)) callback($)
  267. else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false)
  268. return this
  269. },
  270. get: function(idx){
  271. return idx === undefined ? slice.call(this) : this[idx]
  272. },
  273. toArray: function(){ return this.get() },
  274. size: function(){
  275. return this.length
  276. },
  277. remove: function(){
  278. return this.each(function(){
  279. if (this.parentNode != null)
  280. this.parentNode.removeChild(this)
  281. })
  282. },
  283. each: function(callback){
  284. this.forEach(function(el, idx){ callback.call(el, idx, el) })
  285. return this
  286. },
  287. filter: function(selector){
  288. return $([].filter.call(this, function(element){
  289. return zepto.matches(element, selector)
  290. }))
  291. },
  292. add: function(selector,context){
  293. return $(uniq(this.concat($(selector,context))))
  294. },
  295. is: function(selector){
  296. return this.length > 0 && zepto.matches(this[0], selector)
  297. },
  298. not: function(selector){
  299. var nodes=[]
  300. if (isFunction(selector) && selector.call !== undefined)
  301. this.each(function(idx){
  302. if (!selector.call(this,idx)) nodes.push(this)
  303. })
  304. else {
  305. var excludes = typeof selector == 'string' ? this.filter(selector) :
  306. (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
  307. this.forEach(function(el){
  308. if (excludes.indexOf(el) < 0) nodes.push(el)
  309. })
  310. }
  311. return $(nodes)
  312. },
  313. eq: function(idx){
  314. return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1)
  315. },
  316. first: function(){
  317. var el = this[0]
  318. return el && !isObject(el) ? el : $(el)
  319. },
  320. last: function(){
  321. var el = this[this.length - 1]
  322. return el && !isObject(el) ? el : $(el)
  323. },
  324. find: function(selector){
  325. var result
  326. if (this.length == 1) result = zepto.qsa(this[0], selector)
  327. else result = this.map(function(){ return zepto.qsa(this, selector) })
  328. return $(result)
  329. },
  330. closest: function(selector, context){
  331. var node = this[0]
  332. while (node && !zepto.matches(node, selector))
  333. node = node !== context && node !== document && node.parentNode
  334. return $(node)
  335. },
  336. parents: function(selector){
  337. var ancestors = [], nodes = this
  338. while (nodes.length > 0)
  339. nodes = $.map(nodes, function(node){
  340. if ((node = node.parentNode) && node !== document && ancestors.indexOf(node) < 0) {
  341. ancestors.push(node)
  342. return node
  343. }
  344. })
  345. return filtered(ancestors, selector)
  346. },
  347. parent: function(selector){
  348. return filtered(uniq(this.pluck('parentNode')), selector)
  349. },
  350. children: function(selector){
  351. return filtered(this.map(function(){ return slice.call(this.children) }), selector)
  352. },
  353. siblings: function(selector){
  354. return filtered(this.map(function(i, el){
  355. return slice.call(el.parentNode.children).filter(function(child){ return child!==el })
  356. }), selector)
  357. },
  358. empty: function(){
  359. return this.each(function(){ this.innerHTML = '' })
  360. },
  361. // `pluck` is borrowed from Prototype.js
  362. pluck: function(property){
  363. return this.map(function(){ return this[property] })
  364. },
  365. show: function(){
  366. return this.each(function(){
  367. this.style.display == "none" && (this.style.display = null)
  368. if (getComputedStyle(this, '').getPropertyValue("display") == "none")
  369. this.style.display = defaultDisplay(this.nodeName)
  370. })
  371. },
  372. replaceWith: function(newContent){
  373. return this.before(newContent).remove()
  374. },
  375. wrap: function(newContent){
  376. return this.each(function(){
  377. $(this).wrapAll($(newContent)[0].cloneNode(false))
  378. })
  379. },
  380. wrapAll: function(newContent){
  381. if (this[0]) {
  382. $(this[0]).before(newContent = $(newContent))
  383. newContent.append(this)
  384. }
  385. return this
  386. },
  387. unwrap: function(){
  388. this.parent().each(function(){
  389. $(this).replaceWith($(this).children())
  390. })
  391. return this
  392. },
  393. clone: function(){
  394. return $(this.map(function(){ return this.cloneNode(true) }))
  395. },
  396. hide: function(){
  397. return this.css("display", "none")
  398. },
  399. toggle: function(setting){
  400. return (setting === undefined ? this.css("display") == "none" : setting) ? this.show() : this.hide()
  401. },
  402. prev: function(){ return $(this.pluck('previousElementSibling')) },
  403. next: function(){ return $(this.pluck('nextElementSibling')) },
  404. html: function(html){
  405. return html === undefined ?
  406. (this.length > 0 ? this[0].innerHTML : null) :
  407. this.each(function(idx){
  408. var originHtml = this.innerHTML
  409. $(this).empty().append( funcArg(this, html, idx, originHtml) )
  410. })
  411. },
  412. text: function(text){
  413. return text === undefined ?
  414. (this.length > 0 ? this[0].textContent : null) :
  415. this.each(function(){ this.textContent = text })
  416. },
  417. attr: function(name, value){
  418. var result
  419. return (typeof name == 'string' && value === undefined) ?
  420. (this.length == 0 || this[0].nodeType !== 1 ? undefined :
  421. (name == 'value' && this[0].nodeName == 'INPUT') ? this.val() :
  422. (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result
  423. ) :
  424. this.each(function(idx){
  425. if (this.nodeType !== 1) return
  426. if (isObject(name)) for (key in name) this.setAttribute(key, name[key])
  427. else this.setAttribute(name, funcArg(this, value, idx, this.getAttribute(name)))
  428. })
  429. },
  430. removeAttr: function(name){
  431. return this.each(function(){ if (this.nodeType === 1) this.removeAttribute(name) })
  432. },
  433. prop: function(name, value){
  434. return (value === undefined) ?
  435. (this[0] ? this[0][name] : undefined) :
  436. this.each(function(idx){
  437. this[name] = funcArg(this, value, idx, this[name])
  438. })
  439. },
  440. data: function(name, value){
  441. var data = this.attr('data-' + dasherize(name), value)
  442. return data !== null ? data : undefined
  443. },
  444. val: function(value){
  445. return (value === undefined) ?
  446. (this.length > 0 ? this[0].value : undefined) :
  447. this.each(function(idx){
  448. this.value = funcArg(this, value, idx, this.value)
  449. })
  450. },
  451. offset: function(){
  452. if (this.length==0) return null
  453. var obj = this[0].getBoundingClientRect()
  454. return {
  455. left: obj.left + window.pageXOffset,
  456. top: obj.top + window.pageYOffset,
  457. width: obj.width,
  458. height: obj.height
  459. }
  460. },
  461. css: function(property, value){
  462. if (value === undefined && typeof property == 'string')
  463. return (
  464. this.length == 0
  465. ? undefined
  466. : this[0].style[camelize(property)] || getComputedStyle(this[0], '').getPropertyValue(property))
  467. var css = ''
  468. for (key in property)
  469. if(typeof property[key] == 'string' && property[key] == '')
  470. this.each(function(){ this.style.removeProperty(dasherize(key)) })
  471. else
  472. css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'
  473. if (typeof property == 'string')
  474. if (value == '')
  475. this.each(function(){ this.style.removeProperty(dasherize(property)) })
  476. else
  477. css = dasherize(property) + ":" + maybeAddPx(property, value)
  478. return this.each(function(){ this.style.cssText += ';' + css })
  479. },
  480. index: function(element){
  481. return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
  482. },
  483. hasClass: function(name){
  484. if (this.length < 1) return false
  485. else return classRE(name).test(this[0].className)
  486. },
  487. addClass: function(name){
  488. return this.each(function(idx){
  489. classList = []
  490. var cls = this.className, newName = funcArg(this, name, idx, cls)
  491. newName.split(/\s+/g).forEach(function(klass){
  492. if (!$(this).hasClass(klass)) classList.push(klass)
  493. }, this)
  494. classList.length && (this.className += (cls ? " " : "") + classList.join(" "))
  495. })
  496. },
  497. removeClass: function(name){
  498. return this.each(function(idx){
  499. if (name === undefined)
  500. return this.className = ''
  501. classList = this.className
  502. funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass){
  503. classList = classList.replace(classRE(klass), " ")
  504. })
  505. this.className = classList.trim()
  506. })
  507. },
  508. toggleClass: function(name, when){
  509. return this.each(function(idx){
  510. var newName = funcArg(this, name, idx, this.className)
  511. ;(when === undefined ? !$(this).hasClass(newName) : when) ?
  512. $(this).addClass(newName) : $(this).removeClass(newName)
  513. })
  514. }
  515. }
  516. // Generate the `width` and `height` functions
  517. ;['width', 'height'].forEach(function(dimension){
  518. $.fn[dimension] = function(value){
  519. var offset, Dimension = dimension.replace(/./, function(m){ return m[0].toUpperCase() })
  520. if (value === undefined) return this[0] == window ? window['inner' + Dimension] :
  521. this[0] == document ? document.documentElement['offset' + Dimension] :
  522. (offset = this.offset()) && offset[dimension]
  523. else return this.each(function(idx){
  524. var el = $(this)
  525. el.css(dimension, funcArg(this, value, idx, el[dimension]()))
  526. })
  527. }
  528. })
  529. function insert(operator, target, node) {
  530. var parent = (operator % 2) ? target : target.parentNode
  531. parent ? parent.insertBefore(node,
  532. !operator ? target.nextSibling : // after
  533. operator == 1 ? parent.firstChild : // prepend
  534. operator == 2 ? target : // before
  535. null) : // append
  536. $(node).remove()
  537. }
  538. function traverseNode(node, fun) {
  539. fun(node)
  540. for (var key in node.childNodes) traverseNode(node.childNodes[key], fun)
  541. }
  542. // Generate the `after`, `prepend`, `before`, `append`,
  543. // `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods.
  544. adjacencyOperators.forEach(function(key, operator) {
  545. $.fn[key] = function(){
  546. // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
  547. var nodes = $.map(arguments, function(n){ return isObject(n) ? n : zepto.fragment(n) })
  548. if (nodes.length < 1) return this
  549. var size = this.length, copyByClone = size > 1, inReverse = operator < 2
  550. return this.each(function(index, target){
  551. for (var i = 0; i < nodes.length; i++) {
  552. var node = nodes[inReverse ? nodes.length-i-1 : i]
  553. traverseNode(node, function(node){
  554. if (node.nodeName != null && node.nodeName.toUpperCase() === 'SCRIPT' && (!node.type || node.type === 'text/javascript'))
  555. window['eval'].call(window, node.innerHTML)
  556. })
  557. if (copyByClone && index < size - 1) node = node.cloneNode(true)
  558. insert(operator, target, node)
  559. }
  560. })
  561. }
  562. $.fn[(operator % 2) ? key+'To' : 'insert'+(operator ? 'Before' : 'After')] = function(html){
  563. $(html)[key](this)
  564. return this
  565. }
  566. })
  567. zepto.Z.prototype = $.fn
  568. // Export internal API functions in the `$.zepto` namespace
  569. zepto.camelize = camelize
  570. zepto.uniq = uniq
  571. $.zepto = zepto
  572. return $
  573. })()
  574. // If `$` is not yet defined, point it to `Zepto`
  575. window.Zepto = Zepto
  576. '$' in window || (window.$ = Zepto)
  577. ;(function($){
  578. var $$ = $.zepto.qsa, handlers = {}, _zid = 1, specialEvents={}
  579. specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'
  580. function zid(element) {
  581. return element._zid || (element._zid = _zid++)
  582. }
  583. function findHandlers(element, event, fn, selector) {
  584. event = parse(event)
  585. if (event.ns) var matcher = matcherFor(event.ns)
  586. return (handlers[zid(element)] || []).filter(function(handler) {
  587. return handler
  588. && (!event.e || handler.e == event.e)
  589. && (!event.ns || matcher.test(handler.ns))
  590. && (!fn || zid(handler.fn) === zid(fn))
  591. && (!selector || handler.sel == selector)
  592. })
  593. }
  594. function parse(event) {
  595. var parts = ('' + event).split('.')
  596. return {e: parts[0], ns: parts.slice(1).sort().join(' ')}
  597. }
  598. function matcherFor(ns) {
  599. return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
  600. }
  601. function eachEvent(events, fn, iterator){
  602. if ($.isObject(events)) $.each(events, iterator)
  603. else events.split(/\s/).forEach(function(type){ iterator(type, fn) })
  604. }
  605. // WARN fix namespaced focus & blur events delegation. Issues #552 & #597 patched from the upstream changes:
  606. // https://github.com/madrobby/zepto/commit/bbb5ca17d7c1874cff2a9a7c3ea94a8c8d79a791
  607. // https://github.com/madrobby/zepto/commit/74bd6f531c5ba154be48e48ff223929a0f57c2fa
  608. function eventCapture(handler, captureSetting) {
  609. return handler.del &&
  610. (handler.e == 'focus' || handler.e == 'blur') ||
  611. !!captureSetting
  612. }
  613. function add(element, events, fn, selector, getDelegate, capture){
  614. var id = zid(element), set = (handlers[id] || (handlers[id] = []))
  615. eachEvent(events, fn, function(event, fn){
  616. var delegate = getDelegate && getDelegate(fn, event),
  617. callback = delegate || fn
  618. var proxyfn = function (event) {
  619. var result = callback.apply(element, [event].concat(event.data))
  620. if (result === false) event.preventDefault()
  621. return result
  622. }
  623. var handler = $.extend(parse(event), {fn: fn, proxy: proxyfn, sel: selector, del: delegate, i: set.length})
  624. set.push(handler)
  625. element.addEventListener(handler.e, proxyfn, eventCapture(handler, capture))
  626. })
  627. }
  628. function remove(element, events, fn, selector, capture){
  629. var id = zid(element)
  630. eachEvent(events || '', fn, function(event, fn){
  631. findHandlers(element, event, fn, selector).forEach(function(handler){
  632. delete handlers[id][handler.i]
  633. element.removeEventListener(handler.e, handler.proxy, eventCapture(handler, capture))
  634. })
  635. })
  636. }
  637. $.event = { add: add, remove: remove }
  638. $.proxy = function(fn, context) {
  639. if ($.isFunction(fn)) {
  640. var proxyFn = function(){ return fn.apply(context, arguments) }
  641. proxyFn._zid = zid(fn)
  642. return proxyFn
  643. } else if (typeof context == 'string') {
  644. return $.proxy(fn[context], fn)
  645. } else {
  646. throw new TypeError("expected function")
  647. }
  648. }
  649. $.fn.bind = function(event, callback){
  650. return this.each(function(){
  651. add(this, event, callback)
  652. })
  653. }
  654. $.fn.unbind = function(event, callback){
  655. return this.each(function(){
  656. remove(this, event, callback)
  657. })
  658. }
  659. $.fn.one = function(event, callback){
  660. return this.each(function(i, element){
  661. add(this, event, callback, null, function(fn, type){
  662. return function(){
  663. var result = fn.apply(element, arguments)
  664. remove(element, type, fn)
  665. return result
  666. }
  667. })
  668. })
  669. }
  670. var returnTrue = function(){return true},
  671. returnFalse = function(){return false},
  672. eventMethods = {
  673. preventDefault: 'isDefaultPrevented',
  674. stopImmediatePropagation: 'isImmediatePropagationStopped',
  675. stopPropagation: 'isPropagationStopped'
  676. }
  677. function createProxy(event) {
  678. var proxy = $.extend({originalEvent: event}, event)
  679. $.each(eventMethods, function(name, predicate) {
  680. proxy[name] = function(){
  681. this[predicate] = returnTrue
  682. return event[name].apply(event, arguments)
  683. }
  684. proxy[predicate] = returnFalse
  685. })
  686. return proxy
  687. }
  688. // emulates the 'defaultPrevented' property for browsers that have none
  689. function fix(event) {
  690. if (!('defaultPrevented' in event)) {
  691. event.defaultPrevented = false
  692. var prevent = event.preventDefault
  693. event.preventDefault = function() {
  694. this.defaultPrevented = true
  695. prevent.call(this)
  696. }
  697. }
  698. }
  699. $.fn.delegate = function(selector, event, callback){
  700. return this.each(function(i, element){
  701. add(element, event, callback, selector, function(fn){
  702. return function(e){
  703. var evt, match = $(e.target).closest(selector, element).get(0)
  704. if (match) {
  705. evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element})
  706. return fn.apply(match, [evt].concat([].slice.call(arguments, 1)))
  707. }
  708. }
  709. })
  710. })
  711. }
  712. $.fn.undelegate = function(selector, event, callback){
  713. return this.each(function(){
  714. remove(this, event, callback, selector)
  715. })
  716. }
  717. $.fn.live = function(event, callback){
  718. $(document.body).delegate(this.selector, event, callback)
  719. return this
  720. }
  721. $.fn.die = function(event, callback){
  722. $(document.body).undelegate(this.selector, event, callback)
  723. return this
  724. }
  725. $.fn.on = function(event, selector, callback){
  726. return selector == undefined || $.isFunction(selector) ?
  727. this.bind(event, selector) : this.delegate(selector, event, callback)
  728. }
  729. $.fn.off = function(event, selector, callback){
  730. return selector == undefined || $.isFunction(selector) ?
  731. this.unbind(event, selector) : this.undelegate(selector, event, callback)
  732. }
  733. $.fn.trigger = function(event, data){
  734. if (typeof event == 'string') event = $.Event(event)
  735. fix(event)
  736. event.data = data
  737. return this.each(function(){
  738. // items in the collection might not be DOM elements
  739. // (todo: possibly support events on plain old objects)
  740. if('dispatchEvent' in this) this.dispatchEvent(event)
  741. })
  742. }
  743. // triggers event handlers on current element just as if an event occurred,
  744. // doesn't trigger an actual event, doesn't bubble
  745. $.fn.triggerHandler = function(event, data){
  746. var e, result
  747. this.each(function(i, element){
  748. e = createProxy(typeof event == 'string' ? $.Event(event) : event)
  749. e.data = data
  750. e.target = element
  751. $.each(findHandlers(element, event.type || event), function(i, handler){
  752. result = handler.proxy(e)
  753. if (e.isImmediatePropagationStopped()) return false
  754. })
  755. })
  756. return result
  757. }
  758. // shortcut methods for `.bind(event, fn)` for each event type
  759. ;('focusin focusout load resize scroll unload click dblclick '+
  760. 'mousedown mouseup mousemove mouseover mouseout '+
  761. 'change select keydown keypress keyup error').split(' ').forEach(function(event) {
  762. $.fn[event] = function(callback){ return this.bind(event, callback) }
  763. })
  764. ;['focus', 'blur'].forEach(function(name) {
  765. $.fn[name] = function(callback) {
  766. if (callback) this.bind(name, callback)
  767. else if (this.length) try { this.get(0)[name]() } catch(e){}
  768. return this
  769. }
  770. })
  771. $.Event = function(type, props) {
  772. var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true
  773. if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name])
  774. event.initEvent(type, bubbles, true, null, null, null, null, null, null, null, null, null, null, null, null)
  775. return event
  776. }
  777. })(Zepto)
  778. ;(function($){
  779. function detect(ua){
  780. var os = this.os = {}, browser = this.browser = {},
  781. webkit = ua.match(/WebKit\/([\d.]+)/),
  782. android = ua.match(/(Android)\s+([\d.]+)/),
  783. ipad = ua.match(/(iPad).*OS\s([\d_]+)/),
  784. iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/),
  785. webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),
  786. touchpad = webos && ua.match(/TouchPad/),
  787. kindle = ua.match(/Kindle\/([\d.]+)/),
  788. silk = ua.match(/Silk\/([\d._]+)/),
  789. blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/)
  790. // todo clean this up with a better OS/browser
  791. // separation. we need to discern between multiple
  792. // browsers on android, and decide if kindle fire in
  793. // silk mode is android or not
  794. if (browser.webkit = !!webkit) browser.version = webkit[1]
  795. if (android) os.android = true, os.version = android[2]
  796. if (iphone) os.ios = os.iphone = true, os.version = iphone[2].replace(/_/g, '.')
  797. if (ipad) os.ios = os.ipad = true, os.version = ipad[2].replace(/_/g, '.')
  798. if (webos) os.webos = true, os.version = webos[2]
  799. if (touchpad) os.touchpad = true
  800. if (blackberry) os.blackberry = true, os.version = blackberry[2]
  801. if (kindle) os.kindle = true, os.version = kindle[1]
  802. if (silk) browser.silk = true, browser.version = silk[1]
  803. if (!silk && os.android && ua.match(/Kindle Fire/)) browser.silk = true
  804. }
  805. detect.call($, navigator.userAgent)
  806. // make available to unit tests
  807. $.__detect = detect
  808. })(Zepto)
  809. ;(function($, undefined){
  810. var prefix = '', eventPrefix, endEventName, endAnimationName,
  811. vendors = { Webkit: 'webkit', Moz: '', O: 'o', ms: 'MS' },
  812. document = window.document, testEl = document.createElement('div'),
  813. supportedTransforms = /^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,
  814. clearProperties = {}
  815. function downcase(str) { return str.toLowerCase() }
  816. function normalizeEvent(name) { return eventPrefix ? eventPrefix + name : downcase(name) }
  817. $.each(vendors, function(vendor, event){
  818. if (testEl.style[vendor + 'TransitionProperty'] !== undefined) {
  819. prefix = '-' + downcase(vendor) + '-'
  820. eventPrefix = event
  821. return false
  822. }
  823. })
  824. clearProperties[prefix + 'transition-property'] =
  825. clearProperties[prefix + 'transition-duration'] =
  826. clearProperties[prefix + 'transition-timing-function'] =
  827. clearProperties[prefix + 'animation-name'] =
  828. clearProperties[prefix + 'animation-duration'] = ''
  829. $.fx = {
  830. off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined),
  831. cssPrefix: prefix,
  832. transitionEnd: normalizeEvent('TransitionEnd'),
  833. animationEnd: normalizeEvent('AnimationEnd')
  834. }
  835. $.fn.animate = function(properties, duration, ease, callback){
  836. if ($.isObject(duration))
  837. ease = duration.easing, callback = duration.complete, duration = duration.duration
  838. if (duration) duration = duration / 1000
  839. return this.anim(properties, duration, ease, callback)
  840. }
  841. $.fn.anim = function(properties, duration, ease, callback){
  842. var transforms, cssProperties = {}, key, that = this, wrappedCallback, endEvent = $.fx.transitionEnd
  843. if (duration === undefined) duration = 0.4
  844. if ($.fx.off) duration = 0
  845. if (typeof properties == 'string') {
  846. // keyframe animation
  847. cssProperties[prefix + 'animation-name'] = properties
  848. cssProperties[prefix + 'animation-duration'] = duration + 's'
  849. endEvent = $.fx.animationEnd
  850. } else {
  851. // CSS transitions
  852. for (key in properties)
  853. if (supportedTransforms.test(key)) {
  854. transforms || (transforms = [])
  855. transforms.push(key + '(' + properties[key] + ')')
  856. }
  857. else cssProperties[key] = properties[key]
  858. if (transforms) cssProperties[prefix + 'transform'] = transforms.join(' ')
  859. if (!$.fx.off && typeof properties === 'object') {
  860. cssProperties[prefix + 'transition-property'] = Object.keys(properties).join(', ')
  861. cssProperties[prefix + 'transition-duration'] = duration + 's'
  862. cssProperties[prefix + 'transition-timing-function'] = (ease || 'linear')
  863. }
  864. }
  865. wrappedCallback = function(event){
  866. if (typeof event !== 'undefined') {
  867. if (event.target !== event.currentTarget) return // makes sure the event didn't bubble from "below"
  868. $(event.target).unbind(endEvent, arguments.callee)
  869. }
  870. $(this).css(clearProperties)
  871. callback && callback.call(this)
  872. }
  873. if (duration > 0) this.bind(endEvent, wrappedCallback)
  874. setTimeout(function() {
  875. that.css(cssProperties)
  876. if (duration <= 0) setTimeout(function() {
  877. that.each(function(){ wrappedCallback.call(this) })
  878. }, 0)
  879. }, 0)
  880. return this
  881. }
  882. testEl = null
  883. })(Zepto)
  884. ;(function($){
  885. var jsonpID = 0,
  886. isObject = $.isObject,
  887. document = window.document,
  888. key,
  889. name,
  890. rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
  891. scriptTypeRE = /^(?:text|application)\/javascript/i,
  892. xmlTypeRE = /^(?:text|application)\/xml/i,
  893. jsonType = 'application/json',
  894. htmlType = 'text/html',
  895. blankRE = /^\s*$/
  896. // trigger a custom event and return false if it was cancelled
  897. function triggerAndReturn(context, eventName, data) {
  898. var event = $.Event(eventName)
  899. $(context).trigger(event, data)
  900. return !event.defaultPrevented
  901. }
  902. // trigger an Ajax "global" event
  903. function triggerGlobal(settings, context, eventName, data) {
  904. if (settings.global) return triggerAndReturn(context || document, eventName, data)
  905. }
  906. // Number of active Ajax requests
  907. $.active = 0
  908. function ajaxStart(settings) {
  909. if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart')
  910. }
  911. function ajaxStop(settings) {
  912. if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop')
  913. }
  914. // triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable
  915. function ajaxBeforeSend(xhr, settings) {
  916. var context = settings.context
  917. if (settings.beforeSend.call(context, xhr, settings) === false ||
  918. triggerGlobal(settings, context, 'ajaxBeforeSend', [xhr, settings]) === false)
  919. return false
  920. triggerGlobal(settings, context, 'ajaxSend', [xhr, settings])
  921. }
  922. function ajaxSuccess(data, xhr, settings) {
  923. var context = settings.context, status = 'success'
  924. settings.success.call(context, data, status, xhr)
  925. triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data])
  926. ajaxComplete(status, xhr, settings)
  927. }
  928. // type: "timeout", "error", "abort", "parsererror"
  929. function ajaxError(error, type, xhr, settings) {
  930. var context = settings.context
  931. settings.error.call(context, xhr, type, error)
  932. triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error])
  933. ajaxComplete(type, xhr, settings)
  934. }
  935. // status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
  936. function ajaxComplete(status, xhr, settings) {
  937. var context = settings.context
  938. settings.complete.call(context, xhr, status)
  939. triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings])
  940. ajaxStop(settings)
  941. }
  942. // Empty function, used as default callback
  943. function empty() {}
  944. $.ajaxJSONP = function(options){
  945. var callbackName = 'jsonp' + (++jsonpID),
  946. script = document.createElement('script'),
  947. abort = function(){
  948. $(script).remove()
  949. if (callbackName in window) window[callbackName] = empty
  950. ajaxComplete('abort', xhr, options)
  951. },
  952. xhr = { abort: abort }, abortTimeout
  953. if (options.error) script.onerror = function() {
  954. xhr.abort()
  955. options.error()
  956. }
  957. window[callbackName] = function(data){
  958. clearTimeout(abortTimeout)
  959. $(script).remove()
  960. delete window[callbackName]
  961. ajaxSuccess(data, xhr, options)
  962. }
  963. serializeData(options)
  964. script.src = options.url.replace(/=\?/, '=' + callbackName)
  965. $('head').append(script)
  966. if (options.timeout > 0) abortTimeout = setTimeout(function(){
  967. xhr.abort()
  968. ajaxComplete('timeout', xhr, options)
  969. }, options.timeout)
  970. return xhr
  971. }
  972. $.ajaxSettings = {
  973. // Default type of request
  974. type: 'GET',
  975. // Callback that is executed before request
  976. beforeSend: empty,
  977. // Callback that is executed if the request succeeds
  978. success: empty,
  979. // Callback that is executed the the server drops error
  980. error: empty,
  981. // Callback that is executed on request complete (both: error and success)
  982. complete: empty,
  983. // The context for the callbacks
  984. context: null,
  985. // Whether to trigger "global" Ajax events
  986. global: true,
  987. // Transport
  988. xhr: function () {
  989. return new window.XMLHttpRequest()
  990. },
  991. // MIME types mapping
  992. accepts: {
  993. script: 'text/javascript, application/javascript',
  994. json: jsonType,
  995. xml: 'application/xml, text/xml',
  996. html: htmlType,
  997. text: 'text/plain'
  998. },
  999. // Whether the request is to another domain
  1000. crossDomain: false,
  1001. // Default timeout
  1002. timeout: 0
  1003. }
  1004. function mimeToDataType(mime) {
  1005. return mime && ( mime == htmlType ? 'html' :
  1006. mime == jsonType ? 'json' :
  1007. scriptTypeRE.test(mime) ? 'script' :
  1008. xmlTypeRE.test(mime) && 'xml' ) || 'text'
  1009. }
  1010. function appendQuery(url, query) {
  1011. return (url + '&' + query).replace(/[&?]{1,2}/, '?')
  1012. }
  1013. // serialize payload and append it to the URL for GET requests
  1014. function serializeData(options) {
  1015. if (isObject(options.data)) options.data = $.param(options.data)
  1016. if (options.data && (!options.type || options.type.toUpperCase() == 'GET'))
  1017. options.url = appendQuery(options.url, options.data)
  1018. }
  1019. $.ajax = function(options){
  1020. var settings = $.extend({}, options || {})
  1021. for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
  1022. ajaxStart(settings)
  1023. if (!settings.crossDomain) settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) &&
  1024. RegExp.$2 != window.location.host
  1025. var dataType = settings.dataType, hasPlaceholder = /=\?/.test(settings.url)
  1026. if (dataType == 'jsonp' || hasPlaceholder) {
  1027. if (!hasPlaceholder) settings.url = appendQuery(settings.url, 'callback=?')
  1028. return $.ajaxJSONP(settings)
  1029. }
  1030. if (!settings.url) settings.url = window.location.toString()
  1031. serializeData(settings)
  1032. var mime = settings.accepts[dataType],
  1033. baseHeaders = { },
  1034. protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
  1035. xhr = $.ajaxSettings.xhr(), abortTimeout
  1036. if (!settings.crossDomain) baseHeaders['X-Requested-With'] = 'XMLHttpRequest'
  1037. if (mime) {
  1038. baseHeaders['Accept'] = mime
  1039. if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0]
  1040. xhr.overrideMimeType && xhr.overrideMimeType(mime)
  1041. }
  1042. if (settings.contentType || (settings.data && settings.type.toUpperCase() != 'GET'))
  1043. baseHeaders['Content-Type'] = (settings.contentType || 'application/x-www-form-urlencoded')
  1044. settings.headers = $.extend(baseHeaders, settings.headers || {})
  1045. xhr.onreadystatechange = function(){
  1046. if (xhr.readyState == 4) {
  1047. clearTimeout(abortTimeout)
  1048. var result, error = false
  1049. if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) {
  1050. dataType = dataType || mimeToDataType(xhr.getResponseHeader('content-type'))
  1051. result = xhr.responseText
  1052. try {
  1053. if (dataType == 'script') (1,eval)(result)
  1054. else if (dataType == 'xml') result = xhr.responseXML
  1055. else if (dataType == 'json') result = blankRE.test(result) ? null : JSON.parse(result)
  1056. } catch (e) { error = e }
  1057. if (error) ajaxError(error, 'parsererror', xhr, settings)
  1058. else ajaxSuccess(result, xhr, settings)
  1059. } else {
  1060. ajaxError(null, 'error', xhr, settings)
  1061. }
  1062. }
  1063. }
  1064. var async = 'async' in settings ? settings.async : true
  1065. xhr.open(settings.type, settings.url, async)
  1066. for (name in settings.headers) xhr.setRequestHeader(name, settings.headers[name])
  1067. if (ajaxBeforeSend(xhr, settings) === false) {
  1068. xhr.abort()
  1069. return false
  1070. }
  1071. if (settings.timeout > 0) abortTimeout = setTimeout(function(){
  1072. xhr.onreadystatechange = empty
  1073. xhr.abort()
  1074. ajaxError(null, 'timeout', xhr, settings)
  1075. }, settings.timeout)
  1076. // avoid sending empty string (#319)
  1077. xhr.send(settings.data ? settings.data : null)
  1078. return xhr
  1079. }
  1080. $.get = function(url, success){ return $.ajax({ url: url, success: success }) }
  1081. $.post = function(url, data, success, dataType){
  1082. if ($.isFunction(data)) dataType = dataType || success, success = data, data = null
  1083. return $.ajax({ type: 'POST', url: url, data: data, success: success, dataType: dataType })
  1084. }
  1085. $.getJSON = function(url, success){
  1086. return $.ajax({ url: url, success: success, dataType: 'json' })
  1087. }
  1088. $.fn.load = function(url, success){
  1089. if (!this.length) return this
  1090. var self = this, parts = url.split(/\s/), selector
  1091. if (parts.length > 1) url = parts[0], selector = parts[1]
  1092. $.get(url, function(response){
  1093. self.html(selector ?
  1094. $(document.createElement('div')).html(response.replace(rscript, "")).find(selector).html()
  1095. : response)
  1096. success && success.call(self)
  1097. })
  1098. return this
  1099. }
  1100. var escape = encodeURIComponent
  1101. function serialize(params, obj, traditional, scope){
  1102. var array = $.isArray(obj)
  1103. $.each(obj, function(key, value) {
  1104. if (scope) key = traditional ? scope : scope + '[' + (array ? '' : key) + ']'
  1105. // handle data in serializeArray() format
  1106. if (!scope && array) params.add(value.name, value.value)
  1107. // recurse into nested objects
  1108. else if (traditional ? $.isArray(value) : isObject(value))
  1109. serialize(params, value, traditional, key)
  1110. else params.add(key, value)
  1111. })
  1112. }
  1113. $.param = function(obj, traditional){
  1114. var params = []
  1115. params.add = function(k, v){ this.push(escape(k) + '=' + escape(v)) }
  1116. serialize(params, obj, traditional)
  1117. return params.join('&').replace('%20', '+')
  1118. }
  1119. })(Zepto)
  1120. ;(function ($) {
  1121. $.fn.serializeArray = function () {
  1122. var result = [], el
  1123. $( Array.prototype.slice.call(this.get(0).elements) ).each(function () {
  1124. el = $(this)
  1125. var type = el.attr('type')
  1126. if (this.nodeName.toLowerCase() != 'fieldset' &&
  1127. !this.disabled && type != 'submit' && type != 'reset' && type != 'button' &&
  1128. ((type != 'radio' && type != 'checkbox') || this.checked))
  1129. result.push({
  1130. name: el.attr('name'),
  1131. value: el.val()
  1132. })
  1133. })
  1134. return result
  1135. }
  1136. $.fn.serialize = function () {
  1137. var result = []
  1138. this.serializeArray().forEach(function (elm) {
  1139. result.push( encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value) )
  1140. })
  1141. return result.join('&')
  1142. }
  1143. $.fn.submit = function (callback) {
  1144. if (callback) this.bind('submit', callback)
  1145. else if (this.length) {
  1146. var event = $.Event('submit')
  1147. this.eq(0).trigger(event)
  1148. if (!event.defaultPrevented) this.get(0).submit()
  1149. }
  1150. return this
  1151. }
  1152. })(Zepto)
  1153. ;(function($){
  1154. var touch = {}, touchTimeout
  1155. function parentIfText(node){
  1156. return 'tagName' in node ? node : node.parentNode
  1157. }
  1158. function swipeDirection(x1, x2, y1, y2){
  1159. var xDelta = Math.abs(x1 - x2), yDelta = Math.abs(y1 - y2)
  1160. return xDelta >= yDelta ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
  1161. }
  1162. var longTapDelay = 750, longTapTimeout
  1163. function longTap(){
  1164. longTapTimeout = null
  1165. if (touch.last) {
  1166. touch.el.trigger('longTap')
  1167. touch = {}
  1168. }
  1169. }
  1170. function cancelLongTap(){
  1171. if (longTapTimeout) clearTimeout(longTapTimeout)
  1172. longTapTimeout = null
  1173. }
  1174. $(document).ready(function(){
  1175. var now, delta
  1176. $(document.body).bind('touchstart', function(e){
  1177. now = Date.now()
  1178. delta = now - (touch.last || now)
  1179. touch.el = $(parentIfText(e.touches[0].target))
  1180. touchTimeout && clearTimeout(touchTimeout)
  1181. touch.x1 = e.touches[0].pageX
  1182. touch.y1 = e.touches[0].pageY
  1183. if (delta > 0 && delta <= 250) touch.isDoubleTap = true
  1184. touch.last = now
  1185. longTapTimeout = setTimeout(longTap, longTapDelay)
  1186. }).bind('touchmove', function(e){
  1187. cancelLongTap()
  1188. touch.x2 = e.touches[0].pageX
  1189. touch.y2 = e.touches[0].pageY
  1190. }).bind('touchend', function(e){
  1191. cancelLongTap()
  1192. // double tap (tapped twice within 250ms)
  1193. if (touch.isDoubleTap) {
  1194. touch.el.trigger('doubleTap')
  1195. touch = {}
  1196. // swipe
  1197. } else if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
  1198. (touch.y2 && Math.abs(touch.y1 - touch.y2) > 30)) {
  1199. touch.el.trigger('swipe') &&
  1200. touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
  1201. touch = {}
  1202. // normal tap
  1203. } else if ('last' in touch) {
  1204. touch.el.trigger('tap')
  1205. touchTimeout = setTimeout(function(){
  1206. touchTimeout = null
  1207. touch.el.trigger('singleTap')
  1208. touch = {}
  1209. }, 250)
  1210. }
  1211. }).bind('touchcancel', function(){
  1212. if (touchTimeout) clearTimeout(touchTimeout)
  1213. if (longTapTimeout) clearTimeout(longTapTimeout)
  1214. longTapTimeout = touchTimeout = null
  1215. touch = {}
  1216. })
  1217. })
  1218. ;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function(m){
  1219. $.fn[m] = function(callback){ return this.bind(m, callback) }
  1220. })
  1221. })(Zepto)
  1222. // lib/handlebars/base.js
  1223. /*jshint eqnull:true*/
  1224. this.Handlebars = {};
  1225. (function(Handlebars) {
  1226. Handlebars.VERSION = "1.0.rc.1";
  1227. Handlebars.helpers = {};
  1228. Handlebars.partials = {};
  1229. Handlebars.registerHelper = function(name, fn, inverse) {
  1230. if(inverse) { fn.not = inverse; }
  1231. this.helpers[name] = fn;
  1232. };
  1233. Handlebars.registerPartial = function(name, str) {
  1234. this.partials[name] = str;
  1235. };
  1236. Handlebars.registerHelper('helperMissing', function(arg) {
  1237. if(arguments.length === 2) {
  1238. return undefined;
  1239. } else {
  1240. throw new Error("Could not find property '" + arg + "'");
  1241. }
  1242. });
  1243. var toString = Object.prototype.toString, functionType = "[object Function]";
  1244. Handlebars.registerHelper('blockHelperMissing', function(context, options) {
  1245. var inverse = options.inverse || function() {}, fn = options.fn;
  1246. var ret = "";
  1247. var type = toString.call(context);
  1248. if(type === functionType) { context = context.call(this); }
  1249. if(context === true) {
  1250. return fn(this);
  1251. } else if(context === false || context == null) {
  1252. return inverse(this);
  1253. } else if(type === "[object Array]") {
  1254. if(context.length > 0) {
  1255. return Handlebars.helpers.each(context, options);
  1256. } else {
  1257. return inverse(this);
  1258. }
  1259. } else {
  1260. return fn(context);
  1261. }
  1262. });
  1263. Handlebars.K = function() {};
  1264. Handlebars.createFrame = Object.create || function(object) {
  1265. Handlebars.K.prototype = object;
  1266. var obj = new Handlebars.K();
  1267. Handlebars.K.prototype = null;
  1268. return obj;
  1269. };
  1270. Handlebars.registerHelper('each', function(context, options) {
  1271. var fn = options.fn, inverse = options.inverse;
  1272. var ret = "", data;
  1273. if (options.data) {
  1274. data = Handlebars.createFrame(options.data);
  1275. }
  1276. if(context && context.length > 0) {
  1277. for(var i=0, j=context.length; i<j; i++) {
  1278. if (data) { data.index = i; }
  1279. ret = ret + fn(context[i], { data: data });
  1280. }
  1281. } else {
  1282. ret = inverse(this);
  1283. }
  1284. return ret;
  1285. });
  1286. Handlebars.registerHelper('if', function(context, options) {
  1287. var type = toString.call(context);
  1288. if(type === functionType) { context = context.call(this); }
  1289. if(!context || Handlebars.Utils.isEmpty(context)) {
  1290. return options.inverse(this);
  1291. } else {
  1292. return options.fn(this);
  1293. }
  1294. });
  1295. Handlebars.registerHelper('unless', function(context, options) {
  1296. var fn = options.fn, inverse = options.inverse;
  1297. options.fn = inverse;
  1298. options.inverse = fn;
  1299. return Handlebars.helpers['if'].call(this, context, options);
  1300. });
  1301. Handlebars.registerHelper('with', function(context, options) {
  1302. return options.fn(context);
  1303. });
  1304. Handlebars.registerHelper('log', function(context) {
  1305. Handlebars.log(context);
  1306. });
  1307. }(this.Handlebars));
  1308. ;
  1309. // lib/handlebars/compiler/parser.js
  1310. /* Jison generated parser */
  1311. var handlebars = (function(){
  1312. var parser = {trace: function trace() { },
  1313. yy: {},
  1314. symbols_: {"error":2,"root":3,"program":4,"EOF":5,"statements":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"OPEN_PARTIAL":24,"params":25,"hash":26,"DATA":27,"param":28,"STRING":29,"INTEGER":30,"BOOLEAN":31,"hashSegments":32,"hashSegment":33,"ID":34,"EQUALS":35,"pathSegments":36,"SEP":37,"$accept":0,"$end":1},
  1315. terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"OPEN_PARTIAL",27:"DATA",29:"STRING",30:"INTEGER",31:"BOOLEAN",34:"ID",35:"EQUALS",37:"SEP"},
  1316. productions_: [0,[3,2],[4,3],[4,1],[4,0],[6,1],[6,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[7,2],[17,3],[17,2],[17,2],[17,1],[17,1],[25,2],[25,1],[28,1],[28,1],[28,1],[28,1],[28,1],[26,1],[32,2],[32,1],[33,3],[33,3],[33,3],[33,3],[33,3],[21,1],[36,3],[36,1]],
  1317. performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
  1318. var $0 = $$.length - 1;
  1319. switch (yystate) {
  1320. case 1: return $$[$0-1];
  1321. break;
  1322. case 2: this.$ = new yy.ProgramNode($$[$0-2], $$[$0]);
  1323. break;
  1324. case 3: this.$ = new yy.ProgramNode($$[$0]);
  1325. break;
  1326. case 4: this.$ = new yy.ProgramNode([]);
  1327. break;
  1328. case 5: this.$ = [$$[$0]];
  1329. break;
  1330. case 6: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
  1331. break;
  1332. case 7: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1].inverse, $$[$0-1], $$[$0]);
  1333. break;
  1334. case 8: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0]);
  1335. break;
  1336. case 9: this.$ = $$[$0];
  1337. break;
  1338. case 10: this.$ = $$[$0];
  1339. break;
  1340. case 11: this.$ = new yy.ContentNode($$[$0]);
  1341. break;
  1342. case 12: this.$ = new yy.CommentNode($$[$0]);
  1343. break;
  1344. case 13: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]);
  1345. break;
  1346. case 14: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]);
  1347. break;
  1348. case 15: this.$ = $$[$0-1];
  1349. break;
  1350. case 16: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]);
  1351. break;
  1352. case 17: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true);
  1353. break;
  1354. case 18: this.$ = new yy.PartialNode($$[$0-1]);
  1355. break;
  1356. case 19: this.$ = new yy.PartialNode($$[$0-2], $$[$0-1]);
  1357. break;
  1358. case 20:
  1359. break;
  1360. case 21: this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]];
  1361. break;
  1362. case 22: this.$ = [[$$[$0-1]].concat($$[$0]), null];
  1363. break;
  1364. case 23: this.$ = [[$$[$0-1]], $$[$0]];
  1365. break;
  1366. case 24: this.$ = [[$$[$0]], null];
  1367. break;
  1368. case 25: this.$ = [[new yy.DataNode($$[$0])], null];
  1369. break;
  1370. case 26: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
  1371. break;
  1372. case 27: this.$ = [$$[$0]];
  1373. break;
  1374. case 28: this.$ = $$[$0];
  1375. break;
  1376. case 29: this.$ = new yy.StringNode($$[$0]);
  1377. break;
  1378. case 30: this.$ = new yy.IntegerNode($$[$0]);
  1379. break;
  1380. case 31: this.$ = new yy.BooleanNode($$[$0]);
  1381. break;
  1382. case 32: this.$ = new yy.DataNode($$[$0]);
  1383. break;
  1384. case 33: this.$ = new yy.HashNode($$[$0]);
  1385. break;
  1386. case 34: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
  1387. break;
  1388. case 35: this.$ = [$$[$0]];
  1389. break;
  1390. case 36: this.$ = [$$[$0-2], $$[$0]];
  1391. break;
  1392. case 37: this.$ = [$$[$0-2], new yy.StringNode($$[$0])];
  1393. break;
  1394. case 38: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])];
  1395. break;
  1396. case 39: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])];
  1397. break;
  1398. case 40: this.$ = [$$[$0-2], new yy.DataNode($$[$0])];
  1399. break;
  1400. case 41: this.$ = new yy.IdNode($$[$0]);
  1401. break;
  1402. case 42: $$[$0-2].push($$[$0]); this.$ = $$[$0-2];
  1403. break;
  1404. case 43: this.$ = [$$[$0]];
  1405. break;
  1406. }
  1407. },
  1408. table: [{3:1,4:2,5:[2,4],6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],24:[1,15]},{1:[3]},{5:[1,16]},{5:[2,3],7:17,8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,19],20:[2,3],22:[1,13],23:[1,14],24:[1,15]},{5:[2,5],14:[2,5],15:[2,5],16:[2,5],19:[2,5],20:[2,5],22:[2,5],23:[2,5],24:[2,5]},{4:20,6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],24:[1,15]},{4:21,6:3,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],24:[1,15]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],24:[2,9]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],24:[2,10]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],24:[2,11]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],24:[2,12]},{17:22,21:23,27:[1,24],34:[1,26],36:25},{17:27,21:23,27:[1,24],34:[1,26],36:25},{17:28,21:23,27:[1,24],34:[1,26],36:25},{17:29,21:23,27:[1,24],34:[1,26],36:25},{21:30,34:[1,26],36:25},{1:[2,1]},{6:31,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],24:[1,15]},{5:[2,6],14:[2,6],15:[2,6],16:[2,6],19:[2,6],20:[2,6],22:[2,6],23:[2,6],24:[2,6]},{17:22,18:[1,32],21:23,27:[1,24],34:[1,26],36:25},{10:33,20:[1,34]},{10:35,20:[1,34]},{18:[1,36]},{18:[2,24],21:41,25:37,26:38,27:[1,45],28:39,29:[1,42],30:[1,43],31:[1,44],32:40,33:46,34:[1,47],36:25},{18:[2,25]},{18:[2,41],27:[2,41],29:[2,41],30:[2,41],31:[2,41],34:[2,41],37:[1,48]},{18:[2,43],27:[2,43],29:[2,43],30:[2,43],31:[2,43],34:[2,43],37:[2,43]},{18:[1,49]},{18:[1,50]},{18:[1,51]},{18:[1,52],21:53,34:[1,26],36:25},{5:[2,2],8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,2],22:[1,13],23:[1,14],24:[1,15]},{14:[2,20],15:[2,20],16:[2,20],19:[2,20],22:[2,20],23:[2,20],24:[2,20]},{5:[2,7],14:[2,7],15:[2,7],16:[2,7],19:[2,7],20:[2,7],22:[2,7],23:[2,7],24:[2,7]},{21:54,34:[1,26],36:25},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],24:[2,8]},{14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],24:[2,14]},{18:[2,22],21:41,26:55,27:[1,45],28:56,29:[1,42],30:[1,43],31:[1,44],32:40,33:46,34:[1,47],36:25},{18:[2,23]},{18:[2,27],27:[2,27],29:[2,27],30:[2,27],31:[2,27],34:[2,27]},{18:[2,33],33:57,34:[1,58]},{18:[2,28],27:[2,28],29:[2,28],30:[2,28],31:[2,28],34:[2,28]},{18:[2,29],27:[2,29],29:[2,29],30:[2,29],31:[2,29],34:[2,29]},{18:[2,30],27:[2,30],29:[2,30],30:[2,30],31:[2,30],34:[2,30]},{18:[2,31],27:[2,31],29:[2,31],30:[2,31],31:[2,31],34:[2,31]},{18:[2,32],27:[2,32],29:[2,32],30:[2,32],31:[2,32],34:[2,32]},{18:[2,35],34:[2,35]},{18:[2,43],27:[2,43],29:[2,43],30:[2,43],31:[2,43],34:[2,43],35:[1,59],37:[2,43]},{34:[1,60]},{14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],24:[2,13]},{5:[2,16],14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],24:[2,16]},{5:[2,17],14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],24:[2,17]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],24:[2,18]},{18:[1,61]},{18:[1,62]},{18:[2,21]},{18:[2,26],27:[2,26],29:[2,26],30:[2,26],31:[2,26],34:[2,26]},{18:[2,34],34:[2,34]},{35:[1,59]},{21:63,27:[1,67],29:[1,64],30:[1,65],31:[1,66],34:[1,26],36:25},{18:[2,42],27:[2,42],29:[2,42],30:[2,42],31:[2,42],34:[2,42],37:[2,42]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],24:[2,19]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],24:[2,15]},{18:[2,36],34:[2,36]},{18:[2,37],34:[2,37]},{18:[2,38],34:[2,38]},{18:[2,39],34:[2,39]},{18:[2,40],34:[2,40]}],
  1409. defaultActions: {16:[2,1],24:[2,25],38:[2,23],55:[2,21]},
  1410. parseError: function parseError(str, hash) {
  1411. throw new Error(str);
  1412. },
  1413. parse: function parse(input) {
  1414. var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
  1415. this.lexer.setInput(input);
  1416. this.lexer.yy = this.yy;
  1417. this.yy.lexer = this.lexer;
  1418. this.yy.parser = this;
  1419. if (typeof this.lexer.yylloc == "undefined")
  1420. this.lexer.yylloc = {};
  1421. var yyloc = this.lexer.yylloc;
  1422. lstack.push(yyloc);
  1423. var ranges = this.lexer.options && this.lexer.options.ranges;
  1424. if (typeof this.yy.parseError === "function")
  1425. this.parseError = this.yy.parseError;
  1426. function popStack(n) {
  1427. stack.length = stack.length - 2 * n;
  1428. vstack.length = vstack.length - n;
  1429. lstack.length = lstack.length - n;
  1430. }
  1431. function lex() {
  1432. var token;
  1433. token = self.lexer.lex() || 1;
  1434. if (typeof token !== "number") {
  1435. token = self.symbols_[token] || token;
  1436. }
  1437. return token;
  1438. }
  1439. var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
  1440. while (true) {
  1441. state = stack[stack.length - 1];
  1442. if (this.defaultActions[state]) {
  1443. action = this.defaultActions[state];
  1444. } else {
  1445. if (symbol === null || typeof symbol == "undefined") {
  1446. symbol = lex();
  1447. }
  1448. action = table[state] && table[state][symbol];
  1449. }
  1450. if (typeof action === "undefined" || !action.length || !action[0]) {
  1451. var errStr = "";
  1452. if (!recovering) {
  1453. expected = [];
  1454. for (p in table[state])
  1455. if (this.terminals_[p] && p > 2) {
  1456. expected.push("'" + this.terminals_[p] + "'");
  1457. }
  1458. if (this.lexer.showPosition) {
  1459. errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'";
  1460. } else {
  1461. errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
  1462. }
  1463. this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
  1464. }
  1465. }
  1466. if (action[0] instanceof Array && action.length > 1) {
  1467. throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
  1468. }
  1469. switch (action[0]) {
  1470. case 1:
  1471. stack.push(symbol);
  1472. vstack.push(this.lexer.yytext);
  1473. lstack.push(this.lexer.yylloc);
  1474. stack.push(action[1]);
  1475. symbol = null;
  1476. if (!preErrorSymbol) {
  1477. yyleng = this.lexer.yyleng;
  1478. yytext = this.lexer.yytext;
  1479. yylineno = this.lexer.yylineno;
  1480. yyloc = this.lexer.yylloc;
  1481. if (recovering > 0)
  1482. recovering--;
  1483. } else {
  1484. symbol = preErrorSymbol;
  1485. preErrorSymbol = null;
  1486. }
  1487. break;
  1488. case 2:
  1489. len = this.productions_[action[1]][1];
  1490. yyval.$ = vstack[vstack.length - len];
  1491. yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
  1492. if (ranges) {
  1493. yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]];
  1494. }
  1495. r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
  1496. if (typeof r !== "undefined") {
  1497. return r;
  1498. }
  1499. if (len) {
  1500. stack = stack.slice(0, -1 * len * 2);
  1501. vstack = vstack.slice(0, -1 * len);
  1502. lstack = lstack.slice(0, -1 * len);
  1503. }
  1504. stack.push(this.productions_[action[1]][0]);
  1505. vstack.push(yyval.$);
  1506. lstack.push(yyval._$);
  1507. newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
  1508. stack.push(newState);
  1509. break;
  1510. case 3:
  1511. return true;
  1512. }
  1513. }
  1514. return true;
  1515. }
  1516. };
  1517. /* Jison generated lexer */
  1518. var lexer = (function(){
  1519. var lexer = ({EOF:1,
  1520. parseError:function parseError(str, hash) {
  1521. if (this.yy.parser) {
  1522. this.yy.parser.parseError(str, hash);
  1523. } else {
  1524. throw new Error(str);
  1525. }
  1526. },
  1527. setInput:function (input) {
  1528. this._input = input;
  1529. this._more = this._less = this.done = false;
  1530. this.yylineno = this.yyleng = 0;
  1531. this.yytext = this.matched = this.match = '';
  1532. this.conditionStack = ['INITIAL'];
  1533. this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
  1534. if (this.options.ranges) this.yylloc.range = [0,0];
  1535. this.offset = 0;
  1536. return this;
  1537. },
  1538. input:function () {
  1539. var ch = this._input[0];
  1540. this.yytext += ch;
  1541. this.yyleng++;
  1542. this.offset++;
  1543. this.match += ch;
  1544. this.matched += ch;
  1545. var lines = ch.match(/(?:\r\n?|\n).*/g);
  1546. if (lines) {
  1547. this.yylineno++;
  1548. this.yylloc.last_line++;
  1549. } else {
  1550. this.yylloc.last_column++;
  1551. }
  1552. if (this.options.ranges) this.yylloc.range[1]++;
  1553. this._input = this._input.slice(1);
  1554. return ch;
  1555. },
  1556. unput:function (ch) {
  1557. var len = ch.length;
  1558. var lines = ch.split(/(?:\r\n?|\n)/g);
  1559. this._input = ch + this._input;
  1560. this.yytext = this.yytext.substr(0, this.yytext.length-len-1);
  1561. //this.yyleng -= len;
  1562. this.offset -= len;
  1563. var oldLines = this.match.split(/(?:\r\n?|\n)/g);
  1564. this.match = this.match.substr(0, this.match.length-1);
  1565. this.matched = this.matched.substr(0, this.matched.length-1);
  1566. if (lines.length-1) this.yylineno -= lines.length-1;
  1567. var r = this.yylloc.range;
  1568. this.yylloc = {first_line: this.yylloc.first_line,
  1569. last_line: this.yylineno+1,
  1570. first_column: this.yylloc.first_column,
  1571. last_column: lines ?
  1572. (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length:
  1573. this.yylloc.first_column - len
  1574. };
  1575. if (this.options.ranges) {
  1576. this.yylloc.range = [r[0], r[0] + this.yyleng - len];
  1577. }
  1578. return this;
  1579. },
  1580. more:function () {
  1581. this._more = true;
  1582. return this;
  1583. },
  1584. less:function (n) {
  1585. this.unput(this.match.slice(n));
  1586. },
  1587. pastInput:function () {
  1588. var past = this.matched.substr(0, this.matched.length - this.match.length);
  1589. return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
  1590. },
  1591. upcomingInput:function () {
  1592. var next = this.match;
  1593. if (next.length < 20) {
  1594. next += this._input.substr(0, 20-next.length);
  1595. }
  1596. return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
  1597. },
  1598. showPosition:function () {
  1599. var pre = this.pastInput();
  1600. var c = new Array(pre.length + 1).join("-");
  1601. return pre + this.upcomingInput() + "\n" + c+"^";
  1602. },
  1603. next:function () {
  1604. if (this.done) {
  1605. return this.EOF;
  1606. }
  1607. if (!this._input) this.done = true;
  1608. var token,
  1609. match,
  1610. tempMatch,
  1611. index,
  1612. col,
  1613. lines;
  1614. if (!this._more) {
  1615. this.yytext = '';
  1616. this.match = '';
  1617. }
  1618. var rules = this._currentRules();
  1619. for (var i=0;i < rules.length; i++) {
  1620. tempMatch = this._input.match(this.rules[rules[i]]);
  1621. if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
  1622. match = tempMatch;
  1623. index = i;
  1624. if (!this.options.flex) break;
  1625. }
  1626. }
  1627. if (match) {
  1628. lines = match[0].match(/(?:\r\n?|\n).*/g);
  1629. if (lines) this.yylineno += lines.length;
  1630. this.yylloc = {first_line: this.yylloc.last_line,
  1631. last_line: this.yylineno+1,
  1632. first_column: this.yylloc.last_column,
  1633. last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length};
  1634. this.yytext += match[0];
  1635. this.match += match[0];
  1636. this.matches = match;
  1637. this.yyleng = this.yytext.length;
  1638. if (this.options.ranges) {
  1639. this.yylloc.range = [this.offset, this.offset += this.yyleng];
  1640. }
  1641. this._more = false;
  1642. this._input = this._input.slice(match[0].length);
  1643. this.matched += match[0];
  1644. token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
  1645. if (this.done && this._input) this.done = false;
  1646. if (token) return token;
  1647. else return;
  1648. }
  1649. if (this._input === "") {
  1650. return this.EOF;
  1651. } else {
  1652. return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
  1653. {text: "", token: null, line: this.yylineno});
  1654. }
  1655. },
  1656. lex:function lex() {
  1657. var r = this.next();
  1658. if (typeof r !== 'undefined') {
  1659. return r;
  1660. } else {
  1661. return this.lex();
  1662. }
  1663. },
  1664. begin:function begin(condition) {
  1665. this.conditionStack.push(condition);
  1666. },
  1667. popState:function popState() {
  1668. return this.conditionStack.pop();
  1669. },
  1670. _currentRules:function _currentRules() {
  1671. return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
  1672. },
  1673. topState:function () {
  1674. return this.conditionStack[this.conditionStack.length-2];
  1675. },
  1676. pushState:function begin(condition) {
  1677. this.begin(condition);
  1678. }});
  1679. lexer.options = {};
  1680. lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
  1681. var YYSTATE=YY_START
  1682. switch($avoiding_name_collisions) {
  1683. case 0:
  1684. if(yy_.yytext.slice(-1) !== "\\") this.begin("mu");
  1685. if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu");
  1686. if(yy_.yytext) return 14;
  1687. break;
  1688. case 1: return 14;
  1689. break;
  1690. case 2:
  1691. if(yy_.yytext.slice(-1) !== "\\") this.popState();
  1692. if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1);
  1693. return 14;
  1694. break;
  1695. case 3: return 24;
  1696. break;
  1697. case 4: return 16;
  1698. break;
  1699. case 5: return 20;
  1700. break;
  1701. case 6: return 19;
  1702. break;
  1703. case 7: return 19;
  1704. break;
  1705. case 8: return 23;
  1706. break;
  1707. case 9: return 23;
  1708. break;
  1709. case 10: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15;
  1710. break;
  1711. case 11: return 22;
  1712. break;
  1713. case 12: return 35;
  1714. break;
  1715. case 13: return 34;
  1716. break;
  1717. case 14: return 34;
  1718. break;
  1719. case 15: return 37;
  1720. break;
  1721. case 16: /*ignore whitespace*/
  1722. break;
  1723. case 17: this.popState(); return 18;
  1724. break;
  1725. case 18: this.popState(); return 18;
  1726. break;
  1727. case 19: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 29;
  1728. break;
  1729. case 20: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 29;
  1730. break;
  1731. case 21: yy_.yytext = yy_.yytext.substr(1); return 27;
  1732. break;
  1733. case 22: return 31;
  1734. break;
  1735. case 23: return 31;
  1736. break;
  1737. case 24: return 30;
  1738. break;
  1739. case 25: return 34;
  1740. break;
  1741. case 26: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 34;
  1742. break;
  1743. case 27: return 'INVALID';
  1744. break;
  1745. case 28: return 5;
  1746. break;
  1747. }
  1748. };
  1749. lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/];
  1750. lexer.conditions = {"mu":{"rules":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"INITIAL":{"rules":[0,1,28],"inclusive":true}};
  1751. return lexer;})()
  1752. parser.lexer = lexer;
  1753. function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
  1754. return new Parser;
  1755. })();
  1756. if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
  1757. exports.parser = handlebars;
  1758. exports.Parser = handlebars.Parser;
  1759. exports.parse = function () { return handlebars.parse.apply(handlebars, arguments); }
  1760. exports.main = function commonjsMain(args) {
  1761. if (!args[1])
  1762. throw new Error('Usage: '+args[0]+' FILE');
  1763. var source, cwd;
  1764. if (typeof process !== 'undefined') {
  1765. source = require('fs').readFileSync(require('path').resolve(args[1]), "utf8");
  1766. } else {
  1767. source = require("file").path(require("file").cwd()).join(args[1]).read({charset: "utf-8"});
  1768. }
  1769. return exports.parser.parse(source);
  1770. }
  1771. if (typeof module !== 'undefined' && require.main === module) {
  1772. exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args);
  1773. }
  1774. };
  1775. ;
  1776. // lib/handlebars/compiler/base.js
  1777. Handlebars.Parser = handlebars;
  1778. Handlebars.parse = function(string) {
  1779. Handlebars.Parser.yy = Handlebars.AST;
  1780. return Handlebars.Parser.parse(string);
  1781. };
  1782. Handlebars.print = function(ast) {
  1783. return new Handlebars.PrintVisitor().accept(ast);
  1784. };
  1785. Handlebars.logger = {
  1786. DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
  1787. // override in the host environment
  1788. log: function(level, str) {}
  1789. };
  1790. Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
  1791. ;
  1792. // lib/handlebars/compiler/ast.js
  1793. (function() {
  1794. Handlebars.AST = {};
  1795. Handlebars.AST.ProgramNode = function(statements, inverse) {
  1796. this.type = "program";
  1797. this.statements = statements;
  1798. if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
  1799. };
  1800. Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
  1801. this.type = "mustache";
  1802. this.escaped = !unescaped;
  1803. this.hash = hash;
  1804. var id = this.id = rawParams[0];
  1805. var params = this.params = rawParams.slice(1);
  1806. // a mustache is an eligible helper if:
  1807. // * its id is simple (a single part, not `this` or `..`)
  1808. var eligibleHelper = this.eligibleHelper = id.isSimple;
  1809. // a mustache is definitely a helper if:
  1810. // * it is an eligible helper, and
  1811. // * it has at least one parameter or hash segment
  1812. this.isHelper = eligibleHelper && (params.length || hash);
  1813. // if a mustache is an eligible helper but not a definite
  1814. // helper, it is ambiguous, and will be resolved in a later
  1815. // pass or at runtime.
  1816. };
  1817. Handlebars.AST.PartialNode = function(id, context) {
  1818. this.type = "partial";
  1819. // TODO: disallow complex IDs
  1820. this.id = id;
  1821. this.context = context;
  1822. };
  1823. var verifyMatch = function(open, close) {
  1824. if(open.original !== close.original) {
  1825. throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
  1826. }
  1827. };
  1828. Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
  1829. verifyMatch(mustache.id, close);
  1830. this.type = "block";
  1831. this.mustache = mustache;
  1832. this.program = program;
  1833. this.inverse = inverse;
  1834. if (this.inverse && !this.program) {
  1835. this.isInverse = true;
  1836. }
  1837. };
  1838. Handlebars.AST.ContentNode = function(string) {
  1839. this.type = "content";
  1840. this.string = string;
  1841. };
  1842. Handlebars.AST.HashNode = function(pairs) {
  1843. this.type = "hash";
  1844. this.pairs = pairs;
  1845. };
  1846. Handlebars.AST.IdNode = function(parts) {
  1847. this.type = "ID";
  1848. this.original = parts.join(".");
  1849. var dig = [], depth = 0;
  1850. for(var i=0,l=parts.length; i<l; i++) {
  1851. var part = parts[i];
  1852. if(part === "..") { depth++; }
  1853. else if(part === "." || part === "this") { this.isScoped = true; }
  1854. else { dig.push(part); }
  1855. }
  1856. this.parts = dig;
  1857. this.string = dig.join('.');
  1858. this.depth = depth;
  1859. // an ID is simple if it only has one part, and that part is not
  1860. // `..` or `this`.
  1861. this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
  1862. };
  1863. Handlebars.AST.DataNode = function(id) {
  1864. this.type = "DATA";
  1865. this.id = id;
  1866. };
  1867. Handlebars.AST.StringNode = function(string) {
  1868. this.type = "STRING";
  1869. this.string = string;
  1870. };
  1871. Handlebars.AST.IntegerNode = function(integer) {
  1872. this.type = "INTEGER";
  1873. this.integer = integer;
  1874. };
  1875. Handlebars.AST.BooleanNode = function(bool) {
  1876. this.type = "BOOLEAN";
  1877. this.bool = bool;
  1878. };
  1879. Handlebars.AST.CommentNode = function(comment) {
  1880. this.type = "comment";
  1881. this.comment = comment;
  1882. };
  1883. })();;
  1884. // lib/handlebars/utils.js
  1885. Handlebars.Exception = function(message) {
  1886. var tmp = Error.prototype.constructor.apply(this, arguments);
  1887. for (var p in tmp) {
  1888. if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; }
  1889. }
  1890. this.message = tmp.message;
  1891. };
  1892. Handlebars.Exception.prototype = new Error();
  1893. // Build out our basic SafeString type
  1894. Handlebars.SafeString = function(string) {
  1895. this.string = string;
  1896. };
  1897. Handlebars.SafeString.prototype.toString = function() {
  1898. return this.string.toString();
  1899. };
  1900. (function() {
  1901. var escape = {
  1902. "&": "&amp;",
  1903. "<": "&lt;",
  1904. ">": "&gt;",
  1905. '"': "&quot;",
  1906. "'": "&#x27;",
  1907. "`": "&#x60;"
  1908. };
  1909. var badChars = /[&<>"'`]/g;
  1910. var possible = /[&<>"'`]/;
  1911. var escapeChar = function(chr) {
  1912. return escape[chr] || "&amp;";
  1913. };
  1914. Handlebars.Utils = {
  1915. escapeExpression: function(string) {
  1916. // don't escape SafeStrings, since they're already safe
  1917. if (string instanceof Handlebars.SafeString) {
  1918. return string.toString();
  1919. } else if (string == null || string === false) {
  1920. return "";
  1921. }
  1922. if(!possible.test(string)) { return string; }
  1923. return string.replace(badChars, escapeChar);
  1924. },
  1925. isEmpty: function(value) {
  1926. if (typeof value === "undefined") {
  1927. return true;
  1928. } else if (value === null) {
  1929. return true;
  1930. } else if (value === false) {
  1931. return true;
  1932. } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
  1933. return true;
  1934. } else {
  1935. return false;
  1936. }
  1937. }
  1938. };
  1939. })();;
  1940. // lib/handlebars/compiler/compiler.js
  1941. /*jshint eqnull:true*/
  1942. Handlebars.Compiler = function() {};
  1943. Handlebars.JavaScriptCompiler = function() {};
  1944. (function(Compiler, JavaScriptCompiler) {
  1945. // the foundHelper register will disambiguate helper lookup from finding a
  1946. // function in a context. This is necessary for mustache compatibility, which
  1947. // requires that context functions in blocks are evaluated by blockHelperMissing,
  1948. // and then proceed as if the resulting value was provided to blockHelperMissing.
  1949. Compiler.prototype = {
  1950. compiler: Compiler,
  1951. disassemble: function() {
  1952. var opcodes = this.opcodes, opcode, out = [], params, param;
  1953. for (var i=0, l=opcodes.length; i<l; i++) {
  1954. opcode = opcodes[i];
  1955. if (opcode.opcode === 'DECLARE') {
  1956. out.push("DECLARE " + opcode.name + "=" + opcode.value);
  1957. } else {
  1958. params = [];
  1959. for (var j=0; j<opcode.args.length; j++) {
  1960. param = opcode.args[j];
  1961. if (typeof param === "string") {
  1962. param = "\"" + param.replace("\n", "\\n") + "\"";
  1963. }
  1964. params.push(param);
  1965. }
  1966. out.push(opcode.opcode + " " + params.join(" "));
  1967. }
  1968. }
  1969. return out.join("\n");
  1970. },
  1971. guid: 0,
  1972. compile: function(program, options) {
  1973. this.children = [];
  1974. this.depths = {list: []};
  1975. this.options = options;
  1976. // These changes will propagate to the other compiler components
  1977. var knownHelpers = this.options.knownHelpers;
  1978. this.options.knownHelpers = {
  1979. 'helperMissing': true,
  1980. 'blockHelperMissing': true,
  1981. 'each': true,
  1982. 'if': true,
  1983. 'unless': true,
  1984. 'with': true,
  1985. 'log': true
  1986. };
  1987. if (knownHelpers) {
  1988. for (var name in knownHelpers) {
  1989. this.options.knownHelpers[name] = knownHelpers[name];
  1990. }
  1991. }
  1992. return this.program(program);
  1993. },
  1994. accept: function(node) {
  1995. return this[node.type](node);
  1996. },
  1997. program: function(program) {
  1998. var statements = program.statements, statement;
  1999. this.opcodes = [];
  2000. for(var i=0, l=statements.length; i<l; i++) {
  2001. statement = statements[i];
  2002. this[statement.type](statement);
  2003. }
  2004. this.isSimple = l === 1;
  2005. this.depths.list = this.depths.list.sort(function(a, b) {
  2006. return a - b;
  2007. });
  2008. return this;
  2009. },
  2010. compileProgram: function(program) {
  2011. var result = new this.compiler().compile(program, this.options);
  2012. var guid = this.guid++, depth;
  2013. this.usePartial = this.usePartial || result.usePartial;
  2014. this.children[guid] = result;
  2015. for(var i=0, l=result.depths.list.length; i<l; i++) {
  2016. depth = result.depths.list[i];
  2017. if(depth < 2) { continue; }
  2018. else { this.addDepth(depth - 1); }
  2019. }
  2020. return guid;
  2021. },
  2022. block: function(block) {
  2023. var mustache = block.mustache,
  2024. program = block.program,
  2025. inverse = block.inverse;
  2026. if (program) {
  2027. program = this.compileProgram(program);
  2028. }
  2029. if (inverse) {
  2030. inverse = this.compileProgram(inverse);
  2031. }
  2032. var type = this.classifyMustache(mustache);
  2033. if (type === "helper") {
  2034. this.helperMustache(mustache, program, inverse);
  2035. } else if (type === "simple") {
  2036. this.simpleMustache(mustache);
  2037. // now that the simple mustache is resolved, we need to
  2038. // evaluate it by executing `blockHelperMissing`
  2039. this.opcode('pushProgram', program);
  2040. this.opcode('pushProgram', inverse);
  2041. this.opcode('pushLiteral', '{}');
  2042. this.opcode('blockValue');
  2043. } else {
  2044. this.ambiguousMustache(mustache, program, inverse);
  2045. // now that the simple mustache is resolved, we need to
  2046. // evaluate it by executing `blockHelperMissing`
  2047. this.opcode('pushProgram', program);
  2048. this.opcode('pushProgram', inverse);
  2049. this.opcode('pushLiteral', '{}');
  2050. this.opcode('ambiguousBlockValue');
  2051. }
  2052. this.opcode('append');
  2053. },
  2054. hash: function(hash) {
  2055. var pairs = hash.pairs, pair, val;
  2056. this.opcode('push', '{}');
  2057. for(var i=0, l=pairs.length; i<l; i++) {
  2058. pair = pairs[i];
  2059. val = pair[1];
  2060. this.accept(val);
  2061. this.opcode('assignToHash', pair[0]);
  2062. }
  2063. },
  2064. partial: function(partial) {
  2065. var id = partial.id;
  2066. this.usePartial = true;
  2067. if(partial.context) {
  2068. this.ID(partial.context);
  2069. } else {
  2070. this.opcode('push', 'depth0');
  2071. }
  2072. this.opcode('invokePartial', id.original);
  2073. this.opcode('append');
  2074. },
  2075. content: function(content) {
  2076. this.opcode('appendContent', content.string);
  2077. },
  2078. mustache: function(mustache) {
  2079. var options = this.options;
  2080. var type = this.classifyMustache(mustache);
  2081. if (type === "simple") {
  2082. this.simpleMustache(mustache);
  2083. } else if (type === "helper") {
  2084. this.helperMustache(mustache);
  2085. } else {
  2086. this.ambiguousMustache(mustache);
  2087. }
  2088. if(mustache.escaped && !options.noEscape) {
  2089. this.opcode('appendEscaped');
  2090. } else {
  2091. this.opcode('append');
  2092. }
  2093. },
  2094. ambiguousMustache: function(mustache, program, inverse) {
  2095. var id = mustache.id, name = id.parts[0];
  2096. this.opcode('getContext', id.depth);
  2097. this.opcode('pushProgram', program);
  2098. this.opcode('pushProgram', inverse);
  2099. this.opcode('invokeAmbiguous', name);
  2100. },
  2101. simpleMustache: function(mustache, program, inverse) {
  2102. var id = mustache.id;
  2103. if (id.type === 'DATA') {
  2104. this.DATA(id);
  2105. } else if (id.parts.length) {
  2106. this.ID(id);
  2107. } else {
  2108. // Simplified ID for `this`
  2109. this.addDepth(id.depth);
  2110. this.opcode('getContext', id.depth);
  2111. this.opcode('pushContext');
  2112. }
  2113. this.opcode('resolvePossibleLambda');
  2114. },
  2115. helperMustache: function(mustache, program, inverse) {
  2116. var params = this.setupFullMustacheParams(mustache, program, inverse),
  2117. name = mustache.id.parts[0];
  2118. if (this.options.knownHelpers[name]) {
  2119. this.opcode('invokeKnownHelper', params.length, name);
  2120. } else if (this.knownHelpersOnly) {
  2121. throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
  2122. } else {
  2123. this.opcode('invokeHelper', params.length, name);
  2124. }
  2125. },
  2126. ID: function(id) {
  2127. this.addDepth(id.depth);
  2128. this.opcode('getContext', id.depth);
  2129. var name = id.parts[0];
  2130. if (!name) {
  2131. this.opcode('pushContext');
  2132. } else {
  2133. this.opcode('lookupOnContext', id.parts[0]);
  2134. }
  2135. for(var i=1, l=id.parts.length; i<l; i++) {
  2136. this.opcode('lookup', id.parts[i]);
  2137. }
  2138. },
  2139. DATA: function(data) {
  2140. this.options.data = true;
  2141. this.opcode('lookupData', data.id);
  2142. },
  2143. STRING: function(string) {
  2144. this.opcode('pushString', string.string);
  2145. },
  2146. INTEGER: function(integer) {
  2147. this.opcode('pushLiteral', integer.integer);
  2148. },
  2149. BOOLEAN: function(bool) {
  2150. this.opcode('pushLiteral', bool.bool);
  2151. },
  2152. comment: function() {},
  2153. // HELPERS
  2154. opcode: function(name) {
  2155. this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
  2156. },
  2157. declare: function(name, value) {
  2158. this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
  2159. },
  2160. addDepth: function(depth) {
  2161. if(isNaN(depth)) { throw new Error("EWOT"); }
  2162. if(depth === 0) { return; }
  2163. if(!this.depths[depth]) {
  2164. this.depths[depth] = true;
  2165. this.depths.list.push(depth);
  2166. }
  2167. },
  2168. classifyMustache: function(mustache) {
  2169. var isHelper = mustache.isHelper;
  2170. var isEligible = mustache.eligibleHelper;
  2171. var options = this.options;
  2172. // if ambiguous, we can possibly resolve the ambiguity now
  2173. if (isEligible && !isHelper) {
  2174. var name = mustache.id.parts[0];
  2175. if (options.knownHelpers[name]) {
  2176. isHelper = true;
  2177. } else if (options.knownHelpersOnly) {
  2178. isEligible = false;
  2179. }
  2180. }
  2181. if (isHelper) { return "helper"; }
  2182. else if (isEligible) { return "ambiguous"; }
  2183. else { return "simple"; }
  2184. },
  2185. pushParams: function(params) {
  2186. var i = params.length, param;
  2187. while(i--) {
  2188. param = params[i];
  2189. if(this.options.stringParams) {
  2190. if(param.depth) {
  2191. this.addDepth(param.depth);
  2192. }
  2193. this.opcode('getContext', param.depth || 0);
  2194. this.opcode('pushStringParam', param.string);
  2195. } else {
  2196. this[param.type](param);
  2197. }
  2198. }
  2199. },
  2200. setupMustacheParams: function(mustache) {
  2201. var params = mustache.params;
  2202. this.pushParams(params);
  2203. if(mustache.hash) {
  2204. this.hash(mustache.hash);
  2205. } else {
  2206. this.opcode('pushLiteral', '{}');
  2207. }
  2208. return params;
  2209. },
  2210. // this will replace setupMustacheParams when we're done
  2211. setupFullMustacheParams: function(mustache, program, inverse) {
  2212. var params = mustache.params;
  2213. this.pushParams(params);
  2214. this.opcode('pushProgram', program);
  2215. this.opcode('pushProgram', inverse);
  2216. if(mustache.hash) {
  2217. this.hash(mustache.hash);
  2218. } else {
  2219. this.opcode('pushLiteral', '{}');
  2220. }
  2221. return params;
  2222. }
  2223. };
  2224. var Literal = function(value) {
  2225. this.value = value;
  2226. };
  2227. JavaScriptCompiler.prototype = {
  2228. // PUBLIC API: You can override these methods in a subclass to provide
  2229. // alternative compiled forms for name lookup and buffering semantics
  2230. nameLookup: function(parent, name, type) {
  2231. if (/^[0-9]+$/.test(name)) {
  2232. return parent + "[" + name + "]";
  2233. } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
  2234. return parent + "." + name;
  2235. }
  2236. else {
  2237. return parent + "['" + name + "']";
  2238. }
  2239. },
  2240. appendToBuffer: function(string) {
  2241. if (this.environment.isSimple) {
  2242. return "return " + string + ";";
  2243. } else {
  2244. return "buffer += " + string + ";";
  2245. }
  2246. },
  2247. initializeBuffer: function() {
  2248. return this.quotedString("");
  2249. },
  2250. namespace: "Handlebars",
  2251. // END PUBLIC API
  2252. compile: function(environment, options, context, asObject) {
  2253. this.environment = environment;
  2254. this.options = options || {};
  2255. Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n");
  2256. this.name = this.environment.name;
  2257. this.isChild = !!context;
  2258. this.context = context || {
  2259. programs: [],
  2260. aliases: { }
  2261. };
  2262. this.preamble();
  2263. this.stackSlot = 0;
  2264. this.stackVars = [];
  2265. this.registers = { list: [] };
  2266. this.compileStack = [];
  2267. this.compileChildren(environment, options);
  2268. var opcodes = environment.opcodes, opcode;
  2269. this.i = 0;
  2270. for(l=opcodes.length; this.i<l; this.i++) {
  2271. opcode = opcodes[this.i];
  2272. if(opcode.opcode === 'DECLARE') {
  2273. this[opcode.name] = opcode.value;
  2274. } else {
  2275. this[opcode.opcode].apply(this, opcode.args);
  2276. }
  2277. }
  2278. return this.createFunctionContext(asObject);
  2279. },
  2280. nextOpcode: function() {
  2281. var opcodes = this.environment.opcodes, opcode = opcodes[this.i + 1];
  2282. return opcodes[this.i + 1];
  2283. },
  2284. eat: function(opcode) {
  2285. this.i = this.i + 1;
  2286. },
  2287. preamble: function() {
  2288. var out = [];
  2289. if (!this.isChild) {
  2290. var namespace = this.namespace;
  2291. var copies = "helpers = helpers || " + namespace + ".helpers;";
  2292. if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
  2293. if (this.options.data) { copies = copies + " data = data || {};"; }
  2294. out.push(copies);
  2295. } else {
  2296. out.push('');
  2297. }
  2298. if (!this.environment.isSimple) {
  2299. out.push(", buffer = " + this.initializeBuffer());
  2300. } else {
  2301. out.push("");
  2302. }
  2303. // track the last context pushed into place to allow skipping the
  2304. // getContext opcode when it would be a noop
  2305. this.lastContext = 0;
  2306. this.source = out;
  2307. },
  2308. createFunctionContext: function(asObject) {
  2309. var locals = this.stackVars.concat(this.registers.list);
  2310. if(locals.length > 0) {
  2311. this.source[1] = this.source[1] + ", " + locals.join(", ");
  2312. }
  2313. // Generate minimizer alias mappings
  2314. if (!this.isChild) {
  2315. var aliases = [];
  2316. for (var alias in this.context.aliases) {
  2317. this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
  2318. }
  2319. }
  2320. if (this.source[1]) {
  2321. this.source[1] = "var " + this.source[1].substring(2) + ";";
  2322. }
  2323. // Merge children
  2324. if (!this.isChild) {
  2325. this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
  2326. }
  2327. if (!this.environment.isSimple) {
  2328. this.source.push("return buffer;");
  2329. }
  2330. var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
  2331. for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
  2332. params.push("depth" + this.environment.depths.list[i]);
  2333. }
  2334. if (asObject) {
  2335. params.push(this.source.join("\n "));
  2336. return Function.apply(this, params);
  2337. } else {
  2338. var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + this.source.join("\n ") + '}';
  2339. Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
  2340. return functionSource;
  2341. }
  2342. },
  2343. // [blockValue]
  2344. //
  2345. // On stack, before: hash, inverse, program, value
  2346. // On stack, after: return value of blockHelperMissing
  2347. //
  2348. // The purpose of this opcode is to take a block of the form
  2349. // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
  2350. // replace it on the stack with the result of properly
  2351. // invoking blockHelperMissing.
  2352. blockValue: function() {
  2353. this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
  2354. var params = ["depth0"];
  2355. this.setupParams(0, params);
  2356. this.replaceStack(function(current) {
  2357. params.splice(1, 0, current);
  2358. return current + " = blockHelperMissing.call(" + params.join(", ") + ")";
  2359. });
  2360. },
  2361. // [ambiguousBlockValue]
  2362. //
  2363. // On stack, before: hash, inverse, program, value
  2364. // Compiler value, before: lastHelper=value of last found helper, if any
  2365. // On stack, after, if no lastHelper: same as [blockValue]
  2366. // On stack, after, if lastHelper: value
  2367. ambiguousBlockValue: function() {
  2368. this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
  2369. var params = ["depth0"];
  2370. this.setupParams(0, params);
  2371. var current = this.topStack();
  2372. params.splice(1, 0, current);
  2373. this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
  2374. },
  2375. // [appendContent]
  2376. //
  2377. // On stack, before: ...
  2378. // On stack, after: ...
  2379. //
  2380. // Appends the string value of `content` to the current buffer
  2381. appendContent: function(content) {
  2382. this.source.push(this.appendToBuffer(this.quotedString(content)));
  2383. },
  2384. // [append]
  2385. //
  2386. // On stack, before: value, ...
  2387. // On stack, after: ...
  2388. //
  2389. // Coerces `value` to a String and appends it to the current buffer.
  2390. //
  2391. // If `value` is truthy, or 0, it is coerced into a string and appended
  2392. // Otherwise, the empty string is appended
  2393. append: function() {
  2394. var local = this.popStack();
  2395. this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
  2396. if (this.environment.isSimple) {
  2397. this.source.push("else { " + this.appendToBuffer("''") + " }");
  2398. }
  2399. },
  2400. // [appendEscaped]
  2401. //
  2402. // On stack, before: value, ...
  2403. // On stack, after: ...
  2404. //
  2405. // Escape `value` and append it to the buffer
  2406. appendEscaped: function() {
  2407. var opcode = this.nextOpcode(), extra = "";
  2408. this.context.aliases.escapeExpression = 'this.escapeExpression';
  2409. if(opcode && opcode.opcode === 'appendContent') {
  2410. extra = " + " + this.quotedString(opcode.args[0]);
  2411. this.eat(opcode);
  2412. }
  2413. this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")" + extra));
  2414. },
  2415. // [getContext]
  2416. //
  2417. // On stack, before: ...
  2418. // On stack, after: ...
  2419. // Compiler value, after: lastContext=depth
  2420. //
  2421. // Set the value of the `lastContext` compiler value to the depth
  2422. getContext: function(depth) {
  2423. if(this.lastContext !== depth) {
  2424. this.lastContext = depth;
  2425. }
  2426. },
  2427. // [lookupOnContext]
  2428. //
  2429. // On stack, before: ...
  2430. // On stack, after: currentContext[name], ...
  2431. //
  2432. // Looks up the value of `name` on the current context and pushes
  2433. // it onto the stack.
  2434. lookupOnContext: function(name) {
  2435. this.pushStack(this.nameLookup('depth' + this.lastContext, name, 'context'));
  2436. },
  2437. // [pushContext]
  2438. //
  2439. // On stack, before: ...
  2440. // On stack, after: currentContext, ...
  2441. //
  2442. // Pushes the value of the current context onto the stack.
  2443. pushContext: function() {
  2444. this.pushStackLiteral('depth' + this.lastContext);
  2445. },
  2446. // [resolvePossibleLambda]
  2447. //
  2448. // On stack, before: value, ...
  2449. // On stack, after: resolved value, ...
  2450. //
  2451. // If the `value` is a lambda, replace it on the stack by
  2452. // the return value of the lambda
  2453. resolvePossibleLambda: function() {
  2454. this.context.aliases.functionType = '"function"';
  2455. this.replaceStack(function(current) {
  2456. return "typeof " + current + " === functionType ? " + current + "() : " + current;
  2457. });
  2458. },
  2459. // [lookup]
  2460. //
  2461. // On stack, before: value, ...
  2462. // On stack, after: value[name], ...
  2463. //
  2464. // Replace the value on the stack with the result of looking
  2465. // up `name` on `value`
  2466. lookup: function(name) {
  2467. this.replaceStack(function(current) {
  2468. return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
  2469. });
  2470. },
  2471. // [lookupData]
  2472. //
  2473. // On stack, before: ...
  2474. // On stack, after: data[id], ...
  2475. //
  2476. // Push the result of looking up `id` on the current data
  2477. lookupData: function(id) {
  2478. this.pushStack(this.nameLookup('data', id, 'data'));
  2479. },
  2480. // [pushStringParam]
  2481. //
  2482. // On stack, before: ...
  2483. // On stack, after: string, currentContext, ...
  2484. //
  2485. // This opcode is designed for use in string mode, which
  2486. // provides the string value of a parameter along with its
  2487. // depth rather than resolving it immediately.
  2488. pushStringParam: function(string) {
  2489. this.pushStackLiteral('depth' + this.lastContext);
  2490. this.pushString(string);
  2491. },
  2492. // [pushString]
  2493. //
  2494. // On stack, before: ...
  2495. // On stack, after: quotedString(string), ...
  2496. //
  2497. // Push a quoted version of `string` onto the stack
  2498. pushString: function(string) {
  2499. this.pushStackLiteral(this.quotedString(string));
  2500. },
  2501. // [push]
  2502. //
  2503. // On stack, before: ...
  2504. // On stack, after: expr, ...
  2505. //
  2506. // Push an expression onto the stack
  2507. push: function(expr) {
  2508. this.pushStack(expr);
  2509. },
  2510. // [pushLiteral]
  2511. //
  2512. // On stack, before: ...
  2513. // On stack, after: value, ...
  2514. //
  2515. // Pushes a value onto the stack. This operation prevents
  2516. // the compiler from creating a temporary variable to hold
  2517. // it.
  2518. pushLiteral: function(value) {
  2519. this.pushStackLiteral(value);
  2520. },
  2521. // [pushProgram]
  2522. //
  2523. // On stack, before: ...
  2524. // On stack, after: program(guid), ...
  2525. //
  2526. // Push a program expression onto the stack. This takes
  2527. // a compile-time guid and converts it into a runtime-accessible
  2528. // expression.
  2529. pushProgram: function(guid) {
  2530. if (guid != null) {
  2531. this.pushStackLiteral(this.programExpression(guid));
  2532. } else {
  2533. this.pushStackLiteral(null);
  2534. }
  2535. },
  2536. // [invokeHelper]
  2537. //
  2538. // On stack, before: hash, inverse, program, params..., ...
  2539. // On stack, after: result of helper invocation
  2540. //
  2541. // Pops off the helper's parameters, invokes the helper,
  2542. // and pushes the helper's return value onto the stack.
  2543. //
  2544. // If the helper is not found, `helperMissing` is called.
  2545. invokeHelper: function(paramSize, name) {
  2546. this.context.aliases.helperMissing = 'helpers.helperMissing';
  2547. var helper = this.lastHelper = this.setupHelper(paramSize, name);
  2548. this.register('foundHelper', helper.name);
  2549. this.pushStack("foundHelper ? foundHelper.call(" +
  2550. helper.callParams + ") " + ": helperMissing.call(" +
  2551. helper.helperMissingParams + ")");
  2552. },
  2553. // [invokeKnownHelper]
  2554. //
  2555. // On stack, before: hash, inverse, program, params..., ...
  2556. // On stack, after: result of helper invocation
  2557. //
  2558. // This operation is used when the helper is known to exist,
  2559. // so a `helperMissing` fallback is not required.
  2560. invokeKnownHelper: function(paramSize, name) {
  2561. var helper = this.setupHelper(paramSize, name);
  2562. this.pushStack(helper.name + ".call(" + helper.callParams + ")");
  2563. },
  2564. // [invokeAmbiguous]
  2565. //
  2566. // On stack, before: hash, inverse, program, params..., ...
  2567. // On stack, after: result of disambiguation
  2568. //
  2569. // This operation is used when an expression like `{{foo}}`
  2570. // is provided, but we don't know at compile-time whether it
  2571. // is a helper or a path.
  2572. //
  2573. // This operation emits more code than the other options,
  2574. // and can be avoided by passing the `knownHelpers` and
  2575. // `knownHelpersOnly` flags at compile-time.
  2576. invokeAmbiguous: function(name) {
  2577. this.context.aliases.functionType = '"function"';
  2578. this.pushStackLiteral('{}');
  2579. var helper = this.setupHelper(0, name);
  2580. var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
  2581. this.register('foundHelper', helperName);
  2582. var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
  2583. var nextStack = this.nextStack();
  2584. this.source.push('if (foundHelper) { ' + nextStack + ' = foundHelper.call(' + helper.callParams + '); }');
  2585. this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '() : ' + nextStack + '; }');
  2586. },
  2587. // [invokePartial]
  2588. //
  2589. // On stack, before: context, ...
  2590. // On stack after: result of partial invocation
  2591. //
  2592. // This operation pops off a context, invokes a partial with that context,
  2593. // and pushes the result of the invocation back.
  2594. invokePartial: function(name) {
  2595. var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
  2596. if (this.options.data) {
  2597. params.push("data");
  2598. }
  2599. this.context.aliases.self = "this";
  2600. this.pushStack("self.invokePartial(" + params.join(", ") + ");");
  2601. },
  2602. // [assignToHash]
  2603. //
  2604. // On stack, before: value, hash, ...
  2605. // On stack, after: hash, ...
  2606. //
  2607. // Pops a value and hash off the stack, assigns `hash[key] = value`
  2608. // and pushes the hash back onto the stack.
  2609. assignToHash: function(key) {
  2610. var value = this.popStack();
  2611. var hash = this.topStack();
  2612. this.source.push(hash + "['" + key + "'] = " + value + ";");
  2613. },
  2614. // HELPERS
  2615. compiler: JavaScriptCompiler,
  2616. compileChildren: function(environment, options) {
  2617. var children = environment.children, child, compiler;
  2618. for(var i=0, l=children.length; i<l; i++) {
  2619. child = children[i];
  2620. compiler = new this.compiler();
  2621. this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
  2622. var index = this.context.programs.length;
  2623. child.index = index;
  2624. child.name = 'program' + index;
  2625. this.context.programs[index] = compiler.compile(child, options, this.context);
  2626. }
  2627. },
  2628. programExpression: function(guid) {
  2629. this.context.aliases.self = "this";
  2630. if(guid == null) {
  2631. return "self.noop";
  2632. }
  2633. var child = this.environment.children[guid],
  2634. depths = child.depths.list, depth;
  2635. var programParams = [child.index, child.name, "data"];
  2636. for(var i=0, l = depths.length; i<l; i++) {
  2637. depth = depths[i];
  2638. if(depth === 1) { programParams.push("depth0"); }
  2639. else { programParams.push("depth" + (depth - 1)); }
  2640. }
  2641. if(depths.length === 0) {
  2642. return "self.program(" + programParams.join(", ") + ")";
  2643. } else {
  2644. programParams.shift();
  2645. return "self.programWithDepth(" + programParams.join(", ") + ")";
  2646. }
  2647. },
  2648. register: function(name, val) {
  2649. this.useRegister(name);
  2650. this.source.push(name + " = " + val + ";");
  2651. },
  2652. useRegister: function(name) {
  2653. if(!this.registers[name]) {
  2654. this.registers[name] = true;
  2655. this.registers.list.push(name);
  2656. }
  2657. },
  2658. pushStackLiteral: function(item) {
  2659. this.compileStack.push(new Literal(item));
  2660. return item;
  2661. },
  2662. pushStack: function(item) {
  2663. this.source.push(this.incrStack() + " = " + item + ";");
  2664. this.compileStack.push("stack" + this.stackSlot);
  2665. return "stack" + this.stackSlot;
  2666. },
  2667. replaceStack: function(callback) {
  2668. var item = callback.call(this, this.topStack());
  2669. this.source.push(this.topStack() + " = " + item + ";");
  2670. return "stack" + this.stackSlot;
  2671. },
  2672. nextStack: function(skipCompileStack) {
  2673. var name = this.incrStack();
  2674. this.compileStack.push("stack" + this.stackSlot);
  2675. return name;
  2676. },
  2677. incrStack: function() {
  2678. this.stackSlot++;
  2679. if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
  2680. return "stack" + this.stackSlot;
  2681. },
  2682. popStack: function() {
  2683. var item = this.compileStack.pop();
  2684. if (item instanceof Literal) {
  2685. return item.value;
  2686. } else {
  2687. this.stackSlot--;
  2688. return item;
  2689. }
  2690. },
  2691. topStack: function() {
  2692. var item = this.compileStack[this.compileStack.length - 1];
  2693. if (item instanceof Literal) {
  2694. return item.value;
  2695. } else {
  2696. return item;
  2697. }
  2698. },
  2699. quotedString: function(str) {
  2700. return '"' + str
  2701. .replace(/\\/g, '\\\\')
  2702. .replace(/"/g, '\\"')
  2703. .replace(/\n/g, '\\n')
  2704. .replace(/\r/g, '\\r') + '"';
  2705. },
  2706. setupHelper: function(paramSize, name) {
  2707. var params = [];
  2708. this.setupParams(paramSize, params);
  2709. var foundHelper = this.nameLookup('helpers', name, 'helper');
  2710. return {
  2711. params: params,
  2712. name: foundHelper,
  2713. callParams: ["depth0"].concat(params).join(", "),
  2714. helperMissingParams: ["depth0", this.quotedString(name)].concat(params).join(", ")
  2715. };
  2716. },
  2717. // the params and contexts arguments are passed in arrays
  2718. // to fill in
  2719. setupParams: function(paramSize, params) {
  2720. var options = [], contexts = [], param, inverse, program;
  2721. options.push("hash:" + this.popStack());
  2722. inverse = this.popStack();
  2723. program = this.popStack();
  2724. // Avoid setting fn and inverse if neither are set. This allows
  2725. // helpers to do a check for `if (options.fn)`
  2726. if (program || inverse) {
  2727. if (!program) {
  2728. this.context.aliases.self = "this";
  2729. program = "self.noop";
  2730. }
  2731. if (!inverse) {
  2732. this.context.aliases.self = "this";
  2733. inverse = "self.noop";
  2734. }
  2735. options.push("inverse:" + inverse);
  2736. options.push("fn:" + program);
  2737. }
  2738. for(var i=0; i<paramSize; i++) {
  2739. param = this.popStack();
  2740. params.push(param);
  2741. if(this.options.stringParams) {
  2742. contexts.push(this.popStack());
  2743. }
  2744. }
  2745. if (this.options.stringParams) {
  2746. options.push("contexts:[" + contexts.join(",") + "]");
  2747. }
  2748. if(this.options.data) {
  2749. options.push("data:data");
  2750. }
  2751. params.push("{" + options.join(",") + "}");
  2752. return params.join(", ");
  2753. }
  2754. };
  2755. var reservedWords = (
  2756. "break else new var" +
  2757. " case finally return void" +
  2758. " catch for switch while" +
  2759. " continue function this with" +
  2760. " default if throw" +
  2761. " delete in try" +
  2762. " do instanceof typeof" +
  2763. " abstract enum int short" +
  2764. " boolean export interface static" +
  2765. " byte extends long super" +
  2766. " char final native synchronized" +
  2767. " class float package throws" +
  2768. " const goto private transient" +
  2769. " debugger implements protected volatile" +
  2770. " double import public let yield"
  2771. ).split(" ");
  2772. var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
  2773. for(var i=0, l=reservedWords.length; i<l; i++) {
  2774. compilerWords[reservedWords[i]] = true;
  2775. }
  2776. JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
  2777. if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) {
  2778. return true;
  2779. }
  2780. return false;
  2781. };
  2782. })(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
  2783. Handlebars.precompile = function(string, options) {
  2784. options = options || {};
  2785. if (!('data' in options)) {
  2786. options.data = true;
  2787. }
  2788. var ast = Handlebars.parse(string);
  2789. var environment = new Handlebars.Compiler().compile(ast, options);
  2790. return new Handlebars.JavaScriptCompiler().compile(environment, options);
  2791. };
  2792. Handlebars.compile = function(string, options) {
  2793. options = options || {};
  2794. if (!('data' in options)) {
  2795. options.data = true;
  2796. }
  2797. var compiled;
  2798. function compile() {
  2799. var ast = Handlebars.parse(string);
  2800. var environment = new Handlebars.Compiler().compile(ast, options);
  2801. var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
  2802. return Handlebars.template(templateSpec);
  2803. }
  2804. // Template is only compiled on first use and cached after that point.
  2805. return function(context, options) {
  2806. if (!compiled) {
  2807. compiled = compile();
  2808. }
  2809. return compiled.call(this, context, options);
  2810. };
  2811. };
  2812. ;
  2813. // lib/handlebars/runtime.js
  2814. Handlebars.VM = {
  2815. template: function(templateSpec) {
  2816. // Just add water
  2817. var container = {
  2818. escapeExpression: Handlebars.Utils.escapeExpression,
  2819. invokePartial: Handlebars.VM.invokePartial,
  2820. programs: [],
  2821. program: function(i, fn, data) {
  2822. var programWrapper = this.programs[i];
  2823. if(data) {
  2824. programWrapper = Handlebars.VM.program(fn, data);
  2825. programWrapper.program = i;
  2826. } else if (!programWrapper) {
  2827. programWrapper = this.programs[i] = Handlebars.VM.program(fn);
  2828. programWrapper.program = i;
  2829. }
  2830. return programWrapper;
  2831. },
  2832. programWithDepth: Handlebars.VM.programWithDepth,
  2833. noop: Handlebars.VM.noop
  2834. };
  2835. return function(context, options) {
  2836. options = options || {};
  2837. return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
  2838. };
  2839. },
  2840. programWithDepth: function(fn, data, $depth) {
  2841. var args = Array.prototype.slice.call(arguments, 2);
  2842. return function(context, options) {
  2843. options = options || {};
  2844. return fn.apply(this, [context, options.data || data].concat(args));
  2845. };
  2846. },
  2847. program: function(fn, data) {
  2848. return function(context, options) {
  2849. options = options || {};
  2850. return fn(context, options.data || data);
  2851. };
  2852. },
  2853. noop: function() { return ""; },
  2854. invokePartial: function(partial, name, context, helpers, partials, data) {
  2855. var options = { helpers: helpers, partials: partials, data: data };
  2856. if(partial === undefined) {
  2857. throw new Handlebars.Exception("The partial " + name + " could not be found");
  2858. } else if(partial instanceof Function) {
  2859. return partial(context, options);
  2860. } else if (!Handlebars.compile) {
  2861. throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
  2862. } else {
  2863. partials[name] = Handlebars.compile(partial, {data: data !== undefined});
  2864. return partials[name](context, options);
  2865. }
  2866. }
  2867. };
  2868. Handlebars.template = Handlebars.VM.template;
  2869. ;
  2870. // Underscore.js 1.4.2
  2871. // http://underscorejs.org
  2872. // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
  2873. // Underscore may be freely distributed under the MIT license.
  2874. (function() {
  2875. // Baseline setup
  2876. // --------------
  2877. // Establish the root object, `window` in the browser, or `global` on the server.
  2878. var root = this;
  2879. // Save the previous value of the `_` variable.
  2880. var previousUnderscore = root._;
  2881. // Establish the object that gets returned to break out of a loop iteration.
  2882. var breaker = {};
  2883. // Save bytes in the minified (but not gzipped) version:
  2884. var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
  2885. // Create quick reference variables for speed access to core prototypes.
  2886. var push = ArrayProto.push,
  2887. slice = ArrayProto.slice,
  2888. concat = ArrayProto.concat,
  2889. unshift = ArrayProto.unshift,
  2890. toString = ObjProto.toString,
  2891. hasOwnProperty = ObjProto.hasOwnProperty;
  2892. // All **ECMAScript 5** native function implementations that we hope to use
  2893. // are declared here.
  2894. var
  2895. nativeForEach = ArrayProto.forEach,
  2896. nativeMap = ArrayProto.map,
  2897. nativeReduce = ArrayProto.reduce,
  2898. nativeReduceRight = ArrayProto.reduceRight,
  2899. nativeFilter = ArrayProto.filter,
  2900. nativeEvery = ArrayProto.every,
  2901. nativeSome = ArrayProto.some,
  2902. nativeIndexOf = ArrayProto.indexOf,
  2903. nativeLastIndexOf = ArrayProto.lastIndexOf,
  2904. nativeIsArray = Array.isArray,
  2905. nativeKeys = Object.keys,
  2906. nativeBind = FuncProto.bind;
  2907. // Create a safe reference to the Underscore object for use below.
  2908. var _ = function(obj) {
  2909. if (obj instanceof _) return obj;
  2910. if (!(this instanceof _)) return new _(obj);
  2911. this._wrapped = obj;
  2912. };
  2913. // Export the Underscore object for **Node.js**, with
  2914. // backwards-compatibility for the old `require()` API. If we're in
  2915. // the browser, add `_` as a global object via a string identifier,
  2916. // for Closure Compiler "advanced" mode.
  2917. if (typeof exports !== 'undefined') {
  2918. if (typeof module !== 'undefined' && module.exports) {
  2919. exports = module.exports = _;
  2920. }
  2921. exports._ = _;
  2922. } else {
  2923. root['_'] = _;
  2924. }
  2925. // Current version.
  2926. _.VERSION = '1.4.2';
  2927. // Collection Functions
  2928. // --------------------
  2929. // The cornerstone, an `each` implementation, aka `forEach`.
  2930. // Handles objects with the built-in `forEach`, arrays, and raw objects.
  2931. // Delegates to **ECMAScript 5**'s native `forEach` if available.
  2932. var each = _.each = _.forEach = function(obj, iterator, context) {
  2933. if (obj == null) return;
  2934. if (nativeForEach && obj.forEach === nativeForEach) {
  2935. obj.forEach(iterator, context);
  2936. } else if (obj.length === +obj.length) {
  2937. for (var i = 0, l = obj.length; i < l; i++) {
  2938. if (iterator.call(context, obj[i], i, obj) === breaker) return;
  2939. }
  2940. } else {
  2941. for (var key in obj) {
  2942. if (_.has(obj, key)) {
  2943. if (iterator.call(context, obj[key], key, obj) === breaker) return;
  2944. }
  2945. }
  2946. }
  2947. };
  2948. // Return the results of applying the iterator to each element.
  2949. // Delegates to **ECMAScript 5**'s native `map` if available.
  2950. _.map = _.collect = function(obj, iterator, context) {
  2951. var results = [];
  2952. if (obj == null) return results;
  2953. if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
  2954. each(obj, function(value, index, list) {
  2955. results[results.length] = iterator.call(context, value, index, list);
  2956. });
  2957. return results;
  2958. };
  2959. // **Reduce** builds up a single result from a list of values, aka `inject`,
  2960. // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
  2961. _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
  2962. var initial = arguments.length > 2;
  2963. if (obj == null) obj = [];
  2964. if (nativeReduce && obj.reduce === nativeReduce) {
  2965. if (context) iterator = _.bind(iterator, context);
  2966. return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
  2967. }
  2968. each(obj, function(value, index, list) {
  2969. if (!initial) {
  2970. memo = value;
  2971. initial = true;
  2972. } else {
  2973. memo = iterator.call(context, memo, value, index, list);
  2974. }
  2975. });
  2976. if (!initial) throw new TypeError('Reduce of empty array with no initial value');
  2977. return memo;
  2978. };
  2979. // The right-associative version of reduce, also known as `foldr`.
  2980. // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
  2981. _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
  2982. var initial = arguments.length > 2;
  2983. if (obj == null) obj = [];
  2984. if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
  2985. if (context) iterator = _.bind(iterator, context);
  2986. return arguments.length > 2 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
  2987. }
  2988. var length = obj.length;
  2989. if (length !== +length) {
  2990. var keys = _.keys(obj);
  2991. length = keys.length;
  2992. }
  2993. each(obj, function(value, index, list) {
  2994. index = keys ? keys[--length] : --length;
  2995. if (!initial) {
  2996. memo = obj[index];
  2997. initial = true;
  2998. } else {
  2999. memo = iterator.call(context, memo, obj[index], index, list);
  3000. }
  3001. });
  3002. if (!initial) throw new TypeError('Reduce of empty array with no initial value');
  3003. return memo;
  3004. };
  3005. // Return the first value which passes a truth test. Aliased as `detect`.
  3006. _.find = _.detect = function(obj, iterator, context) {
  3007. var result;
  3008. any(obj, function(value, index, list) {
  3009. if (iterator.call(context, value, index, list)) {
  3010. result = value;
  3011. return true;
  3012. }
  3013. });
  3014. return result;
  3015. };
  3016. // Return all the elements that pass a truth test.
  3017. // Delegates to **ECMAScript 5**'s native `filter` if available.
  3018. // Aliased as `select`.
  3019. _.filter = _.select = function(obj, iterator, context) {
  3020. var results = [];
  3021. if (obj == null) return results;
  3022. if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
  3023. each(obj, function(value, index, list) {
  3024. if (iterator.call(context, value, index, list)) results[results.length] = value;
  3025. });
  3026. return results;
  3027. };
  3028. // Return all the elements for which a truth test fails.
  3029. _.reject = function(obj, iterator, context) {
  3030. var results = [];
  3031. if (obj == null) return results;
  3032. each(obj, function(value, index, list) {
  3033. if (!iterator.call(context, value, index, list)) results[results.length] = value;
  3034. });
  3035. return results;
  3036. };
  3037. // Determine whether all of the elements match a truth test.
  3038. // Delegates to **ECMAScript 5**'s native `every` if available.
  3039. // Aliased as `all`.
  3040. _.every = _.all = function(obj, iterator, context) {
  3041. iterator || (iterator = _.identity);
  3042. var result = true;
  3043. if (obj == null) return result;
  3044. if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
  3045. each(obj, function(value, index, list) {
  3046. if (!(result = result && iterator.call(context, value, index, list))) return breaker;
  3047. });
  3048. return !!result;
  3049. };
  3050. // Determine if at least one element in the object matches a truth test.
  3051. // Delegates to **ECMAScript 5**'s native `some` if available.
  3052. // Aliased as `any`.
  3053. var any = _.some = _.any = function(obj, iterator, context) {
  3054. iterator || (iterator = _.identity);
  3055. var result = false;
  3056. if (obj == null) return result;
  3057. if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
  3058. each(obj, function(value, index, list) {
  3059. if (result || (result = iterator.call(context, value, index, list))) return breaker;
  3060. });
  3061. return !!result;
  3062. };
  3063. // Determine if the array or object contains a given value (using `===`).
  3064. // Aliased as `include`.
  3065. _.contains = _.include = function(obj, target) {
  3066. var found = false;
  3067. if (obj == null) return found;
  3068. if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
  3069. found = any(obj, function(value) {
  3070. return value === target;
  3071. });
  3072. return found;
  3073. };
  3074. // Invoke a method (with arguments) on every item in a collection.
  3075. _.invoke = function(obj, method) {
  3076. var args = slice.call(arguments, 2);
  3077. return _.map(obj, function(value) {
  3078. return (_.isFunction(method) ? method : value[method]).apply(value, args);
  3079. });
  3080. };
  3081. // Convenience version of a common use case of `map`: fetching a property.
  3082. _.pluck = function(obj, key) {
  3083. return _.map(obj, function(value){ return value[key]; });
  3084. };
  3085. // Convenience version of a common use case of `filter`: selecting only objects
  3086. // with specific `key:value` pairs.
  3087. _.where = function(obj, attrs) {
  3088. if (_.isEmpty(attrs)) return [];
  3089. return _.filter(obj, function(value) {
  3090. for (var key in attrs) {
  3091. if (attrs[key] !== value[key]) return false;
  3092. }
  3093. return true;
  3094. });
  3095. };
  3096. // Return the maximum element or (element-based computation).
  3097. // Can't optimize arrays of integers longer than 65,535 elements.
  3098. // See: https://bugs.webkit.org/show_bug.cgi?id=80797
  3099. _.max = function(obj, iterator, context) {
  3100. if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
  3101. return Math.max.apply(Math, obj);
  3102. }
  3103. if (!iterator && _.isEmpty(obj)) return -Infinity;
  3104. var result = {computed : -Infinity};
  3105. each(obj, function(value, index, list) {
  3106. var computed = iterator ? iterator.call(context, value, index, list) : value;
  3107. computed >= result.computed && (result = {value : value, computed : computed});
  3108. });
  3109. return result.value;
  3110. };
  3111. // Return the minimum element (or element-based computation).
  3112. _.min = function(obj, iterator, context) {
  3113. if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
  3114. return Math.min.apply(Math, obj);
  3115. }
  3116. if (!iterator && _.isEmpty(obj)) return Infinity;
  3117. var result = {computed : Infinity};
  3118. each(obj, function(value, index, list) {
  3119. var computed = iterator ? iterator.call(context, value, index, list) : value;
  3120. computed < result.computed && (result = {value : value, computed : computed});
  3121. });
  3122. return result.value;
  3123. };
  3124. // Shuffle an array.
  3125. _.shuffle = function(obj) {
  3126. var rand;
  3127. var index = 0;
  3128. var shuffled = [];
  3129. each(obj, function(value) {
  3130. rand = _.random(index++);
  3131. shuffled[index - 1] = shuffled[rand];
  3132. shuffled[rand] = value;
  3133. });
  3134. return shuffled;
  3135. };
  3136. // An internal function to generate lookup iterators.
  3137. var lookupIterator = function(value) {
  3138. return _.isFunction(value) ? value : function(obj){ return obj[value]; };
  3139. };
  3140. // Sort the object's values by a criterion produced by an iterator.
  3141. _.sortBy = function(obj, value, context) {
  3142. var iterator = lookupIterator(value);
  3143. return _.pluck(_.map(obj, function(value, index, list) {
  3144. return {
  3145. value : value,
  3146. index : index,
  3147. criteria : iterator.call(context, value, index, list)
  3148. };
  3149. }).sort(function(left, right) {
  3150. var a = left.criteria;
  3151. var b = right.criteria;
  3152. if (a !== b) {
  3153. if (a > b || a === void 0) return 1;
  3154. if (a < b || b === void 0) return -1;
  3155. }
  3156. return left.index < right.index ? -1 : 1;
  3157. }), 'value');
  3158. };
  3159. // An internal function used for aggregate "group by" operations.
  3160. var group = function(obj, value, context, behavior) {
  3161. var result = {};
  3162. var iterator = lookupIterator(value);
  3163. each(obj, function(value, index) {
  3164. var key = iterator.call(context, value, index, obj);
  3165. behavior(result, key, value);
  3166. });
  3167. return result;
  3168. };
  3169. // Groups the object's values by a criterion. Pass either a string attribute
  3170. // to group by, or a function that returns the criterion.
  3171. _.groupBy = function(obj, value, context) {
  3172. return group(obj, value, context, function(result, key, value) {
  3173. (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
  3174. });
  3175. };
  3176. // Counts instances of an object that group by a certain criterion. Pass
  3177. // either a string attribute to count by, or a function that returns the
  3178. // criterion.
  3179. _.countBy = function(obj, value, context) {
  3180. return group(obj, value, context, function(result, key, value) {
  3181. if (!_.has(result, key)) result[key] = 0;
  3182. result[key]++;
  3183. });
  3184. };
  3185. // Use a comparator function to figure out the smallest index at which
  3186. // an object should be inserted so as to maintain order. Uses binary search.
  3187. _.sortedIndex = function(array, obj, iterator, context) {
  3188. iterator = iterator == null ? _.identity : lookupIterator(iterator);
  3189. var value = iterator.call(context, obj);
  3190. var low = 0, high = array.length;
  3191. while (low < high) {
  3192. var mid = (low + high) >>> 1;
  3193. iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
  3194. }
  3195. return low;
  3196. };
  3197. // Safely convert anything iterable into a real, live array.
  3198. _.toArray = function(obj) {
  3199. if (!obj) return [];
  3200. if (obj.length === +obj.length) return slice.call(obj);
  3201. return _.values(obj);
  3202. };
  3203. // Return the number of elements in an object.
  3204. _.size = function(obj) {
  3205. return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
  3206. };
  3207. // Array Functions
  3208. // ---------------
  3209. // Get the first element of an array. Passing **n** will return the first N
  3210. // values in the array. Aliased as `head` and `take`. The **guard** check
  3211. // allows it to work with `_.map`.
  3212. _.first = _.head = _.take = function(array, n, guard) {
  3213. return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
  3214. };
  3215. // Returns everything but the last entry of the array. Especially useful on
  3216. // the arguments object. Passing **n** will return all the values in
  3217. // the array, excluding the last N. The **guard** check allows it to work with
  3218. // `_.map`.
  3219. _.initial = function(array, n, guard) {
  3220. return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
  3221. };
  3222. // Get the last element of an array. Passing **n** will return the last N
  3223. // values in the array. The **guard** check allows it to work with `_.map`.
  3224. _.last = function(array, n, guard) {
  3225. if ((n != null) && !guard) {
  3226. return slice.call(array, Math.max(array.length - n, 0));
  3227. } else {
  3228. return array[array.length - 1];
  3229. }
  3230. };
  3231. // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
  3232. // Especially useful on the arguments object. Passing an **n** will return
  3233. // the rest N values in the array. The **guard**
  3234. // check allows it to work with `_.map`.
  3235. _.rest = _.tail = _.drop = function(array, n, guard) {
  3236. return slice.call(array, (n == null) || guard ? 1 : n);
  3237. };
  3238. // Trim out all falsy values from an array.
  3239. _.compact = function(array) {
  3240. return _.filter(array, function(value){ return !!value; });
  3241. };
  3242. // Internal implementation of a recursive `flatten` function.
  3243. var flatten = function(input, shallow, output) {
  3244. each(input, function(value) {
  3245. if (_.isArray(value)) {
  3246. shallow ? push.apply(output, value) : flatten(value, shallow, output);
  3247. } else {
  3248. output.push(value);
  3249. }
  3250. });
  3251. return output;
  3252. };
  3253. // Return a completely flattened version of an array.
  3254. _.flatten = function(array, shallow) {
  3255. return flatten(array, shallow, []);
  3256. };
  3257. // Return a version of the array that does not contain the specified value(s).
  3258. _.without = function(array) {
  3259. return _.difference(array, slice.call(arguments, 1));
  3260. };
  3261. // Produce a duplicate-free version of the array. If the array has already
  3262. // been sorted, you have the option of using a faster algorithm.
  3263. // Aliased as `unique`.
  3264. _.uniq = _.unique = function(array, isSorted, iterator, context) {
  3265. var initial = iterator ? _.map(array, iterator, context) : array;
  3266. var results = [];
  3267. var seen = [];
  3268. each(initial, function(value, index) {
  3269. if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
  3270. seen.push(value);
  3271. results.push(array[index]);
  3272. }
  3273. });
  3274. return results;
  3275. };
  3276. // Produce an array that contains the union: each distinct element from all of
  3277. // the passed-in arrays.
  3278. _.union = function() {
  3279. return _.uniq(concat.apply(ArrayProto, arguments));
  3280. };
  3281. // Produce an array that contains every item shared between all the
  3282. // passed-in arrays.
  3283. _.intersection = function(array) {
  3284. var rest = slice.call(arguments, 1);
  3285. return _.filter(_.uniq(array), function(item) {
  3286. return _.every(rest, function(other) {
  3287. return _.indexOf(other, item) >= 0;
  3288. });
  3289. });
  3290. };
  3291. // Take the difference between one array and a number of other arrays.
  3292. // Only the elements present in just the first array will remain.
  3293. _.difference = function(array) {
  3294. var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
  3295. return _.filter(array, function(value){ return !_.contains(rest, value); });
  3296. };
  3297. // Zip together multiple lists into a single array -- elements that share
  3298. // an index go together.
  3299. _.zip = function() {
  3300. var args = slice.call(arguments);
  3301. var length = _.max(_.pluck(args, 'length'));
  3302. var results = new Array(length);
  3303. for (var i = 0; i < length; i++) {
  3304. results[i] = _.pluck(args, "" + i);
  3305. }
  3306. return results;
  3307. };
  3308. // Converts lists into objects. Pass either a single array of `[key, value]`
  3309. // pairs, or two parallel arrays of the same length -- one of keys, and one of
  3310. // the corresponding values.
  3311. _.object = function(list, values) {
  3312. var result = {};
  3313. for (var i = 0, l = list.length; i < l; i++) {
  3314. if (values) {
  3315. result[list[i]] = values[i];
  3316. } else {
  3317. result[list[i][0]] = list[i][1];
  3318. }
  3319. }
  3320. return result;
  3321. };
  3322. // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
  3323. // we need this function. Return the position of the first occurrence of an
  3324. // item in an array, or -1 if the item is not included in the array.
  3325. // Delegates to **ECMAScript 5**'s native `indexOf` if available.
  3326. // If the array is large and already in sort order, pass `true`
  3327. // for **isSorted** to use binary search.
  3328. _.indexOf = function(array, item, isSorted) {
  3329. if (array == null) return -1;
  3330. var i = 0, l = array.length;
  3331. if (isSorted) {
  3332. if (typeof isSorted == 'number') {
  3333. i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
  3334. } else {
  3335. i = _.sortedIndex(array, item);
  3336. return array[i] === item ? i : -1;
  3337. }
  3338. }
  3339. if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
  3340. for (; i < l; i++) if (array[i] === item) return i;
  3341. return -1;
  3342. };
  3343. // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
  3344. _.lastIndexOf = function(array, item, from) {
  3345. if (array == null) return -1;
  3346. var hasIndex = from != null;
  3347. if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
  3348. return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
  3349. }
  3350. var i = (hasIndex ? from : array.length);
  3351. while (i--) if (array[i] === item) return i;
  3352. return -1;
  3353. };
  3354. // Generate an integer Array containing an arithmetic progression. A port of
  3355. // the native Python `range()` function. See
  3356. // [the Python documentation](http://docs.python.org/library/functions.html#range).
  3357. _.range = function(start, stop, step) {
  3358. if (arguments.length <= 1) {
  3359. stop = start || 0;
  3360. start = 0;
  3361. }
  3362. step = arguments[2] || 1;
  3363. var len = Math.max(Math.ceil((stop - start) / step), 0);
  3364. var idx = 0;
  3365. var range = new Array(len);
  3366. while(idx < len) {
  3367. range[idx++] = start;
  3368. start += step;
  3369. }
  3370. return range;
  3371. };
  3372. // Function (ahem) Functions
  3373. // ------------------
  3374. // Reusable constructor function for prototype setting.
  3375. var ctor = function(){};
  3376. // Create a function bound to a given object (assigning `this`, and arguments,
  3377. // optionally). Binding with arguments is also known as `curry`.
  3378. // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
  3379. // We check for `func.bind` first, to fail fast when `func` is undefined.
  3380. _.bind = function bind(func, context) {
  3381. var bound, args;
  3382. if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
  3383. if (!_.isFunction(func)) throw new TypeError;
  3384. args = slice.call(arguments, 2);
  3385. return bound = function() {
  3386. if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
  3387. ctor.prototype = func.prototype;
  3388. var self = new ctor;
  3389. var result = func.apply(self, args.concat(slice.call(arguments)));
  3390. if (Object(result) === result) return result;
  3391. return self;
  3392. };
  3393. };
  3394. // Bind all of an object's methods to that object. Useful for ensuring that
  3395. // all callbacks defined on an object belong to it.
  3396. _.bindAll = function(obj) {
  3397. var funcs = slice.call(arguments, 1);
  3398. if (funcs.length == 0) funcs = _.functions(obj);
  3399. each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
  3400. return obj;
  3401. };
  3402. // Memoize an expensive function by storing its results.
  3403. _.memoize = function(func, hasher) {
  3404. var memo = {};
  3405. hasher || (hasher = _.identity);
  3406. return function() {
  3407. var key = hasher.apply(this, arguments);
  3408. return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
  3409. };
  3410. };
  3411. // Delays a function for the given number of milliseconds, and then calls
  3412. // it with the arguments supplied.
  3413. _.delay = function(func, wait) {
  3414. var args = slice.call(arguments, 2);
  3415. return setTimeout(function(){ return func.apply(null, args); }, wait);
  3416. };
  3417. // Defers a function, scheduling it to run after the current call stack has
  3418. // cleared.
  3419. _.defer = function(func) {
  3420. return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
  3421. };
  3422. // Returns a function, that, when invoked, will only be triggered at most once
  3423. // during a given window of time.
  3424. _.throttle = function(func, wait) {
  3425. var context, args, timeout, throttling, more, result;
  3426. var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
  3427. return function() {
  3428. context = this; args = arguments;
  3429. var later = function() {
  3430. timeout = null;
  3431. if (more) {
  3432. result = func.apply(context, args);
  3433. }
  3434. whenDone();
  3435. };
  3436. if (!timeout) timeout = setTimeout(later, wait);
  3437. if (throttling) {
  3438. more = true;
  3439. } else {
  3440. throttling = true;
  3441. result = func.apply(context, args);
  3442. }
  3443. whenDone();
  3444. return result;
  3445. };
  3446. };
  3447. // Returns a function, that, as long as it continues to be invoked, will not
  3448. // be triggered. The function will be called after it stops being called for
  3449. // N milliseconds. If `immediate` is passed, trigger the function on the
  3450. // leading edge, instead of the trailing.
  3451. _.debounce = function(func, wait, immediate) {
  3452. var timeout, result;
  3453. return function() {
  3454. var context = this, args = arguments;
  3455. var later = function() {
  3456. timeout = null;
  3457. if (!immediate) result = func.apply(context, args);
  3458. };
  3459. var callNow = immediate && !timeout;
  3460. clearTimeout(timeout);
  3461. timeout = setTimeout(later, wait);
  3462. if (callNow) result = func.apply(context, args);
  3463. return result;
  3464. };
  3465. };
  3466. // Returns a function that will be executed at most one time, no matter how
  3467. // often you call it. Useful for lazy initialization.
  3468. _.once = function(func) {
  3469. var ran = false, memo;
  3470. return function() {
  3471. if (ran) return memo;
  3472. ran = true;
  3473. memo = func.apply(this, arguments);
  3474. func = null;
  3475. return memo;
  3476. };
  3477. };
  3478. // Returns the first function passed as an argument to the second,
  3479. // allowing you to adjust arguments, run code before and after, and
  3480. // conditionally execute the original function.
  3481. _.wrap = function(func, wrapper) {
  3482. return function() {
  3483. var args = [func];
  3484. push.apply(args, arguments);
  3485. return wrapper.apply(this, args);
  3486. };
  3487. };
  3488. // Returns a function that is the composition of a list of functions, each
  3489. // consuming the return value of the function that follows.
  3490. _.compose = function() {
  3491. var funcs = arguments;
  3492. return function() {
  3493. var args = arguments;
  3494. for (var i = funcs.length - 1; i >= 0; i--) {
  3495. args = [funcs[i].apply(this, args)];
  3496. }
  3497. return args[0];
  3498. };
  3499. };
  3500. // Returns a function that will only be executed after being called N times.
  3501. _.after = function(times, func) {
  3502. if (times <= 0) return func();
  3503. return function() {
  3504. if (--times < 1) {
  3505. return func.apply(this, arguments);
  3506. }
  3507. };
  3508. };
  3509. // Object Functions
  3510. // ----------------
  3511. // Retrieve the names of an object's properties.
  3512. // Delegates to **ECMAScript 5**'s native `Object.keys`
  3513. _.keys = nativeKeys || function(obj) {
  3514. if (obj !== Object(obj)) throw new TypeError('Invalid object');
  3515. var keys = [];
  3516. for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
  3517. return keys;
  3518. };
  3519. // Retrieve the values of an object's properties.
  3520. _.values = function(obj) {
  3521. var values = [];
  3522. for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
  3523. return values;
  3524. };
  3525. // Convert an object into a list of `[key, value]` pairs.
  3526. _.pairs = function(obj) {
  3527. var pairs = [];
  3528. for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
  3529. return pairs;
  3530. };
  3531. // Invert the keys and values of an object. The values must be serializable.
  3532. _.invert = function(obj) {
  3533. var result = {};
  3534. for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
  3535. return result;
  3536. };
  3537. // Return a sorted list of the function names available on the object.
  3538. // Aliased as `methods`
  3539. _.functions = _.methods = function(obj) {
  3540. var names = [];
  3541. for (var key in obj) {
  3542. if (_.isFunction(obj[key])) names.push(key);
  3543. }
  3544. return names.sort();
  3545. };
  3546. // Extend a given object with all the properties in passed-in object(s).
  3547. _.extend = function(obj) {
  3548. each(slice.call(arguments, 1), function(source) {
  3549. for (var prop in source) {
  3550. obj[prop] = source[prop];
  3551. }
  3552. });
  3553. return obj;
  3554. };
  3555. // Return a copy of the object only containing the whitelisted properties.
  3556. _.pick = function(obj) {
  3557. var copy = {};
  3558. var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
  3559. each(keys, function(key) {
  3560. if (key in obj) copy[key] = obj[key];
  3561. });
  3562. return copy;
  3563. };
  3564. // Return a copy of the object without the blacklisted properties.
  3565. _.omit = function(obj) {
  3566. var copy = {};
  3567. var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
  3568. for (var key in obj) {
  3569. if (!_.contains(keys, key)) copy[key] = obj[key];
  3570. }
  3571. return copy;
  3572. };
  3573. // Fill in a given object with default properties.
  3574. _.defaults = function(obj) {
  3575. each(slice.call(arguments, 1), function(source) {
  3576. for (var prop in source) {
  3577. if (obj[prop] == null) obj[prop] = source[prop];
  3578. }
  3579. });
  3580. return obj;
  3581. };
  3582. // Create a (shallow-cloned) duplicate of an object.
  3583. _.clone = function(obj) {
  3584. if (!_.isObject(obj)) return obj;
  3585. return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
  3586. };
  3587. // Invokes interceptor with the obj, and then returns obj.
  3588. // The primary purpose of this method is to "tap into" a method chain, in
  3589. // order to perform operations on intermediate results within the chain.
  3590. _.tap = function(obj, interceptor) {
  3591. interceptor(obj);
  3592. return obj;
  3593. };
  3594. // Internal recursive comparison function for `isEqual`.
  3595. var eq = function(a, b, aStack, bStack) {
  3596. // Identical objects are equal. `0 === -0`, but they aren't identical.
  3597. // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
  3598. if (a === b) return a !== 0 || 1 / a == 1 / b;
  3599. // A strict comparison is necessary because `null == undefined`.
  3600. if (a == null || b == null) return a === b;
  3601. // Unwrap any wrapped objects.
  3602. if (a instanceof _) a = a._wrapped;
  3603. if (b instanceof _) b = b._wrapped;
  3604. // Compare `[[Class]]` names.
  3605. var className = toString.call(a);
  3606. if (className != toString.call(b)) return false;
  3607. switch (className) {
  3608. // Strings, numbers, dates, and booleans are compared by value.
  3609. case '[object String]':
  3610. // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
  3611. // equivalent to `new String("5")`.
  3612. return a == String(b);
  3613. case '[object Number]':
  3614. // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
  3615. // other numeric values.
  3616. return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
  3617. case '[object Date]':
  3618. case '[object Boolean]':
  3619. // Coerce dates and booleans to numeric primitive values. Dates are compared by their
  3620. // millisecond representations. Note that invalid dates with millisecond representations
  3621. // of `NaN` are not equivalent.
  3622. return +a == +b;
  3623. // RegExps are compared by their source patterns and flags.
  3624. case '[object RegExp]':
  3625. return a.source == b.source &&
  3626. a.global == b.global &&
  3627. a.multiline == b.multiline &&
  3628. a.ignoreCase == b.ignoreCase;
  3629. }
  3630. if (typeof a != 'object' || typeof b != 'object') return false;
  3631. // Assume equality for cyclic structures. The algorithm for detecting cyclic
  3632. // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
  3633. var length = aStack.length;
  3634. while (length--) {
  3635. // Linear search. Performance is inversely proportional to the number of
  3636. // unique nested structures.
  3637. if (aStack[length] == a) return bStack[length] == b;
  3638. }
  3639. // Add the first object to the stack of traversed objects.
  3640. aStack.push(a);
  3641. bStack.push(b);
  3642. var size = 0, result = true;
  3643. // Recursively compare objects and arrays.
  3644. if (className == '[object Array]') {
  3645. // Compare array lengths to determine if a deep comparison is necessary.
  3646. size = a.length;
  3647. result = size == b.length;
  3648. if (result) {
  3649. // Deep compare the contents, ignoring non-numeric properties.
  3650. while (size--) {
  3651. if (!(result = eq(a[size], b[size], aStack, bStack))) break;
  3652. }
  3653. }
  3654. } else {
  3655. // Objects with different constructors are not equivalent, but `Object`s
  3656. // from different frames are.
  3657. var aCtor = a.constructor, bCtor = b.constructor;
  3658. if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
  3659. _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
  3660. return false;
  3661. }
  3662. // Deep compare objects.
  3663. for (var key in a) {
  3664. if (_.has(a, key)) {
  3665. // Count the expected number of properties.
  3666. size++;
  3667. // Deep compare each member.
  3668. if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
  3669. }
  3670. }
  3671. // Ensure that both objects contain the same number of properties.
  3672. if (result) {
  3673. for (key in b) {
  3674. if (_.has(b, key) && !(size--)) break;
  3675. }
  3676. result = !size;
  3677. }
  3678. }
  3679. // Remove the first object from the stack of traversed objects.
  3680. aStack.pop();
  3681. bStack.pop();
  3682. return result;
  3683. };
  3684. // Perform a deep comparison to check if two objects are equal.
  3685. _.isEqual = function(a, b) {
  3686. return eq(a, b, [], []);
  3687. };
  3688. // Is a given array, string, or object empty?
  3689. // An "empty" object has no enumerable own-properties.
  3690. _.isEmpty = function(obj) {
  3691. if (obj == null) return true;
  3692. if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
  3693. for (var key in obj) if (_.has(obj, key)) return false;
  3694. return true;
  3695. };
  3696. // Is a given value a DOM element?
  3697. _.isElement = function(obj) {
  3698. return !!(obj && obj.nodeType === 1);
  3699. };
  3700. // Is a given value an array?
  3701. // Delegates to ECMA5's native Array.isArray
  3702. _.isArray = nativeIsArray || function(obj) {
  3703. return toString.call(obj) == '[object Array]';
  3704. };
  3705. // Is a given variable an object?
  3706. _.isObject = function(obj) {
  3707. return obj === Object(obj);
  3708. };
  3709. // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
  3710. each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
  3711. _['is' + name] = function(obj) {
  3712. return toString.call(obj) == '[object ' + name + ']';
  3713. };
  3714. });
  3715. // Define a fallback version of the method in browsers (ahem, IE), where
  3716. // there isn't any inspectable "Arguments" type.
  3717. if (!_.isArguments(arguments)) {
  3718. _.isArguments = function(obj) {
  3719. return !!(obj && _.has(obj, 'callee'));
  3720. };
  3721. }
  3722. // Optimize `isFunction` if appropriate.
  3723. if (typeof (/./) !== 'function') {
  3724. _.isFunction = function(obj) {
  3725. return typeof obj === 'function';
  3726. };
  3727. }
  3728. // Is a given object a finite number?
  3729. _.isFinite = function(obj) {
  3730. return _.isNumber(obj) && isFinite(obj);
  3731. };
  3732. // Is the given value `NaN`? (NaN is the only number which does not equal itself).
  3733. _.isNaN = function(obj) {
  3734. return _.isNumber(obj) && obj != +obj;
  3735. };
  3736. // Is a given value a boolean?
  3737. _.isBoolean = function(obj) {
  3738. return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
  3739. };
  3740. // Is a given value equal to null?
  3741. _.isNull = function(obj) {
  3742. return obj === null;
  3743. };
  3744. // Is a given variable undefined?
  3745. _.isUndefined = function(obj) {
  3746. return obj === void 0;
  3747. };
  3748. // Shortcut function for checking if an object has a given property directly
  3749. // on itself (in other words, not on a prototype).
  3750. _.has = function(obj, key) {
  3751. return hasOwnProperty.call(obj, key);
  3752. };
  3753. // Utility Functions
  3754. // -----------------
  3755. // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
  3756. // previous owner. Returns a reference to the Underscore object.
  3757. _.noConflict = function() {
  3758. root._ = previousUnderscore;
  3759. return this;
  3760. };
  3761. // Keep the identity function around for default iterators.
  3762. _.identity = function(value) {
  3763. return value;
  3764. };
  3765. // Run a function **n** times.
  3766. _.times = function(n, iterator, context) {
  3767. for (var i = 0; i < n; i++) iterator.call(context, i);
  3768. };
  3769. // Return a random integer between min and max (inclusive).
  3770. _.random = function(min, max) {
  3771. if (max == null) {
  3772. max = min;
  3773. min = 0;
  3774. }
  3775. return min + (0 | Math.random() * (max - min + 1));
  3776. };
  3777. // List of HTML entities for escaping.
  3778. var entityMap = {
  3779. escape: {
  3780. '&': '&amp;',
  3781. '<': '&lt;',
  3782. '>': '&gt;',
  3783. '"': '&quot;',
  3784. "'": '&#x27;',
  3785. '/': '&#x2F;'
  3786. }
  3787. };
  3788. entityMap.unescape = _.invert(entityMap.escape);
  3789. // Regexes containing the keys and values listed immediately above.
  3790. var entityRegexes = {
  3791. escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
  3792. unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
  3793. };
  3794. // Functions for escaping and unescaping strings to/from HTML interpolation.
  3795. _.each(['escape', 'unescape'], function(method) {
  3796. _[method] = function(string) {
  3797. if (string == null) return '';
  3798. return ('' + string).replace(entityRegexes[method], function(match) {
  3799. return entityMap[method][match];
  3800. });
  3801. };
  3802. });
  3803. // If the value of the named property is a function then invoke it;
  3804. // otherwise, return it.
  3805. _.result = function(object, property) {
  3806. if (object == null) return null;
  3807. var value = object[property];
  3808. return _.isFunction(value) ? value.call(object) : value;
  3809. };
  3810. // Add your own custom functions to the Underscore object.
  3811. _.mixin = function(obj) {
  3812. each(_.functions(obj), function(name){
  3813. var func = _[name] = obj[name];
  3814. _.prototype[name] = function() {
  3815. var args = [this._wrapped];
  3816. push.apply(args, arguments);
  3817. return result.call(this, func.apply(_, args));
  3818. };
  3819. });
  3820. };
  3821. // Generate a unique integer id (unique within the entire client session).
  3822. // Useful for temporary DOM ids.
  3823. var idCounter = 0;
  3824. _.uniqueId = function(prefix) {
  3825. var id = idCounter++;
  3826. return prefix ? prefix + id : id;
  3827. };
  3828. // By default, Underscore uses ERB-style template delimiters, change the
  3829. // following template settings to use alternative delimiters.
  3830. _.templateSettings = {
  3831. evaluate : /<%([\s\S]+?)%>/g,
  3832. interpolate : /<%=([\s\S]+?)%>/g,
  3833. escape : /<%-([\s\S]+?)%>/g
  3834. };
  3835. // When customizing `templateSettings`, if you don't want to define an
  3836. // interpolation, evaluation or escaping regex, we need one that is
  3837. // guaranteed not to match.
  3838. var noMatch = /(.)^/;
  3839. // Certain characters need to be escaped so that they can be put into a
  3840. // string literal.
  3841. var escapes = {
  3842. "'": "'",
  3843. '\\': '\\',
  3844. '\r': 'r',
  3845. '\n': 'n',
  3846. '\t': 't',
  3847. '\u2028': 'u2028',
  3848. '\u2029': 'u2029'
  3849. };
  3850. var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
  3851. // JavaScript micro-templating, similar to John Resig's implementation.
  3852. // Underscore templating handles arbitrary delimiters, preserves whitespace,
  3853. // and correctly escapes quotes within interpolated code.
  3854. _.template = function(text, data, settings) {
  3855. settings = _.defaults({}, settings, _.templateSettings);
  3856. // Combine delimiters into one regular expression via alternation.
  3857. var matcher = new RegExp([
  3858. (settings.escape || noMatch).source,
  3859. (settings.interpolate || noMatch).source,
  3860. (settings.evaluate || noMatch).source
  3861. ].join('|') + '|$', 'g');
  3862. // Compile the template source, escaping string literals appropriately.
  3863. var index = 0;
  3864. var source = "__p+='";
  3865. text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
  3866. source += text.slice(index, offset)
  3867. .replace(escaper, function(match) { return '\\' + escapes[match]; });
  3868. source +=
  3869. escape ? "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" :
  3870. interpolate ? "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" :
  3871. evaluate ? "';\n" + evaluate + "\n__p+='" : '';
  3872. index = offset + match.length;
  3873. });
  3874. source += "';\n";
  3875. // If a variable is not specified, place data values in local scope.
  3876. if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
  3877. source = "var __t,__p='',__j=Array.prototype.join," +
  3878. "print=function(){__p+=__j.call(arguments,'');};\n" +
  3879. source + "return __p;\n";
  3880. try {
  3881. var render = new Function(settings.variable || 'obj', '_', source);
  3882. } catch (e) {
  3883. e.source = source;
  3884. throw e;
  3885. }
  3886. if (data) return render(data, _);
  3887. var template = function(data) {
  3888. return render.call(this, data, _);
  3889. };
  3890. // Provide the compiled function source as a convenience for precompilation.
  3891. template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
  3892. return template;
  3893. };
  3894. // Add a "chain" function, which will delegate to the wrapper.
  3895. _.chain = function(obj) {
  3896. return _(obj).chain();
  3897. };
  3898. // OOP
  3899. // ---------------
  3900. // If Underscore is called as a function, it returns a wrapped object that
  3901. // can be used OO-style. This wrapper holds altered versions of all the
  3902. // underscore functions. Wrapped objects may be chained.
  3903. // Helper function to continue chaining intermediate results.
  3904. var result = function(obj) {
  3905. return this._chain ? _(obj).chain() : obj;
  3906. };
  3907. // Add all of the Underscore functions to the wrapper object.
  3908. _.mixin(_);
  3909. // Add all mutator Array functions to the wrapper.
  3910. each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
  3911. var method = ArrayProto[name];
  3912. _.prototype[name] = function() {
  3913. var obj = this._wrapped;
  3914. method.apply(obj, arguments);
  3915. if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
  3916. return result.call(this, obj);
  3917. };
  3918. });
  3919. // Add all accessor Array functions to the wrapper.
  3920. each(['concat', 'join', 'slice'], function(name) {
  3921. var method = ArrayProto[name];
  3922. _.prototype[name] = function() {
  3923. return result.call(this, method.apply(this._wrapped, arguments));
  3924. };
  3925. });
  3926. _.extend(_.prototype, {
  3927. // Start chaining a wrapped Underscore object.
  3928. chain: function() {
  3929. this._chain = true;
  3930. return this;
  3931. },
  3932. // Extracts the result from a wrapped and chained object.
  3933. value: function() {
  3934. return this._wrapped;
  3935. }
  3936. });
  3937. }).call(this);
  3938. // Backbone.js 0.9.10
  3939. // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
  3940. // Backbone may be freely distributed under the MIT license.
  3941. // For all details and documentation:
  3942. // http://backbonejs.org
  3943. (function(){
  3944. // Initial Setup
  3945. // -------------
  3946. // Save a reference to the global object (`window` in the browser, `exports`
  3947. // on the server).
  3948. var root = this;
  3949. // Save the previous value of the `Backbone` variable, so that it can be
  3950. // restored later on, if `noConflict` is used.
  3951. var previousBackbone = root.Backbone;
  3952. // Create a local reference to array methods.
  3953. var array = [];
  3954. var push = array.push;
  3955. var slice = array.slice;
  3956. var splice = array.splice;
  3957. // The top-level namespace. All public Backbone classes and modules will
  3958. // be attached to this. Exported for both CommonJS and the browser.
  3959. var Backbone;
  3960. if (typeof exports !== 'undefined') {
  3961. Backbone = exports;
  3962. } else {
  3963. Backbone = root.Backbone = {};
  3964. }
  3965. // Current version of the library. Keep in sync with `package.json`.
  3966. Backbone.VERSION = '0.9.10';
  3967. // Require Underscore, if we're on the server, and it's not already present.
  3968. var _ = root._;
  3969. if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
  3970. // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
  3971. Backbone.$ = root.jQuery || root.Zepto || root.ender;
  3972. // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
  3973. // to its previous owner. Returns a reference to this Backbone object.
  3974. Backbone.noConflict = function() {
  3975. root.Backbone = previousBackbone;
  3976. return this;
  3977. };
  3978. // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
  3979. // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
  3980. // set a `X-Http-Method-Override` header.
  3981. Backbone.emulateHTTP = false;
  3982. // Turn on `emulateJSON` to support legacy servers that can't deal with direct
  3983. // `application/json` requests ... will encode the body as
  3984. // `application/x-www-form-urlencoded` instead and will send the model in a
  3985. // form param named `model`.
  3986. Backbone.emulateJSON = false;
  3987. // Backbone.Events
  3988. // ---------------
  3989. // Regular expression used to split event strings.
  3990. var eventSplitter = /\s+/;
  3991. // Implement fancy features of the Events API such as multiple event
  3992. // names `"change blur"` and jQuery-style event maps `{change: action}`
  3993. // in terms of the existing API.
  3994. var eventsApi = function(obj, action, name, rest) {
  3995. if (!name) return true;
  3996. if (typeof name === 'object') {
  3997. for (var key in name) {
  3998. obj[action].apply(obj, [key, name[key]].concat(rest));
  3999. }
  4000. } else if (eventSplitter.test(name)) {
  4001. var names = name.split(eventSplitter);
  4002. for (var i = 0, l = names.length; i < l; i++) {
  4003. obj[action].apply(obj, [names[i]].concat(rest));
  4004. }
  4005. } else {
  4006. return true;
  4007. }
  4008. };
  4009. // Optimized internal dispatch function for triggering events. Tries to
  4010. // keep the usual cases speedy (most Backbone events have 3 arguments).
  4011. var triggerEvents = function(events, args) {
  4012. var ev, i = -1, l = events.length;
  4013. switch (args.length) {
  4014. case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx);
  4015. return;
  4016. case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0]);
  4017. return;
  4018. case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1]);
  4019. return;
  4020. case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1], args[2]);
  4021. return;
  4022. default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
  4023. }
  4024. };
  4025. // A module that can be mixed in to *any object* in order to provide it with
  4026. // custom events. You may bind with `on` or remove with `off` callback
  4027. // functions to an event; `trigger`-ing an event fires all callbacks in
  4028. // succession.
  4029. //
  4030. // var object = {};
  4031. // _.extend(object, Backbone.Events);
  4032. // object.on('expand', function(){ alert('expanded'); });
  4033. // object.trigger('expand');
  4034. //
  4035. var Events = Backbone.Events = {
  4036. // Bind one or more space separated events, or an events map,
  4037. // to a `callback` function. Passing `"all"` will bind the callback to
  4038. // all events fired.
  4039. on: function(name, callback, context) {
  4040. if (!(eventsApi(this, 'on', name, [callback, context]) && callback)) return this;
  4041. this._events || (this._events = {});
  4042. var list = this._events[name] || (this._events[name] = []);
  4043. list.push({callback: callback, context: context, ctx: context || this});
  4044. return this;
  4045. },
  4046. // Bind events to only be triggered a single time. After the first time
  4047. // the callback is invoked, it will be removed.
  4048. once: function(name, callback, context) {
  4049. if (!(eventsApi(this, 'once', name, [callback, context]) && callback)) return this;
  4050. var self = this;
  4051. var once = _.once(function() {
  4052. self.off(name, once);
  4053. callback.apply(this, arguments);
  4054. });
  4055. once._callback = callback;
  4056. this.on(name, once, context);
  4057. return this;
  4058. },
  4059. // Remove one or many callbacks. If `context` is null, removes all
  4060. // callbacks with that function. If `callback` is null, removes all
  4061. // callbacks for the event. If `name` is null, removes all bound
  4062. // callbacks for all events.
  4063. off: function(name, callback, context) {
  4064. var list, ev, events, names, i, l, j, k;
  4065. if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
  4066. if (!name && !callback && !context) {
  4067. this._events = {};
  4068. return this;
  4069. }
  4070. names = name ? [name] : _.keys(this._events);
  4071. for (i = 0, l = names.length; i < l; i++) {
  4072. name = names[i];
  4073. if (list = this._events[name]) {
  4074. events = [];
  4075. if (callback || context) {
  4076. for (j = 0, k = list.length; j < k; j++) {
  4077. ev = list[j];
  4078. if ((callback && callback !== ev.callback &&
  4079. callback !== ev.callback._callback) ||
  4080. (context && context !== ev.context)) {
  4081. events.push(ev);
  4082. }
  4083. }
  4084. }
  4085. this._events[name] = events;
  4086. }
  4087. }
  4088. return this;
  4089. },
  4090. // Trigger one or many events, firing all bound callbacks. Callbacks are
  4091. // passed the same arguments as `trigger` is, apart from the event name
  4092. // (unless you're listening on `"all"`, which will cause your callback to
  4093. // receive the true name of the event as the first argument).
  4094. trigger: function(name) {
  4095. if (!this._events) return this;
  4096. var args = slice.call(arguments, 1);
  4097. if (!eventsApi(this, 'trigger', name, args)) return this;
  4098. var events = this._events[name];
  4099. var allEvents = this._events.all;
  4100. if (events) triggerEvents(events, args);
  4101. if (allEvents) triggerEvents(allEvents, arguments);
  4102. return this;
  4103. },
  4104. // An inversion-of-control version of `on`. Tell *this* object to listen to
  4105. // an event in another object ... keeping track of what it's listening to.
  4106. listenTo: function(obj, name, callback) {
  4107. var listeners = this._listeners || (this._listeners = {});
  4108. var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
  4109. listeners[id] = obj;
  4110. obj.on(name, typeof name === 'object' ? this : callback, this);
  4111. return this;
  4112. },
  4113. // Tell this object to stop listening to either specific events ... or
  4114. // to every object it's currently listening to.
  4115. stopListening: function(obj, name, callback) {
  4116. var listeners = this._listeners;
  4117. if (!listeners) return;
  4118. if (obj) {
  4119. obj.off(name, typeof name === 'object' ? this : callback, this);
  4120. if (!name && !callback) delete listeners[obj._listenerId];
  4121. } else {
  4122. if (typeof name === 'object') callback = this;
  4123. for (var id in listeners) {
  4124. listeners[id].off(name, callback, this);
  4125. }
  4126. this._listeners = {};
  4127. }
  4128. return this;
  4129. }
  4130. };
  4131. // Aliases for backwards compatibility.
  4132. Events.bind = Events.on;
  4133. Events.unbind = Events.off;
  4134. // Allow the `Backbone` object to serve as a global event bus, for folks who
  4135. // want global "pubsub" in a convenient place.
  4136. _.extend(Backbone, Events);
  4137. // Backbone.Model
  4138. // --------------
  4139. // Create a new model, with defined attributes. A client id (`cid`)
  4140. // is automatically generated and assigned for you.
  4141. var Model = Backbone.Model = function(attributes, options) {
  4142. var defaults;
  4143. var attrs = attributes || {};
  4144. this.cid = _.uniqueId('c');
  4145. this.attributes = {};
  4146. if (options && options.collection) this.collection = options.collection;
  4147. if (options && options.parse) attrs = this.parse(attrs, options) || {};
  4148. if (defaults = _.result(this, 'defaults')) {
  4149. attrs = _.defaults({}, attrs, defaults);
  4150. }
  4151. this.set(attrs, options);
  4152. this.changed = {};
  4153. this.initialize.apply(this, arguments);
  4154. };
  4155. // Attach all inheritable methods to the Model prototype.
  4156. _.extend(Model.prototype, Events, {
  4157. // A hash of attributes whose current and previous value differ.
  4158. changed: null,
  4159. // The default name for the JSON `id` attribute is `"id"`. MongoDB and
  4160. // CouchDB users may want to set this to `"_id"`.
  4161. idAttribute: 'id',
  4162. // Initialize is an empty function by default. Override it with your own
  4163. // initialization logic.
  4164. initialize: function(){},
  4165. // Return a copy of the model's `attributes` object.
  4166. toJSON: function(options) {
  4167. return _.clone(this.attributes);
  4168. },
  4169. // Proxy `Backbone.sync` by default.
  4170. sync: function() {
  4171. return Backbone.sync.apply(this, arguments);
  4172. },
  4173. // Get the value of an attribute.
  4174. get: function(attr) {
  4175. return this.attributes[attr];
  4176. },
  4177. // Get the HTML-escaped value of an attribute.
  4178. escape: function(attr) {
  4179. return _.escape(this.get(attr));
  4180. },
  4181. // Returns `true` if the attribute contains a value that is not null
  4182. // or undefined.
  4183. has: function(attr) {
  4184. return this.get(attr) != null;
  4185. },
  4186. // ----------------------------------------------------------------------
  4187. // Set a hash of model attributes on the object, firing `"change"` unless
  4188. // you choose to silence it.
  4189. set: function(key, val, options) {
  4190. var attr, attrs, unset, changes, silent, changing, prev, current;
  4191. if (key == null) return this;
  4192. // Handle both `"key", value` and `{key: value}` -style arguments.
  4193. if (typeof key === 'object') {
  4194. attrs = key;
  4195. options = val;
  4196. } else {
  4197. (attrs = {})[key] = val;
  4198. }
  4199. options || (options = {});
  4200. // Run validation.
  4201. if (!this._validate(attrs, options)) return false;
  4202. // Extract attributes and options.
  4203. unset = options.unset;
  4204. silent = options.silent;
  4205. changes = [];
  4206. changing = this._changing;
  4207. this._changing = true;
  4208. if (!changing) {
  4209. this._previousAttributes = _.clone(this.attributes);
  4210. this.changed = {};
  4211. }
  4212. current = this.attributes, prev = this._previousAttributes;
  4213. // Check for changes of `id`.
  4214. if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
  4215. // For each `set` attribute, update or delete the current value.
  4216. for (attr in attrs) {
  4217. val = attrs[attr];
  4218. if (!_.isEqual(current[attr], val)) changes.push(attr);
  4219. if (!_.isEqual(prev[attr], val)) {
  4220. this.changed[attr] = val;
  4221. } else {
  4222. delete this.changed[attr];
  4223. }
  4224. unset ? delete current[attr] : current[attr] = val;
  4225. }
  4226. // Trigger all relevant attribute changes.
  4227. if (!silent) {
  4228. if (changes.length) this._pending = true;
  4229. for (var i = 0, l = changes.length; i < l; i++) {
  4230. this.trigger('change:' + changes[i], this, current[changes[i]], options);
  4231. }
  4232. }
  4233. if (changing) return this;
  4234. if (!silent) {
  4235. while (this._pending) {
  4236. this._pending = false;
  4237. this.trigger('change', this, options);
  4238. }
  4239. }
  4240. this._pending = false;
  4241. this._changing = false;
  4242. return this;
  4243. },
  4244. // Remove an attribute from the model, firing `"change"` unless you choose
  4245. // to silence it. `unset` is a noop if the attribute doesn't exist.
  4246. unset: function(attr, options) {
  4247. return this.set(attr, void 0, _.extend({}, options, {unset: true}));
  4248. },
  4249. // Clear all attributes on the model, firing `"change"` unless you choose
  4250. // to silence it.
  4251. clear: function(options) {
  4252. var attrs = {};
  4253. for (var key in this.attributes) attrs[key] = void 0;
  4254. return this.set(attrs, _.extend({}, options, {unset: true}));
  4255. },
  4256. // Determine if the model has changed since the last `"change"` event.
  4257. // If you specify an attribute name, determine if that attribute has changed.
  4258. hasChanged: function(attr) {
  4259. if (attr == null) return !_.isEmpty(this.changed);
  4260. return _.has(this.changed, attr);
  4261. },
  4262. // Return an object containing all the attributes that have changed, or
  4263. // false if there are no changed attributes. Useful for determining what
  4264. // parts of a view need to be updated and/or what attributes need to be
  4265. // persisted to the server. Unset attributes will be set to undefined.
  4266. // You can also pass an attributes object to diff against the model,
  4267. // determining if there *would be* a change.
  4268. changedAttributes: function(diff) {
  4269. if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
  4270. var val, changed = false;
  4271. var old = this._changing ? this._previousAttributes : this.attributes;
  4272. for (var attr in diff) {
  4273. if (_.isEqual(old[attr], (val = diff[attr]))) continue;
  4274. (changed || (changed = {}))[attr] = val;
  4275. }
  4276. return changed;
  4277. },
  4278. // Get the previous value of an attribute, recorded at the time the last
  4279. // `"change"` event was fired.
  4280. previous: function(attr) {
  4281. if (attr == null || !this._previousAttributes) return null;
  4282. return this._previousAttributes[attr];
  4283. },
  4284. // Get all of the attributes of the model at the time of the previous
  4285. // `"change"` event.
  4286. previousAttributes: function() {
  4287. return _.clone(this._previousAttributes);
  4288. },
  4289. // ---------------------------------------------------------------------
  4290. // Fetch the model from the server. If the server's representation of the
  4291. // model differs from its current attributes, they will be overriden,
  4292. // triggering a `"change"` event.
  4293. fetch: function(options) {
  4294. options = options ? _.clone(options) : {};
  4295. if (options.parse === void 0) options.parse = true;
  4296. var success = options.success;
  4297. options.success = function(model, resp, options) {
  4298. if (!model.set(model.parse(resp, options), options)) return false;
  4299. if (success) success(model, resp, options);
  4300. };
  4301. return this.sync('read', this, options);
  4302. },
  4303. // Set a hash of model attributes, and sync the model to the server.
  4304. // If the server returns an attributes hash that differs, the model's
  4305. // state will be `set` again.
  4306. save: function(key, val, options) {
  4307. var attrs, success, method, xhr, attributes = this.attributes;
  4308. // Handle both `"key", value` and `{key: value}` -style arguments.
  4309. if (key == null || typeof key === 'object') {
  4310. attrs = key;
  4311. options = val;
  4312. } else {
  4313. (attrs = {})[key] = val;
  4314. }
  4315. // If we're not waiting and attributes exist, save acts as `set(attr).save(null, opts)`.
  4316. if (attrs && (!options || !options.wait) && !this.set(attrs, options)) return false;
  4317. options = _.extend({validate: true}, options);
  4318. // Do not persist invalid models.
  4319. if (!this._validate(attrs, options)) return false;
  4320. // Set temporary attributes if `{wait: true}`.
  4321. if (attrs && options.wait) {
  4322. this.attributes = _.extend({}, attributes, attrs);
  4323. }
  4324. // After a successful server-side save, the client is (optionally)
  4325. // updated with the server-side state.
  4326. if (options.parse === void 0) options.parse = true;
  4327. success = options.success;
  4328. options.success = function(model, resp, options) {
  4329. // Ensure attributes are restored during synchronous saves.
  4330. model.attributes = attributes;
  4331. var serverAttrs = model.parse(resp, options);
  4332. if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
  4333. if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
  4334. return false;
  4335. }
  4336. if (success) success(model, resp, options);
  4337. };
  4338. // Finish configuring and sending the Ajax request.
  4339. method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
  4340. if (method === 'patch') options.attrs = attrs;
  4341. xhr = this.sync(method, this, options);
  4342. // Restore attributes.
  4343. if (attrs && options.wait) this.attributes = attributes;
  4344. return xhr;
  4345. },
  4346. // Destroy this model on the server if it was already persisted.
  4347. // Optimistically removes the model from its collection, if it has one.
  4348. // If `wait: true` is passed, waits for the server to respond before removal.
  4349. destroy: function(options) {
  4350. options = options ? _.clone(options) : {};
  4351. var model = this;
  4352. var success = options.success;
  4353. var destroy = function() {
  4354. model.trigger('destroy', model, model.collection, options);
  4355. };
  4356. options.success = function(model, resp, options) {
  4357. if (options.wait || model.isNew()) destroy();
  4358. if (success) success(model, resp, options);
  4359. };
  4360. if (this.isNew()) {
  4361. options.success(this, null, options);
  4362. return false;
  4363. }
  4364. var xhr = this.sync('delete', this, options);
  4365. if (!options.wait) destroy();
  4366. return xhr;
  4367. },
  4368. // Default URL for the model's representation on the server -- if you're
  4369. // using Backbone's restful methods, override this to change the endpoint
  4370. // that will be called.
  4371. url: function() {
  4372. var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
  4373. if (this.isNew()) return base;
  4374. return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
  4375. },
  4376. // **parse** converts a response into the hash of attributes to be `set` on
  4377. // the model. The default implementation is just to pass the response along.
  4378. parse: function(resp, options) {
  4379. return resp;
  4380. },
  4381. // Create a new model with identical attributes to this one.
  4382. clone: function() {
  4383. return new this.constructor(this.attributes);
  4384. },
  4385. // A model is new if it has never been saved to the server, and lacks an id.
  4386. isNew: function() {
  4387. return this.id == null;
  4388. },
  4389. // Check if the model is currently in a valid state.
  4390. isValid: function(options) {
  4391. return !this.validate || !this.validate(this.attributes, options);
  4392. },
  4393. // Run validation against the next complete set of model attributes,
  4394. // returning `true` if all is well. Otherwise, fire a general
  4395. // `"error"` event and call the error callback, if specified.
  4396. _validate: function(attrs, options) {
  4397. if (!options.validate || !this.validate) return true;
  4398. attrs = _.extend({}, this.attributes, attrs);
  4399. var error = this.validationError = this.validate(attrs, options) || null;
  4400. if (!error) return true;
  4401. this.trigger('invalid', this, error, options || {});
  4402. return false;
  4403. }
  4404. });
  4405. // Backbone.Collection
  4406. // -------------------
  4407. // Provides a standard collection class for our sets of models, ordered
  4408. // or unordered. If a `comparator` is specified, the Collection will maintain
  4409. // its models in sort order, as they're added and removed.
  4410. var Collection = Backbone.Collection = function(models, options) {
  4411. options || (options = {});
  4412. if (options.model) this.model = options.model;
  4413. if (options.comparator !== void 0) this.comparator = options.comparator;
  4414. this.models = [];
  4415. this._reset();
  4416. this.initialize.apply(this, arguments);
  4417. if (models) this.reset(models, _.extend({silent: true}, options));
  4418. };
  4419. // Define the Collection's inheritable methods.
  4420. _.extend(Collection.prototype, Events, {
  4421. // The default model for a collection is just a **Backbone.Model**.
  4422. // This should be overridden in most cases.
  4423. model: Model,
  4424. // Initialize is an empty function by default. Override it with your own
  4425. // initialization logic.
  4426. initialize: function(){},
  4427. // The JSON representation of a Collection is an array of the
  4428. // models' attributes.
  4429. toJSON: function(options) {
  4430. return this.map(function(model){ return model.toJSON(options); });
  4431. },
  4432. // Proxy `Backbone.sync` by default.
  4433. sync: function() {
  4434. return Backbone.sync.apply(this, arguments);
  4435. },
  4436. // Add a model, or list of models to the set.
  4437. add: function(models, options) {
  4438. models = _.isArray(models) ? models.slice() : [models];
  4439. options || (options = {});
  4440. var i, l, model, attrs, existing, doSort, add, at, sort, sortAttr;
  4441. add = [];
  4442. at = options.at;
  4443. sort = this.comparator && (at == null) && options.sort != false;
  4444. sortAttr = _.isString(this.comparator) ? this.comparator : null;
  4445. // Turn bare objects into model references, and prevent invalid models
  4446. // from being added.
  4447. for (i = 0, l = models.length; i < l; i++) {
  4448. if (!(model = this._prepareModel(attrs = models[i], options))) {
  4449. this.trigger('invalid', this, attrs, options);
  4450. continue;
  4451. }
  4452. // If a duplicate is found, prevent it from being added and
  4453. // optionally merge it into the existing model.
  4454. if (existing = this.get(model)) {
  4455. if (options.merge) {
  4456. existing.set(attrs === model ? model.attributes : attrs, options);
  4457. if (sort && !doSort && existing.hasChanged(sortAttr)) doSort = true;
  4458. }
  4459. continue;
  4460. }
  4461. // This is a new model, push it to the `add` list.
  4462. add.push(model);
  4463. // Listen to added models' events, and index models for lookup by
  4464. // `id` and by `cid`.
  4465. model.on('all', this._onModelEvent, this);
  4466. this._byId[model.cid] = model;
  4467. if (model.id != null) this._byId[model.id] = model;
  4468. }
  4469. // See if sorting is needed, update `length` and splice in new models.
  4470. if (add.length) {
  4471. if (sort) doSort = true;
  4472. this.length += add.length;
  4473. if (at != null) {
  4474. splice.apply(this.models, [at, 0].concat(add));
  4475. } else {
  4476. push.apply(this.models, add);
  4477. }
  4478. }
  4479. // Silently sort the collection if appropriate.
  4480. if (doSort) this.sort({silent: true});
  4481. if (options.silent) return this;
  4482. // Trigger `add` events.
  4483. for (i = 0, l = add.length; i < l; i++) {
  4484. (model = add[i]).trigger('add', model, this, options);
  4485. }
  4486. // Trigger `sort` if the collection was sorted.
  4487. if (doSort) this.trigger('sort', this, options);
  4488. return this;
  4489. },
  4490. // Remove a model, or a list of models from the set.
  4491. remove: function(models, options) {
  4492. models = _.isArray(models) ? models.slice() : [models];
  4493. options || (options = {});
  4494. var i, l, index, model;
  4495. for (i = 0, l = models.length; i < l; i++) {
  4496. model = this.get(models[i]);
  4497. if (!model) continue;
  4498. delete this._byId[model.id];
  4499. delete this._byId[model.cid];
  4500. index = this.indexOf(model);
  4501. this.models.splice(index, 1);
  4502. this.length--;
  4503. if (!options.silent) {
  4504. options.index = index;
  4505. model.trigger('remove', model, this, options);
  4506. }
  4507. this._removeReference(model);
  4508. }
  4509. return this;
  4510. },
  4511. // Add a model to the end of the collection.
  4512. push: function(model, options) {
  4513. model = this._prepareModel(model, options);
  4514. this.add(model, _.extend({at: this.length}, options));
  4515. return model;
  4516. },
  4517. // Remove a model from the end of the collection.
  4518. pop: function(options) {
  4519. var model = this.at(this.length - 1);
  4520. this.remove(model, options);
  4521. return model;
  4522. },
  4523. // Add a model to the beginning of the collection.
  4524. unshift: function(model, options) {
  4525. model = this._prepareModel(model, options);
  4526. this.add(model, _.extend({at: 0}, options));
  4527. return model;
  4528. },
  4529. // Remove a model from the beginning of the collection.
  4530. shift: function(options) {
  4531. var model = this.at(0);
  4532. this.remove(model, options);
  4533. return model;
  4534. },
  4535. // Slice out a sub-array of models from the collection.
  4536. slice: function(begin, end) {
  4537. return this.models.slice(begin, end);
  4538. },
  4539. // Get a model from the set by id.
  4540. get: function(obj) {
  4541. if (obj == null) return void 0;
  4542. this._idAttr || (this._idAttr = this.model.prototype.idAttribute);
  4543. return this._byId[obj.id || obj.cid || obj[this._idAttr] || obj];
  4544. },
  4545. // Get the model at the given index.
  4546. at: function(index) {
  4547. return this.models[index];
  4548. },
  4549. // Return models with matching attributes. Useful for simple cases of `filter`.
  4550. where: function(attrs) {
  4551. if (_.isEmpty(attrs)) return [];
  4552. return this.filter(function(model) {
  4553. for (var key in attrs) {
  4554. if (attrs[key] !== model.get(key)) return false;
  4555. }
  4556. return true;
  4557. });
  4558. },
  4559. // Force the collection to re-sort itself. You don't need to call this under
  4560. // normal circumstances, as the set will maintain sort order as each item
  4561. // is added.
  4562. sort: function(options) {
  4563. if (!this.comparator) {
  4564. throw new Error('Cannot sort a set without a comparator');
  4565. }
  4566. options || (options = {});
  4567. // Run sort based on type of `comparator`.
  4568. if (_.isString(this.comparator) || this.comparator.length === 1) {
  4569. this.models = this.sortBy(this.comparator, this);
  4570. } else {
  4571. this.models.sort(_.bind(this.comparator, this));
  4572. }
  4573. if (!options.silent) this.trigger('sort', this, options);
  4574. return this;
  4575. },
  4576. // Pluck an attribute from each model in the collection.
  4577. pluck: function(attr) {
  4578. return _.invoke(this.models, 'get', attr);
  4579. },
  4580. // Smartly update a collection with a change set of models, adding,
  4581. // removing, and merging as necessary.
  4582. update: function(models, options) {
  4583. options = _.extend({add: true, merge: true, remove: true}, options);
  4584. if (options.parse) models = this.parse(models, options);
  4585. var model, i, l, existing;
  4586. var add = [], remove = [], modelMap = {};
  4587. // Allow a single model (or no argument) to be passed.
  4588. if (!_.isArray(models)) models = models ? [models] : [];
  4589. // Proxy to `add` for this case, no need to iterate...
  4590. if (options.add && !options.remove) return this.add(models, options);
  4591. // Determine which models to add and merge, and which to remove.
  4592. for (i = 0, l = models.length; i < l; i++) {
  4593. model = models[i];
  4594. existing = this.get(model);
  4595. if (options.remove && existing) modelMap[existing.cid] = true;
  4596. if ((options.add && !existing) || (options.merge && existing)) {
  4597. add.push(model);
  4598. }
  4599. }
  4600. if (options.remove) {
  4601. for (i = 0, l = this.models.length; i < l; i++) {
  4602. model = this.models[i];
  4603. if (!modelMap[model.cid]) remove.push(model);
  4604. }
  4605. }
  4606. // Remove models (if applicable) before we add and merge the rest.
  4607. if (remove.length) this.remove(remove, options);
  4608. if (add.length) this.add(add, options);
  4609. return this;
  4610. },
  4611. // When you have more items than you want to add or remove individually,
  4612. // you can reset the entire set with a new list of models, without firing
  4613. // any `add` or `remove` events. Fires `reset` when finished.
  4614. reset: function(models, options) {
  4615. options || (options = {});
  4616. if (options.parse) models = this.parse(models, options);
  4617. for (var i = 0, l = this.models.length; i < l; i++) {
  4618. this._removeReference(this.models[i]);
  4619. }
  4620. options.previousModels = this.models.slice();
  4621. this._reset();
  4622. if (models) this.add(models, _.extend({silent: true}, options));
  4623. if (!options.silent) this.trigger('reset', this, options);
  4624. return this;
  4625. },
  4626. // Fetch the default set of models for this collection, resetting the
  4627. // collection when they arrive. If `update: true` is passed, the response
  4628. // data will be passed through the `update` method instead of `reset`.
  4629. fetch: function(options) {
  4630. options = options ? _.clone(options) : {};
  4631. if (options.parse === void 0) options.parse = true;
  4632. var success = options.success;
  4633. options.success = function(collection, resp, options) {
  4634. var method = options.update ? 'update' : 'reset';
  4635. collection[method](resp, options);
  4636. if (success) success(collection, resp, options);
  4637. };
  4638. return this.sync('read', this, options);
  4639. },
  4640. // Create a new instance of a model in this collection. Add the model to the
  4641. // collection immediately, unless `wait: true` is passed, in which case we
  4642. // wait for the server to agree.
  4643. create: function(model, options) {
  4644. options = options ? _.clone(options) : {};
  4645. if (!(model = this._prepareModel(model, options))) return false;
  4646. if (!options.wait) this.add(model, options);
  4647. var collection = this;
  4648. var success = options.success;
  4649. options.success = function(model, resp, options) {
  4650. if (options.wait) collection.add(model, options);
  4651. if (success) success(model, resp, options);
  4652. };
  4653. model.save(null, options);
  4654. return model;
  4655. },
  4656. // **parse** converts a response into a list of models to be added to the
  4657. // collection. The default implementation is just to pass it through.
  4658. parse: function(resp, options) {
  4659. return resp;
  4660. },
  4661. // Create a new collection with an identical list of models as this one.
  4662. clone: function() {
  4663. return new this.constructor(this.models);
  4664. },
  4665. // Reset all internal state. Called when the collection is reset.
  4666. _reset: function() {
  4667. this.length = 0;
  4668. this.models.length = 0;
  4669. this._byId = {};
  4670. },
  4671. // Prepare a model or hash of attributes to be added to this collection.
  4672. _prepareModel: function(attrs, options) {
  4673. if (attrs instanceof Model) {
  4674. if (!attrs.collection) attrs.collection = this;
  4675. return attrs;
  4676. }
  4677. options || (options = {});
  4678. options.collection = this;
  4679. var model = new this.model(attrs, options);
  4680. if (!model._validate(attrs, options)) return false;
  4681. return model;
  4682. },
  4683. // Internal method to remove a model's ties to a collection.
  4684. _removeReference: function(model) {
  4685. if (this === model.collection) delete model.collection;
  4686. model.off('all', this._onModelEvent, this);
  4687. },
  4688. // Internal method called every time a model in the set fires an event.
  4689. // Sets need to update their indexes when models change ids. All other
  4690. // events simply proxy through. "add" and "remove" events that originate
  4691. // in other collections are ignored.
  4692. _onModelEvent: function(event, model, collection, options) {
  4693. if ((event === 'add' || event === 'remove') && collection !== this) return;
  4694. if (event === 'destroy') this.remove(model, options);
  4695. if (model && event === 'change:' + model.idAttribute) {
  4696. delete this._byId[model.previous(model.idAttribute)];
  4697. if (model.id != null) this._byId[model.id] = model;
  4698. }
  4699. this.trigger.apply(this, arguments);
  4700. },
  4701. sortedIndex: function (model, value, context) {
  4702. value || (value = this.comparator);
  4703. var iterator = _.isFunction(value) ? value : function(model) {
  4704. return model.get(value);
  4705. };
  4706. return _.sortedIndex(this.models, model, iterator, context);
  4707. }
  4708. });
  4709. // Underscore methods that we want to implement on the Collection.
  4710. var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
  4711. 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
  4712. 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
  4713. 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
  4714. 'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf',
  4715. 'isEmpty', 'chain'];
  4716. // Mix in each Underscore method as a proxy to `Collection#models`.
  4717. _.each(methods, function(method) {
  4718. Collection.prototype[method] = function() {
  4719. var args = slice.call(arguments);
  4720. args.unshift(this.models);
  4721. return _[method].apply(_, args);
  4722. };
  4723. });
  4724. // Underscore methods that take a property name as an argument.
  4725. var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
  4726. // Use attributes instead of properties.
  4727. _.each(attributeMethods, function(method) {
  4728. Collection.prototype[method] = function(value, context) {
  4729. var iterator = _.isFunction(value) ? value : function(model) {
  4730. return model.get(value);
  4731. };
  4732. return _[method](this.models, iterator, context);
  4733. };
  4734. });
  4735. // Backbone.Router
  4736. // ---------------
  4737. // Routers map faux-URLs to actions, and fire events when routes are
  4738. // matched. Creating a new one sets its `routes` hash, if not set statically.
  4739. var Router = Backbone.Router = function(options) {
  4740. options || (options = {});
  4741. if (options.routes) this.routes = options.routes;
  4742. this._bindRoutes();
  4743. this.initialize.apply(this, arguments);
  4744. };
  4745. // Cached regular expressions for matching named param parts and splatted
  4746. // parts of route strings.
  4747. var optionalParam = /\((.*?)\)/g;
  4748. var namedParam = /(\(\?)?:\w+/g;
  4749. var splatParam = /\*\w+/g;
  4750. var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
  4751. // Set up all inheritable **Backbone.Router** properties and methods.
  4752. _.extend(Router.prototype, Events, {
  4753. // Initialize is an empty function by default. Override it with your own
  4754. // initialization logic.
  4755. initialize: function(){},
  4756. // Manually bind a single named route to a callback. For example:
  4757. //
  4758. // this.route('search/:query/p:num', 'search', function(query, num) {
  4759. // ...
  4760. // });
  4761. //
  4762. route: function(route, name, callback) {
  4763. if (!_.isRegExp(route)) route = this._routeToRegExp(route);
  4764. if (!callback) callback = this[name];
  4765. Backbone.history.route(route, _.bind(function(fragment) {
  4766. var args = this._extractParameters(route, fragment);
  4767. callback && callback.apply(this, args);
  4768. this.trigger.apply(this, ['route:' + name].concat(args));
  4769. this.trigger('route', name, args);
  4770. Backbone.history.trigger('route', this, name, args);
  4771. }, this));
  4772. return this;
  4773. },
  4774. // Simple proxy to `Backbone.history` to save a fragment into the history.
  4775. navigate: function(fragment, options) {
  4776. Backbone.history.navigate(fragment, options);
  4777. return this;
  4778. },
  4779. // Bind all defined routes to `Backbone.history`. We have to reverse the
  4780. // order of the routes here to support behavior where the most general
  4781. // routes can be defined at the bottom of the route map.
  4782. _bindRoutes: function() {
  4783. if (!this.routes) return;
  4784. var route, routes = _.keys(this.routes);
  4785. while ((route = routes.pop()) != null) {
  4786. this.route(route, this.routes[route]);
  4787. }
  4788. },
  4789. // Convert a route string into a regular expression, suitable for matching
  4790. // against the current location hash.
  4791. _routeToRegExp: function(route) {
  4792. route = route.replace(escapeRegExp, '\\$&')
  4793. .replace(optionalParam, '(?:$1)?')
  4794. .replace(namedParam, function(match, optional){
  4795. return optional ? match : '([^\/]+)';
  4796. })
  4797. .replace(splatParam, '(.*?)');
  4798. return new RegExp('^' + route + '$');
  4799. },
  4800. // Given a route, and a URL fragment that it matches, return the array of
  4801. // extracted parameters.
  4802. _extractParameters: function(route, fragment) {
  4803. return route.exec(fragment).slice(1);
  4804. }
  4805. });
  4806. // Backbone.History
  4807. // ----------------
  4808. // Handles cross-browser history management, based on URL fragments. If the
  4809. // browser does not support `onhashchange`, falls back to polling.
  4810. var History = Backbone.History = function() {
  4811. this.handlers = [];
  4812. _.bindAll(this, 'checkUrl');
  4813. // Ensure that `History` can be used outside of the browser.
  4814. if (typeof window !== 'undefined') {
  4815. this.location = window.location;
  4816. this.history = window.history;
  4817. }
  4818. };
  4819. // Cached regex for stripping a leading hash/slash and trailing space.
  4820. var routeStripper = /^[#\/]|\s+$/g;
  4821. // Cached regex for stripping leading and trailing slashes.
  4822. var rootStripper = /^\/+|\/+$/g;
  4823. // Cached regex for detecting MSIE.
  4824. var isExplorer = /msie [\w.]+/;
  4825. // Cached regex for removing a trailing slash.
  4826. var trailingSlash = /\/$/;
  4827. // Has the history handling already been started?
  4828. History.started = false;
  4829. // Set up all inheritable **Backbone.History** properties and methods.
  4830. _.extend(History.prototype, Events, {
  4831. // The default interval to poll for hash changes, if necessary, is
  4832. // twenty times a second.
  4833. interval: 50,
  4834. // Gets the true hash value. Cannot use location.hash directly due to bug
  4835. // in Firefox where location.hash will always be decoded.
  4836. getHash: function(window) {
  4837. var match = (window || this).location.href.match(/#(.*)$/);
  4838. return match ? match[1] : '';
  4839. },
  4840. // Get the cross-browser normalized URL fragment, either from the URL,
  4841. // the hash, or the override.
  4842. getFragment: function(fragment, forcePushState) {
  4843. if (fragment == null) {
  4844. if (this._hasPushState || !this._wantsHashChange || forcePushState) {
  4845. fragment = this.location.pathname;
  4846. var root = this.root.replace(trailingSlash, '');
  4847. if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
  4848. } else {
  4849. fragment = this.getHash();
  4850. }
  4851. }
  4852. return fragment.replace(routeStripper, '');
  4853. },
  4854. // Start the hash change handling, returning `true` if the current URL matches
  4855. // an existing route, and `false` otherwise.
  4856. start: function(options) {
  4857. if (History.started) throw new Error("Backbone.history has already been started");
  4858. History.started = true;
  4859. // Figure out the initial configuration. Do we need an iframe?
  4860. // Is pushState desired ... is it available?
  4861. this.options = _.extend({}, {root: '/'}, this.options, options);
  4862. this.root = this.options.root;
  4863. this._wantsHashChange = this.options.hashChange !== false;
  4864. this._wantsPushState = !!this.options.pushState;
  4865. this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
  4866. var fragment = this.getFragment();
  4867. var docMode = document.documentMode;
  4868. var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
  4869. // Normalize root to always include a leading and trailing slash.
  4870. this.root = ('/' + this.root + '/').replace(rootStripper, '/');
  4871. if (oldIE && this._wantsHashChange) {
  4872. this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
  4873. this.navigate(fragment);
  4874. }
  4875. // Depending on whether we're using pushState or hashes, and whether
  4876. // 'onhashchange' is supported, determine how we check the URL state.
  4877. if (this._hasPushState) {
  4878. Backbone.$(window).on('popstate', this.checkUrl);
  4879. } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
  4880. Backbone.$(window).on('hashchange', this.checkUrl);
  4881. } else if (this._wantsHashChange) {
  4882. this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
  4883. }
  4884. // Determine if we need to change the base url, for a pushState link
  4885. // opened by a non-pushState browser.
  4886. this.fragment = fragment;
  4887. var loc = this.location;
  4888. var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
  4889. // If we've started off with a route from a `pushState`-enabled browser,
  4890. // but we're currently in a browser that doesn't support it...
  4891. if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
  4892. this.fragment = this.getFragment(null, true);
  4893. this.location.replace(this.root + this.location.search + '#' + this.fragment);
  4894. // Return immediately as browser will do redirect to new url
  4895. return true;
  4896. // Or if we've started out with a hash-based route, but we're currently
  4897. // in a browser where it could be `pushState`-based instead...
  4898. } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
  4899. this.fragment = this.getHash().replace(routeStripper, '');
  4900. this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
  4901. }
  4902. if (!this.options.silent) return this.loadUrl();
  4903. },
  4904. // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
  4905. // but possibly useful for unit testing Routers.
  4906. stop: function() {
  4907. Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
  4908. clearInterval(this._checkUrlInterval);
  4909. History.started = false;
  4910. },
  4911. // Add a route to be tested when the fragment changes. Routes added later
  4912. // may override previous routes.
  4913. route: function(route, callback) {
  4914. this.handlers.unshift({route: route, callback: callback});
  4915. },
  4916. // Checks the current URL to see if it has changed, and if it has,
  4917. // calls `loadUrl`, normalizing across the hidden iframe.
  4918. checkUrl: function(e) {
  4919. var current = this.getFragment();
  4920. if (current === this.fragment && this.iframe) {
  4921. current = this.getFragment(this.getHash(this.iframe));
  4922. }
  4923. if (current === this.fragment) return false;
  4924. if (this.iframe) this.navigate(current);
  4925. this.loadUrl() || this.loadUrl(this.getHash());
  4926. },
  4927. // Attempt to load the current URL fragment. If a route succeeds with a
  4928. // match, returns `true`. If no defined routes matches the fragment,
  4929. // returns `false`.
  4930. loadUrl: function(fragmentOverride) {
  4931. var fragment = this.fragment = this.getFragment(fragmentOverride);
  4932. var matched = _.any(this.handlers, function(handler) {
  4933. if (handler.route.test(fragment)) {
  4934. handler.callback(fragment);
  4935. return true;
  4936. }
  4937. });
  4938. return matched;
  4939. },
  4940. // Save a fragment into the hash history, or replace the URL state if the
  4941. // 'replace' option is passed. You are responsible for properly URL-encoding
  4942. // the fragment in advance.
  4943. //
  4944. // The options object can contain `trigger: true` if you wish to have the
  4945. // route callback be fired (not usually desirable), or `replace: true`, if
  4946. // you wish to modify the current URL without adding an entry to the history.
  4947. navigate: function(fragment, options) {
  4948. if (!History.started) return false;
  4949. if (!options || options === true) options = {trigger: options};
  4950. fragment = this.getFragment(fragment || '');
  4951. if (this.fragment === fragment) return;
  4952. this.fragment = fragment;
  4953. var url = this.root + fragment;
  4954. // If pushState is available, we use it to set the fragment as a real URL.
  4955. if (this._hasPushState) {
  4956. this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
  4957. // If hash changes haven't been explicitly disabled, update the hash
  4958. // fragment to store history.
  4959. } else if (this._wantsHashChange) {
  4960. this._updateHash(this.location, fragment, options.replace);
  4961. if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
  4962. // Opening and closing the iframe tricks IE7 and earlier to push a
  4963. // history entry on hash-tag change. When replace is true, we don't
  4964. // want this.
  4965. if(!options.replace) this.iframe.document.open().close();
  4966. this._updateHash(this.iframe.location, fragment, options.replace);
  4967. }
  4968. // If you've told us that you explicitly don't want fallback hashchange-
  4969. // based history, then `navigate` becomes a page refresh.
  4970. } else {
  4971. return this.location.assign(url);
  4972. }
  4973. if (options.trigger) this.loadUrl(fragment);
  4974. },
  4975. // Update the hash location, either replacing the current entry, or adding
  4976. // a new one to the browser history.
  4977. _updateHash: function(location, fragment, replace) {
  4978. if (replace) {
  4979. var href = location.href.replace(/(javascript:|#).*$/, '');
  4980. location.replace(href + '#' + fragment);
  4981. } else {
  4982. // Some browsers require that `hash` contains a leading #.
  4983. location.hash = '#' + fragment;
  4984. }
  4985. }
  4986. });
  4987. // Create the default Backbone.history.
  4988. Backbone.history = new History;
  4989. // Backbone.View
  4990. // -------------
  4991. // Creating a Backbone.View creates its initial element outside of the DOM,
  4992. // if an existing element is not provided...
  4993. var View = Backbone.View = function(options) {
  4994. this.cid = _.uniqueId('view');
  4995. this._configure(options || {});
  4996. this._ensureElement();
  4997. this.initialize.apply(this, arguments);
  4998. this.delegateEvents();
  4999. };
  5000. // Cached regex to split keys for `delegate`.
  5001. var delegateEventSplitter = /^(\S+)\s*(.*)$/;
  5002. // List of view options to be merged as properties.
  5003. var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
  5004. // Set up all inheritable **Backbone.View** properties and methods.
  5005. _.extend(View.prototype, Events, {
  5006. // The default `tagName` of a View's element is `"div"`.
  5007. tagName: 'div',
  5008. // jQuery delegate for element lookup, scoped to DOM elements within the
  5009. // current view. This should be prefered to global lookups where possible.
  5010. $: function(selector) {
  5011. return this.$el.find(selector);
  5012. },
  5013. // Initialize is an empty function by default. Override it with your own
  5014. // initialization logic.
  5015. initialize: function(){},
  5016. // **render** is the core function that your view should override, in order
  5017. // to populate its element (`this.el`), with the appropriate HTML. The
  5018. // convention is for **render** to always return `this`.
  5019. render: function() {
  5020. return this;
  5021. },
  5022. // Remove this view by taking the element out of the DOM, and removing any
  5023. // applicable Backbone.Events listeners.
  5024. remove: function() {
  5025. this.$el.remove();
  5026. this.stopListening();
  5027. return this;
  5028. },
  5029. // Change the view's element (`this.el` property), including event
  5030. // re-delegation.
  5031. setElement: function(element, delegate) {
  5032. if (this.$el) this.undelegateEvents();
  5033. this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
  5034. this.el = this.$el[0];
  5035. if (delegate !== false) this.delegateEvents();
  5036. return this;
  5037. },
  5038. // Set callbacks, where `this.events` is a hash of
  5039. //
  5040. // *{"event selector": "callback"}*
  5041. //
  5042. // {
  5043. // 'mousedown .title': 'edit',
  5044. // 'click .button': 'save'
  5045. // 'click .open': function(e) { ... }
  5046. // }
  5047. //
  5048. // pairs. Callbacks will be bound to the view, with `this` set properly.
  5049. // Uses event delegation for efficiency.
  5050. // Omitting the selector binds the event to `this.el`.
  5051. // This only works for delegate-able events: not `focus`, `blur`, and
  5052. // not `change`, `submit`, and `reset` in Internet Explorer.
  5053. delegateEvents: function(events) {
  5054. if (!(events || (events = _.result(this, 'events')))) return;
  5055. this.undelegateEvents();
  5056. for (var key in events) {
  5057. var method = events[key];
  5058. if (!_.isFunction(method)) method = this[events[key]];
  5059. if (!method) throw new Error('Method "' + events[key] + '" does not exist');
  5060. var match = key.match(delegateEventSplitter);
  5061. var eventName = match[1], selector = match[2];
  5062. method = _.bind(method, this);
  5063. eventName += '.delegateEvents' + this.cid;
  5064. if (selector === '') {
  5065. this.$el.on(eventName, method);
  5066. } else {
  5067. this.$el.on(eventName, selector, method);
  5068. }
  5069. }
  5070. },
  5071. // Clears all callbacks previously bound to the view with `delegateEvents`.
  5072. // You usually don't need to use this, but may wish to if you have multiple
  5073. // Backbone views attached to the same DOM element.
  5074. undelegateEvents: function() {
  5075. this.$el.off('.delegateEvents' + this.cid);
  5076. },
  5077. // Performs the initial configuration of a View with a set of options.
  5078. // Keys with special meaning *(model, collection, id, className)*, are
  5079. // attached directly to the view.
  5080. _configure: function(options) {
  5081. if (this.options) options = _.extend({}, _.result(this, 'options'), options);
  5082. _.extend(this, _.pick(options, viewOptions));
  5083. this.options = options;
  5084. },
  5085. // Ensure that the View has a DOM element to render into.
  5086. // If `this.el` is a string, pass it through `$()`, take the first
  5087. // matching element, and re-assign it to `el`. Otherwise, create
  5088. // an element from the `id`, `className` and `tagName` properties.
  5089. _ensureElement: function() {
  5090. if (!this.el) {
  5091. var attrs = _.extend({}, _.result(this, 'attributes'));
  5092. if (this.id) attrs.id = _.result(this, 'id');
  5093. if (this.className) attrs['class'] = _.result(this, 'className');
  5094. var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
  5095. this.setElement($el, false);
  5096. } else {
  5097. this.setElement(_.result(this, 'el'), false);
  5098. }
  5099. }
  5100. });
  5101. // Backbone.sync
  5102. // -------------
  5103. // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
  5104. var methodMap = {
  5105. 'create': 'POST',
  5106. 'update': 'PUT',
  5107. 'patch': 'PATCH',
  5108. 'delete': 'DELETE',
  5109. 'read': 'GET'
  5110. };
  5111. // Override this function to change the manner in which Backbone persists
  5112. // models to the server. You will be passed the type of request, and the
  5113. // model in question. By default, makes a RESTful Ajax request
  5114. // to the model's `url()`. Some possible customizations could be:
  5115. //
  5116. // * Use `setTimeout` to batch rapid-fire updates into a single request.
  5117. // * Send up the models as XML instead of JSON.
  5118. // * Persist models via WebSockets instead of Ajax.
  5119. //
  5120. // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
  5121. // as `POST`, with a `_method` parameter containing the true HTTP method,
  5122. // as well as all requests with the body as `application/x-www-form-urlencoded`
  5123. // instead of `application/json` with the model in a param named `model`.
  5124. // Useful when interfacing with server-side languages like **PHP** that make
  5125. // it difficult to read the body of `PUT` requests.
  5126. Backbone.sync = function(method, model, options) {
  5127. var type = methodMap[method];
  5128. // Default options, unless specified.
  5129. _.defaults(options || (options = {}), {
  5130. emulateHTTP: Backbone.emulateHTTP,
  5131. emulateJSON: Backbone.emulateJSON
  5132. });
  5133. // Default JSON-request options.
  5134. var params = {type: type, dataType: 'json'};
  5135. // Ensure that we have a URL.
  5136. if (!options.url) {
  5137. params.url = _.result(model, 'url') || urlError();
  5138. }
  5139. // Ensure that we have the appropriate request data.
  5140. if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
  5141. params.contentType = 'application/json';
  5142. params.data = JSON.stringify(options.attrs || model.toJSON(options));
  5143. }
  5144. // For older servers, emulate JSON by encoding the request into an HTML-form.
  5145. if (options.emulateJSON) {
  5146. params.contentType = 'application/x-www-form-urlencoded';
  5147. params.data = params.data ? {model: params.data} : {};
  5148. }
  5149. // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
  5150. // And an `X-HTTP-Method-Override` header.
  5151. if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
  5152. params.type = 'POST';
  5153. if (options.emulateJSON) params.data._method = type;
  5154. var beforeSend = options.beforeSend;
  5155. options.beforeSend = function(xhr) {
  5156. xhr.setRequestHeader('X-HTTP-Method-Override', type);
  5157. if (beforeSend) return beforeSend.apply(this, arguments);
  5158. };
  5159. }
  5160. // Don't process data on a non-GET request.
  5161. if (params.type !== 'GET' && !options.emulateJSON) {
  5162. params.processData = false;
  5163. }
  5164. var success = options.success;
  5165. options.success = function(resp) {
  5166. if (success) success(model, resp, options);
  5167. model.trigger('sync', model, resp, options);
  5168. };
  5169. var error = options.error;
  5170. options.error = function(xhr) {
  5171. if (error) error(model, xhr, options);
  5172. model.trigger('error', model, xhr, options);
  5173. };
  5174. // Make the request, allowing the user to override any Ajax options.
  5175. var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
  5176. model.trigger('request', model, xhr, options);
  5177. return xhr;
  5178. };
  5179. // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
  5180. Backbone.ajax = function() {
  5181. return Backbone.$.ajax.apply(Backbone.$, arguments);
  5182. };
  5183. // Helpers
  5184. // -------
  5185. // Helper function to correctly set up the prototype chain, for subclasses.
  5186. // Similar to `goog.inherits`, but uses a hash of prototype properties and
  5187. // class properties to be extended.
  5188. var extend = function(protoProps, staticProps) {
  5189. var parent = this;
  5190. var child;
  5191. // The constructor function for the new subclass is either defined by you
  5192. // (the "constructor" property in your `extend` definition), or defaulted
  5193. // by us to simply call the parent's constructor.
  5194. if (protoProps && _.has(protoProps, 'constructor')) {
  5195. child = protoProps.constructor;
  5196. } else {
  5197. child = function(){ return parent.apply(this, arguments); };
  5198. }
  5199. // Add static properties to the constructor function, if supplied.
  5200. _.extend(child, parent, staticProps);
  5201. // Set the prototype chain to inherit from `parent`, without calling
  5202. // `parent`'s constructor function.
  5203. var Surrogate = function(){ this.constructor = child; };
  5204. Surrogate.prototype = parent.prototype;
  5205. child.prototype = new Surrogate;
  5206. // Add prototype properties (instance properties) to the subclass,
  5207. // if supplied.
  5208. if (protoProps) _.extend(child.prototype, protoProps);
  5209. // Set a convenience property in case the parent's prototype is needed
  5210. // later.
  5211. child.__super__ = parent.prototype;
  5212. return child;
  5213. };
  5214. // Set up inheritance for the model, collection, router, view and history.
  5215. Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
  5216. // Throw an error when a URL is needed, and none is supplied.
  5217. var urlError = function() {
  5218. throw new Error('A "url" property or function must be specified');
  5219. };
  5220. }).call(this);
  5221. /*
  5222. Copyright (c) 2011-2013 @WalmartLabs
  5223. Permission is hereby granted, free of charge, to any person obtaining a copy
  5224. of this software and associated documentation files (the "Software"), to
  5225. deal in the Software without restriction, including without limitation the
  5226. rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  5227. sell copies of the Software, and to permit persons to whom the Software is
  5228. furnished to do so, subject to the following conditions:
  5229. The above copyright notice and this permission notice shall be included in
  5230. all copies or substantial portions of the Software.
  5231. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  5232. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  5233. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  5234. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  5235. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  5236. FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  5237. DEALINGS IN THE SOFTWARE.
  5238. */
  5239. ;;
  5240. (function() {
  5241. /*global cloneInheritVars, createInheritVars, createRegistryWrapper, getValue, inheritVars */
  5242. //support zepto.forEach on jQuery
  5243. if (!$.fn.forEach) {
  5244. $.fn.forEach = function(iterator, context) {
  5245. $.fn.each.call(this, function(index) {
  5246. iterator.call(context || this, this, index);
  5247. });
  5248. };
  5249. }
  5250. var viewNameAttributeName = 'data-view-name',
  5251. viewCidAttributeName = 'data-view-cid',
  5252. viewHelperAttributeName = 'data-view-helper';
  5253. //view instances
  5254. var viewsIndexedByCid = {};
  5255. var Thorax = this.Thorax = {
  5256. VERSION: '2.0.0rc1',
  5257. templatePathPrefix: '',
  5258. templates: {},
  5259. //view classes
  5260. Views: {},
  5261. //certain error prone pieces of code (on Android only it seems)
  5262. //are wrapped in a try catch block, then trigger this handler in
  5263. //the catch, with the name of the function or event that was
  5264. //trying to be executed. Override this with a custom handler
  5265. //to debug / log / etc
  5266. onException: function(name, err) {
  5267. throw err;
  5268. }
  5269. };
  5270. Thorax.View = Backbone.View.extend({
  5271. constructor: function() {
  5272. var response = Backbone.View.apply(this, arguments);
  5273. _.each(inheritVars, function(obj) {
  5274. if (obj.ctor) {
  5275. obj.ctor.call(this, response);
  5276. }
  5277. }, this);
  5278. return response;
  5279. },
  5280. _configure: function(options) {
  5281. var self = this;
  5282. this._objectOptionsByCid = {};
  5283. this._boundDataObjectsByCid = {};
  5284. // Setup object event tracking
  5285. _.each(inheritVars, function(obj) {
  5286. self[obj.name] = [];
  5287. });
  5288. viewsIndexedByCid[this.cid] = this;
  5289. this.children = {};
  5290. this._renderCount = 0;
  5291. //this.options is removed in Thorax.View, we merge passed
  5292. //properties directly with the view and template context
  5293. _.extend(this, options || {});
  5294. // Setup helpers
  5295. bindHelpers.call(this);
  5296. _.each(inheritVars, function(obj) {
  5297. if (obj.configure) {
  5298. obj.configure.call(this);
  5299. }
  5300. }, this);
  5301. },
  5302. setElement : function() {
  5303. var response = Backbone.View.prototype.setElement.apply(this, arguments);
  5304. this.name && this.$el.attr(viewNameAttributeName, this.name);
  5305. this.$el.attr(viewCidAttributeName, this.cid);
  5306. return response;
  5307. },
  5308. _addChild: function(view) {
  5309. this.children[view.cid] = view;
  5310. if (!view.parent) {
  5311. view.parent = this;
  5312. }
  5313. this.trigger('child', view);
  5314. return view;
  5315. },
  5316. _removeChild: function(view) {
  5317. delete this.children[view.cid];
  5318. view.parent = null;
  5319. return view;
  5320. },
  5321. destroy: function(options) {
  5322. options = _.defaults(options || {}, {
  5323. children: true
  5324. });
  5325. _.each(this._boundDataObjectsByCid, this.unbindDataObject, this);
  5326. this.trigger('destroyed');
  5327. delete viewsIndexedByCid[this.cid];
  5328. _.each(this.children, function(child) {
  5329. this._removeChild(child);
  5330. if (options.children) {
  5331. child.destroy();
  5332. }
  5333. }, this);
  5334. if (this.parent) {
  5335. this.parent._removeChild(this);
  5336. }
  5337. this.remove(); // Will call stopListening()
  5338. },
  5339. render: function(output) {
  5340. this._previousHelpers = _.filter(this.children, function(child) { return child._helperOptions; });
  5341. var children = {};
  5342. _.each(this.children, function(child, key) {
  5343. if (!child._helperOptions) {
  5344. children[key] = child;
  5345. }
  5346. });
  5347. this.children = children;
  5348. if (_.isUndefined(output) || (!_.isElement(output) && !Thorax.Util.is$(output) && !(output && output.el) && !_.isString(output) && !_.isFunction(output))) {
  5349. // try one more time to assign the template, if we don't
  5350. // yet have one we must raise
  5351. assignTemplate.call(this, 'template', {
  5352. required: true
  5353. });
  5354. output = this.renderTemplate(this.template);
  5355. } else if (_.isFunction(output)) {
  5356. output = this.renderTemplate(output);
  5357. }
  5358. // Destroy any helpers that may be lingering
  5359. _.each(this._previousHelpers, function(child) {
  5360. child.destroy();
  5361. child.parent = undefined;
  5362. });
  5363. this._previousHelpers = undefined;
  5364. //accept a view, string, Handlebars.SafeString or DOM element
  5365. this.html((output && output.el) || (output && output.string) || output);
  5366. ++this._renderCount;
  5367. this.trigger('rendered');
  5368. return output;
  5369. },
  5370. context: function() {
  5371. return _.extend({}, (this.model && this.model.attributes) || {});
  5372. },
  5373. _getContext: function() {
  5374. return _.extend({}, this, getValue(this, 'context') || {});
  5375. },
  5376. // Private variables in handlebars / options.data in template helpers
  5377. _getData: function(data) {
  5378. return {
  5379. view: this,
  5380. cid: _.uniqueId('t'),
  5381. yield: function() {
  5382. // fn is seeded by template helper passing context to data
  5383. return data.fn && data.fn(data);
  5384. }
  5385. };
  5386. },
  5387. _getHelpers: function() {
  5388. if (this.helpers) {
  5389. return _.extend({}, Handlebars.helpers, this.helpers);
  5390. } else {
  5391. return Handlebars.helpers;
  5392. }
  5393. },
  5394. renderTemplate: function(file, context, ignoreErrors) {
  5395. var template;
  5396. context = context || this._getContext();
  5397. if (_.isFunction(file)) {
  5398. template = file;
  5399. } else {
  5400. template = Thorax.Util.getTemplate(file, ignoreErrors);
  5401. }
  5402. if (!template) {
  5403. return '';
  5404. } else {
  5405. return template(context, {
  5406. helpers: this._getHelpers(),
  5407. data: this._getData(context)
  5408. });
  5409. }
  5410. },
  5411. ensureRendered: function() {
  5412. !this._renderCount && this.render();
  5413. },
  5414. appendTo: function(el) {
  5415. this.ensureRendered();
  5416. $(el).append(this.el);
  5417. this.trigger('ready', {target: this});
  5418. },
  5419. html: function(html) {
  5420. if (_.isUndefined(html)) {
  5421. return this.el.innerHTML;
  5422. } else {
  5423. // Event for IE element fixes
  5424. this.trigger('before:append');
  5425. var element = this._replaceHTML(html);
  5426. this.trigger('append');
  5427. return element;
  5428. }
  5429. },
  5430. _replaceHTML: function(html) {
  5431. this.el.innerHTML = "";
  5432. return this.$el.append(html);
  5433. },
  5434. _anchorClick: function(event) {
  5435. var target = $(event.currentTarget),
  5436. href = target.attr('href');
  5437. // Route anything that starts with # or / (excluding //domain urls)
  5438. if (href && (href[0] === '#' || (href[0] === '/' && href[1] !== '/'))) {
  5439. Backbone.history.navigate(href, {
  5440. trigger: true
  5441. });
  5442. return false;
  5443. }
  5444. return true;
  5445. }
  5446. });
  5447. Thorax.View.extend = function() {
  5448. createInheritVars(this);
  5449. var child = Backbone.View.extend.apply(this, arguments);
  5450. child.__parent__ = this;
  5451. resetInheritVars(child);
  5452. return child;
  5453. };
  5454. createRegistryWrapper(Thorax.View, Thorax.Views);
  5455. function bindHelpers() {
  5456. if (this.helpers) {
  5457. _.each(this.helpers, function(helper, name) {
  5458. var view = this;
  5459. this.helpers[name] = function() {
  5460. var args = _.toArray(arguments),
  5461. options = _.last(args);
  5462. options.context = this;
  5463. return helper.apply(view, args);
  5464. };
  5465. }, this);
  5466. }
  5467. }
  5468. //$(selector).view() helper
  5469. $.fn.view = function(options) {
  5470. options = _.defaults(options || {}, {
  5471. helper: true
  5472. });
  5473. var selector = '[' + viewCidAttributeName + ']';
  5474. if (!options.helper) {
  5475. selector += ':not([' + viewHelperAttributeName + '])';
  5476. }
  5477. var el = $(this).closest(selector);
  5478. return (el && viewsIndexedByCid[el.attr(viewCidAttributeName)]) || false;
  5479. };
  5480. ;;
  5481. /*global createRegistryWrapper:true, cloneEvents: true */
  5482. function createRegistryWrapper(klass, hash) {
  5483. var $super = klass.extend;
  5484. klass.extend = function() {
  5485. var child = $super.apply(this, arguments);
  5486. if (child.prototype.name) {
  5487. hash[child.prototype.name] = child;
  5488. }
  5489. return child;
  5490. };
  5491. }
  5492. function registryGet(object, type, name, ignoreErrors) {
  5493. var target = object[type],
  5494. value;
  5495. if (name.indexOf('.') >= 0) {
  5496. var bits = name.split(/\./);
  5497. name = bits.pop();
  5498. _.each(bits, function(key) {
  5499. target = target[key];
  5500. });
  5501. }
  5502. target && (value = target[name]);
  5503. if (!value && !ignoreErrors) {
  5504. throw new Error(type + ': ' + name + ' does not exist.');
  5505. } else {
  5506. return value;
  5507. }
  5508. }
  5509. function assignTemplate(attributeName, options) {
  5510. var template;
  5511. // if attribute is the name of template to fetch
  5512. if (_.isString(this[attributeName])) {
  5513. template = Thorax.Util.getTemplate(this[attributeName], true);
  5514. // else try and fetch the template based on the name
  5515. } else if (this.name && !_.isFunction(this[attributeName])) {
  5516. template = Thorax.Util.getTemplate(this.name + (options.extension || ''), true);
  5517. }
  5518. // CollectionView and LayoutView have a defaultTemplate that may be used if none
  5519. // was found, regular views must have a template if render() is called
  5520. if (!template && attributeName === 'template' && this._defaultTemplate) {
  5521. template = this._defaultTemplate;
  5522. }
  5523. // if we found something, assign it
  5524. if (template && !_.isFunction(this[attributeName])) {
  5525. this[attributeName] = template;
  5526. }
  5527. // if nothing was found and it's required, throw
  5528. if (options.required && !_.isFunction(this[attributeName])) {
  5529. throw new Error('View ' + (this.name || this.cid) + ' requires: ' + attributeName);
  5530. }
  5531. }
  5532. // getValue is used instead of _.result because we
  5533. // need an extra scope parameter, and will minify
  5534. // better than _.result
  5535. function getValue(object, prop, scope) {
  5536. if (!(object && object[prop])) {
  5537. return null;
  5538. }
  5539. return _.isFunction(object[prop])
  5540. ? object[prop].call(scope || object)
  5541. : object[prop];
  5542. }
  5543. var inheritVars = {};
  5544. function createInheritVars(self) {
  5545. // Ensure that we have our static event objects
  5546. _.each(inheritVars, function(obj) {
  5547. if (!self[obj.name]) {
  5548. self[obj.name] = [];
  5549. }
  5550. });
  5551. }
  5552. function resetInheritVars(self) {
  5553. // Ensure that we have our static event objects
  5554. _.each(inheritVars, function(obj) {
  5555. self[obj.name] = [];
  5556. });
  5557. }
  5558. function walkInheritTree(source, fieldName, isStatic, callback) {
  5559. var tree = [];
  5560. if (_.has(source, fieldName)) {
  5561. tree.push(source);
  5562. }
  5563. var iterate = source;
  5564. if (isStatic) {
  5565. while (iterate = iterate.__parent__) {
  5566. if (_.has(iterate, fieldName)) {
  5567. tree.push(iterate);
  5568. }
  5569. }
  5570. } else {
  5571. iterate = iterate.constructor;
  5572. while (iterate) {
  5573. if (iterate.prototype && _.has(iterate.prototype, fieldName)) {
  5574. tree.push(iterate.prototype);
  5575. }
  5576. iterate = iterate.__super__ && iterate.__super__.constructor;
  5577. }
  5578. }
  5579. var i = tree.length;
  5580. while (i--) {
  5581. _.each(getValue(tree[i], fieldName, source), callback);
  5582. }
  5583. }
  5584. function objectEvents(target, eventName, callback, context) {
  5585. if (_.isObject(callback)) {
  5586. var spec = inheritVars[eventName];
  5587. if (spec && spec.event) {
  5588. addEvents(target['_' + eventName + 'Events'], callback, context);
  5589. return true;
  5590. }
  5591. }
  5592. }
  5593. function addEvents(target, source, context) {
  5594. _.each(source, function(callback, eventName) {
  5595. if (_.isArray(callback)) {
  5596. _.each(callback, function(cb) {
  5597. target.push([eventName, cb, context]);
  5598. });
  5599. } else {
  5600. target.push([eventName, callback, context]);
  5601. }
  5602. });
  5603. }
  5604. function getOptionsData(options) {
  5605. if (!options || !options.data) {
  5606. throw new Error('Handlebars template compiled without data, use: Handlebars.compile(template, {data: true})');
  5607. }
  5608. return options.data;
  5609. }
  5610. // These whitelisted attributes will be the only ones passed
  5611. // from the options hash to Thorax.Util.tag
  5612. var htmlAttributesToCopy = ['id', 'className', 'tagName'];
  5613. // In helpers "tagName" or "tag" may be specified, as well
  5614. // as "class" or "className". Normalize to "tagName" and
  5615. // "className" to match the property names used by Backbone
  5616. // jQuery, etc. Special case for "className" in
  5617. // Thorax.Util.tag: will be rewritten as "class" in
  5618. // generated HTML.
  5619. function normalizeHTMLAttributeOptions(options) {
  5620. if (options.tag) {
  5621. options.tagName = options.tag;
  5622. delete options.tag;
  5623. }
  5624. if (options['class']) {
  5625. options.className = options['class'];
  5626. delete options['class'];
  5627. }
  5628. }
  5629. Thorax.Util = {
  5630. getViewInstance: function(name, attributes) {
  5631. attributes = attributes || {};
  5632. if (_.isString(name)) {
  5633. var Klass = registryGet(Thorax, 'Views', name, false);
  5634. return Klass.cid ? _.extend(Klass, attributes || {}) : new Klass(attributes);
  5635. } else if (_.isFunction(name)) {
  5636. return new name(attributes);
  5637. } else {
  5638. return name;
  5639. }
  5640. },
  5641. getTemplate: function(file, ignoreErrors) {
  5642. //append the template path prefix if it is missing
  5643. var pathPrefix = Thorax.templatePathPrefix,
  5644. template;
  5645. if (pathPrefix && file.substr(0, pathPrefix.length) !== pathPrefix) {
  5646. file = pathPrefix + file;
  5647. }
  5648. // Without extension
  5649. file = file.replace(/\.handlebars$/, '');
  5650. template = Thorax.templates[file];
  5651. if (!template) {
  5652. // With extension
  5653. file = file + '.handlebars';
  5654. template = Thorax.templates[file];
  5655. }
  5656. if (!template && !ignoreErrors) {
  5657. throw new Error('templates: ' + file + ' does not exist.');
  5658. }
  5659. return template;
  5660. },
  5661. //'selector' is not present in $('<p></p>')
  5662. //TODO: investigage a better detection method
  5663. is$: function(obj) {
  5664. return _.isObject(obj) && ('length' in obj);
  5665. },
  5666. expandToken: function(input, scope) {
  5667. if (input && input.indexOf && input.indexOf('{{') >= 0) {
  5668. var re = /(?:\{?[^{]+)|(?:\{\{([^}]+)\}\})/g,
  5669. match,
  5670. ret = [];
  5671. function deref(token, scope) {
  5672. if (token.match(/^("|')/) && token.match(/("|')$/)) {
  5673. return token.replace(/(^("|')|('|")$)/g, '');
  5674. }
  5675. var segments = token.split('.'),
  5676. len = segments.length;
  5677. for (var i = 0; scope && i < len; i++) {
  5678. if (segments[i] !== 'this') {
  5679. scope = scope[segments[i]];
  5680. }
  5681. }
  5682. return scope;
  5683. }
  5684. while (match = re.exec(input)) {
  5685. if (match[1]) {
  5686. var params = match[1].split(/\s+/);
  5687. if (params.length > 1) {
  5688. var helper = params.shift();
  5689. params = _.map(params, function(param) { return deref(param, scope); });
  5690. if (Handlebars.helpers[helper]) {
  5691. ret.push(Handlebars.helpers[helper].apply(scope, params));
  5692. } else {
  5693. // If the helper is not defined do nothing
  5694. ret.push(match[0]);
  5695. }
  5696. } else {
  5697. ret.push(deref(params[0], scope));
  5698. }
  5699. } else {
  5700. ret.push(match[0]);
  5701. }
  5702. }
  5703. input = ret.join('');
  5704. }
  5705. return input;
  5706. },
  5707. tag: function(attributes, content, scope) {
  5708. var htmlAttributes = _.omit(attributes, 'tagName'),
  5709. tag = attributes.tagName || 'div';
  5710. return '<' + tag + ' ' + _.map(htmlAttributes, function(value, key) {
  5711. if (_.isUndefined(value) || key === 'expand-tokens') {
  5712. return '';
  5713. }
  5714. var formattedValue = value;
  5715. if (scope) {
  5716. formattedValue = Thorax.Util.expandToken(value, scope);
  5717. }
  5718. return (key === 'className' ? 'class' : key) + '="' + Handlebars.Utils.escapeExpression(formattedValue) + '"';
  5719. }).join(' ') + '>' + (_.isUndefined(content) ? '' : content) + '</' + tag + '>';
  5720. }
  5721. };
  5722. ;;
  5723. /*global createInheritVars, inheritVars */
  5724. Thorax.Mixins = {};
  5725. inheritVars.mixins = {
  5726. name: 'mixins',
  5727. configure: function() {
  5728. _.each(this.constructor.mixins, this.mixin, this);
  5729. _.each(this.mixins, this.mixin, this);
  5730. }
  5731. };
  5732. _.extend(Thorax.View, {
  5733. mixin: function(mixin) {
  5734. createInheritVars(this);
  5735. this.mixins.push(mixin);
  5736. },
  5737. registerMixin: function(name, callback, methods) {
  5738. Thorax.Mixins[name] = [callback, methods];
  5739. }
  5740. });
  5741. Thorax.View.prototype.mixin = function(name) {
  5742. if (!this._appliedMixins) {
  5743. this._appliedMixins = [];
  5744. }
  5745. if (this._appliedMixins.indexOf(name) === -1) {
  5746. this._appliedMixins.push(name);
  5747. if (_.isFunction(name)) {
  5748. name.call(this);
  5749. } else {
  5750. var mixin = Thorax.Mixins[name];
  5751. _.extend(this, mixin[1]);
  5752. //mixin callback may be an array of [callback, arguments]
  5753. if (_.isArray(mixin[0])) {
  5754. mixin[0][0].apply(this, mixin[0][1]);
  5755. } else {
  5756. mixin[0].apply(this, _.toArray(arguments).slice(1));
  5757. }
  5758. }
  5759. }
  5760. };
  5761. ;;
  5762. /*global createInheritVars, inheritVars, objectEvents, walkInheritTree */
  5763. // Save a copy of the _on method to call as a $super method
  5764. var _on = Thorax.View.prototype.on;
  5765. inheritVars.event = {
  5766. name: '_events',
  5767. configure: function() {
  5768. var self = this;
  5769. walkInheritTree(this.constructor, '_events', true, function(event) {
  5770. self.on.apply(self, event);
  5771. });
  5772. walkInheritTree(this, 'events', false, function(handler, eventName) {
  5773. self.on(eventName, handler, self);
  5774. });
  5775. }
  5776. };
  5777. _.extend(Thorax.View, {
  5778. on: function(eventName, callback) {
  5779. createInheritVars(this);
  5780. if (objectEvents(this, eventName, callback)) {
  5781. return this;
  5782. }
  5783. //accept on({"rendered": handler})
  5784. if (_.isObject(eventName)) {
  5785. _.each(eventName, function(value, key) {
  5786. this.on(key, value);
  5787. }, this);
  5788. } else {
  5789. //accept on({"rendered": [handler, handler]})
  5790. if (_.isArray(callback)) {
  5791. _.each(callback, function(cb) {
  5792. this._events.push([eventName, cb]);
  5793. }, this);
  5794. //accept on("rendered", handler)
  5795. } else {
  5796. this._events.push([eventName, callback]);
  5797. }
  5798. }
  5799. return this;
  5800. }
  5801. });
  5802. _.extend(Thorax.View.prototype, {
  5803. on: function(eventName, callback, context) {
  5804. if (objectEvents(this, eventName, callback, context)) {
  5805. return this;
  5806. }
  5807. if (_.isObject(eventName) && arguments.length < 3) {
  5808. //accept on({"rendered": callback})
  5809. _.each(eventName, function(value, key) {
  5810. this.on(key, value, callback || this); // callback is context in this form of the call
  5811. }, this);
  5812. } else {
  5813. //accept on("rendered", callback, context)
  5814. //accept on("click a", callback, context)
  5815. _.each((_.isArray(callback) ? callback : [callback]), function(callback) {
  5816. var params = eventParamsFromEventItem.call(this, eventName, callback, context || this);
  5817. if (params.type === 'DOM') {
  5818. //will call _addEvent during delegateEvents()
  5819. if (!this._eventsToDelegate) {
  5820. this._eventsToDelegate = [];
  5821. }
  5822. this._eventsToDelegate.push(params);
  5823. } else {
  5824. this._addEvent(params);
  5825. }
  5826. }, this);
  5827. }
  5828. return this;
  5829. },
  5830. delegateEvents: function(events) {
  5831. this.undelegateEvents();
  5832. if (events) {
  5833. if (_.isFunction(events)) {
  5834. events = events.call(this);
  5835. }
  5836. this._eventsToDelegate = [];
  5837. this.on(events);
  5838. }
  5839. this._eventsToDelegate && _.each(this._eventsToDelegate, this._addEvent, this);
  5840. },
  5841. //params may contain:
  5842. //- name
  5843. //- originalName
  5844. //- selector
  5845. //- type "view" || "DOM"
  5846. //- handler
  5847. _addEvent: function(params) {
  5848. if (params.type === 'view') {
  5849. _.each(params.name.split(/\s+/), function(name) {
  5850. _on.call(this, name, bindEventHandler.call(this, 'view-event:', params));
  5851. }, this);
  5852. } else {
  5853. var boundHandler = bindEventHandler.call(this, 'dom-event:', params);
  5854. if (!params.nested) {
  5855. boundHandler = containHandlerToCurentView(boundHandler, this.cid);
  5856. }
  5857. if (params.selector) {
  5858. var name = params.name + '.delegateEvents' + this.cid;
  5859. this.$el.on(name, params.selector, boundHandler);
  5860. } else {
  5861. this.$el.on(params.name, boundHandler);
  5862. }
  5863. }
  5864. }
  5865. });
  5866. // When view is ready trigger ready event on all
  5867. // children that are present, then register an
  5868. // event that will trigger ready on new children
  5869. // when they are added
  5870. Thorax.View.on('ready', function(options) {
  5871. if (!this._isReady) {
  5872. this._isReady = true;
  5873. function triggerReadyOnChild(child) {
  5874. child.trigger('ready', options);
  5875. }
  5876. _.each(this.children, triggerReadyOnChild);
  5877. this.on('child', triggerReadyOnChild);
  5878. }
  5879. });
  5880. var eventSplitter = /^(nested\s+)?(\S+)(?:\s+(.+))?/;
  5881. var domEvents = [],
  5882. domEventRegexp;
  5883. function pushDomEvents(events) {
  5884. domEvents.push.apply(domEvents, events);
  5885. domEventRegexp = new RegExp('^(nested\\s+)?(' + domEvents.join('|') + ')(?:\\s|$)');
  5886. }
  5887. pushDomEvents([
  5888. 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout',
  5889. 'touchstart', 'touchend', 'touchmove',
  5890. 'click', 'dblclick',
  5891. 'keyup', 'keydown', 'keypress',
  5892. 'submit', 'change',
  5893. 'focus', 'blur'
  5894. ]);
  5895. function containHandlerToCurentView(handler, cid) {
  5896. return function(event) {
  5897. var view = $(event.target).view({helper: false});
  5898. if (view && view.cid === cid) {
  5899. event.originalContext = this;
  5900. handler(event);
  5901. }
  5902. };
  5903. }
  5904. function bindEventHandler(eventName, params) {
  5905. eventName += params.originalName;
  5906. var callback = params.handler,
  5907. method = _.isFunction(callback) ? callback : this[callback];
  5908. if (!method) {
  5909. throw new Error('Event "' + callback + '" does not exist ' + (this.name || this.cid) + ':' + eventName);
  5910. }
  5911. return _.bind(function() {
  5912. try {
  5913. method.apply(this, arguments);
  5914. } catch (e) {
  5915. Thorax.onException('thorax-exception: ' + (this.name || this.cid) + ':' + eventName, e);
  5916. }
  5917. }, params.context || this);
  5918. }
  5919. function eventParamsFromEventItem(name, handler, context) {
  5920. var params = {
  5921. originalName: name,
  5922. handler: _.isString(handler) ? this[handler] : handler
  5923. };
  5924. if (name.match(domEventRegexp)) {
  5925. var match = eventSplitter.exec(name);
  5926. params.nested = !!match[1];
  5927. params.name = match[2];
  5928. params.type = 'DOM';
  5929. params.selector = match[3];
  5930. } else {
  5931. params.name = name;
  5932. params.type = 'view';
  5933. }
  5934. params.context = context;
  5935. return params;
  5936. }
  5937. ;;
  5938. /*global getOptionsData, viewHelperAttributeName */
  5939. var viewPlaceholderAttributeName = 'data-view-tmp',
  5940. viewTemplateOverrides = {};
  5941. // Will be shared by HelperView and CollectionHelperView
  5942. var helperViewPrototype = {
  5943. _ensureElement: function() {
  5944. Thorax.View.prototype._ensureElement.apply(this, arguments);
  5945. this.$el.attr(viewHelperAttributeName, this._helperName);
  5946. },
  5947. _getContext: function() {
  5948. return this.parent._getContext.apply(this.parent, arguments);
  5949. }
  5950. };
  5951. Thorax.HelperView = Thorax.View.extend(helperViewPrototype);
  5952. // Ensure nested inline helpers will always have this.parent
  5953. // set to the view containing the template
  5954. function getParent(parent) {
  5955. // The `view` helper is a special case as it embeds
  5956. // a view instead of creating a new one
  5957. while (parent._helperName && parent._helperName !== 'view') {
  5958. parent = parent.parent;
  5959. }
  5960. return parent;
  5961. }
  5962. Handlebars.registerViewHelper = function(name, ViewClass, callback) {
  5963. if (arguments.length === 2) {
  5964. if (ViewClass.factory) {
  5965. callback = ViewClass.callback;
  5966. } else {
  5967. callback = ViewClass;
  5968. ViewClass = Thorax.HelperView;
  5969. }
  5970. }
  5971. Handlebars.registerHelper(name, function() {
  5972. var args = _.toArray(arguments),
  5973. options = args.pop(),
  5974. declaringView = getOptionsData(options).view;
  5975. var viewOptions = {
  5976. template: options.fn || Handlebars.VM.noop,
  5977. inverse: options.inverse,
  5978. options: options.hash,
  5979. declaringView: declaringView,
  5980. parent: getParent(declaringView),
  5981. _helperName: name,
  5982. _helperOptions: {
  5983. options: cloneHelperOptions(options),
  5984. args: _.clone(args)
  5985. }
  5986. };
  5987. normalizeHTMLAttributeOptions(options.hash);
  5988. _.extend(viewOptions, _.pick(options.hash, htmlAttributesToCopy));
  5989. // Check to see if we have an existing instance that we can reuse
  5990. var instance = _.find(declaringView._previousHelpers, function(child) {
  5991. return compareHelperOptions(viewOptions, child);
  5992. });
  5993. // Create the instance if we don't already have one
  5994. if (!instance) {
  5995. if (ViewClass.factory) {
  5996. instance = ViewClass.factory(args, viewOptions);
  5997. if (!instance) {
  5998. return '';
  5999. }
  6000. instance._helperName = viewOptions._helperName;
  6001. instance._helperOptions = viewOptions._helperOptions;
  6002. } else {
  6003. instance = new ViewClass(viewOptions);
  6004. }
  6005. args.push(instance);
  6006. declaringView._addChild(instance);
  6007. declaringView.trigger.apply(declaringView, ['helper', name].concat(args));
  6008. declaringView.trigger.apply(declaringView, ['helper:' + name].concat(args));
  6009. callback && callback.apply(this, args);
  6010. } else {
  6011. declaringView._previousHelpers = _.without(declaringView._previousHelpers, instance);
  6012. declaringView.children[instance.cid] = instance;
  6013. }
  6014. var htmlAttributes = _.pick(options.hash, htmlAttributesToCopy);
  6015. htmlAttributes[viewPlaceholderAttributeName] = instance.cid;
  6016. var expandTokens = options.hash['expand-tokens'];
  6017. return new Handlebars.SafeString(Thorax.Util.tag(htmlAttributes, '', expandTokens ? this : null));
  6018. });
  6019. var helper = Handlebars.helpers[name];
  6020. return helper;
  6021. };
  6022. Thorax.View.on('append', function(scope, callback) {
  6023. (scope || this.$el).find('[' + viewPlaceholderAttributeName + ']').forEach(function(el) {
  6024. var placeholderId = el.getAttribute(viewPlaceholderAttributeName),
  6025. view = this.children[placeholderId];
  6026. if (view) {
  6027. //see if the view helper declared an override for the view
  6028. //if not, ensure the view has been rendered at least once
  6029. if (viewTemplateOverrides[placeholderId]) {
  6030. view.render(viewTemplateOverrides[placeholderId]);
  6031. delete viewTemplateOverrides[placeholderId];
  6032. } else {
  6033. view.ensureRendered();
  6034. }
  6035. $(el).replaceWith(view.el);
  6036. callback && callback(view.el);
  6037. }
  6038. }, this);
  6039. });
  6040. /**
  6041. * Clones the helper options, dropping items that are known to change
  6042. * between rendering cycles as appropriate.
  6043. */
  6044. function cloneHelperOptions(options) {
  6045. var ret = _.pick(options, 'fn', 'inverse', 'hash', 'data');
  6046. ret.data = _.omit(options.data, 'cid', 'view', 'yield');
  6047. return ret;
  6048. }
  6049. /**
  6050. * Checks for basic equality between two sets of parameters for a helper view.
  6051. *
  6052. * Checked fields include:
  6053. * - _helperName
  6054. * - All args
  6055. * - Hash
  6056. * - Data
  6057. * - Function and Invert (id based if possible)
  6058. *
  6059. * This method allows us to determine if the inputs to a given view are the same. If they
  6060. * are then we make the assumption that the rendering will be the same (or the child view will
  6061. * otherwise rerendering it by monitoring it's parameters as necessary) and reuse the view on
  6062. * rerender of the parent view.
  6063. */
  6064. function compareHelperOptions(a, b) {
  6065. function compareValues(a, b) {
  6066. return _.every(a, function(value, key) {
  6067. return b[key] === value;
  6068. });
  6069. }
  6070. if (a._helperName !== b._helperName) {
  6071. return false;
  6072. }
  6073. a = a._helperOptions;
  6074. b = b._helperOptions;
  6075. // Implements a first level depth comparison
  6076. return a.args.length === b.args.length
  6077. && compareValues(a.args, b.args)
  6078. && _.isEqual(_.keys(a.options), _.keys(b.options))
  6079. && _.every(a.options, function(value, key) {
  6080. if (key === 'data' || key === 'hash') {
  6081. return compareValues(a.options[key], b.options[key]);
  6082. } else if (key === 'fn' || key === 'inverse') {
  6083. return b.options[key] === value
  6084. || (value && _.has(value, 'program') && ((b.options[key] || {}).program === value.program));
  6085. }
  6086. return b.options[key] === value;
  6087. });
  6088. }
  6089. ;;
  6090. /*global getValue, inheritVars, walkInheritTree */
  6091. function dataObject(type, spec) {
  6092. spec = inheritVars[type] = _.defaults({
  6093. name: '_' + type + 'Events',
  6094. event: true
  6095. }, spec);
  6096. // Add a callback in the view constructor
  6097. spec.ctor = function() {
  6098. if (this[type]) {
  6099. // Need to null this.model/collection so setModel/Collection will
  6100. // not treat it as the old model/collection and immediately return
  6101. var object = this[type];
  6102. this[type] = null;
  6103. this[spec.set](object);
  6104. }
  6105. };
  6106. function setObject(dataObject, options) {
  6107. var old = this[type],
  6108. $el = getValue(this, spec.$el);
  6109. if (dataObject === old) {
  6110. return this;
  6111. }
  6112. if (old) {
  6113. this.unbindDataObject(old);
  6114. }
  6115. if (dataObject) {
  6116. this[type] = dataObject;
  6117. if (spec.loading) {
  6118. spec.loading.call(this);
  6119. }
  6120. this.bindDataObject(type, dataObject, _.extend({}, this.options, options));
  6121. $el && $el.attr(spec.cidAttrName, dataObject.cid);
  6122. dataObject.trigger('set', dataObject, old);
  6123. } else {
  6124. this[type] = false;
  6125. if (spec.change) {
  6126. spec.change.call(this, false);
  6127. }
  6128. $el && $el.removeAttr(spec.cidAttrName);
  6129. }
  6130. this.trigger('change:data-object', type, dataObject, old);
  6131. return this;
  6132. }
  6133. Thorax.View.prototype[spec.set] = setObject;
  6134. }
  6135. _.extend(Thorax.View.prototype, {
  6136. bindDataObject: function(type, dataObject, options) {
  6137. if (this._boundDataObjectsByCid[dataObject.cid]) {
  6138. return false;
  6139. }
  6140. this._boundDataObjectsByCid[dataObject.cid] = dataObject;
  6141. var options = this._modifyDataObjectOptions(dataObject, _.extend({}, inheritVars[type].defaultOptions, options));
  6142. this._objectOptionsByCid[dataObject.cid] = options;
  6143. bindEvents.call(this, type, dataObject, this.constructor);
  6144. bindEvents.call(this, type, dataObject, this);
  6145. var spec = inheritVars[type];
  6146. spec.bindCallback && spec.bindCallback.call(this, dataObject, options);
  6147. if (dataObject.shouldFetch && dataObject.shouldFetch(options)) {
  6148. loadObject(dataObject, options);
  6149. } else if (inheritVars[type].change) {
  6150. // want to trigger built in rendering without triggering event on model
  6151. inheritVars[type].change.call(this, dataObject, options);
  6152. }
  6153. return true;
  6154. },
  6155. unbindDataObject: function (dataObject) {
  6156. if (!this._boundDataObjectsByCid[dataObject.cid]) {
  6157. return false;
  6158. }
  6159. delete this._boundDataObjectsByCid[dataObject.cid];
  6160. this.stopListening(dataObject);
  6161. delete this._objectOptionsByCid[dataObject.cid];
  6162. return true;
  6163. },
  6164. _modifyDataObjectOptions: function(dataObject, options) {
  6165. return options;
  6166. }
  6167. });
  6168. function bindEvents(type, target, source) {
  6169. var context = this;
  6170. walkInheritTree(source, '_' + type + 'Events', true, function(event) {
  6171. // getEventCallback will resolve if it is a string or a method
  6172. // and return a method
  6173. context.listenTo(target, event[0], _.bind(getEventCallback(event[1], context), event[2] || context));
  6174. });
  6175. }
  6176. function loadObject(dataObject, options) {
  6177. if (dataObject.load) {
  6178. dataObject.load(function() {
  6179. options && options.success && options.success(dataObject);
  6180. }, options);
  6181. } else {
  6182. dataObject.fetch(options);
  6183. }
  6184. }
  6185. function getEventCallback(callback, context) {
  6186. if (_.isFunction(callback)) {
  6187. return callback;
  6188. } else {
  6189. return context[callback];
  6190. }
  6191. }
  6192. ;;
  6193. /*global createRegistryWrapper, dataObject, getValue */
  6194. var modelCidAttributeName = 'data-model-cid';
  6195. Thorax.Model = Backbone.Model.extend({
  6196. isEmpty: function() {
  6197. return !this.isPopulated();
  6198. },
  6199. isPopulated: function() {
  6200. // We are populated if we have attributes set
  6201. var attributes = _.clone(this.attributes),
  6202. defaults = getValue(this, 'defaults') || {};
  6203. for (var default_key in defaults) {
  6204. if (attributes[default_key] != defaults[default_key]) {
  6205. return true;
  6206. }
  6207. delete attributes[default_key];
  6208. }
  6209. var keys = _.keys(attributes);
  6210. return keys.length > 1 || (keys.length === 1 && keys[0] !== this.idAttribute);
  6211. },
  6212. shouldFetch: function(options) {
  6213. // url() will throw if model has no `urlRoot` and no `collection`
  6214. // or has `collection` and `collection` has no `url`
  6215. var url;
  6216. try {
  6217. url = getValue(this, 'url');
  6218. } catch(e) {
  6219. url = false;
  6220. }
  6221. return options.fetch && !!url && !this.isPopulated();
  6222. }
  6223. });
  6224. Thorax.Models = {};
  6225. createRegistryWrapper(Thorax.Model, Thorax.Models);
  6226. dataObject('model', {
  6227. set: 'setModel',
  6228. defaultOptions: {
  6229. render: true,
  6230. fetch: true,
  6231. success: false,
  6232. errors: true
  6233. },
  6234. change: onModelChange,
  6235. $el: '$el',
  6236. cidAttrName: modelCidAttributeName
  6237. });
  6238. function onModelChange(model) {
  6239. var modelOptions = model && this._objectOptionsByCid[model.cid];
  6240. // !modelOptions will be true when setModel(false) is called
  6241. if (!modelOptions || (modelOptions && modelOptions.render)) {
  6242. this.render();
  6243. }
  6244. }
  6245. Thorax.View.on({
  6246. model: {
  6247. error: function(model, errors) {
  6248. if (this._objectOptionsByCid[model.cid].errors) {
  6249. this.trigger('error', errors, model);
  6250. }
  6251. },
  6252. change: function(model) {
  6253. onModelChange.call(this, model);
  6254. }
  6255. }
  6256. });
  6257. $.fn.model = function(view) {
  6258. var $this = $(this),
  6259. modelElement = $this.closest('[' + modelCidAttributeName + ']'),
  6260. modelCid = modelElement && modelElement.attr(modelCidAttributeName);
  6261. if (modelCid) {
  6262. var view = view || $this.view();
  6263. if (view && view.model && view.model.cid === modelCid) {
  6264. return view.model || false;
  6265. }
  6266. var collection = $this.collection(view);
  6267. if (collection) {
  6268. return collection.get(modelCid);
  6269. }
  6270. }
  6271. return false;
  6272. };
  6273. ;;
  6274. /*global createRegistryWrapper, dataObject, getEventCallback, getValue, modelCidAttributeName, viewCidAttributeName */
  6275. var _fetch = Backbone.Collection.prototype.fetch,
  6276. _reset = Backbone.Collection.prototype.reset,
  6277. _replaceHTML = Thorax.View.prototype._replaceHTML,
  6278. collectionCidAttributeName = 'data-collection-cid',
  6279. collectionEmptyAttributeName = 'data-collection-empty',
  6280. collectionElementAttributeName = 'data-collection-element',
  6281. ELEMENT_NODE_TYPE = 1;
  6282. Thorax.Collection = Backbone.Collection.extend({
  6283. model: Thorax.Model || Backbone.Model,
  6284. initialize: function() {
  6285. this.cid = _.uniqueId('collection');
  6286. return Backbone.Collection.prototype.initialize.apply(this, arguments);
  6287. },
  6288. isEmpty: function() {
  6289. if (this.length > 0) {
  6290. return false;
  6291. } else {
  6292. return this.length === 0 && this.isPopulated();
  6293. }
  6294. },
  6295. isPopulated: function() {
  6296. return this._fetched || this.length > 0 || (!this.length && !getValue(this, 'url'));
  6297. },
  6298. shouldFetch: function(options) {
  6299. return options.fetch && !!getValue(this, 'url') && !this.isPopulated();
  6300. },
  6301. fetch: function(options) {
  6302. options = options || {};
  6303. var success = options.success;
  6304. options.success = function(collection, response) {
  6305. collection._fetched = true;
  6306. success && success(collection, response);
  6307. };
  6308. return _fetch.apply(this, arguments);
  6309. },
  6310. reset: function(models, options) {
  6311. this._fetched = !!models;
  6312. return _reset.call(this, models, options);
  6313. }
  6314. });
  6315. Thorax.Collections = {};
  6316. createRegistryWrapper(Thorax.Collection, Thorax.Collections);
  6317. dataObject('collection', {
  6318. set: 'setCollection',
  6319. bindCallback: onSetCollection,
  6320. defaultOptions: {
  6321. render: true,
  6322. fetch: true,
  6323. success: false,
  6324. errors: true
  6325. },
  6326. change: onCollectionReset,
  6327. $el: 'getCollectionElement',
  6328. cidAttrName: collectionCidAttributeName
  6329. });
  6330. Thorax.CollectionView = Thorax.View.extend({
  6331. _defaultTemplate: Handlebars.VM.noop,
  6332. _collectionSelector: '[' + collectionElementAttributeName + ']',
  6333. // preserve collection element if it was not created with {{collection}} helper
  6334. _replaceHTML: function(html) {
  6335. if (this.collection && this._objectOptionsByCid[this.collection.cid] && this._renderCount) {
  6336. var element;
  6337. var oldCollectionElement = this.getCollectionElement();
  6338. element = _replaceHTML.call(this, html);
  6339. if (!oldCollectionElement.attr('data-view-cid')) {
  6340. this.getCollectionElement().replaceWith(oldCollectionElement);
  6341. }
  6342. } else {
  6343. return _replaceHTML.call(this, html);
  6344. }
  6345. },
  6346. //appendItem(model [,index])
  6347. //appendItem(html_string, index)
  6348. //appendItem(view, index)
  6349. appendItem: function(model, index, options) {
  6350. //empty item
  6351. if (!model) {
  6352. return;
  6353. }
  6354. var itemView,
  6355. $el = this.getCollectionElement();
  6356. options = _.defaults(options || {}, {
  6357. filter: true
  6358. });
  6359. //if index argument is a view
  6360. index && index.el && (index = $el.children().indexOf(index.el) + 1);
  6361. //if argument is a view, or html string
  6362. if (model.el || _.isString(model)) {
  6363. itemView = model;
  6364. model = false;
  6365. } else {
  6366. index = index || this.collection.indexOf(model) || 0;
  6367. itemView = this.renderItem(model, index);
  6368. }
  6369. if (itemView) {
  6370. itemView.cid && this._addChild(itemView);
  6371. //if the renderer's output wasn't contained in a tag, wrap it in a div
  6372. //plain text, or a mixture of top level text nodes and element nodes
  6373. //will get wrapped
  6374. if (_.isString(itemView) && !itemView.match(/^\s*</m)) {
  6375. itemView = '<div>' + itemView + '</div>';
  6376. }
  6377. var itemElement = itemView.el ? [itemView.el] : _.filter($(itemView), function(node) {
  6378. //filter out top level whitespace nodes
  6379. return node.nodeType === ELEMENT_NODE_TYPE;
  6380. });
  6381. model && $(itemElement).attr(modelCidAttributeName, model.cid);
  6382. var previousModel = index > 0 ? this.collection.at(index - 1) : false;
  6383. if (!previousModel) {
  6384. $el.prepend(itemElement);
  6385. } else {
  6386. //use last() as appendItem can accept multiple nodes from a template
  6387. var last = $el.find('[' + modelCidAttributeName + '="' + previousModel.cid + '"]').last();
  6388. last.after(itemElement);
  6389. }
  6390. this.trigger('append', null, function(el) {
  6391. el.setAttribute(modelCidAttributeName, model.cid);
  6392. });
  6393. !options.silent && this.trigger('rendered:item', this, this.collection, model, itemElement, index);
  6394. options.filter && applyItemVisiblityFilter.call(this, model);
  6395. }
  6396. return itemView;
  6397. },
  6398. // updateItem only useful if there is no item view, otherwise
  6399. // itemView.render() provides the same functionality
  6400. updateItem: function(model) {
  6401. this.removeItem(model);
  6402. this.appendItem(model);
  6403. },
  6404. removeItem: function(model) {
  6405. var $el = this.getCollectionElement(),
  6406. viewEl = $el.find('[' + modelCidAttributeName + '="' + model.cid + '"]');
  6407. if (!viewEl.length) {
  6408. return false;
  6409. }
  6410. viewEl.remove();
  6411. var viewCid = viewEl.attr(viewCidAttributeName),
  6412. child = this.children[viewCid];
  6413. if (child) {
  6414. this._removeChild(child);
  6415. child.destroy();
  6416. }
  6417. return true;
  6418. },
  6419. renderCollection: function() {
  6420. if (this.collection) {
  6421. if (this.collection.isEmpty()) {
  6422. handleChangeFromNotEmptyToEmpty.call(this);
  6423. } else {
  6424. handleChangeFromEmptyToNotEmpty.call(this);
  6425. this.collection.forEach(function(item, i) {
  6426. this.appendItem(item, i);
  6427. }, this);
  6428. }
  6429. this.trigger('rendered:collection', this, this.collection);
  6430. applyVisibilityFilter.call(this);
  6431. } else {
  6432. handleChangeFromNotEmptyToEmpty.call(this);
  6433. }
  6434. },
  6435. emptyClass: 'empty',
  6436. renderEmpty: function() {
  6437. if (!this.emptyTemplate && !this.emptyView) {
  6438. assignTemplate.call(this, 'emptyTemplate', {
  6439. extension: '-empty',
  6440. required: false
  6441. });
  6442. }
  6443. if (this.emptyView) {
  6444. var viewOptions = {};
  6445. if (this.emptyTemplate) {
  6446. viewOptions.template = this.emptyTemplate;
  6447. }
  6448. var view = Thorax.Util.getViewInstance(this.emptyView, viewOptions);
  6449. view.ensureRendered();
  6450. return view;
  6451. } else {
  6452. return this.emptyTemplate && this.renderTemplate(this.emptyTemplate);
  6453. }
  6454. },
  6455. renderItem: function(model, i) {
  6456. if (!this.itemTemplate && !this.itemView) {
  6457. assignTemplate.call(this, 'itemTemplate', {
  6458. extension: '-item',
  6459. // only require an itemTemplate if an itemView
  6460. // is not present
  6461. required: !this.itemView
  6462. });
  6463. }
  6464. if (this.itemView) {
  6465. var viewOptions = {
  6466. model: model
  6467. };
  6468. if (this.itemTemplate) {
  6469. viewOptions.template = this.itemTemplate;
  6470. }
  6471. var view = Thorax.Util.getViewInstance(this.itemView, viewOptions);
  6472. view.ensureRendered();
  6473. return view;
  6474. } else {
  6475. return this.renderTemplate(this.itemTemplate, this.itemContext(model, i));
  6476. }
  6477. },
  6478. itemContext: function(model /*, i */) {
  6479. return model.attributes;
  6480. },
  6481. appendEmpty: function() {
  6482. var $el = this.getCollectionElement();
  6483. $el.empty();
  6484. var emptyContent = this.renderEmpty();
  6485. emptyContent && this.appendItem(emptyContent, 0, {
  6486. silent: true,
  6487. filter: false
  6488. });
  6489. this.trigger('rendered:empty', this, this.collection);
  6490. },
  6491. getCollectionElement: function() {
  6492. var element = this.$(this._collectionSelector);
  6493. return element.length === 0 ? this.$el : element;
  6494. }
  6495. });
  6496. Thorax.CollectionView.on({
  6497. collection: {
  6498. reset: onCollectionReset,
  6499. sort: onCollectionReset,
  6500. filter: function() {
  6501. applyVisibilityFilter.call(this);
  6502. },
  6503. change: function(model) {
  6504. // If we rendered with item views, model changes will be observed
  6505. // by the generated item view but if we rendered with templates
  6506. // then model changes need to be bound as nothing is watching
  6507. !this.itemView && this.updateItem(model);
  6508. applyItemVisiblityFilter.call(this, model);
  6509. },
  6510. add: function(model) {
  6511. var $el = this.getCollectionElement();
  6512. this.collection.length === 1 && $el.length && handleChangeFromEmptyToNotEmpty.call(this);
  6513. if ($el.length) {
  6514. var index = this.collection.indexOf(model);
  6515. this.appendItem(model, index);
  6516. }
  6517. },
  6518. remove: function(model) {
  6519. var $el = this.getCollectionElement();
  6520. this.removeItem(model);
  6521. this.collection.length === 0 && $el.length && handleChangeFromNotEmptyToEmpty.call(this);
  6522. }
  6523. }
  6524. });
  6525. Thorax.View.on({
  6526. collection: {
  6527. error: function(collection, message) {
  6528. if (this._objectOptionsByCid[collection.cid].errors) {
  6529. this.trigger('error', message, collection);
  6530. }
  6531. }
  6532. }
  6533. });
  6534. function onCollectionReset(collection) {
  6535. var options = collection && this._objectOptionsByCid[collection.cid];
  6536. // we would want to still render in the case that the
  6537. // collection has transitioned to being falsy
  6538. if (!collection || (options && options.render)) {
  6539. this.renderCollection && this.renderCollection();
  6540. }
  6541. }
  6542. // Even if the view is not a CollectionView
  6543. // ensureRendered() to provide similar behavior
  6544. // to a model
  6545. function onSetCollection() {
  6546. this.ensureRendered();
  6547. }
  6548. function applyVisibilityFilter() {
  6549. if (this.itemFilter) {
  6550. this.collection.forEach(function(model) {
  6551. applyItemVisiblityFilter.call(this, model);
  6552. }, this);
  6553. }
  6554. }
  6555. function applyItemVisiblityFilter(model) {
  6556. var $el = this.getCollectionElement();
  6557. this.itemFilter && $el.find('[' + modelCidAttributeName + '="' + model.cid + '"]')[itemShouldBeVisible.call(this, model) ? 'show' : 'hide']();
  6558. }
  6559. function itemShouldBeVisible(model) {
  6560. return this.itemFilter(model, this.collection.indexOf(model));
  6561. }
  6562. function handleChangeFromEmptyToNotEmpty() {
  6563. var $el = this.getCollectionElement();
  6564. this.emptyClass && $el.removeClass(this.emptyClass);
  6565. $el.removeAttr(collectionEmptyAttributeName);
  6566. $el.empty();
  6567. }
  6568. function handleChangeFromNotEmptyToEmpty() {
  6569. var $el = this.getCollectionElement();
  6570. this.emptyClass && $el.addClass(this.emptyClass);
  6571. $el.attr(collectionEmptyAttributeName, true);
  6572. this.appendEmpty();
  6573. }
  6574. //$(selector).collection() helper
  6575. $.fn.collection = function(view) {
  6576. if (view && view.collection) {
  6577. return view.collection;
  6578. }
  6579. var $this = $(this),
  6580. collectionElement = $this.closest('[' + collectionCidAttributeName + ']'),
  6581. collectionCid = collectionElement && collectionElement.attr(collectionCidAttributeName);
  6582. if (collectionCid) {
  6583. view = $this.view();
  6584. if (view) {
  6585. return view.collection;
  6586. }
  6587. }
  6588. return false;
  6589. };
  6590. ;;
  6591. /*global inheritVars */
  6592. inheritVars.model.defaultOptions.populate = true;
  6593. var oldModelChange = inheritVars.model.change;
  6594. inheritVars.model.change = function() {
  6595. oldModelChange.apply(this, arguments);
  6596. // TODO : What can we do to remove this duplication?
  6597. var modelOptions = this.model && this._objectOptionsByCid[this.model.cid];
  6598. if (modelOptions && modelOptions.populate) {
  6599. this.populate(this.model.attributes, modelOptions.populate === true ? {} : modelOptions.populate);
  6600. }
  6601. };
  6602. inheritVars.model.defaultOptions.populate = true;
  6603. _.extend(Thorax.View.prototype, {
  6604. //serializes a form present in the view, returning the serialized data
  6605. //as an object
  6606. //pass {set:false} to not update this.model if present
  6607. //can pass options, callback or event in any order
  6608. serialize: function() {
  6609. var callback, options, event;
  6610. //ignore undefined arguments in case event was null
  6611. for (var i = 0; i < arguments.length; ++i) {
  6612. if (_.isFunction(arguments[i])) {
  6613. callback = arguments[i];
  6614. } else if (_.isObject(arguments[i])) {
  6615. if ('stopPropagation' in arguments[i] && 'preventDefault' in arguments[i]) {
  6616. event = arguments[i];
  6617. } else {
  6618. options = arguments[i];
  6619. }
  6620. }
  6621. }
  6622. if (event && !this._preventDuplicateSubmission(event)) {
  6623. return;
  6624. }
  6625. options = _.extend({
  6626. set: true,
  6627. validate: true,
  6628. children: true,
  6629. silent: true
  6630. }, options || {});
  6631. var attributes = options.attributes || {};
  6632. //callback has context of element
  6633. var view = this;
  6634. var errors = [];
  6635. eachNamedInput.call(this, options, function() {
  6636. var value = view._getInputValue(this, options, errors);
  6637. if (!_.isUndefined(value)) {
  6638. objectAndKeyFromAttributesAndName.call(this, attributes, this.name, {mode: 'serialize'}, function(object, key) {
  6639. if (!object[key]) {
  6640. object[key] = value;
  6641. } else if (_.isArray(object[key])) {
  6642. object[key].push(value);
  6643. } else {
  6644. object[key] = [object[key], value];
  6645. }
  6646. });
  6647. }
  6648. });
  6649. this.trigger('serialize', attributes, options);
  6650. if (options.validate) {
  6651. var validateInputErrors = this.validateInput(attributes);
  6652. if (validateInputErrors && validateInputErrors.length) {
  6653. errors = errors.concat(validateInputErrors);
  6654. }
  6655. this.trigger('validate', attributes, errors, options);
  6656. if (errors.length) {
  6657. this.trigger('error', errors);
  6658. return;
  6659. }
  6660. }
  6661. if (options.set && this.model) {
  6662. if (!this.model.set(attributes, {silent: options.silent})) {
  6663. return false;
  6664. }
  6665. }
  6666. callback && callback.call(this, attributes, _.bind(resetSubmitState, this));
  6667. return attributes;
  6668. },
  6669. _preventDuplicateSubmission: function(event, callback) {
  6670. event.preventDefault();
  6671. var form = $(event.target);
  6672. if ((event.target.tagName || '').toLowerCase() !== 'form') {
  6673. // Handle non-submit events by gating on the form
  6674. form = $(event.target).closest('form');
  6675. }
  6676. if (!form.attr('data-submit-wait')) {
  6677. form.attr('data-submit-wait', 'true');
  6678. if (callback) {
  6679. callback.call(this, event);
  6680. }
  6681. return true;
  6682. } else {
  6683. return false;
  6684. }
  6685. },
  6686. //populate a form from the passed attributes or this.model if present
  6687. populate: function(attributes, options) {
  6688. options = _.extend({
  6689. children: true
  6690. }, options || {});
  6691. var value, attributes = attributes || this._getContext();
  6692. //callback has context of element
  6693. eachNamedInput.call(this, options, function() {
  6694. objectAndKeyFromAttributesAndName.call(this, attributes, this.name, {mode: 'populate'}, function(object, key) {
  6695. if (object && !_.isUndefined(value = object[key])) {
  6696. //will only execute if we have a name that matches the structure in attributes
  6697. if (this.type === 'checkbox' && _.isBoolean(value)) {
  6698. this.checked = value;
  6699. } else if (this.type === 'checkbox' || this.type === 'radio') {
  6700. this.checked = value == this.value;
  6701. } else {
  6702. this.value = value;
  6703. }
  6704. }
  6705. });
  6706. });
  6707. this.trigger('populate', attributes);
  6708. },
  6709. //perform form validation, implemented by child class
  6710. validateInput: function(/* attributes, options, errors */) {},
  6711. _getInputValue: function(input /* , options, errors */) {
  6712. if (input.type === 'checkbox' || input.type === 'radio') {
  6713. if (input.checked) {
  6714. return input.value;
  6715. }
  6716. } else if (input.multiple === true) {
  6717. var values = [];
  6718. $('option', input).each(function() {
  6719. if (this.selected) {
  6720. values.push(this.value);
  6721. }
  6722. });
  6723. return values;
  6724. } else {
  6725. return input.value;
  6726. }
  6727. }
  6728. });
  6729. Thorax.View.on({
  6730. error: function() {
  6731. resetSubmitState.call(this);
  6732. // If we errored with a model we want to reset the content but leave the UI
  6733. // intact. If the user updates the data and serializes any overwritten data
  6734. // will be restored.
  6735. if (this.model && this.model.previousAttributes) {
  6736. this.model.set(this.model.previousAttributes(), {
  6737. silent: true
  6738. });
  6739. }
  6740. },
  6741. deactivated: function() {
  6742. resetSubmitState.call(this);
  6743. }
  6744. });
  6745. function eachNamedInput(options, iterator, context) {
  6746. var i = 0,
  6747. self = this;
  6748. this.$('select,input,textarea', options.root || this.el).each(function() {
  6749. if (!options.children) {
  6750. if (self !== $(this).view({helper: false})) {
  6751. return;
  6752. }
  6753. }
  6754. if (this.type !== 'button' && this.type !== 'cancel' && this.type !== 'submit' && this.name && this.name !== '') {
  6755. iterator.call(context || this, i, this);
  6756. ++i;
  6757. }
  6758. });
  6759. }
  6760. //calls a callback with the correct object fragment and key from a compound name
  6761. function objectAndKeyFromAttributesAndName(attributes, name, options, callback) {
  6762. var key,
  6763. object = attributes,
  6764. keys = name.split('['),
  6765. mode = options.mode;
  6766. for (var i = 0; i < keys.length - 1; ++i) {
  6767. key = keys[i].replace(']', '');
  6768. if (!object[key]) {
  6769. if (mode === 'serialize') {
  6770. object[key] = {};
  6771. } else {
  6772. return callback.call(this, false, key);
  6773. }
  6774. }
  6775. object = object[key];
  6776. }
  6777. key = keys[keys.length - 1].replace(']', '');
  6778. callback.call(this, object, key);
  6779. }
  6780. function resetSubmitState() {
  6781. this.$('form').removeAttr('data-submit-wait');
  6782. }
  6783. ;;
  6784. var layoutCidAttributeName = 'data-layout-cid';
  6785. Thorax.LayoutView = Thorax.View.extend({
  6786. _defaultTemplate: Handlebars.VM.noop,
  6787. render: function() {
  6788. var response = Thorax.View.prototype.render.apply(this, arguments);
  6789. if (this.template === Handlebars.VM.noop) {
  6790. // if there is no template setView will append to this.$el
  6791. ensureLayoutCid.call(this);
  6792. } else {
  6793. // if a template was specified is must declare a layout-element
  6794. ensureLayoutViewsTargetElement.call(this);
  6795. }
  6796. return response;
  6797. },
  6798. setView: function(view, options) {
  6799. options = _.extend({
  6800. scroll: true,
  6801. destroy: true
  6802. }, options || {});
  6803. if (_.isString(view)) {
  6804. view = new (Thorax.Util.registryGet(Thorax, 'Views', view, false))();
  6805. }
  6806. this.ensureRendered();
  6807. var oldView = this._view;
  6808. if (view === oldView) {
  6809. return false;
  6810. }
  6811. if (options.destroy && view) {
  6812. view._shouldDestroyOnNextSetView = true;
  6813. }
  6814. this.trigger('change:view:start', view, oldView, options);
  6815. if (oldView) {
  6816. this._removeChild(oldView);
  6817. oldView.$el.remove();
  6818. triggerLifecycleEvent.call(oldView, 'deactivated', options);
  6819. if (oldView._shouldDestroyOnNextSetView) {
  6820. oldView.destroy();
  6821. }
  6822. }
  6823. if (view) {
  6824. triggerLifecycleEvent.call(this, 'activated', options);
  6825. view.trigger('activated', options);
  6826. this._addChild(view);
  6827. this._view = view;
  6828. this._view.appendTo(getLayoutViewsTargetElement.call(this));
  6829. } else {
  6830. this._view = undefined;
  6831. }
  6832. this.trigger('change:view:end', view, oldView, options);
  6833. return view;
  6834. },
  6835. getView: function() {
  6836. return this._view;
  6837. }
  6838. });
  6839. Handlebars.registerHelper('layout-element', function(options) {
  6840. var view = getOptionsData(options).view;
  6841. // duck type check for LayoutView
  6842. if (!view.getView) {
  6843. throw new Error('layout-element must be used within a LayoutView');
  6844. }
  6845. options.hash[layoutCidAttributeName] = view.cid;
  6846. normalizeHTMLAttributeOptions(options.hash);
  6847. return new Handlebars.SafeString(Thorax.Util.tag.call(this, options.hash, '', this));
  6848. });
  6849. function triggerLifecycleEvent(eventName, options) {
  6850. options = options || {};
  6851. options.target = this;
  6852. this.trigger(eventName, options);
  6853. _.each(this.children, function(child) {
  6854. child.trigger(eventName, options);
  6855. });
  6856. }
  6857. function ensureLayoutCid() {
  6858. ++this._renderCount;
  6859. //set the layoutCidAttributeName on this.$el if there was no template
  6860. this.$el.attr(layoutCidAttributeName, this.cid);
  6861. }
  6862. function ensureLayoutViewsTargetElement() {
  6863. if (!this.$('[' + layoutCidAttributeName + '="' + this.cid + '"]')[0]) {
  6864. throw new Error('No layout element found in ' + (this.name || this.cid));
  6865. }
  6866. }
  6867. function getLayoutViewsTargetElement() {
  6868. return this.$('[' + layoutCidAttributeName + '="' + this.cid + '"]')[0] || this.el[0] || this.el;
  6869. }
  6870. ;;
  6871. /*global createRegistryWrapper */
  6872. //Router
  6873. function initializeRouter() {
  6874. Backbone.history || (Backbone.history = new Backbone.History());
  6875. Backbone.history.on('route', onRoute, this);
  6876. //router does not have a built in destroy event
  6877. //but ViewController does
  6878. this.on('destroyed', function() {
  6879. Backbone.history.off('route', onRoute, this);
  6880. });
  6881. }
  6882. Thorax.Router = Backbone.Router.extend({
  6883. constructor: function() {
  6884. var response = Thorax.Router.__super__.constructor.apply(this, arguments);
  6885. initializeRouter.call(this);
  6886. return response;
  6887. },
  6888. route: function(route, name, callback) {
  6889. if (!callback) {
  6890. callback = this[name];
  6891. }
  6892. //add a route:before event that is fired before the callback is called
  6893. return Backbone.Router.prototype.route.call(this, route, name, function() {
  6894. this.trigger.apply(this, ['route:before', route, name].concat(Array.prototype.slice.call(arguments)));
  6895. return callback.apply(this, arguments);
  6896. });
  6897. }
  6898. });
  6899. Thorax.Routers = {};
  6900. createRegistryWrapper(Thorax.Router, Thorax.Routers);
  6901. function onRoute(router /* , name */) {
  6902. if (this === router) {
  6903. this.trigger.apply(this, ['route'].concat(Array.prototype.slice.call(arguments, 1)));
  6904. }
  6905. }
  6906. ;;
  6907. Thorax.CollectionHelperView = Thorax.CollectionView.extend({
  6908. // Forward render events to the parent
  6909. events: {
  6910. 'rendered:item': forwardRenderEvent('rendered:item'),
  6911. 'rendered:collection': forwardRenderEvent('rendered:collection'),
  6912. 'rendered:empty': forwardRenderEvent('rendered:empty')
  6913. },
  6914. constructor: function(options) {
  6915. _.each(collectionOptionNames, function(viewAttributeName, helperOptionName) {
  6916. if (options.options[helperOptionName]) {
  6917. var value = options.options[helperOptionName];
  6918. if (viewAttributeName === 'itemTemplate' || viewAttributeName === 'emptyTemplate') {
  6919. value = Thorax.Util.getTemplate(value);
  6920. }
  6921. options[viewAttributeName] = value;
  6922. }
  6923. });
  6924. // Handlebars.VM.noop is passed in the handlebars options object as
  6925. // a default for fn and inverse, if a block was present. Need to
  6926. // check to ensure we don't pick the empty / null block up.
  6927. if (!options.itemTemplate && options.template && options.template !== Handlebars.VM.noop) {
  6928. options.itemTemplate = options.template;
  6929. options.template = Handlebars.VM.noop;
  6930. }
  6931. if (!options.emptyTemplate && options.inverse && options.inverse !== Handlebars.VM.noop) {
  6932. options.emptyTemplate = options.inverse;
  6933. options.inverse = Handlebars.VM.noop;
  6934. }
  6935. var response = Thorax.HelperView.call(this, options);
  6936. if (this.parent.name) {
  6937. if (!this.emptyTemplate) {
  6938. this.emptyTemplate = Thorax.Util.getTemplate(this.parent.name + '-empty', true);
  6939. }
  6940. if (!this.itemTemplate) {
  6941. // item template must be present if an itemView is not
  6942. this.itemTemplate = Thorax.Util.getTemplate(this.parent.name + '-item', !!this.itemView);
  6943. }
  6944. }
  6945. return response;
  6946. },
  6947. setAsPrimaryCollectionHelper: function() {
  6948. _.each(forwardableProperties, function(propertyName) {
  6949. forwardMissingProperty.call(this, propertyName);
  6950. }, this);
  6951. if (this.parent.itemFilter && !this.itemFilter) {
  6952. this.itemFilter = function() {
  6953. return this.parent.itemFilter.apply(this.parent, arguments);
  6954. };
  6955. }
  6956. if (this.parent.itemContext) {
  6957. this.itemContext = function() {
  6958. return this.parent.itemContext.apply(this.parent, arguments);
  6959. };
  6960. }
  6961. }
  6962. });
  6963. _.extend(Thorax.CollectionHelperView.prototype, helperViewPrototype);
  6964. var collectionOptionNames = {
  6965. 'item-template': 'itemTemplate',
  6966. 'empty-template': 'emptyTemplate',
  6967. 'item-view': 'itemView',
  6968. 'empty-view': 'emptyView',
  6969. 'empty-class': 'emptyClass'
  6970. };
  6971. function forwardRenderEvent(eventName) {
  6972. return function() {
  6973. var args = _.toArray(arguments);
  6974. args.unshift(eventName);
  6975. this.parent.trigger.apply(this.parent, args);
  6976. }
  6977. }
  6978. var forwardableProperties = [
  6979. 'itemTemplate',
  6980. 'itemView',
  6981. 'emptyTemplate',
  6982. 'emptyView'
  6983. ];
  6984. function forwardMissingProperty(propertyName) {
  6985. var parent = getParent(this);
  6986. if (!this[propertyName]) {
  6987. var prop = parent[propertyName];
  6988. if (prop){
  6989. this[propertyName] = prop;
  6990. }
  6991. }
  6992. }
  6993. Handlebars.registerViewHelper('collection', Thorax.CollectionHelperView, function(collection, view) {
  6994. if (arguments.length === 1) {
  6995. view = collection;
  6996. collection = view.parent.collection;
  6997. collection && view.setAsPrimaryCollectionHelper();
  6998. view.$el.attr(collectionElementAttributeName, 'true');
  6999. // propagate future changes to the parent's collection object
  7000. // to the helper view
  7001. view.listenTo(view.parent, 'change:data-object', function(type, dataObject) {
  7002. if (type === 'collection') {
  7003. view.setAsPrimaryCollectionHelper();
  7004. view.setCollection(dataObject);
  7005. }
  7006. });
  7007. }
  7008. collection && view.setCollection(collection);
  7009. });
  7010. Handlebars.registerHelper('collection-element', function(options) {
  7011. if (!getOptionsData(options).view.renderCollection) {
  7012. throw new Error("collection-element helper must be declared inside of a CollectionView");
  7013. }
  7014. var hash = options.hash;
  7015. normalizeHTMLAttributeOptions(hash);
  7016. hash.tagName = hash.tagName || 'div';
  7017. hash[collectionElementAttributeName] = true;
  7018. return new Handlebars.SafeString(Thorax.Util.tag.call(this, hash, '', this));
  7019. });
  7020. ;;
  7021. Handlebars.registerHelper('empty', function(dataObject, options) {
  7022. if (arguments.length === 1) {
  7023. options = dataObject;
  7024. }
  7025. var view = getOptionsData(options).view;
  7026. if (arguments.length === 1) {
  7027. dataObject = view.model;
  7028. }
  7029. // listeners for the empty helper rather than listeners
  7030. // that are themselves empty
  7031. if (!view._emptyListeners) {
  7032. view._emptyListeners = {};
  7033. }
  7034. // duck type check for collection
  7035. if (dataObject && !view._emptyListeners[dataObject.cid] && dataObject.models && ('length' in dataObject)) {
  7036. view._emptyListeners[dataObject.cid] = true;
  7037. view.listenTo(dataObject, 'remove', function() {
  7038. if (dataObject.length === 0) {
  7039. view.render();
  7040. }
  7041. });
  7042. view.listenTo(dataObject, 'add', function() {
  7043. if (dataObject.length === 1) {
  7044. view.render();
  7045. }
  7046. });
  7047. view.listenTo(dataObject, 'reset', function() {
  7048. view.render();
  7049. });
  7050. }
  7051. return !dataObject || dataObject.isEmpty() ? options.fn(this) : options.inverse(this);
  7052. });
  7053. ;;
  7054. Handlebars.registerHelper('template', function(name, options) {
  7055. var context = _.extend({fn: options && options.fn}, this, options ? options.hash : {});
  7056. var output = getOptionsData(options).view.renderTemplate(name, context);
  7057. return new Handlebars.SafeString(output);
  7058. });
  7059. Handlebars.registerHelper('yield', function(options) {
  7060. return getOptionsData(options).yield && options.data.yield();
  7061. });
  7062. ;;
  7063. Handlebars.registerHelper('url', function(url) {
  7064. var fragment;
  7065. if (arguments.length > 2) {
  7066. fragment = _.map(_.head(arguments, arguments.length - 1), encodeURIComponent).join('/');
  7067. } else {
  7068. var options = arguments[1],
  7069. hash = (options && options.hash) || options;
  7070. if (hash && hash['expand-tokens']) {
  7071. fragment = Thorax.Util.expandToken(url, this);
  7072. } else {
  7073. fragment = url;
  7074. }
  7075. }
  7076. return (Backbone.history._hasPushState ? Backbone.history.options.root : '#') + fragment;
  7077. });
  7078. ;;
  7079. /*global viewTemplateOverrides */
  7080. Handlebars.registerViewHelper('view', {
  7081. factory: function(args, options) {
  7082. var View = args.length >= 1 ? args[0] : Thorax.View;
  7083. return Thorax.Util.getViewInstance(View, options.options);
  7084. },
  7085. callback: function() {
  7086. var instance = arguments[arguments.length-1],
  7087. options = instance._helperOptions.options,
  7088. placeholderId = instance.cid;
  7089. if (options.fn) {
  7090. viewTemplateOverrides[placeholderId] = options.fn;
  7091. }
  7092. }
  7093. });
  7094. ;;
  7095. var callMethodAttributeName = 'data-call-method',
  7096. triggerEventAttributeName = 'data-trigger-event';
  7097. Handlebars.registerHelper('button', function(method, options) {
  7098. if (arguments.length === 1) {
  7099. options = method;
  7100. method = options.hash.method;
  7101. }
  7102. var hash = options.hash,
  7103. expandTokens = hash['expand-tokens'];
  7104. delete hash['expand-tokens'];
  7105. if (!method && !options.hash.trigger) {
  7106. throw new Error("button helper must have a method name as the first argument or a 'trigger', or a 'method' attribute specified.");
  7107. }
  7108. normalizeHTMLAttributeOptions(hash);
  7109. hash.tagName = hash.tagName || 'button';
  7110. hash.trigger && (hash[triggerEventAttributeName] = hash.trigger);
  7111. delete hash.trigger;
  7112. method && (hash[callMethodAttributeName] = method);
  7113. return new Handlebars.SafeString(Thorax.Util.tag(hash, options.fn ? options.fn(this) : '', expandTokens ? this : null));
  7114. });
  7115. Handlebars.registerHelper('link', function() {
  7116. var args = _.toArray(arguments),
  7117. options = args.pop(),
  7118. hash = options.hash,
  7119. // url is an array that will be passed to the url helper
  7120. url = args.length === 0 ? [hash.href] : args,
  7121. expandTokens = hash['expand-tokens'];
  7122. delete hash['expand-tokens'];
  7123. if (!url[0] && url[0] !== '') {
  7124. throw new Error("link helper requires an href as the first argument or an 'href' attribute");
  7125. }
  7126. normalizeHTMLAttributeOptions(hash);
  7127. url.push(options);
  7128. hash.href = Handlebars.helpers.url.apply(this, url);
  7129. hash.tagName = hash.tagName || 'a';
  7130. hash.trigger && (hash[triggerEventAttributeName] = options.hash.trigger);
  7131. delete hash.trigger;
  7132. hash[callMethodAttributeName] = '_anchorClick';
  7133. return new Handlebars.SafeString(Thorax.Util.tag(hash, options.fn ? options.fn(this) : '', expandTokens ? this : null));
  7134. });
  7135. var clickSelector = '[' + callMethodAttributeName + '], [' + triggerEventAttributeName + ']';
  7136. function handleClick(event) {
  7137. var target = $(event.target),
  7138. view = target.view({helper: false}),
  7139. methodName = target.attr(callMethodAttributeName),
  7140. eventName = target.attr(triggerEventAttributeName),
  7141. methodResponse = false;
  7142. methodName && (methodResponse = view[methodName].call(view, event));
  7143. eventName && view.trigger(eventName, event);
  7144. target.tagName === "A" && methodResponse === false && event.preventDefault();
  7145. }
  7146. var lastClickHandlerEventName;
  7147. function registerClickHandler() {
  7148. unregisterClickHandler();
  7149. lastClickHandlerEventName = Thorax._fastClickEventName || 'click';
  7150. $(document).on(lastClickHandlerEventName, clickSelector, handleClick);
  7151. }
  7152. function unregisterClickHandler() {
  7153. lastClickHandlerEventName && $(document).off(lastClickHandlerEventName, clickSelector, handleClick);
  7154. }
  7155. $(document).ready(function() {
  7156. if (!Thorax._fastClickEventName) {
  7157. registerClickHandler();
  7158. }
  7159. });
  7160. ;;
  7161. var elementPlaceholderAttributeName = 'data-element-tmp';
  7162. Handlebars.registerHelper('element', function(element, options) {
  7163. normalizeHTMLAttributeOptions(options.hash);
  7164. var cid = _.uniqueId('element'),
  7165. declaringView = getOptionsData(options).view,
  7166. htmlAttributes = _.pick(options.hash, htmlAttributesToCopy);
  7167. htmlAttributes[elementPlaceholderAttributeName] = cid;
  7168. declaringView._elementsByCid || (declaringView._elementsByCid = {});
  7169. declaringView._elementsByCid[cid] = element;
  7170. return new Handlebars.SafeString(Thorax.Util.tag(htmlAttributes));
  7171. });
  7172. Thorax.View.on('append', function(scope, callback) {
  7173. (scope || this.$el).find('[' + elementPlaceholderAttributeName + ']').forEach(function(el) {
  7174. var $el = $(el),
  7175. cid = $el.attr(elementPlaceholderAttributeName),
  7176. element = this._elementsByCid[cid];
  7177. // A callback function may be specified as the value
  7178. if (_.isFunction(element)) {
  7179. element = element.call(this);
  7180. }
  7181. $el.replaceWith(element);
  7182. callback && callback(element);
  7183. }, this);
  7184. });
  7185. ;;
  7186. Handlebars.registerHelper('super', function(options) {
  7187. var declaringView = getOptionsData(options).view,
  7188. parent = declaringView.constructor && declaringView.constructor.__super__;
  7189. if (parent) {
  7190. var template = parent.template;
  7191. if (!template) {
  7192. if (!parent.name) {
  7193. throw new Error('Cannot use super helper when parent has no name or template.');
  7194. }
  7195. template = parent.name;
  7196. }
  7197. if (_.isString(template)) {
  7198. template = Thorax.Util.getTemplate(template, false);
  7199. }
  7200. return new Handlebars.SafeString(template(this, options));
  7201. } else {
  7202. return '';
  7203. }
  7204. });
  7205. ;;
  7206. /*global collectionOptionNames, inheritVars */
  7207. var loadStart = 'load:start',
  7208. loadEnd = 'load:end',
  7209. rootObject;
  7210. Thorax.setRootObject = function(obj) {
  7211. rootObject = obj;
  7212. };
  7213. Thorax.loadHandler = function(start, end, context) {
  7214. var loadCounter = _.uniqueId();
  7215. return function(message, background, object) {
  7216. var self = context || this;
  7217. self._loadInfo = self._loadInfo || [];
  7218. var loadInfo = self._loadInfo[loadCounter];
  7219. function startLoadTimeout() {
  7220. // If the timeout has been set already but has not triggered yet do nothing
  7221. // Otherwise set a new timeout (either initial or for going from background to
  7222. // non-background loading)
  7223. if (loadInfo.timeout && !loadInfo.run) {
  7224. return;
  7225. }
  7226. var loadingTimeout = self._loadingTimeoutDuration !== undefined ?
  7227. self._loadingTimeoutDuration : Thorax.View.prototype._loadingTimeoutDuration;
  7228. loadInfo.timeout = setTimeout(function() {
  7229. try {
  7230. loadInfo.run = true;
  7231. start.call(self, loadInfo.message, loadInfo.background, loadInfo);
  7232. } catch (e) {
  7233. Thorax.onException('loadStart', e);
  7234. }
  7235. }, loadingTimeout * 1000);
  7236. }
  7237. if (!loadInfo) {
  7238. loadInfo = self._loadInfo[loadCounter] = _.extend({
  7239. events: [],
  7240. timeout: 0,
  7241. message: message,
  7242. background: !!background
  7243. }, Backbone.Events);
  7244. startLoadTimeout();
  7245. } else {
  7246. clearTimeout(loadInfo.endTimeout);
  7247. loadInfo.message = message;
  7248. if (!background && loadInfo.background) {
  7249. loadInfo.background = false;
  7250. startLoadTimeout();
  7251. }
  7252. }
  7253. // Prevent binds to the same object multiple times as this can cause very bad things
  7254. // to happen for the load;load;end;end execution flow.
  7255. if (loadInfo.events.indexOf(object) >= 0) {
  7256. loadInfo.events.push(object);
  7257. return;
  7258. }
  7259. loadInfo.events.push(object);
  7260. object.on(loadEnd, function endCallback() {
  7261. var loadingEndTimeout = self._loadingTimeoutEndDuration;
  7262. if (loadingEndTimeout === void 0) {
  7263. // If we are running on a non-view object pull the default timeout
  7264. loadingEndTimeout = Thorax.View.prototype._loadingTimeoutEndDuration;
  7265. }
  7266. var events = loadInfo.events,
  7267. index = events.indexOf(object);
  7268. if (index >= 0) {
  7269. events.splice(index, 1);
  7270. if (events.indexOf(object) < 0) {
  7271. // Last callback for this particlar object, remove the bind
  7272. object.off(loadEnd, endCallback);
  7273. }
  7274. }
  7275. if (!events.length) {
  7276. clearTimeout(loadInfo.endTimeout);
  7277. loadInfo.endTimeout = setTimeout(function() {
  7278. try {
  7279. if (!events.length) {
  7280. var run = loadInfo.run;
  7281. if (run) {
  7282. // Emit the end behavior, but only if there is a paired start
  7283. end.call(self, loadInfo.background, loadInfo);
  7284. loadInfo.trigger(loadEnd, loadInfo);
  7285. }
  7286. // If stopping make sure we don't run a start
  7287. clearTimeout(loadInfo.timeout);
  7288. loadInfo = self._loadInfo[loadCounter] = undefined;
  7289. }
  7290. } catch (e) {
  7291. Thorax.onException('loadEnd', e);
  7292. }
  7293. }, loadingEndTimeout * 1000);
  7294. }
  7295. });
  7296. };
  7297. };
  7298. /**
  7299. * Helper method for propagating load:start events to other objects.
  7300. *
  7301. * Forwards load:start events that occur on `source` to `dest`.
  7302. */
  7303. Thorax.forwardLoadEvents = function(source, dest, once) {
  7304. function load(message, backgound, object) {
  7305. if (once) {
  7306. source.off(loadStart, load);
  7307. }
  7308. dest.trigger(loadStart, message, backgound, object);
  7309. }
  7310. source.on(loadStart, load);
  7311. return {
  7312. off: function() {
  7313. source.off(loadStart, load);
  7314. }
  7315. };
  7316. };
  7317. //
  7318. // Data load event generation
  7319. //
  7320. /**
  7321. * Mixing for generating load:start and load:end events.
  7322. */
  7323. Thorax.mixinLoadable = function(target, useParent) {
  7324. _.extend(target, {
  7325. //loading config
  7326. _loadingClassName: 'loading',
  7327. _loadingTimeoutDuration: 0.33,
  7328. _loadingTimeoutEndDuration: 0.10,
  7329. // Propagates loading view parameters to the AJAX layer
  7330. onLoadStart: function(message, background, object) {
  7331. var that = useParent ? this.parent : this;
  7332. // Protect against race conditions
  7333. if (!that || !that.el) {
  7334. return;
  7335. }
  7336. if (!that.nonBlockingLoad && !background && rootObject && rootObject !== this) {
  7337. rootObject.trigger(loadStart, message, background, object);
  7338. }
  7339. that._isLoading = true;
  7340. $(that.el).addClass(that._loadingClassName);
  7341. // used by loading helpers
  7342. that.trigger('change:load-state', 'start');
  7343. },
  7344. onLoadEnd: function(/* background, object */) {
  7345. var that = useParent ? this.parent : this;
  7346. // Protect against race conditions
  7347. if (!that || !that.el) {
  7348. return;
  7349. }
  7350. that._isLoading = false;
  7351. $(that.el).removeClass(that._loadingClassName);
  7352. // used by loading helper
  7353. that.trigger('change:load-state', 'end');
  7354. }
  7355. });
  7356. };
  7357. Thorax.mixinLoadableEvents = function(target, useParent) {
  7358. _.extend(target, {
  7359. loadStart: function(message, background) {
  7360. var that = useParent ? this.parent : this;
  7361. that.trigger(loadStart, message, background, that);
  7362. },
  7363. loadEnd: function() {
  7364. var that = useParent ? this.parent : this;
  7365. that.trigger(loadEnd, that);
  7366. }
  7367. });
  7368. };
  7369. Thorax.mixinLoadable(Thorax.View.prototype);
  7370. Thorax.mixinLoadableEvents(Thorax.View.prototype);
  7371. if (Thorax.HelperView) {
  7372. Thorax.mixinLoadable(Thorax.HelperView.prototype, true);
  7373. Thorax.mixinLoadableEvents(Thorax.HelperView.prototype, true);
  7374. }
  7375. if (Thorax.CollectionHelperView) {
  7376. Thorax.mixinLoadable(Thorax.CollectionHelperView.prototype, true);
  7377. Thorax.mixinLoadableEvents(Thorax.CollectionHelperView.prototype, true);
  7378. }
  7379. Thorax.sync = function(method, dataObj, options) {
  7380. var self = this,
  7381. complete = options.complete;
  7382. options.complete = function() {
  7383. self._request = undefined;
  7384. self._aborted = false;
  7385. complete && complete.apply(this, arguments);
  7386. };
  7387. this._request = Backbone.sync.apply(this, arguments);
  7388. return this._request;
  7389. };
  7390. function bindToRoute(callback, failback) {
  7391. var fragment = Backbone.history.getFragment(),
  7392. routeChanged = false;
  7393. function routeHandler() {
  7394. if (fragment === Backbone.history.getFragment()) {
  7395. return;
  7396. }
  7397. routeChanged = true;
  7398. res.cancel();
  7399. failback && failback();
  7400. }
  7401. Backbone.history.on('route', routeHandler);
  7402. function finalizer() {
  7403. Backbone.history.off('route', routeHandler);
  7404. if (!routeChanged) {
  7405. callback.apply(this, arguments);
  7406. }
  7407. }
  7408. var res = _.bind(finalizer, this);
  7409. res.cancel = function() {
  7410. Backbone.history.off('route', routeHandler);
  7411. };
  7412. return res;
  7413. }
  7414. function loadData(callback, failback, options) {
  7415. if (this.isPopulated()) {
  7416. return callback(this);
  7417. }
  7418. if (arguments.length === 2 && !_.isFunction(failback) && _.isObject(failback)) {
  7419. options = failback;
  7420. failback = false;
  7421. }
  7422. var self = this,
  7423. routeChanged = false,
  7424. successCallback = bindToRoute(_.bind(callback, self), function() {
  7425. routeChanged = true;
  7426. if (self._request) {
  7427. self._aborted = true;
  7428. self._request.abort();
  7429. }
  7430. failback && failback.call(self, false);
  7431. });
  7432. this.fetch(_.defaults({
  7433. success: successCallback,
  7434. error: failback && function() {
  7435. if (!routeChanged) {
  7436. failback.apply(self, [true].concat(_.toArray(arguments)));
  7437. }
  7438. },
  7439. complete: function() {
  7440. successCallback.cancel();
  7441. }
  7442. }, options));
  7443. }
  7444. function fetchQueue(options, $super) {
  7445. if (options.resetQueue) {
  7446. // WARN: Should ensure that loaders are protected from out of band data
  7447. // when using this option
  7448. this.fetchQueue = undefined;
  7449. }
  7450. if (!this.fetchQueue) {
  7451. // Kick off the request
  7452. this.fetchQueue = [options];
  7453. options = _.defaults({
  7454. success: flushQueue(this, this.fetchQueue, 'success'),
  7455. error: flushQueue(this, this.fetchQueue, 'error'),
  7456. complete: flushQueue(this, this.fetchQueue, 'complete')
  7457. }, options);
  7458. $super.call(this, options);
  7459. } else {
  7460. // Currently fetching. Queue and process once complete
  7461. this.fetchQueue.push(options);
  7462. }
  7463. }
  7464. function flushQueue(self, fetchQueue, handler) {
  7465. return function() {
  7466. var args = arguments;
  7467. // Flush the queue. Executes any callback handlers that
  7468. // may have been passed in the fetch options.
  7469. _.each(fetchQueue, function(options) {
  7470. if (options[handler]) {
  7471. options[handler].apply(this, args);
  7472. }
  7473. }, this);
  7474. // Reset the queue if we are still the active request
  7475. if (self.fetchQueue === fetchQueue) {
  7476. self.fetchQueue = undefined;
  7477. }
  7478. };
  7479. }
  7480. var klasses = [];
  7481. Thorax.Model && klasses.push(Thorax.Model);
  7482. Thorax.Collection && klasses.push(Thorax.Collection);
  7483. _.each(klasses, function(DataClass) {
  7484. var $fetch = DataClass.prototype.fetch;
  7485. Thorax.mixinLoadableEvents(DataClass.prototype, false);
  7486. _.extend(DataClass.prototype, {
  7487. sync: Thorax.sync,
  7488. fetch: function(options) {
  7489. options = options || {};
  7490. var self = this,
  7491. complete = options.complete;
  7492. options.complete = function() {
  7493. complete && complete.apply(this, arguments);
  7494. self.loadEnd();
  7495. };
  7496. self.loadStart(undefined, options.background);
  7497. return fetchQueue.call(this, options || {}, $fetch);
  7498. },
  7499. load: function(callback, failback, options) {
  7500. if (arguments.length === 2 && !_.isFunction(failback)) {
  7501. options = failback;
  7502. failback = false;
  7503. }
  7504. options = options || {};
  7505. if (!options.background && !this.isPopulated() && rootObject) {
  7506. // Make sure that the global scope sees the proper load events here
  7507. // if we are loading in standalone mode
  7508. Thorax.forwardLoadEvents(this, rootObject, true);
  7509. }
  7510. loadData.call(this, callback, failback, options);
  7511. }
  7512. });
  7513. });
  7514. Thorax.Util.bindToRoute = bindToRoute;
  7515. if (Thorax.Router) {
  7516. Thorax.Router.bindToRoute = Thorax.Router.prototype.bindToRoute = bindToRoute;
  7517. }
  7518. // Propagates loading view parameters to the AJAX layer
  7519. Thorax.View.prototype._modifyDataObjectOptions = function(dataObject, options) {
  7520. options.ignoreErrors = this.ignoreFetchError;
  7521. options.background = this.nonBlockingLoad;
  7522. return options;
  7523. };
  7524. // Thorax.CollectionHelperView inherits from CollectionView
  7525. // not HelperView so need to set it manually
  7526. Thorax.HelperView.prototype._modifyDataObjectOptions = Thorax.CollectionHelperView.prototype._modifyDataObjectOptions = function(dataObject, options) {
  7527. options.ignoreErrors = this.parent.ignoreFetchError;
  7528. options.background = this.parent.nonBlockingLoad;
  7529. return options;
  7530. };
  7531. inheritVars.collection.loading = function() {
  7532. var loadingView = this.loadingView,
  7533. loadingTemplate = this.loadingTemplate,
  7534. loadingPlacement = this.loadingPlacement;
  7535. //add "loading-view" and "loading-template" options to collection helper
  7536. if (loadingView || loadingTemplate) {
  7537. var callback = Thorax.loadHandler(_.bind(function() {
  7538. var item;
  7539. if (this.collection.length === 0) {
  7540. this.$el.empty();
  7541. }
  7542. if (loadingView) {
  7543. var instance = Thorax.Util.getViewInstance(loadingView);
  7544. this._addChild(instance);
  7545. if (loadingTemplate) {
  7546. instance.render(loadingTemplate);
  7547. } else {
  7548. instance.render();
  7549. }
  7550. item = instance;
  7551. } else {
  7552. item = this.renderTemplate(loadingTemplate);
  7553. }
  7554. var index = loadingPlacement
  7555. ? loadingPlacement.call(this)
  7556. : this.collection.length
  7557. ;
  7558. this.appendItem(item, index);
  7559. this.$el.children().eq(index).attr('data-loading-element', this.collection.cid);
  7560. }, this), _.bind(function() {
  7561. this.$el.find('[data-loading-element="' + this.collection.cid + '"]').remove();
  7562. }, this),
  7563. this.collection);
  7564. this.listenTo(this.collection, 'load:start', callback);
  7565. }
  7566. };
  7567. if (collectionOptionNames) {
  7568. collectionOptionNames['loading-template'] = 'loadingTemplate';
  7569. collectionOptionNames['loading-view'] = 'loadingView';
  7570. collectionOptionNames['loading-placement'] = 'loadingPlacement';
  7571. }
  7572. Thorax.View.on({
  7573. 'load:start': Thorax.loadHandler(
  7574. function(message, background, object) {
  7575. this.onLoadStart(message, background, object);
  7576. },
  7577. function(background, object) {
  7578. this.onLoadEnd(object);
  7579. }),
  7580. collection: {
  7581. 'load:start': function(message, background, object) {
  7582. this.trigger(loadStart, message, background, object);
  7583. }
  7584. },
  7585. model: {
  7586. 'load:start': function(message, background, object) {
  7587. this.trigger(loadStart, message, background, object);
  7588. }
  7589. }
  7590. });
  7591. ;;
  7592. Handlebars.registerHelper('loading', function(options) {
  7593. var view = getOptionsData(options).view;
  7594. view.off('change:load-state', onLoadStateChange, view);
  7595. view.on('change:load-state', onLoadStateChange, view);
  7596. return view._isLoading ? options.fn(this) : options.inverse(this);
  7597. });
  7598. function onLoadStateChange() {
  7599. this.render();
  7600. }
  7601. ;;
  7602. /*global pushDomEvents */
  7603. var isiOS = navigator.userAgent.match(/(iPhone|iPod|iPad)/i),
  7604. isAndroid = navigator.userAgent.toLowerCase().indexOf("android") > -1 ? 1 : 0,
  7605. minimumScrollYOffset = isAndroid ? 1 : 0;
  7606. Thorax.Util.scrollTo = function(x, y) {
  7607. y = y || minimumScrollYOffset;
  7608. function _scrollTo() {
  7609. window.scrollTo(x, y);
  7610. }
  7611. if (isiOS) {
  7612. // a defer is required for ios
  7613. _.defer(_scrollTo);
  7614. } else {
  7615. _scrollTo();
  7616. }
  7617. return [x, y];
  7618. };
  7619. Thorax.LayoutView.on('change:view:end', function(newView, oldView, options) {
  7620. options.scroll && Thorax.Util.scrollTo(0, 0);
  7621. });
  7622. Thorax.Util.scrollToTop = function() {
  7623. // android will use height of 1 because of minimumScrollYOffset in scrollTo()
  7624. return this.scrollTo(0, 0);
  7625. };
  7626. pushDomEvents([
  7627. 'singleTap', 'doubleTap', 'longTap',
  7628. 'swipe',
  7629. 'swipeUp', 'swipeDown',
  7630. 'swipeLeft', 'swipeRight'
  7631. ]);
  7632. //built in dom events
  7633. Thorax.View.on({
  7634. 'submit form': function(/* event */) {
  7635. // Hide any virtual keyboards that may be lingering around
  7636. var focused = $(':focus')[0];
  7637. focused && focused.blur();
  7638. }
  7639. });
  7640. ;;
  7641. /*global isAndroid */
  7642. $.fn.tapHoldAndEnd = function(selector, callbackStart, callbackEnd) {
  7643. function triggerEvent(obj, eventType, callback, event) {
  7644. var originalType = event.type,
  7645. result;
  7646. event.type = eventType;
  7647. if (callback) {
  7648. result = callback.call(obj, event);
  7649. }
  7650. event.type = originalType;
  7651. return result;
  7652. }
  7653. var timers = [];
  7654. return this.each(function() {
  7655. var thisObject = this,
  7656. tapHoldStart = false,
  7657. $this = $(thisObject);
  7658. $this.on('touchstart', selector, function(event) {
  7659. tapHoldStart = false;
  7660. var origEvent = event,
  7661. timer;
  7662. function clearTapTimer(event) {
  7663. clearTimeout(timer);
  7664. if (tapHoldStart) {
  7665. var retval = false;
  7666. if (event) {
  7667. // We aren't sending any end events for touchcancel cases,
  7668. // prevent an exception
  7669. retval = triggerEvent(thisObject, 'tapHoldEnd', callbackEnd, event);
  7670. }
  7671. if (retval === false) {
  7672. _.each(timers, clearTimeout);
  7673. timers = [];
  7674. }
  7675. }
  7676. }
  7677. $(document).one('touchcancel', function() {
  7678. clearTapTimer();
  7679. $this.off('touchmove', selector, clearTapTimer);
  7680. $this.off('touchend', selector, clearTapTimer);
  7681. });
  7682. $this.on('touchend', selector, clearTapTimer);
  7683. $this.on('touchmove', selector, clearTapTimer);
  7684. timer = setTimeout(function() {
  7685. tapHoldStart = true;
  7686. var retval = triggerEvent(thisObject, 'tapHoldStart', callbackStart, origEvent);
  7687. if (retval === false) {
  7688. _.each(timers, clearTimeout);
  7689. timers = [];
  7690. }
  7691. }, 150);
  7692. timers.push(timer);
  7693. });
  7694. });
  7695. };
  7696. //only enable on android
  7697. var useNativeHighlight = !isAndroid;
  7698. Thorax.configureTapHighlight = function(useNative) {
  7699. useNativeHighlight = useNative;
  7700. };
  7701. var NATIVE_TAPPABLE = {
  7702. 'A': true,
  7703. 'INPUT': true,
  7704. 'BUTTON': true,
  7705. 'SELECT': true,
  7706. 'TEXTAREA': true
  7707. };
  7708. function fixupTapHighlight(scope) {
  7709. _.each(this._domEvents || [], function(bind) {
  7710. var components = bind.split(' '),
  7711. selector = components.slice(1).join(' ') || undefined; // Needed to make zepto happy
  7712. if (components[0] === 'click') {
  7713. // !selector case is for root click handlers on the view, i.e. 'click'
  7714. $(selector || this.el, selector && (scope || this.el)).forEach(function(el) {
  7715. var $el = $(el).data('tappable', true);
  7716. if (useNativeHighlight && !NATIVE_TAPPABLE[el.tagName]) {
  7717. // Add an explicit NOP bind to allow tap-highlight support
  7718. $el.on('click', function() {});
  7719. }
  7720. });
  7721. }
  7722. }, this);
  7723. }
  7724. _.extend(Thorax.View.prototype, {
  7725. _tapHighlightClassName: 'active',
  7726. _tapHighlightStart: function(event) {
  7727. var target = event.currentTarget,
  7728. tagName = target && target.tagName.toLowerCase();
  7729. // User input controls may be visually part of a larger group. For these cases
  7730. // we want to give priority to any parent that may provide a focus operation.
  7731. if (tagName === 'input' || tagName === 'select' || tagName === 'textarea') {
  7732. target = $(target).closest('[data-tappable=true]')[0] || target;
  7733. }
  7734. if (target) {
  7735. $(target).addClass(this._tapHighlightClassName);
  7736. return false;
  7737. }
  7738. },
  7739. _tapHighlightEnd: function(/* event */) {
  7740. $('.' + this._tapHighlightClassName).removeClass(this._tapHighlightClassName);
  7741. }
  7742. });
  7743. //TODO: examine if these are still needed
  7744. var fixupTapHighlightCallback = function() {
  7745. fixupTapHighlight.call(this);
  7746. };
  7747. Thorax.View.on({
  7748. 'rendered': fixupTapHighlightCallback,
  7749. 'rendered:collection': fixupTapHighlightCallback,
  7750. 'rendered:item': fixupTapHighlightCallback,
  7751. 'rendered:empty': fixupTapHighlightCallback
  7752. });
  7753. var _setElement = Thorax.View.prototype.setElement,
  7754. tapHighlightSelector = '[data-tappable=true], a, input, button, select, textarea';
  7755. Thorax.View.prototype.setElement = function() {
  7756. var response = _setElement.apply(this, arguments);
  7757. if (!this.noTapHighlight) {
  7758. if (!useNativeHighlight) {
  7759. var self = this;
  7760. function exec(name) {
  7761. return function() {
  7762. try {
  7763. self[name].apply(self, arguments);
  7764. } catch(e) {
  7765. Thorax.onException(name, e);
  7766. }
  7767. };
  7768. }
  7769. this.$el.tapHoldAndEnd(tapHighlightSelector, exec('_tapHighlightStart'), exec('_tapHighlightEnd'));
  7770. }
  7771. }
  7772. return response;
  7773. };
  7774. var _addEvent = Thorax.View.prototype._addEvent;
  7775. Thorax.View.prototype._addEvent = function(params) {
  7776. this._domEvents = this._domEvents || [];
  7777. if (params.type === "DOM") {
  7778. this._domEvents.push(params.originalName);
  7779. }
  7780. return _addEvent.call(this, params);
  7781. };
  7782. ;;
  7783. })();