diff --git a/README.md b/README.md new file mode 100644 index 0000000..78359a1 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Ember.js + deppy • [TodoMVC](http://todomvc.com) + + +## Running tests + +To fire specs runner, append `#specs` to the url in address bar, and reload the webpage. + + +## Credit + +Initial release by @tomdale. + +Refactoring and maintenance by @stas. + +Porting to deppy by @thanpolas diff --git a/assets/base.css b/assets/base.css new file mode 100644 index 0000000..8d1db3a --- /dev/null +++ b/assets/base.css @@ -0,0 +1,414 @@ +html, +body { + margin: 0; + padding: 0; +} + +button { + margin: 0; + padding: 0; + border: 0; + background: none; + font-size: 100%; + vertical-align: baseline; + font-family: inherit; + color: inherit; + -webkit-appearance: none; + /*-moz-appearance: none;*/ + -ms-appearance: none; + -o-appearance: none; + appearance: none; +} + +body { + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; + line-height: 1.4em; + background: #eaeaea url('bg.png'); + color: #4d4d4d; + width: 550px; + margin: 0 auto; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + -ms-font-smoothing: antialiased; + -o-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +#todoapp { + background: #fff; + background: rgba(255, 255, 255, 0.9); + margin: 130px 0 40px 0; + border: 1px solid #ccc; + position: relative; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), + 0 25px 50px 0 rgba(0, 0, 0, 0.15); +} + +#todoapp:before { + content: ''; + border-left: 1px solid #f5d6d6; + border-right: 1px solid #f5d6d6; + width: 2px; + position: absolute; + top: 0; + left: 40px; + height: 100%; +} + +#todoapp input::-webkit-input-placeholder { + font-style: italic; +} + +#todoapp input:-moz-placeholder { + font-style: italic; + color: #a9a9a9; +} + +#todoapp h1 { + position: absolute; + top: -120px; + width: 100%; + font-size: 70px; + font-weight: bold; + text-align: center; + color: #b3b3b3; + color: rgba(255, 255, 255, 0.3); + text-shadow: -1px -1px rgba(0, 0, 0, 0.2); + -webkit-text-rendering: optimizeLegibility; + -moz-text-rendering: optimizeLegibility; + -ms-text-rendering: optimizeLegibility; + -o-text-rendering: optimizeLegibility; + text-rendering: optimizeLegibility; +} + +#header { + padding-top: 15px; + border-radius: inherit; +} + +#header:before { + content: ''; + position: absolute; + top: 0; + right: 0; + left: 0; + height: 15px; + z-index: 2; + border-bottom: 1px solid #6c615c; + background: #8d7d77; + background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); + background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); + border-top-left-radius: 1px; + border-top-right-radius: 1px; +} + +#new-todo, +.edit { + position: relative; + margin: 0; + width: 100%; + font-size: 24px; + font-family: inherit; + line-height: 1.4em; + border: 0; + outline: none; + color: inherit; + padding: 6px; + border: 1px solid #999; + box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + -ms-font-smoothing: antialiased; + -o-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +#new-todo { + padding: 16px 16px 16px 60px; + border: none; + background: rgba(0, 0, 0, 0.02); + z-index: 2; + box-shadow: none; +} + +#main { + position: relative; + z-index: 2; + border-top: 1px dotted #adadad; +} + +label[for='toggle-all'] { + display: none; +} + +#toggle-all { + position: absolute; + top: -42px; + left: -4px; + width: 40px; + text-align: center; + border: none; /* Mobile Safari */ +} + +#toggle-all:before { + content: '»'; + font-size: 28px; + color: #d9d9d9; + padding: 0 25px 7px; +} + +#toggle-all:checked:before { + color: #737373; +} + +#todo-list { + margin: 0; + padding: 0; + list-style: none; +} + +#todo-list li { + position: relative; + font-size: 24px; + border-bottom: 1px dotted #ccc; +} + +#todo-list li:last-child { + border-bottom: none; +} + +#todo-list li.editing { + border-bottom: none; + padding: 0; +} + +#todo-list li.editing .edit { + display: block; + width: 506px; + padding: 13px 17px 12px 17px; + margin: 0 0 0 43px; +} + +#todo-list li.editing .view { + display: none; +} + +#todo-list li .toggle { + text-align: center; + width: 40px; + /* auto, since non-WebKit browsers doesn't support input styling */ + height: auto; + position: absolute; + top: 0; + bottom: 0; + margin: auto 0; + border: none; /* Mobile Safari */ + -webkit-appearance: none; + /*-moz-appearance: none;*/ + -ms-appearance: none; + -o-appearance: none; + appearance: none; +} + +#todo-list li .toggle:after { + content: '✔'; + line-height: 43px; /* 40 + a couple of pixels visual adjustment */ + font-size: 20px; + color: #d9d9d9; + text-shadow: 0 -1px 0 #bfbfbf; +} + +#todo-list li .toggle:checked:after { + color: #85ada7; + text-shadow: 0 1px 0 #669991; + bottom: 1px; + position: relative; +} + +#todo-list li label { + word-break: break-word; + padding: 15px; + margin-left: 45px; + display: block; + line-height: 1.2; + -webkit-transition: color 0.4s; + -moz-transition: color 0.4s; + -ms-transition: color 0.4s; + -o-transition: color 0.4s; + transition: color 0.4s; +} + +#todo-list li.completed label { + color: #a9a9a9; + text-decoration: line-through; +} + +#todo-list li .destroy { + display: none; + position: absolute; + top: 0; + right: 10px; + bottom: 0; + width: 40px; + height: 40px; + margin: auto 0; + font-size: 22px; + color: #a88a8a; + -webkit-transition: all 0.2s; + -moz-transition: all 0.2s; + -ms-transition: all 0.2s; + -o-transition: all 0.2s; + transition: all 0.2s; +} + +#todo-list li .destroy:hover { + text-shadow: 0 0 1px #000, + 0 0 10px rgba(199, 107, 107, 0.8); + -webkit-transform: scale(1.3); + -moz-transform: scale(1.3); + -ms-transform: scale(1.3); + -o-transform: scale(1.3); + transform: scale(1.3); +} + +#todo-list li .destroy:after { + content: '✖'; +} + +#todo-list li:hover .destroy { + display: block; +} + +#todo-list li .edit { + display: none; +} + +#todo-list li.editing:last-child { + margin-bottom: -1px; +} + +#footer { + color: #777; + padding: 0 15px; + position: absolute; + right: 0; + bottom: -31px; + left: 0; + height: 20px; + z-index: 1; + text-align: center; +} + +#footer:before { + content: ''; + position: absolute; + right: 0; + bottom: 31px; + left: 0; + height: 50px; + z-index: -1; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), + 0 6px 0 -3px rgba(255, 255, 255, 0.8), + 0 7px 1px -3px rgba(0, 0, 0, 0.3), + 0 43px 0 -6px rgba(255, 255, 255, 0.8), + 0 44px 2px -6px rgba(0, 0, 0, 0.2); +} + +#todo-count { + float: left; + text-align: left; +} + +#filters { + margin: 0; + padding: 0; + list-style: none; + position: absolute; + right: 0; + left: 0; +} + +#filters li { + display: inline; +} + +#filters li a { + color: #83756f; + margin: 2px; + text-decoration: none; +} + +#filters li a.selected { + font-weight: bold; +} + +#clear-completed { + float: right; + position: relative; + line-height: 20px; + text-decoration: none; + background: rgba(0, 0, 0, 0.1); + font-size: 11px; + padding: 0 10px; + border-radius: 3px; + box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); +} + +#clear-completed:hover { + background: rgba(0, 0, 0, 0.15); + box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); +} + +#info { + margin: 65px auto 0; + color: #a6a6a6; + font-size: 12px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); + text-align: center; +} + +#info a { + color: inherit; +} + +/* + Hack to remove background from Mobile Safari. + Can't use it globally since it destroys checkboxes in Firefox and Opera +*/ +@media screen and (-webkit-min-device-pixel-ratio:0) { + #toggle-all, + #todo-list li .toggle { + background: none; + } + + #todo-list li .toggle { + height: 40px; + } + + #toggle-all { + top: -56px; + left: -15px; + width: 65px; + height: 41px; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + -webkit-appearance: none; + appearance: none; + } +} + +.hidden{ + display:none; +} diff --git a/assets/base.js b/assets/base.js new file mode 100644 index 0000000..6cf09a2 --- /dev/null +++ b/assets/base.js @@ -0,0 +1,7 @@ +(function( window ) { + 'use strict'; + + if ( location.hostname === 'todomvc.com' ) { + var _gaq=[['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script')); + } +})( window ); diff --git a/assets/bg.png b/assets/bg.png new file mode 100644 index 0000000..b2a7600 Binary files /dev/null and b/assets/bg.png differ diff --git a/assets/director.min.js b/assets/director.min.js new file mode 100644 index 0000000..478668c --- /dev/null +++ b/assets/director.min.js @@ -0,0 +1,7 @@ + + +// +// Generated on Sat Sep 01 2012 21:49:06 GMT+0530 (IST) by Nodejitsu, Inc (Using Codesurgeon). +// Version 1.1.6 +// +(function(a){function j(a,b){~a.indexOf("*")&&(a=a.replace(/\*/g,"([_.()!\\ %@&a-zA-Z0-9-]+)"));var c=a.match(/:([^\/]+)/ig),d;if(c){d=c.length;for(var e=0;e7))this.history===!0?setTimeout(function(){window.onpopstate=d},500):window.onhashchange=d,this.mode="modern";else{var f=document.createElement("iframe");f.id="state-frame",f.style.display="none",document.body.appendChild(f),this.writeFrame(""),"onpropertychange"in document&&"attachEvent"in document&&document.attachEvent("onpropertychange",function(){event.propertyName==="location"&&c.check()}),window.setInterval(function(){c.check()},50),this.onHashChanged=d,this.mode="legacy"}e.listeners.push(a);return this.mode},destroy:function(a){if(!!e&&!!e.listeners){var b=e.listeners;for(var c=b.length-1;c>=0;c--)b[c]===a&&b.splice(c,1)}},setHash:function(a){this.mode==="legacy"&&this.writeFrame(a),this.history===!0?(window.history.pushState({},document.title,a),this.fire()):b.hash=a[0]==="/"?a:"/"+a;return this},writeFrame:function(a){var b=document.getElementById("state-frame"),c=b.contentDocument||b.contentWindow.document;c.open(),c.write(" + + diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..43e9272 --- /dev/null +++ b/js/app.js @@ -0,0 +1,57 @@ +// Load our app +goog.provide('Todos.app'); + +goog.require('Todos.Router'); +goog.require('Todos.models.Store'); +goog.require('Todos.ctrls.Entries'); +goog.require('Todos.views.Application'); + +Todos.App = Ember.Application.create({ + VERSION: '1.0', + rootElement: '#todoapp', + // Load routes + Router: Todos.Router, + // Extend to inherit outlet support + ApplicationController: Ember.Controller.extend(), + ApplicationView: Todos.views.Application, + entriesController: Todos.ctrls.Entries.create({ + store: new Todos.models.Store('todos-emberjs') + }), + // define all our templates so we can load via ajax + // (templates are out of deppy's scope so tough) + templates: ['clear_button', 'filters', 'items', 'stats'], + ready: function() { + this.loadTemplates(this.templates.shift()); + }, + /** + * Load all templates sequencially. + * @param {?string} template the template's name. + */ + loadTemplates: function(template) { + if (!template) { + this.initialize(); + return; + } + + this.loadTemplate('js/app/templates/' + template + '.html', + template, this.loadTemplates); + }, + /* + * Loads a handlebars.js template at a given URL. Takes an optional name, in which case, + * the template is added and is reference-able via templateName. + */ + loadTemplate: function (url, name, callback) { + var _this = this; + var contents = $.get(url, function(templateText) { + var compiledTemplate = Ember.Handlebars.compile(templateText); + + Ember.TEMPLATES[name] = compiledTemplate + + callback.call(_this, _this.templates.shift()); + }); + } + + + +}); + diff --git a/js/app/controllers/entries.js b/js/app/controllers/entries.js new file mode 100644 index 0000000..a4dd517 --- /dev/null +++ b/js/app/controllers/entries.js @@ -0,0 +1,70 @@ +goog.provide('Todos.ctrls.Entries'); + +/** + * Entries controller + * + * @returns Class + */ +Todos.ctrls.Entries = Ember.ArrayProxy.extend({ + store: null, + content: [], + + createNew: function( value ) { + if ( !value.trim() ) + return; + var todo = this.get( 'store' ).createFromTitle( value ); + this.pushObject( todo ); + }, + + pushObject: function( item, ignoreStorage) { + if ( !ignoreStorage ) + this.get( 'store' ).create( item ); + return this._super( item ); + }, + + removeObject: function( item ) { + this.get( 'store' ).remove( item ); + return this._super( item ); + }, + + clearCompleted: function() { + this.filterProperty( + 'completed', true + ).forEach( this.removeObject, this ); + }, + + total: function() { + return this.get( 'length' ); + }.property( '@each.length' ), + + remaining: function() { + return this.filterProperty( 'completed', false ).get( 'length' ); + }.property( '@each.completed' ), + + completed: function() { + return this.filterProperty( 'completed', true ).get( 'length' ); + }.property( '@each.completed' ), + + noneLeft: function() { + return this.get( 'total' ) === 0; + }.property( 'total' ), + + allAreDone: function( key, value ) { + if ( value !== undefined ) { + this.setEach( 'completed', value ); + return value; + } else { + return !!this.get( 'length' ) && + this.everyProperty( 'completed', true ); + } + }.property( '@each.completed' ), + + init: function() { + this._super(); + // Load items if any upon initialization + var items = this.get( 'store' ).findAll(); + if ( items.get( 'length' ) ) { + this.set( '[]', items ); + }; + } +}); diff --git a/js/app/controllers/todos.js b/js/app/controllers/todos.js new file mode 100644 index 0000000..3216b16 --- /dev/null +++ b/js/app/controllers/todos.js @@ -0,0 +1,25 @@ +goog.provide('Todos.ctrls.Todos'); + +/** + * Todos controller + * + * @returns Class + */ +Todos.ctrls.Todos = Ember.Controller.extend({ + entries: function() { + var filter = this.getPath( 'content.filterBy' ); + + if ( Ember.empty( filter ) ) { + return this.get( 'content' ); + } + + if ( !Ember.compare( filter, 'completed' ) ) { + return this.get( 'content' ).filterProperty( 'completed', true ); + } + + if ( !Ember.compare( filter, 'active' ) ) { + return this.get( 'content' ).filterProperty( 'completed', false ); + } + + }.property( 'content.remaining' ) +}); diff --git a/js/app/models/store.js b/js/app/models/store.js new file mode 100644 index 0000000..3617797 --- /dev/null +++ b/js/app/models/store.js @@ -0,0 +1,71 @@ +goog.provide('Todos.models.Store'); + +goog.require('Todos.models.Todo'); + +// Our Store is represented by a single JS object in *localStorage*. +// Create it with a meaningful name, like the name you'd give a table. +Todos.models.Store = function( name ) { + this.name = name; + var store = localStorage.getItem( this.name ); + this.data = ( store && JSON.parse( store ) ) || {}; + + // Save the current state of the **Store** to *localStorage*. + this.save = function() { + localStorage.setItem( this.name, JSON.stringify( this.data ) ); + }; + + // Wrapper around `this.create` + // Creates a `Todo` model object out of the title + this.createFromTitle = function( title ) { + var todo = Todos.models.Todo.create({ + title: title, + store: this + }); + this.create( todo ); + return todo; + }; + + // Store the model inside the `Store` + this.create = function ( model ) { + if ( !model.get( 'id' ) ) + model.set( 'id', Date.now() ); + return this.update( model ); + }; + + // Update a model by replacing its copy in `this.data`. + this.update = function( model ) { + this.data[ model.get( 'id' ) ] = model.getProperties( + 'id', 'title', 'completed' + ); + this.save(); + return model; + }; + + // Retrieve a model from `this.data` by id. + this.find = function( model ) { + var todo = Todos.models.Todo.create( this.data[ model.get( 'id' ) ] ); + todo.set( 'store', this ); + return todo; + }; + + // Return the array of all models currently in storage. + this.findAll = function() { + var result = [], + key; + + for ( key in this.data ) { + var todo = Todos.models.Todo.create( this.data[ key ] ); + todo.set( 'store', this ); + result.push( todo ); + } + + return result; + }; + + // Delete a model from `this.data`, returning it. + this.remove = function( model ) { + delete this.data[ model.get( 'id' ) ]; + this.save(); + return model; + }; +}; diff --git a/js/app/models/todo.js b/js/app/models/todo.js new file mode 100644 index 0000000..a08b018 --- /dev/null +++ b/js/app/models/todo.js @@ -0,0 +1,18 @@ +goog.provide('Todos.models.Todo'); + +/** + * Todo entry model + * + * @returns Class + */ +Todos.models.Todo = Ember.Object.extend({ + id: null, + title: null, + completed: false, + // set store reference upon creation instead of creating static bindings + store: null, + // Observer that will react on item change and will update the storage + todoChanged: function() { + this.get( 'store' ).update( this ); + }.observes( 'title', 'completed' ) +}); diff --git a/js/app/router.js b/js/app/router.js new file mode 100644 index 0000000..4b2dbb0 --- /dev/null +++ b/js/app/router.js @@ -0,0 +1,78 @@ +goog.provide('Todos.Router'); + +goog.require('Todos.ctrls.Todos'); +goog.require('Todos.views.Items'); +goog.require('Todos.helper'); + +/** +* Todos Router +* +* Defined routes represent filters according to specs +* +* @returns Class +*/ +Todos.Router = Ember.Router.extend({ + + root: Ember.Route.extend({ + + showAll: Ember.Route.transitionTo( 'index' ), + showActive: Ember.Route.transitionTo( 'active' ), + showCompleted: Ember.Route.transitionTo( 'completed' ), + + index: Ember.Route.extend({ + route: '/', + connectOutlets: function( router ) { + var controller = router.get( 'applicationController' ); + var context = controller.namespace.entriesController; + context.set( 'filterBy', '' ); + + // This require was left here exclusively for design purposes + // Loads decoupled controller/view based on current route + controller.connectOutlet({ + viewClass: Todos.views.Items, + controller: Todos.ctrls.Todos.create(), + context: context + }); + } + }), + + active: Ember.Route.extend({ + route: '/active', + connectOutlets: function( router ) { + var controller = router.get( 'applicationController' ); + var context = controller.namespace.entriesController; + context.set( 'filterBy', 'active' ); + + // This require was left here exclusively for design purposes + // Loads decoupled controller/view based on current route + controller.connectOutlet({ + viewClass: Todos.views.Items, + controller: Todos.ctrls.Todos.create(), + context: context + }); + } + }), + + completed: Ember.Route.extend({ + route: '/completed', + connectOutlets: function( router ) { + var controller = router.get( 'applicationController' ); + var context = controller.namespace.entriesController; + context.set( 'filterBy', 'completed' ); + + // This require was left here exclusively for design purposes + // Loads decoupled controller/view based on current route + controller.connectOutlet({ + viewClass: Todos.views.Items, + controller: Todos.ctrls.Todos.create(), + context: context + }); + } + }), + + specs: Ember.Route.extend({ + route: '/specs', + connectOutlets: Todos.helper.start + }) + }) +}); diff --git a/js/app/specs/controllers/todos.js b/js/app/specs/controllers/todos.js new file mode 100644 index 0000000..54ac828 --- /dev/null +++ b/js/app/specs/controllers/todos.js @@ -0,0 +1,65 @@ +/** + * Some smoke tests + */ +describe( 'controllers/todos', function() { + + var controller = Todos.get( 'todosController' ); + var title = 'Another title...'; + + it( 'should have an input for entering new entry', function() { + expect( controller.inputView ).to.be.a( 'object' ); + expect( + $( controller.inputView.get( 'element' ) ).attr( 'placeholder' ) + ).to.equal( controller.inputView.get( 'placeholder' ) ); + }); + + it( 'should not create new entry on empty-ish input', function() { + var counted = controller.get( 'remaining' ); + controller.inputView.set( 'value', ' ' ); + controller.inputView.insertNewline(); + expect( controller.get( 'remaining' ) ).to.equal( counted ) + }); + + it( 'should create new entry on newline', function() { + controller.inputView.set( 'value', title ); + controller.inputView.insertNewline(); + expect( controller.get( 'lastObject' ).title ).to.equal( title ); + controller.removeObject( controller.get( 'lastObject' ) ); + }); + + it( 'should delete item if title is empty-ish', function() { + controller.createNew( title ); + var counted = controller.get( 'remaining' ); + var entry = controller.get( 'lastObject' ); + entry.set( 'title', ' ' ); + var editor = controller.todoEditor.create({ + todo: entry, + storage: controller + }); + editor.whenDone(); + expect( controller.get( 'remaining' ) ).to.equal( counted - 1 ); + }); + + it( 'should reflect the same number of items as in store', function() { + controller.createNew( 'value', title ); + var visibles = controller.todosView. + get( 'childViews' )[ 0 ].get( 'childViews' ).length; + expect( controller.get( 'content' ).length ).to.equal( visibles ); + controller.removeObject( controller.get( 'lastObject' ) ); + }); + + it( 'should allow removing entries', function( done ) { + controller.createNew( 'value', title ); + setTimeout( function(){ + controller.allDoneCheckbox.set( 'value', true ); + }, 100 ); + setTimeout( function(){ + controller.clearCompletedButton.triggerAction(); + }, 200 ); + setTimeout( function(){ + expect( controller.get( 'content' ).length ).to.equal( 0 ); + }, 300 ); + done(); + }); + +}); diff --git a/js/app/specs/helper.js b/js/app/specs/helper.js new file mode 100644 index 0000000..7c82ceb --- /dev/null +++ b/js/app/specs/helper.js @@ -0,0 +1,18 @@ +goog.provide('Todos.helper'); + +goog.require('Todos.todoMVCspec'); + + +/** + * Initialize the tests and run them. + */ +Todos.helper = function() { + + Todos.todoMVCspec(); + + var jasmineEnv = jasmine.getEnv(); + var htmlReporter = new jasmine.HtmlReporter(); + jasmineEnv.addReporter( htmlReporter ); + jasmineEnv.execute(); +}; + diff --git a/js/app/specs/models/store.js b/js/app/specs/models/store.js new file mode 100644 index 0000000..da4cc51 --- /dev/null +++ b/js/app/specs/models/store.js @@ -0,0 +1,31 @@ +/** + * Some integration tests + */ +describe( 'models/store', function() { + + var title = 'Testing title...'; + var store = Todos.todosController.get( 'store' ); + + it( 'should allow creating and removing items', function() { + var count = store.findAll().length; + var todo = store.createFromTitle( title ); + expect( store.findAll().length ).to.equal( count + 1 ); + expect( todo ).to.have.property( 'title', title ); + expect( todo ).to.have.property( 'completed', false ); + expect( todo ).to.have.property( 'store', store ); + store.remove( todo ); + expect( store.findAll().length ).to.equal( count ); + }); + + it( 'should allow finding and changing items', function() { + var todo = store.createFromTitle( title ); + expect( store.find( todo ).id ).to.equal( todo.id ); + expect( store.find( todo ).title ).to.equal( todo.title ); + expect( store.find( todo ).completed ).to.equal( false ); + todo.set( 'completed', true ); + expect( store.find( todo ).id ).to.equal( todo.id ); + expect( store.find( todo ).completed ).to.equal( true ); + store.remove( todo ); + }); + +}); diff --git a/js/app/specs/todoMVC.js b/js/app/specs/todoMVC.js new file mode 100644 index 0000000..3b81048 --- /dev/null +++ b/js/app/specs/todoMVC.js @@ -0,0 +1,116 @@ +/** + * TodoMVC Project Specs + * + * Use `runs` and `waits` to make sure results are run synchroneously + */ +goog.provide('Todos.todoMVCspec'); + +Todos.todoMVCspec = function() { + + describe( 'TodoMVC features.', function(){ + + var enterEvent = $.Event('keyup', { keyCode: 13 }); + var todoTitle = 'Foo Bar Todo'; + + describe( 'Todo creation:', function() { + + beforeEach( function(){ + // Make sure we are always on the main screen + window.location.hash = '#/'; + }); + + it( 'should allow creating a new todo' , function() { + runs( function(){ + $( '#new-todo' ).val( todoTitle ).trigger( enterEvent ); + }); + + waits( 100 ); + + runs( function() { + !!$( '#todo-list li' ).text().match( todoTitle ); + }); + }); + + it( 'should not allow adding an empty todo' , function() { + var ourTodo, + beforeCount = $( '#todo-list li' ).length; + + runs( function(){ + $( '#new-todo' ).val( ' ' ).trigger( enterEvent ); + }); + + waits( 100 ); + + runs( function(){ + expect( $( '#todo-list li' ).length ).toEqual( beforeCount ); + }); + }); + }); + + describe( 'Todo completion:', function() { + it( 'should allow marking a todo complete' , function() { + var ourTodo, + beforeCount = $( '#todo-list li.completed' ).length, + postTitle = ' to be completed'; + + runs( function(){ + $( '#new-todo' ).val( todoTitle + postTitle ).trigger( enterEvent ); + }); + + waits( 100 ); + + runs( function() { + ourTodo = $( '#todo-list li:last-child' ); + + expect( ourTodo.text() ).toMatch( postTitle ); + ourTodo.find( '.toggle' ).click(); + expect( $( '#todo-list li.completed' ).length ).toEqual( beforeCount + 1 ); + }); + }); + + it( 'should allow clearing completed todos' , function() { + var ourTodo, + beforeCount = $( '#todo-list li.completed' ).length, + postTitle = ' to be completed'; + + runs( function(){ + $( '#new-todo' ).val( todoTitle + postTitle ).trigger( enterEvent ); + }); + + waits( 100 ); + + runs( function() { + ourTodo = $( '#todo-list li:last-child' ); + + expect( ourTodo.text() ).toMatch( postTitle ); + ourTodo.find( '.toggle' ).click(); + $( '#clear-completed' ).click(); + expect( $( '#todo-list li.completed' ).length ).toEqual( 0 ); + }); + }); + }); + + describe( 'Todo deletion:', function() { + it( 'should allow deleting a todo' , function() { + var ourTodo, + beforeCount = $( '#todo-list li' ).length, + postTitle = ' to be deleted'; + + runs( function(){ + $( '#new-todo' ).val( todoTitle + postTitle ).trigger( enterEvent ); + }); + + waits( 100 ); + + runs( function() { + ourTodo = $( '#todo-list li:last-child' ); + + expect( ourTodo.text() ).toMatch( postTitle ); + ourTodo.find( '.destroy' ).click(); + expect( $( '#todo-list li' ).length ).toEqual( beforeCount ); + }); + }); + }); + + }); +}; diff --git a/js/app/specs/views/basic_acceptance.js b/js/app/specs/views/basic_acceptance.js new file mode 100644 index 0000000..77557bc --- /dev/null +++ b/js/app/specs/views/basic_acceptance.js @@ -0,0 +1,29 @@ +/** + * Some acceptance testing for views + */ + +describe( 'views/*', function() { + + it( 'should validate clear button view', function( done ) { + require( [ 'text!app/views/clear_button.html' ], function( html ){ + expect( html ).to.be.a( 'string' ); + expect( html ).to.match( /completedCount/ ); + expect( function(){ Em.Handlebars.compile( html ) } ).to.not.throw( Error ); + done(); + }); + }); + + it( 'should validate items view', function( done ) { + require( [ 'text!app/views/items.html' ], function( html ) { + expect( html ).to.be.a( 'string' ); + expect( html ).to.match( /collection/ ); + expect( html ).to.match( /id="todo-list"/ ); + expect( html ).to.match( /Todos\.todosController/ ); + expect( html ).to.match( /Checkbox/ ); + expect( html ).to.match( /class="toggle"/ ); + expect( function(){ Em.Handlebars.compile( html ) } ).to.not.throw( Error ); + done(); + }); + }); + +}); diff --git a/js/app/templates/clear_button.html b/js/app/templates/clear_button.html new file mode 100644 index 0000000..0d39839 --- /dev/null +++ b/js/app/templates/clear_button.html @@ -0,0 +1,5 @@ +{{#with view}} + +{{/with}} diff --git a/js/app/templates/filters.html b/js/app/templates/filters.html new file mode 100644 index 0000000..52e8a8f --- /dev/null +++ b/js/app/templates/filters.html @@ -0,0 +1,11 @@ + diff --git a/js/app/templates/hbsCompiled.js b/js/app/templates/hbsCompiled.js new file mode 100644 index 0000000..8427a19 --- /dev/null +++ b/js/app/templates/hbsCompiled.js @@ -0,0 +1,160 @@ +this["Todos"] = this["Todos"] || {}; +this["Todos"]["tpl"] = this["Todos"]["tpl"] || {}; + +this["Todos"]["tpl"]["clear_button"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { + helpers = helpers || Handlebars.helpers; data = data || {}; + var buffer = "", stack1, stack2, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this; + +function program1(depth0,data) { + + var buffer = "", stack1, foundHelper; + buffer += "\n \n"; + return buffer;} + + stack1 = depth0.view; + stack2 = {}; + stack1 = helpers['with'].call(depth0, stack1, {hash:stack2,inverse:self.noop,fn:self.program(1, program1, data),data:data}); + if(stack1 || stack1 === 0) { buffer += stack1; } + buffer += "\n"; + return buffer;}); + +this["Todos"]["tpl"]["filters"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { + helpers = helpers || Handlebars.helpers; data = data || {}; + var buffer = "", stack1, stack2, foundHelper, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression; + + + buffer += "\n"; + return buffer;}); + +this["Todos"]["tpl"]["items"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { + helpers = helpers || Handlebars.helpers; data = data || {}; + var buffer = "", stack1, stack2, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this; + +function program1(depth0,data) { + + var buffer = "", stack1, stack2, foundHelper; + buffer += "\n "; + stack1 = depth0.Ember; + stack1 = stack1 == null || stack1 === false ? stack1 : stack1.Checkbox; + stack2 = {}; + stack2['checkedBinding'] = "view.content.completed"; + stack2['class'] = "toggle"; + foundHelper = helpers.view; + stack1 = foundHelper ? foundHelper.call(depth0, stack1, {hash:stack2,data:data}) : helperMissing.call(depth0, "view", stack1, {hash:stack2,data:data}); + buffer += escapeExpression(stack1) + "\n \n \n"; + return buffer;} + +function program3(depth0,data) { + + var buffer = "", stack1, stack2, foundHelper; + buffer += "\n "; + stack1 = depth0.view; + stack1 = stack1 == null || stack1 === false ? stack1 : stack1.ItemEditorView; + stack2 = {}; + stack2['contentBinding'] = "view.content"; + foundHelper = helpers.view; + stack1 = foundHelper ? foundHelper.call(depth0, stack1, {hash:stack2,data:data}) : helperMissing.call(depth0, "view", stack1, {hash:stack2,data:data}); + buffer += escapeExpression(stack1) + "\n"; + return buffer;} + + stack1 = depth0.view; + stack1 = stack1 == null || stack1 === false ? stack1 : stack1.content; + stack1 = stack1 == null || stack1 === false ? stack1 : stack1.editing; + stack2 = {}; + stack1 = helpers.unless.call(depth0, stack1, {hash:stack2,inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data}); + if(stack1 || stack1 === 0) { buffer += stack1; } + buffer += "\n"; + return buffer;}); + +this["Todos"]["tpl"]["stats"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { + helpers = helpers || Handlebars.helpers; data = data || {}; + var buffer = "", stack1, stack2, functionType="function", escapeExpression=this.escapeExpression, self=this; + +function program1(depth0,data) { + + var buffer = "", stack1, stack2; + buffer += "\n "; + stack1 = depth0.oneLeft; + stack2 = {}; + stack1 = helpers['if'].call(depth0, stack1, {hash:stack2,inverse:self.program(4, program4, data),fn:self.program(2, program2, data),data:data}); + if(stack1 || stack1 === 0) { buffer += stack1; } + buffer += "\n"; + return buffer;} +function program2(depth0,data) { + + var buffer = "", stack1; + buffer += "\n "; + stack1 = depth0.entries; + stack1 = stack1 == null || stack1 === false ? stack1 : stack1.remaining; + stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; + buffer += escapeExpression(stack1) + " item left\n "; + return buffer;} + +function program4(depth0,data) { + + var buffer = "", stack1; + buffer += "\n "; + stack1 = depth0.entries; + stack1 = stack1 == null || stack1 === false ? stack1 : stack1.remaining; + stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; + buffer += escapeExpression(stack1) + " items left\n "; + return buffer;} + + stack1 = depth0.view; + stack2 = {}; + stack1 = helpers['with'].call(depth0, stack1, {hash:stack2,inverse:self.noop,fn:self.program(1, program1, data),data:data}); + if(stack1 || stack1 === 0) { buffer += stack1; } + buffer += "\n"; + return buffer;}); \ No newline at end of file diff --git a/js/app/templates/items.html b/js/app/templates/items.html new file mode 100644 index 0000000..588ef6a --- /dev/null +++ b/js/app/templates/items.html @@ -0,0 +1,7 @@ +{{#unless view.content.editing}} + {{view Ember.Checkbox checkedBinding="view.content.completed" class="toggle"}} + + +{{else}} + {{view view.ItemEditorView contentBinding="view.content"}} +{{/unless}} diff --git a/js/app/templates/stats.html b/js/app/templates/stats.html new file mode 100644 index 0000000..75b3c30 --- /dev/null +++ b/js/app/templates/stats.html @@ -0,0 +1,7 @@ +{{#with view}} + {{#if oneLeft }} + {{entries.remaining}} item left + {{else}} + {{entries.remaining}} items left + {{/if}} +{{/with}} diff --git a/js/app/views/application.js b/js/app/views/application.js new file mode 100644 index 0000000..daa7427 --- /dev/null +++ b/js/app/views/application.js @@ -0,0 +1,67 @@ +goog.provide('Todos.views.Application'); + +goog.require('Todos.views.Stats'); +goog.require('Todos.views.Filters'); +goog.require('Todos.views.ClearButton'); + +/** + * Main application view + * + * Requires + * Class StatsView, stats view class + * Class FiltersView, filters view class + * Class ClearBtnView, clear button view class + * @returns Class + */ +Todos.views.Application = Ember.ContainerView.extend({ + childViews: [ 'headerView', 'mainView', 'footerView' ], + headerView: Ember.ContainerView.create({ + childViews: [ 'titleView', 'createTodoView' ], + elementId: 'header', + tagName: 'header', + titleView: Ember.View.create({ + tagName: 'h1', + template: function() { + return 'todos'; + } + }), + createTodoView: Ember.TextField.create({ + entriesBinding: 'controller.namespace.entriesController', + placeholder: 'What needs to be done?', + elementId: 'new-todo', + insertNewline: function() { + var value = this.get( 'value' ); + if ( value ) { + this.get( 'entries' ).createNew( value ); + this.set( 'value', '' ); + } + } + }), + }), + mainView: Em.ContainerView.create({ + elementId: 'main', + tagName: 'section', + visibilityBinding: 'controller.namespace.entriesController.noneLeft', + classNameBindings: [ 'visibility:hidden' ], + childViews: [ 'outletView', 'markAllChkbox' ], + outletView: Ember.View.create({ + template: Ember.Handlebars.compile( '{{outlet}}' ), + }), + markAllChkbox: Ember.Checkbox.create({ + entriesBinding: 'controller.namespace.entriesController', + elementId: 'toggle-all', + checkedBinding: 'entries.allAreDone' + }) + }), + footerView: Ember.ContainerView.create({ + elementId: 'footer', + tagName: 'footer', + visibilityBinding: 'controller.namespace.entriesController.noneLeft', + classNameBindings: [ 'visibility:hidden' ], + childViews: [ + Todos.views.Stats.create(), + Todos.views.Filters.create(), + Todos.views.ClearButton.create() + ] + }) +}); diff --git a/js/app/views/clear_button.js b/js/app/views/clear_button.js new file mode 100644 index 0000000..8d9ad24 --- /dev/null +++ b/js/app/views/clear_button.js @@ -0,0 +1,17 @@ +goog.provide('Todos.views.ClearButton'); + +/** + * View to clear completed tasks + * + * @param String button_html, the html for view + * @returns Class + */ +Todos.views.ClearButton = Ember.View.extend({ + entriesBinding: 'controller.namespace.entriesController', + templateName: 'clear_button', + classNameBindings: 'buttonClass', + buttonClass: function () { + if ( !this.getPath( 'entries.completed' ) ) + return 'hidden'; + }.property( 'entries.completed' ) +}); diff --git a/js/app/views/filters.js b/js/app/views/filters.js new file mode 100644 index 0000000..526c307 --- /dev/null +++ b/js/app/views/filters.js @@ -0,0 +1,21 @@ +goog.provide('Todos.views.Filters'); + +/** + * View to render filter links + * + * @param String filters_html, filter links html view + * @returns Class + */ +Todos.views.Filters = Ember.View.extend({ + templateName: 'filters', + filterBinding: 'controller.namespace.entriesController.filterBy', + isAll: function() { + return Ember.empty( this.get('filter') ); + }.property( 'filter' ), + isActive: function() { + return this.get('filter') === 'active'; + }.property('filter'), + isCompleted: function() { + return this.get('filter') === 'completed'; + }.property('filter') +}); diff --git a/js/app/views/items.js b/js/app/views/items.js new file mode 100644 index 0000000..a068884 --- /dev/null +++ b/js/app/views/items.js @@ -0,0 +1,45 @@ +goog.provide('Todos.views.Items'); + +/** + * View to render todos items + * + * @param String items_html, the html view for the `Todos` items + * @returns Class + */ +Todos.views.Items = Ember.CollectionView.extend({ + contentBinding: 'controller.entries', + tagName: 'ul', + elementId: 'todo-list', + itemViewClass: Ember.View.extend({ + templateName: 'items', + classNames: [ 'view' ], + classNameBindings: ['content.completed', 'content.editing'], + doubleClick: function() { + this.get( 'content' ).set( 'editing', true ); + }, + removeItem: function() { + this.getPath( 'controller.content' ).removeObject( this.get( 'content' ) ); + }, + ItemEditorView: Ember.TextField.extend({ + valueBinding: 'content.title', + classNames: [ 'edit' ], + change: function() { + if ( Ember.empty( this.getPath( 'content.title' ) ) ) { + this.getPath( 'controller.content' ).removeObject( this.get( 'content' ) ); + } + }, + whenDone: function() { + this.get( 'content' ).set( 'editing', false ); + }, + focusOut: function() { + this.whenDone(); + }, + didInsertElement: function() { + this.$().focus(); + }, + insertNewline: function() { + this.whenDone(); + } + }) + }) +}); diff --git a/js/app/views/stats.js b/js/app/views/stats.js new file mode 100644 index 0000000..888232d --- /dev/null +++ b/js/app/views/stats.js @@ -0,0 +1,17 @@ +goog.provide('Todos.views.Stats'); + +/** + * View to render todos stats + * + * @param String stats_html, stats indicator view + * @returns Class + */ +Todos.views.Stats = Ember.View.extend({ + entriesBinding: 'controller.namespace.entriesController', + elementId: 'todo-count', + tagName: 'span', + templateName: 'stats', + oneLeft: function() { + return this.getPath( 'entries.remaining' ) === 1; + }.property( 'entries.remaining' ) +}); diff --git a/js/lib/deppy/deppy.js b/js/lib/deppy/deppy.js new file mode 100644 index 0000000..75173d4 --- /dev/null +++ b/js/lib/deppy/deppy.js @@ -0,0 +1,15101 @@ +/** + * This file is prepended on the final built in order to perform + * hacks, stubs and mocks for the closure library and its flow. + * + * Look in grunt config. + */ + +// do not allow closure to ask for deps.js +CLOSURE_NO_DEPS = true; +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Bootstrap for the Google JS Library (Closure). + * + * In uncompiled mode base.js will write out Closure's deps file, unless the + * global CLOSURE_NO_DEPS is set to true. This allows projects to + * include their own deps file(s) from different locations. + * + * + * @provideGoog + */ + + +/** + * @define {boolean} Overridden to true by the compiler when --closure_pass + * or --mark_as_compiled is specified. + */ +var COMPILED = false; + + +/** + * Base namespace for the Closure library. Checks to see goog is + * already defined in the current scope before assigning to prevent + * clobbering if base.js is loaded more than once. + * + * @const + */ +var goog = {}; + + +/** + * Reference to the global context. In most cases this will be 'window'. + */ +goog.global = this; + + +/** + * @define {boolean} DEBUG is provided as a convenience so that debugging code + * that should not be included in a production js_binary can be easily stripped + * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most + * toString() methods should be declared inside an "if (goog.DEBUG)" conditional + * because they are generally used for debugging purposes and it is difficult + * for the JSCompiler to statically determine whether they are used. + */ +goog.DEBUG = true; + + +/** + * @define {string} LOCALE defines the locale being used for compilation. It is + * used to select locale specific data to be compiled in js binary. BUILD rule + * can specify this value by "--define goog.LOCALE=" as JSCompiler + * option. + * + * Take into account that the locale code format is important. You should use + * the canonical Unicode format with hyphen as a delimiter. Language must be + * lowercase, Language Script - Capitalized, Region - UPPERCASE. + * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN. + * + * See more info about locale codes here: + * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers + * + * For language codes you should use values defined by ISO 693-1. See it here + * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from + * this rule: the Hebrew language. For legacy reasons the old code (iw) should + * be used instead of the new code (he), see http://wiki/Main/IIISynonyms. + */ +goog.LOCALE = 'en'; // default to en + + +/** + * @define {boolean} Whether this code is running on trusted sites. + * + * On untrusted sites, several native functions can be defined or overridden by + * external libraries like Prototype, Datejs, and JQuery and setting this flag + * to false forces closure to use its own implementations when possible. + * + * If your javascript can be loaded by a third party site and you are wary about + * relying on non-standard implementations, specify + * "--define goog.TRUSTED_SITE=false" to the JSCompiler. + */ +goog.TRUSTED_SITE = true; + + +/** + * Creates object stubs for a namespace. The presence of one or more + * goog.provide() calls indicate that the file defines the given + * objects/namespaces. Build tools also scan for provide/require statements + * to discern dependencies, build dependency files (see deps.js), etc. + * @see goog.require + * @param {string} name Namespace provided by this file in the form + * "goog.package.part". + */ +goog.provide = function(name) { + if (!COMPILED) { + // Ensure that the same namespace isn't provided twice. This is intended + // to teach new developers that 'goog.provide' is effectively a variable + // declaration. And when JSCompiler transforms goog.provide into a real + // variable declaration, the compiled JS should work the same as the raw + // JS--even when the raw JS uses goog.provide incorrectly. + if (goog.isProvided_(name)) { + throw Error('Namespace "' + name + '" already declared.'); + } + delete goog.implicitNamespaces_[name]; + + var namespace = name; + while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) { + if (goog.getObjectByName(namespace)) { + break; + } + goog.implicitNamespaces_[namespace] = true; + } + } + + goog.exportPath_(name); +}; + + +/** + * Marks that the current file should only be used for testing, and never for + * live code in production. + * @param {string=} opt_message Optional message to add to the error that's + * raised when used in production code. + */ +goog.setTestOnly = function(opt_message) { + if (COMPILED && !goog.DEBUG) { + opt_message = opt_message || ''; + throw Error('Importing test-only code into non-debug environment' + + opt_message ? ': ' + opt_message : '.'); + } +}; + + +if (!COMPILED) { + + /** + * Check if the given name has been goog.provided. This will return false for + * names that are available only as implicit namespaces. + * @param {string} name name of the object to look for. + * @return {boolean} Whether the name has been provided. + * @private + */ + goog.isProvided_ = function(name) { + return !goog.implicitNamespaces_[name] && !!goog.getObjectByName(name); + }; + + /** + * Namespaces implicitly defined by goog.provide. For example, + * goog.provide('goog.events.Event') implicitly declares + * that 'goog' and 'goog.events' must be namespaces. + * + * @type {Object} + * @private + */ + goog.implicitNamespaces_ = {}; +} + + +/** + * Builds an object structure for the provided namespace path, + * ensuring that names that already exist are not overwritten. For + * example: + * "a.b.c" -> a = {};a.b={};a.b.c={}; + * Used by goog.provide and goog.exportSymbol. + * @param {string} name name of the object that this file defines. + * @param {*=} opt_object the object to expose at the end of the path. + * @param {Object=} opt_objectToExportTo The object to add the path to; default + * is |goog.global|. + * @private + */ +goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) { + var parts = name.split('.'); + var cur = opt_objectToExportTo || goog.global; + + // Internet Explorer exhibits strange behavior when throwing errors from + // methods externed in this manner. See the testExportSymbolExceptions in + // base_test.html for an example. + if (!(parts[0] in cur) && cur.execScript) { + cur.execScript('var ' + parts[0]); + } + + // Certain browsers cannot parse code in the form for((a in b); c;); + // This pattern is produced by the JSCompiler when it collapses the + // statement above into the conditional loop below. To prevent this from + // happening, use a for-loop and reserve the init logic as below. + + // Parentheses added to eliminate strict JS warning in Firefox. + for (var part; parts.length && (part = parts.shift());) { + if (!parts.length && goog.isDef(opt_object)) { + // last part and we have an object; use it + cur[part] = opt_object; + } else if (cur[part]) { + cur = cur[part]; + } else { + cur = cur[part] = {}; + } + } +}; + + +/** + * Returns an object based on its fully qualified external name. If you are + * using a compilation pass that renames property names beware that using this + * function will not find renamed properties. + * + * @param {string} name The fully qualified name. + * @param {Object=} opt_obj The object within which to look; default is + * |goog.global|. + * @return {?} The value (object or primitive) or, if not found, null. + */ +goog.getObjectByName = function(name, opt_obj) { + var parts = name.split('.'); + var cur = opt_obj || goog.global; + for (var part; part = parts.shift(); ) { + if (goog.isDefAndNotNull(cur[part])) { + cur = cur[part]; + } else { + return null; + } + } + return cur; +}; + + +/** + * Globalizes a whole namespace, such as goog or goog.lang. + * + * @param {Object} obj The namespace to globalize. + * @param {Object=} opt_global The object to add the properties to. + * @deprecated Properties may be explicitly exported to the global scope, but + * this should no longer be done in bulk. + */ +goog.globalize = function(obj, opt_global) { + var global = opt_global || goog.global; + for (var x in obj) { + global[x] = obj[x]; + } +}; + + +/** + * Adds a dependency from a file to the files it requires. + * @param {string} relPath The path to the js file. + * @param {Array} provides An array of strings with the names of the objects + * this file provides. + * @param {Array} requires An array of strings with the names of the objects + * this file requires. + */ +goog.addDependency = function(relPath, provides, requires) { + if (!COMPILED) { + var provide, require; + var path = relPath.replace(/\\/g, '/'); + var deps = goog.dependencies_; + for (var i = 0; provide = provides[i]; i++) { + deps.nameToPath[provide] = path; + if (!(path in deps.pathToNames)) { + deps.pathToNames[path] = {}; + } + deps.pathToNames[path][provide] = true; + } + for (var j = 0; require = requires[j]; j++) { + if (!(path in deps.requires)) { + deps.requires[path] = {}; + } + deps.requires[path][require] = true; + } + } +}; + + + + +// NOTE(nnaze): The debug DOM loader was included in base.js as an orignal +// way to do "debug-mode" development. The dependency system can sometimes +// be confusing, as can the debug DOM loader's asyncronous nature. +// +// With the DOM loader, a call to goog.require() is not blocking -- the +// script will not load until some point after the current script. If a +// namespace is needed at runtime, it needs to be defined in a previous +// script, or loaded via require() with its registered dependencies. +// User-defined namespaces may need their own deps file. See http://go/js_deps, +// http://go/genjsdeps, or, externally, DepsWriter. +// http://code.google.com/closure/library/docs/depswriter.html +// +// Because of legacy clients, the DOM loader can't be easily removed from +// base.js. Work is being done to make it disableable or replaceable for +// different environments (DOM-less JavaScript interpreters like Rhino or V8, +// for example). See bootstrap/ for more information. + + +/** + * @define {boolean} Whether to enable the debug loader. + * + * If enabled, a call to goog.require() will attempt to load the namespace by + * appending a script tag to the DOM (if the namespace has been registered). + * + * If disabled, goog.require() will simply assert that the namespace has been + * provided (and depend on the fact that some outside tool correctly ordered + * the script). + */ +goog.ENABLE_DEBUG_LOADER = true; + + +/** + * Implements a system for the dynamic resolution of dependencies + * that works in parallel with the BUILD system. Note that all calls + * to goog.require will be stripped by the JSCompiler when the + * --closure_pass option is used. + * @see goog.provide + * @param {string} name Namespace to include (as was given in goog.provide()) + * in the form "goog.package.part". + */ +goog.require = function(name) { + + // if the object already exists we do not need do do anything + // TODO(arv): If we start to support require based on file name this has + // to change + // TODO(arv): If we allow goog.foo.* this has to change + // TODO(arv): If we implement dynamic load after page load we should probably + // not remove this code for the compiled output + if (!COMPILED) { + if (goog.isProvided_(name)) { + return; + } + + if (goog.ENABLE_DEBUG_LOADER) { + var path = goog.getPathFromDeps_(name); + if (path) { + goog.included_[path] = true; + goog.writeScripts_(); + return; + } + } + + var errorMessage = 'goog.require could not find: ' + name; + if (goog.global.console) { + goog.global.console['error'](errorMessage); + } + + + throw Error(errorMessage); + + } +}; + + +/** + * Path for included scripts + * @type {string} + */ +goog.basePath = ''; + + +/** + * A hook for overriding the base path. + * @type {string|undefined} + */ +goog.global.CLOSURE_BASE_PATH; + + +/** + * Whether to write out Closure's deps file. By default, + * the deps are written. + * @type {boolean|undefined} + */ +goog.global.CLOSURE_NO_DEPS; + + +/** + * A function to import a single script. This is meant to be overridden when + * Closure is being run in non-HTML contexts, such as web workers. It's defined + * in the global scope so that it can be set before base.js is loaded, which + * allows deps.js to be imported properly. + * + * The function is passed the script source, which is a relative URI. It should + * return true if the script was imported, false otherwise. + */ +goog.global.CLOSURE_IMPORT_SCRIPT; + + +/** + * Null function used for default values of callbacks, etc. + * @return {void} Nothing. + */ +goog.nullFunction = function() {}; + + +/** + * The identity function. Returns its first argument. + * + * @param {*=} opt_returnValue The single value that will be returned. + * @param {...*} var_args Optional trailing arguments. These are ignored. + * @return {?} The first argument. We can't know the type -- just pass it along + * without type. + * @deprecated Use goog.functions.identity instead. + */ +goog.identityFunction = function(opt_returnValue, var_args) { + return opt_returnValue; +}; + + +/** + * When defining a class Foo with an abstract method bar(), you can do: + * + * Foo.prototype.bar = goog.abstractMethod + * + * Now if a subclass of Foo fails to override bar(), an error + * will be thrown when bar() is invoked. + * + * Note: This does not take the name of the function to override as + * an argument because that would make it more difficult to obfuscate + * our JavaScript code. + * + * @type {!Function} + * @throws {Error} when invoked to indicate the method should be + * overridden. + */ +goog.abstractMethod = function() { + throw Error('unimplemented abstract method'); +}; + + +/** + * Adds a {@code getInstance} static method that always return the same instance + * object. + * @param {!Function} ctor The constructor for the class to add the static + * method to. + */ +goog.addSingletonGetter = function(ctor) { + ctor.getInstance = function() { + if (ctor.instance_) { + return ctor.instance_; + } + if (goog.DEBUG) { + // NOTE: JSCompiler can't optimize away Array#push. + goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor; + } + return ctor.instance_ = new ctor; + }; +}; + + +/** + * All singleton classes that have been instantiated, for testing. Don't read + * it directly, use the {@code goog.testing.singleton} module. The compiler + * removes this variable if unused. + * @type {!Array.} + * @private + */ +goog.instantiatedSingletons_ = []; + + +if (!COMPILED && goog.ENABLE_DEBUG_LOADER) { + /** + * Object used to keep track of urls that have already been added. This + * record allows the prevention of circular dependencies. + * @type {Object} + * @private + */ + goog.included_ = {}; + + + /** + * This object is used to keep track of dependencies and other data that is + * used for loading scripts + * @private + * @type {Object} + */ + goog.dependencies_ = { + pathToNames: {}, // 1 to many + nameToPath: {}, // 1 to 1 + requires: {}, // 1 to many + // used when resolving dependencies to prevent us from + // visiting the file twice + visited: {}, + written: {} // used to keep track of script files we have written + }; + + + /** + * Tries to detect whether is in the context of an HTML document. + * @return {boolean} True if it looks like HTML document. + * @private + */ + goog.inHtmlDocument_ = function() { + var doc = goog.global.document; + return typeof doc != 'undefined' && + 'write' in doc; // XULDocument misses write. + }; + + + /** + * Tries to detect the base path of the base.js script that bootstraps Closure + * @private + */ + goog.findBasePath_ = function() { + if (goog.global.CLOSURE_BASE_PATH) { + goog.basePath = goog.global.CLOSURE_BASE_PATH; + return; + } else if (!goog.inHtmlDocument_()) { + return; + } + var doc = goog.global.document; + var scripts = doc.getElementsByTagName('script'); + // Search backwards since the current script is in almost all cases the one + // that has base.js. + for (var i = scripts.length - 1; i >= 0; --i) { + var src = scripts[i].src; + var qmark = src.lastIndexOf('?'); + var l = qmark == -1 ? src.length : qmark; + if (src.substr(l - 7, 7) == 'base.js') { + goog.basePath = src.substr(0, l - 7); + return; + } + } + }; + + + /** + * Imports a script if, and only if, that script hasn't already been imported. + * (Must be called at execution time) + * @param {string} src Script source. + * @private + */ + goog.importScript_ = function(src) { + var importScript = goog.global.CLOSURE_IMPORT_SCRIPT || + goog.writeScriptTag_; + if (!goog.dependencies_.written[src] && importScript(src)) { + goog.dependencies_.written[src] = true; + } + }; + + + /** + * The default implementation of the import function. Writes a script tag to + * import the script. + * + * @param {string} src The script source. + * @return {boolean} True if the script was imported, false otherwise. + * @private + */ + goog.writeScriptTag_ = function(src) { + if (goog.inHtmlDocument_()) { + var doc = goog.global.document; + + // If the user tries to require a new symbol after document load, + // something has gone terribly wrong. Doing a document.write would + // wipe out the page. + if (doc.readyState == 'complete') { + // Certain test frameworks load base.js multiple times, which tries + // to write deps.js each time. If that happens, just fail silently. + // These frameworks wipe the page between each load of base.js, so this + // is OK. + var isDeps = /\bdeps.js$/.test(src); + if (isDeps) { + return false; + } else { + throw Error('Cannot write "' + src + '" after document load'); + } + } + + doc.write( + ''); +}; + +/** + * Determine the namespace of the entry point file and fetch it. + * + */ +Deppy.ModuleLoader.prototype.startApp = function() { + var configEntryPoint = Deppy.Config.getInstance().getDepFile(); + var elementEntryPoint = goog.dom.dataset.get(this._ownScriptTag, + Deppy.ModuleLoader.ENTRY_POINT_DATA_KEY); + + this._entryPoint = configEntryPoint || elementEntryPoint + || Deppy.ModuleLoader.ENTRY_POINT_DEFAULT + + // hack goog so it won't get caught by scripts + var g = goog; + g.require(this._entryPoint); + +}; + +/** + * On entry point load finish. + * @private + */ +Deppy.ModuleLoader.prototype._onEntryLoad = function() { + // nothing to do, things are rolling... + console.log('loading all finished'); +}; + + +/** + * On entry point error. + * @private + */ +Deppy.ModuleLoader.prototype._onEntryErr = function() { + throw new Error('Failed to load entry point. Check path: ' + this._entryPoint); +}; +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A utility to load JavaScript files via DOM script tags. + * Refactored from goog.net.Jsonp. Works cross-domain. + * + */ + +goog.provide('goog.net.jsloader'); +goog.provide('goog.net.jsloader.Error'); + +goog.require('goog.array'); +goog.require('goog.async.Deferred'); +goog.require('goog.debug.Error'); +goog.require('goog.dom'); +goog.require('goog.userAgent'); + + +/** + * The name of the property of goog.global under which the JavaScript + * verification object is stored by the loaded script. + * @type {string} + * @private + */ +goog.net.jsloader.GLOBAL_VERIFY_OBJS_ = 'closure_verification'; + + +/** + * The default length of time, in milliseconds, we are prepared to wait for a + * load request to complete. + * @type {number} + */ +goog.net.jsloader.DEFAULT_TIMEOUT = 5000; + + +/** + * Optional parameters for goog.net.jsloader.send. + * timeout: The length of time, in milliseconds, we are prepared to wait + * for a load request to complete. Default it 5 seconds. + * document: The HTML document under which to load the JavaScript. Default is + * the current document. + * cleanupWhenDone: If true clean up the script tag after script completes to + * load. This is important if you just want to read data from the JavaScript + * and then throw it away. Default is false. + * + * @typedef {{ + * timeout: (number|undefined), + * document: (HTMLDocument|undefined), + * cleanupWhenDone: (boolean|undefined) + * }} + */ +goog.net.jsloader.Options; + + +/** + * Scripts (URIs) waiting to be loaded. + * @type {Array.} + * @private + */ +goog.net.jsloader.scriptsToLoad_ = []; + + +/** + * Loads and evaluates the JavaScript files at the specified URIs, guaranteeing + * the order of script loads. + * + * Because we have to load the scripts in serial (load script 1, exec script 1, + * load script 2, exec script 2, and so on), this will be slower than doing + * the network fetches in parallel. + * + * If you need to load a large number of scripts but dependency order doesn't + * matter, you should just call goog.net.jsloader.load N times. + * + * If you need to load a large number of scripts on the same domain, + * you may want to use goog.module.ModuleLoader. + * + * @param {Array.} uris The URIs to load. + * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See + * goog.net.jsloader.options documentation for details. + */ +goog.net.jsloader.loadMany = function(uris, opt_options) { + // Loading the scripts in serial introduces asynchronosity into the flow. + // Therefore, there are race conditions where client A can kick off the load + // sequence for client B, even though client A's scripts haven't all been + // loaded yet. + // + // To work around this issue, all module loads share a queue. + if (!uris.length) { + return; + } + + var isAnotherModuleLoading = goog.net.jsloader.scriptsToLoad_.length; + goog.array.extend(goog.net.jsloader.scriptsToLoad_, uris); + if (isAnotherModuleLoading) { + // jsloader is still loading some other scripts. + // In order to prevent the race condition noted above, we just add + // these URIs to the end of the scripts' queue and return. + return; + } + + uris = goog.net.jsloader.scriptsToLoad_; + var popAndLoadNextScript = function() { + var uri = uris.shift(); + var deferred = goog.net.jsloader.load(uri, opt_options); + if (uris.length) { + deferred.addBoth(popAndLoadNextScript); + } + }; + popAndLoadNextScript(); +}; + + +/** + * Loads and evaluates a JavaScript file. + * When the script loads, a user callback is called. + * It is the client's responsibility to verify that the script ran successfully. + * + * @param {string} uri The URI of the JavaScript. + * @param {goog.net.jsloader.Options=} opt_options Optional parameters. See + * goog.net.jsloader.Options documentation for details. + * @return {!goog.async.Deferred} The deferred result, that may be used to add + * callbacks and/or cancel the transmission. + * The error callback will be called with a single goog.net.jsloader.Error + * parameter. + */ +goog.net.jsloader.load = function(uri, opt_options) { + var options = opt_options || {}; + var doc = options.document || document; + + var script = goog.dom.createElement(goog.dom.TagName.SCRIPT); + var request = {script_: script, timeout_: undefined}; + var deferred = new goog.async.Deferred(goog.net.jsloader.cancel_, request); + + // Set a timeout. + var timeout = null; + var timeoutDuration = goog.isDefAndNotNull(options.timeout) ? + options.timeout : goog.net.jsloader.DEFAULT_TIMEOUT; + if (timeoutDuration > 0) { + timeout = window.setTimeout(function() { + goog.net.jsloader.cleanup_(script, true); + deferred.errback(new goog.net.jsloader.Error( + goog.net.jsloader.ErrorCode.TIMEOUT, + 'Timeout reached for loading script ' + uri)); + }, timeoutDuration); + request.timeout_ = timeout; + } + + // Hang the user callback to be called when the script completes to load. + // NOTE(user): This callback will be called in IE even upon error. In any + // case it is the client's responsibility to verify that the script ran + // successfully. + script.onload = script.onreadystatechange = function() { + if (!script.readyState || script.readyState == 'loaded' || + script.readyState == 'complete') { + var removeScriptNode = options.cleanupWhenDone || false; + goog.net.jsloader.cleanup_(script, removeScriptNode, timeout); + deferred.callback(null); + } + }; + + // Add an error callback. + // NOTE(user): Not supported in IE. + script.onerror = function() { + goog.net.jsloader.cleanup_(script, true, timeout); + deferred.errback(new goog.net.jsloader.Error( + goog.net.jsloader.ErrorCode.LOAD_ERROR, + 'Error while loading script ' + uri)); + }; + + // Add the script element to the document. + goog.dom.setProperties(script, { + 'type': 'text/javascript', + 'charset': 'UTF-8', + // NOTE(user): Safari never loads the script if we don't set + // the src attribute before appending. + 'src': uri + }); + var scriptParent = goog.net.jsloader.getScriptParentElement_(doc); + scriptParent.appendChild(script); + + return deferred; +}; + + +/** + * Loads a JavaScript file and verifies it was evaluated successfully, using a + * verification object. + * The verification object is set by the loaded JavaScript at the end of the + * script. + * We verify this object was set and return its value in the success callback. + * If the object is not defined we trigger an error callback. + * + * @param {string} uri The URI of the JavaScript. + * @param {string} verificationObjName The name of the verification object that + * the loaded script should set. + * @param {goog.net.jsloader.Options} options Optional parameters. See + * goog.net.jsloader.Options documentation for details. + * @return {!goog.async.Deferred} The deferred result, that may be used to add + * callbacks and/or cancel the transmission. + * The success callback will be called with a single parameter containing + * the value of the verification object. + * The error callback will be called with a single goog.net.jsloader.Error + * parameter. + */ +goog.net.jsloader.loadAndVerify = function(uri, verificationObjName, options) { + // Define the global objects variable. + if (!goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]) { + goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_] = {}; + } + var verifyObjs = goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]; + + // Verify that the expected object does not exist yet. + if (goog.isDef(verifyObjs[verificationObjName])) { + // TODO(user): Error or reset variable? + return goog.async.Deferred.fail(new goog.net.jsloader.Error( + goog.net.jsloader.ErrorCode.VERIFY_OBJECT_ALREADY_EXISTS, + 'Verification object ' + verificationObjName + ' already defined.')); + } + + // Send request to load the JavaScript. + var sendDeferred = goog.net.jsloader.load(uri, options); + + // Create a deferred object wrapping the send result. + var deferred = new goog.async.Deferred(sendDeferred.cancel); + + // Call user back with object that was set by the script. + sendDeferred.addCallback(function() { + var result = verifyObjs[verificationObjName]; + if (goog.isDef(result)) { + deferred.callback(result); + delete verifyObjs[verificationObjName]; + } else { + // Error: script was not loaded properly. + deferred.errback(new goog.net.jsloader.Error( + goog.net.jsloader.ErrorCode.VERIFY_ERROR, + 'Script ' + uri + ' loaded, but verification object ' + + verificationObjName + ' was not defined.')); + } + }); + + // Pass error to new deferred object. + sendDeferred.addErrback(function(error) { + if (goog.isDef(verifyObjs[verificationObjName])) { + delete verifyObjs[verificationObjName]; + } + deferred.errback(error); + }); + + return deferred; +}; + + +/** + * Gets the DOM element under which we should add new script elements. + * How? Take the first head element, and if not found take doc.documentElement, + * which always exists. + * + * @param {!HTMLDocument} doc The relevant document. + * @return {!Element} The script parent element. + * @private + */ +goog.net.jsloader.getScriptParentElement_ = function(doc) { + var headElements = doc.getElementsByTagName(goog.dom.TagName.HEAD); + if (!headElements || goog.array.isEmpty(headElements)) { + return doc.documentElement; + } else { + return headElements[0]; + } +}; + + +/** + * Cancels a given request. + * @this {{script_: Element, timeout_: number}} The request context. + * @private + */ +goog.net.jsloader.cancel_ = function() { + var request = this; + if (request && request.script_) { + var scriptNode = request.script_; + if (scriptNode && scriptNode.tagName == 'SCRIPT') { + goog.net.jsloader.cleanup_(scriptNode, true, request.timeout_); + } + } +}; + + +/** + * Removes the script node and the timeout. + * + * @param {Node} scriptNode The node to be cleaned up. + * @param {boolean} removeScriptNode If true completely remove the script node. + * @param {?number=} opt_timeout The timeout handler to cleanup. + * @private + */ +goog.net.jsloader.cleanup_ = function(scriptNode, removeScriptNode, + opt_timeout) { + if (goog.isDefAndNotNull(opt_timeout)) { + goog.global.clearTimeout(opt_timeout); + } + + scriptNode.onload = goog.nullFunction; + scriptNode.onerror = goog.nullFunction; + scriptNode.onreadystatechange = goog.nullFunction; + + // Do this after a delay (removing the script node of a running script can + // confuse older IEs). + if (removeScriptNode) { + window.setTimeout(function() { + goog.dom.removeNode(scriptNode); + }, 0); + } +}; + + +/** + * Possible error codes for jsloader. + * @enum {number} + */ +goog.net.jsloader.ErrorCode = { + LOAD_ERROR: 0, + TIMEOUT: 1, + VERIFY_ERROR: 2, + VERIFY_OBJECT_ALREADY_EXISTS: 3 +}; + + + +/** + * A jsloader error. + * + * @param {goog.net.jsloader.ErrorCode} code The error code. + * @param {string=} opt_message Additional message. + * @constructor + * @extends {goog.debug.Error} + */ +goog.net.jsloader.Error = function(code, opt_message) { + var msg = 'Jsloader error (code #' + code + ')'; + if (opt_message) { + msg += ': ' + opt_message; + } + goog.base(this, msg); + + /** + * The code for this error. + * + * @type {goog.net.jsloader.ErrorCode} + */ + this.code = code; +}; +goog.inherits(goog.net.jsloader.Error, goog.debug.Error); +// Copyright 2011 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Definition of the disposable interface. A disposable object + * has a dispose method to to clean up references and resources. + * @author nnaze@google.com (Nathan Naze) + */ + + +goog.provide('goog.disposable.IDisposable'); + + + +/** + * Interface for a disposable object. If a instance requires cleanup + * (references COM objects, DOM notes, or other disposable objects), it should + * implement this interface (it may subclass goog.Disposable). + * @interface + */ +goog.disposable.IDisposable = function() {}; + + +/** + * Disposes of the object and its resources. + * @return {void} Nothing. + */ +goog.disposable.IDisposable.prototype.dispose; + + +/** + * @return {boolean} Whether the object has been disposed of. + */ +goog.disposable.IDisposable.prototype.isDisposed; +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Implements the disposable interface. The dispose method is used + * to clean up references and resources. + * @author arv@google.com (Erik Arvidsson) + */ + + +goog.provide('goog.Disposable'); +goog.provide('goog.dispose'); + +goog.require('goog.disposable.IDisposable'); + + + +/** + * Class that provides the basic implementation for disposable objects. If your + * class holds one or more references to COM objects, DOM nodes, or other + * disposable objects, it should extend this class or implement the disposable + * interface (defined in goog.disposable.IDisposable). + * @constructor + * @implements {goog.disposable.IDisposable} + */ +goog.Disposable = function() { + if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) { + this.creationStack = new Error().stack; + goog.Disposable.instances_[goog.getUid(this)] = this; + } +}; + + +/** + * @enum {number} Different monitoring modes for Disposable. + */ +goog.Disposable.MonitoringMode = { + /** + * No monitoring. + */ + OFF: 0, + /** + * Creating and disposing the goog.Disposable instances is monitored. All + * disposable objects need to call the {@code goog.Disposable} base + * constructor. The PERMANENT mode must bet switched on before creating any + * goog.Disposable instances. + */ + PERMANENT: 1, + /** + * INTERACTIVE mode can be switched on and off on the fly without producing + * errors. It also doesn't warn if the disposable objects don't call the + * {@code goog.Disposable} base constructor. + */ + INTERACTIVE: 2 +}; + + +/** + * @define {number} The monitoring mode of the goog.Disposable + * instances. Default is OFF. Switching on the monitoring is only + * recommended for debugging because it has a significant impact on + * performance and memory usage. If switched off, the monitoring code + * compiles down to 0 bytes. + */ +goog.Disposable.MONITORING_MODE = 0; + + +/** + * Maps the unique ID of every undisposed {@code goog.Disposable} object to + * the object itself. + * @type {!Object.} + * @private + */ +goog.Disposable.instances_ = {}; + + +/** + * @return {!Array.} All {@code goog.Disposable} objects that + * haven't been disposed of. + */ +goog.Disposable.getUndisposedObjects = function() { + var ret = []; + for (var id in goog.Disposable.instances_) { + if (goog.Disposable.instances_.hasOwnProperty(id)) { + ret.push(goog.Disposable.instances_[Number(id)]); + } + } + return ret; +}; + + +/** + * Clears the registry of undisposed objects but doesn't dispose of them. + */ +goog.Disposable.clearUndisposedObjects = function() { + goog.Disposable.instances_ = {}; +}; + + +/** + * Whether the object has been disposed of. + * @type {boolean} + * @private + */ +goog.Disposable.prototype.disposed_ = false; + + +/** + * Callbacks to invoke when this object is disposed. + * @type {Array.} + * @private + */ +goog.Disposable.prototype.onDisposeCallbacks_; + + +/** + * If monitoring the goog.Disposable instances is enabled, stores the creation + * stack trace of the Disposable instance. + * @type {string} + */ +goog.Disposable.prototype.creationStack; + + +/** + * @return {boolean} Whether the object has been disposed of. + * @override + */ +goog.Disposable.prototype.isDisposed = function() { + return this.disposed_; +}; + + +/** + * @return {boolean} Whether the object has been disposed of. + * @deprecated Use {@link #isDisposed} instead. + */ +goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed; + + +/** + * Disposes of the object. If the object hasn't already been disposed of, calls + * {@link #disposeInternal}. Classes that extend {@code goog.Disposable} should + * override {@link #disposeInternal} in order to delete references to COM + * objects, DOM nodes, and other disposable objects. Reentrant. + * + * @return {void} Nothing. + * @override + */ +goog.Disposable.prototype.dispose = function() { + if (!this.disposed_) { + // Set disposed_ to true first, in case during the chain of disposal this + // gets disposed recursively. + this.disposed_ = true; + this.disposeInternal(); + if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) { + var uid = goog.getUid(this); + if (goog.Disposable.MONITORING_MODE == + goog.Disposable.MonitoringMode.PERMANENT && + !goog.Disposable.instances_.hasOwnProperty(uid)) { + throw Error(this + ' did not call the goog.Disposable base ' + + 'constructor or was disposed of after a clearUndisposedObjects ' + + 'call'); + } + delete goog.Disposable.instances_[uid]; + } + } +}; + + +/** + * Associates a disposable object with this object so that they will be disposed + * together. + * @param {goog.disposable.IDisposable} disposable that will be disposed when + * this object is disposed. + */ +goog.Disposable.prototype.registerDisposable = function(disposable) { + this.addOnDisposeCallback(goog.partial(goog.dispose, disposable)); +}; + + +/** + * Invokes a callback function when this object is disposed. Callbacks are + * invoked in the order in which they were added. + * @param {function(this:T):?} callback The callback function. + * @param {T=} opt_scope An optional scope to call the callback in. + * @template T + */ +goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) { + if (!this.onDisposeCallbacks_) { + this.onDisposeCallbacks_ = []; + } + this.onDisposeCallbacks_.push(goog.bind(callback, opt_scope)); +}; + + +/** + * Deletes or nulls out any references to COM objects, DOM nodes, or other + * disposable objects. Classes that extend {@code goog.Disposable} should + * override this method. + * Not reentrant. To avoid calling it twice, it must only be called from the + * subclass' {@code disposeInternal} method. Everywhere else the public + * {@code dispose} method must be used. + * For example: + *
+ *   mypackage.MyClass = function() {
+ *     goog.base(this);
+ *     // Constructor logic specific to MyClass.
+ *     ...
+ *   };
+ *   goog.inherits(mypackage.MyClass, goog.Disposable);
+ *
+ *   mypackage.MyClass.prototype.disposeInternal = function() {
+ *     // Dispose logic specific to MyClass.
+ *     ...
+ *     // Call superclass's disposeInternal at the end of the subclass's, like
+ *     // in C++, to avoid hard-to-catch issues.
+ *     goog.base(this, 'disposeInternal');
+ *   };
+ * 
+ * @protected + */ +goog.Disposable.prototype.disposeInternal = function() { + if (this.onDisposeCallbacks_) { + while (this.onDisposeCallbacks_.length) { + this.onDisposeCallbacks_.shift()(); + } + } +}; + + +/** + * Returns True if we can verify the object is disposed. + * Calls {@code isDisposed} on the argument if it supports it. If obj + * is not an object with an isDisposed() method, return false. + * @param {*} obj The object to investigate. + * @return {boolean} True if we can verify the object is disposed. + */ +goog.Disposable.isDisposed = function(obj) { + if (obj && typeof obj.isDisposed == 'function') { + return obj.isDisposed(); + } + return false; +}; + + +/** + * Calls {@code dispose} on the argument if it supports it. If obj is not an + * object with a dispose() method, this is a no-op. + * @param {*} obj The object to dispose of. + */ +goog.dispose = function(obj) { + if (obj && typeof obj.dispose == 'function') { + obj.dispose(); + } +}; + + +/** + * Calls {@code dispose} on each member of the list that supports it. (If the + * member is an ArrayLike, then {@code goog.disposeAll()} will be called + * recursively on each of its members.) If the member is not an object with a + * {@code dispose()} method, then it is ignored. + * @param {...*} var_args The list. + */ +goog.disposeAll = function(var_args) { + for (var i = 0, len = arguments.length; i < len; ++i) { + var disposable = arguments[i]; + if (goog.isArrayLike(disposable)) { + goog.disposeAll.apply(null, disposable); + } else { + goog.dispose(disposable); + } + } +}; +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A base class for event objects. + * + */ + + +goog.provide('goog.events.Event'); +goog.provide('goog.events.EventLike'); + +// goog.events.Event no longer depends on goog.Disposable. Keep requiring +// goog.Disposable here to not break projects which assume this dependency. +goog.require('goog.Disposable'); + + +/** + * A typedef for event like objects that are dispatchable via the + * goog.events.dispatchEvent function. strings are treated as the type for a + * goog.events.Event. Objects are treated as an extension of a new + * goog.events.Event with the type property of the object being used as the type + * of the Event. + * @typedef {string|Object|goog.events.Event} + */ +goog.events.EventLike; + + + +/** + * A base class for event objects, so that they can support preventDefault and + * stopPropagation. + * + * @param {string} type Event Type. + * @param {Object=} opt_target Reference to the object that is the target of + * this event. It has to implement the {@code EventTarget} interface + * declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}. + * @constructor + */ +goog.events.Event = function(type, opt_target) { + /** + * Event type. + * @type {string} + */ + this.type = type; + + /** + * Target of the event. + * @type {Object|undefined} + */ + this.target = opt_target; + + /** + * Object that had the listener attached. + * @type {Object|undefined} + */ + this.currentTarget = this.target; +}; + + +/** + * For backwards compatibility (goog.events.Event used to inherit + * goog.Disposable). + * @deprecated Events don't need to be disposed. + */ +goog.events.Event.prototype.disposeInternal = function() { +}; + + +/** + * For backwards compatibility (goog.events.Event used to inherit + * goog.Disposable). + * @deprecated Events don't need to be disposed. + */ +goog.events.Event.prototype.dispose = function() { +}; + + +/** + * Whether to cancel the event in internal capture/bubble processing for IE. + * @type {boolean} + * @suppress {underscore} Technically public, but referencing this outside + * this package is strongly discouraged. + */ +goog.events.Event.prototype.propagationStopped_ = false; + + +/** + * Whether the default action has been prevented. + * This is a property to match the W3C specification at {@link + * http://www.w3.org/TR/DOM-Level-3-Events/#events-event-type-defaultPrevented}. + * Must be treated as read-only outside the class. + * @type {boolean} + */ +goog.events.Event.prototype.defaultPrevented = false; + + +/** + * Return value for in internal capture/bubble processing for IE. + * @type {boolean} + * @suppress {underscore} Technically public, but referencing this outside + * this package is strongly discouraged. + */ +goog.events.Event.prototype.returnValue_ = true; + + +/** + * Stops event propagation. + */ +goog.events.Event.prototype.stopPropagation = function() { + this.propagationStopped_ = true; +}; + + +/** + * Prevents the default action, for example a link redirecting to a url. + */ +goog.events.Event.prototype.preventDefault = function() { + this.defaultPrevented = true; + this.returnValue_ = false; +}; + + +/** + * Stops the propagation of the event. It is equivalent to + * {@code e.stopPropagation()}, but can be used as the callback argument of + * {@link goog.events.listen} without declaring another function. + * @param {!goog.events.Event} e An event. + */ +goog.events.Event.stopPropagation = function(e) { + e.stopPropagation(); +}; + + +/** + * Prevents the default action. It is equivalent to + * {@code e.preventDefault()}, but can be used as the callback argument of + * {@link goog.events.listen} without declaring another function. + * @param {!goog.events.Event} e An event. + */ +goog.events.Event.preventDefault = function(e) { + e.preventDefault(); +}; +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview An interface for a listenable JavaScript object. + * + * WARNING(chrishenry): DO NOT USE! SUPPORT NOT FULLY IMPLEMENTED. + */ + +goog.provide('goog.events.Listenable'); +goog.provide('goog.events.ListenableKey'); + +goog.require('goog.events.EventLike'); + + + +/** + * A listenable interface. Also see goog.events.EventTarget. + * @interface + */ +goog.events.Listenable = function() {}; + + +/** + * Whether to use the new listenable interface and mechanism in + * goog.events and goog.events.EventTarget. + * + * TODO(user): Remove this once launched and stable. + * + * @type {boolean} + */ +goog.events.Listenable.USE_LISTENABLE_INTERFACE = false; + + +/** + * An expando property to indicate that an object implements + * goog.events.Listenable. + * + * See addImplementation/isImplementedBy. + * + * @type {string} + * @const + * @private + */ +goog.events.Listenable.IMPLEMENTED_BY_PROP_ = '__closure_listenable'; + + +/** + * Marks a given class (constructor) as an implementation of + * Listenable, do that we can query that fact at runtime. The class + * must have already implemented the interface. + * @param {!Function} cls The class constructor. The corresponding + * class must have already implemented the interface. + */ +goog.events.Listenable.addImplementation = function(cls) { + cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP_] = true; +}; + + +/** + * @param {Object} obj The object to check. + * @return {boolean} Whether a given instance implements + * Listenable. The class/superclass of the instance must call + * addImplementation. + */ +goog.events.Listenable.isImplementedBy = function(obj) { + return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP_]); +}; + + +/** + * Adds an event listener. A listener can only be added once to an + * object and if it is added again the key for the listener is + * returned. Note that if the existing listener is a one-off listener + * (registered via listenOnce), it will no longer be a one-off + * listener after a call to listen(). + * + * @param {string} type Event type or array of event types. + * @param {!Function} listener Callback method, or an object + * with a handleEvent function. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {Object=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} Unique key for the listener. + */ +goog.events.Listenable.prototype.listen; + + +/** + * Adds an event listener that is removed automatically after the + * listener fired once. + * + * If an existing listener already exists, listenOnce will do + * nothing. In particular, if the listener was previously registered + * via listen(), listenOnce() will not turn the listener into a + * one-off listener. Similarly, if there is already an existing + * one-off listener, listenOnce does not modify the listeners (it is + * still a once listener). + * + * @param {string} type Event type or array of event types. + * @param {!Function} listener Callback method, or an object + * with a handleEvent function. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {Object=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} Unique key for the listener. + */ +goog.events.Listenable.prototype.listenOnce; + + +/** + * Removes an event listener which was added with listen() or listenOnce(). + * + * Implementation needs to call goog.events.cleanUp. + * + * @param {string} type Event type or array of event types. + * @param {!Function} listener Callback method, or an object + * with a handleEvent function. TODO(user): Consider whether + * we can remove Object. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {Object=} opt_listenerScope Object in whose scope to call + * the listener. + * @return {boolean} Whether any listener was removed. + */ +goog.events.Listenable.prototype.unlisten; + + +/** + * Removes an event listener which was added with listen() by the key + * returned by listen(). + * + * Implementation needs to call goog.events.cleanUp. + * + * @param {goog.events.ListenableKey} key The key returned by + * listen() or listenOnce(). + * @return {boolean} Whether any listener was removed. + */ +goog.events.Listenable.prototype.unlistenByKey; + + +/** + * Dispatches an event (or event like object) and calls all listeners + * listening for events of this type. The type of the event is decided by the + * type property on the event object. + * + * If any of the listeners returns false OR calls preventDefault then this + * function will return false. If one of the capture listeners calls + * stopPropagation, then the bubble listeners won't fire. + * + * @param {goog.events.EventLike} e Event object. + * @return {boolean} If anyone called preventDefault on the event object (or + * if any of the listeners returns false this will also return false. + */ +goog.events.Listenable.prototype.dispatchEvent; + + +/** + * Removes all listeners from this listenable. If type is specified, + * it will only remove listeners of the particular type. otherwise all + * registered listeners will be removed. + * + * Implementation needs to call goog.events.cleanUp for each removed + * listener. + * + * @param {string=} opt_type Type of event to remove, default is to + * remove all types. + * @return {number} Number of listeners removed. + */ +goog.events.Listenable.prototype.removeAllListeners; + + +/** + * Fires all registered listeners in this listenable for the given + * type and capture mode, passing them the given eventObject. This + * does not perform actual capture/bubble. Only implementors of the + * interface should be using this. + * + * @param {string} type The type of the listeners to fire. + * @param {boolean} capture The capture mode of the listeners to fire. + * @param {goog.events.Event} eventObject The event object to fire. + * @return {boolean} Whether all listeners succeeded without + * attempting to prevent default behavior. If any listener returns + * false or called goog.events.Event#preventDefault, this returns + * false. + */ +goog.events.Listenable.prototype.fireListeners; + + +/** + * Gets all listeners in this listenable for the given type and + * capture mode. + * + * @param {string} type The type of the listeners to fire. + * @param {boolean} capture The capture mode of the listeners to fire. + * @return {!Array.} An array of registered + * listeners. + */ +goog.events.Listenable.prototype.getListeners; + + +/** + * Gets the goog.events.ListenableKey for the event or null if no such + * listener is in use. + * + * @param {string} type The name of the event without the 'on' prefix. + * @param {!Function} listener The listener function to get. + * @param {boolean=} capture Whether the listener is a capturing listener. + * @param {Object=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} the found listener or null if not found. + */ +goog.events.Listenable.prototype.getListener; + + + +/** + * Whether there is any active listeners matching the specified + * signature. If either the type or capture parameters are + * unspecified, the function will match on the remaining criteria. + * + * @param {string=} opt_type Event type. + * @param {boolean=} opt_capture Whether to check for capture or bubble + * listeners. + * @return {boolean} Whether there is any active listeners matching + * the requested type and/or capture phase. + */ +goog.events.Listenable.prototype.hasListener; + + + +/** + * An interface that describes a single registered listener. + * @interface + */ +goog.events.ListenableKey = function() {}; + + +/** + * Counter used to create a unique key + * @type {number} + * @private + */ +goog.events.ListenableKey.counter_ = 0; + + +/** + * Reserves a key to be used for ListenableKey#key field. + * @return {number} A number to be used to fill ListenableKey#key + * field. + */ +goog.events.ListenableKey.reserveKey = function() { + return ++goog.events.ListenableKey.counter_; +}; + + +/** + * The source event target. + * @type {!(Object|goog.events.Listenable|goog.events.EventTarget)} + */ +goog.events.ListenableKey.prototype.src; + + +/** + * The event type the listener is listening to. + * @type {string} + */ +goog.events.ListenableKey.prototype.type; + + +/** + * The listener function. + * TODO(user): Narrow the type if possible. + * @type {Function|Object} + */ +goog.events.ListenableKey.prototype.listener; + + +/** + * Whether the listener works on capture phase. + * @type {boolean} + */ +goog.events.ListenableKey.prototype.capture; + + +/** + * The 'this' object for the listener function's scope. + * @type {Object} + */ +goog.events.ListenableKey.prototype.handler; + + +/** + * A globally unique number to identify the key. + * @type {number} + */ +goog.events.ListenableKey.prototype.key; +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Listener object. + * @see ../demos/events.html + */ + +goog.provide('goog.events.Listener'); + +goog.require('goog.events.ListenableKey'); + + + +/** + * Simple class that stores information about a listener + * @implements {goog.events.ListenableKey} + * @constructor + */ +goog.events.Listener = function() { + if (goog.events.Listener.ENABLE_MONITORING) { + this.creationStack = new Error().stack; + } +}; + + +/** + * @define {boolean} Whether to enable the monitoring of the + * goog.events.Listener instances. Switching on the monitoring is only + * recommended for debugging because it has a significant impact on + * performance and memory usage. If switched off, the monitoring code + * compiles down to 0 bytes. + */ +goog.events.Listener.ENABLE_MONITORING = false; + + +/** + * Whether the listener is a function or an object that implements handleEvent. + * @type {boolean} + * @private + */ +goog.events.Listener.prototype.isFunctionListener_; + + +/** + * Call back function or an object with a handleEvent function. + * @type {Function|Object|null} + */ +goog.events.Listener.prototype.listener; + + +/** + * Proxy for callback that passes through {@link goog.events#HandleEvent_} + * @type {Function} + */ +goog.events.Listener.prototype.proxy; + + +/** + * Object or node that callback is listening to + * @type {Object|goog.events.Listenable|goog.events.EventTarget} + */ +goog.events.Listener.prototype.src; + + +/** + * Type of event + * @type {string} + */ +goog.events.Listener.prototype.type; + + +/** + * Whether the listener is being called in the capture or bubble phase + * @type {boolean} + */ +goog.events.Listener.prototype.capture; + + +/** + * Optional object whose context to execute the listener in + * @type {Object|undefined} + */ +goog.events.Listener.prototype.handler; + + +/** + * The key of the listener. + * @type {number} + * @override + */ +goog.events.Listener.prototype.key = 0; + + +/** + * Whether the listener has been removed. + * @type {boolean} + */ +goog.events.Listener.prototype.removed = false; + + +/** + * Whether to remove the listener after it has been called. + * @type {boolean} + */ +goog.events.Listener.prototype.callOnce = false; + + +/** + * If monitoring the goog.events.Listener instances is enabled, stores the + * creation stack trace of the Disposable instance. + * @type {string} + */ +goog.events.Listener.prototype.creationStack; + + +/** + * Initializes the listener. + * @param {Function|Object} listener Callback function, or an object with a + * handleEvent function. + * @param {Function} proxy Wrapper for the listener that patches the event. + * @param {Object} src Source object for the event. + * @param {string} type Event type. + * @param {boolean} capture Whether in capture or bubble phase. + * @param {Object=} opt_handler Object in whose context to execute the callback. + */ +goog.events.Listener.prototype.init = function(listener, proxy, src, type, + capture, opt_handler) { + // we do the test of the listener here so that we do not need to + // continiously do this inside handleEvent + if (goog.isFunction(listener)) { + this.isFunctionListener_ = true; + } else if (listener && listener.handleEvent && + goog.isFunction(listener.handleEvent)) { + this.isFunctionListener_ = false; + } else { + throw Error('Invalid listener argument'); + } + + this.listener = listener; + this.proxy = proxy; + this.src = src; + this.type = type; + this.capture = !!capture; + this.handler = opt_handler; + this.callOnce = false; + this.key = goog.events.ListenableKey.reserveKey(); + this.removed = false; +}; + + +/** + * Calls the internal listener + * @param {Object} eventObject Event object to be passed to listener. + * @return {boolean} The result of the internal listener call. + */ +goog.events.Listener.prototype.handleEvent = function(eventObject) { + if (this.isFunctionListener_) { + return this.listener.call(this.handler || this.src, eventObject); + } + return this.listener.handleEvent.call(this.listener, eventObject); +}; +// Copyright 2008 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview File which defines dummy object to work around undefined + * properties compiler warning for weak dependencies on + * {@link goog.debug.ErrorHandler#protectEntryPoint}. + * + */ + +goog.provide('goog.debug.errorHandlerWeakDep'); + + +/** + * Dummy object to work around undefined properties compiler warning. + * @type {Object} + */ +goog.debug.errorHandlerWeakDep = { + /** + * @param {Function} fn An entry point function to be protected. + * @param {boolean=} opt_tracers Whether to install tracers around the + * fn. + * @return {Function} A protected wrapper function that calls the + * entry point function. + */ + protectEntryPoint: function(fn, opt_tracers) { return fn; } +}; +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Browser capability checks for the events package. + * + */ + + +goog.provide('goog.events.BrowserFeature'); + +goog.require('goog.userAgent'); + + +/** + * Enum of browser capabilities. + * @enum {boolean} + */ +goog.events.BrowserFeature = { + /** + * Whether the button attribute of the event is W3C compliant. False in + * Internet Explorer prior to version 9; document-version dependent. + */ + HAS_W3C_BUTTON: !goog.userAgent.IE || goog.userAgent.isDocumentMode(9), + + /** + * Whether the browser supports full W3C event model. + */ + HAS_W3C_EVENT_SUPPORT: !goog.userAgent.IE || goog.userAgent.isDocumentMode(9), + + /** + * To prevent default in IE7-8 for certain keydown events we need set the + * keyCode to -1. + */ + SET_KEY_CODE_TO_PREVENT_DEFAULT: goog.userAgent.IE && + !goog.userAgent.isVersion('9'), + + /** + * Whether the {@code navigator.onLine} property is supported. + */ + HAS_NAVIGATOR_ONLINE_PROPERTY: !goog.userAgent.WEBKIT || + goog.userAgent.isVersion('528'), + + /** + * Whether HTML5 network online/offline events are supported. + */ + HAS_HTML5_NETWORK_EVENT_SUPPORT: + goog.userAgent.GECKO && goog.userAgent.isVersion('1.9b') || + goog.userAgent.IE && goog.userAgent.isVersion('8') || + goog.userAgent.OPERA && goog.userAgent.isVersion('9.5') || + goog.userAgent.WEBKIT && goog.userAgent.isVersion('528'), + + /** + * Whether HTML5 network events fire on document.body, or otherwise the + * window. + */ + HTML5_NETWORK_EVENTS_FIRE_ON_BODY: + goog.userAgent.GECKO && !goog.userAgent.isVersion('8') || + goog.userAgent.IE && !goog.userAgent.isVersion('9'), + + /** + * Whether touch is enabled in the browser. + */ + TOUCH_ENABLED: + ('ontouchstart' in goog.global || + !!(goog.global['document'] && + document.documentElement && + 'ontouchstart' in document.documentElement) || + // IE10 uses non-standard touch events, so it has a different check. + !!(goog.global['navigator'] && + goog.global['navigator']['msMaxTouchPoints'])) +}; +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A global registry for entry points into a program, + * so that they can be instrumented. Each module should register their + * entry points with this registry. Designed to be compiled out + * if no instrumentation is requested. + * + * Entry points may be registered before or after a call to + * goog.debug.entryPointRegistry.monitorAll. If an entry point is registered + * later, the existing monitor will instrument the new entry point. + * + * @author nicksantos@google.com (Nick Santos) + */ + +goog.provide('goog.debug.EntryPointMonitor'); +goog.provide('goog.debug.entryPointRegistry'); + +goog.require('goog.asserts'); + + + +/** + * @interface + */ +goog.debug.EntryPointMonitor = function() {}; + + +/** + * Instruments a function. + * + * @param {!Function} fn A function to instrument. + * @return {!Function} The instrumented function. + */ +goog.debug.EntryPointMonitor.prototype.wrap; + + +/** + * Try to remove an instrumentation wrapper created by this monitor. + * If the function passed to unwrap is not a wrapper created by this + * monitor, then we will do nothing. + * + * Notice that some wrappers may not be unwrappable. For example, if other + * monitors have applied their own wrappers, then it will be impossible to + * unwrap them because their wrappers will have captured our wrapper. + * + * So it is important that entry points are unwrapped in the reverse + * order that they were wrapped. + * + * @param {!Function} fn A function to unwrap. + * @return {!Function} The unwrapped function, or {@code fn} if it was not + * a wrapped function created by this monitor. + */ +goog.debug.EntryPointMonitor.prototype.unwrap; + + +/** + * An array of entry point callbacks. + * @type {!Array.} + * @private + */ +goog.debug.entryPointRegistry.refList_ = []; + + +/** + * Monitors that should wrap all the entry points. + * @type {!Array.} + * @private + */ +goog.debug.entryPointRegistry.monitors_ = []; + + +/** + * Whether goog.debug.entryPointRegistry.monitorAll has ever been called. + * Checking this allows the compiler to optimize out the registrations. + * @type {boolean} + * @private + */ +goog.debug.entryPointRegistry.monitorsMayExist_ = false; + + +/** + * Register an entry point with this module. + * + * The entry point will be instrumented when a monitor is passed to + * goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the + * entry point is instrumented immediately. + * + * @param {function(!Function)} callback A callback function which is called + * with a transforming function to instrument the entry point. The callback + * is responsible for wrapping the relevant entry point with the + * transforming function. + */ +goog.debug.entryPointRegistry.register = function(callback) { + // Don't use push(), so that this can be compiled out. + goog.debug.entryPointRegistry.refList_[ + goog.debug.entryPointRegistry.refList_.length] = callback; + // If no one calls monitorAll, this can be compiled out. + if (goog.debug.entryPointRegistry.monitorsMayExist_) { + var monitors = goog.debug.entryPointRegistry.monitors_; + for (var i = 0; i < monitors.length; i++) { + callback(goog.bind(monitors[i].wrap, monitors[i])); + } + } +}; + + +/** + * Configures a monitor to wrap all entry points. + * + * Entry points that have already been registered are immediately wrapped by + * the monitor. When an entry point is registered in the future, it will also + * be wrapped by the monitor when it is registered. + * + * @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor. + */ +goog.debug.entryPointRegistry.monitorAll = function(monitor) { + goog.debug.entryPointRegistry.monitorsMayExist_ = true; + var transformer = goog.bind(monitor.wrap, monitor); + for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) { + goog.debug.entryPointRegistry.refList_[i](transformer); + } + goog.debug.entryPointRegistry.monitors_.push(monitor); +}; + + +/** + * Try to unmonitor all the entry points that have already been registered. If + * an entry point is registered in the future, it will not be wrapped by the + * monitor when it is registered. Note that this may fail if the entry points + * have additional wrapping. + * + * @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap + * the entry points. + * @throws {Error} If the monitor is not the most recently configured monitor. + */ +goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) { + var monitors = goog.debug.entryPointRegistry.monitors_; + goog.asserts.assert(monitor == monitors[monitors.length - 1], + 'Only the most recent monitor can be unwrapped.'); + var transformer = goog.bind(monitor.unwrap, monitor); + for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) { + goog.debug.entryPointRegistry.refList_[i](transformer); + } + monitors.length--; +}; +// Copyright 2009 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Definition of the goog.events.EventWrapper interface. + * + * @author eae@google.com (Emil A Eklund) + */ + +goog.provide('goog.events.EventWrapper'); + + + +/** + * Interface for event wrappers. + * @interface + */ +goog.events.EventWrapper = function() { +}; + + +/** + * Adds an event listener using the wrapper on a DOM Node or an object that has + * implemented {@link goog.events.EventTarget}. A listener can only be added + * once to an object. + * + * @param {EventTarget|goog.events.EventTarget} src The node to listen to + * events on. + * @param {Function|Object} listener Callback method, or an object with a + * handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {Object=} opt_scope Element in whose scope to call the listener. + * @param {goog.events.EventHandler=} opt_eventHandler Event handler to add + * listener to. + */ +goog.events.EventWrapper.prototype.listen = function(src, listener, opt_capt, + opt_scope, opt_eventHandler) { +}; + + +/** + * Removes an event listener added using goog.events.EventWrapper.listen. + * + * @param {EventTarget|goog.events.EventTarget} src The node to remove listener + * from. + * @param {Function|Object} listener Callback method, or an object with a + * handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {Object=} opt_scope Element in whose scope to call the listener. + * @param {goog.events.EventHandler=} opt_eventHandler Event handler to remove + * listener from. + */ +goog.events.EventWrapper.prototype.unlisten = function(src, listener, opt_capt, + opt_scope, opt_eventHandler) { +}; +// Copyright 2010 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Event Types. + * + * @author arv@google.com (Erik Arvidsson) + * @author mirkov@google.com (Mirko Visontai) + */ + + +goog.provide('goog.events.EventType'); + +goog.require('goog.userAgent'); + + +/** + * Constants for event names. + * @enum {string} + */ +goog.events.EventType = { + // Mouse events + CLICK: 'click', + DBLCLICK: 'dblclick', + MOUSEDOWN: 'mousedown', + MOUSEUP: 'mouseup', + MOUSEOVER: 'mouseover', + MOUSEOUT: 'mouseout', + MOUSEMOVE: 'mousemove', + SELECTSTART: 'selectstart', // IE, Safari, Chrome + + // Key events + KEYPRESS: 'keypress', + KEYDOWN: 'keydown', + KEYUP: 'keyup', + + // Focus + BLUR: 'blur', + FOCUS: 'focus', + DEACTIVATE: 'deactivate', // IE only + // NOTE: The following two events are not stable in cross-browser usage. + // WebKit and Opera implement DOMFocusIn/Out. + // IE implements focusin/out. + // Gecko implements neither see bug at + // https://bugzilla.mozilla.org/show_bug.cgi?id=396927. + // The DOM Events Level 3 Draft deprecates DOMFocusIn in favor of focusin: + // http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html + // You can use FOCUS in Capture phase until implementations converge. + FOCUSIN: goog.userAgent.IE ? 'focusin' : 'DOMFocusIn', + FOCUSOUT: goog.userAgent.IE ? 'focusout' : 'DOMFocusOut', + + // Forms + CHANGE: 'change', + SELECT: 'select', + SUBMIT: 'submit', + INPUT: 'input', + PROPERTYCHANGE: 'propertychange', // IE only + + // Drag and drop + DRAGSTART: 'dragstart', + DRAG: 'drag', + DRAGENTER: 'dragenter', + DRAGOVER: 'dragover', + DRAGLEAVE: 'dragleave', + DROP: 'drop', + DRAGEND: 'dragend', + + // WebKit touch events. + TOUCHSTART: 'touchstart', + TOUCHMOVE: 'touchmove', + TOUCHEND: 'touchend', + TOUCHCANCEL: 'touchcancel', + + // Misc + BEFOREUNLOAD: 'beforeunload', + CONTEXTMENU: 'contextmenu', + ERROR: 'error', + HELP: 'help', + LOAD: 'load', + LOSECAPTURE: 'losecapture', + READYSTATECHANGE: 'readystatechange', + RESIZE: 'resize', + SCROLL: 'scroll', + UNLOAD: 'unload', + + // HTML 5 History events + // See http://www.w3.org/TR/html5/history.html#event-definitions + HASHCHANGE: 'hashchange', + PAGEHIDE: 'pagehide', + PAGESHOW: 'pageshow', + POPSTATE: 'popstate', + + // Copy and Paste + // Support is limited. Make sure it works on your favorite browser + // before using. + // http://www.quirksmode.org/dom/events/cutcopypaste.html + COPY: 'copy', + PASTE: 'paste', + CUT: 'cut', + BEFORECOPY: 'beforecopy', + BEFORECUT: 'beforecut', + BEFOREPASTE: 'beforepaste', + + // HTML5 online/offline events. + // http://www.w3.org/TR/offline-webapps/#related + ONLINE: 'online', + OFFLINE: 'offline', + + // HTML 5 worker events + MESSAGE: 'message', + CONNECT: 'connect', + + // CSS transition events. Based on the browser support described at: + // https://developer.mozilla.org/en/css/css_transitions#Browser_compatibility + TRANSITIONEND: goog.userAgent.WEBKIT ? 'webkitTransitionEnd' : + (goog.userAgent.OPERA ? 'oTransitionEnd' : 'transitionend'), + + // IE specific events. + // See http://msdn.microsoft.com/en-us/library/ie/hh673557(v=vs.85).aspx + MSGESTURECHANGE: 'MSGestureChange', + MSGESTUREEND: 'MSGestureEnd', + MSGESTUREHOLD: 'MSGestureHold', + MSGESTURESTART: 'MSGestureStart', + MSGESTURETAP: 'MSGestureTap', + MSGOTPOINTERCAPTURE: 'MSGotPointerCapture', + MSINERTIASTART: 'MSInertiaStart', + MSLOSTPOINTERCAPTURE: 'MSLostPointerCapture', + MSPOINTERCANCEL: 'MSPointerCancel', + MSPOINTERDOWN: 'MSPointerDown', + MSPOINTERMOVE: 'MSPointerMove', + MSPOINTEROVER: 'MSPointerOver', + MSPOINTEROUT: 'MSPointerOut', + MSPOINTERUP: 'MSPointerUp', + + // Native IMEs/input tools events. + TEXTINPUT: 'textinput', + COMPOSITIONSTART: 'compositionstart', + COMPOSITIONUPDATE: 'compositionupdate', + COMPOSITIONEND: 'compositionend' +}; +// Copyright 2009 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Useful compiler idioms. + * + */ + +goog.provide('goog.reflect'); + + +/** + * Syntax for object literal casts. + * @see http://go/jscompiler-renaming + * @see http://code.google.com/p/closure-compiler/wiki/ + * ExperimentalTypeBasedPropertyRenaming + * + * Use this if you have an object literal whose keys need to have the same names + * as the properties of some class even after they are renamed by the compiler. + * + * @param {!Function} type Type to cast to. + * @param {Object} object Object literal to cast. + * @return {Object} The object literal. + */ +goog.reflect.object = function(type, object) { + return object; +}; + + +/** + * To assert to the compiler that an operation is needed when it would + * otherwise be stripped. For example: + * + * // Force a layout + * goog.reflect.sinkValue(dialog.offsetHeight); + * + * @type {!Function} + */ +goog.reflect.sinkValue = function(x) { + goog.reflect.sinkValue[' '](x); + return x; +}; + + +/** + * The compiler should optimize this function away iff no one ever uses + * goog.reflect.sinkValue. + */ +goog.reflect.sinkValue[' '] = goog.nullFunction; + + +/** + * Check if a property can be accessed without throwing an exception. + * @param {Object} obj The owner of the property. + * @param {string} prop The property name. + * @return {boolean} Whether the property is accessible. Will also return true + * if obj is null. + */ +goog.reflect.canAccessProperty = function(obj, prop) { + /** @preserveTry */ + try { + goog.reflect.sinkValue(obj[prop]); + return true; + } catch (e) {} + return false; +}; +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A patched, standardized event object for browser events. + * + *
+ * The patched event object contains the following members:
+ * - type           {string}    Event type, e.g. 'click'
+ * - timestamp      {Date}      A date object for when the event was fired
+ * - target         {Object}    The element that actually triggered the event
+ * - currentTarget  {Object}    The element the listener is attached to
+ * - relatedTarget  {Object}    For mouseover and mouseout, the previous object
+ * - offsetX        {number}    X-coordinate relative to target
+ * - offsetY        {number}    Y-coordinate relative to target
+ * - clientX        {number}    X-coordinate relative to viewport
+ * - clientY        {number}    Y-coordinate relative to viewport
+ * - screenX        {number}    X-coordinate relative to the edge of the screen
+ * - screenY        {number}    Y-coordinate relative to the edge of the screen
+ * - button         {number}    Mouse button. Use isButton() to test.
+ * - keyCode        {number}    Key-code
+ * - ctrlKey        {boolean}   Was ctrl key depressed
+ * - altKey         {boolean}   Was alt key depressed
+ * - shiftKey       {boolean}   Was shift key depressed
+ * - metaKey        {boolean}   Was meta key depressed
+ * - defaultPrevented {boolean} Whether the default action has been prevented
+ * - state          {Object}    History state object
+ *
+ * NOTE: The keyCode member contains the raw browser keyCode. For normalized
+ * key and character code use {@link goog.events.KeyHandler}.
+ * 
+ * + */ + +goog.provide('goog.events.BrowserEvent'); +goog.provide('goog.events.BrowserEvent.MouseButton'); + +goog.require('goog.events.BrowserFeature'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventType'); +goog.require('goog.reflect'); +goog.require('goog.userAgent'); + + + +/** + * Accepts a browser event object and creates a patched, cross browser event + * object. + * The content of this object will not be initialized if no event object is + * provided. If this is the case, init() needs to be invoked separately. + * @param {Event=} opt_e Browser event object. + * @param {EventTarget=} opt_currentTarget Current target for event. + * @constructor + * @extends {goog.events.Event} + */ +goog.events.BrowserEvent = function(opt_e, opt_currentTarget) { + if (opt_e) { + this.init(opt_e, opt_currentTarget); + } +}; +goog.inherits(goog.events.BrowserEvent, goog.events.Event); + + +/** + * Normalized button constants for the mouse. + * @enum {number} + */ +goog.events.BrowserEvent.MouseButton = { + LEFT: 0, + MIDDLE: 1, + RIGHT: 2 +}; + + +/** + * Static data for mapping mouse buttons. + * @type {Array.} + */ +goog.events.BrowserEvent.IEButtonMap = [ + 1, // LEFT + 4, // MIDDLE + 2 // RIGHT +]; + + +/** + * Target that fired the event. + * @override + * @type {Node} + */ +goog.events.BrowserEvent.prototype.target = null; + + +/** + * Node that had the listener attached. + * @override + * @type {Node|undefined} + */ +goog.events.BrowserEvent.prototype.currentTarget; + + +/** + * For mouseover and mouseout events, the related object for the event. + * @type {Node} + */ +goog.events.BrowserEvent.prototype.relatedTarget = null; + + +/** + * X-coordinate relative to target. + * @type {number} + */ +goog.events.BrowserEvent.prototype.offsetX = 0; + + +/** + * Y-coordinate relative to target. + * @type {number} + */ +goog.events.BrowserEvent.prototype.offsetY = 0; + + +/** + * X-coordinate relative to the window. + * @type {number} + */ +goog.events.BrowserEvent.prototype.clientX = 0; + + +/** + * Y-coordinate relative to the window. + * @type {number} + */ +goog.events.BrowserEvent.prototype.clientY = 0; + + +/** + * X-coordinate relative to the monitor. + * @type {number} + */ +goog.events.BrowserEvent.prototype.screenX = 0; + + +/** + * Y-coordinate relative to the monitor. + * @type {number} + */ +goog.events.BrowserEvent.prototype.screenY = 0; + + +/** + * Which mouse button was pressed. + * @type {number} + */ +goog.events.BrowserEvent.prototype.button = 0; + + +/** + * Keycode of key press. + * @type {number} + */ +goog.events.BrowserEvent.prototype.keyCode = 0; + + +/** + * Keycode of key press. + * @type {number} + */ +goog.events.BrowserEvent.prototype.charCode = 0; + + +/** + * Whether control was pressed at time of event. + * @type {boolean} + */ +goog.events.BrowserEvent.prototype.ctrlKey = false; + + +/** + * Whether alt was pressed at time of event. + * @type {boolean} + */ +goog.events.BrowserEvent.prototype.altKey = false; + + +/** + * Whether shift was pressed at time of event. + * @type {boolean} + */ +goog.events.BrowserEvent.prototype.shiftKey = false; + + +/** + * Whether the meta key was pressed at time of event. + * @type {boolean} + */ +goog.events.BrowserEvent.prototype.metaKey = false; + + +/** + * History state object, only set for PopState events where it's a copy of the + * state object provided to pushState or replaceState. + * @type {Object} + */ +goog.events.BrowserEvent.prototype.state; + + +/** + * Whether the default platform modifier key was pressed at time of event. + * (This is control for all platforms except Mac, where it's Meta. + * @type {boolean} + */ +goog.events.BrowserEvent.prototype.platformModifierKey = false; + + +/** + * The browser event object. + * @type {Event} + * @private + */ +goog.events.BrowserEvent.prototype.event_ = null; + + +/** + * Accepts a browser event object and creates a patched, cross browser event + * object. + * @param {Event} e Browser event object. + * @param {EventTarget=} opt_currentTarget Current target for event. + */ +goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) { + var type = this.type = e.type; + goog.events.Event.call(this, type); + + // TODO(nicksantos): Change this.target to type EventTarget. + this.target = /** @type {Node} */ (e.target) || e.srcElement; + + // TODO(nicksantos): Change this.currentTarget to type EventTarget. + this.currentTarget = /** @type {Node} */ (opt_currentTarget); + + var relatedTarget = /** @type {Node} */ (e.relatedTarget); + if (relatedTarget) { + // There's a bug in FireFox where sometimes, relatedTarget will be a + // chrome element, and accessing any property of it will get a permission + // denied exception. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=497780 + if (goog.userAgent.GECKO) { + if (!goog.reflect.canAccessProperty(relatedTarget, 'nodeName')) { + relatedTarget = null; + } + } + // TODO(arv): Use goog.events.EventType when it has been refactored into its + // own file. + } else if (type == goog.events.EventType.MOUSEOVER) { + relatedTarget = e.fromElement; + } else if (type == goog.events.EventType.MOUSEOUT) { + relatedTarget = e.toElement; + } + + this.relatedTarget = relatedTarget; + + // Webkit emits a lame warning whenever layerX/layerY is accessed. + // http://code.google.com/p/chromium/issues/detail?id=101733 + this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ? + e.offsetX : e.layerX; + this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ? + e.offsetY : e.layerY; + + this.clientX = e.clientX !== undefined ? e.clientX : e.pageX; + this.clientY = e.clientY !== undefined ? e.clientY : e.pageY; + this.screenX = e.screenX || 0; + this.screenY = e.screenY || 0; + + this.button = e.button; + + this.keyCode = e.keyCode || 0; + this.charCode = e.charCode || (type == 'keypress' ? e.keyCode : 0); + this.ctrlKey = e.ctrlKey; + this.altKey = e.altKey; + this.shiftKey = e.shiftKey; + this.metaKey = e.metaKey; + this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey; + this.state = e.state; + this.event_ = e; + if (e.defaultPrevented) { + this.preventDefault(); + } + delete this.propagationStopped_; +}; + + +/** + * Tests to see which button was pressed during the event. This is really only + * useful in IE and Gecko browsers. And in IE, it's only useful for + * mousedown/mouseup events, because click only fires for the left mouse button. + * + * Safari 2 only reports the left button being clicked, and uses the value '1' + * instead of 0. Opera only reports a mousedown event for the middle button, and + * no mouse events for the right button. Opera has default behavior for left and + * middle click that can only be overridden via a configuration setting. + * + * There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html. + * + * @param {goog.events.BrowserEvent.MouseButton} button The button + * to test for. + * @return {boolean} True if button was pressed. + */ +goog.events.BrowserEvent.prototype.isButton = function(button) { + if (!goog.events.BrowserFeature.HAS_W3C_BUTTON) { + if (this.type == 'click') { + return button == goog.events.BrowserEvent.MouseButton.LEFT; + } else { + return !!(this.event_.button & + goog.events.BrowserEvent.IEButtonMap[button]); + } + } else { + return this.event_.button == button; + } +}; + + +/** + * Whether this has an "action"-producing mouse button. + * + * By definition, this includes left-click on windows/linux, and left-click + * without the ctrl key on Macs. + * + * @return {boolean} The result. + */ +goog.events.BrowserEvent.prototype.isMouseActionButton = function() { + // Webkit does not ctrl+click to be a right-click, so we + // normalize it to behave like Gecko and Opera. + return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) && + !(goog.userAgent.WEBKIT && goog.userAgent.MAC && this.ctrlKey); +}; + + +/** + * @override + */ +goog.events.BrowserEvent.prototype.stopPropagation = function() { + goog.events.BrowserEvent.superClass_.stopPropagation.call(this); + if (this.event_.stopPropagation) { + this.event_.stopPropagation(); + } else { + this.event_.cancelBubble = true; + } +}; + + +/** + * @override + */ +goog.events.BrowserEvent.prototype.preventDefault = function() { + goog.events.BrowserEvent.superClass_.preventDefault.call(this); + var be = this.event_; + if (!be.preventDefault) { + be.returnValue = false; + if (goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT) { + /** @preserveTry */ + try { + // Most keys can be prevented using returnValue. Some special keys + // require setting the keyCode to -1 as well: + // + // In IE7: + // F3, F5, F10, F11, Ctrl+P, Crtl+O, Ctrl+F (these are taken from IE6) + // + // In IE8: + // Ctrl+P, Crtl+O, Ctrl+F (F1-F12 cannot be stopped through the event) + // + // We therefore do this for all function keys as well as when Ctrl key + // is pressed. + var VK_F1 = 112; + var VK_F12 = 123; + if (be.ctrlKey || be.keyCode >= VK_F1 && be.keyCode <= VK_F12) { + be.keyCode = -1; + } + } catch (ex) { + // IE throws an 'access denied' exception when trying to change + // keyCode in some situations (e.g. srcElement is input[type=file], + // or srcElement is an anchor tag rewritten by parent's innerHTML). + // Do nothing in this case. + } + } + } else { + be.preventDefault(); + } +}; + + +/** + * @return {Event} The underlying browser event object. + */ +goog.events.BrowserEvent.prototype.getBrowserEvent = function() { + return this.event_; +}; + + +/** @override */ +goog.events.BrowserEvent.prototype.disposeInternal = function() { +}; +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Event Manager. + * + * Provides an abstracted interface to the browsers' event + * systems. This uses an indirect lookup of listener functions to avoid circular + * references between DOM (in IE) or XPCOM (in Mozilla) objects which leak + * memory. This makes it easier to write OO Javascript/DOM code. + * + * It simulates capture & bubble in Internet Explorer. + * + * The listeners will also automagically have their event objects patched, so + * your handlers don't need to worry about the browser. + * + * Example usage: + *
+ * goog.events.listen(myNode, 'click', function(e) { alert('woo') });
+ * goog.events.listen(myNode, 'mouseover', mouseHandler, true);
+ * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);
+ * goog.events.removeAll(myNode);
+ * goog.events.removeAll();
+ * 
+ * + * in IE and event object patching] + * + * @supported IE6+, FF1.5+, WebKit, Opera. + * @see ../demos/events.html + * @see ../demos/event-propagation.html + * @see ../demos/stopevent.html + */ + + +// This uses 3 lookup tables/trees. +// listenerTree_ is a tree of type -> capture -> src uid -> [Listener] +// listeners_ is a map of key -> [Listener] +// +// The key is a field of the Listener. The Listener class also has the type, +// capture and the src so one can always trace back in the tree +// +// sources_: src uid -> [Listener] + + +goog.provide('goog.events'); +goog.provide('goog.events.Key'); + +goog.require('goog.array'); +goog.require('goog.debug.entryPointRegistry'); +goog.require('goog.debug.errorHandlerWeakDep'); +goog.require('goog.events.BrowserEvent'); +goog.require('goog.events.BrowserFeature'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventWrapper'); +goog.require('goog.events.Listenable'); +goog.require('goog.events.Listener'); +goog.require('goog.object'); +goog.require('goog.userAgent'); + + +/** + * @typedef {?number} + */ +goog.events.Key; + + +/** + * @typedef {EventTarget|goog.events.Listenable|goog.events.EventTarget} + */ +goog.events.ListenableType; + + +/** + * Container for storing event listeners and their proxies + * @private + * @type {Object.} + */ +goog.events.listeners_ = {}; + + +/** + * The root of the listener tree + * @private + * @type {Object} + */ +goog.events.listenerTree_ = {}; + + +/** + * Lookup for mapping source UIDs to listeners. + * @private + * @type {Object} + */ +goog.events.sources_ = {}; + + +/** + * String used to prepend to IE event types. Not a constant so that it is not + * inlined. + * @type {string} + * @private + */ +goog.events.onString_ = 'on'; + + +/** + * Map of computed on strings for IE event types. Caching this removes an extra + * object allocation in goog.events.listen which improves IE6 performance. + * @type {Object} + * @private + */ +goog.events.onStringMap_ = {}; + + +/** + * Separator used to split up the various parts of an event key, to help avoid + * the possibilities of collisions. + * @type {string} + * @private + */ +goog.events.keySeparator_ = '_'; + + +/** + * Adds an event listener for a specific event on a DOM Node or an + * object that has implemented {@link goog.events.EventTarget}. A + * listener can only be added once to an object and if it is added + * again the key for the listener is returned. Note that if the + * existing listener is a one-off listener (registered via + * listenOnce), it will no longer be a one-off listener after a call + * to listen(). + * + * @param {goog.events.ListenableType} src The node to listen to + * events on. + * @param {string|Array.} type Event type or array of event types. + * @param {Function|Object} listener Callback method, or an object with a + * handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {Object=} opt_handler Element in whose scope to call the listener. + * @return {goog.events.Key} Unique key for the listener. + */ +goog.events.listen = function(src, type, listener, opt_capt, opt_handler) { + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + goog.events.listen(src, type[i], listener, opt_capt, opt_handler); + } + return null; + } + + var listenableKey; + if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && + goog.events.Listenable.isImplementedBy(src)) { + listenableKey = src.listen( + /** @type {string} */ (type), + goog.events.wrapListener_(listener), opt_capt, opt_handler); + } else { + listenableKey = goog.events.listen_( + /** @type {EventTarget|goog.events.EventTarget} */ (src), + type, listener, /* callOnce */ false, opt_capt, opt_handler); + } + + var key = listenableKey.key; + goog.events.listeners_[key] = listenableKey; + return key; +}; + + +/** + * Adds an event listener for a specific event on a DOM Node or an object that + * has implemented {@link goog.events.EventTarget}. A listener can only be + * added once to an object and if it is added again the key for the listener + * is returned. + * + * Note that a one-off listener will not change an existing listener, + * if any. On the other hand a normal listener will change existing + * one-off listener to become a normal listener. + * + * @param {EventTarget|goog.events.EventTarget} src The node to listen to + * events on. + * @param {?string} type Event type or array of event types. + * @param {Function|Object} listener Callback method, or an object with a + * handleEvent function. + * @param {boolean} callOnce Whether the listener is a one-off + * listener or otherwise. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {Object=} opt_handler Element in whose scope to call the listener. + * @return {goog.events.ListenableKey} Unique key for the listener. + * @private + */ +goog.events.listen_ = function( + src, type, listener, callOnce, opt_capt, opt_handler) { + if (!type) { + throw Error('Invalid event type'); + } + + var capture = !!opt_capt; + var map = goog.events.listenerTree_; + + if (!(type in map)) { + map[type] = {count_: 0, remaining_: 0}; + } + map = map[type]; + + if (!(capture in map)) { + map[capture] = {count_: 0, remaining_: 0}; + map.count_++; + } + map = map[capture]; + + var srcUid = goog.getUid(src); + var listenerArray, listenerObj; + + // The remaining_ property is used to be able to short circuit the iteration + // of the event listeners. + // + // Increment the remaining event listeners to call even if this event might + // already have been fired. At this point we do not know if the event has + // been fired and it is too expensive to find out. By incrementing it we are + // guaranteed that we will not skip any event listeners. + map.remaining_++; + + // Do not use srcUid in map here since that will cast the number to a + // string which will allocate one string object. + if (!map[srcUid]) { + listenerArray = map[srcUid] = []; + map.count_++; + } else { + listenerArray = map[srcUid]; + // Ensure that the listeners do not already contain the current listener + for (var i = 0; i < listenerArray.length; i++) { + listenerObj = listenerArray[i]; + if (listenerObj.listener == listener && + listenerObj.handler == opt_handler) { + + // If this listener has been removed we should not return its key. It + // is OK that we create new listenerObj below since the removed one + // will be cleaned up later. + if (listenerObj.removed) { + break; + } + + if (!callOnce) { + // Ensure that, if there is an existing callOnce listener, it is no + // longer a callOnce listener. + listenerArray[i].callOnce = false; + } + + // We already have this listener. Return its key. + return listenerArray[i]; + } + } + } + + var proxy = goog.events.getProxy(); + listenerObj = new goog.events.Listener(); + listenerObj.init(listener, proxy, src, type, capture, opt_handler); + listenerObj.callOnce = callOnce; + + proxy.src = src; + proxy.listener = listenerObj; + + listenerArray.push(listenerObj); + + if (!goog.events.sources_[srcUid]) { + goog.events.sources_[srcUid] = []; + } + goog.events.sources_[srcUid].push(listenerObj); + + // Attach the proxy through the browser's API + if (src.addEventListener) { + if (src == goog.global || !src.customEvent_) { + src.addEventListener(type, proxy, capture); + } + } else { + // The else above used to be else if (src.attachEvent) and then there was + // another else statement that threw an exception warning the developer + // they made a mistake. This resulted in an extra object allocation in IE6 + // due to a wrapper object that had to be implemented around the element + // and so was removed. + src.attachEvent(goog.events.getOnString_(type), proxy); + } + + return listenerObj; +}; + + +/** + * Helper function for returning a proxy function. + * @return {Function} A new or reused function object. + */ +goog.events.getProxy = function() { + var proxyCallbackFunction = goog.events.handleBrowserEvent_; + // Use a local var f to prevent one allocation. + var f = goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ? + function(eventObject) { + return proxyCallbackFunction.call(f.src, f.listener, eventObject); + } : + function(eventObject) { + var v = proxyCallbackFunction.call(f.src, f.listener, eventObject); + // NOTE(user): In IE, we hack in a capture phase. However, if + // there is inline event handler which tries to prevent default (for + // example ...) in a + // descendant element, the prevent default will be overridden + // by this listener if this listener were to return true. Hence, we + // return undefined. + if (!v) return v; + }; + return f; +}; + + +/** + * Adds an event listener for a specific event on a DomNode or an object that + * has implemented {@link goog.events.EventTarget}. After the event has fired + * the event listener is removed from the target. + * + * If an existing listener already exists, listenOnce will do + * nothing. In particular, if the listener was previously registered + * via listen(), listenOnce() will not turn the listener into a + * one-off listener. Similarly, if there is already an existing + * one-off listener, listenOnce does not modify the listeners (it is + * still a once listener). + * + * @param {goog.events.ListenableType} src The node to listen to + * events on. + * @param {string|Array.} type Event type or array of event types. + * @param {Function|Object} listener Callback method. + * @param {boolean=} opt_capt Fire in capture phase?. + * @param {Object=} opt_handler Element in whose scope to call the listener. + * @return {goog.events.Key} Unique key for the listener. + */ +goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) { + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler); + } + return null; + } + + var listenableKey; + if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && + goog.events.Listenable.isImplementedBy(src)) { + listenableKey = src.listenOnce( + /** @type {string} */ (type), + goog.events.wrapListener_(listener), opt_capt, opt_handler); + } else { + listenableKey = goog.events.listen_( + /** @type {EventTarget|goog.events.EventTarget} */ (src), + type, listener, /* callOnce */ true, opt_capt, opt_handler); + } + + var key = listenableKey.key; + goog.events.listeners_[key] = listenableKey; + return key; +}; + + +/** + * Adds an event listener with a specific event wrapper on a DOM Node or an + * object that has implemented {@link goog.events.EventTarget}. A listener can + * only be added once to an object. + * + * @param {EventTarget|goog.events.EventTarget} src The node to listen to + * events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {Function|Object} listener Callback method, or an object with a + * handleEvent function. + * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to + * false). + * @param {Object=} opt_handler Element in whose scope to call the listener. + */ +goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt, + opt_handler) { + wrapper.listen(src, listener, opt_capt, opt_handler); +}; + + +/** + * Removes an event listener which was added with listen(). + * + * @param {goog.events.ListenableType} src The target to stop + * listening to events on. + * @param {string|Array.} type The name of the event without the 'on' + * prefix. + * @param {Function|Object} listener The listener function to remove. + * @param {boolean=} opt_capt In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase of the + * event. + * @param {Object=} opt_handler Element in whose scope to call the listener. + * @return {?boolean} indicating whether the listener was there to remove. + */ +goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) { + if (goog.isArray(type)) { + for (var i = 0; i < type.length; i++) { + goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler); + } + return null; + } + + if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && + goog.events.Listenable.isImplementedBy(src)) { + return src.unlisten( + /** @type {string} */ (type), + goog.events.wrapListener_(listener), opt_capt, opt_handler); + } + + var capture = !!opt_capt; + + var listenerArray = goog.events.getListeners_(src, type, capture); + if (!listenerArray) { + return false; + } + + for (var i = 0; i < listenerArray.length; i++) { + if (listenerArray[i].listener == listener && + listenerArray[i].capture == capture && + listenerArray[i].handler == opt_handler) { + return goog.events.unlistenByKey(listenerArray[i].key); + } + } + + return false; +}; + + +/** + * Removes an event listener which was added with listen() by the key + * returned by listen(). + * + * @param {goog.events.Key} key The key returned by listen() for this + * event listener. + * @return {boolean} indicating whether the listener was there to remove. + */ +goog.events.unlistenByKey = function(key) { + // TODO(user): When we flip goog.events.Key to be ListenableKey, + // we need to change this. + var listener = goog.events.listeners_[key]; + if (!listener) { + return false; + } + if (listener.removed) { + return false; + } + + var src = listener.src; + if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && + goog.events.Listenable.isImplementedBy(src)) { + return src.unlistenByKey(listener); + } + + var type = listener.type; + var proxy = listener.proxy; + var capture = listener.capture; + + if (src.removeEventListener) { + // EventTarget calls unlisten so we need to ensure that the source is not + // an event target to prevent re-entry. + // TODO(arv): What is this goog.global for? Why would anyone listen to + // events on the [[Global]] object? Is it supposed to be window? Why would + // we not want to allow removing event listeners on the window? + if (src == goog.global || !src.customEvent_) { + src.removeEventListener(type, proxy, capture); + } + } else if (src.detachEvent) { + src.detachEvent(goog.events.getOnString_(type), proxy); + } + + var srcUid = goog.getUid(src); + + // In a perfect implementation we would decrement the remaining_ field here + // but then we would need to know if the listener has already been fired or + // not. We therefore skip doing this and in this uncommon case the entire + // ancestor chain will need to be traversed as before. + + // Remove from sources_ + if (goog.events.sources_[srcUid]) { + var sourcesArray = goog.events.sources_[srcUid]; + goog.array.remove(sourcesArray, listener); + if (sourcesArray.length == 0) { + delete goog.events.sources_[srcUid]; + } + } + + listener.removed = true; + + // There are some esoteric situations where the hash code of an object + // can change, and we won't be able to find the listenerArray anymore. + // For example, if you're listening on a window, and the user navigates to + // a different window, the UID will disappear. + // + // It should be impossible to ever find the original listenerArray, so it + // doesn't really matter if we can't clean it up in this case. + var listenerArray = goog.events.listenerTree_[type][capture][srcUid]; + if (listenerArray) { + listenerArray.needsCleanup_ = true; + goog.events.cleanUp_(type, capture, srcUid, listenerArray); + } + + delete goog.events.listeners_[key]; + + return true; +}; + + +/** + * Removes an event listener which was added with listenWithWrapper(). + * + * @param {EventTarget|goog.events.EventTarget} src The target to stop + * listening to events on. + * @param {goog.events.EventWrapper} wrapper Event wrapper to use. + * @param {Function|Object} listener The listener function to remove. + * @param {boolean=} opt_capt In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase of the + * event. + * @param {Object=} opt_handler Element in whose scope to call the listener. + */ +goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt, + opt_handler) { + wrapper.unlisten(src, listener, opt_capt, opt_handler); +}; + + +/** + * Cleans up goog.events internal data structure. This should be + * called by all implementations of goog.events.Listenable when + * removing listeners. + * + * TODO(user): Once we remove numeric key support from + * goog.events.listen and friend, we will be able to remove this + * requirement. + * + * @param {goog.events.ListenableKey} listenableKey The key to clean up. + */ +goog.events.cleanUp = function(listenableKey) { + delete goog.events.listeners_[listenableKey.key]; +}; + + +/** + * Cleans up the listener array as well as the listener tree + * @param {string} type The type of the event. + * @param {boolean} capture Whether to clean up capture phase listeners instead + * bubble phase listeners. + * @param {number} srcUid The unique ID of the source. + * @param {Array.} listenerArray The array being cleaned. + * @private + */ +goog.events.cleanUp_ = function(type, capture, srcUid, listenerArray) { + // The listener array gets locked during the dispatch phase so that removals + // of listeners during this phase does not screw up the indeces. This method + // is called after we have removed a listener as well as after the dispatch + // phase in case any listeners were removed. + if (!listenerArray.locked_) { // catches both 0 and not set + if (listenerArray.needsCleanup_) { + // Loop over the listener array and remove listeners that have removed set + // to true. This could have been done with filter or something similar but + // we want to change the array in place and we want to minimize + // allocations. Adding a listener during this phase adds to the end of the + // array so that works fine as long as the length is rechecked every in + // iteration. + for (var oldIndex = 0, newIndex = 0; + oldIndex < listenerArray.length; + oldIndex++) { + if (listenerArray[oldIndex].removed) { + var proxy = listenerArray[oldIndex].proxy; + proxy.src = null; + continue; + } + if (oldIndex != newIndex) { + listenerArray[newIndex] = listenerArray[oldIndex]; + } + newIndex++; + } + listenerArray.length = newIndex; + + listenerArray.needsCleanup_ = false; + + // In case the length is now zero we release the object. + if (newIndex == 0) { + delete goog.events.listenerTree_[type][capture][srcUid]; + goog.events.listenerTree_[type][capture].count_--; + + if (goog.events.listenerTree_[type][capture].count_ == 0) { + delete goog.events.listenerTree_[type][capture]; + goog.events.listenerTree_[type].count_--; + } + + if (goog.events.listenerTree_[type].count_ == 0) { + delete goog.events.listenerTree_[type]; + } + } + + } + } +}; + + +/** + * Removes all listeners from an object, if no object is specified it will + * remove all listeners that have been registered. You can also optionally + * remove listeners of a particular type or capture phase. + * + * removeAll() will not remove listeners registered directly on a + * goog.events.Listenable and listeners registered via add(Once)Listener. + * + * @param {Object=} opt_obj Object to remove listeners from. + * @param {string=} opt_type Type of event to, default is all types. + * @return {number} Number of listeners removed. + */ +goog.events.removeAll = function(opt_obj, opt_type) { + var count = 0; + + var noObj = opt_obj == null; + var noType = opt_type == null; + + if (!noObj) { + if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && + opt_obj && goog.events.Listenable.isImplementedBy(opt_obj)) { + return opt_obj.removeAllListeners(opt_type); + } + + var srcUid = goog.getUid(/** @type {Object} */ (opt_obj)); + if (goog.events.sources_[srcUid]) { + var sourcesArray = goog.events.sources_[srcUid]; + for (var i = sourcesArray.length - 1; i >= 0; i--) { + var listener = sourcesArray[i]; + if (noType || opt_type == listener.type) { + goog.events.unlistenByKey(listener.key); + count++; + } + } + } + } else { + goog.object.forEach(goog.events.listeners_, function(listener, key) { + goog.events.unlistenByKey(key); + count++; + }); + } + + return count; +}; + + +/** + * Gets the listeners for a given object, type and capture phase. + * + * @param {Object} obj Object to get listeners for. + * @param {string} type Event type. + * @param {boolean} capture Capture phase?. + * @return {Array.} Array of listener objects. + */ +goog.events.getListeners = function(obj, type, capture) { + if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && + goog.events.Listenable.isImplementedBy(obj)) { + return obj.getListeners(type, capture); + } else { + return goog.events.getListeners_(obj, type, capture) || []; + } +}; + + +/** + * Gets the listeners for a given object, type and capture phase. + * + * @param {Object} obj Object to get listeners for. + * @param {?string} type Event type. + * @param {boolean} capture Capture phase?. + * @return {Array.?} Array of listener objects. + * Returns null if object has no listeners of that type. + * @private + */ +goog.events.getListeners_ = function(obj, type, capture) { + var map = goog.events.listenerTree_; + if (type in map) { + map = map[type]; + if (capture in map) { + map = map[capture]; + var objUid = goog.getUid(obj); + if (map[objUid]) { + return map[objUid]; + } + } + } + + return null; +}; + + +/** + * Gets the goog.events.Listener for the event or null if no such listener is + * in use. + * + * @param {EventTarget|goog.events.EventTarget} src The node from which to get + * listeners. + * @param {?string} type The name of the event without the 'on' prefix. + * @param {Function|Object} listener The listener function to get. + * @param {boolean=} opt_capt In DOM-compliant browsers, this determines + * whether the listener is fired during the + * capture or bubble phase of the event. + * @param {Object=} opt_handler Element in whose scope to call the listener. + * @return {goog.events.ListenableKey} the found listener or null if not found. + */ +goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) { + var capture = !!opt_capt; + + if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && + goog.events.Listenable.isImplementedBy(src)) { + return src.getListener( + /** @type {string} */ (type), + goog.events.wrapListener_(listener), capture, opt_handler); + } + + var listenerArray = goog.events.getListeners_(src, type, capture); + if (listenerArray) { + for (var i = 0; i < listenerArray.length; i++) { + // If goog.events.unlistenByKey is called during an event dispatch + // then the listener array won't get cleaned up and there might be + // 'removed' listeners in the list. Ignore those. + if (!listenerArray[i].removed && + listenerArray[i].listener == listener && + listenerArray[i].capture == capture && + listenerArray[i].handler == opt_handler) { + // We already have this listener. Return its key. + return listenerArray[i]; + } + } + } + return null; +}; + + +/** + * Returns whether an event target has any active listeners matching the + * specified signature. If either the type or capture parameters are + * unspecified, the function will match on the remaining criteria. + * + * @param {EventTarget|goog.events.EventTarget} obj Target to get listeners for. + * @param {string=} opt_type Event type. + * @param {boolean=} opt_capture Whether to check for capture or bubble-phase + * listeners. + * @return {boolean} Whether an event target has one or more listeners matching + * the requested type and/or capture phase. + */ +goog.events.hasListener = function(obj, opt_type, opt_capture) { + if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && + goog.events.Listenable.isImplementedBy(obj)) { + return obj.hasListener(opt_type, opt_capture); + } + + var objUid = goog.getUid(obj); + var listeners = goog.events.sources_[objUid]; + + if (listeners) { + var hasType = goog.isDef(opt_type); + var hasCapture = goog.isDef(opt_capture); + + if (hasType && hasCapture) { + // Lookup in the listener tree whether the specified listener exists. + var map = goog.events.listenerTree_[opt_type]; + return !!map && !!map[opt_capture] && objUid in map[opt_capture]; + + } else if (!(hasType || hasCapture)) { + // Simple check for whether the event target has any listeners at all. + return true; + + } else { + // Iterate through the listeners for the event target to find a match. + return goog.array.some(listeners, function(listener) { + return (hasType && listener.type == opt_type) || + (hasCapture && listener.capture == opt_capture); + }); + } + } + + return false; +}; + + +/** + * Provides a nice string showing the normalized event objects public members + * @param {Object} e Event Object. + * @return {string} String of the public members of the normalized event object. + */ +goog.events.expose = function(e) { + var str = []; + for (var key in e) { + if (e[key] && e[key].id) { + str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')'); + } else { + str.push(key + ' = ' + e[key]); + } + } + return str.join('\n'); +}; + + +/** + * Returns a string wth on prepended to the specified type. This is used for IE + * which expects "on" to be prepended. This function caches the string in order + * to avoid extra allocations in steady state. + * @param {string} type Event type strng. + * @return {string} The type string with 'on' prepended. + * @private + */ +goog.events.getOnString_ = function(type) { + if (type in goog.events.onStringMap_) { + return goog.events.onStringMap_[type]; + } + return goog.events.onStringMap_[type] = goog.events.onString_ + type; +}; + + +/** + * Fires an object's listeners of a particular type and phase + * + * @param {Object} obj Object whose listeners to call. + * @param {string} type Event type. + * @param {boolean} capture Which event phase. + * @param {Object} eventObject Event object to be passed to listener. + * @return {boolean} True if all listeners returned true else false. + */ +goog.events.fireListeners = function(obj, type, capture, eventObject) { + if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && + goog.events.Listenable.isImplementedBy(obj)) { + return obj.fireListeners(type, capture, eventObject); + } + + var map = goog.events.listenerTree_; + if (type in map) { + map = map[type]; + if (capture in map) { + return goog.events.fireListeners_(map[capture], obj, type, + capture, eventObject); + } + } + return true; +}; + + +/** + * Fires an object's listeners of a particular type and phase. + * + * @param {Object} map Object with listeners in it. + * @param {Object} obj Object whose listeners to call. + * @param {string} type Event type. + * @param {boolean} capture Which event phase. + * @param {Object} eventObject Event object to be passed to listener. + * @return {boolean} True if all listeners returned true else false. + * @private + */ +goog.events.fireListeners_ = function(map, obj, type, capture, eventObject) { + var retval = 1; + + var objUid = goog.getUid(obj); + if (map[objUid]) { + var remaining = --map.remaining_; + var listenerArray = map[objUid]; + + // If locked_ is not set (and if already 0) initialize it to 1. + if (!listenerArray.locked_) { + listenerArray.locked_ = 1; + } else { + listenerArray.locked_++; + } + + try { + // Events added in the dispatch phase should not be dispatched in + // the current dispatch phase. They will be included in the next + // dispatch phase though. + var length = listenerArray.length; + for (var i = 0; i < length; i++) { + var listener = listenerArray[i]; + // We might not have a listener if the listener was removed. + if (listener && !listener.removed) { + retval &= + goog.events.fireListener(listener, eventObject) !== false; + } + } + } finally { + // Allow the count of targets remaining to increase (if perhaps we have + // added listeners) but do not allow it to decrease if we have reentered + // this method through a listener dispatching the same event type, + // resetting and exhausted the remaining count. + map.remaining_ = Math.max(remaining, map.remaining_); + listenerArray.locked_--; + goog.events.cleanUp_(type, capture, objUid, listenerArray); + } + } + + return Boolean(retval); +}; + + +/** + * Fires a listener with a set of arguments + * + * @param {goog.events.Listener} listener The listener object to call. + * @param {Object} eventObject The event object to pass to the listener. + * @return {boolean} Result of listener. + */ +goog.events.fireListener = function(listener, eventObject) { + if (listener.callOnce) { + goog.events.unlistenByKey(listener.key); + } + return listener.handleEvent(eventObject); +}; + + +/** + * Gets the total number of listeners currently in the system. + * @return {number} Number of listeners. + */ +goog.events.getTotalListenerCount = function() { + return goog.object.getCount(goog.events.listeners_); +}; + + +/** + * Dispatches an event (or event like object) and calls all listeners + * listening for events of this type. The type of the event is decided by the + * type property on the event object. + * + * If any of the listeners returns false OR calls preventDefault then this + * function will return false. If one of the capture listeners calls + * stopPropagation, then the bubble listeners won't fire. + * + * @param {goog.events.Listenable|goog.events.EventTarget} src The + * event target. + * @param {goog.events.EventLike} e Event object. + * @return {boolean} If anyone called preventDefault on the event object (or + * if any of the handlers returns false) this will also return false. + * If there are no handlers, or if all handlers return true, this returns + * true. + */ +goog.events.dispatchEvent = function(src, e) { + if (goog.events.Listenable.USE_LISTENABLE_INTERFACE) { + return src.dispatchEvent(e); + } + + var type = e.type || e; + var map = goog.events.listenerTree_; + if (!(type in map)) { + return true; + } + + // If accepting a string or object, create a custom event object so that + // preventDefault and stopPropagation work with the event. + if (goog.isString(e)) { + e = new goog.events.Event(e, src); + } else if (!(e instanceof goog.events.Event)) { + var oldEvent = e; + e = new goog.events.Event(/** @type {string} */ (type), src); + goog.object.extend(e, oldEvent); + } else { + e.target = e.target || src; + } + + var rv = 1, ancestors; + + map = map[type]; + var hasCapture = true in map; + var targetsMap; + + if (hasCapture) { + // Build ancestors now + ancestors = []; + for (var parent = src; parent; parent = parent.getParentEventTarget()) { + ancestors.push(parent); + } + + targetsMap = map[true]; + targetsMap.remaining_ = targetsMap.count_; + + // Call capture listeners + for (var i = ancestors.length - 1; + !e.propagationStopped_ && i >= 0 && targetsMap.remaining_; + i--) { + e.currentTarget = ancestors[i]; + rv &= goog.events.fireListeners_(targetsMap, ancestors[i], e.type, + true, e) && + e.returnValue_ != false; + } + } + + var hasBubble = false in map; + if (hasBubble) { + targetsMap = map[false]; + targetsMap.remaining_ = targetsMap.count_; + + if (hasCapture) { // We have the ancestors. + + // Call bubble listeners + for (var i = 0; !e.propagationStopped_ && i < ancestors.length && + targetsMap.remaining_; + i++) { + e.currentTarget = ancestors[i]; + rv &= goog.events.fireListeners_(targetsMap, ancestors[i], e.type, + false, e) && + e.returnValue_ != false; + } + } else { + // In case we don't have capture we don't have to build up the + // ancestors array. + + for (var current = src; + !e.propagationStopped_ && current && targetsMap.remaining_; + current = current.getParentEventTarget()) { + e.currentTarget = current; + rv &= goog.events.fireListeners_(targetsMap, current, e.type, + false, e) && + e.returnValue_ != false; + } + } + } + + return Boolean(rv); +}; + + +/** + * Installs exception protection for the browser event entry point using the + * given error handler. + * + * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to + * protect the entry point. + */ +goog.events.protectBrowserEventEntryPoint = function(errorHandler) { + goog.events.handleBrowserEvent_ = errorHandler.protectEntryPoint( + goog.events.handleBrowserEvent_); +}; + + +/** + * Handles an event and dispatches it to the correct listeners. This + * function is a proxy for the real listener the user specified. + * + * @param {goog.events.Listener} listener The listener object. + * @param {Event=} opt_evt Optional event object that gets passed in via the + * native event handlers. + * @return {boolean} Result of the event handler. + * @this {goog.events.EventTarget|Object} The object or Element that + * fired the event. + * @private + */ +goog.events.handleBrowserEvent_ = function(listener, opt_evt) { + if (listener.removed) { + return true; + } + + var type = listener.type; + var map = goog.events.listenerTree_; + + if (!(type in map)) { + return true; + } + map = map[type]; + var retval, targetsMap; + // Synthesize event propagation if the browser does not support W3C + // event model. + if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) { + var ieEvent = opt_evt || + /** @type {Event} */ (goog.getObjectByName('window.event')); + + // Check if we have any capturing event listeners for this type. + var hasCapture = true in map; + var hasBubble = false in map; + + if (hasCapture) { + if (goog.events.isMarkedIeEvent_(ieEvent)) { + return true; + } + + goog.events.markIeEvent_(ieEvent); + } + + var evt = new goog.events.BrowserEvent(); + // TODO(user): update @this for this function + evt.init(ieEvent, /** @type {EventTarget} */ (this)); + + retval = true; + try { + if (hasCapture) { + var ancestors = []; + + for (var parent = evt.currentTarget; + parent; + parent = parent.parentNode) { + ancestors.push(parent); + } + + targetsMap = map[true]; + targetsMap.remaining_ = targetsMap.count_; + + // Call capture listeners + for (var i = ancestors.length - 1; + !evt.propagationStopped_ && i >= 0 && targetsMap.remaining_; + i--) { + evt.currentTarget = ancestors[i]; + retval &= goog.events.fireListeners_(targetsMap, ancestors[i], type, + true, evt); + } + + if (hasBubble) { + targetsMap = map[false]; + targetsMap.remaining_ = targetsMap.count_; + + // Call bubble listeners + for (var i = 0; + !evt.propagationStopped_ && i < ancestors.length && + targetsMap.remaining_; + i++) { + evt.currentTarget = ancestors[i]; + retval &= goog.events.fireListeners_(targetsMap, ancestors[i], type, + false, evt); + } + } + + } else { + // Bubbling, let IE handle the propagation. + retval = goog.events.fireListener(listener, evt); + } + + } finally { + if (ancestors) { + ancestors.length = 0; + } + } + return retval; + } // IE + + // Caught a non-IE DOM event. 1 additional argument which is the event object + var be = new goog.events.BrowserEvent( + opt_evt, /** @type {EventTarget} */ (this)); + retval = goog.events.fireListener(listener, be); + return retval; +}; + + +/** + * This is used to mark the IE event object so we do not do the Closure pass + * twice for a bubbling event. + * @param {Event} e The IE browser event. + * @private + */ +goog.events.markIeEvent_ = function(e) { + // Only the keyCode and the returnValue can be changed. We use keyCode for + // non keyboard events. + // event.returnValue is a bit more tricky. It is undefined by default. A + // boolean false prevents the default action. In a window.onbeforeunload and + // the returnValue is non undefined it will be alerted. However, we will only + // modify the returnValue for keyboard events. We can get a problem if non + // closure events sets the keyCode or the returnValue + + var useReturnValue = false; + + if (e.keyCode == 0) { + // We cannot change the keyCode in case that srcElement is input[type=file]. + // We could test that that is the case but that would allocate 3 objects. + // If we use try/catch we will only allocate extra objects in the case of a + // failure. + /** @preserveTry */ + try { + e.keyCode = -1; + return; + } catch (ex) { + useReturnValue = true; + } + } + + if (useReturnValue || + /** @type {boolean|undefined} */ (e.returnValue) == undefined) { + e.returnValue = true; + } +}; + + +/** + * This is used to check if an IE event has already been handled by the Closure + * system so we do not do the Closure pass twice for a bubbling event. + * @param {Event} e The IE browser event. + * @return {boolean} True if the event object has been marked. + * @private + */ +goog.events.isMarkedIeEvent_ = function(e) { + return e.keyCode < 0 || e.returnValue != undefined; +}; + + +/** + * Counter to create unique event ids. + * @type {number} + * @private + */ +goog.events.uniqueIdCounter_ = 0; + + +/** + * Creates a unique event id. + * + * @param {string} identifier The identifier. + * @return {string} A unique identifier. + */ +goog.events.getUniqueId = function(identifier) { + return identifier + '_' + goog.events.uniqueIdCounter_++; +}; + + +/** + * Expando property for listener function wrapper for Object with + * handleEvent. + * @type {string} + * @private + */ +goog.events.LISTENER_WRAPPER_PROP_ = '__closure_events_fn_' + + Math.floor(Math.random() * 2147483648).toString(36); + + +/** + * @param {Object|Function} listener The listener function or an + * object that contains handleEvent method. + * @return {!Function} Either the original function or a function that + * calls obj.handleEvent. If the same listener is passed to this + * function more than once, the same function is guaranteed to be + * returned. + * @private + */ +goog.events.wrapListener_ = function(listener) { + if (goog.isFunction(listener)) { + return listener; + } + + return listener[goog.events.LISTENER_WRAPPER_PROP_] || + (listener[goog.events.LISTENER_WRAPPER_PROP_] = function(e) { + return listener.handleEvent(e); + }); +}; + + +// Register the browser event handler as an entry point, so that +// it can be monitored for exception handling, etc. +goog.debug.entryPointRegistry.register( + /** + * @param {function(!Function): !Function} transformer The transforming + * function. + */ + function(transformer) { + goog.events.handleBrowserEvent_ = transformer( + goog.events.handleBrowserEvent_); + }); +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Implementation of EventTarget as defined by W3C DOM 2/3. + * + * @author arv@google.com (Erik Arvidsson) [Original implementation] + * @author pupius@google.com (Daniel Pupius) [Port to use goog.events] + * @see ../demos/eventtarget.html + */ + +goog.provide('goog.events.EventTarget'); + +goog.require('goog.Disposable'); +goog.require('goog.events'); +goog.require('goog.events.Event'); +goog.require('goog.events.Listenable'); +goog.require('goog.events.Listener'); +goog.require('goog.object'); + + + +/** + * Inherit from this class to give your object the ability to dispatch events. + * Note that this class provides event sending behaviour, not event + * receiving behaviour: your object will be able to broadcast events, and other + * objects will be able to listen for those events using goog.events.listen(). + * + *

The name "EventTarget" reflects the fact that this class implements the + * + * EventTarget interface as defined by W3C DOM 2/3, with a few differences: + *

    + *
  • Event objects do not have to implement the Event interface. An object + * is treated as an event object if it has a 'type' property. + *
  • You can use a plain string instead of an event object; an event-like + * object will be created with the 'type' set to the string value. + *
+ * + *

Unless propagation is stopped, an event dispatched by an EventTarget + * will bubble to the parent returned by getParentEventTarget. + * To set the parent, call setParentEventTarget or override + * getParentEventTarget in a subclass. Subclasses that don't + * support changing the parent should override the setter to throw an error. + * + *

Example usage: + *

+ *   var source = new goog.events.EventTarget();
+ *   function handleEvent(event) {
+ *     alert('Type: ' + e.type + '\nTarget: ' + e.target);
+ *   }
+ *   goog.events.listen(source, 'foo', handleEvent);
+ *   ...
+ *   source.dispatchEvent({type: 'foo'}); // will call handleEvent
+ *   // or source.dispatchEvent('foo');
+ *   ...
+ *   goog.events.unlisten(source, 'foo', handleEvent);
+ *
+ *   // You can also use the Listener interface:
+ *   var listener = {
+ *     handleEvent: function(event) {
+ *       ...
+ *     }
+ *   };
+ *   goog.events.listen(source, 'bar', listener);
+ * 
+ * + * @constructor + * @extends {goog.Disposable} + * @implements {goog.events.Listenable} + */ +goog.events.EventTarget = function() { + goog.Disposable.call(this); + + if (goog.events.Listenable.USE_LISTENABLE_INTERFACE) { + /** + * Maps of event type to an array of listeners. + * + * @type {Object.>} + * @private + */ + this.eventTargetListeners_ = {}; + + /** + * Whether the EventTarget has been disposed. This is only true + * when disposeInternal of EventTarget is completed (whereas + * this.isDisposed() is true the moment obj.dispose() is called, + * even before calling its disposeInternal). + * @type {boolean} + * @private + */ + this.reallyDisposed_ = false; + } +}; +goog.inherits(goog.events.EventTarget, goog.Disposable); + + +if (goog.events.Listenable.USE_LISTENABLE_INTERFACE) { + goog.events.Listenable.addImplementation(goog.events.EventTarget); +} + + +/** + * Used to tell if an event is a real event in goog.events.listen() so we don't + * get listen() calling addEventListener() and vice-versa. + * @type {boolean} + * @private + */ +goog.events.EventTarget.prototype.customEvent_ = true; + + +/** + * Parent event target, used during event bubbling. + * @type {goog.events.EventTarget?} + * @private + */ +goog.events.EventTarget.prototype.parentEventTarget_ = null; + + +/** + * Returns the parent of this event target to use for bubbling. + * + * @return {goog.events.EventTarget} The parent EventTarget or null if there + * is no parent. + */ +goog.events.EventTarget.prototype.getParentEventTarget = function() { + return this.parentEventTarget_; +}; + + +/** + * Sets the parent of this event target to use for bubbling. + * + * @param {goog.events.EventTarget?} parent Parent EventTarget (null if none). + */ +goog.events.EventTarget.prototype.setParentEventTarget = function(parent) { + this.parentEventTarget_ = parent; +}; + + +/** + * Adds an event listener to the event target. The same handler can only be + * added once per the type. Even if you add the same handler multiple times + * using the same type then it will only be called once when the event is + * dispatched. + * + * Supported for legacy but use goog.events.listen(src, type, handler) instead. + * + * @param {string} type The type of the event to listen for. + * @param {Function|Object} handler The function to handle the event. The + * handler can also be an object that implements the handleEvent method + * which takes the event object as argument. + * @param {boolean=} opt_capture In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase + * of the event. + * @param {Object=} opt_handlerScope Object in whose scope to call + * the listener. + */ +goog.events.EventTarget.prototype.addEventListener = function( + type, handler, opt_capture, opt_handlerScope) { + goog.events.listen(this, type, handler, opt_capture, opt_handlerScope); +}; + + +/** + * Removes an event listener from the event target. The handler must be the + * same object as the one added. If the handler has not been added then + * nothing is done. + * @param {string} type The type of the event to listen for. + * @param {Function|Object} handler The function to handle the event. The + * handler can also be an object that implements the handleEvent method + * which takes the event object as argument. + * @param {boolean=} opt_capture In DOM-compliant browsers, this determines + * whether the listener is fired during the capture or bubble phase + * of the event. + * @param {Object=} opt_handlerScope Object in whose scope to call + * the listener. + */ +goog.events.EventTarget.prototype.removeEventListener = function( + type, handler, opt_capture, opt_handlerScope) { + goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope); +}; + + +/** @override */ +goog.events.EventTarget.prototype.dispatchEvent = function(e) { + if (goog.events.Listenable.USE_LISTENABLE_INTERFACE) { + if (this.reallyDisposed_) { + return true; + } + + var ancestorsTree, ancestor = this.getParentEventTarget(); + if (ancestor) { + ancestorsTree = []; + for (; ancestor; ancestor = ancestor.getParentEventTarget()) { + ancestorsTree.push(ancestor); + } + } + + return goog.events.EventTarget.dispatchEventInternal_( + this, e, ancestorsTree); + } else { + return goog.events.dispatchEvent(this, e); + } +}; + + +/** + * Unattach listeners from this object. Classes that extend EventTarget may + * need to override this method in order to remove references to DOM Elements + * and additional listeners, it should be something like this: + *
+ * MyClass.prototype.disposeInternal = function() {
+ *   MyClass.superClass_.disposeInternal.call(this);
+ *   // Dispose logic for MyClass
+ * };
+ * 
+ * @override + * @protected + */ +goog.events.EventTarget.prototype.disposeInternal = function() { + goog.events.EventTarget.superClass_.disposeInternal.call(this); + + if (goog.events.Listenable.USE_LISTENABLE_INTERFACE) { + this.removeAllListeners(); + this.reallyDisposed_ = true; + } else { + goog.events.removeAll(this); + } + + this.parentEventTarget_ = null; +}; + + +if (goog.events.Listenable.USE_LISTENABLE_INTERFACE) { + +/** @override */ +goog.events.EventTarget.prototype.listen = function( + type, listener, opt_useCapture, opt_listenerScope) { + return this.listenInternal_( + type, listener, false /* callOnce */, opt_useCapture, opt_listenerScope); +}; + + +/** @override */ +goog.events.EventTarget.prototype.listenOnce = function( + type, listener, opt_useCapture, opt_listenerScope) { + return this.listenInternal_( + type, listener, true /* callOnce */, opt_useCapture, opt_listenerScope); +}; + + +/** + * Adds an event listener. A listener can only be added once to an + * object and if it is added again the key for the listener is + * returned. + * + * Note that a one-off listener will not change an existing listener, + * if any. On the other hand a normal listener will change existing + * one-off listener to become a normal listener. + * + * @param {string} type Event type to listen to. + * @param {!Function} listener Callback method. + * @param {boolean} callOnce Whether the listener is a one-off + * listener or otherwise. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {Object=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} Unique key for the listener. + * @private + */ +goog.events.EventTarget.prototype.listenInternal_ = function( + type, listener, callOnce, opt_useCapture, opt_listenerScope) { + goog.asserts.assert( + !this.reallyDisposed_, 'Can not listen on disposed object.'); + + var listenerArray = this.eventTargetListeners_[type] || + (this.eventTargetListeners_[type] = []); + + var listenerObj; + var index = goog.events.EventTarget.findListenerIndex_( + listenerArray, listener, opt_useCapture, opt_listenerScope); + if (index > -1) { + listenerObj = listenerArray[index]; + if (!callOnce) { + // Ensure that, if there is an existing callOnce listener, it is no + // longer a callOnce listener. + listenerObj.callOnce = false; + } + return listenerObj; + } + + listenerObj = new goog.events.Listener(); + listenerObj.init( + listener, null, this, type, !!opt_useCapture, opt_listenerScope); + listenerObj.callOnce = callOnce; + listenerArray.push(listenerObj); + + return listenerObj; +}; + + +/** @override */ +goog.events.EventTarget.prototype.unlisten = function( + type, listener, opt_useCapture, opt_listenerScope) { + if (!(type in this.eventTargetListeners_)) { + return false; + } + + var listenerArray = this.eventTargetListeners_[type]; + var index = goog.events.EventTarget.findListenerIndex_( + listenerArray, listener, opt_useCapture, opt_listenerScope); + if (index > -1) { + var listenerObj = listenerArray[index]; + goog.events.cleanUp(listenerObj); + listenerObj.removed = true; + return goog.array.removeAt(listenerArray, index); + } + return false; +}; + + +/** @override */ +goog.events.EventTarget.prototype.unlistenByKey = function(key) { + var type = key.type; + if (!(type in this.eventTargetListeners_)) { + return false; + } + + var removed = goog.array.remove(this.eventTargetListeners_[type], key); + if (removed) { + goog.events.cleanUp(key); + key.removed = true; + } + return removed; +}; + + +/** @override */ +goog.events.EventTarget.prototype.removeAllListeners = function( + opt_type, opt_capture) { + var count = 0; + for (var type in this.eventTargetListeners_) { + if (!opt_type || type == opt_type) { + var listenerArray = this.eventTargetListeners_[type]; + for (var i = 0; i < listenerArray.length; i++) { + ++count; + goog.events.cleanUp(listenerArray[i]); + listenerArray[i].removed = true; + } + listenerArray.length = 0; + } + } + return count; +}; + + +/** @override */ +goog.events.EventTarget.prototype.fireListeners = function( + type, capture, eventObject) { + goog.asserts.assert( + !this.reallyDisposed_, + 'Can not fire listeners after dispose() completed.'); + + if (!(type in this.eventTargetListeners_)) { + return true; + } + + var rv = true; + var listenerArray = goog.array.clone(this.eventTargetListeners_[type]); + for (var i = 0; i < listenerArray.length; ++i) { + var listener = listenerArray[i]; + // We might not have a listener if the listener was removed. + if (listener && !listener.removed && listener.capture == capture) { + // TODO(user): This logic probably should be in the Listener + // object instead. + if (listener.callOnce) { + this.unlistenByKey(listener); + } + rv = listener.handleEvent(eventObject) !== false && rv; + } + } + + return rv && eventObject.returnValue_ != false; +}; + + +/** @override */ +goog.events.EventTarget.prototype.getListeners = function(type, capture) { + var listenerArray = this.eventTargetListeners_[type]; + var rv = []; + if (listenerArray) { + for (var i = 0; i < listenerArray.length; ++i) { + var listenerObj = listenerArray[i]; + if (listenerObj.capture == capture) { + rv.push(listenerObj); + } + } + } + return rv; +}; + + +/** @override */ +goog.events.EventTarget.prototype.getListener = function( + type, listener, capture, opt_listenerScope) { + var listenerArray = this.eventTargetListeners_[type]; + var i = -1; + if (listenerArray) { + i = goog.events.EventTarget.findListenerIndex_( + listenerArray, listener, capture, opt_listenerScope); + } + return i > -1 ? listenerArray[i] : null; +}; + + +/** @override */ +goog.events.EventTarget.prototype.hasListener = function( + opt_type, opt_capture) { + var hasType = goog.isDef(opt_type); + var hasCapture = goog.isDef(opt_capture); + + return goog.object.some( + this.eventTargetListeners_, function(listenersArray, type) { + for (var i = 0; i < listenersArray.length; ++i) { + if ((!hasType || listenersArray[i].type == opt_type) && + (!hasCapture || listenersArray[i].capture == opt_capture)) { + return true; + } + } + + return false; + }); +}; + + +/** + * Dispatches the given event on the ancestorsTree. + * + * TODO(user): Look for a way to reuse this logic in + * goog.events, if possible. + * + * @param {!goog.events.EventTarget} target The target to dispatch on. + * @param {goog.events.Event|Object|string} e The event object. + * @param {Array.=} opt_ancestorsTree The ancestors + * tree of the target, in reverse order from the closest ancestor + * to the root event target. May be null if the target has no ancestor. + * @return {boolean} If anyone called preventDefault on the event object (or + * if any of the listeners returns false this will also return false. + * @private + */ +goog.events.EventTarget.dispatchEventInternal_ = function( + target, e, opt_ancestorsTree) { + var type = e.type || /** @type {string} */ (e); + + // If accepting a string or object, create a custom event object so that + // preventDefault and stopPropagation work with the event. + if (goog.isString(e)) { + e = new goog.events.Event(e, target); + } else if (!(e instanceof goog.events.Event)) { + var oldEvent = e; + e = new goog.events.Event(type, target); + goog.object.extend(e, oldEvent); + } else { + e.target = e.target || target; + } + + var rv = true, currentTarget; + + // Executes all capture listeners on the ancestors, if any. + if (opt_ancestorsTree) { + for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0; + i--) { + currentTarget = e.currentTarget = opt_ancestorsTree[i]; + rv = currentTarget.fireListeners(type, true, e) && rv; + } + } + + // Executes capture and bubble listeners on the target. + if (!e.propagationStopped_) { + currentTarget = e.currentTarget = target; + rv = currentTarget.fireListeners(type, true, e) && rv; + if (!e.propagationStopped_) { + rv = currentTarget.fireListeners(type, false, e) && rv; + } + } + + // Executes all bubble listeners on the ancestors, if any. + if (opt_ancestorsTree) { + for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) { + currentTarget = e.currentTarget = opt_ancestorsTree[i]; + rv = currentTarget.fireListeners(type, false, e) && rv; + } + } + + return rv; +}; + + +/** + * Finds the index of a matching goog.events.Listener in the given + * listenerArray. + * @param {!Array.} listenerArray Array of listener. + * @param {!Function} listener The listener function. + * @param {boolean=} opt_useCapture The capture flag for the listener. + * @param {Object=} opt_listenerScope The listener scope. + * @return {number} The index of the matching listener within the + * listenerArray. + * @private + */ +goog.events.EventTarget.findListenerIndex_ = function( + listenerArray, listener, opt_useCapture, opt_listenerScope) { + for (var i = 0; i < listenerArray.length; ++i) { + var listenerObj = listenerArray[i]; + if (listenerObj.listener == listener && + listenerObj.capture == !!opt_useCapture && + listenerObj.handler == opt_listenerScope) { + return i; + } + } + return -1; +}; + +} // if (goog.events.Listenable.USE_LISTENABLE_INTERFACE) +/** + * @fileOverview Handles loading, parsing and executing the config file. + */ +goog.provide('Deppy.Config'); +goog.provide('Deppy.Config.EventType'); + +goog.require('goog.events.EventTarget'); +goog.require('goog.async.Deferred'); +goog.require('goog.net.jsloader'); +goog.require('goog.object'); + +goog.require('Deppy.ModuleLoader'); + +/** + * + * @extends {goog.events.EventTarget} + * @constructor + */ +Deppy.Config = function() { + + goog.base(this); + + /** + * @type {Deppy.ModuleLoader} + * @private + */ + this._loader = Deppy.ModuleLoader.getInstance(); + + /** + * @type {boolean} open when config script is loaded. + * @private + */ + this._loadFinish = false; + /** + * @type {boolean} open if config script failed + * @private + */ + this._errLoad = false; + + /** + * The config object as defined by the user. + * @type {?Object} + * @private + */ + this._config = null; + + /** + * The config defined scripts to load (vendor deps). + * @type {Array} + * @private + */ + this._scriptsToLoad = []; + + /** + * The baseUrl option as provided by the user. + * @type {string} + * @private + */ + this._baseUrl = ''; +}; +goog.inherits(Deppy.Config, goog.events.EventTarget); +goog.addSingletonGetter(Deppy.Config); + +/** + * @const {string} the name of the config file. + */ +Deppy.Config.CONFIG_FILE = 'deppyConf.js'; + + +/** + * Events triggered by this class. + * + * @enum {string} + */ +Deppy.Config.EventType = { + CONFIG_FINISH: 'config.finish' +}; + +/** + * Fetch the config file + */ +Deppy.Config.prototype.fetch = function() { + this._loader.writeScript(Deppy.Config.CONFIG_FILE); +}; + +/** + * Main API entry point for configuring deppy. + * Gets exposed as deppy.config() + * + * @param {Object} config The config object from the user. + */ +Deppy.Config.prototype.parse = function(config) { + + this._config = config; + this._baseUrl = config.baseUrl || ''; + + if (goog.isObject(config.paths)) { + this._scriptsToLoad = goog.object.getValues(config.paths); + this._startLoading(); + } else { + this._configDone(); + } +}; + +/** + * Start loading the vendor scripts synchronously and + * sequencially. + * + * @private + */ +Deppy.Config.prototype._startLoading = function() { + + var scriptPath = this._scriptsToLoad.shift(); + while(scriptPath) { + + scriptPath = this._baseUrl + scriptPath + Deppy.ModuleLoader.JS_EXT; + + this._loader.writeScript(scriptPath); + + scriptPath = this._scriptsToLoad.shift(); + } + + this._configDone(); + +}; + +/** + * Config operations are finished, move on. + * + * @private + */ +Deppy.Config.prototype._configDone = function() { + this.dispatchEvent(Deppy.Config.EventType.CONFIG_FINISH); +}; + + +/** + * @return {string|null} Return the dependency file is set, null if not. + */ +Deppy.Config.prototype.getDepFile = function() { + return this._config.depsFile || null; +}; + +goog.provide('Deppy.Core'); +goog.provide('deppy'); + +goog.require('Deppy.Config'); +goog.require('Deppy.Config.EventType'); +goog.require('Deppy.ModuleLoader'); + +/** + * The main constructor + * + * @constructor + */ +Deppy.Core = function() { + + /** + * Instanciate the config class + * @type {Deppy.Config} + * @private + */ + this._config = Deppy.Config.getInstance(); + + /** + * Get the module loader singleton + * @type {Deppy.ModuleLoader} + * @private + */ + this._moduleLoader = Deppy.ModuleLoader.getInstance(); + + // expose config parse method + this.config = goog.bind(this._config.parse, this._config); + // expose startApp method (invoked internally) + this.startApp = goog.bind(this._moduleLoader.startApp, this._moduleLoader); + + goog.events.listen(this._config, Deppy.Config.EventType.CONFIG_FINISH, + this._moduleLoader.start, false, this._moduleLoader); + + + // fetch config + this._config.fetch(); + +}; + +// go +deppy = new Deppy.Core(); +/*! + * deppy. Javascript dependency management. + * @author Athanasios Polychronakis (thanpolas@gmail.com) + * @license BSD + * Copyright (c) 2010-2011 Christian Johansen + */ + + +/** + * @package deppy Javascript dependency management. + * createdate 01/Feb/2013 + */ + +/** @fileoverview deppy bootstrap file */ + +goog.provide('Deppy'); + +goog.require('Deppy.Config'); + +// Deppy.Core is special, is our core. +goog.require('Deppy.Core'); + diff --git a/js/lib/ember-latest.min.js b/js/lib/ember-latest.min.js new file mode 100644 index 0000000..34b1219 --- /dev/null +++ b/js/lib/ember-latest.min.js @@ -0,0 +1,18 @@ +// ========================================================================== +// Project: Ember - JavaScript Application Framework +// Copyright: ©2011-2012 Tilde Inc. and contributors +// Portions ©2006-2011 Strobe Inc. +// Portions ©2008-2011 Apple Inc. All rights reserved. +// License: Licensed under MIT license (see license.js) +// ========================================================================== + + +// Version: v0.9.8.1-484-g73ac0a4 +// Last commit: 73ac0a4 (2012-07-06 11:52:32 -0700) + + +(function(){"undefined"==typeof Ember&&(Ember={}),"undefined"!=typeof window&&(window.Em=window.Ember=Em=Ember),Ember.isNamespace=!0,Ember.toString=function(){return"Ember"},Ember.VERSION="1.0.pre",Ember.ENV="undefined"==typeof ENV?{}:ENV,Ember.config=Ember.config||{},Ember.EXTEND_PROTOTYPES=Ember.ENV.EXTEND_PROTOTYPES!==!1,Ember.SHIM_ES5=Ember.ENV.SHIM_ES5===!1?!1:Ember.EXTEND_PROTOTYPES,Ember.CP_DEFAULT_CACHEABLE=Ember.ENV.CP_DEFAULT_CACHEABLE!==!1,Ember.VIEW_PRESERVES_CONTEXT=Ember.ENV.VIEW_PRESERVES_CONTEXT!==!1,Ember.K=function(){return this},"undefined"==typeof Ember.assert&&(Ember.assert=Ember.K),"undefined"==typeof Ember.warn&&(Ember.warn=Ember.K),"undefined"==typeof Ember.deprecate&&(Ember.deprecate=Ember.K),"undefined"==typeof Ember.deprecateFunc&&(Ember.deprecateFunc=function(a,b){return b}),"undefined"==typeof ember_assert&&(window.ember_assert=Ember.K),"undefined"==typeof ember_warn&&(window.ember_warn=Ember.K),"undefined"==typeof ember_deprecate&&(window.ember_deprecate=Ember.K),"undefined"==typeof ember_deprecateFunc&&(window.ember_deprecateFunc=function(a,b){return b}),Ember.Logger=window.console||{log:Ember.K,warn:Ember.K,error:Ember.K}})(),function(){var a=function(a){return a&&Function.prototype.toString.call(a).indexOf("[native code]")>-1},b=a(Array.prototype.map)?Array.prototype.map:function(a){if(this===void 0||this===null)throw new TypeError;var b=Object(this),c=b.length>>>0;if(typeof a!="function")throw new TypeError;var d=new Array(c),e=arguments[1];for(var f=0;f>>0;if(typeof a!="function")throw new TypeError;var d=arguments[1];for(var e=0;e-1&&f.splice(g,1)},isEmpty:function(){return this.list.length===0},forEach:function(a,b){var c=this.list.slice();for(var d=0,e=c.length;d0?(e=b.slice(b.lastIndexOf(".")+1),b=b.slice(0,b.length-(e.length+1)),b!=="this"&&(a=Ember.getPath(a,b))):e=b;if(!e||e.length===0)throw new Error("You passed an empty path");if(!a){if(d)return;throw new Error("Object in path "+b+" could not be found or was destroyed.")}return Ember.set(a,e,c)},Ember.trySetPath=function(a,b,c){return Ember.setPath(a,b,c,!0)},Ember.isGlobalPath=function(a){return i.test(a)}}(),function(){function m(a,b,c){var d=c[b];return d===undefined&&a.unknownProperty&&(d=a.unknownProperty(b)),d}function n(a,b){return m(a,b,a)}function p(a,b){return m(a,b,d(a,!1).values||o)}function s(a,b,c){var e=d(a),f=e.values,g=c!==f[b];return g&&(Ember.propertyWillChange(a,b),r(a,b,c,e.values),Ember.propertyDidChange(a,b)),c}function t(a){return function(){return p(this,a)}}function u(a){return function(b){return s(this,a,b)}}function v(a,b){return b==="toString"?"function"!=typeof a.toString:!!a[b]}var a=Ember.USE_ACCESSORS,b=Ember.GUID_KEY,c=Ember.META_KEY,d=Ember.meta,e=Ember.platform.defineProperty,f,g,h=Ember.Descriptor=function(){},i=h.setup=function(a,b,c){e(a,b,{writable:!0,configurable:!0,enumerable:!0,value:c})},j=Ember.Descriptor.prototype;j.set=function(a,b,c){return a[b]=c,c},j.get=function(a,b){return n(a,b,a)},j.setup=i,j.teardown=function(a,b){return a[b]},j.val=function(a,b){return a[b]};var k=Ember.Descriptor.MUST_USE_SETTER=function(){this instanceof Ember.Object&&!this.isDestroyed},l={configurable:!0,enumerable:!0,set:k},o={},q=Ember.platform.hasPropertyAccessors,r;r=function(a,b,c,d){d[b]=c},q||(r=function(a,b,c,d){a[b]=c,d[b]=c}),g=new Ember.Descriptor,g.get=p,g.set=s,g.setup=function(a,b,c){e(a,b,{configurable:!0,enumerable:!0,set:k,get:t(b)}),d(a).values[b]=c},g.teardown=function(a,b){var c=d(a).values[b];return delete d(a).values[b],c},Ember.SIMPLE_PROPERTY=new Ember.Descriptor,f=Ember.SIMPLE_PROPERTY,f.unwatched=g.unwatched=f,f.watched=g.watched=g,Ember.defineProperty=function(a,b,c,g){var h=d(a,!1),i=h.descs,j=h.watching[b]>0,k=!0;return g===undefined?(k=!1,g=v(i,b)?i[b].teardown(a,b):a[b]):v(i,b)&&i[b].teardown(a,b),c||(c=f),c instanceof Ember.Descriptor?(h=d(a,!0),i=h.descs,c=(j?c.watched:c.unwatched)||c,i[b]=c,c.setup(a,b,g,j)):(i[b]&&(d(a).descs[b]=null),e(a,b,c)),k&&j&&Ember.overrideChains(a,b,h),this}}(),function(){function i(a,b){var d=c(a),e=d.deps,f;return e?e.__emberproto__!==a&&(e=d.deps=g(e),e.__emberproto__=a):e=d.deps={__emberproto__:a},f=e[b],f?f.__emberproto__!==a&&(f=e[b]=g(f),f.__emberproto__=a):f=e[b]={__emberproto__:a},f}function j(a,b,c){var d=i(a,c);d[b]=(d[b]||0)+1,Ember.watch(a,c)}function k(a,b,c){var d=i(a,c);d[b]=(d[b]||0)-1,Ember.unwatch(a,c)}function l(a,b,c){var d=a._dependentKeys,e=d?d.length:0;for(var f=0;f0,i=b._suspended,j;return b._suspended=this,h&&Ember.propertyWillChange(this,a),d&&delete g.cache[a],j=e.call(this,a,f),d&&(g.cache[a]=j),h&&Ember.propertyDidChange(this,a),b._suspended=i,j}}var a=Ember.get,b=Ember.getPath,c=Ember.meta,d=Ember.guidFor,e=Ember.USE_ACCESSORS,f=[].slice,g=Ember.create,h=Ember.platform.defineProperty;Ember.ComputedProperty=m,m.prototype=new Ember.Descriptor;var n={configurable:!0,enumerable:!0,get:function(){return undefined},set:Ember.Descriptor.MUST_USE_SETTER},q=m.prototype;q.cacheable=function(a){return this._cacheable=a!==!1,this},q.volatile=function(){return this.cacheable(!1)},q.property=function(){return this._dependentKeys=f.call(arguments),this},q.meta=function(a){return this._meta=a,this},q.setup=function(a,b,c){n.get=o(b,this),n.set=p(b,this),h(a,b,n),n.get=n.set=null,l(this,a,b)},q.teardown=function(a,b){var d=this._dependentKeys,e=d?d.length:0;for(var f=0;f0,h=this._suspended,i;return this._suspended=a,g&&Ember.propertyWillChange(a,b),e&&delete f.cache[b],i=this.func.call(a,b,d),e&&(f.cache[b]=i),g&&Ember.propertyDidChange(a,b),this._suspended=h,i},q.val=function(a,b){return c(a,!1).values[b]},Ember.platform.hasPropertyAccessors?e||(q.setup=function(a,b){h(a,b,n),l(this,a,b)}):q.setup=function(a,b,c){a[b]=undefined,l(this,a,b)},Ember.computed=function(a){var b;arguments.length>1&&(b=f.call(arguments,0,-1),a=f.call(arguments,-1)[0]);var c=new m(a);return b&&c.property.apply(c,b),c},Ember.cacheFor=function(a,b){var d=c(a,!1).cache;if(d&&b in d)return d[b]},Ember.computed.not=function(a){return Ember.computed(a,function(c){return!b(this,a)}).cacheable()},Ember.computed.empty=function(c){return Ember.computed(c,function(d){var e=b(this,c);return e===undefined||e===null||e===""||Ember.isArray(e)&&a(e,"length")===0}).cacheable()},Ember.computed.bool=function(a){return Ember.computed(a,function(c){return!!b(this,a)}).cacheable()}}(),function(){function j(a,b,c){d&&!c?h.push(a,b):Ember.sendEvent(a,b)}function k(){i.clear(),h.flush()}function l(b){return b+a}function m(a){return a+b}function n(a){return a.slice(0,-7)}function o(a){return a.slice(0,-7)}function p(a){return function(b,c,d){var e=d[0],f=n(d[1]),g,h=a.slice();c.length>2&&(g=Ember.getPath(Ember.isGlobalPath(f)?window:e,f)),h.unshift(e,f,g),c.apply(b,h)}}function r(a,b,c){var d=c[0],e=o(c[1]),f;b.length>2&&(f=Ember.getPath(d,e)),b.call(a,d,e,f)}var a=":change",b=":before",c=Ember.guidFor,d=0,e=[].slice,f=function(){this.targetSet={}};f.prototype.add=function(a,b){var c=this.targetSet,d=Ember.guidFor(a),e=c[d];return e||(c[d]=e={}),e[b]?!1:e[b]=!0},f.prototype.clear=function(){this.targetSet={}};var g=function(){this.targetSet={},this.queue=[]};g.prototype.push=function(a,b){var c=this.targetSet,d=this.queue,e=Ember.guidFor(a),f=c[e],g;f||(c[e]=f={}),g=f[b],g===undefined?f[b]=d.push(Ember.deferEvent(a,b))-1:d[g]=Ember.deferEvent(a,b)},g.prototype.flush=function(){var a=this.queue;this.queue=[],this.targetSet={};for(var b=0,c=a.length;b4){var g=e.call(arguments,4);f=p(g)}else f=q;return Ember.addListener(a,l(b),c,d,f),Ember.watch(a,b),this},Ember.observersFor=function(a,b){return Ember.listenersFor(a,l(b))},Ember.removeObserver=function(a,b,c,d){return Ember.unwatch(a,b),Ember.removeListener(a,l(b),c,d),this},Ember.addBeforeObserver=function(a,b,c,d){return Ember.addListener(a,m(b),c,d,r),Ember.watch(a,b),this},Ember._suspendBeforeObserver=function(a,b,c,d,e){return Ember._suspendListener(a,m(b),c,d,e)},Ember._suspendObserver=function(a,b,c,d,e){return Ember._suspendListener(a,l(b),c,d,e)},Ember.beforeObserversFor=function(a,b){return Ember.listenersFor(a,m(b))},Ember.removeBeforeObserver=function(a,b,c,d){return Ember.unwatch(a,b),Ember.removeListener(a,m(b),c,d),this},Ember.notifyObservers=function(a,b){if(a.isDestroying)return;j(a,l(b))},Ember.notifyBeforeObservers=function(a,b){if(a.isDestroying)return;var c,e,f=!1;if(d){if(!i.add(a,b))return;f=!0}j(a,m(b),f)}}(),function(){function m(a){return a.match(k)[0]}function n(a){return a==="*"||!l.test(a)}function p(b,c,d,e,f){var g=a(c);e[g]||(e[g]={});if(e[g][d])return;e[g][d]=!0;var h=f.deps;h=h&&h[d];if(h)for(var i in h){if(o[i])continue;b(c,i)}}function s(a,b,c){if(a.isDestroying)return;var d=q,e=!d;e&&(d=q={}),p(G,a,b,d,c),e&&(q=null)}function t(a,b,c){if(a.isDestroying)return;var d=r,e=!d;e&&(d=r={}),p(H,a,b,d,c),e&&(r=null)}function u(c,d,e){if(!c||"object"!=typeof c)return;var f=b(c),g=f.chainWatchers;if(!g||g.__emberproto__!==c)g=f.chainWatchers={__emberproto__:c};g[d]||(g[d]={}),g[d][a(e)]=e,Ember.watch(c,d)}function v(c,d,e){if(!c||"object"!=typeof c)return;var f=b(c,!1),g=f.chainWatchers;if(!g||g.__emberproto__!==c)return;g[d]&&delete g[d][a(e)],Ember.unwatch(c,d)}function x(){if(w.length===0)return;var a=w;w=[],j.call(a,function(a){a[0].add(a[1])})}function y(a){return b(a,!1).proto===a}function B(a){var c=b(a),d=c.chains;return d?d.value()!==a&&(d=c.chains=d.copy(a)):d=c.chains=new z(null,null,a),d}function C(a,b,c,d,e){var f=b.chainWatchers;if(!f||f.__emberproto__!==a)return;f=f[c];if(!f)return;for(var g in f){if(!f.hasOwnProperty(g))continue;f[g][d](e)}}function D(a,b,c){C(a,c,b,"willChange")}function E(a,b,c){C(a,c,b,"didChange")}function G(a,c,d){var e=b(a,!1),f=e.watching[c]>0||c==="length",g=e.proto,h=e.descs[c];if(!f)return;if(g===a)return;h&&h.willChange&&h.willChange(a,c),s(a,c,e),D(a,c,e),Ember.notifyBeforeObservers(a,c)}function H(a,c){var d=b(a,!1),e=d.watching[c]>0||c==="length",f=d.proto,g=d.descs[c];if(f===a)return;g&&g.didChange&&g.didChange(a,c);if(!e&&c!=="length")return;t(a,c,d),E(a,c,d),Ember.notifyObservers(a,c)}var a=Ember.guidFor,b=Ember.meta,c=Ember.get,d=Ember.set,e=Ember.normalizeTuple.primitive,f=Ember.SIMPLE_PROPERTY,g=Ember.GUID_KEY,h=Ember.META_KEY,i=Ember.notifyObservers,j=Ember.ArrayPolyfills.forEach,k=/^([^\.\*]+)/,l=/[\.\*]/,o={__emberproto__:!0},q,r,w=[],z=function(a,b,c,d){var e;this._parent=a,this._key=b,this._watching=c===undefined,this._value=c,this._separator=d||".",this._paths={},this._watching&&(this._object=a.value(),this._object&&u(this._object,this._key,this)),this._parent&&this._parent._key==="@each"&&this.value()},A=z.prototype;A.value=function(){if(this._value===undefined&&this._watching){var a=this._parent.value();this._value=a&&!y(a)?c(a,this._key):undefined}return this._value},A.destroy=function(){if(this._watching){var a=this._object;a&&v(a,this._key,this),this._watching=!1}},A.copy=function(a){var b=new z(null,null,a,this._separator),c=this._paths,d;for(d in c){if(c[d]<=0)continue;b.add(d)}return b},A.add=function(a){var b,c,d,f,g,h;h=this._paths,h[a]=(h[a]||0)+1,b=this.value(),c=e(b,a);if(c[0]&&c[0]===b)a=c[1],d=m(a),a=a.slice(d.length+1);else{if(!c[0]){w.push([this,a]),c.length=0;return}f=c[0],d=a.slice(0,0-(c[1].length+1)),g=a.slice(d.length,d.length+1),a=c[1]}c.length=0,this.chain(d,a,f,g)},A.remove=function(a){var b,c,d,f,g;g=this._paths,g[a]>0&&g[a]--,b=this.value(),c=e(b,a),c[0]===b?(a=c[1],d=m(a),a=a.slice(d.length+1)):(f=c[0],d=a.slice(0,0-(c[1].length+1)),a=c[1]),c.length=0,this.unchain(d,a)},A.count=0,A.chain=function(a,b,c,d){var e=this._chains,f;e||(e=this._chains={}),f=e[a],f||(f=e[a]=new z(this,a,c,d)),f.count++,b&&b.length>0&&(a=m(b),b=b.slice(a.length+1),f.chain(a,b))},A.unchain=function(a,b){var c=this._chains,d=c[a];b&&b.length>1&&(a=m(b),b=b.slice(a.length+1),d.unchain(a,b)),d.count--,d.count<=0&&(delete c[d._key],d.destroy())},A.willChange=function(){var a=this._chains;if(a)for(var b in a){if(!a.hasOwnProperty(b))continue;a[b].willChange()}this._parent&&this._parent.chainWillChange(this,this._key,1)},A.chainWillChange=function(a,b,c){this._key&&(b=this._key+this._separator+b),this._parent?this._parent.chainWillChange(this,b,c+1):(c>1&&Ember.propertyWillChange(this.value(),b),b="this."+b,this._paths[b]>0&&Ember.propertyWillChange(this.value(),b))},A.chainDidChange=function(a,b,c){this._key&&(b=this._key+this._separator+b),this._parent?this._parent.chainDidChange(this,b,c+1):(c>1&&Ember.propertyDidChange(this.value(),b),b="this."+b,this._paths[b]>0&&Ember.propertyDidChange(this.value(),b))},A.didChange=function(a){if(this._watching){var b=this._parent.value();b!==this._object&&(v(this._object,this._key,this),this._object=b,u(b,this._key,this)),this._value=undefined,this._parent&&this._parent._key==="@each"&&this.value()}var c=this._chains;if(c)for(var d in c){if(!c.hasOwnProperty(d))continue;c[d].didChange(a)}if(a)return;this._parent&&this._parent.chainDidChange(this,this._key,1)},Ember.overrideChains=function(a,b,c){C(a,c,b,"didChange",!0)};var F=Ember.SIMPLE_PROPERTY.watched;Ember.watch=function(a,c){if(c==="length"&&Ember.typeOf(a)==="array")return this;var d=b(a),e=d.watching,f;return e[c]?e[c]=(e[c]||0)+1:(e[c]=1,n(c)?("function"==typeof a.willWatchProperty&&a.willWatchProperty(c),f=d.descs[c],f=f?f.watched:F,f&&Ember.defineProperty(a,c,f)):B(a).add(c)),this},Ember.isWatching=function(a,c){return!!b(a).watching[c]},Ember.watch.flushPending=x,Ember.unwatch=function(a,c){if(c==="length"&&Ember.typeOf(a)==="array")return this;var d=b(a).watching,e,g;return d[c]===1?(d[c]=0,n(c)?(e=b(a).descs[c],e=e?e.unwatched:f,e&&Ember.defineProperty(a,c,e),"function"==typeof a.didUnwatchProperty&&a.didUnwatchProperty(c)):B(a).remove(c)):d[c]>1&&d[c]--,this},Ember.rewatch=function(a){var c=b(a,!1),d=c.chains,e=c.bindings,f,h;return g in a&&!a.hasOwnProperty(g)&&Ember.generateGuid(a,"ember"),d&&d.value()!==a&&B(a),this},Ember.propertyWillChange=G,Ember.propertyDidChange=H;var I=[];Ember.destroy=function(a){var b=a[h],c,d,e,f;if(b){a[h]=null,c=b.chains;if(c){I.push(c);while(I.length>0){c=I.pop(),d=c._chains;if(d)for(e in d)d.hasOwnProperty(e)&&I.push(d[e]);c._watching&&(f=c._object,f&&v(f,c._key,c))}}}}}(),function(){function f(a,b,e,f){return c(a,["listeners",b,d(e)],f)}function g(a,c){var d=b(a,!1).listeners;return d?d[c]||!1:!1}function i(a,b,c){if(!a)return!1;for(var d in a){if(h[d])continue;var e=a[d];if(e)for(var f in e){if(h[f])continue;var g=e[f];if(g&&b(g,c)===!0)return!0}}return!1}function j(a,b){var c=a.method,d=a.target,e=a.xform;d||(d=b[0]),"string"==typeof c&&(c=d[c]),e?e(d,c,b):c.apply(d,b)}function k(a,b,c,e,g){!e&&"function"==typeof c&&(e=c,c=null);var h=f(a,b,c,!0),i=d(e);h[i]?h[i].xform=g:h[i]={target:c,method:e,xform:g},"function"==typeof a.didAddListener&&a.didAddListener(b,c,e)}function l(a,b,c,e){!e&&"function"==typeof c&&(e=c,c=null);var g=f(a,b,c,!0),h=d(e);g&&g[h]&&(g[h]=null),a&&"function"==typeof a.didRemoveListener&&a.didRemoveListener(b,c,e)}function m(a,b,c,e,g){!e&&"function"==typeof c&&(e=c,c=null);var h=f(a,b,c,!0),i=d(e),j=h&&h[i];h[i]=null;try{return g.call(c)}finally{h[i]=j}}function n(a){var c=b(a,!1).listeners,d=[];if(c)for(var e in c)!h[e]&&c[e]&&d.push(e);return d}function o(a,b){return a!==Ember&&"function"==typeof a.sendEvent&&a.sendEvent.apply(a,e.call(arguments,1)),i(g(a,b),j,arguments),!0}function p(a,b){var c=g(a,b),d=[],f=arguments;return i(c,function(a){d.push(a)}),function(){if(a.isDestroyed)return;a!==Ember&&"function"==typeof a.sendEvent&&a.sendEvent.apply(a,e.call(f,1));for(var b=0,c=d.length;b0&&(d=d.length>e?a.call(d,e):null);if("function"!=typeof Ember.onerror)return c.apply(b||this,d||[]);try{return c.apply(b||this,d||[])}catch(f){Ember.onerror(f)}}function h(){g=null,f.currentRunLoop&&f.end()}function k(){j=null;var a=+(new Date),b=-1;for(var d in i){if(!i.hasOwnProperty(d))continue;var e=i[d];if(e&&e.expires)if(a>=e.expires)delete i[d],c(e.target,e.method,e.args,2);else if(b<0||e.expires0&&(j=setTimeout(k,b- +(new Date)))}function l(a,b){b[this.tguid]&&delete b[this.tguid][this.mguid],i[a]&&c(this.target,this.method,this.args,2),delete i[a]}function n(){m=null;for(var a in i){if(!i.hasOwnProperty(a))continue;var b=i[a];b.next&&(delete i[a],c(b.target,b.method,b.args,2))}}var a=[].slice,b=Ember.ArrayPolyfills.forEach,d,e=function(a){this._prev=a||null,this.onceTimers={}};e.prototype={end:function(){this.flush()},prev:function(){return this._prev},schedule:function(b,c,d){var e=this._queues,f;e||(e=this._queues={}),f=e[b],f||(f=e[b]=[]);var g=arguments.length>3?a.call(arguments,3):null;return f.push({target:c,method:d,args:g}),this},flush:function(a){function j(a){c(a.target,a.method,a.args)}var e,f,g,h,i;if(!this._queues)return this;Ember.watch.flushPending();if(a)while(this._queues&&(h=this._queues[a])){this._queues[a]=null;if(a==="sync"){i=Ember.LOG_BINDINGS,i&&Ember.Logger.log("Begin: Flush Sync Queue"),Ember.beginPropertyChanges();try{b.call(h,j)}finally{Ember.endPropertyChanges()}i&&Ember.Logger.log("End: Flush Sync Queue")}else b.call(h,j)}else{e=Ember.run.queues,g=e.length,f=0;a:while(f("+this._from+" -> "+this._to+")"+a},connect:function(a){var b=!this._oneWay;return Ember.addObserver(a,this._from,this,this.fromDidChange),b&&Ember.addObserver(a,this._to,this,this.toDidChange),Ember.meta(a,!1).proto!==a&&this._scheduleSync(a,"fwd"),this._readyToSync=!0,this},disconnect:function(a){var b=!this._oneWay;return Ember.removeObserver(a,this._from,this,this.fromDidChange),b&&Ember.removeObserver(a,this._to,this,this.toDidChange),this._readyToSync=!1,this},fromDidChange:function(a){this._scheduleSync(a,"fwd")},toDidChange:function(a){this._scheduleSync(a,"back")},_scheduleSync:function(a,b){var c=this._directionMap,d=c.get(a);d||(Ember.run.schedule("sync",this,this._sync,a),c.set(a,b)),d==="back"&&b==="fwd"&&c.set(a,"fwd")},_sync:function(a){var c=Ember.LOG_BINDINGS;if(a.isDestroyed||!this._readyToSync)return;var d=this._directionMap,e=d.get(a),g=this._from,h=this._to;d.remove(a);if(e==="fwd"){var i=f(a,this._from);c&&Ember.Logger.log(" ",this.toString(),"->",i,a),this._oneWay?Ember.trySetPath(Ember.isGlobalPath(h)?window:a,h,i):Ember._suspendObserver(a,h,this,this.toDidChange,function(){Ember.trySetPath(Ember.isGlobalPath(h)?window:a,h,i)})}else if(e==="back"){var j=b(a,this._to);c&&Ember.Logger.log(" ",this.toString(),"<-",j,a),Ember._suspendObserver(a,g,this,this.fromDidChange,function(){Ember.trySetPath(Ember.isGlobalPath(g)?window:a,g,j)})}}},h(g,{from:function(){var a=this,b=new a;return b.from.apply(b,arguments)},to:function(){var a=this,b=new a;return b.to.apply(b,arguments)},oneWay:function(a,b){var c=this,d=new c(null,a);return d.oneWay(b)}}),Ember.Binding=g,Ember.bind=function(a,b,c){return(new Ember.Binding(b,c)).connect(a)},Ember.oneWay=function(a,b,c){return(new Ember.Binding(b,c)).oneWay().connect(a)}}(),function(){function n(a,b){var c=Ember.meta(a,b!==!1),d=c.mixins;return b===!1?d||j:(d?d.__emberproto__!==a&&(d=c.mixins=l(d),d.__emberproto__=a):d=c.mixins={__emberproto__:a},d)}function o(b,c){return c&&c.length>0&&(b.mixins=f.call(c,function(b){if(b instanceof a)return b;var c=new a;return c.properties=b,c})),b}function q(a){return"function"!=typeof a||a.isMethod===!1?!1:g.call(p,a)<0}function r(c,d,e,f,i){function v(a){delete e[a],delete f[a]}var j=c.length,k,l,n,o,p,s,t,u;for(k=0;k=0||s==="concatenatedProperties"){var y=f[s]||i[s];p=y?y.concat(p):Ember.makeArray(p)}e[s]=Ember.SIMPLE_PROPERTY,f[s]=p}}o.hasOwnProperty("toString")&&(i.toString=o.toString)}else l.mixins&&(r(l.mixins,d,e,f,i),l._without&&h.call(l._without,v))}}function s(a){var b=Ember.meta(a),c=b.required;if(!c||c.__emberproto__!==a)c=b.required=c?l(c):{__ember_count__:0},c.__emberproto__=a;return c}function t(a){return"function"==typeof a&&a.__ember_observes__}function u(a){return"function"==typeof a&&a.__ember_observesBefore__}function w(a,b,c){if(v.test(b)){var d=c.bindings;d?d.__emberproto__!==a&&(d=c.bindings=l(c.bindings),d.__emberproto__=a):d=c.bindings={__emberproto__:a},d[b]=!0}}function x(a,b){var c=(b||Ember.meta(a)).bindings,d,e;if(c)for(d in c)e=d!=="__emberproto__"&&a[d],e&&(e instanceof Ember.Binding?(e=e.copy(),e.to(d.slice(0,-7))):e=new Ember.Binding(d.slice(0,-7),e),e.connect(a),a[d]=e)}function y(a,d,e){var f= +{},g={},h=Ember.meta(a),i=h.required,j,l,m,o,p;r(d,n(a),f,g,a),Ember.MixinDelegate.detect(a)&&(l=g.willApplyProperty||a.willApplyProperty,m=g.didApplyProperty||a.didApplyProperty);for(j in f){if(!f.hasOwnProperty(j))continue;p=f[j],o=g[j];if(p===b)j in a||(i=s(a),i.__ember_count__++,i[j]=!0);else{while(p instanceof c){var q=p.methodName;f[q]?(o=g[q],p=f[q]):h.descs[q]?(p=h.descs[q],o=p.val(a,q)):(o=a[q],p=Ember.SIMPLE_PROPERTY)}l&&l.call(a,j);var v=t(o),y=v&&t(a[j]),z=u(o),A=z&&u(a[j]),B,C;if(y){B=y.length;for(C=0;C0){var D=[];for(j in i){if(k[j])continue;D.push(j)}}return a}function B(a,b,c){var d=m(a);if(c[d])return!1;c[d]=!0;if(a===b)return!0;var e=a.mixins,f=e?e.length:0;while(--f>=0)if(B(e[f],b,c))return!0;return!1}function C(a,b,c){if(c[m(b)])return;c[m(b)]=!0;if(b.properties){var d=b.properties;for(var e in d)d.hasOwnProperty(e)&&(a[e]=!0)}else b.mixins&&h.call(b.mixins,function(b){C(a,b,c)})}function F(a,b,c){var e=a.length;for(var f in b){if(!b.hasOwnProperty||!b.hasOwnProperty(f))continue;var g=b[f];a[e]=f;if(g&&g.toString===d)g[D]=a.join(".");else if(g&&E(g,"isNamespace")){if(c[m(g)])continue;c[m(g)]=!0,F(a,g,c)}}a.length=e}function G(){var a=Ember.Namespace,b,c;if(a.PROCESSED)return;for(var d in window){if(d==="globalStorage"&&window.StorageList&&window.globalStorage instanceof window.StorageList)continue;if(window.hasOwnProperty&&!window.hasOwnProperty(d))continue;try{b=window[d],c=b&&E(b,"isNamespace")}catch(e){continue}c&&(b[D]=d)}}var a,b,c,d,e,f=Ember.ArrayPolyfills.map,g=Ember.ArrayPolyfills.indexOf,h=Ember.ArrayPolyfills.forEach,i=[].slice,j={},k={__emberproto__:!0,__ember_count__:!0},l=Ember.create,m=Ember.guidFor,p=[Boolean,Object,Number,Array,Date,String],v=Ember.IS_BINDING=/^.+Binding$/;Ember.mixin=function(a){var b=i.call(arguments,1);return y(a,b,!1)},Ember.Mixin=function(){return o(this,arguments)},a=Ember.Mixin,a._apply=y,a.applyPartial=function(a){var b=i.call(arguments,1);return y(a,b,!0)},a.finishPartial=function(a){return x(a),a},a.create=function(){d.processed=!1;var a=this;return o(new a,arguments)};var z=a.prototype;z.reopen=function(){var b,c;this.properties?(b=a.create(),b.properties=this.properties,delete this.properties,this.mixins=[b]):this.mixins||(this.mixins=[]);var d=arguments.length,e=this.mixins,f;for(f=0;f=0)return f[h];if(Ember.typeOf(b)==="array"){g=b.slice();if(c){h=g.length;while(--h>=0)g[h]=e(g[h],c,d,f)}}else if(Ember.Copyable&&Ember.Copyable.detect(b))g=b.copy(c,d,f);else{g={};for(i in b){if(!b.hasOwnProperty(i))continue;g[i]=c?e(b[i],c,d,f):b[i]}}return c&&(d.push(b),f.push(g)),g}var a=Ember.EnumerableUtils.indexOf,b={},c="Boolean Number String Function Array Date RegExp Object".split(" ");Ember.ArrayPolyfills.forEach.call(c,function(a){b["[object "+a+"]"]=a.toLowerCase()});var d=Object.prototype.toString;Ember.typeOf=function(a){var c;return c=a===null||a===undefined?String(a):b[d.call(a)]||"object",c==="function"?Ember.Object&&Ember.Object.detect(a)&&(c="class"):c==="object"&&(a instanceof Error?c="error":Ember.Object&&a instanceof Ember.Object?c="instance":c="object"),c},Ember.none=function(a){return a===null||a===undefined},Ember.empty=function(a){return a===null||a===undefined||a.length===0&&typeof a!="function"},Ember.compare=function f(a,b){if(a===b)return 0;var c=Ember.typeOf(a),d=Ember.typeOf(b),e=Ember.Comparable;if(e){if(c==="instance"&&e.detect(a.constructor))return a.constructor.compare(a,b);if(d==="instance"&&e.detect(b.constructor))return 1-b.constructor.compare(b,a)}var g=Ember.ORDER_DEFINITION_MAPPING;if(!g){var h=Ember.ORDER_DEFINITION;g=Ember.ORDER_DEFINITION_MAPPING={};var i,j;for(i=0,j=h.length;il)return 1;switch(c){case"boolean":case"number":if(ab)return 1;return 0;case"string":var m=a.localeCompare(b);if(m<0)return-1;if(m>0)return 1;return 0;case"array":var n=a.length,o=b.length,p=Math.min(n,o),q=0,r=0;while(q===0&&ro)return 1;return 0;case"instance":if(Ember.Comparable&&Ember.Comparable.detect(a))return a.compare(a,b);return 0;default:return 0}},Ember.copy=function(a,b){return"object"!=typeof a||a===null?a:Ember.Copyable&&Ember.Copyable.detect(a)?a.copy(b):e(a,b,b?[]:null,b?[]:null)},Ember.inspect=function(a){var b,c=[];for(var d in a)if(a.hasOwnProperty(d)){b=a[d];if(b==="toString")continue;Ember.typeOf(b)==="function"&&(b="function() { ... }"),c.push(d+": "+b)}return"{"+c.join(" , ")+"}"},Ember.isEqual=function(a,b){return a&&"function"==typeof a.isEqual?a.isEqual(b):a===b},Ember.ORDER_DEFINITION=Ember.ENV.ORDER_DEFINITION||["undefined","null","boolean","number","string","array","object","instance","function","class"],Ember.keys=Object.keys,Ember.keys||(Ember.keys=function(a){var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c);return b}),Ember.Error=function(){var a=Error.prototype.constructor.apply(this,arguments);for(var b in a)a.hasOwnProperty(b)&&(this[b]=a[b]);this.message=a.message},Ember.Error.prototype=Ember.create(Error.prototype)}(),function(){var a=/[ _]/g,b={},c=/([a-z])([A-Z])/g,d=/(\-|_|\s)+(.)?/g,e=/([a-z\d])([A-Z]+)/g,f=/\-|\s+/g;Ember.STRINGS={},Ember.String={fmt:function(a,b){var c=0;return a.replace(/%@([0-9]+)?/g,function(a,d){return d=d?parseInt(d,0)-1:c++,a=b[d],(a===null?"(null)":a===undefined?"":a).toString()})},loc:function(a,b){return a=Ember.STRINGS[a]||a,Ember.String.fmt(a,b)},w:function(a){return a.split(/\s+/)},decamelize:function(a){return a.replace(c,"$1_$2").toLowerCase()},dasherize:function(c){var d=b,e=d[c];return e?e:(e=Ember.String.decamelize(c).replace(a,"-"),d[c]=e,e)},camelize:function(a){return a.replace(d,function(a,b,c){return c?c.toUpperCase():""})},classify:function(a){var b=Ember.String.camelize(a);return b.charAt(0).toUpperCase()+b.substr(1)},underscore:function(a){return a.replace(e,"$1_$2").replace(f,"_").toLowerCase()}}}(),function(){var a=Ember.String.fmt,b=Ember.String.w,c=Ember.String.loc,d=Ember.String.camelize,e=Ember.String.decamelize,f=Ember.String.dasherize,g=Ember.String.underscore;Ember.EXTEND_PROTOTYPES&&(String.prototype.fmt=function(){return a(this,arguments)},String.prototype.w=function(){return b(this)},String.prototype.loc=function(){return c(this,arguments)},String.prototype.camelize=function(){return d(this)},String.prototype.decamelize=function(){return e(this)},String.prototype.dasherize=function(){return f(this)},String.prototype.underscore=function(){return g(this)})}(),function(){var a=Array.prototype.slice;Ember.EXTEND_PROTOTYPES&&(Function.prototype.property=function(){var a=Ember.computed(this);return a.property.apply(a,arguments)},Function.prototype.observes=function(){return this.__ember_observes__=a.call(arguments),this},Function.prototype.observesBefore=function(){return this.__ember_observesBefore__=a.call(arguments),this})}(),function(){}(),function(){function f(){return e.length===0?{}:e.pop()}function g(a){return e.push(a),null}function h(b,c){function e(e){var f=a(e,b);return d?c===f:!!f}var d=arguments.length===2;return e}function i(a,b,c){b.call(a,c[0],c[2],c[3])}var a=Ember.get,b=Ember.set,c=Array.prototype.slice,d=Ember.EnumerableUtils.indexOf,e=[];Ember.Enumerable=Ember.Mixin.create({isEnumerable:!0,nextObject:Ember.required(Function),firstObject:Ember.computed(function(){if(a(this,"length")===0)return undefined;var b=f(),c;return c=this.nextObject(0,null,b),g(b),c}).property("[]").cacheable(),lastObject:Ember.computed(function(){var b=a(this,"length");if(b===0)return undefined;var c=f(),d=0,e,h=null;do h=e,e=this.nextObject(d++,h,c);while(e!==undefined);return g(c),h}).property("[]").cacheable(),contains:function(a){return this.find(function(b){return b===a})!==undefined},forEach:function(b,c){if(typeof b!="function")throw new TypeError;var d=a(this,"length"),e=null,h=f();c===undefined&&(c=null);for(var i=0;i1&&(b=c.call(arguments,1)),this.forEach(function(c,e){var f=c&&c[a];"function"==typeof f&&(d[e]=b?f.apply(c,b):f.call(c))},this),d},toArray:function(){var a=[];return this.forEach(function(b,c){a[c]=b}),a},compact:function(){return this.without(null)},without:function(a){if(!this.contains(a))return this;var b=[];return this.forEach(function(c){c!==a&&(b[b.length]=c)}),b},uniq:function(){var a=[];return this.forEach(function(b){d(a,b)<0&&a.push(b)}),a},"[]":Ember.computed(function(a,b){return this}).property().cacheable(),addEnumerableObserver:function(b,c){var d=c&&c.willChange||"enumerableWillChange",e=c&&c.didChange||"enumerableDidChange",f=a(this,"hasEnumerableObservers");return f||Ember.propertyWillChange(this,"hasEnumerableObservers"),Ember.addListener(this,"@enumerable:before",b,d,i),Ember.addListener(this,"@enumerable:change",b,e,i),f||Ember.propertyDidChange(this,"hasEnumerableObservers"),this},removeEnumerableObserver:function(b,c){var d=c&&c.willChange||"enumerableWillChange",e=c&&c.didChange||"enumerableDidChange",f=a(this,"hasEnumerableObservers");return f&&Ember.propertyWillChange(this,"hasEnumerableObservers"),Ember.removeListener(this,"@enumerable:before",b,d),Ember.removeListener(this,"@enumerable:change",b,e),f&&Ember.propertyDidChange(this,"hasEnumerableObservers"),this},hasEnumerableObservers:Ember.computed(function(){return Ember.hasListeners(this,"@enumerable:change")||Ember.hasListeners(this,"@enumerable:before")}).property().cacheable(),enumerableContentWillChange:function(b,c){var d,e,f;return"number"==typeof b?d=b:b?d=a(b,"length"):d=b=-1,"number"==typeof c?e=c:c?e=a(c,"length"):e=c=-1,f=e<0||d<0||e-d!==0,b===-1&&(b=null),c===-1&&(c=null),Ember.propertyWillChange(this,"[]"),f&&Ember.propertyWillChange(this,"length"),Ember.sendEvent(this,"@enumerable:before",b,c),this},enumerableContentDidChange:function(b,c){var d=this.propertyDidChange,e,f,g;return"number"==typeof b?e=b:b?e=a(b,"length"):e=b=-1,"number"==typeof c?f=c:c?f=a(c,"length"):f=c=-1,g=f<0||e<0||f-e!==0,b===-1&&(b=null),c===-1&&(c=null),Ember.sendEvent(this,"@enumerable:change",b,c),g&&Ember.propertyDidChange(this,"length"),Ember.propertyDidChange(this,"[]"),this}})}(),function(){function f(a){return a===null||a===undefined}function g(a,b,c){b.call(a,c[0],c[2],c[3],c[4])}var a=Ember.get,b=Ember.set,c=Ember.meta,d=Ember.EnumerableUtils.map,e=Ember.cacheFor;Ember.Array=Ember.Mixin.create(Ember.Enumerable,{isSCArray:!0,length:Ember.required(),objectAt:function(b){return b<0||b>=a(this,"length")?undefined:a(this,b)},objectsAt:function(a){var b=this;return d(a,function(a){return b.objectAt(a)})},nextObject:function(a){return this.objectAt(a)},"[]":Ember.computed(function(b,c){return c!==undefined&&this.replace(0,a(this,"length"),c),this}).property().cacheable(),firstObject:Ember.computed(function(){return this.objectAt(0)}).property().cacheable(),lastObject:Ember.computed(function(){return this.objectAt(a(this,"length")-1)}).property().cacheable(),contains:function(a){return this.indexOf(a)>=0},slice:function(b,c){var d=[],e=a(this,"length");f(b)&&(b=0);if(f(c)||c>e)c=e;while(b=e)c=e-1;c<0&&(c+=e);for(d=c;d>=0;d--)if(this.objectAt(d)===b)return d;return-1},addArrayObserver:function(b,c){var d=c&&c.willChange||"arrayWillChange",e=c&&c.didChange||"arrayDidChange",f=a(this,"hasArrayObservers");return f||Ember.propertyWillChange(this,"hasArrayObservers"),Ember.addListener(this,"@array:before",b,d,g),Ember.addListener(this,"@array:change",b,e,g),f||Ember.propertyDidChange(this,"hasArrayObservers"),this},removeArrayObserver:function(b,c){var d=c&&c.willChange||"arrayWillChange",e=c&&c.didChange||"arrayDidChange",f=a(this,"hasArrayObservers");return f&&Ember.propertyWillChange(this,"hasArrayObservers"),Ember.removeListener(this,"@array:before",b,d,g),Ember.removeListener(this,"@array:change",b,e,g),f&&Ember.propertyDidChange(this,"hasArrayObservers"),this},hasArrayObservers:Ember.computed(function(){return Ember.hasListeners(this,"@array:change")||Ember.hasListeners(this,"@array:before")}).property().cacheable(),arrayContentWillChange:function(b,c,d){b===undefined?(b=0,c=d=-1):(c===undefined&&(c=-1),d===undefined&&(d=-1)),Ember.isWatching(this,"@each")&&a(this,"@each"),Ember.sendEvent(this,"@array:before",b,c,d);var e,f;if(b>=0&&c>=0&&a(this,"hasEnumerableObservers")){e=[],f=b+c;for(var g=b;g=0&&d>=0&&a(this,"hasEnumerableObservers")){f=[],g=b+d;for(var h=b;hc(this,"length"))throw new Error(a);return this.replace(b,0,[d]),this},removeAt:function(d,e){var f=0;if("number"==typeof d){if(d<0||d>=c(this,"length"))throw new Error(a);e===undefined&&(e=1),this.replace(d,e,b)}return this},pushObject:function(a){return this.insertAt(c(this,"length"),a),a},pushObjects:function(a){return this.replace(c(this,"length"),0,a),this},popObject:function(){var a=c(this,"length");if(a===0)return null;var b=this.objectAt(a-1);return this.removeAt(a-1,1),b},shiftObject:function(){if(c(this,"length")===0)return null;var a=this.objectAt(0);return this.removeAt(0),a},unshiftObject:function(a){return this.insertAt(0,a),a},unshiftObjects:function(a){return this.replace(0,0,a),this},removeObject:function(a){var b=c(this,"length")||0;while(--b>=0){var d=this.objectAt(b);d===a&&this.removeAt(b)}return this},addObject:function(a){return this.contains(a)||this.pushObject(a),this}})}(),function(){var a=Ember.get,b=Ember.set;Ember.Observable=Ember.Mixin.create({isObserverable:!0,get:function(b){return a(this,b)},getProperties:function(){var b={},c=arguments;arguments.length===1&&Ember.typeOf(arguments[0])==="array"&&(c=arguments[0]);for(var d=0;d"}}),Ember.config.overridePrototypeMixin&&Ember.config.overridePrototypeMixin(k.PrototypeMixin),k.__super__=null;var l=Ember.Mixin.create({ClassMixin:Ember.required(),PrototypeMixin:Ember.required(),isClass:!0,isMethod:!1,extend:function(){var a=j(),b;a.ClassMixin=Ember.Mixin.create(this.ClassMixin),a.PrototypeMixin=Ember.Mixin.create(this.PrototypeMixin),a.ClassMixin.ownerConstructor=a,a.PrototypeMixin.ownerConstructor=a;var c=a.PrototypeMixin;return c.reopen.apply(c,arguments),a.superclass=this,a.__super__=this.prototype,b=a.prototype=e(this.prototype),b.constructor=a,Ember.generateGuid(b,"ember"),h(b).proto=b,a.ClassMixin.apply(a),a},create:function(){var a=this;return arguments.length>0&&this._initMixins(arguments),new a},reopen:function(){this.willReopen();var a=this.PrototypeMixin;return a.reopen.apply(a,arguments),this},reopenClass:function(){var a=this.ClassMixin;return a.reopen.apply(a,arguments),Ember.Mixin._apply(this,arguments,!1),this},detect:function(a){if("function"!=typeof a)return!1;while(a){if(a===this)return!0;a=a.superclass}return!1},detectInstance:function(a){return a instanceof this},metaForProperty:function(a){var b=h(this.proto(),!1).descs[a];return b._meta||{}},eachComputedProperty:function(a,b){var c=this.proto(),d=h(c).descs,e={},f;for(var g in d)f=d[g],f instanceof Ember.ComputedProperty&&a.call(b||this,g,f._meta||e)}});Ember.config.overrideClassMixin&&Ember.config.overrideClassMixin(l),k.ClassMixin=l,l.apply(k),Ember.CoreObject=k}(),function(){var a=Ember.get,b=Ember.set,c=Ember.guidFor,d=Ember.none;Ember.Set=Ember.CoreObject.extend(Ember.MutableEnumerable,Ember.Copyable,Ember.Freezable,{length:0,clear:function(){if(this.isFrozen)throw new Error(Ember.FROZEN_ERROR);var d=a(this,"length");if(d===0)return this;var e;this.enumerableContentWillChange(d,0),Ember.propertyWillChange(this,"firstObject"),Ember.propertyWillChange(this,"lastObject");for(var f=0;f=0)if(!b.contains(this[c]))return!1;return!0},add:Ember.alias("addObject"),remove:Ember.alias("removeObject"),pop:function(){if(a(this,"isFrozen"))throw new Error(Ember.FROZEN_ERROR);var b=this.length>0?this[this.length-1]:null;return this.remove(b),b},push:Ember.alias("addObject"),shift:Ember.alias("pop"),unshift:Ember.alias("push"),addEach:Ember.alias("addObjects"),removeEach:Ember.alias("removeObjects"),init:function(a){this._super(),a&&this.addObjects(a)},nextObject:function(a){return this[a]},firstObject:Ember.computed(function(){return this.length>0?this[0]:undefined}).property().cacheable(),lastObject:Ember.computed(function(){return this.length>0?this[this.length-1]:undefined}).property().cacheable(),addObject:function(e){if(a(this,"isFrozen"))throw new Error(Ember.FROZEN_ERROR);if(d(e))return this;var f=c(e),g=this[f],h=a(this,"length"),i;return g>=0&&g=0&&g=0},copy:function(){var d=this.constructor,e=new d,f=a(this,"length");b(e,"length",f);while(--f>=0)e[f]=this[f],e[c(this[f])]=f;return e},toString:function(){var a=this.length,b,c=[];for(b=0;b".fmt(c.join(","))}})}(),function(){Ember.Object=Ember.CoreObject.extend(Ember.Observable)}(),function(){var a=Ember.ArrayPolyfills.indexOf;Ember.Namespace=Ember.Object.extend({isNamespace:!0,init:function(){Ember.Namespace.NAMESPACES.push(this),Ember.Namespace.PROCESSED=!1},toString:function(){return Ember.identifyNamespaces(),this[Ember.GUID_KEY+"_name"]},destroy:function(){var b=Ember.Namespace.NAMESPACES;window[this.toString()]=undefined,b.splice(a.call(b,this),1),this._super()}}),Ember.Namespace.NAMESPACES=[Ember],Ember.Namespace.PROCESSED=!1}(),function(){Ember.Application=Ember.Namespace.extend()}(),function(){var a=Ember.get,b=Ember.set;Ember.ArrayProxy=Ember.Object.extend(Ember.MutableArray,{content:null,arrangedContent:Ember.computed("content",function(){return a(this,"content")}).cacheable(),objectAtContent:function(b){return a(this,"arrangedContent").objectAt(b)},replaceContent:function(b,c,d){a(this,"arrangedContent").replace(b,c,d)},_contentWillChange:Ember.beforeObserver(function(){var b=a(this,"content");b&&b.removeArrayObserver(this,{willChange:"contentArrayWillChange",didChange:"contentArrayDidChange"})},"content"),contentArrayWillChange:Ember.K,contentArrayDidChange:Ember.K,_contentDidChange:Ember.observer(function(){var b=a(this,"content"),c=b?a(b,"length"):0;b&&b.addArrayObserver(this,{willChange:"contentArrayWillChange",didChange:"contentArrayDidChange"})},"content"),_arrangedContentWillChange:Ember.beforeObserver(function(){var b=a(this,"arrangedContent"),c=b?a(b,"length"):0;this.arrangedContentArrayWillChange(this,0,c,undefined),b&&b.removeArrayObserver(this,{willChange:"arrangedContentArrayWillChange",didChange:"arrangedContentArrayDidChange"})},"arrangedContent"),_arrangedContentDidChange:Ember.observer(function(){var b=a(this,"arrangedContent"),c=b?a(b,"length"):0;b&&b.addArrayObserver(this,{willChange:"arrangedContentArrayWillChange",didChange:"arrangedContentArrayDidChange"}),this.arrangedContentArrayDidChange(this,0,undefined,c)},"arrangedContent"),objectAt:function(b){return a(this,"content")&&this.objectAtContent(b)},length:Ember.computed(function(){var b=a(this,"arrangedContent");return b?a(b,"length"):0}).property().cacheable(),replace:function(b,c,d){return a(this,"content")&&this.replaceContent(b,c,d),this},arrangedContentArrayWillChange:function(a,b,c,d){this.arrayContentWillChange(b,c,d)},arrangedContentArrayDidChange:function(a,b,c,d){this.arrayContentDidChange(b,c,d)},init:function(){this._super(),this._contentWillChange(),this._contentDidChange(),this._arrangedContentWillChange(),this._arrangedContentDidChange()}})}(),function(){function n(a,b){var c="content."+b,f=b+"WillChange",g=b+"DidChange";a[f]=function(){j(this,b)},a[g]=function(){k(this,b)},d(a,c,null,f),e(a,c,null,g)}function o(a,b){var c="content."+b,d=b+"WillChange",e=b+"DidChange";f(a,c,null,d),g(a,c,null,e),delete a[d],delete a[e]}function p(a,b,c){var d="content."+b,e=b+"WillChange",f=b+"DidChange";h(a,d,null,e,function(){i(a,d,null,f,function(){c.call(a)})})}function q(a,b){var c=l(a,"descs");return c[b]===m}function r(a,b){var c=l(a,"descs");c[b].teardown(a,b),delete c[b],delete a[b]}function s(a,b){if(arguments.length===1)return this.delegateGet(a);p(this,a,function(){this.delegateSet(a,b)})}var a=Ember.get,b=Ember.set,c=Ember.defineProperty,d=Ember.addBeforeObserver,e=Ember.addObserver,f=Ember.removeBeforeObserver,g=Ember.removeObserver,h=Ember._suspendBeforeObserver,i=Ember._suspendObserver,j=Ember.propertyWillChange,k=Ember.propertyDidChange,l=Ember.getMeta,m;m=Ember.computed(s).volatile(),Ember.ObjectProxy=Ember.Object.extend({content:null,_contentDidChange:Ember.observer(function(){},"content"),delegateGet:function(b){var c=a(this,"content");if(c)return a(c,b)},delegateSet:function(c,d){var e=a(this,"content");if(!e)throw new Error("Unable to delegate set without content for property: "+c);return b(e,c,d)},willWatchProperty:function(a){if(a in this)return;c(this,a,m),n(this,a)},didUnwatchProperty:function(a){q(this,a)&&(o(this,a),r(this,a))},unknownProperty:function(a){return this.delegateGet(a)},setUnknownProperty:function(a,b){this.delegateSet(a,b)}})}(),function(){function g(a,b,d,e,f){var g=d._objects,h;g||(g=d._objects={});while(--f>=e){var i=a.objectAt(f);i&&(Ember.addBeforeObserver(i,b,d,"contentKeyWillChange"),Ember.addObserver(i,b,d,"contentKeyDidChange"),h=c(i),g[h]||(g[h]=[]),g[h].push(f))}}function h(a,b,d,e,f){var g=d._objects;g||(g=d._objects={});var h,i;while(--f>=e){var j=a.objectAt(f);j&&(Ember.removeBeforeObserver(j,b,d,"contentKeyWillChange"),Ember.removeObserver(j,b,d,"contentKeyDidChange"),i=c(j),h=g[i],h[h.indexOf(f)]=null)}}var a=Ember.set,b=Ember.get,c=Ember.guidFor,d=Ember.EnumerableUtils.forEach,e=Ember.Object.extend(Ember.Array,{init:function(a,b,c){this._super(),this._keyName=b,this._owner=c,this._content=a},objectAt:function(a){var c=this._content.objectAt(a);return c&&b(c,this._keyName)},length:Ember.computed(function(){var a=this._content;return a?b(a,"length"):0}).property().cacheable()}),f=/^.+:(before|change)$/;Ember.EachProxy=Ember.Object.extend({init:function(a){this._super +(),this._content=a,a.addArrayObserver(this),d(Ember.watchedEvents(this),function(a){this.didAddListener(a)},this)},unknownProperty:function(a,b){var c;return c=new e(this._content,a,this),(new Ember.Descriptor).setup(this,a,c),this.beginObservingContentKey(a),c},arrayWillChange:function(a,b,c,d){var e=this._keys,f,g,i;i=c>0?b+c:-1,Ember.beginPropertyChanges(this);for(f in e){if(!e.hasOwnProperty(f))continue;i>0&&h(a,f,this,b,i),Ember.propertyWillChange(this,f)}Ember.propertyWillChange(this._content,"@each"),Ember.endPropertyChanges(this)},arrayDidChange:function(a,b,c,d){var e=this._keys,f,h,i;i=d>0?b+d:-1,Ember.beginPropertyChanges(this);for(f in e){if(!e.hasOwnProperty(f))continue;i>0&&g(a,f,this,b,i),Ember.propertyDidChange(this,f)}Ember.propertyDidChange(this._content,"@each"),Ember.endPropertyChanges(this)},didAddListener:function(a){f.test(a)&&this.beginObservingContentKey(a.slice(0,-7))},didRemoveListener:function(a){f.test(a)&&this.stopObservingContentKey(a.slice(0,-7))},beginObservingContentKey:function(a){var c=this._keys;c||(c=this._keys={});if(!c[a]){c[a]=1;var d=this._content,e=b(d,"length");g(d,a,this,0,e)}else c[a]++},stopObservingContentKey:function(a){var c=this._keys;if(c&&c[a]>0&&--c[a]<=0){var d=this._content,e=b(d,"length");h(d,a,this,0,e)}},contentKeyWillChange:function(a,b){Ember.propertyWillChange(this,b)},contentKeyDidChange:function(a,b){Ember.propertyDidChange(this,b)}})}(),function(){var a=Ember.get,b=Ember.set,c=Ember.Mixin.create(Ember.MutableArray,Ember.Observable,Ember.Copyable,{get:function(a){return a==="length"?this.length:"number"==typeof a?this[a]:this._super(a)},objectAt:function(a){return this[a]},replace:function(b,c,d){if(this.isFrozen)throw Ember.FROZEN_ERROR;var e=d?a(d,"length"):0;this.arrayContentWillChange(b,c,e);if(!d||d.length===0)this.splice(b,c);else{var f=[b,c].concat(d);this.splice.apply(this,f)}return this.arrayContentDidChange(b,c,e),this},unknownProperty:function(a,b){var c;return b!==undefined&&c===undefined&&(c=this[a]=b),c},indexOf:function(a,b){var c,d=this.length;b===undefined?b=0:b=b<0?Math.ceil(b):Math.floor(b),b<0&&(b+=d);for(c=b;c=0;c--)if(this[c]===a)return c;return-1},copy:function(){return this.slice()}}),d=["length"];Ember.EnumerableUtils.forEach(c.keys(),function(a){Array.prototype[a]&&d.push(a)}),d.length>0&&(c=c.without.apply(c,d)),Ember.NativeArray=c,Ember.A=function(a){return a===undefined&&(a=[]),Ember.NativeArray.apply(a)},Ember.NativeArray.activate=function(){c.apply(Array.prototype),Ember.A=function(a){return a||[]}},Ember.EXTEND_PROTOTYPES&&Ember.NativeArray.activate()}(),function(){var a={},b={};Ember.onLoad=function(c,d){var e;a[c]=a[c]||Ember.A(),a[c].pushObject(d),(e=b[c])&&d(e)},Ember.runLoadHooks=function(c,d){var e;b[c]=d,(e=a[c])&&a[c].forEach(function(a){a(d)})}}(),function(){}(),function(){Ember.ControllerMixin=Ember.Mixin.create({target:null,store:null}),Ember.Controller=Ember.Object.extend(Ember.ControllerMixin)}(),function(){var a=Ember.get,b=Ember.set,c=Ember.EnumerableUtils.forEach;Ember.SortableMixin=Ember.Mixin.create(Ember.MutableEnumerable,{sortProperties:null,sortAscending:!0,addObject:function(b){var c=a(this,"content");c.pushObject(b)},removeObject:function(b){var c=a(this,"content");c.removeObject(b)},orderBy:function(b,d){var e=0,f=a(this,"sortProperties"),g=a(this,"sortAscending");return c(f,function(c){e===0&&(e=Ember.compare(a(b,c),a(d,c)),e!==0&&!g&&(e=-1*e))}),e},destroy:function(){var b=a(this,"content"),d=a(this,"sortProperties");return b&&d&&c(b,function(a){c(d,function(b){Ember.removeObserver(a,b,this,"contentItemSortPropertyDidChange")},this)},this),this._super()},isSorted:Ember.computed("sortProperties",function(){return!!a(this,"sortProperties")}),arrangedContent:Ember.computed("content","sortProperties.@each",function(b,d){var e=a(this,"content"),f=a(this,"isSorted"),g=a(this,"sortProperties"),h=this;return e&&f?(e=e.slice(),e.sort(function(a,b){return h.orderBy(a,b)}),c(e,function(a){c(g,function(b){Ember.addObserver(a,b,this,"contentItemSortPropertyDidChange")},this)},this),Ember.A(e)):e}).cacheable(),_contentWillChange:Ember.beforeObserver(function(){var b=a(this,"content"),d=a(this,"sortProperties");b&&d&&c(b,function(a){c(d,function(b){Ember.removeObserver(a,b,this,"contentItemSortPropertyDidChange")},this)},this),this._super()},"content"),sortAscendingWillChange:Ember.beforeObserver(function(){this._lastSortAscending=a(this,"sortAscending")},"sortAscending"),sortAscendingDidChange:Ember.observer(function(){if(a(this,"sortAscending")!==this._lastSortAscending){var b=a(this,"arrangedContent");b.reverse()}},"sortAscending"),contentArrayWillChange:function(b,d,e,f){var g=a(this,"isSorted");if(g){var h=a(this,"arrangedContent"),i=b.slice(d,d+e),j=a(this,"sortProperties");c(i,function(a){h.removeObject(a),c(j,function(b){Ember.removeObserver(a,b,this,"contentItemSortPropertyDidChange")},this)})}return this._super(b,d,e,f)},contentArrayDidChange:function(b,d,e,f){var g=a(this,"isSorted"),h=a(this,"sortProperties");if(g){var i=b.slice(d,d+f),j=a(this,"arrangedContent");c(i,function(a){this.insertItemSorted(a),c(h,function(b){Ember.addObserver(a,b,this,"contentItemSortPropertyDidChange")},this),this.arrayContentDidChange(d,0,1)},this)}return this._super(b,d,e,f)},insertItemSorted:function(b){var c=a(this,"arrangedContent"),d=a(c,"length"),e=this._binarySearch(b,0,d);c.insertAt(e,b)},contentItemSortPropertyDidChange:function(b){var c=a(this,"arrangedContent"),d=c.indexOf(b);c.removeObject(b),this.insertItemSorted(b)},_binarySearch:function(b,c,d){var e,f,g,h;return c===d?c:(h=a(this,"arrangedContent"),e=c+Math.floor((d-c)/2),f=h.objectAt(e),g=this.orderBy(f,b),g<0?this._binarySearch(b,e+1,d):g>0?this._binarySearch(b,c,e):e)}})}(),function(){var a=Ember.get,b=Ember.set;Ember.ArrayController=Ember.ArrayProxy.extend(Ember.ControllerMixin,Ember.SortableMixin)}(),function(){Ember.ObjectController=Ember.ObjectProxy.extend(Ember.ControllerMixin)}(),function(){}(),function(){}(),function(){var a=Ember.get,b=Ember.set;Ember.Application=Ember.Namespace.extend({rootElement:"body",eventDispatcher:null,customEvents:null,init:function(){var c,d=a(this,"rootElement");this._super(),c=Ember.EventDispatcher.create({rootElement:d}),b(this,"eventDispatcher",c);if(Ember.$.isReady)Ember.run.once(this,this.didBecomeReady);else{var e=this;Ember.$(document).ready(function(){Ember.run.once(e,e.didBecomeReady)})}},initialize:function(c){var d=Ember.A(Ember.keys(this)),e=a(this.constructor,"injections"),f=this,g,h;!c&&Ember.Router.detect(f.Router)&&(c=f.Router.create(),this._createdRouter=c),c&&(b(this,"router",c),b(c,"namespace",this)),Ember.runLoadHooks("application",this),e.forEach(function(a){d.forEach(function(b){a[1](f,c,b)})}),c&&c instanceof Ember.Router&&this.startRouting(c)},didBecomeReady:function(){var b=a(this,"eventDispatcher"),c=a(this,"customEvents");b.setup(c),this.ready()},startRouting:function(b){var c=a(b,"location"),d=a(this,"rootElement"),e=a(b,"applicationController");if(this.ApplicationView&&e){var f=this.ApplicationView.create({controller:e});this._createdApplicationView=f,f.appendTo(d)}b.route(c.getURL()),c.onUpdateURL(function(a){b.route(a)})},ready:Ember.K,willDestroy:function(){a(this,"eventDispatcher").destroy(),this._createdRouter&&this._createdRouter.destroy(),this._createdApplicationView&&this._createdApplicationView.destroy()},registerInjection:function(a){this.constructor.registerInjection(a)}}),Ember.Application.reopenClass({concatenatedProperties:["injections"],injections:Ember.A(),registerInjection:function(b){var c=a(this,"injections"),d=b.before,e=b.name,f=b.injection,g;d?(g=c.find(function(a){if(a[0]===d)return!0}),g=c.indexOf(g)):g=a(c,"length"),c.splice(g,0,[e,f])}}),Ember.Application.registerInjection({name:"controllers",injection:function(a,b,c){if(!/^[A-Z].*Controller$/.test(c))return;var d=c.charAt(0).toLowerCase()+c.substr(1),e=a[c].create();b.set(d,e),e.setProperties({target:b,controllers:b,namespace:a})}})}(),function(){var a=Ember.get,b=Ember.set;Ember.Location={create:function(a){var b=a&&a.implementation,c=this.implementations[b];return c.create.apply(c,arguments)},registerImplementation:function(a,b){this.implementations[a]=b},implementations:{}}}(),function(){var a=Ember.get,b=Ember.set;Ember.HashLocation=Ember.Object.extend({init:function(){b(this,"location",a(this,"location")||window.location)},getURL:function(){return a(this,"location").hash.substr(1)},setURL:function(c){a(this,"location").hash=c,b(this,"lastSetURL",c)},onUpdateURL:function(c){var d=this,e=Ember.guidFor(this);Ember.$(window).bind("hashchange.ember-location-"+e,function(){var e=location.hash.substr(1);if(a(d,"lastSetURL")===e)return;b(d,"lastSetURL",null),c(location.hash.substr(1))})},formatURL:function(a){return"#"+a},willDestroy:function(){var a=Ember.guidFor(this);Ember.$(window).unbind("hashchange.ember-location-"+a)}}),Ember.Location.registerImplementation("hash",Ember.HashLocation)}(),function(){var a=Ember.get,b=Ember.set;Ember.HistoryLocation=Ember.Object.extend({init:function(){b(this,"location",a(this,"location")||window.location),b(this,"_initialURL",a(this,"location").pathname)},_initialURL:null,getURL:function(){return a(this,"location").pathname},setURL:function(c){var d=window.history.state,e=a(this,"_initialURL");c===""&&(c="/");if(e&&e!==c||d&&d.path!==c)b(this,"_initialURL",null),window.history.pushState({path:c},null,c)},onUpdateURL:function(a){var b=Ember.guidFor(this);Ember.$(window).bind("popstate.ember-location-"+b,function(b){a(location.pathname)})},formatURL:function(a){return a},willDestroy:function(){var a=Ember.guidFor(this);Ember.$(window).unbind("popstate.ember-location-"+a)}}),Ember.Location.registerImplementation("history",Ember.HistoryLocation)}(),function(){var a=Ember.get,b=Ember.set;Ember.NoneLocation=Ember.Object.extend({path:"",getURL:function(){return a(this,"path")},setURL:function(a){b(this,"path",a)},onUpdateURL:function(a){},formatURL:function(a){return a}}),Ember.Location.registerImplementation("none",Ember.NoneLocation)}(),function(){}(),function(){}(),function(){Ember.$=window.jQuery}(),function(){var a=Ember.get,b=Ember.set,c=Ember.ArrayPolyfills.indexOf,d=function(){this.seen={},this.list=[]};d.prototype={add:function(a){if(a in this.seen)return;this.seen[a]=!0,this.list.push(a)},toDOM:function(){return this.list.join(" ")}},Ember.RenderBuffer=function(a){return new Ember._RenderBuffer(a)},Ember._RenderBuffer=function(a){this.elementTag=a,this.childBuffers=[]},Ember._RenderBuffer.prototype={elementClasses:null,elementId:null,elementAttributes:null,elementTag:null,elementStyle:null,parentBuffer:null,push:function(a){return this.childBuffers.push(String(a)),this},addClass:function(a){var b=this.elementClasses=this.elementClasses||new d;return this.elementClasses.add(a),this},id:function(a){return this.elementId=a,this},attr:function(a,b){var c=this.elementAttributes=this.elementAttributes||{};return arguments.length===1?c[a]:(c[a]=b,this)},removeAttr:function(a){var b=this.elementAttributes;return b&&delete b[a],this},style:function(a,b){var c=this.elementStyle=this.elementStyle||{};return this.elementStyle[a]=b,this},newBuffer:function(a,b,c,d){var e=new Ember._RenderBuffer(a);return e.parentBuffer=b,d&&Ember.$.extend(e,d),c&&c.call(this,e),e},replaceWithBuffer:function(a){var b=this.parentBuffer;if(!b)return;var d=b.childBuffers,e=c.call(d,this);a?d.splice(e,1,a):d.splice(e,1)},begin:function(a){return this.newBuffer(a,this,function(a){this.childBuffers.push(a)})},prepend:function(a){return this.newBuffer(a,this,function(a){this.childBuffers.splice(0,0,a)})},replaceWith:function(a){var b=this.parentBuffer;return this.newBuffer(a,b,function(a){this.replaceWithBuffer(a)})},insertAfter:function(b){var d=a(this,"parentBuffer");return this.newBuffer(b,d,function(a){var b=d.childBuffers,e=c.call(b,this);b.splice(e+1,0,a)})},end:function(){var a=this.parentBuffer;return a||this},remove:function(){this.replaceWithBuffer(null)},element:function(){return Ember.$(this.string())[0]},string:function(){var a="",b=this.elementTag,c;if(b){var d=this.elementId,e=this.elementClasses,f=this.elementAttributes,g=this.elementStyle,h="",i;c=["<"+b],d&&c.push('id="'+this._escapeAttribute(d)+'"'),e&&c.push('class="'+this._escapeAttribute(e.toDOM())+'"');if(g){for(i in g)g.hasOwnProperty(i)&&(h+=i+":"+this._escapeAttribute(g[i])+";");c.push('style="'+h+'"')}if(f)for(i in f)f.hasOwnProperty(i)&&c.push(i+'="'+this._escapeAttribute(f[i])+'"');c=c.join(" ")+">"}var j=this.childBuffers;return Ember.ArrayPolyfills.forEach.call(j,function(b){var c=typeof b=="string";a+=c?b:b.string()}),b?c+a+"":a},_escapeAttribute:function(a){var b={"<":"<",">":">",'"':""","'":"'","`":"`"},c=/&(?!\w+;)|[<>"'`]/g,d=/[&<>"'`]/,e=function(a){return b[a]||"&"},f=a.toString();return d.test(f)?f.replace(c,e):f}}}(),function(){var a=Ember.get,b=Ember.set,c=Ember.String.fmt;Ember.EventDispatcher=Ember.Object.extend({rootElement:"body",setup:function(b){var c,d={touchstart:"touchStart",touchmove:"touchMove",touchend:"touchEnd",touchcancel:"touchCancel",keydown:"keyDown",keyup:"keyUp",keypress:"keyPress",mousedown:"mouseDown",mouseup:"mouseUp",contextmenu:"contextMenu",click:"click",dblclick:"doubleClick",mousemove:"mouseMove",focusin:"focusIn",focusout:"focusOut",mouseenter:"mouseEnter",mouseleave:"mouseLeave",submit:"submit",input:"input",change:"change",dragstart:"dragStart",drag:"drag",dragenter:"dragEnter",dragleave:"dragLeave",dragover:"dragOver",drop:"drop",dragend:"dragEnd"};Ember.$.extend(d,b||{});var e=Ember.$(a(this,"rootElement"));e.addClass("ember-application");for(c in d)d.hasOwnProperty(c)&&this.setupHandler(e,c,d[c])},setupHandler:function(a,b,c){var d=this;a.delegate(".ember-view",b+".ember",function(a,b){var e=Ember.View.views[this.id],f=!0,g=null;return g=d._findNearestEventManager(e,c),g&&g!==b?f=d._dispatchEvent(g,a,c,e):e?f=d._bubbleEvent(e,a,c):a.stopPropagation(),f}),a.delegate("[data-ember-action]",b+".ember",function(a){var b=Ember.$(a.currentTarget).attr("data-ember-action"),d=Ember.Handlebars.ActionHelper.registeredActions[b],e=d.handler;if(d.eventName===c)return a.preventDefault(),e(a)})},_findNearestEventManager:function(b,c){var d=null;while(b){d=a(b,"eventManager");if(d&&d[c])break;b=a(b,"parentView")}return d},_dispatchEvent:function(a,b,c,d){var e=!0,f=a[c];return Ember.typeOf(f)==="function"?(e=f.call(a,b,d),b.stopPropagation()):e=this._bubbleEvent(d,b,c),e},_bubbleEvent:function(a,b,c){return Ember.run(function(){return a.handleEvent(c,b)})},destroy:function(){var b=a(this,"rootElement");return Ember.$(b).undelegate(".ember").removeClass("ember-application"),this._super()}})}(),function(){var a=Ember.run.queues;a.splice(Ember.$.inArray("actions",a)+1,0,"render")}(),function(){var a=Ember.get,b=Ember.set;Ember.ControllerMixin.reopen({target:null,controllers:null,namespace:null,view:null,connectOutlet:function(c,d){var e,f,g,h,i;arguments.length===1?Ember.typeOf(c)==="object"&&(i=c,e=i.outletName,c=i.name,f=i.viewClass,h=i.controller,d=i.context):i={},e=e||"view";if(c){var j=a(this,"namespace"),k=a(this,"controllers"),l=c.charAt(0).toUpperCase()+c.substr(1)+"View";f=a(j,l),h=a(k,c+"Controller")}return h&&d&&h.set("content",d),g=f.create(),h&&b(g,"controller",h),b(this,e,g),g}})}(),function(){}(),function(){var a=Ember.get,b=Ember.set,c=Ember.addObserver,d=Ember.getPath,e=Ember.meta,f=Ember.String.fmt,g=[].slice,h=Ember.EnumerableUtils.forEach,i=Ember.computed(function(){var b=a(this,"_childViews"),c=Ember.A();return h(b,function(b){b.isVirtual?c.pushObjects(a(b,"childViews")):c.push(b)}),c}).property().cacheable(),j=Ember.VIEW_PRESERVES_CONTEXT;Ember.TEMPLATES={};var k={preRender:{},inBuffer:{},hasElement:{},inDOM:{},destroyed:{}};Ember.View=Ember.Object.extend(Ember.Evented,{concatenatedProperties:["classNames","classNameBindings","attributeBindings"],isView:!0,templateName:null,layoutName:null,templates:Ember.TEMPLATES,template:Ember.computed(function(b,c){if(c!==undefined)return c;var d=a(this,"templateName"),e=this.templateForName(d,"template");return e||a(this,"defaultTemplate")}).property("templateName").cacheable(),controller:Ember.computed(function(b,c){var d;return arguments.length===2?c:(d=a(this,"parentView"),d?a(d,"controller"):null)}).property().cacheable(),layout:Ember.computed(function(b,c){if(arguments.length===2)return c;var d=a(this,"layoutName"),e=this.templateForName(d,"layout");return e||a(this,"defaultLayout")}).property("layoutName").cacheable(),templateForName:function(b,c){if(!b)return;var d=a(this,"templates"),e=a(d,b);if(!e)throw new Ember.Error(f('%@ - Unable to find %@ "%@".',[this,c,b]));return e},context:Ember.computed(function(c,d){return arguments.length===2?(b(this,"_context",d),d):a(this,"_context")}).cacheable(),_context:Ember.computed(function(b,c){var d,e;if(arguments.length===2)return c;if(j){if(e=a(this,"controller"))return e;d=a(this,"_parentView");if(d)return a(d,"_context")}return this}).cacheable(),_displayPropertyDidChange:Ember.observer(function(){this.rerender()},"context","controller"),parentView:Ember.computed(function(){var b=a(this,"_parentView");return b&&b.isVirtual?a(b,"parentView"):b}).property("_parentView").volatile(),_parentView:null,concreteView:Ember.computed(function(){return this.isVirtual?a(this,"parentView"):this}).property("_parentView").volatile(),isVisible:!0,childViews:i,_childViews:[],_childViewsWillChange:Ember.beforeObserver(function(){if(this.isVirtual){var b=a(this,"parentView");b&&Ember.propertyWillChange(b,"childViews")}},"childViews"),_childViewsDidChange:Ember.observer(function(){if(this.isVirtual){var b=a(this,"parentView");b&&Ember.propertyDidChange(b,"childViews")}},"childViews"),nearestInstanceOf:function(b){var c=a(this,"parentView");while(c){if(c instanceof b)return c;c=a(c,"parentView")}},nearestWithProperty:function(b){var c=a(this,"parentView");while(c){if(b in c)return c;c=a(c,"parentView")}},nearestChildOf:function(b){var c=a(this,"parentView");while(c){if(a(c,"parentView")instanceof b)return c;c=a(c,"parentView")}},collectionView:Ember.computed(function(){return this.nearestInstanceOf(Ember.CollectionView)}).cacheable(),itemView:Ember.computed(function(){return this.nearestChildOf(Ember.CollectionView)}).cacheable(),contentView:Ember.computed(function(){return this.nearestWithProperty("content")}).cacheable(),_parentViewDidChange:Ember.observer(function(){if(this.isDestroying)return;this.invokeRecursively(function(a){a.propertyDidChange("collectionView"),a.propertyDidChange("itemView"),a.propertyDidChange("contentView")}),d(this,"parentView.controller")&&!a(this,"controller")&&this.notifyPropertyChange("controller")},"_parentView"),_controllerDidChange:Ember.observer(function(){if(this.isDestroying)return;this.forEachChildView(function(a){a.propertyDidChange("controller")})},"controller"),cloneKeywords:function(){var b=a(this,"templateData"),c=b?Ember.copy(b.keywords):{};return c.view=a(this,"concreteView"),c.controller=a(this,"controller"),c},render:function(b){var c=a(this,"layout")||a(this,"template");if(c){var d=a(this,"_context"),e=this.cloneKeywords(),f={view:this,buffer:b,isRenderData:!0,keywords:e},g=c(d,{data:f});g!==undefined&&b.push(g)}},invokeForState:function(a){var b=this.state,c,d;if(d=k[b][a])return c=g.call(arguments),c[0]=this,d.apply(this,c);var e=this,f=e.states,h;while(f){h=f[b];while(h){d=h[a];if(d)return k[b][a]=d,c=g.call(arguments,1),c.unshift(this),d.apply(this,c);h=h.parentState}f=f.parent}},rerender:function(){return this.invokeForState("rerender")},clearRenderedChildren:function(){var b=this.lengthBeforeRender,c=this.lengthAfterRender,d=a(this,"_childViews");for(var e=c-1;e>=b;e--)d[e]&&d[e].destroy()},_applyClassNameBindings:function(){var b=a(this,"classNameBindings"),d=a(this,"classNames"),e,f,g;if(!b)return;h(b,function(a){var b,h,i=function(){f=this._classStringForProperty(a),e=this.$(),b&&(e.removeClass(b),d.removeObject(b)),f?(e.addClass(f),b=f):b=null};g=this._classStringForProperty(a),g&&(d.push(g),b=g),h=a.split(":")[0],c(this,h,i)},this)},_applyAttributeBindings:function(b){var d=a(this,"attributeBindings"),e,f,g;if(!d)return;h(d,function(d){var g=d.split(":"),h=g[0],i=g[1]||h,j=function(){f=this.$(),e=a(this,h),Ember.View.applyAttributeBindings(f,i,e)};c(this,h,j),e=a(this,h),Ember.View.applyAttributeBindings(b,i,e)},this)},_classStringForProperty:function(a){var b=a.split(":"),c=b[1];a=b[0];var d=Ember.getPath(this,a,!1);d===undefined&&Ember.isGlobalPath(a)&&(d=Ember.getPath(window,a));if(!!d&&c)return c;if(d===!0){var e=a.split(".");return Ember.String.dasherize(e[e.length-1])}return d!==!1&&d!==undefined&&d!==null?d:null},element:Ember.computed(function(a,b){return b!==undefined?this.invokeForState("setElement",b):this.invokeForState("getElement")}).property("_parentView").cacheable(),$:function(a){return this.invokeForState("$",a)},mutateChildViews:function(b){var c=a(this,"_childViews"),d=a(c,"length"),e;while(--d>=0)e=c[d],b.call(this,e,d);return this},forEachChildView:function(b){var c=a(this,"_childViews");if(!c)return this;var d=a(c,"length"),e,f;for(f=0;f=0;h--)c[h].removedFromDOM=!0,c[h].destroy();delete Ember.View.views[a(this,"elementId")]},createChildView:function(c,d){return Ember.View.detect(c)?(d=d||{},d._parentView=this,d.templateData=d.templateData||a(this,"templateData"),c=c.create(d),c.viewName&&b(a(this,"concreteView"),c.viewName,c)):(a(c,"templateData")||b(c,"templateData",a(this,"templateData")),b(c,"_parentView",this)),c},becameVisible:Ember.K,becameHidden:Ember.K,_isVisibleDidChange:Ember.observer(function(){var b=a(this,"isVisible");this.$().toggle(b);if(this._isAncestorHidden())return;b?this._notifyBecameVisible():this._notifyBecameHidden()},"isVisible"),_notifyBecameVisible:function(){this.trigger("becameVisible"),this.forEachChildView(function(b){var c=a(b,"isVisible");(c||c===null)&&b._notifyBecameVisible()})},_notifyBecameHidden:function(){this.trigger("becameHidden"),this.forEachChildView(function(b){var c=a(b,"isVisible");(c||c===null)&&b._notifyBecameHidden()})},_isAncestorHidden:function(){var b=a(this,"parentView");while(b){if(a(b,"isVisible")===!1)return!0;b=a(b,"parentView")}return!1},clearBuffer:function(){this.invokeRecursively(function(a){this.buffer=null})},transitionTo:function(a,b){this.state=a,b!==!1&&this.forEachChildView(function(b){b.transitionTo(a)})},trigger:function(a){this._super.apply(this,arguments);if(this[a])return this[a].apply(this,[].slice.call(arguments,1))},has:function(a){return Ember.typeOf(this[a])==="function"||this._super(a)},handleEvent:function(a,b){return this.invokeForState("handleEvent",a,b)}});var l={prepend:function(a,b){b._insertElementLater(function(){var c=a.$();c.prepend(b.$())})},after:function(a,b){b._insertElementLater(function(){var c=a.$();c.after(b.$())})},replace:function(c){var d=a(c,"element");b(c,"element",null),c._insertElementLater(function(){Ember.$(d).replaceWith(a(c,"element"))})},remove:function(c){var d=a(c,"element");b(c,"element",null),c._lastInsert=null,Ember.$(d).remove()},empty:function(a){a.$().empty()}};Ember.View.reopen({states:Ember.View.states,domManager:l}),Ember.View.views={},Ember.View.childViewsProperty=i,Ember.View.applyAttributeBindings=function(a,b,c){var d=Ember.typeOf(c),e=a.attr(b);(d==="string"||d==="number"&&!isNaN(c))&&c!==e?a.attr(b,c):c&&d==="boolean"?a.attr(b,b):c||a.removeAttr(b)}}(),function(){var a=Ember.get,b=Ember.set;Ember.View.states={_default:{appendChild:function(){throw"You can't use appendChild outside of the rendering process"},$:function(){return Ember.$()},getElement:function(){return null},handleEvent:function(){return!0},destroyElement:function(a){return b(a,"element",null),a._lastInsert=null,a}}},Ember.View.reopen({states:Ember.View.states})}(),function(){Ember.View.states.preRender={parentState:Ember.View.states._default,insertElement:function(a,b){if(a._lastInsert!==Ember.guidFor(b))return;a.createElement(),a._notifyWillInsertElement(),b.call(a),a.transitionTo("inDOM"),a._notifyDidInsertElement()},empty:Ember.K,setElement:function(a,b){return a.beginPropertyChanges(),a.invalidateRecursively("element"),b!==null&&a.transitionTo("hasElement"),a.endPropertyChanges(),b}}}(),function(){var a=Ember.get,b=Ember.set,c=Ember.meta;Ember.View.states.inBuffer={parentState:Ember.View.states._default,$:function(a,b){return a.rerender(),Ember.$()},rerender:function(a){a._notifyWillRerender(),a.clearRenderedChildren(),a.renderToBuffer(a.buffer,"replaceWith")},appendChild:function(b,c,d){var e=b.buffer;return c=this.createChildView(c,d),a(b,"_childViews").push(c),c.renderToBuffer(e),b.propertyDidChange("childViews"),c},destroyElement:function(a){return a.clearBuffer(),a._notifyWillDestroyElement(),a.transitionTo("preRender"),a},empty:function(){},insertElement:function(){throw"You can't insert an element that has already been rendered"},setElement:function(a,b){return a.invalidateRecursively("element"),b===null?a.transitionTo("preRender"):(a.clearBuffer(),a.transitionTo("hasElement")),b}}}(),function(){var a=Ember.get,b=Ember.set,c=Ember.meta;Ember.View.states.hasElement={parentState:Ember.View.states._default,$:function(b,c){var d=a(b,"element");return c?Ember.$(c,d):Ember.$(d)},getElement:function(b){var c=a(b,"parentView");return c&&(c=a(c,"element")),c?b.findElementInParentElement(c):Ember.$("#"+a(b,"elementId"))[0]},setElement:function(a,b){if(b!==null)throw"You cannot set an element to a non-null value when the element is already in the DOM.";return a.invalidateRecursively("element"),a.transitionTo("preRender"),b},rerender:function(a){return a._notifyWillRerender(),a.clearRenderedChildren(),a.domManager.replace(a),a},destroyElement:function(a){return a._notifyWillDestroyElement(),a.domManager.remove(a),a},empty:function(b){var c=a(b,"_childViews"),d,e;if(c){d=a(c,"length");for(e=0;e=c;h--)g=f[h],j&&(g.removedFromDOM=!0),g.destroy()},arrayDidChange:function(c,d,e,f){var g=a(this,"itemViewClass"),h=a(this,"childViews"),i=[],j,k,l,m,n;"string"==typeof g&&(g=Ember.getPath(g)),m=c?a(c,"length"):0;if(m)for(l=d;l0&&k[0]===h[0])g=k.shift(),h.shift();f.pathsCache[b]={exitStates:h,enterStates:k,resolveState:g}}n=k.length-1;while(e.length>0){if(n>=0)l=k[n--];else{l=k[0]?a(k[0],"parentState"):g;if(!l)throw"Cannot match all contexts to states";k.unshift(l),h.unshift(l)}o=d&&a(l,"hasContext"),i.unshift(o?e.pop():null)}if(k.length>0){l=k[k.length-1];for(;;){m=a(l,"initialState")||"start",l=c(l,"states."+m);if(!l)break;k.push(l),i.push(undefined)}while(k.length>0){if(k[0]!==h[0])break;if(k.length===i.length){if(this.getStateMeta(k[0],"context")!==i[0])break;i.shift()}g=k.shift(),h.shift()}}}this.enterState(h,k,k[k.length-1]||g),this.triggerSetupContext(k,i)},triggerSetupContext:function(b,c){var d=b.length-c.length;e.call(b,function(b,e){b.trigger(a(this,"transitionEvent"),this,c[e-d])},this)},getState:function(b){var c=a(this,b),d=a(this,"parentState");if(c)return c;if(d)return d.getState(b)},enterState:function(c,d,f){var g=this.enableLogging,h=this;c=c.slice(0).reverse(),e.call(c,function(a){a.trigger("exit",h)}),e.call(d,function(b){g&&Ember.Logger.log("STATEMANAGER: Entering "+a(b,"path")),b.trigger("enter",h)}),b(this,"currentState",f)}})}(),function(){}(),function(){var a=Ember.get,b=Ember.getPath,c=function(a){var b=a.toString(),c=b.split("."),d=c[c.length-1];return Ember.String.underscore(d)+"_id"},d=function(a,b){for(var c in b){if(!b.hasOwnProperty(c))continue;if(a.hasOwnProperty(c))continue;a[c]=b[c]}};Ember.Routable=Ember.Mixin.create({init:function(){var b;this.on("connectOutlets",this,this.stashContext);if(b=a(this,"redirectsTo"))this.connectOutlets=function(a){a.transitionTo(b)};var c=a(this,"route");c===""&&(c="/"),this._super()},stashContext:function(b,c){var d=this.serialize(b,c);b.setStateMeta(this,"context",c),b.setStateMeta(this,"serialized",d),a(this,"isRoutable")&&!a(b,"isRouting")&&this.updateRoute(b,a(b,"location"))},updateRoute:function(b,c){if(a(this,"isLeafRoute")){var d=this.absoluteRoute(b);c.setURL(d)}},absoluteRoute:function(b,c){var e=a(this,"parentState"),f="",g;a(e,"isRoutable")&&(f=e.absoluteRoute(b,c));var h=a(this,"routeMatcher"),i=b.getStateMeta(this,"serialized");return c=c||{},d(c,i),g=h&&h.generate(c),g&&(f=f+"/"+g),f},isRoutable:Ember.computed(function(){return typeof a(this,"route")=="string"}).cacheable(),isLeafRoute:Ember.computed(function(){return a(this,"isLeaf")?!0:!a(this,"childStates").findProperty("isRoutable")}).cacheable(),routeMatcher:Ember.computed(function(){var b=a(this,"route");if(b)return Ember._RouteMatcher.create({route:b})}).cacheable(),hasContext:Ember.computed(function(){var b=a(this,"routeMatcher");if(b)return b.identifiers.length>0}).cacheable(),modelClass:Ember.computed(function(){var b=a(this,"modelType");return typeof b=="string"?Ember.getPath(window,b):b}).cacheable(),modelClassFor:function(b){var c,d,e,f,g;if(c=a(this,"modelClass"))return c;if(!b)return;d=a(this,"routeMatcher");if(!d)return;e=d.identifiers;if(e.length!==2)return;f=e[1].match(/^(.*)_id$/);if(!f)return;return g=Ember.String.classify(f[1]),a(b,g)},deserialize:function(b,d){var e,f,g;return(e=this.modelClassFor(a(b,"namespace")))?e.find(d[c(e)]):d},serialize:function(b,d){var e,f,g,h,i;if(Ember.empty(d))return"";if(e=this.modelClassFor(a(b,"namespace")))h=c(e),i=a(d,"id"),d={},d[h]=i;return d},routePath:function(c,d){if(a(this,"isLeafRoute"))return;var e=a(this,"childStates"),f;e=Ember.A(e.filterProperty("isRoutable")),e=e.sort(function(c,d){var e=b(c,"routeMatcher.identifiers.length"),f=b(d,"routeMatcher.identifiers.length"),g=a(c,"route"),h=a(d,"route");return g.indexOf(h)===0?-1:h.indexOf(g)===0?1:e!==f?e-f:b(d,"route.length")-b(c,"route.length")});var g=e.find(function(b){var c=a(b,"routeMatcher");if(f=c.match(d))return!0}),h=g.deserialize(c,f.hash);c.transitionTo(a(g,"path"),h),c.send("routePath",f.remaining)},unroutePath:function(b,c){if(a(this,"parentState")===b)return;c=c.replace(/^(?=[^\/])/,"/");var d=this.absoluteRoute(b),e=a(this,"route");if(e!=="/"){var f=c.indexOf(d),g=c.charAt(d.length);if(f===0&&(g==="/"||g===""))return}var h=a(a(this,"parentState"),"path");b.transitionTo(h),b.send("unroutePath",c)},connectOutlets:Ember.K,navigateAway:Ember.K})}(),function(){Ember.Route=Ember.State.extend(Ember.Routable)}(),function(){var a=function(a){return a.replace(/[\-\[\]{}()*+?.,\\\^\$|#\s]/g,"\\$&")};Ember._RouteMatcher=Ember.Object.extend({state:null,init:function(){var b=this.route,c=[],d=1,e;b.charAt(0)==="/"&&(b=this.route=b.substr(1)),e=a(b);var f=e.replace(/:([a-z_]+)(?=$|\/)/gi,function(a,b){return c[d++]=b,"([^/]+)"});this.identifiers=c,this.regex=new RegExp("^/?"+f)},match:function(a){var b=a.match(this.regex);if(b){var c=this.identifiers,d={};for(var e=1,f=c.length;e0?d:null}}},generate:function(a){var b=this.identifiers,c=this.route,d;for(var e=1,f=b.length;e"},p=function(){return""};if(e)h=function(a,b){var c=d.createRange(),e=d.getElementById(a.start),f=d.getElementById(a.end);return b?(c.setStartBefore(e),c.setEndAfter(f)):(c.setStartAfter(e),c.setEndBefore(f)),c},i=function(a,b){var c=h(this,b);c.deleteContents();var d=c.createContextualFragment(a);c.insertNode(d)},j=function(){var a=h(this,!0);a.deleteContents()},l=function(a){var b=d.createRange();b.setStart(a),b.collapse(!1);var c=b.createContextualFragment(this.outerHTML());a.appendChild(c)},m=function(a){var b=d.createRange(),c=d.getElementById(this.end);b.setStartAfter(c),b.setEndAfter(c);var e=b.createContextualFragment(a);b.insertNode(e)},n=function(a){var b=d.createRange(),c=d.getElementById(this.start);b.setStartAfter(c),b.setEndAfter(c);var e=b.createContextualFragment(a);b.insertNode(e)};else{var q={select:[1,""],fieldset:[1,"
","
"],table:[1,"","
"],tbody:[2,"","
"],tr:[3,"","
"],colgroup:[2,"","
"],map:[1,"",""],_default:[0,"",""]},r=function(a,b){var c=q[a.tagName.toLowerCase()]||q._default,e=c[0],g=c[1],h=c[2];f&&(b="­"+b);var i=d.createElement("div");i.innerHTML=g+b+h;for(var j=0;j<=e;j++)i=i.firstChild;if(f){var k=i;while(k.nodeType===1&&!k.nodeName)k=k.firstChild;k.nodeType===3&&k.nodeValue.charAt(0)==="­"&&(k.nodeValue=k.nodeValue.slice(1))}return i},s=function(a){while(a.parentNode.tagName==="")a=a.parentNode;return a},t=function(a,b){a.parentNode!==b.parentNode&&b.parentNode.insertBefore(a,b.parentNode.firstChild)};i=function(a,b){var c=s(d.getElementById(this.start)),e=d.getElementById(this.end),f=e.parentNode,g,h,i;t(c,e),g=c.nextSibling;while(g){h=g.nextSibling,i=g===e;if(i){if(!b)break;e=g.nextSibling}g.parentNode.removeChild(g);if(i)break;g=h}g=r(c.parentNode,a);while(g)h=g.nextSibling,f.insertBefore(g,e),g=h},j=function(){var a=s(d.getElementById(this.start)),b=d.getElementById(this.end);this.html(""),a.parentNode.removeChild(a),b.parentNode.removeChild(b)},l=function(a){var b=r(a,this.outerHTML());while(b)nextSibling=b.nextSibling,a.appendChild(b),b=nextSibling},m=function(a){var b=d.getElementById(this.end),c=b.nextSibling,e=b.parentNode,f,g;g=r(e,a);while(g)f=g.nextSibling,e.insertBefore(g,c),g=f},n=function(a){var b=d.getElementById(this.start),c=b.parentNode,e,f;f=r(c,a);var g=b.nextSibling;while(f)e=f.nextSibling,c.insertBefore(f,g),f=e}}g.prototype.html=function(a){this.checkRemoved();if(a===undefined)return this.innerHTML;i.call(this,a),this.innerHTML=a},g.prototype.replaceWith=function(a){this.checkRemoved(),i.call(this,a,!0)},g.prototype.remove=j,g.prototype.outerHTML=k,g.prototype.appendTo=l,g.prototype.after=m,g.prototype.prepend=n,g.prototype.startTag=o,g.prototype.endTag=p,g.prototype.isRemoved=function(){var a=d.getElementById(this.start),b=d.getElementById(this.end);return!a||!b},g.prototype.checkRemoved=function(){if(this.isRemoved())throw new Error("Cannot perform operations on a Metamorph that is not in the DOM.")},a.Metamorph=g})(this)}(),function(){var a=Ember.create;Ember.Handlebars=a(Handlebars),Ember.Handlebars.helpers=a(Handlebars.helpers),Ember.Handlebars.Compiler=function(){},Ember.Handlebars.Compiler.prototype=a(Handlebars.Compiler.prototype),Ember.Handlebars.Compiler.prototype.compiler=Ember.Handlebars.Compiler,Ember.Handlebars.JavaScriptCompiler=function(){},Ember.Handlebars.JavaScriptCompiler.prototype=a(Handlebars.JavaScriptCompiler.prototype),Ember.Handlebars.JavaScriptCompiler.prototype.compiler=Ember.Handlebars.JavaScriptCompiler,Ember.Handlebars.JavaScriptCompiler.prototype.namespace="Ember.Handlebars",Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer=function(){return"''"},Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer=function(a){return"data.buffer.push("+a+");"},Ember.Handlebars.Compiler.prototype.mustache=function(a){if(a.params.length||a.hash)return Handlebars.Compiler.prototype.mustache.call(this,a);var b=new Handlebars.AST.IdNode(["_triageMustache"]);return a.escaped||(a.hash=a.hash||new Handlebars.AST.HashNode([]),a.hash.pairs.push(["unescaped",new Handlebars.AST.StringNode("true")])),a=new Handlebars.AST.MustacheNode([b].concat([a.id]),a.hash,!a.escaped),Handlebars.Compiler.prototype.mustache.call(this,a)},Ember.Handlebars.precompile=function(a){var b=Handlebars.parse(a),c={knownHelpers:{action:!0,unbound:!0,bindAttr:!0,template:!0,view:!0,_triageMustache:!0},data:!0,stringParams:!0},d=(new Ember.Handlebars.Compiler).compile(b,c);return(new Ember.Handlebars.JavaScriptCompiler).compile(d,c,undefined,!0)},Ember.Handlebars.compile=function(a){var b=Handlebars.parse(a),c={data:!0,stringParams:!0},d=(new Ember.Handlebars.Compiler).compile(b,c),e=(new Ember.Handlebars.JavaScriptCompiler).compile(d,c,undefined,!0);return Handlebars.template(e)};var b=Ember.Handlebars.normalizePath=function(a,b,c){var d=c&&c.keywords||{},e,f;return e=b.split(".",1)[0],d.hasOwnProperty(e)&&(a=d[e],f=!0,b===e?b="":b=b.substr(e.length+1)),{root:a,path:b,isKeyword:f}};Ember.Handlebars.getPath=function(a,c,d){var e=d&&d.data,f=b(a,c,e),g;return a=f.root,c=f.path,g=Ember.getPath(a,c),g===undefined&&a!==window&&Ember.isGlobalPath(c)&&(g=Ember.getPath(window,c)),g},Ember.Handlebars.registerHelper("helperMissing",function(a,b){var c,d="";throw c="%@ Handlebars error: Could not find property '%@' on object %@.",b.data&&(d=b.data.view),new Ember.Error(Ember.String.fmt(c,[d,a,this]))})}(),function(){Ember.String.htmlSafe=function(a){return new Handlebars.SafeString(a)};var a=Ember.String.htmlSafe;Ember.EXTEND_PROTOTYPES&&(String.prototype.htmlSafe=function(){return a(this)})}(),function(){var a=Ember.set,b=Ember.get,c=Ember.getPath,d={remove:function(b){var c=b.morph;if(c.isRemoved())return;a(b,"element",null),b._lastInsert=null,c.remove()},prepend:function(a,b){b._insertElementLater(function(){var c=a.morph;c.prepend(b.outerHTML),b.outerHTML=null})},after:function(a,b){b._insertElementLater(function(){var c=a.morph;c.after(b.outerHTML),b.outerHTML=null})},replace:function(a){var c=a.morph;a.transitionTo("preRender"),a.clearRenderedChildren();var d=a.renderToBuffer();Ember.run.schedule("render",this,function(){if(b(a,"isDestroyed"))return;a.invalidateRecursively("element"),a._notifyWillInsertElement(),c.replaceWith(d.string()),a.transitionTo("inDOM"),a._notifyDidInsertElement()})},empty:function(a){a.morph.html("")}};Ember._Metamorph=Ember.Mixin.create({isVirtual:!0,tagName:"",init:function(){this._super(),this.morph=Metamorph()},beforeRender:function(a){a.push(this.morph.startTag())},afterRender:function(a){a.push(this.morph.endTag())},createElement:function(){var a=this.renderToBuffer();this.outerHTML=a.string(),this.clearBuffer()},domManager:d}),Ember._MetamorphView=Ember.View.extend(Ember._Metamorph)}(),function(){var a=Ember.get,b=Ember.set,c=Ember.Handlebars.getPath;Ember._HandlebarsBoundView=Ember._MetamorphView.extend({shouldDisplayFunc:null,preserveContext:!1,previousContext:null,displayTemplate:null,inverseTemplate:null,path:null,pathRoot:null,normalizedValue:Ember.computed(function(){var b=a(this,"path"),d=a(this,"pathRoot"),e=a(this,"valueNormalizerFunc"),f,g;return b===""?f=d:(g=a(this,"templateData"),f=c(d,b,{data:g})),e?e(f):f}).property("path","pathRoot","valueNormalizerFunc").volatile(),rerenderIfNeeded:function(){!a(this,"isDestroyed")&&a(this,"normalizedValue")!==this._lastNormalizedValue&&this.rerender()},render:function(c){var d=a(this,"isEscaped"),e=a(this,"shouldDisplayFunc"),f=a(this,"preserveContext"),g=a(this,"previousContext"),h=a(this,"inverseTemplate"),i=a(this,"displayTemplate"),j=a(this,"normalizedValue");this._lastNormalizedValue=j;if(e(j)){b(this,"template",i);if(f)b(this,"_context",g);else{if(!i){j===null||j===undefined?j="":j instanceof Handlebars.SafeString||(j=String(j)),d&&(j=Handlebars.Utils.escapeExpression(j)),c.push(j);return}b(this,"_context",j)}}else h?(b(this,"template",h),f?b(this,"_context",g):b(this,"_context",j)):b(this,"template",function(){return""});return this._super(c)}})}(),function(){var a=Ember.get,b=Ember.set,c=Ember.String.fmt,d=Ember.Handlebars.getPath,e=Ember.Handlebars.normalizePath,f=Ember.ArrayPolyfills.forEach,g=Ember.Handlebars,h=g.helpers,i=function(a,b,c,f,g){var h=b.data,i=b.fn,j=b.inverse,k=h.view,l=this,m,n,o;o=e(l,a,h),m=o.root,n=o.path;if("object"==typeof this){var p=k.createChildView(Ember._HandlebarsBoundView,{preserveContext:c,shouldDisplayFunc:f,valueNormalizerFunc:g,displayTemplate:i,inverseTemplate:j,path:n,pathRoot:m,previousContext:l,isEscaped:!b.hash.unescaped,templateData:b.data});k.appendChild(p);var q=function(){Ember.run.once(p,"rerenderIfNeeded")};n!==""&&Ember.addObserver(m,n,q)}else h.buffer.push(d(m,n,b))};g.registerHelper("_triageMustache",function(a,b){return h[a]?h[a].call(this,b):h.bind.apply(this,arguments)}),g.registerHelper("bind",function(a,b){var c=b.contexts&&b.contexts[0]||this;return i.call(c,a,b,!1,function(a){return!Ember.none(a)})}),g.registerHelper("boundIf",function(b,c){var d=c.contexts&&c.contexts[0]||this,e=function(b){return Ember.typeOf(b)==="array"?a(b,"length")!==0:!!b};return i.call(d,b,c,!0,e,e)}),g.registerHelper("with",function(a,b){if(arguments.length===4){var c,d,f,g;b=arguments[3],c=arguments[2],d=arguments[0];if(Ember.isGlobalPath(d))Ember.bind(b.data.keywords,c,d);else{g=e(this,d,b.data),d=g.path,f=g.root;var j=Ember.$.expando+Ember.guidFor(f);b.data.keywords[j]=f;var k=d?j+"."+d:j;Ember.bind(b.data.keywords,c,k)}return i.call(this,d,b.fn,!0,function(a){return!Ember.none(a)})}return h.bind.call(b.contexts[0],a,b)}),g.registerHelper("if",function(a,b){return h.boundIf.call(b.contexts[0],a,b)}),g.registerHelper("unless",function(a,b){var c=b.fn,d=b.inverse;return b.fn=d,b.inverse=c,h.boundIf.call(b.contexts[0],a,b)}),g.registerHelper("bindAttr",function(a){var b=a.hash,c=a.data.view,h=[],i=this,j=++Ember.$.uuid,k=b["class"];if(k!==null&&k!==undefined){var l=g.bindClasses(this,k,c,j,a);h.push('class="'+Handlebars.Utils.escapeExpression(l.join(" "))+'"'),delete b["class"]}var m=Ember.keys(b);return f.call(m,function(f){var g=b[f],k,l;l=e(i,g,a.data),k=l.root,g=l.path;var m=g==="this"?k:d(k,g,a),n=Ember.typeOf(m),o,p;o=function(){var e=d(k,g,a),h=c.$("[data-bindattr-"+j+"='"+j+"']");if(h.length===0){Ember.removeObserver(k,g,p);return}Ember.View.applyAttributeBindings(h,f,e)},p=function(){Ember.run.once(o)},g!=="this"&&Ember.addObserver(k,g,p),n==="string"||n==="number"&&!isNaN(m)?h.push(f+'="'+Handlebars.Utils.escapeExpression(m)+'"'):m&&n==="boolean"&&h.push(f+'="'+f+'"')},this),h.push("data-bindattr-"+j+'="'+j+'"'),new g.SafeString(h.join(" "))}),g.bindClasses=function(a,b,c,g,h){var i=[],j,k,l,m=function(a,b,c,e){var f=b!==""?d(a,b,e):!0;if(!!f&&c)return c;if(f===!0){var g=b.split(".");return Ember.String.dasherize(g[g.length-1])}return f!==!1&&f!==undefined&&f!==null?f:null};return f.call(b.split(" "),function(b){var d,f,n,o=b.split(":"),p=o[0],q=o[1],r=a,s;p!==""&&(s=e(a,p,h.data),r=s.root,p=s.path),f=function(){j=m(r,p,q,h),l=g?c.$("[data-bindattr-"+g+"='"+g+"']"):c.$(),l.length===0?Ember.removeObserver(r,p,n):(d&&l.removeClass(d),j?(l.addClass(j),d=j):d=null)},n=function(){Ember.run.once(f)},p!==""&&Ember.addObserver(r,p,n),k=m(r,p,q,h),k&&(i.push(k),d=k)}),i}}(),function(){var a=Ember.get,b=Ember.set,c=/^parentView\./,d=Ember.Handlebars;d.ViewHelper=Ember.Object.create({propertiesFromHTMLOptions:function(a,b){var c=a.hash,d=a.data,e={},f=c["class"],g=!1;c.id&&(e.elementId=c.id,g=!0),f&&(f=f.split(" "),e.classNames=f,g=!0),c.classBinding&&(e.classNameBindings=c.classBinding.split(" "),g=!0),c.classNameBindings&&(e.classNameBindings=c.classNameBindings.split(" "),g=!0),c.attributeBindings&&(e.attributeBindings=null,g=!0),g&&(c=Ember.$.extend({},c),delete c.id,delete c["class"],delete c.classBinding);var h,i;for(var j in c){if(!c.hasOwnProperty(j))continue;Ember.IS_BINDING.test(j)&&typeof c[j]=="string"&&(h=c[j],i=Ember.Handlebars.normalizePath(null,h,d),i.isKeyword?c[j]="templateData.keywords."+h:Ember.isGlobalPath(h)||(h==="this"?c[j]="bindingContext":c[j]="bindingContext."+h))}return e.bindingContext=b,Ember.$.extend(c,e)},helper:function(a,b,c){var e=c.inverse,f=c.data,g=f.view,h=c.fn,i=c.hash,j;"string"==typeof b?j=d.getPath(a,b,c):j=b;var k=this.propertiesFromHTMLOptions(c,a),l=f.view;k.templateData=c.data,h&&(k.template=h),l.appendChild(j,k)}}),d.registerHelper("view",function(a,b){return a&&a.data&&a.data.isRenderData&&(b=a,a="Ember.View"),d.ViewHelper.helper(this,a,b)})}(),function(){var a=Ember.get,b=Ember.Handlebars.getPath,c=Ember.String.fmt;Ember.Handlebars.registerHelper("collection",function(c,d){c&&c.data&&c.data.isRenderData&&(d=c,c=undefined);var e=d.fn,f=d.data,g=d.inverse,h;h=c?b(this,c,d):Ember.CollectionView;var i=d.hash,j={},k,l,m=i.itemViewClass,n=h.proto();delete i.itemViewClass,l=m?b(n,m,d):n.itemViewClass;for(var o in i)i.hasOwnProperty(o)&&(k=o.match(/^item(.)(.*)$/),k&&(j[k[1].toLowerCase()+k[2]]=i[o],delete i[o]));var p=i.tagName||n.tagName;e&&(j.template=e,delete d.fn);var q;g&&g!==Handlebars.VM.noop?(q=a(n,"emptyViewClass"),q=q.extend({template:g,tagName:j.tagName})):i.emptyViewClass&&(q=b(this,i.emptyViewClass,d)),i.emptyView=q,i.eachHelper==="each"&&(j._context=Ember.computed(function(){return a(this,"content")}).property("content"),delete i.eachHelper);var r=Ember.Handlebars.ViewHelper.propertiesFromHTMLOptions({data:f,hash:j},this);return i.itemViewClass=l.extend(r),Ember.Handlebars.helpers.view.call(this,h,d)})}(),function(){var a=Ember.Handlebars.getPath;Ember.Handlebars.registerHelper("unbound",function(b,c){var d=c.contexts&&c.contexts[0]||this;return a(d,b,c)})}(),function(){var a=Ember.Handlebars.getPath,b=Ember.Handlebars.normalizePath;Ember.Handlebars.registerHelper("log",function(c,d){var e=d.contexts&&d.contexts[0]||this,f=b(e,c,d.data),g=f.root,h=f.path,i=h==="this"?g:a(g,h,d);Ember.Logger.log(i)}),Ember.Handlebars.registerHelper("debugger",function(){debugger})}(),function(){var a=Ember.get,b=Ember.set;Ember.Handlebars.EachView=Ember.CollectionView.extend(Ember._Metamorph,{itemViewClass:Ember._MetamorphView,emptyViewClass:Ember._MetamorphView,createChildView:function(c,d){c=this._super(c,d);var e=a(this,"keyword");if(e){var f=a(c,"templateData");f=Ember.copy(f),f.keywords=c.cloneKeywords(),b(c,"templateData",f);var g=a(c,"content");f.keywords[e]=g}return c}}),Ember.Handlebars.registerHelper("each",function(a,b){if(arguments.length===4){var c=arguments[0];b=arguments[3],a=arguments[2],a===""&&(a="this"),b.hash.keyword=c}else b.hash.eachHelper="each";return b.hash.contentBinding=a,Ember.Handlebars.helpers.collection.call(this,"Ember.Handlebars.EachView",b)})}(),function(){Ember.Handlebars.registerHelper("template",function(a,b){var c=Ember.TEMPLATES[a];Ember.TEMPLATES[a](this,{data:b.data})})}(),function(){var a=Ember.Handlebars,b=a.getPath,c=Ember.get,d=a.ActionHelper={registeredActions:{}};d.registerAction=function(a,b,c,e,f){var g=(++Ember.$.uuid).toString();return d.registeredActions[g]={eventName:b,handler:function(b){return b.view=e,b.context=f,c.isState&&typeof c.send=="function"?c.send(a,b):c[a].call(c,b)}},e.on("willRerender",function(){delete d.registeredActions[g]}),g},a.registerHelper("action",function(e,f){var g=f.hash,h=g.on||"click",i=f.data.view,j,k,l;i=c(i,"concreteView");if(g.target)j=b(this,g.target,f);else if(l=f.data.keywords.controller)j=c(l,"target");j=j||i,k=g.context?b(this,g.context,f):f.contexts[0];var m=[],n;g.href&&j.urlForEvent&&(n=j.urlForEvent(e,k),m.push('href="'+n+'"'));var o=d.registerAction(e,h,j,i,k);return m.push('data-ember-action="'+o+'"'),new a.SafeString(m.join(" "))})}(),function(){var a=Ember.get,b=Ember.set;Ember.Handlebars.registerHelper("yield",function(b){var c=b.data.view,d;while(c&&!a(c,"layout"))c=a(c,"parentView");d=a(c,"template"),d&&d(this,b)})}(),function(){Ember.Handlebars.registerHelper("outlet",function(a,b){return a&&a.data&&a.data.isRenderData&&(b=a,a="view"),b.hash.currentViewBinding="controller."+a,Ember.Handlebars.helpers.view.call(this,Ember.ContainerView,b)})}(),function(){}(),function(){}(),function(){var a=Ember.set,b=Ember.get;Ember.Checkbox=Ember.View.extend({classNames:["ember-checkbox"],tagName:"input",attributeBindings:["type","checked","disabled"],type:"checkbox",checked:!1,disabled:!1,init:function(){this._super(),this.on("change",this,this._updateElementValue)},_updateElementValue:function(){a(this,"checked",this.$().prop("checked"))}})}(),function(){var a=Ember.get,b=Ember.set;Ember.TextSupport=Ember.Mixin.create({value:"",attributeBindings:["placeholder","disabled","maxlength"],placeholder:null,disabled:!1,maxlength:null,insertNewline:Ember.K,cancel:Ember.K,init:function(){this._super(),this.on("focusOut",this,this._elementValueDidChange),this.on("change",this,this._elementValueDidChange),this.on("keyUp",this,this.interpretKeyEvents)},interpretKeyEvents:function(a){var b=Ember.TextSupport.KEY_EVENTS,c=b[a.keyCode];this._elementValueDidChange();if(c)return this[c](a)},_elementValueDidChange:function(){b(this,"value",this.$().val())}}),Ember.TextSupport.KEY_EVENTS={13:"insertNewline",27:"cancel"}}(),function(){var a=Ember.get,b=Ember.set;Ember.TextField=Ember.View.extend(Ember.TextSupport,{classNames:["ember-text-field"],tagName:"input",attributeBindings:["type","value","size"],value:"",type:"text",size:null})}(),function(){var a=Ember.get,b=Ember.set;Ember.Button=Ember.View.extend(Ember.TargetActionSupport,{classNames:["ember-button"],classNameBindings:["isActive"],tagName:"button",propagateEvents:!1,attributeBindings:["type","disabled","href"],targetObject:Ember.computed(function(){var b=a(this,"target"),c=a(this,"context"),d=a(this,"templateData");return typeof b!="string"?b:Ember.Handlebars.getPath(c,b,{data:d})}).property("target").cacheable(),type:Ember.computed(function(a,b){var c=this.get("tagName");b!==undefined&&(this._type=b);if(this._type!==undefined)return this._type;if(c==="input"||c==="button")return"button"}).property("tagName").cacheable(),disabled:!1,href:Ember.computed(function(){return this.get("tagName")==="a"?"#":null}).property("tagName").cacheable(),mouseDown:function(){return a(this,"disabled")||(b(this,"isActive",!0),this._mouseDown=!0,this._mouseEntered=!0),a(this,"propagateEvents")},mouseLeave:function(){this._mouseDown&&(b(this,"isActive",!1),this._mouseEntered=!1)},mouseEnter:function(){this._mouseDown&&(b(this,"isActive",!0),this._mouseEntered=!0)},mouseUp:function(c){return a(this,"isActive")&&(this.triggerAction(),b(this,"isActive",!1)),this._mouseDown=!1,this._mouseEntered=!1,a(this,"propagateEvents")},keyDown:function(a){(a.keyCode===13||a.keyCode===32)&&this.mouseDown()},keyUp:function(a){(a.keyCode===13||a.keyCode===32)&&this.mouseUp()},touchStart:function(a){return this.mouseDown(a)},touchEnd:function(a){return this.mouseUp(a)},init:function(){this._super()}})}(),function(){var a=Ember.get,b=Ember.set;Ember.TextArea=Ember.View.extend(Ember.TextSupport,{classNames:["ember-text-area"],tagName:"textarea",attributeBindings:["rows","cols"],rows:null,cols:null,_updateElementValue:Ember.observer(function(){this.$().val(a(this,"value"))},"value"),init:function( +){this._super(),this.on("didInsertElement",this,this._updateElementValue)}})}(),function(){Ember.TabContainerView=Ember.View.extend({init:function(){this._super()}})}(),function(){var a=Ember.get,b=Ember.getPath;Ember.TabPaneView=Ember.View.extend({tabsContainer:Ember.computed(function(){return this.nearestInstanceOf(Ember.TabContainerView)}).property().volatile(),isVisible:Ember.computed(function(){return a(this,"viewName")===b(this,"tabsContainer.currentView")}).property("tabsContainer.currentView").volatile(),init:function(){this._super()}})}(),function(){var a=Ember.get,b=Ember.setPath;Ember.TabView=Ember.View.extend({tabsContainer:Ember.computed(function(){return this.nearestInstanceOf(Ember.TabContainerView)}).property().volatile(),mouseUp:function(){b(this,"tabsContainer.currentView",a(this,"value"))},init:function(){this._super()}})}(),function(){}(),function(){var a=Ember.set,b=Ember.get,c=Ember.getPath,d=Ember.EnumerableUtils.indexOf,e=Ember.EnumerableUtils.indexesOf;Ember.Select=Ember.View.extend({tagName:"select",classNames:["ember-select"],defaultTemplate:Ember.Handlebars.template(function(b,c,d,e,f){function q(a,b){var c="",e,f,g,h;return b.buffer.push(""),c}function r(a,b){var c,e,f,g;c=a,e="Ember.SelectOption",f={},g="this",f.contentBinding=g,g=d.view||a.view,k={},k.hash=f,k.contexts=[],k.contexts.push(c),k.data=b,typeof g===m?c=g.call(a,e,k):g===o?c=n.call(a,"view",e,k):c=g,b.buffer.push(p(c))}d=d||Ember.Handlebars.helpers;var g="",h,i,j,k,l=this,m="function",n=d.helperMissing,o=void 0,p=this.escapeExpression;return h=c,i="view.prompt",j=d["if"],k=l.program(1,q,f),k.hash={},k.contexts=[],k.contexts.push(h),k.fn=k,k.inverse=l.noop,k.data=f,h=j.call(c,i,k),(h||h===0)&&f.buffer.push(h),h=c,i="view.content",j=d.each,k=l.program(3,r,f),k.hash={},k.contexts=[],k.contexts.push(h),k.fn=k,k.inverse=l.noop,k.data=f,h=j.call(c,i,k),(h||h===0)&&f.buffer.push(h),g}),attributeBindings:["multiple"],multiple:!1,content:null,selection:null,value:Ember.computed(function(a,d){if(arguments.length===2)return d;var e=b(this,"optionValuePath").replace(/^content\.?/,"");return e?c(this,"selection."+e):b(this,"selection")}).property("selection").cacheable(),prompt:null,optionLabelPath:"content",optionValuePath:"content",_change:function(){b(this,"multiple")?this._changeMultiple():this._changeSingle()},selectionDidChange:Ember.observer(function(){var c=b(this,"selection"),d=Ember.isArray(c);if(b(this,"multiple")){if(!d){a(this,"selection",Ember.A([c]));return}this._selectionDidChangeMultiple()}else this._selectionDidChangeSingle()},"selection"),valueDidChange:Ember.observer(function(){var a=b(this,"content"),d=b(this,"value"),e=b(this,"optionValuePath").replace(/^content\.?/,""),f=e?c(this,"selection."+e):b(this,"selection"),g;d!==f&&(g=a.find(function(a){return d===(e?b(a,e):a)}),this.set("selection",g))},"value"),_triggerChange:function(){var a=b(this,"selection");a&&this.selectionDidChange(),this._change()},_changeSingle:function(){var c=this.$()[0].selectedIndex,d=b(this,"content"),e=b(this,"prompt");if(!d)return;if(e&&c===0){a(this,"selection",null);return}e&&(c-=1),a(this,"selection",d.objectAt(c))},_changeMultiple:function(){var c=this.$("option:selected"),d=b(this,"prompt"),e=d?1:0,f=b(this,"content");if(!f)return;if(c){var g=c.map(function(){return this.index-e}).toArray();a(this,"selection",f.objectsAt(g))}},_selectionDidChangeSingle:function(){var a=this.$()[0],c=b(this,"content"),e=b(this,"selection"),f=c?d(c,e):-1,g=b(this,"prompt");g&&(f+=1),a&&(a.selectedIndex=f)},_selectionDidChangeMultiple:function(){var a=b(this,"content"),c=b(this,"selection"),f=a?e(a,c):[-1],g=b(this,"prompt"),h=g?1:0,i=this.$("option"),j;i&&i.each(function(){j=this.index>-1?this.index+h:-1,this.selected=d(f,j)>-1})},init:function(){this._super(),this.on("didInsertElement",this,this._triggerChange),this.on("change",this,this._change)}}),Ember.SelectOption=Ember.View.extend({tagName:"option",attributeBindings:["value","selected"],defaultTemplate:function(a,b){b={data:b.data,hash:{}},Ember.Handlebars.helpers.bind.call(a,"view.label",b)},init:function(){this.labelPathDidChange(),this.valuePathDidChange(),this._super()},selected:Ember.computed(function(){var a=b(this,"content"),e=c(this,"parentView.selection");return c(this,"parentView.multiple")?e&&d(e,a)>-1:a==e}).property("content","parentView.selection").volatile(),labelPathDidChange:Ember.observer(function(){var a=c(this,"parentView.optionLabelPath");if(!a)return;Ember.defineProperty(this,"label",Ember.computed(function(){return c(this,a)}).property(a).cacheable())},"parentView.optionLabelPath"),valuePathDidChange:Ember.observer(function(){var a=c(this,"parentView.optionValuePath");if(!a)return;Ember.defineProperty(this,"value",Ember.computed(function(){return c(this,a)}).property(a).cacheable())},"parentView.optionValuePath")})}(),function(){}(),function(){Ember.Handlebars.bootstrap=function(a){var b='script[type="text/x-handlebars"], script[type="text/x-raw-handlebars"]';Ember.ENV.LEGACY_HANDLEBARS_TAGS&&(b+=', script[type="text/html"]'),Ember.$(b,a).each(function(){var a=Ember.$(this),b=a.attr("type"),c=a.attr("type")==="text/x-raw-handlebars"?Ember.$.proxy(Handlebars.compile,Handlebars):Ember.$.proxy(Ember.Handlebars.compile,Ember.Handlebars),d=a.attr("data-template-name")||a.attr("id"),e=c(a.html()),f,g,h,i;if(d)Ember.TEMPLATES[d]=e,a.remove();else{if(a.parents("head").length!==0)throw new Ember.Error("Template found in without a name specified. Please provide a data-template-name attribute.\n"+a.html());g=a.attr("data-view"),f=g?Ember.getPath(g):Ember.View,h=a.attr("data-element-id"),i={template:e},h&&(i.elementId=h),f=f.create(i),f._insertElementLater(function(){a.replaceWith(this.$()),a=null})}})},Ember.$(document).ready(function(){Ember.Handlebars.bootstrap(Ember.$(document))})}(),function(){}();